| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_basegfx.hxx" |
| #include <basegfx/tools/b2dclipstate.hxx> |
| |
| #include <basegfx/range/b2drange.hxx> |
| #include <basegfx/range/b2dpolyrange.hxx> |
| #include <basegfx/range/b2drangeclipper.hxx> |
| #include <basegfx/polygon/b2dpolygon.hxx> |
| #include <basegfx/polygon/b2dpolygontools.hxx> |
| #include <basegfx/polygon/b2dpolypolygon.hxx> |
| #include <basegfx/polygon/b2dpolypolygontools.hxx> |
| #include <basegfx/polygon/b2dpolypolygoncutter.hxx> |
| |
| namespace basegfx |
| { |
| namespace tools |
| { |
| struct ImplB2DClipState |
| { |
| public: |
| enum Operation {UNION, INTERSECT, XOR, SUBTRACT}; |
| |
| ImplB2DClipState() : |
| maPendingPolygons(), |
| maPendingRanges(), |
| maClipPoly(), |
| mePendingOps(UNION) |
| {} |
| |
| explicit ImplB2DClipState( const B2DRange& rRange ) : |
| maPendingPolygons(), |
| maPendingRanges(), |
| maClipPoly( |
| tools::createPolygonFromRect(rRange)), |
| mePendingOps(UNION) |
| {} |
| |
| explicit ImplB2DClipState( const B2DPolygon& rPoly ) : |
| maPendingPolygons(), |
| maPendingRanges(), |
| maClipPoly(rPoly), |
| mePendingOps(UNION) |
| {} |
| |
| explicit ImplB2DClipState( const B2DPolyPolygon& rPoly ) : |
| maPendingPolygons(), |
| maPendingRanges(), |
| maClipPoly(rPoly), |
| mePendingOps(UNION) |
| {} |
| |
| bool isCleared() const |
| { |
| return !maClipPoly.count() |
| && !maPendingPolygons.count() |
| && !maPendingRanges.count(); |
| } |
| |
| void makeClear() |
| { |
| maPendingPolygons.clear(); |
| maPendingRanges.clear(); |
| maClipPoly.clear(); |
| mePendingOps = UNION; |
| } |
| |
| bool isNullClipPoly() const |
| { |
| return maClipPoly.count() == 1 |
| && !maClipPoly.getB2DPolygon(0).count(); |
| } |
| |
| bool isNull() const |
| { |
| return !maPendingPolygons.count() |
| && !maPendingRanges.count() |
| && isNullClipPoly(); |
| } |
| |
| void makeNull() |
| { |
| maPendingPolygons.clear(); |
| maPendingRanges.clear(); |
| maClipPoly.clear(); |
| maClipPoly.append(B2DPolygon()); |
| mePendingOps = UNION; |
| } |
| |
| bool operator==(const ImplB2DClipState& rRHS) const |
| { |
| return maPendingPolygons == rRHS.maPendingPolygons |
| && maPendingRanges == rRHS.maPendingRanges |
| && maClipPoly == rRHS.maClipPoly |
| && mePendingOps == rRHS.mePendingOps; |
| } |
| |
| void addRange(const B2DRange& rRange, Operation eOp) |
| { |
| if( rRange.isEmpty() ) |
| return; |
| |
| commitPendingPolygons(); |
| if( mePendingOps != eOp ) |
| commitPendingRanges(); |
| |
| mePendingOps = eOp; |
| maPendingRanges.appendElement( |
| rRange, |
| ORIENTATION_POSITIVE); |
| } |
| |
| void addPolygon(B2DPolygon aPoly, Operation eOp) |
| { |
| commitPendingRanges(); |
| if( mePendingOps != eOp ) |
| commitPendingPolygons(); |
| |
| mePendingOps = eOp; |
| maPendingPolygons.append(aPoly); |
| } |
| |
| void addPolyPolygon(B2DPolyPolygon aPoly, Operation eOp) |
| { |
| commitPendingRanges(); |
| if( mePendingOps != eOp ) |
| commitPendingPolygons(); |
| |
| mePendingOps = eOp; |
| maPendingPolygons.append(aPoly); |
| } |
| |
| void addClipState(const ImplB2DClipState& rOther, Operation eOp) |
| { |
| if( rOther.mePendingOps == mePendingOps |
| && !rOther.maClipPoly.count() |
| && !rOther.maPendingPolygons.count() ) |
| { |
| maPendingRanges.appendPolyRange( rOther.maPendingRanges ); |
| } |
| else |
| { |
| commitPendingRanges(); |
| commitPendingPolygons(); |
| rOther.commitPendingRanges(); |
| rOther.commitPendingPolygons(); |
| |
| maPendingPolygons = rOther.maClipPoly; |
| mePendingOps = eOp; |
| } |
| } |
| |
| void unionRange(const B2DRange& rRange) |
| { |
| if( isCleared() ) |
| return; |
| |
| addRange(rRange,UNION); |
| } |
| |
| void unionPolygon(const B2DPolygon& rPoly) |
| { |
| if( isCleared() ) |
| return; |
| |
| addPolygon(rPoly,UNION); |
| } |
| |
| void unionPolyPolygon(const B2DPolyPolygon& rPolyPoly) |
| { |
| if( isCleared() ) |
| return; |
| |
| addPolyPolygon(rPolyPoly,UNION); |
| } |
| |
| void unionClipState(const ImplB2DClipState& rOther) |
| { |
| if( isCleared() ) |
| return; |
| |
| addClipState(rOther, UNION); |
| } |
| |
| void intersectRange(const B2DRange& rRange) |
| { |
| if( isNull() ) |
| return; |
| |
| addRange(rRange,INTERSECT); |
| } |
| |
| void intersectPolygon(const B2DPolygon& rPoly) |
| { |
| if( isNull() ) |
| return; |
| |
| addPolygon(rPoly,INTERSECT); |
| } |
| |
| void intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly) |
| { |
| if( isNull() ) |
| return; |
| |
| addPolyPolygon(rPolyPoly,INTERSECT); |
| } |
| |
| void intersectClipState(const ImplB2DClipState& rOther) |
| { |
| if( isNull() ) |
| return; |
| |
| addClipState(rOther, INTERSECT); |
| } |
| |
| void subtractRange(const B2DRange& rRange ) |
| { |
| if( isNull() ) |
| return; |
| |
| addRange(rRange,SUBTRACT); |
| } |
| |
| void subtractPolygon(const B2DPolygon& rPoly) |
| { |
| if( isNull() ) |
| return; |
| |
| addPolygon(rPoly,SUBTRACT); |
| } |
| |
| void subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly) |
| { |
| if( isNull() ) |
| return; |
| |
| addPolyPolygon(rPolyPoly,SUBTRACT); |
| } |
| |
| void subtractClipState(const ImplB2DClipState& rOther) |
| { |
| if( isNull() ) |
| return; |
| |
| addClipState(rOther, SUBTRACT); |
| } |
| |
| void xorRange(const B2DRange& rRange) |
| { |
| addRange(rRange,XOR); |
| } |
| |
| void xorPolygon(const B2DPolygon& rPoly) |
| { |
| addPolygon(rPoly,XOR); |
| } |
| |
| void xorPolyPolygon(const B2DPolyPolygon& rPolyPoly) |
| { |
| addPolyPolygon(rPolyPoly,XOR); |
| } |
| |
| void xorClipState(const ImplB2DClipState& rOther) |
| { |
| addClipState(rOther, XOR); |
| } |
| |
| B2DPolyPolygon getClipPoly() const |
| { |
| commitPendingRanges(); |
| commitPendingPolygons(); |
| |
| return maClipPoly; |
| } |
| |
| private: |
| void commitPendingPolygons() const |
| { |
| if( !maPendingPolygons.count() ) |
| return; |
| |
| // assumption: maClipPoly has kept polygons prepared for |
| // clipping; i.e. no neutral polygons & correct |
| // orientation |
| maPendingPolygons = tools::prepareForPolygonOperation(maPendingPolygons); |
| const bool bIsEmpty=isNullClipPoly(); |
| const bool bIsCleared=!maClipPoly.count(); |
| switch(mePendingOps) |
| { |
| case UNION: |
| OSL_ASSERT( !bIsCleared ); |
| |
| if( bIsEmpty ) |
| maClipPoly = maPendingPolygons; |
| else |
| maClipPoly = tools::solvePolygonOperationOr( |
| maClipPoly, |
| maPendingPolygons); |
| break; |
| case INTERSECT: |
| OSL_ASSERT( !bIsEmpty ); |
| |
| if( bIsCleared ) |
| maClipPoly = maPendingPolygons; |
| else |
| maClipPoly = tools::solvePolygonOperationAnd( |
| maClipPoly, |
| maPendingPolygons); |
| break; |
| case XOR: |
| if( bIsEmpty ) |
| maClipPoly = maPendingPolygons; |
| else if( bIsCleared ) |
| { |
| // not representable, strictly speaking, |
| // using polygons with the common even/odd |
| // or nonzero winding number fill rule. If |
| // we'd want to represent it, fill rule |
| // would need to be "non-negative winding |
| // number" (and we then would return |
| // 'holes' here) |
| |
| // going for an ugly hack meanwhile |
| maClipPoly = tools::solvePolygonOperationXor( |
| B2DPolyPolygon( |
| tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))), |
| maPendingPolygons); |
| } |
| else |
| maClipPoly = tools::solvePolygonOperationXor( |
| maClipPoly, |
| maPendingPolygons); |
| break; |
| case SUBTRACT: |
| OSL_ASSERT( !bIsEmpty ); |
| |
| // first union all pending ones, subtract en bloc then |
| maPendingPolygons = solveCrossovers(maPendingPolygons); |
| maPendingPolygons = stripNeutralPolygons(maPendingPolygons); |
| maPendingPolygons = stripDispensablePolygons(maPendingPolygons, false); |
| |
| if( bIsCleared ) |
| { |
| // not representable, strictly speaking, |
| // using polygons with the common even/odd |
| // or nonzero winding number fill rule. If |
| // we'd want to represent it, fill rule |
| // would need to be "non-negative winding |
| // number" (and we then would return |
| // 'holes' here) |
| |
| // going for an ugly hack meanwhile |
| maClipPoly = tools::solvePolygonOperationDiff( |
| B2DPolyPolygon( |
| tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))), |
| maPendingPolygons); |
| } |
| else |
| maClipPoly = tools::solvePolygonOperationDiff( |
| maClipPoly, |
| maPendingPolygons); |
| break; |
| } |
| |
| maPendingPolygons.clear(); |
| mePendingOps = UNION; |
| } |
| |
| void commitPendingRanges() const |
| { |
| if( !maPendingRanges.count() ) |
| return; |
| |
| // use the specialized range clipper for the win |
| B2DPolyPolygon aCollectedRanges; |
| const bool bIsEmpty=isNullClipPoly(); |
| const bool bIsCleared=!maClipPoly.count(); |
| switch(mePendingOps) |
| { |
| case UNION: |
| OSL_ASSERT( !bIsCleared ); |
| |
| aCollectedRanges = maPendingRanges.solveCrossovers(); |
| aCollectedRanges = stripNeutralPolygons(aCollectedRanges); |
| aCollectedRanges = stripDispensablePolygons(aCollectedRanges, false); |
| if( bIsEmpty ) |
| maClipPoly = aCollectedRanges; |
| else |
| maClipPoly = tools::solvePolygonOperationOr( |
| maClipPoly, |
| aCollectedRanges); |
| break; |
| case INTERSECT: |
| OSL_ASSERT( !bIsEmpty ); |
| |
| aCollectedRanges = maPendingRanges.solveCrossovers(); |
| aCollectedRanges = stripNeutralPolygons(aCollectedRanges); |
| if( maPendingRanges.count() > 1 ) |
| aCollectedRanges = stripDispensablePolygons(aCollectedRanges, true); |
| |
| if( bIsCleared ) |
| maClipPoly = aCollectedRanges; |
| else |
| maClipPoly = tools::solvePolygonOperationAnd( |
| maClipPoly, |
| aCollectedRanges); |
| break; |
| case XOR: |
| aCollectedRanges = maPendingRanges.solveCrossovers(); |
| aCollectedRanges = stripNeutralPolygons(aCollectedRanges); |
| aCollectedRanges = correctOrientations(aCollectedRanges); |
| |
| if( bIsEmpty ) |
| maClipPoly = aCollectedRanges; |
| else if( bIsCleared ) |
| { |
| // not representable, strictly speaking, |
| // using polygons with the common even/odd |
| // or nonzero winding number fill rule. If |
| // we'd want to represent it, fill rule |
| // would need to be "non-negative winding |
| // number" (and we then would return |
| // 'holes' here) |
| |
| // going for an ugly hack meanwhile |
| maClipPoly = tools::solvePolygonOperationXor( |
| B2DPolyPolygon( |
| tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))), |
| aCollectedRanges); |
| } |
| else |
| maClipPoly = tools::solvePolygonOperationXor( |
| maClipPoly, |
| aCollectedRanges); |
| break; |
| case SUBTRACT: |
| OSL_ASSERT( !bIsEmpty ); |
| |
| // first union all pending ranges, subtract en bloc then |
| aCollectedRanges = maPendingRanges.solveCrossovers(); |
| aCollectedRanges = stripNeutralPolygons(aCollectedRanges); |
| aCollectedRanges = stripDispensablePolygons(aCollectedRanges, false); |
| |
| if( bIsCleared ) |
| { |
| // not representable, strictly speaking, |
| // using polygons with the common even/odd |
| // or nonzero winding number fill rule. If |
| // we'd want to represent it, fill rule |
| // would need to be "non-negative winding |
| // number" (and we then would return |
| // 'holes' here) |
| |
| // going for an ugly hack meanwhile |
| maClipPoly = tools::solvePolygonOperationDiff( |
| B2DPolyPolygon( |
| tools::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))), |
| aCollectedRanges); |
| } |
| else |
| maClipPoly = tools::solvePolygonOperationDiff( |
| maClipPoly, |
| aCollectedRanges); |
| break; |
| } |
| |
| maPendingRanges.clear(); |
| mePendingOps = UNION; |
| } |
| |
| mutable B2DPolyPolygon maPendingPolygons; |
| mutable B2DPolyRange maPendingRanges; |
| mutable B2DPolyPolygon maClipPoly; |
| mutable Operation mePendingOps; |
| }; |
| |
| B2DClipState::B2DClipState() : |
| mpImpl() |
| {} |
| |
| B2DClipState::~B2DClipState() |
| {} |
| |
| B2DClipState::B2DClipState( const B2DClipState& rOrig ) : |
| mpImpl(rOrig.mpImpl) |
| {} |
| |
| B2DClipState::B2DClipState( const B2DRange& rRange ) : |
| mpImpl( ImplB2DClipState(rRange) ) |
| {} |
| |
| B2DClipState::B2DClipState( const B2DPolygon& rPoly ) : |
| mpImpl( ImplB2DClipState(rPoly) ) |
| {} |
| |
| B2DClipState::B2DClipState( const B2DPolyPolygon& rPolyPoly ) : |
| mpImpl( ImplB2DClipState(rPolyPoly) ) |
| {} |
| |
| B2DClipState& B2DClipState::operator=( const B2DClipState& rRHS ) |
| { |
| mpImpl = rRHS.mpImpl; |
| return *this; |
| } |
| |
| void B2DClipState::makeUnique() |
| { |
| mpImpl.make_unique(); |
| } |
| |
| void B2DClipState::makeNull() |
| { |
| mpImpl->makeNull(); |
| } |
| |
| bool B2DClipState::isNull() const |
| { |
| return mpImpl->isNull(); |
| } |
| |
| void B2DClipState::makeClear() |
| { |
| mpImpl->makeClear(); |
| } |
| |
| bool B2DClipState::isCleared() const |
| { |
| return mpImpl->isCleared(); |
| } |
| |
| bool B2DClipState::operator==(const B2DClipState& rRHS) const |
| { |
| if(mpImpl.same_object(rRHS.mpImpl)) |
| return true; |
| |
| return ((*mpImpl) == (*rRHS.mpImpl)); |
| } |
| |
| bool B2DClipState::operator!=(const B2DClipState& rRHS) const |
| { |
| return !(*this == rRHS); |
| } |
| |
| void B2DClipState::unionRange(const B2DRange& rRange) |
| { |
| mpImpl->unionRange(rRange); |
| } |
| |
| void B2DClipState::unionPolygon(const B2DPolygon& rPoly) |
| { |
| mpImpl->unionPolygon(rPoly); |
| } |
| |
| void B2DClipState::unionPolyPolygon(const B2DPolyPolygon& rPolyPoly) |
| { |
| mpImpl->unionPolyPolygon(rPolyPoly); |
| } |
| |
| void B2DClipState::unionClipState(const B2DClipState& rState) |
| { |
| mpImpl->unionClipState(*rState.mpImpl); |
| } |
| |
| void B2DClipState::intersectRange(const B2DRange& rRange) |
| { |
| mpImpl->intersectRange(rRange); |
| } |
| |
| void B2DClipState::intersectPolygon(const B2DPolygon& rPoly) |
| { |
| mpImpl->intersectPolygon(rPoly); |
| } |
| |
| void B2DClipState::intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly) |
| { |
| mpImpl->intersectPolyPolygon(rPolyPoly); |
| } |
| |
| void B2DClipState::intersectClipState(const B2DClipState& rState) |
| { |
| mpImpl->intersectClipState(*rState.mpImpl); |
| } |
| |
| void B2DClipState::subtractRange(const B2DRange& rRange) |
| { |
| mpImpl->subtractRange(rRange); |
| } |
| |
| void B2DClipState::subtractPolygon(const B2DPolygon& rPoly) |
| { |
| mpImpl->subtractPolygon(rPoly); |
| } |
| |
| void B2DClipState::subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly) |
| { |
| mpImpl->subtractPolyPolygon(rPolyPoly); |
| } |
| |
| void B2DClipState::subtractClipState(const B2DClipState& rState) |
| { |
| mpImpl->subtractClipState(*rState.mpImpl); |
| } |
| |
| void B2DClipState::xorRange(const B2DRange& rRange) |
| { |
| mpImpl->xorRange(rRange); |
| } |
| |
| void B2DClipState::xorPolygon(const B2DPolygon& rPoly) |
| { |
| mpImpl->xorPolygon(rPoly); |
| } |
| |
| void B2DClipState::xorPolyPolygon(const B2DPolyPolygon& rPolyPoly) |
| { |
| mpImpl->xorPolyPolygon(rPolyPoly); |
| } |
| |
| void B2DClipState::xorClipState(const B2DClipState& rState) |
| { |
| mpImpl->xorClipState(*rState.mpImpl); |
| } |
| |
| B2DPolyPolygon B2DClipState::getClipPoly() const |
| { |
| return mpImpl->getClipPoly(); |
| } |
| |
| } // end of namespace tools |
| } // end of namespace basegfx |
| |
| // eof |