/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 . */ #include <sal/config.h> #include <cstdlib> #include <svx/framelink.hxx> #include <math.h> #include <vcl/outdev.hxx> #include <tools/gen.hxx> #include <editeng/borderline.hxx> #include <svtools/borderhelper.hxx> #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <drawinglayer/primitive2d/borderlineprimitive2d.hxx> using namespace ::com::sun::star; using namespace editeng; namespace svx { namespace frame { namespace { /** Converts a width in twips to a width in another map unit (specified by fScale). */ double lclScaleValue( double nValue, double fScale, sal_uInt16 nMaxWidth ) { return std::min<double>(nValue * fScale, nMaxWidth); } } // namespace // Classes #define SCALEVALUE( value ) lclScaleValue( value, fScale, nMaxWidth ) Style::Style() : meRefMode(RefMode::Centered), mfPatternScale(1.0), mnType(SvxBorderLineStyle::SOLID), mpUsingCell(nullptr) { Clear(); } Style::Style( double nP, double nD, double nS, SvxBorderLineStyle nType ) : meRefMode(RefMode::Centered), mfPatternScale(1.0), mnType(nType), mpUsingCell(nullptr) { Clear(); Set( nP, nD, nS ); } Style::Style( const Color& rColorPrim, const Color& rColorSecn, const Color& rColorGap, bool bUseGapColor, double nP, double nD, double nS, SvxBorderLineStyle nType ) : meRefMode(RefMode::Centered), mfPatternScale(1.0), mnType(nType), mpUsingCell(nullptr) { Set( rColorPrim, rColorSecn, rColorGap, bUseGapColor, nP, nD, nS ); } Style::Style( const editeng::SvxBorderLine* pBorder, double fScale ) : meRefMode(RefMode::Centered), mfPatternScale(fScale), mpUsingCell(nullptr) { Set( pBorder, fScale ); } void Style::SetPatternScale( double fScale ) { mfPatternScale = fScale; } void Style::Clear() { Set( Color(), Color(), Color(), false, 0, 0, 0 ); } void Style::Set( double nP, double nD, double nS ) { /* nP nD nS -> mfPrim mfDist mfSecn -------------------------------------- any any 0 nP 0 0 0 any >0 nS 0 0 >0 0 >0 nP 0 0 >0 >0 >0 nP nD nS */ mfPrim = rtl::math::round(nP ? nP : nS, 2); mfDist = rtl::math::round((nP && nS) ? nD : 0, 2); mfSecn = rtl::math::round((nP && nD) ? nS : 0, 2); } void Style::Set( const Color& rColorPrim, const Color& rColorSecn, const Color& rColorGap, bool bUseGapColor, double nP, double nD, double nS ) { maColorPrim = rColorPrim; maColorSecn = rColorSecn; maColorGap = rColorGap; mbUseGapColor = bUseGapColor; Set( nP, nD, nS ); } void Style::Set( const SvxBorderLine& rBorder, double fScale, sal_uInt16 nMaxWidth ) { maColorPrim = rBorder.GetColorOut(); maColorSecn = rBorder.GetColorIn(); maColorGap = rBorder.GetColorGap(); mbUseGapColor = rBorder.HasGapColor(); sal_uInt16 nPrim = rBorder.GetOutWidth(); sal_uInt16 nDist = rBorder.GetDistance(); sal_uInt16 nSecn = rBorder.GetInWidth(); mnType = rBorder.GetBorderLineStyle(); if( !nSecn ) // no or single frame border { Set( SCALEVALUE( nPrim ), 0, 0 ); } else { Set( SCALEVALUE( nPrim ), SCALEVALUE( nDist ), SCALEVALUE( nSecn ) ); // Enlarge the style if distance is too small due to rounding losses. double nPixWidth = SCALEVALUE( nPrim + nDist + nSecn ); if( nPixWidth > GetWidth() ) mfDist = nPixWidth - mfPrim - mfSecn; // Shrink the style if it is too thick for the control. while( GetWidth() > nMaxWidth ) { // First decrease space between lines. if (mfDist) --mfDist; // Still too thick? Decrease the line widths. if( GetWidth() > nMaxWidth ) { if (mfPrim != 0.0 && rtl::math::approxEqual(mfPrim, mfSecn)) { // Both lines equal - decrease both to keep symmetry. --mfPrim; --mfSecn; } else { // Decrease each line for itself if (mfPrim) --mfPrim; if ((GetWidth() > nMaxWidth) && mfSecn != 0.0) --mfSecn; } } } } } void Style::Set( const SvxBorderLine* pBorder, double fScale, sal_uInt16 nMaxWidth ) { if( pBorder ) Set( *pBorder, fScale, nMaxWidth ); else { Clear(); mnType = SvxBorderLineStyle::SOLID; } } Style& Style::MirrorSelf() { if (mfSecn) std::swap( mfPrim, mfSecn ); if( meRefMode != RefMode::Centered ) meRefMode = (meRefMode == RefMode::Begin) ? RefMode::End : RefMode::Begin; return *this; } bool operator==( const Style& rL, const Style& rR ) { return (rL.Prim() == rR.Prim()) && (rL.Dist() == rR.Dist()) && (rL.Secn() == rR.Secn()) && (rL.GetColorPrim() == rR.GetColorPrim()) && (rL.GetColorSecn() == rR.GetColorSecn()) && (rL.GetColorGap() == rR.GetColorGap()) && (rL.GetRefMode() == rR.GetRefMode()) && (rL.UseGapColor() == rR.UseGapColor() ) && (rL.Type() == rR.Type()); } bool operator<( const Style& rL, const Style& rR ) { // different total widths -> rL<rR, if rL is thinner double nLW = rL.GetWidth(); double nRW = rR.GetWidth(); if( !rtl::math::approxEqual(nLW, nRW) ) return nLW < nRW; // one line double, the other single -> rL<rR, if rL is single if( (rL.Secn() == 0) != (rR.Secn() == 0) ) return rL.Secn() == 0; // both lines double with different distances -> rL<rR, if distance of rL greater if( (rL.Secn() && rR.Secn()) && !rtl::math::approxEqual(rL.Dist(), rR.Dist()) ) return rL.Dist() > rR.Dist(); // both lines single and 1 unit thick, only one is dotted -> rL<rR, if rL is dotted if( (nLW == 1) && (rL.Type() != rR.Type()) ) return rL.Type() != SvxBorderLineStyle::SOLID; // seem to be equal return false; } #undef SCALEVALUE bool CheckFrameBorderConnectable( const Style& rLBorder, const Style& rRBorder, const Style& rTFromTL, const Style& rTFromT, const Style& rTFromTR, const Style& rBFromBL, const Style& rBFromB, const Style& rBFromBR ) { return // returns 1 AND (2a OR 2b) // 1) only, if both frame borders are equal (rLBorder == rRBorder) && ( ( // 2a) if the borders are not double, at least one of the vertical must not be double !rLBorder.Secn() && (!rTFromT.Secn() || !rBFromB.Secn()) ) || ( // 2b) if the borders are double, all other borders must not be double rLBorder.Secn() && !rTFromTL.Secn() && !rTFromT.Secn() && !rTFromTR.Secn() && !rBFromBL.Secn() && !rBFromB.Secn() && !rBFromBR.Secn() ) ); } // Drawing functions struct OffsetPair { double mfLeft; double mfRight; OffsetPair(double a, double b) : mfLeft(a), mfRight(b) {} }; struct OffsetCutSet { double mfLeftLeft; double mfRightLeft; double mfLeftRight; double mfRightRight; }; const OffsetCutSet* getMinMaxCutSet(bool bMin, const std::vector< OffsetCutSet >& myCutSets) { if (myCutSets.empty()) { return nullptr; } if (1 == myCutSets.size()) { return &myCutSets[0]; } const OffsetCutSet* pRetval = &myCutSets[0]; double fRetval(pRetval->mfLeftLeft + pRetval->mfLeftRight + pRetval->mfRightLeft + pRetval->mfRightRight); for (size_t a(1); a < myCutSets.size(); a++) { const OffsetCutSet* pCandidate = &myCutSets[a]; const double fCandidate(pCandidate->mfLeftLeft + pCandidate->mfLeftRight + pCandidate->mfRightLeft + pCandidate->mfRightRight); if ((bMin && fCandidate < fRetval) || (!bMin && fCandidate > fRetval)) { pRetval = pCandidate; fRetval = fCandidate; } } return pRetval; } void getOffsetPairsFromStyle(const Style& rStyle, std::vector< OffsetPair >& offsets) { if (rStyle.Prim()) { if (rStyle.Dist() && rStyle.Secn()) { // both lines used (or all three), push four values, from outer to inner switch (rStyle.GetRefMode()) { case RefMode::Centered: { const double fHalfFullWidth(rStyle.GetWidth() * 0.5); offsets.emplace_back(-fHalfFullWidth, rStyle.Prim() - fHalfFullWidth); offsets.emplace_back((rStyle.Prim() + rStyle.Dist()) - fHalfFullWidth, fHalfFullWidth); break; } case RefMode::Begin: { offsets.emplace_back(0.0, rStyle.Prim()); offsets.emplace_back(rStyle.Prim() + rStyle.Dist(), rStyle.GetWidth()); break; } default: // case RefMode::End: { const double fFullWidth(rStyle.GetWidth()); offsets.emplace_back(-fFullWidth, rStyle.Prim() - fFullWidth); offsets.emplace_back((rStyle.Prim() + rStyle.Dist()) - fFullWidth, 0.0); break; } } } else { // one line used, push two values, from outer to inner switch (rStyle.GetRefMode()) { case RefMode::Centered: offsets.emplace_back(rStyle.Prim() * -0.5, rStyle.Prim() * 0.5); break; case RefMode::Begin: offsets.emplace_back(0.0, rStyle.Prim()); break; default: // case RefMode::End: offsets.emplace_back(-rStyle.Prim(), 0.0); break; } } } } void createCutsWithStyle( const basegfx::B2DPoint& rOrigin, const basegfx::B2DVector& rOtherVector, const basegfx::B2DVector& rOtherUnifiedPerpendicular, const OffsetPair& rOtherOffsets, const Style& rStyle, const basegfx::B2DVector& rMyVector, std::vector< OffsetCutSet>& rOtherCuts) { if (rStyle.Prim()) { // get values dependent on source vector const basegfx::B2DVector aMyUnifiedPerpendicular(basegfx::getNormalizedPerpendicular(rMyVector)); const basegfx::B2DPoint aOtherPosLeft(rOrigin + (rOtherUnifiedPerpendicular * rOtherOffsets.mfLeft)); const basegfx::B2DPoint aOtherPosRight(rOrigin + (rOtherUnifiedPerpendicular * rOtherOffsets.mfRight)); std::vector< OffsetPair > myOffsets; // get offsets from outer to inner from target style (one or two) getOffsetPairsFromStyle(rStyle, myOffsets); for (const auto& myOffset : myOffsets) { // get values for new vectors and create all four cuts const basegfx::B2DPoint aMyPosLeft(rOrigin + (aMyUnifiedPerpendicular * myOffset.mfLeft)); const basegfx::B2DPoint aMyPosRight(rOrigin + (aMyUnifiedPerpendicular * myOffset.mfRight)); OffsetCutSet aNewCuts; basegfx::tools::findCut( aOtherPosLeft, rOtherVector, aMyPosLeft, rMyVector, CutFlagValue::LINE, &aNewCuts.mfLeftLeft); basegfx::tools::findCut( aOtherPosLeft, rOtherVector, aMyPosRight, rMyVector, CutFlagValue::LINE, &aNewCuts.mfLeftRight); basegfx::tools::findCut( aOtherPosRight, rOtherVector, aMyPosLeft, rMyVector, CutFlagValue::LINE, &aNewCuts.mfRightLeft); basegfx::tools::findCut( aOtherPosRight, rOtherVector, aMyPosRight, rMyVector, CutFlagValue::LINE, &aNewCuts.mfRightRight); rOtherCuts.push_back(aNewCuts); } } } double getSimpleExtendedLineValues( const basegfx::B2DPoint& rOrigin, const basegfx::B2DVector& rX, const basegfx::B2DVector& rY, const basegfx::B2DVector& rPerpendX, const OffsetPair& myOffset, const Style& rFirst, const Style& rSecond, bool bEdgeStart, double fLength) { std::vector< OffsetCutSet > myCutSets; createCutsWithStyle(rOrigin, rX, rPerpendX, myOffset, rFirst, rY, myCutSets); createCutsWithStyle(rOrigin, rX, rPerpendX, myOffset, rSecond, rY, myCutSets); const OffsetCutSet* pResult = getMinMaxCutSet(bEdgeStart, myCutSets); if (pResult) { if (bEdgeStart) { return (pResult->mfLeftRight + pResult->mfRightRight) * -0.5 * fLength; } else { return (pResult->mfLeftLeft + pResult->mfRightLeft) * 0.5 * fLength; } } return 0.0; } double getComplexExtendedLineValues( const basegfx::B2DPoint& rOrigin, const basegfx::B2DVector& rX, const basegfx::B2DVector& rY, const basegfx::B2DVector& rPerpendX, const OffsetPair& myOffset, const Style& rFirst, const Style& rSecond, bool bEdgeStart, double fLength) { std::vector< OffsetCutSet > myCutSets; createCutsWithStyle(rOrigin, rX, rPerpendX, myOffset, rFirst, rY, myCutSets); const OffsetCutSet* pResult = getMinMaxCutSet(!bEdgeStart, myCutSets); if (!pResult) { createCutsWithStyle(rOrigin, rX, rPerpendX, myOffset, rSecond, rY, myCutSets); pResult = getMinMaxCutSet(bEdgeStart, myCutSets); } if (pResult) { if (bEdgeStart) { return (pResult->mfLeftRight + pResult->mfRightRight) * 0.5 * -fLength; } else { return (pResult->mfLeftLeft + pResult->mfRightLeft) * 0.5 * fLength; } } return 0.0; } void CreateBorderPrimitives( drawinglayer::primitive2d::Primitive2DContainer& rTarget, const basegfx::B2DPoint& rOrigin, const basegfx::B2DVector& rX, const basegfx::B2DVector& rY, const Style& rBorder, const Style& /*rLFromTR*/, const Style& rLFromT, const Style& /*rLFromL*/, const Style& rLFromB, const Style& /*rLFromBR*/, const Style& /*rRFromTL*/, const Style& rRFromT, const Style& /*rRFromR*/, const Style& rRFromB, const Style& /*rRFromBL*/, const Color* pForceColor) { if (rBorder.Prim()) { const basegfx::B2DVector aPerpendX(basegfx::getNormalizedPerpendicular(rX)); const double fLength(rX.getLength()); // do not forget RefMode offset, primitive will assume RefMode::Centered basegfx::B2DVector aRefModeOffset; if (RefMode::Centered != rBorder.GetRefMode()) { const double fHalfWidth(rBorder.GetWidth() * 0.5); if (RefMode::Begin == rBorder.GetRefMode()) { // move aligned below vector aRefModeOffset = aPerpendX * fHalfWidth; } else if (RefMode::End == rBorder.GetRefMode()) { // move aligned above vector aRefModeOffset = aPerpendX * -fHalfWidth; } } // create start/end (use RefMode) const basegfx::B2DPoint aStart(rOrigin + aRefModeOffset); const basegfx::B2DPoint aEnd(aStart + rX); // get offsets for my style (one or two) std::vector< OffsetPair > myOffsets; getOffsetPairsFromStyle(rBorder, myOffsets); if (1 == myOffsets.size()) { // we are a single edge, calculate cuts with edges coming from above/below // to detect the line start/end extensions const OffsetPair& myOffset(myOffsets[0]); double mfExtendStart(0.0); double mfExtendEnd(0.0); // for start: get cuts with all left target styles and use the minimum mfExtendStart = getSimpleExtendedLineValues(rOrigin, rX, rY, aPerpendX, myOffset, rLFromT, rLFromB, true, fLength); // for end: get cuts with all right target styles and use the maximum mfExtendEnd = getSimpleExtendedLineValues(rOrigin, rX, rY, aPerpendX, myOffset, rRFromT, rRFromB, false, fLength); rTarget.append( drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::BorderLinePrimitive2D( aStart, aEnd, drawinglayer::primitive2d::BorderLine( rBorder.Prim(), (pForceColor ? *pForceColor : rBorder.GetColorPrim()).getBColor(), drawinglayer::primitive2d::BorderLineExtend( mfExtendStart, mfExtendEnd)), rBorder.Type(), rBorder.PatternScale()))); } else if (2 == myOffsets.size()) { // we are a double edge, calculate cuts with edges coming from above/below // for both edges to detect the line start/end extensions. In the future this // needs to be extended to use two values per extension, getComplexExtendedLineValues // internally prepares these already. drawinglayer::primitive2d::BorderLine will // then need to take these double entries (maybe a pair) and use them internally. double mfExtendLeftStart(0.0); double mfExtendLeftEnd(0.0); double mfExtendRightStart(0.0); double mfExtendRightEnd(0.0); // for start of first edge, get cuts with left targets. Start with upper and take maximum when // cut exists. Else use lower and take minimum when cut exists mfExtendLeftStart = getComplexExtendedLineValues(rOrigin, rX, rY, aPerpendX, myOffsets[0], rLFromT, rLFromB, true, fLength); // for end of first edge, get cuts with right targets. Start with upper and take minimum when // cut exists. Else use lower and take maximum when cut exists mfExtendLeftEnd = getComplexExtendedLineValues(rOrigin, rX, rY, aPerpendX, myOffsets[0], rRFromT, rRFromB, false, fLength); // for start of second edge, get cuts with left targets. Start with lower and take maximum when // cut exists. Else use upper and take minimum when cut exists mfExtendRightStart = getComplexExtendedLineValues(rOrigin, rX, rY, aPerpendX, myOffsets[1], rLFromB, rLFromT, true, fLength); // for end of second edge, get cuts with right targets. Start with lower and take minimum when // cut exists. Else use upper and take maximum when cut exists mfExtendRightEnd = getComplexExtendedLineValues(rOrigin, rX, rY, aPerpendX, myOffsets[1], rRFromB, rRFromT, false, fLength); // needs to be determined in detail later, for now use the max prolongation // from left/right, but do not less than half (0.0). This works decently, // but not perfect (see Writer, use three-color-style, look at upper/lower# // connections) const double fGapLeft(std::max(0.0, std::max(mfExtendLeftStart, mfExtendRightStart))); const double fGapRight(std::max(0.0, std::max(mfExtendLeftEnd, mfExtendRightEnd))); rTarget.append( drawinglayer::primitive2d::Primitive2DReference( new drawinglayer::primitive2d::BorderLinePrimitive2D( aStart, aEnd, drawinglayer::primitive2d::BorderLine( rBorder.Prim(), (pForceColor ? *pForceColor : rBorder.GetColorPrim()).getBColor(), drawinglayer::primitive2d::BorderLineExtend( mfExtendLeftStart, mfExtendLeftEnd)), drawinglayer::primitive2d::BorderLine( rBorder.Dist(), (pForceColor ? *pForceColor : rBorder.GetColorGap()).getBColor(), drawinglayer::primitive2d::BorderLineExtend( fGapLeft, fGapRight)), drawinglayer::primitive2d::BorderLine( rBorder.Secn(), (pForceColor ? *pForceColor : rBorder.GetColorSecn()).getBColor(), drawinglayer::primitive2d::BorderLineExtend( mfExtendRightStart, mfExtendRightEnd)), rBorder.UseGapColor(), rBorder.Type(), rBorder.PatternScale()))); } } } void CreateBorderPrimitives( drawinglayer::primitive2d::Primitive2DContainer& rTarget, const basegfx::B2DPoint& rOrigin, const basegfx::B2DVector& rX, const basegfx::B2DVector& rY, const Style& rBorder, const Style& rLFromT, const Style& rLFromL, const Style& rLFromB, const Style& rRFromT, const Style& rRFromR, const Style& rRFromB, const Color* pForceColor) { if (rBorder.Prim() || rBorder.Secn()) { CreateBorderPrimitives( rTarget, rOrigin, rX, rY, rBorder, Style(), rLFromT, rLFromL, rLFromB, Style(), Style(), rRFromT, rRFromR, rRFromB, Style(), pForceColor); } } void CreateDiagFrameBorderPrimitives( drawinglayer::primitive2d::Primitive2DContainer& rTarget, const basegfx::B2DPoint& rOrigin, const basegfx::B2DVector& rXAxis, const basegfx::B2DVector& rYAxis, const Style& rTLBR, const Style& rBLTR, const Style& /*rTLFromB*/, const Style& /*rTLFromR*/, const Style& /*rBRFromT*/, const Style& /*rBRFromL*/, const Style& /*rBLFromT*/, const Style& /*rBLFromR*/, const Style& /*rTRFromB*/, const Style& /*rTRFromL*/, const Color* pForceColor) { // currently the diagonal edges are just added as-is without cutting them against the incoming // edges. This needs to be improved in the future, so please do *not* remove the currently unused // parameters from above if (rTLBR.Prim()) { // top-left to bottom-right if (basegfx::fTools::equalZero(rTLBR.Secn())) { rTarget.append( new drawinglayer::primitive2d::BorderLinePrimitive2D( rOrigin, rOrigin + rXAxis + rYAxis, drawinglayer::primitive2d::BorderLine( rTLBR.Prim(), (pForceColor ? *pForceColor : rTLBR.GetColorPrim()).getBColor()), rTLBR.Type(), rTLBR.PatternScale())); } else { rTarget.append( new drawinglayer::primitive2d::BorderLinePrimitive2D( rOrigin, rOrigin + rXAxis + rYAxis, drawinglayer::primitive2d::BorderLine( rTLBR.Prim(), (pForceColor ? *pForceColor : rTLBR.GetColorPrim()).getBColor()), drawinglayer::primitive2d::BorderLine( rTLBR.Dist(), (pForceColor ? *pForceColor : rTLBR.GetColorGap()).getBColor()), drawinglayer::primitive2d::BorderLine( rTLBR.Secn(), (pForceColor ? *pForceColor : rTLBR.GetColorSecn()).getBColor()), rTLBR.UseGapColor(), rTLBR.Type(), rTLBR.PatternScale())); } } if (rBLTR.Prim()) { // bottom-left to top-right if (basegfx::fTools::equalZero(rTLBR.Secn())) { rTarget.append( new drawinglayer::primitive2d::BorderLinePrimitive2D( rOrigin + rYAxis, rOrigin + rXAxis, drawinglayer::primitive2d::BorderLine( rTLBR.Prim(), (pForceColor ? *pForceColor : rTLBR.GetColorPrim()).getBColor()), rBLTR.Type(), rBLTR.PatternScale())); } else { rTarget.append( new drawinglayer::primitive2d::BorderLinePrimitive2D( rOrigin + rYAxis, rOrigin + rXAxis, drawinglayer::primitive2d::BorderLine( rTLBR.Prim(), (pForceColor ? *pForceColor : rTLBR.GetColorPrim()).getBColor()), drawinglayer::primitive2d::BorderLine( rTLBR.Dist(), (pForceColor ? *pForceColor : rTLBR.GetColorGap()).getBColor()), drawinglayer::primitive2d::BorderLine( rTLBR.Secn(), (pForceColor ? *pForceColor : rTLBR.GetColorSecn()).getBColor()), rBLTR.UseGapColor(), rBLTR.Type(), rBLTR.PatternScale())); } } } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */