[mapbox-wagyu] 01/02: Imported Upstream version 0.4.2
Bas Couwenberg
sebastic at debian.org
Sat Jun 10 20:50:59 UTC 2017
This is an automated email from the git hooks/post-receive script.
sebastic pushed a commit to branch master
in repository mapbox-wagyu.
commit 0e0469f7ddad6be84284e4dc7632ef519a0d4e67
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date: Fri Jun 9 10:18:31 2017 +0200
Imported Upstream version 0.4.2
---
.clang-format | 18 +
.gitignore | 8 +
.gitmodules | 3 +
.travis.yml | 116 +
CHANGELOG.md | 45 +
LICENSE | 37 +
Makefile | 96 +
README.md | 17 +
docs/README.md | 28 +
docs/bad_intersection.png | Bin 0 -> 91229 bytes
docs/bounds.md | 20 +
docs/bounds_red_blue.png | Bin 0 -> 84919 bytes
docs/building_and_testing.md | 59 +
docs/complex_self_intersection.png | Bin 0 -> 109774 bytes
docs/edges.md | 17 +
docs/example.md | 52 +
docs/getting_started.md | 19 +
docs/invalid_self_intersection.png | Bin 0 -> 109251 bytes
docs/local_min_and_max.md | 15 +
docs/local_min_max.png | Bin 0 -> 29848 bytes
docs/new_ring_si.png | Bin 0 -> 111628 bytes
docs/new_ring_si2.png | Bin 0 -> 90996 bytes
docs/overview.md | 22 +
docs/point_intersections.md | 78 +
docs/simple_self_intersection.png | Bin 0 -> 113411 bytes
docs/split_self_intersection.png | Bin 0 -> 98600 bytes
docs/vatti.md | 13 +
.../mapbox/geometry/wagyu/active_bound_list.hpp | 406 +
include/mapbox/geometry/wagyu/bound.hpp | 101 +
include/mapbox/geometry/wagyu/bubble_sort.hpp | 28 +
include/mapbox/geometry/wagyu/build_edges.hpp | 186 +
.../geometry/wagyu/build_local_minima_list.hpp | 26 +
include/mapbox/geometry/wagyu/build_result.hpp | 72 +
include/mapbox/geometry/wagyu/config.hpp | 53 +
include/mapbox/geometry/wagyu/edge.hpp | 123 +
include/mapbox/geometry/wagyu/intersect.hpp | 72 +
include/mapbox/geometry/wagyu/intersect_util.hpp | 372 +
include/mapbox/geometry/wagyu/local_minimum.hpp | 118 +
.../mapbox/geometry/wagyu/local_minimum_util.hpp | 323 +
include/mapbox/geometry/wagyu/point.hpp | 111 +
.../mapbox/geometry/wagyu/process_horizontal.hpp | 268 +
include/mapbox/geometry/wagyu/process_maxima.hpp | 127 +
include/mapbox/geometry/wagyu/quick_clip.hpp | 133 +
include/mapbox/geometry/wagyu/ring.hpp | 645 ++
include/mapbox/geometry/wagyu/ring_util.hpp | 852 ++
include/mapbox/geometry/wagyu/scanbeam.hpp | 36 +
include/mapbox/geometry/wagyu/snap_rounding.hpp | 195 +
.../mapbox/geometry/wagyu/topology_correction.hpp | 1369 +++
include/mapbox/geometry/wagyu/util.hpp | 85 +
include/mapbox/geometry/wagyu/vatti.hpp | 66 +
include/mapbox/geometry/wagyu/wagyu.hpp | 141 +
mason.sh | 189 +
scripts/coverage.sh | 41 +
tests/benchmark.cpp | 387 +
tests/catch.hpp | 9460 ++++++++++++++++++++
...ifference-clockwise-polygon-clockwise-hole.json | 1 +
...e-clockwise-polygon-counter-clockwise-hole.json | 1 +
tests/expected/difference-clockwise-polygon.json | 1 +
...e-counter-clockwise-polygon-clockwise-hole.json | 1 +
...r-clockwise-polygon-counter-clockwise-hole.json | 1 +
.../difference-counter-clockwise-polygon.json | 1 +
...rence-multi-polygon-with-duplicate-polygon.json | 1 +
.../difference-multi-polygon-with-shared-edge.json | 1 +
.../difference-multi-polygon-with-spikes.json | 1 +
.../difference-multipolygon-both-clockwise.json | 1 +
...erence-multipolygon-both-counter-clockwise.json | 1 +
...ultipolygon-overlap-different-orientations.json | 1 +
...r-clockwise-inner-clockwise-hole-clockwise.json | 1 +
...ise-inner-clockwise-hole-counter-clockwise.json | 1 +
...ti-polygon-outer-clockwise-inner-clockwise.json | 1 +
...ise-inner-counter-clockwise-hole-clockwise.json | 1 +
...r-counter-clockwise-hole-counter-clockwise.json | 1 +
...on-outer-clockwise-inner-counter-clockwise.json | 1 +
...r-clockwise-inner-clockwise-hole-clockwise.json | 1 +
...ise-inner-clockwise-hole-counter-clockwise.json | 1 +
...on-outer-counter-clockwise-inner-clockwise.json | 1 +
...ise-inner-counter-clockwise-hole-clockwise.json | 1 +
...r-counter-clockwise-hole-counter-clockwise.json | 1 +
...-counter-clockwise-inner-counter-clockwise.json | 1 +
.../difference-overlapping-multi-polygon.json | 1 +
.../difference-polygon-covered-with-hole.json | 1 +
tests/expected/difference-polygon-no-interior.json | 1 +
...o-intersecting-holes-and-self-intersection.json | 1 +
.../difference-polygon-two-intersecting-holes.json | 1 +
...ifference-polygon-with-double-nested-holes.json | 1 +
.../difference-polygon-with-extending-hole.json | 1 +
.../difference-polygon-with-exterior-hole.json | 1 +
.../difference-polygon-with-hole-shared-edge.json | 1 +
...erence-polygon-with-hole-with-shared-point.json | 1 +
tests/expected/difference-polygon-with-spike.json | 1 +
...lygon-with-two-holes-outside-exterior-ring.json | 1 +
.../difference-self-intersecting-ring-polygon.json | 1 +
...ersection-clockwise-polygon-clockwise-hole.json | 1 +
...n-clockwise-polygon-counter-clockwise-hole.json | 1 +
tests/expected/intersection-clockwise-polygon.json | 1 +
...n-counter-clockwise-polygon-clockwise-hole.json | 1 +
...r-clockwise-polygon-counter-clockwise-hole.json | 1 +
.../intersection-counter-clockwise-polygon.json | 1 +
...ction-multi-polygon-with-duplicate-polygon.json | 1 +
...ntersection-multi-polygon-with-shared-edge.json | 1 +
.../intersection-multi-polygon-with-spikes.json | 1 +
.../intersection-multipolygon-both-clockwise.json | 1 +
...ection-multipolygon-both-counter-clockwise.json | 1 +
...ultipolygon-overlap-different-orientations.json | 1 +
...r-clockwise-inner-clockwise-hole-clockwise.json | 1 +
...ise-inner-clockwise-hole-counter-clockwise.json | 1 +
...ti-polygon-outer-clockwise-inner-clockwise.json | 1 +
...ise-inner-counter-clockwise-hole-clockwise.json | 1 +
...r-counter-clockwise-hole-counter-clockwise.json | 1 +
...on-outer-clockwise-inner-counter-clockwise.json | 1 +
...r-clockwise-inner-clockwise-hole-clockwise.json | 1 +
...ise-inner-clockwise-hole-counter-clockwise.json | 1 +
...on-outer-counter-clockwise-inner-clockwise.json | 1 +
...ise-inner-counter-clockwise-hole-clockwise.json | 1 +
...r-counter-clockwise-hole-counter-clockwise.json | 1 +
...-counter-clockwise-inner-counter-clockwise.json | 1 +
.../intersection-overlapping-multi-polygon.json | 1 +
.../intersection-polygon-covered-with-hole.json | 1 +
.../expected/intersection-polygon-no-interior.json | 1 +
...o-intersecting-holes-and-self-intersection.json | 1 +
...ntersection-polygon-two-intersecting-holes.json | 1 +
...ersection-polygon-with-double-nested-holes.json | 1 +
.../intersection-polygon-with-extending-hole.json | 1 +
.../intersection-polygon-with-exterior-hole.json | 1 +
...intersection-polygon-with-hole-shared-edge.json | 1 +
...ection-polygon-with-hole-with-shared-point.json | 1 +
.../expected/intersection-polygon-with-spike.json | 1 +
...lygon-with-two-holes-outside-exterior-ring.json | 1 +
...ntersection-self-intersecting-ring-polygon.json | 1 +
.../union-clockwise-polygon-clockwise-hole.json | 1 +
...n-clockwise-polygon-counter-clockwise-hole.json | 1 +
tests/expected/union-clockwise-polygon.json | 1 +
...n-counter-clockwise-polygon-clockwise-hole.json | 1 +
...r-clockwise-polygon-counter-clockwise-hole.json | 1 +
.../expected/union-counter-clockwise-polygon.json | 1 +
...union-multi-polygon-with-duplicate-polygon.json | 1 +
.../union-multi-polygon-with-shared-edge.json | 1 +
.../expected/union-multi-polygon-with-spikes.json | 1 +
.../union-multipolygon-both-clockwise.json | 1 +
.../union-multipolygon-both-counter-clockwise.json | 1 +
...ultipolygon-overlap-different-orientations.json | 1 +
...r-clockwise-inner-clockwise-hole-clockwise.json | 1 +
...ise-inner-clockwise-hole-counter-clockwise.json | 1 +
...ti-polygon-outer-clockwise-inner-clockwise.json | 1 +
...ise-inner-counter-clockwise-hole-clockwise.json | 1 +
...r-counter-clockwise-hole-counter-clockwise.json | 1 +
...on-outer-clockwise-inner-counter-clockwise.json | 1 +
...r-clockwise-inner-clockwise-hole-clockwise.json | 1 +
...ise-inner-clockwise-hole-counter-clockwise.json | 1 +
...on-outer-counter-clockwise-inner-clockwise.json | 1 +
...ise-inner-counter-clockwise-hole-clockwise.json | 1 +
...r-counter-clockwise-hole-counter-clockwise.json | 1 +
...-counter-clockwise-inner-counter-clockwise.json | 1 +
.../expected/union-overlapping-multi-polygon.json | 1 +
.../expected/union-polygon-covered-with-hole.json | 1 +
tests/expected/union-polygon-no-interior.json | 1 +
...o-intersecting-holes-and-self-intersection.json | 1 +
.../union-polygon-two-intersecting-holes.json | 1 +
.../union-polygon-with-double-nested-holes.json | 1 +
.../union-polygon-with-extending-hole.json | 1 +
.../expected/union-polygon-with-exterior-hole.json | 1 +
.../union-polygon-with-hole-shared-edge.json | 1 +
.../union-polygon-with-hole-with-shared-point.json | 1 +
tests/expected/union-polygon-with-spike.json | 1 +
...lygon-with-two-holes-outside-exterior-ring.json | 1 +
.../union-self-intersecting-ring-polygon.json | 1 +
.../x_or-clockwise-polygon-clockwise-hole.json | 1 +
...r-clockwise-polygon-counter-clockwise-hole.json | 1 +
tests/expected/x_or-clockwise-polygon.json | 1 +
...r-counter-clockwise-polygon-clockwise-hole.json | 1 +
...r-clockwise-polygon-counter-clockwise-hole.json | 1 +
tests/expected/x_or-counter-clockwise-polygon.json | 1 +
.../x_or-multi-polygon-with-duplicate-polygon.json | 1 +
.../x_or-multi-polygon-with-shared-edge.json | 1 +
tests/expected/x_or-multi-polygon-with-spikes.json | 1 +
.../expected/x_or-multipolygon-both-clockwise.json | 1 +
.../x_or-multipolygon-both-counter-clockwise.json | 1 +
...ultipolygon-overlap-different-orientations.json | 1 +
...r-clockwise-inner-clockwise-hole-clockwise.json | 1 +
...ise-inner-clockwise-hole-counter-clockwise.json | 1 +
...ti-polygon-outer-clockwise-inner-clockwise.json | 1 +
...ise-inner-counter-clockwise-hole-clockwise.json | 1 +
...r-counter-clockwise-hole-counter-clockwise.json | 1 +
...on-outer-clockwise-inner-counter-clockwise.json | 1 +
...r-clockwise-inner-clockwise-hole-clockwise.json | 1 +
...ise-inner-clockwise-hole-counter-clockwise.json | 1 +
...on-outer-counter-clockwise-inner-clockwise.json | 1 +
...ise-inner-counter-clockwise-hole-clockwise.json | 1 +
...r-counter-clockwise-hole-counter-clockwise.json | 1 +
...-counter-clockwise-inner-counter-clockwise.json | 1 +
tests/expected/x_or-overlapping-multi-polygon.json | 1 +
tests/expected/x_or-polygon-covered-with-hole.json | 1 +
tests/expected/x_or-polygon-no-interior.json | 1 +
...o-intersecting-holes-and-self-intersection.json | 1 +
.../x_or-polygon-two-intersecting-holes.json | 1 +
.../x_or-polygon-with-double-nested-holes.json | 1 +
.../expected/x_or-polygon-with-extending-hole.json | 1 +
.../expected/x_or-polygon-with-exterior-hole.json | 1 +
.../x_or-polygon-with-hole-shared-edge.json | 1 +
.../x_or-polygon-with-hole-with-shared-point.json | 1 +
tests/expected/x_or-polygon-with-spike.json | 1 +
...lygon-with-two-holes-outside-exterior-ring.json | 1 +
.../x_or-self-intersecting-ring-polygon.json | 1 +
tests/fixture-tester.cpp | 239 +
tests/fixtures/clip-clockwise-square.json | 9 +
tests/fixtures/clockwise-triangle.json | 8 +
tests/fuzzer.cpp | 242 +
tests/quick_clip_profile.cpp | 51 +
tests/run-benchmark-tests.sh | 39 +
tests/run-geometry-tests.sh | 61 +
tests/test.cpp | 10 +
tests/unit/clipper_get_bounds.cpp | 81 +
tests/unit/edge.cpp | 68 +
tests/unit/local_minimum_util.cpp | 685 ++
tests/unit/process_horizontal.hpp | 31 +
tests/unit/quick_clip.cpp | 297 +
tests/unit/vatti.cpp | 406 +
tests/util/boost_geometry_adapters.hpp | 124 +
218 files changed, 19147 insertions(+)
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..ff0f39e
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,18 @@
+Standard: Cpp11
+IndentWidth: 4
+AccessModifierOffset: -4
+UseTab: Never
+BinPackParameters: false
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+AlwaysBreakTemplateDeclarations: true
+NamespaceIndentation: None
+PointerBindsToType: true
+SpacesInParentheses: false
+BreakBeforeBraces: Attach
+ColumnLimit: 100
+Cpp11BracedListStyle: false
+SpacesBeforeTrailingComments: 1
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3412483
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+test
+fixture-tester
+fuzzer
+build
+*.swp
+mason_packages
+*.dSYM
+tests/output-polyjson
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..6bba66e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "tests/geometry-test-data"]
+ path = tests/geometry-test-data
+ url = https://github.com/mapnik/geometry-test-data.git
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..0185f6d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,116 @@
+language: generic
+
+sudo: false
+
+matrix:
+ include:
+ - os: linux
+ env: CXX=g++-4.9
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'g++-4.9' ]
+ # override before_install to use apt installed compiler
+ # rather than mason installed clang++
+ before_install:
+ - which ${CXX}
+ - os: linux
+ env: CXX=g++-5
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'g++-5' ]
+ # override before_install to use apt installed compiler
+ # rather than mason installed clang++
+ before_install:
+ - which ${CXX}
+ - os: linux
+ env: CXX=g++-6
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'g++-6' ]
+ # override before_install to use apt installed compiler
+ # rather than mason installed clang++
+ before_install:
+ - which ${CXX}
+ - os: linux
+ env: CXX=clang++ CXXFLAGS="-flto"
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'libstdc++-5-dev' ]
+ - os: linux
+ env: CXX=clang++ LLVM_VERSION="4.0.0" CXXFLAGS="-flto"
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'libstdc++-5-dev' ]
+ - os: linux
+ env: CXX=clang++ CXXFLAGS="-flto -fsanitize=cfi -fvisibility=hidden"
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'libstdc++-5-dev' ]
+ - os: linux
+ env: CXX=clang++ CXXFLAGS="-fsanitize=address -fsanitize-address-use-after-scope -fno-omit-frame-pointer -fno-common"
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'libstdc++-5-dev' ]
+ - os: linux
+ env: CXX=clang++ CXXFLAGS="-fsanitize=undefined"
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'libstdc++-5-dev' ]
+ - os: linux
+ env: CXX=clang++ CXXFLAGS="-fsanitize=integer"
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'libstdc++-5-dev' ]
+ # OS X / apple clang / xcode 7.x
+ - os: osx
+ osx_image: xcode7.3
+ env: CXX=clang++
+ # override before_install to use apple clang
+ before_install:
+ - which ${CXX}
+ # OS X / mason clang 4.0.0 / xcode 8.x
+ - os: osx
+ osx_image: xcode8.2
+ env: CXX=clang++ LLVM_VERSION="4.0.0"
+ - os: linux
+ env: CXX=clang++ COVERAGE=true CXXFLAGS="--coverage"
+ addons:
+ apt:
+ sources: [ 'ubuntu-toolchain-r-test' ]
+ packages: [ 'libstdc++-5-dev' ]
+ # override before_script to run coverage
+ before_script:
+ - make debug
+ - ./mason.sh install llvm-cov 3.9.1
+ - export PATH=$(./mason.sh prefix llvm-cov 3.9.1)/bin:${PATH}
+ - which llvm-cov
+ - curl -S -f https://codecov.io/bash -o codecov
+ - chmod +x codecov
+ - ./codecov -x "llvm-cov gcov" -Z
+
+before_install:
+ - git submodule update --init
+ - export LLVM_VERSION="${LLVM_VERSION:-3.9.1}"
+ - |
+ if [[ ${CXX} == "clang++" ]]; then
+ ./mason.sh install clang++ ${LLVM_VERSION}
+ export PATH=$(./mason.sh prefix clang++ ${LLVM_VERSION})/bin:${PATH}
+ ./mason.sh install binutils 2.27
+ export PATH=$(./mason.sh prefix binutils 2.27)/bin:${PATH}
+ fi
+ - which ${CXX}
+
+before_script:
+ - make test
+ - make clean
+ - make debug
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..e4b14c6
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,45 @@
+# Changelog
+
+## 0.1.0
+
+- Initial Release of Wagyu
+
+## 0.2.0
+
+- Fixed winding order issues related to [issue #51](https://github.com/mapbox/wagyu/issues/51)
+
+## 0.3.0
+
+- Added quick clip as path for quickly clipping large polygon data specifically to a bounding box.
+- Removed linestring code from wagyu, going forward library is only planning on supporting polygon data.
+- Removed some dead code paths
+- Fixed some bugs associated with multipolygons that are intersecting
+- Fixed rare bug where holes were sometimes being considered as new polygons
+
+## 0.4.0
+
+- Completely reworked the way topology correction works. It is not seperated into more discrete steps rather then attempting to process it all in one loop through all points. This has made the code much easier to debug.
+- Removed the need to process certain intersections before others in order to gain correct results.
+- Updated `poly2_contain_poly1` such that the rare situation where one ring contains all the same points as another ring, it properly returns results in all situations.
+- Added several more fields to the `ring` struct so that it now tracks area and size more efficiently. Also added a bounding box to the calculation for each `ring`.
+- Replaced the scanbeam tracking to no longer use a priority queue as a std vector was shown to be slightly faster
+- Replaced std list in active bounds list with a std vector
+- Fixed bug in bound construtors where `next_edge` was not being properly initialized.
+- Fixed bug in snap rounding where `next_edge` of bounds were not being properly set.
+- Added ability for `fixture-tester` to repeatedly test the same test.
+
+## 0.4.1
+
+- The integer type of input and output can now be different then the integer type used in wagyu's processing
+- Added -Wshorten-64-to-32 to warnings during builds
+
+## 0.4.2
+
+- Fixed issue found with -Wconversion through out the code
+- Put rounding into place in quick clip when it was previously truncating points while clipping.
+- Removed unrequired referencing of children ring pointers in several locations within loops
+- Switched to `mason.sh` client script over including entire mason repository
+- Deleted default copy constructor on bound structure
+- Fixed bug in `get_dx`
+- Fixed some includes that were missing in some headers
+- Removed some dead code paths and checks that are no longer required
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f3eb477
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,37 @@
+Parts of the code in the Wagyu Library are derived from the version of the
+Clipper Library by Angus Johnson listed below.
+
+Author : Angus Johnson
+Version : 6.4.0
+Date : 2 July 2015
+Website : http://www.angusj.com
+
+Copyright for portions of the derived code in the Wagyu library are held
+by Angus Johnson, 2010-2015. All other copyright for the Wagyu Library are held by
+Mapbox, 2016. This code is published in accordance with, and retains the same license
+as the Clipper Library by Angus Johnson.
+
+Copyright (c) 2010-2015, Angus Johnson
+Copyright (c) 2016, Mapbox
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN 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/Makefile b/Makefile
new file mode 100644
index 0000000..cbfb1ba
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,96 @@
+BOOST_VERSION=1.63.0
+RAPIDJSON_VERSION=1.1.0
+GEOMETRY_VERSION=0.9.0
+
+CC := $(CC)
+CXX := $(CXX)
+CXXFLAGS := $(CXXFLAGS) -Iinclude -isystem mason_packages/headers/boost/$(BOOST_VERSION)/include -isystem mason_packages/headers/rapidjson/$(RAPIDJSON_VERSION)/include -isystem mason_packages/headers/geometry/$(GEOMETRY_VERSION)/include -std=c++11
+RELEASE_FLAGS := -O3 -DNDEBUG
+WARNING_FLAGS := -Wall -Wextra -Weffc++ -Werror -Wsign-compare -Wfloat-equal -Wshadow -Wconversion
+DEBUG_FLAGS := -g -O0 -DDEBUG -fno-inline-functions -fno-omit-frame-pointer
+CLIPPER_REVISION=ac8d6bf2517f46c05647b5c19cac113fb180ffb4
+ANGUS_DEFINES := -D'CLIPPER_INTPOINT_IMPL=mapbox::geometry::point<cInt>' -D'CLIPPER_PATH_IMPL=mapbox::geometry::linear_ring<cInt>' -D'CLIPPER_PATHS_IMPL=mapbox::geometry::polygon<cInt>' -D'CLIPPER_IMPL_INCLUDE=<mapbox/geometry/polygon.hpp>'
+
+
+default: test
+
+mason_packages/headers/boost/$(BOOST_VERSION)/include:
+ ./mason.sh install --header-only boost $(BOOST_VERSION)
+
+mason_packages/headers/rapidjson/$(RAPIDJSON_VERSION)/include:
+ ./mason.sh install --header-only rapidjson $(RAPIDJSON_VERSION)
+
+mason_packages/headers/geometry/$(GEOMETRY_VERSION)/include:
+ ./mason.sh install --header-only geometry $(GEOMETRY_VERSION)
+
+deps: mason_packages/headers/boost/$(BOOST_VERSION)/include mason_packages/headers/rapidjson/$(RAPIDJSON_VERSION)/include mason_packages/headers/geometry/$(GEOMETRY_VERSION)/include
+
+build-test: tests/* include/mapbox/geometry/* deps Makefile
+ $(CXX) $(RELEASE_FLAGS) tests/test.cpp tests/unit/*.cpp $(WARNING_FLAGS) $(CXXFLAGS) -isystem ./tests -o test
+
+build-debug: tests/* include/mapbox/geometry/* deps Makefile
+ $(CXX) $(DEBUG_FLAGS) tests/test.cpp tests/unit/*.cpp $(WARNING_FLAGS) $(CXXFLAGS) -isystem ./tests -o test
+
+build-fixture-tester-r:
+ $(CXX) $(RELEASE_FLAGS) tests/fixture-tester.cpp $(WARNING_FLAGS) $(CXXFLAGS) -o fixture-tester
+
+build-fixture-tester:
+ $(CXX) $(DEBUG_FLAGS) tests/fixture-tester.cpp $(WARNING_FLAGS) $(CXXFLAGS) -o fixture-tester
+
+build-fuzzer-r:
+ $(CXX) $(RELEASE_FLAGS) tests/fuzzer.cpp $(WARNING_FLAGS) $(CXXFLAGS) -o fuzzer
+
+build-fuzzer:
+ $(CXX) $(DEBUG_FLAGS) tests/fuzzer.cpp $(WARNING_FLAGS) $(CXXFLAGS) -o fuzzer
+
+quick_clip_profile: tests/quick_clip_profile.cpp
+ $(CXX) $(DEBUG_FLAGS) tests/quick_clip_profile.cpp $(WARNING_FLAGS) $(CXXFLAGS) -o quick_clip_profile
+
+# angus clipper for benchmark
+./deps/clipper:
+ git clone https://github.com/mapnik/clipper.git -b r496-mapnik ./deps/clipper && cd ./deps/clipper && git checkout $(CLIPPER_REVISION) && ./cpp/fix_members.sh
+
+build-benchmark: ./deps/clipper
+ $(CXX) -c $(RELEASE_FLAGS) deps/clipper/cpp/clipper.cpp $(ANGUS_DEFINES) $(CXXFLAGS) -isystem ./deps/clipper/cpp
+ $(CXX) -c $(RELEASE_FLAGS) tests/benchmark.cpp $(ANGUS_DEFINES) $(CXXFLAGS) -isystem ./deps/clipper/cpp
+ $(CXX) $(RELEASE_FLAGS) clipper.o benchmark.o $(CXXFLAGS) -o benchmark
+
+build-benchmark-d: ./deps/clipper
+ $(CXX) -c $(DEBUG_FLAGS) deps/clipper/cpp/clipper.cpp $(ANGUS_DEFINES) $(CXXFLAGS) -isystem ./deps/clipper/cpp
+ $(CXX) -c $(DEBUG_FLAGS) tests/benchmark.cpp $(ANGUS_DEFINES) $(CXXFLAGS) -isystem ./deps/clipper/cpp
+ $(CXX) $(DEBUG_FLAGS) clipper.o benchmark.o $(CXXFLAGS) -o benchmark
+
+benchmark: build-benchmark
+ ./tests/run-benchmark-tests.sh ./benchmark
+
+test: build-test build-fixture-tester-r
+ ./test
+ ./tests/run-geometry-tests.sh ./fixture-tester
+
+debug: build-debug build-fixture-tester
+ ./test
+ ./tests/run-geometry-tests.sh ./fixture-tester
+
+coverage: Makefile
+ ./scripts/coverage.sh
+
+fuzzer: build-fuzzer
+ ./fuzzer
+
+# avoids tools from getting deleted by make when it fails or you ctrl-c process
+.PRECIOUS: fuzzer benchmark fixture-tester test
+
+clean:
+ rm -rf *dSYM
+ rm -rf deps/
+ rm -f *.o
+ rm -f benchmark
+ rm -f test
+ rm -f fuzzer
+ rm -f fixture-tester
+
+distclean: clean
+ rm -rf ./mason_packages
+
+indent:
+ clang-format -i $(filter-out ./tests/catch.hpp, $(shell find . -path ./mason_packages -prune -o '(' -name '*.hpp' -o -name '*.cpp' ')' -type f -print))
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..007ac1c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+## Wagyu Geometry Processing Library
+
+[![Build Status](https://travis-ci.org/mapbox/wagyu.svg?branch=master)](https://travis-ci.org/mapbox/wagyu)
+[![codecov](https://codecov.io/gh/mapbox/wagyu/branch/master/graph/badge.svg)](https://codecov.io/gh/mapbox/wagyu)
+
+Wagyu is a general library for the following basic geometric operations:
+
+* Union
+* Intersection
+* Difference
+* XOR
+
+The output geometry from each of these operations is guaranteed to be [valid and simple as per the OGC](http://postgis.net/docs/using_postgis_dbmanagement.html#OGC_Validity).
+
+## Documentation
+
+Documentation of any library is critical to its existance and it really takes a community of effort. All of the documentation for the library is [included with the library](https://github.com/mapbox/wagyu/blob/master/docs/README.md). The Wagyu project loves pull requests so please feel free to contribute at any point in time to the `docs/` directory in any way you see fit! If you see a problem in documentation, at least please make an issue in the github repository.
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..b08cbf7
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,28 @@
+## Wagyu Documentation
+
+Welcome to the Wagyu documentation, the purpose of this documentation is to explain the concepts
+and algorithm used within Wagyu.
+
+### Origins of Wagyu
+
+Wagyu originated as a fork of the [Angus Johnson Clipper Library](http://www.angusj.com/delphi/clipper.php) and still
+shares some of the same code, however, some of the algorithm has been changed. Both libraries still utilize the [Vatti Clipping Algorithm](https://en.wikipedia.org/wiki/Vatti_clipping_algorithm). Wagyu, however, follows this clipping algorithm up with a topology correction algorithm. This is used to garuantee that all geometry created by the Vatti clipping is returned as being Valid and Simple as per the OGC specification.
+
+### Documentation Map
+
+* Wagyu Algorithm Documentation
+ * [Algorithm Overview](overview.md)
+ * [Vatti Algorithm](vatti.md)
+ * [Vatti Intersections](vatti_intersections.md)
+ * [Snap Rounding](snap_rounding.md)
+ * [Topology Correction](topology_correction.md)
+ * [Point Intersections](point_intersections.md)
+ * [Intersection Chains](intersections_chains.md)
+* Examples and Usage
+ * [Getting Started](getting_started.md)
+ * [Example Code](example.md)
+* Contributing to Wagyu
+ * [Rules for Contributing](contributing.md)
+ * [Building and Testing](building_and_testing.md)
+ * [Making a Testcase](make_a_testcase.md)
+ * [Using the Fuzzer](fuzzer.md)
diff --git a/docs/bad_intersection.png b/docs/bad_intersection.png
new file mode 100644
index 0000000..341d49e
Binary files /dev/null and b/docs/bad_intersection.png differ
diff --git a/docs/bounds.md b/docs/bounds.md
new file mode 100644
index 0000000..7ccb628
--- /dev/null
+++ b/docs/bounds.md
@@ -0,0 +1,20 @@
+### Bounds
+
+A bound is a series of [edges](edges.md), that start at a [local mimimum and end at a local maximum](local_min_and_max.md). The edges of a bound are ordered from starting at the local miminum and ending at the local maximum.
+
+Bounds are important in the vatti algorithm because they share a common set of data, such as a winding delta and winding count. Bounds are not used outside of the vatti algorithm in other steps such as topology correction.
+
+The basic structure of a bound is defined in [bound.hpp](https://github.com/mapbox/wagyu/blob/master/include/mapbox/geometry/wagyu/bound.hpp). A simplified bound structure is provided below, please note not all its fields are shown:
+
+```
+struct bound {
+ edge_list<T> edges; // A list or vector of edges
+ ring_ptr<T> ring; // The current ring
+ bound_ptr<T> maximum_bound; // the bound who's maximum connects with this bound
+ std::int32_t winding_count; // The current winding count of the bound
+ std::int8_t winding_delta; // 1 or -1 depending on winding direction
+ polygon_type poly_type;
+}
+```
+
+
diff --git a/docs/bounds_red_blue.png b/docs/bounds_red_blue.png
new file mode 100644
index 0000000..02c0741
Binary files /dev/null and b/docs/bounds_red_blue.png differ
diff --git a/docs/building_and_testing.md b/docs/building_and_testing.md
new file mode 100644
index 0000000..0c92a16
--- /dev/null
+++ b/docs/building_and_testing.md
@@ -0,0 +1,59 @@
+## Building and Testing
+
+### Requirements
+
+* Git
+* make
+* clang++ 3.5 or later or g++-5 or later
+
+#### OS X
+
+* Xcode Command Line Tools
+
+### Steps
+
+1. Clone the waygu repo using git and initialize the git submodules.
+
+```
+git clone https://github.com/mapbox/wagyu.git
+cd wagyu
+git submodule update --init
+```
+
+This will install Mapbox's `mason` package manager as well as [test fixtures](https://github.com/mapnik/geometry-test-data). `mason` is going to install required headers, such as `boost`, `rapidjson`, and `mapbox/geometry`.
+
+2. The default `make` builds and runs the tests (`make test`).
+
+```
+make
+```
+
+`make` will build the library with [Catch](https://github.com/philsquared/Catch) test executables. It will then run the Catch unit tests. If they all pass, you should see something like:
+
+```
+All tests passed (178 assertions in 13 test cases)
+```
+
+Then, it will run the `fixture-tester` executable with all of the geometry tests. If all of these pass, you should see something like:
+
+```
+./tests/run-geometry-tests.sh ./fixture-tester
+ ✓ 2900/2900 ✗ 0/2900
+```
+
+ `run-geometry-tests.sh` is feeding `fixture-tester` all of the input polyjson polygons we have in the test fixtures. The `✓` shows how many fixtures passed, and the `✗` shows how many fixtures failed.
+
+ That's it, you have now built and tested `wagyu`!
+
+### Including in External Project
+
+You may want to use wagyu in your own project. Since wagyu is a header-only library, all you have to do is include `mapbox/geometry/polygon.hpp` and `mapbox/geometry/wagyu/wagyu.hpp`. `polygon.hpp` is the polygon portion of the Mapbox geometry library, a boost-compliant polygon geometry container.
+
+### Debugging
+
+`wagyu` has many `DEBUG` flags [throughout the code](https://github.com/mapbox/wagyu/blob/79d85c720c8fb9ab37d0b677ccf12f83d1015ad7/include/mapbox/geometry/wagyu/local_minimum.hpp#L56-L113) that will help you make sense of the library and what it is doing. To see log messages during execution of the code:
+
+```
+make debug
+```
+
diff --git a/docs/complex_self_intersection.png b/docs/complex_self_intersection.png
new file mode 100644
index 0000000..8adffc5
Binary files /dev/null and b/docs/complex_self_intersection.png differ
diff --git a/docs/edges.md b/docs/edges.md
new file mode 100644
index 0000000..a937e09
--- /dev/null
+++ b/docs/edges.md
@@ -0,0 +1,17 @@
+### Edges
+
+An edge is a line formed between two points on a ring.
+
+Edges are defined in the [edge.hpp](https://github.com/mapbox/wagyu/blob/master/include/mapbox/geometry/wagyu/edge.hpp).
+
+The basic structure of an edge is:
+
+```
+struct edge {
+ mapbox::geometry::point<T> bot;
+ mapbox::geometry::point<T> top;
+ double dx;
+};
+```
+
+Edges are only used in Wagyu to represent the pieces of a [bound](bounds.md).
diff --git a/docs/example.md b/docs/example.md
new file mode 100644
index 0000000..a2358ed
--- /dev/null
+++ b/docs/example.md
@@ -0,0 +1,52 @@
+## Example Use of Wagyu
+
+```
+ mapbox::geometry::wagyu::wagyu<std::int64_t> clipper;
+ mapbox::geometry::polygon<std::int64_t> polygon;
+ mapbox::geometry::linear_ring<std::int64_t> ring0_0;
+ ring0_0.push_back({ -79102, 0 });
+ ring0_0.push_back({ -70312, -55285 });
+ ring0_0.push_back({ 85254, -30747 });
+ ring0_0.push_back({ 58008, 80592 });
+ ring0_0.push_back({ -79102, 0 });
+ polygon0.push_back(ring0_0);
+
+ mapbox::geometry::linear_ring<T> ring0_1;
+ ring0_1.push_back({ 44824, 42149 });
+ ring0_1.push_back({ 51855, -21089 });
+ ring0_1.push_back({ -65918, -32502 });
+ ring0_1.push_back({ -50098, 4394 });
+ ring0_1.push_back({ 44824, 42149 });
+ polygon0.push_back(ring0_1);
+
+ clipper.add_polygon(polygon0, polygon_type::polygon_type_subject);
+
+ mapbox::geometry::polygon<T> polygon1;
+ mapbox::geometry::linear_ring<T> ring1_0;
+ ring1_0.push_back({ 31201, 8349 });
+ ring1_0.push_back({ 4834, 19771 });
+ ring1_0.push_back({ -25488, -6592 });
+ ring1_0.push_back({ 10547, -19771 });
+ ring1_0.push_back({ 31201, 8349 });
+ polygon1.push_back(ring1_0);
+
+ clipper.add_polygon(polygon1, polygon_type::polygon_type_clip);
+
+ mapbox::geometry::polygon<T> polygon2;
+ mapbox::geometry::linear_ring<T> ring2_0;
+ ring2_0.push_back({ -40430, -3076 });
+ ring2_0.push_back({ -26367, -18454 });
+ ring2_0.push_back({ 34277, -4834 });
+ ring2_0.push_back({ 33838, 17136 });
+ ring2_0.push_back({ -40430, -3076 });
+ polygon2.push_back(ring2_0);
+
+ clipper.add_polygon(polygon2, polygon_type::polygon_type_subject);
+
+ mapbox::geometry::multi_polygon<T> solution;
+ clipper.execute(mapbox::geometry::wagyu::clip_type_union,
+ solution,
+ mapbox::geometry::wagyu::fill_type_even_odd,
+ mapbox::geometry::wagyu::fill_type_even_odd);
+```
+
diff --git a/docs/getting_started.md b/docs/getting_started.md
new file mode 100644
index 0000000..368d90b
--- /dev/null
+++ b/docs/getting_started.md
@@ -0,0 +1,19 @@
+## Getting Started
+
+
+### Configuration
+
+Wagyu is a header only library but does have a dependency on [Mapbox Geometry](https://github.com/mapbox/geometry.hpp). It is not packaged with the library, but has a similar license and should be included prior to development.
+
+It should be noted that Wagyu requires a compiler that supports at least C++11. You can garuantee that C++11 is used by including the `-std=c++11` flag with most compilers.
+
+### Geometry Operations
+
+Wagyu supports the following geometric operations:
+
+* Union
+* Intersection
+* Difference
+* XOR
+
+
diff --git a/docs/invalid_self_intersection.png b/docs/invalid_self_intersection.png
new file mode 100644
index 0000000..054a9b7
Binary files /dev/null and b/docs/invalid_self_intersection.png differ
diff --git a/docs/local_min_and_max.md b/docs/local_min_and_max.md
new file mode 100644
index 0000000..90d0509
--- /dev/null
+++ b/docs/local_min_and_max.md
@@ -0,0 +1,15 @@
+### Local Minimum and Local Maximum
+
+The first part in understand Vatti is the concepts of local minima and local maxima. Local minima are points on a ring where both of the line segments from the point connect to values that are "below" the minima point, such that no other local minima will be located until a local maxima is found.
+
+That all sounds a bit complicated but a picture will clear it up quickly. Consider the ring below where the local minima are circled in blue and the local maxima are circled with red.
+
+![Local Minima and Local Maxima](local_min_max.png)
+
+Horizontal segments complicate this a little, but keep in mind that no local mimima can be followed by another local minima as you travel around a ring.
+
+Every local minimum on a ring has two [bounds](bounds.md), a left bound and a right bound. A [bound](bounds.md) is a series of [edges](edges.md) that start at a local minimum and travel towards a local maximum. We can locate the bounds from the ring shown above in the picture below - left bounds are colored red and right bounds are colored in blue.
+
+![Left and Right Bounds](bounds_red_blue.png)
+
+You can see the start and end of the bounds by the direction the arrow travels, starting at a local mimima and traveling to a local maximum.
diff --git a/docs/local_min_max.png b/docs/local_min_max.png
new file mode 100644
index 0000000..65129d9
Binary files /dev/null and b/docs/local_min_max.png differ
diff --git a/docs/new_ring_si.png b/docs/new_ring_si.png
new file mode 100644
index 0000000..dc518e6
Binary files /dev/null and b/docs/new_ring_si.png differ
diff --git a/docs/new_ring_si2.png b/docs/new_ring_si2.png
new file mode 100644
index 0000000..dc034b2
Binary files /dev/null and b/docs/new_ring_si2.png differ
diff --git a/docs/overview.md b/docs/overview.md
new file mode 100644
index 0000000..86d1865
--- /dev/null
+++ b/docs/overview.md
@@ -0,0 +1,22 @@
+## Overview of the Wagyu Algorithm
+
+The Wagyu algorithm is based on the [Vatti Clipping Algorithm](vatti.md), but has several different steps in addition
+to the typical steps of the Vatti algorithm. It is due to these additional steps that Wagyu is able to gaurantee that
+all output geometry is valid and simple.
+
+The complete algorithm is based roughly on the following steps represented as psuedo code below:
+
+```
+wagyu(Geometries) {
+
+ LocalMinimums = build_local_minimums(Geometries);
+
+ HotPixels = build_hot_pixels(LocalMimums);
+
+ Rings = vatti(HotPixels, LocalMimimums);
+
+ CorrectedRings = correct_topology(Rings);
+
+ return CorrectedRings;
+}
+```
diff --git a/docs/point_intersections.md b/docs/point_intersections.md
new file mode 100644
index 0000000..5c565f4
--- /dev/null
+++ b/docs/point_intersections.md
@@ -0,0 +1,78 @@
+## Point Intersections
+
+Intersections of points will be found after the vatti processing. Every point will be part of a ring at this point and no ring will overlap another ring. It is the job of the topology correction code to take the existing rings and handle the different intersections to make valid and simple polygons.
+
+### Types of Intersections
+
+The following are the types of intersections that can occur
+
+* Intersection of points on the same ring (Self Intersections)
+* Intersetion of an exterior ring with another exterior ring
+* Intersection of an exterior ring with an interior ring
+* Intersection of an interior ring with another interior ring
+
+### Self Intersection
+
+A self intersection is an instance where two points belong to the same ring. Each time this occurs a section of the ring will become a new ring.
+
+#### The Fundamental Assumption of Self Intersections
+
+The resulting rings from the vatti processing are **guaranteed** to never result in the path of a ring crossing with itself. While the path might be collinear or share intersection points that need to be cleaned up, it does not ever result in an "crossing intersection".
+
+The image below shows a single ring - this is *not* a crossing intersection because the pairing of any two segments (one being towards, one being away) does not result in the path crossing over itself.
+
+![Simple Self Intersection](simple_self_intersection.png)
+
+The next image however is an example of a crossing intersection.
+
+![Invalid Self Intersetion](invalid_self_intersection.png)
+
+Notice how no pair of towards and away paths can be selected such that the paths do not cross. This will not occur with the output from vatti and we must ensure it does not happen in the topology correction processing. If a crossing self intersection were to occur during our spliting process it would cause the winding order of the expected output to be *reversed*. This would greatly complicate ring determination, so we must avoid this from occuring.
+
+#### Complex Self Intersections
+
+Very complex self intersections can likely occur were *more then 2* paths through the point. The following is an example of where this can occur.
+
+![Complex Self Intersection](complex_self_intersection.png)
+
+While this is not a crossing self intersection intially we must take care with the order of the processing of the paths. Splitting a ring into two distinct rings is the critical part of self intersection processing and *if an intersection is crossing the two resulting rings will be crossing each other such that one ring could be partial inside the other*. This makes it **MUCH** more difficult to properly determine parent child relationship of the resulting rings.
+
+#### What Does Self Intersection Processing Do?
+
+It is difficult to visualize the original path with out "spreading out" the points that intersect ever so slightly, lets explore the processing of the complex self intersection that was shown above.
+
+![Complex Self Intersection Split](split_self_intersection.png)
+
+Assuming that we labeled each of the intersection points from left to right in the picture above as `A, B, C, D`. Lets explore how the order of the self intersection processing can result in intersections.
+
+At its core self intersections are corrected by swapping where each intersection point will travel to next. The code for it is something like this:
+
+```
+ // split the polygon into two ...
+ point_ptr<T> op3 = op->prev;
+ point_ptr<T> op4 = op2->prev;
+ op->prev = op4;
+ op4->next = op;
+ op2->prev = op3;
+ op3->next = op2;
+```
+
+Basically the directions of the paths are switched. If we processed `A` and `B` together from our example above, first it will result in the creation of a new ring as shown below:
+
+![New Ring Created](new_ring_si.png)
+
+This is proper because the resulting two rings -- are either **completely within or completely outside of the other ring**. Notice as well that when we zoom in there are still no crossing intersections.
+
+![New Ring Zoomed In](new_ring_si2.png)
+
+However, lets say we attempt now to process `B` and `D`. Hint: This is not what we want to do!
+
+![Bad Intersection](bad_intersection.png)
+
+Notice that suddenly the new polygon created is both inside and outside the original polygon. This means the ring is both a hole and not a hole!
+
+#### Proper Path Processing for Complex Intersections
+
+In order to prevent this from occuring, self intersections should only be performed between any two paths selected for another intersection where there is no other path that would bisect them! To determine a proper pair of paths for self intersection, the angles of each of the segment of a path are sorted in relation to a single path. Due to the fact that no paths will be crossing, we can first sort all other paths as either on the left or right side of the path. After this all paths on [...]
+
+If we process all paths of the self intersection in this method until no intersections remain, we will always produce valid rings.
diff --git a/docs/simple_self_intersection.png b/docs/simple_self_intersection.png
new file mode 100644
index 0000000..08c1fb8
Binary files /dev/null and b/docs/simple_self_intersection.png differ
diff --git a/docs/split_self_intersection.png b/docs/split_self_intersection.png
new file mode 100644
index 0000000..dd0df82
Binary files /dev/null and b/docs/split_self_intersection.png differ
diff --git a/docs/vatti.md b/docs/vatti.md
new file mode 100644
index 0000000..090852f
--- /dev/null
+++ b/docs/vatti.md
@@ -0,0 +1,13 @@
+## Vatti Algorithm
+
+There is an excellent book that contains a great description of a Vatti algorithm. For more information
+then the information provided here on the Vatti algorithm, please look into this book.
+
+```
+Computer graphics and geometric modeling: implementation and algorithms
+By Max K. Agoston
+Springer; 1 edition (January 4, 2005)
+```
+
+A very simple explanation of the the Vatti algorithm is that it breaking apart rings into [edges](edges.md) - decides what edges should be kept, and then re-assembling the resulting rings.
+
diff --git a/include/mapbox/geometry/wagyu/active_bound_list.hpp b/include/mapbox/geometry/wagyu/active_bound_list.hpp
new file mode 100644
index 0000000..153a365
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/active_bound_list.hpp
@@ -0,0 +1,406 @@
+#pragma once
+
+#ifdef DEBUG
+#include <iostream>
+#include <sstream>
+#endif
+
+#include <mapbox/geometry/wagyu/bound.hpp>
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/edge.hpp>
+#include <mapbox/geometry/wagyu/local_minimum.hpp>
+#include <mapbox/geometry/wagyu/local_minimum_util.hpp>
+#include <mapbox/geometry/wagyu/ring.hpp>
+#include <mapbox/geometry/wagyu/scanbeam.hpp>
+#include <mapbox/geometry/wagyu/util.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+using active_bound_list = std::vector<bound_ptr<T>>;
+
+template <typename T>
+using active_bound_list_itr = typename active_bound_list<T>::iterator;
+
+template <typename T>
+using active_bound_list_rev_itr = typename active_bound_list<T>::reverse_iterator;
+
+#ifdef DEBUG
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ const active_bound_list<T>& bnds) {
+ std::size_t c = 0;
+ for (auto const& bnd : bnds) {
+ out << "Index: " << c++ << std::endl;
+ out << *bnd;
+ }
+ return out;
+}
+
+template <typename T>
+std::string output_edges(active_bound_list<T> const& bnds) {
+ std::ostringstream out;
+ out << "[";
+ bool first = true;
+ for (auto const& bnd : bnds) {
+ if (first) {
+ first = false;
+ } else {
+ out << ",";
+ }
+ out << "[[" << bnd->current_edge->bot.x << "," << bnd->current_edge->bot.y << "],[";
+ out << bnd->current_edge->top.x << "," << bnd->current_edge->top.y << "]]";
+ }
+ out << "]";
+ return out.str();
+}
+
+#endif
+
+template <typename T>
+bool is_even_odd_fill_type(bound<T> const& bound,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+ if (bound.poly_type == polygon_type_subject) {
+ return subject_fill_type == fill_type_even_odd;
+ } else {
+ return clip_fill_type == fill_type_even_odd;
+ }
+}
+
+template <typename T>
+bool is_even_odd_alt_fill_type(bound<T> const& bound,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+ if (bound.poly_type == polygon_type_subject) {
+ return clip_fill_type == fill_type_even_odd;
+ } else {
+ return subject_fill_type == fill_type_even_odd;
+ }
+}
+
+template <typename T>
+struct bound_insert_location {
+
+ bound<T> const& bound2;
+
+ bound_insert_location(bound<T> const& b) : bound2(b) {
+ }
+
+ bool operator()(bound_ptr<T> const& b) {
+ auto const& bound1 = *b;
+ if (values_are_equal(bound2.current_x, bound1.current_x)) {
+ if (bound2.current_edge->top.y > bound1.current_edge->top.y) {
+ return static_cast<double>(bound2.current_edge->top.x) <
+ get_current_x(*(bound1.current_edge), bound2.current_edge->top.y);
+ } else {
+ return static_cast<double>(bound1.current_edge->top.x) >
+ get_current_x(*(bound2.current_edge), bound1.current_edge->top.y);
+ }
+ } else {
+ return bound2.current_x < bound1.current_x;
+ }
+ }
+};
+
+template <typename T>
+active_bound_list_itr<T>
+insert_bound_into_ABL(bound<T>& left, bound<T>& right, active_bound_list<T>& active_bounds) {
+
+ auto itr =
+ std::find_if(active_bounds.begin(), active_bounds.end(), bound_insert_location<T>(left));
+ return active_bounds.insert(itr, { &left, &right });
+}
+
+template <typename T>
+inline bool is_maxima(bound<T>& bnd, T y) {
+ return bnd.next_edge == bnd.edges.end() && bnd.current_edge->top.y == y;
+}
+
+template <typename T>
+inline bool is_maxima(active_bound_list_itr<T>& bnd, T y) {
+ return is_maxima(*(*bnd), y);
+}
+
+template <typename T>
+inline bool is_intermediate(bound<T>& bnd, T y) {
+ return bnd.next_edge != bnd.edges.end() && bnd.current_edge->top.y == y;
+}
+
+template <typename T>
+inline bool is_intermediate(active_bound_list_itr<T>& bnd, T y) {
+ return is_intermediate(*(*bnd), y);
+}
+
+template <typename T>
+inline bool current_edge_is_horizontal(active_bound_list_itr<T>& bnd) {
+ return is_horizontal(*((*bnd)->current_edge));
+}
+
+template <typename T>
+inline bool next_edge_is_horizontal(active_bound_list_itr<T>& bnd) {
+ return is_horizontal(*((*bnd)->next_edge));
+}
+
+template <typename T>
+void next_edge_in_bound(bound<T>& bnd, scanbeam_list<T>& scanbeam) {
+ auto& current_edge = bnd.current_edge;
+ ++current_edge;
+ if (current_edge != bnd.edges.end()) {
+ ++(bnd.next_edge);
+ bnd.current_x = static_cast<double>(current_edge->bot.x);
+ if (!is_horizontal<T>(*current_edge)) {
+ scanbeam.push_back(current_edge->top.y);
+ }
+ }
+}
+
+template <typename T>
+active_bound_list_itr<T> get_maxima_pair(active_bound_list_itr<T> const& bnd,
+ active_bound_list<T>& active_bounds) {
+ bound_ptr<T> maximum = (*bnd)->maximum_bound;
+ return std::find(active_bounds.begin(), active_bounds.end(), maximum);
+}
+
+template <typename T>
+void set_winding_count(active_bound_list_itr<T>& bnd_itr,
+ active_bound_list<T>& active_bounds,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+
+ auto rev_bnd_itr = active_bound_list_rev_itr<T>(bnd_itr);
+ if (rev_bnd_itr == active_bounds.rend()) {
+ (*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
+ (*bnd_itr)->winding_count2 = 0;
+ return;
+ }
+
+ // find the edge of the same polytype that immediately preceeds 'edge' in
+ // AEL
+ while (rev_bnd_itr != active_bounds.rend() &&
+ (*rev_bnd_itr)->poly_type != (*bnd_itr)->poly_type) {
+ ++rev_bnd_itr;
+ }
+ if (rev_bnd_itr == active_bounds.rend()) {
+ (*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
+ (*bnd_itr)->winding_count2 = 0;
+ } else if (is_even_odd_fill_type(*(*bnd_itr), subject_fill_type, clip_fill_type)) {
+ // EvenOdd filling ...
+ (*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
+ (*bnd_itr)->winding_count2 = (*rev_bnd_itr)->winding_count2;
+ } else {
+ // nonZero, Positive or Negative filling ...
+ if ((*rev_bnd_itr)->winding_count * (*rev_bnd_itr)->winding_delta < 0) {
+ // prev edge is 'decreasing' WindCount (WC) toward zero
+ // so we're outside the previous polygon ...
+ if (std::abs(static_cast<int>((*rev_bnd_itr)->winding_count)) > 1) {
+ // outside prev poly but still inside another.
+ // when reversing direction of prev poly use the same WC
+ if ((*rev_bnd_itr)->winding_delta * (*bnd_itr)->winding_delta < 0) {
+ (*bnd_itr)->winding_count = (*rev_bnd_itr)->winding_count;
+ } else {
+ // otherwise continue to 'decrease' WC ...
+ (*bnd_itr)->winding_count =
+ (*rev_bnd_itr)->winding_count + (*bnd_itr)->winding_delta;
+ }
+ } else {
+ // now outside all polys of same polytype so set own WC ...
+ (*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
+ }
+ } else {
+ // prev edge is 'increasing' WindCount (WC) away from zero
+ // so we're inside the previous polygon ...
+ if ((*rev_bnd_itr)->winding_delta * (*bnd_itr)->winding_delta < 0) {
+ // if wind direction is reversing prev then use same WC
+ (*bnd_itr)->winding_count = (*rev_bnd_itr)->winding_count;
+ } else {
+ // otherwise add to WC ...
+ (*bnd_itr)->winding_count =
+ (*rev_bnd_itr)->winding_count + (*bnd_itr)->winding_delta;
+ }
+ }
+ (*bnd_itr)->winding_count2 = (*rev_bnd_itr)->winding_count2;
+ }
+
+ // update winding_count2 ...
+ auto bnd_itr_forward = rev_bnd_itr.base();
+ if (is_even_odd_alt_fill_type(*(*bnd_itr), subject_fill_type, clip_fill_type)) {
+ // EvenOdd filling ...
+ while (bnd_itr_forward != bnd_itr) {
+ (*bnd_itr)->winding_count2 = ((*bnd_itr)->winding_count2 == 0 ? 1 : 0);
+ ++bnd_itr_forward;
+ }
+ } else {
+ // nonZero, Positive or Negative filling ...
+ while (bnd_itr_forward != bnd_itr) {
+ (*bnd_itr)->winding_count2 += (*bnd_itr_forward)->winding_delta;
+ ++bnd_itr_forward;
+ }
+ }
+}
+
+template <typename T>
+bool is_contributing(bound<T> const& bnd,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+ fill_type pft = subject_fill_type;
+ fill_type pft2 = clip_fill_type;
+ if (bnd.poly_type != polygon_type_subject) {
+ pft = clip_fill_type;
+ pft2 = subject_fill_type;
+ }
+
+ switch (pft) {
+ case fill_type_even_odd:
+ break;
+ case fill_type_non_zero:
+ if (std::abs(static_cast<int>(bnd.winding_count)) != 1) {
+ return false;
+ }
+ break;
+ case fill_type_positive:
+ if (bnd.winding_count != 1) {
+ return false;
+ }
+ break;
+ case fill_type_negative:
+ default:
+ if (bnd.winding_count != -1) {
+ return false;
+ }
+ }
+
+ switch (cliptype) {
+ case clip_type_intersection:
+ switch (pft2) {
+ case fill_type_even_odd:
+ case fill_type_non_zero:
+ return (bnd.winding_count2 != 0);
+ case fill_type_positive:
+ return (bnd.winding_count2 > 0);
+ case fill_type_negative:
+ default:
+ return (bnd.winding_count2 < 0);
+ }
+ break;
+ case clip_type_union:
+ switch (pft2) {
+ case fill_type_even_odd:
+ case fill_type_non_zero:
+ return (bnd.winding_count2 == 0);
+ case fill_type_positive:
+ return (bnd.winding_count2 <= 0);
+ case fill_type_negative:
+ default:
+ return (bnd.winding_count2 >= 0);
+ }
+ break;
+ case clip_type_difference:
+ if (bnd.poly_type == polygon_type_subject) {
+ switch (pft2) {
+ case fill_type_even_odd:
+ case fill_type_non_zero:
+ return (bnd.winding_count2 == 0);
+ case fill_type_positive:
+ return (bnd.winding_count2 <= 0);
+ case fill_type_negative:
+ default:
+ return (bnd.winding_count2 >= 0);
+ }
+ } else {
+ switch (pft2) {
+ case fill_type_even_odd:
+ case fill_type_non_zero:
+ return (bnd.winding_count2 != 0);
+ case fill_type_positive:
+ return (bnd.winding_count2 > 0);
+ case fill_type_negative:
+ default:
+ return (bnd.winding_count2 < 0);
+ }
+ }
+ break;
+ case clip_type_x_or:
+ return true;
+ break;
+ default:
+ return true;
+ }
+}
+
+template <typename T>
+void insert_lm_left_and_right_bound(bound<T>& left_bound,
+ bound<T>& right_bound,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& rings,
+ scanbeam_list<T>& scanbeam,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+
+ // Both left and right bound
+ auto lb_abl_itr = insert_bound_into_ABL(left_bound, right_bound, active_bounds);
+ auto rb_abl_itr = std::next(lb_abl_itr);
+ set_winding_count(lb_abl_itr, active_bounds, subject_fill_type, clip_fill_type);
+ (*rb_abl_itr)->winding_count = (*lb_abl_itr)->winding_count;
+ (*rb_abl_itr)->winding_count2 = (*lb_abl_itr)->winding_count2;
+ if (is_contributing(left_bound, cliptype, subject_fill_type, clip_fill_type)) {
+ add_local_minimum_point(*(*lb_abl_itr), *(*rb_abl_itr), active_bounds,
+ (*lb_abl_itr)->current_edge->bot, rings);
+ }
+
+ // Add top of edges to scanbeam
+ scanbeam.push_back((*lb_abl_itr)->current_edge->top.y);
+
+ if (!current_edge_is_horizontal<T>(rb_abl_itr)) {
+ scanbeam.push_back((*rb_abl_itr)->current_edge->top.y);
+ }
+}
+
+template <typename T>
+void insert_local_minima_into_ABL(T const bot_y,
+ local_minimum_ptr_list<T> const& minima_sorted,
+ local_minimum_ptr_list_itr<T>& current_lm,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& rings,
+ scanbeam_list<T>& scanbeam,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+ while (current_lm != minima_sorted.end() && bot_y == (*current_lm)->y) {
+ initialize_lm<T>(current_lm);
+ auto& left_bound = (*current_lm)->left_bound;
+ auto& right_bound = (*current_lm)->right_bound;
+ insert_lm_left_and_right_bound(left_bound, right_bound, active_bounds, rings, scanbeam,
+ cliptype, subject_fill_type, clip_fill_type);
+ ++current_lm;
+ }
+}
+
+template <typename T>
+void insert_horizontal_local_minima_into_ABL(T const top_y,
+ local_minimum_ptr_list<T> const& minima_sorted,
+ local_minimum_ptr_list_itr<T>& current_lm,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& rings,
+ scanbeam_list<T>& scanbeam,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+ while (current_lm != minima_sorted.end() && top_y == (*current_lm)->y &&
+ (*current_lm)->minimum_has_horizontal) {
+ initialize_lm<T>(current_lm);
+ auto& left_bound = (*current_lm)->left_bound;
+ auto& right_bound = (*current_lm)->right_bound;
+ insert_lm_left_and_right_bound(left_bound, right_bound, active_bounds, rings, scanbeam,
+ cliptype, subject_fill_type, clip_fill_type);
+ ++current_lm;
+ }
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/bound.hpp b/include/mapbox/geometry/wagyu/bound.hpp
new file mode 100644
index 0000000..3028e08
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/bound.hpp
@@ -0,0 +1,101 @@
+#pragma once
+
+#include <list>
+
+#include <mapbox/geometry/point.hpp>
+
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/edge.hpp>
+#include <mapbox/geometry/wagyu/ring.hpp>
+
+#ifdef DEBUG
+#include <iostream>
+#endif
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+struct bound {
+
+ edge_list<T> edges;
+ edge_list_itr<T> current_edge;
+ edge_list_itr<T> next_edge;
+ mapbox::geometry::point<T> last_point;
+ ring_ptr<T> ring;
+ bound_ptr<T> maximum_bound; // the bound who's maximum connects with this bound
+ double current_x;
+ std::size_t pos;
+ std::int32_t winding_count;
+ std::int32_t winding_count2; // winding count of the opposite polytype
+ std::int8_t winding_delta; // 1 or -1 depending on winding direction - 0 for linestrings
+ polygon_type poly_type;
+ edge_side side; // side only refers to current side of solution poly
+
+ bound() noexcept
+ : edges(),
+ current_edge(edges.end()),
+ next_edge(edges.end()),
+ last_point({ 0, 0 }),
+ ring(nullptr),
+ maximum_bound(nullptr),
+ current_x(0.0),
+ pos(0),
+ winding_count(0),
+ winding_count2(0),
+ winding_delta(0),
+ poly_type(polygon_type_subject),
+ side(edge_left) {
+ }
+
+ bound(bound<T>&& b) noexcept
+ : edges(std::move(b.edges)),
+ current_edge(std::move(b.current_edge)),
+ next_edge(std::move(b.next_edge)),
+ last_point(std::move(b.last_point)),
+ ring(std::move(b.ring)),
+ maximum_bound(std::move(b.maximum_bound)),
+ current_x(std::move(b.current_x)),
+ pos(std::move(b.pos)),
+ winding_count(std::move(b.winding_count)),
+ winding_count2(std::move(b.winding_count2)),
+ winding_delta(std::move(b.winding_delta)),
+ poly_type(std::move(b.poly_type)),
+ side(std::move(b.side)) {
+ }
+
+ bound(bound<T>const& b) = delete;
+ bound<T>& operator=(bound<T> const&) = delete;
+
+};
+
+#ifdef DEBUG
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ const bound<T>& bnd) {
+ out << " Bound: " << &bnd << std::endl;
+ out << " current_x: " << bnd.current_x << std::endl;
+ out << " last_point: " << bnd.last_point.x << ", " << bnd.last_point.y << std::endl;
+ out << *(bnd.current_edge);
+ out << " winding count: " << bnd.winding_count << std::endl;
+ out << " winding_count2: " << bnd.winding_count2 << std::endl;
+ out << " winding_delta: " << static_cast<int>(bnd.winding_delta) << std::endl;
+ out << " maximum_bound: " << bnd.maximum_bound << std::endl;
+ if (bnd.side == edge_left) {
+ out << " side: left" << std::endl;
+ } else {
+ out << " side: right" << std::endl;
+ }
+ out << " ring: " << bnd.ring << std::endl;
+ if (bnd.ring) {
+ out << " ring index: " << bnd.ring->ring_index << std::endl;
+ }
+ return out;
+}
+
+#endif
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/bubble_sort.hpp b/include/mapbox/geometry/wagyu/bubble_sort.hpp
new file mode 100644
index 0000000..8cfedd6
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/bubble_sort.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename It, class Compare, class MethodOnSwap>
+void bubble_sort(It begin, It end, Compare c, MethodOnSwap m) {
+ if (begin == end) {
+ return;
+ }
+ bool modified = false;
+ auto last = end - 1;
+ do {
+ modified = false;
+ for (auto itr = begin; itr != last; ++itr) {
+ auto next = std::next(itr);
+ if (!c(*itr, *next)) {
+ m(*itr, *next);
+ std::iter_swap(itr, next);
+ modified = true;
+ }
+ }
+ } while (modified);
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/build_edges.hpp b/include/mapbox/geometry/wagyu/build_edges.hpp
new file mode 100644
index 0000000..07cc92b
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/build_edges.hpp
@@ -0,0 +1,186 @@
+#pragma once
+
+#include <mapbox/geometry/line_string.hpp>
+#include <mapbox/geometry/point.hpp>
+#include <mapbox/geometry/polygon.hpp>
+
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/edge.hpp>
+#include <mapbox/geometry/wagyu/util.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+bool point_2_is_between_point_1_and_point_3(mapbox::geometry::point<T> const& pt1,
+ mapbox::geometry::point<T> const& pt2,
+ mapbox::geometry::point<T> const& pt3) {
+ if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) {
+ return false;
+ } else if (pt1.x != pt3.x) {
+ return (pt2.x > pt1.x) == (pt2.x < pt3.x);
+ } else {
+ return (pt2.y > pt1.y) == (pt2.y < pt3.y);
+ }
+}
+
+template <typename T1, typename T2>
+bool build_edge_list(mapbox::geometry::linear_ring<T2> const& path_geometry, edge_list<T1>& edges) {
+
+ if (path_geometry.size() < 3) {
+ return false;
+ }
+
+ // As this is a loop, we need to first go backwards from end to try and find
+ // the proper starting point for the iterators before the beginning
+
+ auto itr_rev = path_geometry.rbegin();
+ auto itr = path_geometry.begin();
+ mapbox::geometry::point<T2> pt1 = *itr_rev;
+ mapbox::geometry::point<T2> pt2 = *itr;
+
+ // Find next non repeated point going backwards from
+ // end for pt1
+ while (pt1 == pt2) {
+ ++itr_rev;
+ if (itr_rev == path_geometry.rend()) {
+ return false;
+ }
+ pt1 = *itr_rev;
+ }
+ ++itr;
+ mapbox::geometry::point<T2> pt3 = *itr;
+ auto itr_last = itr_rev.base();
+ mapbox::geometry::point<T2> front_pt;
+ mapbox::geometry::point<T2> back_pt;
+ while (true) {
+ if (pt3 == pt2) {
+ // Duplicate point advance itr, but do not
+ // advance other points
+ if (itr == itr_last) {
+ break;
+ }
+ ++itr;
+ if (itr == itr_last) {
+ if (edges.empty()) {
+ break;
+ }
+ pt3 = front_pt;
+ } else {
+ pt3 = *itr;
+ }
+ continue;
+ }
+
+ // Now check if slopes are equal between two segments - either
+ // a spike or a collinear point - if so drop point number 2.
+ if (slopes_equal(pt1, pt2, pt3)) {
+ // We need to reconsider previously added points
+ // because the point it was using was found to be collinear
+ // or a spike
+ pt2 = pt1;
+ if (!edges.empty()) {
+ edges.pop_back(); // remove previous edge (pt1)
+ }
+ if (!edges.empty()) {
+ auto const& back_top = edges.back().top;
+ if (static_cast<T1>(back_pt.x) == back_top.x &&
+ static_cast<T1>(back_pt.y) == back_top.y) {
+ auto const& back_bot = edges.back().bot;
+ pt1 = mapbox::geometry::point<T2>(static_cast<T2>(back_bot.x),
+ static_cast<T2>(back_bot.y));
+ } else {
+ pt1 = mapbox::geometry::point<T2>(static_cast<T2>(back_top.x),
+ static_cast<T2>(back_top.y));
+ }
+ back_pt = pt1;
+ } else {
+ // If this occurs we must look to the back of the
+ // ring for new points.
+ while (*itr_rev == pt2) {
+ ++itr_rev;
+ if ((itr + 1) == itr_rev.base()) {
+ return false;
+ }
+ }
+ pt1 = *itr_rev;
+ itr_last = itr_rev.base();
+ }
+ continue;
+ }
+
+ if (edges.empty()) {
+ front_pt = pt2;
+ }
+ edges.emplace_back(pt2, pt3);
+ back_pt = pt2;
+ if (itr == itr_last) {
+ break;
+ }
+ pt1 = pt2;
+ pt2 = pt3;
+ ++itr;
+ if (itr == itr_last) {
+ if (edges.empty()) {
+ break;
+ }
+ pt3 = front_pt;
+ } else {
+ pt3 = *itr;
+ }
+ }
+
+ bool modified = false;
+ do {
+ modified = false;
+ if (edges.size() < 3) {
+ return false;
+ }
+ auto& f = edges.front();
+ auto& b = edges.back();
+ if (slopes_equal(f, b)) {
+ if (f.bot == b.top) {
+ if (f.top == b.bot) {
+ edges.pop_back();
+ edges.erase(edges.begin());
+ } else {
+ f.bot = b.bot;
+ edges.pop_back();
+ }
+ modified = true;
+ } else if (f.top == b.bot) {
+ f.top = b.top;
+ edges.pop_back();
+ modified = true;
+ } else if (f.top == b.top && f.bot == b.bot) {
+ edges.pop_back();
+ edges.erase(edges.begin());
+ modified = true;
+ } else if (f.top == b.top) {
+ if (point_2_is_between_point_1_and_point_3(f.top, f.bot, b.bot)) {
+ b.top = f.bot;
+ edges.erase(edges.begin());
+ } else {
+ f.top = b.bot;
+ edges.pop_back();
+ }
+ modified = true;
+ } else if (f.bot == b.bot) {
+ if (point_2_is_between_point_1_and_point_3(f.bot, f.top, b.top)) {
+ b.bot = f.top;
+ edges.erase(edges.begin());
+ } else {
+ f.bot = b.top;
+ edges.pop_back();
+ }
+ modified = true;
+ }
+ }
+ } while (modified);
+
+ return true;
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/build_local_minima_list.hpp b/include/mapbox/geometry/wagyu/build_local_minima_list.hpp
new file mode 100644
index 0000000..8227383
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/build_local_minima_list.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <mapbox/geometry/wagyu/build_edges.hpp>
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/local_minimum.hpp>
+#include <mapbox/geometry/wagyu/local_minimum_util.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T1, typename T2>
+bool add_linear_ring(mapbox::geometry::linear_ring<T2> const& path_geometry,
+ local_minimum_list<T1>& minima_list,
+ polygon_type p_type) {
+ edge_list<T1> new_edges;
+ new_edges.reserve(path_geometry.size());
+ if (!build_edge_list<T1, T2>(path_geometry, new_edges) || new_edges.empty()) {
+ return false;
+ }
+ add_ring_to_local_minima_list(new_edges, minima_list, p_type);
+ return true;
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/build_result.hpp b/include/mapbox/geometry/wagyu/build_result.hpp
new file mode 100644
index 0000000..a5f6155
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/build_result.hpp
@@ -0,0 +1,72 @@
+#pragma once
+
+#include <mapbox/geometry/wagyu/ring.hpp>
+#include <mapbox/geometry/wagyu/ring_util.hpp>
+
+#include <mapbox/geometry/multi_polygon.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T1, typename T2>
+void push_ring_to_polygon(mapbox::geometry::polygon<T2>& poly,
+ ring_ptr<T1> r,
+ bool reverse_output) {
+ mapbox::geometry::linear_ring<T2> lr;
+ lr.reserve(r->size() + 1);
+ auto firstPt = r->points;
+ auto ptIt = r->points;
+ if (reverse_output) {
+ do {
+ lr.emplace_back(static_cast<T2>(ptIt->x), static_cast<T2>(ptIt->y));
+ ptIt = ptIt->next;
+ } while (ptIt != firstPt);
+ } else {
+ do {
+ lr.emplace_back(static_cast<T2>(ptIt->x), static_cast<T2>(ptIt->y));
+ ptIt = ptIt->prev;
+ } while (ptIt != firstPt);
+ }
+ lr.emplace_back(firstPt->x, firstPt->y); // close the ring
+ poly.push_back(lr);
+}
+
+template <typename T1, typename T2>
+void build_result_polygons(mapbox::geometry::multi_polygon<T2>& solution,
+ ring_vector<T1>const& rings,
+ bool reverse_output) {
+ for (auto r : rings) {
+ if (r == nullptr) {
+ continue;
+ }
+ assert(r->points);
+ solution.emplace_back();
+ push_ring_to_polygon(solution.back(), r, reverse_output);
+ for (auto c : r->children) {
+ if (c == nullptr) {
+ continue;
+ }
+ assert(c->points);
+ push_ring_to_polygon(solution.back(), c, reverse_output);
+ }
+ for (auto c : r->children) {
+ if (c == nullptr) {
+ continue;
+ }
+ if (!c->children.empty()) {
+ build_result_polygons(solution, c->children, reverse_output);
+ }
+ }
+ }
+}
+
+template <typename T1, typename T2>
+void build_result(mapbox::geometry::multi_polygon<T2>& solution,
+ ring_manager<T1>const& rings,
+ bool reverse_output) {
+ build_result_polygons(solution, rings.children, reverse_output);
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/config.hpp b/include/mapbox/geometry/wagyu/config.hpp
new file mode 100644
index 0000000..babff2a
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/config.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <cassert>
+#include <cstdint>
+#include <list>
+#include <stdexcept>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+enum clip_type : std::uint8_t {
+ clip_type_intersection = 0,
+ clip_type_union,
+ clip_type_difference,
+ clip_type_x_or
+};
+
+enum polygon_type : std::uint8_t { polygon_type_subject = 0, polygon_type_clip };
+
+enum fill_type : std::uint8_t {
+ fill_type_even_odd = 0,
+ fill_type_non_zero,
+ fill_type_positive,
+ fill_type_negative
+};
+
+static double const def_arc_tolerance = 0.25;
+
+static int const EDGE_UNASSIGNED = -1; // edge not currently 'owning' a solution
+static int const EDGE_SKIP = -2; // edge that would otherwise close a path
+static std::int64_t const LOW_RANGE = 0x3FFFFFFF;
+static std::int64_t const HIGH_RANGE = 0x3FFFFFFFFFFFFFFFLL;
+
+enum horizontal_direction : std::uint8_t { right_to_left = 0, left_to_right = 1 };
+
+enum edge_side : std::uint8_t { edge_left = 0, edge_right };
+
+enum join_type : std::uint8_t { join_type_square = 0, join_type_round, join_type_miter };
+
+enum end_type {
+ end_type_closed_polygon = 0,
+ end_type_closed_line,
+ end_type_open_butt,
+ end_type_open_square,
+ end_type_open_round
+};
+
+template <typename T>
+using maxima_list = std::list<T>;
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/edge.hpp b/include/mapbox/geometry/wagyu/edge.hpp
new file mode 100644
index 0000000..855f3f0
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/edge.hpp
@@ -0,0 +1,123 @@
+#pragma once
+
+#include <cmath>
+#include <limits>
+#include <list>
+
+#include <mapbox/geometry/point.hpp>
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/util.hpp>
+
+#ifdef DEBUG
+#include <iostream>
+#endif
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+struct bound;
+
+template <typename T>
+using bound_ptr = bound<T>*;
+
+template <typename T>
+struct edge {
+ mapbox::geometry::point<T> bot;
+ mapbox::geometry::point<T> top;
+ double dx;
+
+ edge(edge<T>&& e) noexcept : bot(std::move(e.bot)), top(std::move(e.top)), dx(std::move(e.dx)) {
+ }
+
+ edge& operator=(edge<T>&& e) noexcept {
+ bot = std::move(e.bot);
+ top = std::move(e.top);
+ dx = std::move(e.dx);
+ return *this;
+ }
+
+ template <typename T2>
+ edge(mapbox::geometry::point<T2> const& current,
+ mapbox::geometry::point<T2> const& next_pt) noexcept
+ : bot(static_cast<T>(current.x), static_cast<T>(current.y)),
+ top(static_cast<T>(current.x), static_cast<T>(current.y)),
+ dx(0.0) {
+ if (current.y >= next_pt.y) {
+ top = mapbox::geometry::point<T>(static_cast<T>(next_pt.x), static_cast<T>(next_pt.y));
+ } else {
+ bot = mapbox::geometry::point<T>(static_cast<T>(next_pt.x), static_cast<T>(next_pt.y));
+ }
+ double dy = static_cast<double>(top.y - bot.y);
+ if (value_is_zero(dy)) {
+ dx = std::numeric_limits<double>::infinity();
+ } else {
+ dx = static_cast<double>(top.x - bot.x) / dy;
+ }
+ }
+};
+
+template <typename T>
+using edge_ptr = edge<T>*;
+
+template <typename T>
+using edge_list = std::vector<edge<T>>;
+
+template <typename T>
+using edge_list_itr = typename edge_list<T>::iterator;
+
+template <typename T>
+bool slopes_equal(edge<T> const& e1, edge<T> const& e2) {
+ return (e1.top.y - e1.bot.y) * (e2.top.x - e2.bot.x) ==
+ (e1.top.x - e1.bot.x) * (e2.top.y - e2.bot.y);
+}
+
+template <typename T>
+inline bool is_horizontal(edge<T> const& e) {
+ return std::isinf(e.dx);
+}
+
+template <typename T>
+inline double get_current_x(edge<T> const& edge, const T current_y) {
+ if (current_y == edge.top.y) {
+ return static_cast<double>(edge.top.x);
+ } else {
+ return static_cast<double>(edge.bot.x) +
+ edge.dx * static_cast<double>(current_y - edge.bot.y);
+ }
+}
+
+#ifdef DEBUG
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ const edge<T>& e) {
+ out << " Edge: " << std::endl;
+ out << " bot x: " << e.bot.x << " y: " << e.bot.y << std::endl;
+ out << " top x: " << e.top.x << " y: " << e.top.y << std::endl;
+ return out;
+}
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ edge_list<T> const& edges) {
+ out << "[";
+ bool first = true;
+ for (auto const& e : edges) {
+ if (first) {
+ first = false;
+ } else {
+ out << ",";
+ }
+ out << "[[" << e.bot.x << "," << e.bot.y << "],[";
+ out << e.top.x << "," << e.top.y << "]]";
+ }
+ out << "]";
+ return out;
+}
+
+#endif
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/intersect.hpp b/include/mapbox/geometry/wagyu/intersect.hpp
new file mode 100644
index 0000000..f9fbfd9
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/intersect.hpp
@@ -0,0 +1,72 @@
+#pragma once
+
+#include <set>
+
+#include <mapbox/geometry/point.hpp>
+
+#include <mapbox/geometry/wagyu/active_bound_list.hpp>
+
+#ifdef DEBUG
+#include <iostream>
+#endif
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+struct intersect_node {
+
+ bound_ptr<T> bound1;
+ bound_ptr<T> bound2;
+ mapbox::geometry::point<double> pt;
+
+ intersect_node(intersect_node<T>&& n)
+ : bound1(std::move(n.bound1)), bound2(std::move(n.bound2)), pt(std::move(n.pt)) {
+ }
+
+ intersect_node& operator=(intersect_node<T>&& n) {
+ bound1 = std::move(n.bound1);
+ bound2 = std::move(n.bound2);
+ pt = std::move(n.pt);
+ return *this;
+ }
+
+ intersect_node(bound_ptr<T> const& bound1_,
+ bound_ptr<T> const& bound2_,
+ mapbox::geometry::point<double> const& pt_)
+ : bound1(bound1_), bound2(bound2_), pt(pt_) {
+ }
+};
+
+template <typename T>
+using intersect_list = std::vector<intersect_node<T>>;
+
+#ifdef DEBUG
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ const intersect_node<T>& e) {
+ out << " point x: " << e.pt.x << " y: " << e.pt.y << std::endl;
+ out << " bound 1: " << std::endl;
+ out << *e.bound1 << std::endl;
+ out << " bound 2: " << std::endl;
+ out << *e.bound2 << std::endl;
+ return out;
+}
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ const intersect_list<T>& ints) {
+ std::size_t c = 0;
+ for (auto const& i : ints) {
+ out << "Intersection: " << c++ << std::endl;
+ out << i;
+ }
+ return out;
+}
+
+#endif
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/intersect_util.hpp b/include/mapbox/geometry/wagyu/intersect_util.hpp
new file mode 100644
index 0000000..4608f3b
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/intersect_util.hpp
@@ -0,0 +1,372 @@
+#pragma once
+
+#include <mapbox/geometry/wagyu/active_bound_list.hpp>
+#include <mapbox/geometry/wagyu/bound.hpp>
+#include <mapbox/geometry/wagyu/bubble_sort.hpp>
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/intersect.hpp>
+#include <mapbox/geometry/wagyu/ring_util.hpp>
+#include <mapbox/geometry/wagyu/util.hpp>
+
+#include <algorithm>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+struct intersect_list_sorter {
+ inline bool operator()(intersect_node<T> const& node1, intersect_node<T> const& node2) {
+ if (!values_are_equal(node2.pt.y, node1.pt.y)) {
+ return node2.pt.y < node1.pt.y;
+ } else {
+ return (node2.bound1->winding_count2 + node2.bound2->winding_count2) >
+ (node1.bound1->winding_count2 + node1.bound2->winding_count2);
+ }
+ }
+};
+
+template <typename T>
+inline mapbox::geometry::point<T> round_point(mapbox::geometry::point<double> const& pt) {
+ return mapbox::geometry::point<T>(round_towards_max<T>(pt.x), round_towards_max<T>(pt.y));
+}
+
+template <typename T>
+inline void swap_rings(bound<T>& b1, bound<T>& b2) {
+ ring_ptr<T> ring = b1.ring;
+ b1.ring = b2.ring;
+ b2.ring = ring;
+}
+
+template <typename T>
+inline void swap_sides(bound<T>& b1, bound<T>& b2) {
+ edge_side side = b1.side;
+ b1.side = b2.side;
+ b2.side = side;
+}
+
+template <typename T1, typename T2>
+bool get_edge_intersection(edge<T1> const& e1,
+ edge<T1> const& e2,
+ mapbox::geometry::point<T2>& pt) {
+ T2 p0_x = static_cast<T2>(e1.bot.x);
+ T2 p0_y = static_cast<T2>(e1.bot.y);
+ T2 p1_x = static_cast<T2>(e1.top.x);
+ T2 p1_y = static_cast<T2>(e1.top.y);
+ T2 p2_x = static_cast<T2>(e2.bot.x);
+ T2 p2_y = static_cast<T2>(e2.bot.y);
+ T2 p3_x = static_cast<T2>(e2.top.x);
+ T2 p3_y = static_cast<T2>(e2.top.y);
+ T2 s1_x, s1_y, s2_x, s2_y;
+ s1_x = p1_x - p0_x;
+ s1_y = p1_y - p0_y;
+ s2_x = p3_x - p2_x;
+ s2_y = p3_y - p2_y;
+
+ T2 s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
+ T2 t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
+
+ if (s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0) {
+ pt.x = p0_x + (t * s1_x);
+ pt.y = p0_y + (t * s1_y);
+ return true;
+ }
+ // LCOV_EXCL_START
+ return false;
+ // LCOV_EXCL_END
+}
+
+template <typename T>
+struct intersection_compare {
+ bool operator()(bound_ptr<T> const& b1, bound_ptr<T> const& b2) {
+ return !(b1->current_x > b2->current_x &&
+ !slopes_equal(*(b1->current_edge), *(b2->current_edge)));
+ }
+};
+
+template <typename T>
+struct on_intersection_swap {
+
+ intersect_list<T>& intersects;
+
+ on_intersection_swap(intersect_list<T>& i) : intersects(i) {
+ }
+
+ void operator()(bound_ptr<T> const& b1, bound_ptr<T> const& b2) {
+ mapbox::geometry::point<double> pt;
+ if (!get_edge_intersection<T, double>(*(b1->current_edge), *(b2->current_edge), pt)) {
+ // LCOV_EXCL_START
+ throw std::runtime_error("Trying to find intersection of lines that do not intersect");
+ // LCOV_EXCL_END
+ }
+ intersects.emplace_back(b1, b2, pt);
+ }
+};
+
+template <typename T>
+void build_intersect_list(active_bound_list<T>& active_bounds, intersect_list<T>& intersects) {
+ bubble_sort(active_bounds.begin(), active_bounds.end(), intersection_compare<T>(),
+ on_intersection_swap<T>(intersects));
+}
+
+template <typename T>
+void intersect_bounds(bound<T>& b1,
+ bound<T>& b2,
+ mapbox::geometry::point<T> const& pt,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type,
+ ring_manager<T>& rings,
+ active_bound_list<T>& active_bounds) {
+ bool b1Contributing = (b1.ring != nullptr);
+ bool b2Contributing = (b2.ring != nullptr);
+
+ // update winding counts...
+ // assumes that b1 will be to the Right of b2 ABOVE the intersection
+ if (b1.poly_type == b2.poly_type) {
+ if (is_even_odd_fill_type(b1, subject_fill_type, clip_fill_type)) {
+ std::swap(b1.winding_count, b2.winding_count);
+ } else {
+ if (b1.winding_count + b2.winding_delta == 0) {
+ b1.winding_count = -b1.winding_count;
+ } else {
+ b1.winding_count += b2.winding_delta;
+ }
+ if (b2.winding_count - b1.winding_delta == 0) {
+ b2.winding_count = -b2.winding_count;
+ } else {
+ b2.winding_count -= b1.winding_delta;
+ }
+ }
+ } else {
+ if (!is_even_odd_fill_type(b2, subject_fill_type, clip_fill_type)) {
+ b1.winding_count2 += b2.winding_delta;
+ } else {
+ b1.winding_count2 = (b1.winding_count2 == 0) ? 1 : 0;
+ }
+ if (!is_even_odd_fill_type(b1, subject_fill_type, clip_fill_type)) {
+ b2.winding_count2 -= b1.winding_delta;
+ } else {
+ b2.winding_count2 = (b2.winding_count2 == 0) ? 1 : 0;
+ }
+ }
+
+ fill_type b1FillType, b2FillType, b1FillType2, b2FillType2;
+ if (b1.poly_type == polygon_type_subject) {
+ b1FillType = subject_fill_type;
+ b1FillType2 = clip_fill_type;
+ } else {
+ b1FillType = clip_fill_type;
+ b1FillType2 = subject_fill_type;
+ }
+ if (b2.poly_type == polygon_type_subject) {
+ b2FillType = subject_fill_type;
+ b2FillType2 = clip_fill_type;
+ } else {
+ b2FillType = clip_fill_type;
+ b2FillType2 = subject_fill_type;
+ }
+
+ std::int32_t b1Wc, b2Wc;
+ switch (b1FillType) {
+ case fill_type_positive:
+ b1Wc = b1.winding_count;
+ break;
+ case fill_type_negative:
+ b1Wc = -b1.winding_count;
+ break;
+ case fill_type_even_odd:
+ case fill_type_non_zero:
+ default:
+ b1Wc = std::abs(static_cast<int>(b1.winding_count));
+ }
+ switch (b2FillType) {
+ case fill_type_positive:
+ b2Wc = b2.winding_count;
+ break;
+ case fill_type_negative:
+ b2Wc = -b2.winding_count;
+ break;
+ case fill_type_even_odd:
+ case fill_type_non_zero:
+ default:
+ b2Wc = std::abs(static_cast<int>(b2.winding_count));
+ }
+ if (b1Contributing && b2Contributing) {
+ if ((b1Wc != 0 && b1Wc != 1) || (b2Wc != 0 && b2Wc != 1) ||
+ (b1.poly_type != b2.poly_type && cliptype != clip_type_x_or)) {
+ add_local_maximum_point(b1, b2, pt, rings, active_bounds);
+ } else {
+ add_point(b1, active_bounds, pt, rings);
+ add_point(b2, active_bounds, pt, rings);
+ swap_sides(b1, b2);
+ swap_rings(b1, b2);
+ }
+ } else if (b1Contributing) {
+ if (b2Wc == 0 || b2Wc == 1) {
+ add_point(b1, active_bounds, pt, rings);
+ b2.last_point = pt;
+ swap_sides(b1, b2);
+ swap_rings(b1, b2);
+ }
+ } else if (b2Contributing) {
+ if (b1Wc == 0 || b1Wc == 1) {
+ b1.last_point = pt;
+ add_point(b2, active_bounds, pt, rings);
+ swap_sides(b1, b2);
+ swap_rings(b1, b2);
+ }
+ } else if ((b1Wc == 0 || b1Wc == 1) && (b2Wc == 0 || b2Wc == 1)) {
+ // neither bound is currently contributing ...
+
+ std::int32_t b1Wc2, b2Wc2;
+ switch (b1FillType2) {
+ case fill_type_positive:
+ b1Wc2 = b1.winding_count2;
+ break;
+ case fill_type_negative:
+ b1Wc2 = -b1.winding_count2;
+ break;
+ case fill_type_even_odd:
+ case fill_type_non_zero:
+ default:
+ b1Wc2 = std::abs(static_cast<int>(b1.winding_count2));
+ }
+ switch (b2FillType2) {
+ case fill_type_positive:
+ b2Wc2 = b2.winding_count2;
+ break;
+ case fill_type_negative:
+ b2Wc2 = -b2.winding_count2;
+ break;
+ case fill_type_even_odd:
+ case fill_type_non_zero:
+ default:
+ b2Wc2 = std::abs(static_cast<int>(b2.winding_count2));
+ }
+
+ if (b1.poly_type != b2.poly_type) {
+ add_local_minimum_point(b1, b2, active_bounds, pt, rings);
+ } else if (b1Wc == 1 && b2Wc == 1) {
+ switch (cliptype) {
+ case clip_type_intersection:
+ if (b1Wc2 > 0 && b2Wc2 > 0) {
+ add_local_minimum_point(b1, b2, active_bounds, pt, rings);
+ }
+ break;
+ default:
+ case clip_type_union:
+ if (b1Wc2 <= 0 && b2Wc2 <= 0) {
+ add_local_minimum_point(b1, b2, active_bounds, pt, rings);
+ }
+ break;
+ case clip_type_difference:
+ if (((b1.poly_type == polygon_type_clip) && (b1Wc2 > 0) && (b2Wc2 > 0)) ||
+ ((b1.poly_type == polygon_type_subject) && (b1Wc2 <= 0) && (b2Wc2 <= 0))) {
+ add_local_minimum_point(b1, b2, active_bounds, pt, rings);
+ }
+ break;
+ case clip_type_x_or:
+ add_local_minimum_point(b1, b2, active_bounds, pt, rings);
+ }
+ } else {
+ swap_sides(b1, b2);
+ }
+ }
+}
+
+template <typename T>
+bool bounds_adjacent(intersect_node<T> const& inode, bound_ptr<T> next) {
+ return (next == inode.bound2) || (next == inode.bound1);
+}
+
+template <typename T>
+struct find_first_bound {
+ bound_ptr<T> b1;
+ bound_ptr<T> b2;
+
+ find_first_bound(intersect_node<T> const& inode) : b1(inode.bound1), b2(inode.bound2) {
+ }
+
+ bool operator()(bound_ptr<T> const& b) {
+ return b == b1 || b == b2;
+ }
+};
+
+template <typename T>
+void process_intersect_list(intersect_list<T>& intersects,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type,
+ ring_manager<T>& rings,
+ active_bound_list<T>& active_bounds) {
+ for (auto node_itr = intersects.begin(); node_itr != intersects.end(); ++node_itr) {
+ auto b1 = std::find_if(active_bounds.begin(), active_bounds.end(),
+ find_first_bound<T>(*node_itr));
+ auto b2 = std::next(b1);
+ if (!bounds_adjacent(*node_itr, *b2)) {
+ auto next_itr = std::next(node_itr);
+ while (next_itr != intersects.end()) {
+ auto n1 = std::find_if(active_bounds.begin(), active_bounds.end(),
+ find_first_bound<T>(*next_itr));
+ auto n2 = std::next(n1);
+ if (bounds_adjacent(*next_itr, *n2)) {
+ b1 = n1;
+ b2 = n2;
+ break;
+ }
+ ++next_itr;
+ }
+ if (next_itr == intersects.end()) {
+ throw std::runtime_error("Could not properly correct intersection order.");
+ }
+ std::iter_swap(node_itr, next_itr);
+ }
+ mapbox::geometry::point<T> pt = round_point<T>(node_itr->pt);
+ intersect_bounds(*(node_itr->bound1), *(node_itr->bound2), pt, cliptype, subject_fill_type,
+ clip_fill_type, rings, active_bounds);
+ std::iter_swap(b1, b2);
+ }
+}
+
+template <typename T>
+void update_current_x(active_bound_list<T>& active_bounds, T top_y) {
+ std::size_t pos = 0;
+ for (auto& bnd : active_bounds) {
+ bnd->pos = pos++;
+ bnd->current_x = get_current_x(*bnd->current_edge, top_y);
+ }
+}
+
+template <typename T>
+void process_intersections(T top_y,
+ active_bound_list<T>& active_bounds,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type,
+ ring_manager<T>& rings) {
+ if (active_bounds.empty()) {
+ return;
+ }
+ update_current_x(active_bounds, top_y);
+ intersect_list<T> intersects;
+ build_intersect_list(active_bounds, intersects);
+
+ if (intersects.empty()) {
+ return;
+ }
+
+ // Restore order of active bounds list
+ std::stable_sort(
+ active_bounds.begin(), active_bounds.end(),
+ [](bound_ptr<T> const& b1, bound_ptr<T> const& b2) { return b1->pos < b2->pos; });
+
+ // Sort the intersection list
+ std::stable_sort(intersects.begin(), intersects.end(), intersect_list_sorter<T>());
+
+ process_intersect_list(intersects, cliptype, subject_fill_type, clip_fill_type, rings,
+ active_bounds);
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/local_minimum.hpp b/include/mapbox/geometry/wagyu/local_minimum.hpp
new file mode 100644
index 0000000..fe03d84
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/local_minimum.hpp
@@ -0,0 +1,118 @@
+#pragma once
+
+#ifdef DEBUG
+#include <iostream>
+#include <sstream>
+#endif
+
+#include <queue>
+
+#include <mapbox/geometry/wagyu/bound.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+struct local_minimum {
+ bound<T> left_bound;
+ bound<T> right_bound;
+ T y;
+ bool minimum_has_horizontal;
+
+ local_minimum(bound<T>&& left_bound_, bound<T>&& right_bound_, T y_, bool has_horz_)
+ : left_bound(std::move(left_bound_)),
+ right_bound(std::move(right_bound_)),
+ y(y_),
+ minimum_has_horizontal(has_horz_) {
+ }
+};
+
+template <typename T>
+using local_minimum_list = std::deque<local_minimum<T>>;
+
+template <typename T>
+using local_minimum_itr = typename local_minimum_list<T>::iterator;
+
+template <typename T>
+using local_minimum_ptr = local_minimum<T>*;
+
+template <typename T>
+using local_minimum_ptr_list = std::vector<local_minimum_ptr<T>>;
+
+template <typename T>
+using local_minimum_ptr_list_itr = typename local_minimum_ptr_list<T>::iterator;
+
+template <typename T>
+struct local_minimum_sorter {
+ inline bool operator()(local_minimum_ptr<T> const& locMin1,
+ local_minimum_ptr<T> const& locMin2) {
+ if (locMin2->y == locMin1->y) {
+ return locMin2->minimum_has_horizontal != locMin1->minimum_has_horizontal &&
+ locMin1->minimum_has_horizontal;
+ }
+ return locMin2->y < locMin1->y;
+ }
+};
+
+#ifdef DEBUG
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ const local_minimum<T>& lm) {
+ out << " Local Minimum:" << std::endl;
+ out << " y: " << lm.y << std::endl;
+ if (lm.minimum_has_horizontal) {
+ out << " minimum_has_horizontal: true" << std::endl;
+ } else {
+ out << " minimum_has_horizontal: false" << std::endl;
+ }
+ out << " left_bound: " << std::endl;
+ out << lm.left_bound << std::endl;
+ out << " right_bound: " << std::endl;
+ out << lm.right_bound << std::endl;
+ return out;
+}
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ const local_minimum_ptr_list<T>& lms) {
+ for (auto const& lm : lms) {
+ out << *lm;
+ }
+ return out;
+}
+
+template <typename T>
+std::string output_all_edges(local_minimum_ptr_list<T> const& lms) {
+ std::ostringstream out;
+ out << "[";
+ bool first = true;
+ for (auto const& lm : lms) {
+ for (auto const& e : lm->left_bound.edges) {
+ if (first) {
+ first = false;
+ } else {
+ out << ",";
+ }
+ out << "[[" << e.bot.x << "," << e.bot.y << "],[";
+ out << e.top.x << "," << e.top.y << "]]";
+ }
+ for (auto const& e : lm->right_bound.edges) {
+ if (first) {
+ first = false;
+ } else {
+ out << ",";
+ }
+ out << "[[" << e.bot.x << "," << e.bot.y << "],[";
+ out << e.top.x << "," << e.top.y << "]]";
+ }
+ }
+ out << "]";
+ return out.str();
+}
+
+#endif
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/local_minimum_util.hpp b/include/mapbox/geometry/wagyu/local_minimum_util.hpp
new file mode 100644
index 0000000..32fbf01
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/local_minimum_util.hpp
@@ -0,0 +1,323 @@
+#pragma once
+
+#include <mapbox/geometry/wagyu/edge.hpp>
+#include <mapbox/geometry/wagyu/local_minimum.hpp>
+
+#include <algorithm>
+
+#ifdef DEBUG
+#include <stdexcept>
+#endif
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+inline void reverse_horizontal(edge<T>& e) {
+ // swap horizontal edges' top and bottom x's so they follow the natural
+ // progression of the bounds - ie so their xbots will align with the
+ // adjoining lower edge. [Helpful in the process_horizontal() method.]
+ std::swap(e.top.x, e.bot.x);
+}
+
+// Make a list start on a local maximum by
+// shifting all the points not on a local maximum to the
+template <typename T>
+void start_list_on_local_maximum(edge_list<T>& edges) {
+ if (edges.size() <= 2) {
+ return;
+ }
+ // Find the first local maximum going forward in the list
+ auto prev_edge = edges.end();
+ --prev_edge;
+ bool prev_edge_is_horizontal = is_horizontal(*prev_edge);
+ auto edge = edges.begin();
+ bool edge_is_horizontal;
+ bool y_decreasing_before_last_horizontal = false; // assume false at start
+
+ while (edge != edges.end()) {
+ edge_is_horizontal = is_horizontal(*edge);
+ if ((!prev_edge_is_horizontal && !edge_is_horizontal && edge->top == prev_edge->top)) {
+ break;
+ }
+ if (!edge_is_horizontal && prev_edge_is_horizontal) {
+ if (y_decreasing_before_last_horizontal &&
+ (edge->top == prev_edge->bot || edge->top == prev_edge->top)) {
+ break;
+ }
+ } else if (!y_decreasing_before_last_horizontal && !prev_edge_is_horizontal &&
+ edge_is_horizontal &&
+ (prev_edge->top == edge->top || prev_edge->top == edge->bot)) {
+ y_decreasing_before_last_horizontal = true;
+ }
+ prev_edge_is_horizontal = edge_is_horizontal;
+ prev_edge = edge;
+ ++edge;
+ }
+ std::rotate(edges.begin(), edge, edges.end());
+}
+
+template <typename T>
+bound<T> create_bound_towards_minimum(edge_list<T>& edges) {
+ if (edges.size() == 1) {
+ if (is_horizontal(edges.front())) {
+ reverse_horizontal(edges.front());
+ }
+ bound<T> bnd;
+ std::swap(bnd.edges, edges);
+ return bnd;
+ }
+ auto next_edge = edges.begin();
+ auto edge = next_edge;
+ ++next_edge;
+ bool edge_is_horizontal = is_horizontal(*edge);
+ if (edge_is_horizontal) {
+ reverse_horizontal(*edge);
+ }
+ bool next_edge_is_horizontal;
+ bool y_increasing_before_last_horizontal = false; // assume false at start
+
+ while (next_edge != edges.end()) {
+ next_edge_is_horizontal = is_horizontal(*next_edge);
+ if ((!next_edge_is_horizontal && !edge_is_horizontal && edge->bot == next_edge->bot)) {
+ break;
+ }
+ if (!next_edge_is_horizontal && edge_is_horizontal) {
+ if (y_increasing_before_last_horizontal &&
+ (next_edge->bot == edge->bot || next_edge->bot == edge->top)) {
+ break;
+ }
+ } else if (!y_increasing_before_last_horizontal && !edge_is_horizontal &&
+ next_edge_is_horizontal &&
+ (edge->bot == next_edge->top || edge->bot == next_edge->bot)) {
+ y_increasing_before_last_horizontal = true;
+ }
+ edge_is_horizontal = next_edge_is_horizontal;
+ edge = next_edge;
+ if (edge_is_horizontal) {
+ reverse_horizontal(*edge);
+ }
+ ++next_edge;
+ }
+ bound<T> bnd;
+ if (next_edge == edges.end()) {
+ std::swap(edges, bnd.edges);
+ } else {
+ bnd.edges.reserve(static_cast<std::size_t>(std::distance(edges.begin(), next_edge)));
+ std::move(edges.begin(), next_edge, std::back_inserter(bnd.edges));
+ edges.erase(edges.begin(), next_edge);
+ }
+ std::reverse(bnd.edges.begin(), bnd.edges.end());
+ return bnd;
+}
+
+template <typename T>
+bound<T> create_bound_towards_maximum(edge_list<T>& edges) {
+ if (edges.size() == 1) {
+ bound<T> bnd;
+ std::swap(bnd.edges, edges);
+ return bnd;
+ }
+ auto next_edge = edges.begin();
+ auto edge = next_edge;
+ ++next_edge;
+ bool edge_is_horizontal = is_horizontal(*edge);
+ bool next_edge_is_horizontal;
+ bool y_decreasing_before_last_horizontal = false; // assume false at start
+
+ while (next_edge != edges.end()) {
+ next_edge_is_horizontal = is_horizontal(*next_edge);
+ if ((!next_edge_is_horizontal && !edge_is_horizontal && edge->top == next_edge->top)) {
+ break;
+ }
+ if (!next_edge_is_horizontal && edge_is_horizontal) {
+ if (y_decreasing_before_last_horizontal &&
+ (next_edge->top == edge->bot || next_edge->top == edge->top)) {
+ break;
+ }
+ } else if (!y_decreasing_before_last_horizontal && !edge_is_horizontal &&
+ next_edge_is_horizontal &&
+ (edge->top == next_edge->top || edge->top == next_edge->bot)) {
+ y_decreasing_before_last_horizontal = true;
+ }
+ edge_is_horizontal = next_edge_is_horizontal;
+ edge = next_edge;
+ ++next_edge;
+ }
+ bound<T> bnd;
+ if (next_edge == edges.end()) {
+ std::swap(bnd.edges, edges);
+ } else {
+ bnd.edges.reserve(static_cast<std::size_t>(std::distance(edges.begin(), next_edge)));
+ std::move(edges.begin(), next_edge, std::back_inserter(bnd.edges));
+ edges.erase(edges.begin(), next_edge);
+ }
+ return bnd;
+}
+
+template <typename T>
+void fix_horizontals(bound<T>& bnd) {
+
+ auto edge_itr = bnd.edges.begin();
+ auto next_itr = std::next(edge_itr);
+ if (next_itr == bnd.edges.end()) {
+ return;
+ }
+ if (is_horizontal(*edge_itr) && next_itr->bot != edge_itr->top) {
+ reverse_horizontal(*edge_itr);
+ }
+ auto prev_itr = edge_itr++;
+ while (edge_itr != bnd.edges.end()) {
+ if (is_horizontal(*edge_itr) && prev_itr->top != edge_itr->bot) {
+ reverse_horizontal(*edge_itr);
+ }
+ prev_itr = edge_itr;
+ ++edge_itr;
+ }
+}
+
+template <typename T>
+void move_horizontals_on_left_to_right(bound<T>& left_bound, bound<T>& right_bound) {
+ // We want all the horizontal segments that are at the same Y as the minimum to be on the right
+ // bound
+ auto edge_itr = left_bound.edges.begin();
+ while (edge_itr != left_bound.edges.end()) {
+ if (!is_horizontal(*edge_itr)) {
+ break;
+ }
+ reverse_horizontal(*edge_itr);
+ ++edge_itr;
+ }
+ if (edge_itr == left_bound.edges.begin()) {
+ return;
+ }
+ std::reverse(left_bound.edges.begin(), edge_itr);
+ auto dist = std::distance(left_bound.edges.begin(), edge_itr);
+ std::move(left_bound.edges.begin(), edge_itr, std::back_inserter(right_bound.edges));
+ left_bound.edges.erase(left_bound.edges.begin(), edge_itr);
+ std::rotate(right_bound.edges.begin(), std::prev(right_bound.edges.end(), dist),
+ right_bound.edges.end());
+}
+
+template <typename T>
+void add_ring_to_local_minima_list(edge_list<T>& edges,
+ local_minimum_list<T>& minima_list,
+ polygon_type poly_type) {
+
+ if (edges.empty()) {
+ return;
+ }
+ // Adjust the order of the ring so we start on a local maximum
+ // therefore we start right away on a bound.
+ start_list_on_local_maximum(edges);
+
+ bound_ptr<T> first_minimum = nullptr;
+ bound_ptr<T> last_maximum = nullptr;
+ while (!edges.empty()) {
+ bool lm_minimum_has_horizontal = false;
+ auto to_minimum = create_bound_towards_minimum(edges);
+ if (edges.empty()) {
+ throw std::runtime_error("Edges is empty after only creating a single bound.");
+ }
+ auto to_maximum = create_bound_towards_maximum(edges);
+ fix_horizontals(to_minimum);
+ fix_horizontals(to_maximum);
+ auto to_max_first_non_horizontal = to_maximum.edges.begin();
+ auto to_min_first_non_horizontal = to_minimum.edges.begin();
+ bool minimum_is_left = true;
+ while (to_max_first_non_horizontal != to_maximum.edges.end() &&
+ is_horizontal(*to_max_first_non_horizontal)) {
+ lm_minimum_has_horizontal = true;
+ ++to_max_first_non_horizontal;
+ }
+ while (to_min_first_non_horizontal != to_minimum.edges.end() &&
+ is_horizontal(*to_min_first_non_horizontal)) {
+ lm_minimum_has_horizontal = true;
+ ++to_min_first_non_horizontal;
+ }
+
+ if (to_max_first_non_horizontal == to_maximum.edges.end() ||
+ to_min_first_non_horizontal == to_minimum.edges.end()) {
+ throw std::runtime_error("should not have a horizontal only bound for a ring");
+ }
+
+ if (lm_minimum_has_horizontal) {
+ if (to_max_first_non_horizontal->bot.x > to_min_first_non_horizontal->bot.x) {
+ minimum_is_left = true;
+ move_horizontals_on_left_to_right(to_minimum, to_maximum);
+ } else {
+ minimum_is_left = false;
+ move_horizontals_on_left_to_right(to_maximum, to_minimum);
+ }
+ } else {
+ if (to_max_first_non_horizontal->dx > to_min_first_non_horizontal->dx) {
+ minimum_is_left = false;
+ } else {
+ minimum_is_left = true;
+ }
+ }
+ assert(!to_minimum.edges.empty());
+ assert(!to_maximum.edges.empty());
+ auto const& min_front = to_minimum.edges.front();
+ if (last_maximum) {
+ to_minimum.maximum_bound = last_maximum;
+ }
+ to_minimum.poly_type = poly_type;
+ to_maximum.poly_type = poly_type;
+ if (!minimum_is_left) {
+ to_minimum.side = edge_right;
+ to_maximum.side = edge_left;
+ to_minimum.winding_delta = -1;
+ to_maximum.winding_delta = 1;
+ minima_list.emplace_back(std::move(to_maximum), std::move(to_minimum), min_front.bot.y,
+ lm_minimum_has_horizontal);
+ if (!last_maximum) {
+ first_minimum = &(minima_list.back().right_bound);
+ } else {
+ last_maximum->maximum_bound = &(minima_list.back().right_bound);
+ }
+ last_maximum = &(minima_list.back().left_bound);
+ } else {
+ to_minimum.side = edge_left;
+ to_maximum.side = edge_right;
+ to_minimum.winding_delta = -1;
+ to_maximum.winding_delta = 1;
+ minima_list.emplace_back(std::move(to_minimum), std::move(to_maximum), min_front.bot.y,
+ lm_minimum_has_horizontal);
+ if (!last_maximum) {
+ first_minimum = &(minima_list.back().left_bound);
+ } else {
+ last_maximum->maximum_bound = &(minima_list.back().left_bound);
+ }
+ last_maximum = &(minima_list.back().right_bound);
+ }
+ }
+ last_maximum->maximum_bound = first_minimum;
+ first_minimum->maximum_bound = last_maximum;
+}
+
+template <typename T>
+void initialize_lm(local_minimum_ptr_list_itr<T>& lm) {
+ if (!(*lm)->left_bound.edges.empty()) {
+ (*lm)->left_bound.current_edge = (*lm)->left_bound.edges.begin();
+ (*lm)->left_bound.next_edge = std::next((*lm)->left_bound.current_edge);
+ (*lm)->left_bound.current_x = static_cast<double>((*lm)->left_bound.current_edge->bot.x);
+ (*lm)->left_bound.winding_count = 0;
+ (*lm)->left_bound.winding_count2 = 0;
+ (*lm)->left_bound.side = edge_left;
+ (*lm)->left_bound.ring = nullptr;
+ }
+ if (!(*lm)->right_bound.edges.empty()) {
+ (*lm)->right_bound.current_edge = (*lm)->right_bound.edges.begin();
+ (*lm)->right_bound.next_edge = std::next((*lm)->right_bound.current_edge);
+ (*lm)->right_bound.current_x = static_cast<double>((*lm)->right_bound.current_edge->bot.x);
+ (*lm)->right_bound.winding_count = 0;
+ (*lm)->right_bound.winding_count2 = 0;
+ (*lm)->right_bound.side = edge_right;
+ (*lm)->right_bound.ring = nullptr;
+ }
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/point.hpp b/include/mapbox/geometry/wagyu/point.hpp
new file mode 100644
index 0000000..949a834
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/point.hpp
@@ -0,0 +1,111 @@
+#pragma once
+
+#include <mapbox/geometry/point.hpp>
+
+#ifdef DEBUG
+#include <iostream>
+#endif
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+struct point;
+
+template <typename T>
+using point_ptr = point<T>*;
+
+template <typename T>
+using const_point_ptr = point<T>* const;
+
+template <typename T>
+struct ring;
+
+template <typename T>
+using ring_ptr = ring<T>*;
+
+template <typename T>
+using const_ring_ptr = ring<T>* const;
+
+template <typename T>
+struct point {
+ using coordinate_type = T;
+ ring_ptr<T> ring;
+ T x;
+ T y;
+ point_ptr<T> next;
+ point_ptr<T> prev;
+
+ point() : ring(nullptr), x(0), y(0), prev(this), next(this) {
+ }
+
+ point(T x_, T y_) : ring(nullptr), x(x_), y(y_), next(this), prev(this) {
+ }
+
+ point(ring_ptr<T> ring_, mapbox::geometry::point<T> const& pt)
+ : ring(ring_), x(pt.x), y(pt.y), next(this), prev(this) {
+ }
+
+ point(ring_ptr<T> ring_, mapbox::geometry::point<T> const& pt, point_ptr<T> before_this_point)
+ : ring(ring_), x(pt.x), y(pt.y), next(before_this_point), prev(before_this_point->prev) {
+ before_this_point->prev = this;
+ prev->next = this;
+ }
+};
+
+template <typename T>
+using point_vector = std::vector<point_ptr<T>>;
+
+template <typename T>
+using point_vector_itr = typename point_vector<T>::iterator;
+
+template <typename T>
+bool operator==(point<T> const& lhs, point<T> const& rhs) {
+ return lhs.x == rhs.x && lhs.y == rhs.y;
+}
+
+template <typename T>
+bool operator==(mapbox::geometry::point<T> const& lhs, point<T> const& rhs) {
+ return lhs.x == rhs.x && lhs.y == rhs.y;
+}
+
+template <typename T>
+bool operator==(point<T> const& lhs, mapbox::geometry::point<T> const& rhs) {
+ return lhs.x == rhs.x && lhs.y == rhs.y;
+}
+
+template <typename T>
+bool operator!=(point<T> const& lhs, point<T> const& rhs) {
+ return lhs.x != rhs.x || lhs.y != rhs.y;
+}
+
+template <typename T>
+bool operator!=(mapbox::geometry::point<T> const& lhs, point<T> const& rhs) {
+ return lhs.x != rhs.x || lhs.y != rhs.y;
+}
+
+template <typename T>
+bool operator!=(point<T> const& lhs, mapbox::geometry::point<T> const& rhs) {
+ return lhs.x != rhs.x || lhs.y != rhs.y;
+}
+
+#ifdef DEBUG
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ const point<T>& p) {
+ out << " point at: " << p.x << ", " << p.y;
+ return out;
+}
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ const mapbox::geometry::point<T>& p) {
+ out << " point at: " << p.x << ", " << p.y;
+ return out;
+}
+#endif
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/process_horizontal.hpp b/include/mapbox/geometry/wagyu/process_horizontal.hpp
new file mode 100644
index 0000000..6ba9766
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/process_horizontal.hpp
@@ -0,0 +1,268 @@
+#pragma once
+
+#include <mapbox/geometry/line_string.hpp>
+#include <mapbox/geometry/point.hpp>
+#include <mapbox/geometry/polygon.hpp>
+
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/edge.hpp>
+#include <mapbox/geometry/wagyu/local_minimum.hpp>
+#include <mapbox/geometry/wagyu/util.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+active_bound_list_itr<T> process_horizontal_left_to_right(T scanline_y,
+ active_bound_list_itr<T>& horz_bound,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& rings,
+ scanbeam_list<T>& scanbeam,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+ auto horizontal_itr_behind = horz_bound;
+ bool shifted = false;
+ bool is_maxima_edge = is_maxima(horz_bound, scanline_y);
+ auto bound_max_pair = active_bounds.end();
+ if (is_maxima_edge) {
+ bound_max_pair = get_maxima_pair<T>(horz_bound, active_bounds);
+ }
+
+ auto hp_itr = rings.current_hp_itr;
+ while (hp_itr != rings.hot_pixels.end() &&
+ (hp_itr->y > scanline_y ||
+ (hp_itr->y == scanline_y && hp_itr->x < (*horz_bound)->current_edge->bot.x))) {
+ ++hp_itr;
+ }
+
+ auto bnd = std::next(horz_bound);
+
+ while (bnd != active_bounds.end()) {
+ if (*bnd == nullptr) {
+ ++bnd;
+ continue;
+ }
+ // this code block inserts extra coords into horizontal edges (in output
+ // polygons) wherever hot pixels touch these horizontal edges. This helps
+ //'simplifying' polygons (ie if the Simplify property is set).
+ while (hp_itr != rings.hot_pixels.end() && hp_itr->y == scanline_y &&
+ hp_itr->x < wround<T>((*bnd)->current_x) &&
+ hp_itr->x < (*horz_bound)->current_edge->top.x) {
+ if ((*horz_bound)->ring) {
+ add_point_to_ring(*(*horz_bound), *hp_itr, rings);
+ }
+ ++hp_itr;
+ }
+
+ if ((*bnd)->current_x > static_cast<double>((*horz_bound)->current_edge->top.x)) {
+ break;
+ }
+
+ // Also break if we've got to the end of an intermediate horizontal edge ...
+ // nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
+ if (wround<T>((*bnd)->current_x) == (*horz_bound)->current_edge->top.x &&
+ (*horz_bound)->next_edge != (*horz_bound)->edges.end() &&
+ (*horz_bound)->current_edge->dx < (*horz_bound)->next_edge->dx) {
+ break;
+ }
+
+ // note: may be done multiple times
+ if ((*horz_bound)->ring) {
+ add_point_to_ring(*(*horz_bound),
+ mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
+ rings);
+ }
+
+ // OK, so far we're still in range of the horizontal Edge but make sure
+ // we're at the last of consec. horizontals when matching with eMaxPair
+ if (is_maxima_edge && bnd == bound_max_pair) {
+ if ((*horz_bound)->ring) {
+ add_local_maximum_point(*(*horz_bound), *(*bound_max_pair),
+ (*horz_bound)->current_edge->top, rings, active_bounds);
+ }
+ *bound_max_pair = nullptr;
+ *horz_bound = nullptr;
+ if (!shifted) {
+ ++horizontal_itr_behind;
+ }
+ return horizontal_itr_behind;
+ }
+
+ intersect_bounds(*(*horz_bound), *(*bnd),
+ mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
+ cliptype, subject_fill_type, clip_fill_type, rings, active_bounds);
+ std::iter_swap(horz_bound, bnd);
+ horz_bound = bnd;
+ ++bnd;
+ shifted = true;
+ } // end while (bnd != active_bounds.end())
+
+ if ((*horz_bound)->ring) {
+ while (hp_itr != rings.hot_pixels.end() && hp_itr->y == scanline_y &&
+ hp_itr->x < (*horz_bound)->current_edge->top.x) {
+ add_point_to_ring(*(*horz_bound), *hp_itr, rings);
+ ++hp_itr;
+ }
+ }
+
+ if ((*horz_bound)->ring) {
+ add_point_to_ring(*(*horz_bound), (*horz_bound)->current_edge->top, rings);
+ }
+
+ if ((*horz_bound)->next_edge != (*horz_bound)->edges.end()) {
+ next_edge_in_bound(*(*horz_bound), scanbeam);
+ } else {
+ *horz_bound = nullptr;
+ }
+ if (!shifted) {
+ ++horizontal_itr_behind;
+ }
+ return horizontal_itr_behind;
+}
+
+template <typename T>
+active_bound_list_itr<T> process_horizontal_right_to_left(T scanline_y,
+ active_bound_list_itr<T>& horz_bound_fwd,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& rings,
+ scanbeam_list<T>& scanbeam,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+ auto next_bnd_itr = std::next(horz_bound_fwd);
+ bool is_maxima_edge = is_maxima(horz_bound_fwd, scanline_y);
+ auto bound_max_pair = active_bounds.rend();
+ if (is_maxima_edge) {
+ bound_max_pair =
+ active_bound_list_rev_itr<T>(get_maxima_pair<T>(horz_bound_fwd, active_bounds));
+ --bound_max_pair;
+ }
+ auto hp_itr_fwd = rings.current_hp_itr;
+ while (
+ hp_itr_fwd != rings.hot_pixels.end() &&
+ (hp_itr_fwd->y < scanline_y ||
+ (hp_itr_fwd->y == scanline_y && hp_itr_fwd->x < (*horz_bound_fwd)->current_edge->top.x))) {
+ ++hp_itr_fwd;
+ }
+ auto hp_itr = hot_pixel_rev_itr<T>(hp_itr_fwd);
+
+ auto bnd = active_bound_list_rev_itr<T>(horz_bound_fwd);
+ auto horz_bound = std::prev(bnd);
+ while (bnd != active_bounds.rend()) {
+ if (*bnd == nullptr) {
+ ++bnd;
+ continue;
+ }
+ // this code block inserts extra coords into horizontal edges (in output
+ // polygons) wherever hot pixels touch these horizontal edges.
+ while (hp_itr != rings.hot_pixels.rend() && hp_itr->y == scanline_y &&
+ hp_itr->x > wround<T>((*bnd)->current_x) &&
+ hp_itr->x > (*horz_bound)->current_edge->top.x) {
+ if ((*horz_bound)->ring) {
+ add_point_to_ring(*(*horz_bound), *hp_itr, rings);
+ }
+ ++hp_itr;
+ }
+
+ if ((*bnd)->current_x < static_cast<double>((*horz_bound)->current_edge->top.x)) {
+ break;
+ }
+
+ // Also break if we've got to the end of an intermediate horizontal edge ...
+ // nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
+ if (wround<T>((*bnd)->current_x) == (*horz_bound)->current_edge->top.x &&
+ (*horz_bound)->next_edge != (*horz_bound)->edges.end() &&
+ (*horz_bound)->current_edge->dx < (*horz_bound)->next_edge->dx) {
+ break;
+ }
+
+ // note: may be done multiple times
+ if ((*horz_bound)->ring) {
+ add_point_to_ring(*(*horz_bound),
+ mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
+ rings);
+ }
+
+ // OK, so far we're still in range of the horizontal Edge but make sure
+ // we're at the last of consec. horizontals when matching with eMaxPair
+ if (is_maxima_edge && bnd == bound_max_pair) {
+ if ((*horz_bound)->ring) {
+ add_local_maximum_point(*(*horz_bound), *(*bound_max_pair),
+ (*horz_bound)->current_edge->top, rings, active_bounds);
+ }
+ *bound_max_pair = nullptr;
+ *horz_bound = nullptr;
+ return next_bnd_itr;
+ }
+
+ intersect_bounds(*(*bnd), *(*horz_bound),
+ mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
+ cliptype, subject_fill_type, clip_fill_type, rings, active_bounds);
+ std::iter_swap(horz_bound, bnd);
+ horz_bound = bnd;
+ ++bnd;
+ } // end while (bnd != active_bounds.rend())
+
+ if ((*horz_bound)->ring) {
+ while (hp_itr != rings.hot_pixels.rend() && hp_itr->y == scanline_y &&
+ hp_itr->x > (*horz_bound)->current_edge->top.x) {
+ add_point_to_ring(*(*horz_bound), *hp_itr, rings);
+ ++hp_itr;
+ }
+ }
+ if ((*horz_bound)->ring) {
+ add_point_to_ring(*(*horz_bound), (*horz_bound)->current_edge->top, rings);
+ }
+
+ if ((*horz_bound)->next_edge != (*horz_bound)->edges.end()) {
+ next_edge_in_bound(*(*horz_bound), scanbeam);
+ } else {
+ *horz_bound = nullptr;
+ }
+ return next_bnd_itr;
+}
+
+template <typename T>
+active_bound_list_itr<T> process_horizontal(T scanline_y,
+ active_bound_list_itr<T>& horz_bound,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& rings,
+ scanbeam_list<T>& scanbeam,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+ if ((*horz_bound)->current_edge->bot.x < (*horz_bound)->current_edge->top.x) {
+ return process_horizontal_left_to_right(scanline_y, horz_bound, active_bounds, rings,
+ scanbeam, cliptype, subject_fill_type,
+ clip_fill_type);
+ } else {
+ return process_horizontal_right_to_left(scanline_y, horz_bound, active_bounds, rings,
+ scanbeam, cliptype, subject_fill_type,
+ clip_fill_type);
+ }
+}
+
+template <typename T>
+void process_horizontals(T scanline_y,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& rings,
+ scanbeam_list<T>& scanbeam,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+ for (auto bnd_itr = active_bounds.begin(); bnd_itr != active_bounds.end();) {
+ if (*bnd_itr != nullptr && current_edge_is_horizontal<T>(bnd_itr)) {
+ bnd_itr = process_horizontal(scanline_y, bnd_itr, active_bounds, rings, scanbeam,
+ cliptype, subject_fill_type, clip_fill_type);
+ } else {
+ ++bnd_itr;
+ }
+ }
+ active_bounds.erase(std::remove(active_bounds.begin(), active_bounds.end(), nullptr),
+ active_bounds.end());
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/process_maxima.hpp b/include/mapbox/geometry/wagyu/process_maxima.hpp
new file mode 100644
index 0000000..c133d87
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/process_maxima.hpp
@@ -0,0 +1,127 @@
+#pragma once
+
+#include <mapbox/geometry/wagyu/active_bound_list.hpp>
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/edge.hpp>
+#include <mapbox/geometry/wagyu/intersect_util.hpp>
+#include <mapbox/geometry/wagyu/local_minimum.hpp>
+#include <mapbox/geometry/wagyu/local_minimum_util.hpp>
+#include <mapbox/geometry/wagyu/process_horizontal.hpp>
+#include <mapbox/geometry/wagyu/ring.hpp>
+#include <mapbox/geometry/wagyu/ring_util.hpp>
+#include <mapbox/geometry/wagyu/topology_correction.hpp>
+#include <mapbox/geometry/wagyu/util.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+active_bound_list_itr<T> do_maxima(active_bound_list_itr<T>& bnd,
+ active_bound_list_itr<T>& bndMaxPair,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type,
+ ring_manager<T>& manager,
+ active_bound_list<T>& active_bounds) {
+ auto bnd_next = std::next(bnd);
+ auto return_bnd = bnd;
+ bool skipped = false;
+ while (bnd_next != active_bounds.end() && bnd_next != bndMaxPair) {
+ if (*bnd_next == nullptr) {
+ ++bnd_next;
+ continue;
+ }
+ skipped = true;
+ intersect_bounds(*(*bnd), *(*bnd_next), (*bnd)->current_edge->top, cliptype,
+ subject_fill_type, clip_fill_type, manager, active_bounds);
+ std::iter_swap(bnd, bnd_next);
+ bnd = bnd_next;
+ ++bnd_next;
+ }
+
+ if ((*bnd)->ring && (*bndMaxPair)->ring) {
+ add_local_maximum_point(*(*bnd), *(*bndMaxPair), (*bnd)->current_edge->top, manager,
+ active_bounds);
+ } else if ((*bnd)->ring || (*bndMaxPair)->ring) {
+ throw std::runtime_error("DoMaxima error");
+ }
+ *bndMaxPair = nullptr;
+ *bnd = nullptr;
+ if (!skipped) {
+ ++return_bnd;
+ }
+ return return_bnd;
+}
+
+template <typename T>
+void process_edges_at_top_of_scanbeam(T top_y,
+ active_bound_list<T>& active_bounds,
+ scanbeam_list<T>& scanbeam,
+ local_minimum_ptr_list<T> const& minima_sorted,
+ local_minimum_ptr_list_itr<T>& current_lm,
+ ring_manager<T>& manager,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+
+ for (auto bnd = active_bounds.begin(); bnd != active_bounds.end();) {
+ if (*bnd == nullptr) {
+ ++bnd;
+ continue;
+ }
+ // 1. Process maxima, treating them as if they are "bent" horizontal edges,
+ // but exclude maxima with horizontal edges.
+
+ bool is_maxima_edge = is_maxima(bnd, top_y);
+
+ if (is_maxima_edge) {
+ auto bnd_max_pair = get_maxima_pair(bnd, active_bounds);
+ is_maxima_edge = ((bnd_max_pair == active_bounds.end() ||
+ !current_edge_is_horizontal<T>(bnd_max_pair)) &&
+ is_maxima(bnd_max_pair, top_y));
+ if (is_maxima_edge) {
+ bnd = do_maxima(bnd, bnd_max_pair, cliptype, subject_fill_type, clip_fill_type,
+ manager, active_bounds);
+ continue;
+ }
+ }
+
+ // 2. Promote horizontal edges.
+ if (is_intermediate(bnd, top_y) && next_edge_is_horizontal<T>(bnd)) {
+ if ((*bnd)->ring) {
+ insert_hot_pixels_in_path(*(*bnd), (*bnd)->current_edge->top, manager, false);
+ }
+ next_edge_in_bound(*(*bnd), scanbeam);
+ if ((*bnd)->ring) {
+ add_point_to_ring(*(*bnd), (*bnd)->current_edge->bot, manager);
+ }
+ } else {
+ (*bnd)->current_x = get_current_x(*((*bnd)->current_edge), top_y);
+ }
+ ++bnd;
+ }
+ active_bounds.erase(std::remove(active_bounds.begin(), active_bounds.end(), nullptr),
+ active_bounds.end());
+
+ insert_horizontal_local_minima_into_ABL(top_y, minima_sorted, current_lm, active_bounds,
+ manager, scanbeam, cliptype, subject_fill_type,
+ clip_fill_type);
+
+ process_horizontals(top_y, active_bounds, manager, scanbeam, cliptype, subject_fill_type,
+ clip_fill_type);
+
+ // 4. Promote intermediate vertices
+
+ for (auto bnd = active_bounds.begin(); bnd != active_bounds.end(); ++bnd) {
+ if (is_intermediate(bnd, top_y)) {
+ if ((*bnd)->ring) {
+ add_point_to_ring(*(*bnd), (*bnd)->current_edge->top, manager);
+ }
+ next_edge_in_bound(*(*bnd), scanbeam);
+ }
+ }
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/quick_clip.hpp b/include/mapbox/geometry/wagyu/quick_clip.hpp
new file mode 100644
index 0000000..3ea358b
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/quick_clip.hpp
@@ -0,0 +1,133 @@
+#pragma once
+
+#include <mapbox/geometry/box.hpp>
+#include <mapbox/geometry/multi_polygon.hpp>
+#include <mapbox/geometry/polygon.hpp>
+#include <mapbox/geometry/wagyu/wagyu.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+namespace quick_clip {
+
+template <typename T>
+mapbox::geometry::point<T> intersect(mapbox::geometry::point<T> a,
+ mapbox::geometry::point<T> b,
+ size_t edge,
+ mapbox::geometry::box<T> const& box) {
+ switch (edge) {
+ case 0:
+ return mapbox::geometry::point<T>(
+ mapbox::geometry::wagyu::wround<T>(static_cast<double>(a.x) + static_cast<double>(b.x - a.x) * static_cast<double>(box.min.y - a.y) / static_cast<double>(b.y - a.y)),
+ box.min.y);
+
+ case 1:
+ return mapbox::geometry::point<T>(
+ box.max.x,
+ mapbox::geometry::wagyu::wround<T>(static_cast<double>(a.y) + static_cast<double>(b.y - a.y) * static_cast<double>(box.max.x - a.x) / static_cast<double>(b.x - a.x)));
+
+ case 2:
+ return mapbox::geometry::point<T>(
+ mapbox::geometry::wagyu::wround<T>(static_cast<double>(a.x) + static_cast<double>(b.x - a.x) * static_cast<double>(box.max.y - a.y) / static_cast<double>(b.y - a.y)),
+ box.max.y);
+
+ default: // case 3
+ return mapbox::geometry::point<T>(
+ box.min.x,
+ mapbox::geometry::wagyu::wround<T>(static_cast<double>(a.y) + static_cast<double>(b.y - a.y) * static_cast<double>(box.min.x - a.x) / static_cast<double>(b.x - a.x)));
+ }
+}
+
+template <typename T>
+bool inside(mapbox::geometry::point<T> p, size_t edge, mapbox::geometry::box<T> const& b) {
+ switch (edge) {
+ case 0:
+ return p.y > b.min.y;
+
+ case 1:
+ return p.x < b.max.x;
+
+ case 2:
+ return p.y < b.max.y;
+
+ default: // case 3
+ return p.x > b.min.x;
+ }
+}
+
+template <typename T>
+mapbox::geometry::linear_ring<T> quick_lr_clip(mapbox::geometry::linear_ring<T> const& ring,
+ mapbox::geometry::box<T> const& b) {
+ mapbox::geometry::linear_ring<T> out = ring;
+
+ for (size_t edge = 0; edge < 4; edge++) {
+ if (out.size() > 0) {
+ mapbox::geometry::linear_ring<T> in = out;
+ mapbox::geometry::point<T> S = in[in.size() - 1];
+ out.resize(0);
+
+ for (size_t e = 0; e < in.size(); e++) {
+ mapbox::geometry::point<T> E = in[e];
+
+ if (inside(E, edge, b)) {
+ if (!inside(S, edge, b)) {
+ out.push_back(intersect(S, E, edge, b));
+ }
+ out.push_back(E);
+ } else if (inside(S, edge, b)) {
+ out.push_back(intersect(S, E, edge, b));
+ }
+
+ S = E;
+ }
+ }
+ }
+
+ if (out.size() < 3) {
+ out.clear();
+ return out;
+ }
+ // Close the ring if the first/last point was outside
+ if (out[0] != out[out.size() - 1]) {
+ out.push_back(out[0]);
+ }
+ return out;
+}
+}
+
+template <typename T>
+mapbox::geometry::multi_polygon<T> clip(mapbox::geometry::polygon<T> const& poly,
+ mapbox::geometry::box<T> const& b,
+ fill_type subject_fill_type) {
+ mapbox::geometry::multi_polygon<T> result;
+ wagyu<T> clipper;
+ for (auto const& lr : poly) {
+ auto new_lr = quick_clip::quick_lr_clip(lr, b);
+ if (!new_lr.empty()) {
+ clipper.add_ring(new_lr, polygon_type_subject);
+ }
+ }
+ clipper.execute(clip_type_union, result, subject_fill_type, fill_type_even_odd);
+ return result;
+}
+
+template <typename T>
+mapbox::geometry::multi_polygon<T> clip(mapbox::geometry::multi_polygon<T> const& mp,
+ mapbox::geometry::box<T> const& b,
+ fill_type subject_fill_type) {
+ mapbox::geometry::multi_polygon<T> result;
+ wagyu<T> clipper;
+ for (auto const& poly : mp) {
+ for (auto const& lr : poly) {
+ auto new_lr = quick_clip::quick_lr_clip(lr, b);
+ if (!new_lr.empty()) {
+ clipper.add_ring(new_lr, polygon_type_subject);
+ }
+ }
+ }
+ clipper.execute(clip_type_union, result, subject_fill_type, fill_type_even_odd);
+ return result;
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/ring.hpp b/include/mapbox/geometry/wagyu/ring.hpp
new file mode 100644
index 0000000..978d980
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/ring.hpp
@@ -0,0 +1,645 @@
+#pragma once
+
+#include <assert.h>
+#include <cmath>
+#include <deque>
+#include <list>
+#include <map>
+#include <mapbox/geometry/box.hpp>
+#include <mapbox/geometry/wagyu/point.hpp>
+#include <set>
+#include <sstream>
+#include <vector>
+
+#ifdef DEBUG
+#include <execinfo.h>
+#include <iostream>
+#include <sstream>
+#include <stdio.h>
+//
+// void* callstack[128];
+// int i, frames = backtrace(callstack, 128);
+// char** strs = backtrace_symbols(callstack, frames);
+// for (i = 0; i < frames; ++i) {
+// printf("%s\n", strs[i]);
+// }
+// free(strs);
+#endif
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+double area_from_point(point_ptr<T> op, std::size_t& size, mapbox::geometry::box<T>& bbox) {
+ point_ptr<T> startOp = op;
+ size = 0;
+ double a = 0.0;
+ T min_x = op->x;
+ T max_x = op->x;
+ T min_y = op->y;
+ T max_y = op->y;
+ do {
+ ++size;
+ if (op->x > max_x) {
+ max_x = op->x;
+ } else if (op->x < min_x) {
+ min_x = op->x;
+ }
+ if (op->y > max_y) {
+ max_y = op->y;
+ } else if (op->y < min_y) {
+ min_y = op->y;
+ }
+ a += static_cast<double>(op->prev->x + op->x) * static_cast<double>(op->prev->y - op->y);
+ op = op->next;
+ } while (op != startOp);
+ bbox.min.x = min_x;
+ bbox.max.x = max_x;
+ bbox.min.y = min_y;
+ bbox.max.y = max_y;
+ return a * 0.5;
+}
+
+// NOTE: ring and ring_ptr are forward declared in wagyu/point.hpp
+
+template <typename T>
+using ring_vector = std::vector<ring_ptr<T>>;
+
+template <typename T>
+struct ring {
+ std::size_t ring_index; // To support unset 0 is undefined and indexes offset by 1
+
+ std::size_t size_; // number of points in the ring
+ double area_; // area of the ring
+ mapbox::geometry::box<T> bbox; // bounding box of the ring
+
+ ring_ptr<T> parent;
+ ring_vector<T> children;
+
+ point_ptr<T> points;
+ point_ptr<T> bottom_point;
+ bool is_hole_;
+ bool corrected;
+
+ ring(ring const&) = delete;
+ ring& operator=(ring const&) = delete;
+
+ ring()
+ : ring_index(0),
+ size_(0),
+ area_(std::numeric_limits<double>::quiet_NaN()),
+ bbox({ 0, 0 }, { 0, 0 }),
+ parent(nullptr),
+ children(),
+ points(nullptr),
+ bottom_point(nullptr),
+ is_hole_(false),
+ corrected(false) {
+ }
+
+ void reset_stats() {
+ area_ = std::numeric_limits<double>::quiet_NaN();
+ is_hole_ = false;
+ bbox.min.x = 0;
+ bbox.min.y = 0;
+ bbox.max.x = 0;
+ bbox.max.y = 0;
+ size_ = 0;
+ }
+
+ void recalculate_stats() {
+ if (points != nullptr) {
+ area_ = area_from_point(points, size_, bbox);
+ is_hole_ = !(area_ > 0.0);
+ }
+ }
+
+ void set_stats(double a, std::size_t s, mapbox::geometry::box<T> const& b) {
+ bbox = b;
+ area_ = a;
+ size_ = s;
+ is_hole_ = !(area_ > 0.0);
+ }
+
+ double area() {
+ if (std::isnan(area_)) {
+ recalculate_stats();
+ }
+ return area_;
+ }
+
+ bool is_hole() {
+ if (std::isnan(area_)) {
+ recalculate_stats();
+ }
+ return is_hole_;
+ }
+
+ std::size_t size() {
+ if (std::isnan(area_)) {
+ recalculate_stats();
+ }
+ return size_;
+ }
+};
+
+template <typename T>
+using hot_pixel_vector = std::vector<mapbox::geometry::point<T>>;
+
+template <typename T>
+using hot_pixel_itr = typename hot_pixel_vector<T>::iterator;
+
+template <typename T>
+using hot_pixel_rev_itr = typename hot_pixel_vector<T>::reverse_iterator;
+
+template <typename T>
+struct ring_manager {
+
+ ring_vector<T> children;
+ point_vector<T> all_points;
+ hot_pixel_vector<T> hot_pixels;
+ hot_pixel_itr<T> current_hp_itr;
+ std::deque<point<T>> points;
+ std::deque<ring<T>> rings;
+ std::vector<point<T>> storage;
+ std::size_t index;
+
+ ring_manager(ring_manager const&) = delete;
+ ring_manager& operator=(ring_manager const&) = delete;
+
+ ring_manager()
+ : children(),
+ all_points(),
+ hot_pixels(),
+ current_hp_itr(hot_pixels.end()),
+ points(),
+ rings(),
+ storage(),
+ index(0) {
+ }
+};
+
+template <typename T>
+void preallocate_point_memory(ring_manager<T>& rings, std::size_t size) {
+ rings.storage.reserve(size);
+ rings.all_points.reserve(size);
+}
+
+template <typename T>
+ring_ptr<T> create_new_ring(ring_manager<T>& manager) {
+ manager.rings.emplace_back();
+ ring_ptr<T> result = &manager.rings.back();
+ result->ring_index = manager.index++;
+ return result;
+}
+
+template <typename T>
+point_ptr<T>
+create_new_point(ring_ptr<T> r, mapbox::geometry::point<T> const& pt, ring_manager<T>& rings) {
+ point_ptr<T> point;
+ if (rings.storage.size() < rings.storage.capacity()) {
+ rings.storage.emplace_back(r, pt);
+ point = &rings.storage.back();
+ } else {
+ rings.points.emplace_back(r, pt);
+ point = &rings.points.back();
+ }
+ rings.all_points.push_back(point);
+ return point;
+}
+
+template <typename T>
+point_ptr<T> create_new_point(ring_ptr<T> r,
+ mapbox::geometry::point<T> const& pt,
+ point_ptr<T> before_this_point,
+ ring_manager<T>& rings) {
+ point_ptr<T> point;
+ if (rings.storage.size() < rings.storage.capacity()) {
+ rings.storage.emplace_back(r, pt, before_this_point);
+ point = &rings.storage.back();
+ } else {
+ rings.points.emplace_back(r, pt, before_this_point);
+ point = &rings.points.back();
+ }
+ rings.all_points.push_back(point);
+ return point;
+}
+
+template <typename T>
+void set_to_children(ring_ptr<T> r, ring_vector<T>& children) {
+ for (auto& c : children) {
+ if (c == nullptr) {
+ c = r;
+ return;
+ }
+ }
+ children.push_back(r);
+}
+
+template <typename T>
+void remove_from_children(ring_ptr<T> r, ring_vector<T>& children) {
+ for (auto& c : children) {
+ if (c == r) {
+ c = nullptr;
+ return;
+ }
+ }
+}
+
+template <typename T>
+void assign_as_child(ring_ptr<T> new_ring, ring_ptr<T> parent, ring_manager<T>& manager) {
+ // Assigning as a child assumes that this is
+ // a brand new ring. Therefore it does
+ // not have any existing relationships
+ if ((parent == nullptr && new_ring->is_hole()) ||
+ (parent != nullptr && new_ring->is_hole() == parent->is_hole())) {
+ throw std::runtime_error(
+ "Trying to assign a child that is the same orientation as the parent");
+ }
+ auto& children = parent == nullptr ? manager.children : parent->children;
+ set_to_children(new_ring, children);
+ new_ring->parent = parent;
+}
+
+template <typename T>
+void reassign_as_child(ring_ptr<T> ring, ring_ptr<T> parent, ring_manager<T>& manager) {
+ // Reassigning a ring assumes it already
+ // has an existing parent
+ if ((parent == nullptr && ring->is_hole()) ||
+ (parent != nullptr && ring->is_hole() == parent->is_hole())) {
+ throw std::runtime_error(
+ "Trying to re-assign a child that is the same orientation as the parent");
+ }
+
+ // Remove the old child relationship
+ auto& old_children = ring->parent == nullptr ? manager.children : ring->parent->children;
+ remove_from_children(ring, old_children);
+
+ // Add new child relationship
+ auto& children = parent == nullptr ? manager.children : parent->children;
+ set_to_children(ring, children);
+ ring->parent = parent;
+}
+
+template <typename T>
+void assign_as_sibling(ring_ptr<T> new_ring, ring_ptr<T> sibling, ring_manager<T>& manager) {
+ // Assigning as a sibling assumes that this is
+ // a brand new ring. Therefore it does
+ // not have any existing relationships
+ if (new_ring->is_hole() != sibling->is_hole()) {
+ throw std::runtime_error(
+ "Trying to assign to be a sibling that is not the same orientation as the sibling");
+ }
+ auto& children = sibling->parent == nullptr ? manager.children : sibling->parent->children;
+ set_to_children(new_ring, children);
+ new_ring->parent = sibling->parent;
+}
+
+template <typename T>
+void reassign_as_sibling(ring_ptr<T> ring, ring_ptr<T> sibling, ring_manager<T>& manager) {
+ if (ring->parent == sibling->parent) {
+ return;
+ }
+ // Assigning as a sibling assumes that this is
+ // a brand new ring. Therefore it does
+ // not have any existing relationships
+ if (ring->is_hole() != sibling->is_hole()) {
+ throw std::runtime_error(
+ "Trying to assign to be a sibling that is not the same orientation as the sibling");
+ }
+ // Remove the old child relationship
+ auto& old_children = ring->parent == nullptr ? manager.children : ring->parent->children;
+ remove_from_children(ring, old_children);
+ // Add new relationship
+ auto& children = sibling->parent == nullptr ? manager.children : sibling->parent->children;
+ set_to_children(ring, children);
+ ring->parent = sibling->parent;
+}
+
+template <typename T>
+void ring1_replaces_ring2(ring_ptr<T> ring1, ring_ptr<T> ring2, ring_manager<T>& manager) {
+ assert(ring1 != ring2);
+ auto& ring1_children = ring1 == nullptr ? manager.children : ring1->children;
+ for (auto& c : ring2->children) {
+ if (c == nullptr) {
+ continue;
+ }
+ c->parent = ring1;
+ set_to_children(c, ring1_children);
+ c = nullptr;
+ }
+ // Remove the old child relationship
+ auto& old_children = ring2->parent == nullptr ? manager.children : ring2->parent->children;
+ remove_from_children(ring2, old_children);
+ ring2->points = nullptr;
+ ring2->reset_stats();
+}
+
+template <typename T>
+void remove_points(point_ptr<T> pt) {
+ if (pt != nullptr) {
+ pt->prev->next = nullptr;
+ while (pt != nullptr) {
+ point_ptr<T> tmp = pt;
+ pt = pt->next;
+ tmp->next = nullptr;
+ tmp->prev = nullptr;
+ tmp->ring = nullptr;
+ }
+ }
+}
+
+template <typename T>
+void remove_ring_and_points(ring_ptr<T> r,
+ ring_manager<T>& manager,
+ bool remove_children = true,
+ bool remove_from_parent = true) {
+ // Removes a ring and any children that might be
+ // under that ring.
+ for (auto& c : r->children) {
+ if (c == nullptr) {
+ continue;
+ }
+ if (remove_children) {
+ remove_ring_and_points(c, manager, true, false);
+ }
+ c = nullptr;
+ }
+ if (remove_from_parent) {
+ // Remove the old child relationship
+ auto& old_children = r->parent == nullptr ? manager.children : r->parent->children;
+ remove_from_children(r, old_children);
+ }
+ point_ptr<T> pt = r->points;
+ if (pt != nullptr) {
+ pt->prev->next = nullptr;
+ while (pt != nullptr) {
+ point_ptr<T> tmp = pt;
+ pt = pt->next;
+ tmp->next = nullptr;
+ tmp->prev = nullptr;
+ tmp->ring = nullptr;
+ }
+ }
+ r->points = nullptr;
+ r->reset_stats();
+}
+
+template <typename T>
+void remove_ring(ring_ptr<T> r,
+ ring_manager<T>& manager,
+ bool remove_children = true,
+ bool remove_from_parent = true) {
+ // Removes a ring and any children that might be
+ // under that ring.
+ for (auto& c : r->children) {
+ if (c == nullptr) {
+ continue;
+ }
+ if (remove_children) {
+ remove_ring(c, manager, true, false);
+ }
+ c = nullptr;
+ }
+ if (remove_from_parent) {
+ // Remove the old child relationship
+ auto& old_children = r->parent == nullptr ? manager.children : r->parent->children;
+ remove_from_children(r, old_children);
+ }
+ r->points = nullptr;
+ r->reset_stats();
+}
+
+template <typename T>
+inline std::size_t ring_depth(ring_ptr<T> r) {
+ std::size_t depth = 0;
+ if (!r) {
+ return depth;
+ }
+ while (r->parent) {
+ depth++;
+ r = r->parent;
+ }
+ return depth;
+}
+
+template <typename T>
+inline bool ring_is_hole(ring_ptr<T> r) {
+ // This is different then the "normal" way of determing if
+ // a ring is a hole or not because it uses the depth of the
+ // the ring to determine if it is a hole or not. This is only done
+ // intially when rings are output from Vatti.
+ return ring_depth(r) & 1;
+}
+
+template <typename T>
+void set_next(const_point_ptr<T>& node, const const_point_ptr<T>& next_node) {
+ node->next = next_node;
+}
+
+template <typename T>
+point_ptr<T> get_next(const_point_ptr<T>& node) {
+ return node->next;
+}
+
+template <typename T>
+point_ptr<T> get_prev(const_point_ptr<T>& node) {
+ return node->prev;
+}
+
+template <typename T>
+void set_prev(const_point_ptr<T>& node, const const_point_ptr<T>& prev_node) {
+ node->prev = prev_node;
+}
+
+template <typename T>
+void init(const_point_ptr<T>& node) {
+ set_next(node, node);
+ set_prev(node, node);
+}
+
+template <typename T>
+void link_before(point_ptr<T>& node, point_ptr<T>& new_node) {
+ point_ptr<T> prev_node = get_prev(node);
+ set_prev(new_node, prev_node);
+ set_next(new_node, node);
+ set_prev(node, new_node);
+ set_next(prev_node, new_node);
+}
+
+template <typename T>
+void link_after(point_ptr<T>& node, point_ptr<T>& new_node) {
+ point_ptr<T> next_node = get_next(node);
+ set_prev(new_node, node);
+ set_next(new_node, next_node);
+ set_next(node, new_node);
+ set_prev(next_node, new_node);
+}
+
+template <typename T>
+void transfer_point(point_ptr<T>& p, point_ptr<T>& b, point_ptr<T>& e) {
+ if (b != e) {
+ point_ptr<T> prev_p = get_prev(p);
+ point_ptr<T> prev_b = get_prev(b);
+ point_ptr<T> prev_e = get_prev(e);
+ set_next(prev_e, p);
+ set_prev(p, prev_e);
+ set_next(prev_b, e);
+ set_prev(e, prev_b);
+ set_next(prev_p, b);
+ set_prev(b, prev_p);
+ } else {
+ link_before(p, b);
+ }
+}
+
+template <typename T>
+void reverse_ring(point_ptr<T> pp) {
+ if (!pp) {
+ return;
+ }
+ point_ptr<T> pp1;
+ point_ptr<T> pp2;
+ pp1 = pp;
+ do {
+ pp2 = pp1->next;
+ pp1->next = pp1->prev;
+ pp1->prev = pp2;
+ pp1 = pp2;
+ } while (pp1 != pp);
+}
+
+#ifdef DEBUG
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ ring<T>& r) {
+ out << " ring_index: " << r.ring_index << std::endl;
+ if (!r.parent) {
+ // out << " parent_ring ptr: nullptr" << std::endl;
+ out << " parent_index: -----" << std::endl;
+ } else {
+ // out << " parent_ring ptr: " << r.parent << std::endl;
+ out << " parent_ring idx: " << r.parent->ring_index << std::endl;
+ }
+ ring_ptr<T> n = const_cast<ring_ptr<T>>(&r);
+ if (ring_is_hole(n)) {
+ out << " is_hole: true " << std::endl;
+ } else {
+ out << " is_hole: false " << std::endl;
+ }
+ auto pt_itr = r.points;
+ if (pt_itr) {
+ out << " area: " << r.area() << std::endl;
+ out << " points:" << std::endl;
+ out << " [[[" << pt_itr->x << "," << pt_itr->y << "],";
+ pt_itr = pt_itr->next;
+ while (pt_itr != r.points) {
+ out << "[" << pt_itr->x << "," << pt_itr->y << "],";
+ pt_itr = pt_itr->next;
+ }
+ out << "[" << pt_itr->x << "," << pt_itr->y << "]]]" << std::endl;
+ } else {
+ out << " area: NONE" << std::endl;
+ out << " points: NONE" << std::endl;
+ }
+ return out;
+}
+
+template <typename T>
+std::string debug_ring_addresses(ring_ptr<T> r) {
+ std::ostringstream out;
+ out << "Ring: " << r->ring_index << std::endl;
+ if (r->points == nullptr) {
+ out << " Ring has no points" << std::endl;
+ return out.str();
+ }
+ auto pt_itr = r->points;
+ do {
+ out << " [" << pt_itr->x << "," << pt_itr->y << "] - " << pt_itr << std::endl;
+ pt_itr = pt_itr->next;
+ } while (pt_itr != r->points);
+ return out.str();
+}
+
+template <typename T>
+std::string output_as_polygon(ring_ptr<T> r) {
+ std::ostringstream out;
+
+ auto pt_itr = r->points;
+ if (pt_itr) {
+ out << "[";
+ out << "[[" << pt_itr->x << "," << pt_itr->y << "],";
+ pt_itr = pt_itr->next;
+ while (pt_itr != r->points) {
+ out << "[" << pt_itr->x << "," << pt_itr->y << "],";
+ pt_itr = pt_itr->next;
+ }
+ out << "[" << pt_itr->x << "," << pt_itr->y << "]]";
+ for (auto const& c : r->children) {
+ if (c == nullptr) {
+ continue;
+ }
+ pt_itr = c->points;
+ if (pt_itr) {
+ out << ",[[" << pt_itr->x << "," << pt_itr->y << "],";
+ pt_itr = pt_itr->next;
+ while (pt_itr != c->points) {
+ out << "[" << pt_itr->x << "," << pt_itr->y << "],";
+ pt_itr = pt_itr->next;
+ }
+ out << "[" << pt_itr->x << "," << pt_itr->y << "]]";
+ }
+ }
+ out << "]" << std::endl;
+ } else {
+ out << "[]" << std::endl;
+ }
+
+ return out.str();
+}
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ ring_vector<T>& rings) {
+ out << "START RING VECTOR" << std::endl;
+ for (auto& r : rings) {
+ if (r == nullptr || !r->points) {
+ continue;
+ }
+ out << " ring: " << r->ring_index << " - " << r << std::endl;
+ out << *r;
+ }
+ out << "END RING VECTOR" << std::endl;
+ return out;
+}
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ std::deque<ring<T>>& rings) {
+ out << "START RING VECTOR" << std::endl;
+ for (auto& r : rings) {
+ if (!r.points) {
+ continue;
+ }
+ out << " ring: " << r.ring_index << std::endl;
+ out << r;
+ }
+ out << "END RING VECTOR" << std::endl;
+ return out;
+}
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ hot_pixel_vector<T>& hp_vec) {
+ out << "Hot Pixels: " << std::endl;
+ for (auto& hp : hp_vec) {
+ out << hp << std::endl;
+ }
+ return out;
+}
+#endif
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/ring_util.hpp b/include/mapbox/geometry/wagyu/ring_util.hpp
new file mode 100644
index 0000000..c499cb5
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/ring_util.hpp
@@ -0,0 +1,852 @@
+#pragma once
+
+#ifdef DEBUG
+#include <iostream>
+// Example debug print for backtrace - only works on IOS
+#include <execinfo.h>
+#include <stdio.h>
+//
+// void* callstack[128];
+// int i, frames = backtrace(callstack, 128);
+// char** strs = backtrace_symbols(callstack, frames);
+// for (i = 0; i < frames; ++i) {
+// printf("%s\n", strs[i]);
+// }
+// free(strs);
+#endif
+
+#include <algorithm>
+
+#include <mapbox/geometry/wagyu/active_bound_list.hpp>
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/edge.hpp>
+#include <mapbox/geometry/wagyu/ring.hpp>
+#include <mapbox/geometry/wagyu/util.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+void set_hole_state(bound<T>& bnd,
+ active_bound_list<T> const& active_bounds,
+ ring_manager<T>& rings) {
+ auto bnd_itr = std::find(active_bounds.rbegin(), active_bounds.rend(), &bnd);
+ ++bnd_itr;
+ bound_ptr<T> bndTmp = nullptr;
+ // Find first non line ring to the left of current bound.
+ while (bnd_itr != active_bounds.rend()) {
+ if (*bnd_itr == nullptr) {
+ ++bnd_itr;
+ continue;
+ }
+ if ((*bnd_itr)->ring) {
+ if (!bndTmp) {
+ bndTmp = (*bnd_itr);
+ } else if (bndTmp->ring == (*bnd_itr)->ring) {
+ bndTmp = nullptr;
+ }
+ }
+ ++bnd_itr;
+ }
+ if (!bndTmp) {
+ bnd.ring->parent = nullptr;
+ rings.children.push_back(bnd.ring);
+ } else {
+ bnd.ring->parent = bndTmp->ring;
+ bndTmp->ring->children.push_back(bnd.ring);
+ }
+}
+
+template <typename T>
+void update_current_hp_itr(T scanline_y, ring_manager<T>& rings) {
+ while (rings.current_hp_itr->y > scanline_y) {
+ ++rings.current_hp_itr;
+ }
+}
+
+template <typename T>
+struct hot_pixel_sorter {
+ inline bool operator()(mapbox::geometry::point<T> const& pt1,
+ mapbox::geometry::point<T> const& pt2) {
+ if (pt1.y == pt2.y) {
+ return pt1.x < pt2.x;
+ } else {
+ return pt1.y > pt2.y;
+ }
+ }
+};
+
+// Due to the nature of floating point calculations
+// and the high likely hood of values around X.5, we
+// need to fudge what is X.5 some for our rounding.
+const double rounding_offset = 1e-12;
+const double rounding_offset_y = 5e-13;
+
+template <typename T>
+T round_towards_min(double val) {
+ // 0.5 rounds to 0
+ // 0.0 rounds to 0
+ // -0.5 rounds to -1
+ return static_cast<T>(std::ceil(val - 0.5 + rounding_offset));
+}
+
+template <typename T>
+T round_towards_max(double val) {
+ // 0.5 rounds to 1
+ // 0.0 rounds to 0
+ // -0.5 rounds to 0
+ return static_cast<T>(std::floor(val + 0.5 + rounding_offset));
+}
+
+template <typename T>
+inline T get_edge_min_x(edge<T> const& edge, const T current_y) {
+ if (is_horizontal(edge)) {
+ if (edge.bot.x < edge.top.x) {
+ return edge.bot.x;
+ } else {
+ return edge.top.x;
+ }
+ } else if (edge.dx > 0.0) {
+ if (current_y == edge.top.y) {
+ return edge.top.x;
+ } else {
+ double lower_range_y = static_cast<double>(current_y - edge.bot.y) - 0.5;
+ double return_val = static_cast<double>(edge.bot.x) + edge.dx * lower_range_y;
+ T value = round_towards_min<T>(return_val);
+ return value;
+ }
+ } else {
+ if (current_y == edge.bot.y) {
+ return edge.bot.x;
+ } else {
+ double return_val =
+ static_cast<double>(edge.bot.x) +
+ edge.dx * (static_cast<double>(current_y - edge.bot.y) + 0.5 - rounding_offset_y);
+ T value = round_towards_min<T>(return_val);
+ return value;
+ }
+ }
+}
+
+template <typename T>
+inline T get_edge_max_x(edge<T> const& edge, const T current_y) {
+ if (is_horizontal(edge)) {
+ if (edge.bot.x > edge.top.x) {
+ return edge.bot.x;
+ } else {
+ return edge.top.x;
+ }
+ } else if (edge.dx < 0.0) {
+ if (current_y == edge.top.y) {
+ return edge.top.x;
+ } else {
+ double lower_range_y = static_cast<double>(current_y - edge.bot.y) - 0.5;
+ double return_val = static_cast<double>(edge.bot.x) + edge.dx * lower_range_y;
+ T value = round_towards_max<T>(return_val);
+ return value;
+ }
+ } else {
+ if (current_y == edge.bot.y) {
+ return edge.bot.x;
+ } else {
+ double return_val =
+ static_cast<double>(edge.bot.x) +
+ edge.dx * (static_cast<double>(current_y - edge.bot.y) + 0.5 - rounding_offset_y);
+ T value = round_towards_max<T>(return_val);
+ return value;
+ }
+ }
+}
+
+template <typename T>
+void hot_pixel_set_left_to_right(T y,
+ T start_x,
+ T end_x,
+ bound<T>& bnd,
+ ring_manager<T>& rings,
+ hot_pixel_itr<T>& itr,
+ hot_pixel_itr<T>& end,
+ bool add_end_point) {
+ T x_min = get_edge_min_x(*(bnd.current_edge), y);
+ x_min = std::max(x_min, start_x);
+ T x_max = get_edge_max_x(*(bnd.current_edge), y);
+ x_max = std::min(x_max, end_x);
+ for (; itr != end; ++itr) {
+ if (itr->x < x_min) {
+ continue;
+ }
+ if (itr->x > x_max) {
+ break;
+ }
+ if (!add_end_point && itr->x == end_x) {
+ continue;
+ }
+ point_ptr<T> op = bnd.ring->points;
+ bool to_front = (bnd.side == edge_left);
+ if (to_front && (*itr == *op)) {
+ continue;
+ } else if (!to_front && (*itr == *op->prev)) {
+ continue;
+ }
+ point_ptr<T> new_point = create_new_point(bnd.ring, *itr, op, rings);
+ if (to_front) {
+ bnd.ring->points = new_point;
+ }
+ }
+}
+
+template <typename T>
+void hot_pixel_set_right_to_left(T y,
+ T start_x,
+ T end_x,
+ bound<T>& bnd,
+ ring_manager<T>& rings,
+ hot_pixel_rev_itr<T>& itr,
+ hot_pixel_rev_itr<T>& end,
+ bool add_end_point) {
+ T x_min = get_edge_min_x(*(bnd.current_edge), y);
+ x_min = std::max(x_min, end_x);
+ T x_max = get_edge_max_x(*(bnd.current_edge), y);
+ x_max = std::min(x_max, start_x);
+ for (; itr != end; ++itr) {
+ if (itr->x > x_max) {
+ continue;
+ }
+ if (itr->x < x_min) {
+ break;
+ }
+ if (!add_end_point && itr->x == end_x) {
+ continue;
+ }
+ point_ptr<T> op = bnd.ring->points;
+ bool to_front = (bnd.side == edge_left);
+ if (to_front && (*itr == *op)) {
+ continue;
+ } else if (!to_front && (*itr == *op->prev)) {
+ continue;
+ }
+ point_ptr<T> new_point = create_new_point(bnd.ring, *itr, op, rings);
+ if (to_front) {
+ bnd.ring->points = new_point;
+ }
+ }
+}
+
+template <typename T>
+void sort_hot_pixels(ring_manager<T>& rings) {
+ std::sort(rings.hot_pixels.begin(), rings.hot_pixels.end(), hot_pixel_sorter<T>());
+ auto last = std::unique(rings.hot_pixels.begin(), rings.hot_pixels.end());
+ rings.hot_pixels.erase(last, rings.hot_pixels.end());
+}
+
+template <typename T>
+void insert_hot_pixels_in_path(bound<T>& bnd,
+ mapbox::geometry::point<T> const& end_pt,
+ ring_manager<T>& rings,
+ bool add_end_point) {
+ if (end_pt == bnd.last_point) {
+ return;
+ }
+
+ T start_y = bnd.last_point.y;
+ T start_x = bnd.last_point.x;
+ T end_y = end_pt.y;
+ T end_x = end_pt.x;
+
+ auto itr = rings.current_hp_itr;
+ while (itr->y <= start_y && itr != rings.hot_pixels.begin()) {
+ --itr;
+ }
+ if (start_x > end_x) {
+ for (; itr != rings.hot_pixels.end();) {
+ if (itr->y > start_y) {
+ ++itr;
+ continue;
+ }
+ if (itr->y < end_y) {
+ break;
+ }
+ T y = itr->y;
+ auto last_itr = hot_pixel_rev_itr<T>(itr);
+ while (itr != rings.hot_pixels.end() && itr->y == y) {
+ ++itr;
+ }
+ auto first_itr = hot_pixel_rev_itr<T>(itr);
+ bool add_end_point_itr = (y != end_pt.y || add_end_point);
+ hot_pixel_set_right_to_left(y, start_x, end_x, bnd, rings, first_itr, last_itr,
+ add_end_point_itr);
+ }
+ } else {
+ for (; itr != rings.hot_pixels.end();) {
+ if (itr->y > start_y) {
+ ++itr;
+ continue;
+ }
+ if (itr->y < end_y) {
+ break;
+ }
+ T y = itr->y;
+ auto first_itr = itr;
+ while (itr != rings.hot_pixels.end() && itr->y == y) {
+ ++itr;
+ }
+ auto last_itr = itr;
+ bool add_end_point_itr = (y != end_pt.y || add_end_point);
+ hot_pixel_set_left_to_right(y, start_x, end_x, bnd, rings, first_itr, last_itr,
+ add_end_point_itr);
+ }
+ }
+ bnd.last_point = end_pt;
+}
+
+template <typename T>
+void add_to_hot_pixels(mapbox::geometry::point<T> const& pt, ring_manager<T>& rings) {
+ rings.hot_pixels.push_back(pt);
+}
+
+template <typename T>
+void add_first_point(bound<T>& bnd,
+ active_bound_list<T>& active_bounds,
+ mapbox::geometry::point<T> const& pt,
+ ring_manager<T>& rings) {
+
+ ring_ptr<T> r = create_new_ring(rings);
+ bnd.ring = r;
+ r->points = create_new_point(r, pt, rings);
+ set_hole_state(bnd, active_bounds, rings);
+ bnd.last_point = pt;
+}
+
+template <typename T>
+void add_point_to_ring(bound<T>& bnd,
+ mapbox::geometry::point<T> const& pt,
+ ring_manager<T>& rings) {
+ assert(bnd.ring);
+ // Handle hot pixels
+ insert_hot_pixels_in_path(bnd, pt, rings, false);
+
+ // bnd.ring->points is the 'Left-most' point & bnd.ring->points->prev is the
+ // 'Right-most'
+ point_ptr<T> op = bnd.ring->points;
+ bool to_front = (bnd.side == edge_left);
+ if (to_front && (pt == *op)) {
+ return;
+ } else if (!to_front && (pt == *op->prev)) {
+ return;
+ }
+ point_ptr<T> new_point = create_new_point(bnd.ring, pt, bnd.ring->points, rings);
+ if (to_front) {
+ bnd.ring->points = new_point;
+ }
+}
+
+template <typename T>
+void add_point(bound<T>& bnd,
+ active_bound_list<T>& active_bounds,
+ mapbox::geometry::point<T> const& pt,
+ ring_manager<T>& rings) {
+ if (bnd.ring == nullptr) {
+ add_first_point(bnd, active_bounds, pt, rings);
+ } else {
+ add_point_to_ring(bnd, pt, rings);
+ }
+}
+
+template <typename T>
+void add_local_minimum_point(bound<T>& b1,
+ bound<T>& b2,
+ active_bound_list<T>& active_bounds,
+ mapbox::geometry::point<T> const& pt,
+ ring_manager<T>& rings) {
+ if (is_horizontal(*b2.current_edge) || (b1.current_edge->dx > b2.current_edge->dx)) {
+ add_point(b1, active_bounds, pt, rings);
+ b2.last_point = pt;
+ b2.ring = b1.ring;
+ b1.side = edge_left;
+ b2.side = edge_right;
+ } else {
+ add_point(b2, active_bounds, pt, rings);
+ b1.last_point = pt;
+ b1.ring = b2.ring;
+ b1.side = edge_right;
+ b2.side = edge_left;
+ }
+}
+
+template <typename T>
+inline double get_dx(point<T> const& pt1, point<T> const& pt2) {
+ if (pt1.y == pt2.y) {
+ return std::numeric_limits<double>::infinity();
+ } else {
+ return static_cast<double>(pt2.x - pt1.x) / static_cast<double>(pt2.y - pt1.y);
+ }
+}
+
+template <typename T>
+bool first_is_bottom_point(const_point_ptr<T> btmPt1, const_point_ptr<T> btmPt2) {
+ point_ptr<T> p = btmPt1->prev;
+ while ((*p == *btmPt1) && (p != btmPt1)) {
+ p = p->prev;
+ }
+ double dx1p = std::fabs(get_dx(*btmPt1, *p));
+
+ p = btmPt1->next;
+ while ((*p == *btmPt1) && (p != btmPt1)) {
+ p = p->next;
+ }
+ double dx1n = std::fabs(get_dx(*btmPt1, *p));
+
+ p = btmPt2->prev;
+ while ((*p == *btmPt2) && (p != btmPt2)) {
+ p = p->prev;
+ }
+ double dx2p = std::fabs(get_dx(*btmPt2, *p));
+
+ p = btmPt2->next;
+ while ((*p == *btmPt2) && (p != btmPt2)) {
+ p = p->next;
+ }
+ double dx2n = std::fabs(get_dx(*btmPt2, *p));
+
+ if (values_are_equal(std::max(dx1p, dx1n), std::max(dx2p, dx2n)) &&
+ values_are_equal(std::min(dx1p, dx1n), std::min(dx2p, dx2n))) {
+ std::size_t s = 0;
+ mapbox::geometry::box<T> bbox({ 0, 0 }, { 0, 0 });
+ return area_from_point(btmPt1, s, bbox) > 0.0; // if otherwise identical use orientation
+ } else {
+ return (greater_than_or_equal(dx1p, dx2p) && greater_than_or_equal(dx1p, dx2n)) ||
+ (greater_than_or_equal(dx1n, dx2p) && greater_than_or_equal(dx1n, dx2n));
+ }
+}
+
+template <typename T>
+point_ptr<T> get_bottom_point(point_ptr<T> pp) {
+ point_ptr<T> dups = nullptr;
+ point_ptr<T> p = pp->next;
+ while (p != pp) {
+ if (p->y > pp->y) {
+ pp = p;
+ dups = nullptr;
+ } else if (p->y == pp->y && p->x <= pp->x) {
+ if (p->x < pp->x) {
+ dups = nullptr;
+ pp = p;
+ } else {
+ if (p->next != pp && p->prev != pp) {
+ dups = p;
+ }
+ }
+ }
+ p = p->next;
+ }
+ if (dups) {
+ // there appears to be at least 2 vertices at bottom_point so ...
+ while (dups != p) {
+ if (!first_is_bottom_point(p, dups)) {
+ pp = dups;
+ }
+ dups = dups->next;
+ while (*dups != *pp) {
+ dups = dups->next;
+ }
+ }
+ }
+ return pp;
+}
+
+template <typename T>
+ring_ptr<T> get_lower_most_ring(ring_ptr<T> outRec1, ring_ptr<T> outRec2) {
+ // work out which polygon fragment has the correct hole state ...
+ if (!outRec1->bottom_point) {
+ outRec1->bottom_point = get_bottom_point(outRec1->points);
+ }
+ if (!outRec2->bottom_point) {
+ outRec2->bottom_point = get_bottom_point(outRec2->points);
+ }
+ point_ptr<T> OutPt1 = outRec1->bottom_point;
+ point_ptr<T> OutPt2 = outRec2->bottom_point;
+ if (OutPt1->y > OutPt2->y) {
+ return outRec1;
+ } else if (OutPt1->y < OutPt2->y) {
+ return outRec2;
+ } else if (OutPt1->x < OutPt2->x) {
+ return outRec1;
+ } else if (OutPt1->x > OutPt2->x) {
+ return outRec2;
+ } else if (OutPt1->next == OutPt1) {
+ return outRec2;
+ } else if (OutPt2->next == OutPt2) {
+ return outRec1;
+ } else if (first_is_bottom_point(OutPt1, OutPt2)) {
+ return outRec1;
+ } else {
+ return outRec2;
+ }
+}
+
+template <typename T>
+bool ring1_child_below_ring2(ring_ptr<T> ring1, ring_ptr<T> ring2) {
+ do {
+ ring1 = ring1->parent;
+ if (ring1 == ring2) {
+ return true;
+ }
+ } while (ring1);
+ return false;
+}
+
+template <typename T>
+void update_points_ring(ring_ptr<T> ring) {
+ point_ptr<T> op = ring->points;
+ do {
+ op->ring = ring;
+ op = op->prev;
+ } while (op != ring->points);
+}
+
+template <typename T>
+void append_ring(bound<T>& b1,
+ bound<T>& b2,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& manager) {
+ // get the start and ends of both output polygons ...
+ ring_ptr<T> outRec1 = b1.ring;
+ ring_ptr<T> outRec2 = b2.ring;
+
+ ring_ptr<T> keep_ring;
+ bound_ptr<T> keep_bound;
+ ring_ptr<T> remove_ring;
+ bound_ptr<T> remove_bound;
+ if (ring1_child_below_ring2(outRec1, outRec2)) {
+ keep_ring = outRec2;
+ keep_bound = &b2;
+ remove_ring = outRec1;
+ remove_bound = &b1;
+ } else if (ring1_child_below_ring2(outRec2, outRec1)) {
+ keep_ring = outRec1;
+ keep_bound = &b1;
+ remove_ring = outRec2;
+ remove_bound = &b2;
+ } else if (outRec1 == get_lower_most_ring(outRec1, outRec2)) {
+ keep_ring = outRec1;
+ keep_bound = &b1;
+ remove_ring = outRec2;
+ remove_bound = &b2;
+ } else {
+ keep_ring = outRec2;
+ keep_bound = &b2;
+ remove_ring = outRec1;
+ remove_bound = &b1;
+ }
+
+ // get the start and ends of both output polygons and
+ // join b2 poly onto b1 poly and delete pointers to b2 ...
+
+ point_ptr<T> p1_lft = keep_ring->points;
+ point_ptr<T> p1_rt = p1_lft->prev;
+ point_ptr<T> p2_lft = remove_ring->points;
+ point_ptr<T> p2_rt = p2_lft->prev;
+
+ // join b2 poly onto b1 poly and delete pointers to b2 ...
+ if (keep_bound->side == edge_left) {
+ if (remove_bound->side == edge_left) {
+ // z y x a b c
+ reverse_ring(p2_lft);
+ p2_lft->next = p1_lft;
+ p1_lft->prev = p2_lft;
+ p1_rt->next = p2_rt;
+ p2_rt->prev = p1_rt;
+ keep_ring->points = p2_rt;
+ } else {
+ // x y z a b c
+ p2_rt->next = p1_lft;
+ p1_lft->prev = p2_rt;
+ p2_lft->prev = p1_rt;
+ p1_rt->next = p2_lft;
+ keep_ring->points = p2_lft;
+ }
+ } else {
+ if (remove_bound->side == edge_right) {
+ // a b c z y x
+ reverse_ring(p2_lft);
+ p1_rt->next = p2_rt;
+ p2_rt->prev = p1_rt;
+ p2_lft->next = p1_lft;
+ p1_lft->prev = p2_lft;
+ } else {
+ // a b c x y z
+ p1_rt->next = p2_lft;
+ p2_lft->prev = p1_rt;
+ p1_lft->prev = p2_rt;
+ p2_rt->next = p1_lft;
+ }
+ }
+
+ keep_ring->bottom_point = nullptr;
+ bool keep_is_hole = ring_is_hole(keep_ring);
+ bool remove_is_hole = ring_is_hole(remove_ring);
+
+ remove_ring->points = nullptr;
+ remove_ring->bottom_point = nullptr;
+ if (keep_is_hole != remove_is_hole) {
+ ring1_replaces_ring2(keep_ring->parent, remove_ring, manager);
+ } else {
+ ring1_replaces_ring2(keep_ring, remove_ring, manager);
+ }
+
+ update_points_ring(keep_ring);
+
+ // nb: safe because we only get here via AddLocalMaxPoly
+ keep_bound->ring = nullptr;
+ remove_bound->ring = nullptr;
+
+ for (auto& b : active_bounds) {
+ if (b == nullptr) {
+ continue;
+ }
+ if (b->ring == remove_ring) {
+ b->ring = keep_ring;
+ b->side = keep_bound->side;
+ break; // Not sure why there is a break here but was transfered logic from angus
+ }
+ }
+}
+
+template <typename T>
+void add_local_maximum_point(bound<T>& b1,
+ bound<T>& b2,
+ mapbox::geometry::point<T> const& pt,
+ ring_manager<T>& rings,
+ active_bound_list<T>& active_bounds) {
+ insert_hot_pixels_in_path(b2, pt, rings, false);
+ add_point(b1, active_bounds, pt, rings);
+ if (b1.ring == b2.ring) {
+ b1.ring = nullptr;
+ b2.ring = nullptr;
+ // I am not certain that order is important here?
+ } else if (b1.ring->ring_index < b2.ring->ring_index) {
+ append_ring(b1, b2, active_bounds, rings);
+ } else {
+ append_ring(b2, b1, active_bounds, rings);
+ }
+}
+
+enum point_in_polygon_result : std::int8_t {
+ point_on_polygon = -1,
+ point_inside_polygon = 0,
+ point_outside_polygon = 1
+};
+
+template <typename T>
+point_in_polygon_result point_in_polygon(point<T> const& pt, point_ptr<T> op) {
+ // returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+ point_in_polygon_result result = point_outside_polygon;
+ point_ptr<T> startOp = op;
+ do {
+ if (op->next->y == pt.y) {
+ if ((op->next->x == pt.x) ||
+ (op->y == pt.y && ((op->next->x > pt.x) == (op->x < pt.x)))) {
+ return point_on_polygon;
+ }
+ }
+ if ((op->y < pt.y) != (op->next->y < pt.y)) {
+ if (op->x >= pt.x) {
+ if (op->next->x > pt.x) {
+ // Switch between point outside polygon and point inside
+ // polygon
+ if (result == point_outside_polygon) {
+ result = point_inside_polygon;
+ } else {
+ result = point_outside_polygon;
+ }
+ } else {
+ double d =
+ static_cast<double>(op->x - pt.x) *
+ static_cast<double>(op->next->y - pt.y) -
+ static_cast<double>(op->next->x - pt.x) * static_cast<double>(op->y - pt.y);
+ if (value_is_zero(d)) {
+ return point_on_polygon;
+ }
+ if ((d > 0) == (op->next->y > op->y)) {
+ // Switch between point outside polygon and point inside
+ // polygon
+ if (result == point_outside_polygon) {
+ result = point_inside_polygon;
+ } else {
+ result = point_outside_polygon;
+ }
+ }
+ }
+ } else {
+ if (op->next->x > pt.x) {
+ double d =
+ static_cast<double>(op->x - pt.x) *
+ static_cast<double>(op->next->y - pt.y) -
+ static_cast<double>(op->next->x - pt.x) * static_cast<double>(op->y - pt.y);
+ if (value_is_zero(d)) {
+ return point_on_polygon;
+ }
+ if ((d > 0) == (op->next->y > op->y)) {
+ // Switch between point outside polygon and point inside
+ // polygon
+ if (result == point_outside_polygon) {
+ result = point_inside_polygon;
+ } else {
+ result = point_outside_polygon;
+ }
+ }
+ }
+ }
+ }
+ op = op->next;
+ } while (startOp != op);
+ return result;
+}
+
+template <typename T>
+point_in_polygon_result point_in_polygon(mapbox::geometry::point<double> const& pt,
+ point_ptr<T> op) {
+ // returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+ point_in_polygon_result result = point_outside_polygon;
+ point_ptr<T> startOp = op;
+ do {
+ double op_x = static_cast<double>(op->x);
+ double op_y = static_cast<double>(op->y);
+ double op_next_x = static_cast<double>(op->next->x);
+ double op_next_y = static_cast<double>(op->next->y);
+ if (values_are_equal(op_next_y, pt.y)) {
+ if (values_are_equal(op_next_x, pt.x) ||
+ (values_are_equal(op_y, pt.y) && ((op_next_x > pt.x) == (op_x < pt.x)))) {
+ return point_on_polygon;
+ }
+ }
+ if ((op_y < pt.y) != (op_next_y < pt.y)) {
+ if (greater_than_or_equal(op_x, pt.x)) {
+ if (op_next_x > pt.x) {
+ // Switch between point outside polygon and point inside
+ // polygon
+ if (result == point_outside_polygon) {
+ result = point_inside_polygon;
+ } else {
+ result = point_outside_polygon;
+ }
+ } else {
+ double d =
+ (op_x - pt.x) * (op_next_y - pt.y) - (op_next_x - pt.x) * (op_y - pt.y);
+ if (value_is_zero(d)) {
+ return point_on_polygon;
+ }
+ if ((d > 0.0) == (op_next_y > op_y)) {
+ // Switch between point outside polygon and point inside
+ // polygon
+ if (result == point_outside_polygon) {
+ result = point_inside_polygon;
+ } else {
+ result = point_outside_polygon;
+ }
+ }
+ }
+ } else {
+ if (op_next_x > pt.x) {
+ double d =
+ (op_x - pt.x) * (op_next_y - pt.y) - (op_next_x - pt.x) * (op_y - pt.y);
+ if (value_is_zero(d)) {
+ return point_on_polygon;
+ }
+ if ((d > 0.0) == (op_next_y > op_y)) {
+ // Switch between point outside polygon and point inside
+ // polygon
+ if (result == point_outside_polygon) {
+ result = point_inside_polygon;
+ } else {
+ result = point_outside_polygon;
+ }
+ }
+ }
+ }
+ }
+ op = op->next;
+ } while (startOp != op);
+ return result;
+}
+
+template <typename T>
+bool is_convex(point_ptr<T> edge) {
+ point_ptr<T> prev = edge->prev;
+ point_ptr<T> next = edge->next;
+ T v1x = edge->x - prev->x;
+ T v1y = edge->y - prev->y;
+ T v2x = next->x - edge->x;
+ T v2y = next->y - edge->y;
+ T cross = v1x * v2y - v2x * v1y;
+ if (cross < 0 && edge->ring->area() > 0) {
+ return true;
+ } else if (cross > 0 && edge->ring->area() < 0) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+template <typename T>
+mapbox::geometry::point<double> centroid_of_points(point_ptr<T> edge) {
+ point_ptr<T> prev = edge->prev;
+ point_ptr<T> next = edge->next;
+ return { static_cast<double>(prev->x + edge->x + next->x) / 3.0, static_cast<double>(prev->y + edge->y + next->y) / 3.0 };
+}
+
+template <typename T>
+point_in_polygon_result inside_or_outside_special(point_ptr<T> first_pt, point_ptr<T> other_poly) {
+
+ // We are going to loop through all the points
+ // of the original triangle. The goal is to find a convex edge
+ // that with its next and previous forms a triangle with its centroid
+ // that is within the first ring. Then we will check the other polygon
+ // to see if it is within this polygon.
+ point_ptr<T> itr = first_pt;
+ do {
+ if (is_convex(itr)) {
+ auto pt = centroid_of_points(itr);
+ if (point_inside_polygon == point_in_polygon(pt, first_pt)) {
+ return point_in_polygon(pt, other_poly);
+ }
+ }
+ itr = itr->next;
+ } while (itr != first_pt);
+
+ throw std::runtime_error("Could not find a point within the polygon to test");
+}
+
+template <typename T>
+bool box2_contains_box1(mapbox::geometry::box<T> const& box1,
+ mapbox::geometry::box<T> const& box2) {
+ return (box2.max.x >= box1.max.x && box2.max.y >= box1.max.y && box2.min.x <= box1.min.x &&
+ box2.min.y <= box1.min.y);
+}
+
+template <typename T>
+bool poly2_contains_poly1(ring_ptr<T> ring1, ring_ptr<T> ring2) {
+ if (!box2_contains_box1(ring1->bbox, ring2->bbox)) {
+ return false;
+ }
+ if (std::fabs(ring2->area()) < std::fabs(ring1->area())) {
+ return false;
+ }
+ point_ptr<T> outpt1 = ring1->points->next;
+ point_ptr<T> outpt2 = ring2->points->next;
+ point_ptr<T> op = outpt1;
+ do {
+ // nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
+ point_in_polygon_result res = point_in_polygon(*op, outpt2);
+ if (res != point_on_polygon) {
+ return res == point_inside_polygon;
+ }
+ op = op->next;
+ } while (op != outpt1);
+ point_in_polygon_result res = inside_or_outside_special(outpt1, outpt2);
+ return res == point_inside_polygon;
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/scanbeam.hpp b/include/mapbox/geometry/wagyu/scanbeam.hpp
new file mode 100644
index 0000000..5ec6408
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/scanbeam.hpp
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/local_minimum.hpp>
+
+#include <algorithm>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+using scanbeam_list = std::vector<T>;
+
+template <typename T>
+bool pop_from_scanbeam(T& Y, scanbeam_list<T>& scanbeam) {
+ if (scanbeam.empty()) {
+ return false;
+ }
+ std::sort(scanbeam.begin(), scanbeam.end());
+ scanbeam.erase(std::unique(scanbeam.begin(), scanbeam.end()), scanbeam.end());
+ Y = scanbeam.back();
+ scanbeam.pop_back();
+ return true;
+}
+
+template <typename T>
+void setup_scanbeam(local_minimum_list<T>& minima_list, scanbeam_list<T>& scanbeam) {
+
+ for (auto lm = minima_list.begin(); lm != minima_list.end(); ++lm) {
+ scanbeam.push_back(lm->y);
+ }
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/snap_rounding.hpp b/include/mapbox/geometry/wagyu/snap_rounding.hpp
new file mode 100644
index 0000000..d10b3d0
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/snap_rounding.hpp
@@ -0,0 +1,195 @@
+#pragma once
+
+#include <mapbox/geometry/wagyu/active_bound_list.hpp>
+#include <mapbox/geometry/wagyu/bound.hpp>
+#include <mapbox/geometry/wagyu/bubble_sort.hpp>
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/edge.hpp>
+#include <mapbox/geometry/wagyu/intersect.hpp>
+#include <mapbox/geometry/wagyu/intersect_util.hpp>
+#include <mapbox/geometry/wagyu/ring.hpp>
+#include <mapbox/geometry/wagyu/ring_util.hpp>
+#include <mapbox/geometry/wagyu/util.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+struct hp_intersection_swap {
+
+ ring_manager<T>& manager;
+
+ hp_intersection_swap(ring_manager<T>& m) : manager(m) {
+ }
+
+ void operator()(bound_ptr<T> const& b1, bound_ptr<T> const& b2) {
+ mapbox::geometry::point<double> pt;
+ if (!get_edge_intersection<T, double>(*(b1->current_edge), *(b2->current_edge), pt)) {
+ // LCOV_EXCL_START
+ throw std::runtime_error("Trying to find intersection of lines that do not intersect");
+ // LCOV_EXCL_END
+ }
+ add_to_hot_pixels(round_point<T>(pt), manager);
+ }
+};
+
+template <typename T>
+void process_hot_pixel_intersections(T top_y,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& manager) {
+ if (active_bounds.empty()) {
+ return;
+ }
+ update_current_x(active_bounds, top_y);
+ bubble_sort(active_bounds.begin(), active_bounds.end(), intersection_compare<T>(),
+ hp_intersection_swap<T>(manager));
+}
+
+template <typename T>
+bool horizontals_at_top_scanbeam(T top_y,
+ active_bound_list_itr<T>& bnd_curr,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& manager) {
+ bool shifted = false;
+ auto& current_edge = (*bnd_curr)->current_edge;
+ (*bnd_curr)->current_x = static_cast<double>(current_edge->top.x);
+ if (current_edge->bot.x < current_edge->top.x) {
+ // left to right
+ auto bnd_next = std::next(bnd_curr);
+ while (bnd_next != active_bounds.end() &&
+ (*bnd_next == nullptr || (*bnd_next)->current_x < (*bnd_curr)->current_x)) {
+ if (*bnd_next != nullptr && (*bnd_next)->current_edge->top.y != top_y &&
+ (*bnd_next)->current_edge->bot.y != top_y) {
+ mapbox::geometry::point<T> pt(wround<T>((*bnd_next)->current_x), top_y);
+ add_to_hot_pixels(pt, manager);
+ }
+ std::iter_swap(bnd_curr, bnd_next);
+ ++bnd_curr;
+ ++bnd_next;
+ shifted = true;
+ }
+ } else {
+ // right to left
+ if (bnd_curr != active_bounds.begin()) {
+ auto bnd_prev = std::prev(bnd_curr);
+ while (bnd_curr != active_bounds.begin() &&
+ (*bnd_prev == nullptr || (*bnd_prev)->current_x > (*bnd_curr)->current_x)) {
+ if (*bnd_prev != nullptr && (*bnd_prev)->current_edge->top.y != top_y &&
+ (*bnd_prev)->current_edge->bot.y != top_y) {
+ mapbox::geometry::point<T> pt(wround<T>((*bnd_prev)->current_x), top_y);
+ add_to_hot_pixels(pt, manager);
+ }
+ std::iter_swap(bnd_curr, bnd_prev);
+ --bnd_curr;
+ if (bnd_curr != active_bounds.begin()) {
+ --bnd_prev;
+ }
+ }
+ }
+ }
+ return shifted;
+}
+
+template <typename T>
+void process_hot_pixel_edges_at_top_of_scanbeam(T top_y,
+ scanbeam_list<T>& scanbeam,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& manager) {
+ for (auto bnd = active_bounds.begin(); bnd != active_bounds.end();) {
+ if (*bnd == nullptr) {
+ ++bnd;
+ continue;
+ }
+ bound<T>& current_bound = *(*bnd);
+ auto bnd_curr = bnd;
+ bool shifted = false;
+ auto& current_edge = current_bound.current_edge;
+ while (current_edge != current_bound.edges.end() && current_edge->top.y == top_y) {
+ add_to_hot_pixels(current_edge->top, manager);
+ if (is_horizontal(*current_edge)) {
+ if (horizontals_at_top_scanbeam(top_y, bnd_curr, active_bounds, manager)) {
+ shifted = true;
+ }
+ }
+ next_edge_in_bound(current_bound, scanbeam);
+ }
+ if (current_edge == current_bound.edges.end()) {
+ *bnd_curr = nullptr;
+ }
+ if (!shifted) {
+ ++bnd;
+ }
+ }
+ active_bounds.erase(std::remove(active_bounds.begin(), active_bounds.end(), nullptr),
+ active_bounds.end());
+}
+
+template <typename T>
+void insert_local_minima_into_ABL_hot_pixel(T top_y,
+ local_minimum_ptr_list<T>& minima_sorted,
+ local_minimum_ptr_list_itr<T>& lm,
+ active_bound_list<T>& active_bounds,
+ ring_manager<T>& manager,
+ scanbeam_list<T>& scanbeam) {
+ while (lm != minima_sorted.end() && (*lm)->y == top_y) {
+ add_to_hot_pixels((*lm)->left_bound.edges.front().bot, manager);
+ auto& left_bound = (*lm)->left_bound;
+ auto& right_bound = (*lm)->right_bound;
+ left_bound.current_edge = left_bound.edges.begin();
+ left_bound.next_edge = std::next(left_bound.current_edge);
+ left_bound.current_x = static_cast<double>(left_bound.current_edge->bot.x);
+ right_bound.current_edge = right_bound.edges.begin();
+ right_bound.next_edge = std::next(right_bound.current_edge);
+ right_bound.current_x = static_cast<double>(right_bound.current_edge->bot.x);
+ auto lb_abl_itr = insert_bound_into_ABL(left_bound, right_bound, active_bounds);
+ if (!current_edge_is_horizontal<T>(lb_abl_itr)) {
+ scanbeam.push_back((*lb_abl_itr)->current_edge->top.y);
+ }
+ auto rb_abl_itr = std::next(lb_abl_itr);
+ if (!current_edge_is_horizontal<T>(rb_abl_itr)) {
+ scanbeam.push_back((*rb_abl_itr)->current_edge->top.y);
+ }
+ ++lm;
+ }
+}
+
+template <typename T>
+void build_hot_pixels(local_minimum_list<T>& minima_list, ring_manager<T>& manager) {
+ active_bound_list<T> active_bounds;
+ scanbeam_list<T> scanbeam;
+ T scanline_y = std::numeric_limits<T>::max();
+
+ local_minimum_ptr_list<T> minima_sorted;
+ minima_sorted.reserve(minima_list.size());
+ for (auto& lm : minima_list) {
+ minima_sorted.push_back(&lm);
+ }
+ std::stable_sort(minima_sorted.begin(), minima_sorted.end(), local_minimum_sorter<T>());
+ local_minimum_ptr_list_itr<T> current_lm = minima_sorted.begin();
+
+ setup_scanbeam(minima_list, scanbeam);
+
+ // Estimate size for reserving hot pixels
+ std::size_t reserve = 0;
+ for (auto& lm : minima_list) {
+ reserve += lm.left_bound.edges.size() + 2;
+ reserve += lm.right_bound.edges.size() + 2;
+ }
+ manager.hot_pixels.reserve(reserve);
+
+ while (pop_from_scanbeam(scanline_y, scanbeam) || current_lm != minima_sorted.end()) {
+
+ process_hot_pixel_intersections(scanline_y, active_bounds, manager);
+
+ insert_local_minima_into_ABL_hot_pixel(scanline_y, minima_sorted, current_lm, active_bounds,
+ manager, scanbeam);
+
+ process_hot_pixel_edges_at_top_of_scanbeam(scanline_y, scanbeam, active_bounds, manager);
+ }
+ preallocate_point_memory(manager, manager.hot_pixels.size());
+ sort_hot_pixels(manager);
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/topology_correction.hpp b/include/mapbox/geometry/wagyu/topology_correction.hpp
new file mode 100644
index 0000000..ffe27bc
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/topology_correction.hpp
@@ -0,0 +1,1369 @@
+#pragma once
+
+#define _USE_MATH_DEFINES
+#include <cmath>
+
+#include <algorithm>
+#include <list>
+#include <map>
+#include <set>
+#include <unordered_map>
+#include <utility>
+
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/ring.hpp>
+#include <mapbox/geometry/wagyu/ring_util.hpp>
+
+#ifdef DEBUG
+#include <iostream>
+#include <sstream>
+#endif
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+struct point_ptr_pair {
+ point_ptr<T> op1;
+ point_ptr<T> op2;
+
+ constexpr point_ptr_pair(point_ptr<T> o1, point_ptr<T> o2) : op1(o1), op2(o2) {
+ }
+
+ point_ptr_pair(point_ptr_pair<T> const& p) = default;
+
+ point_ptr_pair(point_ptr_pair<T>&& p) : op1(std::move(p.op1)), op2(std::move(p.op2)) {
+ }
+
+ point_ptr_pair& operator=(point_ptr_pair<T>&& p) {
+ op1 = std::move(p.op1);
+ op2 = std::move(p.op2);
+ return *this;
+ }
+};
+
+#ifdef DEBUG
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>&
+operator<<(std::basic_ostream<charT, traits>& out,
+ const std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring) {
+
+ out << " BEGIN CONNECTIONS: " << std::endl;
+ for (auto& r : dupe_ring) {
+ out << " Ring: ";
+ if (r.second.op1->ring) {
+ out << r.second.op1->ring->ring_index;
+ } else {
+ out << "---";
+ }
+ out << " to ";
+ if (r.second.op2->ring) {
+ out << r.second.op2->ring->ring_index;
+ } else {
+ out << "---";
+ }
+ out << " ( at " << r.second.op1->x << ", " << r.second.op1->y << " )";
+ out << " Ring1 ( ";
+ if (r.second.op1->ring) {
+ out << "area: " << r.second.op1->ring->area << " parent: ";
+ if (r.second.op1->ring->parent) {
+ out << r.second.op1->ring->parent->ring_index;
+ } else {
+ out << "---";
+ }
+ } else {
+ out << "---";
+ }
+ out << " )";
+ out << " Ring2 ( ";
+ if (r.second.op2->ring) {
+ out << "area: " << r.second.op2->ring->area << " parent: ";
+ if (r.second.op2->ring->parent) {
+ out << r.second.op2->ring->parent->ring_index;
+ } else {
+ out << "---";
+ }
+ } else {
+ out << "---";
+ }
+ out << " )";
+ out << std::endl;
+ }
+ out << " END CONNECTIONS: " << std::endl;
+ return out;
+}
+
+#endif
+
+template <typename T>
+bool find_intersect_loop(std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring,
+ std::list<std::pair<ring_ptr<T>, point_ptr_pair<T>>>& iList,
+ ring_ptr<T> ring_parent,
+ ring_ptr<T> ring_origin,
+ ring_ptr<T> ring_search,
+ std::set<ring_ptr<T>>& visited,
+ point_ptr<T> orig_pt,
+ point_ptr<T> prev_pt,
+ ring_manager<T>& rings) {
+ {
+ auto range = dupe_ring.equal_range(ring_search);
+ // Check for direct connection
+ for (auto& it = range.first; it != range.second;) {
+ ring_ptr<T> it_ring1 = it->second.op1->ring;
+ ring_ptr<T> it_ring2 = it->second.op2->ring;
+ if (!it_ring1 || !it_ring2 || it_ring1 != ring_search ||
+ (!it_ring1->is_hole() && !it_ring2->is_hole())) {
+ it = dupe_ring.erase(it);
+ continue;
+ }
+ if (it_ring2 == ring_origin &&
+ (ring_parent == it_ring2 || ring_parent == it_ring2->parent) &&
+ *prev_pt != *it->second.op2 && *orig_pt != *it->second.op2) {
+ iList.emplace_front(ring_search, it->second);
+ return true;
+ }
+ ++it;
+ }
+ }
+ auto range = dupe_ring.equal_range(ring_search);
+ visited.insert(ring_search);
+ // Check for connection through chain of other intersections
+ for (auto& it = range.first;
+ it != range.second && it != dupe_ring.end() && it->first == ring_search; ++it) {
+ ring_ptr<T> it_ring = it->second.op2->ring;
+ if (visited.count(it_ring) > 0 || it_ring == nullptr ||
+ (ring_parent != it_ring && ring_parent != it_ring->parent) ||
+ value_is_zero(it_ring->area()) || *prev_pt == *it->second.op2) {
+ continue;
+ }
+ if (find_intersect_loop(dupe_ring, iList, ring_parent, ring_origin, it_ring, visited,
+ orig_pt, it->second.op2, rings)) {
+ iList.emplace_front(ring_search, it->second);
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename T>
+struct point_ptr_cmp {
+ inline bool operator()(point_ptr<T> op1, point_ptr<T> op2) {
+ if (op1->y != op2->y) {
+ return (op1->y > op2->y);
+ } else if (op1->x != op2->x) {
+ return (op1->x < op2->x);
+ } else {
+ std::size_t depth_1 = ring_depth(op1->ring);
+ std::size_t depth_2 = ring_depth(op2->ring);
+ return depth_1 > depth_2;
+ }
+ }
+};
+
+template <typename T>
+void correct_orientations(ring_manager<T>& manager) {
+ for (auto& r : manager.rings) {
+ if (!r.points) {
+ continue;
+ }
+ r.recalculate_stats();
+ if (r.size() < 3) {
+ remove_ring_and_points(&r, manager, false);
+ continue;
+ }
+ if (ring_is_hole(&r) != r.is_hole()) {
+ reverse_ring(r.points);
+ r.recalculate_stats();
+ }
+ }
+}
+
+template <typename T>
+point_vector<T> sort_ring_points(ring_ptr<T> r) {
+ point_vector<T> sorted_points;
+ point_ptr<T> point_itr = r->points;
+ point_ptr<T> last_point = point_itr->prev;
+ while (point_itr != last_point) {
+ sorted_points.push_back(point_itr);
+ point_itr = point_itr->next;
+ }
+ sorted_points.push_back(last_point);
+ std::stable_sort(sorted_points.begin(), sorted_points.end(),
+ [](point_ptr<T> const& pt1, point_ptr<T> const& pt2) {
+ if (pt1->y != pt2->y) {
+ return (pt1->y > pt2->y);
+ }
+ return (pt1->x < pt2->x);
+ });
+ return sorted_points;
+}
+
+template <typename T>
+ring_ptr<T> correct_self_intersection(point_ptr<T> pt1,
+ point_ptr<T> pt2,
+ ring_manager<T>& manager) {
+ if (pt1->ring != pt2->ring) {
+ return static_cast<ring_ptr<T>>(nullptr);
+ }
+
+ ring_ptr<T> ring = pt1->ring;
+
+ // split the polygon into two ...
+ point_ptr<T> pt3 = pt1->prev;
+ point_ptr<T> pt4 = pt2->prev;
+ pt1->prev = pt4;
+ pt4->next = pt1;
+ pt2->prev = pt3;
+ pt3->next = pt2;
+
+ ring_ptr<T> new_ring = create_new_ring(manager);
+ std::size_t size_1 = 0;
+ std::size_t size_2 = 0;
+ mapbox::geometry::box<T> box1({ 0, 0 }, { 0, 0 });
+ mapbox::geometry::box<T> box2({ 0, 0 }, { 0, 0 });
+ double area_1 = area_from_point(pt1, size_1, box1);
+ double area_2 = area_from_point(pt2, size_2, box2);
+
+ if (std::fabs(area_1) > std::fabs(area_2)) {
+ ring->points = pt1;
+ ring->set_stats(area_1, size_1, box1);
+ new_ring->points = pt2;
+ new_ring->set_stats(area_2, size_2, box2);
+ } else {
+ ring->points = pt2;
+ ring->set_stats(area_2, size_2, box2);
+ new_ring->points = pt1;
+ new_ring->set_stats(area_1, size_1, box1);
+ }
+ update_points_ring(new_ring);
+ return new_ring;
+}
+
+template <typename T>
+void correct_repeated_points(ring_manager<T>& manager,
+ ring_vector<T>& new_rings,
+ point_vector_itr<T> const& begin,
+ point_vector_itr<T> const& end) {
+ for (auto itr1 = begin; itr1 != end; ++itr1) {
+ if ((*itr1)->ring == nullptr) {
+ continue;
+ }
+ for (auto itr2 = std::next(itr1); itr2 != end; ++itr2) {
+ if ((*itr2)->ring == nullptr) {
+ continue;
+ }
+ ring_ptr<T> new_ring = correct_self_intersection(*itr1, *itr2, manager);
+ if (new_ring != nullptr) {
+ new_rings.push_back(new_ring);
+ }
+ }
+ }
+}
+
+template <typename T>
+void find_and_correct_repeated_points(ring_ptr<T> r,
+ ring_manager<T>& manager,
+ ring_vector<T>& new_rings) {
+ auto sorted_points = sort_ring_points(r);
+ // Find sets of repeated points
+ std::size_t count = 0;
+ auto prev_itr = sorted_points.begin();
+ auto itr = std::next(prev_itr);
+ while (itr != sorted_points.end()) {
+ if (*(*prev_itr) == *(*(itr))) {
+ ++count;
+ ++prev_itr;
+ ++itr;
+ if (itr != sorted_points.end()) {
+ continue;
+ } else {
+ ++prev_itr;
+ }
+ } else {
+ ++prev_itr;
+ ++itr;
+ }
+ if (count == 0) {
+ continue;
+ }
+ auto first = prev_itr;
+ std::advance(first, -(static_cast<int>(count) + 1));
+ correct_repeated_points(manager, new_rings, first, prev_itr);
+ count = 0;
+ }
+}
+
+template <typename T>
+void reassign_children_if_necessary(ring_ptr<T> new_ring,
+ ring_ptr<T> sibling_ring,
+ ring_manager<T>& manager,
+ ring_vector<T>& new_rings) {
+ auto& children = sibling_ring == nullptr ? manager.children : sibling_ring->children;
+ for (auto c : children) {
+ if (c == nullptr) {
+ continue;
+ }
+ if (std::find(new_rings.begin(), new_rings.end(), c) != new_rings.end()) {
+ continue;
+ }
+ if (poly2_contains_poly1(c, new_ring)) {
+ reassign_as_child(c, new_ring, manager);
+ }
+ }
+}
+
+template <typename T>
+bool find_parent_in_tree(ring_ptr<T> r, ring_ptr<T> possible_parent, ring_manager<T>& manager) {
+ // Before starting this we are assuming that possible_parent
+ // and r have opposite signs of their areas
+
+ // First we must search all grandchildren
+ for (auto c : possible_parent->children) {
+ if (c == nullptr) {
+ continue;
+ }
+ for (auto gc : c->children) {
+ if (gc == nullptr) {
+ continue;
+ }
+ if (find_parent_in_tree(r, gc, manager)) {
+ return true;
+ }
+ }
+ }
+
+ if (poly2_contains_poly1(r, possible_parent)) {
+ reassign_as_child(r, possible_parent, manager);
+ return true;
+ }
+ return false;
+}
+
+template <typename T>
+void assign_new_ring_parents(ring_manager<T>& manager,
+ ring_ptr<T> original_ring,
+ ring_vector<T>& new_rings) {
+
+ // First lets remove any rings that have zero area
+ // or have no points
+ new_rings.erase(std::remove_if(new_rings.begin(), new_rings.end(),
+ [](ring_ptr<T> const& r) {
+ if (r->points == nullptr) {
+ return true;
+ }
+ return value_is_zero(r->area());
+ }),
+ new_rings.end());
+
+ if (new_rings.empty()) {
+ // No new rings created simply return;
+ return;
+ }
+
+ // We should not have to re-assign the parent of the original ring
+ // because we always maintained the largest ring during splitting
+ // on repeated points.
+
+ double original_ring_area = original_ring->area();
+ bool original_positive = original_ring_area > 0.0;
+
+ // If there is only one new ring the logic is very simple and we
+ // do not have to check which ring contains, we only need to compare
+ // the areas of the original ring and that of the new ring.
+ if (new_rings.size() == 1) {
+ double new_ring_area = new_rings.front()->area();
+ bool new_positive = new_ring_area > 0.0;
+ if (original_positive == new_positive) {
+ // The rings should be siblings
+ assign_as_child(new_rings.front(), original_ring->parent, manager);
+ reassign_children_if_necessary(new_rings.front(), original_ring, manager, new_rings);
+ } else {
+ // The new ring is a child of original ring
+ // Check the
+ assign_as_child(new_rings.front(), original_ring, manager);
+ reassign_children_if_necessary(new_rings.front(), original_ring->parent, manager,
+ new_rings);
+ }
+ return;
+ }
+
+ // Now we want to sort rings from the largest in absolute area to the smallest
+ // as we will assign the rings with the largest areas first
+ std::stable_sort(new_rings.begin(), new_rings.end(),
+ [](ring_ptr<T> const& r1, ring_ptr<T> const& r2) {
+ return std::fabs(r1->area()) > std::fabs(r2->area());
+ });
+
+ for (auto r_itr = new_rings.begin(); r_itr != new_rings.end(); ++r_itr) {
+ double new_ring_area = (*r_itr)->area();
+ bool new_positive = new_ring_area > 0.0;
+ bool same_orientation = new_positive == original_positive;
+ bool found = false;
+ // First lets check the trees of any new_rings that might have
+ // been assigned as siblings to the original ring.
+ for (auto s_itr = new_rings.begin(); s_itr != r_itr; ++s_itr) {
+ if ((*s_itr)->parent != original_ring->parent) {
+ continue;
+ }
+ if (same_orientation) {
+ for (auto s_child : (*s_itr)->children) {
+ if (s_child == nullptr) {
+ continue;
+ }
+ if (find_parent_in_tree(*r_itr, s_child, manager)) {
+ reassign_children_if_necessary(*r_itr, original_ring, manager, new_rings);
+ found = true;
+ break;
+ }
+ }
+ } else {
+ if (find_parent_in_tree(*r_itr, *s_itr, manager)) {
+ reassign_children_if_necessary(*r_itr, original_ring->parent, manager,
+ new_rings);
+ found = true;
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+
+ if (found) {
+ continue;
+ }
+
+ // Next lets check the tree of the original_ring
+ if (same_orientation) {
+ for (auto o_child : original_ring->children) {
+ if (o_child == nullptr) {
+ continue;
+ }
+ if (find_parent_in_tree(*r_itr, o_child, manager)) {
+ reassign_children_if_necessary(*r_itr, original_ring, manager, new_rings);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ // If we didn't find any parent and the same orientation
+ // then it must be a sibling of the original ring
+ assign_as_child(*r_itr, original_ring->parent, manager);
+ reassign_children_if_necessary(*r_itr, original_ring, manager, new_rings);
+ }
+ } else {
+ if (find_parent_in_tree(*r_itr, original_ring, manager)) {
+ reassign_children_if_necessary(*r_itr, original_ring->parent, manager, new_rings);
+ } else {
+ throw std::runtime_error("Unable to find a proper parent ring");
+ }
+ }
+ }
+}
+
+template <typename T>
+bool correct_ring_self_intersections(ring_manager<T>& manager, ring_ptr<T> r, bool correct_tree) {
+
+ if (r->corrected || !r->points) {
+ return false;
+ }
+
+ ring_vector<T> new_rings;
+
+ find_and_correct_repeated_points(r, manager, new_rings);
+
+ if (correct_tree) {
+ assign_new_ring_parents(manager, r, new_rings);
+ }
+
+ r->corrected = true;
+ return true;
+}
+
+template <typename T>
+void process_single_intersection(
+ std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& connection_map,
+ point_ptr<T> op_j,
+ point_ptr<T> op_k,
+ ring_manager<T>& manager) {
+ ring_ptr<T> ring_j = op_j->ring;
+ ring_ptr<T> ring_k = op_k->ring;
+ if (ring_j == ring_k) {
+ return;
+ }
+
+ if (!ring_j->is_hole() && !ring_k->is_hole()) {
+ // Both are not holes, return nothing to do.
+ return;
+ }
+
+ ring_ptr<T> ring_origin;
+ ring_ptr<T> ring_search;
+ ring_ptr<T> ring_parent;
+ point_ptr<T> op_origin_1;
+ point_ptr<T> op_origin_2;
+ if (!ring_j->is_hole()) {
+ ring_origin = ring_j;
+ ring_parent = ring_origin;
+ ring_search = ring_k;
+ op_origin_1 = op_j;
+ op_origin_2 = op_k;
+ } else if (!ring_k->is_hole()) {
+ ring_origin = ring_k;
+ ring_parent = ring_origin;
+ ring_search = ring_j;
+ op_origin_1 = op_k;
+ op_origin_2 = op_j;
+
+ } else {
+ // both are holes
+ // Order doesn't matter
+ ring_origin = ring_j;
+ ring_parent = ring_origin->parent;
+ ring_search = ring_k;
+ op_origin_1 = op_j;
+ op_origin_2 = op_k;
+ }
+ if (ring_parent != ring_search->parent) {
+ // The two holes do not have the same parent, do not add them
+ // simply return!
+ return;
+ }
+ bool found = false;
+ std::list<std::pair<ring_ptr<T>, point_ptr_pair<T>>> iList;
+ {
+ auto range = connection_map.equal_range(ring_search);
+ // Check for direct connection
+ for (auto& it = range.first; it != range.second;) {
+ if (!it->second.op1->ring) {
+ it = connection_map.erase(it);
+ continue;
+ }
+ if (!it->second.op2->ring) {
+ it = connection_map.erase(it);
+ continue;
+ }
+ ring_ptr<T> it_ring2 = it->second.op2->ring;
+ if (it_ring2 == ring_origin) {
+ found = true;
+ if (*op_origin_1 != *(it->second.op2)) {
+ iList.emplace_back(ring_search, it->second);
+ break;
+ }
+ }
+ ++it;
+ }
+ }
+ if (iList.empty()) {
+ auto range = connection_map.equal_range(ring_search);
+ std::set<ring_ptr<T>> visited;
+ visited.insert(ring_search);
+ // Check for connection through chain of other intersections
+ for (auto& it = range.first;
+ it != range.second && it != connection_map.end() && it->first == ring_search; ++it) {
+ ring_ptr<T> it_ring = it->second.op2->ring;
+ if (it_ring != ring_search && *op_origin_2 != *it->second.op2 && it_ring != nullptr &&
+ (ring_parent == it_ring || ring_parent == it_ring->parent) &&
+ !value_is_zero(it_ring->area()) &&
+ find_intersect_loop(connection_map, iList, ring_parent, ring_origin, it_ring,
+ visited, op_origin_2, it->second.op2, manager)) {
+ found = true;
+ iList.emplace_front(ring_search, it->second);
+ break;
+ }
+ }
+ }
+ if (!found) {
+ point_ptr_pair<T> intPt_origin = { op_origin_1, op_origin_2 };
+ point_ptr_pair<T> intPt_search = { op_origin_2, op_origin_1 };
+ connection_map.emplace(ring_origin, std::move(intPt_origin));
+ connection_map.emplace(ring_search, std::move(intPt_search));
+ return;
+ }
+
+ if (iList.empty()) {
+ // The situation where both origin and search are holes might have a missing
+ // search condition, we must check if a new pair must be added.
+ bool missing = true;
+ auto rng = connection_map.equal_range(ring_origin);
+ // Check for direct connection
+ for (auto& it = rng.first; it != rng.second; ++it) {
+ ring_ptr<T> it_ring2 = it->second.op2->ring;
+ if (it_ring2 == ring_search) {
+ missing = false;
+ }
+ }
+ if (missing) {
+ point_ptr_pair<T> intPt_origin = { op_origin_1, op_origin_2 };
+ connection_map.emplace(ring_origin, std::move(intPt_origin));
+ }
+ return;
+ }
+ if (ring_origin->is_hole()) {
+ for (auto& iRing : iList) {
+ ring_ptr<T> ring_itr = iRing.first;
+ if (!ring_itr->is_hole()) {
+ // Make the hole the origin!
+ point_ptr<T> op1 = op_origin_1;
+ op_origin_1 = iRing.second.op1;
+ iRing.second.op1 = op1;
+ point_ptr<T> op2 = op_origin_2;
+ op_origin_2 = iRing.second.op2;
+ iRing.second.op2 = op2;
+ iRing.first = ring_origin;
+ ring_origin = ring_itr;
+ ring_parent = ring_origin;
+ break;
+ }
+ }
+ }
+ bool origin_is_hole = ring_origin->is_hole();
+
+ // Switch
+ point_ptr<T> op_origin_1_next = op_origin_1->next;
+ point_ptr<T> op_origin_2_next = op_origin_2->next;
+ op_origin_1->next = op_origin_2_next;
+ op_origin_2->next = op_origin_1_next;
+ op_origin_1_next->prev = op_origin_2;
+ op_origin_2_next->prev = op_origin_1;
+
+ for (auto& iRing : iList) {
+ point_ptr<T> op_search_1 = iRing.second.op1;
+ point_ptr<T> op_search_2 = iRing.second.op2;
+ point_ptr<T> op_search_1_next = op_search_1->next;
+ point_ptr<T> op_search_2_next = op_search_2->next;
+ op_search_1->next = op_search_2_next;
+ op_search_2->next = op_search_1_next;
+ op_search_1_next->prev = op_search_2;
+ op_search_2_next->prev = op_search_1;
+ }
+
+ ring_ptr<T> ring_new = create_new_ring(manager);
+ ring_origin->corrected = false;
+ std::size_t size_1 = 0;
+ std::size_t size_2 = 0;
+ mapbox::geometry::box<T> box1({ 0, 0 }, { 0, 0 });
+ mapbox::geometry::box<T> box2({ 0, 0 }, { 0, 0 });
+ double area_1 = area_from_point(op_origin_1, size_1, box1);
+ double area_2 = area_from_point(op_origin_2, size_2, box2);
+ if (origin_is_hole && ((area_1 < 0.0))) {
+ ring_origin->points = op_origin_1;
+ ring_origin->set_stats(area_1, size_1, box1);
+ ring_new->points = op_origin_2;
+ ring_new->set_stats(area_2, size_2, box2);
+ } else {
+ ring_origin->points = op_origin_2;
+ ring_origin->set_stats(area_2, size_2, box2);
+ ring_new->points = op_origin_1;
+ ring_new->set_stats(area_1, size_1, box1);
+ }
+
+ update_points_ring(ring_origin);
+ update_points_ring(ring_new);
+
+ ring_origin->bottom_point = nullptr;
+
+ for (auto& iRing : iList) {
+ ring_ptr<T> ring_itr = iRing.first;
+ ring_itr->bottom_point = nullptr;
+ if (origin_is_hole) {
+ ring1_replaces_ring2(ring_origin, ring_itr, manager);
+ } else {
+ ring1_replaces_ring2(ring_origin->parent, ring_itr, manager);
+ }
+ }
+ if (origin_is_hole) {
+ assign_as_child(ring_new, ring_origin, manager);
+ // The parent ring in this situation might need to give up children
+ // to the new ring.
+ for (auto c : ring_parent->children) {
+ if (c == nullptr) {
+ continue;
+ }
+ if (poly2_contains_poly1(c, ring_new)) {
+ reassign_as_child(c, ring_new, manager);
+ }
+ }
+ } else {
+ // The new ring and the origin ring need to be siblings
+ // however some children ring from the ring origin might
+ // need to be re-assigned to the new ring
+ assign_as_sibling(ring_new, ring_origin, manager);
+ for (auto c : ring_origin->children) {
+ if (c == nullptr) {
+ continue;
+ }
+ if (poly2_contains_poly1(c, ring_new)) {
+ reassign_as_child(c, ring_new, manager);
+ }
+ }
+ }
+
+ std::list<std::pair<ring_ptr<T>, point_ptr_pair<T>>> move_list;
+
+ for (auto& iRing : iList) {
+ auto range_itr = connection_map.equal_range(iRing.first);
+ if (range_itr.first != range_itr.second) {
+ for (auto& it = range_itr.first; it != range_itr.second; ++it) {
+ ring_ptr<T> it_ring = it->second.op1->ring;
+ ring_ptr<T> it_ring2 = it->second.op2->ring;
+ if (it_ring == nullptr || it_ring2 == nullptr || it_ring == it_ring2) {
+ continue;
+ }
+ if (it_ring->is_hole() || it_ring2->is_hole()) {
+ move_list.emplace_back(it_ring, it->second);
+ }
+ }
+ connection_map.erase(iRing.first);
+ }
+ }
+
+ auto range_itr = connection_map.equal_range(ring_origin);
+ for (auto& it = range_itr.first; it != range_itr.second;) {
+ ring_ptr<T> it_ring = it->second.op1->ring;
+ ring_ptr<T> it_ring2 = it->second.op2->ring;
+ if (it_ring == nullptr || it_ring2 == nullptr || it_ring == it_ring2) {
+ it = connection_map.erase(it);
+ continue;
+ }
+ if (it_ring != ring_origin) {
+ if (it_ring->is_hole() || it_ring2->is_hole()) {
+ move_list.emplace_back(it_ring, it->second);
+ }
+ it = connection_map.erase(it);
+ } else {
+ if (it_ring->is_hole() || it_ring2->is_hole()) {
+ ++it;
+ } else {
+ it = connection_map.erase(it);
+ }
+ }
+ }
+
+ if (!move_list.empty()) {
+ connection_map.insert(move_list.begin(), move_list.end());
+ }
+
+ return;
+}
+
+template <typename T>
+void correct_chained_repeats(
+ ring_manager<T>& manager,
+ std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& connection_map,
+ point_vector_itr<T> const& begin,
+ point_vector_itr<T> const& end) {
+ for (auto itr1 = begin; itr1 != end; ++itr1) {
+ if ((*itr1)->ring == nullptr) {
+ continue;
+ }
+ for (auto itr2 = std::next(itr1); itr2 != end; ++itr2) {
+ if ((*itr2)->ring == nullptr) {
+ continue;
+ }
+ process_single_intersection(connection_map, *itr1, *itr2, manager);
+ }
+ }
+}
+
+template <typename T>
+void correct_chained_rings(ring_manager<T>& manager) {
+
+ if (manager.all_points.size() < 2) {
+ return;
+ }
+ // Setup connection map which is a map of rings and their
+ // connection point pairs with other rings.
+ std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>> connection_map;
+ connection_map.reserve(manager.rings.size());
+
+ // Now lets find and process any points
+ // that overlap -- we should have solved
+ // all situations where these points
+ // would be self intersections of a ring with
+ // earlier processing so this should just be
+ // points where different rings are touching.
+ std::size_t count = 0;
+ auto prev_itr = manager.all_points.begin();
+ auto itr = std::next(prev_itr);
+ while (itr != manager.all_points.end()) {
+ if (*(*prev_itr) == *(*(itr))) {
+ ++count;
+ ++prev_itr;
+ ++itr;
+ if (itr != manager.all_points.end()) {
+ continue;
+ } else {
+ ++prev_itr;
+ }
+ } else {
+ ++prev_itr;
+ ++itr;
+ }
+ if (count == 0) {
+ continue;
+ }
+ auto first = prev_itr;
+ std::advance(first, -(static_cast<int>(count) + 1));
+ correct_chained_repeats(manager, connection_map, first, prev_itr);
+ count = 0;
+ }
+}
+
+template <typename T>
+ring_vector<T> sort_rings_largest_to_smallest(ring_manager<T>& manager) {
+ ring_vector<T> sorted_rings;
+ sorted_rings.reserve(manager.rings.size());
+ for (auto& r : manager.rings) {
+ sorted_rings.push_back(&r);
+ }
+ std::stable_sort(sorted_rings.begin(), sorted_rings.end(),
+ [](ring_ptr<T> const& r1, ring_ptr<T> const& r2) {
+ if (!r1->points || !r2->points) {
+ return r1->points != nullptr;
+ }
+ return std::fabs(r1->area()) > std::fabs(r2->area());
+ });
+ return sorted_rings;
+}
+
+template <typename T>
+ring_vector<T> sort_rings_smallest_to_largest(ring_manager<T>& manager) {
+ ring_vector<T> sorted_rings;
+ sorted_rings.reserve(manager.rings.size());
+ for (auto& r : manager.rings) {
+ sorted_rings.push_back(&r);
+ }
+ std::stable_sort(sorted_rings.begin(), sorted_rings.end(),
+ [](ring_ptr<T> const& r1, ring_ptr<T> const& r2) {
+ if (!r1->points || !r2->points) {
+ return r1->points != nullptr;
+ }
+ return std::fabs(r1->area()) < std::fabs(r2->area());
+ });
+ return sorted_rings;
+}
+
+template <typename T>
+struct collinear_path {
+ // Collinear edges are in opposite directions
+ // such that start_1 is at the same position
+ // of end_2 and vise versa. Start to end is
+ // always forward (next) on path.
+ point_ptr<T> start_1;
+ point_ptr<T> end_1;
+ point_ptr<T> start_2;
+ point_ptr<T> end_2;
+};
+
+template <typename T>
+struct collinear_result {
+ point_ptr<T> pt1;
+ point_ptr<T> pt2;
+};
+
+template <typename T>
+collinear_result<T> fix_collinear_path(collinear_path<T>& path) {
+ // Left and right are just the opposite ends of the
+ // collinear path, they may not be actually left
+ // and right of each other.
+ // The left end is start_1 and end_2
+ // The right end is start_2 and end_1
+
+ // NOTE spike detection is checking that the
+ // pointers are the same values, not position!
+ // additionally duplicate points we will treat
+ // if they are a spike left.
+ bool spike_left = (path.start_1 == path.end_2);
+ bool spike_right = (path.start_2 == path.end_1);
+
+ if (spike_left && spike_right) {
+ // If both ends are spikes we should simply
+ // delete all the points. (they should be in a loop)
+ point_ptr<T> itr = path.start_1;
+ while (itr != nullptr) {
+ itr->prev->next = nullptr;
+ itr->prev = nullptr;
+ itr->ring = nullptr;
+ itr = itr->next;
+ }
+ return { nullptr, nullptr };
+ } else if (spike_left) {
+ point_ptr<T> prev = path.start_2->prev;
+ point_ptr<T> itr = path.start_2;
+ while (itr != path.end_1) {
+ itr->prev->next = nullptr;
+ itr->prev = nullptr;
+ itr->ring = nullptr;
+ itr = itr->next;
+ }
+ prev->next = path.end_1;
+ path.end_1->prev = prev;
+ return { path.end_1, nullptr };
+ } else if (spike_right) {
+ point_ptr<T> prev = path.start_1->prev;
+ point_ptr<T> itr = path.start_1;
+ while (itr != path.end_2) {
+ itr->prev->next = nullptr;
+ itr->prev = nullptr;
+ itr->ring = nullptr;
+ itr = itr->next;
+ }
+ prev->next = path.end_2;
+ path.end_2->prev = prev;
+ return { path.end_2, nullptr };
+ } else {
+ point_ptr<T> prev_1 = path.start_1->prev;
+ point_ptr<T> prev_2 = path.start_2->prev;
+ point_ptr<T> itr = path.start_1;
+ do {
+ itr->prev->next = nullptr;
+ itr->prev = nullptr;
+ itr->ring = nullptr;
+ itr = itr->next;
+ } while (itr != path.end_1 && itr != nullptr);
+ itr = path.start_2;
+ do {
+ itr->prev->next = nullptr;
+ itr->prev = nullptr;
+ itr->ring = nullptr;
+ itr = itr->next;
+ } while (itr != path.end_2 && itr != nullptr);
+ if (path.start_1 == path.end_1 && path.start_2 == path.end_2) {
+ return { nullptr, nullptr };
+ } else if (path.start_1 == path.end_1) {
+ prev_2->next = path.end_2;
+ path.end_2->prev = prev_2;
+ return { path.end_2, nullptr };
+ } else if (path.start_2 == path.end_2) {
+ prev_1->next = path.end_1;
+ path.end_1->prev = prev_1;
+ return { path.end_1, nullptr };
+ } else {
+ prev_1->next = path.end_2;
+ path.end_2->prev = prev_1;
+ prev_2->next = path.end_1;
+ path.end_1->prev = prev_2;
+ return { path.end_1, path.end_2 };
+ }
+ }
+}
+
+template <typename T>
+collinear_path<T> find_start_and_end_of_collinear_edges(point_ptr<T> pt_a, point_ptr<T> pt_b) {
+ // Search backward on A, forwards on B first
+ bool same_ring = pt_a->ring == pt_b->ring;
+ point_ptr<T> back = pt_a;
+ point_ptr<T> forward = pt_b;
+ bool first = true;
+ do {
+ while (*(back->prev) == *back && back != forward) {
+ back = back->prev;
+ if (back == pt_a) {
+ break;
+ }
+ }
+ if (back == forward) {
+ back = back->prev;
+ forward = forward->next;
+ break;
+ }
+ while (*(forward->next) == *forward && back != forward) {
+ forward = forward->next;
+ if (forward == pt_b) {
+ break;
+ }
+ }
+ if (!first && (back == pt_a || forward == pt_b)) {
+ break;
+ }
+ if (back == forward) {
+ back = back->prev;
+ forward = forward->next;
+ break;
+ }
+ back = back->prev;
+ forward = forward->next;
+ first = false;
+ } while (*back == *forward);
+ point_ptr<T> start_a = back->next;
+ // If there are repeated points at the diverge we want to select
+ // only the first of those repeated points.
+ while (!same_ring && *start_a == *start_a->next && start_a != pt_a) {
+ start_a = start_a->next;
+ }
+ point_ptr<T> end_b = forward->prev;
+ while (!same_ring && *end_b == *end_b->prev && end_b != pt_b) {
+ end_b = end_b->prev;
+ }
+ // Search backward on B, forwards on A next
+ back = pt_b;
+ forward = pt_a;
+ first = true;
+ do {
+ while (*(back->prev) == *back && back != forward) {
+ back = back->prev;
+ if (back == pt_b) {
+ break;
+ }
+ }
+ if (back == forward) {
+ back = back->prev;
+ forward = forward->next;
+ break;
+ }
+ while (*(forward->next) == *forward && back != forward) {
+ forward = forward->next;
+ if (forward == pt_a) {
+ break;
+ }
+ }
+ if (!first && (back == pt_b || forward == pt_a)) {
+ break;
+ }
+ if (back == forward || (!first && (back == end_b || forward == start_a))) {
+ back = back->prev;
+ forward = forward->next;
+ break;
+ }
+ back = back->prev;
+ forward = forward->next;
+ first = false;
+ } while (*back == *forward);
+ point_ptr<T> start_b = back->next;
+ while (!same_ring && *start_b == *start_b->next && start_b != pt_b) {
+ start_b = start_b->next;
+ }
+ point_ptr<T> end_a = forward->prev;
+ while (!same_ring && *end_a == *end_a->prev && end_a != pt_a) {
+ end_a = end_a->prev;
+ }
+ return { start_a, end_a, start_b, end_b };
+}
+
+template <typename T>
+bool has_collinear_edge(point_ptr<T> pt_a, point_ptr<T> pt_b) {
+ // It is assumed pt_a and pt_b are at the same location.
+ return (*pt_a->next == *pt_b->prev || *pt_b->next == *pt_a->prev);
+}
+
+template <typename T>
+void process_collinear_edges_same_ring(point_ptr<T> pt_a,
+ point_ptr<T> pt_b,
+ ring_manager<T>& manager) {
+ ring_ptr<T> original_ring = pt_a->ring;
+ // As they are the same ring that are forming a collinear edge
+ // we should expect the creation of two different rings.
+ auto path = find_start_and_end_of_collinear_edges(pt_a, pt_b);
+ auto results = fix_collinear_path(path);
+ if (results.pt1 == nullptr) {
+ // If pt1 is a nullptr, both values
+ // are nullptrs. This mean the ring was
+ // removed during this processing.
+ remove_ring(original_ring, manager, false);
+ } else if (results.pt2 == nullptr) {
+ // If a single point is only returned, we simply removed a spike.
+ // In this case, we don't need to worry about parent or children
+ // and we simply need to set the points and clear the area
+ original_ring->points = results.pt1;
+ original_ring->recalculate_stats();
+ } else {
+ // If we have two seperate points, the ring has split into
+ // two different rings.
+ ring_ptr<T> ring_new = create_new_ring(manager);
+ ring_new->points = results.pt2;
+ ring_new->recalculate_stats();
+ update_points_ring(ring_new);
+ original_ring->points = results.pt1;
+ original_ring->recalculate_stats();
+ }
+}
+
+template <typename T>
+void process_collinear_edges_different_rings(point_ptr<T> pt_a,
+ point_ptr<T> pt_b,
+ ring_manager<T>& manager) {
+ ring_ptr<T> ring_a = pt_a->ring;
+ ring_ptr<T> ring_b = pt_b->ring;
+ bool ring_a_larger = std::fabs(ring_a->area()) > std::fabs(ring_b->area());
+ auto path = find_start_and_end_of_collinear_edges(pt_a, pt_b);
+ // This should result in two rings becoming one.
+ auto results = fix_collinear_path(path);
+ if (results.pt1 == nullptr) {
+ remove_ring(ring_a, manager, false);
+ remove_ring(ring_b, manager, false);
+ return;
+ }
+ // Rings should merge into a single ring of the same orientation.
+ // Therefore, we we will need to replace one ring with the other
+ ring_ptr<T> merged_ring = ring_a_larger ? ring_a : ring_b;
+ ring_ptr<T> deleted_ring = ring_a_larger ? ring_b : ring_a;
+
+ merged_ring->points = results.pt1;
+ update_points_ring(merged_ring);
+ merged_ring->recalculate_stats();
+ if (merged_ring->size() < 3) {
+ remove_ring_and_points(merged_ring, manager, false);
+ }
+ remove_ring(deleted_ring, manager, false);
+}
+
+template <typename T>
+bool remove_duplicate_points(point_ptr<T> pt_a, point_ptr<T> pt_b, ring_manager<T>& manager) {
+ if (pt_a->ring == pt_b->ring) {
+ if (pt_a->next == pt_b) {
+ pt_a->next = pt_b->next;
+ pt_a->next->prev = pt_a;
+ pt_b->next = nullptr;
+ pt_b->prev = nullptr;
+ pt_b->ring = nullptr;
+ if (pt_a->ring->points == pt_b) {
+ pt_a->ring->points = pt_a;
+ }
+ return true;
+ } else if (pt_b->next == pt_a) {
+ pt_a->prev = pt_b->prev;
+ pt_a->prev->next = pt_a;
+ pt_b->next = nullptr;
+ pt_b->prev = nullptr;
+ pt_b->ring = nullptr;
+ if (pt_a->ring->points == pt_b) {
+ pt_a->ring->points = pt_a;
+ }
+ return true;
+ }
+ }
+ while (*pt_a->next == *pt_a && pt_a->next != pt_a) {
+ point_ptr<T> remove = pt_a->next;
+ pt_a->next = remove->next;
+ pt_a->next->prev = pt_a;
+ remove->next = nullptr;
+ remove->prev = nullptr;
+ remove->ring = nullptr;
+ if (pt_a->ring->points == remove) {
+ pt_a->ring->points = pt_a;
+ }
+ }
+ while (*pt_a->prev == *pt_a && pt_a->prev != pt_a) {
+ point_ptr<T> remove = pt_a->prev;
+ pt_a->prev = remove->prev;
+ pt_a->prev->next = pt_a;
+ remove->next = nullptr;
+ remove->prev = nullptr;
+ remove->ring = nullptr;
+ if (pt_a->ring->points == remove) {
+ pt_a->ring->points = pt_a;
+ }
+ }
+ if (pt_a->next == pt_a) {
+ remove_ring_and_points(pt_a->ring, manager, false);
+ return true;
+ }
+ if (pt_b->ring == nullptr) {
+ return true;
+ }
+ while (*pt_b->next == *pt_b && pt_b->next != pt_b) {
+ point_ptr<T> remove = pt_b->next;
+ pt_b->next = remove->next;
+ pt_b->next->prev = pt_b;
+ remove->next = nullptr;
+ remove->prev = nullptr;
+ remove->ring = nullptr;
+ if (pt_b->ring->points == remove) {
+ pt_b->ring->points = pt_b;
+ }
+ }
+ while (*pt_b->prev == *pt_b && pt_b->prev != pt_b) {
+ point_ptr<T> remove = pt_b->prev;
+ pt_b->prev = remove->prev;
+ pt_b->prev->next = pt_b;
+ remove->next = nullptr;
+ remove->prev = nullptr;
+ remove->ring = nullptr;
+ if (pt_b->ring->points == remove) {
+ pt_b->ring->points = pt_b;
+ }
+ }
+ if (pt_b->next == pt_b) {
+ remove_ring_and_points(pt_b->ring, manager, false);
+ return true;
+ }
+ if (pt_a->ring == nullptr) {
+ return true;
+ }
+ return false;
+}
+
+template <typename T>
+bool process_collinear_edges(point_ptr<T> pt_a, point_ptr<T> pt_b, ring_manager<T>& manager) {
+ // Neither point assigned to a ring (deleted points)
+ if (!pt_a->ring || !pt_b->ring) {
+ return false;
+ }
+
+ if (remove_duplicate_points(pt_a, pt_b, manager)) {
+ return true;
+ }
+
+ if (!has_collinear_edge(pt_a, pt_b)) {
+ if (pt_a->ring == pt_b->ring) {
+ correct_self_intersection(pt_a, pt_b, manager);
+ return true;
+ }
+ return false;
+ }
+
+ if (pt_a->ring == pt_b->ring) {
+ process_collinear_edges_same_ring(pt_a, pt_b, manager);
+ } else {
+ process_collinear_edges_different_rings(pt_a, pt_b, manager);
+ }
+ return true;
+}
+
+template <typename T>
+void correct_collinear_repeats(ring_manager<T>& manager,
+ point_vector_itr<T> const& begin,
+ point_vector_itr<T> const& end) {
+ for (auto itr1 = begin; itr1 != end; ++itr1) {
+ if ((*itr1)->ring == nullptr) {
+ continue;
+ }
+ for (auto itr2 = begin; itr2 != end;) {
+ if ((*itr1)->ring == nullptr) {
+ break;
+ }
+ if ((*itr2)->ring == nullptr || *itr2 == *itr1) {
+ ++itr2;
+ continue;
+ }
+ if (process_collinear_edges(*itr1, *itr2, manager)) {
+ itr2 = begin;
+ } else {
+ ++itr2;
+ }
+ }
+ }
+}
+
+template <typename T>
+void correct_collinear_edges(ring_manager<T>& manager) {
+
+ if (manager.all_points.size() < 2) {
+ return;
+ }
+ std::size_t count = 0;
+ auto prev_itr = manager.all_points.begin();
+ auto itr = std::next(prev_itr);
+ while (itr != manager.all_points.end()) {
+ if (*(*prev_itr) == *(*(itr))) {
+ ++count;
+ ++prev_itr;
+ ++itr;
+ if (itr != manager.all_points.end()) {
+ continue;
+ } else {
+ ++prev_itr;
+ }
+ } else {
+ ++prev_itr;
+ ++itr;
+ }
+ if (count == 0) {
+ continue;
+ }
+ auto first = prev_itr;
+ std::advance(first, -(static_cast<int>(count) + 1));
+ correct_collinear_repeats(manager, first, prev_itr);
+ count = 0;
+ }
+}
+
+template <typename T>
+void correct_tree(ring_manager<T>& manager) {
+
+ // It is possible that vatti resulted in some parent child
+ // relationships that are not quite right, therefore, we
+ // need to just rebuild the entire tree of rings
+
+ // First lets process the rings in order of size from largest
+ // area to smallest, we know right away that no smaller ring could ever
+ // contain a larger ring so we can use this to our advantage
+ // as we iterate over the rings.
+ using rev_itr = typename ring_vector<T>::reverse_iterator;
+ ring_vector<T> sorted_rings = sort_rings_largest_to_smallest(manager);
+ for (auto itr = sorted_rings.begin(); itr != sorted_rings.end(); ++itr) {
+ if ((*itr)->points == nullptr) {
+ continue;
+ }
+ if ((*itr)->size() < 3 || value_is_zero((*itr)->area())) {
+ remove_ring_and_points(*itr, manager, false);
+ continue;
+ }
+ (*itr)->corrected = true;
+ bool found = false;
+ // Search in reverse from the current iterator back to the begining
+ // to see if any of those rings might be its parent.
+ for (auto r_itr = rev_itr(itr); r_itr != sorted_rings.rend(); ++r_itr) {
+ // If orientations are not different, this can't be its parent.
+ if ((*r_itr)->is_hole() == (*itr)->is_hole()) {
+ continue;
+ }
+ if (poly2_contains_poly1(*itr, *r_itr)) {
+ reassign_as_child(*itr, *r_itr, manager);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if ((*itr)->is_hole()) {
+ throw std::runtime_error("Could not properly place hole to a parent.");
+ } else {
+ // Assign to base of tree by passing nullptr
+ reassign_as_child(*itr, static_cast<ring_ptr<T>>(nullptr), manager);
+ }
+ }
+ }
+}
+
+template <typename T>
+bool correct_self_intersections(ring_manager<T>& manager, bool correct_tree) {
+ bool fixed_intersections = false;
+ auto sorted_rings = sort_rings_smallest_to_largest(manager);
+ for (auto const& r : sorted_rings) {
+ if (correct_ring_self_intersections(manager, r, correct_tree)) {
+ fixed_intersections = true;
+ }
+ }
+ return fixed_intersections;
+}
+
+template <typename T>
+void correct_topology(ring_manager<T>& manager) {
+
+ // Sort all the points, this will be used for the locating of chained rings
+ // and the collinear edges and only needs to be done once.
+ std::stable_sort(manager.all_points.begin(), manager.all_points.end(), point_ptr_cmp<T>());
+
+ // Initially the orientations of the rings
+ // could be incorrect, we need to adjust them
+ correct_orientations(manager);
+
+ // We should only have to fix collinear edges once.
+ // During this we also correct self intersections
+ correct_collinear_edges(manager);
+
+ correct_self_intersections(manager, false);
+
+ correct_tree(manager);
+
+ bool fixed_intersections = true;
+ while (fixed_intersections) {
+ correct_chained_rings(manager);
+ fixed_intersections = correct_self_intersections(manager, true);
+ }
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/util.hpp b/include/mapbox/geometry/wagyu/util.hpp
new file mode 100644
index 0000000..622d4fc
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/util.hpp
@@ -0,0 +1,85 @@
+#pragma once
+
+#include <cmath>
+
+#include <mapbox/geometry/point.hpp>
+#include <mapbox/geometry/polygon.hpp>
+#include <mapbox/geometry/wagyu/point.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+double area(mapbox::geometry::linear_ring<T> const& poly) {
+ std::size_t size = poly.size();
+ if (size < 3) {
+ return 0.0;
+ }
+
+ double a = 0.0;
+ auto itr = poly.begin();
+ auto itr_prev = poly.end();
+ --itr_prev;
+ a += static_cast<double>(itr_prev->x + itr->x) * static_cast<double>(itr_prev->y - itr->y);
+ ++itr;
+ itr_prev = poly.begin();
+ for (; itr != poly.end(); ++itr, ++itr_prev) {
+ a += static_cast<double>(itr_prev->x + itr->x) * static_cast<double>(itr_prev->y - itr->y);
+ }
+ return -a * 0.5;
+}
+
+inline bool value_is_zero(double val) {
+ return std::fabs(val) < (5.0 * std::numeric_limits<double>::epsilon());
+}
+
+inline bool values_are_equal(double x, double y) {
+ return value_is_zero(x - y);
+}
+
+inline bool greater_than_or_equal(double x, double y) {
+ return x > y || values_are_equal(x, y);
+}
+
+template <typename T>
+bool slopes_equal(mapbox::geometry::point<T> const& pt1,
+ mapbox::geometry::point<T> const& pt2,
+ mapbox::geometry::point<T> const& pt3) {
+ return (pt1.y - pt2.y) * (pt2.x - pt3.x) == (pt1.x - pt2.x) * (pt2.y - pt3.y);
+}
+
+template <typename T>
+bool slopes_equal(mapbox::geometry::wagyu::point<T> const& pt1,
+ mapbox::geometry::wagyu::point<T> const& pt2,
+ mapbox::geometry::point<T> const& pt3) {
+ return (pt1.y - pt2.y) * (pt2.x - pt3.x) == (pt1.x - pt2.x) * (pt2.y - pt3.y);
+}
+
+template <typename T>
+bool slopes_equal(mapbox::geometry::wagyu::point<T> const& pt1,
+ mapbox::geometry::wagyu::point<T> const& pt2,
+ mapbox::geometry::wagyu::point<T> const& pt3) {
+ return (pt1.y - pt2.y) * (pt2.x - pt3.x) == (pt1.x - pt2.x) * (pt2.y - pt3.y);
+}
+
+template <typename T>
+bool slopes_equal(mapbox::geometry::point<T> const& pt1,
+ mapbox::geometry::point<T> const& pt2,
+ mapbox::geometry::point<T> const& pt3,
+ mapbox::geometry::point<T> const& pt4) {
+ return (pt1.y - pt2.y) * (pt3.x - pt4.x) == (pt1.x - pt2.x) * (pt3.y - pt4.y);
+}
+
+template <typename T>
+inline T wround(double value) {
+ return static_cast<T>(std::llround(value));
+}
+
+template <>
+inline std::int64_t wround<std::int64_t>(double value) {
+ return std::llround(value);
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/vatti.hpp b/include/mapbox/geometry/wagyu/vatti.hpp
new file mode 100644
index 0000000..e75e72c
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/vatti.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <algorithm>
+#include <set>
+
+#include <mapbox/geometry/wagyu/active_bound_list.hpp>
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/intersect_util.hpp>
+#include <mapbox/geometry/wagyu/local_minimum.hpp>
+#include <mapbox/geometry/wagyu/local_minimum_util.hpp>
+#include <mapbox/geometry/wagyu/process_horizontal.hpp>
+#include <mapbox/geometry/wagyu/process_maxima.hpp>
+#include <mapbox/geometry/wagyu/ring.hpp>
+#include <mapbox/geometry/wagyu/ring_util.hpp>
+#include <mapbox/geometry/wagyu/util.hpp>
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+void execute_vatti(local_minimum_list<T>& minima_list,
+ ring_manager<T>& manager,
+ clip_type cliptype,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+ active_bound_list<T> active_bounds;
+ scanbeam_list<T> scanbeam;
+ T scanline_y = std::numeric_limits<T>::max();
+
+ local_minimum_ptr_list<T> minima_sorted;
+ minima_sorted.reserve(minima_list.size());
+ for (auto& lm : minima_list) {
+ minima_sorted.push_back(&lm);
+ }
+ std::stable_sort(minima_sorted.begin(), minima_sorted.end(), local_minimum_sorter<T>());
+ local_minimum_ptr_list_itr<T> current_lm = minima_sorted.begin();
+ // std::clog << output_all_edges(minima_sorted) << std::endl;
+
+ setup_scanbeam(minima_list, scanbeam);
+ manager.current_hp_itr = manager.hot_pixels.begin();
+
+ while (pop_from_scanbeam(scanline_y, scanbeam) || current_lm != minima_sorted.end()) {
+
+ process_intersections(scanline_y, active_bounds, cliptype, subject_fill_type,
+ clip_fill_type, manager);
+
+ update_current_hp_itr(scanline_y, manager);
+
+ // First we process bounds that has already been added to the active bound list --
+ // if the active bound list is empty local minima that are at this scanline_y and
+ // have a horizontal edge at the local minima will be processed
+ process_edges_at_top_of_scanbeam(scanline_y, active_bounds, scanbeam, minima_sorted,
+ current_lm, manager, cliptype, subject_fill_type,
+ clip_fill_type);
+
+ // Next we will add local minima bounds to the active bounds list that are on the local
+ // minima queue at
+ // this current scanline_y
+ insert_local_minima_into_ABL(scanline_y, minima_sorted, current_lm, active_bounds, manager,
+ scanbeam, cliptype, subject_fill_type, clip_fill_type);
+ }
+}
+}
+}
+}
diff --git a/include/mapbox/geometry/wagyu/wagyu.hpp b/include/mapbox/geometry/wagyu/wagyu.hpp
new file mode 100644
index 0000000..784b9fb
--- /dev/null
+++ b/include/mapbox/geometry/wagyu/wagyu.hpp
@@ -0,0 +1,141 @@
+#pragma once
+
+#include <list>
+
+#include <mapbox/geometry/box.hpp>
+#include <mapbox/geometry/line_string.hpp>
+#include <mapbox/geometry/multi_polygon.hpp>
+#include <mapbox/geometry/polygon.hpp>
+
+#include <mapbox/geometry/wagyu/build_local_minima_list.hpp>
+#include <mapbox/geometry/wagyu/build_result.hpp>
+#include <mapbox/geometry/wagyu/config.hpp>
+#include <mapbox/geometry/wagyu/local_minimum.hpp>
+#include <mapbox/geometry/wagyu/snap_rounding.hpp>
+#include <mapbox/geometry/wagyu/topology_correction.hpp>
+#include <mapbox/geometry/wagyu/vatti.hpp>
+
+#define WAGYU_MAJOR_VERSION 0
+#define WAGYU_MINOR_VERSION 4
+#define WAGYU_PATCH_VERSION 2
+
+#define WAGYU_VERSION \
+ (WAGYU_MAJOR_VERSION * 100000) + (WAGYU_MINOR_VERSION * 100) + (WAGYU_PATCH_VERSION)
+
+namespace mapbox {
+namespace geometry {
+namespace wagyu {
+
+template <typename T>
+class wagyu {
+private:
+ local_minimum_list<T> minima_list;
+ bool reverse_output;
+
+ wagyu(wagyu const&) = delete;
+ wagyu& operator=(wagyu const&) = delete;
+
+public:
+ wagyu() : minima_list(), reverse_output(false) {
+ }
+
+ ~wagyu() {
+ clear();
+ }
+
+ template <typename T2>
+ bool add_ring(mapbox::geometry::linear_ring<T2> const& pg,
+ polygon_type p_type = polygon_type_subject) {
+ return add_linear_ring(pg, minima_list, p_type);
+ }
+
+ template <typename T2>
+ bool add_polygon(mapbox::geometry::polygon<T2> const& ppg,
+ polygon_type p_type = polygon_type_subject) {
+ bool result = false;
+ for (auto const& r : ppg) {
+ if (add_ring(r, p_type)) {
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ void reverse_rings(bool value) {
+ reverse_output = value;
+ }
+
+ void clear() {
+ minima_list.clear();
+ }
+
+ mapbox::geometry::box<T> get_bounds() {
+ mapbox::geometry::point<T> min = { 0, 0 };
+ mapbox::geometry::point<T> max = { 0, 0 };
+ if (minima_list.empty()) {
+ return mapbox::geometry::box<T>(min, max);
+ }
+ bool first_set = false;
+ for (auto const& lm : minima_list) {
+ if (!lm.left_bound.edges.empty()) {
+ if (!first_set) {
+ min = lm.left_bound.edges.front().top;
+ max = lm.left_bound.edges.back().bot;
+ first_set = true;
+ } else {
+ min.y = std::min(min.y, lm.left_bound.edges.front().top.y);
+ max.y = std::max(max.y, lm.left_bound.edges.back().bot.y);
+ max.x = std::max(max.x, lm.left_bound.edges.back().top.x);
+ min.x = std::min(min.x, lm.left_bound.edges.back().top.x);
+ }
+ for (auto const& e : lm.left_bound.edges) {
+ max.x = std::max(max.x, e.bot.x);
+ min.x = std::min(min.x, e.bot.x);
+ }
+ }
+ if (!lm.right_bound.edges.empty()) {
+ if (!first_set) {
+ min = lm.right_bound.edges.front().top;
+ max = lm.right_bound.edges.back().bot;
+ first_set = true;
+ } else {
+ min.y = std::min(min.y, lm.right_bound.edges.front().top.y);
+ max.y = std::max(max.y, lm.right_bound.edges.back().bot.y);
+ max.x = std::max(max.x, lm.right_bound.edges.back().top.x);
+ min.x = std::min(min.x, lm.right_bound.edges.back().top.x);
+ }
+ for (auto const& e : lm.right_bound.edges) {
+ max.x = std::max(max.x, e.bot.x);
+ min.x = std::min(min.x, e.bot.x);
+ }
+ }
+ }
+ return mapbox::geometry::box<T>(min, max);
+ }
+
+ template <typename T2>
+ bool execute(clip_type cliptype,
+ mapbox::geometry::multi_polygon<T2>& solution,
+ fill_type subject_fill_type,
+ fill_type clip_fill_type) {
+
+ if (minima_list.empty()) {
+ return false;
+ }
+
+ ring_manager<T> manager;
+
+ build_hot_pixels(minima_list, manager);
+
+ execute_vatti(minima_list, manager, cliptype, subject_fill_type, clip_fill_type);
+
+ correct_topology(manager);
+
+ build_result(solution, manager, reverse_output);
+
+ return true;
+ }
+};
+}
+}
+}
diff --git a/mason.sh b/mason.sh
new file mode 100755
index 0000000..fbf13cf
--- /dev/null
+++ b/mason.sh
@@ -0,0 +1,189 @@
+#!/usr/bin/env bash
+
+# Mason Client Version 1.0.0
+
+# See below for `set -euo pipefail`
+
+# Print file + line number when not in CLI mode
+if [[ "$0" != "$BASH_SOURCE" ]]; then
+function mason_error {
+ local _LINE _FN _FILE
+ read _LINE _FN _FILE <<< "`caller 1`"
+ if [ -t 1 ]; then
+ >&2 echo -e "\033[1m\033[31m$@ in ${_FILE} on line ${_LINE}\033[0m"
+ else
+ >&2 echo "$@ in ${_FILE} on line ${_LINE}"
+ fi
+}
+else
+function mason_error {
+ if [ -t 1 ]; then
+ >&2 echo -e "\033[1m\033[31m$@\033[0m"
+ else
+ >&2 echo "$@"
+ fi
+}
+fi
+
+function mason_info {
+ if [ -t 1 ]; then
+ >&2 echo -e "\033[1m\033[36m$@\033[0m"
+ else
+ >&2 echo "$@"
+ fi
+}
+
+function mason_detect_platform {
+ # Determine platform
+ if [[ -z "${MASON_PLATFORM:-}" ]]; then
+ if [[ "`uname -s`" = 'Darwin' ]]; then
+ MASON_PLATFORM="osx"
+ else
+ MASON_PLATFORM="linux"
+ fi
+ fi
+
+ # Determine platform version string
+ if [[ -z "${MASON_PLATFORM_VERSION:-}" ]]; then
+ MASON_PLATFORM_VERSION="`uname -m`"
+ fi
+}
+
+function mason_trim {
+ local _TMP="${1#"${1%%[![:space:]]*}"}"
+ echo -n "${_TMP%"${_TMP##*[![:space:]]}"}"
+}
+
+function mason_uppercase {
+ echo -n "$1" | tr "[a-z]" "[A-Z]"
+}
+
+function mason_use {
+ local _HEADER_ONLY=false _PACKAGE _SAFE_PACKAGE _VERSION _PLATFORM_ID _SLUG _INSTALL_PATH _INSTALL_PATH_RELATIVE
+
+ while [[ $# -gt 0 ]]; do
+ if [[ $1 == "--header-only" ]]; then
+ _HEADER_ONLY=true
+ elif [[ -z "${_PACKAGE:-}" ]]; then
+ _PACKAGE="$1"
+ elif [[ -z "${_VERSION:-}" ]]; then
+ _VERSION="$1"
+ else
+ mason_error "[Mason] mason_use() called with unrecognized arguments: '$@'"
+ exit 1
+ fi
+ shift
+ done
+
+ if [[ -z "${_PACKAGE:-}" ]]; then
+ mason_error "[Mason] No package name given"
+ exit 1
+ fi
+
+ # Create a package name that we can use as shell variable names.
+ _SAFE_PACKAGE="${_PACKAGE//[![:alnum:]]/_}"
+
+ if [[ -z "${_VERSION:-}" ]]; then
+ mason_error "[Mason] Specifying a version is required"
+ exit 1
+ fi
+
+ _PLATFORM_ID="${MASON_PLATFORM}-${MASON_PLATFORM_VERSION}"
+ if [[ "${_HEADER_ONLY}" = true ]] ; then
+ _PLATFORM_ID="headers"
+ fi
+
+ _SLUG="${_PLATFORM_ID}/${_PACKAGE}/${_VERSION}"
+ _INSTALL_PATH="${MASON_PACKAGE_DIR}/${_SLUG}"
+ _INSTALL_PATH_RELATIVE="${_INSTALL_PATH#`pwd`/}"
+
+ if [[ ! -d "${_INSTALL_PATH}" ]]; then
+ local _CACHE_PATH _URL _CACHE_DIR _ERROR
+ _CACHE_PATH="${MASON_PACKAGE_DIR}/.binaries/${_SLUG}.tar.gz"
+ if [ ! -f "${_CACHE_PATH}" ]; then
+ # Download the package
+ _URL="${MASON_REPOSITORY}/${_SLUG}.tar.gz"
+ mason_info "[Mason] Downloading package ${_URL}..."
+ _CACHE_DIR="`dirname "${_CACHE_PATH}"`"
+ mkdir -p "${_CACHE_DIR}"
+ if ! _ERROR=$(curl --retry 3 --silent --fail --show-error --location "${_URL}" --output "${_CACHE_PATH}.tmp" 2>&1); then
+ mason_error "[Mason] ${_ERROR}"
+ exit 1
+ else
+ # We downloaded to a temporary file to prevent half-finished downloads
+ mv "${_CACHE_PATH}.tmp" "${_CACHE_PATH}"
+ fi
+ fi
+
+ # Unpack the package
+ mason_info "[Mason] Unpacking package to ${_INSTALL_PATH_RELATIVE}..."
+ mkdir -p "${_INSTALL_PATH}"
+ tar xzf "${_CACHE_PATH}" -C "${_INSTALL_PATH}"
+ fi
+
+ # Error out if there is no config file.
+ if [[ ! -f "${_INSTALL_PATH}/mason.ini" ]]; then
+ mason_error "[Mason] Could not find mason.ini for package ${_PACKAGE} ${_VERSION}"
+ exit 1
+ fi
+
+ # We use this instead of declare, since it declare makes local variables when run in a function.
+ read "MASON_PACKAGE_${_SAFE_PACKAGE}_PREFIX" <<< "${_INSTALL_PATH}"
+
+ # Load the configuration from the ini file
+ local _LINE _KEY _VALUE
+ while read _LINE; do
+ _KEY="`mason_trim "${_LINE%%=*}"`"
+ if [[ "${_KEY}" =~ ^[a-z_]+$ ]]; then
+ _KEY="`mason_uppercase "${_KEY}"`" # Convert to uppercase
+ _LINE="${_LINE%%;*}" # Trim trailing comments
+ _VALUE="`mason_trim "${_LINE#*=}"`"
+ _VALUE="${_VALUE//\{prefix\}/${_INSTALL_PATH}}" # Replace {prefix}
+ read "MASON_PACKAGE_${_SAFE_PACKAGE}_${_KEY}" <<< "${_VALUE}"
+ fi
+ done < "${_INSTALL_PATH}/mason.ini"
+
+ # We're using the fact that this variable is declared to pass back the package name we parsed
+ # from the argument string to avoid polluting the global namespace.
+ if [ ! -z ${_MASON_SAFE_PACKAGE_NAME+x} ]; then
+ _MASON_SAFE_PACKAGE_NAME="${_SAFE_PACKAGE}"
+ fi
+}
+
+function mason_cli {
+ local _MASON_SAFE_PACKAGE_NAME= _PROP _VAR
+ if [[ $# -lt 1 ]]; then
+ mason_error "[Mason] Usage: $0 <property> [--header-only] <name> <version>"
+ mason_error "[Mason] <property> is one of 'include_dirs', 'definitions', 'options', 'ldflags', 'static_libs', or any custom variables in the package's mason.ini."
+ exit 1
+ fi
+
+ # Store first argument and pass the remaining arguments to mason_use
+ _PROP="`mason_uppercase "$1"`"
+ shift
+ mason_use "$@"
+
+ # Optionally print variables
+ _VAR="MASON_PACKAGE_${_MASON_SAFE_PACKAGE_NAME}_${_PROP}"
+ if [[ ! -z "${!_VAR:-}" ]]; then
+ echo "${!_VAR}"
+ fi
+}
+
+# Directory where Mason packages are located; typically ends with mason_packages
+if [[ -z "${MASON_PACKAGE_DIR:-}" ]]; then
+ MASON_PACKAGE_DIR="`pwd`/mason_packages"
+fi
+
+# URL prefix of where packages are located.
+if [[ -z "${MASON_REPOSITORY:-}" ]]; then
+ MASON_REPOSITORY="https://mason-binaries.s3.amazonaws.com"
+fi
+
+mason_detect_platform
+
+# Print variables if this shell script is invoked directly.
+if [[ "$0" = "$BASH_SOURCE" ]]; then
+ set -euo pipefail
+ mason_cli "$@"
+fi
diff --git a/scripts/coverage.sh b/scripts/coverage.sh
new file mode 100755
index 0000000..22589bf
--- /dev/null
+++ b/scripts/coverage.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+set -eu
+set -o pipefail
+
+./.mason/mason install clang++ 4.0.0
+./.mason/mason install llvm-cov 4.0.0
+
+export CXX="$(./.mason/mason prefix clang++ 4.0.0)/bin/clang++"
+export PROF=$(./.mason/mason prefix llvm-cov 4.0.0)/bin/llvm-profdata
+export COV=$(./.mason/mason prefix llvm-cov 4.0.0)/bin/llvm-cov
+export CXXFLAGS="-fprofile-instr-generate -fcoverage-mapping"
+export LDFLAGS="-fprofile-instr-generate"
+export LLVM_PROFILE_FILE="code-%p.profraw"
+
+function run_cov() {
+ local target=$1
+ local binary=$2
+ local cmd=$3
+ local name=$4
+
+ echo "** cleaning"
+ make clean
+ make ${target}
+ rm -f code-*.profraw
+ rm -f *profdata
+ echo "** running '${name}'"
+ ${cmd}
+ echo "** merging '${name}' results"
+ ${PROF} merge -output="${name}.profdata" code-*.profraw
+ # note: demangling is not working - https://llvm.org/bugs/show_bug.cgi?id=31394
+ ${COV} report ${binary} -instr-profile="${name}.profdata" -use-color
+ ${COV} show ${binary} -instr-profile="${name}.profdata" include/mapbox/geometry/wagyu/*hpp -use-color --format html > /tmp/${name}.html
+ rm -f code-*.profraw
+ open /tmp/${name}.html
+ echo "** open /tmp/${name}.html"
+}
+
+# unit test report
+run_cov build-debug ./test ./test "wagyu-unit-test-coverage"
+run_cov build-fixture-tester ./fixture-tester "./tests/run-geometry-tests.sh ./fixture-tester" "wagyu-fixture-test-coverage"
diff --git a/tests/benchmark.cpp b/tests/benchmark.cpp
new file mode 100644
index 0000000..a7d5129
--- /dev/null
+++ b/tests/benchmark.cpp
@@ -0,0 +1,387 @@
+#include "util/boost_geometry_adapters.hpp"
+#include <chrono>
+#include <cstdio>
+#include <iostream>
+#include <mapbox/geometry/polygon.hpp>
+#include <mapbox/geometry/wagyu/wagyu.hpp>
+#include <ostream>
+#include <sstream>
+
+#include "clipper.hpp"
+#include "rapidjson/writer.h"
+#include <rapidjson/document.h>
+#include <rapidjson/filereadstream.h>
+#include <rapidjson/filewritestream.h>
+
+using namespace rapidjson;
+using namespace mapbox::geometry::wagyu;
+using value_type = std::int64_t;
+
+struct Options {
+ clip_type operation = clip_type_intersection;
+ fill_type fill = fill_type_even_odd;
+ char* subject_file;
+ char* clip_file;
+ std::size_t iter = 100;
+} options;
+
+void log_ring(mapbox::geometry::polygon<std::int64_t> const& p) {
+ bool first = true;
+ std::clog << "[";
+ for (auto const& r : p) {
+ if (first) {
+ std::clog << "[";
+ first = false;
+ } else {
+ std::clog << ",[";
+ }
+ bool first2 = true;
+ for (auto const& pt : r) {
+ if (first2) {
+ std::clog << "[";
+ first2 = false;
+ } else {
+ std::clog << ",[";
+ }
+ std::clog << pt.x << "," << pt.y << "]";
+ }
+ std::clog << "]";
+ }
+ std::clog << "]" << std::endl;
+}
+
+void log_ring(mapbox::geometry::multi_polygon<std::int64_t> const& mp) {
+ bool first_p = true;
+ std::clog << "[";
+ for (auto const& p : mp) {
+ bool first = true;
+ if (first_p) {
+ std::clog << "[";
+ first_p = false;
+ } else {
+ std::clog << ",[";
+ }
+ for (auto const& r : p) {
+ if (first) {
+ std::clog << "[";
+ first = false;
+ } else {
+ std::clog << ",[";
+ }
+ bool first2 = true;
+ for (auto const& pt : r) {
+ if (first2) {
+ std::clog << "[";
+ first2 = false;
+ } else {
+ std::clog << ",[";
+ }
+ std::clog << pt.x << "," << pt.y << "]";
+ }
+ std::clog << "]";
+ }
+ std::clog << "]";
+ }
+ std::clog << "]" << std::endl;
+}
+
+mapbox::geometry::polygon<value_type> parse_file(const char* file_path) {
+ // todo safety checks opening files
+ FILE* file = fopen(file_path, "r");
+ char read_buffer[65536];
+ FileReadStream in_stream(file, read_buffer, sizeof(read_buffer));
+ Document document;
+ document.ParseStream<0, UTF8<>, FileReadStream>(in_stream);
+
+ if (!document.IsArray()) {
+ throw std::runtime_error(("Input file (" + std::string(file_path) + ") is not valid json"));
+ }
+ // todo catch parsing errors
+ mapbox::geometry::polygon<value_type> poly;
+ for (SizeType i = 0; i < document.Size(); ++i) {
+ mapbox::geometry::linear_ring<value_type> lr;
+
+ if (!document[i].IsArray()) {
+ throw std::runtime_error("A ring (in " + std::string(file_path) +
+ ") is not a valid json array");
+ }
+ for (SizeType j = 0; j < document[i].Size(); ++j) {
+ lr.push_back({ document[i][j][0].GetInt(), document[i][j][1].GetInt() });
+ }
+ poly.emplace_back(lr);
+ }
+ fclose(file);
+ return poly;
+}
+
+void polys_to_json(Document& output, std::vector<mapbox::geometry::polygon<value_type>>& solution) {
+ output.SetArray();
+ Document::AllocatorType& allocator = output.GetAllocator();
+ output.Reserve(solution.size(), allocator);
+
+ // Polygons
+ for (std::size_t p = 0; p < solution.size(); ++p) {
+ output.PushBack(Value().SetArray(), allocator);
+ output[p].Reserve(solution[p].size(), allocator);
+
+ // Rings
+ for (std::size_t r = 0; r < solution[p].size(); ++r) {
+ output[p].PushBack(Value().SetArray(), allocator);
+ output[p][r].Reserve(solution[p][r].size(), allocator);
+
+ // Coordinates
+ for (auto coord : solution[p][r]) {
+ Value cvalue;
+ cvalue.SetArray();
+ cvalue.PushBack(Value().SetInt(coord.x), allocator);
+ cvalue.PushBack(Value().SetInt(coord.y), allocator);
+ output[p][r].PushBack(cvalue, allocator);
+ }
+ }
+ }
+}
+
+void parse_options(int argc, char* const argv[]) {
+ bool skip = false;
+ for (int i = 1; i < argc; ++i) {
+ // if this argument is already being used
+ // as the value for a flag, we skip it
+ if (skip) {
+ skip = false;
+ continue;
+ }
+
+ if (strcmp(argv[i], "-t") == 0) {
+ std::string type = argv[i + 1];
+ if (type.compare("union") == 0) {
+ options.operation = clip_type_union;
+ } else if (type.compare("intersection") == 0) {
+ options.operation = clip_type_intersection;
+ } else if (type.compare("difference") == 0) {
+ options.operation = clip_type_difference;
+ } else if (type.compare("x_or") == 0) {
+ options.operation = clip_type_x_or;
+ }
+ skip = true;
+ } else if (strcmp(argv[i], "-f") == 0) {
+ std::string type = argv[i + 1];
+ if (type.compare("even_odd") == 0) {
+ options.fill = fill_type_even_odd;
+ } else if (type.compare("non_zero") == 0) {
+ options.fill = fill_type_non_zero;
+ } else if (type.compare("positive") == 0) {
+ options.fill = fill_type_positive;
+ } else if (type.compare("negative") == 0) {
+ options.fill = fill_type_negative;
+ }
+ skip = true;
+ } else if (strcmp(argv[i], "-i") == 0) {
+ std::istringstream ss(argv[i + 1]);
+ if (!(ss >> options.iter)) {
+ std::clog << "Invalid number " << argv[1] << std::endl;
+ }
+ skip = true;
+ } else {
+ // If we didn't catch this argument as a flag or a flag value,
+ // set the input files
+ if (options.subject_file == NULL) {
+ options.subject_file = argv[i];
+ } else {
+ options.clip_file = argv[i];
+ }
+ }
+ }
+}
+
+inline void process_polynode_branch(ClipperLib::PolyNode* polynode,
+ mapbox::geometry::multi_polygon<std::int64_t>& mp) {
+ mapbox::geometry::polygon<std::int64_t> polygon;
+ polygon.push_back(std::move(polynode->Contour));
+ if (polygon.back().size() > 2) // Throw out invalid polygons
+ {
+ if (polygon.back().back() != polygon.back().front()) {
+ polygon.back().push_back(polygon.back().front());
+ }
+ double outer_area = ClipperLib::Area(polygon.back());
+ if (outer_area > 0) {
+ std::reverse(polygon.back().begin(), polygon.back().end());
+ }
+
+ // children of exterior ring are always interior rings
+ for (auto* ring : polynode->Childs) {
+ if (ring->Contour.size() < 3) {
+ continue; // Throw out invalid holes
+ }
+ double inner_area = ClipperLib::Area(ring->Contour);
+
+ if (inner_area < 0) {
+ std::reverse(ring->Contour.begin(), ring->Contour.end());
+ }
+ polygon.push_back(std::move(ring->Contour));
+ if (polygon.back().back() != polygon.back().front()) {
+ polygon.back().push_back(polygon.back().front());
+ }
+ }
+ mp.push_back(std::move(polygon));
+ }
+ for (auto* ring : polynode->Childs) {
+ for (auto* sub_ring : ring->Childs) {
+ process_polynode_branch(sub_ring, mp);
+ }
+ }
+}
+
+inline ClipperLib::PolyFillType get_angus_fill_type(fill_type type) {
+ switch (type) {
+ case fill_type_even_odd:
+ return ClipperLib::pftEvenOdd;
+ case fill_type_non_zero:
+ return ClipperLib::pftNonZero;
+ case fill_type_positive:
+ return ClipperLib::pftPositive;
+ case fill_type_negative:
+ return ClipperLib::pftNegative;
+ }
+}
+
+inline ClipperLib::ClipType get_angus_clip_type(clip_type type) {
+ switch (type) {
+ case clip_type_intersection:
+ return ClipperLib::ctIntersection;
+ case clip_type_union:
+ return ClipperLib::ctUnion;
+ case clip_type_difference:
+ return ClipperLib::ctDifference;
+ case clip_type_x_or:
+ return ClipperLib::ctXor;
+ }
+}
+
+int main(int argc, char* const argv[]) {
+ if (argc < 3) {
+ std::cout << "Error: too few parameters\n" << std::endl;
+ std::cout << "Usage:" << std::endl;
+ std::cout << " ./benchmark ./path/to/subject.json ./path/to/object.json\n" << std::endl;
+ std::cout << "Options:" << std::endl;
+ std::cout << " -t type of operation (default: union)\n" << std::endl;
+ std::cout << " -f fill_type (default: even_odd)\n" << std::endl;
+ std::cout << " -i number of iterations for test\n" << std::endl;
+ return -1;
+ }
+ parse_options(argc, argv);
+
+ auto poly_subject = parse_file(options.subject_file);
+ auto poly_clip = parse_file(options.clip_file);
+
+ using double_seconds = std::chrono::duration<double, std::chrono::seconds::period>;
+
+ double time_wagyu;
+ double time_angus;
+ bool wagyu_valid = true;
+ bool angus_valid = true;
+ {
+ wagyu<value_type> clipper;
+ clipper.add_polygon(poly_subject, polygon_type_subject);
+ clipper.add_polygon(poly_clip, polygon_type_clip);
+ mapbox::geometry::multi_polygon<value_type> solution;
+ clipper.execute(options.operation, solution, options.fill, fill_type_even_odd);
+
+ for (auto const& p : solution) {
+ std::string message;
+ if (!boost::geometry::is_valid(p, message)) {
+ wagyu_valid = false;
+ }
+ }
+ }
+
+ {
+ auto t1 = std::chrono::high_resolution_clock::now();
+
+ for (std::size_t i = 0; i < options.iter; ++i) {
+ wagyu<value_type> clipper;
+ clipper.add_polygon(poly_subject, polygon_type_subject);
+ clipper.add_polygon(poly_clip, polygon_type_clip);
+ mapbox::geometry::multi_polygon<value_type> solution;
+ clipper.execute(options.operation, solution, options.fill, fill_type_even_odd);
+ }
+ auto t2 = std::chrono::high_resolution_clock::now();
+ time_wagyu = double_seconds(t2 - t1).count();
+ }
+
+ ClipperLib::PolyFillType angus_fill_type = get_angus_fill_type(options.fill);
+ ClipperLib::ClipType angus_clip_type = get_angus_clip_type(options.operation);
+ {
+ ClipperLib::Clipper clipper;
+ clipper.StrictlySimple(true);
+ for (auto& r : poly_subject) {
+ // ClipperLib::CleanPolygon(r, 1.415);
+ clipper.AddPath(r, ClipperLib::ptSubject, true);
+ }
+ for (auto& r : poly_clip) {
+ clipper.AddPath(r, ClipperLib::ptClip, true);
+ }
+ ClipperLib::PolyTree polygons;
+ clipper.Execute(angus_clip_type, polygons, angus_fill_type, ClipperLib::pftEvenOdd);
+ clipper.Clear();
+ mapbox::geometry::multi_polygon<value_type> solution;
+
+ for (auto* polynode : polygons.Childs) {
+ process_polynode_branch(polynode, solution);
+ }
+
+ for (auto const& p : solution) {
+ std::string message;
+ if (!boost::geometry::is_valid(p, message)) {
+ angus_valid = false;
+ }
+ }
+ }
+
+ {
+ auto t1 = std::chrono::high_resolution_clock::now();
+
+ for (std::size_t i = 0; i < options.iter; ++i) {
+ ClipperLib::Clipper clipper;
+ clipper.StrictlySimple(true);
+ for (auto& r : poly_subject) {
+ // ClipperLib::CleanPolygon(r, 1.415);
+ clipper.AddPath(r, ClipperLib::ptSubject, true);
+ }
+ for (auto& r : poly_clip) {
+ clipper.AddPath(r, ClipperLib::ptClip, true);
+ }
+ ClipperLib::PolyTree polygons;
+ clipper.Execute(angus_clip_type, polygons, angus_fill_type, ClipperLib::pftEvenOdd);
+ clipper.Clear();
+ mapbox::geometry::multi_polygon<value_type> solution;
+
+ for (auto* polynode : polygons.Childs) {
+ process_polynode_branch(polynode, solution);
+ }
+ }
+ auto t2 = std::chrono::high_resolution_clock::now();
+ time_angus = double_seconds(t2 - t1).count();
+ }
+ if (wagyu_valid) {
+ std::clog << "\033[1;32m";
+ } else {
+ std::clog << "\033[0;31m";
+ }
+ std::clog << time_wagyu << "\033[0m, ";
+ if (angus_valid) {
+ std::clog << "\033[1;32m";
+ } else {
+ std::clog << "\033[0;31m";
+ }
+ std::clog << time_angus << "\033[0m - ";
+ double factor = time_wagyu / time_angus;
+ if (factor < 1.0) {
+ std::clog << "\033[1;34m";
+ } else {
+ std::clog << "\033[0;36m";
+ }
+ std::clog << factor << "\033[0m" << std::endl;
+ if (factor > 1.0) {
+ return -1;
+ }
+}
diff --git a/tests/catch.hpp b/tests/catch.hpp
new file mode 100644
index 0000000..391c7ab
--- /dev/null
+++ b/tests/catch.hpp
@@ -0,0 +1,9460 @@
+/*
+ * CATCH v1.1 build 3 (master branch)
+ * Generated: 2015-05-21 06:16:00.388118
+ * ----------------------------------------------------------
+ * This file has been merged from multiple headers. Please don't edit it directly
+ * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
+ *
+ * Distributed under the Boost Software License, Version 1.0. (See accompanying
+ * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+
+#define TWOBLUECUBES_CATCH_HPP_INCLUDED
+
+#ifdef __clang__
+# pragma clang system_header
+#elif defined __GNUC__
+# pragma GCC system_header
+#endif
+
+// #included from: internal/catch_suppress_warnings.h
+
+#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED
+
+#ifdef __clang__
+# ifdef __ICC // icpc defines the __clang__ macro
+# pragma warning(push)
+# pragma warning(disable: 161 1682)
+# else // __ICC
+# pragma clang diagnostic ignored "-Wglobal-constructors"
+# pragma clang diagnostic ignored "-Wvariadic-macros"
+# pragma clang diagnostic ignored "-Wc99-extensions"
+# pragma clang diagnostic ignored "-Wunused-variable"
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wpadded"
+# pragma clang diagnostic ignored "-Wc++98-compat"
+# pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+# pragma clang diagnostic ignored "-Wswitch-enum"
+# endif
+#elif defined __GNUC__
+# pragma GCC diagnostic ignored "-Wvariadic-macros"
+# pragma GCC diagnostic ignored "-Wunused-variable"
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wpadded"
+#endif
+
+#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)
+# define CATCH_IMPL
+#endif
+
+#ifdef CATCH_IMPL
+# ifndef CLARA_CONFIG_MAIN
+# define CLARA_CONFIG_MAIN_NOT_DEFINED
+# define CLARA_CONFIG_MAIN
+# endif
+#endif
+
+// #included from: internal/catch_notimplemented_exception.h
+#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED
+
+// #included from: catch_common.h
+#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED
+
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
+#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )
+
+#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr
+#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr )
+
+#include <sstream>
+#include <stdexcept>
+#include <algorithm>
+
+// #included from: catch_compiler_capabilities.h
+#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED
+
+// Detect a number of compiler features - mostly C++11/14 conformance - by compiler
+// The following features are defined:
+//
+// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported?
+// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported?
+// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods
+// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported?
+// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported
+
+// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported?
+
+// CATCH_CONFIG_SFINAE : is basic (C++03) SFINAE supported?
+// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported?
+
+// A lot of this code is based on Boost (1.53)
+
+#ifdef __clang__
+
+# if __has_feature(cxx_nullptr)
+# define CATCH_CONFIG_CPP11_NULLPTR
+# endif
+
+# if __has_feature(cxx_noexcept)
+# define CATCH_CONFIG_CPP11_NOEXCEPT
+# endif
+
+#endif // __clang__
+
+////////////////////////////////////////////////////////////////////////////////
+// Borland
+#ifdef __BORLANDC__
+
+#if (__BORLANDC__ > 0x582 )
+//#define CATCH_CONFIG_SFINAE // Not confirmed
+#endif
+
+#endif // __BORLANDC__
+
+////////////////////////////////////////////////////////////////////////////////
+// EDG
+#ifdef __EDG_VERSION__
+
+#if (__EDG_VERSION__ > 238 )
+//#define CATCH_CONFIG_SFINAE // Not confirmed
+#endif
+
+#endif // __EDG_VERSION__
+
+////////////////////////////////////////////////////////////////////////////////
+// Digital Mars
+#ifdef __DMC__
+
+#if (__DMC__ > 0x840 )
+//#define CATCH_CONFIG_SFINAE // Not confirmed
+#endif
+
+#endif // __DMC__
+
+////////////////////////////////////////////////////////////////////////////////
+// GCC
+#ifdef __GNUC__
+
+#if __GNUC__ < 3
+
+#if (__GNUC_MINOR__ >= 96 )
+//#define CATCH_CONFIG_SFINAE
+#endif
+
+#elif __GNUC__ >= 3
+
+// #define CATCH_CONFIG_SFINAE // Taking this out completely for now
+
+#endif // __GNUC__ < 3
+
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) )
+
+#define CATCH_CONFIG_CPP11_NULLPTR
+#endif
+
+#endif // __GNUC__
+
+////////////////////////////////////////////////////////////////////////////////
+// Visual C++
+#ifdef _MSC_VER
+
+#if (_MSC_VER >= 1310 ) // (VC++ 7.0+)
+//#define CATCH_CONFIG_SFINAE // Not confirmed
+#endif
+
+#if (_MSC_VER >= 1600)
+#define CATCH_CONFIG_CPP11_NULLPTR
+#endif
+
+#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015))
+#define CATCH_CONFIG_CPP11_NOEXCEPT
+#define CATCH_CONFIG_CPP11_GENERATED_METHODS
+#endif
+
+#endif // _MSC_VER
+
+// Use variadic macros if the compiler supports them
+#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \
+ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \
+ ( defined __GNUC__ && __GNUC__ >= 3 ) || \
+ ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L )
+
+#ifndef CATCH_CONFIG_NO_VARIADIC_MACROS
+#define CATCH_CONFIG_VARIADIC_MACROS
+#endif
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// C++ language feature support
+
+// catch all support for C++11
+#if (__cplusplus >= 201103L)
+
+# define CATCH_CPP11_OR_GREATER
+
+# ifndef CATCH_CONFIG_CPP11_NULLPTR
+# define CATCH_CONFIG_CPP11_NULLPTR
+# endif
+
+# ifndef CATCH_CONFIG_CPP11_NOEXCEPT
+# define CATCH_CONFIG_CPP11_NOEXCEPT
+# endif
+
+# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS
+# define CATCH_CONFIG_CPP11_GENERATED_METHODS
+# endif
+
+# ifndef CATCH_CONFIG_CPP11_IS_ENUM
+# define CATCH_CONFIG_CPP11_IS_ENUM
+# endif
+
+# ifndef CATCH_CONFIG_CPP11_TUPLE
+# define CATCH_CONFIG_CPP11_TUPLE
+# endif
+
+# ifndef CATCH_CONFIG_SFINAE
+//# define CATCH_CONFIG_SFINAE // Don't use, for now
+# endif
+
+# ifndef CATCH_CONFIG_VARIADIC_MACROS
+# define CATCH_CONFIG_VARIADIC_MACROS
+# endif
+
+#endif // __cplusplus >= 201103L
+
+// noexcept support:
+#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT)
+# define CATCH_NOEXCEPT noexcept
+# define CATCH_NOEXCEPT_IS(x) noexcept(x)
+#else
+# define CATCH_NOEXCEPT throw()
+# define CATCH_NOEXCEPT_IS(x)
+#endif
+
+namespace Catch {
+
+ class NonCopyable {
+#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ NonCopyable( NonCopyable const& ) = delete;
+ NonCopyable( NonCopyable && ) = delete;
+ NonCopyable& operator = ( NonCopyable const& ) = delete;
+ NonCopyable& operator = ( NonCopyable && ) = delete;
+#else
+ NonCopyable( NonCopyable const& info );
+ NonCopyable& operator = ( NonCopyable const& );
+#endif
+
+ protected:
+ NonCopyable() {}
+ virtual ~NonCopyable();
+ };
+
+ class SafeBool {
+ public:
+ typedef void (SafeBool::*type)() const;
+
+ static type makeSafe( bool value ) {
+ return value ? &SafeBool::trueValue : 0;
+ }
+ private:
+ void trueValue() const {}
+ };
+
+ template<typename ContainerT>
+ inline void deleteAll( ContainerT& container ) {
+ typename ContainerT::const_iterator it = container.begin();
+ typename ContainerT::const_iterator itEnd = container.end();
+ for(; it != itEnd; ++it )
+ delete *it;
+ }
+ template<typename AssociativeContainerT>
+ inline void deleteAllValues( AssociativeContainerT& container ) {
+ typename AssociativeContainerT::const_iterator it = container.begin();
+ typename AssociativeContainerT::const_iterator itEnd = container.end();
+ for(; it != itEnd; ++it )
+ delete it->second;
+ }
+
+ bool startsWith( std::string const& s, std::string const& prefix );
+ bool endsWith( std::string const& s, std::string const& suffix );
+ bool contains( std::string const& s, std::string const& infix );
+ void toLowerInPlace( std::string& s );
+ std::string toLower( std::string const& s );
+ std::string trim( std::string const& str );
+ bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
+
+ struct pluralise {
+ pluralise( std::size_t count, std::string const& label );
+
+ friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser );
+
+ std::size_t m_count;
+ std::string m_label;
+ };
+
+ struct SourceLineInfo {
+
+ SourceLineInfo();
+ SourceLineInfo( char const* _file, std::size_t _line );
+ SourceLineInfo( SourceLineInfo const& other );
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ SourceLineInfo( SourceLineInfo && ) = default;
+ SourceLineInfo& operator = ( SourceLineInfo const& ) = default;
+ SourceLineInfo& operator = ( SourceLineInfo && ) = default;
+# endif
+ bool empty() const;
+ bool operator == ( SourceLineInfo const& other ) const;
+ bool operator < ( SourceLineInfo const& other ) const;
+
+ std::string file;
+ std::size_t line;
+ };
+
+ std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info );
+
+ // This is just here to avoid compiler warnings with macro constants and boolean literals
+ inline bool isTrue( bool value ){ return value; }
+ inline bool alwaysTrue() { return true; }
+ inline bool alwaysFalse() { return false; }
+
+ void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo );
+
+ // Use this in variadic streaming macros to allow
+ // >> +StreamEndStop
+ // as well as
+ // >> stuff +StreamEndStop
+ struct StreamEndStop {
+ std::string operator+() {
+ return std::string();
+ }
+ };
+ template<typename T>
+ T const& operator + ( T const& value, StreamEndStop ) {
+ return value;
+ }
+}
+
+#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) )
+#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO );
+
+#include <ostream>
+
+namespace Catch {
+
+ class NotImplementedException : public std::exception
+ {
+ public:
+ NotImplementedException( SourceLineInfo const& lineInfo );
+ NotImplementedException( NotImplementedException const& ) {}
+
+ virtual ~NotImplementedException() CATCH_NOEXCEPT {}
+
+ virtual const char* what() const CATCH_NOEXCEPT;
+
+ private:
+ std::string m_what;
+ SourceLineInfo m_lineInfo;
+ };
+
+} // end namespace Catch
+
+///////////////////////////////////////////////////////////////////////////////
+#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO )
+
+// #included from: internal/catch_context.h
+#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED
+
+// #included from: catch_interfaces_generators.h
+#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ struct IGeneratorInfo {
+ virtual ~IGeneratorInfo();
+ virtual bool moveNext() = 0;
+ virtual std::size_t getCurrentIndex() const = 0;
+ };
+
+ struct IGeneratorsForTest {
+ virtual ~IGeneratorsForTest();
+
+ virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0;
+ virtual bool moveNext() = 0;
+ };
+
+ IGeneratorsForTest* createGeneratorsForTest();
+
+} // end namespace Catch
+
+// #included from: catch_ptr.hpp
+#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+namespace Catch {
+
+ // An intrusive reference counting smart pointer.
+ // T must implement addRef() and release() methods
+ // typically implementing the IShared interface
+ template<typename T>
+ class Ptr {
+ public:
+ Ptr() : m_p( NULL ){}
+ Ptr( T* p ) : m_p( p ){
+ if( m_p )
+ m_p->addRef();
+ }
+ Ptr( Ptr const& other ) : m_p( other.m_p ){
+ if( m_p )
+ m_p->addRef();
+ }
+ ~Ptr(){
+ if( m_p )
+ m_p->release();
+ }
+ void reset() {
+ if( m_p )
+ m_p->release();
+ m_p = NULL;
+ }
+ Ptr& operator = ( T* p ){
+ Ptr temp( p );
+ swap( temp );
+ return *this;
+ }
+ Ptr& operator = ( Ptr const& other ){
+ Ptr temp( other );
+ swap( temp );
+ return *this;
+ }
+ void swap( Ptr& other ) { std::swap( m_p, other.m_p ); }
+ T* get() { return m_p; }
+ const T* get() const{ return m_p; }
+ T& operator*() const { return *m_p; }
+ T* operator->() const { return m_p; }
+ bool operator !() const { return m_p == NULL; }
+ operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); }
+
+ private:
+ T* m_p;
+ };
+
+ struct IShared : NonCopyable {
+ virtual ~IShared();
+ virtual void addRef() const = 0;
+ virtual void release() const = 0;
+ };
+
+ template<typename T = IShared>
+ struct SharedImpl : T {
+
+ SharedImpl() : m_rc( 0 ){}
+
+ virtual void addRef() const {
+ ++m_rc;
+ }
+ virtual void release() const {
+ if( --m_rc == 0 )
+ delete this;
+ }
+
+ mutable unsigned int m_rc;
+ };
+
+} // end namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#include <memory>
+#include <vector>
+#include <stdlib.h>
+
+namespace Catch {
+
+ class TestCase;
+ class Stream;
+ struct IResultCapture;
+ struct IRunner;
+ struct IGeneratorsForTest;
+ struct IConfig;
+
+ struct IContext
+ {
+ virtual ~IContext();
+
+ virtual IResultCapture* getResultCapture() = 0;
+ virtual IRunner* getRunner() = 0;
+ virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0;
+ virtual bool advanceGeneratorsForCurrentTest() = 0;
+ virtual Ptr<IConfig const> getConfig() const = 0;
+ };
+
+ struct IMutableContext : IContext
+ {
+ virtual ~IMutableContext();
+ virtual void setResultCapture( IResultCapture* resultCapture ) = 0;
+ virtual void setRunner( IRunner* runner ) = 0;
+ virtual void setConfig( Ptr<IConfig const> const& config ) = 0;
+ };
+
+ IContext& getCurrentContext();
+ IMutableContext& getCurrentMutableContext();
+ void cleanUpContext();
+ Stream createStream( std::string const& streamName );
+
+}
+
+// #included from: internal/catch_test_registry.hpp
+#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED
+
+// #included from: catch_interfaces_testcase.h
+#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED
+
+#include <vector>
+
+namespace Catch {
+
+ class TestSpec;
+
+ struct ITestCase : IShared {
+ virtual void invoke () const = 0;
+ protected:
+ virtual ~ITestCase();
+ };
+
+ class TestCase;
+ struct IConfig;
+
+ struct ITestCaseRegistry {
+ virtual ~ITestCaseRegistry();
+ virtual std::vector<TestCase> const& getAllTests() const = 0;
+ virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases, bool negated = false ) const = 0;
+
+ };
+}
+
+namespace Catch {
+
+template<typename C>
+class MethodTestCase : public SharedImpl<ITestCase> {
+
+public:
+ MethodTestCase( void (C::*method)() ) : m_method( method ) {}
+
+ virtual void invoke() const {
+ C obj;
+ (obj.*m_method)();
+ }
+
+private:
+ virtual ~MethodTestCase() {}
+
+ void (C::*m_method)();
+};
+
+typedef void(*TestFunction)();
+
+struct NameAndDesc {
+ NameAndDesc( const char* _name = "", const char* _description= "" )
+ : name( _name ), description( _description )
+ {}
+
+ const char* name;
+ const char* description;
+};
+
+struct AutoReg {
+
+ AutoReg( TestFunction function,
+ SourceLineInfo const& lineInfo,
+ NameAndDesc const& nameAndDesc );
+
+ template<typename C>
+ AutoReg( void (C::*method)(),
+ char const* className,
+ NameAndDesc const& nameAndDesc,
+ SourceLineInfo const& lineInfo ) {
+ registerTestCase( new MethodTestCase<C>( method ),
+ className,
+ nameAndDesc,
+ lineInfo );
+ }
+
+ void registerTestCase( ITestCase* testCase,
+ char const* className,
+ NameAndDesc const& nameAndDesc,
+ SourceLineInfo const& lineInfo );
+
+ ~AutoReg();
+
+private:
+ AutoReg( AutoReg const& );
+ void operator= ( AutoReg const& );
+};
+
+} // end namespace Catch
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_TESTCASE( ... ) \
+ static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\
+ static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )()
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\
+ namespace{ \
+ struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \
+ void test(); \
+ }; \
+ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \
+ } \
+ void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test()
+
+#else
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \
+ static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\
+ static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )()
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\
+ namespace{ \
+ struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \
+ void test(); \
+ }; \
+ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \
+ } \
+ void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test()
+
+#endif
+
+// #included from: internal/catch_capture.hpp
+#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED
+
+// #included from: catch_result_builder.h
+#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED
+
+// #included from: catch_result_type.h
+#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED
+
+namespace Catch {
+
+ // ResultWas::OfType enum
+ struct ResultWas { enum OfType {
+ Unknown = -1,
+ Ok = 0,
+ Info = 1,
+ Warning = 2,
+
+ FailureBit = 0x10,
+
+ ExpressionFailed = FailureBit | 1,
+ ExplicitFailure = FailureBit | 2,
+
+ Exception = 0x100 | FailureBit,
+
+ ThrewException = Exception | 1,
+ DidntThrowException = Exception | 2,
+
+ FatalErrorCondition = 0x200 | FailureBit
+
+ }; };
+
+ inline bool isOk( ResultWas::OfType resultType ) {
+ return ( resultType & ResultWas::FailureBit ) == 0;
+ }
+ inline bool isJustInfo( int flags ) {
+ return flags == ResultWas::Info;
+ }
+
+ // ResultDisposition::Flags enum
+ struct ResultDisposition { enum Flags {
+ Normal = 0x01,
+
+ ContinueOnFailure = 0x02, // Failures fail test, but execution continues
+ FalseTest = 0x04, // Prefix expression with !
+ SuppressFail = 0x08 // Failures are reported but do not fail the test
+ }; };
+
+ inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {
+ return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) );
+ }
+
+ inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; }
+ inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; }
+ inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; }
+
+} // end namespace Catch
+
+// #included from: catch_assertionresult.h
+#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ struct AssertionInfo
+ {
+ AssertionInfo() {}
+ AssertionInfo( std::string const& _macroName,
+ SourceLineInfo const& _lineInfo,
+ std::string const& _capturedExpression,
+ ResultDisposition::Flags _resultDisposition );
+
+ std::string macroName;
+ SourceLineInfo lineInfo;
+ std::string capturedExpression;
+ ResultDisposition::Flags resultDisposition;
+ };
+
+ struct AssertionResultData
+ {
+ AssertionResultData() : resultType( ResultWas::Unknown ) {}
+
+ std::string reconstructedExpression;
+ std::string message;
+ ResultWas::OfType resultType;
+ };
+
+ class AssertionResult {
+ public:
+ AssertionResult();
+ AssertionResult( AssertionInfo const& info, AssertionResultData const& data );
+ ~AssertionResult();
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ AssertionResult( AssertionResult const& ) = default;
+ AssertionResult( AssertionResult && ) = default;
+ AssertionResult& operator = ( AssertionResult const& ) = default;
+ AssertionResult& operator = ( AssertionResult && ) = default;
+# endif
+
+ bool isOk() const;
+ bool succeeded() const;
+ ResultWas::OfType getResultType() const;
+ bool hasExpression() const;
+ bool hasMessage() const;
+ std::string getExpression() const;
+ std::string getExpressionInMacro() const;
+ bool hasExpandedExpression() const;
+ std::string getExpandedExpression() const;
+ std::string getMessage() const;
+ SourceLineInfo getSourceInfo() const;
+ std::string getTestMacroName() const;
+
+ protected:
+ AssertionInfo m_info;
+ AssertionResultData m_resultData;
+ };
+
+} // end namespace Catch
+
+namespace Catch {
+
+ struct TestFailureException{};
+
+ template<typename T> class ExpressionLhs;
+
+ struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison;
+
+ struct CopyableStream {
+ CopyableStream() {}
+ CopyableStream( CopyableStream const& other ) {
+ oss << other.oss.str();
+ }
+ CopyableStream& operator=( CopyableStream const& other ) {
+ oss.str("");
+ oss << other.oss.str();
+ return *this;
+ }
+ std::ostringstream oss;
+ };
+
+ class ResultBuilder {
+ public:
+ ResultBuilder( char const* macroName,
+ SourceLineInfo const& lineInfo,
+ char const* capturedExpression,
+ ResultDisposition::Flags resultDisposition );
+
+ template<typename T>
+ ExpressionLhs<T const&> operator->* ( T const& operand );
+ ExpressionLhs<bool> operator->* ( bool value );
+
+ template<typename T>
+ ResultBuilder& operator << ( T const& value ) {
+ m_stream.oss << value;
+ return *this;
+ }
+
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& );
+
+ ResultBuilder& setResultType( ResultWas::OfType result );
+ ResultBuilder& setResultType( bool result );
+ ResultBuilder& setLhs( std::string const& lhs );
+ ResultBuilder& setRhs( std::string const& rhs );
+ ResultBuilder& setOp( std::string const& op );
+
+ void endExpression();
+
+ std::string reconstructExpression() const;
+ AssertionResult build() const;
+
+ void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal );
+ void captureResult( ResultWas::OfType resultType );
+ void captureExpression();
+ void react();
+ bool shouldDebugBreak() const;
+ bool allowThrows() const;
+
+ private:
+ AssertionInfo m_assertionInfo;
+ AssertionResultData m_data;
+ struct ExprComponents {
+ ExprComponents() : testFalse( false ) {}
+ bool testFalse;
+ std::string lhs, rhs, op;
+ } m_exprComponents;
+ CopyableStream m_stream;
+
+ bool m_shouldDebugBreak;
+ bool m_shouldThrow;
+ };
+
+} // namespace Catch
+
+// Include after due to circular dependency:
+// #included from: catch_expression_lhs.hpp
+#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED
+
+// #included from: catch_evaluate.hpp
+#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4389) // '==' : signed/unsigned mismatch
+#endif
+
+#include <cstddef>
+
+namespace Catch {
+namespace Internal {
+
+ enum Operator {
+ IsEqualTo,
+ IsNotEqualTo,
+ IsLessThan,
+ IsGreaterThan,
+ IsLessThanOrEqualTo,
+ IsGreaterThanOrEqualTo
+ };
+
+ template<Operator Op> struct OperatorTraits { static const char* getName(){ return "*error*"; } };
+ template<> struct OperatorTraits<IsEqualTo> { static const char* getName(){ return "=="; } };
+ template<> struct OperatorTraits<IsNotEqualTo> { static const char* getName(){ return "!="; } };
+ template<> struct OperatorTraits<IsLessThan> { static const char* getName(){ return "<"; } };
+ template<> struct OperatorTraits<IsGreaterThan> { static const char* getName(){ return ">"; } };
+ template<> struct OperatorTraits<IsLessThanOrEqualTo> { static const char* getName(){ return "<="; } };
+ template<> struct OperatorTraits<IsGreaterThanOrEqualTo>{ static const char* getName(){ return ">="; } };
+
+ template<typename T>
+ inline T& opCast(T const& t) { return const_cast<T&>(t); }
+
+// nullptr_t support based on pull request #154 from Konstantin Baumann
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+ inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; }
+#endif // CATCH_CONFIG_CPP11_NULLPTR
+
+ // So the compare overloads can be operator agnostic we convey the operator as a template
+ // enum, which is used to specialise an Evaluator for doing the comparison.
+ template<typename T1, typename T2, Operator Op>
+ class Evaluator{};
+
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsEqualTo> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs) {
+ return opCast( lhs ) == opCast( rhs );
+ }
+ };
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsNotEqualTo> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+ return opCast( lhs ) != opCast( rhs );
+ }
+ };
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsLessThan> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+ return opCast( lhs ) < opCast( rhs );
+ }
+ };
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsGreaterThan> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+ return opCast( lhs ) > opCast( rhs );
+ }
+ };
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsGreaterThanOrEqualTo> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+ return opCast( lhs ) >= opCast( rhs );
+ }
+ };
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsLessThanOrEqualTo> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+ return opCast( lhs ) <= opCast( rhs );
+ }
+ };
+
+ template<Operator Op, typename T1, typename T2>
+ bool applyEvaluator( T1 const& lhs, T2 const& rhs ) {
+ return Evaluator<T1, T2, Op>::evaluate( lhs, rhs );
+ }
+
+ // This level of indirection allows us to specialise for integer types
+ // to avoid signed/ unsigned warnings
+
+ // "base" overload
+ template<Operator Op, typename T1, typename T2>
+ bool compare( T1 const& lhs, T2 const& rhs ) {
+ return Evaluator<T1, T2, Op>::evaluate( lhs, rhs );
+ }
+
+ // unsigned X to int
+ template<Operator Op> bool compare( unsigned int lhs, int rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
+ }
+ template<Operator Op> bool compare( unsigned long lhs, int rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
+ }
+ template<Operator Op> bool compare( unsigned char lhs, int rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
+ }
+
+ // unsigned X to long
+ template<Operator Op> bool compare( unsigned int lhs, long rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
+ }
+ template<Operator Op> bool compare( unsigned long lhs, long rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
+ }
+ template<Operator Op> bool compare( unsigned char lhs, long rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
+ }
+
+ // int to unsigned X
+ template<Operator Op> bool compare( int lhs, unsigned int rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( int lhs, unsigned long rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( int lhs, unsigned char rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
+ }
+
+ // long to unsigned X
+ template<Operator Op> bool compare( long lhs, unsigned int rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( long lhs, unsigned long rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( long lhs, unsigned char rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+ }
+
+ // pointer to long (when comparing against NULL)
+ template<Operator Op, typename T> bool compare( long lhs, T* rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
+ }
+ template<Operator Op, typename T> bool compare( T* lhs, long rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
+ }
+
+ // pointer to int (when comparing against NULL)
+ template<Operator Op, typename T> bool compare( int lhs, T* rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
+ }
+ template<Operator Op, typename T> bool compare( T* lhs, int rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
+ }
+
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+ // pointer to nullptr_t (when comparing against nullptr)
+ template<Operator Op, typename T> bool compare( std::nullptr_t, T* rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( NULL, rhs );
+ }
+ template<Operator Op, typename T> bool compare( T* lhs, std::nullptr_t ) {
+ return Evaluator<T*, T*, Op>::evaluate( lhs, NULL );
+ }
+#endif // CATCH_CONFIG_CPP11_NULLPTR
+
+} // end of namespace Internal
+} // end of namespace Catch
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+// #included from: catch_tostring.h
+#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED
+
+// #included from: catch_sfinae.hpp
+#define TWOBLUECUBES_CATCH_SFINAE_HPP_INCLUDED
+
+// Try to detect if the current compiler supports SFINAE
+
+namespace Catch {
+
+ struct TrueType {
+ static const bool value = true;
+ typedef void Enable;
+ char sizer[1];
+ };
+ struct FalseType {
+ static const bool value = false;
+ typedef void Disable;
+ char sizer[2];
+ };
+
+#ifdef CATCH_CONFIG_SFINAE
+
+ template<bool> struct NotABooleanExpression;
+
+ template<bool c> struct If : NotABooleanExpression<c> {};
+ template<> struct If<true> : TrueType {};
+ template<> struct If<false> : FalseType {};
+
+ template<int size> struct SizedIf;
+ template<> struct SizedIf<sizeof(TrueType)> : TrueType {};
+ template<> struct SizedIf<sizeof(FalseType)> : FalseType {};
+
+#endif // CATCH_CONFIG_SFINAE
+
+} // end namespace Catch
+
+#include <sstream>
+#include <iomanip>
+#include <limits>
+#include <vector>
+#include <cstddef>
+
+#ifdef __OBJC__
+// #included from: catch_objc_arc.hpp
+#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED
+
+#import <Foundation/Foundation.h>
+
+#ifdef __has_feature
+#define CATCH_ARC_ENABLED __has_feature(objc_arc)
+#else
+#define CATCH_ARC_ENABLED 0
+#endif
+
+void arcSafeRelease( NSObject* obj );
+id performOptionalSelector( id obj, SEL sel );
+
+#if !CATCH_ARC_ENABLED
+inline void arcSafeRelease( NSObject* obj ) {
+ [obj release];
+}
+inline id performOptionalSelector( id obj, SEL sel ) {
+ if( [obj respondsToSelector: sel] )
+ return [obj performSelector: sel];
+ return nil;
+}
+#define CATCH_UNSAFE_UNRETAINED
+#define CATCH_ARC_STRONG
+#else
+inline void arcSafeRelease( NSObject* ){}
+inline id performOptionalSelector( id obj, SEL sel ) {
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+#endif
+ if( [obj respondsToSelector: sel] )
+ return [obj performSelector: sel];
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+ return nil;
+}
+#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained
+#define CATCH_ARC_STRONG __strong
+#endif
+
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_TUPLE
+#include <tuple>
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_IS_ENUM
+#include <type_traits>
+#endif
+
+namespace Catch {
+
+// Why we're here.
+template<typename T>
+std::string toString( T const& value );
+
+// Built in overloads
+
+std::string toString( std::string const& value );
+std::string toString( std::wstring const& value );
+std::string toString( const char* const value );
+std::string toString( char* const value );
+std::string toString( const wchar_t* const value );
+std::string toString( wchar_t* const value );
+std::string toString( int value );
+std::string toString( unsigned long value );
+std::string toString( unsigned int value );
+std::string toString( const double value );
+std::string toString( const float value );
+std::string toString( bool value );
+std::string toString( char value );
+std::string toString( signed char value );
+std::string toString( unsigned char value );
+
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+std::string toString( std::nullptr_t );
+#endif
+
+#ifdef __OBJC__
+ std::string toString( NSString const * const& nsstring );
+ std::string toString( NSString * CATCH_ARC_STRONG const& nsstring );
+ std::string toString( NSObject* const& nsObject );
+#endif
+
+namespace Detail {
+
+ extern std::string unprintableString;
+
+// SFINAE is currently disabled by default for all compilers.
+// If the non SFINAE version of IsStreamInsertable is ambiguous for you
+// and your compiler supports SFINAE, try #defining CATCH_CONFIG_SFINAE
+#ifdef CATCH_CONFIG_SFINAE
+
+ template<typename T>
+ class IsStreamInsertableHelper {
+ template<int N> struct TrueIfSizeable : TrueType {};
+
+ template<typename T2>
+ static TrueIfSizeable<sizeof((*(std::ostream*)0) << *((T2 const*)0))> dummy(T2*);
+ static FalseType dummy(...);
+
+ public:
+ typedef SizedIf<sizeof(dummy((T*)0))> type;
+ };
+
+ template<typename T>
+ struct IsStreamInsertable : IsStreamInsertableHelper<T>::type {};
+
+#else
+
+ struct BorgType {
+ template<typename T> BorgType( T const& );
+ };
+
+ TrueType& testStreamable( std::ostream& );
+ FalseType testStreamable( FalseType );
+
+ FalseType operator<<( std::ostream const&, BorgType const& );
+
+ template<typename T>
+ struct IsStreamInsertable {
+ static std::ostream &s;
+ static T const&t;
+ enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) };
+ };
+
+#endif
+
+#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
+ template<typename T,
+ bool IsEnum = std::is_enum<T>::value
+ >
+ struct EnumStringMaker
+ {
+ static std::string convert( T const& ) { return unprintableString; }
+ };
+
+ template<typename T>
+ struct EnumStringMaker<T,true>
+ {
+ static std::string convert( T const& v )
+ {
+ return ::Catch::toString(
+ static_cast<typename std::underlying_type<T>::type>(v)
+ );
+ }
+ };
+#endif
+ template<bool C>
+ struct StringMakerBase {
+#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
+ template<typename T>
+ static std::string convert( T const& v )
+ {
+ return EnumStringMaker<T>::convert( v );
+ }
+#else
+ template<typename T>
+ static std::string convert( T const& ) { return unprintableString; }
+#endif
+ };
+
+ template<>
+ struct StringMakerBase<true> {
+ template<typename T>
+ static std::string convert( T const& _value ) {
+ std::ostringstream oss;
+ oss << _value;
+ return oss.str();
+ }
+ };
+
+ std::string rawMemoryToString( const void *object, std::size_t size );
+
+ template<typename T>
+ inline std::string rawMemoryToString( const T& object ) {
+ return rawMemoryToString( &object, sizeof(object) );
+ }
+
+} // end namespace Detail
+
+template<typename T>
+struct StringMaker :
+ Detail::StringMakerBase<Detail::IsStreamInsertable<T>::value> {};
+
+template<typename T>
+struct StringMaker<T*> {
+ template<typename U>
+ static std::string convert( U* p ) {
+ if( !p )
+ return INTERNAL_CATCH_STRINGIFY( NULL );
+ else
+ return Detail::rawMemoryToString( p );
+ }
+};
+
+template<typename R, typename C>
+struct StringMaker<R C::*> {
+ static std::string convert( R C::* p ) {
+ if( !p )
+ return INTERNAL_CATCH_STRINGIFY( NULL );
+ else
+ return Detail::rawMemoryToString( p );
+ }
+};
+
+namespace Detail {
+ template<typename InputIterator>
+ std::string rangeToString( InputIterator first, InputIterator last );
+}
+
+//template<typename T, typename Allocator>
+//struct StringMaker<std::vector<T, Allocator> > {
+// static std::string convert( std::vector<T,Allocator> const& v ) {
+// return Detail::rangeToString( v.begin(), v.end() );
+// }
+//};
+
+template<typename T, typename Allocator>
+std::string toString( std::vector<T,Allocator> const& v ) {
+ return Detail::rangeToString( v.begin(), v.end() );
+}
+
+#ifdef CATCH_CONFIG_CPP11_TUPLE
+
+// toString for tuples
+namespace TupleDetail {
+ template<
+ typename Tuple,
+ std::size_t N = 0,
+ bool = (N < std::tuple_size<Tuple>::value)
+ >
+ struct ElementPrinter {
+ static void print( const Tuple& tuple, std::ostream& os )
+ {
+ os << ( N ? ", " : " " )
+ << Catch::toString(std::get<N>(tuple));
+ ElementPrinter<Tuple,N+1>::print(tuple,os);
+ }
+ };
+
+ template<
+ typename Tuple,
+ std::size_t N
+ >
+ struct ElementPrinter<Tuple,N,false> {
+ static void print( const Tuple&, std::ostream& ) {}
+ };
+
+}
+
+template<typename ...Types>
+struct StringMaker<std::tuple<Types...>> {
+
+ static std::string convert( const std::tuple<Types...>& tuple )
+ {
+ std::ostringstream os;
+ os << '{';
+ TupleDetail::ElementPrinter<std::tuple<Types...>>::print( tuple, os );
+ os << " }";
+ return os.str();
+ }
+};
+#endif // CATCH_CONFIG_CPP11_TUPLE
+
+namespace Detail {
+ template<typename T>
+ std::string makeString( T const& value ) {
+ return StringMaker<T>::convert( value );
+ }
+} // end namespace Detail
+
+/// \brief converts any type to a string
+///
+/// The default template forwards on to ostringstream - except when an
+/// ostringstream overload does not exist - in which case it attempts to detect
+/// that and writes {?}.
+/// Overload (not specialise) this template for custom typs that you don't want
+/// to provide an ostream overload for.
+template<typename T>
+std::string toString( T const& value ) {
+ return StringMaker<T>::convert( value );
+}
+
+ namespace Detail {
+ template<typename InputIterator>
+ std::string rangeToString( InputIterator first, InputIterator last ) {
+ std::ostringstream oss;
+ oss << "{ ";
+ if( first != last ) {
+ oss << Catch::toString( *first );
+ for( ++first ; first != last ; ++first )
+ oss << ", " << Catch::toString( *first );
+ }
+ oss << " }";
+ return oss.str();
+ }
+}
+
+} // end namespace Catch
+
+namespace Catch {
+
+// Wraps the LHS of an expression and captures the operator and RHS (if any) -
+// wrapping them all in a ResultBuilder object
+template<typename T>
+class ExpressionLhs {
+ ExpressionLhs& operator = ( ExpressionLhs const& );
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ ExpressionLhs& operator = ( ExpressionLhs && ) = delete;
+# endif
+
+public:
+ ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {}
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ ExpressionLhs( ExpressionLhs const& ) = default;
+ ExpressionLhs( ExpressionLhs && ) = default;
+# endif
+
+ template<typename RhsT>
+ ResultBuilder& operator == ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsEqualTo>( rhs );
+ }
+
+ template<typename RhsT>
+ ResultBuilder& operator != ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsNotEqualTo>( rhs );
+ }
+
+ template<typename RhsT>
+ ResultBuilder& operator < ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsLessThan>( rhs );
+ }
+
+ template<typename RhsT>
+ ResultBuilder& operator > ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsGreaterThan>( rhs );
+ }
+
+ template<typename RhsT>
+ ResultBuilder& operator <= ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsLessThanOrEqualTo>( rhs );
+ }
+
+ template<typename RhsT>
+ ResultBuilder& operator >= ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsGreaterThanOrEqualTo>( rhs );
+ }
+
+ ResultBuilder& operator == ( bool rhs ) {
+ return captureExpression<Internal::IsEqualTo>( rhs );
+ }
+
+ ResultBuilder& operator != ( bool rhs ) {
+ return captureExpression<Internal::IsNotEqualTo>( rhs );
+ }
+
+ void endExpression() {
+ bool value = m_lhs ? true : false;
+ m_rb
+ .setLhs( Catch::toString( value ) )
+ .setResultType( value )
+ .endExpression();
+ }
+
+ // Only simple binary expressions are allowed on the LHS.
+ // If more complex compositions are required then place the sub expression in parentheses
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& );
+
+private:
+ template<Internal::Operator Op, typename RhsT>
+ ResultBuilder& captureExpression( RhsT const& rhs ) {
+ return m_rb
+ .setResultType( Internal::compare<Op>( m_lhs, rhs ) )
+ .setLhs( Catch::toString( m_lhs ) )
+ .setRhs( Catch::toString( rhs ) )
+ .setOp( Internal::OperatorTraits<Op>::getName() );
+ }
+
+private:
+ ResultBuilder& m_rb;
+ T m_lhs;
+};
+
+} // end namespace Catch
+
+
+namespace Catch {
+
+ template<typename T>
+ inline ExpressionLhs<T const&> ResultBuilder::operator->* ( T const& operand ) {
+ return ExpressionLhs<T const&>( *this, operand );
+ }
+
+ inline ExpressionLhs<bool> ResultBuilder::operator->* ( bool value ) {
+ return ExpressionLhs<bool>( *this, value );
+ }
+
+} // namespace Catch
+
+// #included from: catch_message.h
+#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ struct MessageInfo {
+ MessageInfo( std::string const& _macroName,
+ SourceLineInfo const& _lineInfo,
+ ResultWas::OfType _type );
+
+ std::string macroName;
+ SourceLineInfo lineInfo;
+ ResultWas::OfType type;
+ std::string message;
+ unsigned int sequence;
+
+ bool operator == ( MessageInfo const& other ) const {
+ return sequence == other.sequence;
+ }
+ bool operator < ( MessageInfo const& other ) const {
+ return sequence < other.sequence;
+ }
+ private:
+ static unsigned int globalCount;
+ };
+
+ struct MessageBuilder {
+ MessageBuilder( std::string const& macroName,
+ SourceLineInfo const& lineInfo,
+ ResultWas::OfType type )
+ : m_info( macroName, lineInfo, type )
+ {}
+
+ template<typename T>
+ MessageBuilder& operator << ( T const& value ) {
+ m_stream << value;
+ return *this;
+ }
+
+ MessageInfo m_info;
+ std::ostringstream m_stream;
+ };
+
+ class ScopedMessage {
+ public:
+ ScopedMessage( MessageBuilder const& builder );
+ ScopedMessage( ScopedMessage const& other );
+ ~ScopedMessage();
+
+ MessageInfo m_info;
+ };
+
+} // end namespace Catch
+
+// #included from: catch_interfaces_capture.h
+#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ class TestCase;
+ class AssertionResult;
+ struct AssertionInfo;
+ struct SectionInfo;
+ struct MessageInfo;
+ class ScopedMessageBuilder;
+ struct Counts;
+
+ struct IResultCapture {
+
+ virtual ~IResultCapture();
+
+ virtual void assertionEnded( AssertionResult const& result ) = 0;
+ virtual bool sectionStarted( SectionInfo const& sectionInfo,
+ Counts& assertions ) = 0;
+ virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0;
+ virtual void pushScopedMessage( MessageInfo const& message ) = 0;
+ virtual void popScopedMessage( MessageInfo const& message ) = 0;
+
+ virtual std::string getCurrentTestName() const = 0;
+ virtual const AssertionResult* getLastResult() const = 0;
+
+ virtual void handleFatalErrorCondition( std::string const& message ) = 0;
+ };
+
+ IResultCapture& getResultCapture();
+}
+
+// #included from: catch_debugger.h
+#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED
+
+// #included from: catch_platform.h
+#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED
+
+#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
+#define CATCH_PLATFORM_MAC
+#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
+#define CATCH_PLATFORM_IPHONE
+#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
+#define CATCH_PLATFORM_WINDOWS
+#endif
+
+#include <string>
+
+namespace Catch{
+
+ bool isDebuggerActive();
+ void writeToDebugConsole( std::string const& text );
+}
+
+#ifdef CATCH_PLATFORM_MAC
+
+ // The following code snippet based on:
+ // http://cocoawithlove.com/2008/03/break-into-debugger.html
+ #ifdef DEBUG
+ #if defined(__ppc64__) || defined(__ppc__)
+ #define CATCH_BREAK_INTO_DEBUGGER() \
+ if( Catch::isDebuggerActive() ) { \
+ __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \
+ : : : "memory","r0","r3","r4" ); \
+ }
+ #else
+ #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );}
+ #endif
+ #endif
+
+#elif defined(_MSC_VER)
+ #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); }
+#elif defined(__MINGW32__)
+ extern "C" __declspec(dllimport) void __stdcall DebugBreak();
+ #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); }
+#endif
+
+#ifndef CATCH_BREAK_INTO_DEBUGGER
+#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue();
+#endif
+
+// #included from: catch_interfaces_runner.h
+#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED
+
+namespace Catch {
+ class TestCase;
+
+ struct IRunner {
+ virtual ~IRunner();
+ virtual bool aborting() const = 0;
+ };
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// In the event of a failure works out if the debugger needs to be invoked
+// and/or an exception thrown and takes appropriate action.
+// This needs to be done as a macro so the debugger will stop in the user
+// source code rather than in Catch library code
+#define INTERNAL_CATCH_REACT( resultBuilder ) \
+ if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \
+ resultBuilder.react();
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+ try { \
+ ( __catchResult->*expr ).endExpression(); \
+ } \
+ catch( ... ) { \
+ __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \
+ } \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \
+ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \
+ if( Catch::getResultCapture().getLastResult()->succeeded() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \
+ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \
+ if( !Catch::getResultCapture().getLastResult()->succeeded() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+ try { \
+ expr; \
+ __catchResult.captureResult( Catch::ResultWas::Ok ); \
+ } \
+ catch( ... ) { \
+ __catchResult.useActiveException( resultDisposition ); \
+ } \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+ if( __catchResult.allowThrows() ) \
+ try { \
+ expr; \
+ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
+ } \
+ catch( ... ) { \
+ __catchResult.captureResult( Catch::ResultWas::Ok ); \
+ } \
+ else \
+ __catchResult.captureResult( Catch::ResultWas::Ok ); \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+ if( __catchResult.allowThrows() ) \
+ try { \
+ expr; \
+ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
+ } \
+ catch( exceptionType ) { \
+ __catchResult.captureResult( Catch::ResultWas::Ok ); \
+ } \
+ catch( ... ) { \
+ __catchResult.useActiveException( resultDisposition ); \
+ } \
+ else \
+ __catchResult.captureResult( Catch::ResultWas::Ok ); \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+
+///////////////////////////////////////////////////////////////////////////////
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+ #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
+ __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \
+ __catchResult.captureResult( messageType ); \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+#else
+ #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
+ __catchResult << log + ::Catch::StreamEndStop(); \
+ __catchResult.captureResult( messageType ); \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_INFO( log, macroName ) \
+ Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log;
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \
+ try { \
+ std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \
+ __catchResult \
+ .setLhs( Catch::toString( arg ) ) \
+ .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \
+ .setOp( "matches" ) \
+ .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \
+ __catchResult.captureExpression(); \
+ } catch( ... ) { \
+ __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \
+ } \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+
+// #included from: internal/catch_section.h
+#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED
+
+// #included from: catch_section_info.h
+#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED
+
+namespace Catch {
+
+ struct SectionInfo {
+ SectionInfo
+ ( SourceLineInfo const& _lineInfo,
+ std::string const& _name,
+ std::string const& _description = std::string() );
+
+ std::string name;
+ std::string description;
+ SourceLineInfo lineInfo;
+ };
+
+} // end namespace Catch
+
+// #included from: catch_totals.hpp
+#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED
+
+#include <cstddef>
+
+namespace Catch {
+
+ struct Counts {
+ Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {}
+
+ Counts operator - ( Counts const& other ) const {
+ Counts diff;
+ diff.passed = passed - other.passed;
+ diff.failed = failed - other.failed;
+ diff.failedButOk = failedButOk - other.failedButOk;
+ return diff;
+ }
+ Counts& operator += ( Counts const& other ) {
+ passed += other.passed;
+ failed += other.failed;
+ failedButOk += other.failedButOk;
+ return *this;
+ }
+
+ std::size_t total() const {
+ return passed + failed + failedButOk;
+ }
+ bool allPassed() const {
+ return failed == 0 && failedButOk == 0;
+ }
+ bool allOk() const {
+ return failed == 0;
+ }
+
+ std::size_t passed;
+ std::size_t failed;
+ std::size_t failedButOk;
+ };
+
+ struct Totals {
+
+ Totals operator - ( Totals const& other ) const {
+ Totals diff;
+ diff.assertions = assertions - other.assertions;
+ diff.testCases = testCases - other.testCases;
+ return diff;
+ }
+
+ Totals delta( Totals const& prevTotals ) const {
+ Totals diff = *this - prevTotals;
+ if( diff.assertions.failed > 0 )
+ ++diff.testCases.failed;
+ else if( diff.assertions.failedButOk > 0 )
+ ++diff.testCases.failedButOk;
+ else
+ ++diff.testCases.passed;
+ return diff;
+ }
+
+ Totals& operator += ( Totals const& other ) {
+ assertions += other.assertions;
+ testCases += other.testCases;
+ return *this;
+ }
+
+ Counts assertions;
+ Counts testCases;
+ };
+}
+
+// #included from: catch_timer.h
+#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED
+
+#ifdef CATCH_PLATFORM_WINDOWS
+typedef unsigned long long uint64_t;
+#else
+#include <stdint.h>
+#endif
+
+namespace Catch {
+
+ class Timer {
+ public:
+ Timer() : m_ticks( 0 ) {}
+ void start();
+ unsigned int getElapsedMicroseconds() const;
+ unsigned int getElapsedMilliseconds() const;
+ double getElapsedSeconds() const;
+
+ private:
+ uint64_t m_ticks;
+ };
+
+} // namespace Catch
+
+#include <string>
+
+namespace Catch {
+
+ class Section : NonCopyable {
+ public:
+ Section( SectionInfo const& info );
+ ~Section();
+
+ // This indicates whether the section should be executed or not
+ operator bool() const;
+
+ private:
+ SectionInfo m_info;
+
+ std::string m_name;
+ Counts m_assertions;
+ bool m_sectionIncluded;
+ Timer m_timer;
+ };
+
+} // end namespace Catch
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+ #define INTERNAL_CATCH_SECTION( ... ) \
+ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) )
+#else
+ #define INTERNAL_CATCH_SECTION( name, desc ) \
+ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) )
+#endif
+
+// #included from: internal/catch_generators.hpp
+#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED
+
+#include <iterator>
+#include <vector>
+#include <string>
+#include <stdlib.h>
+
+namespace Catch {
+
+template<typename T>
+struct IGenerator {
+ virtual ~IGenerator() {}
+ virtual T getValue( std::size_t index ) const = 0;
+ virtual std::size_t size () const = 0;
+};
+
+template<typename T>
+class BetweenGenerator : public IGenerator<T> {
+public:
+ BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){}
+
+ virtual T getValue( std::size_t index ) const {
+ return m_from+static_cast<int>( index );
+ }
+
+ virtual std::size_t size() const {
+ return static_cast<std::size_t>( 1+m_to-m_from );
+ }
+
+private:
+
+ T m_from;
+ T m_to;
+};
+
+template<typename T>
+class ValuesGenerator : public IGenerator<T> {
+public:
+ ValuesGenerator(){}
+
+ void add( T value ) {
+ m_values.push_back( value );
+ }
+
+ virtual T getValue( std::size_t index ) const {
+ return m_values[index];
+ }
+
+ virtual std::size_t size() const {
+ return m_values.size();
+ }
+
+private:
+ std::vector<T> m_values;
+};
+
+template<typename T>
+class CompositeGenerator {
+public:
+ CompositeGenerator() : m_totalSize( 0 ) {}
+
+ // *** Move semantics, similar to auto_ptr ***
+ CompositeGenerator( CompositeGenerator& other )
+ : m_fileInfo( other.m_fileInfo ),
+ m_totalSize( 0 )
+ {
+ move( other );
+ }
+
+ CompositeGenerator& setFileInfo( const char* fileInfo ) {
+ m_fileInfo = fileInfo;
+ return *this;
+ }
+
+ ~CompositeGenerator() {
+ deleteAll( m_composed );
+ }
+
+ operator T () const {
+ size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize );
+
+ typename std::vector<const IGenerator<T>*>::const_iterator it = m_composed.begin();
+ typename std::vector<const IGenerator<T>*>::const_iterator itEnd = m_composed.end();
+ for( size_t index = 0; it != itEnd; ++it )
+ {
+ const IGenerator<T>* generator = *it;
+ if( overallIndex >= index && overallIndex < index + generator->size() )
+ {
+ return generator->getValue( overallIndex-index );
+ }
+ index += generator->size();
+ }
+ CATCH_INTERNAL_ERROR( "Indexed past end of generated range" );
+ return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so
+ }
+
+ void add( const IGenerator<T>* generator ) {
+ m_totalSize += generator->size();
+ m_composed.push_back( generator );
+ }
+
+ CompositeGenerator& then( CompositeGenerator& other ) {
+ move( other );
+ return *this;
+ }
+
+ CompositeGenerator& then( T value ) {
+ ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+ valuesGen->add( value );
+ add( valuesGen );
+ return *this;
+ }
+
+private:
+
+ void move( CompositeGenerator& other ) {
+ std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) );
+ m_totalSize += other.m_totalSize;
+ other.m_composed.clear();
+ }
+
+ std::vector<const IGenerator<T>*> m_composed;
+ std::string m_fileInfo;
+ size_t m_totalSize;
+};
+
+namespace Generators
+{
+ template<typename T>
+ CompositeGenerator<T> between( T from, T to ) {
+ CompositeGenerator<T> generators;
+ generators.add( new BetweenGenerator<T>( from, to ) );
+ return generators;
+ }
+
+ template<typename T>
+ CompositeGenerator<T> values( T val1, T val2 ) {
+ CompositeGenerator<T> generators;
+ ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+ valuesGen->add( val1 );
+ valuesGen->add( val2 );
+ generators.add( valuesGen );
+ return generators;
+ }
+
+ template<typename T>
+ CompositeGenerator<T> values( T val1, T val2, T val3 ){
+ CompositeGenerator<T> generators;
+ ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+ valuesGen->add( val1 );
+ valuesGen->add( val2 );
+ valuesGen->add( val3 );
+ generators.add( valuesGen );
+ return generators;
+ }
+
+ template<typename T>
+ CompositeGenerator<T> values( T val1, T val2, T val3, T val4 ) {
+ CompositeGenerator<T> generators;
+ ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+ valuesGen->add( val1 );
+ valuesGen->add( val2 );
+ valuesGen->add( val3 );
+ valuesGen->add( val4 );
+ generators.add( valuesGen );
+ return generators;
+ }
+
+} // end namespace Generators
+
+using namespace Generators;
+
+} // end namespace Catch
+
+#define INTERNAL_CATCH_LINESTR2( line ) #line
+#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line )
+
+#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" )
+
+// #included from: internal/catch_interfaces_exception.h
+#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED
+
+#include <string>
+// #included from: catch_interfaces_registry_hub.h
+#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ class TestCase;
+ struct ITestCaseRegistry;
+ struct IExceptionTranslatorRegistry;
+ struct IExceptionTranslator;
+ struct IReporterRegistry;
+ struct IReporterFactory;
+
+ struct IRegistryHub {
+ virtual ~IRegistryHub();
+
+ virtual IReporterRegistry const& getReporterRegistry() const = 0;
+ virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;
+ virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0;
+ };
+
+ struct IMutableRegistryHub {
+ virtual ~IMutableRegistryHub();
+ virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0;
+ virtual void registerTest( TestCase const& testInfo ) = 0;
+ virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;
+ };
+
+ IRegistryHub& getRegistryHub();
+ IMutableRegistryHub& getMutableRegistryHub();
+ void cleanUp();
+ std::string translateActiveException();
+
+}
+
+
+namespace Catch {
+
+ typedef std::string(*exceptionTranslateFunction)();
+
+ struct IExceptionTranslator {
+ virtual ~IExceptionTranslator();
+ virtual std::string translate() const = 0;
+ };
+
+ struct IExceptionTranslatorRegistry {
+ virtual ~IExceptionTranslatorRegistry();
+
+ virtual std::string translateActiveException() const = 0;
+ };
+
+ class ExceptionTranslatorRegistrar {
+ template<typename T>
+ class ExceptionTranslator : public IExceptionTranslator {
+ public:
+
+ ExceptionTranslator( std::string(*translateFunction)( T& ) )
+ : m_translateFunction( translateFunction )
+ {}
+
+ virtual std::string translate() const {
+ try {
+ throw;
+ }
+ catch( T& ex ) {
+ return m_translateFunction( ex );
+ }
+ }
+
+ protected:
+ std::string(*m_translateFunction)( T& );
+ };
+
+ public:
+ template<typename T>
+ ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) {
+ getMutableRegistryHub().registerTranslator
+ ( new ExceptionTranslator<T>( translateFunction ) );
+ }
+ };
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \
+ static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \
+ namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\
+ static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature )
+
+// #included from: internal/catch_approx.hpp
+#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED
+
+#include <cmath>
+#include <limits>
+
+namespace Catch {
+namespace Detail {
+
+ class Approx {
+ public:
+ explicit Approx ( double value )
+ : m_epsilon( std::numeric_limits<float>::epsilon()*100 ),
+ m_scale( 1.0 ),
+ m_value( value )
+ {}
+
+ Approx( Approx const& other )
+ : m_epsilon( other.m_epsilon ),
+ m_scale( other.m_scale ),
+ m_value( other.m_value )
+ {}
+
+ static Approx custom() {
+ return Approx( 0 );
+ }
+
+ Approx operator()( double value ) {
+ Approx approx( value );
+ approx.epsilon( m_epsilon );
+ approx.scale( m_scale );
+ return approx;
+ }
+
+ friend bool operator == ( double lhs, Approx const& rhs ) {
+ // Thanks to Richard Harris for his help refining this formula
+ return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) );
+ }
+
+ friend bool operator == ( Approx const& lhs, double rhs ) {
+ return operator==( rhs, lhs );
+ }
+
+ friend bool operator != ( double lhs, Approx const& rhs ) {
+ return !operator==( lhs, rhs );
+ }
+
+ friend bool operator != ( Approx const& lhs, double rhs ) {
+ return !operator==( rhs, lhs );
+ }
+
+ Approx& epsilon( double newEpsilon ) {
+ m_epsilon = newEpsilon;
+ return *this;
+ }
+
+ Approx& scale( double newScale ) {
+ m_scale = newScale;
+ return *this;
+ }
+
+ std::string toString() const {
+ std::ostringstream oss;
+ oss << "Approx( " << Catch::toString( m_value ) << " )";
+ return oss.str();
+ }
+
+ private:
+ double m_epsilon;
+ double m_scale;
+ double m_value;
+ };
+}
+
+template<>
+inline std::string toString<Detail::Approx>( Detail::Approx const& value ) {
+ return value.toString();
+}
+
+} // end namespace Catch
+
+// #included from: internal/catch_matchers.hpp
+#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED
+
+namespace Catch {
+namespace Matchers {
+ namespace Impl {
+
+ template<typename ExpressionT>
+ struct Matcher : SharedImpl<IShared>
+ {
+ typedef ExpressionT ExpressionType;
+
+ virtual ~Matcher() {}
+ virtual Ptr<Matcher> clone() const = 0;
+ virtual bool match( ExpressionT const& expr ) const = 0;
+ virtual std::string toString() const = 0;
+ };
+
+ template<typename DerivedT, typename ExpressionT>
+ struct MatcherImpl : Matcher<ExpressionT> {
+
+ virtual Ptr<Matcher<ExpressionT> > clone() const {
+ return Ptr<Matcher<ExpressionT> >( new DerivedT( static_cast<DerivedT const&>( *this ) ) );
+ }
+ };
+
+ namespace Generic {
+
+ template<typename ExpressionT>
+ class AllOf : public MatcherImpl<AllOf<ExpressionT>, ExpressionT> {
+ public:
+
+ AllOf() {}
+ AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {}
+
+ AllOf& add( Matcher<ExpressionT> const& matcher ) {
+ m_matchers.push_back( matcher.clone() );
+ return *this;
+ }
+ virtual bool match( ExpressionT const& expr ) const
+ {
+ for( std::size_t i = 0; i < m_matchers.size(); ++i )
+ if( !m_matchers[i]->match( expr ) )
+ return false;
+ return true;
+ }
+ virtual std::string toString() const {
+ std::ostringstream oss;
+ oss << "( ";
+ for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
+ if( i != 0 )
+ oss << " and ";
+ oss << m_matchers[i]->toString();
+ }
+ oss << " )";
+ return oss.str();
+ }
+
+ private:
+ std::vector<Ptr<Matcher<ExpressionT> > > m_matchers;
+ };
+
+ template<typename ExpressionT>
+ class AnyOf : public MatcherImpl<AnyOf<ExpressionT>, ExpressionT> {
+ public:
+
+ AnyOf() {}
+ AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {}
+
+ AnyOf& add( Matcher<ExpressionT> const& matcher ) {
+ m_matchers.push_back( matcher.clone() );
+ return *this;
+ }
+ virtual bool match( ExpressionT const& expr ) const
+ {
+ for( std::size_t i = 0; i < m_matchers.size(); ++i )
+ if( m_matchers[i]->match( expr ) )
+ return true;
+ return false;
+ }
+ virtual std::string toString() const {
+ std::ostringstream oss;
+ oss << "( ";
+ for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
+ if( i != 0 )
+ oss << " or ";
+ oss << m_matchers[i]->toString();
+ }
+ oss << " )";
+ return oss.str();
+ }
+
+ private:
+ std::vector<Ptr<Matcher<ExpressionT> > > m_matchers;
+ };
+
+ }
+
+ namespace StdString {
+
+ inline std::string makeString( std::string const& str ) { return str; }
+ inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); }
+
+ struct Equals : MatcherImpl<Equals, std::string> {
+ Equals( std::string const& str ) : m_str( str ){}
+ Equals( Equals const& other ) : m_str( other.m_str ){}
+
+ virtual ~Equals();
+
+ virtual bool match( std::string const& expr ) const {
+ return m_str == expr;
+ }
+ virtual std::string toString() const {
+ return "equals: \"" + m_str + "\"";
+ }
+
+ std::string m_str;
+ };
+
+ struct Contains : MatcherImpl<Contains, std::string> {
+ Contains( std::string const& substr ) : m_substr( substr ){}
+ Contains( Contains const& other ) : m_substr( other.m_substr ){}
+
+ virtual ~Contains();
+
+ virtual bool match( std::string const& expr ) const {
+ return expr.find( m_substr ) != std::string::npos;
+ }
+ virtual std::string toString() const {
+ return "contains: \"" + m_substr + "\"";
+ }
+
+ std::string m_substr;
+ };
+
+ struct StartsWith : MatcherImpl<StartsWith, std::string> {
+ StartsWith( std::string const& substr ) : m_substr( substr ){}
+ StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){}
+
+ virtual ~StartsWith();
+
+ virtual bool match( std::string const& expr ) const {
+ return expr.find( m_substr ) == 0;
+ }
+ virtual std::string toString() const {
+ return "starts with: \"" + m_substr + "\"";
+ }
+
+ std::string m_substr;
+ };
+
+ struct EndsWith : MatcherImpl<EndsWith, std::string> {
+ EndsWith( std::string const& substr ) : m_substr( substr ){}
+ EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){}
+
+ virtual ~EndsWith();
+
+ virtual bool match( std::string const& expr ) const {
+ return expr.find( m_substr ) == expr.size() - m_substr.size();
+ }
+ virtual std::string toString() const {
+ return "ends with: \"" + m_substr + "\"";
+ }
+
+ std::string m_substr;
+ };
+ } // namespace StdString
+ } // namespace Impl
+
+ // The following functions create the actual matcher objects.
+ // This allows the types to be inferred
+ template<typename ExpressionT>
+ inline Impl::Generic::AllOf<ExpressionT> AllOf( Impl::Matcher<ExpressionT> const& m1,
+ Impl::Matcher<ExpressionT> const& m2 ) {
+ return Impl::Generic::AllOf<ExpressionT>().add( m1 ).add( m2 );
+ }
+ template<typename ExpressionT>
+ inline Impl::Generic::AllOf<ExpressionT> AllOf( Impl::Matcher<ExpressionT> const& m1,
+ Impl::Matcher<ExpressionT> const& m2,
+ Impl::Matcher<ExpressionT> const& m3 ) {
+ return Impl::Generic::AllOf<ExpressionT>().add( m1 ).add( m2 ).add( m3 );
+ }
+ template<typename ExpressionT>
+ inline Impl::Generic::AnyOf<ExpressionT> AnyOf( Impl::Matcher<ExpressionT> const& m1,
+ Impl::Matcher<ExpressionT> const& m2 ) {
+ return Impl::Generic::AnyOf<ExpressionT>().add( m1 ).add( m2 );
+ }
+ template<typename ExpressionT>
+ inline Impl::Generic::AnyOf<ExpressionT> AnyOf( Impl::Matcher<ExpressionT> const& m1,
+ Impl::Matcher<ExpressionT> const& m2,
+ Impl::Matcher<ExpressionT> const& m3 ) {
+ return Impl::Generic::AnyOf<ExpressionT>().add( m1 ).add( m2 ).add( m3 );
+ }
+
+ inline Impl::StdString::Equals Equals( std::string const& str ) {
+ return Impl::StdString::Equals( str );
+ }
+ inline Impl::StdString::Equals Equals( const char* str ) {
+ return Impl::StdString::Equals( Impl::StdString::makeString( str ) );
+ }
+ inline Impl::StdString::Contains Contains( std::string const& substr ) {
+ return Impl::StdString::Contains( substr );
+ }
+ inline Impl::StdString::Contains Contains( const char* substr ) {
+ return Impl::StdString::Contains( Impl::StdString::makeString( substr ) );
+ }
+ inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) {
+ return Impl::StdString::StartsWith( substr );
+ }
+ inline Impl::StdString::StartsWith StartsWith( const char* substr ) {
+ return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) );
+ }
+ inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) {
+ return Impl::StdString::EndsWith( substr );
+ }
+ inline Impl::StdString::EndsWith EndsWith( const char* substr ) {
+ return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) );
+ }
+
+} // namespace Matchers
+
+using namespace Matchers;
+
+} // namespace Catch
+
+// #included from: internal/catch_interfaces_tag_alias_registry.h
+#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED
+
+// #included from: catch_tag_alias.h
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ struct TagAlias {
+ TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {}
+
+ std::string tag;
+ SourceLineInfo lineInfo;
+ };
+
+ struct RegistrarForTagAliases {
+ RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
+ };
+
+} // end namespace Catch
+
+#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); }
+// #included from: catch_option.hpp
+#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED
+
+namespace Catch {
+
+ // An optional type
+ template<typename T>
+ class Option {
+ public:
+ Option() : nullableValue( NULL ) {}
+ Option( T const& _value )
+ : nullableValue( new( storage ) T( _value ) )
+ {}
+ Option( Option const& _other )
+ : nullableValue( _other ? new( storage ) T( *_other ) : NULL )
+ {}
+
+ ~Option() {
+ reset();
+ }
+
+ Option& operator= ( Option const& _other ) {
+ if( &_other != this ) {
+ reset();
+ if( _other )
+ nullableValue = new( storage ) T( *_other );
+ }
+ return *this;
+ }
+ Option& operator = ( T const& _value ) {
+ reset();
+ nullableValue = new( storage ) T( _value );
+ return *this;
+ }
+
+ void reset() {
+ if( nullableValue )
+ nullableValue->~T();
+ nullableValue = NULL;
+ }
+
+ T& operator*() { return *nullableValue; }
+ T const& operator*() const { return *nullableValue; }
+ T* operator->() { return nullableValue; }
+ const T* operator->() const { return nullableValue; }
+
+ T valueOr( T const& defaultValue ) const {
+ return nullableValue ? *nullableValue : defaultValue;
+ }
+
+ bool some() const { return nullableValue != NULL; }
+ bool none() const { return nullableValue == NULL; }
+
+ bool operator !() const { return nullableValue == NULL; }
+ operator SafeBool::type() const {
+ return SafeBool::makeSafe( some() );
+ }
+
+ private:
+ T* nullableValue;
+ char storage[sizeof(T)];
+ };
+
+} // end namespace Catch
+
+namespace Catch {
+
+ struct ITagAliasRegistry {
+ virtual ~ITagAliasRegistry();
+ virtual Option<TagAlias> find( std::string const& alias ) const = 0;
+ virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0;
+
+ static ITagAliasRegistry const& get();
+ };
+
+} // end namespace Catch
+
+// These files are included here so the single_include script doesn't put them
+// in the conditionally compiled sections
+// #included from: internal/catch_test_case_info.h
+#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED
+
+#include <string>
+#include <set>
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+namespace Catch {
+
+ struct ITestCase;
+
+ struct TestCaseInfo {
+ enum SpecialProperties{
+ None = 0,
+ IsHidden = 1 << 1,
+ ShouldFail = 1 << 2,
+ MayFail = 1 << 3,
+ Throws = 1 << 4
+ };
+
+ TestCaseInfo( std::string const& _name,
+ std::string const& _className,
+ std::string const& _description,
+ std::set<std::string> const& _tags,
+ SourceLineInfo const& _lineInfo );
+
+ TestCaseInfo( TestCaseInfo const& other );
+
+ bool isHidden() const;
+ bool throws() const;
+ bool okToFail() const;
+ bool expectedToFail() const;
+
+ std::string name;
+ std::string className;
+ std::string description;
+ std::set<std::string> tags;
+ std::set<std::string> lcaseTags;
+ std::string tagsAsString;
+ SourceLineInfo lineInfo;
+ SpecialProperties properties;
+ };
+
+ class TestCase : public TestCaseInfo {
+ public:
+
+ TestCase( ITestCase* testCase, TestCaseInfo const& info );
+ TestCase( TestCase const& other );
+
+ TestCase withName( std::string const& _newName ) const;
+
+ void invoke() const;
+
+ TestCaseInfo const& getTestCaseInfo() const;
+
+ void swap( TestCase& other );
+ bool operator == ( TestCase const& other ) const;
+ bool operator < ( TestCase const& other ) const;
+ TestCase& operator = ( TestCase const& other );
+
+ private:
+ Ptr<ITestCase> test;
+ };
+
+ TestCase makeTestCase( ITestCase* testCase,
+ std::string const& className,
+ std::string const& name,
+ std::string const& description,
+ SourceLineInfo const& lineInfo );
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+
+#ifdef __OBJC__
+// #included from: internal/catch_objc.hpp
+#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED
+
+#import <objc/runtime.h>
+
+#include <string>
+
+// NB. Any general catch headers included here must be included
+// in catch.hpp first to make sure they are included by the single
+// header for non obj-usage
+
+///////////////////////////////////////////////////////////////////////////////
+// This protocol is really only here for (self) documenting purposes, since
+// all its methods are optional.
+ at protocol OcFixture
+
+ at optional
+
+-(void) setUp;
+-(void) tearDown;
+
+ at end
+
+namespace Catch {
+
+ class OcMethod : public SharedImpl<ITestCase> {
+
+ public:
+ OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {}
+
+ virtual void invoke() const {
+ id obj = [[m_cls alloc] init];
+
+ performOptionalSelector( obj, @selector(setUp) );
+ performOptionalSelector( obj, m_sel );
+ performOptionalSelector( obj, @selector(tearDown) );
+
+ arcSafeRelease( obj );
+ }
+ private:
+ virtual ~OcMethod() {}
+
+ Class m_cls;
+ SEL m_sel;
+ };
+
+ namespace Detail{
+
+ inline std::string getAnnotation( Class cls,
+ std::string const& annotationName,
+ std::string const& testCaseName ) {
+ NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()];
+ SEL sel = NSSelectorFromString( selStr );
+ arcSafeRelease( selStr );
+ id value = performOptionalSelector( cls, sel );
+ if( value )
+ return [(NSString*)value UTF8String];
+ return "";
+ }
+ }
+
+ inline size_t registerTestMethods() {
+ size_t noTestMethods = 0;
+ int noClasses = objc_getClassList( NULL, 0 );
+
+ Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses);
+ objc_getClassList( classes, noClasses );
+
+ for( int c = 0; c < noClasses; c++ ) {
+ Class cls = classes[c];
+ {
+ u_int count;
+ Method* methods = class_copyMethodList( cls, &count );
+ for( u_int m = 0; m < count ; m++ ) {
+ SEL selector = method_getName(methods[m]);
+ std::string methodName = sel_getName(selector);
+ if( startsWith( methodName, "Catch_TestCase_" ) ) {
+ std::string testCaseName = methodName.substr( 15 );
+ std::string name = Detail::getAnnotation( cls, "Name", testCaseName );
+ std::string desc = Detail::getAnnotation( cls, "Description", testCaseName );
+ const char* className = class_getName( cls );
+
+ getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) );
+ noTestMethods++;
+ }
+ }
+ free(methods);
+ }
+ }
+ return noTestMethods;
+ }
+
+ namespace Matchers {
+ namespace Impl {
+ namespace NSStringMatchers {
+
+ template<typename MatcherT>
+ struct StringHolder : MatcherImpl<MatcherT, NSString*>{
+ StringHolder( NSString* substr ) : m_substr( [substr copy] ){}
+ StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){}
+ StringHolder() {
+ arcSafeRelease( m_substr );
+ }
+
+ NSString* m_substr;
+ };
+
+ struct Equals : StringHolder<Equals> {
+ Equals( NSString* substr ) : StringHolder( substr ){}
+
+ virtual bool match( ExpressionType const& str ) const {
+ return (str != nil || m_substr == nil ) &&
+ [str isEqualToString:m_substr];
+ }
+
+ virtual std::string toString() const {
+ return "equals string: " + Catch::toString( m_substr );
+ }
+ };
+
+ struct Contains : StringHolder<Contains> {
+ Contains( NSString* substr ) : StringHolder( substr ){}
+
+ virtual bool match( ExpressionType const& str ) const {
+ return (str != nil || m_substr == nil ) &&
+ [str rangeOfString:m_substr].location != NSNotFound;
+ }
+
+ virtual std::string toString() const {
+ return "contains string: " + Catch::toString( m_substr );
+ }
+ };
+
+ struct StartsWith : StringHolder<StartsWith> {
+ StartsWith( NSString* substr ) : StringHolder( substr ){}
+
+ virtual bool match( ExpressionType const& str ) const {
+ return (str != nil || m_substr == nil ) &&
+ [str rangeOfString:m_substr].location == 0;
+ }
+
+ virtual std::string toString() const {
+ return "starts with: " + Catch::toString( m_substr );
+ }
+ };
+ struct EndsWith : StringHolder<EndsWith> {
+ EndsWith( NSString* substr ) : StringHolder( substr ){}
+
+ virtual bool match( ExpressionType const& str ) const {
+ return (str != nil || m_substr == nil ) &&
+ [str rangeOfString:m_substr].location == [str length] - [m_substr length];
+ }
+
+ virtual std::string toString() const {
+ return "ends with: " + Catch::toString( m_substr );
+ }
+ };
+
+ } // namespace NSStringMatchers
+ } // namespace Impl
+
+ inline Impl::NSStringMatchers::Equals
+ Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); }
+
+ inline Impl::NSStringMatchers::Contains
+ Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); }
+
+ inline Impl::NSStringMatchers::StartsWith
+ StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); }
+
+ inline Impl::NSStringMatchers::EndsWith
+ EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); }
+
+ } // namespace Matchers
+
+ using namespace Matchers;
+
+} // namespace Catch
+
+///////////////////////////////////////////////////////////////////////////////
+#define OC_TEST_CASE( name, desc )\
++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \
+{\
+return @ name; \
+}\
++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \
+{ \
+return @ desc; \
+} \
+-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test )
+
+#endif
+
+#ifdef CATCH_IMPL
+// #included from: internal/catch_impl.hpp
+#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED
+
+// Collect all the implementation files together here
+// These are the equivalent of what would usually be cpp files
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+// #included from: ../catch_runner.hpp
+#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED
+
+// #included from: internal/catch_commandline.hpp
+#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED
+
+// #included from: catch_config.hpp
+#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED
+
+// #included from: catch_test_spec_parser.hpp
+#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+// #included from: catch_test_spec.hpp
+#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+#include <string>
+#include <vector>
+
+namespace Catch {
+
+ class TestSpec {
+ struct Pattern : SharedImpl<> {
+ virtual ~Pattern();
+ virtual bool matches( TestCaseInfo const& testCase ) const = 0;
+ };
+ class NamePattern : public Pattern {
+ enum WildcardPosition {
+ NoWildcard = 0,
+ WildcardAtStart = 1,
+ WildcardAtEnd = 2,
+ WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd
+ };
+
+ public:
+ NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) {
+ if( startsWith( m_name, "*" ) ) {
+ m_name = m_name.substr( 1 );
+ m_wildcard = WildcardAtStart;
+ }
+ if( endsWith( m_name, "*" ) ) {
+ m_name = m_name.substr( 0, m_name.size()-1 );
+ m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd );
+ }
+ }
+ virtual ~NamePattern();
+ virtual bool matches( TestCaseInfo const& testCase ) const {
+ switch( m_wildcard ) {
+ case NoWildcard:
+ return m_name == toLower( testCase.name );
+ case WildcardAtStart:
+ return endsWith( toLower( testCase.name ), m_name );
+ case WildcardAtEnd:
+ return startsWith( toLower( testCase.name ), m_name );
+ case WildcardAtBothEnds:
+ return contains( toLower( testCase.name ), m_name );
+ }
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunreachable-code"
+#endif
+ throw std::logic_error( "Unknown enum" );
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+ }
+ private:
+ std::string m_name;
+ WildcardPosition m_wildcard;
+ };
+ class TagPattern : public Pattern {
+ public:
+ TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {}
+ virtual ~TagPattern();
+ virtual bool matches( TestCaseInfo const& testCase ) const {
+ return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end();
+ }
+ private:
+ std::string m_tag;
+ };
+ class ExcludedPattern : public Pattern {
+ public:
+ ExcludedPattern( Ptr<Pattern> const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {}
+ virtual ~ExcludedPattern();
+ virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); }
+ private:
+ Ptr<Pattern> m_underlyingPattern;
+ };
+
+ struct Filter {
+ std::vector<Ptr<Pattern> > m_patterns;
+
+ bool matches( TestCaseInfo const& testCase ) const {
+ // All patterns in a filter must match for the filter to be a match
+ for( std::vector<Ptr<Pattern> >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it )
+ if( !(*it)->matches( testCase ) )
+ return false;
+ return true;
+ }
+ };
+
+ public:
+ bool hasFilters() const {
+ return !m_filters.empty();
+ }
+ bool matches( TestCaseInfo const& testCase ) const {
+ // A TestSpec matches if any filter matches
+ for( std::vector<Filter>::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it )
+ if( it->matches( testCase ) )
+ return true;
+ return false;
+ }
+
+ private:
+ std::vector<Filter> m_filters;
+
+ friend class TestSpecParser;
+ };
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+namespace Catch {
+
+ class TestSpecParser {
+ enum Mode{ None, Name, QuotedName, Tag };
+ Mode m_mode;
+ bool m_exclusion;
+ std::size_t m_start, m_pos;
+ std::string m_arg;
+ TestSpec::Filter m_currentFilter;
+ TestSpec m_testSpec;
+ ITagAliasRegistry const* m_tagAliases;
+
+ public:
+ TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}
+
+ TestSpecParser& parse( std::string const& arg ) {
+ m_mode = None;
+ m_exclusion = false;
+ m_start = std::string::npos;
+ m_arg = m_tagAliases->expandAliases( arg );
+ for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )
+ visitChar( m_arg[m_pos] );
+ if( m_mode == Name )
+ addPattern<TestSpec::NamePattern>();
+ return *this;
+ }
+ TestSpec testSpec() {
+ addFilter();
+ return m_testSpec;
+ }
+ private:
+ void visitChar( char c ) {
+ if( m_mode == None ) {
+ switch( c ) {
+ case ' ': return;
+ case '~': m_exclusion = true; return;
+ case '[': return startNewMode( Tag, ++m_pos );
+ case '"': return startNewMode( QuotedName, ++m_pos );
+ default: startNewMode( Name, m_pos ); break;
+ }
+ }
+ if( m_mode == Name ) {
+ if( c == ',' ) {
+ addPattern<TestSpec::NamePattern>();
+ addFilter();
+ }
+ else if( c == '[' ) {
+ if( subString() == "exclude:" )
+ m_exclusion = true;
+ else
+ addPattern<TestSpec::NamePattern>();
+ startNewMode( Tag, ++m_pos );
+ }
+ }
+ else if( m_mode == QuotedName && c == '"' )
+ addPattern<TestSpec::NamePattern>();
+ else if( m_mode == Tag && c == ']' )
+ addPattern<TestSpec::TagPattern>();
+ }
+ void startNewMode( Mode mode, std::size_t start ) {
+ m_mode = mode;
+ m_start = start;
+ }
+ std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); }
+ template<typename T>
+ void addPattern() {
+ std::string token = subString();
+ if( startsWith( token, "exclude:" ) ) {
+ m_exclusion = true;
+ token = token.substr( 8 );
+ }
+ if( !token.empty() ) {
+ Ptr<TestSpec::Pattern> pattern = new T( token );
+ if( m_exclusion )
+ pattern = new TestSpec::ExcludedPattern( pattern );
+ m_currentFilter.m_patterns.push_back( pattern );
+ }
+ m_exclusion = false;
+ m_mode = None;
+ }
+ void addFilter() {
+ if( !m_currentFilter.m_patterns.empty() ) {
+ m_testSpec.m_filters.push_back( m_currentFilter );
+ m_currentFilter = TestSpec::Filter();
+ }
+ }
+ };
+ inline TestSpec parseTestSpec( std::string const& arg ) {
+ return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec();
+ }
+
+} // namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// #included from: catch_interfaces_config.h
+#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace Catch {
+
+ struct Verbosity { enum Level {
+ NoOutput = 0,
+ Quiet,
+ Normal
+ }; };
+
+ struct WarnAbout { enum What {
+ Nothing = 0x00,
+ NoAssertions = 0x01
+ }; };
+
+ struct ShowDurations { enum OrNot {
+ DefaultForReporter,
+ Always,
+ Never
+ }; };
+ struct RunTests { enum InWhatOrder {
+ InDeclarationOrder,
+ InLexicographicalOrder,
+ InRandomOrder
+ }; };
+
+ class TestSpec;
+
+ struct IConfig : IShared {
+
+ virtual ~IConfig();
+
+ virtual bool allowThrows() const = 0;
+ virtual std::ostream& stream() const = 0;
+ virtual std::string name() const = 0;
+ virtual bool includeSuccessfulResults() const = 0;
+ virtual bool shouldDebugBreak() const = 0;
+ virtual bool warnAboutMissingAssertions() const = 0;
+ virtual int abortAfter() const = 0;
+ virtual bool showInvisibles() const = 0;
+ virtual ShowDurations::OrNot showDurations() const = 0;
+ virtual TestSpec const& testSpec() const = 0;
+ virtual RunTests::InWhatOrder runOrder() const = 0;
+ virtual unsigned int rngSeed() const = 0;
+ virtual bool forceColour() const = 0;
+ };
+}
+
+// #included from: catch_stream.h
+#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED
+
+#include <streambuf>
+
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+namespace Catch {
+
+ class Stream {
+ public:
+ Stream();
+ Stream( std::streambuf* _streamBuf, bool _isOwned );
+ void release();
+
+ std::streambuf* streamBuf;
+
+ private:
+ bool isOwned;
+ };
+
+ std::ostream& cout();
+ std::ostream& cerr();
+}
+
+#include <memory>
+#include <vector>
+#include <string>
+#include <iostream>
+#include <ctime>
+
+#ifndef CATCH_CONFIG_CONSOLE_WIDTH
+#define CATCH_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+namespace Catch {
+
+ struct ConfigData {
+
+ ConfigData()
+ : listTests( false ),
+ listTags( false ),
+ listReporters( false ),
+ listTestNamesOnly( false ),
+ showSuccessfulTests( false ),
+ shouldDebugBreak( false ),
+ noThrow( false ),
+ showHelp( false ),
+ showInvisibles( false ),
+ forceColour( false ),
+ abortAfter( -1 ),
+ rngSeed( 0 ),
+ verbosity( Verbosity::Normal ),
+ warnings( WarnAbout::Nothing ),
+ showDurations( ShowDurations::DefaultForReporter ),
+ runOrder( RunTests::InDeclarationOrder )
+ {}
+
+ bool listTests;
+ bool listTags;
+ bool listReporters;
+ bool listTestNamesOnly;
+
+ bool showSuccessfulTests;
+ bool shouldDebugBreak;
+ bool noThrow;
+ bool showHelp;
+ bool showInvisibles;
+ bool forceColour;
+
+ int abortAfter;
+ unsigned int rngSeed;
+
+ Verbosity::Level verbosity;
+ WarnAbout::What warnings;
+ ShowDurations::OrNot showDurations;
+ RunTests::InWhatOrder runOrder;
+
+ std::string reporterName;
+ std::string outputFilename;
+ std::string name;
+ std::string processName;
+
+ std::vector<std::string> testsOrTags;
+ };
+
+ class Config : public SharedImpl<IConfig> {
+ private:
+ Config( Config const& other );
+ Config& operator = ( Config const& other );
+ virtual void dummy();
+ public:
+
+ Config()
+ : m_os( Catch::cout().rdbuf() )
+ {}
+
+ Config( ConfigData const& data )
+ : m_data( data ),
+ m_os( Catch::cout().rdbuf() )
+ {
+ if( !data.testsOrTags.empty() ) {
+ TestSpecParser parser( ITagAliasRegistry::get() );
+ for( std::size_t i = 0; i < data.testsOrTags.size(); ++i )
+ parser.parse( data.testsOrTags[i] );
+ m_testSpec = parser.testSpec();
+ }
+ }
+
+ virtual ~Config() {
+ m_os.rdbuf( Catch::cout().rdbuf() );
+ m_stream.release();
+ }
+
+ void setFilename( std::string const& filename ) {
+ m_data.outputFilename = filename;
+ }
+
+ std::string const& getFilename() const {
+ return m_data.outputFilename ;
+ }
+
+ bool listTests() const { return m_data.listTests; }
+ bool listTestNamesOnly() const { return m_data.listTestNamesOnly; }
+ bool listTags() const { return m_data.listTags; }
+ bool listReporters() const { return m_data.listReporters; }
+
+ std::string getProcessName() const { return m_data.processName; }
+
+ bool shouldDebugBreak() const { return m_data.shouldDebugBreak; }
+
+ void setStreamBuf( std::streambuf* buf ) {
+ m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() );
+ }
+
+ void useStream( std::string const& streamName ) {
+ Stream stream = createStream( streamName );
+ setStreamBuf( stream.streamBuf );
+ m_stream.release();
+ m_stream = stream;
+ }
+
+ std::string getReporterName() const { return m_data.reporterName; }
+
+ int abortAfter() const { return m_data.abortAfter; }
+
+ TestSpec const& testSpec() const { return m_testSpec; }
+
+ bool showHelp() const { return m_data.showHelp; }
+ bool showInvisibles() const { return m_data.showInvisibles; }
+
+ // IConfig interface
+ virtual bool allowThrows() const { return !m_data.noThrow; }
+ virtual std::ostream& stream() const { return m_os; }
+ virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
+ virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; }
+ virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; }
+ virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; }
+ virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; }
+ virtual unsigned int rngSeed() const { return m_data.rngSeed; }
+ virtual bool forceColour() const { return m_data.forceColour; }
+
+ private:
+ ConfigData m_data;
+
+ Stream m_stream;
+ mutable std::ostream m_os;
+ TestSpec m_testSpec;
+ };
+
+} // end namespace Catch
+
+// #included from: catch_clara.h
+#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED
+
+// Use Catch's value for console width (store Clara's off to the side, if present)
+#ifdef CLARA_CONFIG_CONSOLE_WIDTH
+#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH
+#undef CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH
+
+// Declare Clara inside the Catch namespace
+#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch {
+// #included from: ../external/clara.h
+
+// Only use header guard if we are not using an outer namespace
+#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE)
+
+#ifndef STITCH_CLARA_OPEN_NAMESPACE
+#define TWOBLUECUBES_CLARA_H_INCLUDED
+#define STITCH_CLARA_OPEN_NAMESPACE
+#define STITCH_CLARA_CLOSE_NAMESPACE
+#else
+#define STITCH_CLARA_CLOSE_NAMESPACE }
+#endif
+
+#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE
+
+// ----------- #included from tbc_text_format.h -----------
+
+// Only use header guard if we are not using an outer namespace
+#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE)
+#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+#define TBC_TEXT_FORMAT_H_INCLUDED
+#endif
+
+#include <string>
+#include <vector>
+#include <sstream>
+
+// Use optional outer namespace
+#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE {
+#endif
+
+namespace Tbc {
+
+#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH
+ const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH;
+#else
+ const unsigned int consoleWidth = 80;
+#endif
+
+ struct TextAttributes {
+ TextAttributes()
+ : initialIndent( std::string::npos ),
+ indent( 0 ),
+ width( consoleWidth-1 ),
+ tabChar( '\t' )
+ {}
+
+ TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; }
+ TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; }
+ TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; }
+ TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; }
+
+ std::size_t initialIndent; // indent of first line, or npos
+ std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos
+ std::size_t width; // maximum width of text, including indent. Longer text will wrap
+ char tabChar; // If this char is seen the indent is changed to current pos
+ };
+
+ class Text {
+ public:
+ Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() )
+ : attr( _attr )
+ {
+ std::string wrappableChars = " [({.,/|\\-";
+ std::size_t indent = _attr.initialIndent != std::string::npos
+ ? _attr.initialIndent
+ : _attr.indent;
+ std::string remainder = _str;
+
+ while( !remainder.empty() ) {
+ if( lines.size() >= 1000 ) {
+ lines.push_back( "... message truncated due to excessive size" );
+ return;
+ }
+ std::size_t tabPos = std::string::npos;
+ std::size_t width = (std::min)( remainder.size(), _attr.width - indent );
+ std::size_t pos = remainder.find_first_of( '\n' );
+ if( pos <= width ) {
+ width = pos;
+ }
+ pos = remainder.find_last_of( _attr.tabChar, width );
+ if( pos != std::string::npos ) {
+ tabPos = pos;
+ if( remainder[width] == '\n' )
+ width--;
+ remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 );
+ }
+
+ if( width == remainder.size() ) {
+ spliceLine( indent, remainder, width );
+ }
+ else if( remainder[width] == '\n' ) {
+ spliceLine( indent, remainder, width );
+ if( width <= 1 || remainder.size() != 1 )
+ remainder = remainder.substr( 1 );
+ indent = _attr.indent;
+ }
+ else {
+ pos = remainder.find_last_of( wrappableChars, width );
+ if( pos != std::string::npos && pos > 0 ) {
+ spliceLine( indent, remainder, pos );
+ if( remainder[0] == ' ' )
+ remainder = remainder.substr( 1 );
+ }
+ else {
+ spliceLine( indent, remainder, width-1 );
+ lines.back() += "-";
+ }
+ if( lines.size() == 1 )
+ indent = _attr.indent;
+ if( tabPos != std::string::npos )
+ indent += tabPos;
+ }
+ }
+ }
+
+ void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) {
+ lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) );
+ _remainder = _remainder.substr( _pos );
+ }
+
+ typedef std::vector<std::string>::const_iterator const_iterator;
+
+ const_iterator begin() const { return lines.begin(); }
+ const_iterator end() const { return lines.end(); }
+ std::string const& last() const { return lines.back(); }
+ std::size_t size() const { return lines.size(); }
+ std::string const& operator[]( std::size_t _index ) const { return lines[_index]; }
+ std::string toString() const {
+ std::ostringstream oss;
+ oss << *this;
+ return oss.str();
+ }
+
+ inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
+ for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
+ it != itEnd; ++it ) {
+ if( it != _text.begin() )
+ _stream << "\n";
+ _stream << *it;
+ }
+ return _stream;
+ }
+
+ private:
+ std::string str;
+ TextAttributes attr;
+ std::vector<std::string> lines;
+ };
+
+} // end namespace Tbc
+
+#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+} // end outer namespace
+#endif
+
+#endif // TBC_TEXT_FORMAT_H_INCLUDED
+
+// ----------- end of #include from tbc_text_format.h -----------
+// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h
+
+#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE
+
+#include <map>
+#include <algorithm>
+#include <stdexcept>
+#include <memory>
+
+// Use optional outer namespace
+#ifdef STITCH_CLARA_OPEN_NAMESPACE
+STITCH_CLARA_OPEN_NAMESPACE
+#endif
+
+namespace Clara {
+
+ struct UnpositionalTag {};
+
+ extern UnpositionalTag _;
+
+#ifdef CLARA_CONFIG_MAIN
+ UnpositionalTag _;
+#endif
+
+ namespace Detail {
+
+#ifdef CLARA_CONSOLE_WIDTH
+ const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH;
+#else
+ const unsigned int consoleWidth = 80;
+#endif
+
+ using namespace Tbc;
+
+ inline bool startsWith( std::string const& str, std::string const& prefix ) {
+ return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix;
+ }
+
+ template<typename T> struct RemoveConstRef{ typedef T type; };
+ template<typename T> struct RemoveConstRef<T&>{ typedef T type; };
+ template<typename T> struct RemoveConstRef<T const&>{ typedef T type; };
+ template<typename T> struct RemoveConstRef<T const>{ typedef T type; };
+
+ template<typename T> struct IsBool { static const bool value = false; };
+ template<> struct IsBool<bool> { static const bool value = true; };
+
+ template<typename T>
+ void convertInto( std::string const& _source, T& _dest ) {
+ std::stringstream ss;
+ ss << _source;
+ ss >> _dest;
+ if( ss.fail() )
+ throw std::runtime_error( "Unable to convert " + _source + " to destination type" );
+ }
+ inline void convertInto( std::string const& _source, std::string& _dest ) {
+ _dest = _source;
+ }
+ inline void convertInto( std::string const& _source, bool& _dest ) {
+ std::string sourceLC = _source;
+ std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower );
+ if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" )
+ _dest = true;
+ else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" )
+ _dest = false;
+ else
+ throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" );
+ }
+ inline void convertInto( bool _source, bool& _dest ) {
+ _dest = _source;
+ }
+ template<typename T>
+ inline void convertInto( bool, T& ) {
+ throw std::runtime_error( "Invalid conversion" );
+ }
+
+ template<typename ConfigT>
+ struct IArgFunction {
+ virtual ~IArgFunction() {}
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ IArgFunction() = default;
+ IArgFunction( IArgFunction const& ) = default;
+# endif
+ virtual void set( ConfigT& config, std::string const& value ) const = 0;
+ virtual void setFlag( ConfigT& config ) const = 0;
+ virtual bool takesArg() const = 0;
+ virtual IArgFunction* clone() const = 0;
+ };
+
+ template<typename ConfigT>
+ class BoundArgFunction {
+ public:
+ BoundArgFunction() : functionObj( NULL ) {}
+ BoundArgFunction( IArgFunction<ConfigT>* _functionObj ) : functionObj( _functionObj ) {}
+ BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {}
+ BoundArgFunction& operator = ( BoundArgFunction const& other ) {
+ IArgFunction<ConfigT>* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL;
+ delete functionObj;
+ functionObj = newFunctionObj;
+ return *this;
+ }
+ ~BoundArgFunction() { delete functionObj; }
+
+ void set( ConfigT& config, std::string const& value ) const {
+ functionObj->set( config, value );
+ }
+ void setFlag( ConfigT& config ) const {
+ functionObj->setFlag( config );
+ }
+ bool takesArg() const { return functionObj->takesArg(); }
+
+ bool isSet() const {
+ return functionObj != NULL;
+ }
+ private:
+ IArgFunction<ConfigT>* functionObj;
+ };
+
+ template<typename C>
+ struct NullBinder : IArgFunction<C>{
+ virtual void set( C&, std::string const& ) const {}
+ virtual void setFlag( C& ) const {}
+ virtual bool takesArg() const { return true; }
+ virtual IArgFunction<C>* clone() const { return new NullBinder( *this ); }
+ };
+
+ template<typename C, typename M>
+ struct BoundDataMember : IArgFunction<C>{
+ BoundDataMember( M C::* _member ) : member( _member ) {}
+ virtual void set( C& p, std::string const& stringValue ) const {
+ convertInto( stringValue, p.*member );
+ }
+ virtual void setFlag( C& p ) const {
+ convertInto( true, p.*member );
+ }
+ virtual bool takesArg() const { return !IsBool<M>::value; }
+ virtual IArgFunction<C>* clone() const { return new BoundDataMember( *this ); }
+ M C::* member;
+ };
+ template<typename C, typename M>
+ struct BoundUnaryMethod : IArgFunction<C>{
+ BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {}
+ virtual void set( C& p, std::string const& stringValue ) const {
+ typename RemoveConstRef<M>::type value;
+ convertInto( stringValue, value );
+ (p.*member)( value );
+ }
+ virtual void setFlag( C& p ) const {
+ typename RemoveConstRef<M>::type value;
+ convertInto( true, value );
+ (p.*member)( value );
+ }
+ virtual bool takesArg() const { return !IsBool<M>::value; }
+ virtual IArgFunction<C>* clone() const { return new BoundUnaryMethod( *this ); }
+ void (C::*member)( M );
+ };
+ template<typename C>
+ struct BoundNullaryMethod : IArgFunction<C>{
+ BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {}
+ virtual void set( C& p, std::string const& stringValue ) const {
+ bool value;
+ convertInto( stringValue, value );
+ if( value )
+ (p.*member)();
+ }
+ virtual void setFlag( C& p ) const {
+ (p.*member)();
+ }
+ virtual bool takesArg() const { return false; }
+ virtual IArgFunction<C>* clone() const { return new BoundNullaryMethod( *this ); }
+ void (C::*member)();
+ };
+
+ template<typename C>
+ struct BoundUnaryFunction : IArgFunction<C>{
+ BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {}
+ virtual void set( C& obj, std::string const& stringValue ) const {
+ bool value;
+ convertInto( stringValue, value );
+ if( value )
+ function( obj );
+ }
+ virtual void setFlag( C& p ) const {
+ function( p );
+ }
+ virtual bool takesArg() const { return false; }
+ virtual IArgFunction<C>* clone() const { return new BoundUnaryFunction( *this ); }
+ void (*function)( C& );
+ };
+
+ template<typename C, typename T>
+ struct BoundBinaryFunction : IArgFunction<C>{
+ BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {}
+ virtual void set( C& obj, std::string const& stringValue ) const {
+ typename RemoveConstRef<T>::type value;
+ convertInto( stringValue, value );
+ function( obj, value );
+ }
+ virtual void setFlag( C& obj ) const {
+ typename RemoveConstRef<T>::type value;
+ convertInto( true, value );
+ function( obj, value );
+ }
+ virtual bool takesArg() const { return !IsBool<T>::value; }
+ virtual IArgFunction<C>* clone() const { return new BoundBinaryFunction( *this ); }
+ void (*function)( C&, T );
+ };
+
+ } // namespace Detail
+
+ struct Parser {
+ Parser() : separators( " \t=:" ) {}
+
+ struct Token {
+ enum Type { Positional, ShortOpt, LongOpt };
+ Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {}
+ Type type;
+ std::string data;
+ };
+
+ void parseIntoTokens( int argc, char const * const * argv, std::vector<Parser::Token>& tokens ) const {
+ const std::string doubleDash = "--";
+ for( int i = 1; i < argc && argv[i] != doubleDash; ++i )
+ parseIntoTokens( argv[i] , tokens);
+ }
+ void parseIntoTokens( std::string arg, std::vector<Parser::Token>& tokens ) const {
+ while( !arg.empty() ) {
+ Parser::Token token( Parser::Token::Positional, arg );
+ arg = "";
+ if( token.data[0] == '-' ) {
+ if( token.data.size() > 1 && token.data[1] == '-' ) {
+ token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) );
+ }
+ else {
+ token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) );
+ if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) {
+ arg = "-" + token.data.substr( 1 );
+ token.data = token.data.substr( 0, 1 );
+ }
+ }
+ }
+ if( token.type != Parser::Token::Positional ) {
+ std::size_t pos = token.data.find_first_of( separators );
+ if( pos != std::string::npos ) {
+ arg = token.data.substr( pos+1 );
+ token.data = token.data.substr( 0, pos );
+ }
+ }
+ tokens.push_back( token );
+ }
+ }
+ std::string separators;
+ };
+
+ template<typename ConfigT>
+ struct CommonArgProperties {
+ CommonArgProperties() {}
+ CommonArgProperties( Detail::BoundArgFunction<ConfigT> const& _boundField ) : boundField( _boundField ) {}
+
+ Detail::BoundArgFunction<ConfigT> boundField;
+ std::string description;
+ std::string detail;
+ std::string placeholder; // Only value if boundField takes an arg
+
+ bool takesArg() const {
+ return !placeholder.empty();
+ }
+ void validate() const {
+ if( !boundField.isSet() )
+ throw std::logic_error( "option not bound" );
+ }
+ };
+ struct OptionArgProperties {
+ std::vector<std::string> shortNames;
+ std::string longName;
+
+ bool hasShortName( std::string const& shortName ) const {
+ return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end();
+ }
+ bool hasLongName( std::string const& _longName ) const {
+ return _longName == longName;
+ }
+ };
+ struct PositionalArgProperties {
+ PositionalArgProperties() : position( -1 ) {}
+ int position; // -1 means non-positional (floating)
+
+ bool isFixedPositional() const {
+ return position != -1;
+ }
+ };
+
+ template<typename ConfigT>
+ class CommandLine {
+
+ struct Arg : CommonArgProperties<ConfigT>, OptionArgProperties, PositionalArgProperties {
+ Arg() {}
+ Arg( Detail::BoundArgFunction<ConfigT> const& _boundField ) : CommonArgProperties<ConfigT>( _boundField ) {}
+
+ using CommonArgProperties<ConfigT>::placeholder; // !TBD
+
+ std::string dbgName() const {
+ if( !longName.empty() )
+ return "--" + longName;
+ if( !shortNames.empty() )
+ return "-" + shortNames[0];
+ return "positional args";
+ }
+ std::string commands() const {
+ std::ostringstream oss;
+ bool first = true;
+ std::vector<std::string>::const_iterator it = shortNames.begin(), itEnd = shortNames.end();
+ for(; it != itEnd; ++it ) {
+ if( first )
+ first = false;
+ else
+ oss << ", ";
+ oss << "-" << *it;
+ }
+ if( !longName.empty() ) {
+ if( !first )
+ oss << ", ";
+ oss << "--" << longName;
+ }
+ if( !placeholder.empty() )
+ oss << " <" << placeholder << ">";
+ return oss.str();
+ }
+ };
+
+ // NOTE: std::auto_ptr is deprecated in c++11/c++0x
+#if defined(__cplusplus) && __cplusplus > 199711L
+ typedef std::unique_ptr<Arg> ArgAutoPtr;
+#else
+ typedef std::auto_ptr<Arg> ArgAutoPtr;
+#endif
+
+ friend void addOptName( Arg& arg, std::string const& optName )
+ {
+ if( optName.empty() )
+ return;
+ if( Detail::startsWith( optName, "--" ) ) {
+ if( !arg.longName.empty() )
+ throw std::logic_error( "Only one long opt may be specified. '"
+ + arg.longName
+ + "' already specified, now attempting to add '"
+ + optName + "'" );
+ arg.longName = optName.substr( 2 );
+ }
+ else if( Detail::startsWith( optName, "-" ) )
+ arg.shortNames.push_back( optName.substr( 1 ) );
+ else
+ throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" );
+ }
+ friend void setPositionalArg( Arg& arg, int position )
+ {
+ arg.position = position;
+ }
+
+ class ArgBuilder {
+ public:
+ ArgBuilder( Arg* arg ) : m_arg( arg ) {}
+
+ // Bind a non-boolean data member (requires placeholder string)
+ template<typename C, typename M>
+ void bind( M C::* field, std::string const& placeholder ) {
+ m_arg->boundField = new Detail::BoundDataMember<C,M>( field );
+ m_arg->placeholder = placeholder;
+ }
+ // Bind a boolean data member (no placeholder required)
+ template<typename C>
+ void bind( bool C::* field ) {
+ m_arg->boundField = new Detail::BoundDataMember<C,bool>( field );
+ }
+
+ // Bind a method taking a single, non-boolean argument (requires a placeholder string)
+ template<typename C, typename M>
+ void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) {
+ m_arg->boundField = new Detail::BoundUnaryMethod<C,M>( unaryMethod );
+ m_arg->placeholder = placeholder;
+ }
+
+ // Bind a method taking a single, boolean argument (no placeholder string required)
+ template<typename C>
+ void bind( void (C::* unaryMethod)( bool ) ) {
+ m_arg->boundField = new Detail::BoundUnaryMethod<C,bool>( unaryMethod );
+ }
+
+ // Bind a method that takes no arguments (will be called if opt is present)
+ template<typename C>
+ void bind( void (C::* nullaryMethod)() ) {
+ m_arg->boundField = new Detail::BoundNullaryMethod<C>( nullaryMethod );
+ }
+
+ // Bind a free function taking a single argument - the object to operate on (no placeholder string required)
+ template<typename C>
+ void bind( void (* unaryFunction)( C& ) ) {
+ m_arg->boundField = new Detail::BoundUnaryFunction<C>( unaryFunction );
+ }
+
+ // Bind a free function taking a single argument - the object to operate on (requires a placeholder string)
+ template<typename C, typename T>
+ void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) {
+ m_arg->boundField = new Detail::BoundBinaryFunction<C, T>( binaryFunction );
+ m_arg->placeholder = placeholder;
+ }
+
+ ArgBuilder& describe( std::string const& description ) {
+ m_arg->description = description;
+ return *this;
+ }
+ ArgBuilder& detail( std::string const& detail ) {
+ m_arg->detail = detail;
+ return *this;
+ }
+
+ protected:
+ Arg* m_arg;
+ };
+
+ class OptBuilder : public ArgBuilder {
+ public:
+ OptBuilder( Arg* arg ) : ArgBuilder( arg ) {}
+ OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {}
+
+ OptBuilder& operator[]( std::string const& optName ) {
+ addOptName( *ArgBuilder::m_arg, optName );
+ return *this;
+ }
+ };
+
+ public:
+
+ CommandLine()
+ : m_boundProcessName( new Detail::NullBinder<ConfigT>() ),
+ m_highestSpecifiedArgPosition( 0 ),
+ m_throwOnUnrecognisedTokens( false )
+ {}
+ CommandLine( CommandLine const& other )
+ : m_boundProcessName( other.m_boundProcessName ),
+ m_options ( other.m_options ),
+ m_positionalArgs( other.m_positionalArgs ),
+ m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ),
+ m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens )
+ {
+ if( other.m_floatingArg.get() )
+ m_floatingArg.reset( new Arg( *other.m_floatingArg ) );
+ }
+
+ CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) {
+ m_throwOnUnrecognisedTokens = shouldThrow;
+ return *this;
+ }
+
+ OptBuilder operator[]( std::string const& optName ) {
+ m_options.push_back( Arg() );
+ addOptName( m_options.back(), optName );
+ OptBuilder builder( &m_options.back() );
+ return builder;
+ }
+
+ ArgBuilder operator[]( int position ) {
+ m_positionalArgs.insert( std::make_pair( position, Arg() ) );
+ if( position > m_highestSpecifiedArgPosition )
+ m_highestSpecifiedArgPosition = position;
+ setPositionalArg( m_positionalArgs[position], position );
+ ArgBuilder builder( &m_positionalArgs[position] );
+ return builder;
+ }
+
+ // Invoke this with the _ instance
+ ArgBuilder operator[]( UnpositionalTag ) {
+ if( m_floatingArg.get() )
+ throw std::logic_error( "Only one unpositional argument can be added" );
+ m_floatingArg.reset( new Arg() );
+ ArgBuilder builder( m_floatingArg.get() );
+ return builder;
+ }
+
+ template<typename C, typename M>
+ void bindProcessName( M C::* field ) {
+ m_boundProcessName = new Detail::BoundDataMember<C,M>( field );
+ }
+ template<typename C, typename M>
+ void bindProcessName( void (C::*_unaryMethod)( M ) ) {
+ m_boundProcessName = new Detail::BoundUnaryMethod<C,M>( _unaryMethod );
+ }
+
+ void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const {
+ typename std::vector<Arg>::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it;
+ std::size_t maxWidth = 0;
+ for( it = itBegin; it != itEnd; ++it )
+ maxWidth = (std::max)( maxWidth, it->commands().size() );
+
+ for( it = itBegin; it != itEnd; ++it ) {
+ Detail::Text usage( it->commands(), Detail::TextAttributes()
+ .setWidth( maxWidth+indent )
+ .setIndent( indent ) );
+ Detail::Text desc( it->description, Detail::TextAttributes()
+ .setWidth( width - maxWidth - 3 ) );
+
+ for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) {
+ std::string usageCol = i < usage.size() ? usage[i] : "";
+ os << usageCol;
+
+ if( i < desc.size() && !desc[i].empty() )
+ os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' )
+ << desc[i];
+ os << "\n";
+ }
+ }
+ }
+ std::string optUsage() const {
+ std::ostringstream oss;
+ optUsage( oss );
+ return oss.str();
+ }
+
+ void argSynopsis( std::ostream& os ) const {
+ for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) {
+ if( i > 1 )
+ os << " ";
+ typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( i );
+ if( it != m_positionalArgs.end() )
+ os << "<" << it->second.placeholder << ">";
+ else if( m_floatingArg.get() )
+ os << "<" << m_floatingArg->placeholder << ">";
+ else
+ throw std::logic_error( "non consecutive positional arguments with no floating args" );
+ }
+ // !TBD No indication of mandatory args
+ if( m_floatingArg.get() ) {
+ if( m_highestSpecifiedArgPosition > 1 )
+ os << " ";
+ os << "[<" << m_floatingArg->placeholder << "> ...]";
+ }
+ }
+ std::string argSynopsis() const {
+ std::ostringstream oss;
+ argSynopsis( oss );
+ return oss.str();
+ }
+
+ void usage( std::ostream& os, std::string const& procName ) const {
+ validate();
+ os << "usage:\n " << procName << " ";
+ argSynopsis( os );
+ if( !m_options.empty() ) {
+ os << " [options]\n\nwhere options are: \n";
+ optUsage( os, 2 );
+ }
+ os << "\n";
+ }
+ std::string usage( std::string const& procName ) const {
+ std::ostringstream oss;
+ usage( oss, procName );
+ return oss.str();
+ }
+
+ ConfigT parse( int argc, char const * const * argv ) const {
+ ConfigT config;
+ parseInto( argc, argv, config );
+ return config;
+ }
+
+ std::vector<Parser::Token> parseInto( int argc, char const * const * argv, ConfigT& config ) const {
+ std::string processName = argv[0];
+ std::size_t lastSlash = processName.find_last_of( "/\\" );
+ if( lastSlash != std::string::npos )
+ processName = processName.substr( lastSlash+1 );
+ m_boundProcessName.set( config, processName );
+ std::vector<Parser::Token> tokens;
+ Parser parser;
+ parser.parseIntoTokens( argc, argv, tokens );
+ return populate( tokens, config );
+ }
+
+ std::vector<Parser::Token> populate( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+ validate();
+ std::vector<Parser::Token> unusedTokens = populateOptions( tokens, config );
+ unusedTokens = populateFixedArgs( unusedTokens, config );
+ unusedTokens = populateFloatingArgs( unusedTokens, config );
+ return unusedTokens;
+ }
+
+ std::vector<Parser::Token> populateOptions( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+ std::vector<Parser::Token> unusedTokens;
+ std::vector<std::string> errors;
+ for( std::size_t i = 0; i < tokens.size(); ++i ) {
+ Parser::Token const& token = tokens[i];
+ typename std::vector<Arg>::const_iterator it = m_options.begin(), itEnd = m_options.end();
+ for(; it != itEnd; ++it ) {
+ Arg const& arg = *it;
+
+ try {
+ if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) ||
+ ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) {
+ if( arg.takesArg() ) {
+ if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional )
+ errors.push_back( "Expected argument to option: " + token.data );
+ else
+ arg.boundField.set( config, tokens[++i].data );
+ }
+ else {
+ arg.boundField.setFlag( config );
+ }
+ break;
+ }
+ }
+ catch( std::exception& ex ) {
+ errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" );
+ }
+ }
+ if( it == itEnd ) {
+ if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens )
+ unusedTokens.push_back( token );
+ else if( errors.empty() && m_throwOnUnrecognisedTokens )
+ errors.push_back( "unrecognised option: " + token.data );
+ }
+ }
+ if( !errors.empty() ) {
+ std::ostringstream oss;
+ for( std::vector<std::string>::const_iterator it = errors.begin(), itEnd = errors.end();
+ it != itEnd;
+ ++it ) {
+ if( it != errors.begin() )
+ oss << "\n";
+ oss << *it;
+ }
+ throw std::runtime_error( oss.str() );
+ }
+ return unusedTokens;
+ }
+ std::vector<Parser::Token> populateFixedArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+ std::vector<Parser::Token> unusedTokens;
+ int position = 1;
+ for( std::size_t i = 0; i < tokens.size(); ++i ) {
+ Parser::Token const& token = tokens[i];
+ typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( position );
+ if( it != m_positionalArgs.end() )
+ it->second.boundField.set( config, token.data );
+ else
+ unusedTokens.push_back( token );
+ if( token.type == Parser::Token::Positional )
+ position++;
+ }
+ return unusedTokens;
+ }
+ std::vector<Parser::Token> populateFloatingArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+ if( !m_floatingArg.get() )
+ return tokens;
+ std::vector<Parser::Token> unusedTokens;
+ for( std::size_t i = 0; i < tokens.size(); ++i ) {
+ Parser::Token const& token = tokens[i];
+ if( token.type == Parser::Token::Positional )
+ m_floatingArg->boundField.set( config, token.data );
+ else
+ unusedTokens.push_back( token );
+ }
+ return unusedTokens;
+ }
+
+ void validate() const
+ {
+ if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() )
+ throw std::logic_error( "No options or arguments specified" );
+
+ for( typename std::vector<Arg>::const_iterator it = m_options.begin(),
+ itEnd = m_options.end();
+ it != itEnd; ++it )
+ it->validate();
+ }
+
+ private:
+ Detail::BoundArgFunction<ConfigT> m_boundProcessName;
+ std::vector<Arg> m_options;
+ std::map<int, Arg> m_positionalArgs;
+ ArgAutoPtr m_floatingArg;
+ int m_highestSpecifiedArgPosition;
+ bool m_throwOnUnrecognisedTokens;
+ };
+
+} // end namespace Clara
+
+STITCH_CLARA_CLOSE_NAMESPACE
+#undef STITCH_CLARA_OPEN_NAMESPACE
+#undef STITCH_CLARA_CLOSE_NAMESPACE
+
+#endif // TWOBLUECUBES_CLARA_H_INCLUDED
+#undef STITCH_CLARA_OPEN_NAMESPACE
+
+// Restore Clara's value for console width, if present
+#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+
+#include <fstream>
+
+namespace Catch {
+
+ inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; }
+ inline void abortAfterX( ConfigData& config, int x ) {
+ if( x < 1 )
+ throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" );
+ config.abortAfter = x;
+ }
+ inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); }
+
+ inline void addWarning( ConfigData& config, std::string const& _warning ) {
+ if( _warning == "NoAssertions" )
+ config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions );
+ else
+ throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" );
+ }
+ inline void setOrder( ConfigData& config, std::string const& order ) {
+ if( startsWith( "declared", order ) )
+ config.runOrder = RunTests::InDeclarationOrder;
+ else if( startsWith( "lexical", order ) )
+ config.runOrder = RunTests::InLexicographicalOrder;
+ else if( startsWith( "random", order ) )
+ config.runOrder = RunTests::InRandomOrder;
+ else
+ throw std::runtime_error( "Unrecognised ordering: '" + order + "'" );
+ }
+ inline void setRngSeed( ConfigData& config, std::string const& seed ) {
+ if( seed == "time" ) {
+ config.rngSeed = static_cast<unsigned int>( std::time(0) );
+ }
+ else {
+ std::stringstream ss;
+ ss << seed;
+ ss >> config.rngSeed;
+ if( ss.fail() )
+ throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" );
+ }
+ }
+ inline void setVerbosity( ConfigData& config, int level ) {
+ // !TBD: accept strings?
+ config.verbosity = static_cast<Verbosity::Level>( level );
+ }
+ inline void setShowDurations( ConfigData& config, bool _showDurations ) {
+ config.showDurations = _showDurations
+ ? ShowDurations::Always
+ : ShowDurations::Never;
+ }
+ inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) {
+ std::ifstream f( _filename.c_str() );
+ if( !f.is_open() )
+ throw std::domain_error( "Unable to load input file: " + _filename );
+
+ std::string line;
+ while( std::getline( f, line ) ) {
+ line = trim(line);
+ if( !line.empty() && !startsWith( line, "#" ) )
+ addTestOrTags( config, "\"" + line + "\"," );
+ }
+ }
+
+ inline Clara::CommandLine<ConfigData> makeCommandLineParser() {
+
+ using namespace Clara;
+ CommandLine<ConfigData> cli;
+
+ cli.bindProcessName( &ConfigData::processName );
+
+ cli["-?"]["-h"]["--help"]
+ .describe( "display usage information" )
+ .bind( &ConfigData::showHelp );
+
+ cli["-l"]["--list-tests"]
+ .describe( "list all/matching test cases" )
+ .bind( &ConfigData::listTests );
+
+ cli["-t"]["--list-tags"]
+ .describe( "list all/matching tags" )
+ .bind( &ConfigData::listTags );
+
+ cli["-s"]["--success"]
+ .describe( "include successful tests in output" )
+ .bind( &ConfigData::showSuccessfulTests );
+
+ cli["-b"]["--break"]
+ .describe( "break into debugger on failure" )
+ .bind( &ConfigData::shouldDebugBreak );
+
+ cli["-e"]["--nothrow"]
+ .describe( "skip exception tests" )
+ .bind( &ConfigData::noThrow );
+
+ cli["-i"]["--invisibles"]
+ .describe( "show invisibles (tabs, newlines)" )
+ .bind( &ConfigData::showInvisibles );
+
+ cli["-o"]["--out"]
+ .describe( "output filename" )
+ .bind( &ConfigData::outputFilename, "filename" );
+
+ cli["-r"]["--reporter"]
+// .placeholder( "name[:filename]" )
+ .describe( "reporter to use (defaults to console)" )
+ .bind( &ConfigData::reporterName, "name" );
+
+ cli["-n"]["--name"]
+ .describe( "suite name" )
+ .bind( &ConfigData::name, "name" );
+
+ cli["-a"]["--abort"]
+ .describe( "abort at first failure" )
+ .bind( &abortAfterFirst );
+
+ cli["-x"]["--abortx"]
+ .describe( "abort after x failures" )
+ .bind( &abortAfterX, "no. failures" );
+
+ cli["-w"]["--warn"]
+ .describe( "enable warnings" )
+ .bind( &addWarning, "warning name" );
+
+// - needs updating if reinstated
+// cli.into( &setVerbosity )
+// .describe( "level of verbosity (0=no output)" )
+// .shortOpt( "v")
+// .longOpt( "verbosity" )
+// .placeholder( "level" );
+
+ cli[_]
+ .describe( "which test or tests to use" )
+ .bind( &addTestOrTags, "test name, pattern or tags" );
+
+ cli["-d"]["--durations"]
+ .describe( "show test durations" )
+ .bind( &setShowDurations, "yes/no" );
+
+ cli["-f"]["--input-file"]
+ .describe( "load test names to run from a file" )
+ .bind( &loadTestNamesFromFile, "filename" );
+
+ // Less common commands which don't have a short form
+ cli["--list-test-names-only"]
+ .describe( "list all/matching test cases names only" )
+ .bind( &ConfigData::listTestNamesOnly );
+
+ cli["--list-reporters"]
+ .describe( "list all reporters" )
+ .bind( &ConfigData::listReporters );
+
+ cli["--order"]
+ .describe( "test case order (defaults to decl)" )
+ .bind( &setOrder, "decl|lex|rand" );
+
+ cli["--rng-seed"]
+ .describe( "set a specific seed for random numbers" )
+ .bind( &setRngSeed, "'time'|number" );
+
+ cli["--force-colour"]
+ .describe( "force colourised output" )
+ .bind( &ConfigData::forceColour );
+
+ return cli;
+ }
+
+} // end namespace Catch
+
+// #included from: internal/catch_list.hpp
+#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED
+
+// #included from: catch_text.h
+#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED
+
+#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH
+
+#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch
+// #included from: ../external/tbc_text_format.h
+// Only use header guard if we are not using an outer namespace
+#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED
+# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+# endif
+# else
+# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED
+# endif
+#endif
+#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+#include <string>
+#include <vector>
+#include <sstream>
+
+// Use optional outer namespace
+#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE {
+#endif
+
+namespace Tbc {
+
+#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH
+ const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH;
+#else
+ const unsigned int consoleWidth = 80;
+#endif
+
+ struct TextAttributes {
+ TextAttributes()
+ : initialIndent( std::string::npos ),
+ indent( 0 ),
+ width( consoleWidth-1 ),
+ tabChar( '\t' )
+ {}
+
+ TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; }
+ TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; }
+ TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; }
+ TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; }
+
+ std::size_t initialIndent; // indent of first line, or npos
+ std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos
+ std::size_t width; // maximum width of text, including indent. Longer text will wrap
+ char tabChar; // If this char is seen the indent is changed to current pos
+ };
+
+ class Text {
+ public:
+ Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() )
+ : attr( _attr )
+ {
+ std::string wrappableChars = " [({.,/|\\-";
+ std::size_t indent = _attr.initialIndent != std::string::npos
+ ? _attr.initialIndent
+ : _attr.indent;
+ std::string remainder = _str;
+
+ while( !remainder.empty() ) {
+ if( lines.size() >= 1000 ) {
+ lines.push_back( "... message truncated due to excessive size" );
+ return;
+ }
+ std::size_t tabPos = std::string::npos;
+ std::size_t width = (std::min)( remainder.size(), _attr.width - indent );
+ std::size_t pos = remainder.find_first_of( '\n' );
+ if( pos <= width ) {
+ width = pos;
+ }
+ pos = remainder.find_last_of( _attr.tabChar, width );
+ if( pos != std::string::npos ) {
+ tabPos = pos;
+ if( remainder[width] == '\n' )
+ width--;
+ remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 );
+ }
+
+ if( width == remainder.size() ) {
+ spliceLine( indent, remainder, width );
+ }
+ else if( remainder[width] == '\n' ) {
+ spliceLine( indent, remainder, width );
+ if( width <= 1 || remainder.size() != 1 )
+ remainder = remainder.substr( 1 );
+ indent = _attr.indent;
+ }
+ else {
+ pos = remainder.find_last_of( wrappableChars, width );
+ if( pos != std::string::npos && pos > 0 ) {
+ spliceLine( indent, remainder, pos );
+ if( remainder[0] == ' ' )
+ remainder = remainder.substr( 1 );
+ }
+ else {
+ spliceLine( indent, remainder, width-1 );
+ lines.back() += "-";
+ }
+ if( lines.size() == 1 )
+ indent = _attr.indent;
+ if( tabPos != std::string::npos )
+ indent += tabPos;
+ }
+ }
+ }
+
+ void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) {
+ lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) );
+ _remainder = _remainder.substr( _pos );
+ }
+
+ typedef std::vector<std::string>::const_iterator const_iterator;
+
+ const_iterator begin() const { return lines.begin(); }
+ const_iterator end() const { return lines.end(); }
+ std::string const& last() const { return lines.back(); }
+ std::size_t size() const { return lines.size(); }
+ std::string const& operator[]( std::size_t _index ) const { return lines[_index]; }
+ std::string toString() const {
+ std::ostringstream oss;
+ oss << *this;
+ return oss.str();
+ }
+
+ inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
+ for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
+ it != itEnd; ++it ) {
+ if( it != _text.begin() )
+ _stream << "\n";
+ _stream << *it;
+ }
+ return _stream;
+ }
+
+ private:
+ std::string str;
+ TextAttributes attr;
+ std::vector<std::string> lines;
+ };
+
+} // end namespace Tbc
+
+#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+} // end outer namespace
+#endif
+
+#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+
+namespace Catch {
+ using Tbc::Text;
+ using Tbc::TextAttributes;
+}
+
+// #included from: catch_console_colour.hpp
+#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED
+
+namespace Catch {
+
+ struct Colour {
+ enum Code {
+ None = 0,
+
+ White,
+ Red,
+ Green,
+ Blue,
+ Cyan,
+ Yellow,
+ Grey,
+
+ Bright = 0x10,
+
+ BrightRed = Bright | Red,
+ BrightGreen = Bright | Green,
+ LightGrey = Bright | Grey,
+ BrightWhite = Bright | White,
+
+ // By intention
+ FileName = LightGrey,
+ Warning = Yellow,
+ ResultError = BrightRed,
+ ResultSuccess = BrightGreen,
+ ResultExpectedFailure = Warning,
+
+ Error = BrightRed,
+ Success = Green,
+
+ OriginalExpression = Cyan,
+ ReconstructedExpression = Yellow,
+
+ SecondaryText = LightGrey,
+ Headers = White
+ };
+
+ // Use constructed object for RAII guard
+ Colour( Code _colourCode );
+ Colour( Colour const& other );
+ ~Colour();
+
+ // Use static method for one-shot changes
+ static void use( Code _colourCode );
+
+ private:
+ bool m_moved;
+ };
+
+ inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; }
+
+} // end namespace Catch
+
+// #included from: catch_interfaces_reporter.h
+#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED
+
+#include <string>
+#include <ostream>
+#include <map>
+#include <assert.h>
+
+namespace Catch
+{
+ struct ReporterConfig {
+ explicit ReporterConfig( Ptr<IConfig> const& _fullConfig )
+ : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {}
+
+ ReporterConfig( Ptr<IConfig> const& _fullConfig, std::ostream& _stream )
+ : m_stream( &_stream ), m_fullConfig( _fullConfig ) {}
+
+ std::ostream& stream() const { return *m_stream; }
+ Ptr<IConfig> fullConfig() const { return m_fullConfig; }
+
+ private:
+ std::ostream* m_stream;
+ Ptr<IConfig> m_fullConfig;
+ };
+
+ struct ReporterPreferences {
+ ReporterPreferences()
+ : shouldRedirectStdOut( false )
+ {}
+
+ bool shouldRedirectStdOut;
+ };
+
+ template<typename T>
+ struct LazyStat : Option<T> {
+ LazyStat() : used( false ) {}
+ LazyStat& operator=( T const& _value ) {
+ Option<T>::operator=( _value );
+ used = false;
+ return *this;
+ }
+ void reset() {
+ Option<T>::reset();
+ used = false;
+ }
+ bool used;
+ };
+
+ struct TestRunInfo {
+ TestRunInfo( std::string const& _name ) : name( _name ) {}
+ std::string name;
+ };
+ struct GroupInfo {
+ GroupInfo( std::string const& _name,
+ std::size_t _groupIndex,
+ std::size_t _groupsCount )
+ : name( _name ),
+ groupIndex( _groupIndex ),
+ groupsCounts( _groupsCount )
+ {}
+
+ std::string name;
+ std::size_t groupIndex;
+ std::size_t groupsCounts;
+ };
+
+ struct AssertionStats {
+ AssertionStats( AssertionResult const& _assertionResult,
+ std::vector<MessageInfo> const& _infoMessages,
+ Totals const& _totals )
+ : assertionResult( _assertionResult ),
+ infoMessages( _infoMessages ),
+ totals( _totals )
+ {
+ if( assertionResult.hasMessage() ) {
+ // Copy message into messages list.
+ // !TBD This should have been done earlier, somewhere
+ MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() );
+ builder << assertionResult.getMessage();
+ builder.m_info.message = builder.m_stream.str();
+
+ infoMessages.push_back( builder.m_info );
+ }
+ }
+ virtual ~AssertionStats();
+
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ AssertionStats( AssertionStats const& ) = default;
+ AssertionStats( AssertionStats && ) = default;
+ AssertionStats& operator = ( AssertionStats const& ) = default;
+ AssertionStats& operator = ( AssertionStats && ) = default;
+# endif
+
+ AssertionResult assertionResult;
+ std::vector<MessageInfo> infoMessages;
+ Totals totals;
+ };
+
+ struct SectionStats {
+ SectionStats( SectionInfo const& _sectionInfo,
+ Counts const& _assertions,
+ double _durationInSeconds,
+ bool _missingAssertions )
+ : sectionInfo( _sectionInfo ),
+ assertions( _assertions ),
+ durationInSeconds( _durationInSeconds ),
+ missingAssertions( _missingAssertions )
+ {}
+ virtual ~SectionStats();
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ SectionStats( SectionStats const& ) = default;
+ SectionStats( SectionStats && ) = default;
+ SectionStats& operator = ( SectionStats const& ) = default;
+ SectionStats& operator = ( SectionStats && ) = default;
+# endif
+
+ SectionInfo sectionInfo;
+ Counts assertions;
+ double durationInSeconds;
+ bool missingAssertions;
+ };
+
+ struct TestCaseStats {
+ TestCaseStats( TestCaseInfo const& _testInfo,
+ Totals const& _totals,
+ std::string const& _stdOut,
+ std::string const& _stdErr,
+ bool _aborting )
+ : testInfo( _testInfo ),
+ totals( _totals ),
+ stdOut( _stdOut ),
+ stdErr( _stdErr ),
+ aborting( _aborting )
+ {}
+ virtual ~TestCaseStats();
+
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ TestCaseStats( TestCaseStats const& ) = default;
+ TestCaseStats( TestCaseStats && ) = default;
+ TestCaseStats& operator = ( TestCaseStats const& ) = default;
+ TestCaseStats& operator = ( TestCaseStats && ) = default;
+# endif
+
+ TestCaseInfo testInfo;
+ Totals totals;
+ std::string stdOut;
+ std::string stdErr;
+ bool aborting;
+ };
+
+ struct TestGroupStats {
+ TestGroupStats( GroupInfo const& _groupInfo,
+ Totals const& _totals,
+ bool _aborting )
+ : groupInfo( _groupInfo ),
+ totals( _totals ),
+ aborting( _aborting )
+ {}
+ TestGroupStats( GroupInfo const& _groupInfo )
+ : groupInfo( _groupInfo ),
+ aborting( false )
+ {}
+ virtual ~TestGroupStats();
+
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ TestGroupStats( TestGroupStats const& ) = default;
+ TestGroupStats( TestGroupStats && ) = default;
+ TestGroupStats& operator = ( TestGroupStats const& ) = default;
+ TestGroupStats& operator = ( TestGroupStats && ) = default;
+# endif
+
+ GroupInfo groupInfo;
+ Totals totals;
+ bool aborting;
+ };
+
+ struct TestRunStats {
+ TestRunStats( TestRunInfo const& _runInfo,
+ Totals const& _totals,
+ bool _aborting )
+ : runInfo( _runInfo ),
+ totals( _totals ),
+ aborting( _aborting )
+ {}
+ virtual ~TestRunStats();
+
+# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ TestRunStats( TestRunStats const& _other )
+ : runInfo( _other.runInfo ),
+ totals( _other.totals ),
+ aborting( _other.aborting )
+ {}
+# else
+ TestRunStats( TestRunStats const& ) = default;
+ TestRunStats( TestRunStats && ) = default;
+ TestRunStats& operator = ( TestRunStats const& ) = default;
+ TestRunStats& operator = ( TestRunStats && ) = default;
+# endif
+
+ TestRunInfo runInfo;
+ Totals totals;
+ bool aborting;
+ };
+
+ struct IStreamingReporter : IShared {
+ virtual ~IStreamingReporter();
+
+ // Implementing class must also provide the following static method:
+ // static std::string getDescription();
+
+ virtual ReporterPreferences getPreferences() const = 0;
+
+ virtual void noMatchingTestCases( std::string const& spec ) = 0;
+
+ virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0;
+ virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0;
+
+ virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0;
+ virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0;
+
+ virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;
+
+ // The return value indicates if the messages buffer should be cleared:
+ virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0;
+ virtual void sectionEnded( SectionStats const& sectionStats ) = 0;
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0;
+ virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;
+
+ virtual void skipTest( TestCaseInfo const& testInfo ) = 0;
+ };
+
+ struct IReporterFactory {
+ virtual ~IReporterFactory();
+ virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0;
+ virtual std::string getDescription() const = 0;
+ };
+
+ struct IReporterRegistry {
+ typedef std::map<std::string, IReporterFactory*> FactoryMap;
+
+ virtual ~IReporterRegistry();
+ virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig> const& config ) const = 0;
+ virtual FactoryMap const& getFactories() const = 0;
+ };
+
+}
+
+#include <limits>
+#include <algorithm>
+
+namespace Catch {
+
+ inline std::size_t listTests( Config const& config ) {
+
+ TestSpec testSpec = config.testSpec();
+ if( config.testSpec().hasFilters() )
+ Catch::cout() << "Matching test cases:\n";
+ else {
+ Catch::cout() << "All available test cases:\n";
+ testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
+ }
+
+ std::size_t matchedTests = 0;
+ TextAttributes nameAttr, tagsAttr;
+ nameAttr.setInitialIndent( 2 ).setIndent( 4 );
+ tagsAttr.setIndent( 6 );
+
+ std::vector<TestCase> matchedTestCases;
+ getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases );
+ for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
+ it != itEnd;
+ ++it ) {
+ matchedTests++;
+ TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
+ Colour::Code colour = testCaseInfo.isHidden()
+ ? Colour::SecondaryText
+ : Colour::None;
+ Colour colourGuard( colour );
+
+ Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl;
+ if( !testCaseInfo.tags.empty() )
+ Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl;
+ }
+
+ if( !config.testSpec().hasFilters() )
+ Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl;
+ else
+ Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl;
+ return matchedTests;
+ }
+
+ inline std::size_t listTestsNamesOnly( Config const& config ) {
+ TestSpec testSpec = config.testSpec();
+ if( !config.testSpec().hasFilters() )
+ testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
+ std::size_t matchedTests = 0;
+ std::vector<TestCase> matchedTestCases;
+ getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases );
+ for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
+ it != itEnd;
+ ++it ) {
+ matchedTests++;
+ TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
+ Catch::cout() << testCaseInfo.name << std::endl;
+ }
+ return matchedTests;
+ }
+
+ struct TagInfo {
+ TagInfo() : count ( 0 ) {}
+ void add( std::string const& spelling ) {
+ ++count;
+ spellings.insert( spelling );
+ }
+ std::string all() const {
+ std::string out;
+ for( std::set<std::string>::const_iterator it = spellings.begin(), itEnd = spellings.end();
+ it != itEnd;
+ ++it )
+ out += "[" + *it + "]";
+ return out;
+ }
+ std::set<std::string> spellings;
+ std::size_t count;
+ };
+
+ inline std::size_t listTags( Config const& config ) {
+ TestSpec testSpec = config.testSpec();
+ if( config.testSpec().hasFilters() )
+ Catch::cout() << "Tags for matching test cases:\n";
+ else {
+ Catch::cout() << "All available tags:\n";
+ testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
+ }
+
+ std::map<std::string, TagInfo> tagCounts;
+
+ std::vector<TestCase> matchedTestCases;
+ getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases );
+ for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
+ it != itEnd;
+ ++it ) {
+ for( std::set<std::string>::const_iterator tagIt = it->getTestCaseInfo().tags.begin(),
+ tagItEnd = it->getTestCaseInfo().tags.end();
+ tagIt != tagItEnd;
+ ++tagIt ) {
+ std::string tagName = *tagIt;
+ std::string lcaseTagName = toLower( tagName );
+ std::map<std::string, TagInfo>::iterator countIt = tagCounts.find( lcaseTagName );
+ if( countIt == tagCounts.end() )
+ countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first;
+ countIt->second.add( tagName );
+ }
+ }
+
+ for( std::map<std::string, TagInfo>::const_iterator countIt = tagCounts.begin(),
+ countItEnd = tagCounts.end();
+ countIt != countItEnd;
+ ++countIt ) {
+ std::ostringstream oss;
+ oss << " " << std::setw(2) << countIt->second.count << " ";
+ Text wrapper( countIt->second.all(), TextAttributes()
+ .setInitialIndent( 0 )
+ .setIndent( oss.str().size() )
+ .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) );
+ Catch::cout() << oss.str() << wrapper << "\n";
+ }
+ Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl;
+ return tagCounts.size();
+ }
+
+ inline std::size_t listReporters( Config const& /*config*/ ) {
+ Catch::cout() << "Available reporters:\n";
+ IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
+ IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it;
+ std::size_t maxNameLen = 0;
+ for(it = itBegin; it != itEnd; ++it )
+ maxNameLen = (std::max)( maxNameLen, it->first.size() );
+
+ for(it = itBegin; it != itEnd; ++it ) {
+ Text wrapper( it->second->getDescription(), TextAttributes()
+ .setInitialIndent( 0 )
+ .setIndent( 7+maxNameLen )
+ .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) );
+ Catch::cout() << " "
+ << it->first
+ << ":"
+ << std::string( maxNameLen - it->first.size() + 2, ' ' )
+ << wrapper << "\n";
+ }
+ Catch::cout() << std::endl;
+ return factories.size();
+ }
+
+ inline Option<std::size_t> list( Config const& config ) {
+ Option<std::size_t> listedCount;
+ if( config.listTests() )
+ listedCount = listedCount.valueOr(0) + listTests( config );
+ if( config.listTestNamesOnly() )
+ listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config );
+ if( config.listTags() )
+ listedCount = listedCount.valueOr(0) + listTags( config );
+ if( config.listReporters() )
+ listedCount = listedCount.valueOr(0) + listReporters( config );
+ return listedCount;
+ }
+
+} // end namespace Catch
+
+// #included from: internal/catch_runner_impl.hpp
+#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED
+
+// #included from: catch_test_case_tracker.hpp
+#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
+
+#include <map>
+#include <string>
+#include <assert.h>
+
+namespace Catch {
+namespace SectionTracking {
+
+ class TrackedSection {
+
+ typedef std::map<std::string, TrackedSection> TrackedSections;
+
+ public:
+ enum RunState {
+ NotStarted,
+ Executing,
+ ExecutingChildren,
+ Completed
+ };
+
+ TrackedSection( std::string const& name, TrackedSection* parent )
+ : m_name( name ), m_runState( NotStarted ), m_parent( parent )
+ {}
+
+ RunState runState() const { return m_runState; }
+
+ TrackedSection* findChild( std::string const& childName ) {
+ TrackedSections::iterator it = m_children.find( childName );
+ return it != m_children.end()
+ ? &it->second
+ : NULL;
+ }
+ TrackedSection* acquireChild( std::string const& childName ) {
+ if( TrackedSection* child = findChild( childName ) )
+ return child;
+ m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) );
+ return findChild( childName );
+ }
+ void enter() {
+ if( m_runState == NotStarted )
+ m_runState = Executing;
+ }
+ void leave() {
+ for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end();
+ it != itEnd;
+ ++it )
+ if( it->second.runState() != Completed ) {
+ m_runState = ExecutingChildren;
+ return;
+ }
+ m_runState = Completed;
+ }
+ TrackedSection* getParent() {
+ return m_parent;
+ }
+ bool hasChildren() const {
+ return !m_children.empty();
+ }
+
+ private:
+ std::string m_name;
+ RunState m_runState;
+ TrackedSections m_children;
+ TrackedSection* m_parent;
+
+ };
+
+ class TestCaseTracker {
+ public:
+ TestCaseTracker( std::string const& testCaseName )
+ : m_testCase( testCaseName, NULL ),
+ m_currentSection( &m_testCase ),
+ m_completedASectionThisRun( false )
+ {}
+
+ bool enterSection( std::string const& name ) {
+ TrackedSection* child = m_currentSection->acquireChild( name );
+ if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed )
+ return false;
+
+ m_currentSection = child;
+ m_currentSection->enter();
+ return true;
+ }
+ void leaveSection() {
+ m_currentSection->leave();
+ m_currentSection = m_currentSection->getParent();
+ assert( m_currentSection != NULL );
+ m_completedASectionThisRun = true;
+ }
+
+ bool currentSectionHasChildren() const {
+ return m_currentSection->hasChildren();
+ }
+ bool isCompleted() const {
+ return m_testCase.runState() == TrackedSection::Completed;
+ }
+
+ class Guard {
+ public:
+ Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) {
+ m_tracker.enterTestCase();
+ }
+ ~Guard() {
+ m_tracker.leaveTestCase();
+ }
+ private:
+ Guard( Guard const& );
+ void operator = ( Guard const& );
+ TestCaseTracker& m_tracker;
+ };
+
+ private:
+ void enterTestCase() {
+ m_currentSection = &m_testCase;
+ m_completedASectionThisRun = false;
+ m_testCase.enter();
+ }
+ void leaveTestCase() {
+ m_testCase.leave();
+ }
+
+ TrackedSection m_testCase;
+ TrackedSection* m_currentSection;
+ bool m_completedASectionThisRun;
+ };
+
+} // namespace SectionTracking
+
+using SectionTracking::TestCaseTracker;
+
+} // namespace Catch
+
+// #included from: catch_fatal_condition.hpp
+#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
+
+namespace Catch {
+
+ // Report the error condition then exit the process
+ inline void fatal( std::string const& message, int exitCode ) {
+ IContext& context = Catch::getCurrentContext();
+ IResultCapture* resultCapture = context.getResultCapture();
+ resultCapture->handleFatalErrorCondition( message );
+
+ if( Catch::alwaysTrue() ) // avoids "no return" warnings
+ exit( exitCode );
+ }
+
+} // namespace Catch
+
+#if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
+
+namespace Catch {
+
+ struct FatalConditionHandler {
+ void reset() {}
+ };
+
+} // namespace Catch
+
+#else // Not Windows - assumed to be POSIX compatible //////////////////////////
+
+#include <signal.h>
+
+namespace Catch {
+
+ struct SignalDefs { int id; const char* name; };
+ extern SignalDefs signalDefs[];
+ SignalDefs signalDefs[] = {
+ { SIGINT, "SIGINT - Terminal interrupt signal" },
+ { SIGILL, "SIGILL - Illegal instruction signal" },
+ { SIGFPE, "SIGFPE - Floating point error signal" },
+ { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
+ { SIGTERM, "SIGTERM - Termination request signal" },
+ { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
+ };
+
+ struct FatalConditionHandler {
+
+ static void handleSignal( int sig ) {
+ for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+ if( sig == signalDefs[i].id )
+ fatal( signalDefs[i].name, -sig );
+ fatal( "<unknown signal>", -sig );
+ }
+
+ FatalConditionHandler() : m_isSet( true ) {
+ for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+ signal( signalDefs[i].id, handleSignal );
+ }
+ ~FatalConditionHandler() {
+ reset();
+ }
+ void reset() {
+ if( m_isSet ) {
+ for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+ signal( signalDefs[i].id, SIG_DFL );
+ m_isSet = false;
+ }
+ }
+
+ bool m_isSet;
+ };
+
+} // namespace Catch
+
+#endif // not Windows
+
+#include <set>
+#include <string>
+
+namespace Catch {
+
+ class StreamRedirect {
+
+ public:
+ StreamRedirect( std::ostream& stream, std::string& targetString )
+ : m_stream( stream ),
+ m_prevBuf( stream.rdbuf() ),
+ m_targetString( targetString )
+ {
+ stream.rdbuf( m_oss.rdbuf() );
+ }
+
+ ~StreamRedirect() {
+ m_targetString += m_oss.str();
+ m_stream.rdbuf( m_prevBuf );
+ }
+
+ private:
+ std::ostream& m_stream;
+ std::streambuf* m_prevBuf;
+ std::ostringstream m_oss;
+ std::string& m_targetString;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ class RunContext : public IResultCapture, public IRunner {
+
+ RunContext( RunContext const& );
+ void operator =( RunContext const& );
+
+ public:
+
+ explicit RunContext( Ptr<IConfig const> const& config, Ptr<IStreamingReporter> const& reporter )
+ : m_runInfo( config->name() ),
+ m_context( getCurrentMutableContext() ),
+ m_activeTestCase( NULL ),
+ m_config( config ),
+ m_reporter( reporter ),
+ m_prevRunner( m_context.getRunner() ),
+ m_prevResultCapture( m_context.getResultCapture() ),
+ m_prevConfig( m_context.getConfig() )
+ {
+ m_context.setRunner( this );
+ m_context.setConfig( m_config );
+ m_context.setResultCapture( this );
+ m_reporter->testRunStarting( m_runInfo );
+ }
+
+ virtual ~RunContext() {
+ m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) );
+ m_context.setRunner( m_prevRunner );
+ m_context.setConfig( NULL );
+ m_context.setResultCapture( m_prevResultCapture );
+ m_context.setConfig( m_prevConfig );
+ }
+
+ void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) {
+ m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) );
+ }
+ void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) {
+ m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) );
+ }
+
+ Totals runTest( TestCase const& testCase ) {
+ Totals prevTotals = m_totals;
+
+ std::string redirectedCout;
+ std::string redirectedCerr;
+
+ TestCaseInfo testInfo = testCase.getTestCaseInfo();
+
+ m_reporter->testCaseStarting( testInfo );
+
+ m_activeTestCase = &testCase;
+ m_testCaseTracker = TestCaseTracker( testInfo.name );
+
+ do {
+ do {
+ runCurrentTest( redirectedCout, redirectedCerr );
+ }
+ while( !m_testCaseTracker->isCompleted() && !aborting() );
+ }
+ while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() );
+
+ Totals deltaTotals = m_totals.delta( prevTotals );
+ m_totals.testCases += deltaTotals.testCases;
+ m_reporter->testCaseEnded( TestCaseStats( testInfo,
+ deltaTotals,
+ redirectedCout,
+ redirectedCerr,
+ aborting() ) );
+
+ m_activeTestCase = NULL;
+ m_testCaseTracker.reset();
+
+ return deltaTotals;
+ }
+
+ Ptr<IConfig const> config() const {
+ return m_config;
+ }
+
+ private: // IResultCapture
+
+ virtual void assertionEnded( AssertionResult const& result ) {
+ if( result.getResultType() == ResultWas::Ok ) {
+ m_totals.assertions.passed++;
+ }
+ else if( !result.isOk() ) {
+ m_totals.assertions.failed++;
+ }
+
+ if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) )
+ m_messages.clear();
+
+ // Reset working state
+ m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition );
+ m_lastResult = result;
+ }
+
+ virtual bool sectionStarted (
+ SectionInfo const& sectionInfo,
+ Counts& assertions
+ )
+ {
+ std::ostringstream oss;
+ oss << sectionInfo.name << "@" << sectionInfo.lineInfo;
+
+ if( !m_testCaseTracker->enterSection( oss.str() ) )
+ return false;
+
+ m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;
+
+ m_reporter->sectionStarting( sectionInfo );
+
+ assertions = m_totals.assertions;
+
+ return true;
+ }
+ bool testForMissingAssertions( Counts& assertions ) {
+ if( assertions.total() != 0 ||
+ !m_config->warnAboutMissingAssertions() ||
+ m_testCaseTracker->currentSectionHasChildren() )
+ return false;
+ m_totals.assertions.failed++;
+ assertions.failed++;
+ return true;
+ }
+
+ virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) {
+ if( std::uncaught_exception() ) {
+ m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) );
+ return;
+ }
+
+ Counts assertions = m_totals.assertions - prevAssertions;
+ bool missingAssertions = testForMissingAssertions( assertions );
+
+ m_testCaseTracker->leaveSection();
+
+ m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) );
+ m_messages.clear();
+ }
+
+ virtual void pushScopedMessage( MessageInfo const& message ) {
+ m_messages.push_back( message );
+ }
+
+ virtual void popScopedMessage( MessageInfo const& message ) {
+ m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() );
+ }
+
+ virtual std::string getCurrentTestName() const {
+ return m_activeTestCase
+ ? m_activeTestCase->getTestCaseInfo().name
+ : "";
+ }
+
+ virtual const AssertionResult* getLastResult() const {
+ return &m_lastResult;
+ }
+
+ virtual void handleFatalErrorCondition( std::string const& message ) {
+ ResultBuilder resultBuilder = makeUnexpectedResultBuilder();
+ resultBuilder.setResultType( ResultWas::FatalErrorCondition );
+ resultBuilder << message;
+ resultBuilder.captureExpression();
+
+ handleUnfinishedSections();
+
+ // Recreate section for test case (as we will lose the one that was in scope)
+ TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+ SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description );
+
+ Counts assertions;
+ assertions.failed = 1;
+ SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false );
+ m_reporter->sectionEnded( testCaseSectionStats );
+
+ TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo();
+
+ Totals deltaTotals;
+ deltaTotals.testCases.failed = 1;
+ m_reporter->testCaseEnded( TestCaseStats( testInfo,
+ deltaTotals,
+ "",
+ "",
+ false ) );
+ m_totals.testCases.failed++;
+ testGroupEnded( "", m_totals, 1, 1 );
+ m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) );
+ }
+
+ public:
+ // !TBD We need to do this another way!
+ bool aborting() const {
+ return m_totals.assertions.failed == static_cast<std::size_t>( m_config->abortAfter() );
+ }
+
+ private:
+
+ void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) {
+ TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+ SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description );
+ m_reporter->sectionStarting( testCaseSection );
+ Counts prevAssertions = m_totals.assertions;
+ double duration = 0;
+ try {
+ m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal );
+ TestCaseTracker::Guard guard( *m_testCaseTracker );
+
+ Timer timer;
+ timer.start();
+ if( m_reporter->getPreferences().shouldRedirectStdOut ) {
+ StreamRedirect coutRedir( Catch::cout(), redirectedCout );
+ StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr );
+ invokeActiveTestCase();
+ }
+ else {
+ invokeActiveTestCase();
+ }
+ duration = timer.getElapsedSeconds();
+ }
+ catch( TestFailureException& ) {
+ // This just means the test was aborted due to failure
+ }
+ catch(...) {
+ makeUnexpectedResultBuilder().useActiveException();
+ }
+ handleUnfinishedSections();
+ m_messages.clear();
+
+ Counts assertions = m_totals.assertions - prevAssertions;
+ bool missingAssertions = testForMissingAssertions( assertions );
+
+ if( testCaseInfo.okToFail() ) {
+ std::swap( assertions.failedButOk, assertions.failed );
+ m_totals.assertions.failed -= assertions.failedButOk;
+ m_totals.assertions.failedButOk += assertions.failedButOk;
+ }
+
+ SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions );
+ m_reporter->sectionEnded( testCaseSectionStats );
+ }
+
+ void invokeActiveTestCase() {
+ FatalConditionHandler fatalConditionHandler; // Handle signals
+ m_activeTestCase->invoke();
+ fatalConditionHandler.reset();
+ }
+
+ private:
+
+ ResultBuilder makeUnexpectedResultBuilder() const {
+ return ResultBuilder( m_lastAssertionInfo.macroName.c_str(),
+ m_lastAssertionInfo.lineInfo,
+ m_lastAssertionInfo.capturedExpression.c_str(),
+ m_lastAssertionInfo.resultDisposition );
+ }
+
+ void handleUnfinishedSections() {
+ // If sections ended prematurely due to an exception we stored their
+ // infos here so we can tear them down outside the unwind process.
+ for( std::vector<UnfinishedSections>::const_reverse_iterator it = m_unfinishedSections.rbegin(),
+ itEnd = m_unfinishedSections.rend();
+ it != itEnd;
+ ++it )
+ sectionEnded( it->info, it->prevAssertions, it->durationInSeconds );
+ m_unfinishedSections.clear();
+ }
+
+ struct UnfinishedSections {
+ UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds )
+ : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds )
+ {}
+
+ SectionInfo info;
+ Counts prevAssertions;
+ double durationInSeconds;
+ };
+
+ TestRunInfo m_runInfo;
+ IMutableContext& m_context;
+ TestCase const* m_activeTestCase;
+ Option<TestCaseTracker> m_testCaseTracker;
+ AssertionResult m_lastResult;
+
+ Ptr<IConfig const> m_config;
+ Totals m_totals;
+ Ptr<IStreamingReporter> m_reporter;
+ std::vector<MessageInfo> m_messages;
+ IRunner* m_prevRunner;
+ IResultCapture* m_prevResultCapture;
+ Ptr<IConfig const> m_prevConfig;
+ AssertionInfo m_lastAssertionInfo;
+ std::vector<UnfinishedSections> m_unfinishedSections;
+ };
+
+ IResultCapture& getResultCapture() {
+ if( IResultCapture* capture = getCurrentContext().getResultCapture() )
+ return *capture;
+ else
+ throw std::logic_error( "No result capture instance" );
+ }
+
+} // end namespace Catch
+
+// #included from: internal/catch_version.h
+#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED
+
+namespace Catch {
+
+ // Versioning information
+ struct Version {
+ Version( unsigned int _majorVersion,
+ unsigned int _minorVersion,
+ unsigned int _buildNumber,
+ char const* const _branchName )
+ : majorVersion( _majorVersion ),
+ minorVersion( _minorVersion ),
+ buildNumber( _buildNumber ),
+ branchName( _branchName )
+ {}
+
+ unsigned int const majorVersion;
+ unsigned int const minorVersion;
+ unsigned int const buildNumber;
+ char const* const branchName;
+
+ private:
+ void operator=( Version const& );
+ };
+
+ extern Version libraryVersion;
+}
+
+#include <fstream>
+#include <stdlib.h>
+#include <limits>
+
+namespace Catch {
+
+ class Runner {
+
+ public:
+ Runner( Ptr<Config> const& config )
+ : m_config( config )
+ {
+ openStream();
+ makeReporter();
+ }
+
+ Totals runTests() {
+
+ RunContext context( m_config.get(), m_reporter );
+
+ Totals totals;
+
+ context.testGroupStarting( "all tests", 1, 1 ); // deprecated?
+
+ TestSpec testSpec = m_config->testSpec();
+ if( !testSpec.hasFilters() )
+ testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests
+
+ std::vector<TestCase> testCases;
+ getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases );
+
+ int testsRunForGroup = 0;
+ for( std::vector<TestCase>::const_iterator it = testCases.begin(), itEnd = testCases.end();
+ it != itEnd;
+ ++it ) {
+ testsRunForGroup++;
+ if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) {
+
+ if( context.aborting() )
+ break;
+
+ totals += context.runTest( *it );
+ m_testsAlreadyRun.insert( *it );
+ }
+ }
+ std::vector<TestCase> skippedTestCases;
+ getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true );
+
+ for( std::vector<TestCase>::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end();
+ it != itEnd;
+ ++it )
+ m_reporter->skipTest( *it );
+
+ context.testGroupEnded( "all tests", totals, 1, 1 );
+ return totals;
+ }
+
+ private:
+ void openStream() {
+ // Open output file, if specified
+ if( !m_config->getFilename().empty() ) {
+ m_ofs.open( m_config->getFilename().c_str() );
+ if( m_ofs.fail() ) {
+ std::ostringstream oss;
+ oss << "Unable to open file: '" << m_config->getFilename() << "'";
+ throw std::domain_error( oss.str() );
+ }
+ m_config->setStreamBuf( m_ofs.rdbuf() );
+ }
+ }
+ void makeReporter() {
+ std::string reporterName = m_config->getReporterName().empty()
+ ? "console"
+ : m_config->getReporterName();
+
+ m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() );
+ if( !m_reporter ) {
+ std::ostringstream oss;
+ oss << "No reporter registered with name: '" << reporterName << "'";
+ throw std::domain_error( oss.str() );
+ }
+ }
+
+ private:
+ Ptr<Config> m_config;
+ std::ofstream m_ofs;
+ Ptr<IStreamingReporter> m_reporter;
+ std::set<TestCase> m_testsAlreadyRun;
+ };
+
+ class Session : NonCopyable {
+ static bool alreadyInstantiated;
+
+ public:
+
+ struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; };
+
+ Session()
+ : m_cli( makeCommandLineParser() ) {
+ if( alreadyInstantiated ) {
+ std::string msg = "Only one instance of Catch::Session can ever be used";
+ Catch::cerr() << msg << std::endl;
+ throw std::logic_error( msg );
+ }
+ alreadyInstantiated = true;
+ }
+ ~Session() {
+ Catch::cleanUp();
+ }
+
+ void showHelp( std::string const& processName ) {
+ Catch::cout() << "\nCatch v" << libraryVersion.majorVersion << "."
+ << libraryVersion.minorVersion << " build "
+ << libraryVersion.buildNumber;
+ if( libraryVersion.branchName != std::string( "master" ) )
+ Catch::cout() << " (" << libraryVersion.branchName << " branch)";
+ Catch::cout() << "\n";
+
+ m_cli.usage( Catch::cout(), processName );
+ Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
+ }
+
+ int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) {
+ try {
+ m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail );
+ m_unusedTokens = m_cli.parseInto( argc, argv, m_configData );
+ if( m_configData.showHelp )
+ showHelp( m_configData.processName );
+ m_config.reset();
+ }
+ catch( std::exception& ex ) {
+ {
+ Colour colourGuard( Colour::Red );
+ Catch::cerr() << "\nError(s) in input:\n"
+ << Text( ex.what(), TextAttributes().setIndent(2) )
+ << "\n\n";
+ }
+ m_cli.usage( Catch::cout(), m_configData.processName );
+ return (std::numeric_limits<int>::max)();
+ }
+ return 0;
+ }
+
+ void useConfigData( ConfigData const& _configData ) {
+ m_configData = _configData;
+ m_config.reset();
+ }
+
+ int run( int argc, char* const argv[] ) {
+
+ int returnCode = applyCommandLine( argc, argv );
+ if( returnCode == 0 )
+ returnCode = run();
+ return returnCode;
+ }
+
+ int run() {
+ if( m_configData.showHelp )
+ return 0;
+
+ try
+ {
+ config(); // Force config to be constructed
+
+ std::srand( m_configData.rngSeed );
+
+ Runner runner( m_config );
+
+ // Handle list request
+ if( Option<std::size_t> listed = list( config() ) )
+ return static_cast<int>( *listed );
+
+ return static_cast<int>( runner.runTests().assertions.failed );
+ }
+ catch( std::exception& ex ) {
+ Catch::cerr() << ex.what() << std::endl;
+ return (std::numeric_limits<int>::max)();
+ }
+ }
+
+ Clara::CommandLine<ConfigData> const& cli() const {
+ return m_cli;
+ }
+ std::vector<Clara::Parser::Token> const& unusedTokens() const {
+ return m_unusedTokens;
+ }
+ ConfigData& configData() {
+ return m_configData;
+ }
+ Config& config() {
+ if( !m_config )
+ m_config = new Config( m_configData );
+ return *m_config;
+ }
+
+ private:
+ Clara::CommandLine<ConfigData> m_cli;
+ std::vector<Clara::Parser::Token> m_unusedTokens;
+ ConfigData m_configData;
+ Ptr<Config> m_config;
+ };
+
+ bool Session::alreadyInstantiated = false;
+
+} // end namespace Catch
+
+// #included from: catch_registry_hub.hpp
+#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED
+
+// #included from: catch_test_case_registry_impl.hpp
+#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED
+
+#include <vector>
+#include <set>
+#include <sstream>
+#include <iostream>
+#include <algorithm>
+
+namespace Catch {
+
+ class TestRegistry : public ITestCaseRegistry {
+ struct LexSort {
+ bool operator() (TestCase i,TestCase j) const { return (i<j);}
+ };
+ struct RandomNumberGenerator {
+ int operator()( int n ) const { return std::rand() % n; }
+ };
+
+ public:
+ TestRegistry() : m_unnamedCount( 0 ) {}
+ virtual ~TestRegistry();
+
+ virtual void registerTest( TestCase const& testCase ) {
+ std::string name = testCase.getTestCaseInfo().name;
+ if( name == "" ) {
+ std::ostringstream oss;
+ oss << "Anonymous test case " << ++m_unnamedCount;
+ return registerTest( testCase.withName( oss.str() ) );
+ }
+
+ if( m_functions.find( testCase ) == m_functions.end() ) {
+ m_functions.insert( testCase );
+ m_functionsInOrder.push_back( testCase );
+ if( !testCase.isHidden() )
+ m_nonHiddenFunctions.push_back( testCase );
+ }
+ else {
+ TestCase const& prev = *m_functions.find( testCase );
+ {
+ Colour colourGuard( Colour::Red );
+ Catch::cerr() << "error: TEST_CASE( \"" << name << "\" ) already defined.\n"
+ << "\tFirst seen at " << prev.getTestCaseInfo().lineInfo << "\n"
+ << "\tRedefined at " << testCase.getTestCaseInfo().lineInfo << std::endl;
+ }
+ exit(1);
+ }
+ }
+
+ virtual std::vector<TestCase> const& getAllTests() const {
+ return m_functionsInOrder;
+ }
+
+ virtual std::vector<TestCase> const& getAllNonHiddenTests() const {
+ return m_nonHiddenFunctions;
+ }
+
+ virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases, bool negated = false ) const {
+
+ for( std::vector<TestCase>::const_iterator it = m_functionsInOrder.begin(),
+ itEnd = m_functionsInOrder.end();
+ it != itEnd;
+ ++it ) {
+ bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() );
+ if( includeTest != negated )
+ matchingTestCases.push_back( *it );
+ }
+ sortTests( config, matchingTestCases );
+ }
+
+ private:
+
+ static void sortTests( IConfig const& config, std::vector<TestCase>& matchingTestCases ) {
+
+ switch( config.runOrder() ) {
+ case RunTests::InLexicographicalOrder:
+ std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() );
+ break;
+ case RunTests::InRandomOrder:
+ {
+ RandomNumberGenerator rng;
+ std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng );
+ }
+ break;
+ case RunTests::InDeclarationOrder:
+ // already in declaration order
+ break;
+ }
+ }
+ std::set<TestCase> m_functions;
+ std::vector<TestCase> m_functionsInOrder;
+ std::vector<TestCase> m_nonHiddenFunctions;
+ size_t m_unnamedCount;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ class FreeFunctionTestCase : public SharedImpl<ITestCase> {
+ public:
+
+ FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {}
+
+ virtual void invoke() const {
+ m_fun();
+ }
+
+ private:
+ virtual ~FreeFunctionTestCase();
+
+ TestFunction m_fun;
+ };
+
+ inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) {
+ std::string className = classOrQualifiedMethodName;
+ if( startsWith( className, "&" ) )
+ {
+ std::size_t lastColons = className.rfind( "::" );
+ std::size_t penultimateColons = className.rfind( "::", lastColons-1 );
+ if( penultimateColons == std::string::npos )
+ penultimateColons = 1;
+ className = className.substr( penultimateColons, lastColons-penultimateColons );
+ }
+ return className;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ AutoReg::AutoReg( TestFunction function,
+ SourceLineInfo const& lineInfo,
+ NameAndDesc const& nameAndDesc ) {
+ registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo );
+ }
+
+ AutoReg::~AutoReg() {}
+
+ void AutoReg::registerTestCase( ITestCase* testCase,
+ char const* classOrQualifiedMethodName,
+ NameAndDesc const& nameAndDesc,
+ SourceLineInfo const& lineInfo ) {
+
+ getMutableRegistryHub().registerTest
+ ( makeTestCase( testCase,
+ extractClassName( classOrQualifiedMethodName ),
+ nameAndDesc.name,
+ nameAndDesc.description,
+ lineInfo ) );
+ }
+
+} // end namespace Catch
+
+// #included from: catch_reporter_registry.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED
+
+#include <map>
+
+namespace Catch {
+
+ class ReporterRegistry : public IReporterRegistry {
+
+ public:
+
+ virtual ~ReporterRegistry() {
+ deleteAllValues( m_factories );
+ }
+
+ virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig> const& config ) const {
+ FactoryMap::const_iterator it = m_factories.find( name );
+ if( it == m_factories.end() )
+ return NULL;
+ return it->second->create( ReporterConfig( config ) );
+ }
+
+ void registerReporter( std::string const& name, IReporterFactory* factory ) {
+ m_factories.insert( std::make_pair( name, factory ) );
+ }
+
+ FactoryMap const& getFactories() const {
+ return m_factories;
+ }
+
+ private:
+ FactoryMap m_factories;
+ };
+}
+
+// #included from: catch_exception_translator_registry.hpp
+#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED
+
+#ifdef __OBJC__
+#import "Foundation/Foundation.h"
+#endif
+
+namespace Catch {
+
+ class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {
+ public:
+ ~ExceptionTranslatorRegistry() {
+ deleteAll( m_translators );
+ }
+
+ virtual void registerTranslator( const IExceptionTranslator* translator ) {
+ m_translators.push_back( translator );
+ }
+
+ virtual std::string translateActiveException() const {
+ try {
+#ifdef __OBJC__
+ // In Objective-C try objective-c exceptions first
+ @try {
+ throw;
+ }
+ @catch (NSException *exception) {
+ return Catch::toString( [exception description] );
+ }
+#else
+ throw;
+#endif
+ }
+ catch( TestFailureException& ) {
+ throw;
+ }
+ catch( std::exception& ex ) {
+ return ex.what();
+ }
+ catch( std::string& msg ) {
+ return msg;
+ }
+ catch( const char* msg ) {
+ return msg;
+ }
+ catch(...) {
+ return tryTranslators( m_translators.begin() );
+ }
+ }
+
+ std::string tryTranslators( std::vector<const IExceptionTranslator*>::const_iterator it ) const {
+ if( it == m_translators.end() )
+ return "Unknown exception";
+
+ try {
+ return (*it)->translate();
+ }
+ catch(...) {
+ return tryTranslators( it+1 );
+ }
+ }
+
+ private:
+ std::vector<const IExceptionTranslator*> m_translators;
+ };
+}
+
+namespace Catch {
+
+ namespace {
+
+ class RegistryHub : public IRegistryHub, public IMutableRegistryHub {
+
+ RegistryHub( RegistryHub const& );
+ void operator=( RegistryHub const& );
+
+ public: // IRegistryHub
+ RegistryHub() {
+ }
+ virtual IReporterRegistry const& getReporterRegistry() const {
+ return m_reporterRegistry;
+ }
+ virtual ITestCaseRegistry const& getTestCaseRegistry() const {
+ return m_testCaseRegistry;
+ }
+ virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() {
+ return m_exceptionTranslatorRegistry;
+ }
+
+ public: // IMutableRegistryHub
+ virtual void registerReporter( std::string const& name, IReporterFactory* factory ) {
+ m_reporterRegistry.registerReporter( name, factory );
+ }
+ virtual void registerTest( TestCase const& testInfo ) {
+ m_testCaseRegistry.registerTest( testInfo );
+ }
+ virtual void registerTranslator( const IExceptionTranslator* translator ) {
+ m_exceptionTranslatorRegistry.registerTranslator( translator );
+ }
+
+ private:
+ TestRegistry m_testCaseRegistry;
+ ReporterRegistry m_reporterRegistry;
+ ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
+ };
+
+ // Single, global, instance
+ inline RegistryHub*& getTheRegistryHub() {
+ static RegistryHub* theRegistryHub = NULL;
+ if( !theRegistryHub )
+ theRegistryHub = new RegistryHub();
+ return theRegistryHub;
+ }
+ }
+
+ IRegistryHub& getRegistryHub() {
+ return *getTheRegistryHub();
+ }
+ IMutableRegistryHub& getMutableRegistryHub() {
+ return *getTheRegistryHub();
+ }
+ void cleanUp() {
+ delete getTheRegistryHub();
+ getTheRegistryHub() = NULL;
+ cleanUpContext();
+ }
+ std::string translateActiveException() {
+ return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
+ }
+
+} // end namespace Catch
+
+// #included from: catch_notimplemented_exception.hpp
+#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED
+
+#include <ostream>
+
+namespace Catch {
+
+ NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo )
+ : m_lineInfo( lineInfo ) {
+ std::ostringstream oss;
+ oss << lineInfo << ": function ";
+ oss << "not implemented";
+ m_what = oss.str();
+ }
+
+ const char* NotImplementedException::what() const CATCH_NOEXCEPT {
+ return m_what.c_str();
+ }
+
+} // end namespace Catch
+
+// #included from: catch_context_impl.hpp
+#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED
+
+// #included from: catch_stream.hpp
+#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED
+
+// #included from: catch_streambuf.h
+#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED
+
+#include <streambuf>
+
+namespace Catch {
+
+ class StreamBufBase : public std::streambuf {
+ public:
+ virtual ~StreamBufBase() CATCH_NOEXCEPT;
+ };
+}
+
+#include <stdexcept>
+#include <cstdio>
+#include <iostream>
+
+namespace Catch {
+
+ template<typename WriterF, size_t bufferSize=256>
+ class StreamBufImpl : public StreamBufBase {
+ char data[bufferSize];
+ WriterF m_writer;
+
+ public:
+ StreamBufImpl() {
+ setp( data, data + sizeof(data) );
+ }
+
+ ~StreamBufImpl() CATCH_NOEXCEPT {
+ sync();
+ }
+
+ private:
+ int overflow( int c ) {
+ sync();
+
+ if( c != EOF ) {
+ if( pbase() == epptr() )
+ m_writer( std::string( 1, static_cast<char>( c ) ) );
+ else
+ sputc( static_cast<char>( c ) );
+ }
+ return 0;
+ }
+
+ int sync() {
+ if( pbase() != pptr() ) {
+ m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) );
+ setp( pbase(), epptr() );
+ }
+ return 0;
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ struct OutputDebugWriter {
+
+ void operator()( std::string const&str ) {
+ writeToDebugConsole( str );
+ }
+ };
+
+ Stream::Stream()
+ : streamBuf( NULL ), isOwned( false )
+ {}
+
+ Stream::Stream( std::streambuf* _streamBuf, bool _isOwned )
+ : streamBuf( _streamBuf ), isOwned( _isOwned )
+ {}
+
+ void Stream::release() {
+ if( isOwned ) {
+ delete streamBuf;
+ streamBuf = NULL;
+ isOwned = false;
+ }
+ }
+
+#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions
+ std::ostream& cout() {
+ return std::cout;
+ }
+ std::ostream& cerr() {
+ return std::cerr;
+ }
+#endif
+}
+
+namespace Catch {
+
+ class Context : public IMutableContext {
+
+ Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {}
+ Context( Context const& );
+ void operator=( Context const& );
+
+ public: // IContext
+ virtual IResultCapture* getResultCapture() {
+ return m_resultCapture;
+ }
+ virtual IRunner* getRunner() {
+ return m_runner;
+ }
+ virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) {
+ return getGeneratorsForCurrentTest()
+ .getGeneratorInfo( fileInfo, totalSize )
+ .getCurrentIndex();
+ }
+ virtual bool advanceGeneratorsForCurrentTest() {
+ IGeneratorsForTest* generators = findGeneratorsForCurrentTest();
+ return generators && generators->moveNext();
+ }
+
+ virtual Ptr<IConfig const> getConfig() const {
+ return m_config;
+ }
+
+ public: // IMutableContext
+ virtual void setResultCapture( IResultCapture* resultCapture ) {
+ m_resultCapture = resultCapture;
+ }
+ virtual void setRunner( IRunner* runner ) {
+ m_runner = runner;
+ }
+ virtual void setConfig( Ptr<IConfig const> const& config ) {
+ m_config = config;
+ }
+
+ friend IMutableContext& getCurrentMutableContext();
+
+ private:
+ IGeneratorsForTest* findGeneratorsForCurrentTest() {
+ std::string testName = getResultCapture()->getCurrentTestName();
+
+ std::map<std::string, IGeneratorsForTest*>::const_iterator it =
+ m_generatorsByTestName.find( testName );
+ return it != m_generatorsByTestName.end()
+ ? it->second
+ : NULL;
+ }
+
+ IGeneratorsForTest& getGeneratorsForCurrentTest() {
+ IGeneratorsForTest* generators = findGeneratorsForCurrentTest();
+ if( !generators ) {
+ std::string testName = getResultCapture()->getCurrentTestName();
+ generators = createGeneratorsForTest();
+ m_generatorsByTestName.insert( std::make_pair( testName, generators ) );
+ }
+ return *generators;
+ }
+
+ private:
+ Ptr<IConfig const> m_config;
+ IRunner* m_runner;
+ IResultCapture* m_resultCapture;
+ std::map<std::string, IGeneratorsForTest*> m_generatorsByTestName;
+ };
+
+ namespace {
+ Context* currentContext = NULL;
+ }
+ IMutableContext& getCurrentMutableContext() {
+ if( !currentContext )
+ currentContext = new Context();
+ return *currentContext;
+ }
+ IContext& getCurrentContext() {
+ return getCurrentMutableContext();
+ }
+
+ Stream createStream( std::string const& streamName ) {
+ if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false );
+ if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false );
+ if( streamName == "debug" ) return Stream( new StreamBufImpl<OutputDebugWriter>, true );
+
+ throw std::domain_error( "Unknown stream: " + streamName );
+ }
+
+ void cleanUpContext() {
+ delete currentContext;
+ currentContext = NULL;
+ }
+}
+
+// #included from: catch_console_colour_impl.hpp
+#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED
+
+namespace Catch {
+ namespace {
+
+ struct IColourImpl {
+ virtual ~IColourImpl() {}
+ virtual void use( Colour::Code _colourCode ) = 0;
+ };
+
+ struct NoColourImpl : IColourImpl {
+ void use( Colour::Code ) {}
+
+ static IColourImpl* instance() {
+ static NoColourImpl s_instance;
+ return &s_instance;
+ }
+ };
+
+ } // anon namespace
+} // namespace Catch
+
+#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )
+# ifdef CATCH_PLATFORM_WINDOWS
+# define CATCH_CONFIG_COLOUR_WINDOWS
+# else
+# define CATCH_CONFIG_COLOUR_ANSI
+# endif
+#endif
+
+#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#ifdef __AFXDLL
+#include <AfxWin.h>
+#else
+#include <windows.h>
+#endif
+
+namespace Catch {
+namespace {
+
+ class Win32ColourImpl : public IColourImpl {
+ public:
+ Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) )
+ {
+ CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+ GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo );
+ originalAttributes = csbiInfo.wAttributes;
+ }
+
+ virtual void use( Colour::Code _colourCode ) {
+ switch( _colourCode ) {
+ case Colour::None: return setTextAttribute( originalAttributes );
+ case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
+ case Colour::Red: return setTextAttribute( FOREGROUND_RED );
+ case Colour::Green: return setTextAttribute( FOREGROUND_GREEN );
+ case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE );
+ case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN );
+ case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN );
+ case Colour::Grey: return setTextAttribute( 0 );
+
+ case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY );
+ case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED );
+ case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN );
+ case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
+
+ case Colour::Bright: throw std::logic_error( "not a colour" );
+ }
+ }
+
+ private:
+ void setTextAttribute( WORD _textAttribute ) {
+ SetConsoleTextAttribute( stdoutHandle, _textAttribute );
+ }
+ HANDLE stdoutHandle;
+ WORD originalAttributes;
+ };
+
+ IColourImpl* platformColourInstance() {
+ static Win32ColourImpl s_instance;
+ return &s_instance;
+ }
+
+} // end anon namespace
+} // end namespace Catch
+
+#elif defined( CATCH_CONFIG_COLOUR_ANSI ) //////////////////////////////////////
+
+#include <unistd.h>
+
+namespace Catch {
+namespace {
+
+ // use POSIX/ ANSI console terminal codes
+ // Thanks to Adam Strzelecki for original contribution
+ // (http://github.com/nanoant)
+ // https://github.com/philsquared/Catch/pull/131
+ class PosixColourImpl : public IColourImpl {
+ public:
+ virtual void use( Colour::Code _colourCode ) {
+ switch( _colourCode ) {
+ case Colour::None:
+ case Colour::White: return setColour( "[0m" );
+ case Colour::Red: return setColour( "[0;31m" );
+ case Colour::Green: return setColour( "[0;32m" );
+ case Colour::Blue: return setColour( "[0:34m" );
+ case Colour::Cyan: return setColour( "[0;36m" );
+ case Colour::Yellow: return setColour( "[0;33m" );
+ case Colour::Grey: return setColour( "[1;30m" );
+
+ case Colour::LightGrey: return setColour( "[0;37m" );
+ case Colour::BrightRed: return setColour( "[1;31m" );
+ case Colour::BrightGreen: return setColour( "[1;32m" );
+ case Colour::BrightWhite: return setColour( "[1;37m" );
+
+ case Colour::Bright: throw std::logic_error( "not a colour" );
+ }
+ }
+ static IColourImpl* instance() {
+ static PosixColourImpl s_instance;
+ return &s_instance;
+ }
+
+ private:
+ void setColour( const char* _escapeCode ) {
+ Catch::cout() << '\033' << _escapeCode;
+ }
+ };
+
+ IColourImpl* platformColourInstance() {
+ Ptr<IConfig const> config = getCurrentContext().getConfig();
+ return (config && config->forceColour()) || isatty(STDOUT_FILENO)
+ ? PosixColourImpl::instance()
+ : NoColourImpl::instance();
+ }
+
+} // end anon namespace
+} // end namespace Catch
+
+#else // not Windows or ANSI ///////////////////////////////////////////////
+
+namespace Catch {
+
+ static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); }
+
+} // end namespace Catch
+
+#endif // Windows/ ANSI/ None
+
+namespace Catch {
+
+ Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); }
+ Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast<Colour&>( _other ).m_moved = true; }
+ Colour::~Colour(){ if( !m_moved ) use( None ); }
+
+ void Colour::use( Code _colourCode ) {
+ static IColourImpl* impl = isDebuggerActive()
+ ? NoColourImpl::instance()
+ : platformColourInstance();
+ impl->use( _colourCode );
+ }
+
+} // end namespace Catch
+
+// #included from: catch_generators_impl.hpp
+#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED
+
+#include <vector>
+#include <string>
+#include <map>
+
+namespace Catch {
+
+ struct GeneratorInfo : IGeneratorInfo {
+
+ GeneratorInfo( std::size_t size )
+ : m_size( size ),
+ m_currentIndex( 0 )
+ {}
+
+ bool moveNext() {
+ if( ++m_currentIndex == m_size ) {
+ m_currentIndex = 0;
+ return false;
+ }
+ return true;
+ }
+
+ std::size_t getCurrentIndex() const {
+ return m_currentIndex;
+ }
+
+ std::size_t m_size;
+ std::size_t m_currentIndex;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ class GeneratorsForTest : public IGeneratorsForTest {
+
+ public:
+ ~GeneratorsForTest() {
+ deleteAll( m_generatorsInOrder );
+ }
+
+ IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) {
+ std::map<std::string, IGeneratorInfo*>::const_iterator it = m_generatorsByName.find( fileInfo );
+ if( it == m_generatorsByName.end() ) {
+ IGeneratorInfo* info = new GeneratorInfo( size );
+ m_generatorsByName.insert( std::make_pair( fileInfo, info ) );
+ m_generatorsInOrder.push_back( info );
+ return *info;
+ }
+ return *it->second;
+ }
+
+ bool moveNext() {
+ std::vector<IGeneratorInfo*>::const_iterator it = m_generatorsInOrder.begin();
+ std::vector<IGeneratorInfo*>::const_iterator itEnd = m_generatorsInOrder.end();
+ for(; it != itEnd; ++it ) {
+ if( (*it)->moveNext() )
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ std::map<std::string, IGeneratorInfo*> m_generatorsByName;
+ std::vector<IGeneratorInfo*> m_generatorsInOrder;
+ };
+
+ IGeneratorsForTest* createGeneratorsForTest()
+ {
+ return new GeneratorsForTest();
+ }
+
+} // end namespace Catch
+
+// #included from: catch_assertionresult.hpp
+#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED
+
+namespace Catch {
+
+ AssertionInfo::AssertionInfo( std::string const& _macroName,
+ SourceLineInfo const& _lineInfo,
+ std::string const& _capturedExpression,
+ ResultDisposition::Flags _resultDisposition )
+ : macroName( _macroName ),
+ lineInfo( _lineInfo ),
+ capturedExpression( _capturedExpression ),
+ resultDisposition( _resultDisposition )
+ {}
+
+ AssertionResult::AssertionResult() {}
+
+ AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data )
+ : m_info( info ),
+ m_resultData( data )
+ {}
+
+ AssertionResult::~AssertionResult() {}
+
+ // Result was a success
+ bool AssertionResult::succeeded() const {
+ return Catch::isOk( m_resultData.resultType );
+ }
+
+ // Result was a success, or failure is suppressed
+ bool AssertionResult::isOk() const {
+ return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );
+ }
+
+ ResultWas::OfType AssertionResult::getResultType() const {
+ return m_resultData.resultType;
+ }
+
+ bool AssertionResult::hasExpression() const {
+ return !m_info.capturedExpression.empty();
+ }
+
+ bool AssertionResult::hasMessage() const {
+ return !m_resultData.message.empty();
+ }
+
+ std::string AssertionResult::getExpression() const {
+ if( isFalseTest( m_info.resultDisposition ) )
+ return "!" + m_info.capturedExpression;
+ else
+ return m_info.capturedExpression;
+ }
+ std::string AssertionResult::getExpressionInMacro() const {
+ if( m_info.macroName.empty() )
+ return m_info.capturedExpression;
+ else
+ return m_info.macroName + "( " + m_info.capturedExpression + " )";
+ }
+
+ bool AssertionResult::hasExpandedExpression() const {
+ return hasExpression() && getExpandedExpression() != getExpression();
+ }
+
+ std::string AssertionResult::getExpandedExpression() const {
+ return m_resultData.reconstructedExpression;
+ }
+
+ std::string AssertionResult::getMessage() const {
+ return m_resultData.message;
+ }
+ SourceLineInfo AssertionResult::getSourceInfo() const {
+ return m_info.lineInfo;
+ }
+
+ std::string AssertionResult::getTestMacroName() const {
+ return m_info.macroName;
+ }
+
+} // end namespace Catch
+
+// #included from: catch_test_case_info.hpp
+#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED
+
+namespace Catch {
+
+ inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
+ if( startsWith( tag, "." ) ||
+ tag == "hide" ||
+ tag == "!hide" )
+ return TestCaseInfo::IsHidden;
+ else if( tag == "!throws" )
+ return TestCaseInfo::Throws;
+ else if( tag == "!shouldfail" )
+ return TestCaseInfo::ShouldFail;
+ else if( tag == "!mayfail" )
+ return TestCaseInfo::MayFail;
+ else
+ return TestCaseInfo::None;
+ }
+ inline bool isReservedTag( std::string const& tag ) {
+ return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] );
+ }
+ inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
+ if( isReservedTag( tag ) ) {
+ {
+ Colour colourGuard( Colour::Red );
+ Catch::cerr()
+ << "Tag name [" << tag << "] not allowed.\n"
+ << "Tag names starting with non alpha-numeric characters are reserved\n";
+ }
+ {
+ Colour colourGuard( Colour::FileName );
+ Catch::cerr() << _lineInfo << std::endl;
+ }
+ exit(1);
+ }
+ }
+
+ TestCase makeTestCase( ITestCase* _testCase,
+ std::string const& _className,
+ std::string const& _name,
+ std::string const& _descOrTags,
+ SourceLineInfo const& _lineInfo )
+ {
+ bool isHidden( startsWith( _name, "./" ) ); // Legacy support
+
+ // Parse out tags
+ std::set<std::string> tags;
+ std::string desc, tag;
+ bool inTag = false;
+ for( std::size_t i = 0; i < _descOrTags.size(); ++i ) {
+ char c = _descOrTags[i];
+ if( !inTag ) {
+ if( c == '[' )
+ inTag = true;
+ else
+ desc += c;
+ }
+ else {
+ if( c == ']' ) {
+ TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag );
+ if( prop == TestCaseInfo::IsHidden )
+ isHidden = true;
+ else if( prop == TestCaseInfo::None )
+ enforceNotReservedTag( tag, _lineInfo );
+
+ tags.insert( tag );
+ tag.clear();
+ inTag = false;
+ }
+ else
+ tag += c;
+ }
+ }
+ if( isHidden ) {
+ tags.insert( "hide" );
+ tags.insert( "." );
+ }
+
+ TestCaseInfo info( _name, _className, desc, tags, _lineInfo );
+ return TestCase( _testCase, info );
+ }
+
+ TestCaseInfo::TestCaseInfo( std::string const& _name,
+ std::string const& _className,
+ std::string const& _description,
+ std::set<std::string> const& _tags,
+ SourceLineInfo const& _lineInfo )
+ : name( _name ),
+ className( _className ),
+ description( _description ),
+ tags( _tags ),
+ lineInfo( _lineInfo ),
+ properties( None )
+ {
+ std::ostringstream oss;
+ for( std::set<std::string>::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) {
+ oss << "[" << *it << "]";
+ std::string lcaseTag = toLower( *it );
+ properties = static_cast<SpecialProperties>( properties | parseSpecialTag( lcaseTag ) );
+ lcaseTags.insert( lcaseTag );
+ }
+ tagsAsString = oss.str();
+ }
+
+ TestCaseInfo::TestCaseInfo( TestCaseInfo const& other )
+ : name( other.name ),
+ className( other.className ),
+ description( other.description ),
+ tags( other.tags ),
+ lcaseTags( other.lcaseTags ),
+ tagsAsString( other.tagsAsString ),
+ lineInfo( other.lineInfo ),
+ properties( other.properties )
+ {}
+
+ bool TestCaseInfo::isHidden() const {
+ return ( properties & IsHidden ) != 0;
+ }
+ bool TestCaseInfo::throws() const {
+ return ( properties & Throws ) != 0;
+ }
+ bool TestCaseInfo::okToFail() const {
+ return ( properties & (ShouldFail | MayFail ) ) != 0;
+ }
+ bool TestCaseInfo::expectedToFail() const {
+ return ( properties & (ShouldFail ) ) != 0;
+ }
+
+ TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {}
+
+ TestCase::TestCase( TestCase const& other )
+ : TestCaseInfo( other ),
+ test( other.test )
+ {}
+
+ TestCase TestCase::withName( std::string const& _newName ) const {
+ TestCase other( *this );
+ other.name = _newName;
+ return other;
+ }
+
+ void TestCase::swap( TestCase& other ) {
+ test.swap( other.test );
+ name.swap( other.name );
+ className.swap( other.className );
+ description.swap( other.description );
+ tags.swap( other.tags );
+ lcaseTags.swap( other.lcaseTags );
+ tagsAsString.swap( other.tagsAsString );
+ std::swap( TestCaseInfo::properties, static_cast<TestCaseInfo&>( other ).properties );
+ std::swap( lineInfo, other.lineInfo );
+ }
+
+ void TestCase::invoke() const {
+ test->invoke();
+ }
+
+ bool TestCase::operator == ( TestCase const& other ) const {
+ return test.get() == other.test.get() &&
+ name == other.name &&
+ className == other.className;
+ }
+
+ bool TestCase::operator < ( TestCase const& other ) const {
+ return name < other.name;
+ }
+ TestCase& TestCase::operator = ( TestCase const& other ) {
+ TestCase temp( other );
+ swap( temp );
+ return *this;
+ }
+
+ TestCaseInfo const& TestCase::getTestCaseInfo() const
+ {
+ return *this;
+ }
+
+} // end namespace Catch
+
+// #included from: catch_version.hpp
+#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED
+
+namespace Catch {
+
+ // These numbers are maintained by a script
+ Version libraryVersion( 1, 1, 3, "master" );
+}
+
+// #included from: catch_message.hpp
+#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED
+
+namespace Catch {
+
+ MessageInfo::MessageInfo( std::string const& _macroName,
+ SourceLineInfo const& _lineInfo,
+ ResultWas::OfType _type )
+ : macroName( _macroName ),
+ lineInfo( _lineInfo ),
+ type( _type ),
+ sequence( ++globalCount )
+ {}
+
+ // This may need protecting if threading support is added
+ unsigned int MessageInfo::globalCount = 0;
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ ScopedMessage::ScopedMessage( MessageBuilder const& builder )
+ : m_info( builder.m_info )
+ {
+ m_info.message = builder.m_stream.str();
+ getResultCapture().pushScopedMessage( m_info );
+ }
+ ScopedMessage::ScopedMessage( ScopedMessage const& other )
+ : m_info( other.m_info )
+ {}
+
+ ScopedMessage::~ScopedMessage() {
+ getResultCapture().popScopedMessage( m_info );
+ }
+
+} // end namespace Catch
+
+// #included from: catch_legacy_reporter_adapter.hpp
+#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED
+
+// #included from: catch_legacy_reporter_adapter.h
+#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED
+
+namespace Catch
+{
+ // Deprecated
+ struct IReporter : IShared {
+ virtual ~IReporter();
+
+ virtual bool shouldRedirectStdout() const = 0;
+
+ virtual void StartTesting() = 0;
+ virtual void EndTesting( Totals const& totals ) = 0;
+ virtual void StartGroup( std::string const& groupName ) = 0;
+ virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0;
+ virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0;
+ virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0;
+ virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0;
+ virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0;
+ virtual void NoAssertionsInSection( std::string const& sectionName ) = 0;
+ virtual void NoAssertionsInTestCase( std::string const& testName ) = 0;
+ virtual void Aborted() = 0;
+ virtual void Result( AssertionResult const& result ) = 0;
+ };
+
+ class LegacyReporterAdapter : public SharedImpl<IStreamingReporter>
+ {
+ public:
+ LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter );
+ virtual ~LegacyReporterAdapter();
+
+ virtual ReporterPreferences getPreferences() const;
+ virtual void noMatchingTestCases( std::string const& );
+ virtual void testRunStarting( TestRunInfo const& );
+ virtual void testGroupStarting( GroupInfo const& groupInfo );
+ virtual void testCaseStarting( TestCaseInfo const& testInfo );
+ virtual void sectionStarting( SectionInfo const& sectionInfo );
+ virtual void assertionStarting( AssertionInfo const& );
+ virtual bool assertionEnded( AssertionStats const& assertionStats );
+ virtual void sectionEnded( SectionStats const& sectionStats );
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats );
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats );
+ virtual void testRunEnded( TestRunStats const& testRunStats );
+ virtual void skipTest( TestCaseInfo const& );
+
+ private:
+ Ptr<IReporter> m_legacyReporter;
+ };
+}
+
+namespace Catch
+{
+ LegacyReporterAdapter::LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter )
+ : m_legacyReporter( legacyReporter )
+ {}
+ LegacyReporterAdapter::~LegacyReporterAdapter() {}
+
+ ReporterPreferences LegacyReporterAdapter::getPreferences() const {
+ ReporterPreferences prefs;
+ prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout();
+ return prefs;
+ }
+
+ void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {}
+ void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) {
+ m_legacyReporter->StartTesting();
+ }
+ void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) {
+ m_legacyReporter->StartGroup( groupInfo.name );
+ }
+ void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) {
+ m_legacyReporter->StartTestCase( testInfo );
+ }
+ void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) {
+ m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description );
+ }
+ void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) {
+ // Not on legacy interface
+ }
+
+ bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) {
+ if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+ for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
+ it != itEnd;
+ ++it ) {
+ if( it->type == ResultWas::Info ) {
+ ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal );
+ rb << it->message;
+ rb.setResultType( ResultWas::Info );
+ AssertionResult result = rb.build();
+ m_legacyReporter->Result( result );
+ }
+ }
+ }
+ m_legacyReporter->Result( assertionStats.assertionResult );
+ return true;
+ }
+ void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) {
+ if( sectionStats.missingAssertions )
+ m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name );
+ m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions );
+ }
+ void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) {
+ m_legacyReporter->EndTestCase
+ ( testCaseStats.testInfo,
+ testCaseStats.totals,
+ testCaseStats.stdOut,
+ testCaseStats.stdErr );
+ }
+ void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) {
+ if( testGroupStats.aborting )
+ m_legacyReporter->Aborted();
+ m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals );
+ }
+ void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) {
+ m_legacyReporter->EndTesting( testRunStats.totals );
+ }
+ void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) {
+ }
+}
+
+// #included from: catch_timer.hpp
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++11-long-long"
+#endif
+
+#ifdef CATCH_PLATFORM_WINDOWS
+#include <windows.h>
+#else
+#include <sys/time.h>
+#endif
+
+namespace Catch {
+
+ namespace {
+#ifdef CATCH_PLATFORM_WINDOWS
+ uint64_t getCurrentTicks() {
+ static uint64_t hz=0, hzo=0;
+ if (!hz) {
+ QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) );
+ QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) );
+ }
+ uint64_t t;
+ QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) );
+ return ((t-hzo)*1000000)/hz;
+ }
+#else
+ uint64_t getCurrentTicks() {
+ timeval t;
+ gettimeofday(&t,NULL);
+ return static_cast<uint64_t>( t.tv_sec ) * 1000000ull + static_cast<uint64_t>( t.tv_usec );
+ }
+#endif
+ }
+
+ void Timer::start() {
+ m_ticks = getCurrentTicks();
+ }
+ unsigned int Timer::getElapsedMicroseconds() const {
+ return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
+ }
+ unsigned int Timer::getElapsedMilliseconds() const {
+ return static_cast<unsigned int>(getElapsedMicroseconds()/1000);
+ }
+ double Timer::getElapsedSeconds() const {
+ return getElapsedMicroseconds()/1000000.0;
+ }
+
+} // namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+// #included from: catch_common.hpp
+#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED
+
+namespace Catch {
+
+ bool startsWith( std::string const& s, std::string const& prefix ) {
+ return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix;
+ }
+ bool endsWith( std::string const& s, std::string const& suffix ) {
+ return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix;
+ }
+ bool contains( std::string const& s, std::string const& infix ) {
+ return s.find( infix ) != std::string::npos;
+ }
+ void toLowerInPlace( std::string& s ) {
+ std::transform( s.begin(), s.end(), s.begin(), ::tolower );
+ }
+ std::string toLower( std::string const& s ) {
+ std::string lc = s;
+ toLowerInPlace( lc );
+ return lc;
+ }
+ std::string trim( std::string const& str ) {
+ static char const* whitespaceChars = "\n\r\t ";
+ std::string::size_type start = str.find_first_not_of( whitespaceChars );
+ std::string::size_type end = str.find_last_not_of( whitespaceChars );
+
+ return start != std::string::npos ? str.substr( start, 1+end-start ) : "";
+ }
+
+ bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {
+ bool replaced = false;
+ std::size_t i = str.find( replaceThis );
+ while( i != std::string::npos ) {
+ replaced = true;
+ str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() );
+ if( i < str.size()-withThis.size() )
+ i = str.find( replaceThis, i+withThis.size() );
+ else
+ i = std::string::npos;
+ }
+ return replaced;
+ }
+
+ pluralise::pluralise( std::size_t count, std::string const& label )
+ : m_count( count ),
+ m_label( label )
+ {}
+
+ std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) {
+ os << pluraliser.m_count << " " << pluraliser.m_label;
+ if( pluraliser.m_count != 1 )
+ os << "s";
+ return os;
+ }
+
+ SourceLineInfo::SourceLineInfo() : line( 0 ){}
+ SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line )
+ : file( _file ),
+ line( _line )
+ {}
+ SourceLineInfo::SourceLineInfo( SourceLineInfo const& other )
+ : file( other.file ),
+ line( other.line )
+ {}
+ bool SourceLineInfo::empty() const {
+ return file.empty();
+ }
+ bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const {
+ return line == other.line && file == other.file;
+ }
+ bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const {
+ return line < other.line || ( line == other.line && file < other.file );
+ }
+
+ std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {
+#ifndef __GNUG__
+ os << info.file << "(" << info.line << ")";
+#else
+ os << info.file << ":" << info.line;
+#endif
+ return os;
+ }
+
+ void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) {
+ std::ostringstream oss;
+ oss << locationInfo << ": Internal Catch error: '" << message << "'";
+ if( alwaysTrue() )
+ throw std::logic_error( oss.str() );
+ }
+}
+
+// #included from: catch_section.hpp
+#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED
+
+namespace Catch {
+
+ SectionInfo::SectionInfo
+ ( SourceLineInfo const& _lineInfo,
+ std::string const& _name,
+ std::string const& _description )
+ : name( _name ),
+ description( _description ),
+ lineInfo( _lineInfo )
+ {}
+
+ Section::Section( SectionInfo const& info )
+ : m_info( info ),
+ m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) )
+ {
+ m_timer.start();
+ }
+
+ Section::~Section() {
+ if( m_sectionIncluded )
+ getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() );
+ }
+
+ // This indicates whether the section should be executed or not
+ Section::operator bool() const {
+ return m_sectionIncluded;
+ }
+
+} // end namespace Catch
+
+// #included from: catch_debugger.hpp
+#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED
+
+#include <iostream>
+
+#ifdef CATCH_PLATFORM_MAC
+
+ #include <assert.h>
+ #include <stdbool.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+ #include <sys/sysctl.h>
+
+ namespace Catch{
+
+ // The following function is taken directly from the following technical note:
+ // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html
+
+ // Returns true if the current process is being debugged (either
+ // running under the debugger or has a debugger attached post facto).
+ bool isDebuggerActive(){
+
+ int mib[4];
+ struct kinfo_proc info;
+ size_t size;
+
+ // Initialize the flags so that, if sysctl fails for some bizarre
+ // reason, we get a predictable result.
+
+ info.kp_proc.p_flag = 0;
+
+ // Initialize mib, which tells sysctl the info we want, in this case
+ // we're looking for information about a specific process ID.
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = getpid();
+
+ // Call sysctl.
+
+ size = sizeof(info);
+ if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) {
+ Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl;
+ return false;
+ }
+
+ // We're being debugged if the P_TRACED flag is set.
+
+ return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
+ }
+ } // namespace Catch
+
+#elif defined(_MSC_VER)
+ extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
+ namespace Catch {
+ bool isDebuggerActive() {
+ return IsDebuggerPresent() != 0;
+ }
+ }
+#elif defined(__MINGW32__)
+ extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
+ namespace Catch {
+ bool isDebuggerActive() {
+ return IsDebuggerPresent() != 0;
+ }
+ }
+#else
+ namespace Catch {
+ inline bool isDebuggerActive() { return false; }
+ }
+#endif // Platform
+
+#ifdef CATCH_PLATFORM_WINDOWS
+ extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* );
+ namespace Catch {
+ void writeToDebugConsole( std::string const& text ) {
+ ::OutputDebugStringA( text.c_str() );
+ }
+ }
+#else
+ namespace Catch {
+ void writeToDebugConsole( std::string const& text ) {
+ // !TBD: Need a version for Mac/ XCode and other IDEs
+ Catch::cout() << text;
+ }
+ }
+#endif // Platform
+
+// #included from: catch_tostring.hpp
+#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED
+
+namespace Catch {
+
+namespace Detail {
+
+ std::string unprintableString = "{?}";
+
+ namespace {
+ struct Endianness {
+ enum Arch { Big, Little };
+
+ static Arch which() {
+ union _{
+ int asInt;
+ char asChar[sizeof (int)];
+ } u;
+
+ u.asInt = 1;
+ return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little;
+ }
+ };
+ }
+
+ std::string rawMemoryToString( const void *object, std::size_t size )
+ {
+ // Reverse order for little endian architectures
+ int i = 0, end = static_cast<int>( size ), inc = 1;
+ if( Endianness::which() == Endianness::Little ) {
+ i = end-1;
+ end = inc = -1;
+ }
+
+ unsigned char const *bytes = static_cast<unsigned char const *>(object);
+ std::ostringstream os;
+ os << "0x" << std::setfill('0') << std::hex;
+ for( ; i != end; i += inc )
+ os << std::setw(2) << static_cast<unsigned>(bytes[i]);
+ return os.str();
+ }
+}
+
+std::string toString( std::string const& value ) {
+ std::string s = value;
+ if( getCurrentContext().getConfig()->showInvisibles() ) {
+ for(size_t i = 0; i < s.size(); ++i ) {
+ std::string subs;
+ switch( s[i] ) {
+ case '\n': subs = "\\n"; break;
+ case '\t': subs = "\\t"; break;
+ default: break;
+ }
+ if( !subs.empty() ) {
+ s = s.substr( 0, i ) + subs + s.substr( i+1 );
+ ++i;
+ }
+ }
+ }
+ return "\"" + s + "\"";
+}
+std::string toString( std::wstring const& value ) {
+
+ std::string s;
+ s.reserve( value.size() );
+ for(size_t i = 0; i < value.size(); ++i )
+ s += value[i] <= 0xff ? static_cast<char>( value[i] ) : '?';
+ return Catch::toString( s );
+}
+
+std::string toString( const char* const value ) {
+ return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" );
+}
+
+std::string toString( char* const value ) {
+ return Catch::toString( static_cast<const char*>( value ) );
+}
+
+std::string toString( const wchar_t* const value )
+{
+ return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" );
+}
+
+std::string toString( wchar_t* const value )
+{
+ return Catch::toString( static_cast<const wchar_t*>( value ) );
+}
+
+std::string toString( int value ) {
+ std::ostringstream oss;
+ oss << value;
+ if( value >= 255 )
+ oss << " (0x" << std::hex << value << ")";
+ return oss.str();
+}
+
+std::string toString( unsigned long value ) {
+ std::ostringstream oss;
+ oss << value;
+ if( value >= 255 )
+ oss << " (0x" << std::hex << value << ")";
+ return oss.str();
+}
+
+std::string toString( unsigned int value ) {
+ return Catch::toString( static_cast<unsigned long>( value ) );
+}
+
+template<typename T>
+std::string fpToString( T value, int precision ) {
+ std::ostringstream oss;
+ oss << std::setprecision( precision )
+ << std::fixed
+ << value;
+ std::string d = oss.str();
+ std::size_t i = d.find_last_not_of( '0' );
+ if( i != std::string::npos && i != d.size()-1 ) {
+ if( d[i] == '.' )
+ i++;
+ d = d.substr( 0, i+1 );
+ }
+ return d;
+}
+
+std::string toString( const double value ) {
+ return fpToString( value, 10 );
+}
+std::string toString( const float value ) {
+ return fpToString( value, 5 ) + "f";
+}
+
+std::string toString( bool value ) {
+ return value ? "true" : "false";
+}
+
+std::string toString( char value ) {
+ return value < ' '
+ ? toString( static_cast<unsigned int>( value ) )
+ : Detail::makeString( value );
+}
+
+std::string toString( signed char value ) {
+ return toString( static_cast<char>( value ) );
+}
+
+std::string toString( unsigned char value ) {
+ return toString( static_cast<char>( value ) );
+}
+
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+std::string toString( std::nullptr_t ) {
+ return "nullptr";
+}
+#endif
+
+#ifdef __OBJC__
+ std::string toString( NSString const * const& nsstring ) {
+ if( !nsstring )
+ return "nil";
+ return "@" + toString([nsstring UTF8String]);
+ }
+ std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) {
+ if( !nsstring )
+ return "nil";
+ return "@" + toString([nsstring UTF8String]);
+ }
+ std::string toString( NSObject* const& nsObject ) {
+ return toString( [nsObject description] );
+ }
+#endif
+
+} // end namespace Catch
+
+// #included from: catch_result_builder.hpp
+#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED
+
+namespace Catch {
+
+ ResultBuilder::ResultBuilder( char const* macroName,
+ SourceLineInfo const& lineInfo,
+ char const* capturedExpression,
+ ResultDisposition::Flags resultDisposition )
+ : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ),
+ m_shouldDebugBreak( false ),
+ m_shouldThrow( false )
+ {}
+
+ ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) {
+ m_data.resultType = result;
+ return *this;
+ }
+ ResultBuilder& ResultBuilder::setResultType( bool result ) {
+ m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed;
+ return *this;
+ }
+ ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) {
+ m_exprComponents.lhs = lhs;
+ return *this;
+ }
+ ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) {
+ m_exprComponents.rhs = rhs;
+ return *this;
+ }
+ ResultBuilder& ResultBuilder::setOp( std::string const& op ) {
+ m_exprComponents.op = op;
+ return *this;
+ }
+
+ void ResultBuilder::endExpression() {
+ m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition );
+ captureExpression();
+ }
+
+ void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) {
+ m_assertionInfo.resultDisposition = resultDisposition;
+ m_stream.oss << Catch::translateActiveException();
+ captureResult( ResultWas::ThrewException );
+ }
+
+ void ResultBuilder::captureResult( ResultWas::OfType resultType ) {
+ setResultType( resultType );
+ captureExpression();
+ }
+
+ void ResultBuilder::captureExpression() {
+ AssertionResult result = build();
+ getResultCapture().assertionEnded( result );
+
+ if( !result.isOk() ) {
+ if( getCurrentContext().getConfig()->shouldDebugBreak() )
+ m_shouldDebugBreak = true;
+ if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) )
+ m_shouldThrow = true;
+ }
+ }
+ void ResultBuilder::react() {
+ if( m_shouldThrow )
+ throw Catch::TestFailureException();
+ }
+
+ bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; }
+ bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); }
+
+ AssertionResult ResultBuilder::build() const
+ {
+ assert( m_data.resultType != ResultWas::Unknown );
+
+ AssertionResultData data = m_data;
+
+ // Flip bool results if testFalse is set
+ if( m_exprComponents.testFalse ) {
+ if( data.resultType == ResultWas::Ok )
+ data.resultType = ResultWas::ExpressionFailed;
+ else if( data.resultType == ResultWas::ExpressionFailed )
+ data.resultType = ResultWas::Ok;
+ }
+
+ data.message = m_stream.oss.str();
+ data.reconstructedExpression = reconstructExpression();
+ if( m_exprComponents.testFalse ) {
+ if( m_exprComponents.op == "" )
+ data.reconstructedExpression = "!" + data.reconstructedExpression;
+ else
+ data.reconstructedExpression = "!(" + data.reconstructedExpression + ")";
+ }
+ return AssertionResult( m_assertionInfo, data );
+ }
+ std::string ResultBuilder::reconstructExpression() const {
+ if( m_exprComponents.op == "" )
+ return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs;
+ else if( m_exprComponents.op == "matches" )
+ return m_exprComponents.lhs + " " + m_exprComponents.rhs;
+ else if( m_exprComponents.op != "!" ) {
+ if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 &&
+ m_exprComponents.lhs.find("\n") == std::string::npos &&
+ m_exprComponents.rhs.find("\n") == std::string::npos )
+ return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs;
+ else
+ return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs;
+ }
+ else
+ return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}";
+ }
+
+} // end namespace Catch
+
+// #included from: catch_tag_alias_registry.hpp
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED
+
+// #included from: catch_tag_alias_registry.h
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED
+
+#include <map>
+
+namespace Catch {
+
+ class TagAliasRegistry : public ITagAliasRegistry {
+ public:
+ virtual ~TagAliasRegistry();
+ virtual Option<TagAlias> find( std::string const& alias ) const;
+ virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const;
+ void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
+ static TagAliasRegistry& get();
+
+ private:
+ std::map<std::string, TagAlias> m_registry;
+ };
+
+} // end namespace Catch
+
+#include <map>
+#include <iostream>
+
+namespace Catch {
+
+ TagAliasRegistry::~TagAliasRegistry() {}
+
+ Option<TagAlias> TagAliasRegistry::find( std::string const& alias ) const {
+ std::map<std::string, TagAlias>::const_iterator it = m_registry.find( alias );
+ if( it != m_registry.end() )
+ return it->second;
+ else
+ return Option<TagAlias>();
+ }
+
+ std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const {
+ std::string expandedTestSpec = unexpandedTestSpec;
+ for( std::map<std::string, TagAlias>::const_iterator it = m_registry.begin(), itEnd = m_registry.end();
+ it != itEnd;
+ ++it ) {
+ std::size_t pos = expandedTestSpec.find( it->first );
+ if( pos != std::string::npos ) {
+ expandedTestSpec = expandedTestSpec.substr( 0, pos ) +
+ it->second.tag +
+ expandedTestSpec.substr( pos + it->first.size() );
+ }
+ }
+ return expandedTestSpec;
+ }
+
+ void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
+
+ if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) {
+ std::ostringstream oss;
+ oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo;
+ throw std::domain_error( oss.str().c_str() );
+ }
+ if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) {
+ std::ostringstream oss;
+ oss << "error: tag alias, \"" << alias << "\" already registered.\n"
+ << "\tFirst seen at " << find(alias)->lineInfo << "\n"
+ << "\tRedefined at " << lineInfo;
+ throw std::domain_error( oss.str().c_str() );
+ }
+ }
+
+ TagAliasRegistry& TagAliasRegistry::get() {
+ static TagAliasRegistry instance;
+ return instance;
+
+ }
+
+ ITagAliasRegistry::~ITagAliasRegistry() {}
+ ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); }
+
+ RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
+ try {
+ TagAliasRegistry::get().add( alias, tag, lineInfo );
+ }
+ catch( std::exception& ex ) {
+ Colour colourGuard( Colour::Red );
+ Catch::cerr() << ex.what() << std::endl;
+ exit(1);
+ }
+ }
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_xml.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED
+
+// #included from: catch_reporter_bases.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED
+
+#include <cstring>
+
+namespace Catch {
+
+ struct StreamingReporterBase : SharedImpl<IStreamingReporter> {
+
+ StreamingReporterBase( ReporterConfig const& _config )
+ : m_config( _config.fullConfig() ),
+ stream( _config.stream() )
+ {}
+
+ virtual ~StreamingReporterBase();
+
+ virtual void noMatchingTestCases( std::string const& ) {}
+
+ virtual void testRunStarting( TestRunInfo const& _testRunInfo ) {
+ currentTestRunInfo = _testRunInfo;
+ }
+ virtual void testGroupStarting( GroupInfo const& _groupInfo ) {
+ currentGroupInfo = _groupInfo;
+ }
+
+ virtual void testCaseStarting( TestCaseInfo const& _testInfo ) {
+ currentTestCaseInfo = _testInfo;
+ }
+ virtual void sectionStarting( SectionInfo const& _sectionInfo ) {
+ m_sectionStack.push_back( _sectionInfo );
+ }
+
+ virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) {
+ m_sectionStack.pop_back();
+ }
+ virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) {
+ currentTestCaseInfo.reset();
+ }
+ virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) {
+ currentGroupInfo.reset();
+ }
+ virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) {
+ currentTestCaseInfo.reset();
+ currentGroupInfo.reset();
+ currentTestRunInfo.reset();
+ }
+
+ virtual void skipTest( TestCaseInfo const& ) {
+ // Don't do anything with this by default.
+ // It can optionally be overridden in the derived class.
+ }
+
+ Ptr<IConfig> m_config;
+ std::ostream& stream;
+
+ LazyStat<TestRunInfo> currentTestRunInfo;
+ LazyStat<GroupInfo> currentGroupInfo;
+ LazyStat<TestCaseInfo> currentTestCaseInfo;
+
+ std::vector<SectionInfo> m_sectionStack;
+ };
+
+ struct CumulativeReporterBase : SharedImpl<IStreamingReporter> {
+ template<typename T, typename ChildNodeT>
+ struct Node : SharedImpl<> {
+ explicit Node( T const& _value ) : value( _value ) {}
+ virtual ~Node() {}
+
+ typedef std::vector<Ptr<ChildNodeT> > ChildNodes;
+ T value;
+ ChildNodes children;
+ };
+ struct SectionNode : SharedImpl<> {
+ explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {}
+ virtual ~SectionNode();
+
+ bool operator == ( SectionNode const& other ) const {
+ return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo;
+ }
+ bool operator == ( Ptr<SectionNode> const& other ) const {
+ return operator==( *other );
+ }
+
+ SectionStats stats;
+ typedef std::vector<Ptr<SectionNode> > ChildSections;
+ typedef std::vector<AssertionStats> Assertions;
+ ChildSections childSections;
+ Assertions assertions;
+ std::string stdOut;
+ std::string stdErr;
+ };
+
+ struct BySectionInfo {
+ BySectionInfo( SectionInfo const& other ) : m_other( other ) {}
+ BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}
+ bool operator() ( Ptr<SectionNode> const& node ) const {
+ return node->stats.sectionInfo.lineInfo == m_other.lineInfo;
+ }
+ private:
+ void operator=( BySectionInfo const& );
+ SectionInfo const& m_other;
+ };
+
+ typedef Node<TestCaseStats, SectionNode> TestCaseNode;
+ typedef Node<TestGroupStats, TestCaseNode> TestGroupNode;
+ typedef Node<TestRunStats, TestGroupNode> TestRunNode;
+
+ CumulativeReporterBase( ReporterConfig const& _config )
+ : m_config( _config.fullConfig() ),
+ stream( _config.stream() )
+ {}
+ ~CumulativeReporterBase();
+
+ virtual void testRunStarting( TestRunInfo const& ) {}
+ virtual void testGroupStarting( GroupInfo const& ) {}
+
+ virtual void testCaseStarting( TestCaseInfo const& ) {}
+
+ virtual void sectionStarting( SectionInfo const& sectionInfo ) {
+ SectionStats incompleteStats( sectionInfo, Counts(), 0, false );
+ Ptr<SectionNode> node;
+ if( m_sectionStack.empty() ) {
+ if( !m_rootSection )
+ m_rootSection = new SectionNode( incompleteStats );
+ node = m_rootSection;
+ }
+ else {
+ SectionNode& parentNode = *m_sectionStack.back();
+ SectionNode::ChildSections::const_iterator it =
+ std::find_if( parentNode.childSections.begin(),
+ parentNode.childSections.end(),
+ BySectionInfo( sectionInfo ) );
+ if( it == parentNode.childSections.end() ) {
+ node = new SectionNode( incompleteStats );
+ parentNode.childSections.push_back( node );
+ }
+ else
+ node = *it;
+ }
+ m_sectionStack.push_back( node );
+ m_deepestSection = node;
+ }
+
+ virtual void assertionStarting( AssertionInfo const& ) {}
+
+ virtual bool assertionEnded( AssertionStats const& assertionStats ) {
+ assert( !m_sectionStack.empty() );
+ SectionNode& sectionNode = *m_sectionStack.back();
+ sectionNode.assertions.push_back( assertionStats );
+ return true;
+ }
+ virtual void sectionEnded( SectionStats const& sectionStats ) {
+ assert( !m_sectionStack.empty() );
+ SectionNode& node = *m_sectionStack.back();
+ node.stats = sectionStats;
+ m_sectionStack.pop_back();
+ }
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats ) {
+ Ptr<TestCaseNode> node = new TestCaseNode( testCaseStats );
+ assert( m_sectionStack.size() == 0 );
+ node->children.push_back( m_rootSection );
+ m_testCases.push_back( node );
+ m_rootSection.reset();
+
+ assert( m_deepestSection );
+ m_deepestSection->stdOut = testCaseStats.stdOut;
+ m_deepestSection->stdErr = testCaseStats.stdErr;
+ }
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats ) {
+ Ptr<TestGroupNode> node = new TestGroupNode( testGroupStats );
+ node->children.swap( m_testCases );
+ m_testGroups.push_back( node );
+ }
+ virtual void testRunEnded( TestRunStats const& testRunStats ) {
+ Ptr<TestRunNode> node = new TestRunNode( testRunStats );
+ node->children.swap( m_testGroups );
+ m_testRuns.push_back( node );
+ testRunEndedCumulative();
+ }
+ virtual void testRunEndedCumulative() = 0;
+
+ virtual void skipTest( TestCaseInfo const& ) {}
+
+ Ptr<IConfig> m_config;
+ std::ostream& stream;
+ std::vector<AssertionStats> m_assertions;
+ std::vector<std::vector<Ptr<SectionNode> > > m_sections;
+ std::vector<Ptr<TestCaseNode> > m_testCases;
+ std::vector<Ptr<TestGroupNode> > m_testGroups;
+
+ std::vector<Ptr<TestRunNode> > m_testRuns;
+
+ Ptr<SectionNode> m_rootSection;
+ Ptr<SectionNode> m_deepestSection;
+ std::vector<Ptr<SectionNode> > m_sectionStack;
+
+ };
+
+ template<char C>
+ char const* getLineOfChars() {
+ static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
+ if( !*line ) {
+ memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
+ line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
+ }
+ return line;
+ }
+
+} // end namespace Catch
+
+// #included from: ../internal/catch_reporter_registrars.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED
+
+namespace Catch {
+
+ template<typename T>
+ class LegacyReporterRegistrar {
+
+ class ReporterFactory : public IReporterFactory {
+ virtual IStreamingReporter* create( ReporterConfig const& config ) const {
+ return new LegacyReporterAdapter( new T( config ) );
+ }
+
+ virtual std::string getDescription() const {
+ return T::getDescription();
+ }
+ };
+
+ public:
+
+ LegacyReporterRegistrar( std::string const& name ) {
+ getMutableRegistryHub().registerReporter( name, new ReporterFactory() );
+ }
+ };
+
+ template<typename T>
+ class ReporterRegistrar {
+
+ class ReporterFactory : public IReporterFactory {
+
+ // *** Please Note ***:
+ // - If you end up here looking at a compiler error because it's trying to register
+ // your custom reporter class be aware that the native reporter interface has changed
+ // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via
+ // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter.
+ // However please consider updating to the new interface as the old one is now
+ // deprecated and will probably be removed quite soon!
+ // Please contact me via github if you have any questions at all about this.
+ // In fact, ideally, please contact me anyway to let me know you've hit this - as I have
+ // no idea who is actually using custom reporters at all (possibly no-one!).
+ // The new interface is designed to minimise exposure to interface changes in the future.
+ virtual IStreamingReporter* create( ReporterConfig const& config ) const {
+ return new T( config );
+ }
+
+ virtual std::string getDescription() const {
+ return T::getDescription();
+ }
+ };
+
+ public:
+
+ ReporterRegistrar( std::string const& name ) {
+ getMutableRegistryHub().registerReporter( name, new ReporterFactory() );
+ }
+ };
+}
+
+#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \
+ namespace{ Catch::LegacyReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); }
+#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \
+ namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); }
+
+// #included from: ../internal/catch_xmlwriter.hpp
+#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace Catch {
+
+ class XmlWriter {
+ public:
+
+ class ScopedElement {
+ public:
+ ScopedElement( XmlWriter* writer )
+ : m_writer( writer )
+ {}
+
+ ScopedElement( ScopedElement const& other )
+ : m_writer( other.m_writer ){
+ other.m_writer = NULL;
+ }
+
+ ~ScopedElement() {
+ if( m_writer )
+ m_writer->endElement();
+ }
+
+ ScopedElement& writeText( std::string const& text, bool indent = true ) {
+ m_writer->writeText( text, indent );
+ return *this;
+ }
+
+ template<typename T>
+ ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
+ m_writer->writeAttribute( name, attribute );
+ return *this;
+ }
+
+ private:
+ mutable XmlWriter* m_writer;
+ };
+
+ XmlWriter()
+ : m_tagIsOpen( false ),
+ m_needsNewline( false ),
+ m_os( &Catch::cout() )
+ {}
+
+ XmlWriter( std::ostream& os )
+ : m_tagIsOpen( false ),
+ m_needsNewline( false ),
+ m_os( &os )
+ {}
+
+ ~XmlWriter() {
+ while( !m_tags.empty() )
+ endElement();
+ }
+
+ XmlWriter& startElement( std::string const& name ) {
+ ensureTagClosed();
+ newlineIfNecessary();
+ stream() << m_indent << "<" << name;
+ m_tags.push_back( name );
+ m_indent += " ";
+ m_tagIsOpen = true;
+ return *this;
+ }
+
+ ScopedElement scopedElement( std::string const& name ) {
+ ScopedElement scoped( this );
+ startElement( name );
+ return scoped;
+ }
+
+ XmlWriter& endElement() {
+ newlineIfNecessary();
+ m_indent = m_indent.substr( 0, m_indent.size()-2 );
+ if( m_tagIsOpen ) {
+ stream() << "/>\n";
+ m_tagIsOpen = false;
+ }
+ else {
+ stream() << m_indent << "</" << m_tags.back() << ">\n";
+ }
+ m_tags.pop_back();
+ return *this;
+ }
+
+ XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) {
+ if( !name.empty() && !attribute.empty() ) {
+ stream() << " " << name << "=\"";
+ writeEncodedText( attribute );
+ stream() << "\"";
+ }
+ return *this;
+ }
+
+ XmlWriter& writeAttribute( std::string const& name, bool attribute ) {
+ stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\"";
+ return *this;
+ }
+
+ template<typename T>
+ XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
+ if( !name.empty() )
+ stream() << " " << name << "=\"" << attribute << "\"";
+ return *this;
+ }
+
+ XmlWriter& writeText( std::string const& text, bool indent = true ) {
+ if( !text.empty() ){
+ bool tagWasOpen = m_tagIsOpen;
+ ensureTagClosed();
+ if( tagWasOpen && indent )
+ stream() << m_indent;
+ writeEncodedText( text );
+ m_needsNewline = true;
+ }
+ return *this;
+ }
+
+ XmlWriter& writeComment( std::string const& text ) {
+ ensureTagClosed();
+ stream() << m_indent << "<!--" << text << "-->";
+ m_needsNewline = true;
+ return *this;
+ }
+
+ XmlWriter& writeBlankLine() {
+ ensureTagClosed();
+ stream() << "\n";
+ return *this;
+ }
+
+ void setStream( std::ostream& os ) {
+ m_os = &os;
+ }
+
+ private:
+ XmlWriter( XmlWriter const& );
+ void operator=( XmlWriter const& );
+
+ std::ostream& stream() {
+ return *m_os;
+ }
+
+ void ensureTagClosed() {
+ if( m_tagIsOpen ) {
+ stream() << ">\n";
+ m_tagIsOpen = false;
+ }
+ }
+
+ void newlineIfNecessary() {
+ if( m_needsNewline ) {
+ stream() << "\n";
+ m_needsNewline = false;
+ }
+ }
+
+ void writeEncodedText( std::string const& text ) {
+ static const char* charsToEncode = "<&\"";
+ std::string mtext = text;
+ std::string::size_type pos = mtext.find_first_of( charsToEncode );
+ while( pos != std::string::npos ) {
+ stream() << mtext.substr( 0, pos );
+
+ switch( mtext[pos] ) {
+ case '<':
+ stream() << "<";
+ break;
+ case '&':
+ stream() << "&";
+ break;
+ case '\"':
+ stream() << """;
+ break;
+ }
+ mtext = mtext.substr( pos+1 );
+ pos = mtext.find_first_of( charsToEncode );
+ }
+ stream() << mtext;
+ }
+
+ bool m_tagIsOpen;
+ bool m_needsNewline;
+ std::vector<std::string> m_tags;
+ std::string m_indent;
+ std::ostream* m_os;
+ };
+
+}
+namespace Catch {
+ class XmlReporter : public StreamingReporterBase {
+ public:
+ XmlReporter( ReporterConfig const& _config )
+ : StreamingReporterBase( _config ),
+ m_sectionDepth( 0 )
+ {}
+
+ virtual ~XmlReporter();
+
+ static std::string getDescription() {
+ return "Reports test results as an XML document";
+ }
+
+ public: // StreamingReporterBase
+ virtual ReporterPreferences getPreferences() const {
+ ReporterPreferences prefs;
+ prefs.shouldRedirectStdOut = true;
+ return prefs;
+ }
+
+ virtual void noMatchingTestCases( std::string const& s ) {
+ StreamingReporterBase::noMatchingTestCases( s );
+ }
+
+ virtual void testRunStarting( TestRunInfo const& testInfo ) {
+ StreamingReporterBase::testRunStarting( testInfo );
+ m_xml.setStream( stream );
+ m_xml.startElement( "Catch" );
+ if( !m_config->name().empty() )
+ m_xml.writeAttribute( "name", m_config->name() );
+ }
+
+ virtual void testGroupStarting( GroupInfo const& groupInfo ) {
+ StreamingReporterBase::testGroupStarting( groupInfo );
+ m_xml.startElement( "Group" )
+ .writeAttribute( "name", groupInfo.name );
+ }
+
+ virtual void testCaseStarting( TestCaseInfo const& testInfo ) {
+ StreamingReporterBase::testCaseStarting(testInfo);
+ m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) );
+
+ if ( m_config->showDurations() == ShowDurations::Always )
+ m_testCaseTimer.start();
+ }
+
+ virtual void sectionStarting( SectionInfo const& sectionInfo ) {
+ StreamingReporterBase::sectionStarting( sectionInfo );
+ if( m_sectionDepth++ > 0 ) {
+ m_xml.startElement( "Section" )
+ .writeAttribute( "name", trim( sectionInfo.name ) )
+ .writeAttribute( "description", sectionInfo.description );
+ }
+ }
+
+ virtual void assertionStarting( AssertionInfo const& ) { }
+
+ virtual bool assertionEnded( AssertionStats const& assertionStats ) {
+ const AssertionResult& assertionResult = assertionStats.assertionResult;
+
+ // Print any info messages in <Info> tags.
+ if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+ for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
+ it != itEnd;
+ ++it ) {
+ if( it->type == ResultWas::Info ) {
+ m_xml.scopedElement( "Info" )
+ .writeText( it->message );
+ } else if ( it->type == ResultWas::Warning ) {
+ m_xml.scopedElement( "Warning" )
+ .writeText( it->message );
+ }
+ }
+ }
+
+ // Drop out if result was successful but we're not printing them.
+ if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) )
+ return true;
+
+ // Print the expression if there is one.
+ if( assertionResult.hasExpression() ) {
+ m_xml.startElement( "Expression" )
+ .writeAttribute( "success", assertionResult.succeeded() )
+ .writeAttribute( "type", assertionResult.getTestMacroName() )
+ .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+ .writeAttribute( "line", assertionResult.getSourceInfo().line );
+
+ m_xml.scopedElement( "Original" )
+ .writeText( assertionResult.getExpression() );
+ m_xml.scopedElement( "Expanded" )
+ .writeText( assertionResult.getExpandedExpression() );
+ }
+
+ // And... Print a result applicable to each result type.
+ switch( assertionResult.getResultType() ) {
+ case ResultWas::ThrewException:
+ m_xml.scopedElement( "Exception" )
+ .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+ .writeAttribute( "line", assertionResult.getSourceInfo().line )
+ .writeText( assertionResult.getMessage() );
+ break;
+ case ResultWas::FatalErrorCondition:
+ m_xml.scopedElement( "Fatal Error Condition" )
+ .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+ .writeAttribute( "line", assertionResult.getSourceInfo().line )
+ .writeText( assertionResult.getMessage() );
+ break;
+ case ResultWas::Info:
+ m_xml.scopedElement( "Info" )
+ .writeText( assertionResult.getMessage() );
+ break;
+ case ResultWas::Warning:
+ // Warning will already have been written
+ break;
+ case ResultWas::ExplicitFailure:
+ m_xml.scopedElement( "Failure" )
+ .writeText( assertionResult.getMessage() );
+ break;
+ default:
+ break;
+ }
+
+ if( assertionResult.hasExpression() )
+ m_xml.endElement();
+
+ return true;
+ }
+
+ virtual void sectionEnded( SectionStats const& sectionStats ) {
+ StreamingReporterBase::sectionEnded( sectionStats );
+ if( --m_sectionDepth > 0 ) {
+ XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" );
+ e.writeAttribute( "successes", sectionStats.assertions.passed );
+ e.writeAttribute( "failures", sectionStats.assertions.failed );
+ e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk );
+
+ if ( m_config->showDurations() == ShowDurations::Always )
+ e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds );
+
+ m_xml.endElement();
+ }
+ }
+
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats ) {
+ StreamingReporterBase::testCaseEnded( testCaseStats );
+ XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" );
+ e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() );
+
+ if ( m_config->showDurations() == ShowDurations::Always )
+ e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
+
+ m_xml.endElement();
+ }
+
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats ) {
+ StreamingReporterBase::testGroupEnded( testGroupStats );
+ // TODO: Check testGroupStats.aborting and act accordingly.
+ m_xml.scopedElement( "OverallResults" )
+ .writeAttribute( "successes", testGroupStats.totals.assertions.passed )
+ .writeAttribute( "failures", testGroupStats.totals.assertions.failed )
+ .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk );
+ m_xml.endElement();
+ }
+
+ virtual void testRunEnded( TestRunStats const& testRunStats ) {
+ StreamingReporterBase::testRunEnded( testRunStats );
+ m_xml.scopedElement( "OverallResults" )
+ .writeAttribute( "successes", testRunStats.totals.assertions.passed )
+ .writeAttribute( "failures", testRunStats.totals.assertions.failed )
+ .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk );
+ m_xml.endElement();
+ }
+
+ private:
+ Timer m_testCaseTimer;
+ XmlWriter m_xml;
+ int m_sectionDepth;
+ };
+
+ INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter )
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_junit.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED
+
+#include <assert.h>
+
+namespace Catch {
+
+ class JunitReporter : public CumulativeReporterBase {
+ public:
+ JunitReporter( ReporterConfig const& _config )
+ : CumulativeReporterBase( _config ),
+ xml( _config.stream() )
+ {}
+
+ ~JunitReporter();
+
+ static std::string getDescription() {
+ return "Reports test results in an XML format that looks like Ant's junitreport target";
+ }
+
+ virtual void noMatchingTestCases( std::string const& /*spec*/ ) {}
+
+ virtual ReporterPreferences getPreferences() const {
+ ReporterPreferences prefs;
+ prefs.shouldRedirectStdOut = true;
+ return prefs;
+ }
+
+ virtual void testRunStarting( TestRunInfo const& runInfo ) {
+ CumulativeReporterBase::testRunStarting( runInfo );
+ xml.startElement( "testsuites" );
+ }
+
+ virtual void testGroupStarting( GroupInfo const& groupInfo ) {
+ suiteTimer.start();
+ stdOutForSuite.str("");
+ stdErrForSuite.str("");
+ unexpectedExceptions = 0;
+ CumulativeReporterBase::testGroupStarting( groupInfo );
+ }
+
+ virtual bool assertionEnded( AssertionStats const& assertionStats ) {
+ if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException )
+ unexpectedExceptions++;
+ return CumulativeReporterBase::assertionEnded( assertionStats );
+ }
+
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats ) {
+ stdOutForSuite << testCaseStats.stdOut;
+ stdErrForSuite << testCaseStats.stdErr;
+ CumulativeReporterBase::testCaseEnded( testCaseStats );
+ }
+
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats ) {
+ double suiteTime = suiteTimer.getElapsedSeconds();
+ CumulativeReporterBase::testGroupEnded( testGroupStats );
+ writeGroup( *m_testGroups.back(), suiteTime );
+ }
+
+ virtual void testRunEndedCumulative() {
+ xml.endElement();
+ }
+
+ void writeGroup( TestGroupNode const& groupNode, double suiteTime ) {
+ XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
+ TestGroupStats const& stats = groupNode.value;
+ xml.writeAttribute( "name", stats.groupInfo.name );
+ xml.writeAttribute( "errors", unexpectedExceptions );
+ xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions );
+ xml.writeAttribute( "tests", stats.totals.assertions.total() );
+ xml.writeAttribute( "hostname", "tbd" ); // !TBD
+ if( m_config->showDurations() == ShowDurations::Never )
+ xml.writeAttribute( "time", "" );
+ else
+ xml.writeAttribute( "time", suiteTime );
+ xml.writeAttribute( "timestamp", "tbd" ); // !TBD
+
+ // Write test cases
+ for( TestGroupNode::ChildNodes::const_iterator
+ it = groupNode.children.begin(), itEnd = groupNode.children.end();
+ it != itEnd;
+ ++it )
+ writeTestCase( **it );
+
+ xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false );
+ xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false );
+ }
+
+ void writeTestCase( TestCaseNode const& testCaseNode ) {
+ TestCaseStats const& stats = testCaseNode.value;
+
+ // All test cases have exactly one section - which represents the
+ // test case itself. That section may have 0-n nested sections
+ assert( testCaseNode.children.size() == 1 );
+ SectionNode const& rootSection = *testCaseNode.children.front();
+
+ std::string className = stats.testInfo.className;
+
+ if( className.empty() ) {
+ if( rootSection.childSections.empty() )
+ className = "global";
+ }
+ writeSection( className, "", rootSection );
+ }
+
+ void writeSection( std::string const& className,
+ std::string const& rootName,
+ SectionNode const& sectionNode ) {
+ std::string name = trim( sectionNode.stats.sectionInfo.name );
+ if( !rootName.empty() )
+ name = rootName + "/" + name;
+
+ if( !sectionNode.assertions.empty() ||
+ !sectionNode.stdOut.empty() ||
+ !sectionNode.stdErr.empty() ) {
+ XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
+ if( className.empty() ) {
+ xml.writeAttribute( "classname", name );
+ xml.writeAttribute( "name", "root" );
+ }
+ else {
+ xml.writeAttribute( "classname", className );
+ xml.writeAttribute( "name", name );
+ }
+ xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) );
+
+ writeAssertions( sectionNode );
+
+ if( !sectionNode.stdOut.empty() )
+ xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false );
+ if( !sectionNode.stdErr.empty() )
+ xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false );
+ }
+ for( SectionNode::ChildSections::const_iterator
+ it = sectionNode.childSections.begin(),
+ itEnd = sectionNode.childSections.end();
+ it != itEnd;
+ ++it )
+ if( className.empty() )
+ writeSection( name, "", **it );
+ else
+ writeSection( className, name, **it );
+ }
+
+ void writeAssertions( SectionNode const& sectionNode ) {
+ for( SectionNode::Assertions::const_iterator
+ it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end();
+ it != itEnd;
+ ++it )
+ writeAssertion( *it );
+ }
+ void writeAssertion( AssertionStats const& stats ) {
+ AssertionResult const& result = stats.assertionResult;
+ if( !result.isOk() ) {
+ std::string elementName;
+ switch( result.getResultType() ) {
+ case ResultWas::ThrewException:
+ case ResultWas::FatalErrorCondition:
+ elementName = "error";
+ break;
+ case ResultWas::ExplicitFailure:
+ elementName = "failure";
+ break;
+ case ResultWas::ExpressionFailed:
+ elementName = "failure";
+ break;
+ case ResultWas::DidntThrowException:
+ elementName = "failure";
+ break;
+
+ // We should never see these here:
+ case ResultWas::Info:
+ case ResultWas::Warning:
+ case ResultWas::Ok:
+ case ResultWas::Unknown:
+ case ResultWas::FailureBit:
+ case ResultWas::Exception:
+ elementName = "internalError";
+ break;
+ }
+
+ XmlWriter::ScopedElement e = xml.scopedElement( elementName );
+
+ xml.writeAttribute( "message", result.getExpandedExpression() );
+ xml.writeAttribute( "type", result.getTestMacroName() );
+
+ std::ostringstream oss;
+ if( !result.getMessage().empty() )
+ oss << result.getMessage() << "\n";
+ for( std::vector<MessageInfo>::const_iterator
+ it = stats.infoMessages.begin(),
+ itEnd = stats.infoMessages.end();
+ it != itEnd;
+ ++it )
+ if( it->type == ResultWas::Info )
+ oss << it->message << "\n";
+
+ oss << "at " << result.getSourceInfo();
+ xml.writeText( oss.str(), false );
+ }
+ }
+
+ XmlWriter xml;
+ Timer suiteTimer;
+ std::ostringstream stdOutForSuite;
+ std::ostringstream stdErrForSuite;
+ unsigned int unexpectedExceptions;
+ };
+
+ INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter )
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_console.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED
+
+namespace Catch {
+
+ struct ConsoleReporter : StreamingReporterBase {
+ ConsoleReporter( ReporterConfig const& _config )
+ : StreamingReporterBase( _config ),
+ m_headerPrinted( false )
+ {}
+
+ virtual ~ConsoleReporter();
+ static std::string getDescription() {
+ return "Reports test results as plain lines of text";
+ }
+ virtual ReporterPreferences getPreferences() const {
+ ReporterPreferences prefs;
+ prefs.shouldRedirectStdOut = false;
+ return prefs;
+ }
+
+ virtual void noMatchingTestCases( std::string const& spec ) {
+ stream << "No test cases matched '" << spec << "'" << std::endl;
+ }
+
+ virtual void assertionStarting( AssertionInfo const& ) {
+ }
+
+ virtual bool assertionEnded( AssertionStats const& _assertionStats ) {
+ AssertionResult const& result = _assertionStats.assertionResult;
+
+ bool printInfoMessages = true;
+
+ // Drop out if result was successful and we're not printing those
+ if( !m_config->includeSuccessfulResults() && result.isOk() ) {
+ if( result.getResultType() != ResultWas::Warning )
+ return false;
+ printInfoMessages = false;
+ }
+
+ lazyPrint();
+
+ AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
+ printer.print();
+ stream << std::endl;
+ return true;
+ }
+
+ virtual void sectionStarting( SectionInfo const& _sectionInfo ) {
+ m_headerPrinted = false;
+ StreamingReporterBase::sectionStarting( _sectionInfo );
+ }
+ virtual void sectionEnded( SectionStats const& _sectionStats ) {
+ if( _sectionStats.missingAssertions ) {
+ lazyPrint();
+ Colour colour( Colour::ResultError );
+ if( m_sectionStack.size() > 1 )
+ stream << "\nNo assertions in section";
+ else
+ stream << "\nNo assertions in test case";
+ stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl;
+ }
+ if( m_headerPrinted ) {
+ if( m_config->showDurations() == ShowDurations::Always )
+ stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl;
+ m_headerPrinted = false;
+ }
+ else {
+ if( m_config->showDurations() == ShowDurations::Always )
+ stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl;
+ }
+ StreamingReporterBase::sectionEnded( _sectionStats );
+ }
+
+ virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) {
+ StreamingReporterBase::testCaseEnded( _testCaseStats );
+ m_headerPrinted = false;
+ }
+ virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) {
+ if( currentGroupInfo.used ) {
+ printSummaryDivider();
+ stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n";
+ printTotals( _testGroupStats.totals );
+ stream << "\n" << std::endl;
+ }
+ StreamingReporterBase::testGroupEnded( _testGroupStats );
+ }
+ virtual void testRunEnded( TestRunStats const& _testRunStats ) {
+ printTotalsDivider( _testRunStats.totals );
+ printTotals( _testRunStats.totals );
+ stream << std::endl;
+ StreamingReporterBase::testRunEnded( _testRunStats );
+ }
+
+ private:
+
+ class AssertionPrinter {
+ void operator= ( AssertionPrinter const& );
+ public:
+ AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages )
+ : stream( _stream ),
+ stats( _stats ),
+ result( _stats.assertionResult ),
+ colour( Colour::None ),
+ message( result.getMessage() ),
+ messages( _stats.infoMessages ),
+ printInfoMessages( _printInfoMessages )
+ {
+ switch( result.getResultType() ) {
+ case ResultWas::Ok:
+ colour = Colour::Success;
+ passOrFail = "PASSED";
+ //if( result.hasMessage() )
+ if( _stats.infoMessages.size() == 1 )
+ messageLabel = "with message";
+ if( _stats.infoMessages.size() > 1 )
+ messageLabel = "with messages";
+ break;
+ case ResultWas::ExpressionFailed:
+ if( result.isOk() ) {
+ colour = Colour::Success;
+ passOrFail = "FAILED - but was ok";
+ }
+ else {
+ colour = Colour::Error;
+ passOrFail = "FAILED";
+ }
+ if( _stats.infoMessages.size() == 1 )
+ messageLabel = "with message";
+ if( _stats.infoMessages.size() > 1 )
+ messageLabel = "with messages";
+ break;
+ case ResultWas::ThrewException:
+ colour = Colour::Error;
+ passOrFail = "FAILED";
+ messageLabel = "due to unexpected exception with message";
+ break;
+ case ResultWas::FatalErrorCondition:
+ colour = Colour::Error;
+ passOrFail = "FAILED";
+ messageLabel = "due to a fatal error condition";
+ break;
+ case ResultWas::DidntThrowException:
+ colour = Colour::Error;
+ passOrFail = "FAILED";
+ messageLabel = "because no exception was thrown where one was expected";
+ break;
+ case ResultWas::Info:
+ messageLabel = "info";
+ break;
+ case ResultWas::Warning:
+ messageLabel = "warning";
+ break;
+ case ResultWas::ExplicitFailure:
+ passOrFail = "FAILED";
+ colour = Colour::Error;
+ if( _stats.infoMessages.size() == 1 )
+ messageLabel = "explicitly with message";
+ if( _stats.infoMessages.size() > 1 )
+ messageLabel = "explicitly with messages";
+ break;
+ // These cases are here to prevent compiler warnings
+ case ResultWas::Unknown:
+ case ResultWas::FailureBit:
+ case ResultWas::Exception:
+ passOrFail = "** internal error **";
+ colour = Colour::Error;
+ break;
+ }
+ }
+
+ void print() const {
+ printSourceInfo();
+ if( stats.totals.assertions.total() > 0 ) {
+ if( result.isOk() )
+ stream << "\n";
+ printResultType();
+ printOriginalExpression();
+ printReconstructedExpression();
+ }
+ else {
+ stream << "\n";
+ }
+ printMessage();
+ }
+
+ private:
+ void printResultType() const {
+ if( !passOrFail.empty() ) {
+ Colour colourGuard( colour );
+ stream << passOrFail << ":\n";
+ }
+ }
+ void printOriginalExpression() const {
+ if( result.hasExpression() ) {
+ Colour colourGuard( Colour::OriginalExpression );
+ stream << " ";
+ stream << result.getExpressionInMacro();
+ stream << "\n";
+ }
+ }
+ void printReconstructedExpression() const {
+ if( result.hasExpandedExpression() ) {
+ stream << "with expansion:\n";
+ Colour colourGuard( Colour::ReconstructedExpression );
+ stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n";
+ }
+ }
+ void printMessage() const {
+ if( !messageLabel.empty() )
+ stream << messageLabel << ":" << "\n";
+ for( std::vector<MessageInfo>::const_iterator it = messages.begin(), itEnd = messages.end();
+ it != itEnd;
+ ++it ) {
+ // If this assertion is a warning ignore any INFO messages
+ if( printInfoMessages || it->type != ResultWas::Info )
+ stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n";
+ }
+ }
+ void printSourceInfo() const {
+ Colour colourGuard( Colour::FileName );
+ stream << result.getSourceInfo() << ": ";
+ }
+
+ std::ostream& stream;
+ AssertionStats const& stats;
+ AssertionResult const& result;
+ Colour::Code colour;
+ std::string passOrFail;
+ std::string messageLabel;
+ std::string message;
+ std::vector<MessageInfo> messages;
+ bool printInfoMessages;
+ };
+
+ void lazyPrint() {
+
+ if( !currentTestRunInfo.used )
+ lazyPrintRunInfo();
+ if( !currentGroupInfo.used )
+ lazyPrintGroupInfo();
+
+ if( !m_headerPrinted ) {
+ printTestCaseAndSectionHeader();
+ m_headerPrinted = true;
+ }
+ }
+ void lazyPrintRunInfo() {
+ stream << "\n" << getLineOfChars<'~'>() << "\n";
+ Colour colour( Colour::SecondaryText );
+ stream << currentTestRunInfo->name
+ << " is a Catch v" << libraryVersion.majorVersion << "."
+ << libraryVersion.minorVersion << " b"
+ << libraryVersion.buildNumber;
+ if( libraryVersion.branchName != std::string( "master" ) )
+ stream << " (" << libraryVersion.branchName << ")";
+ stream << " host application.\n"
+ << "Run with -? for options\n\n";
+
+ if( m_config->rngSeed() != 0 )
+ stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
+
+ currentTestRunInfo.used = true;
+ }
+ void lazyPrintGroupInfo() {
+ if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) {
+ printClosedHeader( "Group: " + currentGroupInfo->name );
+ currentGroupInfo.used = true;
+ }
+ }
+ void printTestCaseAndSectionHeader() {
+ assert( !m_sectionStack.empty() );
+ printOpenHeader( currentTestCaseInfo->name );
+
+ if( m_sectionStack.size() > 1 ) {
+ Colour colourGuard( Colour::Headers );
+
+ std::vector<SectionInfo>::const_iterator
+ it = m_sectionStack.begin()+1, // Skip first section (test case)
+ itEnd = m_sectionStack.end();
+ for( ; it != itEnd; ++it )
+ printHeaderString( it->name, 2 );
+ }
+
+ SourceLineInfo lineInfo = m_sectionStack.front().lineInfo;
+
+ if( !lineInfo.empty() ){
+ stream << getLineOfChars<'-'>() << "\n";
+ Colour colourGuard( Colour::FileName );
+ stream << lineInfo << "\n";
+ }
+ stream << getLineOfChars<'.'>() << "\n" << std::endl;
+ }
+
+ void printClosedHeader( std::string const& _name ) {
+ printOpenHeader( _name );
+ stream << getLineOfChars<'.'>() << "\n";
+ }
+ void printOpenHeader( std::string const& _name ) {
+ stream << getLineOfChars<'-'>() << "\n";
+ {
+ Colour colourGuard( Colour::Headers );
+ printHeaderString( _name );
+ }
+ }
+
+ // if string has a : in first line will set indent to follow it on
+ // subsequent lines
+ void printHeaderString( std::string const& _string, std::size_t indent = 0 ) {
+ std::size_t i = _string.find( ": " );
+ if( i != std::string::npos )
+ i+=2;
+ else
+ i = 0;
+ stream << Text( _string, TextAttributes()
+ .setIndent( indent+i)
+ .setInitialIndent( indent ) ) << "\n";
+ }
+
+ struct SummaryColumn {
+
+ SummaryColumn( std::string const& _label, Colour::Code _colour )
+ : label( _label ),
+ colour( _colour )
+ {}
+ SummaryColumn addRow( std::size_t count ) {
+ std::ostringstream oss;
+ oss << count;
+ std::string row = oss.str();
+ for( std::vector<std::string>::iterator it = rows.begin(); it != rows.end(); ++it ) {
+ while( it->size() < row.size() )
+ *it = " " + *it;
+ while( it->size() > row.size() )
+ row = " " + row;
+ }
+ rows.push_back( row );
+ return *this;
+ }
+
+ std::string label;
+ Colour::Code colour;
+ std::vector<std::string> rows;
+
+ };
+
+ void printTotals( Totals const& totals ) {
+ if( totals.testCases.total() == 0 ) {
+ stream << Colour( Colour::Warning ) << "No tests ran\n";
+ }
+ else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) {
+ stream << Colour( Colour::ResultSuccess ) << "All tests passed";
+ stream << " ("
+ << pluralise( totals.assertions.passed, "assertion" ) << " in "
+ << pluralise( totals.testCases.passed, "test case" ) << ")"
+ << "\n";
+ }
+ else {
+
+ std::vector<SummaryColumn> columns;
+ columns.push_back( SummaryColumn( "", Colour::None )
+ .addRow( totals.testCases.total() )
+ .addRow( totals.assertions.total() ) );
+ columns.push_back( SummaryColumn( "passed", Colour::Success )
+ .addRow( totals.testCases.passed )
+ .addRow( totals.assertions.passed ) );
+ columns.push_back( SummaryColumn( "failed", Colour::ResultError )
+ .addRow( totals.testCases.failed )
+ .addRow( totals.assertions.failed ) );
+ columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure )
+ .addRow( totals.testCases.failedButOk )
+ .addRow( totals.assertions.failedButOk ) );
+
+ printSummaryRow( "test cases", columns, 0 );
+ printSummaryRow( "assertions", columns, 1 );
+ }
+ }
+ void printSummaryRow( std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row ) {
+ for( std::vector<SummaryColumn>::const_iterator it = cols.begin(); it != cols.end(); ++it ) {
+ std::string value = it->rows[row];
+ if( it->label.empty() ) {
+ stream << label << ": ";
+ if( value != "0" )
+ stream << value;
+ else
+ stream << Colour( Colour::Warning ) << "- none -";
+ }
+ else if( value != "0" ) {
+ stream << Colour( Colour::LightGrey ) << " | ";
+ stream << Colour( it->colour )
+ << value << " " << it->label;
+ }
+ }
+ stream << "\n";
+ }
+
+ static std::size_t makeRatio( std::size_t number, std::size_t total ) {
+ std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0;
+ return ( ratio == 0 && number > 0 ) ? 1 : ratio;
+ }
+ static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) {
+ if( i > j && i > k )
+ return i;
+ else if( j > k )
+ return j;
+ else
+ return k;
+ }
+
+ void printTotalsDivider( Totals const& totals ) {
+ if( totals.testCases.total() > 0 ) {
+ std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() );
+ std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() );
+ std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() );
+ while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 )
+ findMax( failedRatio, failedButOkRatio, passedRatio )++;
+ while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 )
+ findMax( failedRatio, failedButOkRatio, passedRatio )--;
+
+ stream << Colour( Colour::Error ) << std::string( failedRatio, '=' );
+ stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' );
+ if( totals.testCases.allPassed() )
+ stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' );
+ else
+ stream << Colour( Colour::Success ) << std::string( passedRatio, '=' );
+ }
+ else {
+ stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' );
+ }
+ stream << "\n";
+ }
+ void printSummaryDivider() {
+ stream << getLineOfChars<'-'>() << "\n";
+ }
+
+ private:
+ bool m_headerPrinted;
+ };
+
+ INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter )
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_compact.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED
+
+namespace Catch {
+
+ struct CompactReporter : StreamingReporterBase {
+
+ CompactReporter( ReporterConfig const& _config )
+ : StreamingReporterBase( _config )
+ {}
+
+ virtual ~CompactReporter();
+
+ static std::string getDescription() {
+ return "Reports test results on a single line, suitable for IDEs";
+ }
+
+ virtual ReporterPreferences getPreferences() const {
+ ReporterPreferences prefs;
+ prefs.shouldRedirectStdOut = false;
+ return prefs;
+ }
+
+ virtual void noMatchingTestCases( std::string const& spec ) {
+ stream << "No test cases matched '" << spec << "'" << std::endl;
+ }
+
+ virtual void assertionStarting( AssertionInfo const& ) {
+ }
+
+ virtual bool assertionEnded( AssertionStats const& _assertionStats ) {
+ AssertionResult const& result = _assertionStats.assertionResult;
+
+ bool printInfoMessages = true;
+
+ // Drop out if result was successful and we're not printing those
+ if( !m_config->includeSuccessfulResults() && result.isOk() ) {
+ if( result.getResultType() != ResultWas::Warning )
+ return false;
+ printInfoMessages = false;
+ }
+
+ AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
+ printer.print();
+
+ stream << std::endl;
+ return true;
+ }
+
+ virtual void testRunEnded( TestRunStats const& _testRunStats ) {
+ printTotals( _testRunStats.totals );
+ stream << "\n" << std::endl;
+ StreamingReporterBase::testRunEnded( _testRunStats );
+ }
+
+ private:
+ class AssertionPrinter {
+ void operator= ( AssertionPrinter const& );
+ public:
+ AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages )
+ : stream( _stream )
+ , stats( _stats )
+ , result( _stats.assertionResult )
+ , messages( _stats.infoMessages )
+ , itMessage( _stats.infoMessages.begin() )
+ , printInfoMessages( _printInfoMessages )
+ {}
+
+ void print() {
+ printSourceInfo();
+
+ itMessage = messages.begin();
+
+ switch( result.getResultType() ) {
+ case ResultWas::Ok:
+ printResultType( Colour::ResultSuccess, passedString() );
+ printOriginalExpression();
+ printReconstructedExpression();
+ if ( ! result.hasExpression() )
+ printRemainingMessages( Colour::None );
+ else
+ printRemainingMessages();
+ break;
+ case ResultWas::ExpressionFailed:
+ if( result.isOk() )
+ printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) );
+ else
+ printResultType( Colour::Error, failedString() );
+ printOriginalExpression();
+ printReconstructedExpression();
+ printRemainingMessages();
+ break;
+ case ResultWas::ThrewException:
+ printResultType( Colour::Error, failedString() );
+ printIssue( "unexpected exception with message:" );
+ printMessage();
+ printExpressionWas();
+ printRemainingMessages();
+ break;
+ case ResultWas::FatalErrorCondition:
+ printResultType( Colour::Error, failedString() );
+ printIssue( "fatal error condition with message:" );
+ printMessage();
+ printExpressionWas();
+ printRemainingMessages();
+ break;
+ case ResultWas::DidntThrowException:
+ printResultType( Colour::Error, failedString() );
+ printIssue( "expected exception, got none" );
+ printExpressionWas();
+ printRemainingMessages();
+ break;
+ case ResultWas::Info:
+ printResultType( Colour::None, "info" );
+ printMessage();
+ printRemainingMessages();
+ break;
+ case ResultWas::Warning:
+ printResultType( Colour::None, "warning" );
+ printMessage();
+ printRemainingMessages();
+ break;
+ case ResultWas::ExplicitFailure:
+ printResultType( Colour::Error, failedString() );
+ printIssue( "explicitly" );
+ printRemainingMessages( Colour::None );
+ break;
+ // These cases are here to prevent compiler warnings
+ case ResultWas::Unknown:
+ case ResultWas::FailureBit:
+ case ResultWas::Exception:
+ printResultType( Colour::Error, "** internal error **" );
+ break;
+ }
+ }
+
+ private:
+ // Colour::LightGrey
+
+ static Colour::Code dimColour() { return Colour::FileName; }
+
+#ifdef CATCH_PLATFORM_MAC
+ static const char* failedString() { return "FAILED"; }
+ static const char* passedString() { return "PASSED"; }
+#else
+ static const char* failedString() { return "failed"; }
+ static const char* passedString() { return "passed"; }
+#endif
+
+ void printSourceInfo() const {
+ Colour colourGuard( Colour::FileName );
+ stream << result.getSourceInfo() << ":";
+ }
+
+ void printResultType( Colour::Code colour, std::string passOrFail ) const {
+ if( !passOrFail.empty() ) {
+ {
+ Colour colourGuard( colour );
+ stream << " " << passOrFail;
+ }
+ stream << ":";
+ }
+ }
+
+ void printIssue( std::string issue ) const {
+ stream << " " << issue;
+ }
+
+ void printExpressionWas() {
+ if( result.hasExpression() ) {
+ stream << ";";
+ {
+ Colour colour( dimColour() );
+ stream << " expression was:";
+ }
+ printOriginalExpression();
+ }
+ }
+
+ void printOriginalExpression() const {
+ if( result.hasExpression() ) {
+ stream << " " << result.getExpression();
+ }
+ }
+
+ void printReconstructedExpression() const {
+ if( result.hasExpandedExpression() ) {
+ {
+ Colour colour( dimColour() );
+ stream << " for: ";
+ }
+ stream << result.getExpandedExpression();
+ }
+ }
+
+ void printMessage() {
+ if ( itMessage != messages.end() ) {
+ stream << " '" << itMessage->message << "'";
+ ++itMessage;
+ }
+ }
+
+ void printRemainingMessages( Colour::Code colour = dimColour() ) {
+ if ( itMessage == messages.end() )
+ return;
+
+ // using messages.end() directly yields compilation error:
+ std::vector<MessageInfo>::const_iterator itEnd = messages.end();
+ const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) );
+
+ {
+ Colour colourGuard( colour );
+ stream << " with " << pluralise( N, "message" ) << ":";
+ }
+
+ for(; itMessage != itEnd; ) {
+ // If this assertion is a warning ignore any INFO messages
+ if( printInfoMessages || itMessage->type != ResultWas::Info ) {
+ stream << " '" << itMessage->message << "'";
+ if ( ++itMessage != itEnd ) {
+ Colour colourGuard( dimColour() );
+ stream << " and";
+ }
+ }
+ }
+ }
+
+ private:
+ std::ostream& stream;
+ AssertionStats const& stats;
+ AssertionResult const& result;
+ std::vector<MessageInfo> messages;
+ std::vector<MessageInfo>::const_iterator itMessage;
+ bool printInfoMessages;
+ };
+
+ // Colour, message variants:
+ // - white: No tests ran.
+ // - red: Failed [both/all] N test cases, failed [both/all] M assertions.
+ // - white: Passed [both/all] N test cases (no assertions).
+ // - red: Failed N tests cases, failed M assertions.
+ // - green: Passed [both/all] N tests cases with M assertions.
+
+ std::string bothOrAll( std::size_t count ) const {
+ return count == 1 ? "" : count == 2 ? "both " : "all " ;
+ }
+
+ void printTotals( const Totals& totals ) const {
+ if( totals.testCases.total() == 0 ) {
+ stream << "No tests ran.";
+ }
+ else if( totals.testCases.failed == totals.testCases.total() ) {
+ Colour colour( Colour::ResultError );
+ const std::string qualify_assertions_failed =
+ totals.assertions.failed == totals.assertions.total() ?
+ bothOrAll( totals.assertions.failed ) : "";
+ stream <<
+ "Failed " << bothOrAll( totals.testCases.failed )
+ << pluralise( totals.testCases.failed, "test case" ) << ", "
+ "failed " << qualify_assertions_failed <<
+ pluralise( totals.assertions.failed, "assertion" ) << ".";
+ }
+ else if( totals.assertions.total() == 0 ) {
+ stream <<
+ "Passed " << bothOrAll( totals.testCases.total() )
+ << pluralise( totals.testCases.total(), "test case" )
+ << " (no assertions).";
+ }
+ else if( totals.assertions.failed ) {
+ Colour colour( Colour::ResultError );
+ stream <<
+ "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", "
+ "failed " << pluralise( totals.assertions.failed, "assertion" ) << ".";
+ }
+ else {
+ Colour colour( Colour::ResultSuccess );
+ stream <<
+ "Passed " << bothOrAll( totals.testCases.passed )
+ << pluralise( totals.testCases.passed, "test case" ) <<
+ " with " << pluralise( totals.assertions.passed, "assertion" ) << ".";
+ }
+ }
+ };
+
+ INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter )
+
+} // end namespace Catch
+
+namespace Catch {
+ NonCopyable::~NonCopyable() {}
+ IShared::~IShared() {}
+ StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {}
+ IContext::~IContext() {}
+ IResultCapture::~IResultCapture() {}
+ ITestCase::~ITestCase() {}
+ ITestCaseRegistry::~ITestCaseRegistry() {}
+ IRegistryHub::~IRegistryHub() {}
+ IMutableRegistryHub::~IMutableRegistryHub() {}
+ IExceptionTranslator::~IExceptionTranslator() {}
+ IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {}
+ IReporter::~IReporter() {}
+ IReporterFactory::~IReporterFactory() {}
+ IReporterRegistry::~IReporterRegistry() {}
+ IStreamingReporter::~IStreamingReporter() {}
+ AssertionStats::~AssertionStats() {}
+ SectionStats::~SectionStats() {}
+ TestCaseStats::~TestCaseStats() {}
+ TestGroupStats::~TestGroupStats() {}
+ TestRunStats::~TestRunStats() {}
+ CumulativeReporterBase::SectionNode::~SectionNode() {}
+ CumulativeReporterBase::~CumulativeReporterBase() {}
+
+ StreamingReporterBase::~StreamingReporterBase() {}
+ ConsoleReporter::~ConsoleReporter() {}
+ CompactReporter::~CompactReporter() {}
+ IRunner::~IRunner() {}
+ IMutableContext::~IMutableContext() {}
+ IConfig::~IConfig() {}
+ XmlReporter::~XmlReporter() {}
+ JunitReporter::~JunitReporter() {}
+ TestRegistry::~TestRegistry() {}
+ FreeFunctionTestCase::~FreeFunctionTestCase() {}
+ IGeneratorInfo::~IGeneratorInfo() {}
+ IGeneratorsForTest::~IGeneratorsForTest() {}
+ TestSpec::Pattern::~Pattern() {}
+ TestSpec::NamePattern::~NamePattern() {}
+ TestSpec::TagPattern::~TagPattern() {}
+ TestSpec::ExcludedPattern::~ExcludedPattern() {}
+
+ Matchers::Impl::StdString::Equals::~Equals() {}
+ Matchers::Impl::StdString::Contains::~Contains() {}
+ Matchers::Impl::StdString::StartsWith::~StartsWith() {}
+ Matchers::Impl::StdString::EndsWith::~EndsWith() {}
+
+ void Config::dummy() {}
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif
+
+#ifdef CATCH_CONFIG_MAIN
+// #included from: internal/catch_default_main.hpp
+#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED
+
+#ifndef __OBJC__
+
+// Standard C/C++ main entry point
+int main (int argc, char * const argv[]) {
+ return Catch::Session().run( argc, argv );
+}
+
+#else // __OBJC__
+
+// Objective-C entry point
+int main (int argc, char * const argv[]) {
+#if !CATCH_ARC_ENABLED
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+#endif
+
+ Catch::registerTestMethods();
+ int result = Catch::Session().run( argc, (char* const*)argv );
+
+#if !CATCH_ARC_ENABLED
+ [pool drain];
+#endif
+
+ return result;
+}
+
+#endif // __OBJC__
+
+#endif
+
+#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED
+# undef CLARA_CONFIG_MAIN
+#endif
+
+//////
+
+// If this config identifier is defined then all CATCH macros are prefixed with CATCH_
+#ifdef CATCH_CONFIG_PREFIX_ALL
+
+#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" )
+#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" )
+
+#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" )
+#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" )
+#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" )
+
+#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" )
+#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" )
+#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" )
+#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" )
+#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" )
+
+#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" )
+#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" )
+#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" )
+
+#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" )
+#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" )
+
+#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
+#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg )
+#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
+#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" )
+#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" )
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+ #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+ #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+ #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+ #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+ #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ )
+ #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ )
+#else
+ #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
+ #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
+ #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
+ #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
+ #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg )
+ #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg )
+#endif
+#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
+
+#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType )
+#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType )
+
+#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr )
+
+// "BDD-style" convenience wrappers
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ )
+#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
+#else
+#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags )
+#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags )
+#endif
+#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" )
+#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" )
+#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" )
+#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" )
+#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" )
+
+// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
+#else
+
+#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" )
+#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" )
+
+#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" )
+#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" )
+#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" )
+
+#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" )
+#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" )
+#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" )
+#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" )
+#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" )
+
+#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" )
+#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" )
+#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" )
+
+#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" )
+#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" )
+
+#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" )
+#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg )
+#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" )
+#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" )
+#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" )
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+ #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+ #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+ #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+ #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+ #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ )
+ #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ )
+#else
+ #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
+ #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
+ #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
+ #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
+ #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg )
+ #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg )
+#endif
+#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
+
+#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType )
+#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType )
+
+#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr )
+
+#endif
+
+#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature )
+
+// "BDD-style" convenience wrappers
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ )
+#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
+#else
+#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags )
+#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags )
+#endif
+#define GIVEN( desc ) SECTION( " Given: " desc, "" )
+#define WHEN( desc ) SECTION( " When: " desc, "" )
+#define AND_WHEN( desc ) SECTION( "And when: " desc, "" )
+#define THEN( desc ) SECTION( " Then: " desc, "" )
+#define AND_THEN( desc ) SECTION( " And: " desc, "" )
+
+using Catch::Detail::Approx;
+
+// #included from: internal/catch_reenable_warnings.h
+
+#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED
+
+#ifdef __clang__
+# ifdef __ICC // icpc defines the __clang__ macro
+# pragma warning(pop)
+# else
+# pragma clang diagnostic pop
+# endif
+#elif defined __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
+#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+
diff --git a/tests/expected/difference-clockwise-polygon-clockwise-hole.json b/tests/expected/difference-clockwise-polygon-clockwise-hole.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/difference-clockwise-polygon-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-clockwise-polygon-counter-clockwise-hole.json b/tests/expected/difference-clockwise-polygon-counter-clockwise-hole.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/difference-clockwise-polygon-counter-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-clockwise-polygon.json b/tests/expected/difference-clockwise-polygon.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/expected/difference-clockwise-polygon.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/expected/difference-counter-clockwise-polygon-clockwise-hole.json b/tests/expected/difference-counter-clockwise-polygon-clockwise-hole.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/difference-counter-clockwise-polygon-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-counter-clockwise-polygon-counter-clockwise-hole.json b/tests/expected/difference-counter-clockwise-polygon-counter-clockwise-hole.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/difference-counter-clockwise-polygon-counter-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-counter-clockwise-polygon.json b/tests/expected/difference-counter-clockwise-polygon.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/expected/difference-counter-clockwise-polygon.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/expected/difference-multi-polygon-with-duplicate-polygon.json b/tests/expected/difference-multi-polygon-with-duplicate-polygon.json
new file mode 100644
index 0000000..7387b8e
--- /dev/null
+++ b/tests/expected/difference-multi-polygon-with-duplicate-polygon.json
@@ -0,0 +1 @@
+[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-multi-polygon-with-shared-edge.json b/tests/expected/difference-multi-polygon-with-shared-edge.json
new file mode 100644
index 0000000..4b58655
--- /dev/null
+++ b/tests/expected/difference-multi-polygon-with-shared-edge.json
@@ -0,0 +1 @@
+[[[[2500,1543],[2116,2500],[1285,2500],[-517,2129],[-2500,-482],[-2500,-2500],[2500,-2500],[2500,1543]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-multi-polygon-with-spikes.json b/tests/expected/difference-multi-polygon-with-spikes.json
new file mode 100644
index 0000000..7b9ff56
--- /dev/null
+++ b/tests/expected/difference-multi-polygon-with-spikes.json
@@ -0,0 +1 @@
+[[[[1462,2500],[-2500,2500],[-2500,-2500],[-2466,-2500],[-2443,-2448],[-2439,-2470],[1462,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-multipolygon-both-clockwise.json b/tests/expected/difference-multipolygon-both-clockwise.json
new file mode 100644
index 0000000..14ec906
--- /dev/null
+++ b/tests/expected/difference-multipolygon-both-clockwise.json
@@ -0,0 +1 @@
+[[[[1747,-852],[1127,1031],[-1427,264],[153,-1611],[1747,-852]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-multipolygon-both-counter-clockwise.json b/tests/expected/difference-multipolygon-both-counter-clockwise.json
new file mode 100644
index 0000000..14ec906
--- /dev/null
+++ b/tests/expected/difference-multipolygon-both-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[1747,-852],[1127,1031],[-1427,264],[153,-1611],[1747,-852]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-multipolygon-overlap-different-orientations.json b/tests/expected/difference-multipolygon-overlap-different-orientations.json
new file mode 100644
index 0000000..14ec906
--- /dev/null
+++ b/tests/expected/difference-multipolygon-overlap-different-orientations.json
@@ -0,0 +1 @@
+[[[[1747,-852],[1127,1031],[-1427,264],[153,-1611],[1747,-852]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-clockwise.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-clockwise.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-overlapping-multi-polygon.json b/tests/expected/difference-overlapping-multi-polygon.json
new file mode 100644
index 0000000..caeca22
--- /dev/null
+++ b/tests/expected/difference-overlapping-multi-polygon.json
@@ -0,0 +1 @@
+[[[[698,-113],[1281,2274],[-658,2500],[-1679,2500],[-2307,-247],[698,-113]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-covered-with-hole.json b/tests/expected/difference-polygon-covered-with-hole.json
new file mode 100644
index 0000000..7387b8e
--- /dev/null
+++ b/tests/expected/difference-polygon-covered-with-hole.json
@@ -0,0 +1 @@
+[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-no-interior.json b/tests/expected/difference-polygon-no-interior.json
new file mode 100644
index 0000000..7387b8e
--- /dev/null
+++ b/tests/expected/difference-polygon-no-interior.json
@@ -0,0 +1 @@
+[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-two-intersecting-holes-and-self-intersection.json b/tests/expected/difference-polygon-two-intersecting-holes-and-self-intersection.json
new file mode 100644
index 0000000..0708966
--- /dev/null
+++ b/tests/expected/difference-polygon-two-intersecting-holes-and-self-intersection.json
@@ -0,0 +1 @@
+[[[[-1792,2388],[-1903,2500],[-2500,2500],[-2500,1282],[-1792,2388]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-two-intersecting-holes.json b/tests/expected/difference-polygon-two-intersecting-holes.json
new file mode 100644
index 0000000..be4d1b5
--- /dev/null
+++ b/tests/expected/difference-polygon-two-intersecting-holes.json
@@ -0,0 +1 @@
+[[[[222,-1510],[-810,-161],[858,390],[228,2500],[-1720,2500],[-2500,1282],[-2500,-1252],[-1211,-2262],[222,-1510]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-with-double-nested-holes.json b/tests/expected/difference-polygon-with-double-nested-holes.json
new file mode 100644
index 0000000..e73e6a8
--- /dev/null
+++ b/tests/expected/difference-polygon-with-double-nested-holes.json
@@ -0,0 +1 @@
+[[[[-1796,996],[819,1219],[1422,-713],[-761,-1085],[-1796,996]]],[[[2500,-2027],[2500,1567],[2283,2500],[-1351,2500],[-2500,1983],[-2500,-1710],[-2291,-2500],[1330,-2500],[2500,-2027]],[[-1796,996],[819,1219],[1422,-713],[-761,-1085],[-1796,996]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-with-extending-hole.json b/tests/expected/difference-polygon-with-extending-hole.json
new file mode 100644
index 0000000..14ec906
--- /dev/null
+++ b/tests/expected/difference-polygon-with-extending-hole.json
@@ -0,0 +1 @@
+[[[[1747,-852],[1127,1031],[-1427,264],[153,-1611],[1747,-852]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-with-exterior-hole.json b/tests/expected/difference-polygon-with-exterior-hole.json
new file mode 100644
index 0000000..af6b1ed
--- /dev/null
+++ b/tests/expected/difference-polygon-with-exterior-hole.json
@@ -0,0 +1 @@
+[[[[-152,-2332],[-247,2500],[-2500,2500],[-2500,1535],[-1672,2163],[-1597,855],[-2500,-3],[-2500,-2500],[-50,-2500],[-152,-2332]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-with-hole-shared-edge.json b/tests/expected/difference-polygon-with-hole-shared-edge.json
new file mode 100644
index 0000000..4b58655
--- /dev/null
+++ b/tests/expected/difference-polygon-with-hole-shared-edge.json
@@ -0,0 +1 @@
+[[[[2500,1543],[2116,2500],[1285,2500],[-517,2129],[-2500,-482],[-2500,-2500],[2500,-2500],[2500,1543]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-with-hole-with-shared-point.json b/tests/expected/difference-polygon-with-hole-with-shared-point.json
new file mode 100644
index 0000000..3bbaa0a
--- /dev/null
+++ b/tests/expected/difference-polygon-with-hole-with-shared-point.json
@@ -0,0 +1 @@
+[[[[2500,588],[2500,1252],[-1657,1185],[-2500,-146],[-2500,-1308],[1563,-1814],[2500,588]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-with-spike.json b/tests/expected/difference-polygon-with-spike.json
new file mode 100644
index 0000000..bf317a4
--- /dev/null
+++ b/tests/expected/difference-polygon-with-spike.json
@@ -0,0 +1 @@
+[[[[2500,2497],[2500,2500],[-2500,2500],[-2500,-2484],[-2482,-2452],[-2475,-2478],[2500,2497]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-polygon-with-two-holes-outside-exterior-ring.json b/tests/expected/difference-polygon-with-two-holes-outside-exterior-ring.json
new file mode 100644
index 0000000..e73e6a8
--- /dev/null
+++ b/tests/expected/difference-polygon-with-two-holes-outside-exterior-ring.json
@@ -0,0 +1 @@
+[[[[-1796,996],[819,1219],[1422,-713],[-761,-1085],[-1796,996]]],[[[2500,-2027],[2500,1567],[2283,2500],[-1351,2500],[-2500,1983],[-2500,-1710],[-2291,-2500],[1330,-2500],[2500,-2027]],[[-1796,996],[819,1219],[1422,-713],[-761,-1085],[-1796,996]]]]
\ No newline at end of file
diff --git a/tests/expected/difference-self-intersecting-ring-polygon.json b/tests/expected/difference-self-intersecting-ring-polygon.json
new file mode 100644
index 0000000..dc30b5a
--- /dev/null
+++ b/tests/expected/difference-self-intersecting-ring-polygon.json
@@ -0,0 +1 @@
+[[[[-2453,2500],[-2500,2500],[-2500,2448],[-2453,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-clockwise-polygon-clockwise-hole.json b/tests/expected/intersection-clockwise-polygon-clockwise-hole.json
new file mode 100644
index 0000000..1c0a7c7
--- /dev/null
+++ b/tests/expected/intersection-clockwise-polygon-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]],[[[2500,2500],[-1579,2500],[-2500,-923],[-2500,-2500],[2500,-2500],[2500,2500]],[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-clockwise-polygon-counter-clockwise-hole.json b/tests/expected/intersection-clockwise-polygon-counter-clockwise-hole.json
new file mode 100644
index 0000000..1c0a7c7
--- /dev/null
+++ b/tests/expected/intersection-clockwise-polygon-counter-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]],[[[2500,2500],[-1579,2500],[-2500,-923],[-2500,-2500],[2500,-2500],[2500,2500]],[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-clockwise-polygon.json b/tests/expected/intersection-clockwise-polygon.json
new file mode 100644
index 0000000..7387b8e
--- /dev/null
+++ b/tests/expected/intersection-clockwise-polygon.json
@@ -0,0 +1 @@
+[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-counter-clockwise-polygon-clockwise-hole.json b/tests/expected/intersection-counter-clockwise-polygon-clockwise-hole.json
new file mode 100644
index 0000000..1c0a7c7
--- /dev/null
+++ b/tests/expected/intersection-counter-clockwise-polygon-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]],[[[2500,2500],[-1579,2500],[-2500,-923],[-2500,-2500],[2500,-2500],[2500,2500]],[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-counter-clockwise-polygon-counter-clockwise-hole.json b/tests/expected/intersection-counter-clockwise-polygon-counter-clockwise-hole.json
new file mode 100644
index 0000000..1c0a7c7
--- /dev/null
+++ b/tests/expected/intersection-counter-clockwise-polygon-counter-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]],[[[2500,2500],[-1579,2500],[-2500,-923],[-2500,-2500],[2500,-2500],[2500,2500]],[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-counter-clockwise-polygon.json b/tests/expected/intersection-counter-clockwise-polygon.json
new file mode 100644
index 0000000..7387b8e
--- /dev/null
+++ b/tests/expected/intersection-counter-clockwise-polygon.json
@@ -0,0 +1 @@
+[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-multi-polygon-with-duplicate-polygon.json b/tests/expected/intersection-multi-polygon-with-duplicate-polygon.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/expected/intersection-multi-polygon-with-duplicate-polygon.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/expected/intersection-multi-polygon-with-shared-edge.json b/tests/expected/intersection-multi-polygon-with-shared-edge.json
new file mode 100644
index 0000000..a8adc73
--- /dev/null
+++ b/tests/expected/intersection-multi-polygon-with-shared-edge.json
@@ -0,0 +1 @@
+[[[[-517,2129],[1285,2500],[-2500,2500],[-2500,-482],[-517,2129]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-multi-polygon-with-spikes.json b/tests/expected/intersection-multi-polygon-with-spikes.json
new file mode 100644
index 0000000..32451cc
--- /dev/null
+++ b/tests/expected/intersection-multi-polygon-with-spikes.json
@@ -0,0 +1 @@
+[[[[1467,2500],[1462,2500],[-2439,-2470],[-2436,-2484],[1467,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-multipolygon-both-clockwise.json b/tests/expected/intersection-multipolygon-both-clockwise.json
new file mode 100644
index 0000000..46a8216
--- /dev/null
+++ b/tests/expected/intersection-multipolygon-both-clockwise.json
@@ -0,0 +1 @@
+[[[[153,-1611],[-1427,264],[1127,1031],[644,2500],[-2500,2500],[-2500,-2328],[-2258,-2500],[-1714,-2500],[153,-1611]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-multipolygon-both-counter-clockwise.json b/tests/expected/intersection-multipolygon-both-counter-clockwise.json
new file mode 100644
index 0000000..46a8216
--- /dev/null
+++ b/tests/expected/intersection-multipolygon-both-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[153,-1611],[-1427,264],[1127,1031],[644,2500],[-2500,2500],[-2500,-2328],[-2258,-2500],[-1714,-2500],[153,-1611]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-multipolygon-overlap-different-orientations.json b/tests/expected/intersection-multipolygon-overlap-different-orientations.json
new file mode 100644
index 0000000..46a8216
--- /dev/null
+++ b/tests/expected/intersection-multipolygon-overlap-different-orientations.json
@@ -0,0 +1 @@
+[[[[153,-1611],[-1427,264],[1127,1031],[644,2500],[-2500,2500],[-2500,-2328],[-2258,-2500],[-1714,-2500],[153,-1611]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ef2ddaa
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-686,-577],[-1275,-303],[-1683,-581],[-1185,-817],[-686,-577]]],[[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]],[[[2500,2500],[-1399,2500],[-2500,-1591],[-2500,-2500],[2500,-2500],[2500,2500]],[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ef2ddaa
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-686,-577],[-1275,-303],[-1683,-581],[-1185,-817],[-686,-577]]],[[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]],[[[2500,2500],[-1399,2500],[-2500,-1591],[-2500,-2500],[2500,-2500],[2500,2500]],[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-clockwise.json
new file mode 100644
index 0000000..1c0a7c7
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-clockwise.json
@@ -0,0 +1 @@
+[[[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]],[[[2500,2500],[-1579,2500],[-2500,-923],[-2500,-2500],[2500,-2500],[2500,2500]],[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ef2ddaa
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-686,-577],[-1275,-303],[-1683,-581],[-1185,-817],[-686,-577]]],[[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]],[[[2500,2500],[-1399,2500],[-2500,-1591],[-2500,-2500],[2500,-2500],[2500,2500]],[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ef2ddaa
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-686,-577],[-1275,-303],[-1683,-581],[-1185,-817],[-686,-577]]],[[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]],[[[2500,2500],[-1399,2500],[-2500,-1591],[-2500,-2500],[2500,-2500],[2500,2500]],[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json
new file mode 100644
index 0000000..1c0a7c7
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]],[[[2500,2500],[-1579,2500],[-2500,-923],[-2500,-2500],[2500,-2500],[2500,2500]],[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ef2ddaa
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-686,-577],[-1275,-303],[-1683,-581],[-1185,-817],[-686,-577]]],[[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]],[[[2500,2500],[-1399,2500],[-2500,-1591],[-2500,-2500],[2500,-2500],[2500,2500]],[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ef2ddaa
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-686,-577],[-1275,-303],[-1683,-581],[-1185,-817],[-686,-577]]],[[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]],[[[2500,2500],[-1399,2500],[-2500,-1591],[-2500,-2500],[2500,-2500],[2500,2500]],[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json
new file mode 100644
index 0000000..1c0a7c7
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json
@@ -0,0 +1 @@
+[[[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]],[[[2500,2500],[-1579,2500],[-2500,-923],[-2500,-2500],[2500,-2500],[2500,2500]],[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ef2ddaa
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-686,-577],[-1275,-303],[-1683,-581],[-1185,-817],[-686,-577]]],[[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]],[[[2500,2500],[-1399,2500],[-2500,-1591],[-2500,-2500],[2500,-2500],[2500,2500]],[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ef2ddaa
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-686,-577],[-1275,-303],[-1683,-581],[-1185,-817],[-686,-577]]],[[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]],[[[2500,2500],[-1399,2500],[-2500,-1591],[-2500,-2500],[2500,-2500],[2500,2500]],[[-2053,-581],[-437,1289],[1935,-729],[-1177,-1092],[-2053,-581]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json
new file mode 100644
index 0000000..1c0a7c7
--- /dev/null
+++ b/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]],[[[2500,2500],[-1579,2500],[-2500,-923],[-2500,-2500],[2500,-2500],[2500,2500]],[[-1900,-720],[-1492,-442],[-903,-716],[-1401,-956],[-1900,-720]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-overlapping-multi-polygon.json b/tests/expected/intersection-overlapping-multi-polygon.json
new file mode 100644
index 0000000..6fc24fa
--- /dev/null
+++ b/tests/expected/intersection-overlapping-multi-polygon.json
@@ -0,0 +1 @@
+[[[[-2307,-247],[-1679,2500],[-2500,2500],[-2500,-2500],[114,-2500],[698,-113],[-2307,-247]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-covered-with-hole.json b/tests/expected/intersection-polygon-covered-with-hole.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/expected/intersection-polygon-covered-with-hole.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-no-interior.json b/tests/expected/intersection-polygon-no-interior.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/expected/intersection-polygon-no-interior.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-two-intersecting-holes-and-self-intersection.json b/tests/expected/intersection-polygon-two-intersecting-holes-and-self-intersection.json
new file mode 100644
index 0000000..51ff27f
--- /dev/null
+++ b/tests/expected/intersection-polygon-two-intersecting-holes-and-self-intersection.json
@@ -0,0 +1 @@
+[[[[-1211,-2262],[-2450,-1292],[-2500,-1338],[-2500,-2500],[979,-2500],[222,-1510],[-1211,-2262]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-two-intersecting-holes.json b/tests/expected/intersection-polygon-two-intersecting-holes.json
new file mode 100644
index 0000000..9b1e361
--- /dev/null
+++ b/tests/expected/intersection-polygon-two-intersecting-holes.json
@@ -0,0 +1 @@
+[[[[-1720,2500],[-2500,2500],[-2500,1282],[-1720,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-with-double-nested-holes.json b/tests/expected/intersection-polygon-with-double-nested-holes.json
new file mode 100644
index 0000000..bd60d12
--- /dev/null
+++ b/tests/expected/intersection-polygon-with-double-nested-holes.json
@@ -0,0 +1 @@
+[[[[-1351,2500],[-2500,2500],[-2500,1983],[-1351,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-with-extending-hole.json b/tests/expected/intersection-polygon-with-extending-hole.json
new file mode 100644
index 0000000..46a8216
--- /dev/null
+++ b/tests/expected/intersection-polygon-with-extending-hole.json
@@ -0,0 +1 @@
+[[[[153,-1611],[-1427,264],[1127,1031],[644,2500],[-2500,2500],[-2500,-2328],[-2258,-2500],[-1714,-2500],[153,-1611]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-with-exterior-hole.json b/tests/expected/intersection-polygon-with-exterior-hole.json
new file mode 100644
index 0000000..8f70926
--- /dev/null
+++ b/tests/expected/intersection-polygon-with-exterior-hole.json
@@ -0,0 +1 @@
+[[[[2500,2500],[-247,2500],[-152,-2332],[-50,-2500],[2500,-2500],[2500,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-with-hole-shared-edge.json b/tests/expected/intersection-polygon-with-hole-shared-edge.json
new file mode 100644
index 0000000..a8adc73
--- /dev/null
+++ b/tests/expected/intersection-polygon-with-hole-shared-edge.json
@@ -0,0 +1 @@
+[[[[-517,2129],[1285,2500],[-2500,2500],[-2500,-482],[-517,2129]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-with-hole-with-shared-point.json b/tests/expected/intersection-polygon-with-hole-with-shared-point.json
new file mode 100644
index 0000000..e7ebb15
--- /dev/null
+++ b/tests/expected/intersection-polygon-with-hole-with-shared-point.json
@@ -0,0 +1 @@
+[[[[-1657,1185],[2500,1252],[2500,2500],[-2500,2500],[-2500,-146],[-1657,1185]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-with-spike.json b/tests/expected/intersection-polygon-with-spike.json
new file mode 100644
index 0000000..8e1f135
--- /dev/null
+++ b/tests/expected/intersection-polygon-with-spike.json
@@ -0,0 +1 @@
+[[[[2500,2487],[2500,2497],[-2475,-2478],[-2471,-2493],[2500,2487]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-polygon-with-two-holes-outside-exterior-ring.json b/tests/expected/intersection-polygon-with-two-holes-outside-exterior-ring.json
new file mode 100644
index 0000000..bd60d12
--- /dev/null
+++ b/tests/expected/intersection-polygon-with-two-holes-outside-exterior-ring.json
@@ -0,0 +1 @@
+[[[[-1351,2500],[-2500,2500],[-2500,1983],[-1351,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/intersection-self-intersecting-ring-polygon.json b/tests/expected/intersection-self-intersecting-ring-polygon.json
new file mode 100644
index 0000000..a1523d6
--- /dev/null
+++ b/tests/expected/intersection-self-intersecting-ring-polygon.json
@@ -0,0 +1 @@
+[[[[2500,1061],[-324,-1745],[-2104,-430],[-947,1164],[2500,1717],[2500,2500],[-2453,2500],[-2500,2448],[-2500,-2500],[2500,-2500],[2500,1061]]]]
\ No newline at end of file
diff --git a/tests/expected/union-clockwise-polygon-clockwise-hole.json b/tests/expected/union-clockwise-polygon-clockwise-hole.json
new file mode 100644
index 0000000..25203e7
--- /dev/null
+++ b/tests/expected/union-clockwise-polygon-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]
\ No newline at end of file
diff --git a/tests/expected/union-clockwise-polygon-counter-clockwise-hole.json b/tests/expected/union-clockwise-polygon-counter-clockwise-hole.json
new file mode 100644
index 0000000..25203e7
--- /dev/null
+++ b/tests/expected/union-clockwise-polygon-counter-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]
\ No newline at end of file
diff --git a/tests/expected/union-clockwise-polygon.json b/tests/expected/union-clockwise-polygon.json
new file mode 100644
index 0000000..0977387
--- /dev/null
+++ b/tests/expected/union-clockwise-polygon.json
@@ -0,0 +1 @@
+[[[[1790,-4475],[5174,4458],[-2137,5008],[-4826,-4992],[1790,-4475]]]]
\ No newline at end of file
diff --git a/tests/expected/union-counter-clockwise-polygon-clockwise-hole.json b/tests/expected/union-counter-clockwise-polygon-clockwise-hole.json
new file mode 100644
index 0000000..25203e7
--- /dev/null
+++ b/tests/expected/union-counter-clockwise-polygon-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]
\ No newline at end of file
diff --git a/tests/expected/union-counter-clockwise-polygon-counter-clockwise-hole.json b/tests/expected/union-counter-clockwise-polygon-counter-clockwise-hole.json
new file mode 100644
index 0000000..25203e7
--- /dev/null
+++ b/tests/expected/union-counter-clockwise-polygon-counter-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]
\ No newline at end of file
diff --git a/tests/expected/union-counter-clockwise-polygon.json b/tests/expected/union-counter-clockwise-polygon.json
new file mode 100644
index 0000000..0977387
--- /dev/null
+++ b/tests/expected/union-counter-clockwise-polygon.json
@@ -0,0 +1 @@
+[[[[1790,-4475],[5174,4458],[-2137,5008],[-4826,-4992],[1790,-4475]]]]
\ No newline at end of file
diff --git a/tests/expected/union-multi-polygon-with-duplicate-polygon.json b/tests/expected/union-multi-polygon-with-duplicate-polygon.json
new file mode 100644
index 0000000..7387b8e
--- /dev/null
+++ b/tests/expected/union-multi-polygon-with-duplicate-polygon.json
@@ -0,0 +1 @@
+[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/union-multi-polygon-with-shared-edge.json b/tests/expected/union-multi-polygon-with-shared-edge.json
new file mode 100644
index 0000000..5e343eb
--- /dev/null
+++ b/tests/expected/union-multi-polygon-with-shared-edge.json
@@ -0,0 +1 @@
+[[[[1285,2500],[2053,2658],[2116,2500],[1285,2500]]],[[[-2500,-2500],[2500,-2500],[2500,1543],[4366,-3119],[4837,6264],[-5163,2658],[-4971,-3736],[-2500,-482],[-2500,-2500]],[[1285,2500],[2053,2658],[2116,2500],[1285,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/union-multi-polygon-with-spikes.json b/tests/expected/union-multi-polygon-with-spikes.json
new file mode 100644
index 0000000..5b7e73d
--- /dev/null
+++ b/tests/expected/union-multi-polygon-with-spikes.json
@@ -0,0 +1 @@
+[[[[-2462,-2500],[2500,-2500],[2500,2500],[1467,2500],[4324,6148],[1462,2500],[-2500,2500],[-2500,-2500],[-2466,-2500],[-2472,-2513],[-2462,-2500]]]]
\ No newline at end of file
diff --git a/tests/expected/union-multipolygon-both-clockwise.json b/tests/expected/union-multipolygon-both-clockwise.json
new file mode 100644
index 0000000..ee672c5
--- /dev/null
+++ b/tests/expected/union-multipolygon-both-clockwise.json
@@ -0,0 +1 @@
+[[[[4333,-1783],[2500,1224],[2500,2500],[644,2500],[-787,6847],[-5667,-76],[-2500,-2328],[-2500,-2500],[-2258,-2500],[-2040,-2655],[-1714,-2500],[903,-2500],[1453,-3153],[4333,-1783]]]]
\ No newline at end of file
diff --git a/tests/expected/union-multipolygon-both-counter-clockwise.json b/tests/expected/union-multipolygon-both-counter-clockwise.json
new file mode 100644
index 0000000..ee672c5
--- /dev/null
+++ b/tests/expected/union-multipolygon-both-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[4333,-1783],[2500,1224],[2500,2500],[644,2500],[-787,6847],[-5667,-76],[-2500,-2328],[-2500,-2500],[-2258,-2500],[-2040,-2655],[-1714,-2500],[903,-2500],[1453,-3153],[4333,-1783]]]]
\ No newline at end of file
diff --git a/tests/expected/union-multipolygon-overlap-different-orientations.json b/tests/expected/union-multipolygon-overlap-different-orientations.json
new file mode 100644
index 0000000..ee672c5
--- /dev/null
+++ b/tests/expected/union-multipolygon-overlap-different-orientations.json
@@ -0,0 +1 @@
+[[[[4333,-1783],[2500,1224],[2500,2500],[644,2500],[-787,6847],[-5667,-76],[-2500,-2328],[-2500,-2500],[-2258,-2500],[-2040,-2655],[-1714,-2500],[903,-2500],[1453,-3153],[4333,-1783]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..9e67857
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..9e67857
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-clockwise.json
new file mode 100644
index 0000000..25203e7
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-clockwise.json
@@ -0,0 +1 @@
+[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..9e67857
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..9e67857
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json
new file mode 100644
index 0000000..25203e7
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..9e67857
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..9e67857
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json
new file mode 100644
index 0000000..25203e7
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json
@@ -0,0 +1 @@
+[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..9e67857
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..9e67857
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]
\ No newline at end of file
diff --git a/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json
new file mode 100644
index 0000000..25203e7
--- /dev/null
+++ b/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]
\ No newline at end of file
diff --git a/tests/expected/union-overlapping-multi-polygon.json b/tests/expected/union-overlapping-multi-polygon.json
new file mode 100644
index 0000000..c437fb4
--- /dev/null
+++ b/tests/expected/union-overlapping-multi-polygon.json
@@ -0,0 +1 @@
+[[[[-658,2500],[-1679,2500],[-1652,2616],[-658,2500]]],[[[-3017,2775],[-4598,-6334],[-708,-5864],[114,-2500],[2500,-2500],[2500,-33],[5366,94],[5402,3666],[-1419,3635],[-1652,2616],[-3017,2775]],[[-658,2500],[-1679,2500],[-1652,2616],[-658,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-covered-with-hole.json b/tests/expected/union-polygon-covered-with-hole.json
new file mode 100644
index 0000000..7387b8e
--- /dev/null
+++ b/tests/expected/union-polygon-covered-with-hole.json
@@ -0,0 +1 @@
+[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-no-interior.json b/tests/expected/union-polygon-no-interior.json
new file mode 100644
index 0000000..5096cf4
--- /dev/null
+++ b/tests/expected/union-polygon-no-interior.json
@@ -0,0 +1 @@
+[[[[-5000,-5000],[0,-5000],[-5000,0],[-5000,-5000]]]]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-two-intersecting-holes-and-self-intersection.json b/tests/expected/union-polygon-two-intersecting-holes-and-self-intersection.json
new file mode 100644
index 0000000..1d79632
--- /dev/null
+++ b/tests/expected/union-polygon-two-intersecting-holes-and-self-intersection.json
@@ -0,0 +1 @@
+[[[[-1720,2500],[-392,4575],[228,2500],[-1720,2500]]],[[[979,-2500],[1300,-2500],[1071,-2620],[979,-2500]]],[[[2500,-1965],[2500,-1872],[2953,-1635],[2500,-816],[2500,2500],[1669,2500],[4591,5190],[-5096,5738],[-1903,2500],[-2500,2500],[-2500,1282],[-3580,-406],[-2500,-1252],[-2500,-1338],[-5235,-3856],[4765,-4262],[2500,-1965]],[[-1720,2500],[-392,4575],[228,2500],[-1720,2500]],[[979,-2500],[1300,-2500],[1071,-2620],[979,-2500]]]]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-two-intersecting-holes.json b/tests/expected/union-polygon-two-intersecting-holes.json
new file mode 100644
index 0000000..9d66037
--- /dev/null
+++ b/tests/expected/union-polygon-two-intersecting-holes.json
@@ -0,0 +1 @@
+[[[[-1720,2500],[-392,4575],[228,2500],[-1720,2500]]],[[[-3580,-406],[-2500,1282],[-2500,-1252],[-3580,-406]]],[[[2500,-816],[2953,-1635],[2500,-1872],[2500,-816]]],[[[979,-2500],[1300,-2500],[1071,-2620],[979,-2500]]],[[[4591,5190],[-5096,5738],[-5235,-3856],[4765,-4262],[4591,5190]],[[-1720,2500],[-392,4575],[228,2500],[-1720,2500]],[[-3580,-406],[-2500,1282],[-2500,-1252],[-3580,-406]],[[2500,-816],[2953,-1635],[2500,-1872],[2500,-816]],[[979,-2500],[1300,-2500],[1071,-2620],[979,-2500]]]]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-with-double-nested-holes.json b/tests/expected/union-polygon-with-double-nested-holes.json
new file mode 100644
index 0000000..daa7d66
--- /dev/null
+++ b/tests/expected/union-polygon-with-double-nested-holes.json
@@ -0,0 +1 @@
+[[[[-1351,2500],[1940,3982],[2283,2500],[-1351,2500]]],[[[-3376,1588],[-2500,1983],[-2500,-1710],[-3376,1588]]],[[[2500,1567],[3261,-1720],[2500,-2027],[2500,1567]]],[[[-2291,-2500],[1330,-2500],[-1940,-3821],[-2291,-2500]]],[[[5129,-5027],[5043,4787],[-4871,4897],[-4871,-5103],[5129,-5027]],[[-1351,2500],[1940,3982],[2283,2500],[-1351,2500]],[[-3376,1588],[-2500,1983],[-2500,-1710],[-3376,1588]],[[2500,1567],[3261,-1720],[2500,-2027],[2500,1567]],[[-2291,-2500],[1330,-2500],[-1940,-3821 [...]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-with-extending-hole.json b/tests/expected/union-polygon-with-extending-hole.json
new file mode 100644
index 0000000..ee672c5
--- /dev/null
+++ b/tests/expected/union-polygon-with-extending-hole.json
@@ -0,0 +1 @@
+[[[[4333,-1783],[2500,1224],[2500,2500],[644,2500],[-787,6847],[-5667,-76],[-2500,-2328],[-2500,-2500],[-2258,-2500],[-2040,-2655],[-1714,-2500],[903,-2500],[1453,-3153],[4333,-1783]]]]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-with-exterior-hole.json b/tests/expected/union-polygon-with-exterior-hole.json
new file mode 100644
index 0000000..a95666e
--- /dev/null
+++ b/tests/expected/union-polygon-with-exterior-hole.json
@@ -0,0 +1 @@
+[[[[7071,1704],[-265,3394],[-247,2500],[-2500,2500],[-2500,1535],[-2929,1209],[-2904,-387],[-2500,-3],[-2500,-2500],[-50,-2500],[2448,-6606],[7071,1704]]]]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-with-hole-shared-edge.json b/tests/expected/union-polygon-with-hole-shared-edge.json
new file mode 100644
index 0000000..5e343eb
--- /dev/null
+++ b/tests/expected/union-polygon-with-hole-shared-edge.json
@@ -0,0 +1 @@
+[[[[1285,2500],[2053,2658],[2116,2500],[1285,2500]]],[[[-2500,-2500],[2500,-2500],[2500,1543],[4366,-3119],[4837,6264],[-5163,2658],[-4971,-3736],[-2500,-482],[-2500,-2500]],[[1285,2500],[2053,2658],[2116,2500],[1285,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-with-hole-with-shared-point.json b/tests/expected/union-polygon-with-hole-with-shared-point.json
new file mode 100644
index 0000000..3ed36f2
--- /dev/null
+++ b/tests/expected/union-polygon-with-hole-with-shared-point.json
@@ -0,0 +1 @@
+[[[[2500,1252],[2761,1256],[2500,588],[2500,1252]]],[[[-3181,-1223],[-2500,-146],[-2500,-1308],[-3181,-1223]]],[[[6665,2813],[-3335,4503],[-3181,-1223],[-2500,-2043],[-2500,-2500],[-2122,-2500],[364,-5497],[6665,2813]],[[2500,1252],[2761,1256],[2500,588],[2500,1252]],[[-3181,-1223],[-2500,-146],[-2500,-1308],[-3181,-1223]]]]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-with-spike.json b/tests/expected/union-polygon-with-spike.json
new file mode 100644
index 0000000..7c27597
--- /dev/null
+++ b/tests/expected/union-polygon-with-spike.json
@@ -0,0 +1 @@
+[[[[-2496,-2500],[2500,-2500],[2500,2487],[7477,7473],[2500,2497],[2500,2500],[-2500,2500],[-2500,-2484],[-2523,-2527],[-2496,-2500]]]]
\ No newline at end of file
diff --git a/tests/expected/union-polygon-with-two-holes-outside-exterior-ring.json b/tests/expected/union-polygon-with-two-holes-outside-exterior-ring.json
new file mode 100644
index 0000000..daa7d66
--- /dev/null
+++ b/tests/expected/union-polygon-with-two-holes-outside-exterior-ring.json
@@ -0,0 +1 @@
+[[[[-1351,2500],[1940,3982],[2283,2500],[-1351,2500]]],[[[-3376,1588],[-2500,1983],[-2500,-1710],[-3376,1588]]],[[[2500,1567],[3261,-1720],[2500,-2027],[2500,1567]]],[[[-2291,-2500],[1330,-2500],[-1940,-3821],[-2291,-2500]]],[[[5129,-5027],[5043,4787],[-4871,4897],[-4871,-5103],[5129,-5027]],[[-1351,2500],[1940,3982],[2283,2500],[-1351,2500]],[[-3376,1588],[-2500,1983],[-2500,-1710],[-3376,1588]],[[2500,1567],[3261,-1720],[2500,-2027],[2500,1567]],[[-2291,-2500],[1330,-2500],[-1940,-3821 [...]
\ No newline at end of file
diff --git a/tests/expected/union-self-intersecting-ring-polygon.json b/tests/expected/union-self-intersecting-ring-polygon.json
new file mode 100644
index 0000000..d891e9c
--- /dev/null
+++ b/tests/expected/union-self-intersecting-ring-polygon.json
@@ -0,0 +1 @@
+[[[[2500,1061],[2500,1717],[3288,1843],[2500,1061]]],[[[601,5883],[-2453,2500],[-2500,2500],[-2500,2448],[-6285,-1745],[-1232,-4117],[3715,-2696],[3288,1843],[601,5883]],[[2500,1061],[2500,1717],[3288,1843],[2500,1061]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-clockwise-polygon-clockwise-hole.json b/tests/expected/x_or-clockwise-polygon-clockwise-hole.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/x_or-clockwise-polygon-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-clockwise-polygon-counter-clockwise-hole.json b/tests/expected/x_or-clockwise-polygon-counter-clockwise-hole.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/x_or-clockwise-polygon-counter-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-clockwise-polygon.json b/tests/expected/x_or-clockwise-polygon.json
new file mode 100644
index 0000000..25aaeda
--- /dev/null
+++ b/tests/expected/x_or-clockwise-polygon.json
@@ -0,0 +1 @@
+[[[[-2500,-2500],[-2500,2500],[2500,2500],[2500,-2500],[-2500,-2500]]],[[[1790,-4475],[5174,4458],[-2137,5008],[-4826,-4992],[1790,-4475]],[[-2500,-2500],[-2500,2500],[2500,2500],[2500,-2500],[-2500,-2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-counter-clockwise-polygon-clockwise-hole.json b/tests/expected/x_or-counter-clockwise-polygon-clockwise-hole.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/x_or-counter-clockwise-polygon-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-counter-clockwise-polygon-counter-clockwise-hole.json b/tests/expected/x_or-counter-clockwise-polygon-counter-clockwise-hole.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/x_or-counter-clockwise-polygon-counter-clockwise-hole.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-counter-clockwise-polygon.json b/tests/expected/x_or-counter-clockwise-polygon.json
new file mode 100644
index 0000000..25aaeda
--- /dev/null
+++ b/tests/expected/x_or-counter-clockwise-polygon.json
@@ -0,0 +1 @@
+[[[[-2500,-2500],[-2500,2500],[2500,2500],[2500,-2500],[-2500,-2500]]],[[[1790,-4475],[5174,4458],[-2137,5008],[-4826,-4992],[1790,-4475]],[[-2500,-2500],[-2500,2500],[2500,2500],[2500,-2500],[-2500,-2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-multi-polygon-with-duplicate-polygon.json b/tests/expected/x_or-multi-polygon-with-duplicate-polygon.json
new file mode 100644
index 0000000..7387b8e
--- /dev/null
+++ b/tests/expected/x_or-multi-polygon-with-duplicate-polygon.json
@@ -0,0 +1 @@
+[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-multi-polygon-with-shared-edge.json b/tests/expected/x_or-multi-polygon-with-shared-edge.json
new file mode 100644
index 0000000..b62ec04
--- /dev/null
+++ b/tests/expected/x_or-multi-polygon-with-shared-edge.json
@@ -0,0 +1 @@
+[[[[2053,2658],[2116,2500],[2500,2500],[2500,1543],[4366,-3119],[4837,6264],[-5163,2658],[-4971,-3736],[-2500,-482],[-2500,2500],[1285,2500],[2053,2658]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-multi-polygon-with-spikes.json b/tests/expected/x_or-multi-polygon-with-spikes.json
new file mode 100644
index 0000000..e32b46b
--- /dev/null
+++ b/tests/expected/x_or-multi-polygon-with-spikes.json
@@ -0,0 +1 @@
+[[[[-2443,-2448],[-2439,-2470],[1462,2500],[-2500,2500],[-2500,-2500],[-2466,-2500],[-2443,-2448]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-multipolygon-both-clockwise.json b/tests/expected/x_or-multipolygon-both-clockwise.json
new file mode 100644
index 0000000..4e588cd
--- /dev/null
+++ b/tests/expected/x_or-multipolygon-both-clockwise.json
@@ -0,0 +1 @@
+[[[[-2040,-2655],[-1714,-2500],[-2258,-2500],[-2040,-2655]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-multipolygon-both-counter-clockwise.json b/tests/expected/x_or-multipolygon-both-counter-clockwise.json
new file mode 100644
index 0000000..4e588cd
--- /dev/null
+++ b/tests/expected/x_or-multipolygon-both-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-2040,-2655],[-1714,-2500],[-2258,-2500],[-2040,-2655]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-multipolygon-overlap-different-orientations.json b/tests/expected/x_or-multipolygon-overlap-different-orientations.json
new file mode 100644
index 0000000..4e588cd
--- /dev/null
+++ b/tests/expected/x_or-multipolygon-overlap-different-orientations.json
@@ -0,0 +1 @@
+[[[[-2040,-2655],[-1714,-2500],[-2258,-2500],[-2040,-2655]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-clockwise.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-clockwise.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
new file mode 100644
index 0000000..ed9f280
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json
new file mode 100644
index 0000000..071ff0a
--- /dev/null
+++ b/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json
@@ -0,0 +1 @@
+[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-overlapping-multi-polygon.json b/tests/expected/x_or-overlapping-multi-polygon.json
new file mode 100644
index 0000000..e49d5fd
--- /dev/null
+++ b/tests/expected/x_or-overlapping-multi-polygon.json
@@ -0,0 +1 @@
+[[[[5366,94],[5402,3666],[-1419,3635],[-1652,2616],[-658,2500],[2500,2500],[2500,-33],[5366,94]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-covered-with-hole.json b/tests/expected/x_or-polygon-covered-with-hole.json
new file mode 100644
index 0000000..7387b8e
--- /dev/null
+++ b/tests/expected/x_or-polygon-covered-with-hole.json
@@ -0,0 +1 @@
+[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-no-interior.json b/tests/expected/x_or-polygon-no-interior.json
new file mode 100644
index 0000000..5096cf4
--- /dev/null
+++ b/tests/expected/x_or-polygon-no-interior.json
@@ -0,0 +1 @@
+[[[[-5000,-5000],[0,-5000],[-5000,0],[-5000,-5000]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-two-intersecting-holes-and-self-intersection.json b/tests/expected/x_or-polygon-two-intersecting-holes-and-self-intersection.json
new file mode 100644
index 0000000..20a1f1a
--- /dev/null
+++ b/tests/expected/x_or-polygon-two-intersecting-holes-and-self-intersection.json
@@ -0,0 +1 @@
+[[[[858,390],[345,221],[1154,-600],[858,390]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-two-intersecting-holes.json b/tests/expected/x_or-polygon-two-intersecting-holes.json
new file mode 100644
index 0000000..d3aa11f
--- /dev/null
+++ b/tests/expected/x_or-polygon-two-intersecting-holes.json
@@ -0,0 +1 @@
+[[[[-2500,1282],[-2500,-1252],[-1211,-2262],[222,-1510],[-810,-161],[858,390],[228,2500],[-1720,2500],[-2500,1282]]],[[[2500,-1872],[2500,-816],[1681,662],[858,390],[1263,-964],[222,-1510],[979,-2500],[1300,-2500],[2500,-1872]]],[[[1071,-2620],[979,-2500],[-2500,-2500],[-2500,-1252],[-3580,-406],[-2500,1282],[-2500,2500],[-1720,2500],[-392,4575],[228,2500],[2500,2500],[2500,-816],[2953,-1635],[2500,-1872],[2500,-2500],[1300,-2500],[1071,-2620]]],[[[4591,5190],[-5096,5738],[-5235,-3856],[ [...]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-with-double-nested-holes.json b/tests/expected/x_or-polygon-with-double-nested-holes.json
new file mode 100644
index 0000000..1ddf266
--- /dev/null
+++ b/tests/expected/x_or-polygon-with-double-nested-holes.json
@@ -0,0 +1 @@
+[[[[2500,-2027],[2500,1567],[2283,2500],[-1351,2500],[-2500,1983],[-2500,-1710],[-2291,-2500],[1330,-2500],[2500,-2027]]],[[[-1940,-3821],[-2291,-2500],[-2500,-2500],[-2500,-1710],[-3376,1588],[-2500,1983],[-2500,2500],[-1351,2500],[1940,3982],[2283,2500],[2500,2500],[2500,1567],[3261,-1720],[2500,-2027],[2500,-2500],[1330,-2500],[-1940,-3821]]],[[[-1796,996],[819,1219],[1422,-713],[-761,-1085],[-1796,996]]],[[[5129,-5027],[5043,4787],[-4871,4897],[-4871,-5103],[5129,-5027]],[[-1940,-382 [...]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-with-extending-hole.json b/tests/expected/x_or-polygon-with-extending-hole.json
new file mode 100644
index 0000000..4e588cd
--- /dev/null
+++ b/tests/expected/x_or-polygon-with-extending-hole.json
@@ -0,0 +1 @@
+[[[[-2040,-2655],[-1714,-2500],[-2258,-2500],[-2040,-2655]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-with-exterior-hole.json b/tests/expected/x_or-polygon-with-exterior-hole.json
new file mode 100644
index 0000000..af6b1ed
--- /dev/null
+++ b/tests/expected/x_or-polygon-with-exterior-hole.json
@@ -0,0 +1 @@
+[[[[-152,-2332],[-247,2500],[-2500,2500],[-2500,1535],[-1672,2163],[-1597,855],[-2500,-3],[-2500,-2500],[-50,-2500],[-152,-2332]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-with-hole-shared-edge.json b/tests/expected/x_or-polygon-with-hole-shared-edge.json
new file mode 100644
index 0000000..b62ec04
--- /dev/null
+++ b/tests/expected/x_or-polygon-with-hole-shared-edge.json
@@ -0,0 +1 @@
+[[[[2053,2658],[2116,2500],[2500,2500],[2500,1543],[4366,-3119],[4837,6264],[-5163,2658],[-4971,-3736],[-2500,-482],[-2500,2500],[1285,2500],[2053,2658]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-with-hole-with-shared-point.json b/tests/expected/x_or-polygon-with-hole-with-shared-point.json
new file mode 100644
index 0000000..f77ed90
--- /dev/null
+++ b/tests/expected/x_or-polygon-with-hole-with-shared-point.json
@@ -0,0 +1 @@
+[[[[-2500,-1308],[-3181,-1223],[-2500,-2043],[-2500,-1308]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-with-spike.json b/tests/expected/x_or-polygon-with-spike.json
new file mode 100644
index 0000000..c33b2a4
--- /dev/null
+++ b/tests/expected/x_or-polygon-with-spike.json
@@ -0,0 +1 @@
+[[[[-2482,-2452],[-2475,-2478],[2500,2497],[2500,2500],[-2500,2500],[-2500,-2484],[-2482,-2452]]]]
\ No newline at end of file
diff --git a/tests/expected/x_or-polygon-with-two-holes-outside-exterior-ring.json b/tests/expected/x_or-polygon-with-two-holes-outside-exterior-ring.json
new file mode 100644
index 0000000..1ddf266
--- /dev/null
+++ b/tests/expected/x_or-polygon-with-two-holes-outside-exterior-ring.json
@@ -0,0 +1 @@
+[[[[2500,-2027],[2500,1567],[2283,2500],[-1351,2500],[-2500,1983],[-2500,-1710],[-2291,-2500],[1330,-2500],[2500,-2027]]],[[[-1940,-3821],[-2291,-2500],[-2500,-2500],[-2500,-1710],[-3376,1588],[-2500,1983],[-2500,2500],[-1351,2500],[1940,3982],[2283,2500],[2500,2500],[2500,1567],[3261,-1720],[2500,-2027],[2500,-2500],[1330,-2500],[-1940,-3821]]],[[[-1796,996],[819,1219],[1422,-713],[-761,-1085],[-1796,996]]],[[[5129,-5027],[5043,4787],[-4871,4897],[-4871,-5103],[5129,-5027]],[[-1940,-382 [...]
\ No newline at end of file
diff --git a/tests/expected/x_or-self-intersecting-ring-polygon.json b/tests/expected/x_or-self-intersecting-ring-polygon.json
new file mode 100644
index 0000000..5a456b6
--- /dev/null
+++ b/tests/expected/x_or-self-intersecting-ring-polygon.json
@@ -0,0 +1 @@
+[[[[-947,1164],[-2104,-430],[-324,-1745],[2500,1061],[2500,1717],[-947,1164]]]]
\ No newline at end of file
diff --git a/tests/fixture-tester.cpp b/tests/fixture-tester.cpp
new file mode 100644
index 0000000..02036d7
--- /dev/null
+++ b/tests/fixture-tester.cpp
@@ -0,0 +1,239 @@
+#include "util/boost_geometry_adapters.hpp"
+#include <cstdio>
+#include <iostream>
+#include <mapbox/geometry/polygon.hpp>
+#include <mapbox/geometry/wagyu/wagyu.hpp>
+#include <ostream>
+
+#include "rapidjson/writer.h"
+#include <rapidjson/document.h>
+#include <rapidjson/filereadstream.h>
+#include <rapidjson/filewritestream.h>
+
+using namespace rapidjson;
+using namespace mapbox::geometry::wagyu;
+using value_type = std::int64_t;
+
+struct Options {
+ std::size_t iterations = 1;
+ clip_type operation = clip_type_union;
+ fill_type fill = fill_type_even_odd;
+ char* subject_file = nullptr;
+ char* clip_file = nullptr;
+} options;
+
+void log_ring(mapbox::geometry::polygon<std::int64_t> const& p) {
+ bool first = true;
+ std::clog << "[";
+ for (auto const& r : p) {
+ if (first) {
+ std::clog << "[";
+ first = false;
+ } else {
+ std::clog << ",[";
+ }
+ bool first2 = true;
+ for (auto const& pt : r) {
+ if (first2) {
+ std::clog << "[";
+ first2 = false;
+ } else {
+ std::clog << ",[";
+ }
+ std::clog << pt.x << "," << pt.y << "]";
+ }
+ std::clog << "]";
+ }
+ std::clog << "]" << std::endl;
+}
+
+void log_ring(mapbox::geometry::multi_polygon<std::int64_t> const& mp) {
+ bool first_p = true;
+ std::clog << "[";
+ for (auto const& p : mp) {
+ bool first = true;
+ if (first_p) {
+ std::clog << "[";
+ first_p = false;
+ } else {
+ std::clog << ",[";
+ }
+ for (auto const& r : p) {
+ if (first) {
+ std::clog << "[";
+ first = false;
+ } else {
+ std::clog << ",[";
+ }
+ bool first2 = true;
+ for (auto const& pt : r) {
+ if (first2) {
+ std::clog << "[";
+ first2 = false;
+ } else {
+ std::clog << ",[";
+ }
+ std::clog << pt.x << "," << pt.y << "]";
+ }
+ std::clog << "]";
+ }
+ std::clog << "]";
+ }
+ std::clog << "]" << std::endl;
+}
+
+mapbox::geometry::polygon<value_type> parse_file(const char* file_path) {
+ // todo safety checks opening files
+ FILE* file = fopen(file_path, "r");
+ char read_buffer[65536];
+ FileReadStream in_stream(file, read_buffer, sizeof(read_buffer));
+ Document document;
+ document.ParseStream<0, UTF8<>, FileReadStream>(in_stream);
+
+ if (!document.IsArray()) {
+ throw std::runtime_error(("Input file (" + std::string(file_path) + ") is not valid json"));
+ }
+ // todo catch parsing errors
+ mapbox::geometry::polygon<value_type> poly;
+ for (SizeType i = 0; i < document.Size(); ++i) {
+ mapbox::geometry::linear_ring<value_type> lr;
+
+ if (!document[i].IsArray()) {
+ throw std::runtime_error("A ring (in " + std::string(file_path) +
+ ") is not a valid json array");
+ }
+ for (SizeType j = 0; j < document[i].Size(); ++j) {
+ lr.push_back({ document[i][j][0].GetInt(), document[i][j][1].GetInt() });
+ }
+ poly.emplace_back(lr);
+ }
+ fclose(file);
+ return poly;
+}
+
+void polys_to_json(Document& output, std::vector<mapbox::geometry::polygon<value_type>>& solution) {
+ output.SetArray();
+ Document::AllocatorType& allocator = output.GetAllocator();
+ output.Reserve(static_cast<unsigned>(solution.size()), allocator);
+
+ // Polygons
+ for (unsigned p = 0; p < solution.size(); ++p) {
+ output.PushBack(Value().SetArray(), allocator);
+ output[p].Reserve(static_cast<unsigned>(solution[p].size()), allocator);
+
+ // Rings
+ for (unsigned r = 0; r < solution[p].size(); ++r) {
+ output[p].PushBack(Value().SetArray(), allocator);
+ output[p][r].Reserve(static_cast<unsigned>(solution[p][r].size()), allocator);
+
+ // Coordinates
+ for (auto coord : solution[p][r]) {
+ Value cvalue;
+ cvalue.SetArray();
+ cvalue.PushBack(Value().SetInt(static_cast<int>(coord.x)), allocator);
+ cvalue.PushBack(Value().SetInt(static_cast<int>(coord.y)), allocator);
+ output[p][r].PushBack(cvalue, allocator);
+ }
+ }
+ }
+}
+
+void parse_options(int argc, char* const argv[]) {
+ for (int i = 1; i < argc; ++i) {
+
+ if (strcmp(argv[i], "-t") == 0) {
+ std::string type = argv[++i];
+ if (type.compare("union") == 0) {
+ options.operation = clip_type_union;
+ } else if (type.compare("intersection") == 0) {
+ options.operation = clip_type_intersection;
+ } else if (type.compare("difference") == 0) {
+ options.operation = clip_type_difference;
+ } else if (type.compare("x_or") == 0) {
+ options.operation = clip_type_x_or;
+ }
+ } else if (strcmp(argv[i], "-f") == 0) {
+ std::string type = argv[++i];
+ if (type.compare("even_odd") == 0) {
+ options.fill = fill_type_even_odd;
+ } else if (type.compare("non_zero") == 0) {
+ options.fill = fill_type_non_zero;
+ } else if (type.compare("positive") == 0) {
+ options.fill = fill_type_positive;
+ } else if (type.compare("negative") == 0) {
+ options.fill = fill_type_negative;
+ }
+ } else if (strcmp(argv[i], "-i") == 0) {
+ std::string iters = argv[++i];
+ options.iterations = static_cast<std::size_t>(std::stoul(iters));
+ } else {
+ // If we didn't catch this argument as a flag or a flag value,
+ // set the input files
+ if (options.subject_file == nullptr) {
+ options.subject_file = argv[i];
+ } else {
+ options.clip_file = argv[i];
+ }
+ }
+ }
+}
+
+int main(int argc, char* const argv[]) {
+ if (argc < 3) {
+ std::cout << "Error: too few parameters\n" << std::endl;
+ std::cout << "Usage:" << std::endl;
+ std::cout << " ./fixture-test ./path/to/subject.json ./path/to/object.json\n" << std::endl;
+ std::cout << "Options:" << std::endl;
+ std::cout << " -t type of operation (default: union)\n" << std::endl;
+ std::cout << " -f fill_type (default: even_odd)\n" << std::endl;
+ return -1;
+ }
+ parse_options(argc, argv);
+ auto poly_subject = parse_file(options.subject_file);
+ mapbox::geometry::polygon<value_type> poly_clip;
+ mapbox::geometry::multi_polygon<value_type> solution;
+ if (options.clip_file != nullptr) {
+ poly_clip = parse_file(options.clip_file);
+ }
+ while (options.iterations > 0) {
+ options.iterations--;
+ solution.clear();
+
+ wagyu<value_type> clipper;
+ clipper.add_polygon(poly_subject, polygon_type_subject);
+ if (!poly_clip.empty()) {
+ clipper.add_polygon(poly_clip, polygon_type_clip);
+ }
+ clipper.execute(options.operation, solution, options.fill, fill_type_even_odd);
+ }
+
+ Document output;
+ polys_to_json(output, solution);
+ for (auto const& p : solution) {
+ std::string message;
+ if (!boost::geometry::is_valid(p, message)) {
+ std::clog << std::endl;
+ std::clog << "Error: geometry not valid" << std::endl;
+ std::clog << message << std::endl;
+ log_ring(p);
+ return -1;
+ }
+ }
+ /*
+ * uncomment once https://svn.boost.org/trac/boost/ticket/12503 is resolved
+ std::string message;
+ if (!boost::geometry::is_valid(solution, message)) {
+ std::clog << std::endl;
+ std::clog << "Error: multi geometry not valid" << std::endl;
+ std::clog << message << std::endl;
+ log_ring(solution);
+ return -1;
+ }
+ */
+ char write_buffer[65536];
+ FileWriteStream out_stream(stdout, write_buffer, sizeof(write_buffer));
+ Writer<FileWriteStream> writer(out_stream);
+
+ output.Accept(writer);
+ std::cout << std::endl;
+}
diff --git a/tests/fixtures/clip-clockwise-square.json b/tests/fixtures/clip-clockwise-square.json
new file mode 100644
index 0000000..dbf47c1
--- /dev/null
+++ b/tests/fixtures/clip-clockwise-square.json
@@ -0,0 +1,9 @@
+[
+ [
+ [-2500,-2500],
+ [-2500,2500],
+ [2500,2500],
+ [2500,-2500],
+ [-2500,-2500]
+ ]
+]
diff --git a/tests/fixtures/clockwise-triangle.json b/tests/fixtures/clockwise-triangle.json
new file mode 100644
index 0000000..7c219ac
--- /dev/null
+++ b/tests/fixtures/clockwise-triangle.json
@@ -0,0 +1,8 @@
+[
+ [
+ [0,0],
+ [5,7],
+ [7,5],
+ [0,0]
+ ]
+]
diff --git a/tests/fuzzer.cpp b/tests/fuzzer.cpp
new file mode 100644
index 0000000..2c57828
--- /dev/null
+++ b/tests/fuzzer.cpp
@@ -0,0 +1,242 @@
+#include "util/boost_geometry_adapters.hpp"
+#include <mapbox/geometry/multi_polygon.hpp>
+#include <mapbox/geometry/polygon.hpp>
+#include <mapbox/geometry/wagyu/wagyu.hpp>
+
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include <signal.h>
+#include <time.h>
+#include <vector>
+
+static int s_int = 0;
+
+static void signal_handler(int value) {
+ s_int = value;
+}
+
+static void catch_signals() {
+ struct sigaction action;
+ action.sa_handler = signal_handler;
+ action.sa_flags = 0;
+ sigemptyset(&action.sa_mask);
+ sigaction(SIGINT, &action, NULL);
+ sigaction(SIGTERM, &action, NULL);
+}
+
+void log_ring(mapbox::geometry::polygon<std::int64_t> const& p) {
+ bool first = true;
+ std::clog << "[";
+ for (auto const& r : p) {
+ if (first) {
+ std::clog << "[";
+ first = false;
+ } else {
+ std::clog << ",[";
+ }
+ bool first2 = true;
+ for (auto const& pt : r) {
+ if (first2) {
+ std::clog << "[";
+ first2 = false;
+ } else {
+ std::clog << ",[";
+ }
+ std::clog << pt.x << "," << pt.y << "]";
+ }
+ std::clog << "]";
+ }
+ std::clog << "]" << std::endl;
+}
+
+void log_ring(mapbox::geometry::multi_polygon<std::int64_t> const& mp) {
+ bool first_p = true;
+ std::clog << "[";
+ for (auto const& p : mp) {
+ bool first = true;
+ if (first_p) {
+ std::clog << "[";
+ first_p = false;
+ } else {
+ std::clog << ",[";
+ }
+ for (auto const& r : p) {
+ if (first) {
+ std::clog << "[";
+ first = false;
+ } else {
+ std::clog << ",[";
+ }
+ bool first2 = true;
+ for (auto const& pt : r) {
+ if (first2) {
+ std::clog << "[";
+ first2 = false;
+ } else {
+ std::clog << ",[";
+ }
+ std::clog << pt.x << "," << pt.y << "]";
+ }
+ std::clog << "]";
+ }
+ std::clog << "]";
+ }
+ std::clog << "]" << std::endl;
+}
+
+void create_test(mapbox::geometry::polygon<std::int64_t> const& polygon,
+ unsigned seed,
+ size_t iteration) {
+ std::string fname = "tests/geometry-test-data/input-polyjson/fuzzer-" + std::to_string(seed) +
+ "-" + std::to_string(iteration) + ".json";
+ std::clog << "Creating " << fname << "\n";
+ std::ofstream out;
+ out.open(fname);
+
+ out << "[";
+
+ for (size_t i = 0; i < polygon.size(); i++) {
+ if (i != 0) {
+ out << ",";
+ }
+
+ out << "[";
+
+ for (size_t j = 0; j < polygon[i].size(); j++) {
+ if (j != 0) {
+ out << ",";
+ }
+
+ out << "[" << polygon[i][j].x << "," << polygon[i][j].y << "]";
+ }
+
+ out << "]";
+ }
+
+ out << "]";
+
+ out.close();
+}
+
+void print_clip_type(mapbox::geometry::wagyu::clip_type ct) {
+ switch (ct) {
+ default:
+ case mapbox::geometry::wagyu::clip_type_union:
+ std::clog << "Union Clip Type" << std::endl;
+ break;
+ case mapbox::geometry::wagyu::clip_type_intersection:
+ std::clog << "Intersection Clip Type" << std::endl;
+ break;
+ case mapbox::geometry::wagyu::clip_type_difference:
+ std::clog << "Difference Clip Type" << std::endl;
+ break;
+ case mapbox::geometry::wagyu::clip_type_x_or:
+ std::clog << "X OR Clip Type" << std::endl;
+ break;
+ }
+}
+
+void print_fill_type(mapbox::geometry::wagyu::fill_type ft) {
+ switch (ft) {
+ default:
+ case mapbox::geometry::wagyu::fill_type_even_odd:
+ std::clog << "Even Odd Fill Type" << std::endl;
+ break;
+ case mapbox::geometry::wagyu::fill_type_non_zero:
+ std::clog << "Non Zero Fill Type" << std::endl;
+ break;
+ case mapbox::geometry::wagyu::fill_type_positive:
+ std::clog << "Positive Fill Type" << std::endl;
+ break;
+ case mapbox::geometry::wagyu::fill_type_negative:
+ std::clog << "Negative Fill Type" << std::endl;
+ break;
+ }
+}
+
+int main() {
+ catch_signals();
+ unsigned seed = static_cast<unsigned>(time(0));
+ std::size_t count = 0;
+ srand(seed);
+
+ std::clog << std::endl;
+ for (size_t iteration = 0;; iteration++) {
+ std::size_t len = std::rand() % 50 + 3;
+
+ for (auto clip_type : { mapbox::geometry::wagyu::clip_type_union,
+ mapbox::geometry::wagyu::clip_type_intersection,
+ mapbox::geometry::wagyu::clip_type_difference,
+ mapbox::geometry::wagyu::clip_type_x_or }) {
+ for (auto fill_type : { mapbox::geometry::wagyu::fill_type_even_odd,
+ mapbox::geometry::wagyu::fill_type_non_zero,
+ mapbox::geometry::wagyu::fill_type_positive,
+ mapbox::geometry::wagyu::fill_type_negative }) {
+
+ mapbox::geometry::wagyu::wagyu<std::int64_t> clipper;
+ mapbox::geometry::polygon<std::int64_t> polygon;
+
+ std::size_t num_rings = 1;
+ // num_rings += std::rand() % 5;
+ // std::clog << "rings: " << num_rings << std::endl;
+ // std::clog << "len: " << len << std::endl;
+ while (num_rings > 0) {
+ mapbox::geometry::linear_ring<std::int64_t> ring;
+ for (std::size_t i = 0; i < len; ++i) {
+ std::int64_t x = std::rand() % 50;
+ std::int64_t y = std::rand() % 50;
+
+ ring.push_back({ x, y });
+ }
+ polygon.emplace_back(ring);
+ --num_rings;
+ }
+ ++count;
+ std::clog << "\r Number of Tests: " << count << std::flush;
+ mapbox::geometry::multi_polygon<std::int64_t> solution;
+ try {
+ clipper.add_polygon(polygon, mapbox::geometry::wagyu::polygon_type_subject);
+ clipper.execute(clip_type, solution, fill_type,
+ mapbox::geometry::wagyu::fill_type_even_odd);
+ } catch (std::exception const& ex) {
+ create_test(polygon, seed, iteration);
+ std::clog << std::endl;
+ std::clog << ex.what() << std::endl;
+ return -1;
+ }
+
+ for (auto const& p : solution) {
+ std::string message;
+ if (!boost::geometry::is_valid(p, message)) {
+ std::clog << std::endl;
+ std::clog << message << std::endl;
+ print_clip_type(clip_type);
+ print_fill_type(fill_type);
+ log_ring(p);
+ create_test(polygon, seed, iteration);
+ return -1;
+ }
+ }
+ /*
+ * uncomment once https://svn.boost.org/trac/boost/ticket/12503 is resolved
+ std::string message;
+ if (!boost::geometry::is_valid(solution, message)) {
+ std::clog << std::endl;
+ std::clog << "Multipolygon failure case:" << std::endl;
+ std::clog << message << std::endl;
+ print_clip_type(clip_type);
+ print_fill_type(fill_type);
+ log_ring(solution);
+ create_test(polygon, seed, iteration);
+ return -1;
+ }
+ */
+ if (s_int) {
+ return 0;
+ }
+ }
+ }
+ }
+}
diff --git a/tests/quick_clip_profile.cpp b/tests/quick_clip_profile.cpp
new file mode 100644
index 0000000..0c7fa64
--- /dev/null
+++ b/tests/quick_clip_profile.cpp
@@ -0,0 +1,51 @@
+#include <iostream>
+#include <mapbox/geometry/wagyu/quick_clip.hpp>
+#include <mapbox/geometry/wagyu/wagyu.hpp>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+using namespace mapbox::geometry::wagyu;
+using T = std::int64_t;
+
+void check(int pass) {
+ srand(0);
+ clock_t before = clock();
+
+ for (size_t i = 0; i < 10000; i++) {
+ mapbox::geometry::point<T> p1 = { 100, 100 };
+ mapbox::geometry::point<T> p2 = { 200, 200 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ size_t n = rand() % 500 + 4;
+ for (size_t j = 0; j < n; j++) {
+ lr.push_back(mapbox::geometry::point<T>(rand() % 300, rand() % 300));
+ }
+ lr.push_back(lr[0]);
+
+ if (pass == 0) {
+ optional_linear_ring<T> out =
+ mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+ } else {
+ optional_linear_ring<T> out =
+ mapbox::geometry::wagyu::quick_clip::quick_lr_clip1(lr, bbox);
+ }
+ }
+
+ clock_t after = clock();
+
+ if (pass == 0) {
+ std::cout << "quick_lr_clip";
+ } else {
+ std::cout << "quick_lr_clip1";
+ }
+
+ std::cout << ": " << (after - before) << "\n";
+}
+
+int main() {
+ check(0);
+ check(1);
+ return 0;
+}
diff --git a/tests/run-benchmark-tests.sh b/tests/run-benchmark-tests.sh
new file mode 100755
index 0000000..493fc8b
--- /dev/null
+++ b/tests/run-benchmark-tests.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+
+# ./tests/run-geometry-tests.sh ./fixture-tester
+TESTER="$1"
+PASSES=0
+FAILS=0
+
+if [ -z "$TESTER" ]; then
+ echo "Error: path to a fixture-tester binary must be supplied"
+ exit 1
+elif [ ! -e "$TESTER" ]; then
+ echo "Error: path to fixture-tester binary is invalid"
+ exit 1
+fi
+
+for filename in $(ls ./tests/geometry-test-data/input-polyjson)
+do
+ for type in union difference x_or intersection
+ do
+ for filltype in even_odd non_zero positive negative
+ do
+ #echo $TESTER -t $type -f $filltype -i 10 ./tests/geometry-test-data/input-polyjson/$filename ./tests/fixtures/clip-clockwise-square.json
+ $TESTER -t $type -f $filltype -i 10 \
+ ./tests/geometry-test-data/input-polyjson/$filename \
+ ./tests/fixtures/clip-clockwise-square.json;
+
+ # Check exit code of last command
+ if [ "$?" -eq "0" ]; then
+ PASSES=$((PASSES + 1))
+ else
+ FAILS=$((FAILS + 1))
+ fi
+ done
+ done
+done
+
+TOTAL=$((PASSES + FAILS))
+echo -e "Wagyu Faster On: \033[1;32m ✓ $PASSES/$TOTAL \033[0;31m ✗ $FAILS/$TOTAL \033[0m"
+
diff --git a/tests/run-geometry-tests.sh b/tests/run-geometry-tests.sh
new file mode 100755
index 0000000..d0d6993
--- /dev/null
+++ b/tests/run-geometry-tests.sh
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+
+# ./tests/run-geometry-tests.sh ./fixture-tester
+TESTER="$1"
+PASSES=0
+FAILS=0
+
+if [ -z "$TESTER" ]; then
+ echo "Error: path to a fixture-tester binary must be supplied"
+ exit 1
+elif [ ! -e "$TESTER" ]; then
+ echo "Error: path to fixture-tester binary is invalid"
+ exit 1
+fi
+
+
+mkdir -p ./tests/output-polyjson
+
+for filename in $(ls ./tests/geometry-test-data/input-polyjson)
+do
+ for type in union difference x_or intersection
+ do
+ for filltype in even_odd non_zero positive negative
+ do
+ if [ "$type" = "union" ]; then
+ $TESTER -t $type -f $filltype \
+ ./tests/geometry-test-data/input-polyjson/$filename \
+ 1>./tests/output-polyjson/$type-$filename;
+
+ # Check exit code of last command
+ if [ "$?" -eq "0" ]; then
+ PASSES=$((PASSES + 1))
+ else
+ echo --- Test failure: $type $filltype $filename
+ echo $TESTER -t $type -f $filltype ./tests/geometry-test-data/input-polyjson/$filename
+ FAILS=$((FAILS + 1))
+ fi
+ fi
+ $TESTER -t $type -f $filltype \
+ ./tests/geometry-test-data/input-polyjson/$filename \
+ ./tests/fixtures/clip-clockwise-square.json \
+ 1>./tests/output-polyjson/$type-$filename;
+
+ # Check exit code of last command
+ if [ "$?" -eq "0" ]; then
+ PASSES=$((PASSES + 1))
+ else
+ echo --- Test failure: $type $filltype $filename
+ echo $TESTER -t $type -f $filltype ./tests/geometry-test-data/input-polyjson/$filename ./tests/fixtures/clip-clockwise-square.json
+ FAILS=$((FAILS + 1))
+ fi
+ done
+ done
+done
+
+TOTAL=$((PASSES + FAILS))
+echo -e "\033[1;32m ✓ $PASSES/$TOTAL \033[0;31m ✗ $FAILS/$TOTAL \033[0m"
+if [ "$FAILS" -gt "0" ]; then
+ exit 1;
+fi
+
diff --git a/tests/test.cpp b/tests/test.cpp
new file mode 100644
index 0000000..189613f
--- /dev/null
+++ b/tests/test.cpp
@@ -0,0 +1,10 @@
+// https://github.com/philsquared/Catch/blob/master/docs/own-main.md
+#define CATCH_CONFIG_RUNNER
+#include "catch.hpp"
+
+int main(int argc, char* const argv[]) {
+ int result = Catch::Session().run(argc, argv);
+ if (!result)
+ printf("\x1b[1;32m ✓ \x1b[0m\n");
+ return result;
+}
diff --git a/tests/unit/clipper_get_bounds.cpp b/tests/unit/clipper_get_bounds.cpp
new file mode 100644
index 0000000..0a4d36e
--- /dev/null
+++ b/tests/unit/clipper_get_bounds.cpp
@@ -0,0 +1,81 @@
+#include "catch.hpp"
+
+#include <mapbox/geometry/wagyu/wagyu.hpp>
+
+using namespace mapbox::geometry::wagyu;
+using T = std::int64_t;
+
+TEST_CASE("test returns zero with no data provided - int64") {
+ mapbox::geometry::wagyu::wagyu<T> clipper;
+ auto bounds = clipper.get_bounds();
+ CHECK(bounds.min.x == 0);
+ CHECK(bounds.min.y == 0);
+ CHECK(bounds.max.x == 0);
+ CHECK(bounds.max.y == 0);
+}
+
+TEST_CASE("test returns simple box - int64") {
+ mapbox::geometry::polygon<T> polygon;
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 0, 0 });
+ polygon.push_back(ring);
+ mapbox::geometry::wagyu::wagyu<T> clipper;
+ CHECK(clipper.add_polygon(polygon));
+ auto bounds = clipper.get_bounds();
+ CHECK(bounds.min.x == 0);
+ CHECK(bounds.min.y == 0);
+ CHECK(bounds.max.x == 5);
+ CHECK(bounds.max.y == 5);
+}
+
+TEST_CASE("test returns simple box negative - int64") {
+ mapbox::geometry::polygon<T> polygon;
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, -5 });
+ ring.push_back({ -5, -5 });
+ ring.push_back({ -5, 0 });
+ ring.push_back({ 0, 0 });
+ polygon.push_back(ring);
+ mapbox::geometry::wagyu::wagyu<T> clipper;
+ CHECK(clipper.add_polygon(polygon));
+ auto bounds = clipper.get_bounds();
+ CHECK(bounds.min.x == -5);
+ CHECK(bounds.min.y == -5);
+ CHECK(bounds.max.x == 0);
+ CHECK(bounds.max.y == 0);
+}
+
+TEST_CASE("two polygons - int64") {
+ mapbox::geometry::wagyu::wagyu<T> clipper;
+ // Polygon 1
+ mapbox::geometry::polygon<T> polygon;
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 0, 0 });
+ polygon.push_back(ring);
+ CHECK(clipper.add_polygon(polygon));
+ polygon.clear();
+ ring.clear();
+ // Polygon 2
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 10 });
+ ring.push_back({ 10, 10 });
+ ring.push_back({ 10, 5 });
+ ring.push_back({ 5, 5 });
+ polygon.push_back(ring);
+ CHECK(clipper.add_polygon(polygon));
+
+ auto bounds = clipper.get_bounds();
+ CHECK(bounds.min.x == 0);
+ CHECK(bounds.min.y == 0);
+ CHECK(bounds.max.x == 10);
+ CHECK(bounds.max.y == 10);
+}
diff --git a/tests/unit/edge.cpp b/tests/unit/edge.cpp
new file mode 100644
index 0000000..ea14220
--- /dev/null
+++ b/tests/unit/edge.cpp
@@ -0,0 +1,68 @@
+#include "catch.hpp"
+
+#include <mapbox/geometry/wagyu/edge.hpp>
+
+using namespace mapbox::geometry::wagyu;
+using T = std::int64_t;
+
+TEST_CASE("test edge initialization - same two points") {
+ mapbox::geometry::point<T> p1 = { 100, 10 };
+ mapbox::geometry::point<T> p2 = { 100, 10 };
+ edge<T> e1(p1, p2);
+
+ CHECK(e1.bot.x == 100);
+ CHECK(e1.bot.y == 10);
+ CHECK(e1.top.x == 100);
+ CHECK(e1.top.y == 10);
+ CHECK(std::isinf(e1.dx));
+}
+
+TEST_CASE("test edge initialization - different type") {
+ mapbox::geometry::point<std::int32_t> p1 = { 100, 10 };
+ mapbox::geometry::point<std::int32_t> p2 = { 10, 15 };
+ edge<std::int64_t> e1(p1, p2);
+
+ CHECK(e1.bot.x == 10);
+ CHECK(e1.bot.y == 15);
+ CHECK(e1.top.x == 100);
+ CHECK(e1.top.y == 10);
+ CHECK(e1.dx == Approx(-18.0));
+}
+
+TEST_CASE("test edge initialization - horizontal segment") {
+ mapbox::geometry::point<T> p1 = { 10, 10 };
+ mapbox::geometry::point<T> p2 = { 100, 10 };
+ edge<T> e1(p1, p2);
+ edge<T> e2(p2, p1);
+
+ CHECK(e1.bot.x == 10);
+ CHECK(e1.bot.y == 10);
+ CHECK(e1.top.x == 100);
+ CHECK(e1.top.y == 10);
+ CHECK(std::isinf(e1.dx));
+
+ CHECK(e2.bot.x == 100);
+ CHECK(e2.bot.y == 10);
+ CHECK(e2.top.x == 10);
+ CHECK(e2.top.y == 10);
+ CHECK(std::isinf(e2.dx));
+}
+
+TEST_CASE("test edge initialization - vertical segment") {
+ mapbox::geometry::point<T> p1 = { 10, 10 };
+ mapbox::geometry::point<T> p2 = { 10, 100 };
+ edge<T> e1(p1, p2);
+ edge<T> e2(p2, p1);
+
+ CHECK(e1.bot.x == 10);
+ CHECK(e1.bot.y == 100);
+ CHECK(e1.top.x == 10);
+ CHECK(e1.top.y == 10);
+ CHECK(e1.dx == Approx(0.0));
+
+ CHECK(e2.bot.x == 10);
+ CHECK(e2.bot.y == 100);
+ CHECK(e2.top.x == 10);
+ CHECK(e2.top.y == 10);
+ CHECK(e2.dx == Approx(0.0));
+}
diff --git a/tests/unit/local_minimum_util.cpp b/tests/unit/local_minimum_util.cpp
new file mode 100644
index 0000000..3e622bf
--- /dev/null
+++ b/tests/unit/local_minimum_util.cpp
@@ -0,0 +1,685 @@
+#include "catch.hpp"
+
+#include <mapbox/geometry/point.hpp>
+#include <mapbox/geometry/polygon.hpp>
+#include <mapbox/geometry/wagyu/build_local_minima_list.hpp>
+#include <mapbox/geometry/wagyu/local_minimum_util.hpp>
+
+using namespace mapbox::geometry::wagyu;
+using T = std::int64_t;
+
+TEST_CASE("test reverse horizontal") {
+ mapbox::geometry::point<T> p1 = { 0, 5 };
+ mapbox::geometry::point<T> p2 = { 5, 5 };
+ edge<T> e1(p1, p2);
+
+ CHECK(e1.bot.x == 0);
+ CHECK(e1.bot.y == 5);
+ CHECK(e1.top.x == 5);
+ CHECK(e1.top.y == 5);
+
+ reverse_horizontal(e1);
+
+ CHECK(e1.bot.x == 5);
+ CHECK(e1.bot.y == 5);
+ CHECK(e1.top.x == 0);
+ CHECK(e1.top.y == 5);
+}
+
+TEST_CASE("edge adding ring - square closed") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 0, 0 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 1);
+ auto& lm = minima_list.front();
+ auto& edges = lm.left_bound.edges;
+ REQUIRE(edges.size() == 1);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.right_bound.edges;
+ REQUIRE(edges_r.size() == 3);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 5);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 0);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr == edges_r.end());
+}
+
+TEST_CASE("edge adding ring - square not closed") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 0 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 1);
+ auto& lm = minima_list.front();
+ auto& edges = lm.left_bound.edges;
+ REQUIRE(edges.size() == 1);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.right_bound.edges;
+ REQUIRE(edges_r.size() == 3);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 5);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 0);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr == edges_r.end());
+}
+
+TEST_CASE("edge adding ring - triangle closed") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 10, 5 });
+ ring.push_back({ 5, 10 });
+ ring.push_back({ 0, 0 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 1);
+ auto& lm = minima_list.front();
+ auto& edges = lm.right_bound.edges;
+ REQUIRE(edges.size() == 2);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 10);
+ CHECK(itr->top.y == 5);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 10);
+ CHECK(itr->dx == Approx(-1.0));
+ ++itr;
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 10);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(2.0));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.left_bound.edges;
+ REQUIRE(edges_r.size() == 1);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 10);
+ CHECK(itr->dx == Approx(0.5));
+ ++itr;
+ CHECK(itr == edges_r.end());
+}
+
+TEST_CASE("edge adding ring - triangle not closed") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 10, 5 });
+ ring.push_back({ 5, 10 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 1);
+ auto& lm = minima_list.front();
+ auto& edges = lm.right_bound.edges;
+ REQUIRE(edges.size() == 2);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 10);
+ CHECK(itr->top.y == 5);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 10);
+ CHECK(itr->dx == Approx(-1.0));
+ ++itr;
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 10);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(2.0));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.left_bound.edges;
+ REQUIRE(edges_r.size() == 1);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 10);
+ CHECK(itr->dx == Approx(0.5));
+ ++itr;
+ CHECK(itr == edges_r.end());
+}
+
+TEST_CASE("edge adding ring - square closed - collinear points") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 3 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 3, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 3 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 3, 0 });
+ ring.push_back({ 0, 0 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 1);
+ auto& lm = minima_list.front();
+ auto& edges = lm.left_bound.edges;
+ REQUIRE(edges.size() == 1);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.right_bound.edges;
+ REQUIRE(edges_r.size() == 3);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 5);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 0);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr == edges_r.end());
+}
+
+TEST_CASE("edge adding ring - square not closed - collinear points") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 4, 0 });
+ ring.push_back({ 3, 0 });
+ ring.push_back({ 2, 0 });
+ ring.push_back({ 1, 0 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 1);
+ auto& lm = minima_list.front();
+ auto& edges = lm.left_bound.edges;
+ REQUIRE(edges.size() == 1);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.right_bound.edges;
+ REQUIRE(edges_r.size() == 3);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 5);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 0);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr == edges_r.end());
+}
+
+TEST_CASE("edge adding ring - square closed - repeated points") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 0 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 1);
+ auto& lm = minima_list.front();
+ auto& edges = lm.left_bound.edges;
+ REQUIRE(edges.size() == 1);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.right_bound.edges;
+ REQUIRE(edges_r.size() == 3);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 5);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 0);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr == edges_r.end());
+}
+
+TEST_CASE("edge adding ring - square closed - repeated and collinear points") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 3 });
+ ring.push_back({ 0, 3 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 3, 5 });
+ ring.push_back({ 3, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 3 });
+ ring.push_back({ 5, 3 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 3, 0 });
+ ring.push_back({ 3, 0 });
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 0 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 1);
+ auto& lm = minima_list.front();
+ auto& edges = lm.left_bound.edges;
+ REQUIRE(edges.size() == 1);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.right_bound.edges;
+ REQUIRE(edges_r.size() == 3);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 5);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 0);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr == edges_r.end());
+}
+
+TEST_CASE("edge adding ring - square closed - spikes") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 10 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 10, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 0, 0 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 1);
+ auto& lm = minima_list.front();
+ auto& edges = lm.left_bound.edges;
+ REQUIRE(edges.size() == 1);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.right_bound.edges;
+ REQUIRE(edges_r.size() == 3);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 5);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 0);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr == edges_r.end());
+}
+
+TEST_CASE("edge adding ring - square closed - zigzag") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 0, 5 });
+ ring.push_back({ 5, 5 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 0, 0 });
+ ring.push_back({ 5, 0 });
+ ring.push_back({ 0, 0 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 1);
+ auto& lm = minima_list.front();
+ auto& edges = lm.left_bound.edges;
+ REQUIRE(edges.size() == 1);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.right_bound.edges;
+ REQUIRE(edges_r.size() == 3);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 5);
+ CHECK(itr->bot.x == 0);
+ CHECK(itr->bot.y == 5);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr->top.x == 5);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 5);
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr->top.x == 0);
+ CHECK(itr->top.y == 0);
+ CHECK(itr->bot.x == 5);
+ CHECK(itr->bot.y == 0);
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr == edges_r.end());
+}
+
+TEST_CASE("edge adding - odd shape") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 65542, 44380 }); // A
+ ring.push_back({ 65542, 45062 }); // B
+ ring.push_back({ 64947, 45062 }); // C
+ ring.push_back({ 64832, 44579 }); // D
+ ring.push_back({ 65176, 44820 }); // E
+ ring.push_back({ 65542, 44380 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 2);
+ auto& lm = minima_list.front();
+ auto& edges = lm.left_bound.edges;
+ REQUIRE(edges.size() == 1);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 64832); // D
+ CHECK(itr->top.y == 44579); // D
+ CHECK(itr->bot.x == 64947); // C
+ CHECK(itr->bot.y == 45062); // C
+ CHECK(itr->dx == Approx(0.2380952381));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.right_bound.edges;
+ REQUIRE(edges_r.size() == 2);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 65542); // B
+ CHECK(itr->top.y == 45062); // B
+ CHECK(itr->bot.x == 64947); // C
+ CHECK(itr->bot.y == 45062); // C
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr->top.x == 65542); // A
+ CHECK(itr->top.y == 44380); // A
+ CHECK(itr->bot.x == 65542); // B
+ CHECK(itr->bot.y == 45062); // B
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr == edges_r.end());
+
+ auto& lm2 = minima_list.back();
+ auto& edges2 = lm2.left_bound.edges;
+ REQUIRE(edges2.size() == 1);
+ itr = edges2.begin();
+ CHECK(itr->top.x == 64832); // D
+ CHECK(itr->top.y == 44579); // D
+ CHECK(itr->bot.x == 65176); // E
+ CHECK(itr->bot.y == 44820); // E
+ CHECK(itr->dx == Approx(1.4273858921));
+ ++itr;
+ CHECK(itr == edges2.end());
+ auto& edges2_r = lm2.right_bound.edges;
+ REQUIRE(edges2_r.size() == 1);
+ itr = edges2_r.begin();
+ CHECK(itr->top.x == 65542); // A
+ CHECK(itr->top.y == 44380); // A
+ CHECK(itr->bot.x == 65176); // E
+ CHECK(itr->bot.y == 44820); // E
+ CHECK(itr->dx == Approx(-0.8318181818));
+ ++itr;
+ CHECK(itr == edges2_r.end());
+}
+
+TEST_CASE("edge adding insane set of lines") {
+ mapbox::geometry::linear_ring<T> ring;
+ ring.push_back({ 65542, 44237 });
+ ring.push_back({ 65542, 44505 });
+ ring.push_back({ 65542, 44461 });
+ ring.push_back({ 65542, 44718 });
+ ring.push_back({ 65542, 44766 });
+ ring.push_back({ 65542, 44754 });
+ ring.push_back({ 65542, 44901 });
+ ring.push_back({ 65542, 45003 });
+ ring.push_back({ 65542, 45062 }); // B
+ ring.push_back({ 64989, 45062 });
+ ring.push_back({ 64844, 45062 });
+ ring.push_back({ 65032, 45062 });
+ ring.push_back({ 65101, 45062 });
+ ring.push_back({ 64726, 45062 });
+ ring.push_back({ 64457, 45062 });
+ ring.push_back({ 63901, 45062 });
+ ring.push_back({ 63864, 45062 });
+ ring.push_back({ 64336, 45062 });
+ ring.push_back({ 64947, 45062 }); // C
+ ring.push_back({ 64832, 44579 }); // D
+ ring.push_back({ 65176, 44820 }); // E
+ ring.push_back({ 65542, 44380 }); // A
+ ring.push_back({ 65542, 43911 });
+ ring.push_back({ 65542, 43794 });
+ ring.push_back({ 65542, 43997 });
+ ring.push_back({ 65542, 44007 });
+ ring.push_back({ 65542, 44237 });
+
+ local_minimum_list<T> minima_list;
+ polygon_type p_type = polygon_type_subject;
+
+ CHECK(add_linear_ring(ring, minima_list, p_type));
+
+ REQUIRE(minima_list.size() == 2);
+ auto& lm = minima_list.front();
+ auto& edges = lm.left_bound.edges;
+ REQUIRE(edges.size() == 1);
+ auto itr = edges.begin();
+ CHECK(itr->top.x == 64832); // D
+ CHECK(itr->top.y == 44579); // D
+ CHECK(itr->bot.x == 64947); // C
+ CHECK(itr->bot.y == 45062); // C
+ CHECK(itr->dx == Approx(0.2380952381));
+ ++itr;
+ CHECK(itr == edges.end());
+ auto& edges_r = lm.right_bound.edges;
+ REQUIRE(edges_r.size() == 2);
+ itr = edges_r.begin();
+ CHECK(itr->top.x == 65542); // B
+ CHECK(itr->top.y == 45062); // B
+ CHECK(itr->bot.x == 64947); // C
+ CHECK(itr->bot.y == 45062); // C
+ CHECK(std::isinf(itr->dx));
+ ++itr;
+ CHECK(itr->top.x == 65542); // A
+ CHECK(itr->top.y == 44380); // A
+ CHECK(itr->bot.x == 65542); // B
+ CHECK(itr->bot.y == 45062); // B
+ CHECK(itr->dx == Approx(0.0));
+ ++itr;
+ CHECK(itr == edges_r.end());
+
+ auto& lm2 = minima_list.back();
+ auto& edges2 = lm2.left_bound.edges;
+ REQUIRE(edges2.size() == 1);
+ itr = edges2.begin();
+ CHECK(itr->top.x == 64832); // D
+ CHECK(itr->top.y == 44579); // D
+ CHECK(itr->bot.x == 65176); // E
+ CHECK(itr->bot.y == 44820); // E
+ CHECK(itr->dx == Approx(1.4273858921));
+ ++itr;
+ CHECK(itr == edges2.end());
+ auto& edges2_r = lm2.right_bound.edges;
+ REQUIRE(edges2_r.size() == 1);
+ itr = edges2_r.begin();
+ CHECK(itr->top.x == 65542); // A
+ CHECK(itr->top.y == 44380); // A
+ CHECK(itr->bot.x == 65176); // E
+ CHECK(itr->bot.y == 44820); // E
+ CHECK(itr->dx == Approx(-0.8318181818));
+ ++itr;
+ CHECK(itr == edges2_r.end());
+}
diff --git a/tests/unit/process_horizontal.hpp b/tests/unit/process_horizontal.hpp
new file mode 100644
index 0000000..b34db66
--- /dev/null
+++ b/tests/unit/process_horizontal.hpp
@@ -0,0 +1,31 @@
+#include "catch.hpp"
+
+#include <mapbox/geometry/wagyu/process_horizontal.hpp>
+
+using namespace mapbox::geometry::wagyu;
+using T = std::int64_t;
+
+TEST_CASE("get_horizontal_direction finds edge direction") {
+ mapbox::geometry::point<T> p1 = { 0, 5 };
+ mapbox::geometry::point<T> p2 = { 0, 7 };
+ edge<T> e1(p1, p2, polygon_type_subject);
+
+ T left;
+ T right;
+ horizontal_direction dir;
+
+ process_horizontal(&e1, dir, left, right);
+
+ CHECK(dir == left_to_right);
+ CHECK(left == p1.x);
+ CHECK(right == p2.x);
+
+ // flip horizontal direction
+ edge<T> e2(p2, p1, polygon_type_subject);
+
+ process_horizontal(&e2, dir, left, right);
+
+ CHECK(dir == right_to_left);
+ CHECK(left == p2.x);
+ CHECK(right == p1.x);
+};
\ No newline at end of file
diff --git a/tests/unit/quick_clip.cpp b/tests/unit/quick_clip.cpp
new file mode 100644
index 0000000..5551dd4
--- /dev/null
+++ b/tests/unit/quick_clip.cpp
@@ -0,0 +1,297 @@
+#include "catch.hpp"
+#include <iostream>
+
+#include <mapbox/geometry/wagyu/quick_clip.hpp>
+#include <mapbox/geometry/wagyu/wagyu.hpp>
+
+template <class charT, class traits, typename T>
+inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
+ const mapbox::geometry::linear_ring<T>& ring) {
+ out << "[";
+ bool first = true;
+ for (auto const& pt : ring) {
+ if (first) {
+ out << "[";
+ first = false;
+ } else {
+ out << ",[";
+ }
+ out << pt.x << "," << pt.y << "]";
+ }
+ out << "]";
+ return out;
+}
+
+using namespace mapbox::geometry::wagyu;
+using T = std::int64_t;
+
+TEST_CASE("square entirely within bbox") {
+ mapbox::geometry::point<T> p1 = { 0, 0 };
+ mapbox::geometry::point<T> p2 = { 100, 100 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ lr.push_back(mapbox::geometry::point<T>(25, 25));
+ lr.push_back(mapbox::geometry::point<T>(75, 25));
+ lr.push_back(mapbox::geometry::point<T>(75, 75));
+ lr.push_back(mapbox::geometry::point<T>(25, 75));
+ lr.push_back(mapbox::geometry::point<T>(25, 25));
+
+ auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+
+ CHECK(out == lr);
+}
+
+TEST_CASE("square cut at right") {
+ mapbox::geometry::point<T> p1 = { 0, 0 };
+ mapbox::geometry::point<T> p2 = { 100, 100 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ lr.push_back(mapbox::geometry::point<T>(25, 25));
+ lr.push_back(mapbox::geometry::point<T>(175, 25));
+ lr.push_back(mapbox::geometry::point<T>(175, 75));
+ lr.push_back(mapbox::geometry::point<T>(25, 75));
+ lr.push_back(mapbox::geometry::point<T>(25, 25));
+
+ auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+
+ mapbox::geometry::linear_ring<T> want;
+ want.push_back(mapbox::geometry::point<T>(25, 25));
+ want.push_back(mapbox::geometry::point<T>(100, 25));
+ want.push_back(mapbox::geometry::point<T>(100, 75));
+ want.push_back(mapbox::geometry::point<T>(25, 75));
+ want.push_back(mapbox::geometry::point<T>(25, 25));
+
+ CHECK(out == want);
+}
+
+TEST_CASE("square cut at left") {
+ mapbox::geometry::point<T> p1 = { 0, 0 };
+ mapbox::geometry::point<T> p2 = { 100, 100 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ lr.push_back(mapbox::geometry::point<T>(-25, 25));
+ lr.push_back(mapbox::geometry::point<T>(75, 25));
+ lr.push_back(mapbox::geometry::point<T>(75, 75));
+ lr.push_back(mapbox::geometry::point<T>(-25, 75));
+ lr.push_back(mapbox::geometry::point<T>(-25, 25));
+
+ auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+
+ mapbox::geometry::linear_ring<T> want;
+ want.push_back(mapbox::geometry::point<T>(0, 25));
+ want.push_back(mapbox::geometry::point<T>(75, 25));
+ want.push_back(mapbox::geometry::point<T>(75, 75));
+ want.push_back(mapbox::geometry::point<T>(0, 75));
+ want.push_back(mapbox::geometry::point<T>(0, 25));
+
+ CHECK(out == want);
+}
+
+TEST_CASE("square cut at top") {
+ mapbox::geometry::point<T> p1 = { 0, 0 };
+ mapbox::geometry::point<T> p2 = { 100, 100 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ lr.push_back(mapbox::geometry::point<T>(25, 25));
+ lr.push_back(mapbox::geometry::point<T>(75, 25));
+ lr.push_back(mapbox::geometry::point<T>(75, 175));
+ lr.push_back(mapbox::geometry::point<T>(25, 175));
+ lr.push_back(mapbox::geometry::point<T>(25, 25));
+
+ auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+
+ mapbox::geometry::linear_ring<T> want;
+ want.push_back(mapbox::geometry::point<T>(25, 25));
+ want.push_back(mapbox::geometry::point<T>(75, 25));
+ want.push_back(mapbox::geometry::point<T>(75, 100));
+ want.push_back(mapbox::geometry::point<T>(25, 100));
+ want.push_back(mapbox::geometry::point<T>(25, 25));
+
+ CHECK(out == want);
+}
+
+TEST_CASE("square cut at bottom") {
+ mapbox::geometry::point<T> p1 = { 0, 0 };
+ mapbox::geometry::point<T> p2 = { 100, 100 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ lr.push_back(mapbox::geometry::point<T>(-25, 25));
+ lr.push_back(mapbox::geometry::point<T>(75, 25));
+ lr.push_back(mapbox::geometry::point<T>(75, 75));
+ lr.push_back(mapbox::geometry::point<T>(-25, 75));
+ lr.push_back(mapbox::geometry::point<T>(-25, 25));
+
+ auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+
+ mapbox::geometry::linear_ring<T> want;
+ want.push_back(mapbox::geometry::point<T>(0, 25));
+ want.push_back(mapbox::geometry::point<T>(75, 25));
+ want.push_back(mapbox::geometry::point<T>(75, 75));
+ want.push_back(mapbox::geometry::point<T>(0, 75));
+ want.push_back(mapbox::geometry::point<T>(0, 25));
+
+ CHECK(out == want);
+}
+
+TEST_CASE("square cut at top right") {
+ mapbox::geometry::point<T> p1 = { 0, 0 };
+ mapbox::geometry::point<T> p2 = { 100, 100 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ lr.push_back(mapbox::geometry::point<T>(25, 25));
+ lr.push_back(mapbox::geometry::point<T>(175, 25));
+ lr.push_back(mapbox::geometry::point<T>(175, 175));
+ lr.push_back(mapbox::geometry::point<T>(25, 175));
+ lr.push_back(mapbox::geometry::point<T>(25, 25));
+
+ auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+
+ mapbox::geometry::linear_ring<T> want;
+ want.push_back(mapbox::geometry::point<T>(25, 25));
+ want.push_back(mapbox::geometry::point<T>(100, 25));
+ want.push_back(mapbox::geometry::point<T>(100, 100));
+ want.push_back(mapbox::geometry::point<T>(25, 100));
+ want.push_back(mapbox::geometry::point<T>(25, 25));
+
+ CHECK(out == want);
+}
+
+TEST_CASE("square cut at top and bottom right") {
+ mapbox::geometry::point<T> p1 = { 0, 0 };
+ mapbox::geometry::point<T> p2 = { 100, 100 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ lr.push_back(mapbox::geometry::point<T>(25, -25));
+ lr.push_back(mapbox::geometry::point<T>(175, -25));
+ lr.push_back(mapbox::geometry::point<T>(175, 175));
+ lr.push_back(mapbox::geometry::point<T>(25, 175));
+ lr.push_back(mapbox::geometry::point<T>(25, -25));
+
+ auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+
+ mapbox::geometry::linear_ring<T> want;
+ want.push_back(mapbox::geometry::point<T>(100, 0));
+ want.push_back(mapbox::geometry::point<T>(100, 100));
+ want.push_back(mapbox::geometry::point<T>(25, 100));
+ want.push_back(mapbox::geometry::point<T>(25, 0));
+ want.push_back(mapbox::geometry::point<T>(100, 0));
+
+ CHECK(out == want);
+}
+
+TEST_CASE("square entirely out of bounds") {
+ mapbox::geometry::point<T> p1 = { 0, 0 };
+ mapbox::geometry::point<T> p2 = { 100, 100 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ lr.push_back(mapbox::geometry::point<T>(125, 125));
+ lr.push_back(mapbox::geometry::point<T>(175, 125));
+ lr.push_back(mapbox::geometry::point<T>(175, 175));
+ lr.push_back(mapbox::geometry::point<T>(125, 175));
+ lr.push_back(mapbox::geometry::point<T>(125, 125));
+
+ auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+
+ CHECK(out.empty());
+}
+
+TEST_CASE("square entirely enclosing bbox") {
+ mapbox::geometry::point<T> p1 = { 0, 0 };
+ mapbox::geometry::point<T> p2 = { 100, 100 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ lr.push_back(mapbox::geometry::point<T>(-25, -25));
+ lr.push_back(mapbox::geometry::point<T>(175, -25));
+ lr.push_back(mapbox::geometry::point<T>(175, 175));
+ lr.push_back(mapbox::geometry::point<T>(-25, 175));
+ lr.push_back(mapbox::geometry::point<T>(-25, -25));
+
+ auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+
+ mapbox::geometry::linear_ring<T> want;
+ want.push_back(mapbox::geometry::point<T>(0, 0));
+ want.push_back(mapbox::geometry::point<T>(100, 0));
+ want.push_back(mapbox::geometry::point<T>(100, 100));
+ want.push_back(mapbox::geometry::point<T>(0, 100));
+ want.push_back(mapbox::geometry::point<T>(0, 0));
+
+ CHECK(out == want);
+}
+
+TEST_CASE("sticking out and back in") {
+ mapbox::geometry::point<T> p1 = { 0, 0 };
+ mapbox::geometry::point<T> p2 = { 100, 100 };
+ mapbox::geometry::box<T> bbox(p1, p2);
+
+ mapbox::geometry::linear_ring<T> lr;
+ lr.push_back(mapbox::geometry::point<T>(25, 25));
+ lr.push_back(mapbox::geometry::point<T>(150, 25));
+ lr.push_back(mapbox::geometry::point<T>(150, 150));
+ lr.push_back(mapbox::geometry::point<T>(25, 150));
+ lr.push_back(mapbox::geometry::point<T>(25, 90));
+ lr.push_back(mapbox::geometry::point<T>(75, 90));
+ lr.push_back(mapbox::geometry::point<T>(75, 125));
+ lr.push_back(mapbox::geometry::point<T>(125, 125));
+ lr.push_back(mapbox::geometry::point<T>(125, 75));
+ lr.push_back(mapbox::geometry::point<T>(25, 75));
+ lr.push_back(mapbox::geometry::point<T>(25, 25));
+
+ auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox);
+
+ mapbox::geometry::linear_ring<T> want;
+ want.push_back(mapbox::geometry::point<T>(25, 25));
+ want.push_back(mapbox::geometry::point<T>(100, 25));
+ want.push_back(mapbox::geometry::point<T>(100, 100));
+ want.push_back(mapbox::geometry::point<T>(25, 100));
+ want.push_back(mapbox::geometry::point<T>(25, 90));
+ want.push_back(mapbox::geometry::point<T>(75, 90));
+ want.push_back(mapbox::geometry::point<T>(75, 100));
+ want.push_back(mapbox::geometry::point<T>(100, 100));
+ want.push_back(mapbox::geometry::point<T>(100, 75));
+ want.push_back(mapbox::geometry::point<T>(25, 75));
+ want.push_back(mapbox::geometry::point<T>(25, 25));
+
+ CHECK(out == want);
+
+ mapbox::geometry::wagyu::wagyu<T> clipper;
+ clipper.add_ring(out);
+ mapbox::geometry::multi_polygon<T> solution;
+ clipper.execute(clip_type_union, solution, fill_type_even_odd, fill_type_even_odd);
+
+ REQUIRE(solution.size() == 2);
+
+ mapbox::geometry::linear_ring<T> want0;
+ want0.push_back(mapbox::geometry::point<T>(75, 90));
+ want0.push_back(mapbox::geometry::point<T>(75, 100));
+ want0.push_back(mapbox::geometry::point<T>(25, 100));
+ want0.push_back(mapbox::geometry::point<T>(25, 90));
+ want0.push_back(mapbox::geometry::point<T>(75, 90));
+
+ mapbox::geometry::linear_ring<T> want1;
+ want1.push_back(mapbox::geometry::point<T>(100, 75));
+ want1.push_back(mapbox::geometry::point<T>(25, 75));
+ want1.push_back(mapbox::geometry::point<T>(25, 25));
+ want1.push_back(mapbox::geometry::point<T>(100, 25));
+ want1.push_back(mapbox::geometry::point<T>(100, 75));
+
+ CHECK(solution[0][0] == want0);
+ CHECK(solution[1][0] == want1);
+
+ mapbox::geometry::polygon<T> poly;
+ poly.push_back(lr);
+ mapbox::geometry::multi_polygon<T> poly_out;
+ poly_out = mapbox::geometry::wagyu::clip(poly, bbox, fill_type_even_odd);
+
+ REQUIRE(poly_out.size() == 2);
+ CHECK(poly_out[0][0] == want0);
+ CHECK(poly_out[1][0] == want1);
+}
diff --git a/tests/unit/vatti.cpp b/tests/unit/vatti.cpp
new file mode 100644
index 0000000..12021e4
--- /dev/null
+++ b/tests/unit/vatti.cpp
@@ -0,0 +1,406 @@
+#include "catch.hpp"
+
+#include <mapbox/geometry/polygon.hpp>
+
+#include <mapbox/geometry/wagyu/wagyu.hpp>
+
+using namespace mapbox::geometry::wagyu;
+using T = std::int64_t;
+
+TEST_CASE("no input test") {
+
+ mapbox::geometry::wagyu::wagyu<std::int64_t> wagyu;
+
+ mapbox::geometry::multi_polygon<std::int64_t> solution;
+ CHECK_FALSE(wagyu.execute(mapbox::geometry::wagyu::clip_type_union, solution,
+ mapbox::geometry::wagyu::fill_type_positive,
+ mapbox::geometry::wagyu::fill_type_positive));
+}
+
+TEST_CASE("simple test for winding order - positive") {
+
+ // This ring is counter-clockwise
+ mapbox::geometry::linear_ring<std::int64_t> ring;
+ ring.push_back(mapbox::geometry::point<std::int64_t>(0, 0));
+ ring.push_back(mapbox::geometry::point<std::int64_t>(1, 0));
+ ring.push_back(mapbox::geometry::point<std::int64_t>(1, 1));
+ ring.push_back(mapbox::geometry::point<std::int64_t>(0, 1));
+ ring.push_back(mapbox::geometry::point<std::int64_t>(0, 0));
+
+ mapbox::geometry::wagyu::wagyu<std::int64_t> wagyu;
+ wagyu.add_ring(ring);
+
+ mapbox::geometry::multi_polygon<std::int64_t> solution;
+ wagyu.execute(mapbox::geometry::wagyu::clip_type_union, solution,
+ mapbox::geometry::wagyu::fill_type_positive,
+ mapbox::geometry::wagyu::fill_type_positive);
+
+ REQUIRE(solution.size() == 1);
+ // Check first polygon number of rings
+ REQUIRE(solution[0].size() == 1);
+
+ // Check Ring 1 is counter clockwise as well
+ REQUIRE(solution[0][0].size() == 5);
+ CHECK(solution[0][0][0].x == 1);
+ CHECK(solution[0][0][0].y == 0);
+
+ CHECK(solution[0][0][1].x == 1);
+ CHECK(solution[0][0][1].y == 1);
+
+ CHECK(solution[0][0][2].x == 0);
+ CHECK(solution[0][0][2].y == 1);
+
+ CHECK(solution[0][0][3].x == 0);
+ CHECK(solution[0][0][3].y == 0);
+
+ CHECK(solution[0][0][4].x == 1);
+ CHECK(solution[0][0][4].y == 0);
+}
+
+TEST_CASE("simple test for winding order - negative") {
+ mapbox::geometry::linear_ring<std::int64_t> ring;
+ ring.push_back(mapbox::geometry::point<std::int64_t>(0, 0));
+ ring.push_back(mapbox::geometry::point<std::int64_t>(1, 0));
+ ring.push_back(mapbox::geometry::point<std::int64_t>(1, 1));
+ ring.push_back(mapbox::geometry::point<std::int64_t>(0, 1));
+ ring.push_back(mapbox::geometry::point<std::int64_t>(0, 0));
+
+ mapbox::geometry::wagyu::wagyu<std::int64_t> wagyu;
+ wagyu.add_ring(ring);
+
+ mapbox::geometry::multi_polygon<std::int64_t> solution;
+ wagyu.execute(mapbox::geometry::wagyu::clip_type_union, solution,
+ mapbox::geometry::wagyu::fill_type_negative,
+ mapbox::geometry::wagyu::fill_type_negative);
+
+ REQUIRE(solution.size() == 0);
+}
+
+TEST_CASE("simple test of entire vatti") {
+
+ mapbox::geometry::wagyu::wagyu<T> clipper;
+ mapbox::geometry::polygon<T> polygon0;
+ mapbox::geometry::linear_ring<T> ring0_0;
+ ring0_0.push_back({ -79102, 0 });
+ ring0_0.push_back({ -70312, -55285 });
+ ring0_0.push_back({ 85254, -30747 });
+ ring0_0.push_back({ 58008, 80592 });
+ ring0_0.push_back({ -79102, 0 });
+ polygon0.push_back(ring0_0);
+
+ mapbox::geometry::linear_ring<T> ring0_1;
+ ring0_1.push_back({ 44824, 42149 });
+ ring0_1.push_back({ 51855, -21089 });
+ ring0_1.push_back({ -65918, -32502 });
+ ring0_1.push_back({ -50098, 4394 });
+ ring0_1.push_back({ 44824, 42149 });
+ polygon0.push_back(ring0_1);
+
+ clipper.add_polygon(polygon0, polygon_type::polygon_type_subject);
+
+ mapbox::geometry::polygon<T> polygon1;
+ mapbox::geometry::linear_ring<T> ring1_0;
+ ring1_0.push_back({ 31201, 8349 });
+ ring1_0.push_back({ 4834, 19771 });
+ ring1_0.push_back({ -25488, -6592 });
+ ring1_0.push_back({ 10547, -19771 });
+ ring1_0.push_back({ 31201, 8349 });
+ polygon1.push_back(ring1_0);
+
+ clipper.add_polygon(polygon1, polygon_type::polygon_type_clip);
+
+ mapbox::geometry::polygon<T> polygon2;
+ mapbox::geometry::linear_ring<T> ring2_0;
+ ring2_0.push_back({ -40430, -3076 });
+ ring2_0.push_back({ -26367, -18454 });
+ ring2_0.push_back({ 34277, -4834 });
+ ring2_0.push_back({ 33838, 17136 });
+ ring2_0.push_back({ -40430, -3076 });
+ polygon2.push_back(ring2_0);
+
+ clipper.add_polygon(polygon2, polygon_type::polygon_type_subject);
+
+ mapbox::geometry::multi_polygon<T> solution;
+ clipper.execute(clip_type_union, solution, fill_type_even_odd, fill_type_even_odd);
+
+ REQUIRE(solution.size() == 2);
+ // Check first polygon number of rings
+ REQUIRE(solution[0].size() == 2);
+ // Check second polygon number of rings
+ REQUIRE(solution[1].size() == 1);
+
+ // Check Ring 1
+ REQUIRE(solution[0][0].size() == 5);
+ CHECK(solution[0][0][0].x == -70312);
+ CHECK(solution[0][0][0].y == -55285);
+ CHECK(solution[0][0][1].x == 85254);
+ CHECK(solution[0][0][1].y == -30747);
+ CHECK(solution[0][0][2].x == 58008);
+ CHECK(solution[0][0][2].y == 80592);
+ CHECK(solution[0][0][3].x == -79102);
+ CHECK(solution[0][0][3].y == 0);
+ CHECK(solution[0][0][4].x == -70312);
+ CHECK(solution[0][0][4].y == -55285);
+
+ REQUIRE(solution[0][1].size() == 5);
+ CHECK(solution[0][1][0].x == -65918);
+ CHECK(solution[0][1][0].y == -32502);
+ CHECK(solution[0][1][1].x == -50098);
+ CHECK(solution[0][1][1].y == 4394);
+ CHECK(solution[0][1][2].x == 44824);
+ CHECK(solution[0][1][2].y == 42149);
+ CHECK(solution[0][1][3].x == 51855);
+ CHECK(solution[0][1][3].y == -21089);
+ CHECK(solution[0][1][4].x == -65918);
+ CHECK(solution[0][1][4].y == -32502);
+
+ CHECK(solution[1][0].size() == 11);
+}
+
+TEST_CASE("simple test of entire vatti - reverse output") {
+
+ mapbox::geometry::wagyu::wagyu<T> clipper;
+ mapbox::geometry::polygon<T> polygon0;
+ mapbox::geometry::linear_ring<T> ring0_0;
+ ring0_0.push_back({ -79102, 0 });
+ ring0_0.push_back({ -70312, -55285 });
+ ring0_0.push_back({ 85254, -30747 });
+ ring0_0.push_back({ 58008, 80592 });
+ ring0_0.push_back({ -79102, 0 });
+ polygon0.push_back(ring0_0);
+
+ mapbox::geometry::linear_ring<T> ring0_1;
+ ring0_1.push_back({ 44824, 42149 });
+ ring0_1.push_back({ 51855, -21089 });
+ ring0_1.push_back({ -65918, -32502 });
+ ring0_1.push_back({ -50098, 4394 });
+ ring0_1.push_back({ 44824, 42149 });
+ polygon0.push_back(ring0_1);
+
+ clipper.add_polygon(polygon0, polygon_type::polygon_type_subject);
+
+ mapbox::geometry::polygon<T> polygon1;
+ mapbox::geometry::linear_ring<T> ring1_0;
+ ring1_0.push_back({ 31201, 8349 });
+ ring1_0.push_back({ 4834, 19771 });
+ ring1_0.push_back({ -25488, -6592 });
+ ring1_0.push_back({ 10547, -19771 });
+ ring1_0.push_back({ 31201, 8349 });
+ polygon1.push_back(ring1_0);
+
+ clipper.add_polygon(polygon1, polygon_type::polygon_type_clip);
+
+ mapbox::geometry::polygon<T> polygon2;
+ mapbox::geometry::linear_ring<T> ring2_0;
+ ring2_0.push_back({ -40430, -3076 });
+ ring2_0.push_back({ -26367, -18454 });
+ ring2_0.push_back({ 34277, -4834 });
+ ring2_0.push_back({ 33838, 17136 });
+ ring2_0.push_back({ -40430, -3076 });
+ polygon2.push_back(ring2_0);
+
+ clipper.add_polygon(polygon2, polygon_type::polygon_type_subject);
+
+ mapbox::geometry::multi_polygon<T> solution;
+ clipper.reverse_rings(true);
+ clipper.execute(clip_type_union, solution, fill_type_even_odd, fill_type_even_odd);
+
+ REQUIRE(solution.size() == 2);
+ // Check first polygon number of rings
+ REQUIRE(solution[0].size() == 2);
+ // Check second polygon number of rings
+ REQUIRE(solution[1].size() == 1);
+
+ // Check Ring 1
+ REQUIRE(solution[0][0].size() == 5);
+ CHECK(solution[0][0][0].x == -70312);
+ CHECK(solution[0][0][0].y == -55285);
+ CHECK(solution[0][0][1].x == -79102);
+ CHECK(solution[0][0][1].y == 0);
+ CHECK(solution[0][0][2].x == 58008);
+ CHECK(solution[0][0][2].y == 80592);
+ CHECK(solution[0][0][3].x == 85254);
+ CHECK(solution[0][0][3].y == -30747);
+ CHECK(solution[0][0][4].x == -70312);
+ CHECK(solution[0][0][4].y == -55285);
+
+ REQUIRE(solution[0][1].size() == 5);
+ CHECK(solution[0][1][0].x == -65918);
+ CHECK(solution[0][1][0].y == -32502);
+ CHECK(solution[0][1][1].x == 51855);
+ CHECK(solution[0][1][1].y == -21089);
+ CHECK(solution[0][1][2].x == 44824);
+ CHECK(solution[0][1][2].y == 42149);
+ CHECK(solution[0][1][3].x == -50098);
+ CHECK(solution[0][1][3].y == 4394);
+ CHECK(solution[0][1][4].x == -65918);
+ CHECK(solution[0][1][4].y == -32502);
+
+ CHECK(solution[1][0].size() == 11);
+}
+
+TEST_CASE("simple test of entire vatti -- ring input and output as int32_t") {
+
+ using T2 = std::int32_t;
+
+ mapbox::geometry::wagyu::wagyu<T> clipper;
+ mapbox::geometry::polygon<T2> polygon0;
+ mapbox::geometry::linear_ring<T2> ring0_0;
+ ring0_0.push_back({ -79102, 0 });
+ ring0_0.push_back({ -70312, -55285 });
+ ring0_0.push_back({ 85254, -30747 });
+ ring0_0.push_back({ 58008, 80592 });
+ ring0_0.push_back({ -79102, 0 });
+ polygon0.push_back(ring0_0);
+
+ mapbox::geometry::linear_ring<T2> ring0_1;
+ ring0_1.push_back({ 44824, 42149 });
+ ring0_1.push_back({ 51855, -21089 });
+ ring0_1.push_back({ -65918, -32502 });
+ ring0_1.push_back({ -50098, 4394 });
+ ring0_1.push_back({ 44824, 42149 });
+ polygon0.push_back(ring0_1);
+
+ clipper.add_polygon(polygon0, polygon_type::polygon_type_subject);
+
+ mapbox::geometry::polygon<T2> polygon1;
+ mapbox::geometry::linear_ring<T2> ring1_0;
+ ring1_0.push_back({ 31201, 8349 });
+ ring1_0.push_back({ 4834, 19771 });
+ ring1_0.push_back({ -25488, -6592 });
+ ring1_0.push_back({ 10547, -19771 });
+ ring1_0.push_back({ 31201, 8349 });
+ polygon1.push_back(ring1_0);
+
+ clipper.add_polygon(polygon1, polygon_type::polygon_type_clip);
+
+ mapbox::geometry::polygon<T2> polygon2;
+ mapbox::geometry::linear_ring<T2> ring2_0;
+ ring2_0.push_back({ -40430, -3076 });
+ ring2_0.push_back({ -26367, -18454 });
+ ring2_0.push_back({ 34277, -4834 });
+ ring2_0.push_back({ 33838, 17136 });
+ ring2_0.push_back({ -40430, -3076 });
+ polygon2.push_back(ring2_0);
+
+ clipper.add_polygon(polygon2, polygon_type::polygon_type_subject);
+
+ mapbox::geometry::multi_polygon<T2> solution;
+ clipper.execute(clip_type_union, solution, fill_type_even_odd, fill_type_even_odd);
+
+ REQUIRE(solution.size() == 2);
+ // Check first polygon number of rings
+ REQUIRE(solution[0].size() == 2);
+ // Check second polygon number of rings
+ REQUIRE(solution[1].size() == 1);
+
+ // Check Ring 1
+ REQUIRE(solution[0][0].size() == 5);
+ CHECK(solution[0][0][0].x == -70312);
+ CHECK(solution[0][0][0].y == -55285);
+ CHECK(solution[0][0][1].x == 85254);
+ CHECK(solution[0][0][1].y == -30747);
+ CHECK(solution[0][0][2].x == 58008);
+ CHECK(solution[0][0][2].y == 80592);
+ CHECK(solution[0][0][3].x == -79102);
+ CHECK(solution[0][0][3].y == 0);
+ CHECK(solution[0][0][4].x == -70312);
+ CHECK(solution[0][0][4].y == -55285);
+
+ REQUIRE(solution[0][1].size() == 5);
+ CHECK(solution[0][1][0].x == -65918);
+ CHECK(solution[0][1][0].y == -32502);
+ CHECK(solution[0][1][1].x == -50098);
+ CHECK(solution[0][1][1].y == 4394);
+ CHECK(solution[0][1][2].x == 44824);
+ CHECK(solution[0][1][2].y == 42149);
+ CHECK(solution[0][1][3].x == 51855);
+ CHECK(solution[0][1][3].y == -21089);
+ CHECK(solution[0][1][4].x == -65918);
+ CHECK(solution[0][1][4].y == -32502);
+
+ CHECK(solution[1][0].size() == 11);
+}
+
+TEST_CASE("simple test of entire vatti -- all processing as int32_t") {
+
+ using T2 = std::int32_t;
+
+ mapbox::geometry::wagyu::wagyu<T2> clipper;
+ mapbox::geometry::polygon<T2> polygon0;
+ mapbox::geometry::linear_ring<T2> ring0_0;
+ ring0_0.push_back({ -79102, 0 });
+ ring0_0.push_back({ -70312, -55285 });
+ ring0_0.push_back({ 85254, -30747 });
+ ring0_0.push_back({ 58008, 80592 });
+ ring0_0.push_back({ -79102, 0 });
+ polygon0.push_back(ring0_0);
+
+ mapbox::geometry::linear_ring<T2> ring0_1;
+ ring0_1.push_back({ 44824, 42149 });
+ ring0_1.push_back({ 51855, -21089 });
+ ring0_1.push_back({ -65918, -32502 });
+ ring0_1.push_back({ -50098, 4394 });
+ ring0_1.push_back({ 44824, 42149 });
+ polygon0.push_back(ring0_1);
+
+ clipper.add_polygon(polygon0, polygon_type::polygon_type_subject);
+
+ mapbox::geometry::polygon<T2> polygon1;
+ mapbox::geometry::linear_ring<T2> ring1_0;
+ ring1_0.push_back({ 31201, 8349 });
+ ring1_0.push_back({ 4834, 19771 });
+ ring1_0.push_back({ -25488, -6592 });
+ ring1_0.push_back({ 10547, -19771 });
+ ring1_0.push_back({ 31201, 8349 });
+ polygon1.push_back(ring1_0);
+
+ clipper.add_polygon(polygon1, polygon_type::polygon_type_clip);
+
+ mapbox::geometry::polygon<T2> polygon2;
+ mapbox::geometry::linear_ring<T2> ring2_0;
+ ring2_0.push_back({ -40430, -3076 });
+ ring2_0.push_back({ -26367, -18454 });
+ ring2_0.push_back({ 34277, -4834 });
+ ring2_0.push_back({ 33838, 17136 });
+ ring2_0.push_back({ -40430, -3076 });
+ polygon2.push_back(ring2_0);
+
+ clipper.add_polygon(polygon2, polygon_type::polygon_type_subject);
+
+ mapbox::geometry::multi_polygon<T2> solution;
+ clipper.execute(clip_type_union, solution, fill_type_even_odd, fill_type_even_odd);
+
+ REQUIRE(solution.size() == 2);
+ // Check first polygon number of rings
+ REQUIRE(solution[0].size() == 2);
+ // Check second polygon number of rings
+ REQUIRE(solution[1].size() == 1);
+
+ // Check Ring 1
+ REQUIRE(solution[0][0].size() == 5);
+ CHECK(solution[0][0][0].x == -70312);
+ CHECK(solution[0][0][0].y == -55285);
+ CHECK(solution[0][0][1].x == 85254);
+ CHECK(solution[0][0][1].y == -30747);
+ CHECK(solution[0][0][2].x == 58008);
+ CHECK(solution[0][0][2].y == 80592);
+ CHECK(solution[0][0][3].x == -79102);
+ CHECK(solution[0][0][3].y == 0);
+ CHECK(solution[0][0][4].x == -70312);
+ CHECK(solution[0][0][4].y == -55285);
+
+ REQUIRE(solution[0][1].size() == 5);
+ CHECK(solution[0][1][0].x == -65918);
+ CHECK(solution[0][1][0].y == -32502);
+ CHECK(solution[0][1][1].x == -50098);
+ CHECK(solution[0][1][1].y == 4394);
+ CHECK(solution[0][1][2].x == 44824);
+ CHECK(solution[0][1][2].y == 42149);
+ CHECK(solution[0][1][3].x == 51855);
+ CHECK(solution[0][1][3].y == -21089);
+ CHECK(solution[0][1][4].x == -65918);
+ CHECK(solution[0][1][4].y == -32502);
+
+ CHECK(solution[1][0].size() == 11);
+}
diff --git a/tests/util/boost_geometry_adapters.hpp b/tests/util/boost_geometry_adapters.hpp
new file mode 100644
index 0000000..837f71d
--- /dev/null
+++ b/tests/util/boost_geometry_adapters.hpp
@@ -0,0 +1,124 @@
+#include <mapbox/geometry/line_string.hpp>
+#include <mapbox/geometry/multi_polygon.hpp>
+#include <mapbox/geometry/point.hpp>
+#include <mapbox/geometry/polygon.hpp>
+
+#include <boost/geometry.hpp>
+#include <boost/range/iterator_range_core.hpp>
+
+#include <cassert>
+
+namespace boost {
+namespace geometry {
+namespace traits {
+
+template <typename CoordinateType>
+struct tag<mapbox::geometry::point<CoordinateType>> {
+ using type = point_tag;
+};
+
+template <typename CoordinateType>
+struct coordinate_type<mapbox::geometry::point<CoordinateType>> {
+ using type = CoordinateType;
+};
+
+template <typename CoordinateType>
+struct coordinate_system<mapbox::geometry::point<CoordinateType>> {
+ using type = boost::geometry::cs::cartesian;
+};
+
+template <typename CoordinateType>
+struct dimension<mapbox::geometry::point<CoordinateType>> : boost::mpl::int_<2> {};
+
+template <typename CoordinateType>
+struct access<mapbox::geometry::point<CoordinateType>, 0> {
+ static CoordinateType get(mapbox::geometry::point<CoordinateType> const& p) {
+ return p.x;
+ }
+
+ static void set(mapbox::geometry::point<CoordinateType>& p, CoordinateType x) {
+ p.x = x;
+ }
+};
+
+template <typename CoordinateType>
+struct access<mapbox::geometry::point<CoordinateType>, 1> {
+ static CoordinateType get(mapbox::geometry::point<CoordinateType> const& p) {
+ return p.y;
+ }
+
+ static void set(mapbox::geometry::point<CoordinateType>& p, CoordinateType y) {
+ p.y = y;
+ }
+};
+
+template <typename CoordinateType>
+struct tag<mapbox::geometry::linear_ring<CoordinateType>> {
+ using type = ring_tag;
+};
+
+template <typename CoordinateType>
+struct point_order<mapbox::geometry::linear_ring<CoordinateType>> {
+ static const order_selector value = counterclockwise;
+};
+
+template <typename CoordinateType>
+struct tag<mapbox::geometry::polygon<CoordinateType>> {
+ using type = polygon_tag;
+};
+
+template <typename CoordinateType>
+struct ring_mutable_type<mapbox::geometry::polygon<CoordinateType>> {
+ using type = mapbox::geometry::linear_ring<CoordinateType>&;
+};
+
+template <typename CoordinateType>
+struct ring_const_type<mapbox::geometry::polygon<CoordinateType>> {
+ using type = mapbox::geometry::linear_ring<CoordinateType> const&;
+};
+
+template <typename CoordinateType>
+struct interior_mutable_type<mapbox::geometry::polygon<CoordinateType>> {
+ using type =
+ boost::iterator_range<typename mapbox::geometry::polygon<CoordinateType>::iterator>;
+};
+
+template <typename CoordinateType>
+struct interior_const_type<mapbox::geometry::polygon<CoordinateType>> {
+ using type =
+ boost::iterator_range<typename mapbox::geometry::polygon<CoordinateType>::const_iterator>;
+};
+
+template <typename CoordinateType>
+struct exterior_ring<mapbox::geometry::polygon<CoordinateType>> {
+ static mapbox::geometry::linear_ring<CoordinateType>&
+ get(mapbox::geometry::polygon<CoordinateType>& p) {
+ return p.at(0);
+ }
+
+ static mapbox::geometry::linear_ring<CoordinateType> const&
+ get(mapbox::geometry::polygon<CoordinateType> const& p) {
+ return p.at(0);
+ }
+};
+
+template <typename CoordinateType>
+struct interior_rings<mapbox::geometry::polygon<CoordinateType>> {
+ static boost::iterator_range<typename mapbox::geometry::polygon<CoordinateType>::iterator>
+ get(mapbox::geometry::polygon<CoordinateType>& p) {
+ return boost::make_iterator_range(p.begin() + 1, p.end());
+ }
+
+ static boost::iterator_range<typename mapbox::geometry::polygon<CoordinateType>::const_iterator>
+ get(mapbox::geometry::polygon<CoordinateType> const& p) {
+ return boost::make_iterator_range(p.begin() + 1, p.end());
+ }
+};
+
+template <typename CoordinateType>
+struct tag<mapbox::geometry::multi_polygon<CoordinateType>> {
+ using type = multi_polygon_tag;
+};
+}
+}
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mapbox-wagyu.git
More information about the Pkg-grass-devel
mailing list