pax_global_header00006660000000000000000000000064131406222070014507gustar00rootroot0000000000000052 comment=0ff3b5f82908af45f32266b8cb5f107c86147d87 wagyu-0.4.3/000077500000000000000000000000001314062220700126475ustar00rootroot00000000000000wagyu-0.4.3/.clang-format000066400000000000000000000010141314062220700152160ustar00rootroot00000000000000Standard: 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 wagyu-0.4.3/.gitignore000066400000000000000000000001231314062220700146330ustar00rootroot00000000000000test fixture-tester fuzzer build *.swp mason_packages *.dSYM tests/output-polyjson wagyu-0.4.3/.gitmodules000066400000000000000000000002001314062220700150140ustar00rootroot00000000000000[submodule "tests/geometry-test-data"] path = tests/geometry-test-data url = https://github.com/mapnik/geometry-test-data.git wagyu-0.4.3/.travis.yml000066400000000000000000000066001314062220700147620ustar00rootroot00000000000000language: 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 wagyu-0.4.3/CHANGELOG.md000066400000000000000000000046131314062220700144640ustar00rootroot00000000000000# 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 ## 0.4.3 - Use `::llround()` instead of `std::llround()` for old libstdc++ compatibility. wagyu-0.4.3/LICENSE000066400000000000000000000035101314062220700136530ustar00rootroot00000000000000Parts 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. wagyu-0.4.3/Makefile000066400000000000000000000076151314062220700143200ustar00rootroot00000000000000BOOST_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' -D'CLIPPER_PATH_IMPL=mapbox::geometry::linear_ring' -D'CLIPPER_PATHS_IMPL=mapbox::geometry::polygon' -D'CLIPPER_IMPL_INCLUDE=' 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)) wagyu-0.4.3/README.md000066400000000000000000000020251314062220700141250ustar00rootroot00000000000000## 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. wagyu-0.4.3/docs/000077500000000000000000000000001314062220700135775ustar00rootroot00000000000000wagyu-0.4.3/docs/README.md000066400000000000000000000026211314062220700150570ustar00rootroot00000000000000## 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) wagyu-0.4.3/docs/bad_intersection.png000066400000000000000000002621351314062220700176320ustar00rootroot00000000000000PNG  IHDRd; iCCPICC ProfileHWXS[R -)7AzޥJ! J AŎ,*v誈m-Q,,ETu7I]|3ϙsܙrPr˜`?fRr $)0bEpewm\UW8#bB| \-@hz)~H 9֑49Cb 3Pg3`%Ķ|vؙ,΀X ywq23m4&1Ȅ rXroa5S#mo0)ܑ( n8$~ؾ-5 PaA k2؞%B{47ӄ3b8K3#+ IB Wz03.Qm*%DBq(;6laa䈍P#l taPٰY4!ό bI\QR7 Pp0b}K9X%7'8F^g차 vķ#.0yYr؀ ?:N A8 İ Z{z?H`!\`=Ha qhO6PeT+Al@ Bk^{qWmď<2+1@ !-Fy!؄otaɅIGrNxLIDej:Hs&-h84g7p?q qG/ ssG}IYϰ^RiE1w5g؏R(֌.c':`X vJGWJ-F-~aBY ?C0[g/2gیc9 ?o6¸MwRcp)o:7p{T[,,piG wFd`"q LUL0,% f {pԁ6p܃k}` "BBhB G\/$ Gbd$@,Fʐ5fdR@!vA P .jG]Q_4 ChZ+ЍhEϡWћ}cSfbXcBl>VcUA>D3qk>Cxf|/^7Gx@#PB!0PB('&'\{0@$D3 ܛ,rV!Yb;O"HV$ORE'6ΐ:HݤdE>ٞDN!Er>iryPAED]!J0[a.k UœGɢ,l\ܧUTT4TtSS\Q%GjTK?u UL]AC=KC}KLi>Z>mvAdQZTTԡJYADWyrrQkʽ* ***,**'T:UUvQU^V}FR3U TT;EFt:~ޭNT7SUR/S?ުާᨑ1KB㔆1LJ-Ƨ1c|p,spLǘc5}44oj~bjjek֪zk[jOҞ]}AwX챥cX٩Ӣӯ+ݤ{^W磗N^>]KN ӗlb v 2|`D1r5J7Zghgoa<׸IffKLLi՘7{W߰ ZZd[lhD-,3-+,YVVe:gf7ѮȮ=۾!aCkG+Gcm'SF/.B=...[\:]]]^r#-p;=_B Y2qĦ0jXl4bbڈ&Ⱥ(6AYt^o'ULzc379;=v_@_ʸ{) $J'KKO!$N8y)NSJܚj6uӴL;5]y:kTBjbϬ(V?-4mKZ۟Yzrp{Iᙱ6';<{-}vT졜ĜC|5~6iތY3V$=o}^0L[QEl.ĪGgjm9{gA45hyvGo\`xA{Qe/ȶhMѻʼnuwSMRsǒmK񥼥mZSz̶r+?+ZW:\E\_ukkTZvs]w맯\XmexdcMƛVm9s C[t,~+gkGOmʶ}~{G*Ӫĝ;J/ջwG7foSKu>}+kqM)?h}p!ơ_Su$HQףr~]WY'Oo?1DcGl~sd)S+OSN:Sxl﹌s]O:iRS녰 .]<|祓/z;~չ˵6 ;;]~F荫7#oߊusJ6;9w^-;xo}*zDswSgϪ??b%cҗZzZos|p w`}{?~lϤX|iPА%dɎlhz:o@Kgx(_2AwF h2'=>/ Q*a3 w@F۰屨C04VR_CC[dp6O~ kJQK QlC pHYs%%IR$iTXtXML:com.adobe.xmp 868 962 ?biDOT(*@IDATxwE9`D1}fQ0`9̨9GsEEkFEDs:i=hwfgj3S]۳YE7`iرckSwßpV_+.o5&w;cY!L2S v$J]Ivy{4 d萙B | \#w7ܵU$ʽB@l N^l_:X̛;Gd.]E域/*ȝwUE.7W>}EV2q"5})N?S:O[om6-[oE}{oST-77营q Njҕ͉.%8]&>O_+w:d :dCV9VN,.tb7?Q Eyy^W~3Mvl,(}+Wٮ}\tT]f-"YUQMeUdӧOrf~ɇRepիWTUo=7F^pe"[l)2eee{"6BdZRU--]*KkM}>_~#d3_{g_ɪZoMWJE"{HO&]vd4 sŸXF~/8Cx"ߞ;%r~=4٢y3?<-d"v\/Q$9RzE|*pI@UԢ|wq"Ͻ&\sMf>zߝRe؃wa"O9CU/<|9O>HAj1w}Hf _|9曑ϝ*"o 'i+F(TꉠCUC2CVy!|EsiCfyi:d&CI(;y+I,ybHB~ƈ<:EqVw_|k}|G~vWx+B79%9eyX=Sҫ+sqأ|wDZiO'&ƎT_XQۭ/V lܸqʢhr4Jij֬^-:k%QC'ߊӏ>@dfmD~?+Hߋ*1r("X :m/FE ZzUHzD>9ҟ܉/Jf~.r#y~@?}ϮRK"tG$}jXTܫKMg#8Bd/Z(UCdHz]vIV \?I/X]"vП(O ͋W&r d:fI q`\LEj=0ׇTE-z~ww"E-hfnU@3 M/9D׮UC#UT;6ADwHkq}HVJթoCb{ꩰ'}Hծp`f/U￿V+"*+v/'޽{{U'sGwس0cV4rBJUuTw~ҲJu׶usRu /Vz/yWuB闞!?|+u]%V)ݏLH^&Y?=+m͵"lzHWJI;HT+3!Y I5E<>\pd5nH >n N<ȤT뽌,^ H>7Fdok/nx_y ߜq'ޱ,K."=/t*(%2{X;f+:co;K'Nҟ};S#dͥ§""0~>. U$/~TU\ըѲA"{`GbH%~JQ&~;+W+)򪫮DKC|saX=$ιj-贖}ZV+vC++r=z6!RT}}Im Dy+v{iK&X{?X[?{zKJ-[L>>pȉ^K`Լ߁6w9K_Y3㎖נ 0|";>c~!?O=z\4zEawɡ_9aJy;0y/*ڑȫ}N>8J뇢z%|ޒӐEUGzEȡS{zvW(9XRi;wߍ쫯Z Dn y*;A:18 F;oDDrr1{7iyā]zFz/(0{v1t{aED/\-끮wqՠ!^ <@]ETD|ڀ.\gH|tFqVez}#;[eDD|Xo* 33|zz~ ЬC3Yn^RW`rj4nX=i|}'d͏l\yg~‘Ȟ/#7|m/K$NdZHщg > zvuz مp y8Lo\ʝ]I_Uϙj!v Y:d2@L={e2<`!H M:dt2%2{UEj%m%Wz~To ߽._̑daEjz8W,X+?$ Na"VNWOM+}w8O;_pxOH\"2ڞ/.ŠOX>7f}ʱ6e"5-uyֲPM2xtZU} Kd /d5CnJKoN5Wvb~]Gn_JN}:Z''?Y]QN}\+пN@džXP@zblr<"pњ=4_/XX?9|zk04,Z-9G]*=!N+F?9yώ"JXqbgˉQQѲWUBA|dw 1jϿ QP?poqa'U6͟MiAW{f_;] ;LEڈ4]=^Y i ϡad3ևBƫW+YGgK֟CNz>7SMUqƽsS+;xv(v)g@ޯb,NQ"Sp?ׇNO0n:9 +:rfǴ2[+ʝ'lV꼁v|_ԽVZV=K?vTkJ~ÒRJΧGC aEjvXQkfLTt+{mUrW" [.x 菵яl#"otT 7*rGKX?ϺW*?ERҵ"/ [>=F̫*0.8OE'*3C 7-7hN,V$_퇕?^4ĴSz%s>*8z=p}FbfHw'>+JDj8Z復`f=/=ϖj^Ԇ=]>Q9|ܨfq1=g|Ȯ:-;i? t:U Yx"t*C,::dѹ\:d?ɑ#~y3喨:_O]ȎR+Fj/F~=`vVJ,OΑCvsIEDzŻrT8}TՑs{JH~K'k δnu; ƾXaouVTYAi"9ֺVԾUfIFȴogEVԒm9"X= OJ)e3|߆PFXKT. ;REȰ0',׿~=#EtCֽq9`#V6ߜFȴ5O#RֿBjL_/y<v8.\lK9znU+o\YO6 H-B/NYX^_[9x"dZhZq_vmZL}f9"M*ڭfU0QZ!RVT[>yS'R\wyʟوdmyRiehijUgn{sj2e`o4_W!l!K7lfd5ևER p=,\sV/G*MF}qgӸ i'qޫh_ˇp9RŰaگ~ 8R!3!3y$C&E:tȢsѹt4Yt.:&]!E!$LIM!I.MFx/Pg㪻9%V]s8<WRHzXU])jDWJ'抾mՊکXQj$Lbj$dS+j)Ft6NBev;瞷2ʂVKUo"U_݋VD|bGu Yrɚ7۝XѸhR﬐}JxVR6iv_DkC+^U}tȢsѹt4Yt.KQ>yaby1"P&rY]6gm}"BY!]9ʟ_Xɯ 8Cy #U;IAQu5ۻܺح> ~v{-'oed|R{=տ:/a6T=r,wݾ bZG>ɢX;Wס{B5.SP&FwH~[S?ۊ}cYa-Wh">$?ܯ?ow]dP1Bf2P!!dѰ>fJ̟YRC!eIYMQ2˽r7ş=&=r ;UDdv~ *wT/TUk; ?VVEܫ"^{,2vgr/=}?|4ANNoҌ=B#aQ>AwßpV_+ʝqb2K/<ݘEk&~~,A)o tz^ᮭ&aW]^k%b'6c8P&+`鐙<ҖC68l7Lrwۓ?ᮭ&aW;2ö5:dO~'b1+ +x˞x; >'rw-5s]65$w7ܵU$JrkD½{<^Sβ(V!3!3y-E,mh3RqXn(OnkIؕaNpe+U؝;o8t"k_EBiF rw][%M®$wKN"D !kKVȉ,[ߝȐt̎Cf|YȗFɄH ,wßpV_+XPkt1U5kJ])zDnA>ڒns#dU (&m"tҭZ7W(%=TIdPO@DlZqP'n-*d'!3!3y&E,4]TCrH!Dn;'w7ܵU$ʠpC#N8\tV|f/+l%]o-* ʍ)wßpV_+]s/[,'D>H"5 WK~cI:d&Ф萅jER΀J rw][%M®t͝c!2?ek!ۺ+uT"kT'"N[=)/TI5:*$w7ܵU$JrkD]q//^7Dc'o9ra;ctLO! |@W7 _b% rw][%M®tŝ9鐙<8qSYR\$2++KdvvȲ2&f?WDۙMϒ*kv%F^"vҮh'uvN8`V萙BvMZF RrwY?ᮭ&aWN LtL*([w:`G9g! P۾L/GnzpV_+.o5ͽS|oh@!3;#cRtݕo_?h"d^SgO^umsCf22Gƥ׬>ɹ97's}Qըlrw][%M®$wKN:]KHl9_mnAtLC.M׍"4쵚fQIqeȿ2:;.t3:d&':d&MȹmwUEʦ\V>L[u+=y*Rr~" f."7 wm5 2U˰g/؋΋ Α?JZe+f!3yd|Y88E82x$w}Bn*kve鐥_tL~tLZhx Pvv)/X ka/SٱΩ࿴HFebrw?ᮭ&aWʽRiqSEXgQZ򹹹T|:dfw!3yd|Y88E82x$w}Bn*kve鐥_tL~tLL)މ~;ɑ#Ei+(ڸ VNn;'w7ܵU$dvt=6 :dIމBn'{̳Fn'w7ܵU$d!󧟼ϙh :dU=gƧ%sJ!˜L({]JS/]+GVlF]-/,#t|.E~ML'˯rRҹ~xn٢)4o9}'~ O@ϙ) Qe:d!,Mw!KOUi%8o,|ktϙv[2wm9щR +LّCe%A'B˿d42RC3LEB/{Sۈ?oSc33Ű1na¯3)H93%c!L,䢉N:dQtR^)$#jܣbIP4w:dkD35̑!:dla:r*/.٠lZ@kdOpV}C"O [ʕ=e)) %]9%s(z/ru8)93%#!L,DeN:dX7XVM5 7 V9ŝ9Nk[Cق&J^vtԩ(W#Ӗ#:D5FźQ.rwۓ?ᮭ&aWz/ ?"gNI,)]AҵjI%Psf^Y{0Mj!Kx{H5j]p#ɟpk /w:dvLqo>d bMUVI{oj9"AZ4c|\S4WFQIQ3 U4{׫TU^<02_*h[./z^L m۶F9&#931-SYgk!sLeFzDq+%QZ+Ir>a==27=9Mk[C'A' \?PV#euQ!h$DhW66 yx^-^B:VOލ{˱W,:W3Ò? m_uM_ҦM)x93萅~' :7xU"O[ΝY#z֧C~t{6[C:ÌԐrI s08? %qʁBnc("A#hܨգtn74jk:Em ±$-o5ۡ#cm}Z@v=d39%Zsg٤IvBP#TY:#HMIDC_/'KDG42$2G萹t=g=!K]FLD9Å߬SVXg:Sd盺/ S7Knk_3UZxhʹb݅DUN?X";sF-ی:զ:ZޭoiO67~}hS 'o-.!UDFa(H&GlQ]z#ܗ>'t{1j>: sna:t?gmCtO:dw$oЕIQrO?@C}|dZ.`h3qYUI[3&"Tһg/*`=p;L2=Tm@T`n:4roryGzs\#潴U%"+¿/UovhdF5]RuR>4m\DUE-??$jpQMČz=Ufvr']0~a5h_t@IDATvxbp(E~:3"q}DƮj _Ku[ċ"rA*Y+o:cǎ Yenl=g vښ(tȢw,oѹ;7(!{!>ERs鐅kz :da)=Q9S6L_O`2xiP%XF7QEƴf]9CHd[rtKY$5۟ͺ.TbvjQ}0z9X1rȍ刘Խ{vˋ]9#sSJ]ץGj ._9ġ(Qac=9~F{zG+&FJe.c:w N"Wd#°==与$ma(\?|y[1oY{nvadz٪_T$KGa'|/֡{@z#rVKd-gVmDtY+пcuYaU2̬{/sMяb\vqៈXi!CD2BfĠL7gU:dV%mO:d103"m!3y!Ã42s\0\k93<DY0.}9B0$V\EgχTc Z澽 𞷎Xq{\nH/mn-P@nJnňHL?5MH[gI֩ j| J UJoבuw!Rs\F*ؑ!8 KޣەC^SOk%k4F&}%X a"WUIЯUA0Uvt$k|y D.GtjE=XD>k_Q:uaSC]ey2D¾x.+ZeD@_d9ِ=žah oD~>m3/"*ӡuKh2D̞ R֢9\E@;^V[Se; "ҼJK/$2Ȳi6G,it]DC}Ks1-c9Zt!CAYuY(sfX! KOYng'YXurfS(R,+Dv~+yY{F,jeoLËp #EU*2>'On] 1Xq\9~_1"nFGÐnM{oCd Ecrb.@WOQ{r#-={J#sDv[n=DA(5UDz랎 (׫KANEv;;S{Rҟzsq=Hd"_z%oZ'~;O?7Po]语ک/^櫈~r:D&ˇx*װT/W[_f2zGW"/)+/ 9rcFMu'dF/ }ш9n_F$#dn=ϙn*ytȒg5=QU>|xO{,+V~9R :dxԎ25)-萅 ?,_5Q ~QNdv$V{aC퓛K:[WjjF~Lc|n̓[F=[o+9n÷0ʥHu'~h~-ȋxI]xL=:d~0=(t(ARN ttD,>Nեpt3qLM3.9]eRDʭ( t;>uMK7w޾m1"aSLDv@dE=mVF# ig7ގ:޳OU#2wDXQ u}Qӝܡ5$=\5w#REkഢ E`+U[dt~%[~N%p-˻_vtmO4ݥ \ִ=DE6أ>oU$SgݥBxRgoUwK1;񓕒=yNu'ƅr'u8MZ}ͥ׈}4}#rz̭= i 2&A hG ~0Cf"2GXdOr>(ptО3]ӣCj?b {V1f1Ha^1]ܵ# Hy/ 0a~R}΍#r#-_D"tZ  5jLǔz짉 wP]ylx}!Bd? qrK=7oq^=l'Wכ>H>L/^_2mg/`_Cjoo~Et׭CA i Y>de;ɶ'lzs6"`x-)}&LJsHFȢJ{fP3~1 !gu!#3矮#:dt4$6H| sf|2fƠOAW+Y+"^E`OXovx-. hͽ+嫈d]Y0~ߨ9rR uvW^mbOڇaQU:>gVr=X"5UՏxS裏 vW;oCDSN)qZ 0k!?ƥA[opGu'RlS$ێիPo-yeܯ BПs:&萙QqFt%:d42:d~%z٤?gƕkCV9j{4,傫Η>W"ktބI]7;;V:Ov Bqh}7)SCJ'k"fC['De蝈嫷~ŋEkj_Uch V^^]"d/@)Gln!3sJ…uC v% sNB'ov2S@X& 2Y? `D,юĸZZsC*}IE,|}9mNg:dba(_{;{f5t\Ԃ7փQ[=5rrjawEs%z˔(]>aw ثyؖx[ם@lo-^=_g>" դN"ĕntM86Q萅tٶELw LNKtRܺa{L7I:d&Ra(𮐾sq":PKK.T,CJm9dޓ)/3>W>ȜD̼35AP-S/Y=E$ሌ=7b{ٸ?=tIMvӓb|Y[a5dY)"MԖJӰ0IE"BC萡21c,ԑ)5ۢC? k sfY!K7D]9E0iLח-/Zr  B\s/+C[9] f/Z$g ؓ9Hޟz),`ou7Hl-Fi\Ciȧ7ԁuzi(um>~_G,5':d&L:d~p`Dltguy۟c7]t营2~K/L[4"2;A(}=ȁ"n)W#Wct};Y wk |>>H[τcÖL>a""'O[+P _'dYȬf5DvMA>?HqXl);#D^|+Q6T*?DmteٙtLL)A(t8TA (Ftt1unNG:d -sf:=n:dѨ0/!I~[u0)(Au AnVu"dkdc`cv2y%␋רƱL(W'ȉbL|P;IKE$_w~_tmz cvHN ߄N"xe`DƩ!{LY*S7S3:dɒz>QnzDVz1aWH'Y[𕫨BAл S?4K9\l u8-=}"umGm .|^zE-WUf?TutLL)>QqG##:dEʣC. u'֦ LL9DUev߭ң!)w~g t ޠErA^1J<|f4iE"|+k_ފ ^G31HLz5d*+ʏk'2lHvIAjJ!e4UxYY6Qxv.pʵRD"^B݆HDFeʃw![=cͫ#.~'RZ~"/SKqzOB~ugq?{iBݺu4=X7a!3y0TBCoAy0C=d6g$27-CYnϙUCVjzN/=d"{t=9mo.5v-sŽw~huD`l85 0zQ_tmzևB-31Kgd/[,mPd^gD;yA Y;XmRu}α co¡Cf`JYaecٔi鐅c! G?93O:dT6Q< V#È M!3eH•CVoO\:b,_9DVmd-r_!qs2ʕF'`:5{+]]wsu,Ȳ^et%k+q|%~kn)qn9qAXMtLL)m!#:dm6e w:dnG2f=gV?tȪ"TMWr'~3{b/O^)WO!kw5e,+˲h)VW[_#EZ`>5 +X[5Lq a^R w޹Ld> D6ku'UN?+GrprIwVKuV>~dX_]s,c萙gL"RP6q)Ԅ'D\yDf :Qˇ{SsO;Ͳ*-* bEzbJ䣫D/A$f};G%5b+t6Ngb(G.r}wJFߊl>ZcӟsNWfM:tLL)(:d Ѱ<!  f鐹헰\wD,Qbի<3Cf`JDo(xI)xf\o5#(:qL{}XMxݥ"MIaq :+6{kc>,omR{=diu'^xG쬞IU6I?4~sڟ?,$cr%&߄C"PC ˃p~ u'^Jt%U9:d&NĆ3/aqKClX۹ _58m2]`X.)j="R6SWڔ:T ?.Yo3kD`OYx+cк$,ם+";,?=|UuСr:tO/萙7!3y0p$6%+`EKT!K^uzݡCzRCl|4!3y0p7}v4\T($R^O*m\3Wj 7J}ΐBy{"BO ޮ! +֣kl[]=c i c q42lܷ Nq]3FavDvlYzxⓓr$ּ"7.%|qIoCf`JDo(!SXJˍ~CuC,d 9dE"P(*['+ +:!|9{ak-h7ho25to%<*j]r^jI/Z 0.BL1 ¤|^&y&uANRA4?D}"cwvx||OEnP($sv鐙omuVPwIЮ;Sq91C_{{TsΕ }{Z.|q3oCf`JDq3萹ᮭH+QI,Qbջ<22㡺XstLL)(nBzޱ W|=3~|W?'/ =%Kr4Dw߈Uϵc`22㠺YstLL)(n;o#L9SY]F4?dP8uɟu~+gP#z˰r|3^^G|s@cDhTĕ+(ם§o?K{ssɳR:pMtLL)(n27ܵՠ<*鐥JzԧC涟rݡCvT7|4{Ƀ)EPy;E1oCf`JD;k18c ȫJdv6~oo]c?3I_l?*- x)xKݨgJ^\#xh}I>e{8hAz68ڃ}ijmVZu<͖V ~5"Yi";ﲇ{/pvU}C&:d&NC]ޱ`ˎ|:dH2uY|8d鐙d5$"cK"=Ӻ@r cm_ gݿNPE`rjӈjy{O9t*Aq jg -YG^z>;Y%kA ޳;!G79!3y0p COz0򧵛kC9TMYՌYuًgLJMtLL)(nB'iY.20Dʦ Bd]"BVnO4`m=Wb{ջԢ2o."f"qct7%!Aq jtv}|[o\*|nVצS"`O]RDvHFD웉OtI3_Lb$M"v($˟YjZm:dbHQ}66&JMbtLL)(nBN}\r9"cy=Vh=h6#w^`|F\9/S> a<2/?Fl97ڱA"7+]ם6 wT{]8O"֦ ~MT}6S:/7I!3y0p C\(X03O2}5ly*oCf`JDq;Rߴ!R6<@7֡=T>: t=4#%?{4i`U͖cpݞts%r ^ƃTDl,DQQ*)鎮PFK;Eo~mOd4dO.lvY tLL)(nB%~?%׊עC~a@mo}ݡC_z K"@&:d&NC!Q7ni3n~~J}c-:^Pku<#di;HZs."O?Q n 'F%|| 'A6,ȯ)Tv}W*@v~4ɯN74k?:f D&~:d&NC!Qt/iM!K0jC!K&"@&!:d&NC!Q4{ȇYYX^k:p{׃QNk攮CĢXmD"6+h9#ݎ 9WO9o"afv;@i{ %g`Qkf1"9aF0aDQ L ADQq_=3JwfzvgjkݳW[=x.而Y[$ X*jsux^=Khgm$625'ޡ-dUYS0 m"Zf@׬z0 :K#@*!d*jNCn+W:b3UfzۭۻUnGK?Bl-Xsҥ]v*+0jix ^Ye-EV9 1+]Y5%D;{-Er;vOLYjH~ )S}=+~ @U( T (z`.g%~ZdVPFؙFr߳!ː T (zSWY- ="3nރa^v k O;.Wu:0RIPi~G>2=ٽ;l`ϝ#2BjyM1K$=܀~xR%ꕚIM T (zS#w4dxZ€,zÀ,~,8=|g@fD;n:iO~v5er}՘"NW0Jp"WB˱e4\Ȱg-ӁA0vi^Xf#ㅉhoΞ϶I*nE%u"oeNφJR^"<8Q3 CH侮;2&edz{)s==H\25'ޡM_8Pd9Ȕ@fl}mepY56 i͵Rп D0$K7}_Ӂk:@sEnf^\Oin;Gkc*c6HN<=k:B{8[3Wy3 SyP3p 3 [-nTKa 2w v2=u}i' T (zn[n}O%rȌm{+̐)y/X Ovȍ~-Ȁ+nmS&jr}"޵>N&G{am5:$Hvs3 S+<\3Jf]lhAW;ʃAEP͟YK& lf@dzjx%/<8Q7kɺV@lJڽŎ!%ܚUFQo4!i)Ȍ_4iI% 2 S UOhNqrjv~s)xn;e_Ã猑wN=//O䇟1C R9MjW3 SyP3p 3 kwM`,2ȀL߳zZ25'ޡmȜ`o( p#C_aF,݅ x/_2f3ʤUo*_ꎴҩ$_w%xLA̹+;<=wJ~<\RεnLF_Hg m%-25'ޡ?^e6 2=u}i' T (z vA+2eS|8x ֘0CwX}{[R}qY̌]AdޓU?k܋Ǫ%w>v|͇LI^jM`wؾһ ,!%W;ʃAEPp?2FzG9 2gYgZ8^_+d*jNCAmB쪘* RRFM?}zՋL{,d8^M :Z/fU`o<@B^5Ob-boB2dkN%k^Jjw@vcKP4ܥI]b՞b@fD;tg@Z:kndy:2ɀL]߳zZ> 25'ޡ(u1P95bFZnw/>bG;ҪxG*A'rd֎];/@&rې9{bǝ]*˱d'ֆ0ok7[O%Umktu~{֫@j2 SyP3p Ÿqbwdvu]d 2{j=Qgcm#<8QD_TuMe]`JB^{BV!S NN~׫X70:Icj۶a-YW [E6@ jWb\iKZ&f65j*U(m% YZWq2 SyP3p Ɵbwdvu]d2{j=Ѿgcm[#g<8QD_Z{,/S3*EKݓyi znxgLy{aV XYX\=Ϛ1߫ j(#3#5>,oXџhGWK;F*}s7bWm;l\=k F_΀LA w($*dzǍYMԝ/ Dna@fD;EDtdGz$~1ϙAލ`f`-P6K/?hEg ,X}[XZmLE>7^cvso͝H'z99'XW9 zɱ eXp6HY8fU T (zBg@wXU$m3ÀΑzIHr 25'ޡg~" o$ ۿߗ!C$fK?B앛kI]D>ּy+k kaB[U{@➗`h r '9xp}laa?dG=@ w,&74`<8Qg@wYU$i€Yy{6X~b@fD;?8Kn}&;ħub $wY L~7迆?A.[ퟎL2쮹'k[ ֪ 'ؗU_3W52@ՏwW}ې˦c_ vhVDxÆ Ƀ={*Y%525'ޡ?V{g@f5Qwc@L0 ss^{6v=ÀLA w($*ڇ미Ps` c7Jc;Uݍ \Cw{E):jȄ)sbߒAΨLY;wŗ*0pDU^ֆfRCHLkWz$f;kb|r3`c$,W3 SyP3p ʟqwdLo jD퐿 T (zB?ow^~&~iOˌ/k%{VkvN&6!Oa)'*rM[q9OcBv%Ȍš0Yk.}~NH _€LA w($dzǛwdD3 Y.k=kX풿JʃAEPHV=*KO+2-+ю`,4m3)+uE,Dw񞱇G6`ԖyȘk_nM4Tt,Jw}| K5srBg y]oʟʃAEPHV p#o d/D<8QdäNVM]SV~_v~,0f"øg1nd7)i` /GI b  kg"v,cjuFY- JIۅUŗ]e/6)wݣ#jC?#dC!rj&_ŀLA w($;dz_8 J B|j7W{ʃAEP ?#AE}5LDf%r;8̐E,@IDATR VV-#1LpAy9F_uF֛mS;"lvK|3.<kR?2#K ymd&.k%^<'Ȧn͒ ʃAEP g@w΀,<π,t1 'Q{VoϑʟʃAEP [ʉYE>},v5(l0CrJ+|O&.{pAvy_hrv޺#|]qykvWKSXC }3QrQ$25'ޡ@*d*h ՃYtE˭=g_πLA w(??{|]" Xd:MJ>A#kS|`@fD;?2.V b%E "({-CjO0 SyP3p  7lVdz01CwtIXK֨ ,Y̼i-B>Wi``@ϻr8\ܬȬf3=VJ#{QKR_yR#QRU T (zGǟYt.̀jc@7|*g<8Q gw[%2At2f"^D kö}OCj9a͔Ȕg" BŨ q#5=-b;&QHbwS?ɶ4{[5JaZU T (zƟYlܢY]Ya@W{VoOʟʃAEP >D1_"3Δ홲A6\FC`^\P WTbMXu##f5^xD>v_4py Ex8eZ}V*J*R)S]8gVA{P4\ 325'ޡ@g@pw3 G(3 Sëg,<8QOC:!22:_n7Jn$w/#2P\`M ׷ q+cP[^[#0?F\_ZeZAK?剷XV2e;gXkK/W"[F*v mC*<8Q?2k8[a@O$ud {V{~ͪ_%ɀLA w(Ov\sW7/KVY"w/bL@x> .nꊥ{k`AD7PL眅aܷ]l-ƚ|#sqm<=Xܐ|%ޚΝ;]dC%|J*ad*jNCπZ1 F{Z];~ZM4:{b@fD;Z##[m|[ȞXu i;!]5&\CsN%ȴ|0)VrCٻ2Cb `ZTVo7Ⱦ[ zNI#Uv}ʩl|ۂ5Qre@fD;Z Ȭpq2wD=W7I@2 SyP3p o .;_ kbi #Cq[#Cf I(>Y:uxol}mT ix,:b;ۇ.A?SLYM }uJiy-6k&<M,|i;n;VbϪ9>UNi~_€LA w(5Y1V+ b%} 93߳zU T (z[ooNzVdͪX+VM|ܑ A`첸s.) Y|ͻ~QbEd̟3}sXw5xج(Nϐ+_)֌}{n|U CWE'U'fm56OPrHmՎk̛k7KcmXNCm6䈣__.|`|ޭ;szO/rߗ_e΀LA w(3 ?9M\칣f5rht_ŀLA w(ḷoQO*L";.mGx!0F1{=֝]LX}N +笙pGG4h RÑ^OT" '*ƼkZ kZZ^!)EuQXwh5J䮷_πLA w(3 _E-pwr۷g@fD;_`U*tK"{Q6̐)V\Ӷ4#3)\5d/g>l*r( ǭm ysP9o1L۫y;hڀru`k!w=J*d*jNCƝYt,:^J3 E^칣f5rht_ŀLA w(;5(G\+j_k*Tŗ%Pb K;xxL .{3CfO>}s./%-\U(݃5?\nCf܋yY^5ePߪs'~ r}e_eˀLA w(Y Ȭ5dssBz<8Q>uT3O>*rƍ"SS RR!7$եw/r _#B' `1.=5Wf"kx·K7kjvo' "WwN#qr [wh%ޤIn;ϝYfj^W3 SyP3p 䯇ɝ3˙2{GXb[7 #<8Q?^7{TDf ָޯޫg 5}wU>v쑅ؑ}` Y+kZƚ lxsΝxyD~toM)C'QCz{U T (z/wdxE[hL2yLh+T<8Q*4Ew䚲@=ZTZXb) Y Z2/uf1)ֹ\n5g{B'C=:x)r䓇rg{zA {P4\ 325'ޡ@z[ŝYdǀ,2NVb@ƀ̪;V=BG_΀LA w(Vs;!ӷ'2?M7]j%.wVRX딳HL~ubaNٰT\ |ʾE*yx_WuDo"r(YP.U5"/v[W=~U$v6<8Qj "Gdqx s{j=䮷O_πLA w(vq=i{[ifʖ]L㌄ВD&!ˀjسcM-g)<8Qna H?Dl^rѵu-H }?g賾.;v4vo+͗V@]3ŘE; ۳ kײ3QUmإtJ 'AV<8Qn B+|bʀ,VrgsǙV$rgg@fD;_a-Lf黥aff +|g C2d$CV]wM3 ;d޷ՙȜ}[ 9?&.W~]V_tpx?,̄cZ)Fbwg-#mMV?w:$X'd*jNC;Ȁ,42 =v~SKY%w}J*d*jNCW_% ye"SS*51h>_ 9Jj}םsvoly\ 2a!cv{*29fu.@欴2)عgoU6 X Y"잹.s8;32fC5ƼS玧rn_ĀLA w(Vsg@bYdVǞ{j=䮷O_πLA w(?cːQKLy53d,dW gxK 2d cs>2hOO{쫙, k>ʯ" 9<2d&u1u-An+_OԏfxJ~x f!(`PU T (zᯛ;2dV|dVҴϖ}-ser?g@fD;_p“य़_v73e?TF!eKl'5®{%^TNcBql44pQWׯeH.r>,YV# ~Z3Qy0yʻu(n{xkv{8B^'/25'ޡ@z;2dfPt۞;C.{|⽛U T (zV&?/@LAjO)]&Wގ5UrDo`=꟩[W,N+9qz. J݋p^ػ*FlBv/\wvYG?w] V섁'*!$ς[_ȀLA w(n΀Y,;N⑌/KJʃAEP =}̓Ɖ\3/&GOnu-FEVO[ȩL=2'"Axۋw޹*֭SUj:>ʐRGޑ]'wsǫDz{U T (zv  QIsn$jJ4D<8Qwlڶv݈H|`^ej~?kNjsO}%XSg݌ YQlɖ[xs갼w_nrs'v>rCg@fD;_sg@ƀ,`@{^rՌd@fD;_rݽ[hPQ8Ē!ao!Cj2dV>B pyCꉬRɧd'2gU`y> YOxYynĕlrf2x_JpsCz{U T (zV E2#EB}eq)kkD<8Qm {ui7UWeM 0N25eS7wAm[5h1xugMˑ:6dOiUqf쯛~KGk^w4m f7λ,]oOʟʃAEP =Ɲ:#2hL81,Sj0idrʥ3ٵKrdyo Dḇ3Q3}hc{y?l n\ws]oʟʃAEP =ʝY`.r֭ϝDk==VrG*Gd*jNC w#>i~ =t{ }LU3E(saҢ;PGd]2V7-7!Ș~D+ZӞ.Nu1W3 SyP3p 䯇W3 c@ b@ -zc={-|Y'25'ޡ@z{' zJ,Wwv7th]xמ;Y䮷_πLA w(^΀L8r+2Hk{iYgܭc%W1 SyP3p 䯇WOt]"S`7Brѯȴ}vjxI35D[i/V韩2e:kǺ#CvR1֎U7v/߲X]'}O_+^'sy]4{Z 2vCۖ" .P$ <8Q߫Ov{e@f7ag{ ؽ{쬸U T (zu[E49uZU*Ez":ӶIr򐙹Nus}ꨟ߸ǟ5\Z_][i%qfl:L5 ן;nJr3g@fD;_sg@g\Y}ן;nOz{U T (z,Ǎ{P"Ú`76󵺻+C?*UN6K._$!KyșFJP>7 m͐C놣G ι0Z,dydW|$w@*d*jNCO H2 7%smEz{U T (zusJ~,X{/!}3ded4lOW_A\p %M96)x/پ|;caH,FF!+[1@,>vM cs'L]&wm1<8QWze@-.[{ <8Q߫ܧy"=j l3 !u"Qю4S_ҏj((K4^@- +Zi(lWHdMV`-؊'d'2a%; }׉sW9rCg@fD;_rg@g<Y~>wN<8Qk܋xSX*-~KC$Dg! YrdNL) 7 7/]K[Úհl':Dl|)ëE6 iX֪&2o{)ҫo^{XC~+n?P_ÀLA w(^΀L8+2^{'YQU T (zOʇk!2xYWb͐*ޏSU/{FiByw,X avkԖ]i5e'ӛ'x;`G_ɀLA w( `^#|GrJ& Hd*jNC'w?|/'{ȴAQKYś!SXݻU krɉFlT*Z͝LKϛˇ_@&J$1>wt>nz%$25'ޡ@zʝCƀLyg g/OYU T (zO/X\KD8SV8k\߾pf7Դ2[rJDoڢO٤w>Ά"oH9yTq@',=̞ɽimԏDNT,r"wc(քkvz엁jx0 KQMQOjFq!;llԩ Y]?Z%&;8H3).S$rQ#=f*a -%25'ޡ@z{h B*2;zp7IʃAEP ==2, ?Ad"LՉaæ̠Q* ٺGߞ^)Ȅ[k~ڲuOZ-\?MɖVo_͖ >rk_"4_C/2==]$s'>~M>W92 SyP3p 䯇?GƝYd-ŀ,^q?;zp7IʃAEP ==2Ə-r쪸p?a!molTd.]Ӱ&lp12dSy /zfR=qBmwڼb-ld0k mC ]W.%25'ޡ@z{dE)R b%񹣧]w+$ <8QßCsRא{Nśfڛ!SO vY5 !Śa-ٲ}Um샨/#װql@[K].&vgL)r֬Y'KDG*/d*jNC'Ud}|GrJ& Hd*jNC'.ڢH^e@)(玞~&w=Mod@fD;_rU:bN?3t̮ Y]ѾK.mmCc6{d{*yDd}j2Cb:xߚvX+wf95lǬ%Ęo;59p@Aہ'w=Mod@fD;_r;<,W䮇M T (zOp_f>Df'p̐5\Mʥ.Z,ޭv*V{vwQtd홁WR9_'7$` W1f.\]r%"9t$;ZqD W3 SyP3p 䯇?ÝYd\Ekӣ䮇M T (zOr_~88A"3Vvr9Q7kaW1SKG|~tcMeޢA;>$O.]U ntnYa庿R+vY,z{\۳!+>f9'N6IB$;25'ޡ@zˀ,0d9玞&w=Mod@fD;_rwMXu寠t8Ne5ݧcUߑ)V;wbY:i[}utfcxO\֩/'.wDyWxW$ǽ`v =]w+$ <8Qßܝ΀L̀Ll;zzp7IʃAEP =Y8l}@f*/45:bEjW15cd^z1 U.Y&3/`HO횳Kly/So"V奝D}k|a\j8D[8k",qo#ɟ^πLA w(n/wd*_d*d䮇M T (zOr_ k͛'RbZGyDΏ=Z] ` mdֽYNuIÚk5[3R7KG&zlov^\52t_LR)7x<2gMX>Z)-h|h]W.%25'ޡ@zh3 c@fDюDnNz{U T (zOz^#X! ⡍KaV~ Mhk)6!ˇhx ELoV>ԬY3Jc\JJJў{vY>2'KDG*/d*jNC'w=Mg@ȓ9r[F:=W3 SyP3p 䯇?nz __1\dEe&xW\ʾO͈,T)HdV}{ž $k$Ca۹r9ȼU?*𚴲Oq3>/8S}g$-^7&- 9Qßp7π$e ZoO8'-wd (0 XOK:tnz]E9XkZ4ӯ)+{m|RKHpvKBM;?-G5Q[ͤ,˺\%ׯRvqw t_s0?w^3 Ypsxz^Y +8Q$Oz^π$X=7'@D;2gC^9:$*р՗8C!}3 ]]w,˗Ge`wƾUDo.~Fisu)uv9[r ֢{JCx\G;ߢHz+ZqTIV'k+nJn^AAd쮻ѧw:XdFU<Ϯك̿RX- Ut%2Gq'3N}EzW3 3In7jߟ3zY/ֻ1 s_F(z'w=MNPqsȿY+4:fflkn_;q\z}2췫5kk` sRnĈL9r)4&$ O?#sDcDtA/K>`E,z)Z{k7cmƭ؅5{pq? =FF F`Ex/a׋>N]*?fwaXܤ)}5t32gy'7N]EzW3 3C:]J$,=ɀ5k &I5dI᚝,_8NzȓW>0q]|R_b ׫Df^*vW`l=9?j,Ú1޳oD&휞(fQc3~~xu;>?$M)(=zXڴ-[㑱q_fߣ#2m9p9ߥ vW $a1KW "Y_0!Ͽ:%o9<*w^P5h =rɔe.&a-TdFf9goi=2=R(z;'w=MNg@Ns72: À:"[c@g[mڈ D,/D#ʠ.G:BwE.#_C*~as`?r6ы{l}*~Z.Z7i'ȇ)S? MG]ܝnny0oߡUq0W2=2&"bg@"GuțoYd0PsamѥO"c;lzXn=1+1of~#v^.C=!2pGu\v^bdytbX9ƿL_Æ 備DFIoc_Wf.g{2<7 k̲L]+ۺ|d~JAl++G??}5qgdՂEo䮇Ud C9 a 2u\حzݮX3 D䯇?nzſyc ld@:_@Nt+/|5g1dmȇ]aMK/Ko:폿j7g˩ >+KzbgweϼFϽ iCDN|uY$ r_UqkEx)W{,Y!g#;Ѣ_s5!~0_{X#5ߚ"IDAT!]A0`\wf > '+!C4 AC!sVXLUa!v/Rk"P.[VMy5g5S-Z Õvee!Y`eUH,$|YYuZwah~_}9A2u3zۤ1vn!cQgd6LԏZX'Zn2å9ОC.VSG;+@۝Xu b5wʀբ~dF\qG !3.v?`zqihY.^y։qҸ12t"ѴKĆ X\&{t TniwVEo䮇) L3 c@4db@/f@=-2O3a@6]&wFhֵkc-J~lq4֐ތ_w@Fd{IK y7~xCD>UL;_Α]t!??_z&kUz|͚ wѣr#F\*oCd*wsYZ OJ ڙi)x҆UBȳʱVxҕz/Yﺇ 2w3u3q7?2q+yq543UDqRz yh|bvY Yc)jb Z~8~U+tP 7.ᮛhdƌm֨r"2q#oA#+`܃߼Q#srn9XC{JU`[&@ 4w[N=DzWg@fvHdLAdAzx-7΀r0?7l+?,"[ -X#6zF`VbA[jj7[OiDaMы) 󍮂au|s"NXy*}{qh62dDaT: \N5i_Ƴ2yVq2v{Ifx |ժ@~*2W"Lوtdo-6F4ؑBw߅;'Z5Ze{kP7rlD}`BF뗑yۜ.amW:Xxpd-,)kֻg|p),_ߜ)~Rqw<wL|bX= [9Q.Oz^]ϟt2sB2 Sy#cydp*2&]a|U:5Rn߱- k@gdί.A׸62Bm%qYȤ k]}\{eHNO?LbMsOqAp-[.21*}gdhZomS󔇐y)lCzu3_t[v"CXTtPt”~ޭRb du;p9H1OC3͑hFڅJAkJSjrD1+ W-{ZedJҜ(z'w=MnπH3a@rq΀Loy?Â! _$w=Mn߻2'EInCf +|.nSsFn 2 Ϧ Qdx~uyU`Ftfؿje@u}i"S]dCiشVw:Ț)f;F$v ~uZ|׬-`2RxӐ!{Z5dce]ny[߲,2 D䯇?nzu df#ƀ,wg@?K aOD\W][Z`;4wMArڕԘkH&75&ޫD[V$2d^]1(~]_ykȎs^'&w/i&iw[>^>xJNtQ3N۞N2"ԅ'ށ@zWg@f*<ռǮΝ]# VQ%0E c4:"-y~׏BdX;)*5dtXv8rx!ݭlzajڔkvNz=0pu-ꭔN9G!=zv7|p^3d;9Q Oz^]ǟ56z``#L{;73 syb8tFͲZe(2DQ᲼pokbX7)rM<'u ,2?L-d mH^^5WiXzg6+7o:Dy;ŃS'#=]wkg@fXN^΀QcmdMhˉQBCQyKn#L'n CN֍a|0E kߣ%F- }`8U/((dgg+a~ʷ"wTEni=?RR֖KtxaZK̹x1\LYa|^g?Oӵ,Q֎1 ?Ce>sIz^?2)c!A,mv=ΝFuu`@fKOYJ? 䯧']wӫWTTHQOB\k?L>vD..N%H~_A*2=KMc|P^==x>HsÞf@QrD䯇?nz*dfS" PpaE^dJ^(Q:nz:SE22b>/R{L^q |׹;k{|0jРA̬h1 XD䯇?nz:dfOSKmugsg@. 3J^Jzzp7zESYQ)v=yۚH^q8!8q׹>J 6zy-1dK(z;'w=M^πi} x=0طnu 4ׅYxFIYS_OW[S{^5hq-2c+>*Cʅ'6A^!1!u׹#cg F꛽Xy>NLzWg@f0e ^  ΝFYu`@9*?_OOWy5Ҵ_~EdhR7ˇӑ!;:_qy]p?L]\7zCϔ77{y/d(z;'w=M^πYPjk^΀L7%^(:nzիWKӖ.]*%KD4E߱cSb}:1V?+: *4qvmj>\{]g@P^D䯇?nzdfR" Tu^:wd:GWE,)Fi䯧]wk^pȩ酪H^q8!8q׹uk8vvYlڴi5YH@IDATwE]rbD &0NtϜsY,x13d]~+aww 0q鏧XCq֤V[6|}}i}י 5J3(6Xmc{1'h!}Y.ݥsV(Ȃ|+ 2O K=Nd]~+aww 0qҤ Ǩ8=d~_oj ?LCי_ܹTӔ LI󙲛ﻗC=dx: zww 0Yז/Awdi[yNj,Ko?tuY8|P0o?)p<4Mҝb_a]:3v+̽/˲?[XZfc\ÁGveeuFۏ>h#^(Ȃhep.]ǝŸ%Y÷V GQu7 .E0]񇻎;g?Kǟ@<ſnN?5m~{;̳y3=KCx  zww FdAZΗ(\]yDž,Ko?tuYo$=uǝ(| =x%=ِSN60_BgO= 4-=g784}Y.ݥsV d"# әgQ鬫YQE8Hwww #..n[RXeUmUđOw2-FaBeWg|w?,zƙOlt-YzAA@++ Ew)_:F∾΢(\]yDž,Ko?tuY&.[2e ũSR?|vkB47|w| ٩=I'f@גAdAPtuY& z0pu|wGA=.d]~+aww /YvϛS|h?Lu )sN~{]KVaP= B] 񇻎;g?KQ^Օ{\(rVHw _:,.X:Ox+7aZk?N|{]KVaP= B] 񇻎;g?K(yk<QL#ߔЎw4>}8:B?wy?u9+Y"ZYt`f?Pl|b}vӻi}j]>S]lI-ZCEnQ= B] 񇻎;g?KD(Ȣi{a[[~Q6nn漨 _:,QX;PZBm۲@+W0gF8А?Q w N(ڟ}>)#zEA@++ Ew)_:,QXDAVѾҞY+P1΍0ҝ]ǝŸ% _|u3~'7w?k{&[cq~07 M%KQ|,D!wݙ/Q>ȋ,VVR?u9+Y0?}- Yؕ(ܘFN uYQSGobJݗ;b͏mLCF!޸3vOM[h kڗ?R,zDA@++ Ew)_:,ODAcҽV$W keL_:,QXP<sGld)>l'G i|qocL(j#SRjϒ-5Ovyʨ  ZY\(K:pqg" 0v+4/( ] n̍ypn_:,-^zأ]i6gInhCF p&}= Ib*s$ѱ_57ػl@4{M6>C)J?>(,VVR?u9+Y"ZDAM(_ mǰ}qGAv>*Hw ww 90:q(~\6ka?3S*?mF;;d!Ka W`B/\ Im{8^(Ȃhep.]ǝŸ%Edܴ0-(jy7 ss^GNuY>:6mW^yQGL;d {a gB/!g$v)#^(Ȃhep.]ǝŸ%(7 wwdq~P8. a;e񇻎;g?KĽފZ5otI, :+OoC ^Ӂ,VVR?u9+YB& q/A}(ȴVV(Ȣya;񇻎;g?KĉSAR3 wdY|/ gNw+ZC{y?o$xN ZY\(K:pqg Lƹ,󾻣 ZY Q_:,!{ܘ^}U)oT*+kFJO'{a^_Մ; \GFqP= B] 񇻎;g?KFdv6 |]i̕8PTt{FSsVld;;^k@ ݴ_v/ףo(\_quY>E,AYnV{fMR-K)[Ӏb2m2f `LJff;mCd ^{0{gZ/w>ڼm|yR/Q= B] 񇻎;g?KFdv6 \m掂Օ߸PTt{FSsVl|)a~(N#?ʕ+)v`;oA<*{ Ww*߶XRք/k`k{G=4EQ= B] 񇻎;g?KFdv6_ \o⎂PTt{FSsVl}4y7|) 6=ӥK͝-^6nݏj(~6nl߽Ke{QbRd.ƅ;;񇻎;g?KQ%;*߻/(_= POAޠSp "u:,!ú|hÇ-ئ6FQ͗ m34%["_mf++ӱqBѝ]ǝŸ%dcXwdΏAZ; ֊DO($S#t O!_gzY1N(O}[ twwj*¾85,Tą;񇻎;g?Kư( xwwdPI(0G7C]ǝŸ%dcTQVQGAmVUSMoymb{a|[ʉ{TD}q$b 1} l_:,! g|/ QQſfY);tO٩_g:YP3؟2MeO,%|/ \_ݫ*kmkYfMZRǥP];?q [ԓpN$uY1; x zYrk&ɞQ%⾣A:u:,!ϙ3:ydSq҇Qq(n|fwF<,ka :o5XY֞₌e}C+s~p3ulj'0d %.Y?u9+YB6FuGAaŋ͝-_6[9f]vXZj5mjm*S'rr V Bѝe]ǝŸ%d; Ka; 2wX UDJAiS(v{l:$vGi?|w-SO)y_|QI{[}) rN}weߦM: fCuǩ1d9P) ww (Ȃ{a<[wZ sg3d(>oEH\)ÿNĞ{byu bIMr_>{^?Ha(qx=^:_?!k/NMS _a_pb5qgXGXXH厂̲ZwwdaV(hA;0(+/%u9+YB6jWUUщ{K+E,s3Jـ6m<ϣ3}!Th8 E/_C/YKe ɵ&(¬}Qρ#zvCaPW@_JsVlrD<4ث!2 )._֋bFX?Δ>\7M;̩zq WAA+ ._,4: 2_bb!; 23˾k9L.QY ӟ'G$ ீpqg (A{OYZ83k}giyΗ4w4wj*^w::b~ _:,!QW gwwdk,P*}ޠ4ԩ?Wl;=6H?[Iϝ3{˭(VVTPܰY ̝+xgYݕھ>^:o|!+mpMGj2R;o}@AVP> Ewww R(rϫA; 25f(hѾRoEDT+ekjjh%%-[F)SP:u*/LYz1E_|/ \7![٬4x]匆K?%m_{uK7\ݑ@A&-|Pt' :pqg ; 2P,P si si6oQ%2'Zop((ox=Oܙͷ4zB){· -g4S|[Ȳ?#6*ΡV*S4 2iÅ;Q񇻎;g?KFQɮbɆlM5 27V< 27QX̠yv< mZ_%:)FsKs@lt`e.l0p2|qߨL:3d5+L\Sӈo|KiuƭZ~- y\(sVlL; 2u{6d(\Z(\ ƒ7hb c̻3MȎON~Օg9hݻQcQj&]}0pշq>o^eWi룗+NxaGQ,ЬY3jZ S?.݉?u9+YB65 2d.md.͆CcICt 0b=rYZݷىq\s[P%*w /uw6%I';u>םP%%~qN uY1(d׉oPhZ_wRCAlŅ;񇻎;g?KƴշULUC3fPuWR|&]0pwg5w~ZxM'vj*4 S?.݉?u9+YB65 2ݙMNZ(ȒNiPt':pqg i3a/#Pb=|b&3f@ g_@2b6 ,-%MSML6y톂,6J:…;񇻎;g?KFQɮbɆLw]ݑAA&-|Pt' :pqg G c3Ρ:̷+2;+֡ysgN;\i,d)ɇ Ewww  ]Œ L#LZ<%pNuY19}sGϒj{a^Dݟ!O?_6,i4 YTŵ#.?u9+YB6z% 2ݙLNz%8?~`nKDECAUpN0uYWwd(mPΘ;QUQE8\( sVl,v;o/O>fmQj|/ ݝ %ظ(lIB?u9+YB6; 2J6d3Q;> 2[mww p7ޯ"1u(wƤg iDt7EJ8YQ= B] 񇻎;g?KFodN; 2NY.ݥsVl{+Ϡ wMqw{a3Wl;٥]u',VVR?u9+YB6=荂,k u'菂,VVR?u9+YB6=^{nAO/|}qJڡ[F;gA=c5w>d|巨YJם ZY\(K:pqg (rLu'菂,VVR?u9+YB6= ?v !;ois/.8wަ>^ww!tJ;A&dAPtuYQve+ 2ݙNY.ݥsVl{w}PRs/śGqw>_{ gބb Rje5i;RgS(j?u'8(Ȃhep.]ǝŸ%d#FAVOZEA;sx zww p֛\z S[{R| (}0 o5Dآ4#2OnH?q2ſ]uey^w(Ȃhep.]ǝŸ%d#FAVOZEAvu'ȏ,VVR?u9+YB6=]UUEvq'gΦx s~t%ZQPѷ +*.7J;cZ'2n?)Sh;ex .~dAPtuYAodA_[(PQ4oкsVl{{?BƏGGf]^Nc;+}cQSF!Nى]}qߵ;migm a^Rb>ؤIjp|u'Ȏ,VVR?u9+YB6=荂,k  26 2fá Zw2ww p={0ulΔɓiI~Dq$G<ڂnjKaP}(^{p&;@A@++ Ew)_:,!Yn_ ӝIQ= B] 񇻎;g?KFG/V/ Cs}+^wL(Ȃhep.]ǝŸ%d#ܣy Q(tg;AdAPtuYyuΤx%[ _|w?!{靉FHי;AdAPtuYy +OhdZ&/^w(Ȃhep.]ǝŸ%d#:bh͟ۀyu{aNOh={ӐkOם ZY\(K:pqg xQ( 2)ytAA@++ Ew)_:,!A:e[.˙ॗ=?9d|/&M.'Q8dd #ם  ZY\(K:pqg xQTo(Ȓͯ_P= B] 񇻎;g?KF=sLxmqVIqw6بq8I=KAtͣhʃם  ZY\(K:pqg xQTo(Ȓͯ_P= B] 񇻎;g?KF'=r]s^Fq_ڐb>ek;d {A6e ك#G7m>TBA@++ Ew)_:,!ឌ7 d\Y\N Y.ݥsVl{=uN;Si.ٓed;^ٚs;dC}^MQUs#};AndAPtuYz 3P%%_x : zww p{ҋx zww pY3h߆.h' {A6kV% ew|ѻNY.ݥsVl -"FA&\[eP= B] 񇻎;g?KFzؿcGx7\z/gUQ)dg;d7ݼ1 Bu?z䒤gd),ɡBm.Db %km|I7 *=Pũ/^(Ȍ k(;񇻎;g?KFz.lO#3uPRqG=х" ^}CvM=hA%t{dYB -BѝA]ǝŸ%d#el쏂̖I,YzuH oPt':pqg wYo;-7.\~}48z ?^ՆE|/55zvJ4}BiR- L@qzoȴ.\[&v'[_? yYCVڷ\NG֣-)!W_;dW]kѠ:=E,nq uYv6GAf$FAo}GAfPշby\(sVlmִU;b뗘!3L&w> !{A6oCvU8Gx=Nu'h,VVR?u9+YB6]Άi q- ^w2(Ȃhep.]ǝŸ%d#elmdxmkszjg̝Cq(40+M|/dO;HP_~0NY.ݥsVl -"FA&\[eP= B] 񇻎;g?KFz1cCQܥ߿) d˗UkOxuݺ/>ܮ23 27E}TPt:pqg wYo;[}(lx(qK}>ji=YZg.qBI_PBO=!<P1ACv #]AU+Ɛ|w r-ZO?.0_lM=+t#캗t>d)ɇ Ewww pGAf Fk CAm$s d\Sk7ԟc' ;g?KFzxםSW||).XCa]KڙOe2X&SVsGAwx*q uYv(lhmd(ȢdBAk{ v3!pqg wYo;[TjPv;mhwh/_nhϜ?/ٍͧy~Kyz~ҥ{yCy]WCYw]RQ׽ OI>\(sVl-? 2[2\ p+&ٽQ%ޣA8u&:,!.mg+Qe~GFvMAxyk/ֆ/YmOg{9w6wNKs7cr(Ȕ'Ptg:pqg wYo;[(l(Ȍ K{ KZ8vfי sVlP R֗)'^_Rܬ|k̝s [uRtuy5dqIz. ?u9+YB6]V? 2[46 ګuԸEA%x^\(sVl-.'u=SŻ[xc)6lF?^-Zd~=Nv\ީ*`0( P\( sVl-.dlmdu$l\>qJLJ:eypNuYv_}u;RUw/f8}I36':vʂG [!;TO>_&3w$b 1} l_:,!.mgꏂ̖ FA+J(\ Ɓ EwBww pEo|lӚ2G#9cJJiuR\۾dKp-iz;w$b 1} l_:,!.mgꏂ̖ FA+J(\ Ɓ EwBww pE4iu5~x?⤏L8m{hB[lٲjckFg?tj֣{N" ӧp&uYv(lpm .߿ O>} ,ACv`1 #vp <~V>qBYYcy?mL[ngLz](›Pt:pqg wYo;[(lqFAEjk^<ʃd1.IPxƠv:ס;g?KFz٤:jJ}Nٺ6RTmş~ZMs)~dS+::b~ _:,!.mgGAGAnI{YT9ϏÅ;񇻎;g?KFz٤kJ}xHkAdq:Π }M[',XQUDK{'N6A w;񇻎;g?KFz٤QQ=[R^CAUpN0uYv6iICX'dUU[6[}+V,7;֡tKmu 5i{j^ nHj@IDAT( Ewww pI 33 }-jQi;[ a _=ww pi4#V[63Fj>svNcg/?ª3cgfZe Gs,YSvwrobsǬU֢EO2D2d!iW\( sVlMٗ4_jOI(R5]Y4DqrB/Kj6k"ʵ򯨨Ax`K7)g y#wy!{IsGsİ4+0sOlRھ6;R,++XRj>\jԷ/Eu/u~a +V$Bѝh]ǝŸ%d#elZ(C̾FAyK|ZoXJ_gYv6-|BCз*͝~d͘1=ڹL/wj_!gWԶvury$EAR1 Ewww pi %QcG!~&]ǝŸ%d#el FPIi1)Z^K.,iًwȪ_@kMujޤ׽S'c0(r`S_B3pqg wYo;? 23((ۖ^g> fEq.i?u9+YB6]&_V͛if5q^wvŚ5ʏ[?=d/ھd5gf?ziN͛Ժw :p)\(sVlMYPYC%+j>dQ P@&|L{ JgYv6-d~"3d,vDAN BK  ;g?KFz\i ECg],wv!{wȜ^(Ȝ'"ޜ ,wݬ{d"(LdwW R r p 2b7Wap/]?} ?g&xn^,wv|ק|IIFBA&-|Pt' :pqg wYo;(KWY: Y:I|A'0x6u9+YB6] hȧ֗㏖ڧv![ԍs}K/jd)ɇ Ewww p̞tQcPcG8pB g]ǝŸ%d#elih3g\JS=҆m/}Cby5inl7b-T=UZ}g3(7\(S sVl--(sGAt_gfYv_qũt xhBq~懖'M,/t)ŋu3(޵-ŋ{c)TSuOh > K7]Bѝ=]ǝŸ%d#eliGAfϠ[mdnGAAT헶7ߦ:3 ww p>؄_)]֊ S5wS3dò3c)eg3foS[nH;>)dIɦ_\(sVl-(m /4; B==>oLuf:,!.mgK)GC^x VK3͕͗ {Rtϐ͟o>ֱfD^-?=7l3ѩJۺOY)_:,!.mgK? 2{u(tʎ,.IIg(u9+YB6]}GCr3sǫ_OUL{wu)7dIɦ_\(sVl-(tyGA_g&Yv6zj$7P<1.;dl=e˖Z䁼Eh V Bѝe]ǝŸ%d#el gXL?(*|{H_gYv6_;0:գ[4O]],w7kݺ;'u(ŠPt':pqg wYo;(i qYX"7Luf :,!.mgտNSWm5wȆKmڴQu侮{>YX"D_:,!.mg=:md:a +V$郿L]ǝŸ%d#elt=%-3xA_#:Qرa`rz۵k7e|_aQP+qN4uYv6Q3.FA&5 r;g?KFzيJgQUO<%2sڔh>Ϝ}$#˺7ʏŲeFAT Ewww p? 2{e(d ͂PAO/7W:3ww pg:2w^ޔHb)i~l5ֻۗ/?Dd Pt':pqg wYo;[ W@2mdɸ&+ dSoA6]יsVlwwǚ߿sԏ\Ә (N6ۍI?zn3)^ՖݭgNr?XZ(ŠPt':pqg wYo;(O,YߤzGAl 避 ]ǝŸ%d#el?ȣtgp}*uϘ;cKL|_&o#Ť|Cb3Θx_~uWSǜtHuYT9ϏÅ;񇻎;g?KFz|GAftmd&; Sڿoiu9+YB6]G}D1'I(~\;67 [G=/w.0[㧤s[uM m/)5w(yGF{lzuGCAUpN0uYv6_Q3LY2ҽ OI>_ RŸL]ǝŸ%d#el?~x"2h2wvl۾!4wa>eoe8>}c6mP,Aѣo?Ržmgd$ Ew!_:,!.mg+vdFAսQ:3*7e~!S{%lZ]ǝHsg3/x`YwY}.Ǟ`WN:wXAfdAPtuYv6dʈ,Ǥ{AApJ_:,!.mgPO {&?|k`p[rό]y˄CXADdAPtuYv6EP= m +TPxd2Ω˂7)?u9+YB6][Ĵw߽d/ j.gΊI+^?tC XAudAPtuYv6",Kحǣ K? 2v Bwww p1?x)^wMpoY@i5kVܱV!;p/lA-"(Ȃhep.]ǝŸ%d#elELYn[QY;E񇻎;g?KFzowy-mq;|Mqj6(P,wN:H.]v͗((Ȃhep.]ǝŸ%d#elEmdA-datGA|VANuYv6"?O4 ZK(N|<) /[pNyr >H,VVR?u9+YB6][$FABAVLgd:g_:,!.mg-b'~1)|~ffc=~*X,w;<ݻw'靰(Ȃhep.]ǝŸ%d#elELYn[QY;E񇻎;g?KFz9hØ_ث!uV;=gʨVJ@#P;d mc6>`F>/QayQ= B] 񇻎;g?KFzAABAUN8d:g_:,!.mgPdѢEa̙NJq'}19_~NSd;d ~2wƺo8jKbC)](Ȃhep.]ǝŸ%d#el zm +(ܘF7)?u9+YB6][$vyy9xR|ŋ̝.͝V;c6kg:=u{]DXAfdAPtuYv6"QP=BA/ kIZ` %4ͳ[ Uno&=س(^rQ~w|CbE5z|Jq3%&Wokа m()5>_ט;j_z1m?(Ȃhep.]ǝŸ%d#elEµQ b>*ANuYv6"7`ldҝ/wF<`rsG${'4SNKwڶ55{ケ+udFA@++ Ew)_:,!.mg- YQ(ܘF7)?u9+YB6][:*-ZLv%ts3I'8 (Ȃhep.]ǝŸ%d#elEQvCAŵO]GƃBw"ww p=k,hԩLD1/P\V)۝'fUSvm8U4 zww p8 g=% S? t':pqg wYo;mv[M&p3&_Pϐ`Ax8Iu$AA@++ Ew)_:,!.mg-"FA&\[ ZY\(K:pqg wYo;moOj>cu2yXύ>TU8ũ7ᅲP>ȃ,VVR?u9+YB6][D J/ n/AN9uYv6"2mvw߾KQHf1gi3QיUmsfld p\GN:U*GAi$.y_g5uLϥ6'o\Wץs쎂 Yr,QoUT{  i?utb]ǝŸ%d~h#ŵnԩVyƲeՔ3(v˘qOe2*6?痬6w8ϴGBw΀Q.5zJ sVl LvGALf΂,Ko_Dtpqg wYo;m>w\J|){ǐ7]oVP{;n~1]ycVRRB|5ls6^zQ,--ب\߾tQHt dNOpf]ǝŸ%d#elEdڶ; 2d2+/wd]~BU Ϧ;g?KFzoȴks5j8 K͝T9n\WffjY]i 1k,}s&2b\("̵&4>Dy%aͻSM뎵 jϟ@AG==\(Q98azJf?'Z_>F}QϩnFW{wvxءm(p5U4~;d;SC P)⻜_:,!.mg-"ӶQɸs۟kDAV3_yB(0p-ýn_Cs߻iߦX n VJoDžww pi; 2o; 32(6 B]񇻎;g?KFzoȴ}˞w&GϜIqŪU;7jLq^e+{R;-]o>CNY~: 3Ӂ̉e p uYv6"2mvGA&mgadFB&\( :pqg wYo;mv}K,L:OGqKq?57T]fܻĐwb@A&TPtg :pqg wYo;mv}(Ȓ^(ܛ'F Ewww piGuomi*ؼyeFϐm;]w53CR*q uYv6"2(♟dwd͉#…; 񇻎;g?KFzoȴO>8C(Gr+~̷,yG.xRC,WGPt:pqg wYo;mvTwdOTx 2ĉBѝ]ǝŸ%d#elEdڅq YaK:bE5=/oYM3 E Q-IPt':pqg wYo;mv( B (ܛ'F Ewww pi~is]#z6xE(&Xύ̝*P%52yvOI !~+Y vBAI". _r ɿ.\K(מ$L\(ȢR\Ѳw 2ĉBѝ]ǝŸ%d#elEdq4x.43[n9}1wfDNpOkΠxGUۜydgٮ7)|hatBѝ0]ǝŸ%d#elEdq 6_qGQ(ܛ'F Ewww pi~0W[P,֦xA)޸Y3c*wg+M&GY+͝.nB3; =/4Ҡ K6Bѝ?]ǝŸ%d#elEdq 7oq(ܛ'F Ewww piێ;р˦o5<|gqYv3m|\q jJ${'Oۗbii)&M̝6ڈRqK7<(ȒNiPt':pqg wYo;mv\(ȢW\Ѳw 2ĉBѝ]ǝŸ%d#elEdڅkՊI#jsKjLmTb>u\jBĬ,cmh,_geEAm{ƅ;}񇻎;g?KFzoȴ uGAV<_XvFAޜ81"\(sVl LPc@=>0R8y$jϜ;Vͷ-!͔z}B|4Qx:.$u%ܓPͿOopS3|_o +l /,{G soN.i?u9+YB6][D->tetBlŁ{79l;ZAub>C-m]ێ̵qdۻ6w:v6!4hP)_?A)LԸPtg:pqg wYo;m; JBAޜ81"\(sVl L[}Μ9tb]kp0{CzC(/, GidSp uYv6"2miwdyfw̽9qbDPt:pqg wYo;mO<@'|cuuoܸ$=־?"AZqG\ Kҳ~pN(uYv6"2m-wdf~eVW,(›Pt:pqg wYo;m9gB'zq(nޫ1ʼn)(œNjG1w,C5+œO9%lI) |>_:,!.mg-"vGA6&z2xdO_ەי sVl L[}ʕt={VRu(x@k>̝[EnsV#/X@j?st|I<= ѻ^pN)uYv6"2mmwdC땎gR_gYv6"2mmE̝{CvԾߍ⦛ϘQo1wƶM`SʖSoخ}R m7 |l?\(sVl L[z Y@יxsVl L[F zϭ2wz#Ξ5^k* ̇zm[eΘ g~-=պM/tI,)ٔ Ewww pik 3/gAAެ(;񇻎;g?KFzoȴܛ4jD'ؽy+뭮 L濝g> marsOmgƖ,XC֘n3U?2 i{iYy>a/n Z2g> fEq.i?u9+YB6][D厂̯ Yx84_:,!.mg-"r_b)S(N6O>8(vY|󯶥v}ϐZe1|:ޕ##:wzQsjUc9?/eLJ:eypNuYv6"2 YYPiπB(_Â:{$l:{(wyu6_|YWP|fY|Me;Ԝ%+q{|7?idI \(sVl L;m(dօVdZM 㜡dX#wt >F ^z?MqS#v]UUCqee;DiO YR)_:,!.mg-"N; 2!xJ*%%aͻSMp3tgMiwi0o.3OVYnjFqrW^DOvP-IPt':pqg wYo;mvQɬ,(ȤS'/T)p pa5 kI '[o(>뮧xnEke̷.YH)>w@CZrCAlŅ;񇻎;g?KFzoȴꎂLf}HgAA&-|i}J oÄDDXySź#c Cr@ZW\Icm(vv>C-֬|f1]ݧi`~瞥vRzH 2);a񇻎;g?KFzoȴ⎂Lf=hgAA=O | Fb;QbaZܧOF3p=({;^]R|R3K8s 햊6.?Iq@橾}y%AA&%Twzxfwٟom_K_oQd/- ϳ(/?J_==OLd9I8Ro,zc_ $p0_C_i^_NWGq~?~O|懧׿G2Q_JARBsqT%ܳ3 k8^[>QQQ9;ʿ${{"O>yS?~}~>ץ>et}Ye^K"[gg)AJq}zk^K"[gg)_?_|Oӏ3HytwiH|,^K"[gg)AJq=ѿQ.;o{/liHxg,~`w~G?? "';wW*VYdwweRd/hyoT/ ^K"[gg)bߦX/ELyX }AP_/(o?J_==OLpAqꍼ ;/{/liH8'?y;b>;e?A}Uܢi;w+Id+L?E2ù;2J)W}}oToyaw+Id+L?E2ùp}{_{z8/ -}ǽRDrz4S$?,38+=E駟>=磏>zy p?*%g~>_(t~<>_Zzvu(ǟ{/liH~d{G;~c/ ^K"[gg)go){莀E;w+Id+L?E2=-qu(ǟ{/liHq2;ewsqT%ܳ33η]A=tG;ʿ${{"{¿8ȺXQ}ǽRDrz4S$s8RwY; wW*VYdz[ . #`QvG?J_==OL=|KedCw,(^K"[gg)go){莀E;w+Id+L?E2=-qu(ǟ{/liHq2;ewsqT%ܳ33η]A=tG;ʿ${{"{¿8ȺXQ}ǽRDrz4S$s8RwY; wW*VYdz[ . #`QvG?J_==OL=|KedCw,(^K"[gg)go){莀E;w+Id+L?E2=-qu(ǟ{/liHq2;ewsqT%ܳ33η]A=tG;ʿ${{"{¿8ȺXQ}ǽRDrz4S$s8RwY; wW*VYdz[ . #`QvG?J_==OL=|KedCw,(^K"[gg)go){莀E;w+Id+L?E2=-qu(ǟ{/liHq2;ewsqT%ܳ33η]A=tG;ʿ${{"{¿8ȺXQ}ǽRDrz4S$s8RwY; wW*VYdz[ . #`QvG?J_==OL=|KedCw,(^K"[gg)go){莀E;w+Id+L?E2=-qu(ǟ{/liHq2;ewsqT%ܳ33η]A=tG;ʿ${{"{¿8ȺXQ}ǽRDrz4S$s8RwY; wW*VYdz[ . #`QvG?J_==OL=|KedCw,(^K"[gg)go){莀E;w+Id+L?E2=-qu(ǟ{/liHq2;ewsqT%ܳ33η]A=tG;ʿ${{"{¿8ȺXQ}ǽRDrz4S$s8RwY; wW*VYdz[ . #`QvG?J_==OL=|KedCw,(^K"[gg)go){莀E;w+Id+L?E2=-qu(ǟ{/liHq2;ewsqT%ܳ33η]A=tG;ʿ${{"{¿8ȺXQ}ǽRDrz4S$s8RwY; wW*VYdz[ . #`QvG?J_==OL=|KedCw,(^K"[gg)go){莀E;w+Id+L?E2=-qu(ǟ{/liHq2;ewsqT%ܳ33η]A=tG;ʿ${{"{¿8ȺXQ}ǽRDrz4S$s8RwY; wW*VYdz[ . #`QvG?J_==OL=|KedCw,(^K"[gg)go){莀E;w+Id+L?E2=-qu(ǟ{/liHq2;ewsqT%ܳ33η]A=tG;ʿ${{"{¿8ȺXQ}ǽRDrz4S$s8RwY; wW*VYdz[ . #`QvG?J_==OL=|KedCw,(^K"[gg)go){莀E;w+Id+L?E2=-qu(ǟ{/liHqPVW.IDAT2;ewsqT%ܳ33η]b5iBIENDB`wagyu-0.4.3/docs/bounds.md000066400000000000000000000020701314062220700154120ustar00rootroot00000000000000### 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 edges; // A list or vector of edges ring_ptr ring; // The current ring bound_ptr 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; } ``` wagyu-0.4.3/docs/bounds_red_blue.png000066400000000000000000002456671314062220700174640ustar00rootroot00000000000000PNG  IHDRLQ iCCPICC ProfileHWXS[R -)wWH! JbG\ ]"E^,I]_9g3 6lT ~ :ȗ$ X\pe n%U9\!$ T1puv B;AU D\ӥX]SRb7d*%H@A̛YNqm*=, ə "b-fxL+}Ksٟ'f?KNhl}بhqpjf1ԈHU H^(8Nf?5 PaA1C#v,ڣXN̎G 8+3!cx;W3f Vz0#6Am-G@q0+&L0/bF s6] 0Zj¬,\0`/&qp0'fGh:c1c=dF͏rQ?`l`6K:X@X4c >|ƀB'D\ rAJV M2Z  5qOOopȕ86+1O &y!lota\c9|GxF"^\Q"u[5>GKl%vkaX#`bg&;%TR cEKe81z~?͒/^/a>wnc;OKgݘ ᳭-v6Nv!ٳƕo@e7Sl@oǀ5Wt0'A $p3@<,KA1(fPvݠG@#8 ΁*7}X}%`ABC!x"H8$!)H:GDdRl@*]H+r9\Fcy|B1ڨ1: uA}04yh!]~=^Eo=Kt<0+"d, ` b=_z#N8l<_+ZoůA+F"X!DB:aPFK8N>0HdML"fW!A y"I,R>tM#} ˓uv@r2O."O#rJrFrnrryrk5](SL(XJ&e)rrV^^^_U~m vA`QXPРЭJQNHGqbbQkJrJJ~J,EJJ'n+ )ӕm#sW+SBbQY[J/l2z*QD5D5STjꠚZ\JSj= aad320n1>MО3;aՄ'WU/Q?~SS#@#KcFCM\\s4&NtȞX2{ZV|ZZC:Aڹ[k0tu2u6ץzt7af3˙A=-`=.}8"C (.i Z  u .07g$gbaŨ轱q F&&!&&&Li^yզ7̈f.fYf:QsG JkbE%ՒoYmyۊjcU`Uoؚan]dhjI'Mjhm澭mmm;s;] {}b&\wSW88~qrv8tw6tNqr+uI׏nNnnGrrrbd={==X?aCBOąW4xIMɤCm7qz[3Lf̝qy왧f)b:BHIHٗɪf V[/9ޜM~wyGچ322x~ "jFsRrNUY:ʵ-sۜ7("¦|Uxi~=.,,0'~ѹss[5ya`/- ,]x]EZ,^oIВڥYK/)PnY²˗,)bbA+vWVv_uNɕRҲϫ٫lsϣktuZ}qݭ^k7(o(лqƆMM%mrCَ---=M[ "fo*UUquo~pvvڸl7qwg{K^ͽ{kzjk[i[[֋Oy@A1E5[GŽu9zѱ% Hüƌƞ'Zݛf[IN=M93CgsK?27Zv\pbm>mg.y\:y+.W:]mhwl?;:9_ktlu۫uoܸz3f׭[wnOss컯ACeUzzN=$^v˧§?=+{݋Leˑ?ze_&}ۚwZ /ǶO L\KװFsFGsY(Z<;t@Q޽$H$O.5- Qf1w7@ǛLivXTx!|} /ёm_@w8';I~[ (l8) pHYs%%IR$iTXtXML:com.adobe.xmp 962 844 CiDOT(n @IDATx]w`U% !zSv* vA`A 6AATPg( ( i{MH˽sqnvv37osfޔsyo7b>kӦ ]Ŕ)S|p5叩ꮺ'1FTߣǡG孺cTw=sIK5z8Twlc쨾G-.C[uǼ{4F ǦQ1}Q;V@=*o0*Tު;VuDX3abvn1rzTߣG孺cTw=:",-%BM ǥVSc^uWݣv{TbaTUw눰(g8TwpcG-&C[uǼ{tDXZK6K[GꮺGX¨Qy7[uaQ0jqQ}[LySU\舰7 m865ZM}{]u9QyQV1o#¢aPUw]ӣ ?*oai .Qoplj8.ms`Tߣ G孺cl]GE9è&fǡ#G=*o1*Tު;MuWs=#\(p\:j5=Uw=j G-F[uǼ٪rQ;L̎CuW w=FN{Tb2TUwꮺz@G%DQhñḴuj{L߫{!P}[,yUw vꮺzdQy7]uKKpzІcSqiWUC+X?*of:",F01;]u79=QyPV1oF ǦQ1}Q;V@=*o0*Tު;VuDX3abvn1rzTߣG孺cTw=:",-%BM ǥVSc^uWݣv{TbaTUw눰(g8TwpcG-&C[uǼ{tDXZK6K[GꮺGX¨Qy7[uaQ0jqQ}[LySU\舰7 m865ZM}{]u9QyQV1o#¢aPUw]ӣ ?*oai .Qoplj8.ms`Tߣ G孺cl]GE9è&fǡ#G=*o1*Tު;MuWs=#\(p\:j5=Uw=j G-F[uǼ٪rQ;L̎CuW w=FN{Tb2TUwꮺz@G%DQhñḴuj{L߫{!P}[,yUw vꮺzdQy7]uKKpzІcSqiWUC+X?*of:",F01;]u79=QyPV1oF ǦQ1}Q;V@=*o0*Tު;VuDX3abvn1rzTߣG孺cTw=֌n:GLk͛Goڴ0vn叩ꮺt jQy7]u-\&d͢(1;]u79;QyPV1okiMtHJ?[~JU[Qc^uWݽCl:Qy7QV1osE9è&fǡ#G=*o1*Tު;MuWs=`&b (1;]u79=QyPV1oM%DQhñḴuj{L߫{!P}[,yUw vꮺzdQy7]uKKpzІcSqiWUC+X?*of:",F01;]u79=QyPV1oF ǦQ1}Q;V@=*o0*Tު;VuDX3abvn1rzTߣG孺cTw=:",-%BM ǥVSc^uWݣv{TbaTUw눰(g8TwpcG-&C[uǼ{tDXZK6K[GꮺGX¨Qy7[uaQ0jqQ}[LySU\舰7 m865ZM}{]u9QyQV1o#¢aPUw]ӣ ?*oai .Qoplj8.ms`Tߣ G孺cl]GE9è&fǡ#G=*o1*Tު;MuWs=#\(p\:j5=Uw=j G-F[uǼ٪rQ;L̎CuW w=FN{Tb2TUwꮺz@G%DQhñḴuj{L߫{!P}[,yUw vꮺzdQy7]uKKpzІcSqiWUC+X?*of:",F01;]u79=QyPV1oF ǦQ1}Q;V@=*o0*Tު;VuDX3abvn1rzTߣG孺cTw=:",-%BM ǥVSc^uWݣv{TbaTUw눰(g8TwpcG-&C[uǼ{tDXZK6K[GꮺGX¨Qy7[uaQ0jqQ}[LySU\舰7 m865ZM}{]u9QyQV1o#¢aPUw]ӣ ?*oai .Qoplj8.ms`Tߣ G孺cl]GE9è&fǡ#G=*o1*Tު;MuWs=#\(p\:j5=Uw=j G-F[uǼ٪rQ;L̎CuW w=FN{Tb2TUwꮺz@G%DQhñḴuj{L߫{!P}[,yUw vꮺzdQy7]uKKpzІcSqiWUC+X?*of:",F01;]u79=QyPV1oF ǦQ1}Q;V@=*o0*Tު;VuDX3abvn1rzTߣG孺cTw=:",-%BM ǥVSc^uWݣv{TbaTUw눰(g8TwpcG-&C[uǼ{tDXZK6K[GꮺGX¨Qy7[uaQ0jqQ}[LySU\舰7 m865ZM}{]u9QyQV1o#¢aPUw]ӣ ?*oai .Qoplj8.ms`Tߣ G孺cl]GE9è&fǡ#G=*o1*Tު;MuWs=#\(p\:j5=Uw=j G-F[uǼ٪rQ;L̎CuW w=FN{Tb2TUwꮺz@G%DQhñḴuj{L߫{!P}[,yUw vꎥIMSO?ob1szTߣG孺cDoATߣUwME9è 󆡺c鮉0wG-WTUw-{T޶ꮉ(g`0Tw,Sҩ9Ӻ5q@=*o3*Tު;MD=*o[uDX3p0o;sG{Tr{E[uǺނGmrQ Cu瞣恡c$ٽ09c/kVV0 TߣG孺cD{Tޢm5 m:\QyPk",3ʺX1TTUwMs=`ktįEmv^:ޣp<]?FM2[iK痩M8ByYWAKh#\'cz~/wŏQy79?oME9hq*T(a}J~K@UwITƶDX3pG-A_di\W|dF%fG<; _y BYLT8G_(m {~1PyKPVuD8hi h[O|iq*T(a]G Q.GmI[Α7ͣ7m#&Ĺ?{u_<z_+[IqdgQs7Fp a݄K0Q)5?۶qϠժm"َ{Tb"TU`DH{Tk",FیuP&¢t(]Py%TTUwMs=6.O6S ̹4{0t?fMAڵ㹽CFrg(B[~ONkgW\ a^p3*oٸL2o&mz%>S#U-p[E*TUwMs= D'8^ ?*o5DXmA[_z/@4k8TwusJ3R9c'ƌ![ݯ _^|% < Zd;oMGFXd>iS[_aŹ?P7Lxq۶v -;[HnMQ}[|bME9hq*Tk"x w7 x?*o]KKpahñḴuj{}Fتukt$dϧMg&tA׫Wnu_x1/ݭSc]/Qv\8af2i}]oQ9} 'qiel~z:5Vp _o޶|@:QyPVFtH4{NC"6DjT(~ᯉ&ɢD(9tlT Qyz/|5k8Tw<D[u#Qh5N36DfT ~㯉&͢@(8tLT Qyz@G%D0p\:j5?|pBҪK><|^FTHMt,6oLΝsfg?,nݺQo}=F;5Q#^Ѿ=EU/]48e]F ˥R}lobGأy:@'\h|G-r6x.T?~ᯉ(5X^WQߠG孺c~crQ;L̎Cu*Sxo*?eV;h}ٻy.lZ5 c$J;vu/_>땧<:O>f8<owzj5']!Ǘ=@xtڄG'^O޴*uVS!b~O}&L-J~*TCTUw|^D;$l3NqF["ph< [ #QG孺k"끢%>+,j"\zvo&j5?:}dS ;x[t-\}z?aM9k[sfRyIRj{GAi1b3Gx9"^TAC[|5C6k"hD8mƉ@#ըQyQ_aQ$5GaK~{ayĻ?*TTUwMs=#\vplj8.mh|مR_wFp+VXjԠgIxaI~og2i}$l݆ q,l}gIS!5"9gw+ a3!̚1p_qLJTߢq3G_LXfXtT5ڢ{Q?*o*Tު{|mQ밡G-)*K .~.*?_˨QyQV5KKpahñḴuj{o}j*'@q+dzYZNmzs~9 *Vjgdxۮ]Tw}d;0Ӫ :t[ףG->E[us:"gfÆkKdC*THTUwMs=#\vplj8.m[ߟA?(g$A1GN9Buw֭[Ixb'-~jT <@(OZ}jZ= ?5"l[y+y a/3~ש|E{>n]BVt Z L aQ0fÅkKdnuD:zunu~[tA[uV5 v^;wR<ޙTSNؖr%?S d`*7,^$¶HbWҴ2 =%>}:jAp]W-g>.[ҿ3. lXp##bazšj$*Vz8sPyyt!eŊRۖ/uƿ KKpahñḴujAٳ)m9 ^r omNo<6Pa6u32hSL*X$ MO=(ժ{4󜧖G+#~kTNm v!ׯ 8};tLr&߿-%ݽ#*o#*Tު{ {#gfÆ?5R{xykmᯉpf^g^p1PyK PV5KKpahñḴujA/@1o?v"ܳ?a3 и a1^|1޽EBn5J-/_NRCtaTZ;) K2τ,V tZ\_09X[q浑w#ӭ_Ѯ]/QyXQy;ꮉ(g8l]ao;[tuP[<_55G-@V5 6M [ӏsw=-w>3e~;pҥG,Iq7?E-3 ^h{WJՓRݴieؓ<׷fMkM-5{\tY^7~pZG-~&¢a8^ yݛnsgvJ8gB$+TްVIWLY=F dV[V3g 4tK^JZUw@[|k",Fm8'‚'"ϡ]{t&…Bۻ9νQ}[\ji(gm)BhއWNrm??鞱$]LJ~^xʨ%ިm-HNN ~Hoe²׆>{`3i}z >N <A]تWzG-AV5 6C j"(G?wm]N m ;F=*oqm5 m:\{͚5$YN | GdR_G:4d{Qſ?x:Wu,Ik?OXI3;w y}A]]hG-~@V5 6Ƹ(-pQF7ck{k&5=3ͶDX3pS}%pI_ M!LK)N0%T⧟իGXTx$긨%Qx_p$$}iӦ,Pt'*TTm]aQ0joc&±Duk{+"MCc]{#0JG-&¢a8^  } ƒ[L罟;I27HXs.\H8y~OZ^x!?aQ-_Tד:6g/F9>r3TU\L2EA磉hM)밠򏗷&^;0NjW^eѝ yk"]֢G-ʣGmrQ7^iy#'ۿAW^IئVm²CTX ON}"eW/Ox\崐!C?*o󬭺k",F01;xuDp-սm等p|Mvc*Tޢ<*T޶ꮉ(gh"Ƃ۷ojU#R½;l=ngQyP{߾}$]*U Q!O8bI"yף¿E-B[urܨf-*1إ~!tFݏk҂ܰ/pY^Fx5WvЁ0-D**o *Tު;ꮉ(g84{T͢ oM0(c{h-TQy۪&¢aԆϚ匠y&9oiCxVuB !ۃR@=*o-*c~JIݮ$A_ 0QyK(PVݱ>ϊނ^aQ0fÅ_avnPyaPtwZQyPUwME9è o=9(ѣ7 Kؿ6*GX5)ғg,xnD=*o)*yjIt~›O,B"*o *TުݟgEx6k"gqnG孺yVm&*~c֏k"#P}[\5z~[ ?*oq*T޶ꮉ(g 9hAm:<sː߄unaŌaꮺ`U0h0ki."HËȧmXGb{Tb TUwk",F084.\A=*oq *DX-]woz4TQy۪&¢aԆcg"os>.%y}V5pvNExQuWNQl2:m.!rg!ۿj3&=KX6k'~ {TTUwk",F084.\A=*oq *DX-]woz4TQy۪&¢aԆD͙gʤ&hʢoxCӡO-OunI+ӣG9O}횐=DTw=659͹QŖv֟y9S 9aG-@[uǼ٪&¢a&‰mG-Bo oMũޠ-{УG-@V5 6$+1qFr{ 9/'̘1pK/,G>9tN@۵#T;ꮺ'n11aw};_ݙګ:n:c{TbTUwk",F0qh"؆{T.TDX ڢ7l= *TTm]aQ0j1_c /# 7o {v+c]qUwǪWUw+/q};)]TG-A[uǼ٪&¢alǡ{T2TDX ڢ7l= *TTm]aQ0j1O80+ωZ%\д &j+c=ꮺ//YS~dX~DeLqD=*o*Tު;V5 vf;M4Tߣ7[aq`bѴe{PGmrQSIVrI6myݯ\H Y^^Q ۶t֫S VVUw;^:޽KÇR+  ~= Q}[yUwME9è&† G-@o&ĢiгG-@V5 6oᚇF,=e4Z5i`»&錘4*[gxg Z_؅#Q{q%huv.} GXͳQ}[yUwME9è&† G-@o&ĢiгG-@V5 6oaVbERCrq@IDATo&dgp[SY۶m̙C8w6Ϛ1'< ]oM&?i^'X0둄&|v Z"LNN&w{TTUwk",F0846lO{TbTyk",L,=l=*TTm]aQ0j&>}:)PRnD8J^_\QCBS MyUw=^x_ *}DK)SU;'?߯ET]a TߣG孺cl]aQ0jMǡa#xzTߣ{ oMʼnA?*oq*T޶ꮉ(gx6\>` a҄;Oy~hLuugߥ-tO$LΘOJZO>ŋ'w{TTUwk",F0846lO{TbT~ᭉ811гG-@V5 6;2=0aF TivvF(A>Yg>ڛ; 4PnJDWuWpyuzۣ{u~A{8hϼr7l B4E\T޶DX3p0Dذ1G-vBޚ~=1l= MhQy_aQ0j '%%2u8u?G'A[ʿyI'v* 9y<2u\4*UAR EͿ/PF-A :6SlgTgN3>c*oοX@bu$*om5 m:\k"uy**TITE[aquC&5>T5A=J8DI +iںV^7*TTENP^BX9-O.*s* &zQPyK|l}v䤓.]Jx-8*Tb9TE{ĉG8vݓ\>{'<$a֭ (jyPyKlYVDe2EkVP%ۄ[>D8| K"즛w&O]O;s2 :gB#,{8y ^iM6r6x.ᯉבDw/PyQ'&4`tC4M$kL΁[ba֭[c%!4m=-xraQk"-\^<4n_)xp95}8ԐKWƟw2;{hZwSqC-񶍿&¢a8^k&^Ԋxk",N&Jw=*LVC)5u/Jn6hQ0SժQ#:g}w)l98+WvUouG[*D`Եܑӿ֩sED/jĩ07#^l9~$|woڴW9#]vKW?ͤSeʢ3WЪ;L6`m\V>޼^QyUrQZ }BVE/k",Fیu4ևey)?OoMX]Mak"K+6q_aQ0fXõ_hy͛SۅNz}tݣxq3&7odP_QP:pr谸qFZygs1}q֫X>ayOճ~&BLWIةS'{L\CUK6jra&/>!}|^?*om5 mƉ5\Xu:o-*Tޢ<*DDXL`{U_CciӚxuckE-&¢a8k&Myӡgfҡ*hskZ~:moѢa XA=ZQ?/,o4'<)Խ9\B ,,_"PyKjըA{uVZɦҖ-iew l_GXwxA¶۸+N_ /߳xj*G[L;ҽu;owE{*~5xVI=KyN}LS$}|^?*om5 mƉ5\Xu:o-*Tޢ<*DXdVwz3@4>A\m㯉OZmƉ7lC?59:D779|snW_Q:+Sr?#?*o*z˭¯&\F3.w~ yM%M_G՗.v X6r66\:OySP3.v+)MG-/,;vP8&a~]4~ˆ޹#7o qr[y?z/O;>B2o_ڴq;ٷs?zqS?XByA "|2adʕ!\ף֭[Vf{/;FxCXE4‰ȴnׯHǭXɰ?m/x.33RaJ$鮉pbl'"4@Qy [aq]XXMDp5ևeIv- M}/یU>j:T ,]ա8"*Tު;?<N9\ 7ۂP۾_m}~R/ш{Sqjo=F}zkS̔n]j[fME9hq צСŽxu]:_TUwozn>c',#vH-Nw@(VM-P}oeZ0''ǕZz,ƪ{x)NxZ2Ƿy:;suT62#g߽'| 7nLEauDiw _~Gn y~~[Tk",FیU4惱Wz$8G-BoMIvWVX#mꮉz$h=QYT籍&EkqbwHuǾJq-CBh1QyJI!}K+ڷ'LOOP +R..&^ދ,o#h/ʰx~g$xYD&ڼpX~kE#A-q&¢a8nåp ۊ(+ޚxuD}#]ek")vWw;EJk"]ӄ԰8тuVrB:\5,;4g93gΤ#9v^۶MX4_OJ!ڴq#՝;KZp}R9d cEy]OM -aRZaE[oUsUgeQ- 6,.au߶mիXbAYw4^8c><߸GzH|r N.ο9M;:ߎvh#co"न%DX3'Z4!4ݱΫ(?oM!@MYD85m_뵍&…Uܣm3[BU k$ 炜T~wn7wvEEPuw*T Tx~) csDG Lo>paU?6p/9K.Fk[D8~ҽhG&…ܓ#fXI7]Kw8tת[/XmuMT>ĆK'L~ 3~fEG_z_in.iO9/mI}p9̥ܽ|9#D*#33ϣjisx76v,ֽ;P왿jmk=/~oSsT ?{39^D~SeTok",Fیk4uZTEyTnyk",N1{w揖hޚ< ?X]m5'`M~Qw!e˩~JpN1vE˖-^WE3Ѽ۟_!:p}jxxv&+JL T]k",Fیo4JQyDXb56n}Y-Ѽ56y$Zwλ k"l3NڵTMP8G孺s/'iv"u@خoD^(OAyo} ۗl=A~[G3,><X03yoߧw;oE>}#?{EԣyEkPF_~.6_%Qgz쇛{/IJwded𚧯,iiiq)Q=B6r6.MC#{(*Tޢ<*5ć˭(ޚR8y{_kM{6-{Ю{09J( ?*o݈?x_Sj,joڴSvmj%KTGzʅ ^ VJߙ`z*ozq4ajz婜Qy޺uk _#?vt zꄩ)IXYvʼ9gLwŷK,gDo?Ȫ+i*MXxq3=չ>ZYb?6xΦk6=PGosZBX#†G+^A?#HzTGk",Fی)\GLA={)DZQyKPF׽A;x}]z)lNŒl(sO%nc4錸Ĺlq{y{*oJ'CJsyBo~jsz'oJTvT])ͭN'm .#=ø oꛝt_D:~ExuA[|ʿTmPU \>Z?5*+N5a(v#5Tzޘɿ4zOiqy:s}<;Ϣv7ͷe޽TA˥>.6>OSv{=Cwvgn4pԏӯNiEsMxG][C-q&¢a8¥pȄ_ó?*oq*M DXZH&E{Ѱ;m5'ZNs=~#UmV9*=qf9a\jThQy?P yP(a֎ {'e8R#Yg+|ԯ]gA8?W_{~wQF o|5a]RGC۟6t~b<wcPLO+.ֵ݋xNޮ/U%;慐z^vAw채?ϛGxQGx[czߖH{xDwKI}EW6Œ [,I8yMxn'Q޿L;oy3? yy̙鮾} h7 .*࣏I={_O3gŜi'ӾÍ5I]_BTB-!&¢a8¥pu^-TEyT[aM- Dh[_u/ZyG&y6 Ç^~-J(~?*o՝%m Loš̭khMSN[| qϾa&)!}pB$?ȿHMM}J8D4*oI_|H*W)s#`BGJ׮] UH z;M}Cی6l^0뼭Qy&šK[@@&DfD81GD6r6 )SJvנK_+QyKPVyd/.OFUv*&̡uݴ%thaص zOk{-^MX&±*\Dm30h"\p{G-ʣޚ<",>DX"a'5!S?jM%#%*DX3m^”Tak%*o *T躷<5A G zqZZ7b Y8y?\}^4dwU<8sR*qHη?HG:{tY㎓MCC|USN?ʚmDʻɣ?|Xn%|u`/gyP;fa55m3۰i"\p{G-ʣDXaiHރ[[oPu/uVk"l3Ag׳ΈxHx)Q<~8dGzT'Tu\ ~FxNm~M||Zʕ#kzH-[=ըAUVL޽?sw?Z_ʽEBa%j"l~(_aQ0f:i+WFٙKHg smoo~7w 4"Wi{Rs9T}am5.oqD8DܕmȵP'DG[PusDXaqplwm5 muѪ&{&o3rrhK7iB8 s#ö'Rq,PyKPF}nOݧ=;Mon]4?<%{?_74ܱ/D۸ ?iv%4@=*o*T/_ړ3a߇{ӟ~Bݭ[7b5._aQ0fD8DܕmȵP'DXgD=ub%Qyk"0[{R֭yNyљ6mj ̞6(W7r i 4UZR_%WG卦]r}4cRy]2y/ )ԕ~ZjڨG-A[t9{.{I 3wH+R9'/:y ۊ_a4ی)lGLA=\rﱝ)=[[w#d_}zvm5rS~rMҡ>UE5PiCzYÆ V/!m)lRGL{trM?Vn))S )s BXBࢶ†dm)lMWu>;ҢT \خS\+7/W~_~ջNGX=%?D_7rwy'w26~)9նĉ.)#EArss妌ϕ[| Ad.6R'{Ӵ-۷֭  _1Gmb 4xMqϕ[|HxǍ l hW\vzzzj kWiU֍niQ_s=<ضK!Om[G a bzzϕϕ[|  'SVqޮ†lq^?n +'UMnCnz*@zMHjj*~.Mϕ[|K=Wn\}ƪ|h^ 諓&Co~όVOFàn9) ylq֛07\y\w~_ks妼_ aX04VMB'lK^Ε+{A!s C祤8)RmCa+u0ф髸V +Y+7+?Wn] PHAD{zMY+~wK/U+hFFڻR j>}QH7VFZTXnԠlBؐ-q(lRS$S[}Os ϕ[| !*{ly/p^ aC϶ġ.Z{y}?諸V +Y+7+?Wn=)׼M˕+۾oSM:毇&!;ۼt@ݞvB="mq{gsM •+nCKr{ܔ7nK!L6S-Z)݊lږ8Ï4?Z髸V +Y+7+?Wn] PȿҞCrSVͿ{ 6uCϯ z`iu̟9 /pkZ߶!l))'F{Z\rSp- P x;)#}M-&؟3k,vmݬ4kW{ ?z@㝘/Gs8q-KA)hO\w\)ޗ\ m2x2`ŏІ?P'^q͑hK!Ӛ35qltXw6Lך\rS"q- P xW;)'|E-H&ُӻKh14:D=T~ڵmo.y41?Ҹϕƕ+.Q( =r=q妬vB֒g&߮M\|ifWܮ^CK!t]8Rp>LÕ+7eW~⻜ 倜=}B^=^hZ6WB㝘]6~)u\%zw=epMs- P xi;);|F-pymYK/Aҏ=6HƟlrSsߥ D'P;ܔզ?2c38ot]wAk\t+ׯ;1;^D_ Dּm#w=epMs- P x;)?S}D-px<߶zUPCS%6 _+7Ł+?Wn] PHAD{zMYm*s1.6]hέmOۺ Tx9]6~)uZz%z6P•+79ϕ+.'@B9 '@hOࡦv7L=‘"|[hZDfט1к{* -rϕ+.Q( =r=q妬6ɲZ xMޱKMk_ a –đBXe}{و~ϕ+?Wn]Nr@NОCM;I!Mޙ{4j)EȣMI{2 4vzBa])}ݵh';1?YxMqϕ[|(RўCrSVŸ!%BhqBm6cӠNLwܺ_ a]'ُ)#pFƹ)9lmsMÕ+.'@B9 '@hOࡦ6L=Vj)c˙8:tqK5CMȀvɁvӠ]v:1?q'WnW~DxǕ4UVah p;6y K\lOڄTA[/B薍롦qW  )bلi#p,%i'O_\rSvp- P x( &)IDATi;);|F-pyiCՃ.mpHHl(.F{^~ [i|L\ah W\)~\rRr@ "x(Wnj\-ά~dCM/n^ܫ8 6ߥ&|VG ao4߽. W~}@SSS)4VƁ8ba.ۜ狖,;< h 8VoYFsFAmBYMO)I}wW-0@ˎ5rSF_ ar46w)cLO>B &MQg®yy11_+lθrS¿a ͝;:m4Os@@ woeր>C! $(WnW~{05|Wm5kؓN6W]/-QoMRTk]WrӎR#]~B؝1wwz e)(Z~\w)C9`kA(0m;)׵MO{fJs:jZauƕB.Ho6ƑꞡÏE;q+hF^ =$+fB(IP|/'\w)C9`[!w^n]·U9oQirb` 0x'utazH!0GwNP//{|V"^hˋv`4^|?B8۶K!6=qyo#Z6PGk-Wn AsWCy"4F8_۽LsIs3 :ǕÕ+.WC9``㌌ ˯uAdC3R|ێwR'aUMO)u]֗龗ԝOAB8< Q=G RmK!-w̜9l{@AXyb+7'_| =tz*vߠyyyЬ,u :\w"ʁ^B}n]Џ?Q i:&6$CN)I}lAB>XM1ϕ[|B8RӞCm;I!lH^8]=/@D~[=^=)CөO&;?'>Wn@n<=^ T7KЕ>r8p-K!ʁc/MPo͛h>S^z*͆d߉#?Pm e>q"5_n.S.pPN_ arw9WTg~kXVT<=os"5U';|+s妐q?KOv7 d -Mqϕ[|+¡)~֥Btͨ;"zt$4ێwR~'$߾C]U.Ry|)\\\ :tEҊ̙ oޟUS;WvXNI5*]6:zWڵ+,oxǝ\)n\^9UKp*tBu6EVAp=?\w]ʁ/5Zw[Ѥ5{_`'N? ԉm;) DSG ao߽.~)<}{Ej]NS.pPNrBWjeڿ<eUԕGF+3[۴h;} BT|:V+śnmV*t}ЕWC:WVߓ+7ō+?WnL ȒK޷h!^4z+D̝vr0Ku"}$R16ߥ6$LK)I |l+K!\a"l=pp-K!ʁBto-'_>жmۺ#]S KJSӝ+-̙G -Zchk^?k;qJq'B3{Nl]\)~\rRr 0=`钗ྙKcǎ~PnێwR%$i{C]..O kurS sߥ´'PێwR%3ŋ^jLw\\)/_ݡ#ީܫ`ګxzƍߜ{~غ;OF_-Y^2hBhP'?\w)C9`k!RC7x+)wmܢ%&׫+,*Po^ &?\w)C9`["u8JnS޽b3v”>;>lk͠.mRy'|O\)\rv.5N.l] ֶ׉3lcN; vh-[+[h0M+P4j4J g9gP|_;y #ޣWӮ}{hn.;`Ohݺ5o3Wn7W~¡0m MTwLR{x5e(Md+Yqpg^nu(B /?ŕb+nWA@~R^ 26ߥNgk8))ކJKU%%h>Jn\uM5DMc({׭>1i4W/"tטAS׺j7m]r%tŊJ-^zc((TQ;`%0&~WrSsߥ倭W1;3Rv>]Nny1贯A1j+7ĶIsUG a.ߗ_?Rw_ aw|֫߾Gxr-K![ B)iOm;I!-W+`z0 ΕΕ}-gͪ\ ws妸Hv6YМ-;~WOq#h׼hf.d'G?]:\ZZ]Z߾,o\w)C9`k!QZ;tyڝ|@ӥmDێwR'aUJ)u_~HYo~)5Z~m|n}ϕɕ+.p(l-=8>x'p|߉ӧkW]D۸͹7?ס8CuWn_wկ]BWȏ()!^=mݡyoжOk܂Np妸s-K![ fSHsر##Od+`~8Rc߾C]U.O~kmrp-U_B8Lw)YZ~'N~ziƹ>ZY~W6&/q?o< ኞЏU!A_(.sU>{^UIYh/\~ze] /#Wn;W~¡0ŋmڴ2WG=}v2ڣq{pg2_-MI:uxk7/R+)V*cf&|-$\'\w)C9`[<㠟64pܹ!ZUNd+5]^G a н߾GguK!eZ6w-b\w KRr~LHq|߉\0 h;=–Wn . AǠWD[h든Wn+WBW,_ԹgоiiJ.fd2{'9KǡVK=z+*+DŅ+7ĕ+.p(L"4_k;|C?zɆ;ݮ+fKH!쒱Q(sk]Rn ]<+7+?WnݮҥºS}|rE8R\ڱNd+pr~8Rlp=°<_ a,Ӳ!]kps-U_ԼBXÕcVGNoLw \A9T|R{؅qP&n{vrStR=Aj*=NEgЦ}5k`G_a.i2oY/z|zn+7ō+?Wn] PvE>@j|Yh˟dϒoY#~dەrE2gu{ǑBg#lm#l֘ٺ6Ҙ6fB\\w KRr~LHq|W\)-IWC;n]h)+hS h•x+7Ljz 5~?; }㏡~O{O?zQ cshߴ+n IazOmNjq-4mh%uUA N!_s>W#Fm+6kLSX@t Z+7Y+.WC9` O)itGm;I!NݫW$to1>[5XWa OPԥKu55^]p/]_YuTw#ݹs'>vo<fEŘMj ꞟbf:ÜKܧUmF&ߴHnG+7ŕ+?Wn] Pv߹O.8oCA{~ݲeKh{)pjǑB%+뫅K!,Iǡi\)ɸs6w)!wԶB؝BUVq3ųerSLL_v:r(ZZGBdu,WXtb B:z Ь,<NVnUgn/{h"MBU s妼ϕ/߷oWo<i~/h[|+ $#pBi}%y/w2:n5E|]M0C;:6#=Yj"l5uuSjC1:x0W⊶pΕ߅=Eg@1 Sw=;l'_6k-?y*LW^$~nWFrS~r-<<<~yki}m޼9t*`;'e+G a=k͠.E8ReG>{Nĕ+7eW~~.0E_-7_ܺ_Ӡ0CyZޟ_E Wn H8]|UM -6M>7NJڵ+=tpw6ݷ:߹%꽆7|+z pmu\)>\rv]#ty?nx } K7R+ 9kn86~)uZz~%š;|s-/®%Ct\rSrqRSU^ aϭ8[/ch0?`-Mp^{/_ twf>yC=?8WL[s/~JjU AS[Bz4еϨg;OsL}RL\)8\rrE8^= _~Aʝqz hhO{z '@uOd+ֽ$؟_; i5|4 BX ᤓࡺ24\)sw))my/ֽN=&` uzϱk~?+fPalύn \Xަ ^&- ))h>?tqP[&C<pT˴,u8vt->пK_B}T \)4\rv]#t_y'#JJz&tFwCߧ'= MMM&;;qZ6~)u9d?^'Iiu}4H!\yVq jWz\\w)C9տFSI!LGm;I!OU݉SRRmLz]#ر#dMD7_n7;v@(5lߧtT5~zJwԿX|||brh;O-[{lKN0%+쥮W)؉vBf4hH~Ǒ+7ŝ+?Wn] Px}ot޺úCKKiU?7o t˖ڵ[*Hb"]y/pɮsU;:q/ݾ7zBBخ?w3o\)/sw)ԶB؟<9`n%NFժV:uC[U'v /6νU]={l^m{h_;hGVЩTW>k{A':tp@ÖM!ϕ[|yoo{} B9n'v´n8Rllͻ{ͺqR<^A6p+7W~~.09ږR'lխyttBJNߧM_q1Fʼn[.YKAthM4Fʷ>{0^?Uq{qI)FsGk>soь-ps-P$}ٲeHs_ AvɍSCky/0nWK!kf;.`nఒ^M Ⴧ0w)|{Pbϕ+?WnݾK!LCm{) +g~u/pꩴ:O~ֹLtq0t-_ a-'߉ۉ#pу۾º"zlZo|7mn+?Wn#\u.0E-6$JO? oFD+~#` `us.)GQpR4ܫ7WM0RG{S^tsOG;H?f7*X-ټ:?;>  @mDⷍ#r8q-P${0QԋԳG WB{TڳU|Q{)}MܨW;9^,F|Bb֊{8#pMy+[K!L5[m{) 'gc錓N4"^[np֭~%БFAty:4´gϞ/D0guT+j{tuw*ڇMr{ KnSteK#'\)\rv]#t?WahKfu<Zg'4Ph9%h &=M/V̫đB8qX+ߓFRh^1[sϕ-ߥȚ†WkA\o^+~xbݎׯG3?fA Wh5\vcw͚cUkC/~ qhK(MJ-{f䫧g*W߹rSr-<=<'] 4mB%A=, /CnWK!՞e;^8RG1㯽=Q,)דBخ?w3oMc'IlI\)Ksvw))fmy/!+q֭[;Yf4#=WXdž.~ڴ3f]\YFu8^OOO}Ɵ}뭷}φv:]sUXuonx6z(d+7YÕ+[Tm/ՕukbSIihǎGu4%ʳJƉv?t8R'ݭK!Ndw)?w'Js;@vp-U_TwK!wR%%779sXcYc:[*,UC7WNv;^sw? ʕЂ=/6<}ӍЌ(\!nРʂFxm<^}_?lP:7UWOnRݫyh ‡pPs-ueҥimDy{Oj5}+6~)uZzG aԝnߣ S hhK!^o%^qMŕ+wK!L ږRwn%=xMAzso-О4e ^zF-~_bظM+oWUOyUEC~ƀ0 q)ݽ5 xH4{i=خ{2x ƯaFvϕ[|+¡v}@<7K'Tf{z:⎛Od+6drkǡRbt0=l363~¾ƣچ +?WnJ;\] aX0Զjk@IDATw`IIЫҋ {Q" 6O} 6P|"){DB/I $!w/n6e{ٹs9ܝWʀ Q0O?_W=d1=_LFwvv6x[jqp?3V6l}^5p[¿2q: SCWmZlY-mu\)N\rwrG5<pPd~8^V?:UVCRv}f&f[(`RvIK!z/w5\rSqRSĢCm{) ;T~?A2r!p߿Z^q]gv}'<~'N@?~ cZ!FōW."uj/uAiwhO4hgdC|h_z%P[6_J-<+7Ň+?Wnݮ1) 6ۇ.6l ?/ԎmS~ ݛj&XnxlB)#<[#pƸq|/8l) FDo|Bץ0;ƕ+.r;)iD}'!yVyM]kZK&i0żV_~%`F =Q,ɓ<}0tN/p2J[4 &\~y\<]ozF;fck)=e1m/NJ 6_H!5T\w)~7vRfS-ڪ_^ݻBO?th:umdەR2ܚ8Rbpw)%w)eEؒudaH+7+?Wp}B"gږRon%΁;a{լi0żV܏<`ֿtwfЙyWюIJ/4i&(ݍ&\~) rxwA+|x%ljcR+ƙGԊvSq&SsZ#`~6EB׷NqVRbK(w3\rSrMK!my/09YMI}?xUоbbK|wQ:ڝ:un{.qGܝZ@QkeAChAn7'qbuWg\S?u~:l9.Xm u\rS~pMlqqQ {ߔF?q˽xQt1gZ鿽w}5z^ӏMp-u$veu7˞~bywvK##ݮB؈iSTG aC q.p6`}B0"NG؍ϕ+?Wn] aY\0Y60V6֬ 9jEv3t(?+a8oTի}:2ݿ9OqjV[mP^W8b _eKS{ܻ/9^UMzZF 叴>ϕ|ϕ[|kerJ)_S~h46QocLN3hvn;5U a;I˽sN nM󶣽i貕5UN+hh G4$4\w[NŮ_xzt6hquEgP/ժ_aE\#l5R28RbhÐB w)H7\w[w|]TMvd](nWK!\T&{ߩ#E؝v}aFh.(*_{Tŕ+7W~}B"Cm{) K'㣏@[E]3X !,~NߋyV\z:&zFbYALW+/FՊA7^'WZq[kBisx1gOԼMעMwMqϕ[|̄[=&}w3 v†LB&†0 3H[K<.p|*sM?Z.\i/pZ8[|ω4y2٩^N>r_-Ӭ)MstiCoCwXTU+[q S*B'Ce@:={^0QC$+Pw Os#Hh k#/߈A0J\ެ,H9=MUP|'q~s)]eەR2#8RbXÐB;ؼ/.[ ak-v%^쇣M\)eG+”مk^8m^P˞`Ju^qLL.(vC  msF ;v 9q\ed}зr?x4/Qoڴ m;_ݵ oheߵhXpC*FYk|!ڧ4k~.T~usB\wVȯ`QV84]М_/*< j9{fs`nWK!lȤ vH!la!p 6<5-5;P}/dɕ+7(WhB2_wmRzeO3·"bL^v\2eО~j:hzlB7e4UJ%za#q*oW 7)<=khjj*v+\!.?A]~.\~vs:\wVȯ`uqt, 3w_|E{[v†LP'†jއ׋y [ a eDJ&˕+7"WhB2_wmRzeO.ju;1F}5O/M:wB: Si5qz׫!}e\v\׿_+c;w.3յ״UϟWQY>xpq8r-۵2F~)CP;Z!N(%}qQnWK!lD wH!lRCy^o|*\n)0{8}\)G;”lB8C-{MoRb>Z;'.Zp·h pCǭ=N1ЇuuIzS|w2Wp^Q>[|yZ>.:)Wn !W~]+cWwϙ}G%CoQLI*WW_з7tРAh|+6d&;q6@)!ܼ7s>.xH=L W~ܔ\[ apvi[K!衖=&γq<z孷jH_w] RԡޣA$IrhXh]]>vNffh]y5;|aaKFuh6nڌ-7-Wpe+7Yƕ+nN٨F2l|Sսj:t6׼Mycf4qv@)gQg4:Btg_V{ϕ2+sK!L?m`6~)=Բ'Rמa]u?-t.]n]YժUs|vC/G{M6qZ+=(tJeEXV͜9sJ-Uyxj&Okq @Ӗ8fSw+/{ƈiݫpr-۹"<Xѷ yX.^}r{dmRs5đBv'n_wm'VZݍ{A5ۣtEq;X_oh܀N [Uy>v_t!MI;qq妸q-۹"|z3aӠ>WAM1y ڧTMp{mRs5đBv'n].TYv6tiR"T/e4F;܍)?qs9u в-A.ڣV'߁ tPǕĕ+n1ca݋BdVSO-ahܫ ׼My`f-)q )3RRqѦrK!]{tQpMs巕[ aVã mRzeOQb[Y{c\vhw ksՊQ+w7N7E{= Qg PowA'1Pp:n.w!29~ؽj&\~['WnW~⻝+3f̀uCoZZuϊ̵y>&oc|+Ŀ$k[>7l8:;|+_~OЮݻ*{z=VڶwMf]7\֯] =k Rʠ-Pr|; eE>yg\)\rvo_eh]l۲ڼu{h6j׼ʍ$6~)9lH!QR/6E-sH}wgTޝ+?Wn,sK!L{hG/pZ8{kbՊYsCZ84XPnȠ_0o:t9ϙ|ݹ+>^mjÕ?RUijY խZVxH=qr-۽"<%~qpݺv۫`(\)lBӬ&¡%ph`q_R;wwFYso;”FxmRzeO܅a.\:uhl\q8t- k=;D˯~PS62Hƕ+n֊{ 1oEO~b-wt\+] arN:q0)B{]tPv= w=tW\)-0ej6~)=Բ';*Ac|+Z@%oذYqK!L鮚滻g?6,Yd k׮Yy59n4Q+@p;q7lڼ9Qf]Vš4#zQhbYuw;զ$nơr~z8~\) AU)PMwMqϕ[|7ڙfmy96]F&Fc5rS~/09YM)U! u`ޡ9MRc]D :3\rS r7[ aLwTݡ ksFwcfđBX aͩiEp$ w\rSq7[ aLwTݡ ѯN7=Wn&4kQ#2F a REKsZ6bvf\rS r[ a8zi{_ @))q?~9تk_/j~9yϕR6~)9ZRH!,uޕN 8R`jCp 4\){-0e^w/pZ8JCi'7-{cCsÍrW &t MIG+7qqGGP ذn t7MЎ3ܸ6lѼ__&\~㎴7\woWu B>qE\rsо-A#yϕ6~)9ZRH! /w5\Bؘ k Vg~+?WnJA^sK!LW]/m`K!衖=%%nƸ*>/h% ݇]9|2=ꮷU[ ]шMIG1:иko"nV+^~)X_P+ssj|ô56`:Mϕ[|vEx„ ݯ} M);O~Ћ.uz5rS/09YKJ)5R%RƜ+Ra $\qM)ȕkn))׾ 6~)=t_vnTvEozA/x_7ڶnC`²I1]mVV׶ G?5۾0+k׭} u ZPw~8n4jޡa&R~Wn W~⻷+7nDT'K}%߿U[NQ=rJ=Wn&4R/RSS5enmt*ZrK!ޞ'R߽qMĕkn))׾ 6~)=Բv͚Lhvn{ u-G^wКpS%E=~G_‰{kwBݪ{e>mM(]\,ǟnm۾0?W~AC9+`eg3N[wdP+7+?Wn'LɁ ڹS'hyh\)lBӬRK!9toSAr[ aq;~ -jSm?Wn+[kľZ2z]z5yU߽|q[w{ܑ+7ō+?Wnݙs"]w4xZ-[=5dDt5S< ׼Mff-8Rk6ģ QtÕ-n)IBvfN\);-0ev6~)=Բ`yƑ~=4]ץkzVt8uU/(~u@ݵywrs15.WY) zPMVr叔;=]] 믿"+W_:c2h kcsǎVu xM^שrSsߝY{%F<7s|<ɽ'C(׼MIff-8Rk6ģ QtÕ?Rn)I!uf\rS rw[ a$;) mRzeOItľ}]WVpyл|[:h8@'dgs7oތwTEr&_Gam۟ y 4V5CmgᅥDʯkܑ˕ƕ+̊0űi>:qL=?_j8w  <5rS~/09Y &š {ԭ1p叔[ acR8D{Hx0W~ܔ\B2luwiGgzgZCQFASSS|_^]ԨXlǍ6VRFF'A6qۙj͙{ <,бФVgBյvֱL ]|7wl~wo^y T=ݥsghv7N &y˕x5Oi~O@'\)t\r:^pB˽ӻwΝ/ZhvY{gӔks_ arNJ!04x}a8NsK!tfs>}wg+?Wn$rK!LAHajӷU+;'{۩^|h/ZV=z׬tIZ~ć}%w2{cq*^\)~\fwe]~[в ;ytُ|?zqWnW~{p?z˕CȚPQlUyϕ6~)9*pplt;-әݝQwV\)-0eJthGm mRzʞ_~2hWguGnZٙs'^q֭f͂&{zGǮ>=R:tӦM͛7m؀v%J_v塺6MĕǕ-LkoK{vӯW_hP]u/WnW~}]$࣏>뫾).?nI :훱hvxԕmyϕ6~)9T m)"\)R\B2Nz^޹s /शw)5e#F}1]=~J!'IPCb4,6q 5WnW~~q2Z8>!V+?\ުU+`OٓCAOWw^ۜ%`yϕ6~)9U a6qG~+7E+RSU}K;W~ܔ\aԶ.lV2FF'^Zvs䠝V5+nl8Nř+7ŏ+ܟR/Եg5Q+5|EǕ+7w{ذZi6Cl246访ھ\)_mBӤR6qJ?^sK!LW]/m`\rSpBXVipR{LE]Zd FЮ];##VU]co%833>@;׷BܰI˖UwFâ WrSjr1, 1Y =rӠ~}쏏'\Q]\)T\rGIu@4-!38$4 hWz7o&BX޶Tpq[ a8zi{ϕ2 &ghxwrSl㗟Fs^q%rC{Iml)SMWn W~2SLQqM-RimQ\)K!Ly'kESϕ2 utGbFwMƕǕ-RDžAϺԬ;L(ЌO>s+.}n5^˕Ǖ+ww/uAѥ[ƾdZ)^p3m5rS~/09J!p&oq妈qw[ a,3-ͤ W~ܔ/09Fŕ"`f-qW~ܔ?\޼y3B|T& cO'<ںFp@qm_|ٕ@W4дW/jL.OB"0ؖR%aϕ+RSnnmhs ~)4Z|6(&4mts5!|hή Ok~O+7+?WnS}߽{76h%yLC-V+VUw}x8.FChN}hnT|a$my/! j[86\){-0e^wsM`䌻j柝+7E6~)9j[8.\)<zAdSB:MϕT'LPD塱C΃S4d?z/ڏyf ;CsԽb'M \)mBӬ%ϕ+.n)).ʕ+7eiR3iKv&4mtsʯ?3iǠwBVx=,3 \MaWch?6oA/|R`@IDATtȩYx1^NjEteuw菗ms}O_NhϘ1pZ!QhW\x⻬c&߶Bؐ,-qW~ܔ?\MB25woh{ϕ2(~))BѩET\)K!LiVpqMÕW]O.m_,Y㪚*d!'Mϕ$߯z$IjM2k7v'/ڶeEضubN9lKN+7ϕn))QS|6\)B"ZIOŕ"`f-qW~ܔ?\M~3u9s:h-WAwTkh7k H9B/09YmKŕ+7W~]RSQ] +sK!N+?Wnr6~)9j[8.\)gߞ܁_V'~J:Wn W~[/W߻_չ_d]q>j| [|w\)lBӬ%ϕ+)RS&z m`/\mB80g#c0Y\)lBӬ%ϕ+iO|I]Gwu%V+|\)\muo킦~L1K?vSh9ZK9h0[VpMydf-qW~ܔ?\MB2]5wwiΕn)s6= cqϕr6~)9j[8.\)ݺð$7/zaWȷH4HyP+㗪k˖ AzTigb?~>Ѓ}[|w\)lBӬ%ϕ+iRSF.mٹ-p`F#a,\rS./09YmKŕ+7W~Ӹ8 KsU5BNʕBt} @<>zk4N;𶕠-Q̦W^xoV\-}w;\rS>/09YmKŕ+7W~ӸtWM]s7[ \ub;X9s\_ arNږ8N+?WnqJWXDjիWCWZ֭Ap7rS_۞\rf3jvwی+7) {e|w:8ND12efE-6$'mKƕ+7W~]'N@KUjwhnNnǏA:C w?:9?^s7j!S];oꈛ/箁Vq94IhƤ85; 1AlbH•˕+K!LiV8/SPK\^Z;@ ^sK!9}{ϕ_ arNږ8N+?WnsԵ+r.&6U+?5_]]|tzhíH7#wMqwzz:B]zMhREB7Vn/S^ vCj6w5N ݻn4#CvIKs8smRsU&kNA-s{]RkIT?/{-/нW֘&W~ܔxK!LiVpqMÕ_7 ziq6lʚwks}+ȷ#' \)Z\y(ЧpZ zp屿?zӨ*ϣQ+b-l\m] arNđa){y[ a-^Jz{f+|71pM9`f-qW~ܔ?\M{`? -g <4~J:Wn W~Gz;B_O Cu硗Azp:M!ϕVߥ&4LY֜ZqK!M7p[ \3WZs_ arNږ8N+?Wnr?'fNuW \GV9; YɸrS(ʝҥKS( Փ4QVNj =·޿W6T~z7\)\rf#+šSPK\Tn)ݝ.uٹ-p~*Tmf-l\rS/09YmKŕ+7W~Sd ?;w,zZdj*+7+?BvMO61/]ŋи$h|#ֻ%P7=ϕ˕+K!LiV8"9t5MBi`R矝+RͯBfΕ+7m1ϣ%KvK9wWUw)0\)Eʽ~F,/r@&CwQWHCFyk333~=޺U+u8y4\؄+7+?Wn[}BӬ2qn"z ^ BN2߃I")|n~:?B^~|uF~Jd1ϕR+܏i[ZbvɢДB5Tol"υtP7\+77{{{uГO>B_d BOV۠4mϪxCa nS rsN aU&]ǩtwr)H!Nw^] a5W=WnϕVߥ&4L^ Q;]|9է4ܻe_7̕nSio5rS~p?Zݸ}BХus/?@d@?\z_Z=ngM*i[_۶`5/{mS7|'rsN aU&]ǩtwr)H!Nw^] a5W=WnϕVߥ&4L^ Q; zeWb /v:t5rSp?vL͓ )^~[hJy;luh5vI\hjJ^0\)\rf#=׼7[ awKv.R{W\|MqS)6ߥ6$=mKƕ+7W~ӹ'SXtCc3'vZua.?vHZ!1TͧEHAj{ArSsw)9*GV5R3LrB8?5_n.+” &† ϕ+n?<rS sw)9*G a){yo*w^^{u-uN:>h)VAshSCx']<ΒKSӺ]ZO1:i4s_+V.n_rMƕ+K!LiV8RkNA-s{Svw껻g/p:^]M1ϕVߥ&4L)5-<"gfދvVfzDb D|+7BN0]oPMQ ?"^4ԩ.nys|ϕVߥ&4L)5-R;;-lYB8t}rpmRsU&šSPK\6~X|]zm\)[Z{C]5;[9lj4n!G&A_E{SHpMyƕ+K!LiV8RkNA-s{۸vfz3g_ X*RmbXrS sw)9*G a){yo=ǐC]cn.gq8D'Tc}pz>𩧞J]餅jye|hk %4VhطVp`)Cz \)ٸsw)9*G a){yoL|w:,K!K^EM+WnW~ܶ.09YeH!9t5m>t,1zfųjG/GsDߩgjk5uZg|zyebо Es?MSaz#+P6r;5]ϕ+?Wn[}BӬ2q֜ZqK!wg)UrpmRsU&šSPK\V'\rS~rmRsU&šSPK\Vn)#&u#B8?6wX>V\m] arNđBXs jkޛνcâEV~<]ӷoD{ju /765Hs妸?k;7߇SU}cj%յYԟ).)t\rSpmRsU&šSPK\tn)ݙuYB8?6 wY>f\m] arNđBXs jkޛ]Z=Q."5@k6&RmvA{ ]z(dc:!CvdWn 9Vn+URΜR>̋_@&5 =sE5<,ڦl;55Ք!y:\)sw)9*G a){yo:L}w:/p~L8"߹CWni\m] arNđBXs jkޛν~CA3&J˧n :btb\)d?guo&moٓNX~vwg͞vi@׮\__GCㆸr\wf/L_.4,U'\)9sw)9*G "){yo]=h|WA'~h*8;嶌h Qo}utԅJ+ZWB_"/;3f %]/6y*ǏC޷mW~ܔ\rf#+šSPK\vn)Û.^Z )mNY^\)sw)9*G a){yo;Sw#_5W+|TM)l oG o\ m֨>t/ӡ7S7tˆfQ7\H~ ]ta6Z|\)Osw)9*G a){yo;M}'j))imQ\rSpmRsU&šSPK\v s^k%Aޔׂe;A`O雡qtϵЖLIe~!TuӨ;w=c SՊ4&|UVںÍ?W~ܔ'\rf#=׼[ 4Z|6(\)sw)9*G a){yo+wNNA/_ M>2޲i#_~1CPOW[}upI-Ϝ}lZ65ʗF ?`Bpq-{Vߥ&4|aw_=!u/pH 88Z#Z|/7sTϕVߥ&4L)4-O=8.7Z U4~ ʃ Wn -W~;Ͽgm] arN&/]|SlR4mK!a}4ÂC\rSpmRsU&DSPK\VK]<~/@?#  -jc+Q<7guW'M+2sl?4.&:m%SOiګk/z4~ ʃ Wn -W~;Ͽgm] arN&/]|RRMRX|wrM+?S,Qwl׮ah[y/ھt"8mn|zu@Om]ҩi3fFܽ[a]JSCk%SVdggxuiKp}?\wf/L_'+lLRc2P\rSpmhrNO)'SPK\>ZSI#uᒒ)ZK,)~MQC= hQښNЧMϕ[|K!LiV!z"^ g|B84"=#\rSpmRsU&DSPK\>ZgmMGl9pzuAQS=9++ -]8o|7߅&wZie?@ϬޛFq?qyW\wf/L_'-|nw)Ckpϕ+?Wn[}BӬ2q ҜZ½iZlAσ*?(ؿw . Ե{9T@{͞cЄ_C:{Lh ՠGE6.-Zq?x9_\wf/L_'-|nw)Ckpϕ+?Wn[}BӬ2q ҜZ}4);zjI[nv.gq455eK.U|4W_ ԵvEmV\RM^u\QNyMW W~;Ͽgm;h=NB@ e3 Q2ˎө3c[Ȳ֙q*VTH *Z?T@Bp  ( x#N֧_.zA9wt;o٧>;RS}˝~Un\0Cr/n=[5᭺]?3-}Gar &3 TT߭rw#qCT驹o ~ܝ0S=@讻:bf+.z)ǿty!zulXw<^c$O>jW5/w_o3϶ݍp+V&q]ŭgw#MmݍVk:}TTߙVNrj!*`܏{؝HvP;[_ٝp>1\cD_}~o}%B?}> %jY|ظʕd+Kw3?zP}Q}筦;l'ba2^zkz7[ww7[w[=|lΜPS}ݍp+V QqK~l?7"?篸.~cy-/NE'uqlg>?Wt|QO%ck_/w'o3϶ݍp+V&q]ŭgFw#<6NWnݿVGMǽU?[3TT߭rw#qCT驹y.W<[_tE/N{ Нw>.듟H>LU?C_7wo3϶ݍp+V&q]ŭgFw#F7[Q}g|_*w7IXm!w_ۏ_?w7ǚ{n3kg#/^O7_OMz=7I گ\za0V}PS}}jArFx$mPշa7Yۛӱ~s^9TTrw#<ݯe檺#dl!wϑ4#/=W}}Ͻ{}h+ [x+__v3Bw/CsǺNN=ߝӯ}_.;;o>}h慨3TTrgi$W6Lf㐻܋[όNF؍EwTߙo3״ݍp+V&q]ŭg뮻׬Yzo=zN?࠸~zUIg\a>g|> =O ڕ^KC}С6lKt=8jQS}˝Un\0Cr/n=5᩷ύ+jNTTrgi$W6Lf㐻܋[6M;n]'?埅>s߿T'.})Dž~s p W;qm4 ]:E;sMO-wVNrjd6˽ln67StW~G9;}M'ba2^zi/_Hwҹ`ٛo~ow'_$^w;tS|= ?O;:t~Ӡ|{}PP};}M'ba2^ziz7Soc+jPS}˝Un\0Cr/n=329}3{uo{^\oڴiyC3~=^3VC\ЅBu?W"7}:>e;_S}};}M'ba2^zftz7›nBwTY TTrgi$W6Lf㐻܋[ϬLޫ?O3ߑjw9Ϻ__z~CСs׿3/TTrgi$W6Lf㐻܋[ϬLFxz7q?K=wUn\Z8A*`܏tgx]w\kG_cc])/.O.ڽgy;^kV}=CuT};sFO-wVNrjd6˽n7[w}S}g[F8LO}_}"O7lЛxJfub#=>tCo_}*k.<+ۺO{v'O<W}mC_G8?շܙg[F8 8.3+ӻmޭ'cRsOin$Wo#X2=5cF_w{7ܸ&?~ կzUL=|GwO3w#|km\ş=_7.\reWtu&}lQ}"?w[rw#Նlr{q뙕oZ1g4SS}ݍp+V 7H,eݭOz˺Bov q/>,teInC=4t~Uŏƞ ]塷3CZyWK]>hq=cᾭ;sBO-wVNrjd6˽lnY%fRS}ݍp+V 7H,ƍt'{]uG8ſ\xЛo8??Bxo痶[__[ ;t\]cᾭ;sBO-wVNrjd6˽lnY%fRS}ݍp+V 7H,~ݿyS? 7^>yޣu\l/??}#`+fFtSO>|?vxiѻ~? 좏uQ0VC?w[rw#Նlr{qovZ2ίRsO)n$Wo#X2=5cK>y:Cw}A??tS^7=-|(N~{豸w7N;ƿn+W^zɥ_/~i<. 퇡guT|?շܙg[F8 8.3+ӻmޭ'cRsOin$Wo#X2=5WNxA˽ЇT豿WS=}B}@w6l,>ezlאk9TTEo3϶ݍp+V&q]ŭgVw#и?#?w;l'ba2^zvnl[;$S}g[>oٲe.CZ*]tiՓ_r=;b?XL;xqֻ#mA=wUn\Z8A*`|ʋM;SS}˝~UIXO)O#X2=54^;O_[?_Y$;ݚ7IX[ L.?ͷ.4YTTrgr$+V(iu#GZ}S}g\.Ig% T a枙{˽9p5Tao3׷V{"Նlr{q){ ?շܙ>ɀ'Y Յipƺwg^rm#@=wF[U'ba2^zJ;CFO-w&wO2pV@upZ*f{PsO;s}k'IXm!w驹ΐQS}˝] x"0P](, g`{{f.0S}g\Zp+V&q]ŭdzj3dTTrgor$g% T a枙{˽9p5Tao3׷V{"Նlr{q){ ?շܙ>ɀ'Y Յipƺwg^rm#@=wF[U'ba2^zJ;CFO-w&wO2pV@upZ*f{PsO;s}k'IXm!w驹ΐQS}˝] x"0P](, g`{{f.0S}g\Zp+V&q]ŭdzj3dTTrgor$g% T a枙{˽9p5Tao3׷V{"Նlr{q){ ?շܙ>ɀ'Y Յipƺwg^rm#@=wF[U'ba2^zJ;CFO-w&wO2pV@upZ*f{PsO;s}k'IXm!w驹ΐQS}˝] x"0P](, g`{{f.0S}g\Zp+V&q]ŭdzj3dTTrgor$g% T a枙{˽9p5Tao3׷V{"Նlr{q){ ?շܙ>ɀ'Y Յipƺwg^rm#@=wF[U'ba2^zJ;CFO-w&wO2pV@upZ*f{PsO;s}k'IXm!w驹ΐQS}˝] x"0P](, g`{{f.0S}g\Zp+V&q]ŭdzj3dTTrgor$g% T a枙{˽9p5Tao3׷V{"Նlr{q){ ?շܙ>ɀ'Y Յipƺwg^rm#@=wF[U'ba2^zJ;CFO-w&wO2pV@upZ*f{PsO;s}k'IXm!w驹ΐQS}˝] x"0P](, g`{{f.0S}g\Zp+V&q]ŭdzj3dTTrgor$g% T a枙{˽9p5Tao3׷V{"Նlr{q){ ?շܙ>ɀ'Y Յipƺwg^rm#@=wF[U'ba2^zJ;CFO-w&wO2pV@upZ*f{PsO;s}k'IXm!w驹ΐQS}˝] x"0P](, g`{{f.0S}g\Zp+V&q]ŭdzj3dTTrgor$g% T a枙{˽9p5Tao3׷V{"Նlr{q){ ?շܙ>ɀ'Y Յipƺwg^rm#@=wF[U'ba2^zJ;CFO-w&wO2pV@upZ*f{PsO;s}k'IXm!w驹ΐQS}˝] x"0P](, g`{{f.0S}g\Zp+V&q]ŭdzj3dTTrgor$g% T a枙{˽9p5Tao3׷V[l٦|jժ~ҥ/ln3]eMpbj3jTTrgor$g %q]ŭdzj3dTTrgor$l7= mRLT{f.!-j3TTrgorò\0Cr/n=%SsO!;s}'h`ˍpVlP2^zJ;CFO-w&wO2F8+aPX8-X3]aSS}˝$W6Lf㐻܋[OS}gȨ\.I<J.NK30ֽ=3r{osj3TTrgorD8 8.S2=5T2o37}O R u0s̽F8{0?շܙ[=Nrjd6˽LO=w[MrdᬄBaT8c;3s/w6;#LO-w*wO\0Cr/n=%SsO!;s}'D8+aPX8-X3]aSS}˝$W6Lf㐻܋[OS}gȨ\.I<J.NK30ֽ=3r{osj3TTrgorD8 8.S2=5T2o37}O R u0s̽F8{0?շܙ[=Nrjd6˽LO=w[MrdᬄBaT8c;3s/w6;#LO-w*wO\0Cr/n=%SsO!;s}'D8+aPX8-X3]aSS}˝$W6Lf㐻܋[OS}gȨ\.I<J.NK30ֽ=3r{osj3TTrgorD8 8.S2=5T2o37}O R u0s̽F8{0?շܙ[=Nrjd6˽LO=w[MrdᬄBaT8c;3s/w6;#LO-w*wO\0Cr/n=%SsO!;s}'D8+aPX8-X3]aSS}˝$W6Lf㐻܋[OS}gȨ\.I<J.NK30ֽ=3r{osj3TTrgorD8 8.S2=5T2o37}O R u0s̽F8{0?շܙ[=Nrjd6˽LO=w[MrdᬄBaT8c;3s/w6;#LO-w*wO\0Cr/n=%SsO!;s}'D8+aPX8-X3]aSS}˝$W6Lf㐻܋[OS}gȨ\.I<J.NK30ֽ=3r{osj3TTrgorD8 8.S2=5T2o37}O R u0s̽F8{0?շܙ[=Nrjd6˽LO=w[MrdᬄBaT8c;3s/w6;#LO-w*wO\0Cr/n=%SsO!;s}'D8+aPX8-X3]aSS}˝$W6Lf㐻܋[OS}gȨ\.I<J.NK30ֽ=3r{osj3TTrgorD8 8.S2=5T2o37}O R u0s̽F8{0?շܙ[=Nrjd6˽LO=w[MrdᬄBaT8c;3s/w6;#LO-w*yAwIENDB`wagyu-0.4.3/docs/building_and_testing.md000066400000000000000000000037401314062220700203010ustar00rootroot00000000000000## 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 ``` wagyu-0.4.3/docs/complex_self_intersection.png000066400000000000000000003263161314062220700215660ustar00rootroot00000000000000PNG  IHDRsN iCCPICC ProfileHWXS[R -)7AzޥJ! J AŎ,*v誈m-Q,,ETu7I]|3ϙsܙrPr˜`?fRr $)0bEpewm\UW8#bB| \-@hz)~H 9֑49Cb 3Pg3`%Ķ|vؙ,΀X ywq23m4&1Ȅ rXroa5S#mo0)ܑ( n8$~ؾ-5 PaA k2؞%B{47ӄ3b8K3#+ IB Wz03.Qm*%DBq(;6laa䈍P#l taPٰY4!ό bI\QR7 Pp0b}K9X%7'8F^g차 vķ#.0yYr؀ ?:N A8 İ Z{z?H`!\`=Ha qhO6PeT+Al@ Bk^{qWmď<2+1@ !-Fy!؄otaɅIGrNxLIDej:Hs&-h84g7p?q qG/ ssG}IYϰ^RiE1w5g؏R(֌.c':`X vJGWJ-F-~aBY ?C0[g/2gیc9 ?o6¸MwRcp)o:7p{T[,,piG wFd`"q LUL0,% f {pԁ6p܃k}` "BBhB G\/$ Gbd$@,Fʐ5fdR@!vA P .jG]Q_4 ChZ+ЍhEϡWћ}cSfbXcBl>VcUA>D3qk>Cxf|/^7Gx@#PB!0PB('&'\{0@$D3 ܛ,rV!Yb;O"HV$ORE'6ΐ:HݤdE>ٞDN!Er>iryPAED]!J0[a.k UœGɢ,l\ܧUTT4TtSS\Q%GjTK?u UL]AC=KC}KLi>Z>mvAdQZTTԡJYADWyrrQkʽ* ***,**'T:UUvQU^V}FR3U TT;EFt:~ޭNT7SUR/S?ުާᨑ1KB㔆1LJ-Ƨ1c|p,spLǘc5}44oj~bjjek֪zk[jOҞ]}AwX챥cX٩Ӣӯ+ݤ{^W磗N^>]KN ӗlb v 2|`D1r5J7Zghgoa<׸IffKLLi՘7{W߰ ZZd[lhD-,3-+,YVVe:gf7ѮȮ=۾!aCkG+Gcm'SF/.B=...[\:]]]^r#-p;=_B Y2qĦ0jXl4bbڈ&Ⱥ(6AYt^o'ULzc379;=v_@_ʸ{) $J'KKO!$N8y)NSJܚj6uӴL;5]y:kTBjbϬ(V?-4mKZ۟Yzrp{Iᙱ6';<{-}vT졜ĜC|5~6iތY3V$=o}^0L[QEl.ĪGgjm9{gA45hyvGo\`xA{Qe/ȶhMѻʼnuwSMRsǒmK񥼥mZSz̶r+?+ZW:\E\_ukkTZvs]w맯\XmexdcMƛVm9s C[t,~+gkGOmʶ}~{G*Ӫĝ;J/ջwG7foSKu>}+kqM)?h}p!ơ_Su$HQףr~]WY'Oo?1DcGl~sd)S+OSN:Sxl﹌s]O:iRS녰 .]<|祓/z;~չ˵6 ;;]~F荫7#oߊusJ6;9w^-;xo}*zDswSgϪ??b%cҗZzZos|p w`}{?~lϤX|iPА%dɎlhz:o@Kgx(_2AwF h2'=>/ Q*a3 w@F۰屨C04VR_CC[dp6O~ kJQK QlC pHYs%%IR$iTXtXML:com.adobe.xmp 906 960 HiDOT(34C@IDATxwEg]r Q ( HP@`" ĈCO0 ( H9Jewp{]SNOTU~Ǿ^^I)/T:uDG=k֬"x2wa 򖭁LLpvd PDuMU+WE26=/յ_5Qw']_z=:ݥ௖0E((m4f6l7Z2I7 2pV_P#("PTI.`ki.̀mԃ Cod% 8Ej=A& zpc( ̀p ́?܅U$HxK-.Ai:w@_3`nP6-w $@ZrІ Hs1klmC%3.1-swa 5"E5R &KP&jb F=ۀ1 @Kf]b8 j#PD֣a;\ ۨw0h K@ '0p]XABD@Q'Ԃ j Zpw5Q6` e!ВpNZ((m4f6l7Z2I7 2pV_P#("PTI.`ki.̀mԃ Cod% 8Ej=A& zpc( ̀p ́?܅U$HxK-.Ai:w@_3`nP6-w $@ZrІ Hs1klmC%3.1-swa 5"E5R &KP&jb F=ۀ1 @Kf]b8 j#PD֣a;\ ۨw0h K@ '0p]XABD@Q'Ԃ j Zpw5Q6` e!ВpNZ((m4f6l7Z2I7 2pV_P#("PTI.`ki.̀mԃ Cod% 8Ej=A& zpc( ̀p ́?܅U$HxK-.Ai:w@_3`nP6-w $@ZrІ Hs1klmC%3.1-swa 5"E5R &KP&jb F=ۀ1 @Kf]b8 j#PD֣a;\ ۨw0h K@ '0p]XABD@Q'Ԃ j Zpw5Q6` e!ВpNZ((m4f6l7Z2I7 2pV_P#("PTI.`ki.̀mԃ Cod% 8Ej=A& zpc( ̀p ́?܅U$HxK-.Ai:w@_3`nP6-w $@ZrІ Hs1klmC%3.1-swa 5"E5R &KP&jb F=ۀ1 @Kf]b8 j#PD֣a;\ ۨw0h K@ '0p]XABD@Q'Ԃ j Zpw5Q6` e!ВpNZ((m4f6l7Z2I7 2pV_P#("PTI.`ki.̀mԃ Cod% 8Ej=A& zpc( ̀p ́?܅U$HxK-.Ai:w@_3`nP6-w $@ZrІ Hs1klmC%3.1-swa 5"E5R &KP&jb F=ۀ1 @Kf]b8 j#PD֣a;\ ۨw0h K@ '0p]XABD@Q'Ԃ j Zpw5Q6` e!ВpNZ);v,P21-Z:ڪUpH?]XA¬wek/1w3o(z0=S Q LquW ԥ:pw JS5A-;\ jSz֣@_3`nP6-w $nw /H($Z0]T 5uP 4f6l7Z2IW "E b F=ۀ1 @Kf]b8 [* j$Ej<ɥL`4UM`Ԃ zpc( ̀pG@QG9hv5Q6` e!ВpNa9p @Or%(M_XHs1klmC%3.1("PTQ0i.̀mԃ Cod% oe. F"PDƓ\jv JS5A-;\ ۨw0h K@ '_-pz6L`@_3`nP6-w $nw /H($Z0]T 5uP 4f6l7Z2IW "E b F=ۀ1 @Kf]b8 [* j$Ej<ɥL`4UM`Ԃ zpc( ̀pG@QG9hv5Q6` e!ВpNa9p @Or%(M_XHs1klmC%3.1("PTQ0i.̀mԃ Cod% oe. F"PDƓ\jv JS5A-;\ ۨw0h K@ '_-pz6L`@_3`nP6-w $nw /H($Z0]T 5uP 4f6l7Z2IW "E b F=ۀ1 @Kf]b8 [* j$Ej<ɥL`4UM`Ԃ zpc( ̀pG@QG9hv5Q6` e!ВpNa9p @Or%(M_XHs1klmC%3.1("PTQ0i.̀mԃ Cod% oe. F"PDƓ\jv JS5A-;\ ۨw0h K@ '_-pz6L`@_3`nP6-w $nw /H($Z0]T 5uP 4f6l7Z2IW "E b F=ۀ1 @Kf]b8 [* j$Ej<ɥL`4UM`Ԃ zpc( ̀pG@QG9hv5Q6` e!ВpNa9p @Or%(M_XHs1klmC%3.1("PTQ0i.̀mԃ Cod% oe. F"PDƓ\jv JS5A-;\ ۨw0h K@ '_-pz6L`@_3`nP6-w $nw /H($Z0]T 5uP 4f6l7Z2IW "E b F=ۀ1 @Kf]b8 [* j$Ej<ɥL`4UM`Ԃ zpc( ̀pG@QG9hv5Q6` e!ВpNa9p @Or%(M_XHs1klmC%3.1("PTQ0i.̀mԃ Cod% oe. F"PDƓ\jv JS5A-;\ ۨw0h K@ '_-p (dg'd9BbŊj=C6L`M`]Ww@cT332D%HH&z͛6Qjի,YdR|$w5~#\lYge.jۻj2]r_eTY { kN"Ux{;$? lJfjvVS`[?.<jЌ^ȦUx>?ո{ljzˮ^<5|yW\If^Zi0 .~ќCYW#٪U+ cvw7UkI֩SǓ>7]KYϾ&XI?>~p ɃQlP*;6lWeXTJ-+XvlۚTt;7oL}ϓJ:vג}o$S=yAʚHW y媞7^ݛ- C@ wYP־<~};ﰖIϧ_'yz#ԤJ $K/Fr$+UD뿥LMdt\*h+G@ѕ{#P bd>~#r( b89@ESyEAhJ ([U+Cwq<|oڍ-%WH򉑣||1Rwo2I8.zI6ȝs9qh'Nж[)|eI&V̿bR://]{I+W.{.l޴Ɍ,;o[}C n'%Hݼ36E6_{i,<;5/\f"z;xmujUl};ѵ9^j$V~~U 8Y}pf-$|ILⲓI3$IګY_HR$JÓ~h3ヷgux\`sO_}˳I~h9ɺuASZ%y$33<n ً;CM{W3ycV]֦ArcH.[ٮILii/kyGƤGj_yuؙʬtޑ~&҂[nsn}}"=ƕ>JcG=JE?M2=^Sok*< ~s n}uǮRyݔwt_7=]ӈ5+SN;|;3 'zQYlY R{HVy$\S"~]B/ɟ=@<rĭ@bTEObd>ù')2N#PE"P$Xub89@Q&i\[ʕ+\f}9Cŋ.HhW4Ozړ%uCۣfR|{KJ!lǓ?=6.j1ekK~}!5}S|u h=҇nߞU٭N[C*w>ҴiSOE?ΥogwS~|3V#{&[x'ʨtόsGq\~붫ygjûH۲o5zp74+w,7O3OjFK,Dy$'+'Q|zA濴T x&qb弳ΆeJxίKSxwv|E|2z EV }zdϞ==/s91oݺuLsss/>KrHԁBzŤ_e]Pj맓z*exa%skDT,UU~al">;F<;K?η][XEyAA}̰*[ d&dɵC!9rzx,u/:9~Wөcʪ;Kzz9S|g#'m'P;y_zvlG#򅽑MEfMVW|~Ӳ0ڕ9vR3䆫)kX r,Ϸs*K(N;^9;NWSTq/q|u'Ņ~_GE9Eʣ|ދwi/?CUWZfJ=JGsy>}AV_#.ňX 3(@1 GaB|q!P,Dp,y)Њ~G(F@12b&Aŀ3c\pmßڍ>WE[)P\x5Iź'q |' Ni;L~qvjE|]\ؙgРAa>';?W3|>Վ]_!l$;JoO/H-J[)=` $΁>a*O<#دo/z$+vM_❃Q/E /$Oܿ$wҙlzPg[o=-SZm CM U;};+>ze>s"E |X0֕wG蕥\7)+$`󬍈杉=M>se`+97O<> u(IqBoCRu@ύvdflgIC)-H S?[8ɜyu ˸<> ۰.뷸Rf^ǒ^<*VP˗YVm)r*$.-@;YKx'Nԯ9X%HRGu~H>Ж+8YeD=/.*/uaW~:cxgℶ' U$eޢPֿo&*օ2{E7)CWẄKe>s3wuy }\>T#ڋjb/'Meq$4WX8CvT[frxgldPn 347@COL՚f ݴ+R~Α|#ɵV ɟI5*¥o.*dYSzM|<%1VOWc8CߜXAB7x^hx 5-[ ǵP!WK\V/>/ֆǵxS?A/Ha;P(o9 j\SKB践SZ6.>g+u~?X-ŅY( ɓ¥8@12EbF'cI@#7b~!PXI"Ekf@AbŃ$V7xYߗks2Ο}:}V8oWT/ܣ|*}u7_!4ZW 4+ [\ ̱Mۺx-e;JԐpGS@^|~i++xҬ+*r,=+kÑ3Qzj_֯}uSyV[jDOi:˭@e Yo,/uڱT^bx8r!{7Tx }ջ)e+ lʼn"x3o͸7:sW4/ )@dT?Va҃oGbB'Mֺa~B{܍PU+]z).|A5c[;̥ЎuW +Ļ?~81s+7睉RY=K VP;\ܿ=w Z X,%1辩ss9\ol6ϧg+PQ2퇽 HҊ\)w-BHKݟ 6=jDm^?wX z Ke&-vZ:Իo1;񎟘B[l _DUǟrFsT^2[l~zUցv\G#ǏyR\ꘕ,->.y02>Tpg -SJ k 6x~1Ptޑn88QՓ\}rjz^wzy<i(ɣ(Z׿pCH`T |(%&X?!PHIRPZ%>VR,#AE(Hm@1`}HcHX߈ H~js>W*+¿- "Bo_Azt _ٯy_);W=W6UW ne֓5x.->@Vn dZyc#;s2Һ||'v/k)ؔw(~"FTdmށP}XY뙆&{2| P ()DDΣ(,PlBMd|%)V=r|EvuxY|eitێ_!{<[ȜM| N}Ap^;ybEk3 Bbg m( u:CBUgԢ<4;G?e|E-Ddy؟ < B(Ne|;Oy΃|Vs+wqvxjd5.N-Efzzǵ[u~֩|ܮx s1u cW~fq֎bvo$e<θH͎8=Ϭv:ޯkߎпZ73NE-Ջ*qʞXX"E)oQj9A͎пn(,;YwJx[x篌Ż kFޙzo׸AXۄvZNWrz-bkV5ķ(oߚp+QATKubꑾx*~tM#y}8N^Kvq޳MTT;rO^MHw-_J?( #zX0(FBE(HO(Se"i<E&";EA"\b;(*sʻVx'[/.qTnNnռv9]Ζ,Jod>3 mJjXel-cyZޱ-iv~-y]RՒ]g-͓cg|x[&yR5}h&XQ'η=撷wVT_*-snU>.㕻ejW˝wīW1v},Ӟk;<^_;Ti;y/UWEWu'B(R"YBM뭕%cs7ygFq/59ׅى) W+*zG~V©jvDwDR J|gb9|L̺Bwqsy.G;+z[orf~'ݻt]'};/֪*H (/BS#şYdHTC,N(+>!PD<'h!P@X-ׂEA"D@1g\I( ݵԷe_)Je YF7 rVjR wIm|~Z֛0Nֳ5X;ϲ{s{KB#[OcT v2_ok+w_ k|E**ngz =G{;NB{d8 bHقr?8{IkZr^sk\AF qZŎb(K PfFIс!P@+EABL7=4iws#YHT%*w}#- PL@Q8ួGYůqdlΚ|˗%ߑ^].MT>DM?#pV_0+,oD̤}B~6[ *wdZwD5nؘ͟ (MV,EQNj XP9&>ஊ7=[b#Z#PL@Q;jgtjY;-|K川~. aVY޲5𗉘I{XMF;ʮm;Q1[fO v2E@< ډTmA *'wDcRUUqUи#PwDv{I(+':Io4aivdAKf=6p+ <[w /HA kY+ŵSRJ$vՎEQNj XP/p`|?>~^[Wrjځj :whѮ@1]vGw8Ll%om!Z!P3 I>~?#pV_0+,oD̤=kQб3FUj$s fSO&hv("P$BvbU[PKU]Iozx[{k:(zѢb333cnn.T))"]PWZ_p:(_PP Hϰ?܅U$Jp7[23q=gU'?(}a H~g$۶mV/QQT;R(G!PT;-h_`A夺_சhl?6^j*7n whn@1IXZ[@@1̣(HDP zpc( ;Y EQF,c>jG; YsFd? pV_0+,oD̤枳rzq4́ vEQ= bT< WXSapWң.f'8=6;N4rp~уÛ!P#{M96fhNyli~z֎b684j@!3wa 򖭁LLZ5@H ,Nw /HNgӁErt{~G8gMoc{A)@Qp#PDգ(FœpOw[fwa R5wx9F@1Lxy$ϫ$PAi*Q(FL_`A9!?]XA¬ ~Vb뇫|O+kE= bD, ,i:p֥Zw Jq5pW 4Fu#0EusG}(Fk)E}f֭?c ))oŋS/̾b^@e{HpRntd;\pM8~GWnYw[iE0H֝Oe(51z(#zňX>SXt Kjh?F`@1@!P'Rcچʏt?HXYށt/dd\pM8~GW)"N/㤺.ٻyǰ&O|uډ(wN~0C%>j "E@Q)Nt})h)wpV_0+uqGn(En}%zmM3NXy~fQSpeg* f%-[.Y&.g:^yyeWrdM#PT;((KE-XV ,%pwwa Rw;Nnk!PDWK_7=2Sot|(kM6)=&6m"r-6 ;WO4%` ]&b6 fy k.H#?gLgj-ƛBݺu@*E@Z!PԂ5n8q' F eL=&\+r:qG J="ň+i_tUO2iՋJV$8hp79 GckB5[sۧo^L8닓lQ$Hp:t/NapEO;;p Y;E=tjjE@Ѩw$kE L-OzBBF?8qޖCiS'L#٣S[G6. yjOI|rӔ?wܞgӆZ(n'0E=*>w /HN(z{<("P4ͱNKri"?wF*O9"gZ?LD)D=H]-Xw5jkgǽ }ۥ rc?oqbK7 -b=dF&yfd-("P`D:(/0VM&2i7[XwA @xziWk"Eu_ֿs屿L;Gt,[έE9H_880wS#\t炻nt>J}-7иQ]̤ҥXZL2aw@IDATE9u#PT30v_`jCp#c&pLl#PJz{("P4N+n;ۏI:|'bvQ4v_`"OpWҋ&B-6?x4<Mm)݋n-<3X@Qw#Po/[zேVpw".sGhGJO~zzZ("P4N\xwoI^v|+?I%]KkG=v OD5u]R\ QLQu^cHcЏBF hbEF> @1_`M6OZDc¥2+GJ!¤kCU@1*&mgjX*F@Ѩz_ԉ٫$/k&_ ?8DkGʢ/ſ@1 @bXP . yrhE{=9$SdܹSZ@rw/ O9;$ ("P4^'0E5Ä5cS[t ݒSpE*^3UO6=(X'[Q>&s/ lWWmW}9Ǖ(pH?܅U$Jp7[25_oXF#Wȧt\ek$.X3X3t* PDh}cNt ݒSpu NpU5LUvUEF}; lݺWRS-"NyO^ڱŌ_Ѝw /Hnl e"jG;V<ι˲,W-{p!yN%oZn[RzꁿNZ݉rznA@Ѩo79kԴcyNp Y fy_&'^~Y/WPw48wF(QBO50I@@Ѩ{#+N"sѝ G*w]dw⭅@1^jۛ:TjC@Ѩw=ߐLk8W?"UII\_n(i83Rwa 򖭁LDm:{=9؎ᷰO~uN-E9 I6i҄$%`"P3Ep"PDhs3i_Lk[S~⼃hqF2S{8)7M큻?܅U$ʼ#|Rʒy3hDJ۱?%yYgCB3Z("P4~M`<8q0/!p Z@Q-O:T}Aч@Q_{6Y$7MXEn${FSQ$Nؔ`=!Dž/7ˆ N왯yD͚5ɓ'S~5ʑK@{%(‘8[%~O`h mvPO) ^M=x T P%@Y~g PDđ* U uyvLB&bQt;L'lΌttu=%௖rW]:uh (9;W2_ #PD֣e#Pl0R(VI'l*iƮ cg]{g%HT#yV~OP麝 UeC'[>Np Y fy_&-ap1W{o7"svAF@QG9h{#P@#gUq1NQLQupWңNj@Qs("PiiS uz^d>,>~ŦcD_]{gOKp^m(gQjeJ.w\)ۗ3>qN+C@Zrfz#P 80wS#\Bm$G6>8"P%YrMg& 7@@7't3vG2DށŠR8So]OM׳ut?p[Ȫ8en䇛K/RqGHY LISiHoݛL-fgݺ6wK4.Mxem&_KKj,s?diu'HuaӃkv|u'+'9< |(B)7Qsn'M%Ej=A[L`-2.\DD.u'2$Kng& ojMF=H|o;KkQÉil/Qp]&H*(`?;sUQSS-sR[(~VsyC"T&J/(5{RK/#}iFb'qRZ\vĎ⒁ۤ .KRƥ?{r#tNC^iR;qIƉFwF2>y}ϷsO}xm zjzlKL/h E&-hQHZcz†@1K"Pbd.su1 iz%y^kG@1~/ACNG~-o,vtֳVԹU(b|W%+q'q7BO w~U;B%O,h$񫾚wvVzJ]ydJy~zRF 1Sijv;3Nx ;;Y ^ź|y߉S(߹\u%Qs\\T@ZrЖ8*D=aCuXC#AD]wLsBh^{zw(z-}?toB4qks"cRkoW=u'0)w~ο;gNbnjQy$svj9`/#4#ݎ]7QQuIlj+}ON;\KU^;yG_G?'=KצayTy[4u} e7BV@JooZ/%s*we*;m۟,;y|ܩ,S6p'QWx5΋}zi{4wX}t>e3X~gGA.a%𳝜C~+L q1VZwgImN#N2=#]-Y<9LOz_~vղ6w~+ފ;vAx/Y7Dz gӧ7N"T*?}a4jDvN2#Φ{og>3q\݂')_{[O$yuI$>t5-xD ofKu7x=;2YIxp0ʹGd9ω|tc@ZrЖ,@8@!P"<bl"QS@("mNgfo "E{P,8%O t#,Q|&v TN؎,s~[cgxuNajh /R喼Tl*;Vbgngk%T2e۱ey~*^smڹ;Yb0]19q<۴[ bw~+[ES7]Gn9`xޚ{iT;^~ 5_ScN.1NkDlP2;粽_c:1y Vacʸxrᯫ^ڵM<~Y֗wdsSR݂ea-tYrݕy CYޖ;a(u'(r?瞼pK6g)Z(bP@ZrЖ,X!Ptp!PDD@(.{ԓ<3(@Q_L ,)[w 2h&_A_s>_)|&_6 9,QTEA~xYQ ;g'3]?L`+?w$Ziyɞ5_}2$vQҫ6N'4&~m;խ$~ޫjj.${I|t˓(_3NVYJ%|²5=h{&jRf<5GF4YUx'qw IK&A%5"Y<ףEUyyT+x^-glCfQ0hߜr=Z7YK$}$?XoܺwOF%N~u<ʫH\S=ݼoKr_<㬶ɍi;n{UJ߸>qag#>q yT*ŇFKG=3ΕUVQ3+t6o4;B&dKxgJg~ӕU cё}~>.$yV(Z((m:(: , ŨX@1:". 꺣ee"ɝN̠ EF}L_{NE0Nlh-*Om޽woo}}A}ҫo~ѥ$7vXRs2^x^m"em3BO?}lӦ ˶EQ\2?ŤqRJťt㠮]ܕPwĺTt F^PU=*e'ɡtޣ$uj޺I;(_H PD֣ @XqPO( z@1%J)D) c:k(& M3ʵ TZ5`rR4zDi5fAIJ_޻Quߕ'fPt෎dAI{@*8y:ַ^6^_Bjuw+~ݺuUw%L_P>I@ZrV&0E("A;aCXD z@gPd"I&NbF@Ѩ VYZ*31m*#z6xZ_ PD֣ @18h'lrxA](Au"I&NE@Ѩ ƴ7_&z?Sc$όeLNA Z긂'(suw:K__]̿تU+7v;舣;Qh, wm/>5)AYwL~2c~z<}n}yGYW3z_ PD֣ @A8('lg%󰂶#P EU$[O֝Ħ !PDhԇ1w!y۵$?^){;L,%P9e,XW3 xG9)._.KlRH@ %O޻9_f>]6#So3N>ҍK3g]^ugPڑA@QG9hf@%Ɋr†@;&V{&Gߜ-X';,Z("P0c)cM_mN`GQ0(bLͿXy0*Yr)|~}a剚\G UdS1AYwDt̗x;^MMM 3|~i\FV:4W}|AjG"E !Ptp$+ E(ZA[(}s(c$h}D@1(/+xͪĴk{I\}t0#>{ʠ,GOwiG'>jߢDb 3(33y' [Q.v*6QXeG0sLJϦo;h跓<?c}gSիکrS~o,z:ʼn EID;aChLb}E>>@ў JB!SNQa@Q_/x;'wSRxFMnU|~|5Jxnhq:_]݈/VQ➟czzm $o<^-JkϨUȿIQh{ :Sg}֩/ޗKv("PTQڊF IZh'l bMu'Q@OdOԺc߃*A@ѨGcdž?=dy,&ۉCl4hs3gV!h|Xƺ?0neʼgHjM߸o,FouJr܇wWMsuz6?g7;mr:w7uK2_=8>0|@ZrІ H*F(IdOde"H{!`zG>JA_w(Rj^w:Z("P4Ꝙp63j8{HvJyO ~D~S~~ zXw"@1ww_~<ڟ}W.˛>И J})e.''qiT\~7C Nz_]~}@qE@ZrІ Ȧ  Fvd_=E;"ԉ_NL(ULpmbw|tj7ԣteIā:Y{h4䭃$y]]t-; _y` y8w^kN/CVe94חӶ%xjaP+~tzgcf; _"PD֣a;r(F(ʼnr&C(A:~yeAˬB]'~;NJr.&_ }9ky{&sQ]Pv?M~ =W3\OW75!5U"uǟa{A?V{%:Xo7R>YMu~W=\cyNlVG~cNJHW;((m\#Pt * M`C(H@z!z@G+^fZw~$K"E C}?m)y99SvvOĎZiK?;s3^WQ^YJR*Xw7t_d x' YMu'k3/,lkMDyb KWmt)%/_j#PD֣a;b|h'l}r$3z@)It8~;>v(jw+ ߟ>OyVෝ PVN8Ithܷ?On?<Cc-,]ջ!$4iȡºϰvޏsߧ/wL0f5Q֝\~4+?Dƍ#%u._ PD֣a;rY@%TK6 {wf*:`U9tD@QG9hvdS@LdM`D(H@ `zG>jA_w(Rj^w:ZR:v(k n/5,Qup辪w:H䑌#$K7(e#EZ$n}[og&!F?dޝ;V;\ H6{]O Uv(h6L`c@,o;kA=am>E[4(;=1꺃@1|RKӆ[OqQ-Fq[B|^H6Y -`e|.UXw.4z͛[M m)Lԫ?SҬjN~f"k?k4IRjOJH E@QG9hv&.}沛!PtHWT#P͝^w(6j/NI(aL`f6gR^VLi[J5rguTX}K_=wNܚO?M3`j͛6YnɮM3%޲wIYM$3 =2wtfEM=G[ndU}~81Ej=A& 5QoƼlʑ&B=Ժ@1|\T}v("Pc⬍ '8 O?4i+IϒSN'Y$S'lzi3Cڵ+R,)]jS&IF>{ [ue,\F%)!i<(W "Nz6L`@AKMIf'(jGz*@G;Zw(j׎w"[9EyL1gsIE#٣>co|aҥ8QԚ:a3Om-  4Wdw&q yzFw~YJ2!YgPz۳_ζW[=mx\Gƍ3.O2LբE@QG9hvX@Q3pK_'ln‰_^oT Ew$Zw(vRnQKеEM"PDh11#:&|!s7sKgnjh_'l:C{P32+VA%̽%䝳yqX_V׺.ǣ^ߊze~|G3$X_-Tz6L`@e5u¦(&F PtGүu"vyZ= Vz)&Q3&Ӻ%7g_[?8 7;Qs?v3 Nb袧{A<϶=#$\ܣ;~$Okҗĺza׭]KG=Ar܋/'KB' ']j"PD֣a;\@Q3`Ol<rIPz@1^w(x裎RW((m4㦍ԣnA22!FL6Ot5?v?\W.T^7 &y}I-0/V7Х"aB_s$/b+Pc8HSN.o s;䫪#\("PTQ0i.?E5bMMo PtfP[fݑ2 P ]~n);oG[>ESw+yJԴz;oǖ^^CcGkƒЊ(tםŹdבz]Qt}#8f(ڸlL`=\j[‘>alU_.E}lIƉE'B\{Ayt}dkȕ L$4EyP1czmF=zm},{a+yj_A)Gt{'m<%NN9RWjPgZT;}T(%{̓L+F]N2b)$N|Eh1s@-V3VmG+q[oB]Qlzc |]prJvL#s@W6eT߹k'S_~]:T274`ٸ4ɢO [P9={aih޼]UDW}ʕDϿA2WB1Xם}?Խ;{8}d1PXIi!G8T}y0z j"E bU(6PŦ=x(oL葪F Nƺ Ptթjwp#EPL1c¥j{><NÍ2)TW3keH<ߡ_3/t%ɇ}CVL{v6`(Q"Q?u mAY_M-[^M (]1h^8r JɊDD,* *b@sH0b@AE 9޷jog{f{ggj:Uwzڢg*eM'=|VXb\+k GZh\?Ek f6kZ÷]Pձ܁]W+/Hp($Z-TM[;P`SmwZ8iiR;knpu|IE@v~r$/>!y׽=IvG yqEbI⟀]GQh96~7^F򓎹eS8EnذWy~OصK_-Q8pZD6b](^8ptp܁:صzCk_K$4E8Y6)7wv[L:S1FS9x(P킢(_IE:3 +NSء+SRmL";}Ŋ4˻nlM|fM|ft/ؚj‘JDu>wN|q{zs)ߥK4v>+GZh\?Ek lz ZpoMB9"8jW'EgaVfߌ|8pYV)jm7H#ڳ#e EQ:&zruTʉ$%?"W~@\Maį\*weܩS];Y(M3GX~7[Sc.T[+J>w2+Y8͛5%٩s˔2dI\S?5_ G"Ea !!lډ*pM-p_e G1KP>o(Y 8wjւ j$E8j,ɢl`4US4µkג_f!  zj +Г5M/l:-l'ds`/Z4Z{MeʔYuUv)w\o)$K8=/)yKY*/z%w9}d|5 ^SvUJ0~ySTUd=p((6l` ŪQPN`KֆksdN7pլ;zEkA 5"E5dQ 6EP_|Hk _Ei2=(~}2O<۴iCr؈$63PPxqN!"UJ_A,ֺxDr ՠ>wrsy 4&3g<))~ Nvo5NVkQhVVѱQIkVsxk (F⪩s89m;8pZD6bgeO3Mp猷ɥz1,lNv` 3#PQ;l.N?+G$vMw Sn}Ïr4eГ"F,s'ـˇ{4ou- eGZh\68U4px^܊Q ڽ~ю ZKGQEIaKi.!RqR?#)(xvyGق QH88ʩ/3Wk p((6l` οT>]Is_o,(j6%MGzlm{wYi͢Q,^\@C%NOѬ _gZpa5\&W| G/F8fgS]Ef~X8vqHJ5]n8pZD6pGQH=n>o(:[X8θnU#+GZh\]‘‰w[3la }VsD#(j6O[zqœ$>kt(woox-I)8`95nԦVn yE}M3l j"E%ц ,8\QԼab-8}Qt pS̩ݫEj"E%ц ,8׮ZfܧA5q$1g930Q*?fBz緷(fdS˒\BYL5ȑ#4kjK2lum4$Wkqg)Sa8]ܙ_Q֢$ڰ%48zdB8&k#i#8pMCG6u 8,/ׄW}Q zQ -oћ݋|(QTkQm@#̮i$cb4SES7p$k[‹O_1H Zs_gΜ+g㮬Q+f,_ɴ*զqqF`$?/Js#_+H֪UKT  M4G1y/VԎHGj]8FyG1QLE _W:묬ԄGъ( #EyiSI 3(6׳9r*gQ^Wã[!ɾ 3I~=eOw[K\Z J?PdlX_C?[믾.7DRi>4|0"]8pZD6fC z8!̞7p.E|e^jB E8B"CٴNW\Ow8A2ϙ~斑j$㊩XDOQl>CUPy8i$/ -58m׫qbBI4ƌ9,߃  Kj3tEQ.YsvuOR's.Ĥw58x(6l` y?E"pC:ݘ=o(Y8zjfv3Y+8plCK>6UojwdlAD{+t)]|h"a7xb,}#MM6F>M+8lȤ ڽt&[}ԈGhYK_'{3v%n)KŮO-j&ж$eDsூtQ?k6p "`#b,!ʞ7p@(.fš勮_2Q֢$ڰ%4umn.~1sh98e$V,!"t*!*,7ݘ{{%*SWhdڮg˸ZX;%YuqNͦHb U(˷-U e]JG6Fp(l`T(Zp G1VAd8Qt-Tdvq~_Q֢$ڰ%4uqӳ~ @IDATb91T%‰QljMi|U0LZg&K84=Џ7tCJy}p*U e]JG6Fp(l`T̫(Zp G1VAd8.+HGծE8j-J XHsq^U˦DlժU$sswz<"]0_l`EX EͦPOPyחtILE{OM6$^;vf9|yF4^|. >Vu:+t^JH('ʑRpI+Q(5UҴ+h¡pXc0{Q5NVmyugD~yK=38PK**ymMf:Ft-vhws4~ jNSzE9cԘr-o 2E8R#QYX%M )C8m#-({}=o([8x]W~ծ4E8j-J XHs13L7wbK=#32m>ݔ.]ڑ"ؽ#lA72>pҽO$ H 7$Fg_N(_Lxd2UI.{5Iq1/!w=\jՃGњ( C5(:47b3{Q5NVmyu5"R}v)T)pi.Ns(Qtj;a;¦G G?s(茛hGQLg]7W"E%ц ,ww0]0哝~*~c{ SD D.Ys82qdqƶztɓ&'%l=If+qD1i1Wq2ɸq$%cMM[I~1;F!+x^ E8!7lo |*ygQt 3n κ"௖?E8j-J XHs1eFжE={dr{dG DDfmԯɓիW4ؽ%L+%~jQ.F͹EHw<<̑kjLgL^ɌaJ0V)mJqz(QTkQm@?0`8U G- G?88EJ-;JZp((6l` -־^N㭧xx멌P3Rێ\q K4"9l$۴iC%tݺ;Z\,ڭmЌN/|ˠβrd5>LnR#;%\;p_?X(Q)l`̀%? 8K.pY {〣Gўwu(QTkQm@m(y$kcDQv#1)eBE[׏CIg=o;RIkib6Mؽs[sdA,ТXalK[[gY^dks&+DGGs5 wN(mw[WH(֢$ڰ%47`8<5G1\W&"JAwg]_-w8pZD6bo?~<_~e +IUVhBD$TJD}֙^uN\މ9Ӫe3*>c6d.y*KK]'+qcRQ-؎xgV*?0n )mJq⭧x5(6l`!7pKQ 7񼁣GўDvmaҝuQDDQEIaKi.5{ IId\؀Q ǴpMT֯&G~gY0|IIII~ubўٴiS#iԜ>pM`iT:m䳚ߟAW_}5`/` :kθjH8pZD6bW2-pe»QWSwݕ(QTkQm@_-ྏ!&F2:G|{ADїHt~yU,րpl])9$.}?ɧ{voFFo~ըخj:5gڢJݟzlYwGv$@!8E_HC E8j-J XHs1 GQ-O "ȶcǿvjyQ֢$ڰ%4PqսbmY/zEm3<Tȵwf7#7?8uƸJQt{hϜcY4}1/^=|fޥSŗ^%ٷ_?^`tܝqS Ud=p((6l` 0E=\MQ4ENpy5(U 8P28p[M-)+@tI'SIdL,?쨚/GgE kw_E{XeXtx|Pۀ5)5^u%0N|'.NsvoFZo>+2B(Qg1A`?Hp%QT T:8 "05w(QTkQm@_3`C-;݉5Hd[:W3ZeDDQJߣk 8̙3컕c^C/3dg;+fො>pq2 ؽ+=w+ j$E8j,ɢl`4UM`}Q) GQXZ8Q 'YwWGQEIaKi.g:~k̛S5'9Xjd O(w@D{֗<9ͥ *xv#ăؾΞE>tdχ azg?/ߔ=OHuݻC.zABGQ%YԂ lj ^f(ѓG?W8\;ZQ^{q]8lߨQpjே}H][H,gDo]z4j"e .dv T"RYO jSH5vR>t#96ݓX$ƨ@;EE=,CfJ]Ykz'(Qj+Ja+X ;F!ŀxQԎث8p  /w G9)Zh \ j;ơF$hHĺ;)Nmt'~dEXxw9rhpӧ|Mnݺ$az֬Cxh?I磷A!%>2a~o8z4abaR'WL>+`]  GK"(M_-X8jy:G)Qd~pκ௖?E8j-J XHs1klޗcsN$s G(L#[B mX)g@L KD-Xl۵$.n2^9ٰI,v[yܰܭ&$"4;qZnN{net>)ݒƑwwWGQEIaKi.̀Mrha6ElGQ ҐXs}^O.E8j-J XHs1klތqOQ|C22QlG&'I+F]38Ud)*xIvԙ+vPII㳷gX^2fPåq".jC4u9?Gtm=n+ jQ֢$ڰ%4f&͸Q4f3M`QdpV3{Gt"nv(QTkQm@_3`2Ͼ W$c?xfA*O1zJw/kc5cnpHlkn\(H*D/;_T A_9d;ːN5W!̣|ֵ|_Ҕfo=aVF-{5/௖?E8j-J XHs1kl^ 8I6E Q"SndD.E8j-J XHs1kl*1=G7dL}VHmPezT zlaջH{ )?-~._g).mnj}Z>ܼQLv| oBBɫ5 ffplWtbtZp((6l` z(4Ɇh&p(º;D.E8j-J XHs1kl.|4Mg:Ɵ󧍈}Mz;BⳉڔtIHH<<-̿#78畈bl?Jsk؀#'jܼ4C)'))ɷJHv;!\ ]\W"pPkQm@_3`vQ4 GHpQ_SvyZp((6l` z_|z$i: ל~hjDŬ'(ş5sN.][ {/LnʜH(fgxbj!>w"bR0Hpww_-8pZD6b D]pM@dQ$ٝMnBM(ՂGQEIaKi.̀M[ʘQwHꤗƏeNqD;O"ui o[;jz=$yԈbv^bmi>׼"%a)q+jQ֢$ڰ%4f&rha6E,6hKլ>w\^]ZW"E%ц ,56Q/Sˇ!Y(߲xrs.Lmo,rI}!CwP2x hupo7R[b$&/}O,BreϝF ]2W"E%ц ,56Q/Gl8@j(K칣ݵW"E%ц ,56Q/㞓Gܺu+i(,՛vɘ"h2|q>GӉD9߼y3Ux'Iv}$;vHrHX KD/hh9;ϐ=ʑóDz(}x62Es~tfHFE܉y(QTkQm@_3`2pM)ʆh$E8,/$ {_X Zp((6l` z]c7~+cLd@Lq G;@ʾuJ󰆓'mʖY^]# 6[d+U3WPyɫ.3 &Q\+K=wJ.9bzwrzD( CoGL8pNkXѤ;EkG'Q "Ea zIlj"E%ц ,56QR.f,:~\2GL"U|%KC_&٩*Z܇J,l,ՔMl77'QAUHn{7ɚ9M /קQErYO'J_)Y9q.ЫNb>lp׎8`B8pmM0 |[ Q *p1+G6oN@޼g8pZD6b D}Ot"$;x s#p$[Ϸb:6i7vB&J7wSBՇ]GXVl}+"bgwfp( yo>]j"E%ц ,56QjSB#yc$Oˑħ*[+V#yvw79r t3~9_ߎn>zg@ƵI&M,$9 p(fvիиk֨5_~֬$9a^呖piTUՆGQf#J˱ⴭ m#S-py(gpVk;n7\wwWGQEIaKi.̀Mԇ};Hxq T ȫYGb NJ<%mOoy}БϦι?#=ŸA٧+Y`1/IG{d+-i b֯Ovz}kW>h6*JN15k.~˼௖?E8j-J XHs1kl>(BQ41Hpm ~ɴ]௖?E8j-J XHs1kl>T۵nN#(t_ Ko{1pD8]lrތ(q1Zr(+/,Q)y>Ѝ7\M@yĞ`rvD1cc솤H0 7e깓9!p`?H>(Q1 Il`|e_FHOyQ^?8<(:%nP=wܝe GZh\ =7f݌oi+W"o33IL>Ks&ӚS"]Ri6;>C֣UfnIʿ{1Ķ5*"9XnieO:sqАGY206!ʺjvj]` p}wwWGQEIaKi.̀Mԇ+w8_S(7\8DJn>w"qSrjځB E8B"Cٴ7E ܹq&/^t[ZN,eS. ¸E$+Q0dnIKJHx#p88w,G)/ɫK,GZh\ wpy(z0Eo N:^pJJO=W"E%ц ,56Q)goA4kY&*kXdeMfٲb4Л JrcGh"aÆ$-J2%%7$N.2U}6S9xsmx8e>Wxq]zS3pwwE_-8pZD6b D}pG& Gd#GXv1rdqϓI"Gv.Ƒ3yxd[lOy/ <>>><&ieJp1#,pyú$/nД?LD*wzEC/n%_h G'~H{ut GZh\ 4p(ה(lɎN`ܥVx(QTkQm@_3`EЌY7sf>F vy#xzI:ͳ_A$J2^>xڪOF&l} ւ(ה#Es.8] \tEsy\ ^/#Ygѓ)}M7oH}D:+jQ֢$ڰ%4f&#;E85i8&;̳#Xw)"_-^8pZD6b D}roV|Yy伶'ZQ#oD}w&I%+$9l$/D/ő=NѬq ?Z~Vn.s]Ŭ/ erl sCTw8ػwoy܉wwWGQEIaKi.̀MG*w8pkpM6vgGs'̱JRDZ+Zp((6l` HឝolbKwlX#|zLK2"w捈i|7}d@,ɡÆ|Q>ǵ":a4?&sg4/o>g%dfgh I26X5,y۶mK2],X@S).^}dČc<2aYYuqc@^X'ښZ޾/p(gA6[c:+jQ]^GѢ!i58a0A+?wģ9kCkI1[dE8EEEl`i_/[z?ߓp=ԗSV|_dGmY,X#X~[P<%L9vB598/O&y6#I b,_eL6{ NN-2"௖?E8j-J XHs1kl>phba G1s!m]ZK&˕(Ql,**b\;gLUG2 GXK3k&6=>gD`odˮϸOSMٲeCKts3Fg\mmaL9Նnf@ uwwWGQEIaKi.̀M7pM !L(9  #Ei#8pMCG6uuV*kWk֭!>y7ɵ?p:m2.h=!Ej7{ѣԨoOnu Ysɒ%4y> bT*Ov,8{uYv+gj@~}؂2kjC%ۀe*E8D]l`u,h'Ԃo_Q vtjsm Zp((6l` ν`b<9I+h|/9##Ŝ94Ђm<0 AQP֬o}ENc>H-;oжsdZPȯsǭuws௖?E8j-J XHs1kl>shb(jsǭws௖?E8j-J XHs1kl>r/Z(#, (n hDc3(F#$jڴ( K٫]4E 瓼{HtєVYYD1c ?46O!y8a,XPmē'w+jQ֢$ڰ%4f&+w8&l8!GZh\ o/^H>E$Yeg=)ir-g$6ٹ'ZS֤8(Mғn&yCWHɳ_eIM,T#db\ސe~6t;$"~1f){t>@LQ k l௚5};EkvE݄[~{j+jQ֢$ڰ%4f&cǎZFGY8!sr9T/;Y38,eWa ~kSӒe."ϒ; IP?]U7V<KHAr\ݧR97G Gs'0Е{X QqGsq4M_!Lw86#U(rvߞ;JZp((6l` zp7cdڽn{ɸ׷[ٲ^?2e/OP\ \ Nႝ!TH>(Z.^ݿ+E##;.V jwVQpԝ59҂~dWъT9 I GZh\ w0F6|-,l;p FQƒ,jJS5V%={]^Hbqf0L.OFCl^ȱ'e8V]G n_&yo>rнT{7p):sƑ$s1r.\ґm*W˕ꇲ GW)"kM/[dE8VmEI=l`%+jQi-8NEf;{ʵpM݄w\B jIQ֢$ڰ%4f&$b86Exu:_Mz~[yЖ =I&7TUiw[H|7 OYz0k\kHpHBjqQ֢$ڰ%4f&$b8Ajxw+ j$E8j,ɢl`4UM`%j]ȧxՔӪEp.83h-ߋ.EXlD|<0hg;ӻЈbX;̳%kHյW\NrzNzJ1cN] x؀*+@; E8C}6zv4Zꂻ=pՆ(#7qg]W$H8pXE-AiJԂQo\_gFR2LzJZ_亳H7+VXC͵kגֻd\(dd}S֯WK (ݡU$?f򪅄.x"X/棻GQEIaKi.̀Mԃ l8>@LQ `7sǝww^_P#(QTcI`[k+Q @A9s44mg DCQL]Xg(gH!A,pr$gGs$_}Ul0dɑ߇zT{G* G˔)2=M eZpUkQm@_3`nFQ6E8L)Osǝww^_P#(QTcI`[k+Q @IG=CQIH[ 4 W(5zZs7&++ K$YU1N;o3[Lߍ4S7#| g^ݗHh௖7E8j-J XHs1klM(ΆGQIE:pw 5"E5dQ 6EP&.8+#_ Z[8jSj)k{;MEM3SIz={v2`~E緊>Ѐkzn6u# u,U#=Q=^QME&Ţ(ģ#LW"E%ц ,56Q&`4eQɴ"J-;,] uמS*XgG5,5-vpwsۢŋhkq$-LjϠSg^:hivbYɺ%YT>+y(ӕ/\*ɘ.wyff&5H^uG<\2LWn;s5iԌT %> /eZp((6l` zp79"T=;, ] zzƒ,j JS5V%4 ;o*$9&]HڿS:ڥY9g(wE!7N&מ>+Yr\~qA!ƊɔT.k%s$L"GX=r+Z E`bՁEPZp((6l` zp7l"\Xt#zhC۷7%\",fkl`W@-w4Uk԰i.o?sqJgr@ѓGNtQ8g#r`WqD%uHy .6m"-mmB%H˳;8BivO/ʲcT&$$vB׎oK2_-j8pZD6b D= Q6E8!2w.zABGQ%YԂ lj D-Ki*3kiHW4^0"e^4?98a5ߜJhG4+JQ|X񷹿UmI+^$܌"ʔ᳈{CO^`4hIŬEH֪UKa0iT h/(QTkQm@_3`nFs6E8M,;E/Hp($Z-T 5w Żv"7ujGr>;_!z%dtB4I)T\K_=fi?mV[Tu_$K|eesD1w*sh^5I8>z>žezE=LEFJO9* 2Bz_-_8pZD6b D=є Gq'"D-;,]  GK"(M_XZp\lk.tO'S^dlmYUGwvw"ݞ9[SêUNpww_-8pZD6b D= QS-7ȯ)?vvQw62Zi3;(nܹzv3;zv6 &"}=O"ݨޘ)w7~?p(:GaSmR^)8-Sւ Z޾/p(gA6[c:+jQ[ZttǓ$c qd1mAJudJ2$K_[% Gk2÷O?D_oe-Y$8@ב%h&]>mZWEAU!?wV.zABGQ%YԂ lj D-Ki.?Eg ,wg Dh%ooK$4E8Y6)ZTAiU[Eo$y͉$e咜],G?gҍn( &RVB,\zq-Z_,.+׬ty#1 Pz$z*zF;k p FQƒ,jJS5V%4GBnW_ }G18 &0_1P"(MTuhᖙcI~О#v~̪FDœ3P3Kΐ$@ƫ/Q֮!ٵ:{O>+nb Oj5Z0=?F{.Uq2|Ydpw 5"E5d#IDATQ 6EP&.X8Jw{ Dh%ooK$4E8Y6)ZTAi,7=K#5 泉bC2Wng?^WSWY‡B#G,RE޶QȡlOX>ݚe,c>~?viӿ `k^W,1]  GK"(M_XZp\,8(XzE+AJp-oߗHpi8p mS\-T)WY2g | ϴbvRc?S7qDQT>zG7⡣|)#I^8lq bu,MŞ'֣%!Fvu]ۀbwgW$H8pXE-AiJԂb(0N+Z /HV{hyDKQl lEunjN9o'_idQQvh>'T;،Cx%3j3؁"3"/Yz=Jf͚Ts,vg*HIxKfR'9KnkQ/}NO~ 8]  GK"(M_XZp\?)zEkAJp-oߗHpi8p mS\-T-;ёf,wQ:z%P:.$e V,YӔr$G[tnN{q$RT#BY6n yM۶$rLsDCM25M#bՂ;Kp FQƒ,jJS5V%4?E^PslFY2pw 5"E5dQ 6EP&.-ﺁf$7V>3XZ>+_#iauS}>8sLt$ 3@ o{5$%++nᄑɉS>%YP!QEtDRwgW$H8pXE-AiJԂbQlG+;jpݲ{?ZZpZh\ w0!vMvzW;RxT~dS:ɳ9~7]'8"yO(=|%apq{ pu~pw 5"E5dQ 6EP&.mpi^aGm72GZh\ w0!f4_4g[K75jD2-(v.hH_ Kp7.w?#)WK"E%ц ,56Q&`B.(h o8GZh\ w0!W4;ラdLLLש@ZkXhJ}pW{8(mTĥEcL|R5TpVuJֻ$}yOs璊۷V[n5j-6ԩSH"j/5 xy$!(QTkQm@_3`n&D١G{Aw8\|Sp}DvZ=EwrGQEIaKi.̀Mr߱}ՌOU&涳_i,mK/#P+9*VXXzkw6PE|$T>EJsDn]^g{è.Q,\yϜ.d[Qs8m!/YW?Ĺs,@UK'rE]$SaH!杞IS ܯ%%*}sG*$SO.kr ʚ3wQDN=_oM'٩S ;;G ,^D:HKƍ뼰a"% _6QqŊ/f~|'IGVV6hVl4\#.[qNUz^4߻ɇDr$vd*sd0{ ye墆4uY^Ry"87~#e˖ Tl2x'K͢!#P7πVJw/䊓Iڴd?F*=1dy֭%٠AυiӿS_()GC<^z QD֮W"}]vUW(ǑS( J}|f.(4^z5ׯATyQpauJCIGDW l8pXE-A(G?8Qd.pۇo.E_"psQGwGQ i8pZҚU+tr$˔)wxAj2|(޶w M:ޥxY-͗;OrVZ״|,GmaR۶mߖlU$g@RgS8Ӽ}0qJ:*Q{VOQz(Y{Yy|z\g<:5ӧiF529{H&D琼Xv%`{Ya|9TuZ)nѢLERvƖ,¿P~*U7+{)GÑ>IU )g&}OvJL;Ki>Tآ%kL%y,͞1l{rR#&"l7c\prs7|ȍǑqlqqT@L״jFvu2ǿxw492k/q״KKm6sMl/Aw(I|Ж *O.fSCWp/]PFr=ɨ 0i}"#y,T2Fb{Oo:/{ɩjvukTtGI|<ث]1[ݻ3'zEժmL?W_}5`/}v_ʃPMMB$0OV`g⌈yiE7ڿΘ5"۩Rw4a^nf腔(Gx>-Hv _̟XRu|+O% S;>><ҷL&yIv֍E[v6GDZCPGڰ9ugK_N|Ӎtvn_ƫ8=Lcߢr? |ql3ȯuKx_=c%e0lCOfo!ER#@pd{5FE/~яs̥SsNzM`ǧ/ܝ'*p yzhN3S!=;yˁ8ϲޔyy/ND' sx9%^F{_*]9r&"~Qx i1`C͉M< zzj]e(OnqcWL{i|ͻx*oީ-ɸXN2#HNJHujGPf0͙ؑҽ<JMGF7" F$7!>EDTH1ݛWQ_JN$HFt358!r!FוIyZt`*g p9F%ϧG=E7u"zQ_xEqDFDΦqԱ=ni|v$DgJ(x a1+sQoF_βn/z^Yq&rz|F`sp>j1.xߖUr/xDN;?/nmԾiY˽A"?}v^q=ʈ\#*HgOjeܣNmڴ!)ފȨ/ҾS!6&Sء/$MQ $U%FSȻ_M'Tw_ڋ!Itѻl7T EtM$CgCh( GEQq>Gap(E1M?d(#G-Q$p((cURQH"D|_˿6ʿ}t(: :v#=m_JYx۞h/Yd[eA)'9eogP1^!E(ˈ<jѕ?fZ8*42Βeg3Te Q/7ȡ#$32ĸ1n!EG7nv+=߲Wg/YgQ#|&q9VHYf/Ѽ>"t| /"jI[8pnlҫKY pۉo)Eo"1h8d p_| R6 QGQDD$/##:x[ߢYf2'w|#jǎ/esovŽ~ȿXBY I$?׺Gz,`N;ƿ(7_/nVW^y _{G(?."%Y9Wf;G%~qcXVa1N;yHT9˜˝Y):ϻPPT>Όqle4<+=|gxy*鲉2:SPلiW6;o@H+rW >LK+ʨ6 ;|GpxȨDU;,zIs#%|ֳ0'evU`Q~DL{#+8~Zݱ.F=pMgc#\q 0c+K"|d0Mso"kN%K:p_c{ m=$ W䧶XϏ⺢ Xto K9iݴjZ[)=sL?ʊ[ԑ@R)C`)S$i-0qu=Jg;SWKXXDm+Tc\uN۔ 5m:.Ǩ󀒾pPk9طRoj<:.Et Y(?g\{w:Lid~B.L9I Y1n՟JV/<޾l"ˈdPS]\]qOcvlfz*Zs\w&3;6I~{)X6 >ߛӀZ;SX 5 }~6&>O/3U-ag,vSRְox浘ϳs˨0~%@Dth7j0֝f"0:`!( GVpo,(kȑN"Eo\儣(Ekó\8hJ8hB\uG@8wȧnԧrJDt!D+#,H_3;}y;UXaP()XIBïPD쯡2S"ay`2 `ʮUf'd7#]cPs.y ;,: "1gm҆QWperDlyڴnTG`]3o?NL:o0sF$"cED)(^y gp )!@WJAxtnܸ(=__HQ;Tc| 7܃W Z/>_Xs&~%3{&mj'L{LPUy9ٞ+[_.jBlߐb{ s77U;_SG/26۵ҫ x?:``#( GkE G n(vgpa0 GIb DG7Qp}/](/?%~r.([؅`F q0)5؋MD<`r6߯Q O{,;TN%j.8 G^0|%``a>;S>iǙ-Tu_CxE^.p1 1q;&q#g/|hjwfn(kVT\߃%n6rMm?=%/Emp}((U(EQucht]83Q@IDATw` :(]X@DEEņ<{vE XA&t$dCw{ٹs~Ǟ;'g;wg&ꟿ$;ד&M2_1ڿ໷uOVd~l|=w,MGhv7-^lͶdEo ˬ] IHſhE=2Yku_&xKz Xp!9w9d3OuqJZl -Ӓ.}n_νb.}j@&X- v_nѢ B1| ~`8BH2ĽYԼtc&(:zZJ .T jFpwjv^ l_0Tͼ!zCzY/|b#d~/F-_V#{ʰr)+L^Qy>Q 7Q`FQI(6bx3=_N~PtG~q\ֆp"rM('NLBx])RL=MOcWN𯎌_07YN%5Eλ-kri ֊U"b5j?Tyyw w!_*cȵam8'"gtN^P/hPq _Qg\F-o;+pMO~qeCy^Rঢ়3=ީKze9_{p駐 }]ZԠv}a|"-!]05>ӹVr̟p 6TeQt ,FqE!"v T, _>pwjB!VZ\k:wE{m^gXp_d3dh{'P-h%( +YK^֝rdWOlE,uhZ׿EVǦMKBrYPP@pody*ٝ}5ˡe?dq5#g*TWE8o5|%E~jƮ3)&^<55z&_@NC(:Z+kM(7NL Bط?]N6A1hjf @oLVۧٷC!Wq:쏮t*/♂ n__/IӸI,YD]!^riKݴYx-1(v !FT o%1n32pWݩoP7ڰNÉY6;0EKF1BٍI=4 =+{'`WSroUۏltr~/Ϛ=xEd3[_m=_X½do]Gq>d_ ,;kE~4T({EEÛ) 4F7 jpwj?C8{ڰ6l wE9`z- Eiv|%bpC#ȮhJ㧞VP|;YҘ5%*c#(=  D/O7=9 ~^#gcSXs HOf1, ޛ *xrUp{b~]Hx9- ;uRZ1X WC(B(&:}M[j pwjB!݊6l 'D,kBQN XR aB~zos2PU90feӪj/mH24Bij@rq=yD/[UN`o+>ܓÊVMS=KBӹWU|6klVECPP LO>=8bC(B(J Z{MI*sPLpYEÄ?LA|d^5 ?;;?zXNJl^DqTuvteFe9 I6\քgLկJvZz<1o;}Y u_Ͼ\ ǹ;y$&ܝXGΙ67AjvÌXb#*7hi ZBBщluqoSuܝctoaB ysד{Ev||e?^;haMOZ@î]%xݟoETbJڜGmNp޵P[X?}*rӝmZ앝Ԧ彮t˔긓3|N}Gӹ;B10E짮+1(vd !FT o'~3"pWݩ:LBщluqoW/!)-P4T(:AjJzd7w$koTƶN T+ 4&`pу;$2>=ߋxˡ`oI\2Zܨ=F].]Jۻ;'{BQpREÅA}+}=;NX. l'nTӁtYx!7m=GSmڸ] v{bI -URܫ-^zkE/{ORZk%Fk_N6LbZ._8쬙=C6iXuxbRA?pWy?3&y7/&[_,2e 0(v !FT o$v3pWݩ5E&D6VKza wEbKE -FytdWUU:mI6W3:ஆSk,/=y&Ϧ]233]VVBO=Bv;X^kfΈ˱& w.<7'=3bIC(B(LO1fW3DஆSk,B12Ɗ{ojLhjnEKoNrϏwd]"ƣW+ ZT%q.X望im9m:Qv&_ LL^ߧ5I)GQxMO<ԢQBBQlDfj-c6w5ܝZn!ukuqk{mi!m@PD(._ۛ)Bu=bUftv:jؙ;oPK!GFWq#˩\[CvNޘƽnZ6.3ᄏ>dO:"Z,cFQ0@(B(LK1cfW3dஆSk8E; 莗q=Ri!%2gj޽jb̙3ݺu37|ݺutw߬ڴ)jFpwj-^WsdӪ9Mȶi)@B~mWZNZށM^ʲ 崾V!۱ ᵘ}W1⧜6 Ubjbkzom%{BBQj̙@BXK[U _!BP/o;~[~zjOO'OL#m_-++_~;ǒjTPf06OB >LIDV 9WFȆ5v0ڞE6- t(yj@WS+QL!! v3%&;5crPowp"z.#SC(jP4L(uck b|'#g c._I)ŔE|j욒@MG/)],poƬUm' do?HY}/̹BxgM7 %;@gB>u~}|QJ(8vf W97{nϖbܡWAEŸET![(l\B_yPwԌ-!ďnB(&u6 ȞOp0X`-W[[Z'{'oS֓-9MtG-2e<6*?N<8)3?nϤǕCyG G^l^6 A͚f<3n#6zyE"x- T0i_ꐊ=o  q C5Cc wE5[ E'8hzZ bڒ@@#?hvw]67q{6Ӈ_uQ.hCZ,C$vjloJ~m6Ti wE5[RcҖ*_ L,E!@B1W![C(jP4\(:կ7_=WZ}MZY[VP]4]XŒ79**5Ad׫CkSgnC9CxwĂK i{kbI~i@3ngp/{Qӟyyyq3PY((6bx%覶_ЀrPh; -f͢X=D5 HQ%j5ܨw8pV+׽86Q!e+i|>Jۏ8∐rXF|y${Eody'1n^{<IzNdϸbP$ c: Ci |/)9wp#ł Rh[phɇ"\U6!;y sD,lܬ1ZG4IE׌ 8{oڸKݺwq "b\"- T~_6qWQ^!ELNbܼe wE`ŋSDsYd33@mIzҍ*ȏՔW)ӿ=רQ#bE+{IuZK6{ ? Q@Qp6NO7W.kEw ,FqE!"v- T,y_ki( OY,i wEQa/EK) OT̖j<9p˽S{ƒ;\aǷÆR{ C_4vomK;jFtt+ n${]V'੧.USBBg *򼂿<ֻB;C6EygO&2֘BQFS%Bqu}D6~]$@A䶂Td /mrsM+v=Z?a:W%P8|]9ٜRV޳PlM#1;q󺗩˖PwKw;~i㯡oyGv.EEjj GQ\{8lPԭ'lrGyG.o6SC(:#;EKuh\{E6݇Y&P}zk {\mT`6;f6۶iІ[1>K,@uܻw հFK_XEg{їƧtKy-11M^V$."Lb8=MPx-;Z4F@(B(J [T8* #T Bq7} BQ #^i!G˻PD(nٲƵǙf_r%Pld2(Y$?-ͅWkmz_畲 ё챮:GAJ:ٗ{d\3}>L5#[dĸy;O7 ~j m|b%*]6Ty圿9/HZV^ ݿ#$r(5MIRH %ޭ*p FQ6E9#sx-pP 9,G"hP>}:ܹsɖݱKOr|'5׫>mjKV) T6Y,ҡ{(XKJvMWN BbWv[ܛ&rE۫,qB p38|v?e![Fu%$TԐRXNĖ3{U'n-O7Sn7hЀ@i&MhGbz<#b")N/$8c\1W3d;;^vN ՌG#9  =]}+i yw|E_65㽦NA!!DR^lIqvWbfHwEwt-휰A(!pBB ={P4L(:7ChՔs7;Blg䍇C3ell(YKoc>I_z":{r?lj+Q1 pwY-87[8VoD3mIF;jFGA 53;bBBQL$Ŗgw+j'SJ9'ljF yܽPBϞ}! N~-?ΡUn%K>+i9R} __g֗u ^wШ;圍`fOQNYG/b־ZwԌ-1^H_ZhZ8"Ԡ%J&2Ӆ+p B1:S=aP;;by  NxΞ;}=Qdk]ɿ%̯ڒ@@#?hvˊ-ǵn{@ܯq0ym>D y'j{}f1a$?}eK~ڴwBzH4*sC(B(J^[Th+0]Ȱ #sem'lb"yG G^lv,hPt^dkv֖c*v=h@} -%n)Q"+NGv$pc䄙@w&`[>O@TZj%.[hV R#֖*_ L=b([A(z oF]A(Z&=-בͨUڒ@1½dE)Q+[WF6l1eNϚë`]=PeL,,-g@s%MV6Y$` ~J_Zn^fk[-"%J&2Ӆd"(6EoA,y%{BQ|lB2xSlfLҖTM Y@oZEC=-kbw|uݴD ]y,{ƖhDqWI6>mWl> 691dH8}۫ /|E۴iw":O•Rޖ*_ L;HN  d;o/[C(#&yPL(r)+?ٍ5J[&8]7]#CpUZI_D>|j;Qt`fݟF_wL J8퇍hE/^ #-F)z6u_-/,yGa4{KWWCO u;+IR*Pp,!$*" B]$KqGҦsP?FLB2x3)W@6IVhz f %Yѻ%?h~?h" $…(~3xۇ3xx XSt $ɖwtvS+'d>w_|T攜T.5_<:Z?uAOwPP$PP$VaVUqPm6Eoly-q{BQܘ BPx߽R\,)&[Z%6e5ٌTUD۾&-<~Z-H0%J@ W=%Y>| tm"_`m6v+ Iϟ3ԅɎ#yC,|c3YN,}<>/'N)~5#E_ɦbN?Cႚ|;tKY3 !i T* H?W>aPtLɒwQi! kP4T(m?&?uyy\Nev>PIܚ@@Rn׶sKF!h"f(kG'Ktm;yg 'Lޥ/:uW?򎯝9"p%J&2Ӆ+۹C($** B1rPٞw"ZZ[C(%ZhPtZק1-iTs>ߒ35{- T67Q(ʽ|6? MҲSlw5BG}Υds٤%j3Fb on31M m^tCWy85Kw=g^5,oZwt=9M캧є>./=0o3]t1bhT#$HE&]lIP'o wE5akN 9L;ſ-!mhP|n O>F! %w;=8eԏAu߳t)|WU~IKJh CbTsۧ?A6+Oxcrri4#[:dm0&<[>v4bT# h$"p^ԖꝄQ @(FŃ>aP,kwSCiOl9B(z kvPL(v/uԖjj5#g*=Wm%pM|ns\j}yv$ܨVՂ%v , =#+ٯ+"NƼmi}NSܣhg$합>Rgmo&(JeEEnK M`e/ WrPt1(=H]ajޑ ɇlCpB28v,?jŒ5ٖo|rl*t|TʡDƑmd&;IduEǯv>Ե%ߑ}όa?I6_55(&r[x{'xwr!BurkPPq$PVape*wE{P}XDc{M_Wá@(IeE˄O|7#Oȶ%jEc,EM^ybDΡt%{1g!Y]?T ].wP5GiCkqLZAcjϧiys/[RLCՕdSs-\ڈ!w⡑'P=kW3qPT'* B14L;7wtƞ-PL(?Oo [~M>MO w\_@½|c ߙXy98ګ}WD,JقE~nl۷o.4:#[p ٲ2~/h\~zoy\Zdm0%WWP\'ӁNnN,zzV;{5R#. @"e)!CT" B14L;6TǞ=PL(N37w-K|ԏIħ 4SrPHJjj-XĵlOqߴi-(( ;NyL9gӲw'jL;N{mq/+⧛.!XpPT֪ WL5El$ I\"u%Pe|}í+>J|8'>bȬͺ %Zݹ#ΣNfx_uS#3m2sRFwU񠲜iiTng98yt/Z~OD`C#A(B(J 5Ɣ*_"ݪҝ;n~(lh7{ h  wu";iU{Rk i97r9{gaJCv=/8ק V1n E=eS*]i1/=C_ٜ.ytnoR&o-Czπh{n:gfI֭")yWC(B( خLK{dV W3^pP&VSN 4@}.>A(K*9A(*_~ מA.<)9aeO{ W%P%|}ŵlXLoe[_*|r^=~zz>. RWL,:e@aӸ:T"ٍ;ngky[}ǍWDCս{w#̴;TEE1jK M`e/ WC(NByZ2;Ȱ/~5/B W_|~1nrV3W;X[x*p!1V4%-sffW[~QEb٣GeLTlZq7e!!Ɲ- T*4@.\:88q!k4"zd{hB(E, E'>|M:^~uݶ|3^7q*$P=TyUS_/Wd?N_{̾'W5MeSx]B(B(JM[Th+0]RB '.U`QSC( $t*ߋF ( -N=c'}#/ٮڒ@UsL~OTs"'PG c-OQ@UV:ަ Mq,ӹwgo֑}/d7 U}֡N[}~({:NJ.(5lIR tJ5wi&Y]A(* +UEPEl?8,["!{t S[.<ݶS^/_;:r!ҡ5ҭd|5?m S4_}1P=?Ĺ|bJ j=4P$4P|ZϞ=%^iy{oy%u˘EW3TC(xAMܛBQMV|/#hfPL(\"ߕ'^iKՋjY,){ ~CR7.xywG ۺ54F,lJCn _B>D1 ߛC~cFܭlo34J{zeO[y^z1#`HEEjK M`e/ WC(NByZ2;Hzd{B(E, ſ"ā H[^To JdIY˖N?"a/Si)׭㧚Θ1zR0o.ۙ#; NmJu!+Uń=묳T!uU)yUPP~$PVap%;bACd.~5;ߑ\e{B(M  ō7Rw1d.䧶$P]xm%&y&-_8IL-b`a+M}\O)&_;tSfνt$3bā ];a 5uS""`LORaP5~sP>8qǯrP+bw߄hPܾ};E}h9i'P`&O]^UQE+{ɳi93S(ra`Iĝlfg yD.+hBR5{ILy?o 5)YpV,ZyGlEEgj GQ\B1 6a7BQbX_o~6j޽RO3gΤ}u떰wK?ӃOkKo=%E; [\Y&W3n6۪[Uו]zwP6hjk{^O/ڴ".Ǥ2ʝ|\]JŎl]|ҳ'W15U=DK?A(B(W4-VC7sBQ8A,xB1HHrnj:G@S~z(vމ"`~CnO27u?+%r9}3U}~ܘ<4[k&ЊE/ !`O C:BpK/'CRVeɗeyǗF$SSQ* %8X/i|ϼهmNV,yGtEEYF@BXK[UnC(OW8d@pX| *f hP-'퉇{JK7 /U5Ϯ! Q^A++lMIxȮe^,{x9ق+WrK>8[ !,%,$ XZ m"ֶd;h tni+MU<);{O}㤓N"Y,m0$Kq%NB@;I;‰n$ wEawD**țQ/Bq۶ma˜SRPx<ѐEq]8>ځ }Zi Ԍ[ Y+Ң2jW}D_I_lk|%`m?HC0Ad7_\ВOZg)C5Hi½;#g>Aԯ_?#F+߃w%tʠW3X^C(z78xB1Hk?^v@(*æYjҹ<ر!˴oqJi=tṎJ]%J&2ӅDW-NI͜29zZ3G~EM^Rqc!W<+/؏CJDY\-wL (8֩jĨ%[{P !$АNj+Qb 'b8l!FрPmDԴBO'!{hr^E*kKUk`b'=kK&MBpÐ\^s/ L .n;%O'9kg}֐!x?aEh}!!ZH4՞dk mq|?r(~:ǹh{ޑBBQjْ@BX U!]@R'Qv>uBQQE˄F-:7E6nNE֖j3„^5?+qlzׯ_O5j\n]:sGͷKoG˝S;AxkƧ};$<}T~\xF30b m;BBQjْ@BX •[.Q'q@!} \vBQב.EKb}(<Հ&7͖gXj)^LWSCVMvH}.ula!ߌXPP@CR8w6iSi[)8&Kʐśw4&*VgFhEKFBٍ\$Xu;X(u/9GGi.Xt6p.O+$,b–baQ[o>QgO]wĎ "؈͖n5C;VC@(u)V7PT(s(r3lNS\$x[9;"gV[K7QK* [[,IRP4{(mȾnZJ8n<½x-'C,Xb#*7[hnj \ՆCB(uɖ,_ ŋ:@IDAT"m.2|*mK nw!̽(xoޛ'SvV-˼%+y>`3ng7 %۟E^k:E7W >h.ГOBBqs O62| B,T~É;bd.58 hK$Bwdk1@=v_/wvVR, Y\Y Ɲ5ˆ-9^OQ XDtܣge@l@65#H{%TeKx&1-e܁P`1uq_v0kl" EEÛ 4F r BQ.jÉCud]B(FcVSFPT(^sIY_O #)N&8}1W3D:%q2)YES"Jm;! w۟"hgv|Eh-\(#+]|)N+~/N[L-aJ b/wLڧ7SkW i>뮾'_|Ck׮!7~ /Xm_Y@;<DcХGw> E[oq+0@Ao x7""b˶LE=F 'jܣsPԭg!M@P4\(U߿NCyf1'%;h}FơhG2\O[j?&b ӘL&ߴI&᮰, о+pI}~pӃWuk*: iU388cYXn ?/9uELW@!9q?oѳ-0BQ‰!#se-YE["[?  ?uϑ}7lo"*EI%J&23y&`¨ϩԥ^J(Ip=#aܻo-X˟NM>k6=pNs^d@rp3(y}hłXb#*7[hnj ւxI-yB(FbZ[D~@(Z"0Sw]q'ʋZi}$Pi;h߰.}>7Y0=wEȟ_E;O'7?_ ٬cAzErp (8^tKĂXb#*7[hnjE&'莗m9B(arhPt§9gY5٤ڒ@BP9ZA;8/&l9y9}=z-n'!8/tT!>!hJdsɎ1}\FpV~8DKBBQlDfKMm7PtK8qP32.;rP1zhP|w)nf"mIZLgh%diO3mQ{ֺV%_X/iW^d0;$Xw5a@;霏:2N{yG2BBQlDfKMm7PtK8qP32.;X~y8 WBR8n8['HѵL[0h']=xޥd9;._sLzɃ<%z%{+jU~!^(EEÛ- 4F7 bbt 'jFrP/og!B(Z*'L@x㸡d󎫡Edڒ@@#?h5rhyd~Y>ÇWJ NCpH_ȩBp=F,M6kVHE%KeO?_v,K6N؏>/8Aޑw PPQ1ْ@ctSmh S7NԐwC(UW["l? ů"s!dk"RmIZLv2x^׳i#'{ymdhz慗v 3!0,@(O/>D>l dBvF:2>08[@3Wnz?yGlC(B(LO1f6D'jȃ!囨wӏA(Z*'OL{~JUnL-"D~&ܓvтpdO>X'~K CBpWCZ͛G>`>l<8+w3Gߑnql $:4w ߃/ރ^T{gXEE1ShӾ'6Dqe/8 p;|nqB1{?EK?@{ 9,gSjnw$Ҏ**_~GuR'!8/tT!}Qx7d5~IRv9q ї۳voހO)L9Vl/L<3ᓒN‰w!˛)YE". ="rŲ+֗rZp03e%ZS-mJ*JkTNg#c ˠg'/k&L@W3Cx_d8uv6ŕdo9=ւ`a.r{x &,?Nvjw (^l9B()fP4\(["ydW' Ndp ~mGImj (*|򏳄 PkdBvw!vҶ}[7sNv-٭sS6D’;bC(B(lI1m,lNd_]?*A(bqBQMV+%B1<ϣU|Ѱ.->sRmIR ,Yo|Om_!׏jꠝ÷\<%V^$YƄ<yOA{̨|x [Ph˨#nڭs~C(ZH̨ϖ[nU'jpwjuC(:DX[rEZ -s̡;ֻhKfHvۿDv}7٥)Kfuy1wqNp3"W#VH[嗩SG6)_Ry 3rJy;7o]6:q㭘PPIqz%]%;EBRJp HpC(Jn=rF -˺@LvzཉcJҐ̹G4ԝe"#C̽< ppWC@mqרA}0#Bۺ+h}vΓMoYrDgb{Fbxh (5߻تU+Z~wGUELOu/|>9#uo&ۿ߳=։U{rV3xFluqoD5l$"԰%J&d(0 r5j;VB!䏵8 O|BRXRq<ٗ`FѴN?k-/Mva]\7JZ[?՝pxvwZFJ_(٩L+8OkHX@+υ-ْ˴?.hPP$PV!pf] wC(:g!ӼB(Z*~RcPL^Elڒ@@#`:/lQÈ%\5Tw3}O}߽wXg gKr\dz}-^Ǟ ESŰPPIqz1=Mm%EmCQjp +P(YE{m E':}qe T6/% E<~Bz_B}OFʟs†+ + Prp /_j9N+^KKuyS#/xF1"Y6o+gs<;{98ފEE1ShӾXP>6'Rq2p/ %_rzP'hPŌKfT3UjjUKtU]W9!_B4„9'l™>;g 9 t֬Y仰lyyod,_BMɦljAggm#$^?>VsJe}wgiTWjtV̰@(B(8@1lhL*i(N`Ow5ܝZ9ִ,0EÅbii)^NNYg>]'sbU/e5̢|o-aZצ_UQ-RA[ gAM˜崽yJ8Wꜰ g89/sx-ND[/sYd3ϔ^[j38[?Q ?OQlW35|㯳:qU5l ""%>4ɔXx!É` 8%,c'!>g!;ٓBqخ49YtLYI?f򅟔l~NJyaVYG֧da) ԇkT?m&~E|ES~[J`|sx*׳gO-=aӢ5 &Ԛ(Q"C'cبL=ΆwjbO~9鶜h]H˿0o\'jFpwjM?C01kq6DsBp5.MC)Vvǎ˿-z[9X[X>Tb:E;{wC|؅s\xH0`.36ͶzW3ஆSWg]7).P@.sཊhӷKxF]'Ӫz٤ZwNA !- T*4BQ`0$+8lpWݩ+E;kq-C"-%B nV{CGm!"Iq]Z[Th+l~P>;/w^-*^⮼%^3&] wVnJzٝlEvrù)Kٿ֖쎥<ػ$yވFtRb#!!%[J- T*4BQ`P$+8dpWݩ+E;kqBݸZB2. lQbmIJ teʉB}kXه캂)d;?hA6MjW3ஆS(9d./AK[AQ\njC6Y?L?:Vg+9VlՊ{]']RcΖ*L(0N 6*?C4>kq%C"-B(}=tZfK55mʿ=ﱝ1׈Cp56m!앢Ndۖ_HN:r=~dSZ_ՐzݖOݬp)M1ˢ^NJl"p=RaGbĪ !nTB Pz b8\PT(:|뜖mid$PiT߼-!o(j`]WbV>apK*5 j;_TTD.O݃kޝqxgqMi}MSUg{l=6"Z.{-;QRΖ*L(0N :*?C4>kq6wDsBrx5Pd{5ZST- T?ƿKI|Z~QG=^9_ vRIpWݩ+Q=E_SLc:ھNsjM6_.Q#~vUIpFR~ZZuF@(B(J C[Th+3?ObW8qP3ஆSWIwִluPLrP\(^y#?Pdl)! \>ЎͰj\W3$ஆSWGq}NǙ3gRn6Y??"QV%E;jUͿMBѡ/7:oqI0Cߛ>ɒo2 pWݩU6uzSYg*hZ>[?TT.ln(oURɎ{)RX "3-J#2!0%!' a{FɁl<\nB-*Zӭ[Rn)-ތzy;d(VGy>EY?LI\d׍˩cu{$oZ\Y0o_W3ஆS* &Pk 6E]{Xgʒ Hݎ{,6ҚӔ8Tc{2J<9qP^U+鬄J!!) %iPn0E " 5 j;XDC|g(ZvPD(jƏ*4 J~/]j.'+]asX Aw5ܝZuuWP^gNNGٌV?(2=>qij|GbBLc6C(B(J VUBEj'5L)*&Yo4!p:bEP8<گP~4Bb~i^m~MJ ?e{pC~*%DP?Ϳ2q26̜2KwDO6_:gSW3HஆSjw FMW~~a naY CЊr̵)8WOJ{;,as^ʃw5ܝZULhj7uv/*?=EQ_dgE;܏#/_}E\VZ(EEa [h=֫l<8p?5] wVU!hݔ BahE˅ĉ)hnxZz "7e 7mK^(%L)-8][iy} *s=ɀ>:aAI 8ԪDMX4w ]f=| }z[>guPm :Pƿ.MUqQoPP$PV?bbWbDHx.?b 3#hPtݛC(BkR^[PWQhR3PkVҗc6-1m&َ;r$Tu–6D pJt_^3T~}iZ.7x=κηexZٗO}Kc^dTPPw$PV?bbWbDHx.E#?EBr8uTū^L67@Uz*mKg4éugKzr6%-Ռ8Ԫ;@m?gWjrΠ4F[QYJ SʿyYubկ{jn!!Ƥ- T*4nPprKtiM4>'JBѯBQ Gӽ@(Z.'"}RҘ%TvE6'mDԋ>u /4~潇z{5j;4ӵ/^MM<*>:)˖.AUesve~ڍ|n<"3=JCeC(7H8pɯR~=:BHpB/KC(Z.7KH6+u'٫|onsudIv~[&)'lYpWݩ4?p;5}vg; U=jk%8߯E:ݕdg_3N}xs~/ՠUW84Ž&تm"bS,TB1Qā+>N~F 4~GDt$VE˅9s(/^y-T7~_)ћy-"۩7!WO=K6Y?L;am_͈N?ԅn%^# +^BܘlCjCqVVvʜ]MUsR'lS\L^-1A:N^q]!!Ƣi T* nprKtiM4>'JBѯB12e ЈWvJKv/t7Pe[7 Q!?D hk+~丹;SG~zިQlh -jFpwj5ҥK g;li[~'Wm}nJ%Peq|x=Q"U~DG0VxV}z'czӂ3|k⧝ i'ƽ.+""g8zPLl(qJ_Iw~/ѥM(:"PkC @(Z"GO;f޵3fk^QB˹99|h2^nP{܏׊*Tķ(<&ʿl>gߗ\<6fwW3ஆSOdNc圶4+g?[w{⇗S!l` 6f^.ĶުTn#1%K}5cj;ƿv\j G4{qr )'~_Z?V֖6>/h0 8W⾺~BBQj,ڒ@BX@.\Eq8 X4>C!f. E'V"W-;9ٜ5"R-T{T^~"~^^&yy|%d?]QSǻMVO{fFIW3NஆSiMFM;g9-م^;զL-hayNEpF7OӈrM1-[ !F- T*4A( \.`P}KpELc0$B1hhPܾ};mcwDB!.y*n2lr+A_,g:LǐXpG6wӿ4#pWݩV>uD.kam9ϩ(q)j9J#l(]u"ܖ/S!Ռ\j;CBwk?bH WӼB(Z.KKK)&> 9=iBQy76ӆ"J7qZ.eւ?x>"` [B0 S%j;{yMe>IR{~p?:M;Fy_ԧFt=RcΖ*C( \QH G#@v|MhP;QL| *Ն/+%c[/䧟?!n{ǎ~ﻃ#VH[i;y;'y4Q+ ?F-ƊbQijO_qk4= BBQNk%J&pFqW86P$?/_SC(Z.@|;~~mY **FED\d}}nEd4= i >f] wdˣ"s?]*_smwS/e9-_RLUm0erZnDW:\%m!!$АNwUgT@0HRW( 1`B0뺻k5-fW슋 A`" "I%H;/5U]u?us~uU?A,o=Dd.+JPʀhe4b=r6Hbf͔*M[^[D0y 砬o7ǡL Wnؒ:sJX*j0x=-ڶ 5ӽzgڝ=շٜYk Iы?zW}%TV>l̑ ȵBBv% hdaAP=xGLe8{E5^^=bzB1G~GK\=s^PfJd7m~Asڅl={f.e [wsD w5/_v]]l dg$\xaG*7ݱETF.$$_3:7O$N.N˹aC(B(α$zMډ,.@(9xr]EEBւ,o=ZP3 2bzr7bd} En}d~/b%1ov{\aK (n#?ϴnJL%⧝DLO9?NN:Y"۸}z4PP,P~2eL8 \Hր$ZC(F*ňsc8b\bb-)sۅ;^܈kU\f99;xײk5ߴY''b>y۔q9 ip'pÌbc߰E_wA=/pWQ hWwyg+%ۣ h\W=m6@l-rSW٩I7)EEŠLfP̌t+~oWp#d=!]B1&BQ%ܧ_:fOR}Ve[xFQ۲q~NT3 |毈f7]oNN_L3Mb ^q~zZP|~-Q+/z'=쪭=%y:dqϩߦnz9/.f{KÇPPP>ŃPOz4>'dR%p":NC(>j!Mn?1*)AGrمo.z'ݔF qMV ?SmqB#W^lA<2n,f27lR䢍hd'U\$=OT/?Ȁ,;@ޛ h6|곛bb#W$|d{(l2Jd~%E?BSDž?j-ٯlATQ hwD%r~ɾVvQyĈd1HaC2bܭ#n0s~԰"-?x{_M?O/Ѿa87 BBlFxg7ś!ő\i2# a SPLEPԉfB1BQEm1٦yfQKYE'x9+t ٹS#{ҟ;d1HaC2bܭ#n0Ƴh$?(PxJ~akrb͓^ʯ?g_"5yo4"ٌ n5C(\E rQB(B(Dr #B+/ -:xE3ث֐OGtK@ǝx2ٛo,fSV6kh9@wwHr zgmn&[t"ﮠZ-(%_v8*f"ٌ ~nPMج|p噮7OncX/09ņA(ʍf_9ek5>.͜d8b4KlE&I Gb l:P?ȃ=2H?/>:~z2٢~%w~G ȶ#٦~ClsBqͿ}sͥPP4Q>bsO[.|!;nkP'E0U #B[~adw/uѰWVZLw<3p Seh9T7c~p7oe/qϩvɟKd׮gD=aPP4Q>rMkZu.x}/"+ N?*Ԝ SsɵZy #eNrB1M쉟~Ծ`>W>~_;nBa<### =C32a$4#rPP4Q>޲{!3u\o! SPRTA(&CPL%K^͝>"c_?`9ٺU5dk*jɮOuk/%{p>TCh\x{xj zуUU测ķzڽlTvOCx&Qc^ o|IzeYf3[^@}v+fŴe|p=|?ᮢ"l!y"%=;l}=|[&:"SYe*rU<؟co{no 7<{Yr"#C  ߛ8njs'[أ}S*o-ڍgڞܖv뗼:Oܔh-lRL^L7Fyo3"ٌmP ]Vn**+B1b2 @(L(>3t׬oxj'~qdmB6W K7gōB1ZkPý?Cs[?wpWQ_f!!eJnPPwSE Sޙ]O?m&YQfOtA(K,7 w 3BBL&eŘ E;3; qmظ}ZaI7Orc|W|z9F=7Ix .:e Oz%[G`oOڍ֭1٢@Tg?{ jI ?BL)# EE-nPmPEr{ pw]EEŒPP4IB1BQc?+Υ>T-"sxCˮQ%cʒ-!]bfaXrƁ{4~QGTǏw<5<8V~:yI!y v~[5V=˴f͚ߨyM!&Ef>q$>5wpWQ_0k!!fTf{P8qD?=z!fG3ׅbՏ=~]>377(y {ō7_#[Z &&{+5 lvnY5g?)mDii yR] !H]!!gQycn**+v,Q"L#B?#-> 9J**a_V~> !apM!Uw)ҩj.k6|LԮ9Վ2 OS<8y'?l,aEE BB'Ebٌ.w7UTW$ZE|OX!sD(~?{ΡhrM(VL[C<)7U_?1ݸEOgvmJ/p& w Y h1S~w?Gۉ]x&qG8βpۿyʼ[zr5d7TSMAޛ% h6|A(B(H, Y h62b3o54PnA_ce:f"?t]wUM77(x웣"70矓>/\>+*Tnwn{ߊl,*g^[D`ޟ+?(u>_{K{BBlFxی]EE6|p"Wr[Ґ_!!fTvxׯ*;VN6|ӘLv~=?_f6J^z/JCīY\EqV7p{iK?vݝ[j%=z\{}]y n**+/C >vF۸,}K-vNÆ2yo+"ٌ"l n**+BQ yVOOs䧧Ͽ?(c O8aG5%xBS#Up?mȐ!aF"Ez0Fgd pOog`gŗ_{*[xWm@]i37,y%%ͨ.Q?{ϘzTiCB+s6TI$E]\n nHZBQC(*",b̄+BQ>c o/Ɛ->Tj樮[ ӗTLL/:X8>KBVXXHvڅo?F \@ao ܷ`@ߐ չ_tEC7EmS-VL㧝Lb~bw_o&O qޚw_H2:H $P/ չC(/=~ c&ǿ3{e)Uu5W| u+U7Ÿ(֎fNX]Tu7p3PS$2M ܃q mmد;u;91 =pẅOKo8~C[O`ڵ4B?\PEEćP]/.Tf j[}YB No@ c.UL9ؿmnD6o3M(֬_7 ~ lD*.o\j* nHZpGLY>t <#ק?մVkǶ sh!>QjFqA+;:[!!EBQw`C"R"=' nsEdB fO:ώhDB1DŽއEQwJ%FE Vl1)Sڵg^'|=Zz>#5$,.nᮢ"!k] u"2e)9sȖ\O5{&ފliW~).V?GСí PPl(?A(GC\@e3CyGFGzB14Pu`f9&{} ǭ&[д`s2HdPxBAo#{VG*Of.1p54CpZ'𷆶A oqaoU~`7SBOoy*~'Ql( p=Xmwx}/"+rP4s87=~^ sL(=`~K6n.U Ū憎G }Ȫ^;+~UMYequs w Y h)G>ɴ6>JfUr)U}:Q"*QOTC2FQ;( p=Xmwx}/"+rPvX=~!sL(1p%HehVgPY]KĶd'~A^|O:ߋ` lAw7UTW$d-֣NDl?F’gȖ3zI,~g D=Ks{ʹ,w!!EsBQw`C"R"=' nsEd))ob Ps.٢mfu Ū{?{ q())! PipWQ_.[:iׇ6|6.Kl_gJ ?{=۩ㇰ#JrC>{zqr΅T?;4ǻ<<"hB( zHD*]gDcܭut0B1dzB1B!rno|*7Pyʴɮ+RO̧r] Ϭ5ݭ-kC6۩``)B5ޯe \@#pw]EEBւ,o=Ddʦ/X6#[8TߢQ}u5wWW*X;g]bH⾏'6~շ]2V:t@ݸ1p[fG{KEE~̸پjpjT-KNSA hEEфP]/.Tf j[}Y;t\d_9H?8ҭSA[ʭ~M ڵomMq>|PLXN kwpWQ_wg@;>7d%X?w=}3lK_ɖ짃~k ~ c[S?ʹE4.aBB1l AC#pO oi No m3;P13Pf\?Ձ1Sd'lY[)h.2, Edp/`0p 72-G½@~C3x7*^V>^I54< FHvPI9+mqW3{fBBL&vFB1 `n t Af{pPČP1/R\d[?o6|joyFqR =@.BX#)`6>7 w Yiܟ{9pg=_mRG;ַ [ZM󹞗yyup&y1߇UrCG55!!PZ!e3.Ƒr0Y6c#N ӸC(扗Tb@찢lJm{m ۷9% x ` N]_j#׭'[T\, 6`ֻA(B(M2E8;8@=&k{x7u)ą;bÛs9&.\HI~![U4 Mji+Wg[ ]3\mqɴp7M4=/Sp~?ۯ&w]q&'IBR|+? u oVZ]C(B(9  ZG2"V b{#BMdjT gN4;f[{o>gQ,+KL9xMl=ȁ VƽA l\Bj_r5⒒K5f+)>ƁPPL7gP I.) ᮢ"!k] u"2l(B1DŽ+(G9`M[U o*xfqS5ۅxqS(~Νn3ln nHl>o<t䡽.[3unJ]{w=eȘjEEhoqMnch'] _a(B1aΙA1%VVSFL|cԈ!X˶NgsZnd(Oݧفݧ#?%GZ}z,ȶ04伃T?unqC!:ڲڵk ʦaA?ݴQFTgMsOo/zC(B(RA(ʝt.#>ᮢ"!k] u"2l(B1Bqj~ m[SN>lōy_ś4m.\֬'nEso%yӛ?7T?Qd=Ф~(!`vhyw9֩"**>"Ct쵁=A<J@(B(R@(?i'.nᮢ"!k] u"2epȄPPa7N\EǶ_7 S}+~MQ,x&SiWNp+n,n"w7UTW$d-֣N$ZBB2B1ډ-qus w Y h) g(E&\=bE#^'|ބlwQ]BWxfr]/]:8^cʴ{ZwH9@ukht fC(B(RFA(=2.n ᮢ"!k] u"2epȄP1n:ʔnw"m6*tӨ}1줛i7'px7u){J,b/:)'/8r nHIfNL #nHZpGLe8{E/2!sL(4}iz 07E\lM?Ŵ6aWUQ}ҍd|aWz%N`@2`F@ka]"\ap  Hht/9B wYz4׉Ȕ]W"B1GbcJK Ȯzc)eج䍞3U\p r3[=Uh- k@/ <pWQ_0c!!) ͜PP7GpWQ_.[:2p^dC(Pu!Fr' o{p#T B0.0D8pw]EEŒPPLP4sBe\@!pw]EEBւ,o=Dd.+ { WBQ}6٢MRfqtӫ~w5=t(a?M}r^X'"[Y*+n,n"w7UTW$XEE$E3'T{w7UTW$d-֣ND 2pnhv6m@Ϟ={GBnoOk%EGq/\XX.|Fd-p`_5 EnHIfNL #nHZpGLe8{E/2!c*7m .Z2cIG%ɾ8ݺv#kk`~?'ӽ4zLwS$pܢh2.n+ᮢ"!k] u"2epȄPP,kVBѢivxnK)ٟV lpa>Z`~?'ӽ4zLwS$pܼFA(B(Rn@(z"w7UTW$d-֣ND 2p1c^z2. k6nĉk5ʚu\ay2`R3[-e;OndH70EEJ E3$fո9 wYz4׉Ȕ]W"B1BQ73g{Ns(K\|U4k֌,-q[-e;OndH7\a`6BBRBQ;3bŹw7UTW$d-֣ND 2p1zZݣUOdio|0.B'-0N{ix HEQ F!!)' S#e\@Wpw]EEBւ,o=Dd.+ { WcBQɹO'N 䋯ӧb5ShGp73mg62#N?4H=ȃ?2$IEl 9 wYz4׉Ȕ]W"B1GJu/=,-ZPMV,N`+X;vw8vi#32܍` C4#<##LrH t^Ķ C nHZpGLe8{E/2!s\(Kpggb$pOofp_oOpgd h6|d-p`_5 EnHf2)AYw@2`F@ka]"\!!f7>,7eP5 €D w 3BBL&8 (KXr3[=Uh- k@o8"ٌf ={!Zp׀_x"ᮢ"aB(B(ɤ^pe[}Ynˀ=܃jw pPP4Q>p {w0B/Z " Ow7UTW$XEE3 N,uK`}܂ o{p#T B0.Yf3N`@2`F@ka]"\ap  h&z no [pd-p`_5 E7 BBlFx r3[=Uh- k@/ <pWQ_0c!!dR@/8 -q >,7eP5 €DfC(B((o8}Ynˀ=܃jw p'**+f,"L 'p@P%>nf ={!Zp׀,pEE ' o{p#T B0.0D8pw]EEŒPP4IR7- {w0B/Z "h6|d-p`_5 EnHf2)AYw@2`F@ka]"\!!f7>,7eP5 €D w 3BBL&8 (KXr3[=Uh- k@o8"ٌf ={!Zp׀_x"ᮢ"aB(B(ɤ^pe[}Ynˀ=܃jw pPP4Q>p {w0B/Z " Ow7UTW$XEE3 N,uK`}܂ o{p#T B0.Yf3N`@2`F@ka]"\ap  h&z no [pd-p`_5 E7 BBlFx r3[=Uh- k@/ <pWQ_0c!!dR@/8 -q >,7eP5 €DfC(B((o8}Ynˀ=܃jw p'**+f,"L 'p@P%>nf ={!Zp׀,pEE ' o{p#T B0.0D8pw]EEŒPP4IR7- {w0B/Z "h6|d-p`_5 EnHf2)AYw@2`F@ka]"\!!f7>,7eP5 €D w 3BBL&8 (KXr3[=Uh- k@o8"ٌf ={!Zp׀_x"ᮢ"aB(B(ɤ^pe[}Ynˀ=܃jw pPP4Q>p {w0B/Z " Ow7UTW$SА<IENDB`wagyu-0.4.3/docs/edges.md000066400000000000000000000006411314062220700152110ustar00rootroot00000000000000### 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 bot; mapbox::geometry::point top; double dx; }; ``` Edges are only used in Wagyu to represent the pieces of a [bound](bounds.md). wagyu-0.4.3/docs/example.md000066400000000000000000000035211314062220700155550ustar00rootroot00000000000000## Example Use of Wagyu ``` mapbox::geometry::wagyu::wagyu clipper; mapbox::geometry::polygon polygon; mapbox::geometry::linear_ring 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 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 polygon1; mapbox::geometry::linear_ring 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 polygon2; mapbox::geometry::linear_ring 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 solution; clipper.execute(mapbox::geometry::wagyu::clip_type_union, solution, mapbox::geometry::wagyu::fill_type_even_odd, mapbox::geometry::wagyu::fill_type_even_odd); ``` wagyu-0.4.3/docs/getting_started.md000066400000000000000000000010651314062220700173120ustar00rootroot00000000000000## 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 wagyu-0.4.3/docs/invalid_self_intersection.png000066400000000000000000003253031314062220700215400ustar00rootroot00000000000000PNG  IHDRB1 iCCPICC ProfileHWXS[R -)7AzޥJ! J AŎ,*v誈m-Q,,ETu7I]|3ϙsܙrPr˜`?fRr $)0bEpewm\UW8#bB| \-@hz)~H 9֑49Cb 3Pg3`%Ķ|vؙ,΀X ywq23m4&1Ȅ rXroa5S#mo0)ܑ( n8$~ؾ-5 PaA k2؞%B{47ӄ3b8K3#+ IB Wz03.Qm*%DBq(;6laa䈍P#l taPٰY4!ό bI\QR7 Pp0b}K9X%7'8F^g차 vķ#.0yYr؀ ?:N A8 İ Z{z?H`!\`=Ha qhO6PeT+Al@ Bk^{qWmď<2+1@ !-Fy!؄otaɅIGrNxLIDej:Hs&-h84g7p?q qG/ ssG}IYϰ^RiE1w5g؏R(֌.c':`X vJGWJ-F-~aBY ?C0[g/2gیc9 ?o6¸MwRcp)o:7p{T[,,piG wFd`"q LUL0,% f {pԁ6p܃k}` "BBhB G\/$ Gbd$@,Fʐ5fdR@!vA P .jG]Q_4 ChZ+ЍhEϡWћ}cSfbXcBl>VcUA>D3qk>Cxf|/^7Gx@#PB!0PB('&'\{0@$D3 ܛ,rV!Yb;O"HV$ORE'6ΐ:HݤdE>ٞDN!Er>iryPAED]!J0[a.k UœGɢ,l\ܧUTT4TtSS\Q%GjTK?u UL]AC=KC}KLi>Z>mvAdQZTTԡJYADWyrrQkʽ* ***,**'T:UUvQU^V}FR3U TT;EFt:~ޭNT7SUR/S?ުާᨑ1KB㔆1LJ-Ƨ1c|p,spLǘc5}44oj~bjjek֪zk[jOҞ]}AwX챥cX٩Ӣӯ+ݤ{^W磗N^>]KN ӗlb v 2|`D1r5J7Zghgoa<׸IffKLLi՘7{W߰ ZZd[lhD-,3-+,YVVe:gf7ѮȮ=۾!aCkG+Gcm'SF/.B=...[\:]]]^r#-p;=_B Y2qĦ0jXl4bbڈ&Ⱥ(6AYt^o'ULzc379;=v_@_ʸ{) $J'KKO!$N8y)NSJܚj6uӴL;5]y:kTBjbϬ(V?-4mKZ۟Yzrp{Iᙱ6';<{-}vT졜ĜC|5~6iތY3V$=o}^0L[QEl.ĪGgjm9{gA45hyvGo\`xA{Qe/ȶhMѻʼnuwSMRsǒmK񥼥mZSz̶r+?+ZW:\E\_ukkTZvs]w맯\XmexdcMƛVm9s C[t,~+gkGOmʶ}~{G*Ӫĝ;J/ջwG7foSKu>}+kqM)?h}p!ơ_Su$HQףr~]WY'Oo?1DcGl~sd)S+OSN:Sxl﹌s]O:iRS녰 .]<|祓/z;~չ˵6 ;;]~F荫7#oߊusJ6;9w^-;xo}*zDswSgϪ??b%cҗZzZos|p w`}{?~lϤX|iPА%dɎlhz:o@Kgx(_2AwF h2'=>/ Q*a3 w@F۰屨C04VR_CC[dp6O~ kJQK QlC pHYs%%IR$iTXtXML:com.adobe.xmp 1054 1090 B@iDOT!(!!_k@IDATxwEGI E <gNrw_ o >ZOS_]x!V[ao&Fw7nou#d'M x0ap.^_Hw (/En:gw+&5L!:+ {07:Mw|㬃!;nrE὆ 3]x!V[ao&Fw7nou#d'M<<0Atp/$Jpw; Ds"c7q䃻&w[ nypQd즃]q?|p7"`^]p+-7qQ^?tp7:&WLk :CuWn%a"ntpw9 GvY8Bv{ Dgw B­w?Lčn8Gy(2v.8GN>\x0yxa p.^_Hw (/En:gw+&5L!:+ {07:Mw|㬃!;nrE὆ 3]x!V[ao&Fw7nou#d'M<<0Atp/$Jpw; Ds"c7q䃻&w[ nypQd즃]q?|p7"`^]p+-7qQ^?tp7:&WLk :CuWn%a"ntpw9 GvY8Bv{ Dgw B­w?Lčn8Gy(2v.8GN>\x0yxa p.^_Hw (/En:gw+&5L!:+ {07:Mw|㬃!;nrE὆ 3]x!V[ao&Fw7nou#d'M<<0Atp/$Jpw; Ds"c7q䃻&w[ nypQd즃]q?|p7"`^]p+-7qQ^?tp7:&WLk :CuWn%a"ntpw9 GvY8Bvڠ+$]mȑԀJ!zw[ nypQd즃]q?|p7"`^]p+-7qQ^?tp7:&W|὆-A:CuWn%a"ntpw9 GvY8Bv{ Dgw B­w?Lčn8Gy(2v.8GN>\x0yxa p.^_Hw (/En:gw+&5L!:+ {07:Mw|㬃!;nrE὆ 3]x!V[ao&Fw7nou#d'M<<0Atp/$Jpw; Ds"c7q䃻&w[ nypQd즃]q?|p7"`^]p+-7qQ^?tp7:&WLk :CuWn%a"ntpw9 GvY8Bv{ Dgw B­w?Lčn8Gy(2v.8GN>\x0yxa p.^_Hw (/En:gw+&5L!:+ {07:Mw|㬃!;nrE὆ 3]x!V[ao&Fw7nou#d'M<<0Atp/$Jpw; Ds"c7q䃻&w[ nypQd즃]q?|p7"`^]p+-7qQ^?tp7:&WLk :CuWn%a"ntpw9 GvY8Bv{ Dgw B­w?Lčn8Gy(2v.8GN>\x0yxa p.^_Hw (/En:gw+&5L!:+ {07:Mw|㬃!;nrE὆ 3]x!V[ao&Fw7nou#d'M<<0Atp/$Jpw; Ds"c7q䃻&w[ nypQd즃]q?|p7"`^]p+-7qQ^?tp7:&WLk :CuWn%a"ntpw9 GvY8Bv{ Dgw B­w?Lčn8Gy(2v.8GN>\x0yxa p.^_Hw (/En:gw+&5L!:+ {07:Mw|㬃!;nrE὆ 3]x!V[ao&Fw7nou#d'M<<0Atp/$Jpw; Ds"c7q䃻&w[ nypQd즃]q?|p7"`^]p+-7qQ^?tp7:&WLk :CuWn%a"ntpw9 GvY8Bv{ Dgw B­w?Lčn8Gy(2v.8GN>\x0yxa p.^_Hw (/En:gw+&5L!:+ {07:Mw|㬃!;nrE὆ 3]x!V[ao&Fw7nou#d'M<<0Atp/$Jpw; Ds"c7q䃻&w[ nypQd즃]q?|p7"`^]p+-7qQ^?tp7:&WLk :CuWnes_t)iܤ ɊTM4uԉdONjkk?#[lڴa:)e˖Er}#ٳfw~aGE2oL}w$>L7^zŕ$8~Rb;n]#yKV*Bv {g*vȱGBg_7[nS}wؚL<;5۷7L&hwI~h 64'U>zI"wcXP!೯2Kx鹧QՑ_z SIڅlՎ俟z^Rߕ}Iz-#/(vc<<0Atp/$BCCf.,x\$!L6Y~-p7c7BcK.HG&>yw ǐ|3Oè[G$5=oq|&}ʱTui5[g4~mG~7xSFI7Zoz<32UWUޱ}?*c|OUwށSa2)} '_޽{*7]})U5ܟ3gLzV4x$?~)_5F~o@&&M3,h2)[nkN/~-*;%_H>p~|dIp$?<IS/!}R~H "/=~NmtIb:/yKnjdNWi_~3K$>mݖdk5_LlZN/yGB׮] ӹylq$,KWިQ#I?;9\x|w\%+u/?62HtCdgbEo}a7WRwnx\_"y7Njt8x[nVs| m;|ufTei<_7֭a2[s̡zו䜳y6wT,^7nlVT“䰃\>yyxx聻;Cy]5C^]ҿ?dҏ;6ɝ;$G.8`O:<tf?=L&[mԓ 7 >4oDv R <]n&5&<H*B”<<a"#𐙋" $2K2sA!3IEAH@$}UKyٴ7?y|i<єgߎ#;Upɵ nMd3lO /xqKW;/٧Gxdr1?ijg~d_og7+7O 3:"wNT&9d$?y\I?szuuب1۫wR J?!$<+g~>`؁}"N='b2[ve'S՗qЖ?G$7h#1v7 yK_mo &6mdeW UVsqW(Mxޮy=Hy{6ߊ8wr`3e܋}rʆ^QRZѹSWy  CJOɰq_Pӎ97',"y/'}0f>\LTE"a;;wR\1k,:_;ꨣ(G%)o~/znj<.9bJ?0>/2lOE|.Aqx _O'Jɵ OK'x>Ϗ_JrFY;x}|'WƋrLy{3Բ}˗\ke_*A/*ձ}zَI1DtcO ɕ|zPOxU~/q+N;۞ԲA?Oz(UڹJcMڳ>f۔߭a_|9yݙϤz-Z`@[2w+x0yx2Ax<xLCf<"ÏLk\4r=:8kI(ɵ~3A4$/ܞ< N+On|.!PV6~24hG7'07_'#;J'+b_jozoq!Zݳ'kZI U'\#^&x;)eYUACmD7'b/,|$}"^xa~rb)S~(\x={Ůȹ]87/< z['L+xY<>i*ߡn]XfF>QBO)'w~@N~ Zn^YkNLbSߒ.Rg t.s)iT~_kIJ}2su|)7ɔ ڒZ9?,;)߶`gH6ITtT0nj54.[(mu9#'k0HU;d'I|HpWk~k.jRD?МWW7;=')AJ00x|~9.Ovmm D`gU*T-~)<.^vmJjمUnoՋGOխ*&tf%*\bf )gܩ oM1.X7QcV2#"rMsC;;+M8$؛,_?b^V fO`O鲳|Y}C7ח?絤V{#9$˃|W/lG# Yhž/4ٶMR<<׊} ïW&7<7,x2<CE/#|rpAaA*S̩9*>AxAHi{.y$I֑vrĺQ3WH@]ts9g6?!y~\rC7y28b2u߽k\G '5O_qhhwZ<Y-`C[<<=< fo= 9>0 ޓ56?jO,V Po9w \ݗ?-<7xBl~!/vf_s?=ˋw`|oO[lUx.D@69rNm[2sG*3ȁ2Hn0.OCqѺ̥͹b*5|BAFs;nwxfO~-'?qDJuNwh:Dy*;zuUe:uUv&sQ<^:3Uu>/N|pbwU,~߸Uﲼw-7h":GYdޣ]N|}x^#!Rkxv8,S/ylW^dqlu+Sla`W'ڕS.K;wm"E/Cڏ,?YQxɕyu{x}t~\Rɼ3Ꮍ yCǓx|&[V:_+["vm.큻IZL ez z!P?Cf>|82YA?q> Xx(c Fyx,}#}Fb>=noǃ_|''IAK'$R>W?dgIׯ\Lt8{FS_(?Z*?I[kH IFgY$.xuOn;:ߎq=?xrn?ɛݽxsnl?=eܤ!+w<{x:\1__zFv<3IF-+H^rJNSxgnp ϝd6\.)Mݯe>aIuyCN $ޙy{~"'ymғQZ-E&}3ΉdVμA+O[OvZl;7+6҉dZ;Vٿv4n;[$?)w?m x ?YXK~r<~:O~z)(=]+yw,Ov_`aֻ]'߭oz7K*W;g&]$5d6hѓr+e&]W.#_q׮ԯY;+A$vEV﬈˿z.jHioXƵ?\>W&ҨU:;k˗[㳼Yž*{7؟{y$ -WB+c=VL,}AnAVdC!P?i< xxERxe܍{7Tn.h$ b ǝW䃿NOw B­w?Lč^lܫ?_8 1%/žźYp7#`^+ C8b[%::]x!Vw܎RVUi i-r{Ny!Yێ, [:}ƙ<{&*K1gb^9w0/z:R>e9w&5_'~bYZ/np.^_HԽz_կDWTz[iߦ!'%53J{w;&5L<5D~l7GAx^CǾW{3y^-ÇcCi+ U&YLk _dþ_y}o&ɟ*|z$sо R+:= :+ {0769M]{bQgXt0{>3 N灻&9x0y *Uqq惿]Q=tw9E;.tWx0yxadOe\6͟A-8b~MI|$sо R+:= :+ {07zp_-dItoGoVZqFb}ӁnrG὆ y KʂTxf{-);8Gn>eP#w0p/$Jpw; DZܫu>A'Kܜvx^+{ <s2t5"Y~PsG_2ryͺQ6h-lK1w]p+]s}]q :&wLk ʊ*t?CuWnk<Õ&iLk oG}K(;=>'dszl$ᆀ*|/GuWneWiwKU3f,'Y7SX or9?nrG὆ @!] ծR = :]x!V % &9Lk vqcaJr ꅤ!Eӻr$@ "7,=/ۧuWneWV:i_FSÏ݌ۿm' ѻ-닫Y^7zC:p':M<<0A pMj pq:+ 2_<_X/imp7!`^;9DrI޲'G[j@zSbqQ3K v)w[ʩ7?x8~Se4 =Nǀ&!]I JzuWneZxHoX'o-p7 "`^3nb2!$ߙB v|8rswjH$ሀ-_n]_?p/$ʴ >ֈN`;_'2m4w|䮻jM^{ $!CdÆx0Eƭ3sw&5L! _uWnewG:Ӯw<<0At@{tiA f$';ֹxȥ'DB {lTm{<#%&h½ljkSwM"Y@vk; 66~WH#\יɼVDx0yxa \#n?ɂ=5 kzX3:X%3Ԇ#ř:YJ{ Dgdۗx@HهLnWcC.=) "rYpϟa>?z{rvi4(nCwVw<ұ$߀k,'6#Y dL`U!&Lk :C [<Y!`oUp"&p{0:-ބ;BĭvU >F὆ 3kWjuܫ{%ɺyK?kD=n'8 B!T.\)pLj˯^ON'X@ya9e޽_,x^;n9k&ZLk :C ;v%jAd+]p+#֙nZp7&>j(j9Dr%$ Y֠s]kq9G$QddBS'FWE/@IDATp _ޕ?o q_pb;+7uQ/4L ٱcD}w0&5L!7 r np.^_HqxQL^a1{ Dg}woɊP_-꫋=94js3KI3{[Qk$#x;=wߓd&9a$I+|dM uf֎=/@὆ 3Cn JJs%nyOg=[RvʁqV#G.י鴪<<0At-qo:Y;L!YѶB kԂ]w.]p+ܫFPI^yUt͕SɧFxȈ)6:3ֱ<<0At-W)~=&-I:= :+ {0dj~'TZ2P5d*68}9㖑lԠ Sgp>>ptzr A὆ 3\sGg,DM4pwE:v*&\}O@὆ 3\qn^twm; 9w<;vmA@O73"B+$? , v2Lݰu7^?lޖ坔SrݡcYW7\3H9wx0yxa WxܿXfb;m>V."]ώS\)Vufa~kLk :C6>NW,,V:~BF~C:S;)EKZb!ӽ]p+-7O_dnˏD`tftpv"יA%p7;& w[d qJM47{<*|8k}{ Dg^YYI'xb弳!,5jDl7AQ/.OJ ~`!ө]p+-7IGIDޯɃzՑl;QSz:^a%[יٶ÷rn8&5L!`;+ fgv)pOhn?7^ٖF![R:l3uΪ𽂻G<<0At@r?o% Nb~bCɞW,D2G~gg. ^>w<0/9ο^h{&O%٩S'pCPיn^ x0yxa Bx+nt{uw B"]C<ӶV̴ϳ쁻#<<0At@s?^%h;RW_СW,DgfIpOf?wfi4(&e?UVu?|wFSO??6lcb@3ힽup7#`^qQ^quW)IRiL3ힽup7#`^o_J}dM5ϣ(M'unm4GX,Ȅf?!<{y U ^l׏}݌HKX֙v)&sLk :CX#3>J+nt{uw B"C:m[)um탻I&(6W|%zvS$w::{ww: T2tk9LfN&(6<茓R]O"?׵m随-nE὆ 3{E9a37W,DsRqpO dB3\=OyVVM&}mJ>b]durEPu: M<<0At@rGAgW$wD2C2nڵu-_nD὆ 3{fMԬǚJ1ŽU^qOuWY2+gUq_޴iӬ :'*BYnrG὆ 3;:Լ@G_?p/$}i:fsz߾3.\{i(F+uNim6tx0yxa b(UY:+ < ذث ;/؟eNX9YN.w;&5L!P,x7= :]x!GjŲ,5n(&5L!P܇>6IT;]p+-7aniLa< ҿN|'qjgsϼ0JN?&(U<茧bݞ]0%&RJuYf!`^d.|XY+V,ooӶ;X"[]p\M8}pU*gGzUөzMx'ċ/$-H.PP99P( q &wLk :CԹ#3 +nt{uw B~C|-יn &5L! '{w]Bz2:iOXdmג"V$VXDV U= s;oW~wXk u4)|Ωjw;&5L! wtWz nπp.^_Hdw@b Q P0 Y@X-­4AVv y|QETfV;?@Gsl>K0Y}p7!`^qU^Kwd# ] @33rA$ <<0AtgRO&Y> xDSKv!s"c7^?N}I&o}՞mBPl|TnCJ>9`Ll˫"x0yxa p\J%To_]x-UrOGOVMOdYXXkcY)&VLk :CC|5To?_]x-UG}:tp"~_?pZMI!ɫwYJatX&\}𯟏\p7"`^wrU}B%.{α5 &dxHƭPjrP(07:v}ʣ"|ݒ,4unp7#`^]" $bpKǾ78pD]ZG(eOXg.x0yxa p~5 3Қ^ooԉF S Pl7)rAwXW&_/ۮ]NoI'$۷oO>&wLk :Cu#] $(p14`m &wܯ rIΚ.5#YWրKIV;֊t|CXn9³:}:kw:4zGL^nrG὆ 3-wV7Q.tIp.^ ?Si:3Mw&5L!nϘ11_O/'Y^^NY3~tӽ7>gWц)  @+Q]:g~[o=izQKstu&wxp;[DT_']x-tdճmD *W38BG_]x_R9 Ii?Co'R:[j}>nrG὆ 3]x͕?B.?}_닿6!px.]@M+iWnv/&5L!:kR!db\$u1bM)}D~K_h 3]xus#pv 5ڱ\[.}$K#u8hw;&5L!:kR<d @֕~-cp.^]GALz5@KJ˯x0yxa p.^ݾ!$/躌dk;぀n'#py.i)|ռ_qi7~8ntKSZ"lip6{ Dgw/dn @n45p# Y{5A˕J,nrD὆ 3]xMf}&l2=V.xJW7ǿX<wWm1Xs??WL:[bX;Cm8wG&5L!:kZxIZW_]x^ÇW-d,is\|ً<<0Atp?+"yDkŷa{A*n >NwſjF55ًMH.2{."yux>wM&w5_<ܤ @n,ӷ]jG;d j|9m.n"&5L!:kR+X{ѧ$SE.xi~U7W-6cq g3]x_O~AMO$#YjIA|_<x0yxa p.^GA&Z7Z[z_O]xv<ԐI9m-%n&&5L!:kg_+ĔɓIW`Y9KSlԻZ7s!V[ao׾-Ի.}M"ګzrޅޤ "c"L:[XMw;&5L!:kxrI7jNw?L>d:?Zw&5L!:ū+^~.|5}.I7;_#d'p֪+K~\Xj%\mKlIK? L &w8KW7WhB[ nyIu Mp+{ DgwEW_H_[2ɊW/=HW7Ld,7߷v&Yom™{f.Rmy'FA۶m]ZAѺ4_x0yxa p.^#0=+ }Tvm[Gg,1WKjmU 3HrnJIL@:{?w;&5L!:ū6zHW@:$ æ+p\#Ni_g9:}&wLk :CuB7Pn!/D. z OcV=WbϕGSjX@ownmV'GW_&y'sA|BnrG὆ 3]x-]Io>)b_]˿}~9. .kM&Ҕ$N_ xǃZ({p7&wU? zC!|o6\nwduMҫ:} &wLk :CubRq+Ɇ;N9bZ{_G]xMʿrB5en$|$yɹk~)'NiuT uzM<<0Atp14$K÷6]G!~+l~gY_o{ DgwZl{1j-Ha@ 2?=)te-]p5w!?#寿-خ?ӣnrG὆ 3]x-6t=<&)Lk :CuRߪtJ(Մϰ7>5wpEp.^PR?.ZMk$_~}ȏ@_g;{nrG὆ 3]x-vI|\Q3?M:>"u6/X*Yw&5L!:ūox[ ?n&xvK3w;4jI&wWǎ&G8CC\tr.: 7Gp)@hsrO$={7<3vǟ<@Zx@X!, >D| "* (Yel""FYTAd@L23s稷ZNէO*aïE+,ꟳ5"W=1 # 3pqתxfu]xP ۘꟳʕ_٦%Ǻ4b3&]?tk/,8+:V |k-ύ^Wn+/x[5pυ 9[`!=F{$1A|>Z5u:Ҋ|޸xqת9^ycwGgU/N}Qqno% ÿ nq}w<|ذG>5fYnpջgӊ;8q&l=ZXC|kUiD>\gminP O?p`{s;[ M{L!H>c }ܵ**"أ]3>\YmiZ5u|R(|}8 boAmφ}w<A5spoM|<ܑ%_l&˄7*/\twa%G"aؚC|kii<4uzygqi<gL!V_%j deQG;#j]Nw->LUȊ:NqfeYWHxp{ݼ䂋~$bx\*={$1A|>Z-xͩk ឯ;|=%[~)pEi<gL!V_%_56{e < >-Zpֵͩpe/Tⷷ wfݾpo#| a9yT,={$1A|>Z/xϫlk;|=%[~)pEi<gL!V_%o/C v$i;W :գHll㜭{V| dí.\ٳ%~`4o͞+^0?nN?0Kpi<gL!V_%4ޚxj/9k%w)UcaGgUW|LvԻ| m~?O'K |6'>} rhk1511)# 3pqתD>C>EE wUϟ Tg4b3&]ƍ6_[vܼ$޹:Km׷7>ڸ7'sK՜˿v}~_Wn+eC;¿!7=&{$1A|>Z7x38o{mN֢PIܯ؝C|kUU"xM7Ȏ~x%޺hC%;zF6*gEj>#+~zã%~j0w# eS˸4b3&]D8>-qeWi< $T9{}=v{$1A|>Zh.e)}aB\G7Z5 < ؔ_pEMO濴@[Fd}b}; ؕC|kUUH9?l-^;ʺ6^}qi<gL!V_%lx޻I|g%v bG_G ]|ݯqǃ@?bWi%pBɗ)ǦH?~FxoTpoί٭oVqh<gL!V_%4sފ>WNgyUY/PcwGgUW ۸cJ_Z^q_6m"j|~:U}ic6 _nx[rao^Bk9~O8٢aeCsa{'d4b3&]ƃw"69ƃx+Kx=Ϙ >CwwJFu=~an;l$j|o03%>ph%>?qCwwJFu`؛CѮ#W;z+{ᇥnuIUW ۘu$9#Jc }ܵ**a4llpW8x=-˾߷|BW#[|>ZAr a_st>/ïa߳Fmq˿%Ľ_NyC蕿Ĭp_%6P}]mTZ%[}>4b3&];`lܳ"!пKZ]ί灿=v{$1A|>Z[%3K![daȕp#jo{l}W3?[n mۢ&Ի?֯MڜZ d؇C|kUU6Nb}+XCѮ@ze9/};# 3pqתmo"JN,μ'G}þlMBܻya{ f畮[o%ƌSi^}RMYph<gL!V_%l@4|.ַq24*|}<>؝C|kUU6Vr;t 䀾7;羐~Xy+FIjpmNy{-nYZh/yW_iܛ&lj7Ƹt4b3&];`wVIݝ;D TzOp\cwGgUW ۘu쒋F _z?wQg9W 62-X@qݻ 7yj;;oybqf=֣{$1A|>ZYwwi<{,,~ҹ\ 4b3&ǗS@IDAT]1>o<9zHbGGGxo] H6?צZ kF/3>~<9hǭa6ap]^x=Ϙ >CwwJƬ;[vlY{vwPXHi}J^s*x=Ϙ >CwwJƼO>$9߽uĎ ۞HVke,>Sz޳Wf Zu W/' %}[(cwGgUW ۘ;Ʈ[}nL~vs`?Zb^xe8f};# 3pqתms)'p$vnT·9ϖF~>cdN y1v*<0K_jĿ~~f+k _wyx,n2^B}qi<gL!V_%lc4~[ic }ܵ**ar?3D~=;7|ק>~EcN<^-ƏU.UwK0'l+[; 0q,_ss$/\w\N{=v{$1A|>ZEx:ovu7ZrQpe8F};# 3pqתm,''g}/H2>W؝C|kUU6Zזȉ?Q? w8l!Æ8Wkμ˷i ;eSW$|ͿQO▛;룒[?XXW{=v{$1A|>ZV4-ϫNCwwJƢiZEDFL@+{~bPoAgw;:Gw8Wkάo7)˓z@zZyϟ?_N׿J?{LbGGCy;4a!Y?VbH|#%N8-q ~i.A{=v{$1A|>Zyxvq?Z~os/ñspi<gL!V_%lcYy/ĿsY;>Ɲv6_3e`G1CYw Op x=Ϙ >CwwJƲxX'^ƃ(}u"aؚC|kUU6o!-YVls3bĮ $.;eѴ3D^W$3Fb<}Uܚ=Nܛln{ktkc9GgUW X6w?j4||w|5ng_%S)5ccGgUW XV~w@|J|xaS|f_|8jei~?s3bpv:yr!\ĭ#ɟ5\3,hDz_Ç{tl.uC|kUU6՝ƃ8H+^_^q W)n-# 3pqתm,w. '_~!;;䇯-%?;;emCwwJƲxT{S\u܋eע:Ǧ4b3&]Ut}vx>N{EYjevVqy 1Do7L{)K_VyhV8qU/u x=Ϙ >CwwJVq`;.ڽ+*;JW=z=Z{lI!H>c }ܵ**a[[O`Y_95x}CvfViZNv\{5WUw|{z[ؒC|kUU6ս7_m7P@s7]1aUφ,-OJxE[pp|ew' q>{;?嗗koE-xM$M)9 6 c4GgUW XVw j4|xYw|U{*_MeǶ4b3&]W^s8_8lhΎ;:H޵`đk.%qFKlnz]υCG.:뢪6uTYꪫV]l S6Ey6$# 3pqתm,;qZ5W;*WǽxjS2c[GgUW .=v!; zaY~LZk/ZkǯG h}v_wK?;# 3pqתmlwիxXu7s Ԭ}_V4b3&]ܟy}%v5W+W,{ύEskpog[^ }qi<gL!V_%lcx?Zƃk_ګ^Uk_=6{$1A|>Z ^|L+;"s%qpg [r>vb%ߑ{׌"3)v9IOzd9v}G^m-{ƿ=6{$1A|>ZNvZ5W]w|UFE{lC!H>c }ܵ**a_>QC#¯apXd&IH^3=+]0W;Dž_}sT}w+?;# 3pqתmlwwUxR[:m,o&4b3&]1W_{U`w>dGOĹ'; ;?ᎇ`;mxyT9-g.ry%兛{X: mSˇ؝C|kUU6Nv\+;NWw-mh<gL!V_%lcjK ~ Â;8k]ysN Q`h"V}Stu·{ w>?6qyӯmNY.'WcwGgUW ۘ;Uj4|Lj;-<>h;# 3pqתmL}Kӟ+q2_X쇎ᎇX2ƃͽ;[_&l@7{% _0Ab?S눻؝C|kUU6Nv\+;NWw-mh<gL!V_%lcog~bPE_ǝZDjV3wrqw:[1_qvh^?} >4b3&]i<؎VFJnWX;kx=Ϙ >CwwJvw:uΜ9SbGGGq%wG<$څXwipG̓UF+$W횴NYWcwGgUW 4lST{}Wru+X,B5pMh<gL!V_%l#{Cˇ -e`|_lU-Z$ˇ Ru6XE]؛C|kUU6޿7]UW}ܵ**aqi<gL!V_%l#ս߷&%H~cR㾥E \(dq'|#A݁OX"Tk'[&&# 3pqתmĽ7>ƃ}wwJFcoGgUW ۈ{m[ؽ\¯^,:_ qKKwJz(@uS_{N[Uhmߗ<]yߩS˸ <Ÿǰ4b3&]ڼi<*kxRUW ۈ{M!H>c }ܵ**aq{]/uo$pW Κ>+״?Tpf;<E/;^F6\Tr?$.(W} Np/]_Rx=Ϙ >CwwJFPWYצ{ex]؛C|kUU6^ O yߺ+'>;>Jxaz?;(P3_~J87Ѳ%!hŒΈ 5Nrsqqתm={$1A|>ZM>M㏻V_%l#7# 3pqתmĽ6i,+nQϋ6~ky'-ޘ@ 3B.%/\%n d;Yuqi<gL!V_%l#yxͩU֢{x]؛C|kUU6^Ý묵8a4DG?i̒U%PuqsRheJGuNþ~yX^Pbksqqתm={$1A|>ZWPݧUxrUW ۈ{M!H>c }ܵ**aq=glYsϕb߯t!˧mġ~vKu4}mewj_3ۭ4Wݿ%OCR-\}W)j),# 3pqתmĽ7>ƃ}wwJFcoGgUW ۈ{1|bos<%qJjp;Śu_x_&CIH<}%^_Hww)U܋XC|kUU6^7b\+$;5 6=55# 3pqתmĽX>xݏI\9m:{} e9K?=V[m륾V_%l#7# 3pqתmĽXo6w;8ֻw}uoR4b3&]wC9T c|N_aiY>i9gCu VwގH|INj Ә[[ެ`sߜ_[x=Ϙ >CwwJFmi<8ZCRŬN1]{,C!H>c }ܵ**aqqGHyP˧Hjn}o-eJ!_=7 W~yoxkUU6{x=Ϙ >CwwJFmiZxp5R謯%';͖xǮ ;㎇ >-;ϿS76a; w-:;;s}'WΚw{T!C|kUU6nMƹRd|^}wߪ{c;chǯe?tMݎ!\}}ܵ**aq/ֻ;|wsφq=P00h~WѣFH@2a!KXa&w޷ÝۭdsYK+"N.ur\9k1# 3pqתmĽXoֻw>;ŸW*v9V; "H> !V_%l#jO=TY ۓ7&ė G-X$)߼):2쳏D4޲gElsm*"# 3pqתm;[?T9|=[q_~X>{mmN4b3&]w[luiɍӯرQv,x=|ƌΔ{1;~` *񃇟"k;O$!7}nQ${aoN.qi<gL!V_%l#jxȊ5xh/ya?ux{N!H>c }ܵ**aqVΖM5*ks;C='|wCWʖL:_p-+O|wc#Pϧ{QU'⏻@\C|]w[lZi4b3&]w[lzeW>3-o=O.>.?tswsΓ8q~i͛'5؜@㾹jlOw*#7&!V_%l#jx ֖xjzǽq{}0>4b3&]w[lFE\q_izl'w<ْ̪I]O_c}Zzg5O!+NWqx=Ϙ >CwwJFmdWo 'Zzg Α]<~c?h< /pl{=+5cx״GNFq5w+r{=ژi&lh7ĖFFY׎ph<gL!V_%l#jxi<;[~^!}qi<gL!V_%l#jqD8jhS7 %_~%.u츰bCwwJFmjhc }ܵ**aqV+ڟ+C^/9p*x=Ϙ >CwwJFmլoíRԫN8տa)490|lhdO91Œ/v&"/FGj%0});pi<gL!V_%l#jV4hCwwJFmլ'9QK&Hso|?jƛ̗Z/?LbY>m"CxηwY\x$rjx]}v׺ H]؛C|kUU6n띭fO!\kuO=gUW ۈ{M!H>c }ܵ**aqV랻P$Ξt8yk߸;|w2xW;O] ǻū\!1p=K+8NZkD >wwJFcoGgUW ۈw?RLI+5=wk?;# 3pqתm;[]CpGC¯C?W!:~MY>b졻G>Mvo`c}.">Z4b3&]w[l5o82{N!H>c }ܵ**aqV?ȃP6|fl<7:5~xu\aʾw<s.l͢7r8u}},>Z4b3&]w[l5/|Cv,Z^X w߫?;# 3pqתm;[=wCxɻ$nBEᎂE%-|xXPN#;Jc샵{nsEqqתm={$1A|>ZzgYxW={SkUU6{x=Ϙ >CwwJFmլ'zŃ>(o>"~?,'E%9i $CwwJFmլi<+`ힽ}ܵ**aqi<gL!V_%l#jensrw@dϣcM_=l͸pGzcC?˯^9fiwϥ]؛C|kUU6n띭Vv+F@}X}{U=v{$1A|>Zzgȡ=$vܝ _](yP]✡;F-yyu{} >Z4b3&]w[lVrʸo˸-WcwGgUW ۈwZ29]?ġw(={t1VʻuQ[wHW-6bp}}h5fϷlsEpqתm={$1A|>Zzg?$oD}#Xmp*x=Ϙ >CwwJFmZ_SKvx8O}F|ynaj$%$v"qܽxIp%oH<%6Ъ͞wYJUW ۈ{M!H>c }ܵ**aqVkU+I^@zα{U=v{$1A|>ZzgY[sw::'|Q w,t"bFG_ ǫ'+3fHbޙ9_ﻵBZݽ3/ǖ\}ܵ**aqi<gL!V_%l#jO!{Ekhq_9q} >4b3&]w[lVnpG?-__9|à3g|f*Aaɂ;[VYa^nuζ|ksMpqתm={$1A|>Zzg?%E}-Xup*x=Ϙ >CwwJFmZ֕S9]KdpGBp/!~䓟8FynX`تLÿ0ڪ;ƽ*O /,4b3&]w[lV񐽒긯˸.WcwGgUW ۈwZ?Cr Gy'/s80fx8QBc }ܵ**aqVkkVNV#xpg!NW6l#]ܽm{cnym^؋C|kUU6n띭.4W@jXe^}qi<gL!V_%l#jo|]NG&qҤI'O!qJ?bď{ğ|Z-%Y~p+[;x=Ϙ >CwwJFmZݟC"㾖s,:^}qi<gL!V_%l#j)\psᎇu__^8 %ÎaVw7ʽִCkb*l% ccGgUW ۈwZx^QZZ}re\w߫?;# 3pqתm;[]?Ԟf79Kp+~& +Vx7o,5jT5{;{kUU6{x=Ϙ >CwwJFm͟C 'n㾿s,k^}qi<gL!V_%l#jȩu$%H>|jՋ_^q{ qKHz%<#J_EMvqo qc}qqתm={$1A|>Zzg?%&.9qWcwGgUW ۈwZg F~bP_oa˄-\$qȯFH<3_x!Hs} }hfGwwJFcoGgUW ۈwZx^Yj2c{U=v{$1A|>ZzgNҟ:/q΅y+_0H;^;Yeӡa|_--«nV[Ilݛ=kUU6{x=Ϙ >CwwJFmZݟC"㾖s,:^}qi<gL!V_%l#jʩ >}w4b3&]w[lV;wֻZ~D+'w_xsOʒ;)zCr`h _{tyG rzl5N4RUW ۈ{M!H>c }ܵ**aqVk5+Hވ@Fα{U=v{$1A|>Zzgտ7܁0i$9GyDU\,q*}_;/eay=>^?$k$PVί UW ۈ{M!H>c }ܵ**aqV+?"S>s,p*x=Ϙ >CwwJFmȡ=$>|aU|e$^\}ǃlTCua.wwJFcoGgUW ۈwZYiZzgşCʐ)Pq_9q߸^}qi<gL!V_%l#je?G$~s+Go~[m<^햑XC׃·jdܛ:V_%l#7# 3pqתm;[l4W"α{N!H>c }ܵ**aqV+?@{?ջI{^,+qܡ+Iz:TɊ+P鲺7t2-> wwJFcoGgUW ۈwZYiwwJFcoGgUW ۈwZiZzgşCʐ)Pq_9q߸^}qi<gL!V_%l#j>9/:J]w#d;+ES$~`tu_(:*2_8ēxqkUU6{x=Ϙ >CwwJFmռi*p8Z~-~ 9R}.9>Z4b3&]w[l5o82{N!H>c }ܵ**aqV+Ѣp'I䣏>?^]ï@^werK\4yĮ$v wBL>[aÆI>垭C޿*E W?}Z{,K!H>c }ܵ**aqV+ʟX+S^Ը/9Xp*x=Ϙ >CwwJFm?}տQF.)q¯BllCïW}|.!=yЮr*;~O)o~bE+By91+# 3pqתm;[-o4cy㾌Xc{>xw 2~-{ᇥnv )UW ۈwZ:xKGH\mL;tVZoK/tt yG;'P YBXk)5S"1'# 3pqתm;[-oACv+{ܗ{4^}qG }ܵ**aqV+_8ZJK%N<[ Gr+; V8zB<. wCwwJFmՊKx=ƽy>WcwGgUW ۈwZQ;ﻓz/Ili%q*#]./ yo8K{lZ9GqD(jsW}%>4b3&]w[li<4bﬨq}^e؝C|kUU6n띭wp칟JْI={_p3JE7t)~lD{sW})>4b3&]w[liCwwJFmIþqġkӟV/ b7dw8}oBa3dUku|_},>4b3&]w[li<IJ4beyWُw+?;# 3pqתm;[-/-wBvݳ%, IDAT-q5:$nRu˃N.{xBdwߋ?;# 3pqתm;[-/,أlY^lU {N!H>c }ܵ**aqV?{lY^裏_x)#?e I{w<,Za59Zw? }G}V;JҬu؝C|kUU6n띭VɟCVC}^kWǑZ=}8>4b3&]w[lJ?EN=QG ٰs%xŰ{^ Qᎇq/XUs'.+ ];W^ye<#PiS*G J!H>gUW ۈwZ%Y|szֻJ~}-Zph<gL!V_%l#jdkěژ۟ S'+NxwxǪÂWk=MY؜@㾹u%+ؼs # 3pqתm;[m Y|r86}eW)z)E cGgUW ۈwZ~b]mʰޥ:!]u_<=çJZzgO!+\N9fu7[cc kP؝C|kUU6n띭V 7 ~S$=uΑEc }ܵ**aqV՟CVCs~n]o;# 3pqתm;[m Yf&$?|p[闽,]_^C∕G5%l6mk?Q-}ȳB>#FA&0и-qc }ܵ**aqVȟCV,C>eq~ٮ}^؅C|kUU6n띭6G+<$#s'Cְ|ƃs]+;(s1o?-񷖔 $P@㾶V+Yph<gL!V_%l#jxȊxDZѽ 4/UOK/Zx=Ϙ >CwwJFm*_vեw;Wȭf7%oBì_*57X+C;%OUՑ* Tmpoί٭oVqh<gL!V_%l#jiZzge8EVرK_v <*5ݿv \GwZ=+6<c }ܵ**aqVSZp~ġKwf7!ARo<'{6gmRaC~F˓Ko6,,&'Ni}VG| 5# 3pqתm;[Mizֺ7c)GgUW ۈ{޳zK _qy$jqO&OV9m x',[V= C0|!v?%cdGgUW ۈ{4mt4g;wqw/+Yph<gL!V_%l#6/#*Jgr΀[DZكƗl^fzPݷdoUxߩS" ĭa׀T*Ǩ4b3&]wo6εVPT1S@{} b_ox=Ϙ >CwwJFmo)O| ųdž9k KyMY>b 1GQu^7J_'-ǣl]smqqתm={$1A|>Zzk5*iZz;.A7J\eta^HKI!_zT&_8d#䥯}k߸paÆ_1w6~V_%l##}@IDATwUJH!DzM0 ^HJJ.EM Ҥ@.jH!!ɽɽ7wwgv<3sc=333O]m2ow3fL*]_gqqY~#~z)Ixҵ'Hf7Iݜ4?DڽDnpTpS'qC";xɧɌwNʮWyYumV߈ݖƒ zDgno%Fx/YDf}]Ab .&{uM-ݓy)w[w=f͒[IߗD/yVba%uY7KZ-/qmx޹k/~WpnxqumV߈M3pqY~#~)}̂Z-v#{{' j xnumV߈M3pqY~#ySxOkk Z&/;:۬[ wכƒ|Dg no%Fy}clx'^ 4Gb+J-o/[O(7޸L_zeݻw/9e/Q38۬[ wכƒ|Dg no%FySx疕(<;:۬[ wכƒ|Dg no%Fys eö5߮ămP/oIOrT7L6rEqK[ooKl֝oF9h3H۬[ wכƒ|Dg no%FySx疕(<;:۬[ wכƒ|Dg no%FܣyreGm.:hvV.[VYsoz;_Iꏿ1XG7jB ;:È͊qw)<8@t:6+Vo=7h^Y]ƒ㏻͊qw)<8@t:6+VoĽ:'zBV'Js6V.+naΚ?x<ϐcdc0no%F]o G-)͊qΛCuNyYƒH㏻͊qw)<8@t:6+VoĽwss#%vAD|4kw?k7~(v\:EY".sL ī$qym3r۬[ wכƒ|Dg no%F{Sx(G)<;:۬[ wכƒ|Dg no%F܍̙3N]]I$nĮj +7_ٕjO:cxwi0jÿ 1CFw; ky 緼 :6+Vozљ۬[ wMFAwx]fJ\[ :Sww+7~{ 3>+qb?#w=H-?ЃY]ڝvԍpR#od}t] ᒉM0b3d~ވo;eQ<<2)/C\[ :Sww+7Nm+.͝uuez.nc 2#]fJSx;ϲƒ'N3pqY~cg_gm~It%Oʌ}ɝh~ ڟizț{n;#GhĽ u'"WbeVF\[ :Sww+7N|J6 #뎖>Z&/:\[ :Sww+7n*s gI\:iSF_p pI8O/0H{ kNM|7=6]"cNpw(<8@t:6+VoxSx;ﴳQx^wtqqY~#7#]fJO4Q;`?6rESdefm$t z7yuok\|m۶z uǷ͇Љ#fzIg no%)I/>HM;Z〻ɋ?;#]fJSx;߲ƒHɋ?;#]fJouDg˳X-ywoY4\Vl$/د6^}u]͂܆͏㏻N3pqY~cγe;"誷i㏻N3pqY~ch$vS~w&- -N IvM{_d>3իĬ܄{Vty㏻N3pqY~chίf;2j/-4ᯡ'}ZSxh)x& ͊CspRm3.VxXx͹ߚ+<^qo~ M<^wH㏻N3pqY~chίf;2j/-4ᯡ'ZiL)#JcJQF)AiumVXf- ,K\0s3C[=n%[ԅH][Yi}ɂn]'uzu6(KYͼd60Ið>Sƶk/-|զzљ۬[ (Λg;EyUfInkşZ(]fJyw? ]-q#ٷQ7|ҡn }ly+O#wld2m'5q٤ziϟhs}/u'UU )wƒ|Dg no%ƼSx;_ƒHuGW/~v%%I(Fw׌ƒ|Dg no%Ƽr8hn%Jjy/;;y\ `-ݽf#ߟ;={LSӓWwE_wםƒ|Dg no%ƼSx;_ƒHuGW/~v%%I(Fw׌ƒ|Dg no%Ƣ_}%x/OK$.h2}ۢ_+󶚾 wl:sK1Zxh;qrG-mO2JOZ\[ :Sww+7͝ƒltGh;gǽz4? }Qxp=oqLumVX4;C}ϟ{_Ki6\|[ `eVӇ\ {W&>(OZr^^_apw(<8@t:6+Vo,;'(<\^wt5ώ{Vi5zљ۬[ (~rusGg?7Gv=:+znr왵b^c>#?;v,i4w\YޫW'(;Iyw_ҥ_%\[ :Sww+7ŝƒyl(<}yYQuzљ۬[ ({M5Qb;lYwS«F). 8EH]^?aduI{̘%>I7򺓴Kាp/֣\[ :Sww+7ŝƒyl(<}yYQuzљ۬[ 1_c.֕uҟtZ}.IwPݛ[*'3%.^|aӺNZ.i{ǿOZRxp=oqLumVߘww ~KQQxPy>s ;#]fJyuo{u(~ݒpR~q ?c?!Ƿ1.mUu'puqw)<8@t:6+Vo̫;h(]_-]i G-)͊N<)Z6 6y>y}auF wםƒ|Dg no%ƬϞ=[[o%qܛoJ{E~u8UbH{oy 8k_M%n>Ԍv'6lEhH6+;)=v㏻N3pqY~cV)< ;g@!C +!93\[ :Sww+7f!O{_8t%Rđm9cEG9 8k{M2$6O2i|{&q$}םg]wDuzљ۬[ 1+Qx!y]w G-)͊~Gb6}-uW͚~p|ݥkx|a lw :\[ :Sww+7f͝ƒ5w)<Emeέ=/[Iw9/#]fJYu7{>joͯ^\R6Nn\/XٸNp/ |Y?-w^k+ָ4;5>o㏻N3pqY~cV)<ePz)o㏻N3pqY~cV)<ePz)';KtX[ٸܫs^V%bs+3LܼXoֲh_E_㏻N3pqY~;2k:CuCuNyYK>/Ni'iɖwׇƒ|Dg no%F- &=?8}$vZ_l\ ݗ6-/~K$YQB>6/!w^oTySv5u]w G-)͊)T:{2n0x1s|wҥ~ݩj'\ wA_wםƒ|Dg no%ƴ)<O.(RxHW/izy̓Sxp=oqLumV-O60'|akw `=w=l`>tc_;UL@+;Sxp=oqLumV˝CqKKqOFC2zz-:#N3pqY~c/<.ۚ qio8Pniܓuo9/92x' Z˒NkyC_ _wםƒ|Dg no%ƴ)<O.(RxH3~Ok/:#N3pqY~cm,j?,;#]fJi~,Oc$v[k'lږ]hr3:' .8B'N0rwٟu@Zuqz_g,qw)<8@t:6+VoL˝CupuNI{Ң? &kZI__gqw)<8@t:6+VoLG'NyYf7:c{0 r3)q!feƬ̊zHN( :N3pqY~cѼZ$KCᡴҤ_ﵟO3b\[ :Sww+7&>|-Lb}"9ឮ>,.Apĉ'JƏ@R~xYS]w G-)͊roܸVV*X~{ }M^kuzљ۬[ 1 dG$^{5?hĮ;K䦼}zde6xrEe~f^rwNU%!v- 3;#]fJq)<$3N\'ܣ_Cy<>+_gqw)<8@t:6+Vo~쑇ɺw'K{J]%ٻY7n=T'puNI{O{O:ha-;6]n3[˂'|<J^v"$ >;#]fJ)<;\Z︷&o9xnz{BˇΈSxp=oqLumVX9Pvh«K|`]\~C4beYx; pܺMnӬ&~B͓%6k~ݢwǞnj#?^EY{]w G-)͊+SxHw<N׷qoMjK{Jiuf;#]fJպ?8~ٱ}_}Z[|mZQ"7.֣%[߅Y<Ƽ~Ѥ}O*Z\[ :Sww+7VN!q8J^I()|?ܒjlۺۤ.#]fJպSxHg\NǵRWJq {m/ָr\[ :Sww+7V.Ȏ5P/}+vo?_,wƛ{~}XF 28)>{Utmn=X'#bkV+P}ٮ}zwWƒ|Dg no%j)<3.lS^K ^BO$^ uzљ۬[ ~+١#ۺ` 0 ~w-oݬ[;sI<%rn;Dz7֎*Tduzљ۬[ ;tǣX n%tbK ~ơ뽟7 :cN3pqY~ck>W'nw:fkyVggx_qz7wdոgK$ _}Dh>Z/WrmG3pqY~ckC 0?YɂhOxz';Yי\[ :Sww+7Ȏt; ,[ ]^?n,$^1#ͯ|QGY^I"7Z{?X2:Rxp=oqLumVؚ;?OV;ZSx3Nu;#]fJ&LDx[> JZܤ#,,R"~0Ei>0tN|C/;OM5uoʝ'_kpKXlmb:zљ۬[ ?ƒJY$㸧ZmJv=gQŒYwבƒ|Dg no%҉t|߃HRW/0loy,xpW&tNǵRWJ|Sk m/ TVIy=ZϤ}I&ܒ $E3pqYD + xnImE!)h>+OZpw(<8@t:6+V"ҋ/HǻﺓKwnx˛[ag nղ_x--\] G-)͊)f}Vwuzљ۬[ ?ƒJY^Uǫ:֊O!}6ǸWpw(<8@t:6+V"8i`96m헼MdiI:#t^oztU'EV[nDrY_wםƒ|Dg no%ҍ{ yX݊Oᡶ}6ZƿVxQxp=oqLumVDq̗8cI4%Uo5 `q/[l~}g-$OYv?O.0z[_[_`qw(<8@t:6+V"xeH7NNbov WuwwVw̯׿+OZpwS # ҙ۬[qB?||XWW'qܛj^y޴۷ݻ{mf4rG%w#+UqO3FWqdo>|ozKOy;&H Y?;#]fJѸ`y{Y_uG$?ƍjZmF3pqYo=KV %nNG^`riy`h?o/o^|j.-O8]IeNo 1{@ty{eOB$yY_wםƒ|Dg no%G }h =Ϭ/#ןCmlm~nuzљ۬[qɒ%I-q J\];HhwLU.w0>;Sxp=oqLumVDH᡼O,o7+HO!>-OJ2Z?^\[ :Sww+-y1`=xþoG 0q{;_dǵ^씅U _5U+(g_Dgo?:G>;Sxp=oqLumVDH!WN,k7+HO>[R}Iypw=(<8@t:6+V"Zgo(qƅy|'Ć׮'K7i_<vI{)i ߗ~;Sxp=oqLumVDH!Wu筽?뎀/ 8>zn[uzљ۬[9sdmZb#O2{ŧwOW9mU^N퍯 zjKt_7u%KP Kyhuqw)<8@t:6+VH:,wn>_\wg]-}|\[ :Sww+Q]QˊyGbKnluODؕ}]|DWW w_ҥ_w>Kv调/c_㏻N3pqYE 9e}-`Y.Ƿ?3ϖK̓A3pqY7]++\Elڱ}Paw{!]foyVKxDGO=(q7XguG]w G-)͊()<ۣZ`ysJzqOZ4ZZv3fLcD8I1r'd\[ :Sww+Q:N2Y}%vP̭mksj5#]fJ^=%~ç;usw :Cͪ?e PvIuG޸>XguG]w G-)͊p#ףh- 9F}>GKv}- |!ٙ7sy%6$#]f ޼yԟz)k+ϛXϒ8dǾ{ObR7ZI_gqqY}/^DR~vG,3fyfxl+wF?YPg6|;#]f ͟ƒ q%)<[;#]f ԩԿ:$q!x4L5{ty [_ n_gHqqY}~xznVi/٭- > \i3%&٬:#N3pqYC`G>,l?{ܿns/ >>{:cN3pqYCW`=̗~nh>1}jk_%K$Hdg :ͪ_?iB$ºli7O5^~>`fe ]w G-)?;ŒZ`ajqB㞖?~Bcۜqw)<8@t:6+FbӍ֖;OaoW|a߬);`6V. %:6kW=iy*>;Sxp=oqLumV;#ˆ 9 " 4;#]f ?Q⢕OZ'<+Xb.jz61]fͪw@AyBguzљ۬Sx3! #U C?_]y G-)sslH]`~g޺J }nbV/jzR9]fͺo/?&7zcY4uFwםƒ|Dg nO΀0c/:*l)<ΏfI|W 8o Uc^.TRLeƽ 3u>}{}rqֹ, k+dٌqF#%.##]fͻ?;(Y\.u ~q9 G-)tJ7@IDAT͚wV Oe˾s$UghM6Q7o(f@'N\2%Y-̺{'+xxv`eCuμEjIZ{_gpw)<8@t:6k)<ؑ$FX璧uqSxНE͞󜼎 Qxp=oqLum֢I?wJ\}g_w=t=aUYuHm/Xw{#/M5MÔ'֛|^ 17Eyͺskk2.zљ۬E`GX@^.y.yZwʋ?yRE9۸#]f-N8V- 3}h[ɂ>2(@'N\2%Y-,'"NbwGz3hq 4_Gwםƒ|Dg n͟ƒYb9\{Y| wQ)?yE;ɋ?HQxp=oqLum֢>uyJ(4p5˝H\_0k-d2⟌c^p*Eonѓ?-P}M,ފ>nN. qw)<8@t:6kQ)<%V#P js]wOAw>%{Qsmw+a"#]f-ێ{v0`;=6;Mh`yCuGh ?\$#XCEyme~1:CN3pqYO(]U󜳰P4 )/ًro[ )<8@t:6kO>gT]nkvO-ӱh.s@weRտ_ns<=>_,ٌ'YmoR\[ :Sww5H,ƺkc}] 9_FX;N\[ :Sww5?iv)" IuF ww7_xW)wM=wM'XCSQ$}ٴ]|/i7#]fͫ?;8Ecc}(ZyN ww)<8@t:6keGM]moJb/>:6k&_|6L,oH|by|ⴏݧOi}6m[uzљ۬y`GX@/jIs[ԭw)g :N3pqYO1 _aD'Sx?7eVsl熻;\[ :Sww5+G+,qG3$ͽIk_&@/"ax\w%RͿyby*qᎽ:L;=y̒ȁ{(?s!-r+Sxp=oqLum֬ἳd@⪽힚 YEXBNZE ^kɊ?^] ?8KCփ'l)fG],OM >sr;#]f͊?;"DEaDܓPG)<ğ !m\qwGƒ|Dg nfߖ]g%p?Yma̷c;dJb.rߝ_gpqY_?y<ՆI M}7xwu7n>yg%:#N3pqYO 1M_iWK߸עWEP)bY;)q焻B3pqYŵW.^xWgᨵH{JvS :#&󉹶wtןkIĬzyH Sxp=oqLum֬Sx#DLC 4,{ ՟Cs)O=f<'OqwWƒ|Dg nfkrĎ6{Z[O=V(qo4+6 :#[␕#α4ƬϦd39:N3pqYO 1M/Ҵ-7t,t ϱ,fyN']U G-)͚5Rvms$vX_獛/wW.U @{umVD&rmrg#,Q*1kﳩ< wSxp=oqLum֬Sx#CLS 4u[m|ln[b٨[Q]w G-)ͪOa G.`dž`ٖr:?i{!|[Vo'wADiᏗR Wb]w G-)ͪOa G.p>fW9FD4"ܤ>a^uzљ۬iSx¥#]|-ߗww WƒQVZ9EJyJRxp=oqLum֤<`{.q$l+o ?=c!{2$nduqqY7s!w>6K/p _ nX;IFͺm3 pw)<8@t:6kl@y?m^R4_3.y>Ƴ^#]fM?m;{ D[O:H7g=cث鿟G.tGumV{?{WtXl>鰑]_WZ?{̹0VL}6N:N3pqY`eG.(i .o)4Iu?|mN]7.fuqqY&6n~~D^o͓\e-=k~ݺVIk3\[ :Sww5m Vڍ\[7pqY&Rxp=J/]A G-)͚mw&ۙg<74[{[ז #?:6+VY3w;[68C۰Zi϶HG-@<5qw)<8@t:6kp=|-nno%sZidk<#]fMbv鰶6n6~1k|i~[v9㏻͊o2{ၗd=zD H; >;#]fM˟ƒ.(k)<[-+[u G-)͚.)>dN+u)FiwY^#?:6+V"ZlP6loH>i'^uzљ۬iSx¥#]|-ߗww xees#vvZ.m;Vv`ԨQ;hvuww5i>2ZW ;gўv޵MX/AČRXP~f.Vtw%;\|'Dv/ْIYتҤ./#]fMڟƒ-(-\KZmj:瑷#ƟZH]fMޕ.9 s)gLU}1_xb_oKl߾믹Z9g)'%N8fH<^?Rxp=oqLum֤)i?2qKR9(ypwGƒ|Dg n&K \bnl+ễoqww+Q[o~bɯhto~u$ă0xI<i?2qKR9(ypwGƒ|Dg n?oߓ$>K%+ 8+#]fm͟ƒJ'rk_Tឬg*Vz} ]oQ ww$)<8@t:6kktrOOZGW?2e}N#ܖ,O:qdxԋf4Ɇ*qW-ϯ{)w&ϚM[KB3pqY[`҉\ZmW+z'7)_oo^_L<.rg髛ܖ,O:qdx3X6$9jӖI{'v\#񨣎X}VV&5S-1.#]fOJ%H3joGKf}ܓq (Z:|mCvm%V>KK[i:Sxp=oqLumj)8r-*$Lzљ۬|_e [t 6Ž6?]4],k;+P?]vӧ (Fh<'Z/UwWƒ|Dg nO Hǵ^V*pO3joGm} %uk9IO?pw](<8@t:6kK>H$v2_`W'&$@B1?&\^#`_#`=slyX{7t3W\sDnhyLRIwWƒ|Dg n`eҍ\[w+ 8V+J%d=<'Ow׍ƒ|Dg nZnI}QoN}:J&Y.QŒYd\ڶW.Ow$qo!̳ΑM2w2r;β`GJٳ8Ϭ/Ƹt\[ :St)<w.%8FbɮzPIgqnwWƒ|Dg }ْh]oc|1Gh^i|YmR\$%պ ֶ=E#4֛tz ~ ɇ=L<Q~y3(,#ݏ;?fZt?J^I(O׷eZ9]GJ ]}'~Yف7~heKK6Dn YϨU,qO1n/ǕmΟ" ؽE%6'qZߔ8qkS&13.#]ǝƒЉ#yOAfB#yLuzљN!]߸G=J _%-\hTw ɎzV#=yI[G+( QNXYdnUw{T*;;]c> 89.i_zM*1:ww+7Fvq(g՝RQxp=oqLܓq㏥qI$.:Ld/,*{Td?Yj{ýZt?Juox w~F=>x+E͕v!9~]w G-){2qKQ_W<0:6+VoN!q<3ǨQxp=oqLܣtq;^8$vjgt&0MlAMom/w0TbuW0f4kF7ǿF.O2b7պ#VNVM­O>+U?m`Vඤ%YR_KL3pN![VD4+[_gDqqY~c3q+#6{tp߄66-$u%-o=!jp֭ͯU0sKj+O\i$Q$ƥMZw3hFv(9p]w G-){mj:-/:#͊SxHf8L1j/b\[ :Sxuo_8H1]wu{de'D-Mh^IҢ{uNiZM}<^'m>P~/}Luzљs-+[%}"畗_gpqY~cѼZwWƒ|Dg [;m,.9hv]̷_߄.$Vܬ}ȉI9hUVi<%k$^di7(9[x'/rrK3ywWƒ|Dg ϝCo9;Ҷ + Q>㏻͊vo\]NƮ.O.g̘!g~xLAuzљWN:h^yumVߘ;yfyݕzљw_tޖJ~\D|45:۬[ їͯXi>&L׮Kg$^x~ySxp=oqL˻Sx(G}'?m^R}Iy|SxpmL+7zSxp=oqL܍~*wqx|]mH7:KL QR[~pIuqqY~ocč5J?HZ+ϳݹdem]P3ww(<8@t@t[V}"_gdpqY~cʏgUwטƒ|Dg hez iϚqsr̂n>!Jh7 C?:6+VoL'[Peb>)@I>][>\]{ ~~ŏ+O3Bw3ﴳ}"]fJiSx(?gIQ][ G-)qɝ^o͒xf,.Ĥn>!Jj?#?:6+Voxn) M2y擕@9:N3p7tVV_'Z/y!umV˝Cqpɇ.]ǬЭv0;no%Ƭ7>a>q n;B)[Jmpw)<8@t@tv֬j;h_Gww+7fŝCXbw=[-E(< 3Bwa~%v١@,oVs5w=\)¬\o㏻͊Y47|C<ʬBYqoՌU}Yʄ)eսQkYWka-,1c Yޯ_g}ahYݑzљSxЙgY˚Ѭ9?%[_(i ?JKJB=3ࡹo(^vXv/Ϛ"Uq\&epR|q*RX%Kɳ]v57~y=titӍ謗FhYݑzљSxЙgY˚Ѭ9?%[_(i ?JKJB=3yw_d͚5Kb]]8W_k/I\g=Fu}"m㞶p/֣%[]WZYwox|s??$:h[j/qv̼yJH3NAg=kOD[i$㸧ZmW+zYwxӛ}H!iќ3yw?믓Gi0oG&_xqȕw#˵o~BV~Ӓ_sJz-ܓѼZ;qh76<}GnρU|tEbRY?!RI19)V5U )FX 5^6Mi-xW.S??Cy9\żg +;W0{ƒ|Dg ݝƒμ{֬3U,qO1n/ǕmSxm<ٺ:W,_CVA3Esֿ ?FÇIj~&Ov^CE֬ؽ6qZuqqY~cg\|<5A` sr[|ܿ< .f<3/#E3NAg-kNDW_`qV'%Sx6~Oh܊×4(=-gLs_6C[&i YOE֬ؽ6qyHQd|Y’d b`ȩ?1z3SQϜAOOEO3` *AI6?*{̛WOUMW;w 2۹/)! IV-|5=w1̵uf\S<<a;(n^}!73%MypcP+\zpک >QOE?D͹}I93)`%}]xnH\Ygd6Txy$^3r;vO\u!Wj_%bFw3ݼqqse}U?8pڨ,3m4ٷo߀P- C/x\>c Pܰd~P*7N~-yGD:wg\.HؑvqoܹWg=tl;ڷs^ag^se[r H bg w쌟zB4E_0+,oDqqk^re}UQ;d#g p}szԱ2׸n xYS vv /H½USvڙ[יz5pG5L;C x3n5Wqsn Y+x0;n-#5јӁq>yK=83seA p^_0+s{oǜIyʹ C[h̸3usme<<aq+^se!;=v /H&⺾]dcjNŕA'`k Yq-;Cz͕QH ƫqCpSꔎ?%퉹ƽrbva7qGJ璼y$m}uiWT~]&#&!W</5qcn Ykx0;~-{]xE6v1At\p_f 2e ə3g5KG Hv:-f7'׏\[ťnOn Y˞gwߛLz ]/y> Z̖u÷e<<av@BB4. vz p^_0+s;fQܼe>[!-=%/>'⿉l"}8rKk3ҐkgNg.;nn Yk7)$F/$y5H^w@ɝjLAw$m}d: 2yd0A lCg; ^sm!n;=v /H'޲e}-xȖȒv`lط ' "9f(GOQI/(}^Xtg';"k h7uihwmh}_"/{UxC˕E0nph߈ yh^PƢRΰmL8w52k v@qG8H/Dcaow 2x0;`r[mcEvdL;w٩QwOwNޣzRz~|;Cz(۪ȳ_p^_0+ν=sC+89|W/ڝ|4]e:ӥٱOw xy$^3;vE{B4vzp^_0+%We4gLr v:([d<KI%$[6#ޠ[=q(nE{]#\㾩w8g=ZL`K'9ߑ$_yM;ٺTs*]#&!x35q7cn Ykx0;~-[x"`|1ƽ@>`7;Zt{~oO;@BAgQ!n Y4e3!9o4`oٶT*]#&!mx35i l_#n Y4<_-9!`?&6g^|$u*Ɛפ- atx]+#o6NƽyE3,gmdB3MM_.GAx 6<I󚴅h/p]xA¬Lw̎lm{ۜxYNd 7$eaI[]#q 1FqW;K*AO&wpG5_ei˽#&!-xlg$kRo'w 2x0;β[ﳅ YL;a+uNפ.l~]#s/{?}$cϔh"JرcיAz. 2k vm<,v@B&}!jN+ feҹ#`ve7l ځ I0A [gϞM|-\|akD灻-w;܅W$ʤs($o5$_Kpեo:k&MH*֙Aۛ+]Id0A [xNΨCx/ƽw;܅W$ʤsGxorj;xP$\3tqfLY;oZx$+ LVw-w;܅W$Jpg3J˟J|7Hދ~/IEJ֙4-Mܽ<<a#? )R5f* Q fq [ ܙ aGRZNj–"m& bg޴Q}:{p=?yΧ^W,t. aVYު7W9~f1}i6o#q'WUM_^'DkQr ]#&!; ".Qg+`v? "v$ū}(li-[Xf vnN>hdoV:Ж񎇶7Hu;~J宂w;܅W$Jp7[*3zRo/佇2 VI/.C$y%Ja̰.,2k vn<פ.Sџ w}lX?/TN,Vcۋ ySO;dj/NSANZ,=j{Jhk#HgRDv /HI?yÊm~̿[~֨u)1H\ OS̈3f]Jd0A SxHݿI]a.ͱN SQџK*w8X6vb)ߒwE֑܀̧/ Wp@Kaɥr3 "= v{w;܅W$Jpg%ub(b^! xOgޱcGѧufD93.w)2k vnn\x[in/&f.uqKUߣK dZiyCD:9Nw;܅W$Jp7[*3:\|°SQJ:m]<<a#? )R5f* Q fq [ DҵDA%p}@r󗑬3 ΁+Dv: p^_0+,oDy W?-Z̀R:ub p H bgD xEUܣ"%>6gFxekz~ۅ_R ) b^RRBa{wv:Q,P G4 ʂ{t,X /n/=rZׯƍS,ufF s.#AAx ; `p䢩p gF xekz| LxyL;?@ 1cx瑬ס.I|'Qz>r]YvK{D34#Y&x <IN4 Eo yUאT?3U;3#2/d0A x cV=Sbіhy~I)zzYw/BxH%.Aq9LۉCr<&>Saӧ~Cr$mHWs~3g<<3,qGM43{J{T$`pK\۱xMja7=M!CR:Zg]F#&!^nݍt7Y*:- L"Z^{3ij&~gQþ?'o ٸ@no'jRB 3d>nDnd^^ߋ|pկUK*r-&V{T4S_;v$]ZgGCe<<a^x/XfȘI3U/1Y I2m^AA7a~4CjCc};|Jw;-oJ `A\pE֟])R5f* HVmo9*/cUlT6/WOLHHep<<a~# *{қzYw72f g DR<T_F!Sb9^Ng'}u$wo8 Gт[+Dv: p^_0+,oDsr*뭿(g9ցwԖy5ycDެQ?GL7;Hό˼xy$^32CQ~%,N>Lt_%}lх_wHGzX+ugqGM43{J{0ż"2P2ϑSgJg:Lkw2k v_<,p ^惿^nݍt7Y*:8ek)lmB!j1 br?E jJ t ~xEdp <{Ҕ 4 >AE\ #+{E)oJG~י)+#10p! H bgHW/EHo>fȘI3U/FG!]mvxȶL;oevg8imyłN+ f%zp,g@ZLxjJG^̔xy$^3#_ :1f8^]%bV7p$d<<aӧM#1 N7/#}ym(_=g ?{Rோlz\M8}pOEWN+y_~/K˛f?t5v7u< xy$^;x7u, W [ V܃qGE23;xK09" H bv\piW_!U$_iUjDNXii=i-ZwW4F2m6H"M0eyb❳]a:," H bv `7,Mw|!=இ_T=Z5T"١Jd0A^y kYoNmadw K7,t8. aVYު7W]/F3VHZ7g[jԥygp]%EAx @,o7oXћzzY/Bz]WV/hˁ{94ޣ{Q:>HW/EHo>fȘI3U/ѫ,+%m$[7g<|[&=p]%sEAx @ g^ K*r-LĢ)p j W˴6SU2Wd0A #A§;HQ#;I`A"n,79 pO`ᴶ/| "yp]%92k ff8"Njo} F܃qGE23;KDwtxE8]\W\xy$^;9xH߳8!W/7FFo:ew=\Z-Ԛʿd&2qQ7<ġDyp]%CBAx D@A/ߠrP;pόWԥ?j쁻?NJ.{z>sU<&q]%sFAx Dl2|uQ3߯,'izwY]#mܗ<<a<R^'G v{ w;܅W$Jp7[X4:d<<a{7cIҭ6]mdٺ䤅$^mђܚORj?b>́OP&fl <1?7QvmӅytk/UJ?# or͚53u܅<<a<R^'G v{ w;܅W$Jp7[X4:d<<a_};|`2p8Rs5)}ztuBHY Pfd3ya/ B mZLeq%\|{-%9v= }ix J#&H!C0nRK=!gKrnp]xA¬wUo*TBzt\W\k 8oִiӦ}ZkC_j%U/$+9r\ߋY5s[тbZs=i+p ofn Y fyT%d?*xڨ.O^l(oggW44p]%AAx $@!l]po?. aVYު7?*!=:d#e66l@?n_gK6|e.%ʮ$8izwY]#k܋@D"jh#˧:䓆S$'~;HIGҮ/B OA!apu]!!vv /HnMTҲ*xؖIS:ASw;`=גrbW IFUrap]xA¬wUo1n^_QRIVZ@rM$>_zy%$^W3nd CBsQMуV:J6ݛ௓mpwgc"'j<뵤^WALB1A/}Yh pdw?k-ށ Z !?>!lN⪃.w;܅W$Jp7[*3:%*'&_kh*>21)'tɤ}Jqd0Ax 𐬩 9Y-g.gEMb6=sfQֈ? d^<<%}LlF.%$>df$JO7݃#3B$&p]xA¬wUo1n^ۣ Ś I6ivC/-p_2:v*xP$\OA! r2?jp.m{̢Qo Q2*7=5ɆݝPo:![iXOwytt偻>~,_{/WD' Qg ?m7?SzfΖa?͡2IrB$4=xuMݛ௓mpwgc"MPod.<Ť^WQALBӓ:Aν\M$Y 70uB0;=v /Hn U"ftp7͋iŋi{,ۗܚIrT$4=xMM{у6 yyn Y fyLG{ U:$Hy띷osɂR?A0}B}(7;?]Rv* l9!:ܬsvp^_0+,oDn[7mg9,w>5mv$_:㢽ӖڦEdZuU@!,k Y,*p^_0+,o՛-<J]1~xK#nw?\ <|7;:3FN|k_B_*g'YkSOo,}:!X:ܬs vp^_0+,oDn[l&-{*H=?ˡ;PRxL3$+UQQ;2Axȑi0l ^ֺw]v+ f%zxz,"5 |˕Þ,-;ԗzw4Eam| PJ~mSࣴ{B/\+TȑZ -!LZYƑ:NY(L"w_^?#%7 / B mZL׎8pOXb_؋,]LΛ$\xdɕ sʖ^˝Rn/p]xA¬wUog#<ڸ*:1_Ö}^cyg\*f$;޲ $ºVny~@v* / oV4P~fu~}(qsC_;dۙ[ym;ZFTvh#;~HÞ ~~A;B; Ch %˜>)]YF Z~k)QhߦNv8;7[jԊx8*zV=[ۛk;= v q_n+wKB,eܯCDcn/zB^ HxP(* EEa &&|G^qq; ~\'M$^5I6<Τ(vL;bGE@~/BadjEyH QuI\0V_~!|"A"D!57"# HnCL>?8|PP==ڟQUߞқ8hө^I|<`+97tIanv yfj_,:;W^{:*? J;N~'zgKQ2~vA{wJY>ҟ'RղI)܎ Vdoc Htv"3a?S˗?LݛwvaMSE$+9:)[RB&"Cr~{ٸ7?&lW8O_օdݐh}i=l (+%U8dYF 6#_Dkwn +W3k9;&']Q_/r~SVVm vN^.bqt;A9h_ߠ拦_vL\|_Z/nUa!pm o!  Q/x*YGA汍!Qwe ;exhGN6x'C.ϲ;QK;[yb'ErGnw!' a$8{DA-RIllP=?I1lrw"QEg?t2v70ώt)0yӹްۿjVq,vj?l OO W)vv؍ܖonǃx Xmǿ_w[)܎N˚x'Ef<.?k'܎^ߨy]KN<85r:ڝoٴ;n`}ޙ=qu8:X }fhG_9WFB{"d/CvZulsGB\y{Ό $Sm߅u dZp;}x>M?o6kw ֕3/R۞v[Ϸ..a>yyVRƓeܿ.L:*ku?׎WB&/UVCSZJ " |ˋA<mTxWxH|T\SxF/wG~D&*=XEa |L ndl.q‰7N9W9;DTHINz~/$od"ߩ~ٗ)]ͮHד>"o$\B"o#xōQ¦M_*>M|G+ +O=N.ؑ8݇ly7BǞz* A {>_zNMlBy@_۟'Y!:EO]XLRv> ?~hz˳[ƣOs'dpY<~߷8چ\x!XY|9lGN(vyCh?]g;2~V\ryeB_qx`;Jڇ/]{Ai2_β~ywиg!vR,wvR׸-}KOwpk1{`0̴ݢ?~?Q!=[*%{o;\U4%pvR̹|*M3H25..|]ԑ~1?.װotޮ2˱ҨTt%Nv#Y;$`e%"缯ö*ݼxM8f6AxC"@@/DA\P #w/ ̧"CzN<#?;"Ը]WiPmj?q sMN?[wb CFB|%˩G1CǴ$}ޒ0g=jpꎼϦpd:|9)}Ĉs'p&pv(#$)W0'H{`D?ϳ|CR/t!ݢ"3KZsvxwPTk{H/H?hʙ'KOH>:={ߧ{Gʡ|'׸>ҷߞ=S *_|6dH–_ĎNsS1>O7"KQGR yNO|gj)jO:}QTӺטּg^oKQA=S4}D9~/>{"Ͻ2&E-懶rV`lϹ./ws%&~!rџIN/Ե=6J袋HBЅQΐS5$}8<®<`_A<{)][QM bg͙p}8،?|uH]vLeKy_}ťԾ W|%eG{w <\"tU~gTocI~| ߙ?yuoAN>;bL3gK: 9b6,Rx>4|Ws\g (7ci?3oo. U)PH܁eΌmv9y8SƼ)w8.8-%ڌd3N"clAJXz<}%e:Z!8o9+/e{*;ђ =|'ƭT&mbڱ}V Q)Y}V?Ձ%Ǿ,3o\љ,v #,ᑺdwcin?a%6# |xHE" Hx +q!).Ą_(";jPzyp1ǓG2=Qy@|g֑|gkEI믡CK"yYgVz[)yg_#]w$w}LM,ӹӵ:}/T];SRD}oPxl(;VB- _YƿOnu{g$~a*:cv$WU_>P*u=}Io—q9*?"ϼ[U_YCuq>Jn_v }uHnZHːH^r%$~y3q21 _0φȴ޴ m8j:߹co݃:(}QKuW쑷>wi'Q4#fj*cIwn4w _τi+JF>|oո3I=Qk^Rʿر[5Zf߼Cg24U9$Oo 6uzŴHؖ)e2ܺ[}+^IxKV")~TljzQ6KH~psy"y{^N_.H.o}SFƐy ϫ_I {T_wyTsIãJg}HBnPr&?T7sxɶ I}>}|u$vJRԫP˖N;GR{ׁg^7V*riTż~Vsɹjh/v Nb'Ѭhw>TuUHɢ{)|Ko>dwg|/C +wطT&ݮ }$߫QoP $- {҃_/I9?xԸ~_FPէF. Yp$oA?n"7 ΢}T )#MU/̿xҏ9映^fH7>_?Eswfᅼ3èMo2y΋Iyun'YX;j׮Mz_vUi0%q#d:>L./=L#.f1AtgxHGh<DAjDE>lh ?A/yQGȞZYԩx W-Kh*h?nSY|A$1\Fi_ |꿞KUyo?6djHyU#.>N0A. aVYު7xp:BYG!5 !KdwnMpGI#{>&w;܅W$Jp7[*3:%,^DmI֨> %R9syCYS.}⇼R0h bHo# LNiQ~橃)s. 9`@Hm{.v!EUgHv7oȾZiܛdY)u \P#-+/XR<$=lp֧Y *b1 ́"* h&#~ |qCqU=*qC\{NS1A40 4gf.d7Zq *w;܅W$Jpy# H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p" H bg+ f%zpvnd^^ߋ|p8p`dW6m5o߾vPow VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\W G5l 3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\xy$^3w VJČf8y72z]/_/EHO>\PSIENDB`wagyu-0.4.3/docs/local_min_and_max.md000066400000000000000000000023701314062220700175470ustar00rootroot00000000000000### 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. wagyu-0.4.3/docs/local_min_max.png000066400000000000000000000722301314062220700171130ustar00rootroot00000000000000PNG  IHDRVG iCCPICC ProfileHWXS[R -)wWH! JbG\ ]"E^,I]_9g3 6lT ~ :ȗ$ X\pe n%U9\!$ T1puv B;AU D\ӥX]SRb7d*%H@A̛YNqm*=, ə "b-fxL+}Ksٟ'f?KNhl}بhqpjf1ԈHU H^(8Nf?5 PaA1C#v,ڣXN̎G 8+3!cx;W3f Vz0#6Am-G@q0+&L0/bF s6] 0Zj¬,\0`/&qp0'fGh:c1c=dF͏rQ?`l`6K:X@X4c >|ƀB'D\ rAJV M2Z  5qOOopȕ86+1O &y!lota\c9|GxF"^\Q"u[5>GKl%vkaX#`bg&;%TR cEKe81z~?͒/^/a>wnc;OKgݘ ᳭-v6Nv!ٳƕo@e7Sl@oǀ5Wt0'A $p3@<,KA1(fPvݠG@#8 ΁*7}X}%`ABC!x"H8$!)H:GDdRl@*]H+r9\Fcy|B1ڨ1: uA}04yh!]~=^Eo=Kt<0+"d, ` b=_z#N8l<_+ZoůA+F"X!DB:aPFK8N>0HdML"fW!A y"I,R>tM#} ˓uv@r2O."O#rJrFrnrryrk5](SL(XJ&e)rrV^^^_U~m vA`QXPРЭJQNHGqbbQkJrJJ~J,EJJ'n+ )ӕm#sW+SBbQY[J/l2z*QD5D5STjꠚZ\JSj= aad320n1>MО3;aՄ'WU/Q?~SS#@#KcFCM\\s4&NtȞX2{ZV|ZZC:Aڹ[k0tu2u6ץzt7af3˙A=-`=.}8"C (.i Z  u .07g$gbaŨ轱q F&&!&&&Li^yզ7̈f.fYf:QsG JkbE%ՒoYmyۊjcU`Uoؚan]dhjI'Mjhm澭mmm;s;] {}b&\wSW88~qrv8tw6tNqr+uI׏nNnnGrrrbd={==X?aCBOąW4xIMɤCm7qz[3Lf̝qy왧f)b:BHIHٗɪf V[/9ޜM~wyGچ322x~ "jFsRrNUY:ʵ-sۜ7("¦|Uxi~=.,,0'~ѹss[5ya`/- ,]x]EZ,^oIВڥYK/)PnY²˗,)bbA+vWVv_uNɕRҲϫ٫lsϣktuZ}qݭ^k7(o(лqƆMM%mrCَ---=M[ "fo*UUquo~pvvڸl7qwg{K^ͽ{kzjk[i[[֋Oy@A1E5[GŽu9zѱ% Hüƌƞ'Zݛf[IN=M93CgsK?27Zv\pbm>mg.y\:y+.W:]mhwl?;:9_ktlu۫uoܸz3f׭[wnOss컯ACeUzzN=$^v˧§?=+{݋Leˑ?ze_&}ۚwZ /ǶO L\KװFsFGsY(Z<;t@Q޽$H$O.5- Qf1w7@ǛLivXTx!|} /ёm_@w8';I~[ (l8)bKGD pHYs%%IR$tIME (XO IDATxwuu ҥHAC[&Qc5o4QR7c Qؕ^." y9ész[){YiѢEA͛W󛍬v~w~~~~j+00000000000000000000000000000000000000000000000Js]4gQ{1+wnooooSMjEͨ¼y?a?;ou[u[u[u[uO5 `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `( `)|S>w/׭`L)X094t8Xmi$L,3T<`@I1IU͟?+0q$6nn[<La 4nw>cZv=MeSe±ǎ\ˏ64 &f_k, &׭Q'/|1,8%_xE#?%ޞfĒ@`B :½ݮ@`j& N>y{l9FfuSk4Y0=%@`RLV:@ `b4wE3qUrY\nD3߭ߺߺߺߺSP08 SS}ؤ:j6oW U]|`fq֪}u#7<_m&f?8C`H 0WW_9v?eV燾RVUCU;lo~pȒ1s!Ѓo?;` $6wB%#3Ɓط1]g4\z^~KzާVwLx]3h0vc: %@`Lw v8@`B|_ߣxhj`/{{ҬʠW>^lhѢ.>mL83WuQGTuUCWݙLXyew`v, .sz4zKqqWڣ_]Տo-OꄥKaYr;+RU;$vK3.꣟`]o1_c^<ꪫ&4`2ܵ9S}2_UOU>b5pEg\~x~l{lvԥg]Qǿ᪎zz^q<$V_}u$ &D&0`bI 0&zkU{kUg_G&q#Yl۟ 6seUՏUuqGWn{UL&Z 4 &K_RUo:ت>u0gG߽)p C_qgD}uŶ=o?za<30] %r-UWWy{rml~to}U۪˫?WCz)&0$$XI_:U02?Rqі;mض 6oKUػa:Uu` Po~^32?=3V_kǃG<\}OY~Tnz!+&0$$f/z^#vy>3 =!\l 6\zmU?㿪?-6q sFf&l馊 , `( Y⦛nGYՍ__՟i3mm{`s??SՉfU=v$pV=H1@@X} 'W]n;u"tۍ?ӟju{9U[(&PI @`_~/zλ8-uW fb|WcU믹AUx2a뭷VLe$$V`>ު{U>Xqj-6\lۓyW[Ukod\նn@@~lWudU]ƪ')f7Xlȷ ?|}Y0H&<00I CI 0:jjvT&\ Ol~q/:oU7~zWؑdsjvPL!$$|077gɊôަVvNugݪNzZ땪; ) `( iڟ^[3zC88huY(ZY#ed;877BriW\MC$4w]1`H CI LU#Zi۪÷um7QV_k>gŶypԆO};U]7r4yf&> BJ`'?ȣjխ?MOP$qvmoU]|eUo_WyyZվ;5 8v;)' %0_OVuToƊĪ ~U]| {gUzõU2rCG~-5Ipc<L `( ꫯ7 f*Y0#:i߇,O#^7U<琪;UpEh裗F)LPf:x V0+2xe}vHR`tf 9+PU=qU ɰݓ LcQtC%@XFW]uUu笃w_!+v|Vٺ~}vKj_\y<{nniKjEgyU5Yìv~_yUyԫzCWj'6>'xVٺ~}^'|eg_ǭVU?Ƌ^48c ~[[[4" ( #NU}zVj7P>3뜳 [V#Q`Z; :*+ `( `y߇[NjUov;aLk^ָeG* + `( `-XWUm{U=UWu .V$`F8#^`+ `( `ZUs_ZoMfk6b+" `( `Ҝ{޹U#z![VW8vV>`% %L]UzUZk(0]{`#gE`% %>WvX +`i/65`@Ӌɸzvi՝ԝ:un瞴׾ euy晟i"r`L/䗵ypukߒXisOEel,u4`@ɰcIܗd!wݪ^}̫'8wqIVz<O7_RL|R-x%`rI ,r[ϮĪV]ͯT*T{( p&LvS9sxw󝪎<ꈪ]ܓV/'yͰē@dSK!{8إ^U?9gUrU`)xd6jwWfz,\',~QSGEG/r^淾YիzUU>w0-|S:shL.lo5% %hۻwޡ?昪zZxU=U*zc1:`U7>{Lػ%|F׌q8"L>2$`:n:hO=^}#z̟U!O4}pKި0knsmF0H C4wE3qUrY,ỸnU7ZZV+vܺߺ\7?v7晟iIKmVM74xq17XoX+F{'qo0J-Q yU5Yì_Tˑ ZŨG>iV_Ѫ^q⋭՗?_vm7߬ߺbIG:E>~p߫^KYUkZUU_=Y'N>>=e:Bn}H l7qNt<[j`5'|_z,.W߬LG? Apgwҧ|=.?MLKU;]y]U+/<_Yo亣_ӫ/[mxU>c~3%}30&~`jI tp %Aa :콽{_E1u0{a00G-v>N=4@}ronr}pkojj4#ܫy/?॓<<0xOpxo檅WW際Wfbk3 ttW1|U~Ur\; `QQ/?Dx3>iDUժ#3j/^Tձf0]Us9% w Km.0FO<`^^&L5#,ySC[XU1}뮻* h3 w a_PM 0K"a2$m , ^fḮ/:ħ 0E4`/YՍ7j*p{ªWoS h`h x۫ GH6ƒeǖ8|Σ3<L04}ozT@ih;emAU^EX?p0AlUpEa}`h_5خuI5SHG=¿OUzkUz}Ӂ0E4a Ѕ /[98 5X+fYfABfvZ Kr >Z+N|nw?ޣ_oO<ߜ0VV` <9+r?juj]vQx% X{?}Dx^80A$$`ZpU=s`9~_C1 ˞O3U}۫z ^80$$`OZV5YCQ`tn]}0[INx΃ӫz߇/y8@@idgv7z=X~b M€;UuWTW+U0L# .}aV 1r) &axeUd֪^ʿUXNP0uYUm:gcN̐y) p?=aPՅ߸cvLUGŁ1@@i ӂwy`jyeg0i0x$aU~SI0U]˫zÛ^W՛Łe$ %~XoV]͏&8oջc5a ,9apη]0vw۪.U#zߩ8009aXp0=|,~x 생vIQx' `:z>Tuoz[߫8p/$$`8~nb,sO>xǑV04a_TH`׫$ X1mVU~?>)܍0Lg/6Q }F>7cN0xUYg]7G\3v IDAT]K^>|GFH CI 4pTKqIWW0q|؃Zek:ϯ'|Rq$$` ]zUmV^IQd0} ^ zaӪVs_Eq$$` -\ZG1YEe>WuIUuk߻B +~xEUO{S+ N(Y+?m볞GU'Cq@̦\EՓB@)t΂s`Xa-o>8#|?s0 JMިUJUU_iA`:4 7(0L2 B.3?V1Xstן&Xfsx:0lywU}?>8h wMyzʼn/~;SDFwQ?9[(qo̓+{1wXU=z[I⠁0R8>nnUů3x),0.\X[of?k~.ۿXG58_?uq alNUدC<|vYY poI,ѽ% Ʊ;  ZG1Ya'|_z/ƥ?jT'ud[k5zͩx@ OB)#SϬjí=fGflK{&F۪c?v `ʭU=-1s]7maƓ@&ĸF9L .8Gpf_68 q[7nQUlAU_׫{0nu.0UW_)U=r>UO\Cwn럎pAjRU묳=I C9 L .u\[1ڋ{b~p?wKoSW>e{r4pKɞާU?W믯8L[P0^pVU?tCVxwM!4rݓww 3TM.\. XVUmɦô# % Ϋ>J1Ǿ%&FSW%A,F pۻmPs_>jͷP `( 7pCUUZp0@X48L V6(B]cI kH+تu/c>PCaH jYR0H $XpႪoi)Qw=)lehH_7y˫zka;?Lq@fei"ZL> g/8MH1YcEx~A5Tw_ƪvmaҘLzѯySK& ϯyb]_%iqPUoz1UŃߓ쵏04I/hU8 ӌL'ZMQszׇVo;Oq0f CI ZpaUnb01U#nRƝ0LGVk+搿xtU۪wø@@ tgV(>UvU=+0L .b0ix#Σ3=?S@@ pgWL;{=mתٓ'[Ջb^I CI pU=zik'?U}cUpP0.袪6zC`xv/fyWBq= `( G .j-Q fЪꂪwVuˏT$$`s9UmzZp`[{sU+,& %nyb0|U-֥U8St%^:rW;EB0n~tՕU>K0ilLɵ~Wuoz]Uo~[g@`ݗnO#`h 8XpaUob؟DGnSե#_DU|g@`B_/7Cha-V C`i/a^q⋇ޖ&LںW׼) s( 0̓z˜^/'h 8{Y6f6faV[~}o`|mVmV=l{jW|ie{|챃 <`4`]pNnqn0k--0\f?tosҞ'|v[c} xv6Wӯ: {_^O9 7tSU?ϫZw( y?"TuK]ֹjsVu/%`vAUUͶ[ h3b:Tp\xUm DK;Cq$|z0=C|䪾y l"`Pp\x]nb'>U] p㖒=0c =VYխ_[mf0 &d%&Js]4gQ{1+wO_|E?Fkϕ ~\V;o>+l]L[2]oh 7UjUߍi{[[[QF^exWůn<qp=HjEͨ¼y?a?=v^5蠮r?ǿU>[?}#! X~q3o9\RG~Nooog ,}{Uu_Vտ|ϛ::G{3H 0) f+GapWFߡk~1|-<;>?՝I_z45rЅeoRU;_p ) s( %a…UmzK^Yr[UӪz {/vcU[kpɅkLG>s'<-rW8o@`\L3u"t&a Zw˵x/?ھgUwLa~xhUzGug)4@mS_.MUx3qM%Ϭj}7V z½׭16f;>XZ`Wu[?+0 &|oC`jH rg;.=۫{ݼ$hv5`rqyU=u;,ei"TZԁ@1?8kRjk_Q~x'Nc8aŸ́[oGL_\˪~z /),[c,|ɡcpª6j]`gήj_ |KXcySO`MTYD 7j$hL> /L]ouYww/䪞yW'z2]&iSאָ_xRUo"LaG`;Vu=r.04` ^06V f+ϿS>>jv]s557 0~uU]:_(J #cp EOQ Vx}V~058tvLS?>H"w{c@@epWwbW* gUO)=ySxV_}nvLs:H簪vqGE$$`\tEUU^S~U=l* >knU7|p4u6XN v S>_W$$``0~U Vg|ܪVfp|q>xWu7T0Nzߥ0,s]ՎOR1ޓzcR՟y~;nЪwf?0QUvygE$$`)-ZT%\\[(8?x0=K9( xo;pٙ0A~sU]+gNTAJb…Um͆sIU+W{|FcmW?N rQ{% %K1@XwKǝgNG>m٦v.OU2@@%Xpp<܍P1r?U}=,jUu~WsOMct-Uu\P}#˜I CI ,h@Xw˵)s/\[)_>XuUge=f~|sV7:OJϬj6P &W?ͪ\e>Wj뭷Wߠ.%Uh {キ$$` ο༪?dO`]{ϪǽU7(lU]WBpwvGUU+ 0ů~~_V Q&9aT5s_jWWxȶ%ޜTcS ƍ0… h |vS_}2;lЪ~Iws/jM^վ( `(  ^Xպ[|UwT}SݶUF1`Ģ-ye}XUMYfKmZ ju=Ԫ_m=zS[ڽGG[֪Hd} ;!(H(I,q*Ify>}_g:nO8S(L8 ` .YTο0x.>~hYqUb-0Wuuu④SF?ŗT_9TwUU߻m${C[YUn[[Ϻu/'~ƺ'0hիWO‚ Zp0럜zUꙿwȔZ`U;ճrߧ/=iIU;>#z}UUxgV{~3|?cݓN.}+a0Y>xSlٷ+.XZ|>f `T^vAUzvi`@i @uK|0ߨj˫QQ6l#)`||7.Yx'ltU= 0HYkꑏڴM6D(ׄG?w}Bva wn73w.6ٳϜ/6 `ڒ%K~/>xMU}N7W̧?S89U-fEUgW0c:K' Nv⋫v1 ->U]uU}j4β+ fS>1jOM7u6< `E]X՞I嬪y2͛swեS¶k窞)CU=5?+VUw~zg go)=c?{0r4AJַV e) WO~SU™AǫqM`:Q􆪶b 0h 4,YRն{l-蔏M_Sf}o'5|{U[KWTţ#S>vnU_䗄 @`V:%˜Ʈ8ʪ.?>ce]3 ;w.[=@`:_ϫuGLr=Tuw]ա{,ih9sܷ/~B}ΫꬓOSnaUq˪^ iCrW˜Fk[~T>_8n[uқ0v4A*K,Q{l)igNYX~ՍUWգh4V,QL9g|jt޽mV(L; f /^Siǜ[՞έq?JlͪayspU=S켋P4A*/'s˜Bn[u{UǾ\ ª,X $ZteuGϩ_?^җ iKpmUuÊW.e ߮}#cU!o:w٩UB`;WuK_UՎ;(= `’%KaO/Lg6ޫcpP0sGwՅ`;]׌~V fŗ/j=[wNY~兿"&XU7/[% ֻS>2:u=Z8 f /u #U^aҌW|Jaޜ zŋ]wU(8 f˖\Za~0փ3?~LU xPXo6x4̣wj7U. .oY++:K fo[pKU[op?wTo~Wp`OU˗4~tt‘a0i 4і,YR{m'I F,}EՏNYXs|ؐ_U,$&ι_}翬<1Ba@i Mg{ IDAT0-|qUϜ_?إtPr̙[՗/a0a\[g?Ba@i 0]p';0;︫cyRUߪ%0eUu sF.|o0H%U_;Lw.nUlQS{o0UZ}6x#-U_j= 0H=UmfByUmjN:BajUw}V^E: YKdɒs[aﺻ㤪^W\8L{̙W KWi |#gWOz fK_Z6{l%k:yzouv'_pjΜ9Ba@m4/Z4:fKWf[l:}eKL{T]|g_{랱ճ;U}{go`ѵnft?wo@mzi@X`AU .fCz>tz_]}OU_yU_/{u_k^6zsgt7[o}:_7oo@9ꫯj7=7Ok/Q蔅?Yc]v[~X՝wU&)˪zSFMnL f%KTUm-:k{r0͝3VTBU+FS]}uU,)4A(_6{n=+wT +U~ǓϜ}n镕#'䬪mW0@r Gϊ^dt:GzcQa.:~qUx `3_^S^{^/]2zM:㤳l> n,vMUuTϟ;R(4AW\qEU;Ì^WuRU{ :gaUrM~t? $ `3’%KvgԺ]q?]ՁhAnzѧzGUmH?,U-;~vwLC'}ʩ >h 4,YR{nAst1|?l`;>N8QgN]G'=I(04ALK/mz' oj_fmfs`ju `{: :k  `_N?z>xZU8iUï0MU0xԅ;0HiŗTyLp͊) g]S%ܦnZՎT7Q;n-)s]ռC g ;S{nr?F?ߖ[aOƑtGϩꌓl  n̤>4:GsRջ?3ܾsꬥ'U5 ԅ90Hi[oj=&Z˗~tԧ fs:` WUV;0f԰vjyᯗU=Nv: U^io^緻oTuY?ae` N; aa7f` ֟I '}̪Uӿ,7>>^Պ{uF_7vw*|4~<ʕC$i 0ux^[c7ͼNWsW9:e/zaϰ/}^Uƪm[GW^0:u׍{3)b4Ã.ѯ(ghx K,j}'N?K^Kkm$pʅo~X{FO^vAUcӾ6[Tuʳ7}7Fo~DZvZs~ kvZo"lU>U5G 8 `O_0_LUco-_Tէ?٪va# PUu㲛0Q5\]FCv`@m4/Z:hVntXsϽϟ|'WU{oc_?Ǿ[uϚ蜪vFwW_=x̬ٺ~ng;y}U+6٤T߬ߺ `FWV  FpYabC9k u';z>Ztޢx[Y+~7ţ{^]YuݾD==kjF>GҰ[[ r okN_3R/rHU;,^A7gtZ~ww`;3^-jSF?y+Ιs:,i"t @`j!ݣ5N_ori~tpkOiK/:+:VGڪ~il:찵'<=P @`\?_[ {Fk?~t{督5ͅ׾'=6ꖓneU~yUqUmi }=l0_ @`ršox_3~׻~}1 X@U1EU-;U卣Sp&l0l<4L~ xFg͹k{,`vNF\tM*W]zMUg3:]7˪.4!{hu};M7D7 ='_w9sZtEUE(v罍S:7ޭO~S?︣a1@`Y d_̫ꚥWTuUu闿Y[0aNCo  ǿC`̛oU,Z$ `F̣߫/ه<GS:,XP… e ꖯ. `F8賫zĊͫz|K8:X xcccUݸ&aFȜqZ.oQ|0H6lS&oZ~P[o&`JZyݍUqԨqpXՙ'}@8@i Z̝3VT 0ZuŨ)o8Jk1o~U} + `֢TuTUƟpI @7w^Uv0 ֛nΫjcr- Xṋ֫E_q}UoyU= OAh 4`-4V\{0IK|pS0Hb-jjՊ[z<,Y]I=WTתvyg!S0H9{W|Jx.> U7P3 S0H~sW5=:ft3+`@i :;>/<^:9sG/|j||/WL+ Xk~b[sjѵ4`򂫪:ѵ^߯er0H_iKjƄ3mn}ܪƷݻ/뗫z뭅J xϩ0`Y)˪:MYՓ$ xǫU€i%G]PՋ߫~_8@ž{YkoL3wuwU|쪶sǪ>ޣu] xkVT O pYUo}U= h 4!U-_J]}CUgu~Ux₪޳x4A͟?tz-ꠃfsݾ뮫j6;?jB՗-j3+}߾Oo wlT՜Um~~V^= ~m…rìv߾w>qU_ꄼw^wg%g~3׿Wߺߺ'0) Wuӵ7 ֓7x;:SF_/m&0Ș]wݵo{uG&ٟ?Zo~^80H9sZtEU$x:ªU?Ml@ x3-@k8=5{pT6 ` @i ðWK."= `L9sVo UfCѪc0ch 4`UuӲU˜A.;U 7s h^hQUtЬ0럝ݾOwNUw{??]}Ҫ<1rߧ㮪nn$fmkgͺ5A^zZ5,XP… gY\}}{ᆲ~}_]YSmg~zۮmU~oo?֭ r ݷK~z sO^Xڪ~-.f= `m waL!VRnju0oG  @`|lU&)/^Tղso-o<p@i z066VM׮pե߭cF̓UΧW84A.Uq󎻪>OΪN;ܪvx>OU; $ `{z4gܪ/[Qծ; e]teU]oU7NW8C0H֣}[յK4kgܪL0 `G/PéG]#Vn^U^I @h||[MӮꌣUoxKU{0H֣nfaŊko~8x~O̓> @4AmvUm|#wTٖNuVU-oU LUݰtEUmVEߩ̣ϯj-vS' L1 {;\zq5{x[U}^UoU_Wzы^S0H6}Sբ3Ϙ=K5g^_[p<`@i 066Vժ:#K: ~wm>LS ǫZy3b=wqWUuNUۯ޹?U69 `l[neU[mUUx۴\',/.~t{MFsꆥ˧߯ꌣϯYO#m&p NIDAT؀{.\z~3:]aY{ՏNf> `l@sVu 'Lǵ+:EUQ|0H6n 8V^SUgu^U{RU6`0H65Xz>w~U7/zcks0@ a?v??&:r6X_Ãu=66V]?>;k0>}'7xWǝ>{&L jNzlF&Okxz#FoyKc {OaX^pW^9p-tGn7޴9ck lu[u[u[u `FWV  FpYa?;o} u=a5N<U_eU|+'1w~~~^ 0dn74=g掮u+`i 4`p-<5vMsC04`M֯`=@Zk_;5 {Oi[`LTc@4`"X\hD&7m[6$@0DX3X7f`xL2,2DX[x?L2i7< lkɟ ֗  62 X`AU .00d 2@ A `0d 2@ A `0d 2@ A `0d 2@ A `0d 2@ A `0d 2@m4/Z:hVnwn߭{C@mzi@X`AU .fsݾw~~~~4A `0d 2@ A `0d 2@ A `0d 2@ A `0d 2@ A `0d 2@ A `0IENDB`wagyu-0.4.3/docs/new_ring_si.png000066400000000000000000003320141314062220700166130ustar00rootroot00000000000000PNG  IHDR t iCCPICC ProfileHWXS[R -)7AzޥJ! J AŎ,*v誈m-Q,,ETu7I]|3ϙsܙrPr˜`?fRr $)0bEpewm\UW8#bB| \-@hz)~H 9֑49Cb 3Pg3`%Ķ|vؙ,΀X ywq23m4&1Ȅ rXroa5S#mo0)ܑ( n8$~ؾ-5 PaA k2؞%B{47ӄ3b8K3#+ IB Wz03.Qm*%DBq(;6laa䈍P#l taPٰY4!ό bI\QR7 Pp0b}K9X%7'8F^g차 vķ#.0yYr؀ ?:N A8 İ Z{z?H`!\`=Ha qhO6PeT+Al@ Bk^{qWmď<2+1@ !-Fy!؄otaɅIGrNxLIDej:Hs&-h84g7p?q qG/ ssG}IYϰ^RiE1w5g؏R(֌.c':`X vJGWJ-F-~aBY ?C0[g/2gیc9 ?o6¸MwRcp)o:7p{T[,,piG wFd`"q LUL0,% f {pԁ6p܃k}` "BBhB G\/$ Gbd$@,Fʐ5fdR@!vA P .jG]Q_4 ChZ+ЍhEϡWћ}cSfbXcBl>VcUA>D3qk>Cxf|/^7Gx@#PB!0PB('&'\{0@$D3 ܛ,rV!Yb;O"HV$ORE'6ΐ:HݤdE>ٞDN!Er>iryPAED]!J0[a.k UœGɢ,l\ܧUTT4TtSS\Q%GjTK?u UL]AC=KC}KLi>Z>mvAdQZTTԡJYADWyrrQkʽ* ***,**'T:UUvQU^V}FR3U TT;EFt:~ޭNT7SUR/S?ުާᨑ1KB㔆1LJ-Ƨ1c|p,spLǘc5}44oj~bjjek֪zk[jOҞ]}AwX챥cX٩Ӣӯ+ݤ{^W磗N^>]KN ӗlb v 2|`D1r5J7Zghgoa<׸IffKLLi՘7{W߰ ZZd[lhD-,3-+,YVVe:gf7ѮȮ=۾!aCkG+Gcm'SF/.B=...[\:]]]^r#-p;=_B Y2qĦ0jXl4bbڈ&Ⱥ(6AYt^o'ULzc379;=v_@_ʸ{) $J'KKO!$N8y)NSJܚj6uӴL;5]y:kTBjbϬ(V?-4mKZ۟Yzrp{Iᙱ6';<{-}vT졜ĜC|5~6iތY3V$=o}^0L[QEl.ĪGgjm9{gA45hyvGo\`xA{Qe/ȶhMѻʼnuwSMRsǒmK񥼥mZSz̶r+?+ZW:\E\_ukkTZvs]w맯\XmexdcMƛVm9s C[t,~+gkGOmʶ}~{G*Ӫĝ;J/ջwG7foSKu>}+kqM)?h}p!ơ_Su$HQףr~]WY'Oo?1DcGl~sd)S+OSN:Sxl﹌s]O:iRS녰 .]<|祓/z;~չ˵6 ;;]~F荫7#oߊusJ6;9w^-;xo}*zDswSgϪ??b%cҗZzZos|p w`}{?~lϤX|iPА%dɎlhz:o@Kgx(_2AwF h2'=>/ Q*a3 w@F۰屨C04VR_CC[dp6O~ kJQK QlC pHYs%%IR$iTXtXML:com.adobe.xmp 904 970 FR`iDOT(݆@IDATxw/ D 1П<#{&0+9bbzfD1b% H a7w;3ճcku];ŻWBgϞtb*xa ZQV_p7^ߐЕ[zIDGwn^ߍL/D $( qvoHJp-$g7/F&rp @UWw;܍W7$t%_]w#l9{QB *+w܁+uyKo/ ܽE(x!e;w ] 7Dttp["A@ wn w]K"::pvnd-wo| ^ DYe;pxCBW.o %u8y727H/2pn!+]:ݼ`_$HQV_p7^ߐЕ[zIDGwn^ߍL/D $( qvoHJp-$g7/F&rp @UWw;܍W7$t%_]w#l9{QB *+w܁+uyKo/ ܽE(x!e;w ] 7Dttp["A@ wn w]K"::pvnd-wo| ^ DYe;pxCBW.o %u8y727H/2pn!+]:ݼ`_$HQV_p7^ߐЕ[zIDGwn^ߍL/D $( qvoHJp-$g7/F&rp @UWw;܍W7$t%_]w#l9{QB *+w܁+uyKo/ ܽE(x!e;w ] 7Dttp["A@ wn w]K"::pvnd-wo| ^ DYe;pxCBW.o %u8y727H/2pn!+]:ݼ`_$HQV_p7^ߐЕ[zIDGwn^ߍL/D $( qvoHJp-$g7/F&rp @UWw;܍W7$t%_]w#l9{QB *+w܁+uyKo/ ܽE(x!e;w ] 7Dttp["A@ wn w]K"::pvnd-wo| ^ DYe;pxCBW.o %u8y727H/2pn!+]:ݼ`_$HQV_p7^ߐЕ[zIDGwn^ߍL/D $( qvoHJp-$g7/F&rp @UWw;܍W7$t%_]w#l9{QB *+w܁+uyKo/ ܽE(x!e;w ] 7Dttp%8h7$*)=NH@2pn!+]:ݼ$nr$bx7$jJUvw;܍W7$ʖ*Tk/e7(Sͅ lo+V,BtK?PBtʩe餉$I2K,_}9s/]rjym{~9-~q ?yכY[#h'|rd;dvy^Y3'M91!A㖴$eFMZ!\zGt.ZE:G?:HKt. !M$4$Pk;8[oѹʕIfddQ7sGs%Լy$ef7$;wLIede$uմ4ޡC|{7F}k].ɣ:ʜEsؑΝ;b|͕txWlӦ94c^&{5jԈˮict1$?s9gLsU8+_#o<^xy*zOͩCYsH?q[g9|ٿNȱEo,4mX;~dv6ΎŒ_~"#} zZN;wܱ$?s:u"F+} əlڴ)x_n22cOsv䲲2mJzٙp~Tt&TKvЁo^5Æe4~f#t<;IMS9o]5yG2ͬ;yӆ r˯&@ AB@HňyS =R$DDCjH%֑ Ŕ"A4$JHKt.ft> rङD;8sgӆ T!3W\3f$7oNүի*W"tZLz&ٺUs_~/Nff&Cr+|G<^'s~E&w/;Y?p}"wޡ{H}}C'&sٓ/?g z١I.|tW~|9^;98~-; jpܛ;yvϜ?gx4;+\j/'w yzInXNMNZhF&W[G2oM2c$+;|O?/vhKvdn%yx>>K{eR܋y@N4yHzvF1G~HK.$c}t378 ٬)1>*j=k,ɻ?Fr~3|0Jvo?7*zAK?5 9{8nZ {j[ҁSъ˨U|(~ɤd">[$ylX_85vܝ$kenaٛij7ʋI8kߖ{ŋ~vS?))["A| BE(8*ҹR$D b\ Ŕ"A4$"%HRC(t. ! '3ۺN9lS"_Edg c>q<בd4~6*so8nRܑw$w >*%ߟTNk;$/nw8xXMSyNyIo~\~HVK?z-qu|_~1X rHNϤcwzv;7EI %|9Ԥq$U@r3>Jkxxͥ{WJrˍ;IVw A=k;ģH~x&]_.[鱾*ߡ~:I[aXMS䝟?D^ygq)OIkC3/HL|_V:4VԮA$^zy/o7C;'YZ5;}x>/?8d˼'?pOOyjH|el7өyQHg00s_oJU3מּ viLO8>O$Ԑ H2{Q`*ҹ A,)EhHDJ$<Qa b\ Ŕ"Ad&aCh"ÛL 6D6$3۷+3$wOw>C;KWDtPڋ_#vIUd,9hs^-З1;;dtKl筓} ;4wM}2;wdvwJ_>8bֳygQ΂|!`M[wTnvxwM.U}VߝNA`޼wUWQ=پm6t߃o3H;F;@Zߨae@%t_ٳӸN"]ľiO==f=?1/g{m ? v?]d*$.0ӝ!N ݓkdrƼ@gKlbEVlyft?r"ǹ-8!U<WӮs<2o]skVH)cgnGSQ G߆r!ߞXe>hc=txDn$5r%ƶt7RqZGy^8Gg*0$vb*z$Q80M;b,otHRK{E˩ʷ8.pKYT`^iiFr).vc%TaUi6|6ÂYKzVi?qp+/өN!ٲeKS52!Ap4d_D1"A72e$DH E, D$Ap AtG'BD#A#p aD~`{R ;K3Cg6++6 cT&KHhw6:7N>;gǝ;;\\rUܹT|$r<[|$M?͝9sߜwRd~K;#۴B{o_fSeΝL 3-ZS$M{)MBٰ ݒ+9ȷ|ЬtP 4Kn y@UKI;;ԡ"^rG]/Fhd;;gXrVsVwLo@EGsތw vlAUr?^\W1իhJ?uvq;Xw:;#G籃_=j︑dn<.v(lέb]SOFVy~_|I9O_e:;kpTY,|';n]7NvY8-+}$o6?/?Ļh<0J]vRUƟ\h1j5y_u8_NM,$C0{g  $$ߧӐ .BE( "T$P BE( "T$"A,)MeQ @2-C=o$/wҠ|Ǵ5MMO|6lH=Gڴa[h>z1 Яgvn./txֹ3 fp ;+7bg +sp I^љόoqlVw*:?oǹq6r]mfQϼ@Nu4dݻJow9ͯC>q,;j?;m; |zfaށs\BSq[ _# ,)5p8;q^rPӞfzBNӟż%TT gv\檧xre?ƷXb:\"5ADcȹ gGݭT^cf*;-{wigˇy'k!)_ `*󟦾5wk&3;8/O3;9ǽzŷ3o%tX$T͜ErWBՖ|7+DZ3J ف3[1:|YQS1FԲ5$b6#C2;fY,l8Пgi0b:ތPϳ4ƾyfp󏼓Zh'Wc[vӳxOϺZ3RoN%;~%g\xeRSi_!6]~cw̉8l8mt'~bU7뒾t8,Y "wn#[Zߚ3:a8颓.voHJp-Owm'ϸ-9˴{w8 YEy}aSm}8 AS- HQ5oX>,ogw ] G(G,5u$bS-A4ot8 [wbPJ6 vGw;܍W7$t%\6?AQ?1<==6lJ^~驺DA.U S +Y,n!+]( ʑK- TM ^'w?3A4K&VrQ}5࿛kD5(;wGz.Nޥ=K|q‡$vJ2/:>HT $" RD VapxCBW.o- KM TM?d"~$?;O-gAj߰U; π{t˷ 3 A5za#bW\ve^ԍ\;dKT]:HT $"RL V ipxCBW.o- ʑJm TKߛD`|M$b/rdױ}:bZ#[G Յ p7^ߐЕ[z %yy^v9)T[;hHT $"RT V qpxCBW.o-l $"R%A\n]5_L$;o/*gW#,/a8w#v+uyKoa_뺌񕨋?}:Uv5TY5hHT $bS\ VypxCBW.o-, ʑwHNcq$ⲣVB>?juh*e⌦T&voHJp-ٻ#ߧ}Wv5t 'D $ $jXް|i`nj.?ĸ2 AËQqQu ?SFVS~Vѥza8pTw v+uyKoA:;ھhikY/-I)@$<5&w ] o\.ybɟ.On=3g9} {oQB QHjAaC-3n w][ Jc}_:R$ H^6Rj1yƛ$'v{$hz-HVX3;#voHJp-iߙ cCDbQM*{NFњ8?H&v#w;܍W7$t%ނå_AKӏI {9 ̩X{^$HRE%V +nw;܍W7$t%ނQgҹ"Ad@H9_wt zUήt ?CZҒנ'G8 8WGNw ] .'WN>n::],{o#QB H zJ˷M3n w][P Jґ:<Ӑ B 577jL:dchinBEB2.iCNKU Z#{$m ?pxQf+S'O3 F @Q (AN nw;܍W7$t%Q.]t.nHzג{h=MsJSQnW;f,K¨ l_q*Dcw#pa\?.|170ZŅyiդ N:/Y`}md ^ $` b4VP#̧̀lqy Z4^)GGL lA J^Ef7>_m&"Cy79n!+]Vw.&ykH6loQlSwMa֭[K/[)DA$(H@) ˥}">h=: 4Js% b0౾ @f-gV%${ k;gwv"q7x}l>AFgw-p7{/PH1'a6_tG_%yqǑK/[)DA$(H;{òӫ vp7^ߐЕ[z+?DI{o ^ DY5s7$ϧ՞GNs9;~t;:H"ﵼ7uyNL#9`]If}9iҤX/ @U .xoXK~oogw ] 7 Jf}1:~H'۰>y|VWgiu7 v ?7,?|n࿛kD5(ʿCuTt07Ij΃Is$R:KVQA *De;oXvz^؂+uyKo^#Acӱ @>{oLbKyܡ,<y}'l&Yh&=}L4J]hOIFx9+/_|Nc!I$R:KVQA J*D%.n}ri8 cl1ңpG/xD(x! 6gNHplwCI3/"= x@oXyAk7F&rpoy5+?D@ 3X H F爛fɬxQI㮼7А^pxCBW.o͍QB,j3(~L@IDATC@ج'_+$bL@-$^`͍ ܃[u/P0=Zu1ZՋv}kQ)HQVѡWJ2 $2dnoXB;3?Zc%C/n ^GN5k3X(!u}Y0E G ʪWH 7,Jyu}4Q HQVc12oX5ZDcw#pa\?.|17G ΨĺAUD($(ǯ./̃V5ʿ/Mۺ=a @pCWĽ}Q&^wQ@k IV;m[O_ӱ2հ!A#@x#Am`_/쀻7^~Fgܣo-$}K{~kH!e/xz>;Hܓ?gXWNwX0p7^ߐЕ{mM]xq,ۍJk7lBe Q$bP@$ o ~G)Zٲ{|:/k}Ooo #ſ?kLoo;G"7|2v#F  #'n w]_YILxN~T} ֬@J*|k}kBl  Â/kDq El^6"AhlZ_=Z!Ac@xҞz|k_Ivk̟7qd>Wyd<,LؑvpFrU$?tHoHk׎$^%ބD1F DY? ظ "{wmht=:N~B7Qo]_z 1D jof{wuEddrISRaޕwL#,>voHJp?9Kq\?Tbwxd޽I_/EXC( $(G@ax[ckqEқp+H%OXחxO<+HŘ!eU^,̎DʗwX0 p7^ߐЕ;w v0r8ɱ$:Ğݨ&٩S'x䳆Q)HQV#AX,"yhkM+ux#Ae {=$b@t)z @_QR] wp1 ?T46/9SXwoQB ʪ6$w'w~FReZ x{-ٛYD{͓SIHκh'JYlXg\uKܽ -D $(aQySlvp7^ q7DoZ;,˰B( e6+>!?sX1+"yyf.c  `Fk5Q{im Ӑpw*wvxˆsRKuY2ì!K-lB2Ue,ykա*]kj,ϜeeshaA}X;~.u! ԤsF/.)2wqls\woQB ʪmH*8E QlH+IJy!A,OPge c1Vrhg{}  `@U}]uY ~tcj*7~$3wy HfGD윘g4Wc%d4,Y/S?ZYw;Ѷ/ޜ! nmwd.gն{ghFUocKEptmjmUwNżsrl??;3.ߗWj0JVЙ~6[/@Uk>:`n&:%Ip6H/_lH9 J$eIE#m{}h C j鵗аO"; rXޝ6󬕑fD*oaϭ 6&ҠG}E{IV:.*=Jh>mߨɡU Hޔxy5Uj5mxֽe}r)5^mNY|{yq\A<5kyoU#gg8;|3.᝗3΁{yEQ~%}տw.i;U{Վo옻v"D CVu*/Q$űy^os".6p6xH/&$lfk, vY !SDX:SѐЕ LmXL F4v#e5Q:9|XFWc/fwX0ČΗ%nbj$?ws9.ZoN?N\V6`ܜw֞7A% ֫;5.W6;;>U3k杊{lk9vQ|M3m5-wg{`sNeNAgr4#X_0"xo%[=r;~ӿ"QwK)\1~_|%;|IEs8ͨq^.ϠwZ;?6g"G_;=HW[M2;_xe^|.^sʼ8  @DQ9P̝["V.#AD"u$$H#ÖfkuE+pK-Hy&:Wx=}$3pn;w:8}Yŷ0a/oBh:o]xHN=W<3^̎w|;ᝠK&)kɬyG0]ر}{I-ys܁?Nx(3OseYh;;Z~kj;e9˾c5 3}1h9zÕ 3a[Y{Tiٔ6Bcl\X_+ﰎ;i[E ը}HfghvV6YWrs~cOQ/Nv9TC$O?<2~ŊI~f;vMp9Tojn< |Y۽:ηUڷ/^2=ʏZSz=$ 78uEt=9@`ܽAG(x!e5#AT$qg{ _JH &ɔe Ach$R$DA$(_%rw/[-O-Z]a hm*ة3IFV{9*d3;ɤ?#{U}jj 3-g2{ *?^MF?4ڭC*S+nR:ݎw+?L=shצ)?['NwGzTe*;rQUsc7l_:qx0b|6$P'|mqd[) *D $( r%;[ 6$HAHsH}?֗ޮ{m$HQVu\ۖպkr#H{Hwu$ߙJP/1Ԯw|ur94~>:wN<$>u 3f<#IܕȿcL4>t$s*:SN٠8촯[x'a[.K*yYG^|8nz5&sIu"P}zSw>qӛZ5yG=虱)_d^=-!٥Qڝ3g~,zz?˳̧~JZt0zîh7y}nua~tڛ߇;ٰMtAt=Y9 ({QB jG8 N{ b8HIJ$g }t,KZi~fiןD;Jۑ|,މeL;}h7,{N_Q7vG.,mF ;$+++b<y*j٪Ja{ p6H/?Gx݅er H; &F{uaozH e#O?҉J͍ſT^ FK,-, byr/W^^P|cwxĉ⚗"YR3ډ2ߘ* ODrp>}c5kĽ !A@ FHV-QlH5\WX{$v?hH&X2$ 5$b@ b2\<^I.DIN%CJg'h[7@WeUoQ&.QX˧'dL;T \\A(x!e##A䑬Z,ؐ &kڹHP^ E6eIHH!e7_}A.>n9iG;MbS%A_wڒ_]]7%(hxyt}UE!ٹsgL2g+4)I(?HEkHcYmk ~$.\K2l/Zqp6H/?Gx%ʂ @I2Z=I&iiH%^h7^H e5/XpYE[ZZQjY$Ҕd/`z.7 e˖6'}^̓K'In)LryXѪ֢V!/6ߘqXp\:<>w~2Av<;xGѴԊ\oXF @TQ9B.lHC8  bd$|czѐ|K ?_hwJI iyу\/`ph7xӇ[l7ђ 6Lg,wmzWc33VQA#k\!I^m=zWAcxLy_RQz̩PHEm ^ DYQ9,KD${$86pG(@|M\ l|7^$3N𶓘 بU&jU4#9趻Iyf_a|cwsmt;ﻏgrIQ?0gSl]ZyO7<;g9 xG˸|ظHvan&kǽ Sp6(H/l -,m1,О r$|HȠ4Mi}H2$b@ o\J2g3|uɫk~6U+H۲[ONo sv`A7ͥ_Iy`;Wg Ϗÿ}! 5OmLm8rWIу1sH^ph#[qpF @U8jh 6$"%ښ .@j5iBĄ6띶5X;QC *'C>8dI;&?>dmIHq̭I~:tpM'o|u:?i#?Xu/tz|F- :aOwߋ[" 5IϪP'y) #K.‰vXFb)RLE7Y%$ '΂7H/0 /a_!Aka{$L?cc$Xh71Lm @ * 'Kc6D88AMmjqDYm[TiRjοKv?5y~FȜ|cwdqt9d`{7YI>|#V;}ZwocQB *  <( 6$AE@j՞ r%|15煠Z{ :"A@ ǯ>ES̏r83Y<6`+I\g>Fy7VitLGࢳڌxQ3 7 {rw `=o)Kr|}i} @!A@ GG-+HHG|C[G|kǽuXI?cŞ7V{ڴitI:tHKK"ܴi#Zn=t~+=Mޗ.[Ui_:&ۗMfv}6!{ML[#-[l>^~ZZ}H&{$qdkA ܽ>b*xa ZQV_](.Gv=7l/^MU;3H{ZR3@bggpUԕ6x:-ᣡBOv.DpF @UWChjkf% E255#A/$,DA$( qg#A?, 6 _$ێwMБ z7Hqc[Wۯ&^$Nk$b@ wys?o0;?y3F۹_7JzgJeW;2}5cs_9v^Ҭ :x7DD햕H>uT "aoVe&;E$pA8D $( q'#A,j$~Ln;rމj FG0, D F{WᵂQ HQV_΍o ~g]{&N1s};}3ߑC ;^ò` cFaB2پ#K!#U\v>.h5[{̝IP.eYo}e*)!7Σy`}JߩǓ$y$7nl$;bl$HQV_΍;D`2 4I͹;^.Yo @mtZ󍱛 Y2p]y/O5O83KZaZxO9TDGa}汿U_,0VwpVNNEU4;'tٺ.Y^sw<%u)_IA[2ڵ=QmJE5ר>}HfeЖZ9 B` ;D $( qWw$Woiy'֫FX69 Ğ4@A,;N>|w?b  2p]yܯx>lMWwFrb6pMxx{`[څvɅ2,Ӥ2?38n;qS=pQ^ށw}^t;}DqEu@%P~!Enm *@ *k.̹Qz-PB(ٯ}"@LP~y=V]%YG)@40rw_|9|v$*>2[O&DS]5ݿ;^w+BU9Pk,ɞ{BkZØ#Por?M7ާzH;F(ʮLrłFZF!@Tx U \S#@ԻMl "v}ǮY!@L2 D.[ /8D sr?p#XϮy@h.ٜ18+h=>[[y6P$,,C+#䖀GОd\'#͙e_>y&yj P9 VlѢEDse g>] 8D srGߺ o؃ Jĵ~5WoK{oFz=p^p f5~igc_*t [4k]3cr:u.7:6n7 E赅BN~YyGH~|'1 _"ݻ7W~8>{*"@Tx U \;D_|D̃٢]#DI³wAo^w>wkQRhV_3pano}{=gO~*M T/F_BR&>ԫ4g?gtvɄq֎(od2z߫*K`~R4o}Lb2)?kp Ѭf\#@n=;k!@<-Zw ڳv7U;j#ȸ.Gp)@40hO_oފ,ϙÙIM4k,XDoifN&yiȤBȨ:D$K(Jc$ǓE$kBҦ搡?~BH|&ɨUI~#wkD *@ *k.;Dj/l]AFemAx>uFn [4.o @T U \ su/ͷ0.gq1*2fR9=opF Ѭfœ.]]=J ߚ}=d~ѷYA+D<>p f5WZ:?Nw9pu*YgM y=4[oiO^:@_owjJCNw6V[pfQ֏T~3glzv#ʌ ~Ju|"vߴOD g /8D s ރ.]# @gUo V?kh- $Dp)@40(|YH>d}6Ĝ_$)pPCtfrߏŸ4M!GlҤ I/{ʿ׽ɍi^>2d*u,G\dt绑{uIц1ȅ}{?L_ݚ @Tx U \ wޭy{B|kbbD;[-_q}{7nDHY@q߱cYعs'ɨ( * 3F>noӦ1~n75pF Ѭf\sGh/t[EY{VD{8ڋU9wgyiBp f5B{ZelI~9\'Q Oj6>s.nQaCF޶oh ɂ7 ٹ1u2tphxF %yu <&ɾ}iäbUl \wX"@T U \ .u;VD{8ڋU9wgyiBp f5B{%iī%YٿD>O,b*6$ETdl\ydAЩU G^d`75@=g4:s+QCҟ{y!7V7+hwgѬf\pGh?t[E{VD{8ڋU9wgyiBp f5悕{VB4-.,ůsux'2?ߣW~`u. [ ;`LYN}[lv^e=إ;͛7' YF@7՚U|dw]thQ*#=VM5^p f5悕;D3`*D+Ǿ @~%x"@Tx U \ V֯xn0XQQ|f&:߇)2E ⳈEAKv3\IEs(y.~ogАOLt!"?P{>\g}6m# oz<߇dhrnm1 *@ *k.+wfUW@}uA;D=YQ]=KܭD)@40.srrhF#ߏ3fzoD׬_9J7Zܑ7KҮ߻m34$N :sz;A\+N7J_)'mpYI|c43'i}D8ip Ѭf\pGhebGYjWw~oԷFܭB)@405K3穇|t dVAt7 %2>kO1zo**T i~o=tv+$_!`c+w.O\Tnv*O2NeиO,l-C+W qG`}<y /8D s*D F{wDڝn< Ѭf\sߌ$3aX@IDATSQΩ4v/"}k :Q{/;q"Jo2>SOѽ^Oo>{d*O-}JNNZǣ|wYP-gL |Fsoe>&_[gZDsRkiQI.]#v^~`.p^p f5"{izsY]CQAl gbz "GLp|gD?Շ Ї6rF7CI|3\;k}$ȋh`_dGhZWۗǽNUdŸ7ÀXJJVADH5'b́F)@40) /o"@TxkAy zw`w ܭ DHYH.tF~ZK|7N"XDybA2WD"M96mr 2S Y*R|D(M 8#%2Tw8>~i3Xǥ㩼Qf$A$ /yEAp^p f5";D3~Uy y;7DkLM+킻F)@40)ϟ;G3Vߛvn0ّK bed%-Rw9S^YEI,iS9[lW-Љ;k׮%k_E2[`aqI4 so[fHfWXI`wܭ DHYHьeb^}?~nm7wkQRhV_3pa.ܹ'%Ji .Az3_ǕsYB<;io/5hٲw ww3)vW^1>>ɢ ͘y Y_f1}I6k/=~jӜW^ pJkDwkQRhV_3pa.ܹ#@4Wv[Eh7Qw .}㦻;=HV Ѭf\rLa& ivrsL_/E9X~OރhYMϏcTezU~䃠;MZ5ewΏ3+g>pT{]VOxq*uFVrY%k5@)@40 @YE(fu @2]۝ߛWwk+QRhV_3pa.ܹr&BQQ~=U7'2Y]Lf0pњg+> HɒSk8Vy讯߸3[=Gs,]4٭I[^M@U#TۆRGn  Ѭf\sGhƯebWw Z`}+V Ѭf&t#c ɘ$"蚿 ?sf^*ݻuwWc9#o;z\|9礏 N!Y!^J Kօlu)/AY{h®w\ܕ"@T U \w b>D߸ DBK[3ָ#@Tx U \wܯi}5UH+dL|8rZi1BdU"|/թ/]0*7GzI;x۷SJǸ_ګL׈P: ~CR(pq'>NNRmë́7g!+jgÄw,"z{*5?Tf5 ?>Á U \wk Zewmv5DgyfVܭqG)@40quOTqHSCAO[,׭}w bꎌҍ}9R:뽿Im|hk_"zsq;Ю4jԈl;B{Sg(H f3A#zq|4Μvz*U>~6m(-up ѫ^K%D|tEt` @tk.RfVܭqGQRhV_3pa=snRг ?uSm L8o3ޞZ#虬g8K ǖ,̙+פޞN ڿ!ZPn}3HZG|Ʋ[g47깧H`؜Tz[%fo}𷗧DHY9pg>"@EZ9#@t%\JJ5^p f533g=e3gΤQQ9v<*?ɘb\AtEѱ)4gj ęa{e1?1>^NtE{7`4.O4?4_8MLj΄y=+/駼^龷 [RW!yn_`{3]w "DgyfVܭqG)@40޲)O#T!qdr0:3j;AphSO7OתkvqF[ixoa=gp+T=jg{^]N"@T8 U \pGh/W4DgyfVܭqG)@40ުcK2pzID9sֺg3?1#|>Jh~l iYidEX`vkݢ p Ѭf;#@$["@A~mz=Q"@T U \w=ot :0ؒ|&Qf\[w|gZOJ&?;hfUVu9CK,/l_{rw$/ F,ʖ-e{-hzܭE)@40z#@ξZA A?oͬ[QRhV_3paroӥ5:fSکw!d-i]2v`'I7.]xbŊN}ɧoOwpݿ4SPW!י~*)ñOg1[JZq p Ѭf1 o^#@DD}5n{TVRڅm 65js 扁w3] %szqTnOѺ2(7[/$gj\e{K,>SuB26ؖ-Ct??u6zAmSÔ])4ܬb[~y4˛˗O4Zq p Ѭf10 k^"@DÁfܭqǿ*VhV_3par1y$;Cr<,q9zկ_K>ؿN8lSw{v!lN3hl*%3i5r`t#*˹V\3qFt۰4*NoZBꫯv` ~`yty mp ѬfQoz8V "@T}Ey{On6DHY9p1dpI>۟܎e),N FEfFgazdO91Ū>d(ӧ tRC`޽Qk No;j,drJtTPNCU96f7 $_&v]Xҥ I]HܭF)@40z#@YZA(I7D~*J5^p f5=O{n#ř^ gP,SŨ<? \$pa?6~'E7}umi$Ǎ}'\ W Eg$3y +9cjΨU*27zq1qm4lP3<3T^iv5@5^p f5= ;D"D3V5^p f5 wizIUA2xl{|R=~@˸ڟ䧂7~$٦ $|WǎqըI̻J™Kb/X:\Ù'LC٭E_- ٥s'ݺv&9Y$u]7G7n;DHY9p7]Zu$dM"@+P "@ orPS(ܭDHY9p7]Z͋:P]$WDeQ~ZȡMM|l*g ~-^-UV7lDqH}s(9n} bn>;|c-4_9U')ÇRU8~h?p Ѭf.$Y"@G]u\(k9ڪ!@Tx U \w3ܥUowݑ쾌3\?4T/gJ۱oOS -M<߼=޽;x"apB3aޓ$ ( K1;[hQ ڏ~S J~AEwk *@ *k.́Ҫ Jb%D\L"@4E޳]oϽயWrwk *@ *k.́ҪU]ng?:DzIlRdJ rY& Ysf! b`<5e~ _p fYtrsl)V zX ,#[QRhV_3papVG(9KE֜3d󙨹9XmdA< 8U]zu^Mp}*e>5xtAoLiX?߯qC{HGb'#h>K'Xܼ>u2$zD^qIv=R7n;DHY9p7]Z 4DêUA}dM@ΚW"@T U \w3ܥU]<ћL+dLbHMz 3B%'3CTW0,_̂`h>y[۶!Y1UhU` f~RPXA< 'GIfpBG%|_Y&I\<hxBL>\=r8%C[5oz ix6 mCn DHY9p7]Z5"D鋾HP3~cnaͬ[QRhV_3papVMoߪ o$P9-C8/;!g/MN.[kɧ>E֭[G7zE2_[2@e3e'y3OufLzwHrWZoɁX6Vwk0 *@ *k.́Ҫ) JE"@63uwk *@ *k.́ҪnMSL7.;lP3.|БĉNj?rFzra% 0~Ouڰݓ9v$u9{$^Qؒ *Ϛ_gr]u\gkkkWnU1%-Mg&oG5b^p f5 wiU7L^ P JN"@SҽMͬ[QRhV_3papVu?w4u$͙(Q, QZTSRrJK84Tt5[G-MH˺F >_xY )6L kk k~LGSp Ѭf.Qg.@Aɝ.׵7s3w /8D sn {"[ȡt ^rx͐݃ߴSo=^kgvO^%Sc\ H㣷h@o503+ ָ#@Tx U \w3ܥ`տ%z} =F xUܭD)@40fKahLeΜ+6"_?`z@q M]|9x=$ 槔XҧLؕ֞^J_Wfu|qo)D ߄L >@ [QRhV_3papVC?D,D M*Mh{7@wkT *@ *k.́j`I^:tHhRd\`%Ll;dNeO?Al2kJL[|@|>'/>u =[DqSVyo/')ѮY ujMV*=:[QRhV_3papVC?Dr) "@ OY~[cQRhV_3papVCˆtIY'_[!IlVd\`3JOҐّds#H֪U+Xq42w5;IIe3muĎ_o5-ݺWɋWM(Jr5.j],ڸq#) 6XƟBu %3+ ָ#@Tx U \w3ܥP>Q~CC~t%(5U /8D snBSx{rg*WuM9X*2NYyw&ɟ|ƭ^6 ?M6%.%_sf!hJ_w䌪_"tt3;Q*9.-}PqDPoB} 5^p f5 wi5#@D/#@7ݼGy3 D pF Ѭf.:ZUT}dqe>h ex )"څ~.fVܭqG)@40fK"DþHPM7<8z3ָ#@Tx U \w3ܥP4f~@O9)*eႱtd>XrA!%y7!<^_Uql[Sy7o)46%Y)?ŕ?.*cPQʮNg,U3ӕxY/Z'ݿKH@7gofw /8D snj "@D=ۆ~L%Yp Ѭf.;.82( jɧ'ʇK*O]MڒLT^63pVtnM\dPWKޘvdKSre֟O1֭y>3pog|Yp Ѭf.;rCS"@ us7po;XJ5^p f5 wi5R|mMH$ʔI|n>>ؾ;R\'|Z{f-ȝd9ؽ>7T~e$kôHouʀ5^p f5 wi5R#@+bhQF~cோpw摗Q!RhV_3papV#K? +\I:Ǚ3KNR]+HdHo3XTo[*ap&cO^EUk}7(, q|VUycI.D~"7ܭqG)@40fK\{}|]7r T;Yg>] 8D snGE>=OeUG_&"fєif$gN|_ +پ{{Ͼ;H/y?}o&m~_2Nx'ɦLr_5Ho\_ mܭqG)@40fKs]U7r=EZ?nDHY9p7]Ztc|P|#ۦrV_ - ;c<\+JҥU ҸT4cǎ޺-$NuP-2?/L|K-_F۶NA.롐 D~c 5^p f5 wi5#@W"@;XE~cz 5^p f5 wi5ŋL{ ~ \ 2/WT '< a& i Ž]#=hq3vN%Yg/ye3粱C*Vh*+fVܭqG)@40fK=j%fVܭqG)@40fK?=Z(1K{s'{1 #4~$9a$]Gɍ&99NfТWsF1 Pﯤ^\eVݗ}ziۺ"uO ]sqWQ!RhV_3papV#?Df$D3M[4wi% ܭF)@40fK?;3eK#m.̕#tR:̙/Ul2Xv~rlD/ppO<~}zQGT`9™& |ToYfz%*G7J,%$U;ǦOx~3Ho<3"[QRhV_3papV#?Df%DMYgwv5^p f5 wi5R/X$''K$8S%}ۨ|8+ȩϊ򶣬$yǕ8X<{3mnIjj kO#b6SdV?,ׁ3%n,IQN|/f>}3'6^|ܶFe~J-~F=?"u񇙝mN޳&DHY9p7]ZTm=Rݜwd["@Tx U \w3ܥU$<7I>:dt#י;Ͻ\k\>/ux7Ψ 2~ocϾѮ]l]=ܹs4:58Cx6VÒK_çR2[j+w6-qfKqZ6[#QRhV_3papV_, z] voָ̮#@Tx U \w3ܥU$MmF2wZf_Ia ç:&ROJJq5jy%b 8Y :.#"cѓgH. `"S7fwk *@ *k.́*KIqDoIG=7fwk *@ *k.́*K}L$3g{vK[QRhV_3papV_& Z6Dwd³u3w /8D sn or{Pé9c]3ySy嗢Їl "5}&g2W*YC*s1}/?Y dgwܳ'yU|?1#۷'joo=G /8D sn ood+DdH7fwk *@ *k.́*Ka#>4~fiғAL>v {:C2.ii<ΫniDC+ s{i?o8s(O?% {#'N\mHK.ted*@40fK/I' ZpJn;DHY9p7]ZI7q:j@V$uީ"xNA3'2~͜/, =4Vyzi\wЇJ*3WpM;Х5^p f5 wi% $D߸!@[~cv p Ѭf.$aM._-5x$߽-dGSyjת$6luɜǟx35jԐB>П]oH95.oosS{'p׆ڥ!w%n 1DHY9p7]ZIšDhZJ$2t7fwk *@ *k.́*Kɔ~7iFEE9I[vn}4$c \:dӤ'<@]p42Q@9-~=>|hV{#/lI"}$˗W-Z9x.3oM&NN~L`=|"n+DHY9p7]ZIBDȜ `슀n{TV7FY joذԨQpZH,.$W^ FLXTqY t9#Ҕk*YDcy8Ǚ;%J=Aʉ$//dgyh#_|T/D\bɬWq$At,AsU$]7?/Bָ"@Tx U \w3ܥU$J^3m 3w )hf5 wi% Rr@t Ό3~3{Cjz_:?&UN}}d; ѭ+ $/hTx d(ߡ}(٪W~8CR>?FrC<k)euZ܌Uk֓k?U I}㈴슃n;DHY9p7]ZIB;D{WX^ڃn;DHY9p7]ZIBo86|s42)'QHf ٵ$GNr/y&}H$=*YS.7(63wK f5^p f5 wi% 2P zȭfwk *@ *k.́*Kz܇ @ȿC_O{+SQ̃$ dV>+W#_yw~iW56b7g gm&QC9P$c>3k#}|^ڕ$.=pͮ,ָ#@Tx U \w3ܥU$J#@~vf=rkٵ3w /8D sn ^i^vZCriB8&m^lg_G7c^}dݺp"i/=|-$[$Y5?UjETTJyH7}5Hͮ4ָ#@Tx U \w3ܥU$J#@mmfwk *΁&_s٘hK2QÎА<p'w?Є]/\"\|Xrɨ(#žO sg(_2y˥LNC 9㲲$Eͺָ#@Tx!@TV+a&eofqѿu#5]{7ܭqG Z^ 4l~V <㒝Og; f_>E% Pf^t&nu)T鮎H)SƩ/35-[/NFP֭5n؏^7ݶmݬ]J~ٿ?VS/r8q>-[V*Eگ,5V ?Q%X.)< G4>11cL޷3?Oͧ]v,cm]W#g(Ӷp❨g_"ibyfWwk *ց *+ZE蚋,E(I]s RS=D(S՟{K*0?0\"|37ߤn)yFSG%-25iVwԔ oTEtŁb;sW%7Ǎ%+W[%yY+[:o^; '5*egV~ʤ4/7oJUVXQvm<|SB11>[:SapWH]~ ɏ/I2dVG2!$t_䁄Wp>qq ?H2wKf5^A@rV :P5*g3UCq :V ڟN D끻م3w wowO!E.g "sxyJ\|)ݿAřs&oL@7<ɡC^çw'Cf0[gYoݞ={:jNӞgj6|\ nک>&o??N?@ ~e~L_gv0"çє|IUy3'If*woٿwJE;{FVK=s]{h2$7W5k㝭8WaCҿz7m7]یd<>ԥwnN4?$<81c:﫲>jzE$_ ę7R?vZ׻r} ^pkjzi j!7KH6jԈܩ_ }mXZdEܽvW}CQUI^5DPNT3!x:ܭ[Hy$fσ >}QΞM_}ElZDf[22?˧ j_Ʌ"5^A@V )ĩ)ĩ Dli2ͬ [Q2$Ŕ՜S%mT Wǐl{$^"K\GCvř5^=rU~~pgaލf|{TO-Nv=;ds9_Tǫ8CJ<͔KΚ>*O,ɇE _}}ʫط ~qFxa>U$IJ._~:jg&={ʳSper忬ыzS4g8s#[+.&KyΤ|^d*ΨV屣1}IMjZ@5Mzw&ɳy~^5ҴZ۷l&Sh}U+G'yx؋2󝔔HUE+[@MOwkl *BƁ !@TX]s% k.$DD|Bnꠕ$(dH)7qld3 sKSyAّ $C9;i"&8̊ ܾ奸N0:5hfz!gbt K30{DŽd^$勒7nפqu*&9~O|HgH22^|t+*۫΋:T2L_ot+UyG?-(ˣn$OR^bO=ݏ9!yqNt,Փس8s40d9x=GL~dH2$#3SQ)<vQhg}(")T#zF+3 T1wS۫';t /6nlRO3۩KnKrwigw ;Ns]v/Y_ioTFIJf0^_|[ط\?$Ǧ|^GyՋ6˜_OcT9 jT_83fz*Sݯ~גe5.d6 gp%}dTWIc,TAZSSOnyndYa_TO%wʮ^Errv ^88Lƙ~X_g.M~)Wy}+j``/E[Zd}Ş\W?}ݨPG>D򭉓]74_ܘ6gZ7>vfM<뮻_u)xG7j>rXUT2tu\s5TFWn?~(Wܽ!EpVQ @T؇!@CoGP* ѹQ # z] !@GE(I]s ]_{)Dy; J1~è4r_Y=s35m3933[k3rn襶D91&90gL*KzΜ#-%K)崇%p_'qInI^{ ]R#2/3R9kř iW&Yj>9Cُڿ{ g:w8@dpS}lӼ/ Dsb9v c+E",U5;;zI*zHah/?u==o*gPN <\` =;G)w<}x 5n`;ERlGT!G"w?vy~9sPg8sU)Em]"w-%Wdؗ:x%p~X&2?UkȆrź>j״Ji7{z/+92>ծ/e)Xԓ+R#S{C_i? rau8e2S-2,Ϟi}F/mלknٿ~gئ{CdHB>GzPQ<I3Y,/<$o6gu$DlY.~&u ˧O{O'W{&~I&~:<ףbǴ,}@>^vFR:dj̫4ɳڧR"Xg\H}ZkO)+?EwmG쾊e?XهO+K<܅+?1r#}_& aI>s ?Z1_~|*Y.ż{n, =~tMM6;J%wH;C 3}Z׋O?Aߛ1o'P-_C|yx9_e=U?ƙ_:8q+Ah)%}\θ:'_ᖝ o<[2RyXd7;JSYߪnjrVT|_3)j1/E o39DYҏD G˪m[v/$%90OIԟU3&/`l{>?V)h?R۩JQE.0X_[-]:oX{'pl>_\ݩU%*wja  b;S*9u-_~fyAj|R:8#ps' .M2yQr@-uI24[=%dxum==UVY1":f^3{N}1ykt1$=G]iTTG~ Mw1jJL=9&e#h"22r&D%8&Jr0h+ QFjXD'_]~ixZuWuꫥUuAa&M0/)`G'{(׫Sƒ԰@c9@Tfp!jZo֭ )exlP0Zo m Y JlK5'GQB2 3,e,0Rm S0^-{"8t_qoX3hrr0NwDH$%svoPँ i z2( DK<8$ DFºu\hi 6| 74/H̊ 8i beMmI:ı@d$& [=4UڱcjL Wb$&4Wz;dzjg 3|Z=l bxw05$3Oڮ<*s oG _%9?qJϰ}Rx.FK]3<\.>(3KSClòR줛6Ϧ,7ɋ-T؋O <99 3!- #02xg/11\Crkq ˷2>:e}1җݰ>؋{(<v}1A&#M30vBĸ],0͔ <1B2ɕߦBH;0P*>wigHy0UfRx6̙-w ϊB['C@o`̪NTVpRtf7A_j~7$SugUÂ*S嗍ux1^L1>sť?1"C㿤]7-,l >,3lVKUT%jq7&0-3B/ۑB>7JAt+mbx/e5=zWS05ŗSL y6 "Ulzb kOW; Yi w$ Dt4UZh"#a)hHMJ:N@!& Di Z!ucQ6i r{nj9H&LU ףo{A%pÓܱ|DzH븸 .~GAڰij8MgG~}[~}r _/yx!32 cU'~$C`@ e?)<AsdP; ΩNzZY`4C rܸx΃hQ.$䍕y`Β 1L.?eΊ.I}n?`lk]+ݷLVط~;?@jsσauֆT6.J:roB~9mG}U=3%P + kde~h30YR ]KcPon)ZԞTW_F:f!-?b:&Bp//:w녚u,W"\|*<ʙQxaBYzxNGU΂U*]uIQՑ2 eM&N"6@=Nj{* DI6䤁CXC4U#U TjIfF- U3 sFܹT?#Q(?3W;v$KKa8>Ի7Io߾$! J*z8=(0YTg.r CU$Vysg2IE`)n%nի"g׃rҒTDـ.<y N6aHUgP_]`B J$3|[jy?I C_H~8k/<̰^ U;k 橽z ? >lqg{USqq;P\>?)]-ʩ̮/|HQ i :R5!`JKәF]@yH: DH:.5źxi vF-d Su RZ SQ6loJܝAlLj4\/t8[2jjΥ/O*Lmy1szJcʞqZ $yU7|aY&!Iʭ kJ0:PR))_]oDI/(#&0ruSS,zT%'D.2̧Ƕ "ZlYo]8.簏%KTe,!4UȻ#eP(ko09lܥK/ D/ DK<8$ DFRJB=:^*>G~wp[;nj )Ùv {g_Âް9!.H;{EԠSp\^{Eb vcg{c=ٳCHY->La0!2jHm#_&Ț̸c _9 kEvv0|Ww~G_ZIz e${K7Plb =ѐ4`p)Hb@IDATw`oBBzP"" ,A vA}φ;V{ÊQD v* 7-$?9"w>s&qϝٙ93݌bN[n}۷[c9=mؖ 6;^#>ٽ!kGSƏv>[z# z}C&m! 쒥lqNvU7GE_~׿.7LMVʕ~䌠 tkYOul޽uX{ QA*A *D.]5e=;bJPN[]o@Q!-U ~7ɃOOS3=nGM<>OU*lK.x:]uJK\mѵ3ٷF}>x3jf#Q ,던tZGkܗv <Tj@T+a_(jL% z6wq~;sHDU;BZ@ɓG9DŽ$þ`!f{ȓ*$%~^vK_4hutmdd~S&S];?{aCTaYaq \pp/^w,9X-;؉S_k+^6DIW¾Pv՘J=l*B9Ww1QovDTO kLɰ/L_W]y?;upڵn]~t.vp WW_m?EuNo>guⵈCrH")#\n%o[w,w4ɷG$^ځ @"@XʮQ3Y<](;\7FC /j@TKK,!On/>D%H!օr*kxm_q ya.j:~̙3)߬YȎC6l!Ydv6HG mkyphJ͢5-^4DIWPvU92_O';8JPv]o@OtD@@\r%yrݵǒ2f"@i®?6}^裨Ճ?A냯ݩF6a+١D);4ي7/)…n]3e:VȞe濶=4smzi]=[j@T+A](("wDoa3BٛWzcd:DÔ!HHh=A]a w=+>԰yxpQis9{JJ8_#պzj*V(_(ոb]oH/-²N@nh_t1LRJR|HׅQ!/A *D.]̮ kdPwD@:aH(a 9RC rA *a*7mDہ컣04*PNs(v@衈תOlkZ,Y8}ѐ6oR0ǼO[fw |"BټkhNZOnlw3~$^G'E% Q $Tt0dvM]# 䀊C B  +D@1 H:"@Tf@LJM-/,50 xEh"~TQ:󋰂u 6G2hh\ xϡr(IM' 5YVqSAAWP'~F6%?~k3׌]EiJPv䤁]oP_;gmoz:{nD QAmz@Td *PR./M;*jTt [`JXW0 D%< }Z|ӣJxFF;/wOۻf$+`3w}VPC[kO-~Ux ?y&fMiUpThxtikGw-U/]9kPfp N᧘^zyuX݈, @PRoG!!#!U&]%b@hQo@L4ܓOLӖjvLM^Ӳo52xhc%YIlf^nk]o_'Ov>Z.o"xnݺ5Y[>&O=:K`O؃;7,FW6s eWC:u ou-kuX{ QA,A *Cʾ*rф)wDP;bąK( :!FE@L4k'%{&EjgZ'stDQ[lQ]o_[%=F}QA髮hJu4&e~igAO7[MoNT׶7֩/iZ>pM5W@^7:@.ޡ[e@TJ@JD eO 1@3V1gHa!=7C:Dϡu .i1k/ʫdwO{,"lEl"Hp4ÿ"3e=5A)tRھn/mM^NMd>~ 6 qotuݝ9gZ)Cz"B *y7v <Tj@T+](* ]9"t4 .P 9C  IIcڒ3gS)n؆ o _;#jTo'9G'۠O!n+niI۫֏O-R{hfp7߃z~v <Tj@T+](*=|XwDD_ V٦M=UNyʑ# ]D %T3/!۰Q ߷h#9k>ӧO RQh{Te\vlK]o$Oåy0 ?OVC#a/.=ɞsYk_Gk!;P$@q)LZpB DNK(fS:)!؆#3v w0m4*} P¯D6kl^R)6}+v*[T@QU(FeA{%G"\Jt/]:ε3!Xqo!1ޏ?>/QI_ P|}0FĔXB:b_H(7`! D4Q,E ~v?mG_iq߶ ?LѐPp=Q+'Hk{kޞxb\~ I68ѺU j.1~jͷR.6pwlws˞{e_Ӥ ?!Onw}"B] ^R:8$}@a Sb m#bhh-X@ ¤@i4T @"͞2i}=qdk7kj8h e=w=D0Oxq?R,mOAΝK=G>O͚5-/Սi;>! ]DC)Z+A<5 Ã`@TxI@JDPRAtZSRzI(z]:D}@B J D}=}{y5y%$P&p=Q+'HxKZ?nySA\R#e㨩g]p&}ᵋR/ %7A<)1tP rX;{ ^R;H$]@q+)?~Q 5 yD1>DTpck^'{I~-.DpO i'-KLl;%{׮K~_*uAV⎸dͩ!oUq>Hlt"mL^C\kBurɳvt݅Q%A *EGBB #u/I(zWtK@T#] ydɞ֣I\(ឨ$Rs{,'SZb>a}JVӷR= >+K&o;k \84X@fJ_|Z}??ٜ_d^&H7Ǐ79ƷQ IG Pa -G Խ-b*P^mRC 'L,QJ%~խyvYNBYO|]D >xk_i >ާuNG{Z{=fO-υUE+gY,QRJ:$ƿx%+e!Nu-ϡM?LbQ61H&B\z@T#(0 @tIJ1ڈI( 6i!&D%q’ Ë/>OUͺyꐕ e=w=3{%Vc Z4g?w1m ةi^;L^˔spyQg0:נ.m*K(MG_/\k7t!'K,,tS/xpTz@T#(0 ݡ@t1HI(Ј)!&DC=a)O9X߇طf @w%4v^.%{`ɖYKvsϦty5PZ:ZA *Aށ "#Leh!W=B FI%J"?bx}ä!h',ew}ڸb d/O2BYO]D l׭5^s}o3LI}kuYd?>޹Y>3mIrw5PE+abJT',az?$[ ƊQJ)ǯ[GXI\(ឨ$qNF_:v.9޿f_b5tL>͊|GۿpE^0^w@ߴ6i}`R[Ӧ<>G5C@{Je! a/䣉܎7&e :@$=]xA@3 1J(PWtN5C *ݞõ%8dmE]X%kkmPv"ʼ%벝l"?ǧ.z1vzgqr; p>@Zv4y!rEoo]3/@<E#^#Q dQ $.P\Аv:*o!W@@߇hB K)F[J[7/b/b<Gއ%bۯSfV=״}s)m'8](贆hbELݴI*epC{׃?8 5DpMB *w D%H:"P#(fl!#\W] w^[*DsT-@TuR,9k?gzR?YATT.`DEeT#_5+V߾W&At˭*?INHuyAyѹgKv@R-M(]~z.%*TbK/^O^u\ 'ׇ%;k$xc_aQ!+A *E"PА@й 2-`K(μKD9}+ȖB *4>a)Ň3g#g8fCnAE*0d鴡?g[h6ߝ{>>(J?OYlKc{ 6t$M/XP-}ò2&XqQݼ5$ʳB *ށ "鈀ls\uP[DwA"Py%'rT-@TuR,!ݩΧG`12+][]k]QB[ Q0=4/NZw-i}3gFU~HnyZub16YJj7͈cD7mN33y~^8G~&4xc 6Q-A *E[# d+D:M{@\7e:D9})̖B *t"O.YȾ\țB[/= pvl<^2lD~)׷Ix\05c!3_H$8iv>4\x) x+lt+ǽf&KW 7&e ;P$U8rހL@4s[Do.PymQC ӗl B K).+J0yVh@ )7oy%K餋]팟Xu AG 㽑P]4*,=͜9Zӂ*&?mES*]Ht+yo3ȿh! kἇFB *Фw D%H:" E8rF`&)!v.1}Х ޙW:DX-@T(uRCi Qk֬|l\UQ(,kn@;jש rlT-Jt5ζ{ cb<n-YׄyARh-:*ggP+.P4 j W byqH 1 +Q HT',ZIDp@RJDQt73Qo!C/]${#/%?> NXJg/dfR3E ~›cg?E[!kʇ eSxpr_k^nNRV}7iK2J`cC;lv0Ѧ-J@I뜀'biE=ވ )Q H(4`)! D;t"'5Y:ĤpZ@TB KurÏ1o 붥,iد[m_n|7JP)=hʓʿKyhx%|G#8=WiqHDa]{;|i)dsw7N ]'/ZOc2_O~ذR&k(Q.A *Dœ$?IPto!󩗴bOS!Rx[B * v=O<7 /ރlȚ!BTNNIO*i{'zU+;z=r?/p,Uxmcſ|\{y^xh>sJC56It!]8p,'fМ8}y'kI/B *ځ @"銀TI3Khp4][|AOFk@TJ@ytuɾ5nwxjfRꅲ4 ܝ #4N:y8}Ёg6ύ?Jq]|a{pnn9&m/Y\4t>bhB_Z7.PI_ ]3 'oH;4q*A *EiőS2I(sT[{'=^{QagKy_ӱ~l~qIi p.äe$m.˸<ʔ㧛VIlبQ|72~Sۥhd_w/quKڞO;.P7SοhVBd.g]·v2ޔ>ӶC *A *E2-)%wJ(tSηv'7zw_+̖tigRHZ5%Tq׸ elp0לhtO،m͠]{R#v젣6gbUR2ŒOEz/Zkt>w|ϴ,/]'ooLQQ Bt D%H%`@Ix vтNf ηS=zoxVD-yMųOk~ϓp bMv-9we7q葌l2hʨo+}oFԊ֖_w[Kb?5͆so*P#Oj4+NU%?ÄS7A_V5sMbMT DΖɴL(imNSC Z t%-"wiIC ؃!E}R,yyV豇WS dVvz& z [/K%<2|?''pcr?Ǚ?N%;)= <A:v|H(pO!I<U)uT*Q7 ^D-Q ,i D%P6?br'eOJN*j79=]K@tWrC *TXo.T,'۸1fkaAQ  RTMН=ڽn1,(⟂w%-vy(S/bxoW1uȅ %;42c&)I(_v`T~*'e_qؑL~Z~q W moԸ!3u1NF@ *Pl@J`LK *xgT!;-}WrR@Ӈ$QOLoШVD-袋N u٦Mil%P6q wbaǃ YwZ`ZYg}0ٱHoK–^jR~ [:P~ɳ㧲l)cDTJ+}v]\>j ˶T'fF1g)aч-ԐH?fB|[G1J*wE*D>q|# &bk@T)儥4{$NH! ]%M*.]!Q??~|o8+| hJz(eCgZ!&:Ј#ȃ}3EEvGڬ$[-NnSpR ~ҩ䘱Bׅ2A7YkIN={W/<޿zB]xmJUƑKƝ9j iiK6yژ:inJu D%Hz"KxjDNη:{=uC ꉥFk@}ցrfSlXbUb<#C0^~+-F mI]6bp]{; HVv@RO6nL~^Z.ms3Jʿ] `|?ѐ: ց "@S#->(jܙ Hh[Ohy'j:$@TK@ j&nRd;_oecQ_(彾ր>9*#eW}^q͵K5]Ts%;uDqt0οg3^˩_L W`eKosgA @[:X$[;!5[{M|W+?P@ #¯Rƛ QcK2F=YaRoOBlGpN?ya|Go~Sݕy-J?}TB#%wM~:x^6¸cǎz5v[{ð:n/bu dO`B(U%/ZVHl WyEc" ڣYw:ިq* A *E2-%ma隀[<>s6Q@DD=mo¢  k}aaOLP6 yw1x-#w"n۔T|gg}^G ݖ0c;:&Z``B(M#:һ!^s8S*e+O$& ;۪?6j+Y7A@Tҁ "WPtL+DLjSfeO需@ 54Q} jF׮]]MF o߾L^K4lh/6`{=.2~/O _מiӦ.ZeV[{)4HےD8㪠+kر cQƄ1#7Zlu>Q!jK@TdZ~J±B~C V8e[ h;DPC!5@JǛS-S/,y[u ^=L%jg#z5 W%xk܋lErd>,>-xVw?8Bds5Zj6Dj/V&l^Rnqlja,|j:2D-Q ,i x(i N@t8eF[ XYC 61B;TUYƛ\QgKzWȳ[D%H@@١|A+/ßH׫W/tFk_ʞeƿsc(Tk U 9bLvyxB~A,xc:Y"QA@Tҁ "WPtL+DLjSfeO需?NA!#6ޤ@jKZ;Ƀk֐-O'u.2lFUx!Ӭi6oؐl&lE7nvS/~dZV%֯_?mʶSxo[|* Rׅ?zmr9X)^ mgPeoQ!(A *DS%8\nC i+dl!tUT&W \:%z??E $lŲɊۙ>ݎˡm{^(+C#p.Ü~jiF+>}imQloj QO{mvziv>* ?fC$ A Q 8( ApB2H]9' se(SFD 7)ځBdOȞ O71sϿGbQ酲Q17"jc;8) v%c_t)ч݊pK9Lq/F'vbm(P[oy3g}8##E& Lۋ@܎H@J tE"0dvM"::@x9l @IDAT(jh"Uix4UDa #ɳfżp15S<6+YхY5'&L%-ºdY?P$k?Rt_޽fa;W_~rӍdĶ{ԤU6QzFdƖ!|"PB@IO KZW.ށ "#Lg@ 50! t4 QOlo ӽyVOM$5/Y<;".\g ċZI t,%?9[V\m(a \[o|m1;^x~~_o> o͖ɴ  m'bhhlxY3U6vB M@*+x2BЖ4qD.#;Ϊf&!Tp8u6p_fc<2S+i2K 'Go 7M65'E'KVᖇǞEV7AųA *l@J`LK1-vB Q50'1)D=x;˖ߒg읷}ox" * ZpZ[h[S$Eےo;6'7E:Ў6D%559[/ZO^+%]GӷoJp2?ô`qD-Q ,i @ N>s6 A bVD=x;˖4yd.$;xP@*KBUA W8<>dlwS~Uk絊 4|2K ܜ9sh7I6n*Pkq-;[ +xPzhӕ"{igY1޸A *E2-ĴxB :2ާtNFO$\l 'oq@Txҁ~GxF{j%!Tp_*nsR˗Mj}QzСdMlv!ፀ-7c0aG|@'4+ي,J(NOI_.3W$JV4י~RpAx.  /[:X$@L'uT-#g Խz"wD-h̙s$Cf&!Tp?<)vŊԀ &lHy-gg8}m9 (UNrsmkFf;_Y)K.+ǯdpXt_Wї\|8_uܙ3jx. /[:X$@L'uT-#g ΂茓\z"wD-()hO<E$-[oN$};Ɏ?w3 wu~7W?y,j~> "P9#0- _;`Ef͚FG㍻@ *l@J`LK1-wB S7 wDg@)7C *l@';٧ j$w 6*= rJ2.= fş%pRNHdG_7}iԿ9oRK<Zj x.< /[:X$@L' [@`h(C@LG^D=x;KZڥ~oM▔yf&M"~q͖Nx ? =IpC ZX_MPEHPڸx pR.=h%\;Rǝ$'&]o˗Ӂ_L}oC6TYT[JE~#oN/39 ㍻@ *v D%H" Q"u ;E? @28 `qQ%-ZdƏ;Zw-Wج$x{0OTu2{?3t-8hPRǝ$g%]o?J=7i%b@QboL 7:o܅Q%A *EDG*X!"2H1ZC S@1x`qQeKZOu:t/PR5- '" _ᵾ .~:|d :oT-NS7H,{H.:4~'C3`EW-7>Oo.D5aq QeK@TdZi8 mZC 1h``qQeKڶg2zyě KB ET'!{dG>>zm?O<҉[Ɲ?R,ʕT`=U{.?!PZhJ2s}sck[<#)or9,~<;do`${;r /[:X$@L"22-㽑p4 @r,Y!x;˶ԭ[kp'"(kOB {zu x债dx͛7y㧙&26$2݂8m*x;VG yUw/l.h ?{c|>[Џ}aN~/gݗ3DmQ 0) @ RF h)) 1!"4EcI'.Dmyh^ 2'BCFwoUFxKuLlw+?"/Rn6g4ߛ6ih9\3t.Y|nmbz@͢5~DmQ 0) @ RF h)) 1@ LEaq믬 /:P7^,%EPמPpw9i~ܶ;{&]oh?\/Upq]ɾr7x"I h@sށHHEq]Sͥ}bXժy,// 70m}(F7M%oI Qb[@TdJ)\4v6ދWc?܈A k^;;~ /:YgN> 9U 3Jȑbnq#x ɴZk@{mw= W$'UV?̼c\o?v/No ),>U˵%# eddN_i VRlÆ {,?0޸A *F2%ĔXo@܎ª/҂D 1A Iw`q Qe[(pc Pˎ1I={/_RLvI.]3xMž͛ öq'(.aaN_>vosNCf|7{m%v OԾp_Zd )"oq4mNJӵ; /:`$S@@L1,hx/-\l ViaM_.ƛ|Խ :P>]Aqc~1.Tts?\)VKzMT6*Ɲ@XA,;1cٙ?vdg͚Ev¥d[ْl^5qD!k֬Y4=ւ8DmQ 0) @ B om.6b 4İȦ/Mz>^DT\3;<ǔRz҅dkU%b)9ڴốQم.䯹YfpI!8c=n@6/CBڸE#wa=>hxC-~i9zQYkG r|4}iѢ!x.< /iQ @ 26DOGA{i8&!x ; /i,tEVĊìkoAO)reS["'!T"GN{23O> RJwdZJ>cJڸMw!=^ԗpCbӮR5m{~@_sOCS~n@T A *Ddl65DmmfgdG^7}.Ttr_qG DhtQTy'~9Jԕd{6V/Tn~.jE[Ɨ}C~ l2d1:`$S@@L1,hx/-\'bq@&pVk)߫fJOri= {!yf%+P^ѧ:uh m*w<風|TS_$xr3kNF״pG@@,^kP8;c'5~oju Q!o[@TdJ);m聾 D"1\^Q/ʺQm;}nuKѱY*TtGqv&޽S:j mV.*wB !@uPdEuTҩ~ҋj .љx>]*3G.ע4fiI'껟U}TR):`$S@@L1,hx/-\'bq@ 7.-}yUJm@@u{d;O1UB_ iӶRgOU[~}E\/WSLj6A#pr(w.G1<&^&}yd낀4Xϫ.gU_q#hכ~fњVQ mQ 0) @ FƝ}y8p;G@ajYC *!-AN[f% h:otO [Cyك-_NFۻ 7HVmnNwpT{;!L#~V xm-I<ֺnOJk(u|̿wb)?6V7rrddV 7:jT_=":`$S@L%;Yp *wD =~8@TBl[zA{?Oj*.T̢5pjrz蝫޶ٌ8qeΥ}hO>ߣ6vpw*Պ,M3g1cBvnE6wpG@@Lx5'c%?Z-[RLΐk;v~d+Q m''>lSyFEqۘ-BbSO[@u57Plw¥\K/%j}FIx*O%oR*A@,/8Ey1+JY<k7@A *F2%ĔX"h۸8O>=;Ga ^}D3D%$uz<}x_@~zDNj:CɓK-*mڼJ%)"~MM+~ _{(X"K&T""[2)"һeIdz~&ו:TIo倿7n~ u^KM#{㩦Nbc@,J }A9q_[6"Q.A *DDW,u D{_Q2(荛ߣ~!xD%RR;KO?C<7=#?Vex@v{kmi&[Ehbܿ<Ygwݕf͢|mڴH,ä;R4p544Ɨ*$Y5馅+x|_:9k`Mx Q> wl7D%R;H$]@t+RǝAD\ G \ %.px;~TL-QH~\G]d%|6>s]FXB!ܿ_]~Zf$ 3yoT5O?3u\b˸9pO ڰ?ȃB|Ѳ:l@(Z 8u;0 'bO Q-Q ,i @ N[ƝA\ eqX@,@8+~t;M)Qmx-bQ{**T̢5q_x E6}4~UW$g;{ :¶q iiCGh s)}?5V<}xx!rj@ *}_!{f%M*f 5poh69YV<ϮBze t^RR L4%6DC-=}P#=姤Hp9#x6%mkS9tX3[Ssj4$˼F4~o6Qi[@TdJ)DѶq'2p>+w}>z<<(荛ߣ~!xD%RuǓӧ't 듦pq2yđsVe$&qMN}9yY)]o__raor5g>]jV%w=?PgےRb">?oqw+w}>z<<(荛ߣ~!xD%Ru &~}9ٛoR(N"Tts~Ӹo̿LBqܟv2ezP ^gh!5@m N{p  %lYf}gy{}'52ih ߱yDe|~V\3g;zf[fњku.!u D%H$Kdmw""p >07~VuѵkW)^W wڴiԢ2oYnf ?r~"oԴ|{=w׈GT?m⼁Wa~)CÆ5-ӶqGJ]o_]ܿ9\I:ˑЂ%j;)-l|^Co,VW##ZR;j֮C 8X)XWwFB *ѰA *F2%ĔX"h۸8O>=;" cy~- zۦ'MDd4H}Inu$=/֑w&{!do^W]uY;Rz#z>ydrE6$=U-[m<30ko>6Bz衇l~EVt#u{#DmQ 0) @ FƝ}y8p]!9`;t{v8޶4}tMߦ$M*p pr5{_}DЁlS擲ѶqܥN{=us<y}+ i*FiTeV5ۥ Dtmu%#v)H %M 8u D%H$Kdmw""p us@̧ȵqq Yə3gR=ۋA|ya!ۢ"e+L*A" ܿpߙ]w[mw|pt9Հ9`Bl gK괔lvU]5L43XLם;;k1߻o# u D%H$Kdmw""p Mc8`< WSNY&/B֨o{Ë,šl~OMv&۬kAu8Y].u׫r?p-2k_F~XV P{!p_x1ys'<נxp,;K5jeqgqK.i)>MCj@T+pY8 +Ձ$)!Gp@ )B *0eTUar֭Q=N곿^P]o3%T*lyZ3/C%<:uXN:{Rǝ zczi/<,<~jS玻z8?X39>wŧk׮z:ZM-^j@T+pY8 +Ձ$i!# |l@ YA *0mT8[}z=$ƿĽ_8doqQdJDዤ:> $*pj9Q׾Vƶ 7RAC+2&=d i){y2{>OPW3;N2 L2%!8lWey`PB%6XvPW!L= &rUY!mr?wmd''>F}|ӑuIҶ ǞuVĶݳmKFU=3|?FI8DmQ 0) @ FƝ}y8p!)rnl1fsD3"N+ N9d.Ӝ) (N-5,'wOЪN嵰gggSL9houmzz;ӦL";slMvɊdw۝@wGsJa 7SH>>g}!q :`$S@L%;Yp ẹC S B zr(`_t5M~zC* ,Rs)6,Ɵbz|1s9<8!t5,8b۸ㅁc]YDtFX>.l~=JVa%}0OB;JJx-if0(/~ޣp@Tց #bJ,mm܉ ϊ'@O7;FtbD5WSin-[PzFwe'HXB%Q>ljaq?2g"7gZ~֢ $[mw F $PN^K6:Q=JA,\3y!cA|ږ|ݙk{+{=_  ?:`$S@L%;Yp ҸC z tA  I8,.^=H>xNYP194[xHӟ&=CWS.}>mر$Y .}ܹkO#}nA*mx) ]3^ NoDwt9JAL48>X3●~(,wxٓN:l=yyyNXJhFA *F2b;tY!pvs@t耲A RX1J;ۓݻDz) v8*Wtu ⫯} :x@!)*DA:M6ӱ vқǭ"b:7tɚ!mgo ӭ}BH*   qtehy~Duϝk_ߓ"ME2K~'?H8 0"UZ{y&ba_fg}, ~>m g};/ zczK؏ 'L|' {_Ǚ9-l53_ ='>@P8Ҽ\7&ேT{mk J;]SyG-3!Oq8l~?^]qR}ŞQ*A *EDGB$} L{Ȁ+(+n!C@NaJo6+d3ӳ铳}}E o5>þ ; D $K" qWTTP1k4 [4b 0""FA&Ȱ 0üEֹOW_\WbuR`<# >8.ùOK.hDW; wDpo?p}ڵ/ 9Y>}T yGua+ꕨDcv‹{RO{;5jG ItWR=vn[]7{W$k5ڡUWv7M)E$&~JP>(HI p0Ԕ ĢVuG \=Oyv<ؽP#A1@1KP1mQ8 M7o\6V(~Xu+Q>1' ߑzHZyǎǧ%PZ$6n8m;T&u'jS v}v u<@؁ո /J̵|)ӾnB#A$] $P AL¡zQ psh w$YfH}is$q^8obZi<v Z5 $5j@ ,h.Zw/W;+`0Rh_*!UiLR"+*۪-bmMOW)DZc}:TEBZ  j!AD bu:H'@и#A2024CcHC]pqҮL&ٱC&o]5ʄw[YK!M}643dX!-Z]XaP2` 5^MDԞH!KWVn ޤOHozb8mq 9\7e;Iě7?ܞqTԪ,Ը:DAB( 5$ꊫu> O`7f9\rGefHQ GzsX*S9!.;;6=ˇarT)Jl$&Ԣj -&i/$Q8q bu'^w; ; bv\mQx }L+=Iv>Xy4T eKy#Ҁ)!٤kJ pN#'?3⋀_,4w w3 P|[G=9 c[0XњW0. 5.;fUf0QѠ壘K/t,ԸH9W p*x9NԽv=v*w$ܸŽDAW/CvlWgXy4T 뇑Oл67$Y=a*ڼ㸱, `zjE6 v YH_Ԍ>scX2ҦG$mk?L;6\~^M#Q^(ql$Q8@T$pםw; ;p b|mQx 5kxhFO5qZ=UTo[oΎnΩ]DO4yU̞Z/gP5~N^E|3AT^%/c@ܯ;nop_oxq+ג<9p>?/MƧPbE^7Tn^(q/' !q $¡PhBܯ;Ѧ=;pfQQ(ܑ K~@$ȍQ$Ο4ۇVTwJϣJF5.S9KbYT+ه"*vG _D2q]7aɧ'&`TI nSosꫯܹsI~8e2 &$zqeq c TW]qUX/D$X p(T"1ځNzܽhԀ=m:w${D.%Hs>H~\l| c|XfJ*z|o>rw%{A"[ڵ|Ji[/0%i^D_$oIpIK םp7:6?JL_wAU[^⧼xSuW_;{ ^q $¡"AG{dY?id~ bfH1nQ8ϕ Uhe"YZ;uf9ߑ믹' { ^DU \wAY,lԋ==w$H '%DV.ev46UƲbPu=o$ رiv-.6o)n ++M \ωN+ZWvK?~Cq:D˵B(\ *x9ڵN\胻]O;|L-DCMQ 狉S*mV|%ʣb1??}Yt5Z|'*ܳ=#w?jϏW ?fQteq×YoD)qH/W p*x:NԽv=v{vܑ 1="At9n8ZxobPU|(iѲe۩X~15MsDq/Yp7VߐЕe؈O_?7Vc惩b⭴mOO>ݹP*kӮV%y$Q8:*Ę:.ô]dXjpqG b2W5$³^8_{5Zwy5^SO<*vЫR*4$Wd,חZOM۶m WW>[ov\Y<?IDATvoHJpύzNbxcF7S߫hub)=u_>ۓ/Wh"!܀Qr5 GDEG9MW;yb ;8O'Jp -D$EN<{!A~sVpF͸Xu;.>} jDrb94%n)u ?s5UYIW>Gqn!+=;[߳OD5:/zZ4rY m2 y1L`n}1)g^q/v#Ľ?H/W ptLT$1qTt'л{xB"AdNHZ!Au{|9nXy4T Cy9UյH|/ o쮿7v}j+;>w;܍U7$t%yԠmv$:Ն׀gy" OZrA׸ Vnu~B5ĴbAU?ԩͿL+ 6_<KՈ{ ^D蘩Hc0uzspWGdp)5D1}^Qx Ihӧ]CrbPcoFo~:4ظ7^I?~ Qj܋eFV;w;܍U7$t%=o $5;O7Sw+N;9w'j㲃i1l&rnzĽ?H/W ptLU$1u\izɰlno?1=SѐpS"A~u9uTZ*&s3G>@{G37^'=W>nv<voHJpϏwii) 0sL7g;Ժܜ~|Wv7~\٥o_ZĽ?H/W ptT$1szA9#O2I8p5D18zo$CqpΚ5Vdg.//'}Μ9$?7]>:U8BA|z+Z֫W*41BSjC{h/w;{,_p-k./oEzͽ9&˦eԪk$,zk Z!A1 iz]wB8R_/\5P bJ, \Qr5;hODݬQO]r7+xQ+ƽ|sq E8WSϡ~wrk }\[fn >-|deq] Bo\ sB("ATS3^wp!u]HuxgD( b2"Whʕ$h 眹VO/ZVtxmMo jkK0zP$xm=GC"Vw>}xc\o=zӼbʥ-f MrӋI^ߑO_f?~Câq/ ^DhK*DK#nNı;`Թ! ] F1 ￧p$|Xy4Tw 3N̾;ה7KVM41]B}c(p+,, '^CsG{qf8fy͹ 勷 G.&L !ϛHHĽ?7 A\ $іU$1^w"yHT _.c Eo "A 4,Q8 gii)+N!9% ׫c7nDj?nKiM~.>$M6$~q5I〻?^Af7go+s(ܹsܹ_7ֿCߒ<2ĆGe˂4M47IzZSȗSM7t6lbQr5 G[R Zq^w" Zvގ=H+pK{.]$rmѴ7sLX9gUY;WsΠڵ7=s\n井ƒN~|?g|Jso{L;)ga/ո[PP$x&xZ;h}.ym_Y#YB^Tk;ȍwB,3]|R$Q8ZYE Bק sivvoHJp-y_r%5=2>FF.ճ6<~rՒ*i[ē6FG>¿R2یwH$q $¡HaRu'bPvvƪ$P8ҋ{8?*DCWտL+H޲wroD9U\lFuqUaIY4VLz"oz sp*袋HFŕ:gp=\FL©pf;j&/" u_0;N3#Y fk7v^N?tG=6*h{ p+Q86O b=-W;iJpp7V3GhH+3qZGC(|ZM|]Z. y&^e}qcDeͼ89yz+|/q ;FT\"N;%O'Jp mVVo#5`&uҨGIN<j^]z惹% kO&9q~d4{ Dp pp*<{J]wR.2n)o?nfѐ VfQ>m yP~%W6QA4t^}e#$/{7?`K.N#F5Wެ/xp7VߐЕ[ZW^MMLfbIK/ݰZ{ӛS-,VH)'\ @HT ݓzIZdpp7V%$LRrZGG(|zy*֒o?>S`eq`SN='yV>ŴTScF[Ap7VߐЕ[Z{51j,ɣ_$ޛ?O݄O>5}iP'=;b<>-4אyFe^H'\ $9Hsn) ~I]'n*#A4d•{?:DC1k-[QA3ѫ]իO۶#}Ge%MzGuyKk/ˊ熹ssüyh?'9e;$ןm_Dadg,,sn[ߟ u9 = Df pO O`hׯ;YA-@$C-+Naʋ?, B(z]{~IC|%R8#~ΠĮ9z3{l*n׎wSRq%֩_3Ng c?קg#YҠz6fnSx+Q9\؇)eR>béhʌ/LU$d#I A] $YHf9pN9 O'*pnC bS4ɗ{!.B(zw>HuvE$k [!]Q\ xp7VߐЕ[Z˗~KCsoH.K3es јc΄L9kyGP xM(ǁ垣vC(\z!AϠ"AՁp} )31/ܬƷD;h] vmk vը݃x^wl3 u>$l 5xciV{VBk1ύ9>2A5O^ĝgdױdXs;-GGv$jժ,§tm:#A] $T$:_w TCX6a0TP wV 4p ?s;v#Uv7ooƖ/I i媫_ :.!{xaN{N5xqA_%}-7fN +_#_A{]qVL!A] $p$Ɂz'6z n!+]4$pj=hSQ|5|d vհwm>q#?g:RWjneJ] gkopϕ\0?~GwĂm4[ǝՑ&Z2peKA#s|`2;oMCW2Asu p yR]TkRn!+]4$pj=hSQҕ5jlSb+>qTo/I7mʧQp{w^FT_EK;/Vq+H.kHfQ/oA4rQ/}\]v<voHJp-?&+#d ށ1u|>mMrWWP=kϾ8Z5m;HqW plBE+qzu/;>w;܍U7$t%QNl +|JayA%Y-!k$dEEkNF'a U,X\=_TtK%?3Gh:)]:xp7VߐЕ[Z W _3ks o:ww*{ӛ[pAkxi7_9lh݃D#20DW pP &q%W p7VߐЕ[Z ?DI84V)E(|ZlZᵥMUڽ`כ=|O "YyyguW\rԒU3H,&N2]}ܜv<voHJp-'?.vBU>KbVP}NIR68 ;b$+AaHӣG|\^^- n!+]$lz=(魸SQ:򠖴Ҟ+6&Sk-҇s!EkqڭdCHH>>IW_\ x p7VߐЕ[Z N@rE$kTSh=\w§ٿRNAKa<>tTGS_/gOXǟ#w&zm(rB7Q v= "A!OqjQ*;w;܍U7$t%Qg=lƷ i OGF]5ĩ7]܅xݏ]hĬýt$*ஂ{ Cśqp[ϽۢIj^3\5X6n-Y)]su[_x@(@H 'O(o*uyKk#Ab>Gz2$
  • X$/pp;W|IAMթq\; xp7VߐЕ[ZſrjSHּH,;[9xz1u"u'둾tsv)]yC(P@H 'O(o*uyKk#At:DAFh_b\by9?gJ[g۠Ay`>;w;܍U7$t%l4USLҫ*sUŲmT^>r1Ʌ}*}E=UPTo[G(@H 'O(o*uyKk#A=2&SHb:ƍ1K}0e7мnjS"qa6-|ÒT?IpÍ$񒚀qz);w;܍U7$t%֢7/ydCj%MܵTi9k'o,P6W0TJJXO2D2Qs= C%} v<voHJp-E?D@;V`kFcX~ kS3@v/' GYVdb~ObߪoJ$񒚀qz);w;܍U7$t%֢ƿݩmi%ຑKIo!\V\-wvaPO9j\;?>7k/^l"A\ $Pqu7*uyKkQQz Aq?\1շzXoo~82 3U9w;o:o>5gN]ƍtLAOHw*uyKkQciW_X:5oRvlL7ϙ)Q5>r>H p8T"zGoCn w]Z#AHEDWL/owߥ1ͧW%7yslwՉKOﴎ57z&nI $zܧ\t ߎ3wc ] tM4C&y#O`c 6zOyרp(=Q q= C%} v<voHJp-E?D@;okV۶l!oIZ1d$lPOMy˖|:( b|3|L{Q$g|oⲵkŸ|-qun!+]fHSLWg6އgښ7;-ϬرO)mvSͩ<@D_ms(i!Ah\ $HEhU]p%qn!+]f? %S@(Y۩'lWΧQ$Fx^ї7A? DK];x^=Moڷ4OO'}-IN(l%MނJ[@IA7@;8 ܳFJCǏO53s?yX.ZFr߽d>p(ՋIe[Mh[HJ# %ͤ 8D$"Ӫ}EF8p7VߐЕ[Z "veLґ *ptV Hc|:r,ɞ'9iT4^[ɴ8z sX>ճ'?kU&'վP]x p7VߐЕ[Z nj?$k$Tn:o4ioY>j2.ܣ kZQP]{ȫoEn w]Z\#A+, w\pŴ3jЇHw~(^ϹkqG<LB3{!Ϥw5E)pXCBW.oi-nׇ0uZbR=;u書q? @H&p5a8p7VߐЕ[Z$҃#A~~Fn69 GA\;?&iN`lQw$n!+];\EK{U ʭ|&DW>c⧼!CXNck[Q8B(Qw3n!+];H BG(?\1L#Grp&(S_c# $'K/5HnT̯< 4 /p=h;w;܍U7$t%b?#ֻiI,Er|/ßnR$r z!AJ\xp7VߐЕ[Z-$ҕNH{c+.Y$GjSW<Eyoצ~@jӦ ̊quw*uyKkq N%mĿ*:(X46y:0:vE\ۢQw= C%} v<voHJp-ŕ?DIu$¿qTWXAuwqɱt~~>}Nq1n|I4.^S;^nݺn p'XxA(z!AJ\xp7VߐЕ[Z $s#Avjii)/hwk7g?tʿqFHr#yuבK\`i?4=Jئ .vLa(&Ln;w;܍U7$t%b taAHc+oNo$mr?f֬SE|/`**u${J<:jt%Q${"#\ vvoHJp-$?H/uh-7'91Ihv7DR[=7\vjlY3yܰ(0QP%uvoHJp-$?H/ p0Ԕ\p!qn!+]8#A\ ~ݺEH^^ɪm^3|C {{sW~Zzdٗ^L;.:n o?n!+]8#A\ $PIvvoHJp-$?HB ֧[xM ٩wݺNuy\{v\Ԏޛ{ (B(1}.; :%OE%2pq: Nxu-DP p|Qu3 n!+]8#A %ztD+1w7m{MHWِƪS/tI) [,wmw;܍U7$t%_g$WDW %fpXCBW.oi %qF(xJ]TZ#}Or}̓,:zyiϖp}i# ,T軙`(wE)L ( E9 O'*p U(Q8Bxp7VߐЕ[ZIDGw ^@\҅V{#~5UvP[ooN'%uI8e'TCi)z%3J7덊w;wc ] 5Dttp U(Q8Bxp7VߐЕ[ZIDGwtLl3gڷo?jd$̙kOC]r%շhтdqqj{/ۃ{؄ӏU an\ώSЭ=hj H"A % Z(qU7π*uyKk/3~b*x5לC+$9 ^} 5{l-YPkjSXB/5iV j` SR$WDW %fpXCBW.oi %qF(xJxCZU}gaan\ώSЭ=hj Amύ[A$kQ8jJ}EF8p7VߐЕ[ZIDGw ^PN' d@,*I{aoJސdj,a_SN9%>(5Aq {pp?=j=,ٍ q MxWPI$kQ8jJ}EF8p7VߐЕ[ZIDGw ^PG0V|>,Q۸YTzӛ^zqASÁ{H} pC@;{—sgp j!ADոOZdpXCBW.oi %qF(x9@S:wNZa^I;$?ꓜ߃x+|au[|ɓwnd/L{Ȁ3 T !rXT=`>j@"A $³Pp&-2uo9n w]K"::Qr&&O教DqकpѤld|s?=F+|IB#F3x2 Ã@!U{H`p3p'?H/g ,ttpqn!+]8#A {L$~rv :|p;F ˮE,o9yu8{Y/2ᖃ{|3SpvTϖTO$sQxj*}EF 8p7VߐЕ[ZIDGw ^I$~Z&uIN%4SeZQQAk*z,5 E6q?;NA 5E2q?7nw ^Dip6w_d߃瀻*uyKk/3D2W:iBb ?$3k5P(X3 C98{Q{=-@(8@HNٸ}~vvoHJp-$?H/$UURSS#uܸ "op+48 "~\pOG"AD $PSp.S-2eo9n w]K"::Qr&%9,2;b# 9:uvoHJp-$?H/g ,ttpqn!+]8#A ɓye&V衚O;{4 b |tp{`(ss–w'pa^^r !A $³Pp&-2uo9n w]K"::Q*X ~?_9 sp ,0,,A CaXpHT#A@ > ( ޲ߎwc ] 5Dttp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rp @UW0voHJp-$g/+E&rpFRDIENDB`wagyu-0.4.3/docs/new_ring_si2.png000066400000000000000000002615641314062220700167100ustar00rootroot00000000000000PNG  IHDRL; iCCPICC ProfileHWXS[R -)7AzޥJ! J AŎ,*v誈m-Q,,ETu7I]|3ϙsܙrPr˜`?fRr $)0bEpewm\UW8#bB| \-@hz)~H 9֑49Cb 3Pg3`%Ķ|vؙ,΀X ywq23m4&1Ȅ rXroa5S#mo0)ܑ( n8$~ؾ-5 PaA k2؞%B{47ӄ3b8K3#+ IB Wz03.Qm*%DBq(;6laa䈍P#l taPٰY4!ό bI\QR7 Pp0b}K9X%7'8F^g차 vķ#.0yYr؀ ?:N A8 İ Z{z?H`!\`=Ha qhO6PeT+Al@ Bk^{qWmď<2+1@ !-Fy!؄otaɅIGrNxLIDej:Hs&-h84g7p?q qG/ ssG}IYϰ^RiE1w5g؏R(֌.c':`X vJGWJ-F-~aBY ?C0[g/2gیc9 ?o6¸MwRcp)o:7p{T[,,piG wFd`"q LUL0,% f {pԁ6p܃k}` "BBhB G\/$ Gbd$@,Fʐ5fdR@!vA P .jG]Q_4 ChZ+ЍhEϡWћ}cSfbXcBl>VcUA>D3qk>Cxf|/^7Gx@#PB!0PB('&'\{0@$D3 ܛ,rV!Yb;O"HV$ORE'6ΐ:HݤdE>ٞDN!Er>iryPAED]!J0[a.k UœGɢ,l\ܧUTT4TtSS\Q%GjTK?u UL]AC=KC}KLi>Z>mvAdQZTTԡJYADWyrrQkʽ* ***,**'T:UUvQU^V}FR3U TT;EFt:~ޭNT7SUR/S?ުާᨑ1KB㔆1LJ-Ƨ1c|p,spLǘc5}44oj~bjjek֪zk[jOҞ]}AwX챥cX٩Ӣӯ+ݤ{^W磗N^>]KN ӗlb v 2|`D1r5J7Zghgoa<׸IffKLLi՘7{W߰ ZZd[lhD-,3-+,YVVe:gf7ѮȮ=۾!aCkG+Gcm'SF/.B=...[\:]]]^r#-p;=_B Y2qĦ0jXl4bbڈ&Ⱥ(6AYt^o'ULzc379;=v_@_ʸ{) $J'KKO!$N8y)NSJܚj6uӴL;5]y:kTBjbϬ(V?-4mKZ۟Yzrp{Iᙱ6';<{-}vT졜ĜC|5~6iތY3V$=o}^0L[QEl.ĪGgjm9{gA45hyvGo\`xA{Qe/ȶhMѻʼnuwSMRsǒmK񥼥mZSz̶r+?+ZW:\E\_ukkTZvs]w맯\XmexdcMƛVm9s C[t,~+gkGOmʶ}~{G*Ӫĝ;J/ջwG7foSKu>}+kqM)?h}p!ơ_Su$HQףr~]WY'Oo?1DcGl~sd)S+OSN:Sxl﹌s]O:iRS녰 .]<|祓/z;~չ˵6 ;;]~F荫7#oߊusJ6;9w^-;xo}*zDswSgϪ??b%cҗZzZos|p w`}{?~lϤX|iPА%dɎlhz:o@Kgx(_2AwF h2'=>/ Q*a3 w@F۰屨C04VR_CC[dp6O~ kJQK QlC pHYs%%IR$iTXtXML:com.adobe.xmp 1356 912 {! iDOT(Fw|@IDATxyXB%J "KڴXR*&d)JRJR"BC߯׭1sgYyg;933g/&yfi1}$u͑8QkJ\{*Db'JdX]ϗvSwJ!1EUV|  @&<>VnN[WK倶`yGI܍e?^^@727{[bK[:l9?7 `9`]_ZG P05ܑ9 (o|+@ԷFfhoۤnIltD#ti/Beڄ$A._WD늖x9w}ԑPӦM  PBwEum`G׽0-s `N5Rt@;eߜ!k P0(`zv =(s5 (ן@xL7'FȈfa{oJO&8)m?׿+L}4%t8=E2> |c{Ir}:3~dxA }@s3ޚ^hҬL}4sL}M7 ` J `   RA P0+;`)`o8 Ya:/30ubl/^[^bhNm .&1MD1[r@[*^^E󽤢cfiݯǧa"9UWQS/}%P0,"(n0 (z&@L 9-K,33YAASпԕS!t<@a59S>+F@W ´Ę܈!y>4[M,_֟vʕ3HηLM4`j)f >`d@CP054> _6:Q2 / Kt@ d}]}yQZ5MG U %%ERS g(u<W+{䖖Yg]`xz"͚5(뀂&t@0L 3$ `" &,REP0 `j)>td֡o{!gKD9xn:v8L$5JH @'7_YK/+@V>euIcGH9[}H]ap>-~S##_N5i#x@`OYHz_XO;kRNsF@4O Pt~FgOгe(d*)dH<&@c4 ` 6d P0-n P0u۷uņ%JW6Wbn~[nZ7M&1hgT t<`cFLO NA@_C=/:ؓ*=d  `8-sh2OJDcQ[WnZ^ Sw|?S/<Va@ ,@Cj SלʫL~۷oFVX!10Pr;> zKuwy3@G/56ԕ˜ >=kvK,}ke1 x@A dȐ<9jjEB]UϘ̕6)@M@oN´f`j֙#o@o P0.m#/(B>`Zgd_b\D2JSM!"@1CYfb XG`#Ce0 r?}*2!?0Q^]+J t{.C-qL&4VL5d`}cz@0S"@L vo(gee]Ԧ9H?g  #J6dz@'P?S1! \utz_*ȋx@S# 9!7(zC6@*@Ԭ3G p6 g7(zSm_^/ٔ3D_]BjO6H(T 7S͎~~Ms@+ XB5;$fVgƄst3[eMnοΎHL=Nj)k> 32F(U`YO÷Xi>P}^?Vj˻%V~B _ߺ]9ܪU3O@K <E P0-ȞP03j$@LE  P0uW@L}-N`IM ղ;;8f)2u)͉o%6e%F.1iWEb9ziMV;'ʁ;ÇY P0-؅LR@ P05̐ 7(zS@L=I[`kqwyVyrc/ξ'vʡr#T_> l vty~K' xS`|u$bou=m#8s|kJMMQ5)?`(L]S05l+-@@+ P02&-@kQ0zi\\!-']s\bp>tdr W]yf0A? -O֭[1F@=M6I7>CbM?Zn@2wK|,ʒH9slS꛰o &4RFLm@ P0؄2l$@FmR0$cWvk+ݮ]\s$v LsGS9 M uw/]Yޫ # MK?M:IMr? tEW}# Ĝ=?J<ZSպ^v ' ~7{L>䏀}(g) ` veƈ5(Zs^:* VY/9奇E=%,6+Lz0 S/O#3%/eʔqN.C@8wj%ɜpBbHm p!OsH@+ P026)@ԚjQQ0 {x|uέ(-~"G7MԛW8ED*"U|@l߾].pO݊s7"ܒ-(GGL3׌S+*cBL=VSϰש};ii/?K o77OW/4 Ε3qH,u^d@WtG;ob \֗%5v..A|/qLﮱ<|OpSv]fq"`] ֝[FAf1"`  ֘Gݿ\rr\$10Pט3K1,,LrWo!14!D"@_o8*|2;BnѕÆ  y` '8O[[9{>:Ab O߯,\+ n@L=&韂I&4@LJ @P05$xDGiL<V[6)1$࿘[݁!6"IzH0T6vk~/VɉM?y2@@vӪEc%v&MJ[$v@,{#Yy(T4֟k>OS (݆3 [ 7@(U  2SYۺvQw! ^i}Jž^׺</pbqi#vg*M۠m]WI%,'2e9Rl@)0z'J gm!Mc+SWfqC ZNA< ߤl}r2&[g89)>::`wJvS΀0SL "@N!_($@Ovǀ2]uI MӿHmO?O?vb !ϕ֕/_n՟7j_9Nz6lz@/Ч0}`Xww9|Nq\JD<*xF;ݯn֟?j8wsX90뫟$6h0SMR`jYd Kl@ P03n/@sD`ySZtAঁmU6J>峞JqV% P@ 32e?u[J|4l@1#Hwyw3KODɁ+K,uCKbxOS; m٦]ϐ;w^(z `Z G@sL=gIK `;kzB P0-wK2fʮh]I,6L?tD-ˌ 12?м^^$$$3QB@p>9iGe1=*IG "!>4ڞ\^@) D ckU2⨨(a"@C P05`jd0 i(vH P0ܔ2bP0-OქQ?J 3M7􌀵?qDg%~{?k!BѣG>WI iA@d$fhVJ|[4oaƈS'v*@Ԩ3C^XO!f`jY"G,*@Ԣ˰`\>WD,R+)ݩ=k%g? (D 7}irUrW 81:\"@<)09o}y)O6O[ @WB`J,I's$wr6mTlS;κLēG\'@ P03< @@ `j"3=Dv/.1g K?D]CGVՕb(@˻ui5Kx'y3e[w>cYb @L*'~^7X~}7 %qsu#*KdxJIS/ȕ8ZYpjr`z-`jh)Zf*`j @L-9 S P05t(y=[IX'g~7XYa0w#`#i=SF=ߎlgA4kXN<4vml@(D`{+'17X} g&[RB89n_n|:Y.)Ԭ|JNpnEL=Ϟ@m n L]@S0Ӊ(|5ez^#qm'(NO \Sz'v@ cSV8(m\'ͷtePBBA@$uU۾zDb!4&fGMb庄ޕK g8 9xP^W_m]mD-WYV`jՙ(Z|>`ClB`EQf<(@ԈBNgXdtKWsWǮ#.ו:W?Zv /?0=xDVg5E]QV+|UF/Q"@r/-ÎIN-ء~+L+t; @It|ThtA#-[4X>ZL_| `' vm &! ` @l&@fp0SN )Nᦄ HJҿ|@t,(Hz ʏ!v&!51@HdirmӦ"o@ WCI:!_RC,B9Hߞ)w^ⳏ=WJ[֭[uDL<S M&CAdLM6aDI&4S M&CSӁ?yzt%ו̉@\맅^CoQ- L$)~lU$FGjY'eHEI6Nƃ'(zR '@pSBBN馌@SP054$`jid>`cpcOTnJS"`^ws$K6H 5`@ 9Tb,Ȝ["..΢#eXxN,iL <9I(dH0SLi"` Ds >'C <H lҗI3$&g|5G@KSE sv6cD@zz&#{CT [=Q^\ h@E .Bq5(Zc`uD'@zsʈ0S yAf=.9ΖPHAW-?"p׹z[@a]q~ocΔOlk@J.pBJ,uUXN@,oQ}SFcه5VN OP0"mN馌SO `jidE_ԦLm: [yy1%^pLW=b_p+LU-$OJ{n|@@q\Cwjr^.D{ ZYrEҺ@yQG'"L]B+ P02&+@ԻX]g!= ޳e-@"J`wdOIh2 S` 6˫DHܕ'qG%nl@8S 'Gr8rPșy,0- g}_h`Z}Loy8N@E .Bq5(Zs^`M]F+@ԺsS_ӟ(qsSDGHueA3g;Z^Tl*pb>3IYQ;|k|![⣏賢FkS1S`ٲe2+ ~{'3L#u: P\۟--$eh|vw3ypj=\`2ZIf  q@L>䏀y(g|L7gd%K[z^/1!Z?=0/'OLCj3L穀*m2ψ. ]E߮ʔwq "XZ`ݺu2 }AL]򵽎> ;n)K{08@OW;^tzu}?کw`j e8 P0-܇ pv g  @@S @L}-Nv`jfyytZ Q ԕ_{9)9֢v8 ҷ$/#NԪ^ELx ]t "RSV2e~o<xG&r]\#}gS`^jK<. dȒW{$<N P0-V`jՙe\xOliS+*cBXL5dcM ֜WFaKZ|wƠ401!`X3w볍ۖ\//#1{7ʋH=@w2 ~M_}#]m0Jz]QZ{\m$bR" $}Z.A/w6m$.b`Z[)_W}瞑#%`Z5AW(5xFgi&L{GFΪZ4.$@hlM&I͑ .pP)NJ ^A@{ tVܯA&T=I!cWYEe  P@L=xyQK9n P0u%@^h[)_ %`Z5A(9<+@ԳfLN5A:<4?&ԕV&:v@Gkmc*#}Fݯ߯  @qmܢ" ,n\H^yqt{da_Qiy9O%mq^`e`Sk/C8Lŵ (Z!%`ZR9CwL}gMO2#뿒خZļ<dn~|n0H-9Q1\/`VA5R4!CXv @k; ={ 먿J 2O }O7ԕ}P:J_~gw2Ə($@JXSz `6 f1ELgO%@(!#p@FF͟ğYbP O֕˓VuuAK zZW>- *q%VKA!ˋ'Km  P@J]YZzm\(bY-G''@sL=gIK6`jIf)@M@nGL.@HA"(ikjPtC:7ȋ6FHًuy^^}H0SMZKS=Q#}(w9 P0uWSYg~rn@%>iS),'N0Pmp#xK~P$ ̏l'UI|x()" vd_zTWGZ ]$GIQIbxBD6 `MW[dЏ k3RL*@ԤG`jd4`jf CCE P0-JO愌l(62VHqr:x섮0p+LU-֕9Ӄ%Kfg!) @YYZp9[ J)ُ_QQY@'VdLt,CbLVsdݦ} (Lm0 ѶLm; ,.@pA H\I(tHyy[2.ѿH\K"㛵YK+LTE"`>%鉃Yw[FB g 4oTvO]|BbL3O,,宭2^*8?+KW\5PWZՅq!`ei2N8<\ƆX^`ǎ2of _CO픃wTg>~Y[/|:D^uĈ30S yYZ!PY8ViS ~`) Xz\twJ SM\Ȝi-L^?ʐْ ޚzycҳYq n~6ŷReI1}]Q^=0NL+@ԴSGv`jfV`jf| ` viƉL]J.@3D~!ղ7)}iPggq)/@+tAڃ$Ñ#F4SB(Lq؅9@n}viˤ2.mط/?FwA"0S ! !`j)#a@@ p[ P03h P03]}pKOt@?et%Np)twb]iyA(?u qo$+juiV'OWUo_|0 ` #G ~ħk)@`\Y'S &`j҉#mشI6߿_@/t8l@Y$C *Pr9yp\o׌$#+H$49Y9Nwݝt y1ۥNA P0ޜ2" P0d3T P03@0SNi#S P03l{ ݷW~%r<Ԟ# $LtF%q xWd_ҟ*mk'dh}gS:[+/6GE P020+@&A(L28 P0u`+ P026'@x^\%<4:Oͅ4KӿfΖCxF%⊒y?*?A*г:%FTfe]f h qo%A2TO%;d XR%A!P}8?(C>@(mXEUfq S i :|Hro۵Coq^itӐmr5e%VQW@|>nF<>!&pqmn tFB f_;,^4W+H@qt; ` ֙KF@(S SKP042<*@ԣ49="_ݻ@IDATՕ_\_ _aګ9J@._~V?" `)yxF}A d䟿1_V^'x5s ٻ馛%q `% VMƂ@ (S/$ P %@L*@ԤGxQqi 68l̓۵ӕ@k -mZbdVz @2}'ݸOH:L&@h3B>Q| |) `jy )@ԛ({H;as_Zz)07aCD&&+$r8yq,W?uq^#"X$l@7oa}نn6 ` mM(]Qا  (a? P0=gP0= (.S&`jI#e<%,MHȞuV(#/*"Stۥ#߁E.\W:M.'f|5= !vXp ꫯv sJj?o.bL%WOn)9ߙj$`;kzBpL 7%$dc 6|~`v:E/LNZpO`ŏ˥_~VbP` 1#Sm>KM#N띻@LOnVЕo,kV7 6e  YVX!ntө{Ne`r{%:7R"0񅉒\ήTى}%INʥc/Z%7n,  o a P0d3T P05 XDE&a p30x`FAiii2Ћi&1]j3J+f >4hʫ-n"D/>PW'ц67?+izm^ZK7?e U uC'#Fu~y)9e${ "`/v@Ln` #`Z,KFg2Y'MxO,x`ZL0.G  nq+ PBp8I(tH P05d fOlв ԝŌ܌I{YtY8yC6}&W׈GXN~1]sم:ZcW'Yn  8u=UWoXQ0ӵ> W[q!Lv`W`}cz@{ P03jk P02*!@5%#:WA4t>bj/wi zh(3M"/^,Ac:Gh k d1Ź2:8 pwe$N><; CC`1JB?LC@- nq3`j L-@GK`&]vs֒ئ~ _a-]a S' vpA u~7(Wwم@l=]#` 6h7rss *٤k&gO (zA'WzKH@^\21%wl@._d*1^yv@YeIi홉}Th O V]b||GSKO/C7L}L/-@@3L=H+xR'5i `"[-i.gvK3MfTfOY/ZYJ^\sM|w?@r~]p\V_G >rk96y~ϏwT/jt- 9 %(bq9/@/@5 9qF`j -@0@i$肟iNx:]\?1b$wNoOb>'9_Q:u>{/.9}}r>JƩA+?//mPYtIQQ޾;$@v~Q}Gͫmtem W|KWrWF Ig `} ֟cF(~HL 0 `ji"I `Z @LN P[ɉ'$KLQs<[XKD6Aϧv0+KyBwhװ ;p0F@H/n.1/8L⬍b%D*+/Jz-]+{$v!'w̒xN?mJdxJ$i`6! XX'!W(zFSr!@ ^"1ZwB9Sb G%6)205ƌ7]'%J7!#$]s1E7ʡH)" `K'OʸkT(aB́j6]zݏRW9'"IWOye$Ѱd= *@Ԫ3˸0SO{\Iil"@&0--@0SCO!`OfNO0HW/ȏ/UwWvc=BuA)٦:=+ovCAq {˘,q%bmz]YCMnЕq!bE splI/vE?~8(N ` H(Zi6(J~@lL6c@@S @L=|2[?cs:/_a+L\AoQky?1_"@BPV?U;>9> tJr>4>T dW>SJJ*:N"`Z\1G P0- @ &!"@(S) @!o?E:0FW ` gm>A>B]Zk9&Jo@ {~|gm͚5M5)"hBYY5$3K*Z>Kq - ޒ]pYT\@L$SN<6SSLI"`+ n1%AAA,ѱ3ORO6@Z}Vǰ!pAl-~z J dks$H\GCG2 `_ {Fa(f*HL-0 <*@ԣ4G(z@L=IS [N;J!14z3@n>7p>oFL@ <הJ(g}@^~FAf>4#1SNDE|/ Y!`(q(ZxrHiظ  P0('!(.@E:pĐ}w{u-QI|n=EbF8!hRi1)=4?'ug`٩ΗgV*Pr//@#P05,%@->nSN,Bb P0-67 Pb %F0SM @nI 輂?tE뗼3D٧f]2x>3ŵq_j<)%A&eRﱽ$^h!2 'x'_ӕW'~^qB!$@L=Hf`j$SOI P0u݊+@L=?d%1ܵViˏH~RKu]I06HL\ C)9T^3_-\긄XB_quqMc,1.N}zXMğnwIQ4+/';([@L?d`aP3SSM"(zpQP\`j)!!@󦵥+K8Rvs 3VzJv$~fI'mfxF{#5oxPoժ0O>&<+n}7mTb.WH$=q3ueڑoqArɋN@(%y0SM {@iL%@TE6`jӉgX@&! 78.H!g4l/`٩:mYUx0Y imn~"V[3 $/<-Vmk׮ma+ E2 '%n+MKGh/ԸC& (|I'@{ 2@P0# PӂT8v`jYf P"׵zD/x9_4>ôB{]WN ? |Wl-~֕Z_~A*ХOImw#fT  d̐w>S^A3iy_/6I8SF`z/-'@rSʀSN=SbB!$@H7]-'97tΩ0ЁN@Zd/~~+Gҟ'Wȡ*W긄Ba}5|nw"9@b9_ߗ~)$Zg]g@L=NJ`j pK[|܌@(@L-0 <#0ᇤIo!14X'! =3S_+Jוxr 8(U2&6z}z6 f`j֙#oSҠ(pRH  `Z SrH&T@ǏDu]P~ c?88Xο JBbzA`n.쒪:!AWΛh޼F@: `7E }zuuZޥK>}ƈv#bxM cV{N.}jժz'@P05L'x]׉LO P" %b&<"@#4&`j#u0gJ"_'1>+M13dqU'e$F_ 18i$vDW]uD6 .BI%N֌ow||{$ZbXxoz5[pE 7Cq\zѯ5 (ZnJZ$LK= ` FrS4D(%y@Eq=&W~~l4gFZ\mUF g!P< (2 u_pKOV~n:U`Op7&/:"7x&٭8`jet C >Ħ P0-w !(bH&Lm2 `Z$ xO͑PRheװ23]u ] Cz]4{ɥ|~ω@@Vj o^Z^w(18Jqg"`oiƱLLgg&~9Jڱ=^@O P0(R/ #@8Z\Sg@(%y@OLIE#'I%A*@Ԯ3ϸ@LNL^`E\F P0-7! LB@'@x^\)PJ'%[ribf]qzDV>89E?#B >υ@))N-"vto{0zJ&~UUۧ+E&~َ%>4rd:.@"L-2 #@8sA&%`Zr;D P0'[#L]J@%3냂Y?tB/<\̥$үK%-Sخ];6q7I>teCg/1pb0rJ(&GNC'q%s ={i v`yeY͌DEȾd)ZUJRJTD"[)"]0f0 o繮?G3c|?|s/uu}\&@e4FnĦ+Vi2HfH@$L-NV ajUeN Iԧo@ }<=}O˴M0'Q V^+-[ǖx (piPJk{S[O/hz`IyQMGXVdn~Q"5ƜM,mTv9e@&@4on p SQQ6$LKixMHd&Fa  ʍZ9৅^]+L^aڌ LGasZj9yB Uf_|heZYϊSV.-| bȼ:Wr>!D^,ъ?k%1 n@ @ ?$L{-@٢$L3$[MR+uiȃ@LLx M%?&U Fv/#ϗ[+n%oA {yNܹsrRyKNJ{tw>Tf=k LwOJ>)p S2 'ybMn a&hA$LmL9$LsM@< 0oB\'W/͖{xkŀV֤fU ssr M׊]k]##]nFj>}fg98?%R.tĭ/@n(Xۡ{VP!$ e)xC1ցQd/@4{"@HݎwG}֚"gHzƝ^@ ,n1}i:!2] W'$UV<%@ 6l~KbT\'"CQLi&6l( J.#;J|yV:WR%'  UiuQ릿}QA4%.n؇  H.@w 0u}"ajߵgJdi$LM| +@Լk@lV=%_;\n Ǵ4zrVTpKq+'ә5zwN4}gk\xs5Sf̑Hũ:qDزez-?++T; j` H@w 0u"aj5gJdi $L(@ԌƘ@\i<=/mUS~+MT 8d+O܏Ŀjrƛ>߫f Ip[ܯpDF=??E( Hڠ_;R,M@)@ԝx@-% S /.SCC$L=O ajap `6Xd8x@M=3DKZaZ?TGNGK սJ% %(X`e)sc~봨-1q` `S`)X˞dWbF™D9OHxL~)96c@+@Խ]-! SK//C%$L]J& ajc `{VC2ՕxE=iPaĉ@JV\ 4Tb[$r@ƽ{3^0ű([Bj{ A2%\4M-EzɋO @*@ԭtO S;2sD5$L]J ajb S> ""4a;dfEtO,_JMWIVxSbLtϵ6Vla JL"JH<8_/EkZ_C{DrDrrb$\&Xn&m1/oR[ʋϗ,ϸEDp S7b Po$L 0ͯ ﷂ S+"s@;0*3GZ%<޺W~pS3l`>-BЖhL:1m{k.mXCIѥɄk0q5zCEh2q.ֽætK:[ʋMZa0 S-Fr&@4gN<\5$@J\+@4p S: `6XWÎ{jf" ԣ Zsnz#A ?/M+ʶ~Zqj0"/%߱t'i3$)qg.H ^7@$L 4 SS-E#$L=N aja8 U$L[ϸ^}Zgi8y.OF *vwד´RJv'a .!EJ,?D=K $'ɼqą3:̳˥$F'  aZ_ZGL'@tKf050( !@ <$@CtRi.xeqOӸZ)_r-*w\7=/^7GMZ5:'ѯ{@td;Ս7 f7@\#@5^У aQ~:G$L < $L] L H: f@ wZ]$&9+}ۍ4I0G{&o޳aE>_&}}) `.Z/F]-! SK,#@$LIc& ajc `{LIo˃˦"qՓi{ TLO_&_xF %lٲ` DaI) Hڤ3 k'Y~L S+"s@ @ 삄)!Gym ajc `c6^|D녟cv~q{M0(´6vg 8~OĻGQc%֨QC"@=Qy`2#; &_R$IM`h+SޞbGVi# agBj+|ȿ ҂qHwm yQ= My_f9{+1U ׂ҃}ZW6 S=u ;>q0U??={=) `#G4ڽDVs@伓2?c$ƜՊ/!{--W޸O7, 9Hc% vn'T$L-L| 0o6 S-A\$@E4U 99Yu,Y,[+&||b򹯞p`'  ajat H~ :N1 )@Ԑ ,@ɠ4L V5n{յ."t׿IU?W+d9gf͚]y `bk;_b`C;hEVx ;9pg;HKjoo>Bq0 S-A*@Ԫ+y0͛B $LͰJ1$L*@s 05z1Z@o2HyıwEK}Ztwc='gd^i\-*E,}@a,:S ?|@"@-̦鄄i"k& & ajEb H:@-0jHy4U:y|VVGVlfL8$$gXXmGevX&³ejV?(Sl{mO4Sf~  a@$L]kI~  a/.X# a<ފ ;ZKxQbƷ[ljF9*=v@4:H r1@Q9?~ HZ~  q7[$LͶbHfm 05ޚ0"@H$L@LL̾AzzXS ӊ*? H[z=ޱzsKXx j䊡 7-hA2"=v[TM*R1}t!{i*  ajuf H~:@N1 !@ r(@4P<TMi#Fӽ,_G؋JSU~9혼toۧplma=>H^(qrx2wJ .0cc \-'_8/WKGӿxx{{haUdɌ[D@ 025@$L͸j3 ܛ.@+ az@ az HLԽj5) }/NsjH,T W "9Z+xE+yvJQ(Cǎ9 Μ9#(QaQQQr޴C]߳C `p3d5?ӽ-*ק~Jbݺu > P @ 0u:!$aje`IixIHd& &n@ $'kEbe~ }ާޝ"ӜxZ | BҮ S4Iկ'>J=V@@xxlRА;<]bbݍ׽0OCKnc+F쩟9]A KkVvig/94S:‖~]oh[F^[yN@ N{֫[ϓáo@ 05؂0@Hfc$L;$L S93Fr#@47Z< 1nYbjciV|\/Z]!NL#@+VI\dIӌ"`uO뷄7HQ?w/Z_m)_}ti0?c $n=wys3 GHzN@r+@4b~׏[[#ajf~ sH:ǑV@\$IdڿQ,-QH+R+U믈2K,|KFD)N`]{[@f5ȋImx9hGp@D#ɳ;zC0 SS-E'@~k03ks05:1JG!d/@4{"xX ,,LFo>>>Zazu }D>Ϟy`C҅dr5?/]c)0^,+/sְeČ%&>KM?=IJ҄3h;5F `H\ !@4C^֛ٚK˪%ajՕe^ 1Hc [GZL{aie9 $}5_^T^=<\K`ժUH-[[#oS+LˆjiŐD|܂8},4 `=[Sf _0:2 k 0zu6$Lͺr0 SsDȣ@;(ѷo-%gg۞Js{e&.\ )!%Ixnٚ%X}#-Wҽu??'-@zHmz6  T `^J^?FoM\W͊V" ajb yhPCyg\h4=<\(׏)=6g _C'PYz\r>|˔)#8_ %E+7]Ci1?D[}jKo?Pz`I)y!H"v aj'ajuc ajuʬHZe% ` XF &%=<%8]:V0^7 S7-Iݜ[Sg)Ux8"F_Zn"B@k׮xFbZnh< $EˊսJW?{7LMIu}x HZ~  $L0k]Ff_]{3̜V1" ajb N(]L+I ^Ɵ-t$NuSa#{~zD+&ЊQNݻF_!ƇK֛"8ICҒY%& L+L;ki0uy\:ߪY_Rs-}vy. ZS:T,:\=0 3|~R{.-J⓷i't+عKO`n-R{BW $ҿRt3v?+sD@ ?$L{@l#@XKMXh!@hY05ۊ1^@$L@ iiZ}|ӯ ]Kmwo>GE@k{wv73SVM~c\h۶-p .~ꔼ駟$vQb@@ĉM8%YX"\!x*IHu_& y a'6ބ `7DiF$aO SӭHZa3YKfXQW9!Fǀ2[$U̾207@$~}ZbpC(-r[!9~wKG|w%r@/Rӽ"#.$}w7xVv!2؏tF@ $L*@ȁ  Q az aQ~:G kq@gKZxDʙix2^_yQb%%rȝ@m?lx ԬVYf7QDdӟK?o7 BMVr☙@ZZ p%DyZH\f[j55@<*@ԣt `]i7s 05ze$LͲR@ '$Ls3 8I}MK{& Gz %~DU=Ƕ/ol׶]i,,ݷfEm/86^zkoQ}H%' :$!)+jV,W)#j%syK v8A@$L @6$Lݳ$LL/ aju3I} @nHFg@p@IKk/xa_?W+q,Ra*<$w[K|x Խf AI^m)yڒUcvQZ+chEU4fE6JV " qHg-  HvI֭֗!@hY05J0@g0u"m  K#;$qamômֹl鋯'랍*Qˇ ˖J n*x,x@^NOԿP4A}Ëy $l2/@\*@ԥ4 @$L3wqU=+ 0jo.$L&@ $Lsnœ [ZqsOg/9"´ AT:$zyy/x |4Cܻ&J,Ru'.@msg {7%/֧#_~9  `$FZ Ƃ `y]b7s 05zm$LͶb@JWj@ :uJZy[ߨykNj87GB!7 yHMP#F%W>yj7!`%;'9DbVs@O{mbwd5/k{wޛ졑- Y 0چ; 8M(s \qHl4]n@ 0u)/# (0w\ >>>r=| rkVݪ5έgؠErwa4xyp f<=*Sx6;}Few8%z%*UG8G@$L= @N$Lݻ$LMo aju2(Im/ @v$L 1V+tVD'#񫦕iqH#IH/d:S9P^LZORiA>j–q4,IKf/uW H@ȿ 0͟ﶦ SkfES Jԥm@$ۯJKˢrǗJle^%o9_}ZjڽK'9i+VrD|XfVOJwmS?_wGu[t HJv@p a0͝OKճ%ajaG SO' y=~qh)n~+_NG$1~;>91̮Y`tWrޱ]ߟ,V8uLlٲ:qℜDns@)r?1R,ݪs,5D@L$LʹZ@$Ls a3' SkgE+@  aNmB@IcyCZZ|+I $^V+IBox=*Lct/ӎɼno.$TνRZylr ,nTWb?EP>X.!'H{K+}3r'5E~/W-W݂  aj%eB v a*0܅ ajuv,IZ@ @ @BGOv/'vҊJtoӂ/jiP9{TfIK‰l*U 0~)D'޽swqzUA1_nQ{{jYϸ'pd!iਜ਼s^" /ʻ@0 ScA@ W$Lsua)xaA\T7N @$L 4 @V|%S_--o{kizP@v?uIz^WЗٝ[H!|ezE9{t=/:yX+Oĉ某9W9-Zqz)Q)ʾp1&v6L@$L@$L$Lǻ-@cё05J1N@W0u*m" ">'1P[Mr,߽ҿثZaZ T^$imϩsεk,!T2K|~՟󲏖!͋KgS ߿ 1>č]b%D:/9 H|> H:gI:ǑV-@cё05J1N@g 0u&m! "KO+׵?KJir-W}}N.r,:M3M5k=^ `vcǎ׼YbD~˵-%4 h'K/5n!Q]^ڵkK HZa HfIƕV!@`Q051n@g0u"m  `P3&f!ѷAaD#q$up ={N6hYO-\AOcH؛$P DkF}*U9." `Ff\5ƌ @HfE4{[S'a)yE0 S#cA@E~:UZcuuo:ugf=b!cgM7K.Kÿ"P3V $DꞤ7" @l+@ԶK@$@4&aw)@ԜQ0<"QW1! "i??Dwm܈>ߪ]{4դ(ݫ+LV ]guǗ/3evx2]J @?9sTytrVC@r$L-L@H:ڐ0uZ$L SW > Hi+ N>g)}螦J}E%zD,8)Sz;JtctlJӏ6˅II|G (-jh#zp ()+1slR24Ɓ Hz@ S'a =Nl2 S+*sBȯ ~@L,Oe?&,mT"EX*LkG2Q.%j_PL ]䈀-_&p{؋Ҁ!%dHݫ95RϽcr^qhyn. # `6f[1Ƌ H0ulj "@ a0<"XJ  ; c[OzOnX_v R0="R&X}o$?^r}O$֬r xQ+7X'#-X1#fxVR7'uZբ+ho?џ{۽!F|řà-@L-@@ȟ Sn# 05⪸oL$LgMO  ajݵef d)8ݳO3iEoȸexbQOV\SBC0CMdfHMM9tAbbn;y $}p@ZI}wwݛ?c%\1a@ a @ Sx[vj$L-. S( MHd& Xl@tyq+5U_%$%ˍRfwE-rLMJ=Wŭo2͛[dL ^/ط#1I .篱UqE^rvqGsqh]uҤy֑r}ma d%@4+# `A$L-єHh0UN@ @ 0ݒ3a@; iEɓ'G+,$ύ_>T+3n{,^Xt#~f)3? dMҏ=MGmbSL8[W_kߔ&}}zAJ\V yt^} XN喔 ! ӬmCJ ^UQWX"aj`4  ajub xT`wKHifJӤZyzVږ,Yң@\)0fH9MZ?IM|z頵ZA>}W'ʋGK $н[j'8OPb  `gv^} @H0G(@ԍ&슄 !#Fa  `|oF=WF=+V/"@~{g#1V^ ݟn3N ^=X}iyzoqi0sZYZn +E9xܿ[} IV" O$L. aT7Iċ@ #@0K@@0w+B+/4=QyP+4+"f0M,.ğ# t ?VI,߻X,0+Mzܴ|`7@0 S!3@@$LNNd)@4K[ ajeg N adPC@N+V:wĨIU1F-e^Y 72ii}R2n@ >0 ԟ .hPC \# IԧoT) 0s H:ϒ@Cԓw.gT?8&Jh_Ȑo#[RxMb$r@֭0uJן g3ISt$(/^^ y ag:ވ $LIf| a>k#D@"@.+<@p@ZKx,kX^ʤW%Ua̧$yl 7bH6욶+O~=$H䀀+6n(_6`z+;m kzz޷Sϵ \ $LmL@ $L  @;lަsό#meJӔ#77e9F=Dbߞdȋf^Ob$MԖ !iQ?9`MC2HD3?*qs@.@4v@r)@4`!@2$L =C S]{fq$L3̓z0 @@0M'ZA v-w^lpGk,NJ8w,րQjU_/+  8B 7}АUH[U`хKL=]<žTl6!g{3XNOUiDD @: az3 @'@4Hzw$LKs@g aCt ٶbWhu9=5 ܮSVtS.99GGA`;ƈ}*%/ pzJЋRG$=ü\J/ !B| S\Wf@I$L}rYT*H˫Iz> ~L [D ӭؐӞvI2ˉ v^[oF_mӽJrGFh6iv*sK#pyg,.횵bVrVr @ az3" @. a"@{^LiQ Hf @8ttmݥNfP&9 UEinrP*AUƏgWX) v[9^$zdɒرC_ 6e ^F BDgA!n:C S\6 @ ߿_@>}Zl`*TAjaai.m]xm'p󋜔-[:B`Ϟ=o{2F,d)J6-S8/ 3RQ&*+V{ڒ?-c @@?2s (>~6-$L={} HfU|B $z-늍z}2=梌1CbG !@ddhbs>.gHD>a ұb z2L91wu@ am+F 8M蘉Hf"\7&a x^` @._,Nn&Oii[oV1{Y J7T{kbi{f)}&\\C|Lg%STqk/]TZNoߧ!@:$Ln W 0u I;if/Ϙ @YMiVC ebcU9ZiM![],Gc|`.9no+/@Qv*yCOU5d۹}"s'ņ< 5ṵB=a/޸-2E?z#U??WF>wђz|  H3g@ SnO @V!10M?+ZB So]9 @mۮ7&>CkRUG( 8ӫ0ާ ߶'U{ *LN^׷PEjҺk=*SjD3,鱯MGgydFnfi. =p|Tnט @MG/A p=0Ñ SG"]&aCt w aN @ ۻG_cTY_:TaZS0=>nt|Qm@u]tCʝ>%@zOTlͧ퓋Fg0rRH4_w [S @' aꉫBL d Yo%a]KOԻ֋h!@ a =B FyZQvm)LG[ "PƝ;뷞ũb sI@bG=_p+uO滭Et/[_ArrםwyAԄ@+HB >^7 ޱd$Lc N$LI_ xm[Hn%vX#&&4,c[[֔+>V\\x~Ei*JMؓij7%0_"U"({v̮nݺboЇr,7s`G$xVD@@ aihq @ a+0us# !@֚B `HJRݻ1 :0(H6Uy+۱K끡}uO6=PVp /)=}YUS;XYC !JK1@UJ>kbT@ ӬΘ d)Yo'a]KMԻ֋h!@Iif7 @>IȼP%Z`sJtӮ~/I^:xzD*I'J9ZʵULަz;Ʌin!prq[1't!.Vr-_ǜ}ժ4uK8 @+0e"H@< SOZ ύg SZ x2:@Gf/Kb,eZzN׽T .k_ޮ" +#鷯8D*KsߔˋfFqZo#]ln?qԱcb[6S/O>p[OzQ*U_oUM޼ݛk:65j8`޽{Uٲeϑ#G\h(LΜ9#L"::ZNefjccUVYb~;!SFts;*K+Zm@JLtcvIj7MM4; DEb~z2.9:~Oӟ_V.Gf6sbM.{ [̜i@ ^$LK*v$LScU0M Ӵ0MW!%@s&+"#aSil@H5aѻŌ7q ? '3wL,Q/T0;wu7ߊ!<+Tu݋l?\u-g4]l۸uk_Lqaһ}^[oJ JxcJ#\;sx7s{ ^t]rڵRRnݺWpqzݦe+/+]wl&ڶ/,,J#vR3]/ܡo|>F.ݳGzϘؗ^낷9{_D](OeRsD/x"%K4ƍ=1k4_{:EOI@*LOr\ml=ѿ09?xؐr&Z얆uqQiTYy*?FN~uß~Vz=J^-Q{ P/~{!~_t}-l/O v픓mڋ$Ƚ[u-Vl!s:)W Oˆ=qI3K9""B;u/[jU~~~MrZb%f=&.rLƍ7kwϝ=+. ( u;NERpp={vOO]zu`4>}\ȅ<9rH:tp]KY{*ܥ 7#UTYNP2ɴs6mHΚ7WYog9{NRb7~*E_Uʹ=;gP\p[yUJ o)LU-PDy9\_>IK}^6ML[$?r-kClӪMf@iE@ 'a60i!a6i!a6s!%a2WkIJз0%a@ o п_U$s9V:֦}:I\ ʈ U$R40P6^?ObMN=uYKr=((Xnjl>iӨw>w%b~ԇcŚo6 2߻e|0=t^bVE?ߌh]'GKl }Qe)Gi.wߔ8f\Sˤݞr/uvkK,gUyUֺ>/3pXwiݣCG}+s: bԷiR@pXj??"9ҫ^z)v˗KG[랫\иFbF^tk;KŚUfzvQ.2am*%}~Hl*S-]UfOkH>1O(JĝUy.g)4:t315Ikפ+UNFx?ֲ0kf.?\/1Ll3n\ _a=`m0\O4Z[׆rK'ƎViJlV"ys|]wI-sZ 킡*yRi]z4zR\%Rj<_:,X0NhY<*>%1>TPai~ٺϘ͟;,5 r;=lY؃ 6209Š|^FDh}ׅEuGωo6zgf͚ItBd'L\ެhG>pu  @ @?4ɉ_A$I*1e_&ajñDԑ}=J$LS#$LUΒ~$L=cIz: @ZY^$t:er*b,sgd^[))h- 9UI1R.sܦ Q9sſ4w|_O:+'TQlRaETbRʍDj37ɦ />9 -ңN*VY Ӆeå]B?w?)r*X.k*Ac+~pщ*tTmZbSlJd2 DE2#`Xr6ۄFU6AXcpM͞=.!._V>`^LbY@] KlʧNޞ+~C SlprXBbVTD=}Zǩm.b(_|R6 z?Ѫ9`cϧs>RoS6oD6dn/b=3U^Ϙ=ˊė5^c;CIzߎ/1s?|9RzI[Ry|;ss_eqz/OR?у_>7$;z/z|o،1֞JA\7z]﫾}:Q:>MY+MN)*${|W{Q:NX}sRV緔/G׵E}Mxc_ rN 9=xRچ Ӗ/&mFiRZܯb"qGo3ۥPTAM믿C? o2jɺw|fBgA,8d)}/\YV?{!@p S Sv SM0M9l$L di0zrb5MZseZ3!a̿F43 @W $WSʙHLJ ]S,Z%~{=|H#TA69XYqcsXrN oW-g}rRo-'L;-<+Uz;J&Gj.tH_ƝONVZaSY+({Ӕݠ\&+xMQn_/ƩIwb"ĚG!:~MNbc, *_ W.>f{3LMW[Ư#ʝx@*Z޽}[Sc%y=V,{9p#aJT y$aJԽHԽ<@JWgNohK&Uv*uL;gٺ_71Ӯhr֯7Q9]6Alj-asU9%۲*ׂV*;T6mfiRI*KOyZF&->WzdZҡA}Zѵuܐ1VXMg˱Yj&0nw%$ϩ/Cᅲ~gZ O| *$RX7 cU(iE7 t{2\^[;k GB͚᪤}4nW$ލr{-z_vxQʕ=jrboF|5L?ޏWn K._W|27mGh{Y}Ξ~N ֞{o[{dc@ŭ3F8kƏ>QM%|XJ+뺬[?O$ٓ`YA'ZvyR5N+,ƋV-\XRCy{&~I,"f4)~5N.pp%ڛٳg(ZN>BU @ 0u1 Ӕ0M%ajH[JK$L $L5f$L# S_IeH^ @@:05}OԽ:u.X\\r7[3?r*-uNZ0_RӪ{7[.PTbrvg8$]gP%i໳*kկ\P}_k S?8tڶ[d }deԮ^yK]ַV'ƹ05q}Gb?-@GNhd;; ~Sۋm6UΕn =t&/q 9yޟsجW1BJ}X.՛\~g]KVMZV+BTqx}tOg9U8nF q٨'  dSʉC3Yy{>_Gs3rK F!T}/qA=K0M_.&F;/^Ck:?phoH{UvC!Z SO{qs?mOvC/Rgӑd&R?U}d)yJT[/6J_D?'ϥ)_}gxs-vgӆub;=['Fz!*YVhJȭq@P$L]\)$a2SKԐ$Ly SC"eKԞ S{^S"aKE ӌa&a1^ d5qF[#p*_psJfrU6vMGbj3Zfz2"O>~P o&f_Uc4/UeϞ1%Yj#m۬ ▭TٷwJCnjROTٱsO zHI=T=LG+RpTUd M*=آECz◪tt:͙ ';}ٵyګQ& wLFU >Ly#nu}ri-otoe:q*m4_S +j}n]}o:ne>'NV^xaste\rTTU)z}riO?eS#)P]v[HQtaԚ*MXqNGrAPOMv]x=yߵ{j9:GqctfZ뼹sr6"ua;;Kkcn~>,o৶|%v7#i;vq\ϭ [' 9cnĎMlpb)pرmnPF"^5"L߻=\WW7r\"һߐWvoWwm}g:bz]ȅaJg_Bz߸Ү{~0_iȅK]䂫N  0MF'$L^w)!a2)s1$L {Kt!aH? R"az]Vi00M Ӵ0MW!0sgUY7*LU 'OtVUvs{hǟtIʛU9\dt{w~HUZ1]]Kwlu7=|` ]*V "EӵUea UbZucJNa8Μ٪|\ L/Tt4@$_xR7vaôܴZo%Geh9$n|4&ssEER(K G?(L wkٳvꤶR%tl^}-{q(חV;-"XMË/Z}jPwL-au}EMspU}._PhχU9c`o'Ş.QB~b_uZЭ5bXg֬}CL )\g]Kzd˦upݠձOpɿ\*osDJ*vȐ!KC&bz]taPgӦ7b[nkOW ֭[W-.zإ)fkp^u;Ӓ+źK!?/ o|ň/]d\hܤ}7/J&9oC\Kd։uuO@?2s0qUUHi\H%õNFHf*^㜄!%a2ZD$LyV"a!?HZ3S@@*^{p[w)xyC)4הc1LR<:&L2t .j_bGsFBWE0 U*HGXJ f5Wr=OMcE_(z%*DEV!F՝3PsCzfSbڠu)/ޮnڲq=|w(/|,GyNyǥu\r PʽG'yvucߕeS?ұfMU*_3}5K& {=ygss]Rvu/cG T1y*A͟E7п J|=A~Mnїy>ۻU9N2]Oy-_?[>Wbpt n.KY[>tlG@/"s d1..@jN.uSwn)!a2ZD$LyV"a'a6 6o=j)q TE6_+6o8=n?]+vu*j.F㪬rtZtYi[XGRKt7*#M˶oKyMVm*UPR'[-Z@UD媩ڥ]*N=S>;XFN*ש&68X! V%Qr|rZ7Ie-ٝĚa!x og_~ϕ"U9kvQ:*yM30o %~GB:QRnry7RnbGp0o?㌔NT?KUTÜY}OA_`H$u-#쩟#p:˝]Jaf>b?M:_{7H 9?)e\(v^??}sh軱bKֽ[_yI{ž> mq I[ľ2|؞={YsBzL:M.4k 򯓆~>r{V\xkR]~.lӶ\پ}+=^8{8c~^uݥwK/@\!@Izs֟.?'a$P7w#af)#a(`Lb&zMpqLt0My2$L - Ӵ0MJԐHْ0M  ҫ@~2ۑo;,?#Tr ?):&L/}YU׮RĖɫX{ĞWhUEĨf(L3)4uL>6PAabZ'T%6P)[DŽ0zecn3U-IS%hYF /J}x>Uq_*k'l.G_{-چSf.%vlI zb6j+K݇A<,Boӵ>oUwUs(\rHƟQ&i7 QK=CPTPQ]\ Ht6S6tFE랜f9AG-#U9TI5\,eIFSj}V %>ϳʣ k\  azOПaޖ-Wc _KIl%V-ͤ;ȥDzQרrٱ_z]Bl JyEub}_-][Q)2IJ[=iMj}>I?%:wl)aqj?1Zݻww./\BnZN)w~ ڵ؅Je=#@co 4p땮7ª5k.{r+M:{SobϜ;/6"BkRpGG}$^6nシUsS,'} [:WogQ|sz0uB  0u S']n$L h$2Mbr!aJ<# SC"mK4m>$LSC4e.B Ji%v@UO?~?}UaaM-aj}mRyIAu??U{neV[qLv&EUE_= Ea"ޮ&4=OC?WEQvzt?=nؖQb=H.-ӨrR.JߛE? |d.^ќ WJ* fq"`S[0~M{%v4^RaZ,3kWx%.:(eqOa$51Fg'VޠʴǪ`\[&moqߞMPAw6[zX{G?ƿQZKUW7Z˹uW=}ĴQy$[uc;>RMnrQe6KQfo&~ǽ|(}R춪%Ȝ2n swolkt]OlY#.YU4H6r/IaaQQ%e` @WC[ 55W,/M"vJ+}-u mԿbM?G)|YbW>.]qˀJ*WIϼ9E\n2ߴ/YD[ݯ?+T%3''Q.cj(rkw RoX k87S? wHϘwg{lgKuucK~׭NjstIK.QLQlRzXe5^wRN8[!nm䑓T5˷IC)'j4OϦTJ ^ɉ(4.F =uUšߏqT._Jb)zuT^п8q-abohjs 랜3Gn1bKVR~Ěo75MG尹н-:6/P)/Y bokP{S;z R@IDAT,5?| ^},vg֞Xυ/?}/v̐ž{|q)۶Wo&q1Wo+KW:ڮ S; @F[iK$L"0Mf)s"a  SG0"# P@5 JچoّS;Id];6T=UYV>Ȯue(呷赵\W˪BE ~t}*FtcBUyΒ.:_TQH4ZgRZm)ޜ;Nqb>|yY4Rbvi^WYc4=M“4bK*'6*פ[}\ve)[hg]'^T.;r**LK~QK =\TJAN\UT.6(pp)].(ԶʥۆlrHݻY~i qѬI} =UYp@> - ׋_)\Qu-R^cH5{FWD[({~Te:?wrKVZJ{l5 ⳓ=?~.6. oSeoxQ5~,f[7вI*4Eս-ן?z ,!'4T?‚f<=w\OdQTpm{|Qq7qX3oZYPQbgc{L1WR88t$L+G@ @ h)uq-&NՑ0u UH^$3*Hf* Ӕy S{>$Ly_H'HZw SAԼ1 @z?ԨQ=&i kַ+T)2Y!uh~UfkoSe 7GFfkַW٫{*R[8?J![Gق܊/euDK}򮺴@GRJv]-]\$YkeߨKosP4gyU~[_U׳U-BKawz_* Ӣ'G1\59bQXCI3I;-c}Gqsrr!eVXq<}N/?RZ9*:gqwb.z9UHsVXoQbmЈs Re3xKQ}zT[\x"1FjQ;U0Oen1jK_(|_ԟztOBϕIS+k1{U3qGa> ~L2Hf!?%@4'a2)s1A| \(A jV)^_0 R3F!7OhyXeBLx7e v}\I}xy? d hU &ZS2]\CS`r1Zv**(R#*&.s_:;/~^,گK~M/l u{RSN)|t 30u @Bi+M4e.$LSB4e.BufЖ W& ˡ{wiWF Ť!Y3ˏ?;V˫{U-SrRkI@;{ҟ]H՟NԿ*N r7_^?{-D=*A.n`mS S! 1~LH)!@OiKD4e.B EusuO[e6M=/nmCBfպuk/BxQ2MnX/vbGmx 8wK @E KEU kO: P@C$Lpљ 0u;RB  0M  d} $~b;~Klr'vyg"v#=!@\!z (>L# 0e!(@ S?^|@p$0~~ gRh ,ۋ/b9@8wtntoC9 @ a:C<@| SYJ&EHzb* @~A_,3 M`"ݛUbK}<7իb;*!5gt*Cf = @\&@e8 x?޿ 05d  +C'@}^{/ +N+(Q%^H @$@4h@%$L}i5 a++< @V$Lu K/?ZlxxpMjGć|RQQʡBr @p S Hz a @X$L=vi  >+2R,`عWrz O[߰^t|^+v\KuC8UVp @p n@g aCtH ӔP@2 g L'{WѽANޤy-;;r<^IP.Nn<޻8LNRA+8B  S`  @ k 0Z @43 \ k3 @ DE&eJnX︬ʳ j~}*Qݢ{.sύ!/TxƷʔxZlv&sPv)@ Fk @$L=b. a>:C  a68 xw`ֽKrd,G[nK >WFG랥Պgr|1brr @p@G aQA0HD#@ Hf:b d7O&}u'GGe]p Ir}ӦMb7o,vYb!6iX @!@=@H$L=rY "@4]h@Nۑ z cGTiھM[)'$^'Μrr@^$?s.60 H!@ aCh g 0u =HzZ) @EwB$xݣP{k9)mJ3??߲m@U$ڈA^5B %@s׆ @&@4'@뗐 @ aHz؂ @Jۿ;θ VRnW-p @< SZ B 8K'@א@ YHzz  @ C"#N87Ci 7FL9؈vKJHo-@Qv(@ '@@^MW/CH' @c$Lx: x?{%$M[#6,,' qd#bcvGbkL* @=<(A SZ.u!@`f@| S^\@G9$ݻ[N螥gNI~Z ~[oD d Hbe(HoP%UȔq @ So_A "@ԧ@ K0 @ aCT @ tyLbϪ"|66UN OsgmԨXXnLubܪ??@Nf.,]G@ ;$LpP xDo @V!@&$LigȜ9 UHzr,| S\V&@ H </AUݺg+sJy K%2];7=!.]!Z%^(e @W 0Օe^ Hf)~ @ ht @' 0eeR e$!PEV>~t8qۨ.*ݓ!3> fEkVԎbc}5X.)^J,@ H2/@R$L?CN a4@ H2)@jS>wDhUfTuʖ(=tX7^=!7,Q דB}oM+QXjYzx @ az]q3  O!3 @H' @vvr;žJ6RS d"vP>ZlBgiՂ:x96%"wz# @Q$L}ta xD\ML @O) @  8'_^Z5Ė}PKb!Xbb9@l[n!֯_/vӆuj-v{RFC@| S_]Y@AG,A@) a @Hr@2=m׈ ɧ~bY0_lr )oPcBzJd@2 W@ aʍx;޾@@F 0(1CAWiեWbUbg̐Z MOH7~3{f0> @Cip+ 9~~0} >L   0M  |It,\{=F/@G#m$ +2V @p SW @ asKʄ @p @E@3~['V[lo;>@R$L?CL/A .3 :$Vlh|,]*Ӧ~'q@ 9rDExy߉0@ SoY) ,!@4K3( H"2@ a ϴ!@ m/]ZNUvB@NVߘ?\lRlG% @$L=e%< SZ S/^ڜur؎J@ YHfYBW aUE aꅋF ?%@OiC#8.S=KJ!-GU[p!ߴa˸ό&6{b19[b9@ E"8 %Hf v * @B,a@@Xf8.z,P !bٴ-@+!%R@FQg/ ^Sp낙!@@JHD:@$L}f) cH؂2@ aCT @ u= /-=Ka頋R>r؜9s]@!0Mbg=~[UPdjjUBl G@׋4@@ a@2(8 @ڻ8-˲3 *" "[iVnZZe"df+ejn/+-EqPQ70}>v-5=* 0-Ѕ1,ȎΌkN*=oaz-㹨oyvUq:E}9Qҫtǹı굫s @@.P`@(3i- @":4]ɧһK__/u=ڧS?{GTȌQG yTLݽSY6_&,- @ Ӝ! 0͇>  _-|"@ [@`Zct JSӓ\Q_whv޼yq`~Qyׯn+.Q  @ ƍnjo:u Q;>  @ W\I" 0 N  di\HH@`#h @@~ityUUU^  @I .cۨmۤ\DȔ4S!@V* 0]) @@9 V`ilZ%3Onk[ը  @M6% P|[3#&@'ȥ4"@ .x7ާ @ {g]|V4Ȃv_hgu߁vHmI @`ӕ8A( 0-U3f\@`rg @LUȐiKiS8>qTwؿ @@i|q/D眨6 Pcǎv!Q+4;WOQE&[wՆ \is\O9[g(Xi. @ܒJSsލ=QOs>Y0z @@i|ߌE}꨺g-}`tjChb'@L®SĀ @@ LKnIM-?dZF\aWLT( #&q Q+}`ֽ1=aMPuWR  @6 oiW@ȏ4?z%@( 0-U7g×WrRԳs_GAȯNxgi Bǽ6ӿW)]Ua @ L[6ȏ4?z%@@/(_i @xr/>_={>ww֌J0yml8+ۺdžc6jChr#@ Lsʭ3`@ @@LfM)0eJz vۣN瞋;S9I! ӳ/9+&OGiuiL, @ LsNCh9Z%@@ Lo͘d[@`ma @@oi_ZUUժL-.[]]eiO @ L3NAȤ4"@@ LK͐d[@`ma @@Eq+_ ۯ̖hՄ @lLM  @@>4(rӦ/ӗ_h_Ҩ @qqFmucW-MOVF$@e 0-E6EWŘ P:YK3!@Z\(S-63Qo{u׶K_ B};IQUE]4cQIWLNkD!@W@`Zkoȩ4:#@S?  @@ss=HΎ_=jNJ;Cz7^zjCZ"0s̸kGuA.|2Ս?P:cۥZҗ{ @: ^@`ZKd(IiI.I @ LʫqaI;GP)._ @4MΏ oӦ.:ڨ3>Hrb @b랻bW=sE_j]͋6MeT @ſf@Ls¬ȳ4 { PXC @@1xt>opUZm__MX8??ՆOV =Q?>7+jczE]cգ @/ 0-54D@`f @@L d! A@`t] @?>n]7һLbˣJa@e&dIlNb59I˺o]ۚglHOƎ  PӢ^>'@@3d_@`}c= @BD`H{/H2{Nz"n)S$31L _[;wfG,گ[ӏ*ֿLO @@)LKáy_ 5ih5L ^@`ZKd(LS'koc{٨W?QΛuqmmT P- Q7\oأqbG;K׿ov @" 0 N  @ K,j͐  On[ܨYo8^z){QGܹs @Bxs|ztwaM~ԁ믞:߿7(ٹKyQm @Z r" 0 N @  PHO<$qƕ4 @b3Z!͏'L\u%QU:ebHOjCW`ܹ~;DmLOD(͠  riI>- 0} P_C3 @@vĸҾ ӓk֥'bUm]Ŀ^4 @r䈘z f8=V6O- 0 9\w PӲXf$@2B&>-pSғXVOO^z]q|w Y/8->VP:ٱ.k&EK=>>]gK`@ [inF! 0-u6K(mii @`n\s GvZD8E!@V-iu㢺9gX,ݻW݈+ @L®SpH @tfB' 0-57c?QZWusһ.ط!@>+SܩW iJnpy6 PӂX @@3tL @T`ʘ;j.XaE曢 @ ӗғW^G.EiNڻ_M췺ću0%@ N@`ZpKb@ȍ47z!@<ݬ (cn༨pD3f̈I P?|L7w"W^Zu]]vcQm @ޚ* 0*  @eKe! 0-e6ITT|"=2fpp~ @Y9sf;ל.,wy @( 0-U3f(vi @@9 Lq͙<%=Yzŕ}b޷pYd  @@! 1 ;)  S?(qi/ @@Q Lj T@`Z oҥܯi obԴ?rdtEm׮݊[U @ O'yBl'v>O#- iE @@G 0mK @@y-Z*Kֵ{'F{.meΟ,j@ԿRKt %-E\ 6YI @@SMr D@`Z a @  jdX@`aP @ u_azyFW6;ꐏ;zJ]m߽ac&Nw _z1qDmO137npVUkx'y}){ @ sYj9Y' @ 'Ӝ04K@`,. @{dN CFdiר+;8? @~+cϿaݝc߆Ȟ4{Z&@@N9a Ȫ4'@6(_7Q @š# 0-6S(]i鮭 @@ Lo͌޷6~ըm۶﮻Eғni @ 겨:7jnilI)]C7Ll  @ LFaF@`g @lLM :iM Z}yxq^N:} @@k~\;}sd:i@-7=_~"%@ 0&  i.A# 0͎V  @@&Pr(pˢ^a"@2!0yh&ꘚ.򚗞?cғk|~ط!@' 0͞  iVX5Jr* 0) @@r1'0r|QO?=,#3 me7K~9eK @E@`Z,+e @ sYj4U@`T) @ 3fWswo>ȷs.p?n|H @tfF@ L| dP@`ALM @ULW4%p3>3/w0=YVǶqT׿c-jCW]EEu!1Ȳ4'@+qXte2 @ L3g%4K?wګ"=QZ;.O򖨇v3  P;՘=ދڮK)OY;mء @~D@`f @䲚@0(_j|w:*=һMw6lXT @|&M|Qo-SF+Rr{ @'C] @Ž~FO- 0-1:H`Ĉ$̎Yo?+_pC* И#N_~%QgLOźؿ&tk @OL?#) 0ͧ  @@q Ls݌ [@`ZctIO[:>voNQأQm @w \'IDATF >sTfn'o @Ȯ4Z'@, 0-57ȗ4_%@rO?jԶmֳciӢ^vՏ띷C̙3c~Qx+E;ͯp᳕{׵8Gv @ LWn r" 0 N @@ILKbM \@`Z dx)'??d |ǎI @t9ꈘC-{w4tmOk~~6 @6r" 0 F  @@I LKzyM D@`Z a7d7XLL  @,\0کWnjȨswIOvܢ} @Ȩ4#@e) 0-e7iȑ4Gк!@KzW@!@dU{|!_|m;wfS @"># 0-2R P*RYI @\ Ls/JR;?yM8qN(8%_ ?GՆ mcFuHԪElw} PӢ^>'@ @4$ 0mH1 иqg  JVKv%nZ.Yb;*+c7kSg6]@ȤͿ9 Q>>EJE@`Z*+iM@`7z @M6e @~  !mQzvyŮOkWPǚ!@4QcKLwt~_|?ucOOJ^@`ZKlJ@`+i @-T} PNrZms%@ '0=YF]zJcLڽ{6 @\ ̚Ճ]V+<9aܵS=}cO6K @2b*Y/ @@7s܌ ȒKW_K[FٙgEmWYg6D!@Z.O:,-F6s'oՆV\ Ȫ4'@2( 0  @%&D@&MNO^y&-kmdXS_?{64GȌ9g {cNھmjǎq}[ PF2ZlS%@ zjȞ4{Z&@ޚYᦺaDoCܣ  @1cF4\S#uIz4;Z%@B) 0-e1( @J^@`ZKlJ%3gv?m؟N8_})v$  @WLi&GVUjS4 RJi @\I4U@`T) @@) LKqU͉f 1,_|mX.Q.HulŲ?u QG!@̙3cJ_;z(Xt6 P&2Yh$@, 0m2  @%D@yԸq靥GT* zɥ;o Pjx1K(jW4:źں8_.}Cы$@* 0-Ѕ1,r/ 0ͽ  @dG@`W Pu}.Q[uF=jC(uS|_vܸ})z*޴d8۽ꝷCI@`ZLeD@`f @E$ 0-2ThՄ @n){kѢ#bj}Aԣ+:F<Ϩ6 @rc6{7NN_3)ž (Fi11 UiVy5N  @&SR1bILvQ,I7ۨwO+*&5=m @"p-?9w N3^_q[@z @EhL@vq* P:YK3!@ LWn e"0qRz76:z*^$ @TvbLfE\fć׿^55%@E( 0-E3d2+ 0ͬ @ޚ\@`rg(L}&j6 2i @V*ˎŹIm8n^YZjCĉO @"2fSk @@LKg-̈́. 0m+ (1SNp=Q7۬t @xnQ/; QkjoL|+{tK/3]}[ס  @@L ` LW @xųVFJfRK%u1iӖDVO=.Qﰨ6 @Ko mx8R}y0(Ȃ4 $@|B@`   @d%&Bv[)Sӓ-u:ڲW~Ն @ 3>[Ћ{>jӻM3ӺV @@nq 9Y @F8EE# 0-2PV%p3OO1K|bV;~jO>4  @Kolqgif .'@ L3 @@ -s`ȄD3<5ŵ]vHO8ݩ6b>}D!@Ȍ}6rIQxKfϪh @Ls r+ 0ͭ @~Z>VX hq/ÌͯL1wy6 @'vܼ}Ժi6uojC) 0ͧ Ȫ4'@R@`J" @@ L pQ \Czbw;,௧g?cjC })~oiQ'rzԣzݢ8gT Oi>M@VYa( @ӹ 0. ȎpkF=!N8>j~A @|}wG7F|z/k5 ȧ4&@ ӌrj 1i(5D9YdW`%U?4oy0j @Q7Ǐ4?Z\>'q!@C]dT@`QN @Z- 0m5 @ <9(j6mһ2ӺV @+:ޯ%:X3=i:pNu @ ̛j Ls ; @*r Z@`Zcp4&pN7.o.w @ Gs΍^y啨55@c^y1_?k]+ (i!1 "iDȺ4: @, Li'g wvԩ_Q6mIϘ}ќeeF=  @O<>ӢV]ŵFK溚4 @ӹ( 0" hKZ7|u QWEkV'KOsw7Ն @8N =aXC1DJB@`ZhJC@`Zh @ L+zȦ4&@EoYcu/,tw,  @@q lv1eGoN^Q'@@q Ls݌@I LKzyM RJi @ Ls+Z&kq㗗uӇ/Zv @@A Ԍ|#VӪ7Q <i\M@y@% @ <># 0 C*c('|o;~kԑw:ەuQg|AT @b"v O޾8׺EyHϨ6 @ L(jLªQ @@ L ~ %- 0-59)p }Ώ a&!/袨Ev @^`9d]V[uv*5;k}kT i&A@F @hEtN<x|DmL7:leN?󫯾z9I P\O4hP,<6M|xƩN @ L3 2# 0͌V @. 0-4~ Kb?zӨ?I @4Oc#]z?$}CN@Z- 0m5h'@4, Pb_A'P{J.}11J @[mI3aݻTGأmuDJ}q} Ii&5E@-bs @%&D4xE1R>=tT_'Ozo~Qm @ɓ&+/?)V !@L3¨>) 0 @-T} ikK@mgE]izTEU]mӓߚu޽$@ I_z1vY  @@s7 L@q @ cӌQj6ҶoFCLLO}/霞@'n[ @ gW黦Ս]hIL."@%Ӗ @ӦJ# 0mk h1GwpzgK:D;7vI4=W߿E @8EZA̞Z% 0m h)J!@hr#@ 8F@FTuQ:,j%~rY|8[ @x꽏3j <qW׌ڱWSW;J* 0-ו7o9[W @2ᢛ2( 0" Iw2侠۷_7^=[lsϋz%G!@ {튑Fmm}ݟNNs; ^7q6 @OjL@F @*r$ 0mh7orA}=gϞvuN @hj\gu76~;j 4 l @2! 0̈́6 @Z* 0m @~ȘcF[o;8СQ@C @8ldU=qmzi @@ LG L3g% @ Lo& L W_/4 @hKΫuzˢ]:q}C6Kl  @@P"@yy @ 38j L{#@,IE*i֬_觿$>~]mעVVz$ @ ֦wy7FGQg0j.?ݠCwaT А!L P$"Y($@@ Ll @.j^]l7_Cn6j&9Q[3(w  @@A,X 1f̘C?%}fEm%~;6 @`ԏ~ @"2 @@`Z` M6;GNQSZe3y; @ I`ҤI1'B @@L e%@ L h1  @ cӌQj%- 0-59~G}bizv:~zz"5vl @w#WGL t y]K@`Z\e @ Lq. 0-' ӻJ>G\?>6Ik܅ КK @7~pd d/F^Q @@^y9:% @@r5E@`Z.+mZ!޵qȑke>iݥ3ޛ]vw @(`>jﶩ7E3'EyQ;oҩuv @fC +Ӭj @ ƞ}Û/ޓ^b' PӒ^^#<i\M P^?'@@ْ."7/՚E1_5 鿴  @@^JܳWvzDiS={jǩ[(iiI/h\@`ڸ @) 0-u5+dJ@`)I(@3OQO_һJkkIҶ*2iJԞ={F!@ PgGG_~ޘ1cּKQ_zuuֈjC- 0-52 @4( 0mAX. 0@nvlSEeԫ:'M뮻F!@ @c qӓzB%, 0-55S? @Z/ 0m PLbZ-c%Bg27/gEۨ씞0M?>:  @f ߢB, 0-57~ @\@`r;w @ŸjLIy±oE=tQӖ_? @ _{8vνջ +|"@R*& L2 @ L@q%( 0-E5%01=Az5D=dAQ?_<਷yGT @>+0SSmGDIg/# g,J?׾ @@a L s]@F @@ L (yi/ c`99N?eH_[g_xA9H @eist}]eϺ?QuiQm @>FG #ӌ0j @@=i=;(i,'=d߾23gN|ҥ.q @/r|:oF}GmS& J@`*>7 @hT@`((Zi.XuקIߋ}9x;C @@~}/!'u7һLY-5]Ck6Kl  @(ELIyy @@K-Qs _@`Zkd,chH3t%Mׅ @4.0o޼;G/D޳1Ca;o [@`Zct% 0m  @4K@`,. @hEt^Nޗ)cړǧ:ebc/{]N0}%@ @ o ;.~_v8}c,o14I@`$&ȯ4z'@ iKC LF@[lN.J4dDw+57 @ 1wmu:>5DYfU+ 0ͮ  @dR@`IMm @ @},=9:v!o-:yQ1D!@ @ /  @`U@L f)  @@Mr! B@`Z`xs’zԻ~'nVC @@.ef&j-{ǻ}[6mAȎ4;Z%QiF95F @ /Ӽ6 r/һKxF;|nD7z @E`FNԥsF}Q^uGi0 @ gӜQ@-s' @Bڊ L{#PP'Utb\nڷg0 @4_`ԩq.{>ޏAl}~YTȭ4z#,i\L @(ELI@ LxMp~v]] fG4 @ $yu۶F}(Vjsh6 [inFI&1 @@Q Lj 2z/NO~kZOVy5uͷA: RO @dE'vw?֎=Y{ iNuRr 0 @r;(w6kIENDB`wagyu-0.4.3/docs/overview.md000066400000000000000000000012561314062220700157730ustar00rootroot00000000000000## 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; } ``` wagyu-0.4.3/docs/point_intersections.md000066400000000000000000000120671314062220700202310ustar00rootroot00000000000000## 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 op3 = op->prev; point_ptr 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 each side are sorted by those that are closest to farthest from one segment on our source path. This allows us to find the path that is closest to the first path selected. If we process all paths of the self intersection in this method until no intersections remain, we will always produce valid rings. wagyu-0.4.3/docs/simple_self_intersection.png000066400000000000000000003354031314062220700214050ustar00rootroot00000000000000PNG  IHDR^Ml9 iCCPICC ProfileHWXS[R -)7AzޥJ! J AŎ,*v誈m-Q,,ETu7I]|3ϙsܙrPr˜`?fRr $)0bEpewm\UW8#bB| \-@hz)~H 9֑49Cb 3Pg3`%Ķ|vؙ,΀X ywq23m4&1Ȅ rXroa5S#mo0)ܑ( n8$~ؾ-5 PaA k2؞%B{47ӄ3b8K3#+ IB Wz03.Qm*%DBq(;6laa䈍P#l taPٰY4!ό bI\QR7 Pp0b}K9X%7'8F^g차 vķ#.0yYr؀ ?:N A8 İ Z{z?H`!\`=Ha qhO6PeT+Al@ Bk^{qWmď<2+1@ !-Fy!؄otaɅIGrNxLIDej:Hs&-h84g7p?q qG/ ssG}IYϰ^RiE1w5g؏R(֌.c':`X vJGWJ-F-~aBY ?C0[g/2gیc9 ?o6¸MwRcp)o:7p{T[,,piG wFd`"q LUL0,% f {pԁ6p܃k}` "BBhB G\/$ Gbd$@,Fʐ5fdR@!vA P .jG]Q_4 ChZ+ЍhEϡWћ}cSfbXcBl>VcUA>D3qk>Cxf|/^7Gx@#PB!0PB('&'\{0@$D3 ܛ,rV!Yb;O"HV$ORE'6ΐ:HݤdE>ٞDN!Er>iryPAED]!J0[a.k UœGɢ,l\ܧUTT4TtSS\Q%GjTK?u UL]AC=KC}KLi>Z>mvAdQZTTԡJYADWyrrQkʽ* ***,**'T:UUvQU^V}FR3U TT;EFt:~ޭNT7SUR/S?ުާᨑ1KB㔆1LJ-Ƨ1c|p,spLǘc5}44oj~bjjek֪zk[jOҞ]}AwX챥cX٩Ӣӯ+ݤ{^W磗N^>]KN ӗlb v 2|`D1r5J7Zghgoa<׸IffKLLi՘7{W߰ ZZd[lhD-,3-+,YVVe:gf7ѮȮ=۾!aCkG+Gcm'SF/.B=...[\:]]]^r#-p;=_B Y2qĦ0jXl4bbڈ&Ⱥ(6AYt^o'ULzc379;=v_@_ʸ{) $J'KKO!$N8y)NSJܚj6uӴL;5]y:kTBjbϬ(V?-4mKZ۟Yzrp{Iᙱ6';<{-}vT졜ĜC|5~6iތY3V$=o}^0L[QEl.ĪGgjm9{gA45hyvGo\`xA{Qe/ȶhMѻʼnuwSMRsǒmK񥼥mZSz̶r+?+ZW:\E\_ukkTZvs]w맯\XmexdcMƛVm9s C[t,~+gkGOmʶ}~{G*Ӫĝ;J/ջwG7foSKu>}+kqM)?h}p!ơ_Su$HQףr~]WY'Oo?1DcGl~sd)S+OSN:Sxl﹌s]O:iRS녰 .]<|祓/z;~չ˵6 ;;]~F荫7#oߊusJ6;9w^-;xo}*zDswSgϪ??b%cҗZzZos|p w`}{?~lϤX|iPА%dɎlhz:o@Kgx(_2AwF h2'=>/ Q*a3 w@F۰屨C04VR_CC[dp6O~ kJQK QlC pHYs%%IR$iTXtXML:com.adobe.xmp 1026 1118 G+EiDOT/(//mWKD@IDATxwE$ArPA `FaϜϜΜŬgzfS) EEIJPD$=ƒWtЩf[өSv& ~W#oNAjV]^$WYedDq\Jr7W)bS+C*m\*}J0n,]r>$_xuՔJ + 96W^e.?tXTsu\u_*=av5F$/UUÏ;G߹b$#$v[-jo^ 6`^_:ܑkiKjj.Kpp"?oܑGH0G"r PE"@(H{)iR#`9'5`i[LNjj AO=JCyf[ gB;ǟH^u $MpcI}9Tm7;q;y ?p-A.4Hn6Zӧeez|~$W[0~ifnL*gOV̓qrVTD23N;fDJtyMӄT靼O/ڶm[+2UEu|bKUzG7)>u^Wwh%1z+k]}ϥ駟cttn:]v$ϼFx IݗS;T<8w=F孶*j}4o[ɈO>OW_=࠾}$>#K݃]S7qs5?>Icnj&=뭱y jIN7i_SO:Tw ^ͨmGOGJl^A9x/y_‰GHC'w- }艴*mzT$;oʼ.O8uQPU/ߋoN~s ʊ],X@X{ن۟t+ɽګ⸸~=n${,qy?&L\}[t;R#P YԀ nh5hr>G"*!HTHTD@9 |T ED"2$*s TZrG"sOjŻSי#1W4/Nݘ3;w_|K2~ԡgA]$}ʹ]߇!|NWYۅF1dXq|%*ˆ_gU*wgz=ز~I OާϹʻ[RewޙW_ɋxFmT?iyއ ^w!{TeYGO\{Ɇ&6mP}ŽA7gQ?'ʊ}{_e+$otQ#M?i:2[;Iް*ЖMs ~煊Vn\GC٠e#yM|-`o SYMr/"yg$*sry4Il':O)SӏFGM+"[wKmߦݜOd~+s嗢7GrGZ|oH>#efg)H̝P_XwI w;i4*}Y41$+ߑ5LD$?v ]BQ|*St㞠ѝE쾇t 4d3Ο_NV;_qWq?npԶI ;_JO<1*ϧmOen6ߐ.G='Iκ馛U鴟Nd85^`cR#Pࠬt[mnC"4F"`a&QqD@e>HTjP$%q?PW;7߀ϽVW]|./A[ ߡxTêJ4߮ԺLC9y*֩T)&FT0gʿ-/B=z.ɓ%i}wD/];wQOc;?T>߁@m|>>}ӐǮ;4nqؕk]}'>~utKӐ=yίT8<p^;$Ƌs\nxgNDk@zYO4R}ڧ|">//y͚9]~s֫Mr HvieU\Dyq߅4X!Ϸr}}ҧrP9.;~wZn/uM>`JQzT}P]9s>=H+sESA_[Nua.~}K*? ;߁dI&Q;lG+sG$3ޠNφ}JUy gr>|9P^ AOSg_L9Od|˪qTfG;h\^#yI?Z.N?2j\Qk/mڏ`=1q;<|G|A KţQ.Pw|~߁Κmwډ)=J6֗qҸOcwy/|ٞh=7WaUWi]|иSҸݞ4*oqK[Zj1?8V:B_rT5$Ӟ^©Jܑp1&5`"P PGm(,!$e$DP"Tѣ \ $J u$TĒYZNR# 8h|NMj19`73YRW;,'ۏ>`U}P6¯ToTߣ|gWJ_T9(zar|Bay[yW|GnRH*=ʞjTJiw*ua3Cq'<ű'ARy~*G7Cq|2!X~IW\X3tCeR|hIr- 2Jocw:Dzȿ=ق.ڭ5Nr~ң̙3_w1߱wCA/_i˽ҽHJ;"?M;? {w?K*|b=ړl@)nj=|2y6wOdz0oVp#FtCFU?&U~o ^=ee>GIU&Ys{:Eү (=ҫ'.79TmQsNܤ$՝dMJsJ'Da +?|gYݨ5O/*̚qv;Ão|?vKҘ[ UVR噿>zj*x ׻[oEReШ_U~[=&WnPM$;]|lԛJ ƿ_}rh;믽ͭJ|JUW5)_7w}?ɋ5HUn [MYa]xj\,#>΂P~_wnNk?܌Tuڮ',Q\^ūONugpY>rxߠǚ[ 6?L 7d>~'}'&Zmґ+ysZPzo~j$%>y$TF CkD >D@&GѥܠH4rHJDgAԀ =t]jY/U9Ezt:ܛ3j>T\mttdRtP4}mԧYGB|gN}J}~[;3?{8*9ؿU;JU}|'({rO"^XsoAw8r8).~I5b.-wZv-Iov;s?\NJo(4ش7/m ïp4*uh2i߹|x;<],ڇ)OKu3*^ڙH7Bkֺ LX+Z`?7 jT,Ou ] ;TFJY"9N&{1PKrգhȣX֬azQEmKVs_UE\󾥮uy-Z:e4>mSӭo\7jV8_CXN3Y3eeƩZUEpasxߟ5uz2Wq3v+>1n_\KUW/8շ^qqwP%;/;_|ۛPW!⧓h۳XjLMjC$Ќ~kϯY:q'Sj>}Yy,uaCcO{o?Mev?N,wa>1{Ԇ]\*qhk\ v݋*R͕S[GJ]V*ʪ4M{j= }v܀ߪUQիo=Gj]UF5/<,-|ܑp&5`QD"'Jz$ʼ;C"GB"@,E"HTHTjP$eV~I*w$[VV 5`b?lבl^-}yMC;3fͦqmc_C#lw6_3;=/ֻ0/I՛pf|EYnA|g/l笉̥=Sa? 5}9C$"| IMݳ!T/>uK'1/86;+jO<;C_;SwcmVlR3y"ǏsC|9nZ,\Kcׇq\'=p[ۚדԈCT nW;I6gw|tTү]Ykǰ[^fKw^ߘБy`{/]OX:j%w|R=/ ҉,wul҉;:t!wd3iܻG\ /tژO"_?6{zthGoуT[#݉=v/)"Iw"2=~(F!|z4.L?NEDә֥9]|ԤYIR8THtNI XE"D":/$A"2$*sQH(Rr.A"e$yKH/m~JR#39kq73յ ʺ:rג|a +ݡPߝۮ3d=/ѻ6mS3xӖ'[qw 5Oye?ڍuSD?>"sU캕ſ g;fSa=3wƜ'zuO(>ߝw[7;gq{"@8?J'4O;D7(F'|NzZh\ǥwwOci|"5Wgt<~C˷g}Mۯ'`w"@Y%F_2ݜ}'U9׎b;ΛPOL(=] ;i725n\~Tnމ5Y^-GMi*9M>>5|RHD?E>klgi8s=nu?eeڈ\^Sz_xH#zIlލwۑ_ VksK w5}ImլiՓU=:_E4o&y͍\ziMv{_*w$|EpRVaC"@H'MmPG"H$ E$y$ʹ#HTD󑾿JD@vc̥ϻ\R񟟟$YɀoMz|/yaI;pB)]ۂw]w[ҫv}_UKw.w>ޙSGySzYQ2nݚ,?bKQ}]ۺv+KYE8N rW?=FQRa痞?M=?l@i|dNpwB5L+G6T =mҼ;jAy7^W:ȗ|GE֟6.\(DR}>sϿaG/e֯pqUrza'~js&~A J_P+2w/%<6K K_M^}5ҧ\6) ;E”.$ "P#AA"OD@E"$7DeD@e.EŽD"׼k*$y)'5`S.ga_zl#VB]bAؔi3JwW]1]ؼ+/ZSsKO%ofǶ on{XPuwP_T!nF=D@ro/=tD #pi(X0>g 5y[X*w$4Ro$I"}s?~C+={jX;.I4Lߏkwe Z 7e??r8X3?`|$j|Rcd;㆚9+Eߛ#fFTHoH XӠ0M4nPM5zfI7\7q.(/lC*w$j*$8W-O}ĸ'wmO~D-+u"Y=q4JyI4ZJ>stZW"=Yi3H6oJ+Y+QvrG" sm܁D-+u-g ə&iY 6FhߡGzR_D@zzԀ wOULV(}|"`>_'pf ۤ!/R7GYONo߾$RoHDj_jƅDm²KߠȦ~vobrof|]ҹ#ߗW!߇)5`m9aՖ'׭;事s|"8`JߠH`dcnj|ɞnfr]ҹe{vxZF{kqT}M`qh{g`# ID@,5`m[dW%[4㳲mP ьltuKD@ВW!ާ)5`MC_w/RڢIvkp&~78+qK? --[Z p]YE­w?HMY:yO3{{.VɷE$j{ɬe/R# -RGjƃDi'}mp6.ZmL\րKؒ?|+s s>Ε&kc=F*GM6(ΜJ폏xdӍ H4Tߏ[we Z 7eg:1C [hQƢ=$q*\Q PЮ`A#u"~+HR#6fM(Z$\Ԁ9}kO\?F& ڒit瀻*+n%A"nҸq-|o|N繡 ϢTHVopH]6(vW+G;~+HҸ#E&UH5"#-5`#?l]$nj> b8pWV_p+-5qSƽN޿}8#мys7 {xpWV_p+-5qS}Tuo4r$7X7<[ޏrG"O<*5`C"@G{7w?we 7w$6'<ح nM(x`-_jw+C^)r$n'>:s*Ὼwnvt W?.w?ܕUW$Jpw;h DܔmsDZHӉIΙPOrĹ$5!Qūo ㊂bR#'[R! 3e{l6 ~} ~*+nmHT'j[ϸ^+Cߠ)n_IN?g|,'oLD֫ ʙ ~+H?j8-耕MβOG^T>(P:PŘl {?? ުԀ>?֋ ~}~*+nmHT'j[ϸ^+k-h?)6{EYr? Junq5 _6p6fC|Wh 3I>]CbZ]ZGɿFe˖ey-`dzR#'[! CjR}kw>?we w$*\lJDmgTԀsL4w_>{x'~:mf S͕*pNpWV_p+mqF/ !:=g6 P$93΢fEfbέz1ڡ-qSn8{2孬0I{e +@qP%;ER6,81A?|Xw~?we 4w$dL_ 0z%gZoLu'9oI;3޵Rݫ^ud$s :w?ܕUW$ʤfg>9Y_G:ґٻ_]HFaqR#'[iNMÓnP4ax pWV_p+rG"736ܑywFmHK'$G=lMՉJ'r@ JzKgg=Yw?ܕUW$ʸ?;b8MTT^#5_QznQkqT M@IDATHVxpD"@M3  xᮬ"VD`okVܑ\Aۥl^1qZZK}g׬~tѤGtq7(:60va05>'a~wMpqHݻlsq{vTH|FK ،⌜6t 慌BHVS܍ =Wbp*;Q+hԀ͛;f߭_g ߍ%.j p@|Q;Nl 㒲p Q춃]aړr?kx)m~6mڄFR#'[%  Ytby:Q~] ~*+neRHfy&;q=X~R6n_hi;3dmmmlѢܤ &u,/ $ݠ6e ;GNwe 2-ΣviϏ}ҭB5\2f {?? ުԀ.H (:prpWV_p+rG"71ܑHɂyǟW#$MLy;ydk}i7(S?ᮬ"V&>9>xǩwDZs-{:ɛnU7Nr&3+kAmRR#ԓ/5`?)w$FD Y~}~*+neRHO}Y&;ōɪ+U'ƴڌװ+ Am+\܏Rմz5 v)t9а . L S@30 @P4^\rpCIxI=t(կXd~@Ψįx{E"6{i#qՎq7(U11pO732pJG5Ri\HDL!>31 j$ ;.ş H ?1׺j=R43fBy :cxaq7(eT'%f?V %7.IsÖ'3etePM@*w$bXl޽ڠi0FM=A"nEʎgڙUJD@c/ ldh)~#Zߓuj yb|8@@J/Q?ŵ9neMpgu}= OMFڴiF.D>s!ŨJ@*w$R61tAI{!oovp#pZD@ٲ}Y_TH?RPjZL>ߏӪG7ͭvx%Q`Cp2L 3?|C#CmRF>L) rG" u 5` SܑH)at0*.pKN?5J+GviD@1fgmR#KBj1} hWrɗvlnu+k/z!lRt. Wjw[dxZ0Oƿb4{lR$w%9w؊ʏ>I!ϴ3k^J T ]H9C=~iG{ZrfƁI{RbfYAmH(3e"{ŒK X'h$-}f}{b$X4,hB"9i$g 6ݱd +Q_W͟ɿR_W}Ǻoߞdޖ\jT;p^Hϴ2)aJ $kbߴܑ0ml pLܓ3 㒲pUZP$dɴLYlrG" {dR=I}ʔ)4~dm-.L>1U`JPEЏwe Z ;ql1W$=[QϏd{\㞶t1\W+3L@JrG"@AdR67-w$xC3jG |DYϺ.A35&MYVFl 8 pWV_p+-5[0!q<̓IvhՔmLru!|3ͮ"{ڤrG" {dR=v:6~pWV_p+-5[F".߸}3/rG" .,}s?ǝ'٬W3=14wLI-N" y*l1{"\;q6 EhMmwLcǒ듼~ 7ܐx1K>jM*w$CNg*5`B`7w$F{˃kr?we |jL:߼DuH XN N:ؼ-?䖿L!y[_敨ܴ*dC?w?ܕUW$Jpw;h D>ztqGT'X0RakTH54%5`5%~H+y 6~ ~*+v%v&.etY/;Y,K+F4n&Ya'$9hq)?`Vib@6)iwM_`aod9o7PO'^ ~nߏkrG"O<*5`ŃӜ4Hh:c<&kڪ*o1v.mvY';Y$G͌\v1$kV-q"  `Coi, NoQx IM@lܑJ X4'(;4=I`Coi>o-v*eij=Y#;Y 187٧@k$;dF)u"`jm]2*+n%A"`94$[~҆C> T ~-;~AU+}sG"`y)4=I`Coi>=~FgZoR#;2ڗBq/'N5\}y$?%ʵ|ǿ)˙  Nf}T!O 72"pOxgtx?14`ίHΚ'zvm6܁x TL+ߏӥrG"O<*5`ŃӜ/H1 =%8CȄj=!0?P$qJ/_̬5Oܑ9qp ; C?w?ܕUW$Jpw;h D*:"nRzk,_t2}NsP?N? ުԀNsYD1`C7p]YED":fm5a0^j-Y~鵗kճ)l pWV_p+-5WԱ7/ y&_E)Ӹ"}N`-IDxoUj9rG"@ ?wedd>SǤ,HwY>GKvaN$ڭrGǵ:^2~+Hw$+O70 ~8]hN#GcǎPz>l1,IDxoUj9sG"@3 :r?wett|>7rG" Gs>Nj{M^_䪻Av-}au`ݹe7@].µqᮬ"V[Ak$Yk>@" ifF;`I0si&T^?hs@;?V1 qTwGC̀npzhޫy}SmڶY\n5R#'[iN0ܑ džܯp]YED"2f}UR#Ո3ޓFyK X8؟Y$s2hC_~+E?1׍&RMI+wg|+c_sעs VS)T wED@#!N}+g?Z4H([uR#;2ڗBqVѹʋN%YEG:ѝd]k~&Rmc!nklюngbeEۋ?y|g_Қ5'}"U hxb}S6wAyaR#P;w;y+ꆼ2 ,jQ#"L_^_!ܮԀM{z_{58In]~2`#~~ ږGjLRԍa TFQbdF8Q|G oٺ mm V$'OA 9H鎒]tԀ)e;@"1$ qQ o'j#"XL?? ުԀNs^wޢc>C$sInt/=p*!k/Eߐᮬw _gnFrH5R/ˑcܩS'/zҎ8sx6CHX oj#"X{?? ުԀNs^`>=q.\'W*][ ojQϟ,xǯ}붛I$~:Ist =~iGKD@Z|Ԁ9vq0!`rR] OjQ#"{?~? ުԀNs^p5W}C/K^_ yQjPIeQ7I9&x HSTsͼXiE R8q"Uu5ؤU>G `FJTHrgI ؼr#P#nȥxpWVŔgTHVxpw]EgkhsŇ? wp9jk!exQ7^`W0 8w(95c;: {McG3ǡdTHu.4J \pg8HT 6mC.ͅ#Z4H([bR#'[iN{G}'jku\gRiiPaZNǁi2^z)qsP1k|fh9@T)iMuԀ+o.pgH(,ʆ\7ߏgwe(P3āTHVxpt= Id@Q6( q(&јW |`34mʿɾ瘤\'gfbTHnuH .[]@" 6*! ¤UryD@ '<ŀ=BY+]Ƒmކd X0}`o{~$^$@^7n[}i4{jdQ\0=g>0<(?s9g&s4  P!;)Y!R6]H5:r_LDxl*$lED1bR#'[iN5O46{E;=6iONK'*U -klpWVX>0BU6yeY̓q;G-ݫ>+~|5~qDnVjJ;?p%0ѕn]mm+sqUUH|>ӏ_rG"O<*5`ŃӜ k 2C׿mD-lBC$[˦?w+q"zv62WY0Pl$#yg߼Ϗ9>*ް]jx7u HD@EwRj3nHiK[7䶸 HrKYDɇ=3Q*w$ăxRV<8 &ӦMN#˩ʇJߡW a!řZwp^/.ׂOR#`׹(5`s" Ms̡]; ܄hsV_26mCf5/rWLDxl:_%4;ߝg\X.;nK7rݻWW+\lJDmgTԀ(بvD"*qYߐpBndJ5YD@J|~@*w$ăxRV<8 &{.9 uuu$kkkK5MP7s<>-M/nd}C=+om5^;]U Z> ?4CrG"sBj|+6hfG"fT_7~|,{bp GQ[~BǶfpF~۫CJ)Mma(~C^- xp]Y*xV", }_7R#'[iN5jG"@`Yݐ\a%UH(B!}fjcrW}K RRZ+Y TY=瀿Oj?ws%mdJgjy,THVxpwM$4A& y~\~+YDA"FbR#ܗ!5`z8/l@5m[~2`>H.{qu8A@dmCw]Y~<~+'tE394 ,gϗ3Tvp/ji?2)\D' ~';~AU+] $4}Csy~< ~+#\@ptMID@2|R6|hw~ ~0d c|`&߸Yެ}3 ɘu2=]7M4>pV/ -m}?XSvځ3?Na>S929dneR#'[iN5jD&؈7}3q1ᮬDbѥ 縜CmID@1ƚlg u_Z&,?Źu|'9|"`8ē7IǾǫMiJ'yV j$m=]ʪ4 V$o'&L>?_>LʰL.&?N? ުԀNs Psx$4F !nߏKweU$g mش 55R#H@jVl*ݯ3'MHZuI=Ϲ\7lj':V:'xRچ<xpWV5Ⱦ4ŷ_/s7h'ԄԨdL?g36OP*w$ăxRV<8 &@AHh9\چǦ-TH#\Ԁsy ~ݪ4&2$MLm-PS$[,_ 1_mcN;7ߏ+weU CPS$y;ϳ/˯I.$>g}}PWSG&Fft[FeDJlCg_p]YH>dB*wp y~ 9d~b6ezoh<}^R'>^ZQqC WlJlV)u3R#'[iN5jD@:Hِ}GʪH(@ q1<ڐ#.ՆdC{,ʋ yQFiw;\jſ4^d-N9l}ELHD@g '|ŀ7 _<\۵!>ⴂ_ʪ/HDA>ZdS*w$E*5`"b ܽ`o4jqgK^)>+4㉟CG.l 7!֪pWVM/rړ|넵R|&\Y$Ͽ\.ثقL\Oܑ0)#]"p/d?=jjC^Y;j!;nk\#89l6 ~|.;~AU+]p[_{%I=/xh>h(UCdu9T~j҂5- fw^ßm'劂-JD[;Xj8 z$􆼺5 2f4$H-R#c^*w$=ڥlfƜ8ei~tm6#ٷGaSfHv<uJ?miݾڐ^WV샿Oʪi#H&.? e/HĿCl׮ɢH- :?HMY*w$?sVl@&0'fHv,U%`?DU7ra?(s6l T*w$ăxRV<8 &@_s5MvMi2S7.umº7! L8 pWVM7%`gۓ7}K@@l) W*w$ăxRV<8 &@#P EbCFވ˅)H$sd~oCܑJ X4'5Kӝ7V/tړb_H.sao;/RQhԆ\O pWVM(k|ggϞdR?g+L5UǭR#'[iN5jMbxUCT" !qZa?&s6*N*w$ăxRV<8 &@z/h3'I-B'g?e9*7!QCTX aPΛl3<)fQ!?gaܑJ X4'5KD1"UTHj]Hst(+ܑJ X4'5g{D+m3$oj>ɞ77%eIޜ Iᮬ?y4~lgM|@>BV'n+THVxpwMóMGcx!*6pF .$0/48+ M<'ǑR#'[iN5j 4ԲO:O\'pqC-i{jO7}uꤹT^Evb9k&?k=ijD音1nuR#'[iN5jMcxEUJ8D H/ug音QR#'[iN5jNm$Z~1Ux(p2޲G _rߏGwe5ȿ~t=5}}HXj\F[x񛥓F5]ri"q枇>NjR#'[iN5jMbxUUFcTH1 qR#'[iN5j鴢'kT+\ߔ=joi7rgϙjIWO]7o>Y#[cOxE@lIqTHVxpwMåG"@ӡ@b)A=12#`w2i+qTHVxpwM#g{ɦd ڐk# Kn lLQg04})#y㦿;K6bI#x D*w$ăxRV<8 &@yDf lx rꊆ#Pve^>gAxR~KDxoUj9Ap9(m۶:J dw,eD; 2~V @3ݳB312޽߶]v dt܍wIFB@]Q[ -gŗ^$3:bO,C:>v9VuY)PӞdeܣY_30v8j3GdY7Jl+JkZ6[wκ? u7DZיu]vGQYs֏,BgW3Wkfn ;WBG>QwmDyِ!2)&Y+^+zEOKzmq@^u'v$;Vw`iMجoew8/(晶٣7DCԶLw_?˖ZQpKkf$y?oS p6+|5@ͤ:ғofN9ވ ]fxI4nZD7|H~nŦfP 6?M I)i dٴ\ WB@}Mk g:;ސ qG! WWfYWNid+۫Z6wΪ3;ސqG! WWfYW>iKZ6wN>b!J׷&uh,z:xRf>]ryv]]BLQΦch@JVwR, 5a`[^I'k7(kfR݀{(k;:sn;Z6paϙ;N: m.o\Iڍ;7n/s_o6x5;wIO}hRъ%?gY|gZ ~' uy0 Q(ސ,9,s6T:kQPmrz]kf};./taSl[˵\ocP}ە,UI"S'.9K8? ]@c_{E|b^})gLxHWΦ[mtWJVwG՚VWJ2KP%+gTf'?qgqit\j;Z6we> ?t/ƚzwiU۹wش}Ԥd,NuDp/p~P?}9|ɵ?`AqZzއ 9kQWm;(̌5a3\F^iWF!OWTf; ڎ?gmǤhuG! N椽ք:?ew/_N w=x gN)VNH= Q9?ǹx9^]o=i&\rooyv8V r4 V6h井Vw\tFӚ-{p/K1)VxC*SpOt\]?g]Iڍ;7n/(pw6&lFR<2mM.7Ͻut椁; 'Bސl qo|wɽWG |:isou s7D`<[ͷ7| |'TyRhc XLiuG!Vgc@ w6>?2 Bi72;w(Lk49lqu+(錌5a3[6^Wfڨw^رuf-fgW:e>_ *}oHev<'ڻ~oX>FpwNh@'rX; ζ8[iMl)7p_WF!OWTfsG!'Jɛy/|P?քU\ ܙ GQ3fPlݺ5ź:QѣsQ7cW< :O/)^}EԠy2s(~6omͯtmc⃓_mjx&uwTǕrN; n93iMtQU!9WvG!OW__; 2~V @3,}w+Rl[ j7DL?e*Ϻ`#&1z5ﰑ׵'=_S;vPUR `c PiuG!Fge8 rr2~?gqu5* $^(T *9kuG!@&Ϫ5a1w& ;U?d/j@o[w3Ph2{+_c*#2G؟3{<WP@̯:y\zrٕWUz_(pO 6 帙VwotVӚY-wF@IDATp/'Ukªc.L@fw3cv}>ogR2~ WO7~;tAސZmTW*k7Oo~=`K>ߏ_~c#ޗI4S2<tY; )mxjM]Մҽt}Q$tD!'sG! sVvg/|P?քU\ ܙg&~PN}owG:I4pOfpՀy:ryO)ԍ;3fSjB\{:qG\)v33ք p{O ez oHɤ{|QH極5kuG!@&Ϫ5a1w& ;ZQ̷8 tkrdt#7qܷ{<ӦIS~Eq{t|?.[zޗI<6: s]; wjMaܫ|)20N 'i^R20Ɵ(Y&z8dv?0a {t[u[F0W'6>`Dyt\ Bo6R#MrWzQs;pnZQ`nlVkMجzGHB&߯; ~͆7d=ʾ(T *9ku9rG2.eg2e ]q˶m^I'kO3<=y2MifjԬ0X|w@ͯPc<8XJ@ `p/R:@M>~y/n<{&ew[9G B@Ks u09,xCWm;D!KVYٝVw|P?֏c.L@fw3t_x1"o}>b]]k-l5b<>2@p/x9ĝC{.ݐyL@ps Z6paB@ KP%+gN__; 2~V @3^O# J i)}uP­kbcK\[zPu's<8e72ZQjMXp &ej(X얷71YRo7wRO &zMHlVwdAZV=spg2ß h=sC3m6', i }5ﬠ8pk [^C_i> p;m7wM{SL!NZu\Ukªc.L@fw3-'u?}i4mfH'{qky\p 1(vjƑV /oHް'yu_4qK)>6|B_\gNhrr3,e2ZQjMXp &ew[w, iü=TMhz&^ e6O; 2~V @3u?ܓi{os̷eG]dlwR]ͫ'2o(><⃓u'ue}\0e g՚ ;L@\w,[ )O;(s'!߻_f g՚ ;L@ͣwj3^Y]h>a- (j9R>} k{-ۗofK,8|(7 xtփNZ˸pikuG!@&Ϫ5a1w& ;mQ/ꖷ7E/v7wRMĶn"_f󴺣 g՚ ;L@Ig2:ӌ3l%y]_vD@m:[0Tn}M"oh>3oT܂t}v؎{1w>7pkuG!@&Ϫ5a1w& ;ݓ` ][^ސ}yqG!@,TN^M(lVwdAZV=spg2ß h={SSpc)>$uGuk7-f~]]=PC@^ސ޵Q^ܛ%;.mF+bZe72ZQjMXp &e(XW閗7U_΋; SKq_UDiuG!@&Ϫ5a1w& ;vW;u;Z.4Teݽe 3}O{-HuG*Z e72ZQjMXp &eQ1YCZf^w1x_ܽLoZQaM5aRLZ'7s.5%B٣ސ,-CsG!@w>:P|^'.=VwJWjMجo ew2/#>0tf:> <8ϧm_YD_ǘN:ԜhG:?l/(|=Hh.=K}V; i|kM@9c/RiTX*BGCWhNw6#Iج9 }.BL>Ukªc.L@fw3-K??hm 3 }v߅C̉=4mW̺q?VKE3N\g~=H({^o2GVwlxhM$ɼ\kxI!m>/_]B!8ү;ve2ZQjMXp &ew)g&?N+iOe*'Z>c|", Wk<{sp_4e7ſ?Ukªc.L@fw3-KO:Vf@]]ǷM/BXM1+! ?4'dz8_W܂%K̯ tڵb;R;Re^e g՚ ;L@R( e; R(I¢j!cDkf}/.g? Td@hoHe7-iF'{ CyIuJ[upW%JVwR՚VWI*.=~((?@!UP^w\ݯq.BL>Ukªc.L@fw3-܋wtǭR+XJj{ء_;Kt45ƻ÷>JO+p^w޺Pp寁VwdAZV=spg2ß h=4w,7 P(~w&{J1L(ʹx2ք͊o{9?ǹxP_~%S.GO,E ǙOlٻgCqĈ ?NhwVwdAZV=spg2ß h=+(X&@N YyݑUL>;ܓB@=UK M_?t~yyeHs߻'fe:m.%Q \{Fٗ͠x'X|Bm駟N1+p9kuG!@&Ϫ5a1w& ;CwG!rs uGV~v۹ B@E u|0eCu;~EA[؛״5[֦cnB@Ŀg~.·F(^.wVwdAZV=spg2ß h=Tw,7HDuGV?;ѳ=tO iew2Y^y%w̿Q6cL:P  Kv_qw8W̯9(:u'*Ŭ<^_; 2~V @3; n(px?u;PJ=%ؘ?&fZQpYNkfŷ}\ߧsiuG!=³u&:KSn+epB}(;~MRWʽ!s緝? q4y{A.1)Kx<Lu6 OК_0.ϛ; 2ymVdw$o;_/,$_B] g ]=ߘWKF݉Jr :|7݃~6d;ۓRoHmכ~^5O!³O^V(#;VX;!P@sE; 67ChM,Z{Op_Ws7ߌyuG!@&ߴ̊BNuGV=HoB<f6  Bn A&&+W$F::^|G谜mOPt u>/yjPw}9Y_w Z w 4]3PZQHyj5apa뎔>ܥͼBL>Ukªc.L@fw3-566ғzQh#%[oH-#wpݧMx6oLqMPܦCKj<؁? @1xݑ˸G?7MkhP[9G C! a 뎌?eܣYIZQ̦5a\(-u#Ȅ#^sxWԼȀރ2p7Gr^>Mwbitgo-s"I&@.Ŝ3(̂ҚZ-Vl:eB@tLcd2;Rp7__; 2~V @3WSة'Mqҥ[Qkmbmml>)zGsސ2ˮ=;9O0*pxxgRC^_w;(Y&z8dv?в{^QLtC!@v##^SwVwdAZV=spg2ß h;uXU7u]x p4䷵k;'G< B=웇RәQlݳw̹b] Mܦ3EWoHi0<ຯ|Q95k]dM*E4eܣYIZQ̦5a\(-u#Ȅ=!XQs@9D!@v#whVG~VwA0iM`- wK8G20pOVGDW168G(om~W~h7wJoHi<$uo^Lsuy1耟Kɛy/|P?քU\ ܙgZv%\Q7@ Axݑ񇻌{4+# Q; ~ ٴ&l0 %nwp'|ߌ$jy/z*^-4u8pTי#pqf$7juG!o3ք rps `UPHed7;2pqf$7juG!o3ք rps `Uv'~Pc>cgomߐVY.W澦[<MSWӈ~"G*3r%TIӳ32(o{&l&p++x%{ )xTx)= WpR7=䡵&lȦq8J鵁zF{%k5?M 4 O{D@]F"F}kS\ %ͯź];o'l<]_?wE|"=D}iKc'm{w:|?щQF xل-vG muͨ"ev0P3sNB;h"c3],w՚Y6߽]v/wwdܣYQ$d"^w.# ;+^_; 2~V @3wK8GO#<|+}!;o^LOZH6,nŅ {wqQ|{)voe"E< u΍ \A^l{kuG!vG3Okf`]=HoD!wlx)s w?f9tkuG! }vt ,h̅=&TJla^(ˑmK3Mnzb-Gӿvi>m(%D Bʛ'.^ M =[iOKQO4ɢ7 VM?qL:VwdNkMج]v/whHOD!sYG!P:ណkQWm;#G\V݌6ehͿs3*F+RnݭgTM{qP ͷ֯wڧNV|h~C[Q֭[֬1V[kbc\,+PeS;L<9'ۛA&l [whb"t# V8﫵u7pwh; mx5~%-7w?eܣYwԖ|pf#OW{5=x޽s~HV㠓@ =T:kQPmrz]kf};.񇻌{4k9"t" \vvpss $dZ6w?eܣYmН.|v1-/2q׻']Od VQyWs$罓A1HUW%JS-;VwnY/hMج ew2pqf-G! I'kQ>i^w%l($ܴ֚Y0e.O;5J߼E?2zhu7 mkf &OTLUkªc.L@fw3-Q7[ 7BGy;(Y&z8dv?в;-uh%7ku;1^=!uu(kW~W.BL>Ukªc.L@fw3-Q7? 7.Zr^}e g՚ ;L@ps͕mwF+_P۳pĴ)MN] f.s#IOvYAOR6mE<p~WlpCkuG!@&Ϫ5a1w& ;n 稛+ 7BmG^}i_ew2ZQjMXp &ew[9#ǮzU=PI]Ϳ7g\j GaN_pQ^7㙳fSă_ywwٽVwdAZV=spg2ß hpG!G0>fhpn&e g՚ ;L@ps--oVΠ<kEǯ֗bݻS ~þ[忪xKÇ>lv@Zyj3*e2ZQjMXp &ew[9ꖖ? (8JTI+SYlf__; 2~V @3wK8G\/ZV6d&[_7xeg^{ի'Z՞'^-r̓_%pO[U(く5ag|W%JS-;8x$.sy\Nn]v/|P?քU\ ܙgZv%ng͜I+:d3il¼yY/OtBM}=N\ǚ z$^N sI_& ZQHu\k†+opV+%[y\WI+PH;W]*])c]v/|P?քU\ ܙgZv%n\5k__s奴|ź/|Th: Ysw}1'&{x=Gܽ0eiRT=&lVxNiZDž{erQ@! Mc|nާ< w]VwdAZV=spg2ß hpI?StluZL}ԵiV˯1qż: CS͵JjM{4+# Q; ~ ٴ&l0 %nwp's\h''MxG1\,__; 2~V @3wK8Gݤ_|E3>b4;3axP_c5߁E/3ЖX{9iD٤ݓ7k/pqf$7juG!o3ք rps `KO8NXI}Efe72ZQjMXp &ew[9W_;:q[.ɀK?8G)~XxG4U+(S-nB|CA{fev2Ѭ$F(̓`fӚZ.pdaqs-(8X WQ@KW\d/]vS/|P?քU\ ܙgZv%n_:OPkoOt6̓)E>vt8?P=z4Ÿ;+/pqf$7juG!o3ք rps `kG!c"3x (Y&z8dv?в;-uo~kW)vY1o7"Ok񇾽 /{Lq6Ny>hu_f. HoB<f6  Bn A& 7BpZ`<__; 2~V @3wK8Gݴ?Yv83G/VO 6O4ܾ&ESNfÿ>>[W[wV_f'. HoB<f6  Bn A& 7BpZ`<__; 2~V @3wK8Gݴ3tg?P/Y\p7'[\uuA B/Ow}+Y__O۷o_]Em1ev2Ѭ$F(̓`fӚZ.pdaqsm(8` WR@[ޗ\dO]vS/|P?քU\ ܙgZv%nZ?*}v;[|+jo[bSjޫg;]:ĪEtm6{]c(^8s/jqG/spqf$7juG!o3ք rps `kG!bZ"3x (Y&z8dv?в;-u濨Woݺ5i](.Y8m+ 2ЅV]8~8ab󉁍۔?vO˗r2whVG~VwA0iM`- wK8G20pO渹6o0+)-K.2'.BL>Ukªc.L@fw3-Q?3~^ۍ-5|"`Mtz{qE(>)n~=mLJ¥LJ{i_gKp(Z&l7; ˸GB@IB$-ew2ZQjMXp &ew[9˗Ȯv87Уj7hX|W9cVӗJgZmTWJ:-7VwXkMجo ew2pqf͊? ю"JǹWMm.BL>Ukªc.L@fw3-Q744}(nP9WO\Cjj~Jk6݉L>b׮]ͅSj?5ڊý"O:q PrpRkf}g.񇻌{4kQv@ҽjw݀VwdAZV=spg2ß hpe$H?sf̣L|ۘVdyC9cVSr>,Rkh+ۖߋZ6;w?eܣYB@ÈU9e g՚ ;L@ps-/}_h+m u햣('?8l0i==-? ^w%l($ܴ֚Y0e.͚G@^^n]vG/|P?քU\ ܙgZv%nY+IO/ߠ㾝zP)=G?i?d=m? ^w%l($ܴ֚Y0e.͚uF\W yjzw݀VwdAZV=spg2ß hpeSI踋znƇ Mn2&hUٜ g{".4րZQ}k5apa]=5(D;XJ y_^5n__; 2~V @3wK8Gݲ;Pa+(jo~5y@#dd=\k]=ߨylZ6@˅Q7;L8 9nU'JƆjk&e g՚ ;L@ps-%Kwj&_pCM:lݮd)[NS..u_f. HoB<f6  Bn A& 7Bmew2ZQjMXp &ew[9U_Jw3/Pr-)>sTKCM@~^ŝ{Z_fg. HoB<f6  Bn A& 7Bmew2ZQjMXp &ew[9ĉO1 w<5ŸsiǺSqQ>|8St2ev2Ѭ$F(̓`fӚZ.pdaqPQp9.6]vG/|P?քU\ ܙgZv%nkt'}s?SOOI>0zS\3U;m:?oRӳ]ө]_K񮯭.hs8CxP-oO}7leܣYIZQ̦5a\(-u#Ȅ=!xs>\(ym__; 2~V @3wK8GBzMοgO'^g?|bKlSQ[g09pҗML6ܙ;e2Ѭ$F(̓`fӚZ.pdaqQp9.6]v'/|P?քU\ ܙgZv%n8|{ v[uf@7qÇ?x^K OOtЋIwaBswuZƁN]=ߨylZ6@˅Q7;L8 9n?  ÅY&$e g՚ ;L@ps-?+&b=Z_!6] ^qjz=i~}ɿNfW9ug2; whVG~VwA0iM`- wK8G20pOy((8Jgm.BL>Ukªc.L@fw3-Q7sΥ;=)9э %ΟӹǍ=d''_f. HoB<f6  Bn A& 7B p$=ﳺMpYkuG!@&Ϫ5a1w& ;n 稛vә;ϠضWGwl;k|ll݋Cl)eܣYIZQ̦5a\(-u#Ȅ=!Qp}V ; (Y&z8dv?в;-u:u*o_N::ӿCN2z˂=(~UrZl;_fS. HoB<f6  Bn A& 7G!I&9Q G@*-m.BL>Ukªc.L@fw3-Q7>(1Q<餓(^|/)N6E7(yXx3坥.IDAT㯥xH>}']__f. HoB<f6  Bn A& 7B@&/pqf$7juG!o3ք rps `={6xNk(iFo71tJ:nݩ5foh%]K(^kc-*G/pqf$7juG!o3ք rps `G!lowipB˸G?7MkhP[9G {B0ͥ{wLw))[fnl䝵AR>|B\8wh1;lE񝅭)vdkx(]t)hGdeܣYIZQ̦5a\(-u#Ȅ=!R(Wr>"3 (Y&z8dv?в;-u0< oߴ7 ]xiк_ xeܣYIZQ̦5a\(-u#Ȅ=!R(S9pRyhl wMVwdAZV=spg2ß hpI{tbSxoj:߱9MӶӁ/Y7}d&'0|֌} ?lX?ZR1ev2Ѭ$F(̓`fӚZ.pdaqs)ǩh8O 6__; 2~V @3wK8GݤHo;oO75eɟg4.g g8ot;\Rl˸G?7MkhP[9G {B0ͥQ'r>"3 (Y&z8dv?в;-u?I~w6#t5֏.FMZEqq})v߷'ņߙ_xKI:X:S/pqf$7juG!o3ք rps `G!lowipB˸G?׎9pm;c6ӳ42+ TzvM\]> HӒ&.ukk y[2Z}L_K ܽr7#rB; ^?I&lxV d^[ߵh)VQ(A_($7ݯwl/s47֏ppO91Rjijovգ7y3-h=,X/IqVk)>W&z$^NhuG!7ք O2يuk79ʗ? (6{eew2ZQjMXp &ew[9f___O+uR9ԐenA+__; 2~V @3wK8Gݸ(9j{_q ɀwͥo?3?5*6a#)Fҽ{w:ƃ[wҤInh^'OZQ(]85a3pa]=B@$Y9PUn^oV惻N__; 2~V @3wK8GݸW^yd?x+{x9s71WςvY?_n>PsSӓgm\Sn;]Lf__; 2~V @3wK8Gݸ(9j{_(e g՚ ;L@ps; bv.r۱6Qn;_MxxiR+ 9:Cy?:e2ZQjMXp &ew[9fB@ @! Vy{Y; (Y&z8dv?в;-uKΧXn~VW-'7D2~ ʗWQoq3VwdAZV=spg2ß hpEC>J >g~ߜJ(JxP\7e=rcd7.!:~ҤIGP^M(O׷ZQ(c9?5a-pa]=5G! Q\n(Q('ywTǕrN; n93iMt{4R>|2oVt㩭=0ӕ+UO7wxMwf@G!Fݯwl/s?,Z68Ȅ {B04pp R B@OG!x4p]<EkuG!7ք 2qs;9cB,a[7u=Ԧ5>c&vF]SZI-W&7ӳCqIKG!f!+O5ƐZQyyl5apa]=5G! Q\n(Q('ywTǕrN; n93iMt{㎣']w:uŏ>7 yw=Ysݿ\@B@]˯Ɵx5Žvގ`ٟnFgϞPY {*; wעƃ2/Wj36քzHW}-ȓ(.-{e={F5tkuG! ~T ?=M#9oeiNJQޣPE;'M4 q9kg[&l׿o }j1ܿxWųEyB@LpO׷&u(5arpe \fxtؿ_N4+)jk&Wjtu}nLZ!`53hYAf7ć>zt?\c,⼏ pg?ϺVw4&lkj.񇻌{4k? Lt}^qr~ZQkf{kM̂ew2pqf=X:.~ ֝ͯDT+u[4_ co.L\ׯl(Onau}Gt򟟦'={\{ O uQ-j1eK( 5a3D\V^I{.PNN ͊םHo__,X; ~?Y&lp  `1h*fe˖QΝͷ˗vMҥ .?۬\SOA\! T+?-2p뵷vL]=ߨ ф@IDATwTeYФHQƂ"ĮcDM4YcbFQ4vш  R9wߺ3;{cμr7qW!GYM4IŸyr whTGn#zaΝKv|5]<:\AZ/mvz~;<;{4*# Q{;._F`}ӝ'u촃Ǥ=X/{CigMq)!xH,`Z\:^z(Î!x)!k@J(`C-u p/1o\hR߇B˖(n;^wښwǀq4HuG! ͬ{ܷ1i=Sj'?5ڒý$OD7Om3=z9T)@n h+?O[*kOMuv >=(Ѥ.Xo5' wM8Ko 2a7pOVt 9Bo"whTGnTwܮoF`Ԝ(5,5%Ȅ=!X{1:㯧K~Mq;j)^bS:Cl o/ܫ4_sx:yܣQIRQpMPspׄ v`eNG! (&;s7gP1U}`bҧ[7~Ø=4 ?SpG4W~Dq 5ggO~<򑊋Ӂ?n>AG<Ѩ$F(]ތ&uz9QkYjK {B4?yܣQIRQpMPspׄ v`1Oaۡt I k;vUsɯh{=|G9z]*;>q.ۍkЮDqWNTO HmBuhR7&f'y: 1NC! yXÝ=(Ѥ.Xo5' wM8Ko 2a7pOV8)~X3oH^[[H56_ 8W\z ?愓r'-$<Ѩ$F(]ތ&uz9QkYjK {B2PHaxG?pvx3 D g-A& HG6=bW޲Җ񤤀mO~E mNn^GK?MG,G?pvx3 D g-A& B@QM ^wx HmBuhR7&f'+r!y9e7-II[Mf5G@S&Q]Wx-P&#^wxwhTGnTwܮoF`Ԝ(5,5%Ȅ=!XQ(#t7 ?yܣQIRQpMPspׄ v`yy7\@54_lpb@ͽibO|?Ý=(Ѥ.Xo5' wM8Ko 2a7pOw: y l(Ý=(Ѥ.Xo5' wM8Ko 2a7p/ 裏ҁޓwIqܮQ/)tǫߍߐ&ZЧr7~]u&S=5hbםb2{z9tKuG! |{߫=l {?e"ý0 ]BًBo&whTGnTwܮoF`Ԝ(5,5%Ȅ0믿Nc{=9M ޡX'ޯɭސB&r/S0x#d*xI7yܣQIRQpMPspׄ v`(v e/ ?yܣQIRQpMPspׄ v`کO:WəVAqꄄoHN7ӹݗ|+`w7&ӻo'8ם8Jρ}$=?s`/A$u\b^.;n5[axB@vDÝ=(Ѥ.Xo5' wM8Ko 2a7p/ zk 78&}s8dPj۵+oHUdQ!/T ji~q*AqСCyO& HmBuhR7&fC!GQ^wx HmBuhR7&f;䀽KqVғ{ ^A;|"m <"կNM1d<Ѩ$F(]ތ&uz9QkYjK {a0 Lu<Ѩ$F(]ތ&uz9QkYjK {.Yoe@5'X~Cjr>SV+Uu9v_ZJ'lv2ţ?&x yܣQIRQpMPspׄ v\0r=BB!7x;{4*# Q; nׁ7I]jNpdnϒ̶n[Pg}c]d;ߐZvoZ~%wO,Xw^9lV^w]?~sɫq/ ~T V<nhͳB  r~,(|#BoB}U-?:y-޷Tw,G.Гw ß7/~v̱wޡ/Ū5:m{ >⭷Bq8|6ӶސJuL:PݛOH^Iir!_'CRQH1>w-ulgp9O϶TϾP*8WRN: {{#¿RQpI Qcp@1tOߓnd}Ozk7VO?T8{E_8xaN>[TJ|{g0Yh  ]HuG!@#Yh"unw ß7wxIhQ^wxw{:=_GͼTwsdRlح. 0Ÿ!}s袿{ϡQ(6͊A' '7v} P?/[=CѷV,"FW@D; bRl֭ 5?{; <$QRA! kv=ޛ]ɉTwxփQ.Xp!asj6+{Su?rW%=i U"*f (&9K~Puԗi={Rt+ǁ{¥iJuG! {ޯ9ký,Q'?UޢB@T@PH\9շ`/<Bz?+pp74lC@澹t Z}樂=W4Tt]Cz!;zGS(fYyCb޳[N 3(`%)su^={^O7TA)<Kpgao-NHuG!2g0 A]V+ےL֏o($/.,B WF{}{-ɨTwxփQ.Xp!asj6+=GSX g]յJSYVސ5+sW9dnkrӎCSi4D[X|FOß_; 73Bz?+pp74lC@\(TsY,twR_B^z%V  1B@e 6\7ro/Z@/h{(V9FoH&*+6kO4>9pQN<$')zqrq;or/ ~T V<nh͹QP RZc{/R4S@ѥTwb$/H]??L=P8^IáPƯŴzV,)sӰvد_,oqX ﺃ;o/ ~T V<nh͹QP Rͅk (zzgR1;KuG!N~E :xr0a.:|KGw ӗؿ#ސ$g :}Zkgzݱw~w޼_; TϤbvΗB׋t=&7>m4~S|9cnOuHqգeoHy2 O hup7;o / ~T V<nh͹QP Rͅk Q(^PLz[I]Ý7vPϛ*^6>RqmҨxCln^hGCbUUEy?TwxփQ.Xp!asj6vG!`:en3iftP(B@i_rfk%($cfΖ`COy3 n7Y.?ظR5icn.Xx FRxvo,Z}B#/ZۚQ|𱧣SbEםX $&?뀿^{(3VRlp0y]}Yt5Gn 2*ސ$^ Yo(^$~]hLCuGw;oF/ ~T V<nh](!p(v ePl]mI&G; 򘙳.w ßߕ'xq;QOsrFR\gKjj?{=kWWY OUW]pGez)3;o/ ~T V<nh]P8AxCZ%p/,B@aPzu߶h/;K =p0yM'OV G<(v֍bS7?%7<+>QquHb8/G)M_wb  w2TwZR'.s 9y Sw7f~^X.5}Ł:#/ ~T V<nhMgŏ-xɥS|͗(N⎃ K#!I:ܗ>>sO/FCםDP 5o3nop+yOǵ\(.}oHKuf(q6}w(Ÿ'R ~T V<nhmѻ;,د}#v/8tUG!Ypс}xNpR[(XHʎT7ǕJ@?Obď*u3  P-w7znn*\[^^y'CRQYG`No}̙4#RVil oHyrw6u+(!j'hU/_mD v@?Shދ )۳7zQ\:Q}gU_KߖbCpO/ɓA(r;`V-?{7J [|w[qoHP-'98Jr3lS6 UU_ޙim?3Cnx0R3?pוk0U}rGQHoHt-WPwhRj5C!@͸܍ :Xڽq[;Hus{) Uĝ?1RQ:`Óν"zނkq5 <ѨxCIpwB@$w=(Ѥ.Xo5' wM8Ko { >`sΥCBq݋<;t}b2oh͠kZ=@q;z"mwq`Gg8]9vRQ0k.`/ ]zސ%[_I( ig9uӒ-ݯTwJ-G.w ߮{Q=o~)ݯ_?;'_|⺿Lnԭw4# qE.w o.8e~o߾d;-?(_Β`COy3 (L7!MKtp/QҒ/sJ,%[_([fJ]']َ:Y ſԑcQ|)~:yŵDnԭw4# XgKhb[3FYބŸ_; _}UsذaF_(3j>*Ι t+/=Oo&_/mGqR W?C]wSDab ;o.ێŷٚwԴ{ lQ' RQh*zvΛaG!gZ%}C P7Ps]Z?(} xp+#nghpmR.M}e:uX__Nc)4r$!CPăYޕ(J] 'wC@7kB@ͤo^wӂ;oʒ`'_;kQʙBY^m-u |ap0o4RǕՕHzC^6QC7NҘկ|4_Me >Tg}1W<.KuG!g=Ug8A6!`^s@n&}C$2yS;Y;_WάTwlk 6X ;oo_I~uV7vf^"Gn#z珖=#ͣxk'/T=hWz^)+<l ;'ď*u3  9 v] y>y3;Y;_WάTwlk 6X ;oo? KzL% y~{l O5uK>of Ro?yV[mEww%;Twrf 677G!̏u7D\ m\f4? v򅿳vu{Y;(5Rlw ÿGHqako\!x\8Lɼl w[z$7ivؗbN=ſ_wx(,]\텿+q'l5 H]'a 7P:Λ(qrf``[K]7_y3 ߗL|!'|:s5ţN=┮ORf-ŸIo ?n[4YqW,Qx94@'UOD.^zwab+m ) ~T V<nhQ(޸oB^)wLG!nw֮gTRQz`λ 8ބa/Ǚ;6KnݴyGPL Eg4מlB9gy (J] 'wC@/ B@aP~CKKoQ$x"Zx" O`oCB;~Z>yGNۓ 3cPk>=ՁocӚϳ7i89 .KuG!g=Ug8A6a@ 77DupOK6^iPgK}i _; Rl艁;o_t`۲矦+:{aOLk+`Diݐ[B ܝr,m_kV9dqfM .ii ?m laW{J:w( [Rl ;o_>Dqf_:P_ݳC1}Cs= <;{4+NC~^ D~{zZha(ݛb;˛YKuG!g=Ug8A6a@ Pl]mIl-WIuG!g=Ug8A6aC>>buO<\l+G7iȥ窑ߘrZNYW-F#&P1ғ IuG!g=Ug8A6hf|Yp7&4 m 'Z^B; e0ŪA@y^܋89 ,]| ]Y.r¿P:ǥNU PʇB@ ɳ!xp7&4@? FiDYIȒRQH=~ upoƙYr:wSܨ|c/S/^beJ rC:{9yܣQ7N^NS͞n{DS *fԤ'3RQYG`NͳBB 7Dqr@? NҞAr#-RQRG“4m!}uaJF5Ūu+_豭;i7/͋ϓ&GJ_|dϼISm>h<\Ŭut'sTw7^..y 7B%|8oOƄFHG!(h\F +9eBǀRz/^~0f ~婦$ C>ސ\mϓ5GJ|fS|a4wRbN(VU_ LOS?Oď*u3  B 7Dr\[(XHG!e9dfs&R; RW .Xfԇ{%'D/R_SA7䮮{d<ѨR\6xZRwL݅?"'>Fqv(!.3? ~T V<nh,G ŁR!MgR= ǗIuG!xRc9y΀G4؅u7Bq-ȹCڞ{kzoHݐ f`0pIiR\m)^ڥkIwnȣye%Ÿ'pqF'Ja[N)/36bݚ9}<'spqF7b6"`~;cfvK:?+ѥF-hp$C; ~lԙb&O+wLHuG!g=Ug8A6G!`:e0\}CyΛQ]?u㫗yKuG!VG \?; Ϳ!Gޢ>nDwGwp}!= yܣQ0.AS<"wb HuG!g=Ug8A6G!`:e0\ͳH'fpO6VYG! rI\9.rRQ(`C y3FmgPh~FgLV}U)v誶|Y!d<ѨY_v.} D"YW'}dH; `5g &fpNìhy{2}N1/L; 1Ӥ.w st;l?bSfI:u'{th>!riY?OFU%,'w{8\g\'IRQYG`N͹QN' f! (:ʪ? Z%F\9j^Tw4z3  ^׺n9@afw%;s=\oef&nNq=(֏o E\gm_'sRQYG`N͹QN' f!d3nwcBB1}`&.K  64{m]q] _Cvj,dߐs<;{4jV><~f;h̬]Lqx Κ7ɨTwxփQ.Xp!as(epZkk:{".'gKɫ]xdb}Z4G)s\O{k]āOO3ӻ0=g\JZϓ Gf `Ф(>oSDBQ\9JuG!@j0' VMS{e;7m4^#)N?w=oq#-ʹᇬސKI9y2whԬϼ}"ثR׿DؗOىGӿϓr(J] 'wC@(% 7zZ[߾iG)sB@zkʇMs|FsB"`NRTTyvn/Ρ1g>JqQ|݇ǩoe %ud3WkcB^.2wCǔZ+F[cI`QH}M0}(qRۍ5A۫J6LeM/'pqF5;w.u5(jOQPG;fJEE Gt#7=_vV?bI%.7^c1;ku2 LJk8GO$s4Nշ14{G}3G;'*A;H}[sGt< ;{4*#±tv-|b½ghS; oH]9&/ܓ=|\V+ےLܓy>EQ(Q_{RQ^BǗ`rYܭQjud˿?uk/*kOyߨJ?༵J#ܐfExNix(s,[9\yJuG!@ 6 V:iMh[Mqek -m%rޕG?(~ǝwJ7(slqtX; )%n.X]=nB^~pCfmI&ɼl x(s,_S< m+'i=5Xtxg/QmXg$ܐ&iB@Z_ sֲӇ{YTO0G!@/=!s $d^φ2QyI=>GuITwI]LΆ3vmo']Hv[6!oCtr  'O>WSVo8{igSf~T6pC1)@ޣټn)M8fRQ0Llͥ.P{$uQ0nL[TP=l2IyI=[>G2/(Su|8;'09sGQp`Nw؈)P)`t 2@)2Wyenj{ГP|mAIǶt[KuG!@7`gH'](In8]9vp73m d($zvۼJr4_ w6z8uSxA?)8܍2LGMagiܐI(.? x߾@' ũx(/ 7wzr!E&M*rv)4u-9 67Ñ? nr?_68drVtBLJuG!:f4 @͉]RpީG oRũQAx V%'}6/ϥʗuSxe7R,rR:zB@ez:Λy [/fnb2Q}+A!m&vx3 D gYR<F$j-npCΛÝ=D8J S;tZG[xm_|ȝF0;7%R;hKv,iA 67Ñ? nr?_68drVtBLJuG!:f4 @͉]R{FqY([Wcմ]_mרO  ,Q'?Uޢý(Oy}߻t=Ygur;s ?uHuG!`S =3pp? nr7F1t=]rÿq\ʿq5nǑB@Wf뗺`L7pMe&#҄n0bAukWWm8㆜7;{4*# q<+;_Z: lAl.ԉ  N 67(n8dt}rBzQss*>ոYG; Y_E_-2`vÝ7&ۨO +^ 67Ûo9M`z;Ʒn](m<4u F&g|PJiR\kjGl#C:[oM>3rJuG!\2z\ =p0 Ӗd~,9[ =pinvޥێyfQqCΛsÝ=D:q_>@q8.OܝD;G)n(uS}f*e;B@e 6l'(G<;Xnԕ3k'Z n73f̠>dG|8)wag:!O5n+eӍs(RQI@ ==p?:nˡXH笠Iů??ySk1"܍sQ_L&pO׷\/'w|gWwM}G U? `ڱh"HF|ڍҰJ]aWTnQVo!OK6^d,My=R<>Ph-xhppwⳓ'xhox.OOpc^^N(O׷Xp/&f?q7y>u{:7pu|GQ uX;?|t D7(F\cI S1c1Y?)߽4{i}Lt3pL; .WGcI]jMZlePCdpp$<Ѩ$|w>ӭw4Tw !H]9 p$u^OU_yNʿA)xvZuD?nR#k7*ZIONct>g< :}f (8[~ $u|pOn q)vc7l |Ý=X7y}Q)lzzn7_?bfg?ZmWv=bv·G^+g֮{û;z6) pROGqIRQpMPspׄ+ "0v16`=X7y}Q)lzzn'oN{:;b7(Z,]i;I{{R1߮gފ/H]ۑ5Plõ(8NQu])!39>K; 3HRl E/Ei@! n ' 'pqF$6sG!Mp9(g $ upSSPYYYĭlIǗbGA##t\ Rvσ]Ϥ?|`~Nhԧg'=QGOcWx:Xόd$(XOuJ]a Jz؍ۼϓ1G?p&t?Tw3m`CO gQ#قN8㬟Sg?Qs{.=;(vܰ6:+A'i ]JC[aL Ym;+>oz/x&ˋ_hOO~tq3ŽWDLed(XKqXI]a)5 ؍a(':ϓ!G?p&t?Tw3m`COKgxOt[GqsQ;mW"Gݠ9- f7ׄ3lwC@7l^ߡo)'t{b߾})AOzn`@K]r\[( >A! ;za\ywhTGnc1wnGB@~MRl{ (:a|'(.iRA' e.>AIO&d^φmx=SZg?-/[>R.v(]6L-WWJ >>iB@Z_ sֲӇ{i"Jz4uyܣQI(}[h4(DB`s&K' ~I'x7/^p2n])O}"!k0h Jal$`cv POeЄ?!W._I=/xkݷVp''|Et|옱,Q(q`=g-;}&B!Go }_ =ۘB[gFB@! 6gntR|A:sxSgvQ*^u:P18lI 5&a36!fsmwתOLkid3yR/ ~T V<^>վ1< ;{4*# Q+}ĝHuG! q*@ ]3i…B@ab{qYL&RQH7.uz sYqOHd%ev;pW:ɰ(.yA7(J{f+swٗI} Wq9-:PL J fSO'VRQH/^,uzcYq://p2\sŝ'x[Gu\_A[7(p];{4*# іsޥ7~~&.*;k=5 -+ i+E|.X! uQ}]ެi\=BhJI&>IG! K]Pݗ-Smަ;lB;ȔqߠDq0);@.1Kx)1R8 U-znvUuRőTM7ݔbuu5EBϔ(H_9L`8 ; Ζqzfq𷈙+'JTKnw)RQ0:`-~t1MMMw^?0ªS()xN\j\Ý=/ߣ טvS?e~܁}[7h:G3=nTwZR'.s o( _+5"ywhTGn#; DFzBj`L :)__HW^AÏxσPQNw{A摵wތß<Ѩ$o?~/t)] dI涶:(XOJBnu4j)–O75w3 wa +6Ё, ٮ.L'5j~@4[K:S3elD2'[lOw$}L- g=ߌ wa w$I[gNul[<њm%PL>igΜIGm$g XA8yθ?R/.Yy.v.k]5?7U/x}RͯJYOIK!l֬*F&miDrG"@ӤրMlǒ4Hd"00 ;ϴ3w3܅U$JSܑ`?'m7:Y;|hͶl|R׮:ォʼn#"[rVm61@ X|:iSܗ^DnFoqsPmIKi)$utrG" 鑥hõFmR#`M%r"`j.nզ#NI:3GH%VH$"|OրOBŤry- SH_|7s>gk/vwn ZXAt]; C[ܵjbQ^i;5xⰃSD3u2RaVHv t)@nE>O|3q!. W/l]#/8m ؐIW]|E@yT.o^e3;ܣ3p  -)W=WL=J1q/L[yܑ[6$fXRO88%&q&eff* zeҸ{sz$k/$[K:SaWg+w$5`%oVIDrG" gS:րM)J w$*]7$ma(ᐭRff /HIDH5[H5bnŋͰ"yygeuMWRkK_$YSլZJR9 n"zௗ  [?XߞG}#V7o8O?*?S{jji33;fzbNw$qX ="8I_Ȑj=$0_2ЀELsNF$ivw_އGr6mIg=xKz_`ߦn}o?. W&&@C&5n>SޫMov~ɻB;Xjjiӱ;C!m dЋ>KSܑ`%m= ff* zeҸ#7>jޏ'~րM;Lq uk$qIR)7OU,—-Pp7I7pV_+}Т>Iz{:?w?.;dDk֙arG" 5!&@l XGi6SܑXHž6#pM(E2pKvoM4qG" _+7Sܳ3mvU&xsIn\pk7;u*]JZf= f* zeR\rtx%_䈆T^\]'Kr%*Xbzi1S;P*5`K4MsG"`&9ŭ9 HPZ8pV_+qvk^|"lb{{#K#yLL~p{:~|ni R wJIY$l{n>[_5=;]ோt($*@IDATp__K|d-@6:hP^lBZZ2δiO[644w$fpE@3) Cj7p7]XABL w$Eڭ^{E"Liwi|!~e$p{ TװdUIVѡJ9n"zௗ . |@N u^oj0Tv`+;xk;3ո"@Wij,v[7=ep {Q[6Hh7D<5`O8xBҏI[ތ(,{ꇴ-P'p7)7pV_+}k[_7[m^^gfԅ5`ZLsG"`&IP/?:8#=c?>((ѐ?cLH#E&m شg^|:Fe{uK%ZT ff* zeZclU+5SLܑHwE>:[6%d)Hpub{Yn ^VH荣Y3Dɛf[#7!@m pG޺##i_d=%p7 7pV_+kA T5`> BH6(M;ǗJ΃7iw3܅U$|>^WկӐխ;ɮ ̮]RiӦZ{,6f+w$,S5`M2a[7w$2/ ̣6_w>3 wa 2_#7l{}AIY?[6n}ժU>$o^-Z@IW8@kw3 wa 2WRA@?< m$9W3u.Ԗ/K>6}bOݬg p7]XABL;$wZAV=*K$y5sWYLN9[#RsG,HDi(Ѩkwulh I;w$L4ZfDl)gka~g6ӇU:SoVH$>h*;|J4*Gzƹ,.:] ?%}1Y  hJ)y:l|:lOk;m7ԣz67k(:zw3܅U$ʴr_rutznF.W\{y~u\U:StScVH&+(&;|J4F>,6*]=\?umiDIfY{,M4%l ؔ< YܧLB6۽y8S]N5shg[ޖeN5>O_Ӻ@ݝnCo?. W;5:|׵:@:SS`VH TXm);Ѽ h h8dwH4DSրM ^Ð7J_syސ SwS.f /Hμ+>]Go=G:Z#d3L6EFlD@L2F]#ͻXDw%o<u4G%'gV 31$FNZ˦Dl)gkaGAKt?\ ;K(Yhw sp6BTH {&Ԋ7Rŷ#ƒ,++ w)y:yVHM;P[6Q$w\H9(E Qr8aryR*.H"pl±N?*u^mK6O5@Pg5Y(wsx5{&U?{3;H.E]gJFީ;yր 69>EǰZ=,1_.Ϡ=()5_ W?ILi-E]߫Dj ok&gi/[֪U+S=ڿn0dՖj197bbyn ^ zy2gbBj!!^ڵ+5_ŽΌC";Cl شwsG"@DZ@[XwAŒp7CUĽ;ٹֽ帐ր 鸹ػhٺ n3_uo\Cl/< P(!I@FT11FM;25 龁EOcפWlڤd:3xFVHDgGiD^c nY{3=;w$sIK{}oq!`',k&qs?ƺ韐[ԒϽF9G,[H/`_q/cX-/gPmIjOnJ ۘa,.._pὩ^g~x~Mh[6ssG"@DZ@[XwAŒp7CUĽLi-'x²ya>7nlyCc>=opXCsGX:. W^nk& }*b+{0?$۷oO2{Uƅ#`+w$1oziw;z=^ f$f"LHdHkɽ8ր 鸹[d+|6O|3J_Кda*` $ww@C$uwI #bL *^gor7#{|n$㾸יqa|0rG" 򮗭vG#X- aF?!`* pD@&{[[ =%u}_\=,N^O$kn[#Yց絸E5*>jɭނ$^E/oa  3 Vf{< R$ҶUnyWMIb}ELmܑPj5` ԙ8v{ 3w3܅U$Jpm D=g$35*$LEvm X˱yNo èQ7,۝=?n"zௗ nY{3] ג߶%ֳo^[#DրM{H@1'p7]XABwMDOpF"@Vl=B" 󬟭47̝;Hu"pv[w7e[XwAŒp7C@Պ1ݯ!9?O1bމ5[ϫ:|[k#`g \Tׂj¹n>Z]`z?'ٽ]6GWUH芀ٱ5`)i:%9?9(o刳XU6=kGN 5On"ÛnNrԨQf&Vm=B" Om Xy֎Dm=,PrQ  8x HӃD@8^{z^Uؽ{wN>ƎKtSG!X:ԨQ#kYgQo+XBɪmV R#oo0 4 f /H஗azf@۶\N^Y12[ϫԎ5`mD-;,P ܃Rp ~ԶZ^݋z$nyn \z iw4~ESO%y2e ɣ/9dI/_p\`TJ:JrpD`p-&sm)疑c|1}Hv$UHkjG#`jcvp"pv[w7e[XwABD"@/o5[ϫp{ e"`kvٳi lӞMo@6#(׿F2()5_ W?GHm;ܽ=VMDOyP IN;'k?r;|7LgsAfG" HL [4y$L{@},P^dԃn+& zy k.HHfynOLl X[Ӡ[Og)[ ?[H=Z+?AIjiw?Bj_-_/EFO=n"zsnF i?{›Uͪې|H6i҄;U [ϫ)5`m!D-;,P ܃Rp ~ԶZ^݋z$ny~v[w?UʶciJj9#Pūl|1w(QU "kw3܅U$Jpm D]gNHUmr|kNͨGlѢwTW! QfkB[gXHjr}SSl*dyd(_#쿙0 @$. W^ޫ~g̹g^[ԎlEykHn#?#'^;3 wa v[ 7xe[ϫԎ5`e{>Ry%fU/Quo]KlO5ƃ~Ar \A{PRj~ZݏvWK;{Sw4ޛd֭L8%Vl=B" %&0l XYljD,2]Gmhݬ p7]XABwւG"M.^*$5m XYׯ_OJI6ݼ.o ٢eKݎgiB*~ zxpV_+]/o5wSw=)IՑX-M|@wr}{;Eu-&[?棪U q e O6unN߸*m=B" m HeY J CXoun ^ zyDpUHdWqs 7VBcY\R: )(aƢot/bxڹ#$ ,( $TD@v.Hdbyc.8ljj`e$o[{s&TXQ/&85O7wqhbù{E7=ֲ\I ,b)魻=,kg9x;X6jjqEIH["hϤXEruF$Ydܗ97ەIe~T{|Rulϙ.9ODn܏]W==ޕM&O"QpD~-"⫸C|e@ S8^ʟa7{Wnqg{8v}쇭i?6s>?1|֮[Nz^D@"I|m XG" ?bf,F';}$#ٹZ$L.F"G$!m=B" %(l X}ꩧI>səKxD{w{0*m.EYJC<3d3[U91qGz;rv?"~IUۑY0Wz$t{0 ٧*}Uwt }B UV8NY~7KNuս_|]5w^6^1x ᐷy[.:h|Fy(c${cyv`2dּnN& 9>׺9qvBcһC#_:l0!Ζ4bT_0]{'yDz4/'bֽ3ZYv~~ylW\T[3 ?xZk9"NcgsG{=3?-3=T4<ʷyn?C'Y}QXUB ޜ1'`+VWpdz^D@Eb5`DHdD@v.A"D" ;QD L^%$8E"I @D7]W!`WX3[P]wF8ZP5g [rF)׉xOxGqX!m,^Z0׀3&7c&rz[t/?yʰX/h}VIp:gK8Cr ҳn:g׬e@]bؚmHŊ wGNg4wBUKVgri̧/i;j6 Wfw)J=?pw&f;vJWxGqߩi ۆwjpv<>z^[&,cT{ k$='.e>`nuN죽kr78a1{s/-MُXIVkeU Nvĉiի.g5?K}6\卫M&aQvv\ׯιw&_~ +յ~VT!T\*cYEΕ03yvZ;I+vzUsOsIW ڂfpV} );xBF>>!-wһażc}[d՜-_ًYލx@!ej9;E;|E,1կ;5ʗWxAG9w;@j;b׭>im&yҶ4˫iLO=d,vKbs\Ǖ0ׇݴ= N}CKM}O>nٿ14Ճ8^ޜ[ f.pUzDO~=;]:w$/H*4oذ!Ik⊄9KJ׆9n{gH 2w.[!so`G:/I1ߠ{zr}H~wԿ{ߨ^} ^ /]t,[ZNZ~ A_Ӌ=NOֻ"#FXuHX{&ckB"#ٹZ$\D- sDù4$]Dc" ~9!m=B" %(l Xnz$୚Z=^zfu2wB&svs&5w?3W093oLuAc,wkhSrȅ?iȓJo}VaUe?i_0ιWwyGY_s%/TKnd >Z8w2\kR}IGy~UIUN᝹v6#"K̢!g4>WWm͟z>;OdgڈuW|hLq|Eϵ{ Oyݲ>Ul܊!vNP^agsOؿTxN;+vd ‡w yq׹v މZلwҊ{B 7}1_Wx&髾^;؍Y&gw .8g}#{*/fç\9ьwH|+w{~+wxpc_7L /< ?ԞQ9zN'[q`T*(8M<-ot܉׷r'׹gW;H8bRϗjp|{CTUyʶiehk>2f6;R{({h'$szT7eq3_}1?R{C2X#P} 7 j|'Kzw[Ӱ?:wGs䘠S]~@C\7q>Vnq ^3Hy?/);mMPUOp;M6/_y Jy"ޞ:VlF巳^SNZ3ڟRX)i~jq%e+~5w朇 4wN7ʲbGa׊_WS Wgz3o1wŽf'8/yg Na+v'qU\Z[w4t8oԚ*,gYVJ1ܙ}z"+)(IE SUy !|Ϊ۟8ݜwUDžΏ|˷LUjNʲ:gwdM!k^aZ~8j(*d^bGWvvEߡq;~d.3ο]Vj|7Ҹs[#yTG]y-hsU= b'L촺h?sUH޳4]9 Z=\n{1i]Hzs׋NnE=|*S{7dݺ^_ RۉY)}m4Z \Z;TR+Wl؀R+$z_x/ylA-~gŎwϽ]?Ÿu 2f})-{!Tft}WMѹci|=g[).F~=㔾4Oϑ_;#nj?ȊC;:nM{|n=7C*oR.vF:++֐%VԘ{L{M_~{T[,YB=7ڜwFԐ̙3@ߤi}Z믿z^v'_|3q"94Sրu#F"M$X>u#ďs/"1D@1''nD@E" DHfLD\Tw$X'[Rs0gxtВZPk|Ԓ?:OK^> Xs)N*wQѾt4g/;+.l|![Z-o]WV)GQl_۹"mx'o =\=翦UC_|(t+,-/j:{r?μ .3:[rw8SIx3' Bo[{V:}Qݏigo 9o ;wMiN;bW*v슧3қ4nY$7]KP-S]ggT^4ж'VIDATbyOw^ʋh7UoxxP} HON~ƌΫ8??ϧX_dž{3zhH~p 5?7t;Q^qGzo2Zt%}_+x'3r?O3N&U/1UuƸNm9g3j}vӸ*iI+{A*{j,?ۚ}$7o(~1xܸq !H9\?~h.J4I~-)uey[\U.DFi ~;2~N@pljhv>k_5mD@v}+D$'%<%$D,\D-d Lh;A}l ؠ_ҐᝧΥAZefUI> v\VM6#},ԥr^{^1ֿx;)íN_Yq;nt 駟H{5jף *xOhʋÎ8w C_O>dsH>1ǁ/?A$Wx%C#HG_s0V8NU.~ێIձ}ʌw5j/x!dըQC5k֐YBr6|IJw)@% +B%%ܑ45`2F" 7)$qD@v>Hd"j$2%UMCǶqv^?T`3~vB\#ӧ H^N\)1[~hZyRQTȚZm&J A@ ih1T x1{ xṪ!lPH&D@v>"\^+wMDOp^d~Ov[6(^x?$K$)rTW;~ї^]C xo^?"OaޞLRS%_{ף'IYoڴ_dI)?U ^IB++@% +C5瀣 5@bVHdqzxWROHdcu٦w3܅U$Jpm DmD'Ί8!' !ITA)w@_1`FS5k2c+w$\B i0f /H஗)^Vߋz[#nk&h{@P">jfW C={T @̀ "k;fzbNc9c8#4 % C% R Qk+w$|Ͷlf= f* z%n"zஇ"VH{bpnZpP`4U&.3;lDxުk=c#$ I C$w@uE`}kvYn ^ zy2eȨ;jXlb8+})nŀ=ԃM h.f9[#`&jkZ.=&?&="8I_Ȑj=$0_2Ѐ= (E_XrG"qlkno?. W^nk& z8{Y/2jmDZ'VX'A)w@_1`FS5k2. cVHփ9Ap 0p 0ppN02p Lrw 4:pJQ7WGܑq\6i0f /H஗)^Vߋz[#nk&h{@P">jfW C={T @̀ "k;fzbNc9c8#4 % C% R Qk+w$|Ͷlf= f* z%n"zஇ"VH{bpnZpP`4U&.3;lDxުk=c#$ I C$w@uE`}kvYn ^ zy2eȨ;jXlb8+})nŀ=ԃM h.f9[#`&jkZ.=&?&="8I_Ȑj=$0_2Ѐ= (E_XrG"qlkno?. W^nk& z8{Y/2jmDZ'VX'A)w@_1`FS5k2. cVHփ9Ap 0p 0ppN02p Lrw 4:pJQ7WGܑq\6i0f /H஗)^Vߋz[#nk&h{@P">jfW C={T @̀ "k;fzbNc9c8#4 % C% R Qk+w$|Ͷlf= f* z%n"zஇ"VH{bpnZpP`4U&.3;l^ؽ{Mf:vXХKѪ+})Jz*wO4Z_  ZZqWJFހrG"Ljm[|Ͷ^’vYn ^ zy2eȨ;jXlb8+})nŀ=ԃM h.f9[#`&jkZ.=&?&="8I_Ȑj=$0_2Ѐ= (E_XrG"qlkno?. W^nk& z8{Y/2jmDZ'VX'A)w@_1`FS5k2. cVHփ9Ap 0p 0ppN02p Lrw 4:pJQ7WGܑq\6i0f /H஗)^Vߋz[#nk&h{@P">jfW C={T @̀ "k;fzbNc9c8#4 % C% R Qk+w$|Ͷlf= f* z%n"zஇ"VH{bpnZpP`4U&.3;lDxުk=c#$ I C$w@uE`}kvYn ^ zy2eȨ;jXlb8+})nŀ=ԃM h.f9[#`&jkZ.=&?&="8I_Ȑj=$0_2Ѐ= (E_XrG"qlkno?. W^nk& z8{Y/2jmDZ'VX'A)w@_1`FS5k2. cVHփ9Ap 0p 0ppN02p Lrw 4:pJQ7WGܑq\6i0f /H஗)^Vߋz[#nk&h{@P">jfW C={T @̀ "k;fzbNc9c8#4 % C% R Qk+w$|Ͷlf= f* z%n"zஇ"VH{bpnZpP`4U&.3;lDxުk=c#$ I C$w@uE`}kvYn ^ zy2eȨ;jXlb8+})nŀ=ԃM h.f9[#`&jkZ.=&?&="8I_Ȑj=$0_2Ѐ= (E_XrG"qlkno?. W^nk& z8{Y/2jmDZ'VX'A)w@_1`FS5k2. cVHփ9Ap 0p 0ppN02p Lrw 4:pJQ7WGfxIENDB`wagyu-0.4.3/docs/split_self_intersection.png000066400000000000000000003004501314062220700212410ustar00rootroot00000000000000PNG  IHDR"WvS iCCPICC ProfileHWXS[R -)7AzޥJ! J AŎ,*v誈m-Q,,ETu7I]|3ϙsܙrPr˜`?fRr $)0bEpewm\UW8#bB| \-@hz)~H 9֑49Cb 3Pg3`%Ķ|vؙ,΀X ywq23m4&1Ȅ rXroa5S#mo0)ܑ( n8$~ؾ-5 PaA k2؞%B{47ӄ3b8K3#+ IB Wz03.Qm*%DBq(;6laa䈍P#l taPٰY4!ό bI\QR7 Pp0b}K9X%7'8F^g차 vķ#.0yYr؀ ?:N A8 İ Z{z?H`!\`=Ha qhO6PeT+Al@ Bk^{qWmď<2+1@ !-Fy!؄otaɅIGrNxLIDej:Hs&-h84g7p?q qG/ ssG}IYϰ^RiE1w5g؏R(֌.c':`X vJGWJ-F-~aBY ?C0[g/2gیc9 ?o6¸MwRcp)o:7p{T[,,piG wFd`"q LUL0,% f {pԁ6p܃k}` "BBhB G\/$ Gbd$@,Fʐ5fdR@!vA P .jG]Q_4 ChZ+ЍhEϡWћ}cSfbXcBl>VcUA>D3qk>Cxf|/^7Gx@#PB!0PB('&'\{0@$D3 ܛ,rV!Yb;O"HV$ORE'6ΐ:HݤdE>ٞDN!Er>iryPAED]!J0[a.k UœGɢ,l\ܧUTT4TtSS\Q%GjTK?u UL]AC=KC}KLi>Z>mvAdQZTTԡJYADWyrrQkʽ* ***,**'T:UUvQU^V}FR3U TT;EFt:~ޭNT7SUR/S?ުާᨑ1KB㔆1LJ-Ƨ1c|p,spLǘc5}44oj~bjjek֪zk[jOҞ]}AwX챥cX٩Ӣӯ+ݤ{^W磗N^>]KN ӗlb v 2|`D1r5J7Zghgoa<׸IffKLLi՘7{W߰ ZZd[lhD-,3-+,YVVe:gf7ѮȮ=۾!aCkG+Gcm'SF/.B=...[\:]]]^r#-p;=_B Y2qĦ0jXl4bbڈ&Ⱥ(6AYt^o'ULzc379;=v_@_ʸ{) $J'KKO!$N8y)NSJܚj6uӴL;5]y:kTBjbϬ(V?-4mKZ۟Yzrp{Iᙱ6';<{-}vT졜ĜC|5~6iތY3V$=o}^0L[QEl.ĪGgjm9{gA45hyvGo\`xA{Qe/ȶhMѻʼnuwSMRsǒmK񥼥mZSz̶r+?+ZW:\E\_ukkTZvs]w맯\XmexdcMƛVm9s C[t,~+gkGOmʶ}~{G*Ӫĝ;J/ջwG7foSKu>}+kqM)?h}p!ơ_Su$HQףr~]WY'Oo?1DcGl~sd)S+OSN:Sxl﹌s]O:iRS녰 .]<|祓/z;~չ˵6 ;;]~F荫7#oߊusJ6;9w^-;xo}*zDswSgϪ??b%cҗZzZos|p w`}{?~lϤX|iPА%dɎlhz:o@Kgx(_2AwF h2'=>/ Q*a3 w@F۰屨C04VR_CC[dp6O~ kJQK QlC pHYs%%IR$iTXtXML:com.adobe.xmp 1314 1050 B4iDOT (  ht@IDATxw`TͦлT)DT Mة."Q(E,D {s6OCHr?{wg$Ld?yMYsK ,̱37*uD"v&]Rr*Cc %n*طEZb\ +ِ H9Éј)ir>$#H;%VXQ@HS/#y(Dw!@!()@!ҜF pPѧͥ$FJ{{Ĩ#g K6 `uE:)Oj!)[_Prɓ$8>a^XEE"Ay@%@!G"}M7 `: 2FPD3LS P4崑4WB?no)=;0HO "M(Dt(DjH+@!&=֏/F{.Իil^\" ]-CܹOXjU< F`nzuPg0o(^<.+#\";O8dЂw;@Pb~-E; Pl3V0H#!Ȝ]89y]subqU'Zvw/˯n`Ed -pjI`-euiXQ$6o\" >miu$kwNoH.,[뮽R udt~rel ^y0HO!@ P؄2K P28,)@!#=lݺUb@ԕaaI#I *$@jg'Ȑ.ą]t ur>$ľx=Jb@QrizWqwHԯÝt;%H[^)\<~\]\żxQBqi ^y0HN#I(DtH(D)0HNS7ч J4SB &p`Nyc4馹%{?$^ g\P<'tȈH9>]6Ub$_ yl{O @|P//KmBP:}"H>WBy@@(DpI%1L+#u%Qsr]*d@faH #Y4:1uP ]Y_>8~\o#x_`ƍI-%n/qaӭm3BuvB2E G@zb|7!IM/E! 4~W~:G" `> 32F(Db],9CbPYFn5^c1 7o=4M֢Ek@.!ЭӍr=EJZWp.]?95Rj ry]qG@{@)1zyXpert뛆@_ Pv!Y<ފii#i0HMZ^B姘"KB> tw*T;G=|Nm. .Wm{;e!J`˖-m.k&[J]ȓXy"<"y?pyRqݛZD.{kxTw瓠E@"}퉮(DzB6@ "0Kfi#wPL2ȫȼJ*IF:&J,1s׊Ii)#(@L}㏳'%K,`K @ 'nJNvXO.xA Sjʢc7!Y%_%IɺgRI@'"}N(Dzޔ@"9/did$i9#c(ͯB`+v 3]YEVD|r"R)i}"v8uJWbBrMg7ƋH=ܡYRCܔ09Ƕ˖-LB&B&t@" MH @"sd$~v:E?PzAeHY$G !0|c#Z1D^$νW10\M7{\"M43cG P4Q4D&x\BIi |0H !g(DzJ/O'1i30Ptw; >@\YJcG 'Ğ={J0HHO!(D@&"m2 "M1M$D#6g`oIk}I wP$'@ G -m'uFQrU+z $$$șf5ڋ/  ̔~ Ǿ-Z([j-12228]BgQ, oC P4ܔ\B'-%@!R`@ "dK')\bPu4|;v _)O]ׅh>ypyR%~w+  |k96gĢm|# ]M$9M_tK (D} H:x_B%@!^h)@!ҘBV } 76D=$y,Ġ9c6yC%F.*@_K>;JWBn\+ ~0`p]w^# `3]vɈozشo`_yëeDD/ ͐".!ׯϼ-#in""K = ޳eHk+Hk#@ P,)G$CT#@ꊣ"2_܍͔MX@2x"E> ㊕J\ZAr `cA{_P2B$vU+J,9| |9.Sz @8"3>ɄBO< @!4iigi "=HQG=& Muﵮ t}7U +qA 3\{)))c{CU/^/OjԨa&./ o`y[uejyZ[)H k{E\/Hڙ$$zRbF]+ O:Z7n%@"8y< q UB_L,@!ēGi)#aH?O/w.0rymEײ"_sD @rA$>1 5\F O7KqZB]!Y݃k]\\ .f7VT+Í/v 6hiIg P  bMK  bbHLw^$sRFw8`y=[ym0@(7Uoժ GA w$%{=2y EMuS[(#wAg) N^T+5 z{ů˰2JX@ڧ釙KeXK旱 @__O\UǹcN.""S^bv]H\nqX]BgYH@ގ>3j:B P4ل)(DrH @!fr̤g$텱_H SgX.xQ̏z%%kժ^i0+$_+1]q%3L*;U2or7'eґ6  q6}/"M?  P3@ȧ|q9Y4ނiǿ鋣K RH|V> -,/T|rG@V֯Gu Qułӡy@O-9|0Kbzu8w*ZFC@/@!ƖB `) ND@""/i@"/9 L0k~+Cyi89 r<%pv9i*`^[ס'gJKw˾XRVqܛ.ߖtYB19?sZ `v]pӍm$vhBb{Kq@7$9.vwE+tq:QsEu t// "T^Br ..@!6[ozCP4\u(DZg. [B0K.W$E]y[82z|2>Y^ߛXvl < H3QuXb-%lwNUrzD9ߵKW< VU z$.?) gn+8~]n+ŝs7P)2CzMQyzÀ7$+_'".@!2w^u P䣀Fi!H_ӟ(Da#SB?M… %T{өQώzWMLZI@_VtA?̾e?tڅ3N f3{$6E;Eʮzk ݮ_#E~Ѹ!/GF'qώM񳮄NsSKYy%%^dIX>6D xgZr]G =# (D^J %@!P|< @!4i!9 P4đ6^B N` dS2e =(C}REK $l<+A#[x0_ꊬ1)yo՟*v-#%ڗ6xH Կ|>n\U++y .@!2w^HA [BdxLB&t -@!Cr ` D3 a%itHgnG"G)ja+H +Cd|(YmeG* @m&tNb# (- `3=2I%N}iZ7oׯu .@!݃#/ P20#?(D `1 P ~aSHN'Io6Ebj-:,1ݵEqd]VD  P`佯6m-ވIioH:ĨFJ\0@FG؂(sΗ@ P4 4 &8F"MFH!` ֙KF"=8B ?"ŵ @(Dސ %Zi+"cE o{p\RF]9"}ޥ+Wܣ+"/L 3 )^1 (Dz&)@!&AP3 (Dޜ/@!sD `o Ï~B]񒫳d2|$82~r_Gu%}?$njӯ3цɝD@gJ-5֗ح{AAzN:q< ݠqdA9CzdOk%֪UK_@"KYXAޏ" (Dm׈"8+8"B`%sJ h^/ I"OROI?_лgFջc,)2teDGg9~W| yh׺\Z䄮{ \ɮrqW[@o Fr@L h"r%yed]I+<^U" 3tٵ߭ѻh_x 6o,]z=Lfo 'i<{o+$\+j@J͆W:Jܸ'?3Y>(@ P/{XBAi" Q(Duf "0KviYGK h+κVBV+Խ=Qf _n; w& P`;oo'W ?9e+qdhGl$Ga4Oj-ҴGrzI[nq@"=Ik>#hA"m0  P4O(D@| P7o0Ktz@A򮚺'k|4Ҽ FHѕU7Ԓ}{)GrA{ l߾]:Fbݠ.'%nѽ!,K _d~1%+h@P4Df"sv,]Bdޭ#@!?j. /E P[bo:Q ݔ q^]+"G"RExD wŹrIZBbs@˖.w:HKufkVпP$'@"m07Hs#`$ F rA(D9TB$@"1Ody_3Sۜrb>=weyR~TM9@.#QN_⨡/c@/N9zC~IN0@2[^:` "i(DfHP4T(i)g,.E @!@ yXr\%I߄fgk3Q˹^! 9 deבs>\b r~g@ ̟?_zpĈ>n@Z\wf*z=>sTcߟ1˧y @(D̍wIBP$3DL*@!ҤGY 6ބFBiDÀ#);:NX/ǏDg <  O픗*FI,[#Jty@|%pJW+V Е!/\Fk+Fw nzG@ P4̐W"sE(H0ӉA(Dd"HH/ҬVѽ%yaI,=c,)р8Û jV7Dɓ-q'2Eb=@ $&&JDbh/wCsR Pk.4k۶ᕗ k6 xD0HCOyJB$i P22*@!Ҭ3Gޞ E@'@!xsBF^Xnr32~I.v"t%X{'@pݢ3T׍'L ^# a,(h01͙Hݒm:IJ=+U@*@!Ҩ3C^^VE"-1 K Pt2B P,$ oG "@! AXat<9EZjPu9J .VFK kuWץcUNkN_(ΝzWOZ7/Ibx09@Ǜ ޾M ߓb~4GD_2 i)!!_P2} ` 'D"8"=-J{ (Dן,qFɠ$&'q`qt*D TtUd]o_nQ304PbRF.;K\gvHٛ&mRۯ6|#Ǐ Е?\Tv i  P18!`@ RB&"m2 @" ƛ@ P4/{5LAH;CW&*ٛAV ʱYr~ư%6j(8W +K:cô[/2>جq =_a< kVBbd]צ2XB #9SU+%%E :^D0HcYYB'H?56iQBd,D,#@!2S@ "wZ$ѫuo춮pr,*iF qG}֭'Ox6cg g-e,,q\d ɍj]VHXyRbE+ 1 (DQH[O?G O"E P 㭖i)e@ H7"pweG\*N ݋&ˡ+ 3zˊ؅q")^nW/C@pA7us_tO.e7*\G /='Ivu4` PX ii#i*@!үt-(DbE(D^ (DZlBN֮]+^+U`n<ݳ)P[/\V86 2ݺ[ix 0鹱/XK(< `2T9QjƔTIt}o qq&!"GBd~2"-3  P5!`[ zKB!o2`Gʰ>Yb`VFs-j3#>5T*KuzDWG>(]ŮXQhӟֽPǼ\ץK_H? ~t4H#i =PB"YBg@ P--[P`2UbPOʐ@")sudz}+Xq(N#@__OSs±|0@f~<2aD=rH#"W{02 (Dz f)@!Қʨȏhq-GBd~">CGBtAb]ށ'wSg U~&8v<.UT5Nu/Y@+V^|v~Ȃ.@Gs т__<4=# %(DZrZ(Dz[0H !`V f9Ώhq- `] ֝[FǎJ/h'1n rpTo Xv`5< N>/޺=t!JoսxR}lD@ #hA" 0 E(DZtbH7@ P3pO ?q\kPď:JȄswcۋXIwB''ݽ\UWnЛ:k36JtO؆tVvr S 4h}}U`q49 $ӟi)/Ou=wKl*Je< XSB5QXB?P:]"` 6hBM'a#yN#t%ڛmmK\7u<յ"r0+"ʵA -NW({& NIo ]r%V\@ =]. ү)[NtĐu%u"fLAsu߯q8YYDs̖{+8 `  ֘GFg ~G"KDBM&a Pt@%@!h{*ذx_;~smqqMpa oER=ut__¹HRrYn&}"VX!YĖIvWoysfq[@` tӯ^,T% @ '@!p~"m`iifxEBWXi$"M2QXB+]+)8PtNz!2.C(@z;ξn kDc$>_CEKԫ{>Z\:FW>xVK("1iqQ 煠󄃛h H[{>>w o5Gd  >a Pڌ2..@!6y7'Hk#@%@![ygꊇ>+λ `2$]1@W0-_{™l^7o>빾wpfͺ슉 Bܮ# dIZwTл^9Hi  A(Dd"H"="=P̋טUBYg@P;Tܹs2Ʒ]3CM? ۂ wϐ5iĂdH PkWf*ڰt%z\[9yB gncR$ŦJL<)|ɢrhD@IBdN*CK"K@B&0HO(@!2GN"PBdxHI(7h@ *>^Ebpt{@ ;ٷw֧L| $Y`Ѣu3N-ߵG}kx|B ]Yյtd]Dҥ@8OBy#į ݭؠKֽΞ@zߛ.zyDŚ e?ӟOMZXr\ǚBC \xHN<6HcY  P )@!ҚjQQL3N@7"}L/(#$:d@98"BWP.8'@)\s\ZhԨ<w/hg;&$ ~ҿmn3 (D} H"U{&ǾH>@l#@!6S@(@!Ҋʘ*@!Ҭ3G^Bd i{"gK 3ށNr:)^b`^ r>UW_eÍ@;r;P+Vh!3>l#m6k%n %E]?Ltdԍ%B@VJeVͪV1N;g ]lh@" `iidi fxi7)D~HH[M7 \BI%Z>&V,ӟV,0> į9+I rGzxIJ;:xw}f~7k7zF\(xޜ'/g{P_G?5KwxWG xPB1i  P ?yq^A*"2Hk+B&@!l3F"sBWB^^U} ] Oe,Vx !qN=~!ͯv7M={֍IPSF៺'_e| ,uzӺzz>rbHIK @ P8) "? g.%@!RB(D0 veƈWBqȳ@ܩSrm*$+0ȵ"㮻f?Ċl"H]+#!]7m)xYUÎ+ܯߟ-#'}sbSx{ı_ I\N*,!IBdc P4 P<_XSB5*id 9(Ds`W{B^{ :')ؚHyRn`@O17H`~ʄn@qqqd;w}7= "+򝳋.]_&<'񋯾ذaC8@@0" {H?O#P @-DBI&ʦiR3l@`" 6!@~&&'N Ի ө]qdK]"TB< O.-+_U'x=%X`$=bP-72@|%c천 ߹S-k@'@!@(DH!>3j:*@ P8) "`|=KGT*I!䈌1%׼n:yber݃Xn%>׺ +%Uֽ,9Xenc玝M;"XOB!pI $&@!k4(D} H  9p @o"FO_%\2.l!f>>3)z7ZWDA#FBaDHߛӣ(Dx(DZ}9> 7F.@!3ȃ&WjĠ2wNݡwy֤_''y@N~#;S\t?P)`c̔O={z{@@#"=H#XCB5QSB1煬ȋȼ(q(Dz[@|!@!vh.޶JW%&]@+?Z͊HM-Cu3$Mg[VnG :P^r֎rtJGIoKfw8q}qu:g]i93f,+1 P0 s]XZBZB P8) @Bdx  (DnJH RwHg[2^^.烋\܏啻H#niWx'xE (H7;64S_M`~\uPPZ5iq _z앤hԶ_-c%{R+\QI~!Oxm8 (DoN P=#ɇ P4)C vmƊXGBu撑 P`%_/>}zFbOJ3]tϡctȐ,0:oD#kﰂ"c$^|dVI}Dv1R y~߈\*-E=,qĵ-w/dO@ P%-!`Z :H M&CHN)F!ҔF (D#bcce_5k%:\w[tEg}{9=BΖO MZ\@P]xh~/}rflrxf[ 3U +!+]7w"(^L#- @ P==#w ~@"A  '@!pSb(Dj, `9 Rz=tA]D3= `}uI2_1Hc!&xsϏX2K\J"xS sm}[_ɓ*Uu1Lο9u  O ԧoL*@!ҤGڦii"I Pç {@B P,oF}u%VI {d[#P8d+pNih F 3\\:ObdP ,95I:@0H#!`p  3HSNILB& åi @B P,oF?@)\bPLIx&^W|MԒ7n qֹpcI=v {+חNCjN۽3G;lMaU℧&a،@"-: _ P6}EB]fqQBgͼ9S4ܑ9 ?(Dӄ3 P@w,Ġgc8wGkFq{+HHܩwh| >Rۥ{ {  y%xD`ʕN)2{#{FrH=+!+*3gIq@3 P4l+i "=SP44M(Dt4l ~[@ P*/#`OGG<"aސMKf_Q" 䚉N=sG"9/de 'H{λGM!Ӣ `& f-rE$xE2'%~IdVDd*IӇ0mސxTD)W> u^JWUAkt5g0w˶g0>65SSCn#u,ҭuY7%zCD o+O˅Y*bnoL_3͒'w{(+nQu >'Ӯe_ݲ}#$pR~Mݤkrܯ_?<  `f f=rG " 2aj >7HOӧi $}@| P#@Nʶj*y]1 @WHf[{D]Gsr $lwWN?;_9x@ZUk.VWސ$ŪJ iR\bTC58*H|;S$rQ& m%_p9~n yj@0H#!` &(4HSOɛDBI&"iRD2 @| P#'F??J[Hb,%/mYSzWͿ%^ql2*|+`|1$|-epƟͺֳ Bܮ>ן{濱@.V-\#ǭޥ&: QBgHL$Hrә(Dl<\ ~GH)/ħS_J s{t E"26yU^Ď:Zu  4RW8lP]t I|ES+ӭ"#b?EGʰw~+z}u++č%%@~RNEu Osq- 5(DZciid^e`G"xCB7Ti@l"6c䋀 ƿ{&}qTWz%;( qwR7ʱ3D CDiES']v^ N`@~$F /\dWTg( @"m;  P4ܐq(Dg.z"7H7 (DmF'#86Q;ŵ"fό'8E5dzz#EKoN{SZeQ"ZA ->]+Sbu/ȴ#^bDD@(&H_(ӇY(Du "0KɑBqL@/@!sDN`ýd̕|!q57d6D:̦!H`c[d Jꊭ2C$"EH.-жk(QA~WN`Ӄɘ_jT,Fe],@>O(0  PPcHO @B &M+@!ҴS)D@,"@!"0]];0v-xS.K82]q:ںaV>c@ OWą]tMc^ z'Og_BDٳG^2v/r!@qY{n+ҳqNgWOgDD@"m;  P4ޜ(DoN:"3 H_( (DZmF&?]cFNSb@]b9z^TRx@.1Ebk[t ɾ6Zbz$~zyDXWp $Jg[U~}>yZ s (Do1i)!! P4䐚(D~ 2 ~aS@ P4đ68O" l!y-+#\XW q93u 2е5jftJ]Cr{%ԽVWQ +]XW>+w6/:uؑ1# " i(DvH" [ȧ|qP䃀 @(D݊+@oxS2{w؈**@nvŠ8i%&]ZVWD&o#荷"`-}j֖nw^QQQrA]|h+>/@:rժU8~L5kNy@@"8 P؄2(DI$@!2OL\T@ m iigXS^9<1aQ򷗑fH;.Xj;^ܾx%&:%T%nU@گyyD;s9߾}Er78 `  f=(Dce"sv, P<_YXAޏ `G vuƌoHt6 Ǟו7F,^ +"-p?<ꪫl5~@nv-e;Խ#w}UMWH=tQ7J5 lKAv ]g&YЌ@ P$ q(Dg."}oNi2r"<; `t F!C dwK|\}@WHNpV}F&Epq-EA"9Mջ~_ћ;+"ܮ+$+ KB$+P,2o P5ڥiyd i!;W~%>m96Q7xKro|/٦r5t%a/ Ϲ"`ao|#2KWmj2 +ǟ.=޺q.)?.14Ϗ ABd%@!^mRڌ2O Puڢid$ i9"C7?,hĝ 6K_WU|UcE:_ԓwYÒHX#x3I{OkJ+ lۺUs!7t֕E-E΁ʀ;]1/ "  ~@K0HsYH> 8"GBd~@P# ʖmw[ Лh:="-<ZLҲ9?˓%JXn Ⱦ#htj/$~BLl'v:]\旊 0 /yEP4Th(DKL+@!ҴS)D@p @rRlqrR_gg{sW^#Ju_/#INJ 48Ht\CpSǵﵺ=iq9}_m H IBf"@jU,]^4^bH.u4[jyku]VDH;ߒ?<w6t$@>^|9yo'HJWg7s8A_7' kg_B4@׵c}9%6MOѻb;O$˱3Vwq-M4JRE@"1<(@!҃4e8ai!! P F!E i pgԱc%S Е guI_^d1FH[K|?zIꫯ6b@"+]E-MWo 7{~d@Q7ZI +COMbÆ 9# `? sFy ^HKL#KYe H `  fXz4Kt2қܴ!ga=$0fZ#0e\$UsY74Nd1/ȻJ[/Իc{r[n/@@G"}M7 `] ֝[+Bge P̖Giyf i#{0굫$_ 1ޝ)}b#0,{4#$6[].>~͓#yt{_NKm<%qeo>;li]>t0'G \B? <'@!s; ' PziY @r g@ YF劣qFWJn]^/U,+۾TW q~yk !?C?=.ǃX${ y}7 0U$[0/tw( 4 (V)"4z!b3ggΝ{?wpg* 7E: ȑ>$qAK6-V)[뭺_US\1hs;>8;I]"Xu7rV&&iq  i5b $$"dd$"dt$"sID2+@)@"|FL pV{&~[ʞ+r%u-EyȡNz>θ Qn͝+[ w- hEdd?>32$gD I7^pBܸC\xq΄a! $"aH/|AD?c$W  /$"e'cGem{VF&JȰ}MiePA1a^N~J\>7܇NpE#P>DxnʳM]׾g@^NjoE  z@ i'@o 8j٬$"ͺ2H8  f i`, @&'OiPbӇ$vVB1qEęȻ+{C{r'{nMo}ӓRJ-F|Ď-Hܻmޞ$@/@"k@ HD' ̖oOۥc (@"2G" X@G-k$t\ӷ;(wHvȼ:W V}{>#[-gR`2#1>;ܣftY $NS-ϻw^}7Ӧ=z?)rʙ  ODք!6$"m8H/s;HD:e$"MD @HDLH g֭Y%}H}#mn#c"'TD֞8VB&I-coJӿo4|]% mR.JE~-.9|C@4J0@:ׁU^fN td4ق0@<(@"҃t8#p1y;$;oNO׷b1@%Ttv\@GFZkZdEO%&S%6`<(wNb.]{\ yWȇ(s@HsC@ {p;$"] 霛Y"i֕a\ _DMp@BB }&nN9w $w.l!{ݒJ}FdrZd}dƍΈ ^`21kGK_b L|λ @@HDz1Z"A&*H-CRDd4~uD_-E@-$"H' eVue0y{iGIL҄ d؅H,ܸZq9_Bl@[9 1w}7-tF)C+Sgr?'R^O>)w"=r@MDd(A K։t+'yHD`]D\ @ Ef \2kIx.KpmQ`p1[d}dh̿7Z4ClgW~XBe> 1&F+ti%$IHѷOZ_m5tۊ'$3`  i@$" `#` g!g vpID^. KDH8@r>˄pY9imJHi-  df%nmSf8K]۵.L~Ҭosj*w~\^k2rk=5Qjʁl@@ HD3AV7Hһ oH^?F 'HDzR@(juČJt,ZaZq/};A kK>>QZ4i$Fq8?nxח廝/vkwxVE]dXl!pl^Ƞںn*鳌)s Q  HDz  [@;@]Ys̋D9] .IA@HD2@.v?$6jEђ*֊Ȼ 4]=M)Sߟ"Lѷ*72KYCoҗ[-_sj]GᑅjEzb8@ʟ^yQ\Wq+8{%iNv  @@ er ? >.@"uCzYDdFfnA"̫@H߸sW@kMP۴$NͽXD +iEdl3}ƚM#vB e~EZl,8媚Kl])Yw{YoE7 ^]GשyHkoÇJ\fĘ} Mcv@@ HDr2@$"m t%=[DMHX%ƈ oHDƝ"ǔIltC $we[,k-KHH'pe~]k Ll} 'wfXa}@4@TK/$.qywpA_wm;N" @ d& Hv<$@"Ctk#@"҆ô;$"M4 @4R0@ϟ/1$D+!CC-~|)jS$v,NO>"g"q^}}eP}[nTeAl.^(guSbGX6pʇւ&(\gwp95  ieaP $"]7HDlD HD:o+IDB{" _$"k- qZMk=2HH S)'և].[mג%AJI,8=KRu| r&w5 HY)ƉxID6$"|d$"cHD:1J@ @" @ iV[FEߊH.φtniwg$-{^4eK0eVJ|Ͳ|:m;?Z[l7Riۚduҙd3>C8τ}9?ݛ@@ 0HD2+@e.t$"zyM39]  @\UQ~z[r}+rh~ȋS'7ƻӕYv>φX+&: O{ZbLLY¸L( V+5 t8׹-{D_&JסrܯY7m4 = OD, H.OD˄tlppD A*@"2Hi# Ѫ\re sK옦lJ㨈t֟Jɰ)$1"CNje䨶o~$AJ_=+@S}#}yTȫ$}gĞ3#{-, >oٳq d*@"2S"\/@"z'YHDz  >KzBBd?+g5}VӴx*"`F]>m6U_ &傃qq۴icAeE29ώw}rHn0/i+@RarQ;0 ~'@"# [ܝDiB"ҿVDE@$"ͼ: 0}d4E|-qHx5XNъʼ5D满Jޞ%TÝM'ҋeL3'^LNm[H\cbâ$@J|VSy׸$qyg|;G"@@ +Yp@FD ;9(O/HZ/F HDyu `>= 7_#7[ӭkݵt)TW F|-pyBv@ +{:Ib}IdnĻfA9nxV ioܠqeV֯WGoRA;%A@  @DD$_gHD:|א440:@QD?cF g`!O<)ZdĐ}}XdH.cnBϏM6DAqU⫫r..ڹn\ |H"0+ f iU`  @ tTHD1 iE&i`4 @ZMHd4kI 7N[PAًukG7VF;!: ReLE+P>?s/@ALH_$"ͷ&fHs Hs@@ HD27@mj#8rb3sG(r[J շizsNNxR ק"R |sT̤UEи5c^[S%/_^ ["#CbH>0(&IzN&I+2%Io,%:5  `[ ^a6MHDnIL9 YqW@p^Dv\ @䪿V,qb=}Z~I9^V8q .1@IVϢ36g~C+_WJˉ45X`͚52V:SokEr͌krjdYעu8g^/Je! @P ef yHDg-<9 IDzv-IDz֗@@qq m6{X+}%cokH}~险Ge =.ICNcZضm[=6^5B/$FT/Td/>)qA⪇o!gUV5N@@ t+'!$@"2'8O"20ӳ iaDw  ,@"2g#Z A=Il_Q!htuVDVDiHEaO1#UAwnv:ȇ\%s$cGlRVӚ2Ahn=^]-9"qM/B>Wre˖^ @@` Μ@nI"~|K]<n  o6 %-Jb۫I,l9+I|'$A@-@"ݢ `#@"҆#hvHDRg;Qx$HQs#@ADd@FpM >>^:8qĐ (c?a._/1׍a@2Z!K.5 ڵkEmfoRAbK<~V̺GԠZ ܼIg@F.{whײ`-[ʴ@@WHD*  ̖'hO 'i%iu`  $"ςO &c2MַlSiq}(Ig2*I?s҃ZYԳ^;.@ܤτU4\-1NErI ja@ʗzҙCLVFfQZVPQ@@5q5 HDz$ݒ4Bxx$"= C$"s4 x]Dɹ! #=!7g΅I+.KB*$*G6woϒ+k<#x"xsH\͍VI˭_q>S2o6ٱO m7{i}}W @H3 HD]4ah$"݀D$"@@H0s@Wzx\+b09/B}z/\|EҽϽr o Y%V,#\+׊ȘDd(T4ۤͩҴ[>;KiqM@IDAT xEDW  $"]4$"͹.Hwf]8  i`$ vZă8_m7"N7Hϐ1$|"qjl孽>_x׻cW5ZZ"ֻgƳoN[IY8oOKָE Ȁ\V&Z[Y͆DdV2'g@@HDߚ1b@b"mzjEPY֊2<#ү'񞿤rz!˖-Wa{mo%1W~e*l?r_VY9R++EhJ+vƍDͼ~^WoI_K:}ؿmH2b Hd} t%F)7(W]\=Xz=lybeE9nVPY  yHDg-  $"@&$"h\*HDk~\  i`$ +)g/p>tr^pP~lU=׷/kowS3?' Ɏ|&Nbpͻ O7:&'rEϩm+r0W/+ߨ&7]Jȩ+# > vn HG=HX'WGI"UAD{@|'@"w@4}ޓ.\*1CBmN#^E\ޮ-2ipM]u]-{Zb'I,WD6!pAhZ۬Lܗ,CkB⑍[$VN 5Ig*TH  f i`  ̔%k$"bHh & i҅aX  }utêR隨{>V]ܣ,Z]_~ܨ\ҽ77 L6Y7fhy(fWbЂ2?a31ߟ q?%VVͦ;  HDz? GHDzmt;"!GzA@ 0@ $MgEi @*T}DΟ$VPT)٧Xr$oQnxL  $"a# HD+ 7!4]̖  $"x: #gF5rtV5F|"6*"Wvʄ?گu}wJn8~UJ=sUzY;5~[v֟-ȉvŴAnٴIgE ƚ}2L@UDd,BVDd<9I"4K@HDfbAvS@DDd,$@OU*)\b3n>.VDFՌ0N} |.Ez_$B[$зg7iR~'ږAb[#jaNsNuIb{R'#Y+F'|lR:g  !@"Ґ "HZf^$"_'p@GDd53EZs2b%zJߖHkZIǭo͎EE8stWzfc ={V>U)WJUT> 2q4K<|Mc~ Ң-sKsɁGKs C@\D/ GOD}NfmE"Ҭ+"އ  <$"g) Z4ObHV؅ZcHjQ.o#P J<Mⴣ2C+{{hIa/+h;䷤X̏W-#jke{ˇ }M/B@OD#Fp@DX&nJ"ċ󏡑F6IDf)@hL@Yqɥ3vNZx=r3iZ)ٝrag]_@W~_{[bz$ŋF:)1ϣA{~w}2%Zq]Y;obA 6[e i2B?WϞ>=%A@EDd$@H)H|ڐD}$"s ][f F~(Mİ:yƭ_='xSD'.Wm3'm7RCb~%>CxWZɷJqlRfwɌ_tllMG~S6A@]D GH0&$"Eb$"[9 @WDd-3C''JHbh]Kڧ 6R/U}6 [WfC,.}^Kt\dIn@k$$$H?Zו-P'KvweV͛61@@~[@ H| HDzջOD}NB@ k @< J}KbCзv5IE@8IW]+#n)hC_Z~}ٜg;/ _b-2K?*q4#D@<(@"҃t $"=$"=IDf/I"2{" @ 5g nذnX&>{kiR٦]Պɼ?F V-Zy[6oqq۷oo#>{!}VglNdz]7K5``  Hv@OD}NjE"SwDRC@` l+|@\ؼq\Xǻ%n~ɦ?_ls $/L' *^zAK/xlNI\{ءQCвKeVIǓ$fIgSx\M\@  @  tDmIDz֘D/H[@@GHD:*F{@8x̻qZDfHL`1J(.c^,#gӷg7On-7x=s 5>1i\_AfJ2-D 7J@;^m+/r:]KYtgnwX @@\ #Hs4H Hu&wA@ k@< rJ5=]+g1,,LoٹEGOH $}i۳ ,{sƽ+?{&pψqMX|F깉dwe?ugD^"N: @rA`  t[IDzVD/H[@@{HD+E;@~wr_zm]rI&Wg>x}ۣ␫E Iv"2sq9u_0K7gG> BkWl@@`  Q_$"G$"myIDz $@"2'!# ͒^^WF&׷oՊ\{nJ@vU+L=!k۾CBjժ9ooO/8HnQݤ]M矧u}[ ֯[G!C|:^n EDYVq H./HT 7 8&@"1/Z# _V>D+N'~ghBDrtjk.k%udl%|^+Rn7v.;lrPi3'Q}Z&k2S'xxq}617U-qa)"  $"z< HDzFDg\D>  `Hh [-'Eag$VrZ,WQ>g6,+iEdܓ޼/1 E+$ܮڌ;|L:-\H֝s_6E!1$Lߖz W؛ޖ-v/_ls@AL@|$"ݳ&$"ho/$"UD! $"m=C@C{J{'tgF֞&iEdTDf0g/F]X&R[MK+ ;={DNzիW3"+5kIq=%n9](瓶T(M 9P<@@o 4A@ 8pDX.4%I"҅"  ? >  ='~HBe+C!CcZo'jEY۟)M" #!E$NzaĖ-4bK_s_!1▂Y ɧ4Ya{Usiz@HsC@,} HD$"ID:=  T8 Ǝ-=޷$~bgVDV 4{Ӯ4u2wa}&cIUZg~].wcr64w`<#2r'V>&׬'X0߾zİ0}vA@pXDd\ $"$"$"IDz[@@ HD*3G@ $&j%قye,$1$DmVaZi@\}i om-Lk)ߨI eJ xigϞ;qƍh}xiw6&(?ou^",o}[qΒ!#  i5aD (@"7J">w$" @pMDk~\ GV\!?;وiu~y>2x:fT֯_/my7jh %ιĈҋ?qA⚇mZ_bfnq9'D6   t͏@HxIDfG"Dd"  ^OzC@#֮~W"1%| kug2n%K{)/6ۣ- Ӽx:-1$?C=ψj}Tf7t)vmi@@iNq!  5HHDB+@@5q5 ^XN+7\|rV+J ,g ʘH+#??YߠAtj~UNtgwѦ#?ݤoWyhE~vU~PHF{>;O++%ټ["o]6  $"B@/@"9sٻ$7  {HDǑ^@ ~\bZw%KI,\yIwUkH蝏"y7QyDYƒZYM:~gD|%÷,l|(^\d…HbÆ %A@pNDsn\ OHD:O">/$" @pNDsn\ )6l xݨo/NjHcN /4ȡ#^F`k׮?rO$3Z+ T}$no|Z6o鼾IY@Lq@@>9 @S tlYHDfE"ևD{  $"]z@|(0O B $ψo7P}z󿝗7بg}II2Hlպ6={dmzS+j!ikLK|qЈ@@@BD_,D@ s\D"܅   GzA@'˵KCj_~"ஈ[@Vt֊b~FY^ݟ/^$(G~$EQ`@;{Ud>s>1 $@@ o+x@[Dc_yωV   t΍@)muO3iiV ј%+Db%&~rѹG$.^6Ӕ䈢|~$>>C`IٱiYWֹsqy\J}*qMsyW!  އ JDcA"1/y@@1y@ػ;K b}_}}\|cjĭ'ұɁ& <L;3Q6_Ix> 9,Z(L^={Id  q xED$H{h$"Q   $"]z@@`2;J ؕ2O|o/1NDcsuVX8q21kGK4+FxKZ5Q-gZzQNʫڀ-  OHD" ]$"ƑNs   .IA@(;k/1>j=!5xCެk<0Nߪe+nPp-_svK\|Z y5gUBrfƲ|f~qq@@HDfQ@RDT @@<#@"3 gV3#ϼs@Hտe"2M@$$$Tz>rn,+K%Rbnm&ivM+{/vT}>.ϒ_z?M" '@"2 ! H5"I"2Z  KD$@?8vMO}6v^ÖJ SVb2$&Oȇn!d>JY9yLj$=qEg8kwzo=_E9ա606uscdd[56A@Hzs7@L)@"Db4'B@FDd,%A@q-VBvo6zyVȥ(+ ̊D}rekheޏlRS$d}&lТJʉ#II[ٖ]=(#zilG!!!ٶ$  gHDz֗@0H]@"? @{~L@my\t:1ZhɰhXZVm=r]Ry]qS}r15{܇dv"롞?^N6UR%1xԝYw3ɧ2_[}8$n  Hh @|%IDQ@@wt"}  g;vڵKbhV3RҷHy,0vUgz|R+BoZ:p+ovusO2m%|_L%q>s㯛drq3H@@ 7@  i$"ID~#C@HO' &/3s?yu?'񵻴"Rv,O]o .sX='ۋ/HuS+\SڱDOY}dd=$1m5㙝26  @ f 8+@"RHD: :p@T @$$$HZו>[2[eieaF>/4VY_Νe^?rϳo ?CQ>9d$֩SGv<}Ɉ 6ӑ{aܿXb:})贌]amҠbŊ>77G@pNDsn\ @P $/>Hwj  `~_#F ~ג}<]O۹#*KзOeB疝D/=!\>2)_ qzS[)1VD_'VgChji ˡf͚  ~$@"ҏ" `fY A"1/Z# WD{= @HN ۛԐy{B+  )ӷ"y{Dl.tF|^yw-$LG*Z|+9^zu ݡq8;ԹV!%Ϣ_x{@@HDz1Z@L%@"Tˑ`HDfI @@/ "6B@ PҴrh>spu} :ϔ+"q~5~c/f>#1S4Λ%|#)0bݻMWe?]C$z-m@@HDr1X@)@"ҜbD!AD@H_so@DZR2Ym7ɐQgY+"_D+<][}5˭8(sCB-O #Ub s1&u~_F=mZQ8ߧ7W֭ZHL0j  HGh  / Unn  H' @` Àg%UgQ_]ѷT^2}i{fƛ^L}MN3۵l"XtpLޡ 'zy+<ʕ˪ @@L,@"ċ@0Hnt"}  #@"֊" `۶X'1<,LİpOVN:_>& gA?$HQ޴VMޔ.]HƵaخwkjʺFK5jkohiٍfr@@ 0@OD$HW$"]Z@@HD1r@N&ɘCNhg VrP+ʔ)c^i7>.Zi%<ǎZZ }+1q>Dt r2> 2Vo+k^S"@@ ,@0H.H;  s$"s*@p@uW%峭Hg WΗ{}xqsb>ۓsn\狹Qa/KةD_m>J+1qa )nOT҅4Ţu=K-yi" [Dׇ! $"=$"=J   t͏@@ [ԓ^K gnS ~e?OvT+ ?8 Vj_qX?J> 3Fơ/INb x&IӴuv[3zr$9}A٣s@@ 4ݒ0 @OD{לD{= @@=$"H/  -H/kBĒX58Z݊3qd5m:4N%.gh_d["wCBFgiKNKS+#YY\r9}w==$G@0H-CA@ HD@"=  ^7@pB -U߶'V:J>R֊CżSvM]2YnJӦ7on 'q$=?w+$`LI[Zo^:>n*ժUи  HD[# t7D{@@3$"=J  @HVfje_sAy2oW|~Lzl#phWV}Q7^\#ngV:֋Si%!^M.Z@@ 8HD:3K@L-@"=C"=  qW@C~m}Ν;uzVb1 Gۖ 3"sB%}e̲t_sģk]KO Qbhn<#2i:t*|ad:gzR?;hgڎ  )@"20וY! $"ݻL$"Io   ^OzC@r/99Y >6|ZJ<0$/})CQGtIx6oܸQ37my`'錮[ 3{.t  ($"e% ~(@"ұE#@@%@"\h@@#N?*1o+#w$8|@%:9tH/1bDo;rDbX彝dܕKI\(X ^Kx>c3*^8kW+Pw'ݔ=$MHMa3g@@PDd.:SF@HD0G@QDd0:sF@ @'Z! xVDg}@!♷DzNG+(E[/3KFQQQ^o tD h4* cceڵk/;-D}6XҮ-̴䵞^]N+"j_T7 bsOܲG#j$hGjLOָMݰI\9Ƹu/m&wʹZ,1,pCz۱ lҲq&v^X}+VtB;[_~ߢBnǹrI  $"~  A @"RD~IDf3O"2{" HDzΖ@4;{9V9,Ď;&gϝ-Oߧm[^Z!!X՘;ovfmB۵R/Uv=>="ZYV.ƶÖ94YAdQiIA\}8rB+h#\lD/jbmf fKIGh墋[?W.Zڞ\F뷻ڵ\c%|JaRn.^ ]ymbv]Z?|(NMU2xd_MZ1S.]k^[$NKz[wIJhwdգuMm~N<)/nrkLJ *夿WZFW+N>y]w՗K\;U ]ݴm@h]pďwu!NO \nZ~]_Xg?ZPf-ӭ6igFV5~6aӏK.KFA+kwW٦qE+1FEdvp\FEQ-Z$q NUh#RMW^x!8c\ݣG;zɹI7H"a}:+oݺuѢc?f[&Ϩl{ҿS3*vGzY9e휍ft¸w%vLۼirc}i;g^-I~gGyw7bz^ ғ܇Dd.$"IDo>r:J"2{!gIDG\D"#HDzJ~C`K,\Wx&W>=21sٶwv\m{GO\׆Whٶ/V$Rvkum{{Onݢ+s\roG}VǓE^}~_?zFmUJ3ۚ'}h81ըz^J+z.۷wñoH\\+X(N||ZYZʕsW%eT/JoϹz>_|N?cDndXf>.kS}Q6M#WJVεI+v9Y)v%/6aiWk7{h]͚qcd=];ԵU^lVB>WLWIw;ʮ=h"rdrA$vV李~ȫS6|7l '(ZJb̙0W^vg͒C]׉OF֭+o-dsbe$Oޓ b"n]w-c7H+{t^SY w 'Kκ֗h[/sdjK-,dwoܮV]]Ze'eT~gX VF4*.߇+j74vsp럷iw~vu2kPˀUCgI  yrsܹm6-{fZQ5s?~^|-9`{яq7 k)KE=7c";~W۳Ki=;߇CU"mG!CHtts5Aַ{_}^׷עUgnݺ9ڵMe_4͛"vZUGZQv ~̘B ~=~ʟ] ?lǽvOO}ayo\>K3}ugrm{Z,oiAvџJ[\߸tqU{Ф_ָIy3<-<ܵ||f0׈o5Z>>q)ĵki?9-D|)@"җ;HDf  ]HDfB"2YN/!j#F"R~Fp1"^D"$"m=#yH$"3w14$f id,Ir"geԩC+-&k@ mN?rCtH2ZyA͚5身~Y}W]/jժ׵tn_ 9vR*"KDJ/%HLJDNlVޯ[[\V:~։}}`_3$v˞7vH5[UW;.Wty?Dowɾ=eULV'^WEj%]wF$}Qk_jeljT[{\v]כi%K~YTF}eVRrVRU쏚>\MY|(\!saTFq܈W{Gíc1K4,VwcַI>9'?橅:ΟCe+h VF uK,e\z>?ys%r!ehn9crǽ eH1q_=Nbr}iϑ[~hc_{=QČy>)"ݼ>b\r4ҟ|b8گSWc>7RFx]{HtGVZjտ^E?5}W*)SB=zWqSYC [Ѝu=F~hR]ǻONhϟK}&jH]tqkYH|o3g<5uv&۶/N×Zj<9C˦٣l}?N]ye'鬜3~IDfN"2s܅Dd1A"R^HDHu $" IppK"2{0gIDGGIDG%"!mpǝ2g?b;Ϟ:yRZ~m>+k/?\լ]hgw!- D(}fkCqn*yJ+>, m+D{5' 8yDdKQ>I9~}#Wf4p?^;sɩZbcX+S2&9%I;mOVtN?[KΗ(\ 403t^'wuqߛ:StRr@ !s!Ct?^ʪ2_ߛrhM{72-!o_1Vu}zoxb GB~z߸ǘGtX0H|BCYǭބ,F[ 7_.CiA+:^׸bZAgTRY_o3>J\_U80'oCIַ[>KMD.VZ(߇{XUBOI @hUEdqEA]X(Mp, ]VHQ BIHH=9O$g?ܙ;sϝ;3y3=d:@!(#oC!oBjGʑUkfk~qwjL6h?\yDj%Lm1Y܈ y'M4W2y^?ڛŨ7:+sQ?6?M,px1CU!~'CUث1ކyO])(">❆hdN^VQ .NCx #ݛ Ǘ.!Z='VC/N2k PKzx\2s9xaMpCwfKê_wWVmjÒ@ 2j3?Ъe%imCU=E|'PJq 6WƱ41;]"/rtD~GYTÁXLB"f7dAXa#g_kԓ50~jF9h|c\e: ~r^˩ҕƟNK|.rcgD$zvU=9l"U+z|nO JtvN.[(ӟKu=дZW9?=U-ts7DS[Hx~དྷƂ%"7Ë޴( ݻӥ2VMSjmY>1DxL]rEONy]\^I3xQhD*m_ىb䒧?drx+[4k(*Rw$7!h5-V|w9Q^S>!z;^URG~% <4DC4D  n/4DƅHyeh) ڂB^" B5$"UHUHWdOCd|, J¹ wx Dfda9_+VČL̘z˦=S"+Ɗ\ʸ*IGE2G`&hff1s7ZC<^/9;vGK1C7*Ƴ8pJ%koy~ØjU$Ӫqyl>=|ķi}re{_E'Y(͛7Y:d)}WZ!<Vm)a}P /*uB1 QދP97~uAW}*UJ_W7:$>xݕ9?yg"6].xiK]W{%o=!=s*=Qot:˨sfM Koڦ7\GGۼD<*]J=M|{ ]0Ư,L[/ Ûa6/̄ssr~wydlW@8!9f$-q]xy7{8ԛ+Ax65rQ~)Gx 3~+PI)rX&|vr$NM3DZ_ Vl zc#GxO]ek=(b0g7<r]=QʣPط$L2yi{^AV|&u̴xϷ,?nlcǤ %npx2ԕGzBj:3ʼ_˛zӾ3/zF:zDntxB,3.~[7.)"YpiQ+~} ODq&߇⹴ ?d0ً q'zCCvaV:?x=?s;DO+9s1ub|6Vx%crx_x|-}o{4 hv:pKFʪܫH_Fģg=5%ҕF110GAXQQ^"U%0z`䨜)ԗv: 6kh'%#Fߌ{v#Qz}{ܿ?;, مӆLo7|/$r9fz_11.\ o"\J3ڟ3sL(EnٍsLn%K"7$\ZwU9M?dsۏEoxH]ci&7"OHRTƻuR-#Hit΅H{.4DbCCdm4Db@JC$ "KW!"f骊i!҆H; 0IC ""y  4DTS$=LΙ,X W_3ss̜MŒg9rb'E3 //9XXE$G2՚;xVm%zӊYR$S(C ɲG0:x1ҝq<,ҕ`w{Zcrg$|*^#NM9ʫq$QF"u:]Nw3j|rrߵ`ՏC~MGD[$bf.Ϭtr3.S;EF Ru$'C̶z4wGf'dž^${V}+S9xhN59<,*CRiFC_Lr^+5tהW qz_erPހoQ=Cu'_[6}fsۖzrt$ef«3sB])xQ1mf]+o!.g=+ӟė0C{vyar3y?aM7kFjɗn*x-UoΣwxݑ`ƷR3=Z]g2li*&h-"!mc֎"XZۻ#k9گE}kK_κ2f|lrWۚM?ቭh?h3|}]k^~ݛQ?eL~U 2'=/%n \: ?à_^ D?_c?{ G/#U|hW=y5eۮv:*(e5mPK? $xu+ۡjU<ݓ@EG=s%6,=wY:(I|["V@y 9-}u/(\zDa"0M% \԰JC$ AHc, D4Dp H>LC=OC4DzJ> <4DC4D  eƠCC!6% #BCdnJCy\Ȭ,x _Cf `&l.,jhQCTQ#Gnh!24hO UU)w\3$Æ/5mm9˶Yn%_:"3_Z̐i֤5T^O=4^Ec*!:77cfFyfM<[X׶33H=OjY1[@T(9q9kiX׸5WY]i;4ߝU9.K=z6O:3Corq0imv7\ B:Z^5xi5ªۙ?ziU0Ö~wuѫ'Qn6aOGd~aK|pdHw2 m)hv9 yjA6rY?cF ̸3UFBӏكRc(!f5Zy :_C^izOxCb4oOUK.;Sk|ER=n籶څv4WyH='oC{q__Ȭc&˕jɟ3G+n491szx,ރZ£5"/&ϟX_E# y<"";uWxmпWS#ҥ7O: Ma s4Ur1r+ uL{psIRÉS/(6܋fYeVyv“A`h6FPoJa=޹4we*~s֣- IEUhp} lxƓv,GWG7k>c< dl~ 85x9f5FF%tz>aZtD.4#|&#jw祎4Ԕ$r QlvuGWa^&=(,U]?0 kT^O=PUfxQ !xҚAoyWi困U>*S7a\=׵W&MhtmU[x?c0E5~c{ur#akH8'0Rwrf׫jNtnN=Q@O#Lj ޶{ȁaz Zs"}^(3TUEIM_#r_e,)9O+frE0G$ W oO0 YO"_"G9Y8" , KC@C .4D:"hwi4v}ha(fM퇅gMXKnye^ǃh5?*]cm3>kvO=@+n,ui,CN`-IR!޻SS=Vu:\ $HԿ^{ Uۄzg< B񞹫WZy<*Qf$hd(g^9?W/dֿn~n 34ky_ozV ̸UoރJfO䡷 GdkD%ǧxڝ1+'UZ=03ҟ}3./ϬkO"_CB/#Tg4x0-\x~e$wM *fHUۻ03^=kxd8`X&K0"D$py0߃ڿz:;uNgEӵxZϓ3W3|0پ_qJuczt,GaSEOtH9fMaܴE$#1[fw-`<{=XqkVKfM|[]K譸,{K5C:懲Xc 0 ?NNH2jf&8uG6[,Dv+/ vmfpb=n`!W_赭6cT=!5Z+7{E\rPyZZ3ѬQ0Ndxj9j0kOjw\-WrvwWpPhr~'25%@1@$@$@$@$P,4D  \h!rG   guIHHJ ),x]]KMK Θ&WlT~ OKЊ}+N$HHHH-4DIHH.E4DNJCXyO$@$@$@$PzYzKB$@$@$'87 **O*ɟsٴ4cE>!Wn4H 'h \Rh!м   (h,"  @~fdI.$|.2/휄&d -on)rgyJ爼!'BvY"/GIHHHH4DzYHHHJ7"|h,'"    "}ÑZHHHJ]/T+ݰkVpfrg}z B+WEeYz[4D·gIHHHJ %ÕZIHHDX#rRm ٺ- r풵"4h.iyM6ܼr˺Uo r߁$ax$     Pc   RM Y    !@CdpV   RD{4O` A7cٕ%_lժU)*u)w_'iFd@YYzJȒ \ hH$@$@$"g?&   "YHHH.yg`{(zl\ yoܹ%“ܹkmvdl!    y? \h!?&   "YHHH.Yy_ɽM6Sd&"g|:Ed>O0u//? xak'd, 8!@C("  4HCoyR \nhܞ8HH.y]fȎ;:5\t'@w,+~ O`BIglI$@$@$@$py!z޼[  ,YȒK$@$@$@$p!R}/   l͓na]JO:%iAyC&Cd7D)`!    ˊu!]o@IDATwe0ഓ !A AK* ]@r墀t " (^4MEB $5zJs޳g?1k)oy[Lf  @ 5cc^~g*{KIm;pS0L~~F/fN=;6P @+DVT-RӺXךoxշrY[S:"~{<>Zц @"+;VOr!Y4*Dݨ @rP,̙7ȱ1;'KW릥' k{%*[//8'&uɅgD?z3]cgœHl  @*Z@!o @  R\\D @1t#@J"01q_W;?b"IɅqSNhSӣ{m nǷ"^u(͢Q/{.?wY᠕  @*X@!o @  ̌Bd6bV @rP,L' tqJjU @ (D6Uu @@N<'X}@4@dQ"7;{x/w0wãmC |* @@."se.B!rD. @R@!2i( Pּ̋Qq{E|BxZmĹ#Μ|[1Azw?ͥĉc];myĝǙ['b|Zٳ Il  @r!4ZȦBd6RY)DJ޸ @l(Df#fAr)0ƺVWe8?Lؽ{t6XǑG=k"njm}?0lPsv @_@!sh @  MMQ'YTn @2+ԘȏGb:=Yı#;"p1%/C䓸`ر}ވM/bғѰ!@({ȲO @P,\c P6 @ ; Ʌ @ Ծ y#cu1jtJ_>~ELu`,!E2seBX5 @ g 9K @, (Df1+ٝBdvscf @(DFϽ @@5j6x%,oli? "nK~e/GzHXU @r.[(Bd)wl͝ @PlL9 @EfwBv+} {nQ?nlG{$~tezhv+  @e.Y 4} E,fY~93c @@c 8GHOo}"vMOF37UI??) yKĮkwi`ӣ=ꈃ^y  @l(Df#fAr%t|1 %O  @D@!MuBS`޽ƿ?hÍh3Mw$qľgDs爃FD @l (Df+fCZ@!ӗ+Df>E&HhT@!Q'  @@e xcVz{vSk#.N~Z}kdOO'ц@[ u݁mG`Asfwt܈뮐:]g@_cZVN @dJ@!2S0 Ml%R{4J˸ @V̟RÈ+._qsE5=!  @(Df&&B/i yʦ @ڵS+ @,P[zo{N8氈6 )`AzW7]\pIBo @ (D6 @b"+6\Bd&bR @ (D.  @`@i}Ύ]wuѩ?9O?T'Fbwb]11 @(D( @JP"'WfJTBdeݪ  @@jGϋ3OxÈϭ(ʫhq@Nj|)Y @V (D @  kJ!R2m @@ (D[̗P`{-#zkoGA'?`\أSāGц @0 q+(KȲLI/E@!r)0 @J$Y"x @b pn W31>ӻOIǻ|s"P]9}O]OGpv;GIl  @ "YV @l (Df+fS\z 4ȥ8Nr$1j:hA5Kq՞){z'$sD`),pqF|y{>(E!@(Bda\J2%tL" n8 @R"0ȓм7cY픾D䢵  @@"3)BdAyuNhBdHWӟ?lӾ=^q_k"ͼbJgDppzxxYBbԻS:aK PȂd[@!21 (D @B (DJV @ SӓY%f"ywD{-hCM|ccƌ8/;Ѱ!@(BdhuL'Q (D  @m! @e"0Nȗ3J_ɾ=:⥿&{ц@{Xai<Қ @PN.̄\@!(#2J @PE-4.P{: K; K\Eș9+"v]/=)eZ @ %O  @ (D+Y3s @"+_fK%PxN=vݳY@% lr'oIĪ>*iJ(Bd @J'Y:{#Bď  @P,|-h@ͫuq>x6>d> U.YcqnjY]| @ "ph @|(D#VQ\z<˹ @@ ,_^0?źiѮNOdVvgqkm+WaC@1}=GΉl9ch{Ňn;CF!@h\@!qg  @@&"3 Ie @EP,:  @@>ӸӇDЧ}AS\gzRtue9rdD/pM>s{uD\GzG}k E!@h\@!qg  @@&"30 K @P,8 @@KO"W"@ (D, \" @" (DP @w[|G .\O@WưW#vܹR2߻k%@H@!2GɴȟBdrjEP_N(Bda\J%P?c^\_Wz>u3#}՟ (3bv4b-Q)w} Z=# @*E@!R2m iL4*( @ (D(Ϩ8czz{rRܠ8[~RF @M~"~v>q6#}6 @JPL['dB@!2i0 P,N  @ (D(Br1SgF}DdUzp5i C/;g'Q[A`izo7=|s蓿=4Os$@N@!,D)L"({ȲO @P,n  @@c3?81'#؝8yZ=z,:% Q52fI{vObA=+=qذamȞA; ;i󺈫MsUC\dofD(Bd A&4 BdL  @ (D PK _[lqVK'wwE{Jm Ȝq?:&4W"zO'w1s8/Dۆ Pi q%@L(Df" &AM"ۄQ' @@(DV@-#PB:a#IsKܾ{~E#rLȴ9cƌyr)?x|.:gz&GhkȶhD@!L@!2g  @ &X@fl+}+ȕɓc=MĪr4x+ h8QESնǁSN95%@R6qfN۾ݑ+|8_էRzp ml @  Mi(c2N @MPl @~T'>pB˶OO0M=zq򫮍hCEO=D7#~9ʕ/KOD}:- @L"4qMPF̂@9 (Dc̙hBdkK+^ nc]2TĚAEGq[*^`ԩa+EwD"٠BjWפxs]cgk @2P,ę6X@! 0<P,ܙ9 :*TUXmHS&Eßonp^K}a̫)~!cތ8d{/  @\"5sM%P,) BdY  @V(Dϭ Py5{v;60q8?pFsK8 uKq @@"3#@ (Df-#C  ϱ @JPL['JyqA_~Vf4U>.]/EzX: @@"3!@ (Df53E  ϱ @JPL['HfBmܷ"^}5-MhO>]oX}v~ @@Q"m0(7r˘ȯBd~ske @R"+%I _<ڵ".|l?-&z8w# @{jLi7E:|_Ѯyֹ_Ul| @@(D! @@E (DVd-@."sF @)Yihx:bD3Wjצd;?N'l  -vZdS#v1&EW*m @P,q  @ %78m  @*YTn @@V̏?魈Wmi]-v͘ @ #ƍ|"Nerۭt8_wEf$uA*V@!bSolοȃBdh  @"++VKKq{'#Zz$nKaMokhpK::/|n:lK(Bd LYPR6̅(D6E5 @@" s!@ ԼYcni=jEc6/?^}# @55ݏ~P =#uSy.) . @@@!$@(Df'fB@ 8Kd_@!292C(@w_OQn >;F5NCPoc5Wq)#ݣoāц PjRg PȒV(Dϭ @@&"3 @b }} 9"o(Z|~a2Eh P\ϭN:oF1cƤΈSW.# @J-Y J"Yv V๕ȄBd&` P,ғ\|[ kkh @ǟ#v'# @!$ @@"]k#@"]> ,d @!u] >  ՘Ƽ}DХCe @@ (DV`-(YYf)w@dE@!2+0hYWdE:2?## @Rƾ66EڽRVn @@"!@& (D6EhG@dE@!2+0hGM=3a ګLOBNhwwy64K$|Ř#vJ%Lr_~#vꝒKrhBd@YPB́rP,l+ȧBd>jU.k9&雥'!@_E3𐡋N @6PlF @@"K%o\E@!\2e @  ϱ @ 5oź>>K\g}}}"nmxȫ-7Kҋ"ָs̉8|pԞ9=g v" @J@!$CEP,*(c2N @ (D,C NMOB~fXy K>D`ڴi؅}l7|csA) P |K'@@9 (Dc̙, (Df1+D-Zr'0חk-8W׽MĮw.tMH@!2GɴTBd%d (Bd)M-ZN:}庮: rQ˛c-7_|kĵZf('o]Lw>X')J(Ŭ*X`QQ!N@&"3 @PU:-'0Ťk̏طW+jgb'OGÆ &G;}Ī 6  @ PD[&*̘.Y'@m/z$@fLcz3vM_^ޞ;p) p(>7`ԹF+j;vMO7XhȊN @ ρ @1t#@#- @@f>9-9& ʥ]czt*pGxX5~qSS"~<-s{Gۆ B" PȒMPl2  @(D.a($MU)6ꍘ#<6SLNOt֭4*\:^}PFEgXtJhCX\@!rqm(BdI J& (D6ʅ @R"0W4>;'nU=yҤ?JW_= V믋8Ј[k+q|1ųFIl  @ (D.IP,Q  ,e 9O4U@!R#@?Ζ_j}aqsΏ/:  @m;ω8|k^/]`KXL@!r1M(BdqF (D6Uu @@S"*:hSEGtģMMX'{G=l1/8vLzW7=ݏLfF @  ί @  M @ " @PlkQ @@sǥnUm\wmz'  @ F UN{.͎(Bd LP̼[5PonBd[,OO,= 9p4 @XϤlwٸcy/  @6PlsR @ (D}W@!22 V m%T>׿?;bMu}WkΏ>U؈Æ hCł^]]WꜯZ  bӹ)w@.  @Pw  lOW=3o8{' W앺-nN~;N @@fNOoF.ojғ^$ P e>'@@"K3 @@" s!@PV>̆e+0g|z͟kxȴx}=bg#.8rt–r)u"Vmȩק'駏W;{\:X  gaZ! < @  9L% @V (D P`]_ӯKO\MM\05.8=?)m @ ?9Xq_?"ubz7ӇEۆȯBd~ske(BdI J (Df65&F.Ytr @ ӓ^ [#~;DcW;/6 o/e,wG|6=}¡C# @PonEP,* P6 e*%@P,8 oq,q}L!CgKAȇ.n YvLnyWWء @@"S+"@@Q"m0Bd٤D  @@"Fm S/鉖o,pK[3T^v P[nqqc#n:4{br=|@z;Fzn#zFۆȯBd~ske(BdQ BP,T((Bd H|-#P70 @V ̟+cǦ'#nj~);~RVf @  ϑ @  LI @ %O  @ (Df65&Fl Ծ8/&v:GD<fsfE6mZo @m  @@% (DVRP,  @@V""@@MO8N~8=nZmܾ.bgMJqM7dfhȳ;#Vm!˵6 P ~'@@v ~ PJRW@!F#@@jĜ^>*});0R_>J[ "v[WۀU @ S J @ %@7$,.6ȟBdrjEhq'ۤwGvzbɩ=[)v^a:`KPK[u~RgkXy  @ (Df/'fD(Dݠ Bb  @ G 9J @-&^tw鉔6U.z月6 @-^xnߋXK}ݗq՟.%@2/ J#Yw @"+` gwǼ7fF4%}F{Ѷ!@m)K/~syN큇hCd_@!292CU@!#@(D.a P e]҃}gEUIOWm @ (Df?GfH(DU "BdYd  vu77wDZu' JO>$z;6=+Do8q5  }  PȂH@! uC2$d Z#0uI+ص)On;LK;MK.H_'e]Z3{  @@Q,H _&YYhBdKC (Df0)Dm.:$@EP, PX 17xL_]Wj_lGE\o [ PFO?te4yS%@*Yl'Z,]@!r6 @ (Df53E |vQz2ȳ6OOD.n[/!;xdH'pgĤo/1ȿBdslTBd%r  P |(+ȲJ @Ӯ(.^#yG͏$ȑ## @rzb}SN 3}hϟ\q48A^@!F @@I"KnP(Bd K! , @j {F<#Ƭݶ;-)цc[Īӿhwz- ц(BdD (D` Pb' @  M@r I1+*Fn7ߌG|`˯6 ȃ˸)Ϗv v쟇Z(+ȲJ @` 6rW@!22(ϡ @ ޅu]{|QJ+uN @rcW^r~{ ˥oqCؙ{ @P,1 P" 2+ԘB( @<Yߡ.qE&@-~.N_znRgҫ u8"v^*]`KM@!h"@@a" W(ȓY @ (D @Lj''!>z-ӕ6h??+nc^MOF}{BB"|# @P,  & m¨(s2O @@E (DVT-, ,O_n^aծ>[7-=]qg^ؾ—`eyF,ߓ1]uE @HdD@!2#0 ȕBdi1 P e@'@ ?;XT>Aӓ+Jk=/_wq:aK,S`7kjcׅawhBdkO6Pl#H @F"qX@!'@@"/-k4=]NO@.wޅm;>H,]7ވON/oqh#6 Z-jB @e (D.Ky P8̴mD|ߚ!3[ @/]ˆwalI ШBd\@ѣ>/Drjkb}~RD @ -s'%,. @ (DW @` Kdq-FYnA_W7Ǽٷot-  / q LD @@ (DQLP,<%hBdS\C&4'W @ξ-=8Dd5kK_Rp! P\!h"vfCqga4 P ?'@  L) @V (D @ (D @@s?H{;1#@ Lq[1qwL'l  @ |i]̭jMф @@"3"$@\"5sMS@!?M!@Pl  LgԍG|tBzG\#G6g @Đg{F_),nah߱sTBd%eZ (BdI J "YV @@(DVH- ԼZ3(DOA{+-·g?;%  }Î;,&L"v]K/~K'D\o|D P }k'@ 9(BdI Ne*Y3m+0ژ܈֏x7-qn}vNOJ.b  @L 1]?g3#ΟNCbϗz @"+8N@a" W E,fŜ @  Y͌y PvkӣoHz.ɪ&" @J[D/q!" @xbcƌ8v̫Ϟ=" @ @(D @b h @&x" H.!@MK|o}ZW8W_~;vtm- POapcEizd @P[<KP\c @@s"ZJɴu l=V_oy:M]?ϥ @y@|.C״ @JPĬ[3MPl @F"q*N@!RnTK!\}tqgF!ǾAKv SXWrĮtJ-,[@!rF @@(D! @Pl  @  yή &w' ;n>c\WN&ȟÏ>:c#Vm|J>:5JzF P ;3'@H E6 ȱBdki d&SJ{2}OǾ hrO#ziLĪi @ x։{׬"v];}E{ҥEv=;4b zE!@yPC(DU @"+:OP[v_ngx4hWWWGo߾ k @&0b/񏻧+' HOL @";fO@" K @Bd  Ie lډ^zͭ˾ @fq޽>oprHOD{EH @"\@!v$@L"$QIB8@e,Y3u&03w#nիWqq׊_:NOLFÆ PBѯ>p2?| ewJ6"@,(Df! @@A" ʫs @ 5]@!$@Xww(]sc _L`޼.Ɏ;.  @c߾"b](b=+lhwȀ!@L (Df*&C@[ (D @R (DR V m%2#PTzmO9w337!@F`5w;"\\޽ @ 9P,1  @b(DC P(B Ծ^ccr^`@ @@[ znxѶ5Z>sGh;!f:`K2(-Pl @ (Df77fFPl;(Y]SxN91YĻoH34  @0;{FdHo )FN;+64%@P`RL$@TBdf޺  o|磪c_Q5vRW[OI=ˆ6 @rx7c{衈c_y!☗7EڈwNHah @, (Df1+D@(D! @*Yn[@!2:~'ahqu .:% @r-0qXw&b#;zG-Y3{!YiH @ -@s L@!d&@3MODμ̏;=\:`KQύ~!;X! L(+ȲJlο @KP\3 LX@KqN ~;#|ap_aD @@ }ٱ>.bu*z  @  N P@! @`qE  @ K Yʆ D q6x%6n֌珞EΘ=y  @@ ,Ki3,׶ BdrB(DBU @@"]k#@@ (Do̜@N۝}bOLks?I @@&LHSdףvvgA%OxJ'7)'j9lAm*٦:#@-"RS_ @@% (DVR#Y>2S'P_X{߾}+  @ =7~[vNԾ05ø4I@!IL."@ P7& '~$@h\~\yjՈ3=|5 @ 5I@!/ P8Z&27w YH${q;Gq;u⮱#o) @ f͊>cǎ8ѩҨ/bv;{mK (Y@\] " @P,Q @e -ss-pnکD|ɗWLK|xÈm< @R`ƌfnv^zԱ~ּhz'/s"@ (DO_q/cц @ 3#|MbCg^m @@S".Y?'@r/[ 2!4pқ16NނhqWgZ @3";61c"]L/w hBdS\C@ @ (D.DL(Df" &Ajń^ <_ކ||Ty-l  @$u]tn}."@P[= (DM @(D&B (D`!0yI/?U}"y{BD @| L4)Xu@zgxVi5  m-? YID @@# 8EKP\*,.Pw{:ryZ%wh_K## k @K;ӟθXXՖ@!@6PlSNȷBdku @ (D6Wlοh@?zF\n{6zٳ|I @|u{<4XX5ўR P g$*V {ܧ  & m¨S k"?sVE &#6t=<+r3Lq^q~&K@!e*YTn @^@!Sh(BdAyuN<j>zo2}% W8pm @"++V[ | @@" [L@!2g  wқqғ; ^鮁Av_2}尮~^c9G Pס!]7D#H>^~c;}_tږ*D@!Bm)w@ ڻ8z\EbZt.ij9iy:nvBejQ`Rf y^ {}3{^w]Vg|J @]{mO}ӟ~2j_Da]BKz7۽jADk@"  @B@Y Em @ ?̵XC`^}_,Ǧ'$wQlN;Ƶv @X9-N Q>s}:N5( I5$DWy @"Qs jW@Yskd*49lc?2&ϟ'g^>Z @g}&.QLL%@"3WzJdȒQj @`95( I5$k(,jCl;:_Np>C[g @@1B!.ߨQL3!@ "3:qMAd1j!@( T!@@6ٜ7&P@ }- @C(.G5&6[Hmk3YDfa@%  @@s12+ 8 {|ӷo3  @t9~3Zc/o[ؿ)=9[vQO4KDC=TX@Y p{ @6mIDn HgqG @@ uѧ{^F[64<Ȩ 2# ls @*- ?# ,(@Npa:% _zN{YI @]!pyߊf/ܨS>2-ӻ'n:86(dS@y "s:MqAdO @` ҏ ,^-ꇇο_xhzuNIMrwO?=hI @ 0cVEHv=#V.Vm @6K~yϷ [uALOzGDXՂ) u90| @@@'@@"\F<\|szBrָd/MXp/@ @ ӟ6>9c meIlozIz"26, @ NM駀 @ ,Κ> @j@ZHgN|)DgNz豨 @$p_{Y꾾 @? " &, "4[J QAdGŜODV6yN_>+eMgq @$pݽpwm}{vO_   @,]%@贀 ӄ @@U"bt@'?xW\ }8{<  @dACw8vYtG],t[  @` ҏ "34YJ PrAdI5H "fhy;rDr @ 4NoODuĪuy^s蝒reD mtF@= @Ԋ Vf8ȫ 23oܙX~N|lhhTu @@)w~>Q\<;' m  N]v19 @ 'ȜLa PsȚRʒ@nT ӷbW'^G @@ #g? U_wpl9"ذ @ "+FuD) @ P x;W @JglH6Lu~ce{#=ꛋ  @t[8~|>n};ߣEE1[TT@YQ~7ϫ 23o @DBQ( H۲'Kny%4?5æ'$W$zJS!3 @< ~[zw>`{mZek/>"\[ PAdE4ȼ? @@1b\CDV\I6Ũg3+NJn+ce sn&% @ 1E Gy}yMvo?V,  @*U1 :WAd^g޸  @DT zBO(ԯ۲B]q3n+*}8ȑ>㏉1 @7o^hll:}u}r꣱=ƠݺbIDV. O @! ,%AdL.Wp[hW[:?2cƌjA @NOĨ[ @@v'k"  @@&D@%%n ?uI3 .i8$@ @`M(v҄}vI @@ev# "ߵF @]%]' ,Uhz%̈( @ ~NZ_^zI%DRC/ \3 @Z@YjQ @8Adqn"!/67^=C; @Νz!Q{v[8$=1wmh/% ,E:& 옗  @R@YJMm @xAdv$b_aazdaA!7:8#qk @ @<o5nt3[_=uذ @N ";M "ߵF @6#C@yq-[,(E+6-_uQJڟ:ǎՂ @ |箨g~ԧ+G7 PAdI57Adfx  @jI@YKi,dI@תh{ٳOSNH]gX..v|G?~|l @ P6k,uߎzǗDn`fפhxֈgP}>$@ ";+\ "s9M qAd'P ȼ 2Sh E7`wNwoXK@ @ W1:ƻys[_='n^vt1XK@Y.iiAdMO @d\@ }jF@Y3Si X}{ӷg⨿?*O&Ղ @ |tqf?qVzm6CwX4mҊ%T@YRNU@י7n @,"0KH@yec2yIȡS=xo+/9/O8-:G @ @3gΌN466F{;${YNjH@YCi(D  @J@Y*I @}99@Az?ǻw_{ϾNM{lN @*/0{Q_X^G|5$ 4 "o @J@U%@@DI P@WW>b47wIխ<  @ ?qtG/ZwEV~VZD,Ce'wC @eDڍș 2gn(LI9 ѹ]M @@ qѷg=éj'KȄ 2Ӥ"  @]/ zcw @ _|ͷ)dvtF- @,]4ǡEmv+G omg'. OK@.&' @)AdMM P*]k77v!._\`EqaQ>1; @njaˢIa%Gm+w0$@VV6& ی/ @ ";n % lKž3c|ҷboސZVzدʜo @Nv1Ԩ}20zBzwdɝ @=(9f5;F @Ȓj "s6۶k^Q_d9=(٭ᢞ @Դyb|gԱ_= A0#ӊ%) lμ "6K @`9DvD˹5/7bÛGoGxE}ԿI0 - @=ҷbo?}i:i; JoKS=螺XmKhS@&yDu捛 @w- @Rjj+͏AЗc [(  @dQ@YgR@ٕ.;_{ |+Z{'/:nܸVm @ @3g̈&CQ}Yԯ|('WswqdZ$@@ "k|2iԁ}=VοÒ @%X2q%G?Q9&}=F;V<۴Ò5. yq%@ P= =!@:9/zU@ab+c'i;/GGۢZ @ @T#ԁuDiMHvŭZ @>9 2gn @H@YE+T *E:*2{\Sjb#b}':n @X,}+Ty&;džQsxzr26, P< M6V @) y+G@Y=s'E4?":uԸNNumM=sqhmY) @ @,'7y(jae @RJɻoI%a @DV-  n^@teG۔ @ P+W}wkdԾH?ܔDK}J* ,) @ nI@Ew %l;t  @ @~kA:CR "K%"& @Q@YFl"@Ȋ;Ivaany+Ն76O}}  @ߊĨ;ֵ9Ov{mg'M@Ym3< "s` @tD! @>3N#f ([hI&7mq322$@ @@9_Eϋګ!}׼ f=9C9 2'm @W@^"' QAdF'ֺtڢs:%=A4 RsXo @19sĈԁQ_|x w } PZጌOM @\@n@@w۶h3Ϟw'otƗeM[^K @ԎY>._M:NwSӿmy3h#!@ \Lsv)\) @]# W PyAd@?7ғ#>Ԍ'{!Y'$kG @ @@VM?`b'n^ݗҭϤͳ6/l T g(gDl  @nH?E@qZt,=zWԷߧ~X{N[V) @dT'7(zsߊ:rhzq+bKeH!gPT dE@~ "k|  @ "J5& p ?O=[lPn1ڧO6ϳ @Yhii766y`RlO-LQ- AdVf) 6< @- l7 Ș 2cVk-LNsW?ֆh< @(Y}V?Z]]IJ@Um ]LN"@ @?B@ZnΓ?5ӾR+C3 @E`1}YgwFM(Z@Y4 ;# 쌞k  @ Э OYDfm2ߕB =Yx=7LedDI @1cFtoaK!@w-P@YBLM @ @ Ad(v PȪt^.h\9}7`=_w1?jԨ @ Pu?..Sߍ烽Z5jE scOlg_le.Dv9q>n <% @' 9#Dbo6Ϟu)ћn6Q:'"@ @W;:}K)GoOiѺK @ @.\F<,~W"wԭ.6- ,xN'D& @U# " \ ݝh~8}6xЎ=һ#\Kwz/s7r5 @r.SOczIvI beȸKY@YfN6N @*- ?DWyhQQ/:W]r~\uAXnET  @4*?IDAT _z-Kl7ճceGSM{-  P>Adsq'Ad.  @AdL. !Ad6濥'!~z8宛~wܖ\h~?>Q;fl'Zu) @Kc[ Q6][9DDvT @.Adu͇ȓ 2OjZG_ݗ'-LuɢT{H^r} @ wuE{bfb:I@5 "qV*'Adݒ @DV- AX2}Wg'^'J7oHP^M'MO;,  @ @ S悔jΏ ~VazT + ,VƮD؄ @# \\r y+h1IENDB`wagyu-0.4.3/docs/vatti.md000066400000000000000000000011211314062220700152430ustar00rootroot00000000000000## 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. wagyu-0.4.3/include/000077500000000000000000000000001314062220700142725ustar00rootroot00000000000000wagyu-0.4.3/include/mapbox/000077500000000000000000000000001314062220700155605ustar00rootroot00000000000000wagyu-0.4.3/include/mapbox/geometry/000077500000000000000000000000001314062220700174135ustar00rootroot00000000000000wagyu-0.4.3/include/mapbox/geometry/wagyu/000077500000000000000000000000001314062220700205475ustar00rootroot00000000000000wagyu-0.4.3/include/mapbox/geometry/wagyu/active_bound_list.hpp000066400000000000000000000346061314062220700247660ustar00rootroot00000000000000#pragma once #ifdef DEBUG #include #include #endif #include #include #include #include #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template using active_bound_list = std::vector>; template using active_bound_list_itr = typename active_bound_list::iterator; template using active_bound_list_rev_itr = typename active_bound_list::reverse_iterator; #ifdef DEBUG template inline std::basic_ostream& operator<<(std::basic_ostream& out, const active_bound_list& bnds) { std::size_t c = 0; for (auto const& bnd : bnds) { out << "Index: " << c++ << std::endl; out << *bnd; } return out; } template std::string output_edges(active_bound_list 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 bool is_even_odd_fill_type(bound 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 bool is_even_odd_alt_fill_type(bound 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 struct bound_insert_location { bound const& bound2; bound_insert_location(bound const& b) : bound2(b) { } bool operator()(bound_ptr 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(bound2.current_edge->top.x) < get_current_x(*(bound1.current_edge), bound2.current_edge->top.y); } else { return static_cast(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 active_bound_list_itr insert_bound_into_ABL(bound& left, bound& right, active_bound_list& active_bounds) { auto itr = std::find_if(active_bounds.begin(), active_bounds.end(), bound_insert_location(left)); return active_bounds.insert(itr, { &left, &right }); } template inline bool is_maxima(bound& bnd, T y) { return bnd.next_edge == bnd.edges.end() && bnd.current_edge->top.y == y; } template inline bool is_maxima(active_bound_list_itr& bnd, T y) { return is_maxima(*(*bnd), y); } template inline bool is_intermediate(bound& bnd, T y) { return bnd.next_edge != bnd.edges.end() && bnd.current_edge->top.y == y; } template inline bool is_intermediate(active_bound_list_itr& bnd, T y) { return is_intermediate(*(*bnd), y); } template inline bool current_edge_is_horizontal(active_bound_list_itr& bnd) { return is_horizontal(*((*bnd)->current_edge)); } template inline bool next_edge_is_horizontal(active_bound_list_itr& bnd) { return is_horizontal(*((*bnd)->next_edge)); } template void next_edge_in_bound(bound& bnd, scanbeam_list& scanbeam) { auto& current_edge = bnd.current_edge; ++current_edge; if (current_edge != bnd.edges.end()) { ++(bnd.next_edge); bnd.current_x = static_cast(current_edge->bot.x); if (!is_horizontal(*current_edge)) { scanbeam.push_back(current_edge->top.y); } } } template active_bound_list_itr get_maxima_pair(active_bound_list_itr const& bnd, active_bound_list& active_bounds) { bound_ptr maximum = (*bnd)->maximum_bound; return std::find(active_bounds.begin(), active_bounds.end(), maximum); } template void set_winding_count(active_bound_list_itr& bnd_itr, active_bound_list& active_bounds, fill_type subject_fill_type, fill_type clip_fill_type) { auto rev_bnd_itr = active_bound_list_rev_itr(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((*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 bool is_contributing(bound 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(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 void insert_lm_left_and_right_bound(bound& left_bound, bound& right_bound, active_bound_list& active_bounds, ring_manager& rings, scanbeam_list& 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(rb_abl_itr)) { scanbeam.push_back((*rb_abl_itr)->current_edge->top.y); } } template void insert_local_minima_into_ABL(T const bot_y, local_minimum_ptr_list const& minima_sorted, local_minimum_ptr_list_itr& current_lm, active_bound_list& active_bounds, ring_manager& rings, scanbeam_list& 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(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 void insert_horizontal_local_minima_into_ABL(T const top_y, local_minimum_ptr_list const& minima_sorted, local_minimum_ptr_list_itr& current_lm, active_bound_list& active_bounds, ring_manager& rings, scanbeam_list& 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(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; } } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/bound.hpp000066400000000000000000000061461314062220700223760ustar00rootroot00000000000000#pragma once #include #include #include #include #include #ifdef DEBUG #include #endif namespace mapbox { namespace geometry { namespace wagyu { template struct bound { edge_list edges; edge_list_itr current_edge; edge_list_itr next_edge; mapbox::geometry::point last_point; ring_ptr ring; bound_ptr 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&& 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(boundconst& b) = delete; bound& operator=(bound const&) = delete; }; #ifdef DEBUG template inline std::basic_ostream& operator<<(std::basic_ostream& out, const bound& 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(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 } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/bubble_sort.hpp000066400000000000000000000011541314062220700235630ustar00rootroot00000000000000#pragma once namespace mapbox { namespace geometry { namespace wagyu { template 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); } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/build_edges.hpp000066400000000000000000000134371314062220700235360ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template bool point_2_is_between_point_1_and_point_3(mapbox::geometry::point const& pt1, mapbox::geometry::point const& pt2, mapbox::geometry::point 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 bool build_edge_list(mapbox::geometry::linear_ring const& path_geometry, edge_list& 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 pt1 = *itr_rev; mapbox::geometry::point 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 pt3 = *itr; auto itr_last = itr_rev.base(); mapbox::geometry::point front_pt; mapbox::geometry::point 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(back_pt.x) == back_top.x && static_cast(back_pt.y) == back_top.y) { auto const& back_bot = edges.back().bot; pt1 = mapbox::geometry::point(static_cast(back_bot.x), static_cast(back_bot.y)); } else { pt1 = mapbox::geometry::point(static_cast(back_top.x), static_cast(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; } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/build_local_minima_list.hpp000066400000000000000000000013761314062220700261250ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template bool add_linear_ring(mapbox::geometry::linear_ring const& path_geometry, local_minimum_list& minima_list, polygon_type p_type) { edge_list new_edges; new_edges.reserve(path_geometry.size()); if (!build_edge_list(path_geometry, new_edges) || new_edges.empty()) { return false; } add_ring_to_local_minima_list(new_edges, minima_list, p_type); return true; } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/build_result.hpp000066400000000000000000000041601314062220700237560ustar00rootroot00000000000000#pragma once #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template void push_ring_to_polygon(mapbox::geometry::polygon& poly, ring_ptr r, bool reverse_output) { mapbox::geometry::linear_ring lr; lr.reserve(r->size() + 1); auto firstPt = r->points; auto ptIt = r->points; if (reverse_output) { do { lr.emplace_back(static_cast(ptIt->x), static_cast(ptIt->y)); ptIt = ptIt->next; } while (ptIt != firstPt); } else { do { lr.emplace_back(static_cast(ptIt->x), static_cast(ptIt->y)); ptIt = ptIt->prev; } while (ptIt != firstPt); } lr.emplace_back(firstPt->x, firstPt->y); // close the ring poly.push_back(lr); } template void build_result_polygons(mapbox::geometry::multi_polygon& solution, ring_vectorconst& 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 void build_result(mapbox::geometry::multi_polygon& solution, ring_managerconst& rings, bool reverse_output) { build_result_polygons(solution, rings.children, reverse_output); } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/config.hpp000066400000000000000000000023721314062220700225310ustar00rootroot00000000000000#pragma once #include #include #include #include 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 using maxima_list = std::list; } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/edge.hpp000066400000000000000000000064741314062220700221770ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #ifdef DEBUG #include #endif namespace mapbox { namespace geometry { namespace wagyu { template struct bound; template using bound_ptr = bound*; template struct edge { mapbox::geometry::point bot; mapbox::geometry::point top; double dx; edge(edge&& e) noexcept : bot(std::move(e.bot)), top(std::move(e.top)), dx(std::move(e.dx)) { } edge& operator=(edge&& e) noexcept { bot = std::move(e.bot); top = std::move(e.top); dx = std::move(e.dx); return *this; } template edge(mapbox::geometry::point const& current, mapbox::geometry::point const& next_pt) noexcept : bot(static_cast(current.x), static_cast(current.y)), top(static_cast(current.x), static_cast(current.y)), dx(0.0) { if (current.y >= next_pt.y) { top = mapbox::geometry::point(static_cast(next_pt.x), static_cast(next_pt.y)); } else { bot = mapbox::geometry::point(static_cast(next_pt.x), static_cast(next_pt.y)); } double dy = static_cast(top.y - bot.y); if (value_is_zero(dy)) { dx = std::numeric_limits::infinity(); } else { dx = static_cast(top.x - bot.x) / dy; } } }; template using edge_ptr = edge*; template using edge_list = std::vector>; template using edge_list_itr = typename edge_list::iterator; template bool slopes_equal(edge const& e1, edge 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 inline bool is_horizontal(edge const& e) { return std::isinf(e.dx); } template inline double get_current_x(edge const& edge, const T current_y) { if (current_y == edge.top.y) { return static_cast(edge.top.x); } else { return static_cast(edge.bot.x) + edge.dx * static_cast(current_y - edge.bot.y); } } #ifdef DEBUG template inline std::basic_ostream& operator<<(std::basic_ostream& out, const edge& 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 inline std::basic_ostream& operator<<(std::basic_ostream& out, edge_list 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 } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/intersect.hpp000066400000000000000000000035011314062220700232570ustar00rootroot00000000000000#pragma once #include #include #include #ifdef DEBUG #include #endif namespace mapbox { namespace geometry { namespace wagyu { template struct intersect_node { bound_ptr bound1; bound_ptr bound2; mapbox::geometry::point pt; intersect_node(intersect_node&& n) : bound1(std::move(n.bound1)), bound2(std::move(n.bound2)), pt(std::move(n.pt)) { } intersect_node& operator=(intersect_node&& n) { bound1 = std::move(n.bound1); bound2 = std::move(n.bound2); pt = std::move(n.pt); return *this; } intersect_node(bound_ptr const& bound1_, bound_ptr const& bound2_, mapbox::geometry::point const& pt_) : bound1(bound1_), bound2(bound2_), pt(pt_) { } }; template using intersect_list = std::vector>; #ifdef DEBUG template inline std::basic_ostream& operator<<(std::basic_ostream& out, const intersect_node& 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 inline std::basic_ostream& operator<<(std::basic_ostream& out, const intersect_list& ints) { std::size_t c = 0; for (auto const& i : ints) { out << "Intersection: " << c++ << std::endl; out << i; } return out; } #endif } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/intersect_util.hpp000066400000000000000000000307471314062220700243300ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template struct intersect_list_sorter { inline bool operator()(intersect_node const& node1, intersect_node 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 inline mapbox::geometry::point round_point(mapbox::geometry::point const& pt) { return mapbox::geometry::point(round_towards_max(pt.x), round_towards_max(pt.y)); } template inline void swap_rings(bound& b1, bound& b2) { ring_ptr ring = b1.ring; b1.ring = b2.ring; b2.ring = ring; } template inline void swap_sides(bound& b1, bound& b2) { edge_side side = b1.side; b1.side = b2.side; b2.side = side; } template bool get_edge_intersection(edge const& e1, edge const& e2, mapbox::geometry::point& pt) { T2 p0_x = static_cast(e1.bot.x); T2 p0_y = static_cast(e1.bot.y); T2 p1_x = static_cast(e1.top.x); T2 p1_y = static_cast(e1.top.y); T2 p2_x = static_cast(e2.bot.x); T2 p2_y = static_cast(e2.bot.y); T2 p3_x = static_cast(e2.top.x); T2 p3_y = static_cast(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 struct intersection_compare { bool operator()(bound_ptr const& b1, bound_ptr const& b2) { return !(b1->current_x > b2->current_x && !slopes_equal(*(b1->current_edge), *(b2->current_edge))); } }; template struct on_intersection_swap { intersect_list& intersects; on_intersection_swap(intersect_list& i) : intersects(i) { } void operator()(bound_ptr const& b1, bound_ptr const& b2) { mapbox::geometry::point pt; if (!get_edge_intersection(*(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 void build_intersect_list(active_bound_list& active_bounds, intersect_list& intersects) { bubble_sort(active_bounds.begin(), active_bounds.end(), intersection_compare(), on_intersection_swap(intersects)); } template void intersect_bounds(bound& b1, bound& b2, mapbox::geometry::point const& pt, clip_type cliptype, fill_type subject_fill_type, fill_type clip_fill_type, ring_manager& rings, active_bound_list& 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(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(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(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(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 bool bounds_adjacent(intersect_node const& inode, bound_ptr next) { return (next == inode.bound2) || (next == inode.bound1); } template struct find_first_bound { bound_ptr b1; bound_ptr b2; find_first_bound(intersect_node const& inode) : b1(inode.bound1), b2(inode.bound2) { } bool operator()(bound_ptr const& b) { return b == b1 || b == b2; } }; template void process_intersect_list(intersect_list& intersects, clip_type cliptype, fill_type subject_fill_type, fill_type clip_fill_type, ring_manager& rings, active_bound_list& 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(*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(*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 pt = round_point(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 void update_current_x(active_bound_list& 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 void process_intersections(T top_y, active_bound_list& active_bounds, clip_type cliptype, fill_type subject_fill_type, fill_type clip_fill_type, ring_manager& rings) { if (active_bounds.empty()) { return; } update_current_x(active_bounds, top_y); intersect_list 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 const& b1, bound_ptr const& b2) { return b1->pos < b2->pos; }); // Sort the intersection list std::stable_sort(intersects.begin(), intersects.end(), intersect_list_sorter()); process_intersect_list(intersects, cliptype, subject_fill_type, clip_fill_type, rings, active_bounds); } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/local_minimum.hpp000066400000000000000000000063471314062220700241170ustar00rootroot00000000000000#pragma once #ifdef DEBUG #include #include #endif #include #include namespace mapbox { namespace geometry { namespace wagyu { template struct local_minimum { bound left_bound; bound right_bound; T y; bool minimum_has_horizontal; local_minimum(bound&& left_bound_, bound&& 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 using local_minimum_list = std::deque>; template using local_minimum_itr = typename local_minimum_list::iterator; template using local_minimum_ptr = local_minimum*; template using local_minimum_ptr_list = std::vector>; template using local_minimum_ptr_list_itr = typename local_minimum_ptr_list::iterator; template struct local_minimum_sorter { inline bool operator()(local_minimum_ptr const& locMin1, local_minimum_ptr 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 inline std::basic_ostream& operator<<(std::basic_ostream& out, const local_minimum& 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 inline std::basic_ostream& operator<<(std::basic_ostream& out, const local_minimum_ptr_list& lms) { for (auto const& lm : lms) { out << *lm; } return out; } template std::string output_all_edges(local_minimum_ptr_list 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 } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/local_minimum_util.hpp000066400000000000000000000277071314062220700251570ustar00rootroot00000000000000#pragma once #include #include #include #ifdef DEBUG #include #endif namespace mapbox { namespace geometry { namespace wagyu { template inline void reverse_horizontal(edge& 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 void start_list_on_local_maximum(edge_list& 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 bound create_bound_towards_minimum(edge_list& edges) { if (edges.size() == 1) { if (is_horizontal(edges.front())) { reverse_horizontal(edges.front()); } bound 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 bnd; if (next_edge == edges.end()) { std::swap(edges, bnd.edges); } else { bnd.edges.reserve(static_cast(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 bound create_bound_towards_maximum(edge_list& edges) { if (edges.size() == 1) { bound 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 bnd; if (next_edge == edges.end()) { std::swap(bnd.edges, edges); } else { bnd.edges.reserve(static_cast(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 void fix_horizontals(bound& 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 void move_horizontals_on_left_to_right(bound& left_bound, bound& 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 void add_ring_to_local_minima_list(edge_list& edges, local_minimum_list& 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 first_minimum = nullptr; bound_ptr 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 void initialize_lm(local_minimum_ptr_list_itr& 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((*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((*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; } } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/point.hpp000066400000000000000000000053571314062220700224230ustar00rootroot00000000000000#pragma once #include #ifdef DEBUG #include #endif namespace mapbox { namespace geometry { namespace wagyu { template struct point; template using point_ptr = point*; template using const_point_ptr = point* const; template struct ring; template using ring_ptr = ring*; template using const_ring_ptr = ring* const; template struct point { using coordinate_type = T; ring_ptr ring; T x; T y; point_ptr next; point_ptr 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 ring_, mapbox::geometry::point const& pt) : ring(ring_), x(pt.x), y(pt.y), next(this), prev(this) { } point(ring_ptr ring_, mapbox::geometry::point const& pt, point_ptr 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 using point_vector = std::vector>; template using point_vector_itr = typename point_vector::iterator; template bool operator==(point const& lhs, point const& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } template bool operator==(mapbox::geometry::point const& lhs, point const& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } template bool operator==(point const& lhs, mapbox::geometry::point const& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } template bool operator!=(point const& lhs, point const& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } template bool operator!=(mapbox::geometry::point const& lhs, point const& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } template bool operator!=(point const& lhs, mapbox::geometry::point const& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } #ifdef DEBUG template inline std::basic_ostream& operator<<(std::basic_ostream& out, const point& p) { out << " point at: " << p.x << ", " << p.y; return out; } template inline std::basic_ostream& operator<<(std::basic_ostream& out, const mapbox::geometry::point& p) { out << " point at: " << p.x << ", " << p.y; return out; } #endif } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/process_horizontal.hpp000066400000000000000000000262631314062220700252200ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template active_bound_list_itr process_horizontal_left_to_right(T scanline_y, active_bound_list_itr& horz_bound, active_bound_list& active_bounds, ring_manager& rings, scanbeam_list& 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(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((*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((*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((*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(wround((*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(wround((*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 active_bound_list_itr process_horizontal_right_to_left(T scanline_y, active_bound_list_itr& horz_bound_fwd, active_bound_list& active_bounds, ring_manager& rings, scanbeam_list& 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(get_maxima_pair(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(hp_itr_fwd); auto bnd = active_bound_list_rev_itr(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((*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((*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((*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(wround((*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(wround((*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 active_bound_list_itr process_horizontal(T scanline_y, active_bound_list_itr& horz_bound, active_bound_list& active_bounds, ring_manager& rings, scanbeam_list& 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 void process_horizontals(T scanline_y, active_bound_list& active_bounds, ring_manager& rings, scanbeam_list& 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(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()); } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/process_maxima.hpp000066400000000000000000000116471314062220700243030ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template active_bound_list_itr do_maxima(active_bound_list_itr& bnd, active_bound_list_itr& bndMaxPair, clip_type cliptype, fill_type subject_fill_type, fill_type clip_fill_type, ring_manager& manager, active_bound_list& 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 void process_edges_at_top_of_scanbeam(T top_y, active_bound_list& active_bounds, scanbeam_list& scanbeam, local_minimum_ptr_list const& minima_sorted, local_minimum_ptr_list_itr& current_lm, ring_manager& 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(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(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); } } } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/quick_clip.hpp000066400000000000000000000105611314062220700234060ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { namespace quick_clip { template mapbox::geometry::point intersect(mapbox::geometry::point a, mapbox::geometry::point b, size_t edge, mapbox::geometry::box const& box) { switch (edge) { case 0: return mapbox::geometry::point( mapbox::geometry::wagyu::wround(static_cast(a.x) + static_cast(b.x - a.x) * static_cast(box.min.y - a.y) / static_cast(b.y - a.y)), box.min.y); case 1: return mapbox::geometry::point( box.max.x, mapbox::geometry::wagyu::wround(static_cast(a.y) + static_cast(b.y - a.y) * static_cast(box.max.x - a.x) / static_cast(b.x - a.x))); case 2: return mapbox::geometry::point( mapbox::geometry::wagyu::wround(static_cast(a.x) + static_cast(b.x - a.x) * static_cast(box.max.y - a.y) / static_cast(b.y - a.y)), box.max.y); default: // case 3 return mapbox::geometry::point( box.min.x, mapbox::geometry::wagyu::wround(static_cast(a.y) + static_cast(b.y - a.y) * static_cast(box.min.x - a.x) / static_cast(b.x - a.x))); } } template bool inside(mapbox::geometry::point p, size_t edge, mapbox::geometry::box 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 mapbox::geometry::linear_ring quick_lr_clip(mapbox::geometry::linear_ring const& ring, mapbox::geometry::box const& b) { mapbox::geometry::linear_ring out = ring; for (size_t edge = 0; edge < 4; edge++) { if (out.size() > 0) { mapbox::geometry::linear_ring in = out; mapbox::geometry::point S = in[in.size() - 1]; out.resize(0); for (size_t e = 0; e < in.size(); e++) { mapbox::geometry::point 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 mapbox::geometry::multi_polygon clip(mapbox::geometry::polygon const& poly, mapbox::geometry::box const& b, fill_type subject_fill_type) { mapbox::geometry::multi_polygon result; wagyu 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 mapbox::geometry::multi_polygon clip(mapbox::geometry::multi_polygon const& mp, mapbox::geometry::box const& b, fill_type subject_fill_type) { mapbox::geometry::multi_polygon result; wagyu 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; } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/ring.hpp000066400000000000000000000445631314062220700222330ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #include #include #include #include // // 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 double area_from_point(point_ptr op, std::size_t& size, mapbox::geometry::box& bbox) { point_ptr 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(op->prev->x + op->x) * static_cast(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 using ring_vector = std::vector>; template 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 bbox; // bounding box of the ring ring_ptr parent; ring_vector children; point_ptr points; point_ptr 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::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::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 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 using hot_pixel_vector = std::vector>; template using hot_pixel_itr = typename hot_pixel_vector::iterator; template using hot_pixel_rev_itr = typename hot_pixel_vector::reverse_iterator; template struct ring_manager { ring_vector children; point_vector all_points; hot_pixel_vector hot_pixels; hot_pixel_itr current_hp_itr; std::deque> points; std::deque> rings; std::vector> 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 void preallocate_point_memory(ring_manager& rings, std::size_t size) { rings.storage.reserve(size); rings.all_points.reserve(size); } template ring_ptr create_new_ring(ring_manager& manager) { manager.rings.emplace_back(); ring_ptr result = &manager.rings.back(); result->ring_index = manager.index++; return result; } template point_ptr create_new_point(ring_ptr r, mapbox::geometry::point const& pt, ring_manager& rings) { point_ptr 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 point_ptr create_new_point(ring_ptr r, mapbox::geometry::point const& pt, point_ptr before_this_point, ring_manager& rings) { point_ptr 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 void set_to_children(ring_ptr r, ring_vector& children) { for (auto& c : children) { if (c == nullptr) { c = r; return; } } children.push_back(r); } template void remove_from_children(ring_ptr r, ring_vector& children) { for (auto& c : children) { if (c == r) { c = nullptr; return; } } } template void assign_as_child(ring_ptr new_ring, ring_ptr parent, ring_manager& 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 void reassign_as_child(ring_ptr ring, ring_ptr parent, ring_manager& 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 void assign_as_sibling(ring_ptr new_ring, ring_ptr sibling, ring_manager& 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 void reassign_as_sibling(ring_ptr ring, ring_ptr sibling, ring_manager& 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 void ring1_replaces_ring2(ring_ptr ring1, ring_ptr ring2, ring_manager& 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 void remove_points(point_ptr pt) { if (pt != nullptr) { pt->prev->next = nullptr; while (pt != nullptr) { point_ptr tmp = pt; pt = pt->next; tmp->next = nullptr; tmp->prev = nullptr; tmp->ring = nullptr; } } } template void remove_ring_and_points(ring_ptr r, ring_manager& 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 pt = r->points; if (pt != nullptr) { pt->prev->next = nullptr; while (pt != nullptr) { point_ptr tmp = pt; pt = pt->next; tmp->next = nullptr; tmp->prev = nullptr; tmp->ring = nullptr; } } r->points = nullptr; r->reset_stats(); } template void remove_ring(ring_ptr r, ring_manager& 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 inline std::size_t ring_depth(ring_ptr r) { std::size_t depth = 0; if (!r) { return depth; } while (r->parent) { depth++; r = r->parent; } return depth; } template inline bool ring_is_hole(ring_ptr 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 void set_next(const_point_ptr& node, const const_point_ptr& next_node) { node->next = next_node; } template point_ptr get_next(const_point_ptr& node) { return node->next; } template point_ptr get_prev(const_point_ptr& node) { return node->prev; } template void set_prev(const_point_ptr& node, const const_point_ptr& prev_node) { node->prev = prev_node; } template void init(const_point_ptr& node) { set_next(node, node); set_prev(node, node); } template void link_before(point_ptr& node, point_ptr& new_node) { point_ptr 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 void link_after(point_ptr& node, point_ptr& new_node) { point_ptr 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 void transfer_point(point_ptr& p, point_ptr& b, point_ptr& e) { if (b != e) { point_ptr prev_p = get_prev(p); point_ptr prev_b = get_prev(b); point_ptr 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 void reverse_ring(point_ptr pp) { if (!pp) { return; } point_ptr pp1; point_ptr pp2; pp1 = pp; do { pp2 = pp1->next; pp1->next = pp1->prev; pp1->prev = pp2; pp1 = pp2; } while (pp1 != pp); } #ifdef DEBUG template inline std::basic_ostream& operator<<(std::basic_ostream& out, ring& 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 n = const_cast>(&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 std::string debug_ring_addresses(ring_ptr 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 std::string output_as_polygon(ring_ptr 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 inline std::basic_ostream& operator<<(std::basic_ostream& out, ring_vector& 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 inline std::basic_ostream& operator<<(std::basic_ostream& out, std::deque>& 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 inline std::basic_ostream& operator<<(std::basic_ostream& out, hot_pixel_vector& hp_vec) { out << "Hot Pixels: " << std::endl; for (auto& hp : hp_vec) { out << hp << std::endl; } return out; } #endif } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/ring_util.hpp000066400000000000000000000673061314062220700232700ustar00rootroot00000000000000#pragma once #ifdef DEBUG #include // Example debug print for backtrace - only works on IOS #include #include // // 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 #include #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template void set_hole_state(bound& bnd, active_bound_list const& active_bounds, ring_manager& rings) { auto bnd_itr = std::find(active_bounds.rbegin(), active_bounds.rend(), &bnd); ++bnd_itr; bound_ptr 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 void update_current_hp_itr(T scanline_y, ring_manager& rings) { while (rings.current_hp_itr->y > scanline_y) { ++rings.current_hp_itr; } } template struct hot_pixel_sorter { inline bool operator()(mapbox::geometry::point const& pt1, mapbox::geometry::point 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 T round_towards_min(double val) { // 0.5 rounds to 0 // 0.0 rounds to 0 // -0.5 rounds to -1 return static_cast(std::ceil(val - 0.5 + rounding_offset)); } template T round_towards_max(double val) { // 0.5 rounds to 1 // 0.0 rounds to 0 // -0.5 rounds to 0 return static_cast(std::floor(val + 0.5 + rounding_offset)); } template inline T get_edge_min_x(edge 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(current_y - edge.bot.y) - 0.5; double return_val = static_cast(edge.bot.x) + edge.dx * lower_range_y; T value = round_towards_min(return_val); return value; } } else { if (current_y == edge.bot.y) { return edge.bot.x; } else { double return_val = static_cast(edge.bot.x) + edge.dx * (static_cast(current_y - edge.bot.y) + 0.5 - rounding_offset_y); T value = round_towards_min(return_val); return value; } } } template inline T get_edge_max_x(edge 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(current_y - edge.bot.y) - 0.5; double return_val = static_cast(edge.bot.x) + edge.dx * lower_range_y; T value = round_towards_max(return_val); return value; } } else { if (current_y == edge.bot.y) { return edge.bot.x; } else { double return_val = static_cast(edge.bot.x) + edge.dx * (static_cast(current_y - edge.bot.y) + 0.5 - rounding_offset_y); T value = round_towards_max(return_val); return value; } } } template void hot_pixel_set_left_to_right(T y, T start_x, T end_x, bound& bnd, ring_manager& rings, hot_pixel_itr& itr, hot_pixel_itr& 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 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 new_point = create_new_point(bnd.ring, *itr, op, rings); if (to_front) { bnd.ring->points = new_point; } } } template void hot_pixel_set_right_to_left(T y, T start_x, T end_x, bound& bnd, ring_manager& rings, hot_pixel_rev_itr& itr, hot_pixel_rev_itr& 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 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 new_point = create_new_point(bnd.ring, *itr, op, rings); if (to_front) { bnd.ring->points = new_point; } } } template void sort_hot_pixels(ring_manager& rings) { std::sort(rings.hot_pixels.begin(), rings.hot_pixels.end(), hot_pixel_sorter()); auto last = std::unique(rings.hot_pixels.begin(), rings.hot_pixels.end()); rings.hot_pixels.erase(last, rings.hot_pixels.end()); } template void insert_hot_pixels_in_path(bound& bnd, mapbox::geometry::point const& end_pt, ring_manager& 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(itr); while (itr != rings.hot_pixels.end() && itr->y == y) { ++itr; } auto first_itr = hot_pixel_rev_itr(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 void add_to_hot_pixels(mapbox::geometry::point const& pt, ring_manager& rings) { rings.hot_pixels.push_back(pt); } template void add_first_point(bound& bnd, active_bound_list& active_bounds, mapbox::geometry::point const& pt, ring_manager& rings) { ring_ptr 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 void add_point_to_ring(bound& bnd, mapbox::geometry::point const& pt, ring_manager& 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 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 new_point = create_new_point(bnd.ring, pt, bnd.ring->points, rings); if (to_front) { bnd.ring->points = new_point; } } template void add_point(bound& bnd, active_bound_list& active_bounds, mapbox::geometry::point const& pt, ring_manager& rings) { if (bnd.ring == nullptr) { add_first_point(bnd, active_bounds, pt, rings); } else { add_point_to_ring(bnd, pt, rings); } } template void add_local_minimum_point(bound& b1, bound& b2, active_bound_list& active_bounds, mapbox::geometry::point const& pt, ring_manager& 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 inline double get_dx(point const& pt1, point const& pt2) { if (pt1.y == pt2.y) { return std::numeric_limits::infinity(); } else { return static_cast(pt2.x - pt1.x) / static_cast(pt2.y - pt1.y); } } template bool first_is_bottom_point(const_point_ptr btmPt1, const_point_ptr btmPt2) { point_ptr 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 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 point_ptr get_bottom_point(point_ptr pp) { point_ptr dups = nullptr; point_ptr 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 ring_ptr get_lower_most_ring(ring_ptr outRec1, ring_ptr 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 OutPt1 = outRec1->bottom_point; point_ptr 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 bool ring1_child_below_ring2(ring_ptr ring1, ring_ptr ring2) { do { ring1 = ring1->parent; if (ring1 == ring2) { return true; } } while (ring1); return false; } template void update_points_ring(ring_ptr ring) { point_ptr op = ring->points; do { op->ring = ring; op = op->prev; } while (op != ring->points); } template void append_ring(bound& b1, bound& b2, active_bound_list& active_bounds, ring_manager& manager) { // get the start and ends of both output polygons ... ring_ptr outRec1 = b1.ring; ring_ptr outRec2 = b2.ring; ring_ptr keep_ring; bound_ptr keep_bound; ring_ptr remove_ring; bound_ptr 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 p1_lft = keep_ring->points; point_ptr p1_rt = p1_lft->prev; point_ptr p2_lft = remove_ring->points; point_ptr 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 void add_local_maximum_point(bound& b1, bound& b2, mapbox::geometry::point const& pt, ring_manager& rings, active_bound_list& 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 point_in_polygon_result point_in_polygon(point const& pt, point_ptr op) { // returns 0 if false, +1 if true, -1 if pt ON polygon boundary point_in_polygon_result result = point_outside_polygon; point_ptr 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(op->x - pt.x) * static_cast(op->next->y - pt.y) - static_cast(op->next->x - pt.x) * static_cast(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(op->x - pt.x) * static_cast(op->next->y - pt.y) - static_cast(op->next->x - pt.x) * static_cast(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 point_in_polygon_result point_in_polygon(mapbox::geometry::point const& pt, point_ptr op) { // returns 0 if false, +1 if true, -1 if pt ON polygon boundary point_in_polygon_result result = point_outside_polygon; point_ptr startOp = op; do { double op_x = static_cast(op->x); double op_y = static_cast(op->y); double op_next_x = static_cast(op->next->x); double op_next_y = static_cast(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 bool is_convex(point_ptr edge) { point_ptr prev = edge->prev; point_ptr 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 mapbox::geometry::point centroid_of_points(point_ptr edge) { point_ptr prev = edge->prev; point_ptr next = edge->next; return { static_cast(prev->x + edge->x + next->x) / 3.0, static_cast(prev->y + edge->y + next->y) / 3.0 }; } template point_in_polygon_result inside_or_outside_special(point_ptr first_pt, point_ptr 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 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 bool box2_contains_box1(mapbox::geometry::box const& box1, mapbox::geometry::box 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 bool poly2_contains_poly1(ring_ptr ring1, ring_ptr ring2) { if (!box2_contains_box1(ring1->bbox, ring2->bbox)) { return false; } if (std::fabs(ring2->area()) < std::fabs(ring1->area())) { return false; } point_ptr outpt1 = ring1->points->next; point_ptr outpt2 = ring2->points->next; point_ptr 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; } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/scanbeam.hpp000066400000000000000000000014661314062220700230400ustar00rootroot00000000000000#pragma once #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template using scanbeam_list = std::vector; template bool pop_from_scanbeam(T& Y, scanbeam_list& 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 void setup_scanbeam(local_minimum_list& minima_list, scanbeam_list& scanbeam) { for (auto lm = minima_list.begin(); lm != minima_list.end(); ++lm) { scanbeam.push_back(lm->y); } } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/snap_rounding.hpp000066400000000000000000000172641314062220700241400ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template struct hp_intersection_swap { ring_manager& manager; hp_intersection_swap(ring_manager& m) : manager(m) { } void operator()(bound_ptr const& b1, bound_ptr const& b2) { mapbox::geometry::point pt; if (!get_edge_intersection(*(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(pt), manager); } }; template void process_hot_pixel_intersections(T top_y, active_bound_list& active_bounds, ring_manager& manager) { if (active_bounds.empty()) { return; } update_current_x(active_bounds, top_y); bubble_sort(active_bounds.begin(), active_bounds.end(), intersection_compare(), hp_intersection_swap(manager)); } template bool horizontals_at_top_scanbeam(T top_y, active_bound_list_itr& bnd_curr, active_bound_list& active_bounds, ring_manager& manager) { bool shifted = false; auto& current_edge = (*bnd_curr)->current_edge; (*bnd_curr)->current_x = static_cast(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 pt(wround((*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 pt(wround((*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 void process_hot_pixel_edges_at_top_of_scanbeam(T top_y, scanbeam_list& scanbeam, active_bound_list& active_bounds, ring_manager& manager) { for (auto bnd = active_bounds.begin(); bnd != active_bounds.end();) { if (*bnd == nullptr) { ++bnd; continue; } bound& 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 void insert_local_minima_into_ABL_hot_pixel(T top_y, local_minimum_ptr_list& minima_sorted, local_minimum_ptr_list_itr& lm, active_bound_list& active_bounds, ring_manager& manager, scanbeam_list& 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(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(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(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(rb_abl_itr)) { scanbeam.push_back((*rb_abl_itr)->current_edge->top.y); } ++lm; } } template void build_hot_pixels(local_minimum_list& minima_list, ring_manager& manager) { active_bound_list active_bounds; scanbeam_list scanbeam; T scanline_y = std::numeric_limits::max(); local_minimum_ptr_list 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()); local_minimum_ptr_list_itr 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); } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/topology_correction.hpp000066400000000000000000001333511314062220700253710ustar00rootroot00000000000000#pragma once #define _USE_MATH_DEFINES #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #include #include #endif namespace mapbox { namespace geometry { namespace wagyu { template struct point_ptr_pair { point_ptr op1; point_ptr op2; constexpr point_ptr_pair(point_ptr o1, point_ptr o2) : op1(o1), op2(o2) { } point_ptr_pair(point_ptr_pair const& p) = default; point_ptr_pair(point_ptr_pair&& p) : op1(std::move(p.op1)), op2(std::move(p.op2)) { } point_ptr_pair& operator=(point_ptr_pair&& p) { op1 = std::move(p.op1); op2 = std::move(p.op2); return *this; } }; #ifdef DEBUG template inline std::basic_ostream& operator<<(std::basic_ostream& out, const std::unordered_multimap, point_ptr_pair>& 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 bool find_intersect_loop(std::unordered_multimap, point_ptr_pair>& dupe_ring, std::list, point_ptr_pair>>& iList, ring_ptr ring_parent, ring_ptr ring_origin, ring_ptr ring_search, std::set>& visited, point_ptr orig_pt, point_ptr prev_pt, ring_manager& rings) { { auto range = dupe_ring.equal_range(ring_search); // Check for direct connection for (auto& it = range.first; it != range.second;) { ring_ptr it_ring1 = it->second.op1->ring; ring_ptr 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 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 struct point_ptr_cmp { inline bool operator()(point_ptr op1, point_ptr 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 void correct_orientations(ring_manager& 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 point_vector sort_ring_points(ring_ptr r) { point_vector sorted_points; point_ptr point_itr = r->points; point_ptr 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 const& pt1, point_ptr const& pt2) { if (pt1->y != pt2->y) { return (pt1->y > pt2->y); } return (pt1->x < pt2->x); }); return sorted_points; } template ring_ptr correct_self_intersection(point_ptr pt1, point_ptr pt2, ring_manager& manager) { if (pt1->ring != pt2->ring) { return static_cast>(nullptr); } ring_ptr ring = pt1->ring; // split the polygon into two ... point_ptr pt3 = pt1->prev; point_ptr pt4 = pt2->prev; pt1->prev = pt4; pt4->next = pt1; pt2->prev = pt3; pt3->next = pt2; ring_ptr new_ring = create_new_ring(manager); std::size_t size_1 = 0; std::size_t size_2 = 0; mapbox::geometry::box box1({ 0, 0 }, { 0, 0 }); mapbox::geometry::box 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 void correct_repeated_points(ring_manager& manager, ring_vector& new_rings, point_vector_itr const& begin, point_vector_itr 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 new_ring = correct_self_intersection(*itr1, *itr2, manager); if (new_ring != nullptr) { new_rings.push_back(new_ring); } } } } template void find_and_correct_repeated_points(ring_ptr r, ring_manager& manager, ring_vector& 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(count) + 1)); correct_repeated_points(manager, new_rings, first, prev_itr); count = 0; } } template void reassign_children_if_necessary(ring_ptr new_ring, ring_ptr sibling_ring, ring_manager& manager, ring_vector& 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 bool find_parent_in_tree(ring_ptr r, ring_ptr possible_parent, ring_manager& 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 void assign_new_ring_parents(ring_manager& manager, ring_ptr original_ring, ring_vector& 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 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 const& r1, ring_ptr 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 bool correct_ring_self_intersections(ring_manager& manager, ring_ptr r, bool correct_tree) { if (r->corrected || !r->points) { return false; } ring_vector 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 void process_single_intersection( std::unordered_multimap, point_ptr_pair>& connection_map, point_ptr op_j, point_ptr op_k, ring_manager& manager) { ring_ptr ring_j = op_j->ring; ring_ptr 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 ring_origin; ring_ptr ring_search; ring_ptr ring_parent; point_ptr op_origin_1; point_ptr 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, point_ptr_pair>> 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 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> 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 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 intPt_origin = { op_origin_1, op_origin_2 }; point_ptr_pair 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 it_ring2 = it->second.op2->ring; if (it_ring2 == ring_search) { missing = false; } } if (missing) { point_ptr_pair 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 ring_itr = iRing.first; if (!ring_itr->is_hole()) { // Make the hole the origin! point_ptr op1 = op_origin_1; op_origin_1 = iRing.second.op1; iRing.second.op1 = op1; point_ptr 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 op_origin_1_next = op_origin_1->next; point_ptr 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 op_search_1 = iRing.second.op1; point_ptr op_search_2 = iRing.second.op2; point_ptr op_search_1_next = op_search_1->next; point_ptr 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 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 box1({ 0, 0 }, { 0, 0 }); mapbox::geometry::box 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 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, point_ptr_pair>> 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 it_ring = it->second.op1->ring; ring_ptr 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 it_ring = it->second.op1->ring; ring_ptr 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 void correct_chained_repeats( ring_manager& manager, std::unordered_multimap, point_ptr_pair>& connection_map, point_vector_itr const& begin, point_vector_itr 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 void correct_chained_rings(ring_manager& 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, point_ptr_pair> 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(count) + 1)); correct_chained_repeats(manager, connection_map, first, prev_itr); count = 0; } } template ring_vector sort_rings_largest_to_smallest(ring_manager& manager) { ring_vector 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 const& r1, ring_ptr const& r2) { if (!r1->points || !r2->points) { return r1->points != nullptr; } return std::fabs(r1->area()) > std::fabs(r2->area()); }); return sorted_rings; } template ring_vector sort_rings_smallest_to_largest(ring_manager& manager) { ring_vector 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 const& r1, ring_ptr const& r2) { if (!r1->points || !r2->points) { return r1->points != nullptr; } return std::fabs(r1->area()) < std::fabs(r2->area()); }); return sorted_rings; } template 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 start_1; point_ptr end_1; point_ptr start_2; point_ptr end_2; }; template struct collinear_result { point_ptr pt1; point_ptr pt2; }; template collinear_result fix_collinear_path(collinear_path& 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 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 prev = path.start_2->prev; point_ptr 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 prev = path.start_1->prev; point_ptr 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 prev_1 = path.start_1->prev; point_ptr prev_2 = path.start_2->prev; point_ptr 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 collinear_path find_start_and_end_of_collinear_edges(point_ptr pt_a, point_ptr pt_b) { // Search backward on A, forwards on B first bool same_ring = pt_a->ring == pt_b->ring; point_ptr back = pt_a; point_ptr 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 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 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 start_b = back->next; while (!same_ring && *start_b == *start_b->next && start_b != pt_b) { start_b = start_b->next; } point_ptr 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 bool has_collinear_edge(point_ptr pt_a, point_ptr 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 void process_collinear_edges_same_ring(point_ptr pt_a, point_ptr pt_b, ring_manager& manager) { ring_ptr 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 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 void process_collinear_edges_different_rings(point_ptr pt_a, point_ptr pt_b, ring_manager& manager) { ring_ptr ring_a = pt_a->ring; ring_ptr 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 merged_ring = ring_a_larger ? ring_a : ring_b; ring_ptr 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 bool remove_duplicate_points(point_ptr pt_a, point_ptr pt_b, ring_manager& 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 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 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 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 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 bool process_collinear_edges(point_ptr pt_a, point_ptr pt_b, ring_manager& 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 void correct_collinear_repeats(ring_manager& manager, point_vector_itr const& begin, point_vector_itr 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 void correct_collinear_edges(ring_manager& 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(count) + 1)); correct_collinear_repeats(manager, first, prev_itr); count = 0; } } template void correct_tree(ring_manager& 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::reverse_iterator; ring_vector 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>(nullptr), manager); } } } } template bool correct_self_intersections(ring_manager& 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 void correct_topology(ring_manager& 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()); // 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); } } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/util.hpp000066400000000000000000000047331314062220700222440ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template double area(mapbox::geometry::linear_ring 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(itr_prev->x + itr->x) * static_cast(itr_prev->y - itr->y); ++itr; itr_prev = poly.begin(); for (; itr != poly.end(); ++itr, ++itr_prev) { a += static_cast(itr_prev->x + itr->x) * static_cast(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::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 bool slopes_equal(mapbox::geometry::point const& pt1, mapbox::geometry::point const& pt2, mapbox::geometry::point const& pt3) { return (pt1.y - pt2.y) * (pt2.x - pt3.x) == (pt1.x - pt2.x) * (pt2.y - pt3.y); } template bool slopes_equal(mapbox::geometry::wagyu::point const& pt1, mapbox::geometry::wagyu::point const& pt2, mapbox::geometry::point const& pt3) { return (pt1.y - pt2.y) * (pt2.x - pt3.x) == (pt1.x - pt2.x) * (pt2.y - pt3.y); } template bool slopes_equal(mapbox::geometry::wagyu::point const& pt1, mapbox::geometry::wagyu::point const& pt2, mapbox::geometry::wagyu::point const& pt3) { return (pt1.y - pt2.y) * (pt2.x - pt3.x) == (pt1.x - pt2.x) * (pt2.y - pt3.y); } template bool slopes_equal(mapbox::geometry::point const& pt1, mapbox::geometry::point const& pt2, mapbox::geometry::point const& pt3, mapbox::geometry::point const& pt4) { return (pt1.y - pt2.y) * (pt3.x - pt4.x) == (pt1.x - pt2.x) * (pt3.y - pt4.y); } template inline T wround(double value) { return static_cast(::llround(value)); } template <> inline std::int64_t wround(double value) { return ::llround(value); } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/vatti.hpp000066400000000000000000000051151314062220700224110ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template void execute_vatti(local_minimum_list& minima_list, ring_manager& manager, clip_type cliptype, fill_type subject_fill_type, fill_type clip_fill_type) { active_bound_list active_bounds; scanbeam_list scanbeam; T scanline_y = std::numeric_limits::max(); local_minimum_ptr_list 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()); local_minimum_ptr_list_itr 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); } } } } } wagyu-0.4.3/include/mapbox/geometry/wagyu/wagyu.hpp000066400000000000000000000105721314062220700224210ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #define WAGYU_MAJOR_VERSION 0 #define WAGYU_MINOR_VERSION 4 #define WAGYU_PATCH_VERSION 3 #define WAGYU_VERSION \ (WAGYU_MAJOR_VERSION * 100000) + (WAGYU_MINOR_VERSION * 100) + (WAGYU_PATCH_VERSION) namespace mapbox { namespace geometry { namespace wagyu { template class wagyu { private: local_minimum_list minima_list; bool reverse_output; wagyu(wagyu const&) = delete; wagyu& operator=(wagyu const&) = delete; public: wagyu() : minima_list(), reverse_output(false) { } ~wagyu() { clear(); } template bool add_ring(mapbox::geometry::linear_ring const& pg, polygon_type p_type = polygon_type_subject) { return add_linear_ring(pg, minima_list, p_type); } template bool add_polygon(mapbox::geometry::polygon 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 get_bounds() { mapbox::geometry::point min = { 0, 0 }; mapbox::geometry::point max = { 0, 0 }; if (minima_list.empty()) { return mapbox::geometry::box(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(min, max); } template bool execute(clip_type cliptype, mapbox::geometry::multi_polygon& solution, fill_type subject_fill_type, fill_type clip_fill_type) { if (minima_list.empty()) { return false; } ring_manager 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; } }; } } } wagyu-0.4.3/mason.sh000077500000000000000000000134541314062220700143320ustar00rootroot00000000000000#!/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 [--header-only] " mason_error "[Mason] 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 wagyu-0.4.3/scripts/000077500000000000000000000000001314062220700143365ustar00rootroot00000000000000wagyu-0.4.3/scripts/coverage.sh000077500000000000000000000025741314062220700165000ustar00rootroot00000000000000#!/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" wagyu-0.4.3/tests/000077500000000000000000000000001314062220700140115ustar00rootroot00000000000000wagyu-0.4.3/tests/benchmark.cpp000066400000000000000000000315761314062220700164630ustar00rootroot00000000000000#include "util/boost_geometry_adapters.hpp" #include #include #include #include #include #include #include #include "clipper.hpp" #include "rapidjson/writer.h" #include #include #include 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 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 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 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 poly; for (SizeType i = 0; i < document.Size(); ++i) { mapbox::geometry::linear_ring 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>& 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& mp) { mapbox::geometry::polygon 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 time_wagyu; double time_angus; bool wagyu_valid = true; bool angus_valid = true; { wagyu clipper; clipper.add_polygon(poly_subject, polygon_type_subject); clipper.add_polygon(poly_clip, polygon_type_clip); mapbox::geometry::multi_polygon 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 clipper; clipper.add_polygon(poly_subject, polygon_type_subject); clipper.add_polygon(poly_clip, polygon_type_clip); mapbox::geometry::multi_polygon 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 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 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; } } wagyu-0.4.3/tests/catch.hpp000066400000000000000000012221431314062220700156110ustar00rootroot00000000000000/* * 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 #include #include // #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 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 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 T const& operator + ( T const& value, StreamEndStop ) { return value; } } #define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) #define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); #include 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 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 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 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 #include #include 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 getConfig() const = 0; }; struct IMutableContext : IContext { virtual ~IMutableContext(); virtual void setResultCapture( IResultCapture* resultCapture ) = 0; virtual void setRunner( IRunner* runner ) = 0; virtual void setConfig( Ptr 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 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 const& getAllTests() const = 0; virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const = 0; }; } namespace Catch { template class MethodTestCase : public SharedImpl { 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 AutoReg( void (C::*method)(), char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { registerTestCase( new MethodTestCase( 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( static_cast( lhs ) | static_cast( 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 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 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 ExpressionLhs operator->* ( T const& operand ); ExpressionLhs operator->* ( bool value ); template ResultBuilder& operator << ( T const& value ) { m_stream.oss << value; return *this; } template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); template 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 namespace Catch { namespace Internal { enum Operator { IsEqualTo, IsNotEqualTo, IsLessThan, IsGreaterThan, IsLessThanOrEqualTo, IsGreaterThanOrEqualTo }; template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; template inline T& opCast(T const& t) { return const_cast(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 class Evaluator{}; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs) { return opCast( lhs ) == opCast( rhs ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return opCast( lhs ) != opCast( rhs ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return opCast( lhs ) < opCast( rhs ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return opCast( lhs ) > opCast( rhs ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return opCast( lhs ) >= opCast( rhs ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return opCast( lhs ) <= opCast( rhs ); } }; template bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { return Evaluator::evaluate( lhs, rhs ); } // This level of indirection allows us to specialise for integer types // to avoid signed/ unsigned warnings // "base" overload template bool compare( T1 const& lhs, T2 const& rhs ) { return Evaluator::evaluate( lhs, rhs ); } // unsigned X to int template bool compare( unsigned int lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned long lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned char lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } // unsigned X to long template bool compare( unsigned int lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned long lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned char lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } // int to unsigned X template bool compare( int lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( int lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( int lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // long to unsigned X template bool compare( long lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // pointer to long (when comparing against NULL) template bool compare( long lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, long rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } // pointer to int (when comparing against NULL) template bool compare( int lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, int rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } #ifdef CATCH_CONFIG_CPP11_NULLPTR // pointer to nullptr_t (when comparing against nullptr) template bool compare( std::nullptr_t, T* rhs ) { return Evaluator::evaluate( NULL, rhs ); } template bool compare( T* lhs, std::nullptr_t ) { return Evaluator::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 struct NotABooleanExpression; template struct If : NotABooleanExpression {}; template<> struct If : TrueType {}; template<> struct If : FalseType {}; template struct SizedIf; template<> struct SizedIf : TrueType {}; template<> struct SizedIf : FalseType {}; #endif // CATCH_CONFIG_SFINAE } // end namespace Catch #include #include #include #include #include #ifdef __OBJC__ // #included from: catch_objc_arc.hpp #define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED #import #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 #endif #ifdef CATCH_CONFIG_CPP11_IS_ENUM #include #endif namespace Catch { // Why we're here. template 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 class IsStreamInsertableHelper { template struct TrueIfSizeable : TrueType {}; template static TrueIfSizeable dummy(T2*); static FalseType dummy(...); public: typedef SizedIf type; }; template struct IsStreamInsertable : IsStreamInsertableHelper::type {}; #else struct BorgType { template BorgType( T const& ); }; TrueType& testStreamable( std::ostream& ); FalseType testStreamable( FalseType ); FalseType operator<<( std::ostream const&, BorgType const& ); template 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::value > struct EnumStringMaker { static std::string convert( T const& ) { return unprintableString; } }; template struct EnumStringMaker { static std::string convert( T const& v ) { return ::Catch::toString( static_cast::type>(v) ); } }; #endif template struct StringMakerBase { #if defined(CATCH_CONFIG_CPP11_IS_ENUM) template static std::string convert( T const& v ) { return EnumStringMaker::convert( v ); } #else template static std::string convert( T const& ) { return unprintableString; } #endif }; template<> struct StringMakerBase { template 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 inline std::string rawMemoryToString( const T& object ) { return rawMemoryToString( &object, sizeof(object) ); } } // end namespace Detail template struct StringMaker : Detail::StringMakerBase::value> {}; template struct StringMaker { template static std::string convert( U* p ) { if( !p ) return INTERNAL_CATCH_STRINGIFY( NULL ); else return Detail::rawMemoryToString( p ); } }; template struct StringMaker { static std::string convert( R C::* p ) { if( !p ) return INTERNAL_CATCH_STRINGIFY( NULL ); else return Detail::rawMemoryToString( p ); } }; namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ); } //template //struct StringMaker > { // static std::string convert( std::vector const& v ) { // return Detail::rangeToString( v.begin(), v.end() ); // } //}; template std::string toString( std::vector 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::value) > struct ElementPrinter { static void print( const Tuple& tuple, std::ostream& os ) { os << ( N ? ", " : " " ) << Catch::toString(std::get(tuple)); ElementPrinter::print(tuple,os); } }; template< typename Tuple, std::size_t N > struct ElementPrinter { static void print( const Tuple&, std::ostream& ) {} }; } template struct StringMaker> { static std::string convert( const std::tuple& tuple ) { std::ostringstream os; os << '{'; TupleDetail::ElementPrinter>::print( tuple, os ); os << " }"; return os.str(); } }; #endif // CATCH_CONFIG_CPP11_TUPLE namespace Detail { template std::string makeString( T const& value ) { return StringMaker::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 std::string toString( T const& value ) { return StringMaker::convert( value ); } namespace Detail { template 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 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 ResultBuilder& operator == ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator != ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator < ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator > ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator <= ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator >= ( RhsT const& rhs ) { return captureExpression( rhs ); } ResultBuilder& operator == ( bool rhs ) { return captureExpression( rhs ); } ResultBuilder& operator != ( bool rhs ) { return captureExpression( 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 STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); private: template ResultBuilder& captureExpression( RhsT const& rhs ) { return m_rb .setResultType( Internal::compare( m_lhs, rhs ) ) .setLhs( Catch::toString( m_lhs ) ) .setRhs( Catch::toString( rhs ) ) .setOp( Internal::OperatorTraits::getName() ); } private: ResultBuilder& m_rb; T m_lhs; }; } // end namespace Catch namespace Catch { template inline ExpressionLhs ResultBuilder::operator->* ( T const& operand ) { return ExpressionLhs( *this, operand ); } inline ExpressionLhs ResultBuilder::operator->* ( bool value ) { return ExpressionLhs( *this, value ); } } // namespace Catch // #included from: catch_message.h #define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED #include 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 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 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 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 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 #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 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 #include #include #include namespace Catch { template struct IGenerator { virtual ~IGenerator() {} virtual T getValue( std::size_t index ) const = 0; virtual std::size_t size () const = 0; }; template class BetweenGenerator : public IGenerator { 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( index ); } virtual std::size_t size() const { return static_cast( 1+m_to-m_from ); } private: T m_from; T m_to; }; template class ValuesGenerator : public IGenerator { 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 m_values; }; template 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_iterator it = m_composed.begin(); typename std::vector*>::const_iterator itEnd = m_composed.end(); for( size_t index = 0; it != itEnd; ++it ) { const IGenerator* 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* generator ) { m_totalSize += generator->size(); m_composed.push_back( generator ); } CompositeGenerator& then( CompositeGenerator& other ) { move( other ); return *this; } CompositeGenerator& then( T value ) { ValuesGenerator* valuesGen = new ValuesGenerator(); 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*> m_composed; std::string m_fileInfo; size_t m_totalSize; }; namespace Generators { template CompositeGenerator between( T from, T to ) { CompositeGenerator generators; generators.add( new BetweenGenerator( from, to ) ); return generators; } template CompositeGenerator values( T val1, T val2 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3 ){ CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3, T val4 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); 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 // #included from: catch_interfaces_registry_hub.h #define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED #include 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 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 ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { getMutableRegistryHub().registerTranslator ( new ExceptionTranslator( 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 #include namespace Catch { namespace Detail { class Approx { public: explicit Approx ( double value ) : m_epsilon( std::numeric_limits::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 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 struct Matcher : SharedImpl { typedef ExpressionT ExpressionType; virtual ~Matcher() {} virtual Ptr clone() const = 0; virtual bool match( ExpressionT const& expr ) const = 0; virtual std::string toString() const = 0; }; template struct MatcherImpl : Matcher { virtual Ptr > clone() const { return Ptr >( new DerivedT( static_cast( *this ) ) ); } }; namespace Generic { template class AllOf : public MatcherImpl, ExpressionT> { public: AllOf() {} AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} AllOf& add( Matcher 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 > > m_matchers; }; template class AnyOf : public MatcherImpl, ExpressionT> { public: AnyOf() {} AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} AnyOf& add( Matcher 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 > > 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 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 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 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 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 inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, Impl::Matcher const& m2 ) { return Impl::Generic::AllOf().add( m1 ).add( m2 ); } template inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, Impl::Matcher const& m2, Impl::Matcher const& m3 ) { return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); } template inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, Impl::Matcher const& m2 ) { return Impl::Generic::AnyOf().add( m1 ).add( m2 ); } template inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, Impl::Matcher const& m2, Impl::Matcher const& m3 ) { return Impl::Generic::AnyOf().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 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 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 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 #include #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 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 tags; std::set 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 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 #include // 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. @protocol OcFixture @optional -(void) setUp; -(void) tearDown; @end namespace Catch { class OcMethod : public SharedImpl { 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 struct StringHolder : MatcherImpl{ 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( 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( 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( 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( 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 #include 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( 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 const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} virtual ~ExcludedPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } private: Ptr m_underlyingPattern; }; struct Filter { std::vector > 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 >::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::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) if( it->matches( testCase ) ) return true; return false; } private: std::vector 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(); 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(); addFilter(); } else if( c == '[' ) { if( subString() == "exclude:" ) m_exclusion = true; else addPattern(); startNewMode( Tag, ++m_pos ); } } else if( m_mode == QuotedName && c == '"' ) addPattern(); else if( m_mode == Tag && c == ']' ) addPattern(); } 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 void addPattern() { std::string token = subString(); if( startsWith( token, "exclude:" ) ) { m_exclusion = true; token = token.substr( 8 ); } if( !token.empty() ) { Ptr 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 #include #include 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 #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 #include #include #include #include #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 testsOrTags; }; class Config : public SharedImpl { 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 #include #include // 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::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 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 #include #include #include // 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 struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct IsBool { static const bool value = false; }; template<> struct IsBool { static const bool value = true; }; template 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 inline void convertInto( bool, T& ) { throw std::runtime_error( "Invalid conversion" ); } template 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 class BoundArgFunction { public: BoundArgFunction() : functionObj( NULL ) {} BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} BoundArgFunction& operator = ( BoundArgFunction const& other ) { IArgFunction* 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* functionObj; }; template struct NullBinder : IArgFunction{ virtual void set( C&, std::string const& ) const {} virtual void setFlag( C& ) const {} virtual bool takesArg() const { return true; } virtual IArgFunction* clone() const { return new NullBinder( *this ); } }; template struct BoundDataMember : IArgFunction{ 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::value; } virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } M C::* member; }; template struct BoundUnaryMethod : IArgFunction{ BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); (p.*member)( value ); } virtual void setFlag( C& p ) const { typename RemoveConstRef::type value; convertInto( true, value ); (p.*member)( value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } void (C::*member)( M ); }; template struct BoundNullaryMethod : IArgFunction{ 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* clone() const { return new BoundNullaryMethod( *this ); } void (C::*member)(); }; template struct BoundUnaryFunction : IArgFunction{ 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* clone() const { return new BoundUnaryFunction( *this ); } void (*function)( C& ); }; template struct BoundBinaryFunction : IArgFunction{ BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); function( obj, value ); } virtual void setFlag( C& obj ) const { typename RemoveConstRef::type value; convertInto( true, value ); function( obj, value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* 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& 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& 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 struct CommonArgProperties { CommonArgProperties() {} CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} Detail::BoundArgFunction 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 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 class CommandLine { struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { Arg() {} Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} using CommonArgProperties::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::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 ArgAutoPtr; #else typedef std::auto_ptr 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 void bind( M C::* field, std::string const& placeholder ) { m_arg->boundField = new Detail::BoundDataMember( field ); m_arg->placeholder = placeholder; } // Bind a boolean data member (no placeholder required) template void bind( bool C::* field ) { m_arg->boundField = new Detail::BoundDataMember( field ); } // Bind a method taking a single, non-boolean argument (requires a placeholder string) template void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); m_arg->placeholder = placeholder; } // Bind a method taking a single, boolean argument (no placeholder string required) template void bind( void (C::* unaryMethod)( bool ) ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); } // Bind a method that takes no arguments (will be called if opt is present) template void bind( void (C::* nullaryMethod)() ) { m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); } // Bind a free function taking a single argument - the object to operate on (no placeholder string required) template void bind( void (* unaryFunction)( C& ) ) { m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); } // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) template void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundBinaryFunction( 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() ), 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 void bindProcessName( M C::* field ) { m_boundProcessName = new Detail::BoundDataMember( field ); } template void bindProcessName( void (C::*_unaryMethod)( M ) ) { m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); } void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { typename std::vector::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::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 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 tokens; Parser parser; parser.parseIntoTokens( argc, argv, tokens ); return populate( tokens, config ); } std::vector populate( std::vector const& tokens, ConfigT& config ) const { validate(); std::vector unusedTokens = populateOptions( tokens, config ); unusedTokens = populateFixedArgs( unusedTokens, config ); unusedTokens = populateFloatingArgs( unusedTokens, config ); return unusedTokens; } std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; std::vector errors; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::vector::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::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 populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; int position = 1; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::map::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 populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { if( !m_floatingArg.get() ) return tokens; std::vector 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::const_iterator it = m_options.begin(), itEnd = m_options.end(); it != itEnd; ++it ) it->validate(); } private: Detail::BoundArgFunction m_boundProcessName; std::vector m_options; std::map 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 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( 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( 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( 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 makeCommandLineParser() { using namespace Clara; CommandLine 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 #include #include // 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::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 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 #include #include #include namespace Catch { struct ReporterConfig { explicit ReporterConfig( Ptr const& _fullConfig ) : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} std::ostream& stream() const { return *m_stream; } Ptr fullConfig() const { return m_fullConfig; } private: std::ostream* m_stream; Ptr m_fullConfig; }; struct ReporterPreferences { ReporterPreferences() : shouldRedirectStdOut( false ) {} bool shouldRedirectStdOut; }; template struct LazyStat : Option { LazyStat() : used( false ) {} LazyStat& operator=( T const& _value ) { Option::operator=( _value ); used = false; return *this; } void reset() { Option::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 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 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 FactoryMap; virtual ~IReporterRegistry(); virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; virtual FactoryMap const& getFactories() const = 0; }; } #include #include 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 matchedTestCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); for( std::vector::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 matchedTestCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); for( std::vector::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::const_iterator it = spellings.begin(), itEnd = spellings.end(); it != itEnd; ++it ) out += "[" + *it + "]"; return out; } std::set 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 tagCounts; std::vector matchedTestCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { for( std::set::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::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::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 list( Config const& config ) { Option 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 #include #include namespace Catch { namespace SectionTracking { class TrackedSection { typedef std::map 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 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( "", -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 #include 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 const& config, Ptr 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 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( 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::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 m_testCaseTracker; AssertionResult m_lastResult; Ptr m_config; Totals m_totals; Ptr m_reporter; std::vector m_messages; IRunner* m_prevRunner; IResultCapture* m_prevResultCapture; Ptr m_prevConfig; AssertionInfo m_lastAssertionInfo; std::vector 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 #include #include namespace Catch { class Runner { public: Runner( Ptr 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 testCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); int testsRunForGroup = 0; for( std::vector::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 skippedTestCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); for( std::vector::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 m_config; std::ofstream m_ofs; Ptr m_reporter; std::set 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::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 listed = list( config() ) ) return static_cast( *listed ); return static_cast( runner.runTests().assertions.failed ); } catch( std::exception& ex ) { Catch::cerr() << ex.what() << std::endl; return (std::numeric_limits::max)(); } } Clara::CommandLine const& cli() const { return m_cli; } std::vector 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 m_cli; std::vector m_unusedTokens; ConfigData m_configData; Ptr 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 #include #include #include #include namespace Catch { class TestRegistry : public ITestCaseRegistry { struct LexSort { bool operator() (TestCase i,TestCase j) const { return (i const& getAllTests() const { return m_functionsInOrder; } virtual std::vector const& getAllNonHiddenTests() const { return m_nonHiddenFunctions; } virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const { for( std::vector::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& 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 m_functions; std::vector m_functionsInOrder; std::vector m_nonHiddenFunctions; size_t m_unnamedCount; }; /////////////////////////////////////////////////////////////////////////// class FreeFunctionTestCase : public SharedImpl { 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 namespace Catch { class ReporterRegistry : public IReporterRegistry { public: virtual ~ReporterRegistry() { deleteAllValues( m_factories ); } virtual IStreamingReporter* create( std::string const& name, Ptr 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_iterator it ) const { if( it == m_translators.end() ) return "Unknown exception"; try { return (*it)->translate(); } catch(...) { return tryTranslators( it+1 ); } } private: std::vector 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 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 namespace Catch { class StreamBufBase : public std::streambuf { public: virtual ~StreamBufBase() CATCH_NOEXCEPT; }; } #include #include #include namespace Catch { template 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( c ) ) ); else sputc( static_cast( c ) ); } return 0; } int sync() { if( pbase() != pptr() ) { m_writer( std::string( pbase(), static_cast( 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 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 const& config ) { m_config = config; } friend IMutableContext& getCurrentMutableContext(); private: IGeneratorsForTest* findGeneratorsForCurrentTest() { std::string testName = getResultCapture()->getCurrentTestName(); std::map::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 m_config; IRunner* m_runner; IResultCapture* m_resultCapture; std::map 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, 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 #else #include #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 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 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( _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 #include #include 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::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::const_iterator it = m_generatorsInOrder.begin(); std::vector::const_iterator itEnd = m_generatorsInOrder.end(); for(; it != itEnd; ++it ) { if( (*it)->moveNext() ) return true; } return false; } private: std::map m_generatorsByName; std::vector 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 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 const& _tags, SourceLineInfo const& _lineInfo ) : name( _name ), className( _className ), description( _description ), tags( _tags ), lineInfo( _lineInfo ), properties( None ) { std::ostringstream oss; for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { oss << "[" << *it << "]"; std::string lcaseTag = toLower( *it ); properties = static_cast( 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( 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 { public: LegacyReporterAdapter( Ptr 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 m_legacyReporter; }; } namespace Catch { LegacyReporterAdapter::LegacyReporterAdapter( Ptr 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::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 #else #include #endif namespace Catch { namespace { #ifdef CATCH_PLATFORM_WINDOWS uint64_t getCurrentTicks() { static uint64_t hz=0, hzo=0; if (!hz) { QueryPerformanceFrequency( reinterpret_cast( &hz ) ); QueryPerformanceCounter( reinterpret_cast( &hzo ) ); } uint64_t t; QueryPerformanceCounter( reinterpret_cast( &t ) ); return ((t-hzo)*1000000)/hz; } #else uint64_t getCurrentTicks() { timeval t; gettimeofday(&t,NULL); return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); } #endif } void Timer::start() { m_ticks = getCurrentTicks(); } unsigned int Timer::getElapsedMicroseconds() const { return static_cast(getCurrentTicks() - m_ticks); } unsigned int Timer::getElapsedMilliseconds() const { return static_cast(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 #ifdef CATCH_PLATFORM_MAC #include #include #include #include #include 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( size ), inc = 1; if( Endianness::which() == Endianness::Little ) { i = end-1; end = inc = -1; } unsigned char const *bytes = static_cast(object); std::ostringstream os; os << "0x" << std::setfill('0') << std::hex; for( ; i != end; i += inc ) os << std::setw(2) << static_cast(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( 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( 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( 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( value ) ); } template 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( value ) ) : Detail::makeString( value ); } std::string toString( signed char value ) { return toString( static_cast( value ) ); } std::string toString( unsigned char value ) { return toString( static_cast( 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 namespace Catch { class TagAliasRegistry : public ITagAliasRegistry { public: virtual ~TagAliasRegistry(); virtual Option 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 m_registry; }; } // end namespace Catch #include #include namespace Catch { TagAliasRegistry::~TagAliasRegistry() {} Option TagAliasRegistry::find( std::string const& alias ) const { std::map::const_iterator it = m_registry.find( alias ); if( it != m_registry.end() ) return it->second; else return Option(); } std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { std::string expandedTestSpec = unexpandedTestSpec; for( std::map::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 namespace Catch { struct StreamingReporterBase : SharedImpl { 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 m_config; std::ostream& stream; LazyStat currentTestRunInfo; LazyStat currentGroupInfo; LazyStat currentTestCaseInfo; std::vector m_sectionStack; }; struct CumulativeReporterBase : SharedImpl { template struct Node : SharedImpl<> { explicit Node( T const& _value ) : value( _value ) {} virtual ~Node() {} typedef std::vector > 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 const& other ) const { return operator==( *other ); } SectionStats stats; typedef std::vector > ChildSections; typedef std::vector 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 const& node ) const { return node->stats.sectionInfo.lineInfo == m_other.lineInfo; } private: void operator=( BySectionInfo const& ); SectionInfo const& m_other; }; typedef Node TestCaseNode; typedef Node TestGroupNode; typedef Node 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 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 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 node = new TestGroupNode( testGroupStats ); node->children.swap( m_testCases ); m_testGroups.push_back( node ); } virtual void testRunEnded( TestRunStats const& testRunStats ) { Ptr 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 m_config; std::ostream& stream; std::vector m_assertions; std::vector > > m_sections; std::vector > m_testCases; std::vector > m_testGroups; std::vector > m_testRuns; Ptr m_rootSection; Ptr m_deepestSection; std::vector > m_sectionStack; }; template 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 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 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 catch_internal_RegistrarFor##reporterType( name ); } #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } // #included from: ../internal/catch_xmlwriter.hpp #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED #include #include #include 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 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 << "\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 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 << ""; 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 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 tags. if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { for( std::vector::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 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::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::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 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::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::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 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 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 const& cols, std::size_t row ) { for( std::vector::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::const_iterator itEnd = messages.end(); const std::size_t N = static_cast( 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 messages; std::vector::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 wagyu-0.4.3/tests/expected/000077500000000000000000000000001314062220700156125ustar00rootroot00000000000000wagyu-0.4.3/tests/expected/difference-clockwise-polygon-clockwise-hole.json000066400000000000000000000000711314062220700271510ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]wagyu-0.4.3/tests/expected/difference-clockwise-polygon-counter-clockwise-hole.json000066400000000000000000000000711314062220700306260ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]wagyu-0.4.3/tests/expected/difference-clockwise-polygon.json000066400000000000000000000000021314062220700242350ustar00rootroot00000000000000[]wagyu-0.4.3/tests/expected/difference-counter-clockwise-polygon-clockwise-hole.json000066400000000000000000000000711314062220700306260ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]wagyu-0.4.3/tests/expected/difference-counter-clockwise-polygon-counter-clockwise-hole.json000066400000000000000000000000711314062220700323030ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]wagyu-0.4.3/tests/expected/difference-counter-clockwise-polygon.json000066400000000000000000000000021314062220700257120ustar00rootroot00000000000000[]wagyu-0.4.3/tests/expected/difference-multi-polygon-with-duplicate-polygon.json000066400000000000000000000001051314062220700300160ustar00rootroot00000000000000[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]wagyu-0.4.3/tests/expected/difference-multi-polygon-with-shared-edge.json000066400000000000000000000001511314062220700265300ustar00rootroot00000000000000[[[[2500,1543],[2116,2500],[1285,2500],[-517,2129],[-2500,-482],[-2500,-2500],[2500,-2500],[2500,1543]]]]wagyu-0.4.3/tests/expected/difference-multi-polygon-with-spikes.json000066400000000000000000000001421314062220700256560ustar00rootroot00000000000000[[[[1462,2500],[-2500,2500],[-2500,-2500],[-2466,-2500],[-2443,-2448],[-2439,-2470],[1462,2500]]]]wagyu-0.4.3/tests/expected/difference-multipolygon-both-clockwise.json000066400000000000000000000001011314062220700262420ustar00rootroot00000000000000[[[[1747,-852],[1127,1031],[-1427,264],[153,-1611],[1747,-852]]]]wagyu-0.4.3/tests/expected/difference-multipolygon-both-counter-clockwise.json000066400000000000000000000001011314062220700277170ustar00rootroot00000000000000[[[[1747,-852],[1127,1031],[-1427,264],[153,-1611],[1747,-852]]]]wagyu-0.4.3/tests/expected/difference-multipolygon-overlap-different-orientations.json000066400000000000000000000001011314062220700314550ustar00rootroot00000000000000[[[[1747,-852],[1127,1031],[-1427,264],[153,-1611],[1747,-852]]]]difference-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json000066400000000000000000000000721314062220700357310ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]difference-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json000066400000000000000000000000721314062220700374060ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]wagyu-0.4.3/tests/expected/difference-nested-multi-polygon-outer-clockwise-inner-clockwise.json000066400000000000000000000000711314062220700331010ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json000066400000000000000000000000721314062220700374060ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json000066400000000000000000000000721314062220700410630ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]difference-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json000066400000000000000000000000711314062220700344770ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json000066400000000000000000000000721314062220700374060ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json000066400000000000000000000000721314062220700410630ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]difference-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json000066400000000000000000000000711314062220700344770ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json000066400000000000000000000000721314062220700410630ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]ed9f280ce02a9b4a9a8b457b6295b60a1329161b.paxheader00006660000000000000000000000220131406222070020414xustar00rootroot00000000000000144 path=wagyu-0.4.3/tests/expected/difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json ed9f280ce02a9b4a9a8b457b6295b60a1329161b.data000066400000000000000000000000721314062220700172570ustar00rootroot00000000000000[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]difference-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json000066400000000000000000000000711314062220700361540ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]wagyu-0.4.3/tests/expected/difference-overlapping-multi-polygon.json000066400000000000000000000001151314062220700257350ustar00rootroot00000000000000[[[[698,-113],[1281,2274],[-658,2500],[-1679,2500],[-2307,-247],[698,-113]]]]wagyu-0.4.3/tests/expected/difference-polygon-covered-with-hole.json000066400000000000000000000001051314062220700256030ustar00rootroot00000000000000[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]wagyu-0.4.3/tests/expected/difference-polygon-no-interior.json000066400000000000000000000001051314062220700245230ustar00rootroot00000000000000[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]wagyu-0.4.3/tests/expected/difference-polygon-two-intersecting-holes-and-self-intersection.json000066400000000000000000000001061314062220700330670ustar00rootroot00000000000000[[[[-1792,2388],[-1903,2500],[-2500,2500],[-2500,1282],[-1792,2388]]]]wagyu-0.4.3/tests/expected/difference-polygon-two-intersecting-holes.json000066400000000000000000000001641314062220700267000ustar00rootroot00000000000000[[[[222,-1510],[-810,-161],[858,390],[228,2500],[-1720,2500],[-2500,1282],[-2500,-1252],[-1211,-2262],[222,-1510]]]]wagyu-0.4.3/tests/expected/difference-polygon-with-double-nested-holes.json000066400000000000000000000003701314062220700270750ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/difference-polygon-with-extending-hole.json000066400000000000000000000001011314062220700261350ustar00rootroot00000000000000[[[[1747,-852],[1127,1031],[-1427,264],[153,-1611],[1747,-852]]]]wagyu-0.4.3/tests/expected/difference-polygon-with-exterior-hole.json000066400000000000000000000002031314062220700260140ustar00rootroot00000000000000[[[[-152,-2332],[-247,2500],[-2500,2500],[-2500,1535],[-1672,2163],[-1597,855],[-2500,-3],[-2500,-2500],[-50,-2500],[-152,-2332]]]]wagyu-0.4.3/tests/expected/difference-polygon-with-hole-shared-edge.json000066400000000000000000000001511314062220700263250ustar00rootroot00000000000000[[[[2500,1543],[2116,2500],[1285,2500],[-517,2129],[-2500,-482],[-2500,-2500],[2500,-2500],[2500,1543]]]]wagyu-0.4.3/tests/expected/difference-polygon-with-hole-with-shared-point.json000066400000000000000000000001341314062220700275240ustar00rootroot00000000000000[[[[2500,588],[2500,1252],[-1657,1185],[-2500,-146],[-2500,-1308],[1563,-1814],[2500,588]]]]wagyu-0.4.3/tests/expected/difference-polygon-with-spike.json000066400000000000000000000001401314062220700243410ustar00rootroot00000000000000[[[[2500,2497],[2500,2500],[-2500,2500],[-2500,-2484],[-2482,-2452],[-2475,-2478],[2500,2497]]]]wagyu-0.4.3/tests/expected/difference-polygon-with-two-holes-outside-exterior-ring.json000066400000000000000000000003701314062220700314220ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/difference-self-intersecting-ring-polygon.json000066400000000000000000000000711314062220700266420ustar00rootroot00000000000000[[[[-2453,2500],[-2500,2500],[-2500,2448],[-2453,2500]]]]wagyu-0.4.3/tests/expected/intersection-clockwise-polygon-clockwise-hole.json000066400000000000000000000003301314062220700275630ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/intersection-clockwise-polygon-counter-clockwise-hole.json000066400000000000000000000003301314062220700312400ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/intersection-clockwise-polygon.json000066400000000000000000000001051314062220700246550ustar00rootroot00000000000000[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]wagyu-0.4.3/tests/expected/intersection-counter-clockwise-polygon-clockwise-hole.json000066400000000000000000000003301314062220700312400ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/intersection-counter-clockwise-polygon-counter-clockwise-hole.json000066400000000000000000000003301314062220700327150ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/intersection-counter-clockwise-polygon.json000066400000000000000000000001051314062220700263320ustar00rootroot00000000000000[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]wagyu-0.4.3/tests/expected/intersection-multi-polygon-with-duplicate-polygon.json000066400000000000000000000000021314062220700304260ustar00rootroot00000000000000[]wagyu-0.4.3/tests/expected/intersection-multi-polygon-with-shared-edge.json000066400000000000000000000001031314062220700271410ustar00rootroot00000000000000[[[[-517,2129],[1285,2500],[-2500,2500],[-2500,-482],[-517,2129]]]]wagyu-0.4.3/tests/expected/intersection-multi-polygon-with-spikes.json000066400000000000000000000001051314062220700262710ustar00rootroot00000000000000[[[[1467,2500],[1462,2500],[-2439,-2470],[-2436,-2484],[1467,2500]]]]wagyu-0.4.3/tests/expected/intersection-multipolygon-both-clockwise.json000066400000000000000000000001671314062220700266720ustar00rootroot00000000000000[[[[153,-1611],[-1427,264],[1127,1031],[644,2500],[-2500,2500],[-2500,-2328],[-2258,-2500],[-1714,-2500],[153,-1611]]]]wagyu-0.4.3/tests/expected/intersection-multipolygon-both-counter-clockwise.json000066400000000000000000000001671314062220700303470ustar00rootroot00000000000000[[[[153,-1611],[-1427,264],[1127,1031],[644,2500],[-2500,2500],[-2500,-2328],[-2258,-2500],[-1714,-2500],[153,-1611]]]]wagyu-0.4.3/tests/expected/intersection-multipolygon-overlap-different-orientations.json000066400000000000000000000001671314062220700321050ustar00rootroot00000000000000[[[[153,-1611],[-1427,264],[1127,1031],[644,2500],[-2500,2500],[-2500,-2328],[-2258,-2500],[-1714,-2500],[153,-1611]]]]intersection-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json000066400000000000000000000004341314062220700363470ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-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]]]]intersection-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json000066400000000000000000000004341314062220700400240ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-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]]]]wagyu-0.4.3/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-clockwise.json000066400000000000000000000003301314062220700335130ustar00rootroot00000000000000[[[[-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]]]]intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json000066400000000000000000000004341314062220700400240ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-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]]]]ef2ddaa401851cea8451b56c2d598737caa2a221.paxheader00006660000000000000000000000212131406222070020541xustar00rootroot00000000000000138 path=wagyu-0.4.3/tests/expected/intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json ef2ddaa401851cea8451b56c2d598737caa2a221.data000066400000000000000000000004341314062220700174050ustar00rootroot00000000000000[[[[-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]]]]intersection-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json000066400000000000000000000003301314062220700351110ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-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]]]]intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json000066400000000000000000000004341314062220700400240ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-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]]]]ef2ddaa401851cea8451b56c2d598737caa2a221.paxheader00006660000000000000000000000212131406222070020541xustar00rootroot00000000000000138 path=wagyu-0.4.3/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json ef2ddaa401851cea8451b56c2d598737caa2a221.data000066400000000000000000000004341314062220700174050ustar00rootroot00000000000000[[[[-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]]]]intersection-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json000066400000000000000000000003301314062220700351110ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-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]]]]ef2ddaa401851cea8451b56c2d598737caa2a221.paxheader00006660000000000000000000000212131406222070020541xustar00rootroot00000000000000138 path=wagyu-0.4.3/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json ef2ddaa401851cea8451b56c2d598737caa2a221.data000066400000000000000000000004341314062220700174050ustar00rootroot00000000000000[[[[-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]]]]ef2ddaa401851cea8451b56c2d598737caa2a221.paxheader00006660000000000000000000000222131406222070020542xustar00rootroot00000000000000146 path=wagyu-0.4.3/tests/expected/intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json ef2ddaa401851cea8451b56c2d598737caa2a221.data000066400000000000000000000004341314062220700174050ustar00rootroot00000000000000[[[[-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]]]]intersection-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json000066400000000000000000000003301314062220700365660ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-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]]]]wagyu-0.4.3/tests/expected/intersection-overlapping-multi-polygon.json000066400000000000000000000001361314062220700263540ustar00rootroot00000000000000[[[[-2307,-247],[-1679,2500],[-2500,2500],[-2500,-2500],[114,-2500],[698,-113],[-2307,-247]]]]wagyu-0.4.3/tests/expected/intersection-polygon-covered-with-hole.json000066400000000000000000000000021314062220700262130ustar00rootroot00000000000000[]wagyu-0.4.3/tests/expected/intersection-polygon-no-interior.json000066400000000000000000000000021314062220700251330ustar00rootroot00000000000000[]wagyu-0.4.3/tests/expected/intersection-polygon-two-intersecting-holes-and-self-intersection.json000066400000000000000000000001431314062220700335040ustar00rootroot00000000000000[[[[-1211,-2262],[-2450,-1292],[-2500,-1338],[-2500,-2500],[979,-2500],[222,-1510],[-1211,-2262]]]]wagyu-0.4.3/tests/expected/intersection-polygon-two-intersecting-holes.json000066400000000000000000000000711314062220700273110ustar00rootroot00000000000000[[[[-1720,2500],[-2500,2500],[-2500,1282],[-1720,2500]]]]wagyu-0.4.3/tests/expected/intersection-polygon-with-double-nested-holes.json000066400000000000000000000000711314062220700275070ustar00rootroot00000000000000[[[[-1351,2500],[-2500,2500],[-2500,1983],[-1351,2500]]]]wagyu-0.4.3/tests/expected/intersection-polygon-with-extending-hole.json000066400000000000000000000001671314062220700265650ustar00rootroot00000000000000[[[[153,-1611],[-1427,264],[1127,1031],[644,2500],[-2500,2500],[-2500,-2328],[-2258,-2500],[-1714,-2500],[153,-1611]]]]wagyu-0.4.3/tests/expected/intersection-polygon-with-exterior-hole.json000066400000000000000000000001171314062220700264340ustar00rootroot00000000000000[[[[2500,2500],[-247,2500],[-152,-2332],[-50,-2500],[2500,-2500],[2500,2500]]]]wagyu-0.4.3/tests/expected/intersection-polygon-with-hole-shared-edge.json000066400000000000000000000001031314062220700267360ustar00rootroot00000000000000[[[[-517,2129],[1285,2500],[-2500,2500],[-2500,-482],[-517,2129]]]]wagyu-0.4.3/tests/expected/intersection-polygon-with-hole-with-shared-point.json000066400000000000000000000001211314062220700301340ustar00rootroot00000000000000[[[[-1657,1185],[2500,1252],[2500,2500],[-2500,2500],[-2500,-146],[-1657,1185]]]]wagyu-0.4.3/tests/expected/intersection-polygon-with-spike.json000066400000000000000000000001051314062220700247560ustar00rootroot00000000000000[[[[2500,2487],[2500,2497],[-2475,-2478],[-2471,-2493],[2500,2487]]]]wagyu-0.4.3/tests/expected/intersection-polygon-with-two-holes-outside-exterior-ring.json000066400000000000000000000000711314062220700320340ustar00rootroot00000000000000[[[[-1351,2500],[-2500,2500],[-2500,1983],[-1351,2500]]]]wagyu-0.4.3/tests/expected/intersection-self-intersecting-ring-polygon.json000066400000000000000000000002201314062220700272520ustar00rootroot00000000000000[[[[2500,1061],[-324,-1745],[-2104,-430],[-947,1164],[2500,1717],[2500,2500],[-2453,2500],[-2500,2448],[-2500,-2500],[2500,-2500],[2500,1061]]]]wagyu-0.4.3/tests/expected/union-clockwise-polygon-clockwise-hole.json000066400000000000000000000001541314062220700262110ustar00rootroot00000000000000[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]wagyu-0.4.3/tests/expected/union-clockwise-polygon-counter-clockwise-hole.json000066400000000000000000000001541314062220700276660ustar00rootroot00000000000000[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]wagyu-0.4.3/tests/expected/union-clockwise-polygon.json000066400000000000000000000001061314062220700233000ustar00rootroot00000000000000[[[[1790,-4475],[5174,4458],[-2137,5008],[-4826,-4992],[1790,-4475]]]]wagyu-0.4.3/tests/expected/union-counter-clockwise-polygon-clockwise-hole.json000066400000000000000000000001541314062220700276660ustar00rootroot00000000000000[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]wagyu-0.4.3/tests/expected/union-counter-clockwise-polygon-counter-clockwise-hole.json000066400000000000000000000001541314062220700313430ustar00rootroot00000000000000[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]wagyu-0.4.3/tests/expected/union-counter-clockwise-polygon.json000066400000000000000000000001061314062220700247550ustar00rootroot00000000000000[[[[1790,-4475],[5174,4458],[-2137,5008],[-4826,-4992],[1790,-4475]]]]wagyu-0.4.3/tests/expected/union-multi-polygon-with-duplicate-polygon.json000066400000000000000000000001051314062220700270540ustar00rootroot00000000000000[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]wagyu-0.4.3/tests/expected/union-multi-polygon-with-shared-edge.json000066400000000000000000000003411314062220700255670ustar00rootroot00000000000000[[[[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]]]]wagyu-0.4.3/tests/expected/union-multi-polygon-with-spikes.json000066400000000000000000000002251314062220700247160ustar00rootroot00000000000000[[[[-2462,-2500],[2500,-2500],[2500,2500],[1467,2500],[4324,6148],[1462,2500],[-2500,2500],[-2500,-2500],[-2466,-2500],[-2472,-2513],[-2462,-2500]]]]wagyu-0.4.3/tests/expected/union-multipolygon-both-clockwise.json000066400000000000000000000002711314062220700253100ustar00rootroot00000000000000[[[[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]]]]wagyu-0.4.3/tests/expected/union-multipolygon-both-counter-clockwise.json000066400000000000000000000002711314062220700267650ustar00rootroot00000000000000[[[[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]]]]wagyu-0.4.3/tests/expected/union-multipolygon-overlap-different-orientations.json000066400000000000000000000002711314062220700305230ustar00rootroot00000000000000[[[[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]]]]union-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json000066400000000000000000000001551314062220700347710ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]union-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json000066400000000000000000000001551314062220700364460ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]wagyu-0.4.3/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-clockwise.json000066400000000000000000000001541314062220700321410ustar00rootroot00000000000000[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json000066400000000000000000000001551314062220700364460ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json000066400000000000000000000001551314062220700401230ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]wagyu-0.4.3/tests/expected/union-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json000066400000000000000000000001541314062220700336160ustar00rootroot00000000000000[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json000066400000000000000000000001551314062220700364460ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json000066400000000000000000000001551314062220700401230ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]wagyu-0.4.3/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json000066400000000000000000000001541314062220700336160ustar00rootroot00000000000000[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json000066400000000000000000000001551314062220700401230ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]9e67857d364cc277bdcb0b5efc5c20fea0734bdf.paxheader00006660000000000000000000000213131406222070021011xustar00rootroot00000000000000139 path=wagyu-0.4.3/tests/expected/union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json 9e67857d364cc277bdcb0b5efc5c20fea0734bdf.data000066400000000000000000000001551314062220700176540ustar00rootroot00000000000000[[[[3430,-3627],[6814,5306],[-497,5856],[-1399,2500],[-2500,2500],[-2500,-1591],[-3186,-4144],[3430,-3627]]]]union-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json000066400000000000000000000001541314062220700352140ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[3214,-3766],[6597,5167],[-714,5717],[-1579,2500],[-2500,2500],[-2500,-923],[-3403,-4283],[3214,-3766]]]]wagyu-0.4.3/tests/expected/union-overlapping-multi-polygon.json000066400000000000000000000003701314062220700247760ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/union-polygon-covered-with-hole.json000066400000000000000000000001051314062220700246410ustar00rootroot00000000000000[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]wagyu-0.4.3/tests/expected/union-polygon-no-interior.json000066400000000000000000000000651314062220700235660ustar00rootroot00000000000000[[[[-5000,-5000],[0,-5000],[-5000,0],[-5000,-5000]]]]wagyu-0.4.3/tests/expected/union-polygon-two-intersecting-holes-and-self-intersection.json000066400000000000000000000006631314062220700321350ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/union-polygon-two-intersecting-holes.json000066400000000000000000000007611314062220700257410ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/union-polygon-with-double-nested-holes.json000066400000000000000000000007761314062220700261450ustar00rootroot00000000000000[[[[-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],[-2291,-2500]]]]wagyu-0.4.3/tests/expected/union-polygon-with-extending-hole.json000066400000000000000000000002711314062220700252030ustar00rootroot00000000000000[[[[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]]]]wagyu-0.4.3/tests/expected/union-polygon-with-exterior-hole.json000066400000000000000000000002331314062220700250550ustar00rootroot00000000000000[[[[7071,1704],[-265,3394],[-247,2500],[-2500,2500],[-2500,1535],[-2929,1209],[-2904,-387],[-2500,-3],[-2500,-2500],[-50,-2500],[2448,-6606],[7071,1704]]]]wagyu-0.4.3/tests/expected/union-polygon-with-hole-shared-edge.json000066400000000000000000000003411314062220700253640ustar00rootroot00000000000000[[[[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]]]]wagyu-0.4.3/tests/expected/union-polygon-with-hole-with-shared-point.json000066400000000000000000000005061314062220700265650ustar00rootroot00000000000000[[[[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]]]]wagyu-0.4.3/tests/expected/union-polygon-with-spike.json000066400000000000000000000002071314062220700234030ustar00rootroot00000000000000[[[[-2496,-2500],[2500,-2500],[2500,2487],[7477,7473],[2500,2497],[2500,2500],[-2500,2500],[-2500,-2484],[-2523,-2527],[-2496,-2500]]]]wagyu-0.4.3/tests/expected/union-polygon-with-two-holes-outside-exterior-ring.json000066400000000000000000000007761314062220700304720ustar00rootroot00000000000000[[[[-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],[-2291,-2500]]]]wagyu-0.4.3/tests/expected/union-self-intersecting-ring-polygon.json000066400000000000000000000003351314062220700257030ustar00rootroot00000000000000[[[[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]]]]wagyu-0.4.3/tests/expected/x_or-clockwise-polygon-clockwise-hole.json000066400000000000000000000000711314062220700260260ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]wagyu-0.4.3/tests/expected/x_or-clockwise-polygon-counter-clockwise-hole.json000066400000000000000000000000711314062220700275030ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]wagyu-0.4.3/tests/expected/x_or-clockwise-polygon.json000066400000000000000000000003201314062220700231150ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/x_or-counter-clockwise-polygon-clockwise-hole.json000066400000000000000000000000711314062220700275030ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]wagyu-0.4.3/tests/expected/x_or-counter-clockwise-polygon-counter-clockwise-hole.json000066400000000000000000000000711314062220700311600ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]wagyu-0.4.3/tests/expected/x_or-counter-clockwise-polygon.json000066400000000000000000000003201314062220700245720ustar00rootroot00000000000000[[[[-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]]]]wagyu-0.4.3/tests/expected/x_or-multi-polygon-with-duplicate-polygon.json000066400000000000000000000001051314062220700266730ustar00rootroot00000000000000[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]wagyu-0.4.3/tests/expected/x_or-multi-polygon-with-shared-edge.json000066400000000000000000000002331314062220700254060ustar00rootroot00000000000000[[[[2053,2658],[2116,2500],[2500,2500],[2500,1543],[4366,-3119],[4837,6264],[-5163,2658],[-4971,-3736],[-2500,-482],[-2500,2500],[1285,2500],[2053,2658]]]]wagyu-0.4.3/tests/expected/x_or-multi-polygon-with-spikes.json000066400000000000000000000001441314062220700245350ustar00rootroot00000000000000[[[[-2443,-2448],[-2439,-2470],[1462,2500],[-2500,2500],[-2500,-2500],[-2466,-2500],[-2443,-2448]]]]wagyu-0.4.3/tests/expected/x_or-multipolygon-both-clockwise.json000066400000000000000000000000751314062220700251310ustar00rootroot00000000000000[[[[-2040,-2655],[-1714,-2500],[-2258,-2500],[-2040,-2655]]]]wagyu-0.4.3/tests/expected/x_or-multipolygon-both-counter-clockwise.json000066400000000000000000000000751314062220700266060ustar00rootroot00000000000000[[[[-2040,-2655],[-1714,-2500],[-2258,-2500],[-2040,-2655]]]]wagyu-0.4.3/tests/expected/x_or-multipolygon-overlap-different-orientations.json000066400000000000000000000000751314062220700303440ustar00rootroot00000000000000[[[[-2040,-2655],[-1714,-2500],[-2258,-2500],[-2040,-2655]]]]x_or-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-clockwise.json000066400000000000000000000000721314062220700346060ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]x_or-nested-multi-polygon-outer-clockwise-inner-clockwise-hole-counter-clockwise.json000066400000000000000000000000721314062220700362630ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]wagyu-0.4.3/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-clockwise.json000066400000000000000000000000711314062220700317560ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-clockwise.json000066400000000000000000000000721314062220700362630ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise-hole-counter-clockwise.json000066400000000000000000000000721314062220700377400ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]wagyu-0.4.3/tests/expected/x_or-nested-multi-polygon-outer-clockwise-inner-counter-clockwise.json000066400000000000000000000000711314062220700334330ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-clockwise.json000066400000000000000000000000721314062220700362630ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise-hole-counter-clockwise.json000066400000000000000000000000721314062220700377400ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]wagyu-0.4.3/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-clockwise.json000066400000000000000000000000711314062220700334330ustar00rootroot00000000000000[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-clockwise.json000066400000000000000000000000721314062220700377400ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]ed9f280ce02a9b4a9a8b457b6295b60a1329161b.paxheader00006660000000000000000000000212131406222070020415xustar00rootroot00000000000000138 path=wagyu-0.4.3/tests/expected/x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise-hole-counter-clockwise.json ed9f280ce02a9b4a9a8b457b6295b60a1329161b.data000066400000000000000000000000721314062220700172570ustar00rootroot00000000000000[[[[-1399,2500],[-2500,2500],[-2500,-1591],[-1399,2500]]]]x_or-nested-multi-polygon-outer-counter-clockwise-inner-counter-clockwise.json000066400000000000000000000000711314062220700350310ustar00rootroot00000000000000wagyu-0.4.3/tests/expected[[[[-1579,2500],[-2500,2500],[-2500,-923],[-1579,2500]]]]wagyu-0.4.3/tests/expected/x_or-overlapping-multi-polygon.json000066400000000000000000000001421314062220700246120ustar00rootroot00000000000000[[[[5366,94],[5402,3666],[-1419,3635],[-1652,2616],[-658,2500],[2500,2500],[2500,-33],[5366,94]]]]wagyu-0.4.3/tests/expected/x_or-polygon-covered-with-hole.json000066400000000000000000000001051314062220700244600ustar00rootroot00000000000000[[[[2500,2500],[-2500,2500],[-2500,-2500],[2500,-2500],[2500,2500]]]]wagyu-0.4.3/tests/expected/x_or-polygon-no-interior.json000066400000000000000000000000651314062220700234050ustar00rootroot00000000000000[[[[-5000,-5000],[0,-5000],[-5000,0],[-5000,-5000]]]]wagyu-0.4.3/tests/expected/x_or-polygon-two-intersecting-holes-and-self-intersection.json000066400000000000000000000000571314062220700317510ustar00rootroot00000000000000[[[[858,390],[345,221],[1154,-600],[858,390]]]]wagyu-0.4.3/tests/expected/x_or-polygon-two-intersecting-holes.json000066400000000000000000000013411314062220700255530ustar00rootroot00000000000000[[[[-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],[4765,-4262],[4591,5190]],[[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]]]]wagyu-0.4.3/tests/expected/x_or-polygon-with-double-nested-holes.json000066400000000000000000000013771314062220700257620ustar00rootroot00000000000000[[[[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,-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]]]]wagyu-0.4.3/tests/expected/x_or-polygon-with-extending-hole.json000066400000000000000000000000751314062220700250240ustar00rootroot00000000000000[[[[-2040,-2655],[-1714,-2500],[-2258,-2500],[-2040,-2655]]]]wagyu-0.4.3/tests/expected/x_or-polygon-with-exterior-hole.json000066400000000000000000000002031314062220700246710ustar00rootroot00000000000000[[[[-152,-2332],[-247,2500],[-2500,2500],[-2500,1535],[-1672,2163],[-1597,855],[-2500,-3],[-2500,-2500],[-50,-2500],[-152,-2332]]]]wagyu-0.4.3/tests/expected/x_or-polygon-with-hole-shared-edge.json000066400000000000000000000002331314062220700252030ustar00rootroot00000000000000[[[[2053,2658],[2116,2500],[2500,2500],[2500,1543],[4366,-3119],[4837,6264],[-5163,2658],[-4971,-3736],[-2500,-482],[-2500,2500],[1285,2500],[2053,2658]]]]wagyu-0.4.3/tests/expected/x_or-polygon-with-hole-with-shared-point.json000066400000000000000000000000751314062220700264050ustar00rootroot00000000000000[[[[-2500,-1308],[-3181,-1223],[-2500,-2043],[-2500,-1308]]]]wagyu-0.4.3/tests/expected/x_or-polygon-with-spike.json000066400000000000000000000001421314062220700232200ustar00rootroot00000000000000[[[[-2482,-2452],[-2475,-2478],[2500,2497],[2500,2500],[-2500,2500],[-2500,-2484],[-2482,-2452]]]]wagyu-0.4.3/tests/expected/x_or-polygon-with-two-holes-outside-exterior-ring.json000066400000000000000000000013771314062220700303070ustar00rootroot00000000000000[[[[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,-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]]]]wagyu-0.4.3/tests/expected/x_or-self-intersecting-ring-polygon.json000066400000000000000000000001171314062220700255200ustar00rootroot00000000000000[[[[-947,1164],[-2104,-430],[-324,-1745],[2500,1061],[2500,1717],[-947,1164]]]]wagyu-0.4.3/tests/fixture-tester.cpp000066400000000000000000000200571314062220700175130ustar00rootroot00000000000000#include "util/boost_geometry_adapters.hpp" #include #include #include #include #include #include "rapidjson/writer.h" #include #include #include 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 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 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 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 poly; for (SizeType i = 0; i < document.Size(); ++i) { mapbox::geometry::linear_ring 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>& solution) { output.SetArray(); Document::AllocatorType& allocator = output.GetAllocator(); output.Reserve(static_cast(solution.size()), allocator); // Polygons for (unsigned p = 0; p < solution.size(); ++p) { output.PushBack(Value().SetArray(), allocator); output[p].Reserve(static_cast(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(solution[p][r].size()), allocator); // Coordinates for (auto coord : solution[p][r]) { Value cvalue; cvalue.SetArray(); cvalue.PushBack(Value().SetInt(static_cast(coord.x)), allocator); cvalue.PushBack(Value().SetInt(static_cast(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::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 poly_clip; mapbox::geometry::multi_polygon solution; if (options.clip_file != nullptr) { poly_clip = parse_file(options.clip_file); } while (options.iterations > 0) { options.iterations--; solution.clear(); wagyu 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 writer(out_stream); output.Accept(writer); std::cout << std::endl; } wagyu-0.4.3/tests/fixtures/000077500000000000000000000000001314062220700156625ustar00rootroot00000000000000wagyu-0.4.3/tests/fixtures/clip-clockwise-square.json000066400000000000000000000001711314062220700227620ustar00rootroot00000000000000[ [ [-2500,-2500], [-2500,2500], [2500,2500], [2500,-2500], [-2500,-2500] ] ] wagyu-0.4.3/tests/fixtures/clockwise-triangle.json000066400000000000000000000001131314062220700223360ustar00rootroot00000000000000[ [ [0,0], [5,7], [7,5], [0,0] ] ] wagyu-0.4.3/tests/fuzzer.cpp000066400000000000000000000174311314062220700160500ustar00rootroot00000000000000#include "util/boost_geometry_adapters.hpp" #include #include #include #include #include #include #include #include #include #include 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 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 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 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(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 clipper; mapbox::geometry::polygon 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 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 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; } } } } } wagyu-0.4.3/tests/geometry-test-data/000077500000000000000000000000001314062220700175305ustar00rootroot00000000000000wagyu-0.4.3/tests/quick_clip_profile.cpp000066400000000000000000000024301314062220700203570ustar00rootroot00000000000000#include #include #include #include #include #include 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 p1 = { 100, 100 }; mapbox::geometry::point p2 = { 200, 200 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; size_t n = rand() % 500 + 4; for (size_t j = 0; j < n; j++) { lr.push_back(mapbox::geometry::point(rand() % 300, rand() % 300)); } lr.push_back(lr[0]); if (pass == 0) { optional_linear_ring out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox); } else { optional_linear_ring 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; } wagyu-0.4.3/tests/run-benchmark-tests.sh000077500000000000000000000022321314062220700202430ustar00rootroot00000000000000#!/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" wagyu-0.4.3/tests/run-geometry-tests.sh000077500000000000000000000037121314062220700201500ustar00rootroot00000000000000#!/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 wagyu-0.4.3/tests/test.cpp000066400000000000000000000004451314062220700154770ustar00rootroot00000000000000// 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; } wagyu-0.4.3/tests/unit/000077500000000000000000000000001314062220700147705ustar00rootroot00000000000000wagyu-0.4.3/tests/unit/clipper_get_bounds.cpp000066400000000000000000000045321314062220700213470ustar00rootroot00000000000000#include "catch.hpp" #include using namespace mapbox::geometry::wagyu; using T = std::int64_t; TEST_CASE("test returns zero with no data provided - int64") { mapbox::geometry::wagyu::wagyu 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 polygon; mapbox::geometry::linear_ring 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 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 polygon; mapbox::geometry::linear_ring 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 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 clipper; // Polygon 1 mapbox::geometry::polygon polygon; mapbox::geometry::linear_ring 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); } wagyu-0.4.3/tests/unit/edge.cpp000066400000000000000000000034031314062220700164000ustar00rootroot00000000000000#include "catch.hpp" #include using namespace mapbox::geometry::wagyu; using T = std::int64_t; TEST_CASE("test edge initialization - same two points") { mapbox::geometry::point p1 = { 100, 10 }; mapbox::geometry::point p2 = { 100, 10 }; edge 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 p1 = { 100, 10 }; mapbox::geometry::point p2 = { 10, 15 }; edge 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 p1 = { 10, 10 }; mapbox::geometry::point p2 = { 100, 10 }; edge e1(p1, p2); edge 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 p1 = { 10, 10 }; mapbox::geometry::point p2 = { 10, 100 }; edge e1(p1, p2); edge 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)); } wagyu-0.4.3/tests/unit/local_minimum_util.cpp000066400000000000000000000467661314062220700214010ustar00rootroot00000000000000#include "catch.hpp" #include #include #include #include using namespace mapbox::geometry::wagyu; using T = std::int64_t; TEST_CASE("test reverse horizontal") { mapbox::geometry::point p1 = { 0, 5 }; mapbox::geometry::point p2 = { 5, 5 }; edge 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 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 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 ring; ring.push_back({ 0, 0 }); ring.push_back({ 0, 5 }); ring.push_back({ 5, 5 }); ring.push_back({ 5, 0 }); local_minimum_list 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 ring; ring.push_back({ 0, 0 }); ring.push_back({ 10, 5 }); ring.push_back({ 5, 10 }); ring.push_back({ 0, 0 }); local_minimum_list 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 ring; ring.push_back({ 0, 0 }); ring.push_back({ 10, 5 }); ring.push_back({ 5, 10 }); local_minimum_list 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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()); } wagyu-0.4.3/tests/unit/process_horizontal.hpp000066400000000000000000000013541314062220700214330ustar00rootroot00000000000000#include "catch.hpp" #include using namespace mapbox::geometry::wagyu; using T = std::int64_t; TEST_CASE("get_horizontal_direction finds edge direction") { mapbox::geometry::point p1 = { 0, 5 }; mapbox::geometry::point p2 = { 0, 7 }; edge 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 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); };wagyu-0.4.3/tests/unit/quick_clip.cpp000066400000000000000000000260301314062220700176200ustar00rootroot00000000000000#include "catch.hpp" #include #include #include template inline std::basic_ostream& operator<<(std::basic_ostream& out, const mapbox::geometry::linear_ring& 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 p1 = { 0, 0 }; mapbox::geometry::point p2 = { 100, 100 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; lr.push_back(mapbox::geometry::point(25, 25)); lr.push_back(mapbox::geometry::point(75, 25)); lr.push_back(mapbox::geometry::point(75, 75)); lr.push_back(mapbox::geometry::point(25, 75)); lr.push_back(mapbox::geometry::point(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 p1 = { 0, 0 }; mapbox::geometry::point p2 = { 100, 100 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; lr.push_back(mapbox::geometry::point(25, 25)); lr.push_back(mapbox::geometry::point(175, 25)); lr.push_back(mapbox::geometry::point(175, 75)); lr.push_back(mapbox::geometry::point(25, 75)); lr.push_back(mapbox::geometry::point(25, 25)); auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox); mapbox::geometry::linear_ring want; want.push_back(mapbox::geometry::point(25, 25)); want.push_back(mapbox::geometry::point(100, 25)); want.push_back(mapbox::geometry::point(100, 75)); want.push_back(mapbox::geometry::point(25, 75)); want.push_back(mapbox::geometry::point(25, 25)); CHECK(out == want); } TEST_CASE("square cut at left") { mapbox::geometry::point p1 = { 0, 0 }; mapbox::geometry::point p2 = { 100, 100 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; lr.push_back(mapbox::geometry::point(-25, 25)); lr.push_back(mapbox::geometry::point(75, 25)); lr.push_back(mapbox::geometry::point(75, 75)); lr.push_back(mapbox::geometry::point(-25, 75)); lr.push_back(mapbox::geometry::point(-25, 25)); auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox); mapbox::geometry::linear_ring want; want.push_back(mapbox::geometry::point(0, 25)); want.push_back(mapbox::geometry::point(75, 25)); want.push_back(mapbox::geometry::point(75, 75)); want.push_back(mapbox::geometry::point(0, 75)); want.push_back(mapbox::geometry::point(0, 25)); CHECK(out == want); } TEST_CASE("square cut at top") { mapbox::geometry::point p1 = { 0, 0 }; mapbox::geometry::point p2 = { 100, 100 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; lr.push_back(mapbox::geometry::point(25, 25)); lr.push_back(mapbox::geometry::point(75, 25)); lr.push_back(mapbox::geometry::point(75, 175)); lr.push_back(mapbox::geometry::point(25, 175)); lr.push_back(mapbox::geometry::point(25, 25)); auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox); mapbox::geometry::linear_ring want; want.push_back(mapbox::geometry::point(25, 25)); want.push_back(mapbox::geometry::point(75, 25)); want.push_back(mapbox::geometry::point(75, 100)); want.push_back(mapbox::geometry::point(25, 100)); want.push_back(mapbox::geometry::point(25, 25)); CHECK(out == want); } TEST_CASE("square cut at bottom") { mapbox::geometry::point p1 = { 0, 0 }; mapbox::geometry::point p2 = { 100, 100 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; lr.push_back(mapbox::geometry::point(-25, 25)); lr.push_back(mapbox::geometry::point(75, 25)); lr.push_back(mapbox::geometry::point(75, 75)); lr.push_back(mapbox::geometry::point(-25, 75)); lr.push_back(mapbox::geometry::point(-25, 25)); auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox); mapbox::geometry::linear_ring want; want.push_back(mapbox::geometry::point(0, 25)); want.push_back(mapbox::geometry::point(75, 25)); want.push_back(mapbox::geometry::point(75, 75)); want.push_back(mapbox::geometry::point(0, 75)); want.push_back(mapbox::geometry::point(0, 25)); CHECK(out == want); } TEST_CASE("square cut at top right") { mapbox::geometry::point p1 = { 0, 0 }; mapbox::geometry::point p2 = { 100, 100 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; lr.push_back(mapbox::geometry::point(25, 25)); lr.push_back(mapbox::geometry::point(175, 25)); lr.push_back(mapbox::geometry::point(175, 175)); lr.push_back(mapbox::geometry::point(25, 175)); lr.push_back(mapbox::geometry::point(25, 25)); auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox); mapbox::geometry::linear_ring want; want.push_back(mapbox::geometry::point(25, 25)); want.push_back(mapbox::geometry::point(100, 25)); want.push_back(mapbox::geometry::point(100, 100)); want.push_back(mapbox::geometry::point(25, 100)); want.push_back(mapbox::geometry::point(25, 25)); CHECK(out == want); } TEST_CASE("square cut at top and bottom right") { mapbox::geometry::point p1 = { 0, 0 }; mapbox::geometry::point p2 = { 100, 100 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; lr.push_back(mapbox::geometry::point(25, -25)); lr.push_back(mapbox::geometry::point(175, -25)); lr.push_back(mapbox::geometry::point(175, 175)); lr.push_back(mapbox::geometry::point(25, 175)); lr.push_back(mapbox::geometry::point(25, -25)); auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox); mapbox::geometry::linear_ring want; want.push_back(mapbox::geometry::point(100, 0)); want.push_back(mapbox::geometry::point(100, 100)); want.push_back(mapbox::geometry::point(25, 100)); want.push_back(mapbox::geometry::point(25, 0)); want.push_back(mapbox::geometry::point(100, 0)); CHECK(out == want); } TEST_CASE("square entirely out of bounds") { mapbox::geometry::point p1 = { 0, 0 }; mapbox::geometry::point p2 = { 100, 100 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; lr.push_back(mapbox::geometry::point(125, 125)); lr.push_back(mapbox::geometry::point(175, 125)); lr.push_back(mapbox::geometry::point(175, 175)); lr.push_back(mapbox::geometry::point(125, 175)); lr.push_back(mapbox::geometry::point(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 p1 = { 0, 0 }; mapbox::geometry::point p2 = { 100, 100 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; lr.push_back(mapbox::geometry::point(-25, -25)); lr.push_back(mapbox::geometry::point(175, -25)); lr.push_back(mapbox::geometry::point(175, 175)); lr.push_back(mapbox::geometry::point(-25, 175)); lr.push_back(mapbox::geometry::point(-25, -25)); auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox); mapbox::geometry::linear_ring want; want.push_back(mapbox::geometry::point(0, 0)); want.push_back(mapbox::geometry::point(100, 0)); want.push_back(mapbox::geometry::point(100, 100)); want.push_back(mapbox::geometry::point(0, 100)); want.push_back(mapbox::geometry::point(0, 0)); CHECK(out == want); } TEST_CASE("sticking out and back in") { mapbox::geometry::point p1 = { 0, 0 }; mapbox::geometry::point p2 = { 100, 100 }; mapbox::geometry::box bbox(p1, p2); mapbox::geometry::linear_ring lr; lr.push_back(mapbox::geometry::point(25, 25)); lr.push_back(mapbox::geometry::point(150, 25)); lr.push_back(mapbox::geometry::point(150, 150)); lr.push_back(mapbox::geometry::point(25, 150)); lr.push_back(mapbox::geometry::point(25, 90)); lr.push_back(mapbox::geometry::point(75, 90)); lr.push_back(mapbox::geometry::point(75, 125)); lr.push_back(mapbox::geometry::point(125, 125)); lr.push_back(mapbox::geometry::point(125, 75)); lr.push_back(mapbox::geometry::point(25, 75)); lr.push_back(mapbox::geometry::point(25, 25)); auto out = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(lr, bbox); mapbox::geometry::linear_ring want; want.push_back(mapbox::geometry::point(25, 25)); want.push_back(mapbox::geometry::point(100, 25)); want.push_back(mapbox::geometry::point(100, 100)); want.push_back(mapbox::geometry::point(25, 100)); want.push_back(mapbox::geometry::point(25, 90)); want.push_back(mapbox::geometry::point(75, 90)); want.push_back(mapbox::geometry::point(75, 100)); want.push_back(mapbox::geometry::point(100, 100)); want.push_back(mapbox::geometry::point(100, 75)); want.push_back(mapbox::geometry::point(25, 75)); want.push_back(mapbox::geometry::point(25, 25)); CHECK(out == want); mapbox::geometry::wagyu::wagyu clipper; clipper.add_ring(out); mapbox::geometry::multi_polygon solution; clipper.execute(clip_type_union, solution, fill_type_even_odd, fill_type_even_odd); REQUIRE(solution.size() == 2); mapbox::geometry::linear_ring want0; want0.push_back(mapbox::geometry::point(75, 90)); want0.push_back(mapbox::geometry::point(75, 100)); want0.push_back(mapbox::geometry::point(25, 100)); want0.push_back(mapbox::geometry::point(25, 90)); want0.push_back(mapbox::geometry::point(75, 90)); mapbox::geometry::linear_ring want1; want1.push_back(mapbox::geometry::point(100, 75)); want1.push_back(mapbox::geometry::point(25, 75)); want1.push_back(mapbox::geometry::point(25, 25)); want1.push_back(mapbox::geometry::point(100, 25)); want1.push_back(mapbox::geometry::point(100, 75)); CHECK(solution[0][0] == want0); CHECK(solution[1][0] == want1); mapbox::geometry::polygon poly; poly.push_back(lr); mapbox::geometry::multi_polygon 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); } wagyu-0.4.3/tests/unit/vatti.cpp000066400000000000000000000342561314062220700166350ustar00rootroot00000000000000#include "catch.hpp" #include #include using namespace mapbox::geometry::wagyu; using T = std::int64_t; TEST_CASE("no input test") { mapbox::geometry::wagyu::wagyu wagyu; mapbox::geometry::multi_polygon 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 ring; ring.push_back(mapbox::geometry::point(0, 0)); ring.push_back(mapbox::geometry::point(1, 0)); ring.push_back(mapbox::geometry::point(1, 1)); ring.push_back(mapbox::geometry::point(0, 1)); ring.push_back(mapbox::geometry::point(0, 0)); mapbox::geometry::wagyu::wagyu wagyu; wagyu.add_ring(ring); mapbox::geometry::multi_polygon 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 ring; ring.push_back(mapbox::geometry::point(0, 0)); ring.push_back(mapbox::geometry::point(1, 0)); ring.push_back(mapbox::geometry::point(1, 1)); ring.push_back(mapbox::geometry::point(0, 1)); ring.push_back(mapbox::geometry::point(0, 0)); mapbox::geometry::wagyu::wagyu wagyu; wagyu.add_ring(ring); mapbox::geometry::multi_polygon 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 clipper; mapbox::geometry::polygon polygon0; mapbox::geometry::linear_ring 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 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 polygon1; mapbox::geometry::linear_ring 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 polygon2; mapbox::geometry::linear_ring 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 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 clipper; mapbox::geometry::polygon polygon0; mapbox::geometry::linear_ring 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 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 polygon1; mapbox::geometry::linear_ring 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 polygon2; mapbox::geometry::linear_ring 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 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 clipper; mapbox::geometry::polygon polygon0; mapbox::geometry::linear_ring 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 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 polygon1; mapbox::geometry::linear_ring 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 polygon2; mapbox::geometry::linear_ring 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 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 clipper; mapbox::geometry::polygon polygon0; mapbox::geometry::linear_ring 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 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 polygon1; mapbox::geometry::linear_ring 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 polygon2; mapbox::geometry::linear_ring 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 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); } wagyu-0.4.3/tests/util/000077500000000000000000000000001314062220700147665ustar00rootroot00000000000000wagyu-0.4.3/tests/util/boost_geometry_adapters.hpp000066400000000000000000000073541314062220700224340ustar00rootroot00000000000000#include #include #include #include #include #include #include namespace boost { namespace geometry { namespace traits { template struct tag> { using type = point_tag; }; template struct coordinate_type> { using type = CoordinateType; }; template struct coordinate_system> { using type = boost::geometry::cs::cartesian; }; template struct dimension> : boost::mpl::int_<2> {}; template struct access, 0> { static CoordinateType get(mapbox::geometry::point const& p) { return p.x; } static void set(mapbox::geometry::point& p, CoordinateType x) { p.x = x; } }; template struct access, 1> { static CoordinateType get(mapbox::geometry::point const& p) { return p.y; } static void set(mapbox::geometry::point& p, CoordinateType y) { p.y = y; } }; template struct tag> { using type = ring_tag; }; template struct point_order> { static const order_selector value = counterclockwise; }; template struct tag> { using type = polygon_tag; }; template struct ring_mutable_type> { using type = mapbox::geometry::linear_ring&; }; template struct ring_const_type> { using type = mapbox::geometry::linear_ring const&; }; template struct interior_mutable_type> { using type = boost::iterator_range::iterator>; }; template struct interior_const_type> { using type = boost::iterator_range::const_iterator>; }; template struct exterior_ring> { static mapbox::geometry::linear_ring& get(mapbox::geometry::polygon& p) { return p.at(0); } static mapbox::geometry::linear_ring const& get(mapbox::geometry::polygon const& p) { return p.at(0); } }; template struct interior_rings> { static boost::iterator_range::iterator> get(mapbox::geometry::polygon& p) { return boost::make_iterator_range(p.begin() + 1, p.end()); } static boost::iterator_range::const_iterator> get(mapbox::geometry::polygon const& p) { return boost::make_iterator_range(p.begin() + 1, p.end()); } }; template struct tag> { using type = multi_polygon_tag; }; } } }