summaryrefslogtreecommitdiffstats
path: root/basegfx/source
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /basegfx/source
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'basegfx/source')
-rw-r--r--basegfx/source/color/bcolormodifier.cxx539
-rw-r--r--basegfx/source/color/bcolortools.cxx195
-rw-r--r--basegfx/source/curve/b2dbeziertools.cxx117
-rw-r--r--basegfx/source/curve/b2dcubicbezier.cxx1055
-rw-r--r--basegfx/source/inc/stringconversiontools.hxx64
-rw-r--r--basegfx/source/matrix/b2dhommatrix.cxx430
-rw-r--r--basegfx/source/matrix/b2dhommatrixtools.cxx457
-rw-r--r--basegfx/source/matrix/b3dhommatrix.cxx546
-rw-r--r--basegfx/source/matrix/b3dhommatrixtools.cxx71
-rw-r--r--basegfx/source/numeric/ftools.cxx135
-rw-r--r--basegfx/source/point/b2dpoint.cxx51
-rw-r--r--basegfx/source/point/b2ipoint.cxx51
-rw-r--r--basegfx/source/point/b3dpoint.cxx76
-rw-r--r--basegfx/source/polygon/WaveLine.cxx52
-rw-r--r--basegfx/source/polygon/b2dlinegeometry.cxx998
-rw-r--r--basegfx/source/polygon/b2dpolygon.cxx1455
-rw-r--r--basegfx/source/polygon/b2dpolygonclipper.cxx824
-rw-r--r--basegfx/source/polygon/b2dpolygoncutandtouch.cxx1077
-rw-r--r--basegfx/source/polygon/b2dpolygontools.cxx3702
-rw-r--r--basegfx/source/polygon/b2dpolygontriangulator.cxx434
-rw-r--r--basegfx/source/polygon/b2dpolypolygon.cxx432
-rw-r--r--basegfx/source/polygon/b2dpolypolygoncutter.cxx1135
-rw-r--r--basegfx/source/polygon/b2dpolypolygontools.cxx657
-rw-r--r--basegfx/source/polygon/b2dsvgpolypolygon.cxx931
-rw-r--r--basegfx/source/polygon/b2dtrapezoid.cxx1163
-rw-r--r--basegfx/source/polygon/b3dpolygon.cxx1612
-rw-r--r--basegfx/source/polygon/b3dpolygontools.cxx826
-rw-r--r--basegfx/source/polygon/b3dpolypolygon.cxx400
-rw-r--r--basegfx/source/polygon/b3dpolypolygontools.cxx585
-rw-r--r--basegfx/source/range/b2dpolyrange.cxx153
-rw-r--r--basegfx/source/range/b2drange.cxx81
-rw-r--r--basegfx/source/range/b2drangeclipper.cxx877
-rw-r--r--basegfx/source/range/b2xrange.cxx120
-rw-r--r--basegfx/source/range/b3drange.cxx64
-rw-r--r--basegfx/source/raster/rasterconvert3d.cxx337
-rw-r--r--basegfx/source/tools/b2dclipstate.cxx476
-rw-r--r--basegfx/source/tools/bgradient.cxx997
-rw-r--r--basegfx/source/tools/canvastools.cxx474
-rw-r--r--basegfx/source/tools/gradienttools.cxx756
-rw-r--r--basegfx/source/tools/keystoplerp.cxx90
-rw-r--r--basegfx/source/tools/numbertools.cxx58
-rw-r--r--basegfx/source/tools/stringconversiontools.cxx158
-rw-r--r--basegfx/source/tools/systemdependentdata.cxx166
-rw-r--r--basegfx/source/tools/tools.cxx111
-rw-r--r--basegfx/source/tools/unopolypolygon.cxx446
-rw-r--r--basegfx/source/tools/zoomtools.cxx113
-rw-r--r--basegfx/source/tuple/b2dtuple.cxx43
-rw-r--r--basegfx/source/tuple/b3dtuple.cxx37
-rw-r--r--basegfx/source/vector/b2dvector.cxx184
-rw-r--r--basegfx/source/vector/b2ivector.cxx65
-rw-r--r--basegfx/source/vector/b3dvector.cxx86
-rw-r--r--basegfx/source/workbench/Makefile23
-rw-r--r--basegfx/source/workbench/bezierclip.cxx2006
-rw-r--r--basegfx/source/workbench/bezierclip.hxx84
-rw-r--r--basegfx/source/workbench/convexhull.cxx200
-rw-r--r--basegfx/source/workbench/gauss.hxx163
56 files changed, 28438 insertions, 0 deletions
diff --git a/basegfx/source/color/bcolormodifier.cxx b/basegfx/source/color/bcolormodifier.cxx
new file mode 100644
index 0000000000..75c06f5cb8
--- /dev/null
+++ b/basegfx/source/color/bcolormodifier.cxx
@@ -0,0 +1,539 @@
+/* -*- 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 <algorithm>
+#include <float.h>
+#include <basegfx/color/bcolormodifier.hxx>
+#include <comphelper/random.hxx>
+
+namespace basegfx
+{
+ BColorModifier::~BColorModifier()
+ {
+ }
+
+ BColorModifier_gray::~BColorModifier_gray()
+ {
+ }
+
+ bool BColorModifier_gray::operator==(const BColorModifier& rCompare) const
+ {
+ return dynamic_cast< const BColorModifier_gray* >(&rCompare) != nullptr;
+ }
+
+ ::basegfx::BColor BColorModifier_gray::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ const double fLuminance(aSourceColor.luminance());
+
+ return ::basegfx::BColor(fLuminance, fLuminance, fLuminance);
+ }
+
+ OUString BColorModifier_gray::getModifierName() const
+ {
+ return "gray";
+ }
+
+ BColorModifier_invert::~BColorModifier_invert()
+ {
+ }
+
+ bool BColorModifier_invert::operator==(const BColorModifier& rCompare) const
+ {
+ return dynamic_cast< const BColorModifier_invert* >(&rCompare) != nullptr;
+ }
+
+ ::basegfx::BColor BColorModifier_invert::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ return ::basegfx::BColor(1.0 - aSourceColor.getRed(), 1.0 - aSourceColor.getGreen(), 1.0 - aSourceColor.getBlue());
+ }
+
+ OUString BColorModifier_invert::getModifierName() const
+ {
+ return "invert";
+ }
+
+ BColorModifier_luminance_to_alpha::~BColorModifier_luminance_to_alpha()
+ {
+ }
+
+ bool BColorModifier_luminance_to_alpha::operator==(const BColorModifier& rCompare) const
+ {
+ return dynamic_cast< const BColorModifier_luminance_to_alpha* >(&rCompare) != nullptr;
+ }
+
+ ::basegfx::BColor BColorModifier_luminance_to_alpha::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ const double fAlpha(1.0 - ((aSourceColor.getRed() * 0.2125) + (aSourceColor.getGreen() * 0.7154) + (aSourceColor.getBlue() * 0.0721)));
+
+ return ::basegfx::BColor(fAlpha, fAlpha, fAlpha);
+ }
+
+ OUString BColorModifier_luminance_to_alpha::getModifierName() const
+ {
+ return "luminance_to_alpha";
+ }
+
+ BColorModifier_replace::~BColorModifier_replace()
+ {
+ }
+
+ bool BColorModifier_replace::operator==(const BColorModifier& rCompare) const
+ {
+ const BColorModifier_replace* pCompare = dynamic_cast< const BColorModifier_replace* >(&rCompare);
+
+ if(!pCompare)
+ {
+ return false;
+ }
+
+ return getBColor() == pCompare->getBColor();
+ }
+
+ ::basegfx::BColor BColorModifier_replace::getModifiedColor(const ::basegfx::BColor& /*aSourceColor*/) const
+ {
+ return maBColor;
+ }
+
+ OUString BColorModifier_replace::getModifierName() const
+ {
+ return "replace";
+ }
+
+ BColorModifier_interpolate::~BColorModifier_interpolate()
+ {
+ }
+
+ bool BColorModifier_interpolate::operator==(const BColorModifier& rCompare) const
+ {
+ const BColorModifier_interpolate* pCompare = dynamic_cast< const BColorModifier_interpolate* >(&rCompare);
+
+ if(!pCompare)
+ {
+ return false;
+ }
+
+ return maBColor == pCompare->maBColor && mfValue == pCompare->mfValue;
+ }
+
+ ::basegfx::BColor BColorModifier_interpolate::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ return interpolate(maBColor, aSourceColor, mfValue);
+ }
+
+ OUString BColorModifier_interpolate::getModifierName() const
+ {
+ return "interpolate";
+ }
+
+ BColorModifier_matrix::~BColorModifier_matrix()
+ {
+ }
+
+ bool BColorModifier_matrix::operator==(const BColorModifier& rCompare) const
+ {
+ const BColorModifier_matrix* pCompare = dynamic_cast< const BColorModifier_matrix* >(&rCompare);
+
+ if(!pCompare)
+ {
+ return false;
+ }
+
+ return maVector == pCompare->maVector;
+ }
+
+ ::basegfx::BColor BColorModifier_matrix::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ if (maVector.size() != 20)
+ return aSourceColor;
+
+ const double aRed = maVector[0] * aSourceColor.getRed()
+ + maVector[1] * aSourceColor.getGreen()
+ + maVector[2] * aSourceColor.getBlue()
+ + maVector[3] * 1.0
+ + maVector[4];
+ const double aGreen = maVector[5] * aSourceColor.getRed()
+ + maVector[6] * aSourceColor.getGreen()
+ + maVector[7] * aSourceColor.getBlue()
+ + maVector[8] * 1.0
+ + maVector[9];
+ const double aBlue = maVector[10] * aSourceColor.getRed()
+ + maVector[11] * aSourceColor.getGreen()
+ + maVector[12] * aSourceColor.getBlue()
+ + maVector[13] * 1.0
+ + maVector[14];
+ /*TODO: add support for alpha
+ const double aAlpha = maVector[15] * aSourceColor.getRed()
+ + maVector[16] * aSourceColor.getGreen()
+ + maVector[17] * aSourceColor.getBlue()
+ + maVector[18] * 1.0
+ + maVector[19]);
+ */
+
+ return ::basegfx::BColor(
+ std::clamp(aRed, 0.0, 1.0),
+ std::clamp(aGreen, 0.0, 1.0),
+ std::clamp(aBlue, 0.0, 1.0));
+ }
+
+ OUString BColorModifier_matrix::getModifierName() const
+ {
+ return "matrix";
+ }
+
+ BColorModifier_saturate::BColorModifier_saturate(double fValue)
+ {
+ maSatMatrix.set(0, 0, 0.213 + 0.787 * fValue);
+ maSatMatrix.set(0, 1, 0.715 - 0.715 * fValue);
+ maSatMatrix.set(0, 2, 0.072 - 0.072 * fValue);
+ maSatMatrix.set(1, 0, 0.213 - 0.213 * fValue);
+ maSatMatrix.set(1, 1, 0.715 + 0.285 * fValue);
+ maSatMatrix.set(1, 2, 0.072 - 0.072 * fValue);
+ maSatMatrix.set(2, 0, 0.213 - 0.213 * fValue);
+ maSatMatrix.set(2, 1, 0.715 - 0.715 * fValue);
+ maSatMatrix.set(2, 2, 0.072 + 0.928 * fValue);
+ }
+
+ BColorModifier_saturate::~BColorModifier_saturate()
+ {
+ }
+
+ bool BColorModifier_saturate::operator==(const BColorModifier& rCompare) const
+ {
+ const BColorModifier_saturate* pCompare = dynamic_cast< const BColorModifier_saturate* >(&rCompare);
+
+ if(!pCompare)
+ {
+ return false;
+ }
+
+ return maSatMatrix == pCompare->maSatMatrix;
+ }
+
+ ::basegfx::BColor BColorModifier_saturate::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ basegfx::B3DHomMatrix aColorMatrix;
+ aColorMatrix.set(0, 0, aSourceColor.getRed());
+ aColorMatrix.set(1, 0, aSourceColor.getGreen());
+ aColorMatrix.set(2, 0, aSourceColor.getBlue());
+
+ aColorMatrix = maSatMatrix * aColorMatrix;
+ return ::basegfx::BColor(aColorMatrix.get(0, 0), aColorMatrix.get(1, 0), aColorMatrix.get(2, 0));
+ }
+
+ OUString BColorModifier_saturate::getModifierName() const
+ {
+ return "saturate";
+ }
+
+ BColorModifier_hueRotate::BColorModifier_hueRotate(double fRad)
+ {
+ const double fCos = cos(fRad);
+ const double fSin = sin(fRad);
+
+ maHueMatrix.set(0, 0, 0.213 + fCos * 0.787 - fSin * 0.213);
+ maHueMatrix.set(0, 1, 0.715 - fCos * 0.715 - fSin * 0.715);
+ maHueMatrix.set(0, 2, 0.072 - fCos * 0.072 + fSin * 0.928);
+ maHueMatrix.set(1, 0, 0.213 - fCos * 0.213 + fSin * 0.143);
+ maHueMatrix.set(1, 1, 0.715 + fCos * 0.285 + fSin * 0.140);
+ maHueMatrix.set(1, 2, 0.072 - fCos * 0.072 - fSin * 0.283);
+ maHueMatrix.set(2, 0, 0.213 - fCos * 0.213 - fSin * 0.787);
+ maHueMatrix.set(2, 1, 0.715 - fCos * 0.715 + fSin * 0.715);
+ maHueMatrix.set(2, 2, 0.072 + fCos * 0.928 + fSin * 0.072);
+ }
+
+ BColorModifier_hueRotate::~BColorModifier_hueRotate()
+ {
+ }
+
+ bool BColorModifier_hueRotate::operator==(const BColorModifier& rCompare) const
+ {
+ const BColorModifier_hueRotate* pCompare = dynamic_cast< const BColorModifier_hueRotate* >(&rCompare);
+
+ if(!pCompare)
+ {
+ return false;
+ }
+
+ return maHueMatrix == pCompare->maHueMatrix;
+ }
+
+ ::basegfx::BColor BColorModifier_hueRotate::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ basegfx::B3DHomMatrix aColorMatrix;
+ aColorMatrix.set(0, 0, aSourceColor.getRed());
+ aColorMatrix.set(1, 0, aSourceColor.getGreen());
+ aColorMatrix.set(2, 0, aSourceColor.getBlue());
+
+ aColorMatrix = maHueMatrix * aColorMatrix;
+ return ::basegfx::BColor(
+ std::clamp(aColorMatrix.get(0, 0), 0.0, 1.0),
+ std::clamp(aColorMatrix.get(1, 0), 0.0, 1.0),
+ std::clamp(aColorMatrix.get(2, 0), 0.0, 1.0));
+ }
+
+ OUString BColorModifier_hueRotate::getModifierName() const
+ {
+ return "hueRotate";
+ }
+
+ BColorModifier_black_and_white::~BColorModifier_black_and_white()
+ {
+ }
+
+ bool BColorModifier_black_and_white::operator==(const BColorModifier& rCompare) const
+ {
+ const BColorModifier_black_and_white* pCompare = dynamic_cast< const BColorModifier_black_and_white* >(&rCompare);
+
+ if(!pCompare)
+ {
+ return false;
+ }
+
+ return mfValue == pCompare->mfValue;
+ }
+
+ ::basegfx::BColor BColorModifier_black_and_white::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ const double fLuminance(aSourceColor.luminance());
+
+ if(fLuminance < mfValue)
+ {
+ return ::basegfx::BColor::getEmptyBColor();
+ }
+ else
+ {
+ return ::basegfx::BColor(1.0, 1.0, 1.0);
+ }
+ }
+
+ OUString BColorModifier_black_and_white::getModifierName() const
+ {
+ return "black_and_white";
+ }
+
+ BColorModifier_gamma::BColorModifier_gamma(double fValue)
+ : mfValue(fValue),
+ mfInvValue(fValue),
+ mbUseIt(!basegfx::fTools::equal(fValue, 1.0) && basegfx::fTools::more(fValue, 0.0) && basegfx::fTools::lessOrEqual(fValue, 10.0))
+ {
+ if(mbUseIt)
+ {
+ mfInvValue = 1.0 / mfValue;
+ }
+ }
+
+ BColorModifier_gamma::~BColorModifier_gamma()
+ {
+ }
+
+ bool BColorModifier_gamma::operator==(const BColorModifier& rCompare) const
+ {
+ const BColorModifier_gamma* pCompare = dynamic_cast< const BColorModifier_gamma* >(&rCompare);
+
+ if(!pCompare)
+ {
+ return false;
+ }
+
+ // getValue is sufficient, mfInvValue and mbUseIt are only helper values
+ return mfValue == pCompare->mfValue;
+ }
+
+ ::basegfx::BColor BColorModifier_gamma::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ if(mbUseIt)
+ {
+ ::basegfx::BColor aRetval(
+ pow(aSourceColor.getRed(), mfInvValue),
+ pow(aSourceColor.getGreen(), mfInvValue),
+ pow(aSourceColor.getBlue(), mfInvValue));
+
+ aRetval.clamp();
+ return aRetval;
+ }
+ else
+ {
+ return aSourceColor;
+ }
+ }
+
+ OUString BColorModifier_gamma::getModifierName() const
+ {
+ return "gamma";
+ }
+
+ BColorModifier_RGBLuminanceContrast::BColorModifier_RGBLuminanceContrast(double fRed, double fGreen, double fBlue, double fLuminance, double fContrast)
+ : mfRed(std::clamp(fRed, -1.0, 1.0)),
+ mfGreen(std::clamp(fGreen, -1.0, 1.0)),
+ mfBlue(std::clamp(fBlue, -1.0, 1.0)),
+ mfLuminance(std::clamp(fLuminance, -1.0, 1.0)),
+ mfContrast(std::clamp(fContrast, -1.0, 1.0)),
+ mfContrastOff(1.0),
+ mfRedOff(0.0),
+ mfGreenOff(0.0),
+ mfBlueOff(0.0),
+ mbUseIt(false)
+ {
+ if(basegfx::fTools::equalZero(mfRed)
+ && basegfx::fTools::equalZero(mfGreen)
+ && basegfx::fTools::equalZero(mfBlue)
+ && basegfx::fTools::equalZero(mfLuminance)
+ && basegfx::fTools::equalZero(mfContrast))
+ return;
+
+ // calculate slope
+ if(mfContrast >= 0.0)
+ {
+ mfContrastOff = 128.0 / (128.0 - (mfContrast * 127.0));
+ }
+ else
+ {
+ mfContrastOff = ( 128.0 + (mfContrast * 127.0)) / 128.0;
+ }
+
+ // calculate unified contrast offset
+ const double fPreparedContrastOff((128.0 - mfContrastOff * 128.0) / 255.0);
+ const double fCombinedOffset(mfLuminance + fPreparedContrastOff);
+
+ // set full offsets
+ mfRedOff = mfRed + fCombinedOffset;
+ mfGreenOff = mfGreen + fCombinedOffset;
+ mfBlueOff = mfBlue + fCombinedOffset;
+
+ mbUseIt = true;
+ }
+
+ BColorModifier_RGBLuminanceContrast::~BColorModifier_RGBLuminanceContrast()
+ {
+ }
+
+ bool BColorModifier_RGBLuminanceContrast::operator==(const BColorModifier& rCompare) const
+ {
+ const BColorModifier_RGBLuminanceContrast* pCompare = dynamic_cast< const BColorModifier_RGBLuminanceContrast* >(&rCompare);
+
+ if(!pCompare)
+ {
+ return false;
+ }
+
+ // no need to compare other values, these are just helpers
+ return mfRed == pCompare->mfRed
+ && mfGreen == pCompare->mfGreen
+ && mfBlue == pCompare->mfBlue
+ && mfLuminance == pCompare->mfLuminance
+ && mfContrast == pCompare->mfContrast;
+ }
+
+ ::basegfx::BColor BColorModifier_RGBLuminanceContrast::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ if(mbUseIt)
+ {
+ return basegfx::BColor(
+ std::clamp(aSourceColor.getRed() * mfContrastOff + mfRedOff, 0.0, 1.0),
+ std::clamp(aSourceColor.getGreen() * mfContrastOff + mfGreenOff, 0.0, 1.0),
+ std::clamp(aSourceColor.getBlue() * mfContrastOff + mfBlueOff, 0.0, 1.0));
+ }
+ else
+ {
+ return aSourceColor;
+ }
+ }
+
+ OUString BColorModifier_RGBLuminanceContrast::getModifierName() const
+ {
+ return "RGBLuminanceContrast";
+ }
+
+ BColorModifier_randomize::BColorModifier_randomize(double fRandomPart)
+ : mfRandomPart(fRandomPart)
+ {
+ }
+
+ BColorModifier_randomize::~BColorModifier_randomize()
+ {
+ }
+
+ // compare operator
+ bool BColorModifier_randomize::operator==(const BColorModifier& rCompare) const
+ {
+ const BColorModifier_randomize* pCompare = dynamic_cast< const BColorModifier_randomize* >(&rCompare);
+
+ if(!pCompare)
+ {
+ return false;
+ }
+
+ return mfRandomPart == pCompare->mfRandomPart;
+ }
+
+ // compute modified color
+ ::basegfx::BColor BColorModifier_randomize::getModifiedColor(const ::basegfx::BColor& aSourceColor) const
+ {
+ if(0.0 >= mfRandomPart)
+ {
+ // no randomizing, use orig color
+ return aSourceColor;
+ }
+
+ if(1.0 <= mfRandomPart)
+ {
+ // full randomized color
+ return basegfx::BColor(
+ comphelper::rng::uniform_real_distribution(0.0, nextafter(1.0, DBL_MAX)),
+ comphelper::rng::uniform_real_distribution(0.0, nextafter(1.0, DBL_MAX)),
+ comphelper::rng::uniform_real_distribution(0.0, nextafter(1.0, DBL_MAX)));
+ }
+
+ // mixed color
+ const double fMulA(1.0 - mfRandomPart);
+ return basegfx::BColor(
+ aSourceColor.getRed() * fMulA +
+ comphelper::rng::uniform_real_distribution(0.0, nextafter(mfRandomPart, DBL_MAX)),
+ aSourceColor.getGreen() * fMulA +
+ comphelper::rng::uniform_real_distribution(0.0, nextafter(mfRandomPart, DBL_MAX)),
+ aSourceColor.getBlue() * fMulA +
+ comphelper::rng::uniform_real_distribution(0.0, nextafter(mfRandomPart, DBL_MAX)));
+ }
+
+ OUString BColorModifier_randomize::getModifierName() const
+ {
+ return "randomize";
+ }
+
+ ::basegfx::BColor BColorModifierStack::getModifiedColor(const ::basegfx::BColor& rSource) const
+ {
+ if(maBColorModifiers.empty())
+ {
+ return rSource;
+ }
+
+ ::basegfx::BColor aRetval(rSource);
+
+ for(sal_uInt32 a(maBColorModifiers.size()); a;)
+ {
+ a--;
+ aRetval = maBColorModifiers[a]->getModifiedColor(aRetval);
+ }
+
+ return aRetval;
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/color/bcolortools.cxx b/basegfx/source/color/bcolortools.cxx
new file mode 100644
index 0000000000..160b483289
--- /dev/null
+++ b/basegfx/source/color/bcolortools.cxx
@@ -0,0 +1,195 @@
+/* -*- 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 <basegfx/numeric/ftools.hxx>
+#include <basegfx/color/bcolor.hxx>
+#include <basegfx/color/bcolortools.hxx>
+#include <rtl/math.hxx>
+
+namespace basegfx::utils
+{
+ BColor rgb2hsl(const BColor& rRGBColor)
+ {
+ const double r=rRGBColor.getRed(), g=rRGBColor.getGreen(), b=rRGBColor.getBlue();
+ const double minVal = std::min( std::min( r, g ), b );
+ const double maxVal = std::max( std::max( r, g ), b );
+ const double d = maxVal - minVal;
+
+ double h=0, s=0, l=0;
+
+ l = (maxVal + minVal) / 2.0;
+
+ if( ::basegfx::fTools::equalZero(d) )
+ {
+ s = h = 0; // hue undefined (achromatic case)
+ }
+ else
+ {
+ s = l > 0.5 ? d/(2.0-maxVal-minVal) :
+ d/(maxVal + minVal);
+
+ if( rtl::math::approxEqual(r, maxVal) )
+ h = (g - b)/d;
+ else if( rtl::math::approxEqual(g, maxVal) )
+ h = 2.0 + (b - r)/d;
+ else
+ h = 4.0 + (r - g)/d;
+
+ h *= 60.0;
+
+ if( h < 0.0 )
+ h += 360.0;
+ }
+
+ return BColor(h,s,l);
+ }
+
+ static double hsl2rgbHelper( double nValue1, double nValue2, double nHue )
+ {
+ // clamp hue to [0,360]
+ nHue = fmod( nHue, 360.0 );
+
+ // cope with wrap-arounds
+ if( nHue < 0.0 )
+ nHue += 360.0;
+
+ if( nHue < 60.0 )
+ return nValue1 + (nValue2 - nValue1)*nHue/60.0;
+ else if( nHue < 180.0 )
+ return nValue2;
+ else if( nHue < 240.0 )
+ return nValue1 + (nValue2 - nValue1)*(240.0 - nHue)/60.0;
+ else
+ return nValue1;
+ }
+
+ BColor hsl2rgb(const BColor& rHSLColor)
+ {
+ const double h=rHSLColor.getRed(), s=rHSLColor.getGreen(), l=rHSLColor.getBlue();
+
+ if( fTools::equalZero(s) )
+ return BColor(l, l, l ); // achromatic case
+
+ const double nVal1( l <= 0.5 ? l*(1.0 + s) : l + s - l*s );
+ const double nVal2( 2.0*l - nVal1 );
+
+ return BColor(
+ hsl2rgbHelper(nVal2,
+ nVal1,
+ h + 120.0),
+ hsl2rgbHelper(nVal2,
+ nVal1,
+ h),
+ hsl2rgbHelper(nVal2,
+ nVal1,
+ h - 120.0) );
+ }
+
+ BColor rgb2hsv(const BColor& rRGBColor)
+ {
+ const double r=rRGBColor.getRed(), g=rRGBColor.getGreen(), b=rRGBColor.getBlue();
+ const double maxVal = std::max(std::max(r,g),b);
+ const double minVal = std::min(std::min(r,g),b);
+ const double delta = maxVal-minVal;
+
+ double h=0, s=0, v=0;
+
+ v = maxVal;
+ if( fTools::equalZero(v) )
+ s = 0;
+ else
+ s = delta / v;
+
+ if( !fTools::equalZero(s) )
+ {
+ if( rtl::math::approxEqual(maxVal, r) )
+ {
+ h = (g - b) / delta;
+ }
+ else if( rtl::math::approxEqual(maxVal, g) )
+ {
+ h = 2.0 + (b - r) / delta;
+ }
+ else
+ {
+ h = 4.0 + (r - g) / delta;
+ }
+
+ h *= 60.0;
+
+ if( h < 0 )
+ h += 360;
+ }
+
+ return BColor(h,s,v);
+ }
+
+ BColor hsv2rgb(const BColor& rHSVColor)
+ {
+ double h=rHSVColor.getRed();
+ const double s=rHSVColor.getGreen(), v=rHSVColor.getBlue();
+
+ if( fTools::equalZero(s) )
+ {
+ // achromatic case: no hue.
+ return BColor(v,v,v);
+ }
+ else
+ {
+ if( fTools::equal(h, 360.0) )
+ h = 0; // 360 degrees is equivalent to 0 degrees
+
+ h /= 60.0;
+ const sal_Int32 intval = static_cast< sal_Int32 >( h );
+ const double f = h - intval;
+ const double p = v*(1.0-s);
+ const double q = v*(1.0-(s*f));
+ const double t = v*(1.0-(s*(1.0-f)));
+
+ /* which hue area? */
+ switch( intval )
+ {
+ case 0:
+ return BColor(v,t,p);
+
+ case 1:
+ return BColor(q,v,p);
+
+ case 2:
+ return BColor(p,v,t);
+
+ case 3:
+ return BColor(p,q,v);
+
+ case 4:
+ return BColor(t,p,v);
+
+ case 5:
+ return BColor(v,p,q);
+
+ default:
+ // hue overflow
+ return BColor();
+ }
+ }
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/curve/b2dbeziertools.cxx b/basegfx/source/curve/b2dbeziertools.cxx
new file mode 100644
index 0000000000..8fd4c4b84c
--- /dev/null
+++ b/basegfx/source/curve/b2dbeziertools.cxx
@@ -0,0 +1,117 @@
+/* -*- 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 <basegfx/curve/b2dbeziertools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <algorithm>
+
+namespace basegfx
+{
+ B2DCubicBezierHelper::B2DCubicBezierHelper(const B2DCubicBezier& rBase, sal_uInt32 nDivisions)
+ : mnEdgeCount(0)
+ {
+ const bool bIsBezier(rBase.isBezier());
+
+ if(bIsBezier)
+ {
+ // check nDivisions; at least one is needed, but also prevent too big values
+ if(nDivisions < 1)
+ {
+ nDivisions = 1;
+ }
+ else if(nDivisions > 1000)
+ {
+ nDivisions = 1000;
+ }
+
+ // set nEdgeCount
+ mnEdgeCount = nDivisions + 1;
+
+ // fill in maLengthArray
+ maLengthArray.clear();
+ maLengthArray.reserve(mnEdgeCount);
+ B2DPoint aCurrent(rBase.getStartPoint());
+ double fLength(0.0);
+
+ for(sal_uInt32 a(1);;)
+ {
+ const B2DPoint aNext(rBase.interpolatePoint(static_cast<double>(a) / static_cast<double>(mnEdgeCount)));
+ const B2DVector aEdge(aNext - aCurrent);
+
+ fLength += aEdge.getLength();
+ maLengthArray.push_back(fLength);
+
+ if(++a < mnEdgeCount)
+ {
+ aCurrent = aNext;
+ }
+ else
+ {
+ const B2DPoint& aLastNext(rBase.getEndPoint());
+ const B2DVector aLastEdge(aLastNext - aNext);
+
+ fLength += aLastEdge.getLength();
+ maLengthArray.push_back(fLength);
+ break;
+ }
+ }
+ }
+ else
+ {
+ maLengthArray.clear();
+ maLengthArray.push_back(rBase.getEdgeLength());
+ mnEdgeCount = 1;
+ }
+ }
+
+ double B2DCubicBezierHelper::distanceToRelative(double fDistance) const
+ {
+ if(fDistance <= 0.0)
+ {
+ return 0.0;
+ }
+
+ const double fLength(getLength());
+
+ if(fTools::moreOrEqual(fDistance, fLength))
+ {
+ return 1.0;
+ }
+
+ // fDistance is in ]0.0 .. fLength[
+
+ if(mnEdgeCount == 1)
+ {
+ // not a bezier, linear edge
+ return fDistance / fLength;
+ }
+
+ // it is a bezier
+ std::vector< double >::const_iterator aIter = std::lower_bound(maLengthArray.begin(), maLengthArray.end(), fDistance);
+ const sal_uInt32 nIndex(aIter - maLengthArray.begin());
+ const double fHighBound(maLengthArray[nIndex]);
+ const double fLowBound(nIndex ? maLengthArray[nIndex - 1] : 0.0);
+ const double fLinearInterpolatedLength((fDistance - fLowBound) / (fHighBound - fLowBound));
+
+ return (static_cast< double >(nIndex) + fLinearInterpolatedLength) / static_cast< double >(mnEdgeCount);
+ }
+
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/curve/b2dcubicbezier.cxx b/basegfx/source/curve/b2dcubicbezier.cxx
new file mode 100644
index 0000000000..d33cd82b19
--- /dev/null
+++ b/basegfx/source/curve/b2dcubicbezier.cxx
@@ -0,0 +1,1055 @@
+/* -*- 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 <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+
+#include <osl/diagnose.h>
+
+#include <limits>
+
+// #i37443#
+#define FACTOR_FOR_UNSHARPEN (1.6)
+#ifdef DBG_UTIL
+const double fMultFactUnsharpen = FACTOR_FOR_UNSHARPEN;
+#endif
+
+namespace basegfx
+{
+ namespace
+ {
+ void ImpSubDivAngle(
+ const B2DPoint& rfPA, // start point
+ const B2DPoint& rfEA, // edge on A
+ const B2DPoint& rfEB, // edge on B
+ const B2DPoint& rfPB, // end point
+ B2DPolygon& rTarget, // target polygon
+ double fAngleBound, // angle bound in [0.0 .. 2PI]
+ bool bAllowUnsharpen, // #i37443# allow the criteria to get unsharp in recursions
+ sal_uInt16 nMaxRecursionDepth) // endless loop protection
+ {
+ if(nMaxRecursionDepth)
+ {
+ // do angle test
+ B2DVector aLeft(rfEA - rfPA);
+ B2DVector aRight(rfEB - rfPB);
+
+ // #i72104#
+ if(aLeft.equalZero())
+ {
+ aLeft = rfEB - rfPA;
+ }
+
+ if(aRight.equalZero())
+ {
+ aRight = rfEA - rfPB;
+ }
+
+ const double fCurrentAngle(aLeft.angle(aRight));
+
+ if(fabs(fCurrentAngle) > (M_PI - fAngleBound))
+ {
+ // end recursion
+ nMaxRecursionDepth = 0;
+ }
+ else
+ {
+ if(bAllowUnsharpen)
+ {
+ // #i37443# unsharpen criteria
+#ifdef DBG_UTIL
+ fAngleBound *= fMultFactUnsharpen;
+#else
+ fAngleBound *= FACTOR_FOR_UNSHARPEN;
+#endif
+ }
+ }
+ }
+
+ if(nMaxRecursionDepth)
+ {
+ // divide at 0.5
+ const B2DPoint aS1L(average(rfPA, rfEA));
+ const B2DPoint aS1C(average(rfEA, rfEB));
+ const B2DPoint aS1R(average(rfEB, rfPB));
+ const B2DPoint aS2L(average(aS1L, aS1C));
+ const B2DPoint aS2R(average(aS1C, aS1R));
+ const B2DPoint aS3C(average(aS2L, aS2R));
+
+ // left recursion
+ ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1);
+
+ // right recursion
+ ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1);
+ }
+ else
+ {
+ rTarget.append(rfPB);
+ }
+ }
+
+ void ImpSubDivAngleStart(
+ const B2DPoint& rfPA, // start point
+ const B2DPoint& rfEA, // edge on A
+ const B2DPoint& rfEB, // edge on B
+ const B2DPoint& rfPB, // end point
+ B2DPolygon& rTarget, // target polygon
+ const double& rfAngleBound) // angle bound in [0.0 .. 2PI]
+ {
+ sal_uInt16 nMaxRecursionDepth(8);
+ const B2DVector aLeft(rfEA - rfPA);
+ const B2DVector aRight(rfEB - rfPB);
+ bool bLeftEqualZero(aLeft.equalZero());
+ bool bRightEqualZero(aRight.equalZero());
+ bool bAllParallel(false);
+
+ if(bLeftEqualZero && bRightEqualZero)
+ {
+ nMaxRecursionDepth = 0;
+ }
+ else
+ {
+ const B2DVector aBase(rfPB - rfPA);
+ const bool bBaseEqualZero(aBase.equalZero()); // #i72104#
+
+ if(!bBaseEqualZero)
+ {
+ const bool bLeftParallel(bLeftEqualZero || areParallel(aLeft, aBase));
+ const bool bRightParallel(bRightEqualZero || areParallel(aRight, aBase));
+
+ if(bLeftParallel && bRightParallel)
+ {
+ bAllParallel = true;
+
+ if(!bLeftEqualZero)
+ {
+ double fFactor;
+
+ if(fabs(aBase.getX()) > fabs(aBase.getY()))
+ {
+ fFactor = aLeft.getX() / aBase.getX();
+ }
+ else
+ {
+ fFactor = aLeft.getY() / aBase.getY();
+ }
+
+ if(fFactor >= 0.0 && fFactor <= 1.0)
+ {
+ bLeftEqualZero = true;
+ }
+ }
+
+ if(!bRightEqualZero)
+ {
+ double fFactor;
+
+ if(fabs(aBase.getX()) > fabs(aBase.getY()))
+ {
+ fFactor = aRight.getX() / -aBase.getX();
+ }
+ else
+ {
+ fFactor = aRight.getY() / -aBase.getY();
+ }
+
+ if(fFactor >= 0.0 && fFactor <= 1.0)
+ {
+ bRightEqualZero = true;
+ }
+ }
+
+ if(bLeftEqualZero && bRightEqualZero)
+ {
+ nMaxRecursionDepth = 0;
+ }
+ }
+ }
+ }
+
+ if(nMaxRecursionDepth)
+ {
+ // divide at 0.5 ad test both edges for angle criteria
+ const B2DPoint aS1L(average(rfPA, rfEA));
+ const B2DPoint aS1C(average(rfEA, rfEB));
+ const B2DPoint aS1R(average(rfEB, rfPB));
+ const B2DPoint aS2L(average(aS1L, aS1C));
+ const B2DPoint aS2R(average(aS1C, aS1R));
+ const B2DPoint aS3C(average(aS2L, aS2R));
+
+ // test left
+ bool bAngleIsSmallerLeft(bAllParallel && bLeftEqualZero);
+ if(!bAngleIsSmallerLeft)
+ {
+ const B2DVector aLeftLeft(bLeftEqualZero ? aS2L - aS1L : aS1L - rfPA); // #i72104#
+ const B2DVector aRightLeft(aS2L - aS3C);
+ const double fCurrentAngleLeft(aLeftLeft.angle(aRightLeft));
+ bAngleIsSmallerLeft = (fabs(fCurrentAngleLeft) > (M_PI - rfAngleBound));
+ }
+
+ // test right
+ bool bAngleIsSmallerRight(bAllParallel && bRightEqualZero);
+ if(!bAngleIsSmallerRight)
+ {
+ const B2DVector aLeftRight(aS2R - aS3C);
+ const B2DVector aRightRight(bRightEqualZero ? aS2R - aS1R : aS1R - rfPB); // #i72104#
+ const double fCurrentAngleRight(aLeftRight.angle(aRightRight));
+ bAngleIsSmallerRight = (fabs(fCurrentAngleRight) > (M_PI - rfAngleBound));
+ }
+
+ if(bAngleIsSmallerLeft && bAngleIsSmallerRight)
+ {
+ // no recursion necessary at all
+ nMaxRecursionDepth = 0;
+ }
+ else
+ {
+ // left
+ if(bAngleIsSmallerLeft)
+ {
+ rTarget.append(aS3C);
+ }
+ else
+ {
+ ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, rfAngleBound, true/*bAllowUnsharpen*/, nMaxRecursionDepth);
+ }
+
+ // right
+ if(bAngleIsSmallerRight)
+ {
+ rTarget.append(rfPB);
+ }
+ else
+ {
+ ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, rfAngleBound, true/*bAllowUnsharpen*/, nMaxRecursionDepth);
+ }
+ }
+ }
+
+ if(!nMaxRecursionDepth)
+ {
+ rTarget.append(rfPB);
+ }
+ }
+
+ void ImpSubDivDistance(
+ const B2DPoint& rfPA, // start point
+ const B2DPoint& rfEA, // edge on A
+ const B2DPoint& rfEB, // edge on B
+ const B2DPoint& rfPB, // end point
+ B2DPolygon& rTarget, // target polygon
+ double fDistanceBound2, // quadratic distance criteria
+ double fLastDistanceError2, // the last quadratic distance error
+ sal_uInt16 nMaxRecursionDepth) // endless loop protection
+ {
+ if(nMaxRecursionDepth)
+ {
+ // decide if another recursion is needed. If not, set
+ // nMaxRecursionDepth to zero
+
+ // Perform bezier flatness test (lecture notes from R. Schaback,
+ // Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
+
+ // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)||
+ // 0<=j<=n
+
+ // What is calculated here is an upper bound to the distance from
+ // a line through b_0 and b_3 (rfPA and P4 in our notation) and the
+ // curve. We can drop 0 and n from the running indices, since the
+ // argument of max becomes zero for those cases.
+ const double fJ1x(rfEA.getX() - rfPA.getX() - 1.0/3.0*(rfPB.getX() - rfPA.getX()));
+ const double fJ1y(rfEA.getY() - rfPA.getY() - 1.0/3.0*(rfPB.getY() - rfPA.getY()));
+ const double fJ2x(rfEB.getX() - rfPA.getX() - 2.0/3.0*(rfPB.getX() - rfPA.getX()));
+ const double fJ2y(rfEB.getY() - rfPA.getY() - 2.0/3.0*(rfPB.getY() - rfPA.getY()));
+ const double fDistanceError2(std::max(fJ1x*fJ1x + fJ1y*fJ1y, fJ2x*fJ2x + fJ2y*fJ2y));
+
+ // stop if error measure does not improve anymore. This is a
+ // safety guard against floating point inaccuracies.
+ // stop if distance from line is guaranteed to be bounded by d
+ const bool bFurtherDivision(fLastDistanceError2 > fDistanceError2 && fDistanceError2 >= fDistanceBound2);
+
+ if(bFurtherDivision)
+ {
+ // remember last error value
+ fLastDistanceError2 = fDistanceError2;
+ }
+ else
+ {
+ // stop recursion
+ nMaxRecursionDepth = 0;
+ }
+ }
+
+ if(nMaxRecursionDepth)
+ {
+ // divide at 0.5
+ const B2DPoint aS1L(average(rfPA, rfEA));
+ const B2DPoint aS1C(average(rfEA, rfEB));
+ const B2DPoint aS1R(average(rfEB, rfPB));
+ const B2DPoint aS2L(average(aS1L, aS1C));
+ const B2DPoint aS2R(average(aS1C, aS1R));
+ const B2DPoint aS3C(average(aS2L, aS2R));
+
+ // left recursion
+ ImpSubDivDistance(rfPA, aS1L, aS2L, aS3C, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1);
+
+ // right recursion
+ ImpSubDivDistance(aS3C, aS2R, aS1R, rfPB, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1);
+ }
+ else
+ {
+ rTarget.append(rfPB);
+ }
+ }
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+namespace basegfx
+{
+ B2DCubicBezier::B2DCubicBezier(const B2DCubicBezier&) = default;
+
+ B2DCubicBezier::B2DCubicBezier() = default;
+
+ B2DCubicBezier::B2DCubicBezier(const B2DPoint& rStart, const B2DPoint& rControlPointA, const B2DPoint& rControlPointB, const B2DPoint& rEnd)
+ : maStartPoint(rStart),
+ maEndPoint(rEnd),
+ maControlPointA(rControlPointA),
+ maControlPointB(rControlPointB)
+ {
+ }
+
+ // assignment operator
+ B2DCubicBezier& B2DCubicBezier::operator=(const B2DCubicBezier&) = default;
+
+ // compare operators
+ bool B2DCubicBezier::operator==(const B2DCubicBezier& rBezier) const
+ {
+ return (
+ maStartPoint == rBezier.maStartPoint
+ && maEndPoint == rBezier.maEndPoint
+ && maControlPointA == rBezier.maControlPointA
+ && maControlPointB == rBezier.maControlPointB
+ );
+ }
+
+ bool B2DCubicBezier::operator!=(const B2DCubicBezier& rBezier) const
+ {
+ return (
+ maStartPoint != rBezier.maStartPoint
+ || maEndPoint != rBezier.maEndPoint
+ || maControlPointA != rBezier.maControlPointA
+ || maControlPointB != rBezier.maControlPointB
+ );
+ }
+
+ bool B2DCubicBezier::equal(const B2DCubicBezier& rBezier) const
+ {
+ return (
+ maStartPoint.equal(rBezier.maStartPoint)
+ && maEndPoint.equal(rBezier.maEndPoint)
+ && maControlPointA.equal(rBezier.maControlPointA)
+ && maControlPointB.equal(rBezier.maControlPointB)
+ );
+ }
+
+ // test if vectors are used
+ bool B2DCubicBezier::isBezier() const
+ {
+ return maControlPointA != maStartPoint || maControlPointB != maEndPoint;
+ }
+
+ void B2DCubicBezier::testAndSolveTrivialBezier()
+ {
+ if(maControlPointA == maStartPoint && maControlPointB == maEndPoint)
+ return;
+
+ const B2DVector aEdge(maEndPoint - maStartPoint);
+
+ // controls parallel to edge can be trivial. No edge -> not parallel -> control can
+ // still not be trivial (e.g. ballon loop)
+ if(aEdge.equalZero())
+ return;
+
+ // get control vectors
+ const B2DVector aVecA(maControlPointA - maStartPoint);
+ const B2DVector aVecB(maControlPointB - maEndPoint);
+
+ // check if trivial per se
+ bool bAIsTrivial(aVecA.equalZero());
+ bool bBIsTrivial(aVecB.equalZero());
+
+ // #i102241# prepare inverse edge length to normalize cross values;
+ // else the small compare value used in fTools::equalZero
+ // will be length dependent and this detection will work as less
+ // precise as longer the edge is. In principle, the length of the control
+ // vector would need to be used too, but to be trivial it is assumed to
+ // be of roughly equal length to the edge, so edge length can be used
+ // for both. Only needed when one of both is not trivial per se.
+ const double fInverseEdgeLength(bAIsTrivial && bBIsTrivial
+ ? 1.0
+ : 1.0 / aEdge.getLength());
+
+ // if A is not zero, check if it could be
+ if(!bAIsTrivial)
+ {
+ // #i102241# parallel to edge? Check aVecA, aEdge. Use cross() which does what
+ // we need here with the precision we need
+ const double fCross(aVecA.cross(aEdge) * fInverseEdgeLength);
+
+ if(fTools::equalZero(fCross))
+ {
+ // get scale to edge. Use bigger distance for numeric quality
+ const double fScale(fabs(aEdge.getX()) > fabs(aEdge.getY())
+ ? aVecA.getX() / aEdge.getX()
+ : aVecA.getY() / aEdge.getY());
+
+ // relative end point of vector in edge range?
+ if (fTools::betweenOrEqualEither(fScale, 0.0, 1.0))
+ {
+ bAIsTrivial = true;
+ }
+ }
+ }
+
+ // if B is not zero, check if it could be, but only if A is already trivial;
+ // else solve to trivial will not be possible for whole edge
+ if(bAIsTrivial && !bBIsTrivial)
+ {
+ // parallel to edge? Check aVecB, aEdge
+ const double fCross(aVecB.cross(aEdge) * fInverseEdgeLength);
+
+ if(fTools::equalZero(fCross))
+ {
+ // get scale to edge. Use bigger distance for numeric quality
+ const double fScale(fabs(aEdge.getX()) > fabs(aEdge.getY())
+ ? aVecB.getX() / aEdge.getX()
+ : aVecB.getY() / aEdge.getY());
+
+ // end point of vector in edge range? Caution: controlB is directed AGAINST edge
+ if (fTools::betweenOrEqualEither(fScale, -1.0, 0.0))
+ {
+ bBIsTrivial = true;
+ }
+ }
+ }
+
+ // if both are/can be reduced, do it.
+ // Not possible if only one is/can be reduced (!)
+ if(bAIsTrivial && bBIsTrivial)
+ {
+ maControlPointA = maStartPoint;
+ maControlPointB = maEndPoint;
+ }
+ }
+
+ namespace {
+ double impGetLength(const B2DCubicBezier& rEdge, double fDeviation, sal_uInt32 nRecursionWatch)
+ {
+ const double fEdgeLength(rEdge.getEdgeLength());
+ const double fControlPolygonLength(rEdge.getControlPolygonLength());
+ const double fCurrentDeviation(fTools::equalZero(fControlPolygonLength) ? 0.0 : 1.0 - (fEdgeLength / fControlPolygonLength));
+
+ if(!nRecursionWatch || fTools:: lessOrEqual(fCurrentDeviation, fDeviation))
+ {
+ return (fEdgeLength + fControlPolygonLength) * 0.5;
+ }
+ else
+ {
+ B2DCubicBezier aLeft, aRight;
+ const double fNewDeviation(fDeviation * 0.5);
+ const sal_uInt32 nNewRecursionWatch(nRecursionWatch - 1);
+
+ rEdge.split(0.5, &aLeft, &aRight);
+
+ return impGetLength(aLeft, fNewDeviation, nNewRecursionWatch)
+ + impGetLength(aRight, fNewDeviation, nNewRecursionWatch);
+ }
+ }
+ }
+
+ double B2DCubicBezier::getLength(double fDeviation) const
+ {
+ if(isBezier())
+ {
+ if(fDeviation < 0.00000001)
+ {
+ fDeviation = 0.00000001;
+ }
+
+ return impGetLength(*this, fDeviation, 6);
+ }
+ else
+ {
+ return B2DVector(getEndPoint() - getStartPoint()).getLength();
+ }
+ }
+
+ double B2DCubicBezier::getEdgeLength() const
+ {
+ const B2DVector aEdge(maEndPoint - maStartPoint);
+ return aEdge.getLength();
+ }
+
+ double B2DCubicBezier::getControlPolygonLength() const
+ {
+ const B2DVector aVectorA(maControlPointA - maStartPoint);
+ const B2DVector aVectorB(maEndPoint - maControlPointB);
+
+ if(!aVectorA.equalZero() || !aVectorB.equalZero())
+ {
+ const B2DVector aTop(maControlPointB - maControlPointA);
+ return (aVectorA.getLength() + aVectorB.getLength() + aTop.getLength());
+ }
+ else
+ {
+ return getEdgeLength();
+ }
+ }
+
+ void B2DCubicBezier::adaptiveSubdivideByAngle(B2DPolygon& rTarget, double fAngleBound) const
+ {
+ if(isBezier())
+ {
+ // use support method #i37443# and allow unsharpen the criteria
+ ImpSubDivAngleStart(maStartPoint, maControlPointA, maControlPointB, maEndPoint, rTarget,
+ deg2rad(fAngleBound));
+ }
+ else
+ {
+ rTarget.append(getEndPoint());
+ }
+ }
+
+ B2DVector B2DCubicBezier::getTangent(double t) const
+ {
+ if(fTools::lessOrEqual(t, 0.0))
+ {
+ // tangent in start point
+ B2DVector aTangent(getControlPointA() - getStartPoint());
+
+ if(!aTangent.equalZero())
+ {
+ return aTangent;
+ }
+
+ // start point and control vector are the same, fallback
+ // to implicit start vector to control point B
+ aTangent = (getControlPointB() - getStartPoint()) * 0.3;
+
+ if(!aTangent.equalZero())
+ {
+ return aTangent;
+ }
+
+ // not a bezier at all, return edge vector
+ return (getEndPoint() - getStartPoint()) * 0.3;
+ }
+ else if(fTools::moreOrEqual(t, 1.0))
+ {
+ // tangent in end point
+ B2DVector aTangent(getEndPoint() - getControlPointB());
+
+ if(!aTangent.equalZero())
+ {
+ return aTangent;
+ }
+
+ // end point and control vector are the same, fallback
+ // to implicit start vector from control point A
+ aTangent = (getEndPoint() - getControlPointA()) * 0.3;
+
+ if(!aTangent.equalZero())
+ {
+ return aTangent;
+ }
+
+ // not a bezier at all, return edge vector
+ return (getEndPoint() - getStartPoint()) * 0.3;
+ }
+ else
+ {
+ // t is in ]0.0 .. 1.0[. Split and extract
+ B2DCubicBezier aRight;
+ split(t, nullptr, &aRight);
+
+ return aRight.getControlPointA() - aRight.getStartPoint();
+ }
+ }
+
+ // #i37443# adaptive subdivide by nCount subdivisions
+ void B2DCubicBezier::adaptiveSubdivideByCount(B2DPolygon& rTarget, sal_uInt32 nCount) const
+ {
+ const double fLenFact(1.0 / static_cast< double >(nCount + 1));
+
+ for(sal_uInt32 a(1); a <= nCount; a++)
+ {
+ const double fPos(static_cast< double >(a) * fLenFact);
+ rTarget.append(interpolatePoint(fPos));
+ }
+
+ rTarget.append(getEndPoint());
+ }
+
+ // adaptive subdivide by distance
+ void B2DCubicBezier::adaptiveSubdivideByDistance(B2DPolygon& rTarget, double fDistanceBound, int nRecurseLimit) const
+ {
+ if(isBezier())
+ {
+ ImpSubDivDistance(maStartPoint, maControlPointA, maControlPointB, maEndPoint, rTarget,
+ fDistanceBound * fDistanceBound, std::numeric_limits<double>::max(), nRecurseLimit);
+ }
+ else
+ {
+ rTarget.append(getEndPoint());
+ }
+ }
+
+ B2DPoint B2DCubicBezier::interpolatePoint(double t) const
+ {
+ OSL_ENSURE(t >= 0.0 && t <= 1.0, "B2DCubicBezier::interpolatePoint: Access out of range (!)");
+
+ if(isBezier())
+ {
+ const B2DPoint aS1L(interpolate(maStartPoint, maControlPointA, t));
+ const B2DPoint aS1C(interpolate(maControlPointA, maControlPointB, t));
+ const B2DPoint aS1R(interpolate(maControlPointB, maEndPoint, t));
+ const B2DPoint aS2L(interpolate(aS1L, aS1C, t));
+ const B2DPoint aS2R(interpolate(aS1C, aS1R, t));
+
+ return interpolate(aS2L, aS2R, t);
+ }
+ else
+ {
+ return interpolate(maStartPoint, maEndPoint, t);
+ }
+ }
+
+ double B2DCubicBezier::getSmallestDistancePointToBezierSegment(const B2DPoint& rTestPoint, double& rCut) const
+ {
+ const sal_uInt32 nInitialDivisions(3);
+ B2DPolygon aInitialPolygon;
+
+ // as start make a fix division, creates nInitialDivisions + 2 points
+ aInitialPolygon.append(getStartPoint());
+ adaptiveSubdivideByCount(aInitialPolygon, nInitialDivisions);
+
+ // now look for the closest point
+ const sal_uInt32 nPointCount(aInitialPolygon.count());
+ B2DVector aVector(rTestPoint - aInitialPolygon.getB2DPoint(0));
+ double fQuadDist(aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY());
+ double fNewQuadDist;
+ sal_uInt32 nSmallestIndex(0);
+
+ for(sal_uInt32 a(1); a < nPointCount; a++)
+ {
+ aVector = B2DVector(rTestPoint - aInitialPolygon.getB2DPoint(a));
+ fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY();
+
+ if(fNewQuadDist < fQuadDist)
+ {
+ fQuadDist = fNewQuadDist;
+ nSmallestIndex = a;
+ }
+ }
+
+ // look right and left for even smaller distances
+ double fStepValue(1.0 / static_cast<double>((nPointCount - 1) * 2)); // half the edge step width
+ double fPosition(static_cast<double>(nSmallestIndex) / static_cast<double>(nPointCount - 1));
+
+ while(true)
+ {
+ // test left
+ double fPosLeft(fPosition - fStepValue);
+
+ if(fPosLeft < 0.0)
+ {
+ fPosLeft = 0.0;
+ aVector = B2DVector(rTestPoint - maStartPoint);
+ }
+ else
+ {
+ aVector = B2DVector(rTestPoint - interpolatePoint(fPosLeft));
+ }
+
+ fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY();
+
+ if(fTools::less(fNewQuadDist, fQuadDist))
+ {
+ fQuadDist = fNewQuadDist;
+ fPosition = fPosLeft;
+ }
+ else
+ {
+ // test right
+ double fPosRight(fPosition + fStepValue);
+
+ if(fPosRight > 1.0)
+ {
+ fPosRight = 1.0;
+ aVector = B2DVector(rTestPoint - maEndPoint);
+ }
+ else
+ {
+ aVector = B2DVector(rTestPoint - interpolatePoint(fPosRight));
+ }
+
+ fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY();
+
+ if(fTools::less(fNewQuadDist, fQuadDist))
+ {
+ fQuadDist = fNewQuadDist;
+ fPosition = fPosRight;
+ }
+ else
+ {
+ // not less left or right, done
+ break;
+ }
+ }
+
+ if(fPosition == 0.0 || fPosition == 1.0)
+ {
+ // if we are completely left or right, we are done
+ break;
+ }
+
+ // prepare next step value
+ fStepValue /= 2.0;
+ }
+
+ rCut = fPosition;
+ return sqrt(fQuadDist);
+ }
+
+ void B2DCubicBezier::split(double t, B2DCubicBezier* pBezierA, B2DCubicBezier* pBezierB) const
+ {
+ OSL_ENSURE(t >= 0.0 && t <= 1.0, "B2DCubicBezier::split: Access out of range (!)");
+
+ if(!pBezierA && !pBezierB)
+ {
+ return;
+ }
+
+ if(isBezier())
+ {
+ const B2DPoint aS1L(interpolate(maStartPoint, maControlPointA, t));
+ const B2DPoint aS1C(interpolate(maControlPointA, maControlPointB, t));
+ const B2DPoint aS1R(interpolate(maControlPointB, maEndPoint, t));
+ const B2DPoint aS2L(interpolate(aS1L, aS1C, t));
+ const B2DPoint aS2R(interpolate(aS1C, aS1R, t));
+ const B2DPoint aS3C(interpolate(aS2L, aS2R, t));
+
+ if(pBezierA)
+ {
+ pBezierA->setStartPoint(maStartPoint);
+ pBezierA->setEndPoint(aS3C);
+ pBezierA->setControlPointA(aS1L);
+ pBezierA->setControlPointB(aS2L);
+ }
+
+ if(pBezierB)
+ {
+ pBezierB->setStartPoint(aS3C);
+ pBezierB->setEndPoint(maEndPoint);
+ pBezierB->setControlPointA(aS2R);
+ pBezierB->setControlPointB(aS1R);
+ }
+ }
+ else
+ {
+ const B2DPoint aSplit(interpolate(maStartPoint, maEndPoint, t));
+
+ if(pBezierA)
+ {
+ pBezierA->setStartPoint(maStartPoint);
+ pBezierA->setEndPoint(aSplit);
+ pBezierA->setControlPointA(maStartPoint);
+ pBezierA->setControlPointB(aSplit);
+ }
+
+ if(pBezierB)
+ {
+ pBezierB->setStartPoint(aSplit);
+ pBezierB->setEndPoint(maEndPoint);
+ pBezierB->setControlPointA(aSplit);
+ pBezierB->setControlPointB(maEndPoint);
+ }
+ }
+ }
+
+ B2DCubicBezier B2DCubicBezier::snippet(double fStart, double fEnd) const
+ {
+ B2DCubicBezier aRetval;
+
+ if(fTools::more(fStart, 1.0))
+ {
+ fStart = 1.0;
+ }
+ else if(fTools::less(fStart, 0.0))
+ {
+ fStart = 0.0;
+ }
+
+ if(fTools::more(fEnd, 1.0))
+ {
+ fEnd = 1.0;
+ }
+ else if(fTools::less(fEnd, 0.0))
+ {
+ fEnd = 0.0;
+ }
+
+ if(fEnd <= fStart)
+ {
+ // empty or NULL, create single point at center
+ const double fSplit((fEnd + fStart) * 0.5);
+ const B2DPoint aPoint(interpolate(getStartPoint(), getEndPoint(), fSplit));
+ aRetval.setStartPoint(aPoint);
+ aRetval.setEndPoint(aPoint);
+ aRetval.setControlPointA(aPoint);
+ aRetval.setControlPointB(aPoint);
+ }
+ else
+ {
+ if(isBezier())
+ {
+ // copy bezier; cut off right, then cut off left. Do not forget to
+ // adapt cut value when both cuts happen
+ const bool bEndIsOne(fTools::equal(fEnd, 1.0));
+ const bool bStartIsZero(fTools::equalZero(fStart));
+ aRetval = *this;
+
+ if(!bEndIsOne)
+ {
+ aRetval.split(fEnd, &aRetval, nullptr);
+
+ if(!bStartIsZero)
+ {
+ fStart /= fEnd;
+ }
+ }
+
+ if(!bStartIsZero)
+ {
+ aRetval.split(fStart, nullptr, &aRetval);
+ }
+ }
+ else
+ {
+ // no bezier, create simple edge
+ const B2DPoint aPointA(interpolate(getStartPoint(), getEndPoint(), fStart));
+ const B2DPoint aPointB(interpolate(getStartPoint(), getEndPoint(), fEnd));
+ aRetval.setStartPoint(aPointA);
+ aRetval.setEndPoint(aPointB);
+ aRetval.setControlPointA(aPointA);
+ aRetval.setControlPointB(aPointB);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DRange B2DCubicBezier::getRange() const
+ {
+ B2DRange aRetval(maStartPoint, maEndPoint);
+
+ aRetval.expand(maControlPointA);
+ aRetval.expand(maControlPointB);
+
+ return aRetval;
+ }
+
+ bool B2DCubicBezier::getMinimumExtremumPosition(double& rfResult) const
+ {
+ std::vector< double > aAllResults;
+
+ aAllResults.reserve(4);
+ getAllExtremumPositions(aAllResults);
+
+ const sal_uInt32 nCount(aAllResults.size());
+
+ if(!nCount)
+ {
+ return false;
+ }
+ else if(nCount == 1)
+ {
+ rfResult = aAllResults[0];
+ return true;
+ }
+ else
+ {
+ rfResult = *(std::min_element(aAllResults.begin(), aAllResults.end()));
+ return true;
+ }
+ }
+
+ namespace
+ {
+ void impCheckExtremumResult(double fCandidate, std::vector< double >& rResult)
+ {
+ // check for range ]0.0 .. 1.0[ with excluding 1.0 and 0.0 clearly
+ // by using the equalZero test, NOT ::more or ::less which will use the
+ // ApproxEqual() which is too exact here
+ if(fCandidate > 0.0 && !fTools::equalZero(fCandidate))
+ {
+ if(fCandidate < 1.0 && !fTools::equalZero(fCandidate - 1.0))
+ {
+ rResult.push_back(fCandidate);
+ }
+ }
+ }
+ }
+
+ void B2DCubicBezier::getAllExtremumPositions(std::vector< double >& rResults) const
+ {
+ rResults.clear();
+
+ // calculate the x-extrema parameters by zeroing first x-derivative
+ // of the cubic bezier's parametric formula, which results in a
+ // quadratic equation: dBezier/dt = t*t*fAX - 2*t*fBX + fCX
+ const B2DPoint aControlDiff( maControlPointA - maControlPointB );
+ double fCX = maControlPointA.getX() - maStartPoint.getX();
+ const double fBX = fCX + aControlDiff.getX();
+ const double fAX = 3 * aControlDiff.getX() + (maEndPoint.getX() - maStartPoint.getX());
+
+ if(fTools::equalZero(fCX))
+ {
+ // detect fCX equal zero and truncate to real zero value in that case
+ fCX = 0.0;
+ }
+
+ if( !fTools::equalZero(fAX) )
+ {
+ // derivative is polynomial of order 2 => use binomial formula
+ const double fD = fBX*fBX - fAX*fCX;
+ if( fD >= 0.0 )
+ {
+ const double fS = sqrt(fD);
+ // calculate both roots (avoiding a numerically unstable subtraction)
+ const double fQ = fBX + ((fBX >= 0) ? +fS : -fS);
+ impCheckExtremumResult(fQ / fAX, rResults);
+ if( !fTools::equalZero(fS) ) // ignore root multiplicity
+ impCheckExtremumResult(fCX / fQ, rResults);
+ }
+ }
+ else if( !fTools::equalZero(fBX) )
+ {
+ // derivative is polynomial of order 1 => one extrema
+ impCheckExtremumResult(fCX / (2 * fBX), rResults);
+ }
+
+ // calculate the y-extrema parameters by zeroing first y-derivative
+ double fCY = maControlPointA.getY() - maStartPoint.getY();
+ const double fBY = fCY + aControlDiff.getY();
+ const double fAY = 3 * aControlDiff.getY() + (maEndPoint.getY() - maStartPoint.getY());
+
+ if(fTools::equalZero(fCY))
+ {
+ // detect fCY equal zero and truncate to real zero value in that case
+ fCY = 0.0;
+ }
+
+ if( !fTools::equalZero(fAY) )
+ {
+ // derivative is polynomial of order 2 => use binomial formula
+ const double fD = fBY*fBY - fAY*fCY;
+ if( fD >= 0.0 )
+ {
+ const double fS = sqrt(fD);
+ // calculate both roots (avoiding a numerically unstable subtraction)
+ const double fQ = fBY + ((fBY >= 0) ? +fS : -fS);
+ impCheckExtremumResult(fQ / fAY, rResults);
+ if( !fTools::equalZero(fS) ) // ignore root multiplicity
+ impCheckExtremumResult(fCY / fQ, rResults);
+ }
+ }
+ else if( !fTools::equalZero(fBY) )
+ {
+ // derivative is polynomial of order 1 => one extrema
+ impCheckExtremumResult(fCY / (2 * fBY), rResults);
+ }
+ }
+
+ void B2DCubicBezier::transform(const basegfx::B2DHomMatrix& rMatrix)
+ {
+ if(rMatrix.isIdentity())
+ return;
+
+ if(maControlPointA == maStartPoint)
+ {
+ maControlPointA = maStartPoint = rMatrix * maStartPoint;
+ }
+ else
+ {
+ maStartPoint *= rMatrix;
+ maControlPointA *= rMatrix;
+ }
+
+ if(maControlPointB == maEndPoint)
+ {
+ maControlPointB = maEndPoint = rMatrix * maEndPoint;
+ }
+ else
+ {
+ maEndPoint *= rMatrix;
+ maControlPointB *= rMatrix;
+ }
+ }
+
+ void B2DCubicBezier::fround()
+ {
+ if(maControlPointA == maStartPoint)
+ {
+ maControlPointA = maStartPoint = basegfx::B2DPoint(
+ basegfx::fround(maStartPoint.getX()),
+ basegfx::fround(maStartPoint.getY()));
+ }
+ else
+ {
+ maStartPoint = basegfx::B2DPoint(
+ basegfx::fround(maStartPoint.getX()),
+ basegfx::fround(maStartPoint.getY()));
+ maControlPointA = basegfx::B2DPoint(
+ basegfx::fround(maControlPointA.getX()),
+ basegfx::fround(maControlPointA.getY()));
+ }
+
+ if(maControlPointB == maEndPoint)
+ {
+ maControlPointB = maEndPoint = basegfx::B2DPoint(
+ basegfx::fround(maEndPoint.getX()),
+ basegfx::fround(maEndPoint.getY()));
+ }
+ else
+ {
+ maEndPoint = basegfx::B2DPoint(
+ basegfx::fround(maEndPoint.getX()),
+ basegfx::fround(maEndPoint.getY()));
+ maControlPointB = basegfx::B2DPoint(
+ basegfx::fround(maControlPointB.getX()),
+ basegfx::fround(maControlPointB.getY()));
+ }
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/inc/stringconversiontools.hxx b/basegfx/source/inc/stringconversiontools.hxx
new file mode 100644
index 0000000000..797697d203
--- /dev/null
+++ b/basegfx/source/inc/stringconversiontools.hxx
@@ -0,0 +1,64 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+
+namespace basegfx::internal
+ {
+ void skipSpaces(sal_Int32& io_rPos,
+ std::u16string_view rStr,
+ const sal_Int32 nLen);
+
+ inline bool isOnNumberChar(const sal_Unicode aChar,
+ bool bSignAllowed)
+ {
+ const bool bPredicate( (u'0' <= aChar && u'9' >= aChar)
+ || (bSignAllowed && u'+' == aChar)
+ || (bSignAllowed && u'-' == aChar)
+ || (u'.' == aChar));
+
+ return bPredicate;
+ }
+
+ inline bool isOnNumberChar(std::u16string_view rStr,
+ const sal_Int32 nPos)
+ {
+ return isOnNumberChar(rStr[nPos], true/*bSignAllowed*/);
+ }
+
+ bool importDoubleAndSpaces(double& o_fRetval,
+ sal_Int32& io_rPos,
+ std::u16string_view rStr,
+ const sal_Int32 nLen );
+
+ bool importFlagAndSpaces(sal_Int32& o_nRetval,
+ sal_Int32& io_rPos,
+ std::u16string_view rStr,
+ const sal_Int32 nLen);
+
+} // namespace basegfx::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/matrix/b2dhommatrix.cxx b/basegfx/source/matrix/b2dhommatrix.cxx
new file mode 100644
index 0000000000..e4a9dda9e3
--- /dev/null
+++ b/basegfx/source/matrix/b2dhommatrix.cxx
@@ -0,0 +1,430 @@
+/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/hommatrixtemplate.hxx>
+#include <basegfx/tuple/b2dtuple.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <memory>
+
+namespace basegfx
+{
+ constexpr int RowSize = 3;
+
+ void B2DHomMatrix::set3x2(double f_0x0, double f_0x1, double f_0x2, double f_1x0, double f_1x1, double f_1x2)
+ {
+ mfValues[0][0] = f_0x0;
+ mfValues[0][1] = f_0x1;
+ mfValues[0][2] = f_0x2;
+ mfValues[1][0] = f_1x0;
+ mfValues[1][1] = f_1x1;
+ mfValues[1][2] = f_1x2;
+ }
+
+ bool B2DHomMatrix::isIdentity() const
+ {
+ for(sal_uInt16 a(0); a < RowSize - 1; a++)
+ {
+ for(sal_uInt16 b(0); b < RowSize; b++)
+ {
+ const double fDefault(internal::implGetDefaultValue(a, b));
+ const double fValueAB(get(a, b));
+
+ if(!::basegfx::fTools::equal(fDefault, fValueAB))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ void B2DHomMatrix::identity()
+ {
+ for(sal_uInt16 a(0); a < RowSize - 1; a++)
+ {
+ for(sal_uInt16 b(0); b < RowSize; b++)
+ mfValues[a][b] = internal::implGetDefaultValue(a, b);
+ }
+ }
+
+ bool B2DHomMatrix::isInvertible() const
+ {
+ double dst[6];
+ /* Compute adjoint: */
+ computeAdjoint(dst);
+ /* Compute determinant: */
+ double det = computeDeterminant(dst);
+ if (fTools::equalZero(det))
+ return false;
+ return true;
+ }
+
+ bool B2DHomMatrix::invert()
+ {
+ if(isIdentity())
+ return true;
+
+ double dst[6];
+
+ /* Compute adjoint: */
+ computeAdjoint(dst);
+
+ /* Compute determinant: */
+ double det = computeDeterminant(dst);
+ if (fTools::equalZero(det))
+ return false;
+
+ /* Multiply adjoint with reciprocal of determinant: */
+ det = 1.0 / det;
+ mfValues[0][0] = dst[0] * det;
+ mfValues[0][1] = dst[1] * det;
+ mfValues[0][2] = dst[2] * det;
+ mfValues[1][0] = dst[3] * det;
+ mfValues[1][1] = dst[4] * det;
+ mfValues[1][2] = dst[5] * det;
+
+ return true;
+ }
+
+ /* Compute adjoint, optimised for the case where the last (not stored) row is { 0, 0, 1 } */
+ void B2DHomMatrix::computeAdjoint(double (&dst)[6]) const
+ {
+ dst[0] = + get(1, 1);
+ dst[1] = - get(0, 1);
+ dst[2] = + get(0, 1) * get(1, 2) - get(0, 2) * get(1, 1);
+ dst[3] = - get(1, 0);
+ dst[4] = + get(0, 0);
+ dst[5] = - get(0, 0) * get(1, 2) + get(0, 2) * get(1, 0);
+ }
+
+ /* Compute the determinant, given the adjoint matrix */
+ double B2DHomMatrix::computeDeterminant(double (&dst)[6]) const
+ {
+ return mfValues[0][0] * dst[0] + mfValues[0][1] * dst[3];
+ }
+
+ B2DHomMatrix& B2DHomMatrix::operator*=(const B2DHomMatrix& rMat)
+ {
+ if(rMat.isIdentity())
+ {
+ // multiply with identity, no change -> nothing to do
+ }
+ else if(isIdentity())
+ {
+ // we are identity, result will be rMat -> assign
+ *this = rMat;
+ }
+ else
+ {
+ // multiply
+ doMulMatrix(rMat);
+ }
+
+ return *this;
+ }
+
+ void B2DHomMatrix::doMulMatrix(const B2DHomMatrix& rMat)
+ {
+ // create a copy as source for the original values
+ const B2DHomMatrix aCopy(*this);
+
+ for(sal_uInt16 a(0); a < 2; ++a)
+ {
+ for(sal_uInt16 b(0); b < 3; ++b)
+ {
+ double fValue = 0.0;
+
+ for(sal_uInt16 c(0); c < 2; ++c)
+ fValue += aCopy.mfValues[c][b] * rMat.mfValues[a][c];
+
+ mfValues[a][b] = fValue;
+ }
+ mfValues[a][2] += rMat.mfValues[a][2];
+ }
+ }
+
+ bool B2DHomMatrix::operator==(const B2DHomMatrix& rMat) const
+ {
+ if (&rMat == this)
+ return true;
+ for(sal_uInt16 a(0); a < 2; a++)
+ {
+ for(sal_uInt16 b(0); b < 3; b++)
+ {
+ const double fValueA(mfValues[a][b]);
+ const double fValueB(rMat.mfValues[a][b]);
+
+ if(!::basegfx::fTools::equal(fValueA, fValueB))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ bool B2DHomMatrix::operator!=(const B2DHomMatrix& rMat) const
+ {
+ return !(*this == rMat);
+ }
+
+ void B2DHomMatrix::rotate(double fRadiant)
+ {
+ if(fTools::equalZero(fRadiant))
+ return;
+
+ double fSin(0.0);
+ double fCos(1.0);
+
+ utils::createSinCosOrthogonal(fSin, fCos, fRadiant);
+ B2DHomMatrix aRotMat;
+
+ aRotMat.set(0, 0, fCos);
+ aRotMat.set(1, 1, fCos);
+ aRotMat.set(1, 0, fSin);
+ aRotMat.set(0, 1, -fSin);
+
+ doMulMatrix(aRotMat);
+ }
+
+ void B2DHomMatrix::translate(double fX, double fY)
+ {
+ if(!fTools::equalZero(fX) || !fTools::equalZero(fY))
+ {
+ B2DHomMatrix aTransMat;
+
+ aTransMat.set(0, 2, fX);
+ aTransMat.set(1, 2, fY);
+
+ doMulMatrix(aTransMat);
+ }
+ }
+
+ void B2DHomMatrix::translate(const B2DTuple& rTuple)
+ {
+ translate(rTuple.getX(), rTuple.getY());
+ }
+
+ void B2DHomMatrix::scale(double fX, double fY)
+ {
+ const double fOne(1.0);
+
+ if(!fTools::equal(fOne, fX) || !fTools::equal(fOne, fY))
+ {
+ B2DHomMatrix aScaleMat;
+
+ aScaleMat.set(0, 0, fX);
+ aScaleMat.set(1, 1, fY);
+
+ doMulMatrix(aScaleMat);
+ }
+ }
+
+ void B2DHomMatrix::scale(const B2DTuple& rTuple)
+ {
+ scale(rTuple.getX(), rTuple.getY());
+ }
+
+ void B2DHomMatrix::shearX(double fSx)
+ {
+ // #i76239# do not test against 1.0, but against 0.0. We are talking about a value not on the diagonal (!)
+ if(!fTools::equalZero(fSx))
+ {
+ B2DHomMatrix aShearXMat;
+
+ aShearXMat.set(0, 1, fSx);
+
+ doMulMatrix(aShearXMat);
+ }
+ }
+
+ void B2DHomMatrix::shearY(double fSy)
+ {
+ // #i76239# do not test against 1.0, but against 0.0. We are talking about a value not on the diagonal (!)
+ if(!fTools::equalZero(fSy))
+ {
+ B2DHomMatrix aShearYMat;
+
+ aShearYMat.set(1, 0, fSy);
+
+ doMulMatrix(aShearYMat);
+ }
+ }
+
+ /** Decomposition
+
+ New, optimized version with local shearX detection. Old version (keeping
+ below, is working well, too) used the 3D matrix decomposition when
+ shear was used. Keeping old version as comment below since it may get
+ necessary to add the determinant() test from there here, too.
+ */
+ bool B2DHomMatrix::decompose(B2DTuple& rScale, B2DTuple& rTranslate, double& rRotate, double& rShearX) const
+ {
+ // reset rotate and shear and copy translation values in every case
+ rRotate = rShearX = 0.0;
+ rTranslate.setX(get(0, 2));
+ rTranslate.setY(get(1, 2));
+
+ // test for rotation and shear
+ if(fTools::equalZero(get(0, 1)) && fTools::equalZero(get(1, 0)))
+ {
+ // no rotation and shear, copy scale values
+ rScale.setX(get(0, 0));
+ rScale.setY(get(1, 1));
+
+ // or is there?
+ if( rScale.getX() < 0 && rScale.getY() < 0 )
+ {
+ // there is - 180 degree rotated
+ rScale *= -1;
+ rRotate = M_PI;
+ }
+ }
+ else
+ {
+ // get the unit vectors of the transformation -> the perpendicular vectors
+ B2DVector aUnitVecX(get(0, 0), get(1, 0));
+ B2DVector aUnitVecY(get(0, 1), get(1, 1));
+ const double fScalarXY(aUnitVecX.scalar(aUnitVecY));
+
+ // Test if shear is zero. That's the case if the unit vectors in the matrix
+ // are perpendicular -> scalar is zero. This is also the case when one of
+ // the unit vectors is zero.
+ if(fTools::equalZero(fScalarXY))
+ {
+ // calculate unsigned scale values
+ rScale.setX(aUnitVecX.getLength());
+ rScale.setY(aUnitVecY.getLength());
+
+ // check unit vectors for zero lengths
+ const bool bXIsZero(fTools::equalZero(rScale.getX()));
+ const bool bYIsZero(fTools::equalZero(rScale.getY()));
+
+ if(bXIsZero || bYIsZero)
+ {
+ // still extract as much as possible. Scalings are already set
+ if(!bXIsZero)
+ {
+ // get rotation of X-Axis
+ rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX());
+ }
+ else if(!bYIsZero)
+ {
+ // get rotation of X-Axis. When assuming X and Y perpendicular
+ // and correct rotation, it's the Y-Axis rotation minus 90 degrees
+ rRotate = atan2(aUnitVecY.getY(), aUnitVecY.getX()) - M_PI_2;
+ }
+
+ // one or both unit vectors do not exist, determinant is zero, no decomposition possible.
+ // Eventually used rotations or shears are lost
+ return false;
+ }
+ else
+ {
+ // no shear
+ // calculate rotation of X unit vector relative to (1, 0)
+ rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX());
+
+ // use orientation to evtl. correct sign of Y-Scale
+ const double fCrossXY(aUnitVecX.cross(aUnitVecY));
+
+ if(fCrossXY < 0.0)
+ {
+ rScale.setY(-rScale.getY());
+ }
+ }
+ }
+ else
+ {
+ // fScalarXY is not zero, thus both unit vectors exist. No need to handle that here
+ // shear, extract it
+ double fCrossXY(aUnitVecX.cross(aUnitVecY));
+
+ // get rotation by calculating angle of X unit vector relative to (1, 0).
+ // This is before the parallel test following the motto to extract
+ // as much as possible
+ rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX());
+
+ // get unsigned scale value for X. It will not change and is useful
+ // for further corrections
+ rScale.setX(aUnitVecX.getLength());
+
+ if(fTools::equalZero(fCrossXY))
+ {
+ // extract as much as possible
+ rScale.setY(aUnitVecY.getLength());
+
+ // unit vectors are parallel, thus not linear independent. No
+ // useful decomposition possible. This should not happen since
+ // the only way to get the unit vectors nearly parallel is
+ // a very big shearing. Anyways, be prepared for hand-filled
+ // matrices
+ // Eventually used rotations or shears are lost
+ return false;
+ }
+ else
+ {
+ // calculate the contained shear
+ rShearX = fScalarXY / fCrossXY;
+
+ if(!fTools::equalZero(rRotate))
+ {
+ // To be able to correct the shear for aUnitVecY, rotation needs to be
+ // removed first. Correction of aUnitVecX is easy, it will be rotated back to (1, 0).
+ aUnitVecX.setX(rScale.getX());
+ aUnitVecX.setY(0.0);
+
+ // for Y correction we rotate the UnitVecY back about -rRotate
+ const double fNegRotate(-rRotate);
+ const double fSin(sin(fNegRotate));
+ const double fCos(cos(fNegRotate));
+
+ const double fNewX(aUnitVecY.getX() * fCos - aUnitVecY.getY() * fSin);
+ const double fNewY(aUnitVecY.getX() * fSin + aUnitVecY.getY() * fCos);
+
+ aUnitVecY.setX(fNewX);
+ aUnitVecY.setY(fNewY);
+ }
+
+ // Correct aUnitVecY and fCrossXY to fShear=0. Rotation is already removed.
+ // Shear correction can only work with removed rotation
+ aUnitVecY.setX(aUnitVecY.getX() - (aUnitVecY.getY() * rShearX));
+ fCrossXY = aUnitVecX.cross(aUnitVecY);
+
+ // calculate unsigned scale value for Y, after the corrections since
+ // the shear correction WILL change the length of aUnitVecY
+ rScale.setY(aUnitVecY.getLength());
+
+ // use orientation to set sign of Y-Scale
+ if(fCrossXY < 0.0)
+ {
+ rScale.setY(-rScale.getY());
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/matrix/b2dhommatrixtools.cxx b/basegfx/source/matrix/b2dhommatrixtools.cxx
new file mode 100644
index 0000000000..7fb6bbe0cb
--- /dev/null
+++ b/basegfx/source/matrix/b2dhommatrixtools.cxx
@@ -0,0 +1,457 @@
+/* -*- 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 <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/range/b2drange.hxx>
+
+#include <osl/diagnose.h>
+
+namespace basegfx::utils
+{
+ void createSinCosOrthogonal(double& o_rSin, double& o_rCos, double fRadiant)
+ {
+ if( fTools::equalZero( fmod( fRadiant, M_PI_2 ) ) )
+ {
+ // determine quadrant
+ const sal_Int32 nQuad(
+ (4 + fround( M_2_PI*fmod( fRadiant, 2 * M_PI ) )) % 4 );
+ switch( nQuad )
+ {
+ case 0: // -2pi,0,2pi
+ o_rSin = 0.0;
+ o_rCos = 1.0;
+ break;
+
+ case 1: // -3/2pi,1/2pi
+ o_rSin = 1.0;
+ o_rCos = 0.0;
+ break;
+
+ case 2: // -pi,pi
+ o_rSin = 0.0;
+ o_rCos = -1.0;
+ break;
+
+ case 3: // -1/2pi,3/2pi
+ o_rSin = -1.0;
+ o_rCos = 0.0;
+ break;
+
+ default:
+ OSL_FAIL( "createSinCos: Impossible case reached" );
+ }
+ }
+ else
+ {
+ // TODO(P1): Maybe use glibc's sincos here (though
+ // that's kinda non-portable...)
+ o_rSin = sin(fRadiant);
+ o_rCos = cos(fRadiant);
+ }
+ }
+
+ B2DHomMatrix createScaleB2DHomMatrix(double fScaleX, double fScaleY)
+ {
+ B2DHomMatrix aRetval;
+ const double fOne(1.0);
+
+ if(!fTools::equal(fScaleX, fOne))
+ {
+ aRetval.set(0, 0, fScaleX);
+ }
+
+ if(!fTools::equal(fScaleY, fOne))
+ {
+ aRetval.set(1, 1, fScaleY);
+ }
+
+ return aRetval;
+ }
+
+ B2DHomMatrix createShearXB2DHomMatrix(double fShearX)
+ {
+ B2DHomMatrix aRetval;
+
+ if(!fTools::equalZero(fShearX))
+ {
+ aRetval.set(0, 1, fShearX);
+ }
+
+ return aRetval;
+ }
+
+ B2DHomMatrix createShearYB2DHomMatrix(double fShearY)
+ {
+ B2DHomMatrix aRetval;
+
+ if(!fTools::equalZero(fShearY))
+ {
+ aRetval.set(1, 0, fShearY);
+ }
+
+ return aRetval;
+ }
+
+ B2DHomMatrix createRotateB2DHomMatrix(double fRadiant)
+ {
+ B2DHomMatrix aRetval;
+
+ if(!fTools::equalZero(fRadiant))
+ {
+ double fSin(0.0);
+ double fCos(1.0);
+
+ createSinCosOrthogonal(fSin, fCos, fRadiant);
+ aRetval.set(0, 0, fCos);
+ aRetval.set(1, 1, fCos);
+ aRetval.set(1, 0, fSin);
+ aRetval.set(0, 1, -fSin);
+ }
+
+ return aRetval;
+ }
+
+ B2DHomMatrix createTranslateB2DHomMatrix(double fTranslateX, double fTranslateY)
+ {
+ B2DHomMatrix aRetval;
+
+ if(!(fTools::equalZero(fTranslateX) && fTools::equalZero(fTranslateY)))
+ {
+ aRetval.set(0, 2, fTranslateX);
+ aRetval.set(1, 2, fTranslateY);
+ }
+
+ return aRetval;
+ }
+
+ B2DHomMatrix createScaleShearXRotateTranslateB2DHomMatrix(
+ double fScaleX, double fScaleY,
+ double fShearX,
+ double fRadiant,
+ double fTranslateX, double fTranslateY)
+ {
+ const double fOne(1.0);
+
+ if(fTools::equal(fScaleX, fOne) && fTools::equal(fScaleY, fOne))
+ {
+ /// no scale, take shortcut
+ return createShearXRotateTranslateB2DHomMatrix(fShearX, fRadiant, fTranslateX, fTranslateY);
+ }
+ else
+ {
+ /// scale used
+ if(fTools::equalZero(fShearX))
+ {
+ /// no shear
+ if(fTools::equalZero(fRadiant))
+ {
+ /// no rotate, take shortcut
+ return createScaleTranslateB2DHomMatrix(fScaleX, fScaleY, fTranslateX, fTranslateY);
+ }
+ else
+ {
+ /// rotate and scale used, no shear
+ double fSin(0.0);
+ double fCos(1.0);
+
+ createSinCosOrthogonal(fSin, fCos, fRadiant);
+
+ B2DHomMatrix aRetval(
+ /* Row 0, Column 0 */ fCos * fScaleX,
+ /* Row 0, Column 1 */ fScaleY * -fSin,
+ /* Row 0, Column 2 */ fTranslateX,
+ /* Row 1, Column 0 */ fSin * fScaleX,
+ /* Row 1, Column 1 */ fScaleY * fCos,
+ /* Row 1, Column 2 */ fTranslateY);
+
+ return aRetval;
+ }
+ }
+ else
+ {
+ /// scale and shear used
+ if(fTools::equalZero(fRadiant))
+ {
+ /// scale and shear, but no rotate
+ B2DHomMatrix aRetval(
+ /* Row 0, Column 0 */ fScaleX,
+ /* Row 0, Column 1 */ fScaleY * fShearX,
+ /* Row 0, Column 2 */ fTranslateX,
+ /* Row 1, Column 0 */ 0.0,
+ /* Row 1, Column 1 */ fScaleY,
+ /* Row 1, Column 2 */ fTranslateY);
+
+ return aRetval;
+ }
+ else
+ {
+ /// scale, shear and rotate used
+ double fSin(0.0);
+ double fCos(1.0);
+
+ createSinCosOrthogonal(fSin, fCos, fRadiant);
+
+ B2DHomMatrix aRetval(
+ /* Row 0, Column 0 */ fCos * fScaleX,
+ /* Row 0, Column 1 */ fScaleY * ((fCos * fShearX) - fSin),
+ /* Row 0, Column 2 */ fTranslateX,
+ /* Row 1, Column 0 */ fSin * fScaleX,
+ /* Row 1, Column 1 */ fScaleY * ((fSin * fShearX) + fCos),
+ /* Row 1, Column 2 */ fTranslateY);
+
+ return aRetval;
+ }
+ }
+ }
+ }
+
+ B2DHomMatrix createShearXRotateTranslateB2DHomMatrix(
+ double fShearX,
+ double fRadiant,
+ double fTranslateX, double fTranslateY)
+ {
+ if(fTools::equalZero(fShearX))
+ {
+ /// no shear
+ if(fTools::equalZero(fRadiant))
+ {
+ /// no shear, no rotate, take shortcut
+ return createTranslateB2DHomMatrix(fTranslateX, fTranslateY);
+ }
+ else
+ {
+ /// no shear, but rotate used
+ double fSin(0.0);
+ double fCos(1.0);
+
+ createSinCosOrthogonal(fSin, fCos, fRadiant);
+
+ B2DHomMatrix aRetval(
+ /* Row 0, Column 0 */ fCos,
+ /* Row 0, Column 1 */ -fSin,
+ /* Row 0, Column 2 */ fTranslateX,
+ /* Row 1, Column 0 */ fSin,
+ /* Row 1, Column 1 */ fCos,
+ /* Row 1, Column 2 */ fTranslateY);
+
+ return aRetval;
+ }
+ }
+ else
+ {
+ /// shear used
+ if(fTools::equalZero(fRadiant))
+ {
+ /// no rotate, but shear used
+ B2DHomMatrix aRetval(
+ /* Row 0, Column 0 */ 1.0,
+ /* Row 0, Column 1 */ fShearX,
+ /* Row 0, Column 2 */ fTranslateX,
+ /* Row 1, Column 0 */ 0.0,
+ /* Row 1, Column 1 */ 1.0,
+ /* Row 1, Column 2 */ fTranslateY);
+
+ return aRetval;
+ }
+ else
+ {
+ /// shear and rotate used
+ double fSin(0.0);
+ double fCos(1.0);
+
+ createSinCosOrthogonal(fSin, fCos, fRadiant);
+
+ B2DHomMatrix aRetval(
+ /* Row 0, Column 0 */ fCos,
+ /* Row 0, Column 1 */ (fCos * fShearX) - fSin,
+ /* Row 0, Column 2 */ fTranslateX,
+ /* Row 1, Column 0 */ fSin,
+ /* Row 1, Column 1 */ (fSin * fShearX) + fCos,
+ /* Row 1, Column 2 */ fTranslateY);
+
+ return aRetval;
+ }
+ }
+ }
+
+ B2DHomMatrix createScaleTranslateB2DHomMatrix(
+ double fScaleX, double fScaleY,
+ double fTranslateX, double fTranslateY)
+ {
+ const double fOne(1.0);
+
+ if(fTools::equal(fScaleX, fOne) && fTools::equal(fScaleY, fOne))
+ {
+ /// no scale, take shortcut
+ return createTranslateB2DHomMatrix(fTranslateX, fTranslateY);
+ }
+ else
+ {
+ /// scale used
+ if(fTools::equalZero(fTranslateX) && fTools::equalZero(fTranslateY))
+ {
+ /// no translate, but scale.
+ B2DHomMatrix aRetval;
+
+ aRetval.set(0, 0, fScaleX);
+ aRetval.set(1, 1, fScaleY);
+
+ return aRetval;
+ }
+ else
+ {
+ /// translate and scale
+ B2DHomMatrix aRetval(
+ /* Row 0, Column 0 */ fScaleX,
+ /* Row 0, Column 1 */ 0.0,
+ /* Row 0, Column 2 */ fTranslateX,
+ /* Row 1, Column 0 */ 0.0,
+ /* Row 1, Column 1 */ fScaleY,
+ /* Row 1, Column 2 */ fTranslateY);
+
+ return aRetval;
+ }
+ }
+ }
+
+ B2DHomMatrix createRotateAroundPoint(
+ double fPointX, double fPointY,
+ double fRadiant)
+ {
+ B2DHomMatrix aRetval;
+
+ if(!fTools::equalZero(fRadiant))
+ {
+ double fSin(0.0);
+ double fCos(1.0);
+
+ createSinCosOrthogonal(fSin, fCos, fRadiant);
+
+ aRetval.set3x2(
+ /* Row 0, Column 0 */ fCos,
+ /* Row 0, Column 1 */ -fSin,
+ /* Row 0, Column 2 */ (fPointX * (1.0 - fCos)) + (fSin * fPointY),
+ /* Row 1, Column 0 */ fSin,
+ /* Row 1, Column 1 */ fCos,
+ /* Row 1, Column 2 */ (fPointY * (1.0 - fCos)) - (fSin * fPointX));
+ }
+
+ return aRetval;
+ }
+
+ B2DHomMatrix createRotateAroundCenterKeepAspectRatioStayInsideRange(
+ const basegfx::B2DRange& rTargetRange,
+ double fRotate)
+ {
+ basegfx::B2DHomMatrix aRetval;
+
+ // RotGrfFlyFrame: Create a transformation that maps the range inside of itself
+ // so that it fits, takes as much space as possible and keeps the aspect ratio
+ if(0.0 != fRotate)
+ {
+ // Fit rotated graphic to center of available space, keeping page ratio:
+ // Adapt scaling ratio of unit object and rotate it
+ aRetval.scale(1.0, rTargetRange.getHeight() / rTargetRange.getWidth());
+ aRetval.rotate(fRotate);
+
+ // get the range to see where we are in unit coordinates
+ basegfx::B2DRange aFullRange(0.0, 0.0, 1.0, 1.0);
+ aFullRange.transform(aRetval);
+
+ // detect needed scales in X/Y and choose the smallest for staying inside the
+ // available space while keeping aspect ratio of the source
+ const double fScaleX(rTargetRange.getWidth() / aFullRange.getWidth());
+ const double fScaleY(rTargetRange.getHeight() / aFullRange.getHeight());
+ const double fScaleMin(std::min(fScaleX, fScaleY));
+
+ // TopLeft to zero, then scale, then move to center of available space
+ aRetval.translate(-aFullRange.getMinX(), -aFullRange.getMinY());
+ aRetval.scale(fScaleMin, fScaleMin);
+ aRetval.translate(
+ rTargetRange.getCenterX() - (0.5 * fScaleMin * aFullRange.getWidth()),
+ rTargetRange.getCenterY() - (0.5 * fScaleMin * aFullRange.getHeight()));
+ }
+ else
+ {
+ // just scale/translate needed
+ aRetval *= createScaleTranslateB2DHomMatrix(
+ rTargetRange.getRange(),
+ rTargetRange.getMinimum());
+ }
+
+ return aRetval;
+ }
+
+ /// special for the case to map from source range to target range
+ B2DHomMatrix createSourceRangeTargetRangeTransform(
+ const B2DRange& rSourceRange,
+ const B2DRange& rTargetRange)
+ {
+ B2DHomMatrix aRetval;
+
+ if(&rSourceRange == &rTargetRange)
+ {
+ return aRetval;
+ }
+
+ if(!fTools::equalZero(rSourceRange.getMinX()) || !fTools::equalZero(rSourceRange.getMinY()))
+ {
+ aRetval.set(0, 2, -rSourceRange.getMinX());
+ aRetval.set(1, 2, -rSourceRange.getMinY());
+ }
+
+ const double fSourceW(rSourceRange.getWidth());
+ const double fSourceH(rSourceRange.getHeight());
+ const bool bDivX(!fTools::equalZero(fSourceW) && !fTools::equal(fSourceW, 1.0));
+ const bool bDivY(!fTools::equalZero(fSourceH) && !fTools::equal(fSourceH, 1.0));
+ const double fScaleX(bDivX ? rTargetRange.getWidth() / fSourceW : rTargetRange.getWidth());
+ const double fScaleY(bDivY ? rTargetRange.getHeight() / fSourceH : rTargetRange.getHeight());
+
+ if(!fTools::equalZero(fScaleX) || !fTools::equalZero(fScaleY))
+ {
+ aRetval.scale(fScaleX, fScaleY);
+ }
+
+ if(!fTools::equalZero(rTargetRange.getMinX()) || !fTools::equalZero(rTargetRange.getMinY()))
+ {
+ aRetval.translate(
+ rTargetRange.getMinX(),
+ rTargetRange.getMinY());
+ }
+
+ return aRetval;
+ }
+
+ B2DHomMatrix createCoordinateSystemTransform(
+ const B2DPoint& rOrigin,
+ const B2DVector& rX,
+ const B2DVector& rY)
+ {
+ return basegfx::B2DHomMatrix(
+ rX.getX(), rY.getX(), rOrigin.getX(),
+ rX.getY(), rY.getY(), rOrigin.getY());
+ }
+
+ B2DTuple getColumn(const B2DHomMatrix& rMatrix, sal_uInt16 nCol)
+ {
+ return B2DTuple(rMatrix.get(0, nCol), rMatrix.get(1, nCol));
+ }
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/matrix/b3dhommatrix.cxx b/basegfx/source/matrix/b3dhommatrix.cxx
new file mode 100644
index 0000000000..f9018c6979
--- /dev/null
+++ b/basegfx/source/matrix/b3dhommatrix.cxx
@@ -0,0 +1,546 @@
+/* -*- 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 <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/matrix/hommatrixtemplate.hxx>
+#include <basegfx/vector/b3dvector.hxx>
+#include <memory>
+
+namespace basegfx
+{
+ typedef ::basegfx::internal::ImplHomMatrixTemplate< 4 >Impl3DHomMatrix_Base;
+ class Impl3DHomMatrix : public Impl3DHomMatrix_Base
+ {
+ };
+
+ B3DHomMatrix::B3DHomMatrix() = default;
+
+ B3DHomMatrix::B3DHomMatrix(const B3DHomMatrix&) = default;
+
+ B3DHomMatrix::B3DHomMatrix(B3DHomMatrix&&) = default;
+
+ B3DHomMatrix::~B3DHomMatrix() = default;
+
+ B3DHomMatrix& B3DHomMatrix::operator=(const B3DHomMatrix&) = default;
+
+ B3DHomMatrix& B3DHomMatrix::operator=(B3DHomMatrix&&) = default;
+
+ double B3DHomMatrix::get(sal_uInt16 nRow, sal_uInt16 nColumn) const
+ {
+ return mpImpl->get(nRow, nColumn);
+ }
+
+ void B3DHomMatrix::set(sal_uInt16 nRow, sal_uInt16 nColumn, double fValue)
+ {
+ mpImpl->set(nRow, nColumn, fValue);
+ }
+
+ bool B3DHomMatrix::isLastLineDefault() const
+ {
+ return mpImpl->isLastLineDefault();
+ }
+
+ bool B3DHomMatrix::isIdentity() const
+ {
+ return mpImpl->isIdentity();
+ }
+
+ void B3DHomMatrix::identity()
+ {
+ *mpImpl = Impl3DHomMatrix();
+ }
+
+ void B3DHomMatrix::invert()
+ {
+ Impl3DHomMatrix aWork(*mpImpl);
+ std::unique_ptr<sal_uInt16[]> pIndex( new sal_uInt16[Impl3DHomMatrix_Base::getEdgeLength()] );
+ sal_Int16 nParity;
+
+ if(aWork.ludcmp(pIndex.get(), nParity))
+ {
+ mpImpl->doInvert(aWork, pIndex.get());
+ }
+ }
+
+ double B3DHomMatrix::determinant() const
+ {
+ return mpImpl->doDeterminant();
+ }
+
+ B3DHomMatrix& B3DHomMatrix::operator+=(const B3DHomMatrix& rMat)
+ {
+ mpImpl->doAddMatrix(*rMat.mpImpl);
+ return *this;
+ }
+
+ B3DHomMatrix& B3DHomMatrix::operator-=(const B3DHomMatrix& rMat)
+ {
+ mpImpl->doSubMatrix(*rMat.mpImpl);
+ return *this;
+ }
+
+ B3DHomMatrix& B3DHomMatrix::operator*=(double fValue)
+ {
+ const double fOne(1.0);
+
+ if(!fTools::equal(fOne, fValue))
+ mpImpl->doMulMatrix(fValue);
+
+ return *this;
+ }
+
+ B3DHomMatrix& B3DHomMatrix::operator/=(double fValue)
+ {
+ const double fOne(1.0);
+
+ if(!fTools::equal(fOne, fValue))
+ mpImpl->doMulMatrix(1.0 / fValue);
+
+ return *this;
+ }
+
+ B3DHomMatrix& B3DHomMatrix::operator*=(const B3DHomMatrix& rMat)
+ {
+ if(rMat.isIdentity())
+ {
+ // multiply with identity, no change -> nothing to do
+ }
+ else if(isIdentity())
+ {
+ // we are identity, result will be rMat -> assign
+ *this = rMat;
+ }
+ else
+ {
+ // multiply
+ mpImpl->doMulMatrix(*rMat.mpImpl);
+ }
+ return *this;
+ }
+
+ bool B3DHomMatrix::operator==(const B3DHomMatrix& rMat) const
+ {
+ if(mpImpl.same_object(rMat.mpImpl))
+ return true;
+
+ return mpImpl->isEqual(*rMat.mpImpl);
+ }
+
+ bool B3DHomMatrix::operator!=(const B3DHomMatrix& rMat) const
+ {
+ return !(*this == rMat);
+ }
+
+ void B3DHomMatrix::rotate(double fAngleX,double fAngleY,double fAngleZ)
+ {
+ if(fTools::equalZero(fAngleX) && fTools::equalZero(fAngleY) && fTools::equalZero(fAngleZ))
+ return;
+
+ if(!fTools::equalZero(fAngleX))
+ {
+ Impl3DHomMatrix aRotMatX;
+ double fSin(sin(fAngleX));
+ double fCos(cos(fAngleX));
+
+ aRotMatX.set(1, 1, fCos);
+ aRotMatX.set(2, 2, fCos);
+ aRotMatX.set(2, 1, fSin);
+ aRotMatX.set(1, 2, -fSin);
+
+ mpImpl->doMulMatrix(aRotMatX);
+ }
+
+ if(!fTools::equalZero(fAngleY))
+ {
+ Impl3DHomMatrix aRotMatY;
+ double fSin(sin(fAngleY));
+ double fCos(cos(fAngleY));
+
+ aRotMatY.set(0, 0, fCos);
+ aRotMatY.set(2, 2, fCos);
+ aRotMatY.set(0, 2, fSin);
+ aRotMatY.set(2, 0, -fSin);
+
+ mpImpl->doMulMatrix(aRotMatY);
+ }
+
+ if(fTools::equalZero(fAngleZ))
+ return;
+
+ Impl3DHomMatrix aRotMatZ;
+ double fSin(sin(fAngleZ));
+ double fCos(cos(fAngleZ));
+
+ aRotMatZ.set(0, 0, fCos);
+ aRotMatZ.set(1, 1, fCos);
+ aRotMatZ.set(1, 0, fSin);
+ aRotMatZ.set(0, 1, -fSin);
+
+ mpImpl->doMulMatrix(aRotMatZ);
+ }
+
+ void B3DHomMatrix::rotate(const B3DTuple& rRotation)
+ {
+ rotate(rRotation.getX(), rRotation.getY(), rRotation.getZ());
+ }
+
+ void B3DHomMatrix::translate(double fX, double fY, double fZ)
+ {
+ if(!fTools::equalZero(fX) || !fTools::equalZero(fY) || !fTools::equalZero(fZ))
+ {
+ Impl3DHomMatrix aTransMat;
+
+ aTransMat.set(0, 3, fX);
+ aTransMat.set(1, 3, fY);
+ aTransMat.set(2, 3, fZ);
+
+ mpImpl->doMulMatrix(aTransMat);
+ }
+ }
+
+ void B3DHomMatrix::translate(const B3DTuple& rRotation)
+ {
+ translate(rRotation.getX(), rRotation.getY(), rRotation.getZ());
+ }
+
+ void B3DHomMatrix::scale(double fX, double fY, double fZ)
+ {
+ const double fOne(1.0);
+
+ if(!fTools::equal(fOne, fX) || !fTools::equal(fOne, fY) ||!fTools::equal(fOne, fZ))
+ {
+ Impl3DHomMatrix aScaleMat;
+
+ aScaleMat.set(0, 0, fX);
+ aScaleMat.set(1, 1, fY);
+ aScaleMat.set(2, 2, fZ);
+
+ mpImpl->doMulMatrix(aScaleMat);
+ }
+ }
+
+ void B3DHomMatrix::scale(const B3DTuple& rRotation)
+ {
+ scale(rRotation.getX(), rRotation.getY(), rRotation.getZ());
+ }
+
+ void B3DHomMatrix::shearXY(double fSx, double fSy)
+ {
+ // #i76239# do not test against 1.0, but against 0.0. We are talking about a value not on the diagonal (!)
+ if(!fTools::equalZero(fSx) || !fTools::equalZero(fSy))
+ {
+ Impl3DHomMatrix aShearXYMat;
+
+ aShearXYMat.set(0, 2, fSx);
+ aShearXYMat.set(1, 2, fSy);
+
+ mpImpl->doMulMatrix(aShearXYMat);
+ }
+ }
+
+ void B3DHomMatrix::shearXZ(double fSx, double fSz)
+ {
+ // #i76239# do not test against 1.0, but against 0.0. We are talking about a value not on the diagonal (!)
+ if(!fTools::equalZero(fSx) || !fTools::equalZero(fSz))
+ {
+ Impl3DHomMatrix aShearXZMat;
+
+ aShearXZMat.set(0, 1, fSx);
+ aShearXZMat.set(2, 1, fSz);
+
+ mpImpl->doMulMatrix(aShearXZMat);
+ }
+ }
+ void B3DHomMatrix::frustum(double fLeft, double fRight, double fBottom, double fTop, double fNear, double fFar)
+ {
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ if(!fTools::more(fNear, fZero))
+ {
+ fNear = 0.001;
+ }
+
+ if(!fTools::more(fFar, fZero))
+ {
+ fFar = fOne;
+ }
+
+ if(fTools::equal(fNear, fFar))
+ {
+ fFar = fNear + fOne;
+ }
+
+ if(fTools::equal(fLeft, fRight))
+ {
+ fLeft -= fOne;
+ fRight += fOne;
+ }
+
+ if(fTools::equal(fTop, fBottom))
+ {
+ fBottom -= fOne;
+ fTop += fOne;
+ }
+
+ Impl3DHomMatrix aFrustumMat;
+
+ aFrustumMat.set(0, 0, 2.0 * fNear / (fRight - fLeft));
+ aFrustumMat.set(1, 1, 2.0 * fNear / (fTop - fBottom));
+ aFrustumMat.set(0, 2, (fRight + fLeft) / (fRight - fLeft));
+ aFrustumMat.set(1, 2, (fTop + fBottom) / (fTop - fBottom));
+ aFrustumMat.set(2, 2, -fOne * ((fFar + fNear) / (fFar - fNear)));
+ aFrustumMat.set(3, 2, -fOne);
+ aFrustumMat.set(2, 3, -fOne * ((2.0 * fFar * fNear) / (fFar - fNear)));
+ aFrustumMat.set(3, 3, fZero);
+
+ mpImpl->doMulMatrix(aFrustumMat);
+ }
+
+ void B3DHomMatrix::ortho(double fLeft, double fRight, double fBottom, double fTop, double fNear, double fFar)
+ {
+ if(fTools::equal(fNear, fFar))
+ {
+ fFar = fNear + 1.0;
+ }
+
+ if(fTools::equal(fLeft, fRight))
+ {
+ fLeft -= 1.0;
+ fRight += 1.0;
+ }
+
+ if(fTools::equal(fTop, fBottom))
+ {
+ fBottom -= 1.0;
+ fTop += 1.0;
+ }
+
+ Impl3DHomMatrix aOrthoMat;
+
+ aOrthoMat.set(0, 0, 2.0 / (fRight - fLeft));
+ aOrthoMat.set(1, 1, 2.0 / (fTop - fBottom));
+ aOrthoMat.set(2, 2, -1.0 * (2.0 / (fFar - fNear)));
+ aOrthoMat.set(0, 3, -1.0 * ((fRight + fLeft) / (fRight - fLeft)));
+ aOrthoMat.set(1, 3, -1.0 * ((fTop + fBottom) / (fTop - fBottom)));
+ aOrthoMat.set(2, 3, -1.0 * ((fFar + fNear) / (fFar - fNear)));
+
+ mpImpl->doMulMatrix(aOrthoMat);
+ }
+
+ void B3DHomMatrix::orientation(const B3DPoint& rVRP, B3DVector aVPN, B3DVector aVUV)
+ {
+ Impl3DHomMatrix aOrientationMat;
+
+ // translate -VRP
+ aOrientationMat.set(0, 3, -rVRP.getX());
+ aOrientationMat.set(1, 3, -rVRP.getY());
+ aOrientationMat.set(2, 3, -rVRP.getZ());
+
+ // build rotation
+ aVUV.normalize();
+ aVPN.normalize();
+
+ // build x-axis as perpendicular from aVUV and aVPN
+ B3DVector aRx(aVUV.getPerpendicular(aVPN));
+ aRx.normalize();
+
+ // y-axis perpendicular to that
+ B3DVector aRy(aVPN.getPerpendicular(aRx));
+ aRy.normalize();
+
+ // the calculated normals are the line vectors of the rotation matrix,
+ // set them to create rotation
+ aOrientationMat.set(0, 0, aRx.getX());
+ aOrientationMat.set(0, 1, aRx.getY());
+ aOrientationMat.set(0, 2, aRx.getZ());
+ aOrientationMat.set(1, 0, aRy.getX());
+ aOrientationMat.set(1, 1, aRy.getY());
+ aOrientationMat.set(1, 2, aRy.getZ());
+ aOrientationMat.set(2, 0, aVPN.getX());
+ aOrientationMat.set(2, 1, aVPN.getY());
+ aOrientationMat.set(2, 2, aVPN.getZ());
+
+ mpImpl->doMulMatrix(aOrientationMat);
+ }
+
+ void B3DHomMatrix::decompose(B3DTuple& rScale, B3DTuple& rTranslate, B3DTuple& rRotate, B3DTuple& rShear) const
+ {
+ // when perspective is used, decompose is not made here
+ if(!mpImpl->isLastLineDefault())
+ return;
+
+ // If determinant is zero, decomposition is not possible
+ if(determinant() == 0.0)
+ return;
+
+ // isolate translation
+ rTranslate.setX(mpImpl->get(0, 3));
+ rTranslate.setY(mpImpl->get(1, 3));
+ rTranslate.setZ(mpImpl->get(2, 3));
+
+ // correct translate values
+ rTranslate.correctValues();
+
+ // get scale and shear
+ B3DVector aCol0(mpImpl->get(0, 0), mpImpl->get(1, 0), mpImpl->get(2, 0));
+ B3DVector aCol1(mpImpl->get(0, 1), mpImpl->get(1, 1), mpImpl->get(2, 1));
+ B3DVector aCol2(mpImpl->get(0, 2), mpImpl->get(1, 2), mpImpl->get(2, 2));
+ B3DVector aTemp;
+
+ // get ScaleX
+ rScale.setX(aCol0.getLength());
+ aCol0.normalize();
+
+ // get ShearXY
+ rShear.setX(aCol0.scalar(aCol1));
+
+ if(fTools::equalZero(rShear.getX()))
+ {
+ rShear.setX(0.0);
+ }
+ else
+ {
+ aTemp.setX(aCol1.getX() - rShear.getX() * aCol0.getX());
+ aTemp.setY(aCol1.getY() - rShear.getX() * aCol0.getY());
+ aTemp.setZ(aCol1.getZ() - rShear.getX() * aCol0.getZ());
+ aCol1 = aTemp;
+ }
+
+ // get ScaleY
+ rScale.setY(aCol1.getLength());
+ aCol1.normalize();
+
+ const double fShearX(rShear.getX());
+
+ if(!fTools::equalZero(fShearX))
+ {
+ rShear.setX(rShear.getX() / rScale.getY());
+ }
+
+ // get ShearXZ
+ rShear.setY(aCol0.scalar(aCol2));
+
+ if(fTools::equalZero(rShear.getY()))
+ {
+ rShear.setY(0.0);
+ }
+ else
+ {
+ aTemp.setX(aCol2.getX() - rShear.getY() * aCol0.getX());
+ aTemp.setY(aCol2.getY() - rShear.getY() * aCol0.getY());
+ aTemp.setZ(aCol2.getZ() - rShear.getY() * aCol0.getZ());
+ aCol2 = aTemp;
+ }
+
+ // get ShearYZ
+ rShear.setZ(aCol1.scalar(aCol2));
+
+ if(fTools::equalZero(rShear.getZ()))
+ {
+ rShear.setZ(0.0);
+ }
+ else
+ {
+ aTemp.setX(aCol2.getX() - rShear.getZ() * aCol1.getX());
+ aTemp.setY(aCol2.getY() - rShear.getZ() * aCol1.getY());
+ aTemp.setZ(aCol2.getZ() - rShear.getZ() * aCol1.getZ());
+ aCol2 = aTemp;
+ }
+
+ // get ScaleZ
+ rScale.setZ(aCol2.getLength());
+ aCol2.normalize();
+
+ const double fShearY(rShear.getY());
+
+ if(!fTools::equalZero(fShearY))
+ {
+ // coverity[copy_paste_error : FALSE] - this is correct getZ, not getY
+ rShear.setY(rShear.getY() / rScale.getZ());
+ }
+
+ const double fShearZ(rShear.getZ());
+
+ if(!fTools::equalZero(fShearZ))
+ {
+ // coverity[original] - this is not an original copy-and-paste source for ^^^
+ rShear.setZ(rShear.getZ() / rScale.getZ());
+ }
+
+ // correct shear values
+ rShear.correctValues();
+
+ // Coordinate system flip?
+ if(0.0 > aCol0.scalar(aCol1.getPerpendicular(aCol2)))
+ {
+ rScale = -rScale;
+ aCol0 = -aCol0;
+ aCol1 = -aCol1;
+ aCol2 = -aCol2;
+ }
+
+ // correct scale values
+ rScale.correctValues(1.0);
+
+ // Get rotations
+ {
+ double fy=0;
+ double cy=0;
+
+ if( ::basegfx::fTools::equal( aCol0.getZ(), 1.0 )
+ || aCol0.getZ() > 1.0 )
+ {
+ fy = -M_PI/2.0;
+ cy = 0.0;
+ }
+ else if( ::basegfx::fTools::equal( aCol0.getZ(), -1.0 )
+ || aCol0.getZ() < -1.0 )
+ {
+ fy = M_PI/2.0;
+ cy = 0.0;
+ }
+ else
+ {
+ fy = asin( -aCol0.getZ() );
+ cy = cos(fy);
+ }
+
+ rRotate.setY(fy);
+ if( ::basegfx::fTools::equalZero( cy ) )
+ {
+ if( aCol0.getZ() > 0.0 )
+ rRotate.setX(atan2(-1.0*aCol1.getX(), aCol1.getY()));
+ else
+ rRotate.setX(atan2(aCol1.getX(), aCol1.getY()));
+ rRotate.setZ(0.0);
+ }
+ else
+ {
+ rRotate.setX(atan2(aCol1.getZ(), aCol2.getZ()));
+ rRotate.setZ(atan2(aCol0.getY(), aCol0.getX()));
+ }
+
+ // correct rotate values
+ rRotate.correctValues();
+ }
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/matrix/b3dhommatrixtools.cxx b/basegfx/source/matrix/b3dhommatrixtools.cxx
new file mode 100644
index 0000000000..9125aca4e0
--- /dev/null
+++ b/basegfx/source/matrix/b3dhommatrixtools.cxx
@@ -0,0 +1,71 @@
+/* -*- 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 <basegfx/matrix/b3dhommatrixtools.hxx>
+#include <com/sun/star/drawing/HomogenMatrix.hpp>
+
+namespace basegfx::utils
+{
+B3DHomMatrix UnoHomogenMatrixToB3DHomMatrix(const com::sun::star::drawing::HomogenMatrix& rMatrixIn)
+{
+ B3DHomMatrix aRetval;
+
+ aRetval.set(0, 0, rMatrixIn.Line1.Column1);
+ aRetval.set(0, 1, rMatrixIn.Line1.Column2);
+ aRetval.set(0, 2, rMatrixIn.Line1.Column3);
+ aRetval.set(0, 3, rMatrixIn.Line1.Column4);
+ aRetval.set(1, 0, rMatrixIn.Line2.Column1);
+ aRetval.set(1, 1, rMatrixIn.Line2.Column2);
+ aRetval.set(1, 2, rMatrixIn.Line2.Column3);
+ aRetval.set(1, 3, rMatrixIn.Line2.Column4);
+ aRetval.set(2, 0, rMatrixIn.Line3.Column1);
+ aRetval.set(2, 1, rMatrixIn.Line3.Column2);
+ aRetval.set(2, 2, rMatrixIn.Line3.Column3);
+ aRetval.set(2, 3, rMatrixIn.Line3.Column4);
+ aRetval.set(3, 0, rMatrixIn.Line4.Column1);
+ aRetval.set(3, 1, rMatrixIn.Line4.Column2);
+ aRetval.set(3, 2, rMatrixIn.Line4.Column3);
+ aRetval.set(3, 3, rMatrixIn.Line4.Column4);
+
+ return aRetval;
+}
+
+void B3DHomMatrixToUnoHomogenMatrix(const B3DHomMatrix& rMatrixIn,
+ com::sun::star::drawing::HomogenMatrix& rMatrixOut)
+{
+ rMatrixOut.Line1.Column1 = rMatrixIn.get(0, 0);
+ rMatrixOut.Line1.Column2 = rMatrixIn.get(0, 1);
+ rMatrixOut.Line1.Column3 = rMatrixIn.get(0, 2);
+ rMatrixOut.Line1.Column4 = rMatrixIn.get(0, 3);
+ rMatrixOut.Line2.Column1 = rMatrixIn.get(1, 0);
+ rMatrixOut.Line2.Column2 = rMatrixIn.get(1, 1);
+ rMatrixOut.Line2.Column3 = rMatrixIn.get(1, 2);
+ rMatrixOut.Line2.Column4 = rMatrixIn.get(1, 3);
+ rMatrixOut.Line3.Column1 = rMatrixIn.get(2, 0);
+ rMatrixOut.Line3.Column2 = rMatrixIn.get(2, 1);
+ rMatrixOut.Line3.Column3 = rMatrixIn.get(2, 2);
+ rMatrixOut.Line3.Column4 = rMatrixIn.get(2, 3);
+ rMatrixOut.Line4.Column1 = rMatrixIn.get(3, 0);
+ rMatrixOut.Line4.Column2 = rMatrixIn.get(3, 1);
+ rMatrixOut.Line4.Column3 = rMatrixIn.get(3, 2);
+ rMatrixOut.Line4.Column4 = rMatrixIn.get(3, 3);
+}
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/numeric/ftools.cxx b/basegfx/source/numeric/ftools.cxx
new file mode 100644
index 0000000000..246d8d548a
--- /dev/null
+++ b/basegfx/source/numeric/ftools.cxx
@@ -0,0 +1,135 @@
+/* -*- 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 <basegfx/numeric/ftools.hxx>
+
+namespace basegfx
+{
+ double snapToNearestMultiple(double v, const double fStep)
+ {
+ if(fTools::equalZero(fStep))
+ {
+ // with a zero step, all snaps to 0.0
+ return 0.0;
+ }
+ else
+ {
+ const double fHalfStep(fStep * 0.5);
+ const double fChange(fHalfStep - fmod(v + fHalfStep, fStep));
+
+ if(basegfx::fTools::equal(fabs(v), fabs(fChange)))
+ {
+ return 0.0;
+ }
+ else
+ {
+ return v + fChange;
+ }
+ }
+ }
+
+ double snapToZeroRange(double v, double fWidth)
+ {
+ if(fTools::equalZero(fWidth))
+ {
+ // with no range all snaps to range bound
+ return 0.0;
+ }
+ else
+ {
+ if(v < 0.0 || v > fWidth)
+ {
+ double fRetval(fmod(v, fWidth));
+
+ if(fRetval < 0.0)
+ {
+ fRetval += fWidth;
+ }
+
+ return fRetval;
+ }
+ else
+ {
+ return v;
+ }
+ }
+ }
+
+ double snapToRange(double v, double fLow, double fHigh)
+ {
+ if(fTools::equal(fLow, fHigh))
+ {
+ // with no range all snaps to range bound
+ return 0.0;
+ }
+ else
+ {
+ if(fLow > fHigh)
+ {
+ // correct range order. Evtl. assert this (?)
+ std::swap(fLow, fHigh);
+ }
+
+ if(v < fLow || v > fHigh)
+ {
+ return snapToZeroRange(v - fLow, fHigh - fLow) + fLow;
+ }
+ else
+ {
+ return v;
+ }
+ }
+ }
+
+ double normalizeToRange(double v, const double fRange)
+ {
+ if(fTools::lessOrEqual(fRange, 0.0))
+ {
+ // with a zero (or less) range, all normalizes to 0.0
+ return 0.0;
+ }
+
+ const bool bNegative(fTools::less(v, 0.0));
+
+ if(bNegative)
+ {
+ if(fTools::moreOrEqual(v, -fRange))
+ {
+ // in range [-fRange, 0.0[, shift one step
+ return v + fRange;
+ }
+
+ // re-calculate
+ return v - (floor(v/fRange)*fRange);
+ }
+ else
+ {
+ if(fTools::less(v, fRange))
+ {
+ // already in range [0.0, fRange[, nothing to do
+ return v;
+ }
+
+ // re-calculate
+ return v - (floor(v/fRange)*fRange);
+ }
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/point/b2dpoint.cxx b/basegfx/source/point/b2dpoint.cxx
new file mode 100644
index 0000000000..d12c3dd04c
--- /dev/null
+++ b/basegfx/source/point/b2dpoint.cxx
@@ -0,0 +1,51 @@
+/* -*- 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 <basegfx/point/b2dpoint.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+
+namespace basegfx
+{
+ B2DPoint& B2DPoint::operator*=( const ::basegfx::B2DHomMatrix& rMat )
+ {
+ double fTempX(
+ rMat.get(0, 0) * mnX +
+ rMat.get(0, 1) * mnY +
+ rMat.get(0, 2));
+ double fTempY(
+ rMat.get(1, 0) * mnX +
+ rMat.get(1, 1) * mnY +
+ rMat.get(1, 2));
+
+ mnX = fTempX;
+ mnY = fTempY;
+
+ return *this;
+ }
+
+ B2DPoint operator*( const ::basegfx::B2DHomMatrix& rMat, const B2DPoint& rPoint )
+ {
+ B2DPoint aRes( rPoint );
+ aRes *= rMat;
+ return aRes;
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/point/b2ipoint.cxx b/basegfx/source/point/b2ipoint.cxx
new file mode 100644
index 0000000000..2790fe3a18
--- /dev/null
+++ b/basegfx/source/point/b2ipoint.cxx
@@ -0,0 +1,51 @@
+/* -*- 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 <basegfx/point/b2ipoint.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+
+namespace basegfx
+{
+ B2IPoint& B2IPoint::operator=( const ::basegfx::B2ITuple& rPoint )
+ {
+ mnX = rPoint.getX();
+ mnY = rPoint.getY();
+ return *this;
+ }
+
+ B2IPoint& B2IPoint::operator*=( const ::basegfx::B2DHomMatrix& rMat )
+ {
+ double fTempX(
+ rMat.get(0, 0) * mnX +
+ rMat.get(0, 1) * mnY +
+ rMat.get(0, 2));
+ double fTempY(
+ rMat.get(1, 0) * mnX +
+ rMat.get(1, 1) * mnY +
+ rMat.get(1, 2));
+
+ mnX = fround(fTempX);
+ mnY = fround(fTempY);
+
+ return *this;
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/point/b3dpoint.cxx b/basegfx/source/point/b3dpoint.cxx
new file mode 100644
index 0000000000..7d2d4cf4a2
--- /dev/null
+++ b/basegfx/source/point/b3dpoint.cxx
@@ -0,0 +1,76 @@
+/* -*- 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 <basegfx/point/b3dpoint.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+
+namespace basegfx
+{
+ B3DPoint& B3DPoint::operator*=( const ::basegfx::B3DHomMatrix& rMat )
+ {
+ double fTempX(
+ rMat.get(0, 0) * mnX +
+ rMat.get(0, 1) * mnY +
+ rMat.get(0, 2) * mnZ +
+ rMat.get(0, 3));
+ double fTempY(
+ rMat.get(1, 0) * mnX +
+ rMat.get(1, 1) * mnY +
+ rMat.get(1, 2) * mnZ +
+ rMat.get(1, 3));
+ double fTempZ(
+ rMat.get(2, 0) * mnX +
+ rMat.get(2, 1) * mnY +
+ rMat.get(2, 2) * mnZ +
+ rMat.get(2, 3));
+
+ if(!rMat.isLastLineDefault())
+ {
+ const double fOne(1.0);
+ const double fTempM(
+ rMat.get(3, 0) * mnX +
+ rMat.get(3, 1) * mnY +
+ rMat.get(3, 2) * mnZ +
+ rMat.get(3, 3));
+
+ if(!fTools::equalZero(fTempM) && !fTools::equal(fOne, fTempM))
+ {
+ fTempX /= fTempM;
+ fTempY /= fTempM;
+ fTempZ /= fTempM;
+ }
+ }
+
+ mnX = fTempX;
+ mnY = fTempY;
+ mnZ = fTempZ;
+
+ return *this;
+ }
+
+ B3DPoint operator*( const ::basegfx::B3DHomMatrix& rMat, const B3DPoint& rPoint )
+ {
+ B3DPoint aRes( rPoint );
+ aRes *= rMat;
+ return aRes;
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/WaveLine.cxx b/basegfx/source/polygon/WaveLine.cxx
new file mode 100644
index 0000000000..bc85c45362
--- /dev/null
+++ b/basegfx/source/polygon/WaveLine.cxx
@@ -0,0 +1,52 @@
+/* -*- 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/.
+ *
+ */
+
+#include <basegfx/polygon/WaveLine.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+
+namespace basegfx
+{
+BASEGFX_DLLPUBLIC B2DPolygon createWaveLinePolygon(basegfx::B2DRectangle const& rRectangle)
+{
+ basegfx::B2DPolygon aPolygon;
+
+ double fWaveHeight = rRectangle.getHeight();
+ // Wavelength depends on the wave height so it looks nice
+ double fHalfWaveLength = fWaveHeight + 1.0;
+ double fWaveAmplitude = fWaveHeight / 2.0;
+
+ double fLastX = rRectangle.getMinX();
+ double fBaseY = rRectangle.getMinY() + fWaveAmplitude;
+ double fDirection = 1.0;
+
+ // In quadratic bezier the curve is 1/2 of the control height
+ // so we need to compensate for that.
+ constexpr double fHeightCompensation = 2.0;
+
+ aPolygon.append(basegfx::B2DPoint(fLastX, fBaseY));
+
+ for (double fI = fHalfWaveLength; fI <= rRectangle.getWidth(); fI += fHalfWaveLength)
+ {
+ basegfx::B2DPoint aPoint(fLastX + fHalfWaveLength, fBaseY);
+ basegfx::B2DPoint aControl(fLastX + (fHalfWaveLength / 2.0),
+ fBaseY + fDirection * fWaveAmplitude * fHeightCompensation);
+
+ aPolygon.appendQuadraticBezierSegment(aControl, aPoint);
+
+ fLastX = aPoint.getX(); // next iteration
+ fDirection *= -1.0; // fDirection iterates between 1 and -1
+ }
+
+ return aPolygon;
+}
+
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dlinegeometry.cxx b/basegfx/source/polygon/b2dlinegeometry.cxx
new file mode 100644
index 0000000000..437ebcbb49
--- /dev/null
+++ b/basegfx/source/polygon/b2dlinegeometry.cxx
@@ -0,0 +1,998 @@
+/* -*- 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 <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <com/sun/star/drawing/LineCap.hpp>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dpolygontriangulator.hxx>
+
+namespace basegfx::utils
+{
+ B2DPolyPolygon createAreaGeometryForLineStartEnd(
+ const B2DPolygon& rCandidate,
+ const B2DPolyPolygon& rArrow,
+ bool bStart,
+ double fWidth,
+ double fCandidateLength,
+ double fDockingPosition, // 0->top, 1->bottom
+ double* pConsumedLength,
+ double fShift)
+ {
+ B2DPolyPolygon aRetval;
+ assert((rCandidate.count() > 1) && "createAreaGeometryForLineStartEnd: Line polygon has too few points");
+ assert((rArrow.count() > 0) && "createAreaGeometryForLineStartEnd: Empty arrow utils::PolyPolygon");
+ assert((fWidth > 0.0) && "createAreaGeometryForLineStartEnd: Width too small");
+ assert((fDockingPosition >= 0.0 && fDockingPosition <= 1.0) &&
+ "createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0]");
+
+ if(fWidth < 0.0)
+ {
+ fWidth = -fWidth;
+ }
+
+ if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth))
+ {
+ if(fDockingPosition < 0.0)
+ {
+ fDockingPosition = 0.0;
+ }
+ else if(fDockingPosition > 1.0)
+ {
+ fDockingPosition = 1.0;
+ }
+
+ // init return value from arrow
+ aRetval.append(rArrow);
+
+ // get size of the arrow
+ const B2DRange aArrowSize(getRange(rArrow));
+
+ // build ArrowTransform; center in X, align with axis in Y
+ B2DHomMatrix aArrowTransform(basegfx::utils::createTranslateB2DHomMatrix(
+ -aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY()));
+
+ // scale to target size
+ const double fArrowScale(fWidth / (aArrowSize.getWidth()));
+ aArrowTransform.scale(fArrowScale, fArrowScale);
+
+ // get arrow size in Y
+ B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY());
+ aUpperCenter *= aArrowTransform;
+ const double fArrowYLength(B2DVector(aUpperCenter).getLength());
+
+ // move arrow to have docking position centered
+ aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition + fShift);
+
+ // prepare polygon length
+ if(fTools::equalZero(fCandidateLength))
+ {
+ fCandidateLength = getLength(rCandidate);
+ }
+
+ // get the polygon vector we want to plant this arrow on
+ const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition) - fShift);
+ const B2DVector aHead(rCandidate.getB2DPoint(bStart ? 0 : rCandidate.count() - 1));
+ const B2DVector aTail(getPositionAbsolute(rCandidate,
+ bStart ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength));
+
+ // from that vector, take the needed rotation and add rotate for arrow to transformation
+ const B2DVector aTargetDirection(aHead - aTail);
+ const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + M_PI_2);
+
+ // rotate around docking position
+ aArrowTransform.rotate(fRotation);
+
+ // move arrow docking position to polygon head
+ aArrowTransform.translate(aHead.getX(), aHead.getY());
+
+ // transform retval and close
+ aRetval.transform(aArrowTransform);
+ aRetval.setClosed(true);
+
+ // if pConsumedLength is asked for, fill it
+ if(pConsumedLength)
+ {
+ *pConsumedLength = fConsumedLength;
+ }
+ }
+
+ return aRetval;
+ }
+} // end of namespace
+
+namespace basegfx
+{
+ // anonymous namespace for local helpers
+ namespace
+ {
+ bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
+ {
+ // isBezier() is true, already tested by caller
+ const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint());
+
+ if(aEdge.equalZero())
+ {
+ // start and end point the same, but control vectors used -> balloon curve loop
+ // is not a simple edge
+ return false;
+ }
+
+ // get tangentA and scalar with edge
+ const B2DVector aTangentA(rCandidate.getTangent(0.0));
+ const double fScalarAE(aEdge.scalar(aTangentA));
+
+ if(fTools::lessOrEqual(fScalarAE, 0.0))
+ {
+ // angle between TangentA and Edge is bigger or equal 90 degrees
+ return false;
+ }
+
+ // get self-scalars for E and A
+ const double fScalarE(aEdge.scalar(aEdge));
+ const double fScalarA(aTangentA.scalar(aTangentA));
+ const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad);
+
+ if(fTools::moreOrEqual(fScalarA, fLengthCompareE))
+ {
+ // length of TangentA is more than fMaxPartOfEdge of length of edge
+ return false;
+ }
+
+ if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad))
+ {
+ // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
+ return false;
+ }
+
+ // get tangentB and scalar with edge
+ const B2DVector aTangentB(rCandidate.getTangent(1.0));
+ const double fScalarBE(aEdge.scalar(aTangentB));
+
+ if(fTools::lessOrEqual(fScalarBE, 0.0))
+ {
+ // angle between TangentB and Edge is bigger or equal 90 degrees
+ return false;
+ }
+
+ // get self-scalar for B
+ const double fScalarB(aTangentB.scalar(aTangentB));
+
+ if(fTools::moreOrEqual(fScalarB, fLengthCompareE))
+ {
+ // length of TangentB is more than fMaxPartOfEdge of length of edge
+ return false;
+ }
+
+ if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad))
+ {
+ // angle between TangentB and Edge is bigger or equal defined by fMaxCos
+ return false;
+ }
+
+ return true;
+ }
+
+ void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth)
+ {
+ if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad))
+ {
+ rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint());
+ }
+ else
+ {
+ B2DCubicBezier aLeft, aRight;
+ rCandidate.split(0.5, &aLeft, &aRight);
+
+ impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
+ impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
+ }
+ }
+
+ B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(rCandidate.areControlPointsUsed() && nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DPolygon aRetval;
+ B2DCubicBezier aEdge;
+
+ // prepare edge for loop
+ aEdge.setStartPoint(rCandidate.getB2DPoint(0));
+ aRetval.append(aEdge.getStartPoint());
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // fill B2DCubicBezier
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
+ aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+
+ // get rid of unnecessary bezier segments
+ aEdge.testAndSolveTrivialBezier();
+
+ if(aEdge.isBezier())
+ {
+ // before splitting recursively with internal simple criteria, use
+ // ExtremumPosFinder to remove those
+ std::vector< double > aExtremumPositions;
+
+ aExtremumPositions.reserve(4);
+ aEdge.getAllExtremumPositions(aExtremumPositions);
+
+ const sal_uInt32 nCount(aExtremumPositions.size());
+
+ if(nCount)
+ {
+ if(nCount > 1)
+ {
+ // create order from left to right
+ std::sort(aExtremumPositions.begin(), aExtremumPositions.end());
+ }
+
+ for(sal_uInt32 b(0); b < nCount;)
+ {
+ // split aEdge at next split pos
+ B2DCubicBezier aLeft;
+ const double fSplitPos(aExtremumPositions[b++]);
+
+ aEdge.split(fSplitPos, &aLeft, &aEdge);
+ aLeft.testAndSolveTrivialBezier();
+
+ // consume left part
+ if(aLeft.isBezier())
+ {
+ impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
+ }
+ else
+ {
+ aRetval.append(aLeft.getEndPoint());
+ }
+
+ if(b < nCount)
+ {
+ // correct the remaining split positions to fit to shortened aEdge
+ const double fScaleFactor(1.0 / (1.0 - fSplitPos));
+
+ for(sal_uInt32 c(b); c < nCount; c++)
+ {
+ aExtremumPositions[c] = (aExtremumPositions[c] - fSplitPos) * fScaleFactor;
+ }
+ }
+ }
+
+ // test the shortened rest of aEdge
+ aEdge.testAndSolveTrivialBezier();
+
+ // consume right part
+ if(aEdge.isBezier())
+ {
+ impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
+ }
+ else
+ {
+ aRetval.append(aEdge.getEndPoint());
+ }
+ }
+ else
+ {
+ impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
+ }
+ }
+ else
+ {
+ // straight edge, add point
+ aRetval.append(aEdge.getEndPoint());
+ }
+
+ // prepare edge for next step
+ aEdge.setStartPoint(aEdge.getEndPoint());
+ }
+
+ // copy closed flag and check for double points
+ aRetval.setClosed(rCandidate.isClosed());
+ aRetval.removeDoublePoints();
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolygon createAreaGeometryForEdge(
+ const B2DCubicBezier& rEdge,
+ double fHalfLineWidth,
+ bool bStartRound,
+ bool bEndRound,
+ bool bStartSquare,
+ bool bEndSquare,
+ basegfx::triangulator::B2DTriangleVector* pTriangles)
+ {
+ // create polygon for edge
+ // Unfortunately, while it would be geometrically correct to not add
+ // the in-between points EdgeEnd and EdgeStart, it leads to rounding
+ // errors when converting to integer polygon coordinates for painting
+ if(rEdge.isBezier())
+ {
+ // prepare target and data common for upper and lower
+ B2DPolygon aBezierPolygon;
+ const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint());
+ const double fEdgeLength(aPureEdgeVector.getLength());
+ const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength));
+ B2DVector aTangentA(rEdge.getTangent(0.0)); aTangentA.normalize();
+ B2DVector aTangentB(rEdge.getTangent(1.0)); aTangentB.normalize();
+ const B2DVector aNormalizedPerpendicularA(getPerpendicular(aTangentA));
+ const B2DVector aNormalizedPerpendicularB(getPerpendicular(aTangentB));
+
+ // create upper displacement vectors and check if they cut
+ const B2DVector aPerpendStartA(aNormalizedPerpendicularA * -fHalfLineWidth);
+ const B2DVector aPerpendEndA(aNormalizedPerpendicularB * -fHalfLineWidth);
+ double fCutA(0.0);
+ const CutFlagValue aCutA(utils::findCut(
+ rEdge.getStartPoint(), aPerpendStartA,
+ rEdge.getEndPoint(), aPerpendEndA,
+ CutFlagValue::ALL, &fCutA));
+ const bool bCutA(aCutA != CutFlagValue::NONE);
+
+ // create lower displacement vectors and check if they cut
+ const B2DVector aPerpendStartB(aNormalizedPerpendicularA * fHalfLineWidth);
+ const B2DVector aPerpendEndB(aNormalizedPerpendicularB * fHalfLineWidth);
+ double fCutB(0.0);
+ const CutFlagValue aCutB(utils::findCut(
+ rEdge.getEndPoint(), aPerpendEndB,
+ rEdge.getStartPoint(), aPerpendStartB,
+ CutFlagValue::ALL, &fCutB));
+ const bool bCutB(aCutB != CutFlagValue::NONE);
+
+ // check if cut happens
+ const bool bCut(bCutA || bCutB);
+ B2DPoint aCutPoint;
+
+ // create left edge
+ if(bStartRound || bStartSquare)
+ {
+ if(bStartRound)
+ {
+ basegfx::B2DPolygon aStartPolygon(utils::createHalfUnitCircle());
+
+ aStartPolygon.transform(
+ utils::createScaleShearXRotateTranslateB2DHomMatrix(
+ fHalfLineWidth, fHalfLineWidth,
+ 0.0,
+ atan2(aTangentA.getY(), aTangentA.getX()) + M_PI_2,
+ rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
+ aBezierPolygon.append(aStartPolygon);
+ }
+ else // bStartSquare
+ {
+ const basegfx::B2DPoint aStart(rEdge.getStartPoint() - (aTangentA * fHalfLineWidth));
+
+ if(bCutB)
+ {
+ aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartB);
+ }
+
+ aBezierPolygon.append(aStart + aPerpendStartB);
+ aBezierPolygon.append(aStart + aPerpendStartA);
+
+ if(bCutA)
+ {
+ aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartA);
+ }
+ }
+ }
+ else
+ {
+ // append original in-between point
+ aBezierPolygon.append(rEdge.getStartPoint());
+ }
+
+ // create upper edge.
+ {
+ if(bCutA)
+ {
+ // calculate cut point and add
+ aCutPoint = rEdge.getStartPoint() + (aPerpendStartA * fCutA);
+ aBezierPolygon.append(aCutPoint);
+ }
+ else
+ {
+ // create scaled bezier segment
+ const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStartA);
+ const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEndA);
+ const B2DVector aEdge(aEnd - aStart);
+ const double fLength(aEdge.getLength());
+ const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
+ const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint());
+ const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint());
+
+ aBezierPolygon.append(aStart);
+ aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
+ }
+ }
+
+ // create right edge
+ if(bEndRound || bEndSquare)
+ {
+ if(bEndRound)
+ {
+ basegfx::B2DPolygon aEndPolygon(utils::createHalfUnitCircle());
+
+ aEndPolygon.transform(
+ utils::createScaleShearXRotateTranslateB2DHomMatrix(
+ fHalfLineWidth, fHalfLineWidth,
+ 0.0,
+ atan2(aTangentB.getY(), aTangentB.getX()) - M_PI_2,
+ rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
+ aBezierPolygon.append(aEndPolygon);
+ }
+ else // bEndSquare
+ {
+ const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + (aTangentB * fHalfLineWidth));
+
+ if(bCutA)
+ {
+ aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndA);
+ }
+
+ aBezierPolygon.append(aEnd + aPerpendEndA);
+ aBezierPolygon.append(aEnd + aPerpendEndB);
+
+ if(bCutB)
+ {
+ aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndB);
+ }
+ }
+ }
+ else
+ {
+ // append original in-between point
+ aBezierPolygon.append(rEdge.getEndPoint());
+ }
+
+ // create lower edge.
+ {
+ if(bCutB)
+ {
+ // calculate cut point and add
+ aCutPoint = rEdge.getEndPoint() + (aPerpendEndB * fCutB);
+ aBezierPolygon.append(aCutPoint);
+ }
+ else
+ {
+ // create scaled bezier segment
+ const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEndB);
+ const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStartB);
+ const B2DVector aEdge(aEnd - aStart);
+ const double fLength(aEdge.getLength());
+ const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
+ const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint());
+ const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint());
+
+ aBezierPolygon.append(aStart);
+ aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
+ }
+ }
+
+ // close
+ aBezierPolygon.setClosed(true);
+
+ if(bStartRound || bEndRound)
+ {
+ // double points possible when round caps are used at start or end
+ aBezierPolygon.removeDoublePoints();
+ }
+
+ if(bCut && ((bStartRound || bStartSquare) && (bEndRound || bEndSquare)))
+ {
+ // When cut exists and both ends are extended with caps, a self-intersecting polygon
+ // is created; one cut point is known, but there is a 2nd one in the caps geometry.
+ // Solve by using tooling.
+ // Remark: This nearly never happens due to curve preparations to extreme points
+ // and maximum angle turning, but I constructed a test case and checked that it is
+ // working properly.
+ const B2DPolyPolygon aTemp(utils::solveCrossovers(aBezierPolygon));
+ const sal_uInt32 nTempCount(aTemp.count());
+
+ if(nTempCount)
+ {
+ if(nTempCount > 1)
+ {
+ // as expected, multiple polygons (with same orientation). Remove
+ // the one which contains aCutPoint, or better take the one without
+ for (sal_uInt32 a(0); a < nTempCount; a++)
+ {
+ aBezierPolygon = aTemp.getB2DPolygon(a);
+
+ const sal_uInt32 nCandCount(aBezierPolygon.count());
+
+ for(sal_uInt32 b(0); b < nCandCount; b++)
+ {
+ if(aCutPoint.equal(aBezierPolygon.getB2DPoint(b)))
+ {
+ aBezierPolygon.clear();
+ break;
+ }
+ }
+
+ if(aBezierPolygon.count())
+ {
+ break;
+ }
+ }
+
+ OSL_ENSURE(aBezierPolygon.count(), "Error in line geometry creation, could not solve self-intersection (!)");
+ }
+ else
+ {
+ // none found, use result
+ aBezierPolygon = aTemp.getB2DPolygon(0);
+ }
+ }
+ else
+ {
+ OSL_ENSURE(false, "Error in line geometry creation, could not solve self-intersection (!)");
+ }
+ }
+
+ if(nullptr != pTriangles)
+ {
+ const basegfx::triangulator::B2DTriangleVector aResult(
+ basegfx::triangulator::triangulate(
+ aBezierPolygon));
+ pTriangles->insert(pTriangles->end(), aResult.begin(), aResult.end());
+ aBezierPolygon.clear();
+ }
+
+ // return
+ return aBezierPolygon;
+ }
+ else
+ {
+ // Get start and end point, create tangent and set to needed length
+ B2DVector aTangent(rEdge.getEndPoint() - rEdge.getStartPoint());
+ aTangent.setLength(fHalfLineWidth);
+
+ // prepare return value
+ B2DPolygon aEdgePolygon;
+
+ // buffered angle
+ double fAngle(0.0);
+ bool bAngle(false);
+
+ // buffered perpendicular
+ B2DVector aPerpend;
+ bool bPerpend(false);
+
+ // create left vertical
+ if(bStartRound)
+ {
+ aEdgePolygon = utils::createHalfUnitCircle();
+ fAngle = atan2(aTangent.getY(), aTangent.getX());
+ bAngle = true;
+ aEdgePolygon.transform(
+ utils::createScaleShearXRotateTranslateB2DHomMatrix(
+ fHalfLineWidth, fHalfLineWidth,
+ 0.0,
+ fAngle + M_PI_2,
+ rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
+ }
+ else
+ {
+ aPerpend.setX(-aTangent.getY());
+ aPerpend.setY(aTangent.getX());
+ bPerpend = true;
+
+ if(bStartSquare)
+ {
+ const basegfx::B2DPoint aStart(rEdge.getStartPoint() - aTangent);
+
+ aEdgePolygon.append(aStart + aPerpend);
+ aEdgePolygon.append(aStart - aPerpend);
+ }
+ else
+ {
+ aEdgePolygon.append(rEdge.getStartPoint() + aPerpend);
+ aEdgePolygon.append(rEdge.getStartPoint()); // keep the in-between point for numerical reasons
+ aEdgePolygon.append(rEdge.getStartPoint() - aPerpend);
+ }
+ }
+
+ // create right vertical
+ if(bEndRound)
+ {
+ basegfx::B2DPolygon aEndPolygon(utils::createHalfUnitCircle());
+
+ if(!bAngle)
+ {
+ fAngle = atan2(aTangent.getY(), aTangent.getX());
+ }
+
+ aEndPolygon.transform(
+ utils::createScaleShearXRotateTranslateB2DHomMatrix(
+ fHalfLineWidth, fHalfLineWidth,
+ 0.0,
+ fAngle - M_PI_2,
+ rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
+ aEdgePolygon.append(aEndPolygon);
+ }
+ else
+ {
+ if(!bPerpend)
+ {
+ aPerpend.setX(-aTangent.getY());
+ aPerpend.setY(aTangent.getX());
+ }
+
+ if(bEndSquare)
+ {
+ const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + aTangent);
+
+ aEdgePolygon.append(aEnd - aPerpend);
+ aEdgePolygon.append(aEnd + aPerpend);
+ }
+ else
+ {
+ aEdgePolygon.append(rEdge.getEndPoint() - aPerpend);
+ aEdgePolygon.append(rEdge.getEndPoint()); // keep the in-between point for numerical reasons
+ aEdgePolygon.append(rEdge.getEndPoint() + aPerpend);
+ }
+ }
+
+ // close and return
+ aEdgePolygon.setClosed(true);
+
+ if(nullptr != pTriangles)
+ {
+ const basegfx::triangulator::B2DTriangleVector aResult(
+ basegfx::triangulator::triangulate(
+ aEdgePolygon));
+ pTriangles->insert(pTriangles->end(), aResult.begin(), aResult.end());
+ aEdgePolygon.clear();
+ }
+
+ return aEdgePolygon;
+ }
+ }
+
+ B2DPolygon createAreaGeometryForJoin(
+ const B2DVector& rTangentPrev,
+ const B2DVector& rTangentEdge,
+ const B2DVector& rPerpendPrev,
+ const B2DVector& rPerpendEdge,
+ const B2DPoint& rPoint,
+ double fHalfLineWidth,
+ B2DLineJoin eJoin,
+ double fMiterMinimumAngle)
+ {
+ SAL_WARN_IF(fHalfLineWidth <= 0.0,"basegfx","createAreaGeometryForJoin: LineWidth too small (!)");
+ assert((eJoin != B2DLineJoin::NONE) && "createAreaGeometryForJoin: B2DLineJoin::NONE not allowed (!)");
+
+ // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint
+ B2DPolygon aEdgePolygon;
+ const B2DPoint aStartPoint(rPoint + rPerpendPrev);
+ const B2DPoint aEndPoint(rPoint + rPerpendEdge);
+
+ // test if for Miter, the angle is too small and the fallback
+ // to bevel needs to be used
+ if(eJoin == B2DLineJoin::Miter)
+ {
+ const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge)));
+
+ if((M_PI - fAngle) < fMiterMinimumAngle)
+ {
+ // fallback to bevel
+ eJoin = B2DLineJoin::Bevel;
+ }
+ }
+
+ switch(eJoin)
+ {
+ case B2DLineJoin::Miter :
+ {
+ aEdgePolygon.append(aEndPoint);
+ aEdgePolygon.append(rPoint);
+ aEdgePolygon.append(aStartPoint);
+
+ // Look for the cut point between start point along rTangentPrev and
+ // end point along rTangentEdge. -rTangentEdge should be used, but since
+ // the cut value is used for interpolating along the first edge, the negation
+ // is not needed since the same fCut will be found on the first edge.
+ // If it exists, insert it to complete the mitered fill polygon.
+ double fCutPos(0.0);
+ utils::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CutFlagValue::ALL, &fCutPos);
+
+ if(fCutPos != 0.0)
+ {
+ const B2DPoint aCutPoint(aStartPoint + (rTangentPrev * fCutPos));
+ aEdgePolygon.append(aCutPoint);
+ }
+
+ break;
+ }
+ case B2DLineJoin::Round :
+ {
+ // use tooling to add needed EllipseSegment
+ double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX()));
+ double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX()));
+
+ // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
+ if(fAngleStart < 0.0)
+ {
+ fAngleStart += 2 * M_PI;
+ }
+
+ if(fAngleEnd < 0.0)
+ {
+ fAngleEnd += 2 * M_PI;
+ }
+
+ const B2DPolygon aBow(utils::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd));
+
+ if(aBow.count() > 1)
+ {
+ // #i101491#
+ // use the original start/end positions; the ones from bow creation may be numerically
+ // different due to their different creation. To guarantee good merging quality with edges
+ // and edge roundings (and to reduce point count)
+ aEdgePolygon = aBow;
+ aEdgePolygon.setB2DPoint(0, aStartPoint);
+ aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint);
+ aEdgePolygon.append(rPoint);
+
+ break;
+ }
+ else
+ {
+ [[fallthrough]]; // wanted fall-through to default
+ }
+ }
+ default: // B2DLineJoin::Bevel
+ {
+ aEdgePolygon.append(aEndPoint);
+ aEdgePolygon.append(rPoint);
+ aEdgePolygon.append(aStartPoint);
+
+ break;
+ }
+ }
+
+ // create last polygon part for edge
+ aEdgePolygon.setClosed(true);
+
+ return aEdgePolygon;
+ }
+ } // end of anonymous namespace
+
+ namespace utils
+ {
+ B2DPolyPolygon createAreaGeometry(
+ const B2DPolygon& rCandidate,
+ double fHalfLineWidth,
+ B2DLineJoin eJoin,
+ css::drawing::LineCap eCap,
+ double fMaxAllowedAngle,
+ double fMaxPartOfEdge,
+ double fMiterMinimumAngle)
+ {
+ if(fMaxAllowedAngle > M_PI_2)
+ {
+ fMaxAllowedAngle = M_PI_2;
+ }
+ else if(fMaxAllowedAngle < 0.01 * M_PI_2)
+ {
+ fMaxAllowedAngle = 0.01 * M_PI_2;
+ }
+
+ if(fMaxPartOfEdge > 1.0)
+ {
+ fMaxPartOfEdge = 1.0;
+ }
+ else if(fMaxPartOfEdge < 0.01)
+ {
+ fMaxPartOfEdge = 0.01;
+ }
+
+ if(fMiterMinimumAngle > M_PI)
+ {
+ fMiterMinimumAngle = M_PI;
+ }
+ else if(fMiterMinimumAngle < 0.01 * M_PI)
+ {
+ fMiterMinimumAngle = 0.01 * M_PI;
+ }
+
+ B2DPolygon aCandidate(rCandidate);
+ const double fMaxCos(cos(fMaxAllowedAngle));
+
+ aCandidate.removeDoublePoints();
+ aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge);
+
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(nPointCount)
+ {
+ B2DPolyPolygon aRetval;
+ const bool bIsClosed(aCandidate.isClosed());
+ const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
+ const bool bLineCap(!bIsClosed && eCap != css::drawing::LineCap_BUTT);
+
+ if(nEdgeCount)
+ {
+ B2DCubicBezier aEdge;
+ B2DCubicBezier aPrev;
+
+ const bool bEventuallyCreateLineJoin(eJoin != B2DLineJoin::NONE);
+ // prepare edge
+ aEdge.setStartPoint(aCandidate.getB2DPoint(0));
+
+ if(bIsClosed && bEventuallyCreateLineJoin)
+ {
+ // prepare previous edge
+ const sal_uInt32 nPrevIndex(nPointCount - 1);
+ aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex));
+ aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex));
+ aPrev.setControlPointB(aCandidate.getPrevControlPoint(0));
+ aPrev.setEndPoint(aEdge.getStartPoint());
+ }
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // fill current Edge
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aEdge.setControlPointA(aCandidate.getNextControlPoint(a));
+ aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex));
+ aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex));
+
+ // check and create linejoin
+ if(bEventuallyCreateLineJoin && (bIsClosed || a != 0))
+ {
+ B2DVector aTangentPrev(aPrev.getTangent(1.0)); aTangentPrev.normalize();
+ B2DVector aTangentEdge(aEdge.getTangent(0.0)); aTangentEdge.normalize();
+ B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge));
+
+ if(aOrientation == B2VectorOrientation::Neutral)
+ {
+ // they are parallel or empty; if they are both not zero and point
+ // in opposite direction, a half-circle is needed
+ if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero())
+ {
+ const double fAngle(fabs(aTangentPrev.angle(aTangentEdge)));
+
+ if(fTools::equal(fAngle, M_PI))
+ {
+ // for half-circle production, fallback to positive
+ // orientation
+ aOrientation = B2VectorOrientation::Positive;
+ }
+ }
+ }
+
+ if(aOrientation == B2VectorOrientation::Positive)
+ {
+ const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * -fHalfLineWidth);
+ const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * -fHalfLineWidth);
+
+ aRetval.append(
+ createAreaGeometryForJoin(
+ aTangentPrev,
+ aTangentEdge,
+ aPerpendPrev,
+ aPerpendEdge,
+ aEdge.getStartPoint(),
+ fHalfLineWidth,
+ eJoin,
+ fMiterMinimumAngle));
+ }
+ else if(aOrientation == B2VectorOrientation::Negative)
+ {
+ const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * fHalfLineWidth);
+ const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * fHalfLineWidth);
+
+ aRetval.append(
+ createAreaGeometryForJoin(
+ aTangentEdge,
+ aTangentPrev,
+ aPerpendEdge,
+ aPerpendPrev,
+ aEdge.getStartPoint(),
+ fHalfLineWidth,
+ eJoin,
+ fMiterMinimumAngle));
+ }
+ }
+
+ // create geometry for edge
+ const bool bLast(a + 1 == nEdgeCount);
+
+ if(bLineCap)
+ {
+ const bool bFirst(!a);
+
+ aRetval.append(
+ createAreaGeometryForEdge(
+ aEdge,
+ fHalfLineWidth,
+ bFirst && eCap == css::drawing::LineCap_ROUND,
+ bLast && eCap == css::drawing::LineCap_ROUND,
+ bFirst && eCap == css::drawing::LineCap_SQUARE,
+ bLast && eCap == css::drawing::LineCap_SQUARE,
+ nullptr));
+ }
+ else
+ {
+ aRetval.append(
+ createAreaGeometryForEdge(
+ aEdge,
+ fHalfLineWidth,
+ false,
+ false,
+ false,
+ false,
+ nullptr));
+ }
+
+ // prepare next step
+ if(!bLast)
+ {
+ if(bEventuallyCreateLineJoin)
+ {
+ aPrev = aEdge;
+ }
+
+ aEdge.setStartPoint(aEdge.getEndPoint());
+ }
+ }
+ }
+ else
+ {
+ // point count, but no edge count -> single point
+ const basegfx::B2DPolygon aCircle(
+ createPolygonFromCircle(
+ aCandidate.getB2DPoint(0),
+ fHalfLineWidth));
+
+ aRetval.append(aCircle);
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return B2DPolyPolygon(rCandidate);
+ }
+ }
+ } // end of namespace utils
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dpolygon.cxx b/basegfx/source/polygon/b2dpolygon.cxx
new file mode 100644
index 0000000000..cf7309d20d
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolygon.cxx
@@ -0,0 +1,1455 @@
+/* -*- 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 <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <memory>
+#include <vector>
+
+namespace
+{
+
+class CoordinateDataArray2D
+{
+private:
+ std::vector<basegfx::B2DPoint> maVector;
+
+public:
+ explicit CoordinateDataArray2D(sal_uInt32 nCount)
+ : maVector(nCount)
+ {
+ }
+
+ CoordinateDataArray2D(const CoordinateDataArray2D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maVector(rOriginal.maVector.begin() + nIndex, rOriginal.maVector.begin() + (nIndex + nCount))
+ {
+ }
+
+ sal_uInt32 count() const
+ {
+ return maVector.size();
+ }
+
+ bool operator==(const CoordinateDataArray2D& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ const basegfx::B2DPoint& getCoordinate(sal_uInt32 nIndex) const
+ {
+ assert(nIndex < maVector.size());
+ return maVector[nIndex];
+ }
+
+ void setCoordinate(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue)
+ {
+ assert(nIndex < maVector.size());
+ maVector[nIndex] = rValue;
+ }
+
+ void reserve(sal_uInt32 nCount)
+ {
+ maVector.reserve(nCount);
+ }
+
+ void append(const basegfx::B2DPoint& rValue)
+ {
+ maVector.push_back(rValue);
+ }
+
+ void insert(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue, sal_uInt32 nCount)
+ {
+ assert(nCount > 0);
+ assert(nIndex <= maVector.size());
+ // add nCount copies of rValue
+ maVector.insert(maVector.begin() + nIndex, nCount, rValue);
+ }
+
+ void insert(sal_uInt32 nIndex, const CoordinateDataArray2D& rSource)
+ {
+ assert(rSource.maVector.size() > 0);
+ assert(nIndex <= maVector.size());
+ // insert data
+ auto aIndex = maVector.begin();
+ aIndex += nIndex;
+ auto aStart = rSource.maVector.cbegin();
+ auto aEnd = rSource.maVector.cend();
+ maVector.insert(aIndex, aStart, aEnd);
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ assert(nCount > 0);
+ assert(nIndex + nCount <= maVector.size());
+ // remove point data
+ const auto aStart = maVector.begin() + nIndex;
+ const auto aEnd = aStart + nCount;
+ maVector.erase(aStart, aEnd);
+ }
+
+ void flip(bool bIsClosed)
+ {
+ assert(maVector.size() > 1);
+
+ // to keep the same point at index 0, just flip all points except the
+ // first one when closed
+ const sal_uInt32 nHalfSize(bIsClosed ? (maVector.size() - 1) >> 1 : maVector.size() >> 1);
+ auto aStart = bIsClosed ? maVector.begin() + 1 : maVector.begin();
+ auto aEnd = maVector.end() - 1;
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ std::swap(*aStart, *aEnd);
+ ++aStart;
+ --aEnd;
+ }
+ }
+
+ void removeDoublePointsAtBeginEnd()
+ {
+ // remove from end as long as there are at least two points
+ // and begin/end are equal
+ while((maVector.size() > 1) && (maVector[0] == maVector[maVector.size() - 1]))
+ {
+ maVector.pop_back();
+ }
+ }
+
+ void removeDoublePointsWholeTrack()
+ {
+ sal_uInt32 nIndex(0);
+
+ // test as long as there are at least two points and as long as the index
+ // is smaller or equal second last point
+ while((maVector.size() > 1) && (nIndex <= maVector.size() - 2))
+ {
+ if(maVector[nIndex] == maVector[nIndex + 1])
+ {
+ // if next is same as index, delete next
+ maVector.erase(maVector.begin() + (nIndex + 1));
+ }
+ else
+ {
+ // if different, step forward
+ nIndex++;
+ }
+ }
+ }
+
+ void transform(const basegfx::B2DHomMatrix& rMatrix)
+ {
+ for (auto& point : maVector)
+ {
+ point *= rMatrix;
+ }
+ }
+};
+
+class ControlVectorPair2D
+{
+ basegfx::B2DVector maPrevVector;
+ basegfx::B2DVector maNextVector;
+
+public:
+ explicit ControlVectorPair2D() {}
+
+ const basegfx::B2DVector& getPrevVector() const
+ {
+ return maPrevVector;
+ }
+
+ void setPrevVector(const basegfx::B2DVector& rValue)
+ {
+ if(rValue != maPrevVector)
+ maPrevVector = rValue;
+ }
+
+ const basegfx::B2DVector& getNextVector() const
+ {
+ return maNextVector;
+ }
+
+ void setNextVector(const basegfx::B2DVector& rValue)
+ {
+ if(rValue != maNextVector)
+ maNextVector = rValue;
+ }
+
+ bool operator==(const ControlVectorPair2D& rData) const
+ {
+ return (maPrevVector == rData.getPrevVector() && maNextVector == rData.getNextVector());
+ }
+
+ void flip()
+ {
+ std::swap(maPrevVector, maNextVector);
+ }
+};
+
+class ControlVectorArray2D
+{
+ typedef std::vector< ControlVectorPair2D > ControlVectorPair2DVector;
+
+ ControlVectorPair2DVector maVector;
+ sal_uInt32 mnUsedVectors;
+
+public:
+ explicit ControlVectorArray2D(sal_uInt32 nCount)
+ : maVector(nCount),
+ mnUsedVectors(0)
+ {}
+
+ ControlVectorArray2D(const ControlVectorArray2D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : mnUsedVectors(0)
+ {
+ assert(nIndex + nCount <= rOriginal.maVector.size());
+ auto aStart(rOriginal.maVector.begin() + nIndex);
+ auto aEnd(aStart + nCount);
+ maVector.reserve(nCount);
+
+ for(; aStart != aEnd; ++aStart)
+ {
+ if(!aStart->getPrevVector().equalZero())
+ mnUsedVectors++;
+
+ if(!aStart->getNextVector().equalZero())
+ mnUsedVectors++;
+
+ maVector.push_back(*aStart);
+ }
+ }
+
+ bool operator==(const ControlVectorArray2D& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ bool isUsed() const
+ {
+ return (mnUsedVectors != 0);
+ }
+
+ const basegfx::B2DVector& getPrevVector(sal_uInt32 nIndex) const
+ {
+ assert(nIndex < maVector.size());
+ return maVector[nIndex].getPrevVector();
+ }
+
+ void setPrevVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue)
+ {
+ bool bWasUsed(mnUsedVectors && !maVector[nIndex].getPrevVector().equalZero());
+ bool bIsUsed(!rValue.equalZero());
+
+ if(bWasUsed)
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex].setPrevVector(rValue);
+ }
+ else
+ {
+ maVector[nIndex].setPrevVector(basegfx::B2DVector::getEmptyVector());
+ mnUsedVectors--;
+ }
+ }
+ else
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex].setPrevVector(rValue);
+ mnUsedVectors++;
+ }
+ }
+ }
+
+ const basegfx::B2DVector& getNextVector(sal_uInt32 nIndex) const
+ {
+ assert(nIndex < maVector.size());
+ return maVector[nIndex].getNextVector();
+ }
+
+ void setNextVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue)
+ {
+ bool bWasUsed(mnUsedVectors && !maVector[nIndex].getNextVector().equalZero());
+ bool bIsUsed(!rValue.equalZero());
+
+ if(bWasUsed)
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex].setNextVector(rValue);
+ }
+ else
+ {
+ maVector[nIndex].setNextVector(basegfx::B2DVector::getEmptyVector());
+ mnUsedVectors--;
+ }
+ }
+ else
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex].setNextVector(rValue);
+ mnUsedVectors++;
+ }
+ }
+ }
+
+ void append(const ControlVectorPair2D& rValue)
+ {
+ maVector.push_back(rValue);
+
+ if(!rValue.getPrevVector().equalZero())
+ mnUsedVectors += 1;
+
+ if(!rValue.getNextVector().equalZero())
+ mnUsedVectors += 1;
+ }
+
+ void insert(sal_uInt32 nIndex, const ControlVectorPair2D& rValue, sal_uInt32 nCount)
+ {
+ assert(nCount > 0);
+ assert(nIndex <= maVector.size());
+
+ // add nCount copies of rValue
+ maVector.insert(maVector.begin() + nIndex, nCount, rValue);
+
+ if(!rValue.getPrevVector().equalZero())
+ mnUsedVectors += nCount;
+
+ if(!rValue.getNextVector().equalZero())
+ mnUsedVectors += nCount;
+ }
+
+ void insert(sal_uInt32 nIndex, const ControlVectorArray2D& rSource)
+ {
+ assert(rSource.maVector.size() > 0);
+ assert(nIndex <= maVector.size());
+
+ // insert data
+ ControlVectorPair2DVector::iterator aIndex(maVector.begin() + nIndex);
+ ControlVectorPair2DVector::const_iterator aStart(rSource.maVector.begin());
+ ControlVectorPair2DVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+
+ for(; aStart != aEnd; ++aStart)
+ {
+ if(!aStart->getPrevVector().equalZero())
+ mnUsedVectors++;
+
+ if(!aStart->getNextVector().equalZero())
+ mnUsedVectors++;
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ assert(nCount > 0);
+ assert(nIndex + nCount <= maVector.size());
+
+ const ControlVectorPair2DVector::iterator aDeleteStart(maVector.begin() + nIndex);
+ const ControlVectorPair2DVector::iterator aDeleteEnd(aDeleteStart + nCount);
+ ControlVectorPair2DVector::const_iterator aStart(aDeleteStart);
+
+ for(; mnUsedVectors && aStart != aDeleteEnd; ++aStart)
+ {
+ if(!aStart->getPrevVector().equalZero())
+ mnUsedVectors--;
+
+ if(mnUsedVectors && !aStart->getNextVector().equalZero())
+ mnUsedVectors--;
+ }
+
+ // remove point data
+ maVector.erase(aDeleteStart, aDeleteEnd);
+ }
+
+ void flip(bool bIsClosed)
+ {
+ assert(maVector.size() > 1);
+
+ // to keep the same point at index 0, just flip all points except the
+ // first one when closed
+ const sal_uInt32 nHalfSize(bIsClosed ? (maVector.size() - 1) >> 1 : maVector.size() >> 1);
+ ControlVectorPair2DVector::iterator aStart(bIsClosed ? maVector.begin() + 1 : maVector.begin());
+ ControlVectorPair2DVector::iterator aEnd(maVector.end() - 1);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ // swap Prev and Next
+ aStart->flip();
+ aEnd->flip();
+
+ // swap entries
+ std::swap(*aStart, *aEnd);
+
+ ++aStart;
+ --aEnd;
+ }
+
+ if(aStart == aEnd)
+ {
+ // swap Prev and Next at middle element (if exists)
+ aStart->flip();
+ }
+
+ if(bIsClosed)
+ {
+ // swap Prev and Next at start element
+ maVector.begin()->flip();
+ }
+ }
+};
+
+class ImplBufferedData : public basegfx::SystemDependentDataHolder
+{
+private:
+ // Possibility to hold the last subdivision
+ mutable std::optional< basegfx::B2DPolygon > mpDefaultSubdivision;
+
+ // Possibility to hold the last B2DRange calculation
+ mutable std::optional< basegfx::B2DRange > moB2DRange;
+
+public:
+ ImplBufferedData()
+ {
+ }
+
+ const basegfx::B2DPolygon& getDefaultAdaptiveSubdivision(const basegfx::B2DPolygon& rSource) const
+ {
+ if(!mpDefaultSubdivision)
+ {
+ mpDefaultSubdivision = basegfx::utils::adaptiveSubdivideByAngle(rSource);
+ }
+
+ return *mpDefaultSubdivision;
+ }
+
+ const basegfx::B2DRange& getB2DRange(const basegfx::B2DPolygon& rSource) const
+ {
+ if(!moB2DRange)
+ {
+ basegfx::B2DRange aNewRange;
+ const sal_uInt32 nPointCount(rSource.count());
+
+ if(nPointCount)
+ {
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ aNewRange.expand(rSource.getB2DPoint(a));
+ }
+
+ if(rSource.areControlPointsUsed())
+ {
+ const sal_uInt32 nEdgeCount(rSource.isClosed() ? nPointCount : nPointCount - 1);
+
+ if(nEdgeCount)
+ {
+ basegfx::B2DCubicBezier aEdge;
+ aEdge.setStartPoint(rSource.getB2DPoint(0));
+
+ for(sal_uInt32 b(0); b < nEdgeCount; b++)
+ {
+ const sal_uInt32 nNextIndex((b + 1) % nPointCount);
+ aEdge.setControlPointA(rSource.getNextControlPoint(b));
+ aEdge.setControlPointB(rSource.getPrevControlPoint(nNextIndex));
+ aEdge.setEndPoint(rSource.getB2DPoint(nNextIndex));
+
+ if(aEdge.isBezier())
+ {
+ const basegfx::B2DRange aBezierRangeWithControlPoints(aEdge.getRange());
+
+ if(!aNewRange.isInside(aBezierRangeWithControlPoints))
+ {
+ // the range with control points of the current edge is not completely
+ // inside the current range without control points. Expand current range by
+ // subdividing the bezier segment.
+ // Ideal here is a subdivision at the extreme values, so use
+ // getAllExtremumPositions to get all extremas in one run
+ std::vector< double > aExtremas;
+
+ aExtremas.reserve(4);
+ aEdge.getAllExtremumPositions(aExtremas);
+
+ const sal_uInt32 nExtremaCount(aExtremas.size());
+
+ for(sal_uInt32 c(0); c < nExtremaCount; c++)
+ {
+ aNewRange.expand(aEdge.interpolatePoint(aExtremas[c]));
+ }
+ }
+ }
+
+ // prepare next edge
+ aEdge.setStartPoint(aEdge.getEndPoint());
+ }
+ }
+ }
+ }
+
+ moB2DRange = aNewRange;
+ }
+
+ return *moB2DRange;
+ }
+};
+
+}
+
+class ImplB2DPolygon
+{
+private:
+ // The point vector. This vector exists always and defines the
+ // count of members.
+ CoordinateDataArray2D maPoints;
+
+ // The control point vectors. This vectors are created on demand
+ // and may be zero.
+ std::optional< ControlVectorArray2D > moControlVector;
+
+ // buffered data for e.g. default subdivision and range
+ // we do not want to 'modify' the ImplB2DPolygon,
+ // but add buffered data that is valid for all referencing instances
+ mutable std::unique_ptr<ImplBufferedData> mpBufferedData;
+
+ // flag which decides if this polygon is opened or closed
+ bool mbIsClosed;
+
+public:
+ const basegfx::B2DPolygon& getDefaultAdaptiveSubdivision(const basegfx::B2DPolygon& rSource) const
+ {
+ if(!moControlVector || !moControlVector->isUsed())
+ {
+ return rSource;
+ }
+
+ if(!mpBufferedData)
+ {
+ mpBufferedData.reset(new ImplBufferedData);
+ }
+
+ return mpBufferedData->getDefaultAdaptiveSubdivision(rSource);
+ }
+
+ const basegfx::B2DRange& getB2DRange(const basegfx::B2DPolygon& rSource) const
+ {
+ if(!mpBufferedData)
+ {
+ mpBufferedData.reset(new ImplBufferedData);
+ }
+
+ return mpBufferedData->getB2DRange(rSource);
+ }
+
+ ImplB2DPolygon()
+ : maPoints(0),
+ mbIsClosed(false)
+ {}
+
+ ImplB2DPolygon(const ImplB2DPolygon& rToBeCopied)
+ : maPoints(rToBeCopied.maPoints),
+ mbIsClosed(rToBeCopied.mbIsClosed)
+ {
+ // complete initialization using copy
+ if(rToBeCopied.moControlVector && rToBeCopied.moControlVector->isUsed())
+ {
+ moControlVector.emplace( *rToBeCopied.moControlVector );
+ }
+ }
+
+ ImplB2DPolygon(const ImplB2DPolygon& rToBeCopied, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maPoints(rToBeCopied.maPoints, nIndex, nCount),
+ mbIsClosed(rToBeCopied.mbIsClosed)
+ {
+ // complete initialization using partly copy
+ if(rToBeCopied.moControlVector && rToBeCopied.moControlVector->isUsed())
+ {
+ moControlVector.emplace( *rToBeCopied.moControlVector, nIndex, nCount );
+
+ if(!moControlVector->isUsed())
+ moControlVector.reset();
+ }
+ }
+
+ ImplB2DPolygon& operator=(const ImplB2DPolygon& rOther)
+ {
+ if (this != &rOther)
+ {
+ moControlVector.reset();
+ mpBufferedData.reset();
+ maPoints = rOther.maPoints;
+ mbIsClosed = rOther.mbIsClosed;
+ if (rOther.moControlVector && rOther.moControlVector->isUsed())
+ {
+ moControlVector.emplace( *rOther.moControlVector );
+
+ if(!moControlVector->isUsed())
+ moControlVector.reset();
+ }
+ }
+ return *this;
+ }
+
+ sal_uInt32 count() const
+ {
+ return maPoints.count();
+ }
+
+ bool isClosed() const
+ {
+ return mbIsClosed;
+ }
+
+ void setClosed(bool bNew)
+ {
+ if(bNew != mbIsClosed)
+ {
+ mpBufferedData.reset();
+ mbIsClosed = bNew;
+ }
+ }
+
+ bool operator==(const ImplB2DPolygon& rCandidate) const
+ {
+ if(mbIsClosed != rCandidate.mbIsClosed)
+ return false;
+ if(!(maPoints == rCandidate.maPoints))
+ return false;
+ bool bControlVectorsAreEqual(true);
+
+ if(moControlVector)
+ {
+ if(rCandidate.moControlVector)
+ {
+ bControlVectorsAreEqual = ((*moControlVector) == (*rCandidate.moControlVector));
+ }
+ else
+ {
+ // candidate has no control vector, so it's assumed all unused.
+ bControlVectorsAreEqual = !moControlVector->isUsed();
+ }
+ }
+ else
+ {
+ if(rCandidate.moControlVector)
+ {
+ // we have no control vector, so it's assumed all unused.
+ bControlVectorsAreEqual = !rCandidate.moControlVector->isUsed();
+ }
+ }
+
+ return bControlVectorsAreEqual;
+ }
+
+ const basegfx::B2DPoint& getPoint(sal_uInt32 nIndex) const
+ {
+ return maPoints.getCoordinate(nIndex);
+ }
+
+ void setPoint(sal_uInt32 nIndex, const basegfx::B2DPoint& rValue)
+ {
+ mpBufferedData.reset();
+ maPoints.setCoordinate(nIndex, rValue);
+ }
+
+ void reserve(sal_uInt32 nCount)
+ {
+ maPoints.reserve(nCount);
+ }
+
+ void append(const basegfx::B2DPoint& rPoint)
+ {
+ mpBufferedData.reset(); // TODO: is this needed?
+ const auto aCoordinate = rPoint;
+ maPoints.append(aCoordinate);
+
+ if(moControlVector)
+ {
+ const ControlVectorPair2D aVectorPair;
+ moControlVector->append(aVectorPair);
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const basegfx::B2DPoint& rPoint, sal_uInt32 nCount)
+ {
+ assert(nCount > 0);
+ mpBufferedData.reset();
+ auto aCoordinate = rPoint;
+ maPoints.insert(nIndex, aCoordinate, nCount);
+
+ if(moControlVector)
+ {
+ ControlVectorPair2D aVectorPair;
+ moControlVector->insert(nIndex, aVectorPair, nCount);
+ }
+ }
+
+ void append(const basegfx::B2DPoint& rPoint, sal_uInt32 nCount)
+ {
+ insert(count(), rPoint, nCount);
+ }
+
+ const basegfx::B2DVector& getPrevControlVector(sal_uInt32 nIndex) const
+ {
+ if(moControlVector)
+ {
+ return moControlVector->getPrevVector(nIndex);
+ }
+ else
+ {
+ return basegfx::B2DVector::getEmptyVector();
+ }
+ }
+
+ void setPrevControlVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue)
+ {
+ if(!moControlVector)
+ {
+ if(!rValue.equalZero())
+ {
+ mpBufferedData.reset();
+ moControlVector.emplace(maPoints.count());
+ moControlVector->setPrevVector(nIndex, rValue);
+ }
+ }
+ else
+ {
+ mpBufferedData.reset();
+ moControlVector->setPrevVector(nIndex, rValue);
+
+ if(!moControlVector->isUsed())
+ moControlVector.reset();
+ }
+ }
+
+ const basegfx::B2DVector& getNextControlVector(sal_uInt32 nIndex) const
+ {
+ if(moControlVector)
+ {
+ return moControlVector->getNextVector(nIndex);
+ }
+ else
+ {
+ return basegfx::B2DVector::getEmptyVector();
+ }
+ }
+
+ void setNextControlVector(sal_uInt32 nIndex, const basegfx::B2DVector& rValue)
+ {
+ if(!moControlVector)
+ {
+ if(!rValue.equalZero())
+ {
+ mpBufferedData.reset();
+ moControlVector.emplace(maPoints.count());
+ moControlVector->setNextVector(nIndex, rValue);
+ }
+ }
+ else
+ {
+ mpBufferedData.reset();
+ moControlVector->setNextVector(nIndex, rValue);
+
+ if(!moControlVector->isUsed())
+ moControlVector.reset();
+ }
+ }
+
+ bool areControlPointsUsed() const
+ {
+ return (moControlVector && moControlVector->isUsed());
+ }
+
+ void resetControlVectors()
+ {
+ mpBufferedData.reset();
+ moControlVector.reset();
+ }
+
+ void setControlVectors(sal_uInt32 nIndex, const basegfx::B2DVector& rPrev, const basegfx::B2DVector& rNext)
+ {
+ setPrevControlVector(nIndex, rPrev);
+ setNextControlVector(nIndex, rNext);
+ }
+
+ void appendBezierSegment(const basegfx::B2DVector& rNext, const basegfx::B2DVector& rPrev, const basegfx::B2DPoint& rPoint)
+ {
+ mpBufferedData.reset();
+ const sal_uInt32 nCount(maPoints.count());
+
+ if(nCount)
+ {
+ setNextControlVector(nCount - 1, rNext);
+ }
+
+ insert(nCount, rPoint, 1);
+ setPrevControlVector(nCount, rPrev);
+ }
+
+ void append(const ImplB2DPolygon& rSource)
+ {
+ assert(rSource.maPoints.count() > 0);
+ const sal_uInt32 nIndex = count();
+
+ mpBufferedData.reset();
+
+ maPoints.insert(nIndex, rSource.maPoints);
+
+ if(rSource.moControlVector)
+ {
+ if (!moControlVector)
+ moControlVector.emplace(nIndex);
+ moControlVector->insert(nIndex, *rSource.moControlVector);
+
+ if(!moControlVector->isUsed())
+ moControlVector.reset();
+ }
+ else if(moControlVector)
+ {
+ ControlVectorPair2D aVectorPair;
+ moControlVector->insert(nIndex, aVectorPair, rSource.count());
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ assert(nCount > 0);
+ mpBufferedData.reset();
+ maPoints.remove(nIndex, nCount);
+
+ if(moControlVector)
+ {
+ moControlVector->remove(nIndex, nCount);
+
+ if(!moControlVector->isUsed())
+ moControlVector.reset();
+ }
+ }
+
+ void flip()
+ {
+ assert(maPoints.count() > 1);
+
+ mpBufferedData.reset();
+
+ // flip points
+ maPoints.flip(mbIsClosed);
+
+ if(moControlVector)
+ {
+ // flip control vector
+ moControlVector->flip(mbIsClosed);
+ }
+ }
+
+ bool hasDoublePoints() const
+ {
+ if(mbIsClosed)
+ {
+ // check for same start and end point
+ const sal_uInt32 nIndex(maPoints.count() - 1);
+
+ if(maPoints.getCoordinate(0) == maPoints.getCoordinate(nIndex))
+ {
+ if(moControlVector)
+ {
+ if(moControlVector->getNextVector(nIndex).equalZero() && moControlVector->getPrevVector(0).equalZero())
+ {
+ return true;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+
+ // test for range
+ for(sal_uInt32 a(0); a < maPoints.count() - 1; a++)
+ {
+ if(maPoints.getCoordinate(a) == maPoints.getCoordinate(a + 1))
+ {
+ if(moControlVector)
+ {
+ if(moControlVector->getNextVector(a).equalZero() && moControlVector->getPrevVector(a + 1).equalZero())
+ {
+ return true;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ void removeDoublePointsAtBeginEnd()
+ {
+ // Only remove DoublePoints at Begin and End when poly is closed
+ if(!mbIsClosed)
+ return;
+
+ mpBufferedData.reset();
+
+ if(moControlVector)
+ {
+ bool bRemove;
+
+ do
+ {
+ bRemove = false;
+
+ if(maPoints.count() > 1)
+ {
+ const sal_uInt32 nIndex(maPoints.count() - 1);
+
+ if(maPoints.getCoordinate(0) == maPoints.getCoordinate(nIndex))
+ {
+ if(moControlVector)
+ {
+ if(moControlVector->getNextVector(nIndex).equalZero() && moControlVector->getPrevVector(0).equalZero())
+ {
+ bRemove = true;
+ }
+ }
+ else
+ {
+ bRemove = true;
+ }
+ }
+ }
+
+ if(bRemove)
+ {
+ const sal_uInt32 nIndex(maPoints.count() - 1);
+
+ if(moControlVector && !moControlVector->getPrevVector(nIndex).equalZero())
+ {
+ moControlVector->setPrevVector(0, moControlVector->getPrevVector(nIndex));
+ }
+
+ remove(nIndex, 1);
+ }
+ }
+ while(bRemove);
+ }
+ else
+ {
+ maPoints.removeDoublePointsAtBeginEnd();
+ }
+ }
+
+ void removeDoublePointsWholeTrack()
+ {
+ mpBufferedData.reset();
+
+ if(moControlVector)
+ {
+ sal_uInt32 nIndex(0);
+
+ // test as long as there are at least two points and as long as the index
+ // is smaller or equal second last point
+ while((maPoints.count() > 1) && (nIndex <= maPoints.count() - 2))
+ {
+ bool bRemove(maPoints.getCoordinate(nIndex) == maPoints.getCoordinate(nIndex + 1));
+
+ if(bRemove && moControlVector)
+ {
+ if(!moControlVector->getNextVector(nIndex).equalZero() || !moControlVector->getPrevVector(nIndex + 1).equalZero())
+ {
+ bRemove = false;
+ }
+ }
+
+ if(bRemove)
+ {
+ if(moControlVector && !moControlVector->getPrevVector(nIndex).equalZero())
+ {
+ moControlVector->setPrevVector(nIndex + 1, moControlVector->getPrevVector(nIndex));
+ }
+
+ // if next is same as index and the control vectors are unused, delete index
+ remove(nIndex, 1);
+ }
+ else
+ {
+ // if different, step forward
+ nIndex++;
+ }
+ }
+ }
+ else
+ {
+ maPoints.removeDoublePointsWholeTrack();
+ }
+ }
+
+ void transform(const basegfx::B2DHomMatrix& rMatrix)
+ {
+ mpBufferedData.reset();
+
+ if(moControlVector)
+ {
+ for(sal_uInt32 a(0); a < maPoints.count(); a++)
+ {
+ basegfx::B2DPoint aCandidate = maPoints.getCoordinate(a);
+
+ if(moControlVector->isUsed())
+ {
+ const basegfx::B2DVector& rPrevVector(moControlVector->getPrevVector(a));
+ const basegfx::B2DVector& rNextVector(moControlVector->getNextVector(a));
+
+ if(!rPrevVector.equalZero())
+ {
+ basegfx::B2DVector aPrevVector(rMatrix * rPrevVector);
+ moControlVector->setPrevVector(a, aPrevVector);
+ }
+
+ if(!rNextVector.equalZero())
+ {
+ basegfx::B2DVector aNextVector(rMatrix * rNextVector);
+ moControlVector->setNextVector(a, aNextVector);
+ }
+ }
+
+ aCandidate *= rMatrix;
+ maPoints.setCoordinate(a, aCandidate);
+ }
+
+ if(!moControlVector->isUsed())
+ moControlVector.reset();
+ }
+ else
+ {
+ maPoints.transform(rMatrix);
+ }
+ }
+
+ void addOrReplaceSystemDependentData(basegfx::SystemDependentData_SharedPtr& rData) const
+ {
+ if(!mpBufferedData)
+ {
+ mpBufferedData.reset(new ImplBufferedData);
+ }
+
+ mpBufferedData->addOrReplaceSystemDependentData(rData);
+ }
+
+ basegfx::SystemDependentData_SharedPtr getSystemDependentData(size_t hash_code) const
+ {
+ if(mpBufferedData)
+ {
+ return mpBufferedData->getSystemDependentData(hash_code);
+ }
+
+ return basegfx::SystemDependentData_SharedPtr();
+ }
+};
+
+namespace basegfx
+{
+ static o3tl::cow_wrapper<ImplB2DPolygon> DEFAULT;
+
+ B2DPolygon::B2DPolygon()
+ : mpPolygon(DEFAULT) {}
+
+ B2DPolygon::B2DPolygon(std::initializer_list<basegfx::B2DPoint> aPoints)
+ {
+ for (const basegfx::B2DPoint& rPoint : aPoints)
+ {
+ append(rPoint);
+ }
+ }
+
+ B2DPolygon::B2DPolygon(const B2DPolygon&) = default;
+
+ B2DPolygon::B2DPolygon(B2DPolygon&&) = default;
+
+ B2DPolygon::B2DPolygon(const B2DPolygon& rPolygon, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : mpPolygon(ImplB2DPolygon(*rPolygon.mpPolygon, nIndex, nCount))
+ {
+ }
+
+ B2DPolygon::~B2DPolygon() = default;
+
+ B2DPolygon& B2DPolygon::operator=(const B2DPolygon&) = default;
+
+ B2DPolygon& B2DPolygon::operator=(B2DPolygon&&) = default;
+
+ void B2DPolygon::makeUnique()
+ {
+ mpPolygon.make_unique();
+ }
+
+ bool B2DPolygon::operator==(const B2DPolygon& rPolygon) const
+ {
+ if(mpPolygon.same_object(rPolygon.mpPolygon))
+ return true;
+
+ return ((*mpPolygon) == (*rPolygon.mpPolygon));
+ }
+
+ sal_uInt32 B2DPolygon::count() const
+ {
+ return mpPolygon->count();
+ }
+
+ B2DPoint const & B2DPolygon::getB2DPoint(sal_uInt32 nIndex) const
+ {
+ return mpPolygon->getPoint(nIndex);
+ }
+
+ void B2DPolygon::setB2DPoint(sal_uInt32 nIndex, const B2DPoint& rValue)
+ {
+ if(getB2DPoint(nIndex) != rValue)
+ {
+ mpPolygon->setPoint(nIndex, rValue);
+ }
+ }
+
+ void B2DPolygon::reserve(sal_uInt32 nCount)
+ {
+ mpPolygon->reserve(nCount);
+ }
+
+ void B2DPolygon::insert(sal_uInt32 nIndex, const B2DPoint& rPoint, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ mpPolygon->insert(nIndex, rPoint, nCount);
+ }
+ }
+
+ void B2DPolygon::append(const B2DPoint& rPoint, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ mpPolygon->append(rPoint, nCount);
+ }
+ }
+
+ void B2DPolygon::append(const B2DPoint& rPoint)
+ {
+ mpPolygon->append(rPoint);
+ }
+
+ const basegfx::B2DVector& B2DPolygon::getPrevControlVector(sal_uInt32 nIndex) const
+ {
+ return mpPolygon->getPrevControlVector(nIndex);
+ }
+
+ const basegfx::B2DVector& B2DPolygon::getNextControlVector(sal_uInt32 nIndex) const
+ {
+ return mpPolygon->getNextControlVector(nIndex);
+ }
+
+ B2DPoint B2DPolygon::getPrevControlPoint(sal_uInt32 nIndex) const
+ {
+ if(areControlPointsUsed())
+ {
+ return getB2DPoint(nIndex) + getPrevControlVector(nIndex);
+ }
+ else
+ {
+ return getB2DPoint(nIndex);
+ }
+ }
+
+ B2DPoint B2DPolygon::getNextControlPoint(sal_uInt32 nIndex) const
+ {
+ if(areControlPointsUsed())
+ {
+ return getB2DPoint(nIndex) + getNextControlVector(nIndex);
+ }
+ else
+ {
+ return getB2DPoint(nIndex);
+ }
+ }
+
+ void B2DPolygon::setPrevControlPoint(sal_uInt32 nIndex, const B2DPoint& rValue)
+ {
+ const basegfx::B2DVector aNewVector(rValue - getB2DPoint(nIndex));
+
+ if(getPrevControlVector(nIndex) != aNewVector)
+ {
+ mpPolygon->setPrevControlVector(nIndex, aNewVector);
+ }
+ }
+
+ void B2DPolygon::setNextControlPoint(sal_uInt32 nIndex, const B2DPoint& rValue)
+ {
+ const basegfx::B2DVector aNewVector(rValue - getB2DPoint(nIndex));
+
+ if(getNextControlVector(nIndex) != aNewVector)
+ {
+ mpPolygon->setNextControlVector(nIndex, aNewVector);
+ }
+ }
+
+ void B2DPolygon::setControlPoints(sal_uInt32 nIndex, const basegfx::B2DPoint& rPrev, const basegfx::B2DPoint& rNext)
+ {
+ const B2DPoint aPoint(getB2DPoint(nIndex));
+ const basegfx::B2DVector aNewPrev(rPrev - aPoint);
+ const basegfx::B2DVector aNewNext(rNext - aPoint);
+
+ if(getPrevControlVector(nIndex) != aNewPrev || getNextControlVector(nIndex) != aNewNext)
+ {
+ mpPolygon->setControlVectors(nIndex, aNewPrev, aNewNext);
+ }
+ }
+
+ void B2DPolygon::resetPrevControlPoint(sal_uInt32 nIndex)
+ {
+ if(areControlPointsUsed() && !getPrevControlVector(nIndex).equalZero())
+ {
+ mpPolygon->setPrevControlVector(nIndex, B2DVector::getEmptyVector());
+ }
+ }
+
+ void B2DPolygon::resetNextControlPoint(sal_uInt32 nIndex)
+ {
+ if(areControlPointsUsed() && !getNextControlVector(nIndex).equalZero())
+ {
+ mpPolygon->setNextControlVector(nIndex, B2DVector::getEmptyVector());
+ }
+ }
+
+ void B2DPolygon::resetControlPoints()
+ {
+ if(areControlPointsUsed())
+ {
+ mpPolygon->resetControlVectors();
+ }
+ }
+
+ void B2DPolygon::appendBezierSegment(
+ const B2DPoint& rNextControlPoint,
+ const B2DPoint& rPrevControlPoint,
+ const B2DPoint& rPoint)
+ {
+ const B2DVector aNewNextVector(count() ? B2DVector(rNextControlPoint - getB2DPoint(count() - 1)) : B2DVector::getEmptyVector());
+ const B2DVector aNewPrevVector(rPrevControlPoint - rPoint);
+
+ if(aNewNextVector.equalZero() && aNewPrevVector.equalZero())
+ {
+ mpPolygon->append(rPoint);
+ }
+ else
+ {
+ mpPolygon->appendBezierSegment(aNewNextVector, aNewPrevVector, rPoint);
+ }
+ }
+
+ void B2DPolygon::appendQuadraticBezierSegment(const B2DPoint& rControlPoint, const B2DPoint& rPoint)
+ {
+ if (count() == 0)
+ {
+ mpPolygon->append(rPoint);
+ const double nX((rControlPoint.getX() * 2.0 + rPoint.getX()) / 3.0);
+ const double nY((rControlPoint.getY() * 2.0 + rPoint.getY()) / 3.0);
+ setPrevControlPoint(0, B2DPoint(nX, nY));
+ }
+ else
+ {
+ const B2DPoint aPreviousPoint(getB2DPoint(count() - 1));
+
+ const double nX1((rControlPoint.getX() * 2.0 + aPreviousPoint.getX()) / 3.0);
+ const double nY1((rControlPoint.getY() * 2.0 + aPreviousPoint.getY()) / 3.0);
+ const double nX2((rControlPoint.getX() * 2.0 + rPoint.getX()) / 3.0);
+ const double nY2((rControlPoint.getY() * 2.0 + rPoint.getY()) / 3.0);
+
+ appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), rPoint);
+ }
+ }
+
+ bool B2DPolygon::areControlPointsUsed() const
+ {
+ return mpPolygon->areControlPointsUsed();
+ }
+
+ bool B2DPolygon::isPrevControlPointUsed(sal_uInt32 nIndex) const
+ {
+ return (areControlPointsUsed() && !getPrevControlVector(nIndex).equalZero());
+ }
+
+ bool B2DPolygon::isNextControlPointUsed(sal_uInt32 nIndex) const
+ {
+ return (areControlPointsUsed() && !getNextControlVector(nIndex).equalZero());
+ }
+
+ B2VectorContinuity B2DPolygon::getContinuityInPoint(sal_uInt32 nIndex) const
+ {
+ if(areControlPointsUsed())
+ {
+ const B2DVector& rPrev(getPrevControlVector(nIndex));
+ const B2DVector& rNext(getNextControlVector(nIndex));
+
+ return getContinuity(rPrev, rNext);
+ }
+ else
+ {
+ return B2VectorContinuity::NONE;
+ }
+ }
+
+ void B2DPolygon::getBezierSegment(sal_uInt32 nIndex, B2DCubicBezier& rTarget) const
+ {
+ const bool bNextIndexValidWithoutClose(nIndex + 1 < count());
+
+ if(bNextIndexValidWithoutClose || isClosed())
+ {
+ const sal_uInt32 nNextIndex(bNextIndexValidWithoutClose ? nIndex + 1 : 0);
+ rTarget.setStartPoint(getB2DPoint(nIndex));
+ rTarget.setEndPoint(getB2DPoint(nNextIndex));
+
+ if(areControlPointsUsed())
+ {
+ rTarget.setControlPointA(rTarget.getStartPoint() + getNextControlVector(nIndex));
+ rTarget.setControlPointB(rTarget.getEndPoint() + getPrevControlVector(nNextIndex));
+ }
+ else
+ {
+ // no bezier, reset control points at rTarget
+ rTarget.setControlPointA(rTarget.getStartPoint());
+ rTarget.setControlPointB(rTarget.getEndPoint());
+ }
+ }
+ else
+ {
+ // no valid edge at all, reset rTarget to current point
+ const B2DPoint aPoint(getB2DPoint(nIndex));
+ rTarget.setStartPoint(aPoint);
+ rTarget.setEndPoint(aPoint);
+ rTarget.setControlPointA(aPoint);
+ rTarget.setControlPointB(aPoint);
+ }
+ }
+
+ B2DPolygon const & B2DPolygon::getDefaultAdaptiveSubdivision() const
+ {
+ return mpPolygon->getDefaultAdaptiveSubdivision(*this);
+ }
+
+ B2DRange const & B2DPolygon::getB2DRange() const
+ {
+ return mpPolygon->getB2DRange(*this);
+ }
+
+ void B2DPolygon::append(const B2DPolygon& rPoly, sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ assert(nIndex + nCount <= rPoly.count());
+
+ if(!nCount)
+ {
+ nCount = rPoly.count() - nIndex;
+ if (!nCount)
+ return;
+ }
+
+ if(nIndex == 0 && nCount == rPoly.count())
+ {
+ mpPolygon->append(*rPoly.mpPolygon);
+ }
+ else
+ {
+ mpPolygon->append(ImplB2DPolygon(*rPoly.mpPolygon, nIndex, nCount));
+ }
+ }
+
+ void B2DPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ mpPolygon->remove(nIndex, nCount);
+ }
+ }
+
+ void B2DPolygon::clear()
+ {
+ *mpPolygon = ImplB2DPolygon();
+ }
+
+ bool B2DPolygon::isClosed() const
+ {
+ return mpPolygon->isClosed();
+ }
+
+ void B2DPolygon::setClosed(bool bNew)
+ {
+ if(isClosed() != bNew)
+ {
+ mpPolygon->setClosed(bNew);
+ }
+ }
+
+ void B2DPolygon::flip()
+ {
+ if(count() > 1)
+ {
+ mpPolygon->flip();
+ }
+ }
+
+ bool B2DPolygon::hasDoublePoints() const
+ {
+ return (count() > 1 && mpPolygon->hasDoublePoints());
+ }
+
+ void B2DPolygon::removeDoublePoints()
+ {
+ if(hasDoublePoints())
+ {
+ mpPolygon->removeDoublePointsAtBeginEnd();
+ mpPolygon->removeDoublePointsWholeTrack();
+ }
+ }
+
+ void B2DPolygon::transform(const B2DHomMatrix& rMatrix)
+ {
+ if(count() && !rMatrix.isIdentity())
+ {
+ mpPolygon->transform(rMatrix);
+ }
+ }
+
+ void B2DPolygon::addOrReplaceSystemDependentDataInternal(SystemDependentData_SharedPtr& rData) const
+ {
+ mpPolygon->addOrReplaceSystemDependentData(rData);
+ }
+
+ SystemDependentData_SharedPtr B2DPolygon::getSystemDependantDataInternal(size_t hash_code) const
+ {
+ return mpPolygon->getSystemDependentData(hash_code);
+ }
+
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dpolygonclipper.cxx b/basegfx/source/polygon/b2dpolygonclipper.cxx
new file mode 100644
index 0000000000..e2f1f06038
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolygonclipper.cxx
@@ -0,0 +1,824 @@
+/* -*- 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 <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dpolygoncutandtouch.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/utils/rectcliptools.hxx>
+#include <sal/log.hxx>
+
+namespace basegfx::utils
+{
+ B2DPolyPolygon clipPolygonOnParallelAxis(const B2DPolygon& rCandidate, bool bParallelToXAxis, bool bAboveAxis, double fValueOnOtherAxis, bool bStroke)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(rCandidate.count())
+ {
+ const B2DRange aCandidateRange(getRange(rCandidate));
+
+ if(bParallelToXAxis && fTools::moreOrEqual(aCandidateRange.getMinY(), fValueOnOtherAxis))
+ {
+ // completely above and on the clip line. also true for curves.
+ if(bAboveAxis)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(bParallelToXAxis && fTools::lessOrEqual(aCandidateRange.getMaxY(), fValueOnOtherAxis))
+ {
+ // completely below and on the clip line. also true for curves.
+ if(!bAboveAxis)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(!bParallelToXAxis && fTools::moreOrEqual(aCandidateRange.getMinX(), fValueOnOtherAxis))
+ {
+ // completely right of and on the clip line. also true for curves.
+ if(bAboveAxis)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else if(!bParallelToXAxis && fTools::lessOrEqual(aCandidateRange.getMaxX(), fValueOnOtherAxis))
+ {
+ // completely left of and on the clip line. also true for curves.
+ if(!bAboveAxis)
+ {
+ // add completely
+ aRetval.append(rCandidate);
+ }
+ }
+ else
+ {
+ // add cuts with axis to polygon, including bezier segments
+ // Build edge to cut with. Make it a little big longer than needed for
+ // numerical stability. We want to cut against the edge seen as endless
+ // ray here, but addPointsAtCuts() will limit itself to the
+ // edge's range ]0.0 .. 1.0[.
+ const double fSmallExtension((aCandidateRange.getWidth() + aCandidateRange.getHeight()) * (0.5 * 0.1));
+ const B2DPoint aStart(
+ bParallelToXAxis ? aCandidateRange.getMinX() - fSmallExtension : fValueOnOtherAxis,
+ bParallelToXAxis ? fValueOnOtherAxis : aCandidateRange.getMinY() - fSmallExtension);
+ const B2DPoint aEnd(
+ bParallelToXAxis ? aCandidateRange.getMaxX() + fSmallExtension : fValueOnOtherAxis,
+ bParallelToXAxis ? fValueOnOtherAxis : aCandidateRange.getMaxY() + fSmallExtension);
+ const B2DPolygon aCandidate(addPointsAtCuts(rCandidate, aStart, aEnd));
+ const sal_uInt32 nPointCount(aCandidate.count());
+ const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DCubicBezier aEdge;
+ B2DPolygon aRun;
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ aCandidate.getBezierSegment(a, aEdge);
+ const B2DPoint aTestPoint(aEdge.interpolatePoint(0.5));
+ const bool bInside(bParallelToXAxis ?
+ fTools::moreOrEqual(aTestPoint.getY(), fValueOnOtherAxis) == bAboveAxis :
+ fTools::moreOrEqual(aTestPoint.getX(), fValueOnOtherAxis) == bAboveAxis);
+
+ if(bInside)
+ {
+ if(!aRun.count() || !aRun.getB2DPoint(aRun.count() - 1).equal(aEdge.getStartPoint()))
+ {
+ aRun.append(aEdge.getStartPoint());
+ }
+
+ if(aEdge.isBezier())
+ {
+ aRun.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint());
+ }
+ else
+ {
+ aRun.append(aEdge.getEndPoint());
+ }
+ }
+ else
+ {
+ if(bStroke && aRun.count())
+ {
+ aRetval.append(aRun);
+ aRun.clear();
+ }
+ }
+ }
+
+ if(aRun.count())
+ {
+ if(bStroke)
+ {
+ // try to merge this last and first polygon; they may have been
+ // the former polygon's start/end point
+ if(aRetval.count())
+ {
+ const B2DPolygon aStartPolygon(aRetval.getB2DPolygon(0));
+
+ if(aStartPolygon.count() && aStartPolygon.getB2DPoint(0).equal(aRun.getB2DPoint(aRun.count() - 1)))
+ {
+ // append start polygon to aRun, remove from result set
+ aRun.append(aStartPolygon); aRun.removeDoublePoints();
+ aRetval.remove(0);
+ }
+ }
+
+ aRetval.append(aRun);
+ }
+ else
+ {
+ // set closed flag and correct last point (which is added double now).
+ closeWithGeometryChange(aRun);
+ aRetval.append(aRun);
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon clipPolyPolygonOnParallelAxis(const B2DPolyPolygon& rCandidate, bool bParallelToXAxis, bool bAboveAxis, double fValueOnOtherAxis, bool bStroke)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(const auto& rB2DPolygon : rCandidate )
+ {
+ const B2DPolyPolygon aClippedPolyPolygon(clipPolygonOnParallelAxis(rB2DPolygon, bParallelToXAxis, bAboveAxis, fValueOnOtherAxis, bStroke));
+
+ if(aClippedPolyPolygon.count())
+ {
+ aRetval.append(aClippedPolyPolygon);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon clipPolygonOnRange(const B2DPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ if(!nCount)
+ {
+ // source is empty
+ return aRetval;
+ }
+
+ if(rRange.isEmpty())
+ {
+ if(bInside)
+ {
+ // nothing is inside an empty range
+ return aRetval;
+ }
+ else
+ {
+ // everything is outside an empty range
+ return B2DPolyPolygon(rCandidate);
+ }
+ }
+
+ const B2DRange aCandidateRange(getRange(rCandidate));
+
+ if(rRange.isInside(aCandidateRange))
+ {
+ // candidate is completely inside given range
+ if(bInside)
+ {
+ // nothing to do
+ return B2DPolyPolygon(rCandidate);
+ }
+ else
+ {
+ // nothing is outside, then
+ return aRetval;
+ }
+ }
+
+ if(!bInside)
+ {
+ // cutting off the outer parts of filled polygons at parallel
+ // lines to the axes is only possible for the inner part, not for
+ // the outer part which means cutting a hole into the original polygon.
+ // This is because the inner part is a logical AND-operation of
+ // the four implied half-planes, but the outer part is not.
+ // It is possible for strokes, but with creating unnecessary extra
+ // cuts, so using clipPolygonOnPolyPolygon is better there, too.
+ // This needs to be done with the topology knowledge and is unfortunately
+ // more expensive, too.
+ const B2DPolygon aClip(createPolygonFromRect(rRange));
+
+ return clipPolygonOnPolyPolygon(rCandidate, B2DPolyPolygon(aClip), bInside, bStroke);
+ }
+
+ // clip against the four axes of the range
+ // against X-Axis, lower value
+ aRetval = clipPolygonOnParallelAxis(rCandidate, true, bInside, rRange.getMinY(), bStroke);
+
+ if(aRetval.count())
+ {
+ // against Y-Axis, lower value
+ if(aRetval.count() == 1)
+ {
+ aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0), false, bInside, rRange.getMinX(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnParallelAxis(aRetval, false, bInside, rRange.getMinX(), bStroke);
+ }
+
+ if(aRetval.count())
+ {
+ // against X-Axis, higher value
+ if(aRetval.count() == 1)
+ {
+ aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0), true, false, rRange.getMaxY(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnParallelAxis(aRetval, true, false, rRange.getMaxY(), bStroke);
+ }
+
+ if(aRetval.count())
+ {
+ // against Y-Axis, higher value
+ if(aRetval.count() == 1)
+ {
+ aRetval = clipPolygonOnParallelAxis(aRetval.getB2DPolygon(0), false, false, rRange.getMaxX(), bStroke);
+ }
+ else
+ {
+ aRetval = clipPolyPolygonOnParallelAxis(aRetval, false, false, rRange.getMaxX(), bStroke);
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon clipPolyPolygonOnRange(const B2DPolyPolygon& rCandidate, const B2DRange& rRange, bool bInside, bool bStroke)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(!rCandidate.count())
+ {
+ // source is empty
+ return aRetval;
+ }
+
+ if(rRange.isEmpty())
+ {
+ if(bInside)
+ {
+ // nothing is inside an empty range
+ return aRetval;
+ }
+ else
+ {
+ // everything is outside an empty range
+ return rCandidate;
+ }
+ }
+
+ if(bInside)
+ {
+ for( const auto& rClippedPoly : rCandidate)
+ {
+ const B2DPolyPolygon aClippedPolyPolygon(clipPolygonOnRange(rClippedPoly , rRange, bInside, bStroke));
+
+ if(aClippedPolyPolygon.count())
+ {
+ aRetval.append(aClippedPolyPolygon);
+ }
+ }
+ }
+ else
+ {
+ // for details, see comment in clipPolygonOnRange for the "cutting off
+ // the outer parts of filled polygons at parallel lines" explanations
+ const B2DPolygon aClip(createPolygonFromRect(rRange));
+
+ return clipPolyPolygonOnPolyPolygon(rCandidate, B2DPolyPolygon(aClip), bInside, bStroke);
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon clipPolyPolygonOnPolyPolygon(const B2DPolyPolygon& rCandidate, const B2DPolyPolygon& rClip,
+ bool bInside, bool bStroke, size_t* pPointLimit)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(rCandidate.count() && rClip.count())
+ {
+ // one or both are no rectangle - go the hard way and clip PolyPolygon
+ // against PolyPolygon...
+ if(bStroke)
+ {
+ // line clipping, create line snippets by first adding all cut points and
+ // then marching along the edges and detecting if they are inside or outside
+ // the clip polygon
+ for(const auto& rPolygon : rCandidate)
+ {
+ // add cuts with clip to polygon, including bezier segments
+ const B2DPolygon aCandidate(addPointsAtCuts(rPolygon, rClip));
+ const sal_uInt32 nPointCount(aCandidate.count());
+ const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DCubicBezier aEdge;
+ B2DPolygon aRun;
+
+ for(sal_uInt32 b(0); b < nEdgeCount; b++)
+ {
+ aCandidate.getBezierSegment(b, aEdge);
+ const B2DPoint aTestPoint(aEdge.interpolatePoint(0.5));
+ const bool bIsInside(utils::isInside(rClip, aTestPoint) == bInside);
+
+ if(bIsInside)
+ {
+ if(!aRun.count())
+ {
+ aRun.append(aEdge.getStartPoint());
+ }
+
+ if(aEdge.isBezier())
+ {
+ aRun.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint());
+ }
+ else
+ {
+ aRun.append(aEdge.getEndPoint());
+ }
+ }
+ else
+ {
+ if(aRun.count())
+ {
+ aRetval.append(aRun);
+ aRun.clear();
+ }
+ }
+ }
+
+ if(aRun.count())
+ {
+ // try to merge this last and first polygon; they may have been
+ // the former polygon's start/end point
+ if(aRetval.count())
+ {
+ const B2DPolygon aStartPolygon(aRetval.getB2DPolygon(0));
+
+ if(aStartPolygon.count() && aStartPolygon.getB2DPoint(0).equal(aRun.getB2DPoint(aRun.count() - 1)))
+ {
+ // append start polygon to aRun, remove from result set
+ aRun.append(aStartPolygon); aRun.removeDoublePoints();
+ aRetval.remove(0);
+ }
+ }
+
+ aRetval.append(aRun);
+ }
+ }
+ }
+ else
+ {
+ // check for simplification with ranges if !bStroke (handling as stroke is more simple),
+ // but also only when bInside, else the simplification may lead to recursive calls (see
+ // calls to clipPolyPolygonOnPolyPolygon in clipPolyPolygonOnRange and clipPolygonOnRange)
+ if (bInside && basegfx::utils::isRectangle(rClip))
+ {
+ // #i125349# detect if both given PolyPolygons are indeed ranges
+ if (basegfx::utils::isRectangle(rCandidate))
+ {
+ // both are rectangle
+ if(rCandidate.getB2DRange().equal(rClip.getB2DRange()))
+ {
+ // if both are equal -> no change
+ return rCandidate;
+ }
+ else
+ {
+ // not equal -> create new intersection from both ranges,
+ // but much cheaper based on the ranges
+ basegfx::B2DRange aIntersectionRange(rCandidate.getB2DRange());
+
+ aIntersectionRange.intersect(rClip.getB2DRange());
+
+ if(aIntersectionRange.isEmpty())
+ {
+ // no common IntersectionRange -> the clip will be empty
+ return B2DPolyPolygon();
+ }
+ else
+ {
+ // use common aIntersectionRange as result, convert
+ // to expected utils::PolyPolygon form
+ return basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(aIntersectionRange));
+ }
+ }
+ }
+ else
+ {
+ // rClip is rectangle -> clip rCandidate on rRectangle, use the much
+ // cheaper and numerically more stable clipping against a range
+ return clipPolyPolygonOnRange(rCandidate, rClip.getB2DRange(), bInside, bStroke);
+ }
+ }
+
+ // area clipping
+
+ // First solve all polygon-self and polygon-polygon intersections.
+ // Also get rid of some not-needed polygons (neutral, no area -> when
+ // no intersections, these are tubes).
+ // Now it is possible to correct the orientations in the cut-free
+ // polygons to values corresponding to painting the utils::PolyPolygon with
+ // a XOR-WindingRule.
+ B2DPolyPolygon aMergePolyPolygonA = solveCrossovers(rClip);
+ aMergePolyPolygonA = stripNeutralPolygons(aMergePolyPolygonA);
+ aMergePolyPolygonA = correctOrientations(aMergePolyPolygonA);
+
+ if(!bInside)
+ {
+ // if we want to get the outside of the clip polygon, make
+ // it a 'Hole' in topological sense
+ aMergePolyPolygonA.flip();
+ }
+
+
+ // prepare 2nd source polygon in same way
+ B2DPolyPolygon aMergePolyPolygonB = solveCrossovers(rCandidate, pPointLimit);
+
+ if (pPointLimit && !*pPointLimit)
+ {
+ SAL_WARN("basegfx", "clipPolyPolygonOnPolyPolygon hit point limit");
+ return aRetval;
+ }
+
+ aMergePolyPolygonB = stripNeutralPolygons(aMergePolyPolygonB);
+ aMergePolyPolygonB = correctOrientations(aMergePolyPolygonB);
+
+ // to clip against each other, concatenate and solve all
+ // polygon-polygon crossovers. polygon-self do not need to
+ // be solved again, they were solved in the preparation.
+ aRetval.append(aMergePolyPolygonA);
+ aRetval.append(aMergePolyPolygonB);
+ aRetval = solveCrossovers(aRetval, pPointLimit);
+
+ // now remove neutral polygons (closed, but no area). In a last
+ // step throw away all polygons which have a depth of less than 1
+ // which means there was no logical AND at their position. For the
+ // not-inside solution, the clip was flipped to define it as 'Hole',
+ // so the removal rule is different here; remove all with a depth
+ // of less than 0 (aka holes).
+ aRetval = stripNeutralPolygons(aRetval);
+ aRetval = stripDispensablePolygons(aRetval, bInside);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon clipPolygonOnPolyPolygon(const B2DPolygon& rCandidate, const B2DPolyPolygon& rClip, bool bInside, bool bStroke)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(rCandidate.count() && rClip.count())
+ {
+ aRetval = clipPolyPolygonOnPolyPolygon(B2DPolyPolygon(rCandidate), rClip, bInside, bStroke);
+ }
+
+ return aRetval;
+ }
+
+ namespace {
+
+ /*
+ * let a plane be defined as
+ *
+ * v.n+d=0
+ *
+ * and a ray be defined as
+ *
+ * a+(b-a)*t=0
+ *
+ * substitute and rearranging yields
+ *
+ * t = -(a.n+d)/(n.(b-a))
+ *
+ * if the denominator is zero, the line is either
+ * contained in the plane or parallel to the plane.
+ * in either case, there is no intersection.
+ * if numerator and denominator are both zero, the
+ * ray is contained in the plane.
+ *
+ */
+ struct scissor_plane {
+ double nx,ny; // plane normal
+ double d; // [-] minimum distance from origin
+ sal_uInt32 clipmask; // clipping mask, e.g. 1000 1000
+ };
+
+ }
+
+ /*
+ *
+ * polygon clipping rules (straight out of Foley and Van Dam)
+ * ===========================================================
+ * current |next |emit
+ * ____________________________________
+ * inside |inside |next
+ * inside |outside |intersect with clip plane
+ * outside |outside |nothing
+ * outside |inside |intersect with clip plane followed by next
+ *
+ */
+ static sal_uInt32 scissorLineSegment( ::basegfx::B2DPoint *in_vertex, // input buffer
+ sal_uInt32 in_count, // number of verts in input buffer
+ ::basegfx::B2DPoint *out_vertex, // output buffer
+ scissor_plane const *pPlane, // scissoring plane
+ const ::basegfx::B2DRectangle &rR ) // clipping rectangle
+ {
+
+ sal_uInt32 out_count=0;
+
+ // process all the verts
+ for(sal_uInt32 i=0; i<in_count; i++) {
+
+ // vertices are relative to the coordinate
+ // system defined by the rectangle.
+ ::basegfx::B2DPoint *curr = &in_vertex[i];
+ ::basegfx::B2DPoint *next = &in_vertex[(i+1)%in_count];
+
+ // perform clipping judgement & mask against current plane.
+ sal_uInt32 clip = pPlane->clipmask & ((getCohenSutherlandClipFlags(*curr,rR)<<4)|getCohenSutherlandClipFlags(*next,rR));
+
+ if(clip==0) { // both verts are inside
+ out_vertex[out_count++] = *next;
+ }
+ else if((clip&0x0f) && (clip&0xf0)) { // both verts are outside
+ }
+ else if((clip&0x0f) && (clip&0xf0)==0) { // curr is inside, next is outside
+
+ // direction vector from 'current' to 'next', *not* normalized
+ // to bring 't' into the [0<=x<=1] interval.
+ ::basegfx::B2DPoint dir((*next)-(*curr));
+
+ double denominator = pPlane->nx*dir.getX() +
+ pPlane->ny*dir.getY();
+ double numerator = pPlane->nx*curr->getX() +
+ pPlane->ny*curr->getY() +
+ pPlane->d;
+ double t = -numerator/denominator;
+
+ // calculate the actual point of intersection
+ ::basegfx::B2DPoint intersection( curr->getX()+t*dir.getX(),
+ curr->getY()+t*dir.getY() );
+
+ out_vertex[out_count++] = intersection;
+ }
+ else if((clip&0x0f)==0 && (clip&0xf0)) { // curr is outside, next is inside
+
+ // direction vector from 'current' to 'next', *not* normalized
+ // to bring 't' into the [0<=x<=1] interval.
+ ::basegfx::B2DPoint dir((*next)-(*curr));
+
+ double denominator = pPlane->nx*dir.getX() +
+ pPlane->ny*dir.getY();
+ double numerator = pPlane->nx*curr->getX() +
+ pPlane->ny*curr->getY() +
+ pPlane->d;
+ double t = -numerator/denominator;
+
+ // calculate the actual point of intersection
+ ::basegfx::B2DPoint intersection( curr->getX()+t*dir.getX(),
+ curr->getY()+t*dir.getY() );
+
+ out_vertex[out_count++] = intersection;
+ out_vertex[out_count++] = *next;
+ }
+ }
+
+ return out_count;
+ }
+
+ B2DPolygon clipTriangleListOnRange( const B2DPolygon& rCandidate,
+ const B2DRange& rRange )
+ {
+ B2DPolygon aResult;
+
+ if( !(rCandidate.count()%3) )
+ {
+ const int scissor_plane_count = 4;
+
+ scissor_plane sp[scissor_plane_count];
+
+ sp[0].nx = +1.0;
+ sp[0].ny = +0.0;
+ sp[0].d = -(rRange.getMinX());
+ sp[0].clipmask = (RectClipFlags::LEFT << 4) | RectClipFlags::LEFT; // 0001 0001
+ sp[1].nx = -1.0;
+ sp[1].ny = +0.0;
+ sp[1].d = +(rRange.getMaxX());
+ sp[1].clipmask = (RectClipFlags::RIGHT << 4) | RectClipFlags::RIGHT; // 0010 0010
+ sp[2].nx = +0.0;
+ sp[2].ny = +1.0;
+ sp[2].d = -(rRange.getMinY());
+ sp[2].clipmask = (RectClipFlags::TOP << 4) | RectClipFlags::TOP; // 0100 0100
+ sp[3].nx = +0.0;
+ sp[3].ny = -1.0;
+ sp[3].d = +(rRange.getMaxY());
+ sp[3].clipmask = (RectClipFlags::BOTTOM << 4) | RectClipFlags::BOTTOM; // 1000 1000
+
+ // retrieve the number of vertices of the triangulated polygon
+ const sal_uInt32 nVertexCount = rCandidate.count();
+
+ if(nVertexCount)
+ {
+ // Upper bound for the maximal number of vertices when intersecting an
+ // axis-aligned rectangle with a triangle in E2
+
+ // The rectangle and the triangle are in general position, and have 4 and 3
+ // vertices, respectively.
+
+ // Lemma: Since the rectangle is a convex polygon ( see
+ // http://mathworld.wolfram.com/ConvexPolygon.html for a definition), and
+ // has no holes, it follows that any straight line will intersect the
+ // rectangle's border line at utmost two times (with the usual
+ // tie-breaking rule, if the intersection exactly hits an already existing
+ // rectangle vertex, that this intersection is only attributed to one of
+ // the adjoining edges). Thus, having a rectangle intersected with
+ // a half-plane (one side of a straight line denotes 'inside', the
+ // other 'outside') will at utmost add _one_ vertex to the resulting
+ // intersection polygon (adding two intersection vertices, and removing at
+ // least one rectangle vertex):
+
+ // *
+ // +--+-----------------+
+ // | * |
+ // |* |
+ // + |
+ // *| |
+ // * | |
+ // +--------------------+
+
+ // Proof: If the straight line intersects the rectangle two
+ // times, it does so for distinct edges, i.e. the intersection has
+ // minimally one of the rectangle's vertices on either side of the straight
+ // line (but maybe more). Thus, the intersection with a half-plane has
+ // minimally _one_ rectangle vertex removed from the resulting clip
+ // polygon, and therefore, a clip against a half-plane has the net effect
+ // of adding at utmost _one_ vertex to the resulting clip polygon.
+
+ // Theorem: The intersection of a rectangle and a triangle results in a
+ // polygon with at utmost 7 vertices.
+
+ // Proof: The inside of the triangle can be described as the consecutive
+ // intersection with three half-planes. Together with the lemma above, this
+ // results in at utmost 3 additional vertices added to the already existing 4
+ // rectangle vertices.
+
+ // This upper bound is attained with the following example configuration:
+
+ // *
+ // ***
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ** *
+ // ----*2--------3 *
+ // | ** |*
+ // 1* 4
+ // **| *|
+ // ** | * |
+ // **| * |
+ // 7* * |
+ // --*6-----5-----
+ // ** *
+ // **
+
+ // As we need to scissor all triangles against the
+ // output rectangle we employ an output buffer for the
+ // resulting vertices. the question is how large this
+ // buffer needs to be compared to the number of
+ // incoming vertices. this buffer needs to hold at
+ // most the number of original vertices times '7'. see
+ // figure above for an example. scissoring triangles
+ // with the cohen-sutherland line clipping algorithm
+ // as implemented here will result in a triangle fan
+ // which will be rendered as separate triangles to
+ // avoid pipeline stalls for each scissored
+ // triangle. creating separate triangles from a
+ // triangle fan produces (n-2)*3 vertices where n is
+ // the number of vertices of the original triangle
+ // fan. for the maximum number of 7 vertices of
+ // resulting triangle fans we therefore need 15 times
+ // the number of original vertices.
+
+ //const size_t nBufferSize = sizeof(vertex)*(nVertexCount*16);
+ //vertex *pVertices = (vertex*)alloca(nBufferSize);
+ //sal_uInt32 nNumOutput = 0;
+
+ // we need to clip this triangle against the output rectangle
+ // to ensure that the resulting texture coordinates are in
+ // the valid range from [0<=st<=1]. under normal circumstances
+ // we could use the BORDERCOLOR renderstate but some cards
+ // seem to ignore this feature.
+ ::basegfx::B2DPoint stack[3];
+ unsigned int clipflag = 0;
+
+ for(sal_uInt32 nIndex=0; nIndex<nVertexCount; ++nIndex)
+ {
+ // rotate stack
+ stack[0] = stack[1];
+ stack[1] = stack[2];
+ stack[2] = rCandidate.getB2DPoint(nIndex);
+
+ // clipping judgement
+ clipflag |= unsigned(!(rRange.isInside(stack[2])));
+
+ if(nIndex > 1)
+ {
+ // consume vertices until a single separate triangle has been visited.
+ if(!((nIndex+1)%3))
+ {
+ // if any of the last three vertices was outside
+ // we need to scissor against the destination rectangle
+ if(clipflag & 7)
+ {
+ ::basegfx::B2DPoint buf0[16];
+ ::basegfx::B2DPoint buf1[16];
+
+ sal_uInt32 vertex_count = 3;
+
+ // clip against all 4 planes passing the result of
+ // each plane as the input to the next using a double buffer
+ vertex_count = scissorLineSegment(stack,vertex_count,buf1,&sp[0],rRange);
+ vertex_count = scissorLineSegment(buf1,vertex_count,buf0,&sp[1],rRange);
+ vertex_count = scissorLineSegment(buf0,vertex_count,buf1,&sp[2],rRange);
+ vertex_count = scissorLineSegment(buf1,vertex_count,buf0,&sp[3],rRange);
+
+ if(vertex_count >= 3)
+ {
+ // convert triangle fan back to triangle list.
+ ::basegfx::B2DPoint v0(buf0[0]);
+ ::basegfx::B2DPoint v1(buf0[1]);
+ for(sal_uInt32 i=2; i<vertex_count; ++i)
+ {
+ ::basegfx::B2DPoint v2(buf0[i]);
+ aResult.append(v0);
+ aResult.append(v1);
+ aResult.append(v2);
+ v1 = v2;
+ }
+ }
+ }
+ else
+ {
+ // the last triangle has not been altered, simply copy to result
+ for(const basegfx::B2DPoint & i : stack)
+ aResult.append(i);
+ }
+ }
+ }
+
+ clipflag <<= 1;
+ }
+ }
+ }
+
+ return aResult;
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dpolygoncutandtouch.cxx b/basegfx/source/polygon/b2dpolygoncutandtouch.cxx
new file mode 100644
index 0000000000..c91f0ec48f
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolygoncutandtouch.cxx
@@ -0,0 +1,1077 @@
+/* -*- 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 <basegfx/polygon/b2dpolygoncutandtouch.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+
+#include <vector>
+#include <algorithm>
+#include <memory>
+
+#define SUBDIVIDE_FOR_CUT_TEST_COUNT (50)
+
+namespace basegfx
+{
+ namespace
+ {
+
+ class temporaryPoint
+ {
+ B2DPoint maPoint; // the new point
+ sal_uInt32 mnIndex; // index after which to insert
+ double mfCut; // parametric cut description [0.0 .. 1.0]
+
+ public:
+ temporaryPoint(const B2DPoint& rNewPoint, sal_uInt32 nIndex, double fCut)
+ : maPoint(rNewPoint),
+ mnIndex(nIndex),
+ mfCut(fCut)
+ {
+ }
+
+ bool operator<(const temporaryPoint& rComp) const
+ {
+ if(mnIndex == rComp.mnIndex)
+ {
+ return (mfCut < rComp.mfCut);
+ }
+
+ return (mnIndex < rComp.mnIndex);
+ }
+
+ const B2DPoint& getPoint() const { return maPoint; }
+ sal_uInt32 getIndex() const { return mnIndex; }
+ double getCut() const { return mfCut; }
+ };
+
+ typedef std::vector< temporaryPoint > temporaryPointVector;
+
+ class temporaryPolygonData
+ {
+ B2DPolygon maPolygon;
+ B2DRange maRange;
+ temporaryPointVector maPoints;
+
+ public:
+ const B2DPolygon& getPolygon() const { return maPolygon; }
+ void setPolygon(const B2DPolygon& rNew) { maPolygon = rNew; maRange = utils::getRange(maPolygon); }
+ const B2DRange& getRange() const { return maRange; }
+ temporaryPointVector& getTemporaryPointVector() { return maPoints; }
+ };
+
+ B2DPolygon mergeTemporaryPointsAndPolygon(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints)
+ {
+ // #i76891# mergeTemporaryPointsAndPolygon redesigned to be able to correctly handle
+ // single edges with/without control points
+ // #i101491# added counter for non-changing element count
+ const sal_uInt32 nTempPointCount(rTempPoints.size());
+
+ if(nTempPointCount)
+ {
+ B2DPolygon aRetval;
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nCount)
+ {
+ // sort temp points to assure increasing fCut values and increasing indices
+ std::sort(rTempPoints.begin(), rTempPoints.end());
+
+ // prepare loop
+ B2DCubicBezier aEdge;
+ sal_uInt32 nNewInd(0);
+
+ // add start point
+ aRetval.append(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ // get edge
+ rCandidate.getBezierSegment(a, aEdge);
+
+ if(aEdge.isBezier())
+ {
+ // control vectors involved for this edge
+ double fLeftStart(0.0);
+
+ // now add all points targeted to be at this index
+ while (nNewInd < nTempPointCount && rTempPoints[nNewInd].getIndex() == a && fLeftStart < 1.0)
+ {
+ const temporaryPoint& rTempPoint = rTempPoints[nNewInd++];
+
+ // split curve segment. Splits need to come sorted and need to be < 1.0. Also,
+ // since original segment is consumed from left to right, the cut values need
+ // to be scaled to the remaining part
+ B2DCubicBezier aLeftPart;
+ const double fRelativeSplitPoint((rTempPoint.getCut() - fLeftStart) / (1.0 - fLeftStart));
+ aEdge.split(fRelativeSplitPoint, &aLeftPart, &aEdge);
+ fLeftStart = rTempPoint.getCut();
+
+ // add left bow
+ aRetval.appendBezierSegment(aLeftPart.getControlPointA(), aLeftPart.getControlPointB(), rTempPoint.getPoint());
+ }
+
+ // add remaining bow
+ aRetval.appendBezierSegment(aEdge.getControlPointA(), aEdge.getControlPointB(), aEdge.getEndPoint());
+ }
+ else
+ {
+ // add all points targeted to be at this index
+ while(nNewInd < nTempPointCount && rTempPoints[nNewInd].getIndex() == a)
+ {
+ const temporaryPoint& rTempPoint = rTempPoints[nNewInd++];
+ const B2DPoint& aNewPoint(rTempPoint.getPoint());
+
+ // do not add points double
+ if(!aRetval.getB2DPoint(aRetval.count() - 1).equal(aNewPoint))
+ {
+ aRetval.append(aNewPoint);
+ }
+ }
+
+ // add edge end point
+ aRetval.append(aEdge.getEndPoint());
+ }
+ }
+ }
+
+ if(rCandidate.isClosed())
+ {
+ // set closed flag and correct last point (which is added double now).
+ utils::closeWithGeometryChange(aRetval);
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ void adaptAndTransferCutsWithBezierSegment(
+ const temporaryPointVector& rPointVector, const B2DPolygon& rPolygon,
+ sal_uInt32 nInd, temporaryPointVector& rTempPoints)
+ {
+ // assuming that the subdivision to create rPolygon used equidistant pieces
+ // (as in adaptiveSubdivideByCount) it is now possible to calculate back the
+ // cut positions in the polygon to relative cut positions on the original bezier
+ // segment.
+ const sal_uInt32 nEdgeCount(rPolygon.count() ? rPolygon.count() - 1 : 0);
+
+ if(!rPointVector.empty() && nEdgeCount)
+ {
+ for( const auto& rTempPoint : rPointVector )
+ {
+ const double fCutPosInPolygon(static_cast<double>(rTempPoint.getIndex()) + rTempPoint.getCut());
+ const double fRelativeCutPos(fCutPosInPolygon / static_cast<double>(nEdgeCount));
+ rTempPoints.emplace_back(rTempPoint.getPoint(), nInd, fRelativeCutPos);
+ }
+ }
+ }
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+namespace basegfx
+{
+ namespace
+ {
+
+ // predefines for calls to this methods before method implementation
+
+ void findCuts(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints, size_t* pPointLimit = nullptr);
+ void findTouches(const B2DPolygon& rEdgePolygon, const B2DPolygon& rPointPolygon, temporaryPointVector& rTempPoints);
+ void findCuts(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB);
+
+ void findEdgeCutsTwoEdges(
+ const B2DPoint& rCurrA, const B2DPoint& rNextA,
+ const B2DPoint& rCurrB, const B2DPoint& rNextB,
+ sal_uInt32 nIndA, sal_uInt32 nIndB,
+ temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
+ {
+ // no null length edges
+ if(rCurrA.equal(rNextA) || rCurrB.equal(rNextB))
+ return;
+
+ // no common start/end points, this can be no cuts
+ if(rCurrB.equal(rCurrA) || rCurrB.equal(rNextA) || rNextB.equal(rCurrA) || rNextB.equal(rNextA))
+ return;
+
+ const B2DVector aVecA(rNextA - rCurrA);
+ const B2DVector aVecB(rNextB - rCurrB);
+ double fCut(aVecA.cross(aVecB));
+
+ if(fTools::equalZero(fCut))
+ return;
+
+ const double fZero(0.0);
+ const double fOne(1.0);
+ fCut = (aVecB.getY() * (rCurrB.getX() - rCurrA.getX()) + aVecB.getX() * (rCurrA.getY() - rCurrB.getY())) / fCut;
+
+ if (!fTools::betweenOrEqualEither(fCut, fZero, fOne))
+ return;
+
+ // it's a candidate, but also need to test parameter value of cut on line 2
+ double fCut2;
+
+ // choose the more precise version
+ if(fabs(aVecB.getX()) > fabs(aVecB.getY()))
+ {
+ fCut2 = (rCurrA.getX() + (fCut * aVecA.getX()) - rCurrB.getX()) / aVecB.getX();
+ }
+ else
+ {
+ fCut2 = (rCurrA.getY() + (fCut * aVecA.getY()) - rCurrB.getY()) / aVecB.getY();
+ }
+
+ if (fTools::betweenOrEqualEither(fCut2, fZero, fOne))
+ {
+ // cut is in range, add point. Two edges can have only one cut, but
+ // add a cut point to each list. The lists may be the same for
+ // self intersections.
+ const B2DPoint aCutPoint(interpolate(rCurrA, rNextA, fCut));
+ rTempPointsA.emplace_back(aCutPoint, nIndA, fCut);
+ rTempPointsB.emplace_back(aCutPoint, nIndB, fCut2);
+ }
+ }
+
+ void findCutsAndTouchesAndCommonForBezier(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
+ {
+ // #i76891#
+ // This new method is necessary since in findEdgeCutsBezierAndEdge and in findEdgeCutsTwoBeziers
+ // it is not sufficient to use findCuts() recursively. This will indeed find the cuts between the
+ // segments of the two temporarily adaptive subdivided bezier segments, but not the touches or
+ // equal points of them.
+ // It would be possible to find the touches using findTouches(), but at last with common points
+ // the adding of cut points (temporary points) would fail. But for these temporarily adaptive
+ // subdivided bezier segments, common points may be not very likely, but the bug shows that it
+ // happens.
+ // Touch points are a little bit more likely than common points. All in all it is best to use
+ // a specialized method here which can profit from knowing that it is working on a special
+ // family of B2DPolygons: no curve segments included and not closed.
+ OSL_ENSURE(!rCandidateA.areControlPointsUsed() && !rCandidateB.areControlPointsUsed(), "findCutsAndTouchesAndCommonForBezier only works with subdivided polygons (!)");
+ OSL_ENSURE(!rCandidateA.isClosed() && !rCandidateB.isClosed(), "findCutsAndTouchesAndCommonForBezier only works with opened polygons (!)");
+ const sal_uInt32 nPointCountA(rCandidateA.count());
+ const sal_uInt32 nPointCountB(rCandidateB.count());
+
+ if(nPointCountA <= 1 || nPointCountB <= 1)
+ return;
+
+ const sal_uInt32 nEdgeCountA(nPointCountA - 1);
+ const sal_uInt32 nEdgeCountB(nPointCountB - 1);
+ B2DPoint aCurrA(rCandidateA.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nEdgeCountA; a++)
+ {
+ const B2DPoint aNextA(rCandidateA.getB2DPoint(a + 1));
+ const B2DRange aRangeA(aCurrA, aNextA);
+ B2DPoint aCurrB(rCandidateB.getB2DPoint(0));
+
+ for(sal_uInt32 b(0); b < nEdgeCountB; b++)
+ {
+ const B2DPoint aNextB(rCandidateB.getB2DPoint(b + 1));
+ const B2DRange aRangeB(aCurrB, aNextB);
+
+ if(aRangeA.overlaps(aRangeB))
+ {
+ // no null length edges
+ if(!(aCurrA.equal(aNextA) || aCurrB.equal(aNextB)))
+ {
+ const B2DVector aVecA(aNextA - aCurrA);
+ const B2DVector aVecB(aNextB - aCurrB);
+ double fCutA(aVecA.cross(aVecB));
+
+ if(!fTools::equalZero(fCutA))
+ {
+ const double fZero(0.0);
+ const double fOne(1.0);
+ fCutA = (aVecB.getY() * (aCurrB.getX() - aCurrA.getX()) + aVecB.getX() * (aCurrA.getY() - aCurrB.getY())) / fCutA;
+
+ // use range [0.0 .. 1.0[, thus in the loop, all direct aCurrA cuts will be registered
+ // as 0.0 cut. The 1.0 cut will be registered in the next loop step
+ if(fTools::moreOrEqual(fCutA, fZero) && fTools::less(fCutA, fOne))
+ {
+ // it's a candidate, but also need to test parameter value of cut on line 2
+ double fCutB;
+
+ // choose the more precise version
+ if(fabs(aVecB.getX()) > fabs(aVecB.getY()))
+ {
+ fCutB = (aCurrA.getX() + (fCutA * aVecA.getX()) - aCurrB.getX()) / aVecB.getX();
+ }
+ else
+ {
+ fCutB = (aCurrA.getY() + (fCutA * aVecA.getY()) - aCurrB.getY()) / aVecB.getY();
+ }
+
+ // use range [0.0 .. 1.0[, thus in the loop, all direct aCurrA cuts will be registered
+ // as 0.0 cut. The 1.0 cut will be registered in the next loop step
+ if(fTools::moreOrEqual(fCutB, fZero) && fTools::less(fCutB, fOne))
+ {
+ // cut is in both ranges. Add points for A and B
+ // #i111715# use fTools::equal instead of fTools::equalZero for better accuracy
+ if(fTools::equal(fCutA, fZero))
+ {
+ // ignore for start point in first edge; this is handled
+ // by outer methods and would just produce a double point
+ if(a)
+ {
+ rTempPointsA.emplace_back(aCurrA, a, 0.0);
+ }
+ }
+ else
+ {
+ const B2DPoint aCutPoint(interpolate(aCurrA, aNextA, fCutA));
+ rTempPointsA.emplace_back(aCutPoint, a, fCutA);
+ }
+
+ // #i111715# use fTools::equal instead of fTools::equalZero for better accuracy
+ if(fTools::equal(fCutB, fZero))
+ {
+ // ignore for start point in first edge; this is handled
+ // by outer methods and would just produce a double point
+ if(b)
+ {
+ rTempPointsB.emplace_back(aCurrB, b, 0.0);
+ }
+ }
+ else
+ {
+ const B2DPoint aCutPoint(interpolate(aCurrB, aNextB, fCutB));
+ rTempPointsB.emplace_back(aCutPoint, b, fCutB);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // prepare next step
+ aCurrB = aNextB;
+ }
+
+ // prepare next step
+ aCurrA = aNextA;
+ }
+ }
+
+ void findEdgeCutsBezierAndEdge(
+ const B2DCubicBezier& rCubicA,
+ const B2DPoint& rCurrB, const B2DPoint& rNextB,
+ sal_uInt32 nIndA, sal_uInt32 nIndB,
+ temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
+ {
+ // find all cuts between given bezier segment and edge. Add an entry to the tempPoints
+ // for each common point with the cut value describing the relative position on given
+ // bezier segment and edge.
+ B2DPolygon aTempPolygonA;
+ B2DPolygon aTempPolygonEdge;
+ temporaryPointVector aTempPointVectorA;
+ temporaryPointVector aTempPointVectorEdge;
+
+ // create subdivided polygons and find cuts between them
+ // Keep adaptiveSubdivideByCount due to needed quality
+ aTempPolygonA.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
+ aTempPolygonA.append(rCubicA.getStartPoint());
+ rCubicA.adaptiveSubdivideByCount(aTempPolygonA, SUBDIVIDE_FOR_CUT_TEST_COUNT);
+ aTempPolygonEdge.append(rCurrB);
+ aTempPolygonEdge.append(rNextB);
+
+ // #i76891# using findCuts recursively is not sufficient here
+ findCutsAndTouchesAndCommonForBezier(aTempPolygonA, aTempPolygonEdge, aTempPointVectorA, aTempPointVectorEdge);
+
+ if(!aTempPointVectorA.empty())
+ {
+ // adapt tempVector entries to segment
+ adaptAndTransferCutsWithBezierSegment(aTempPointVectorA, aTempPolygonA, nIndA, rTempPointsA);
+ }
+
+ // append remapped tempVector entries for edge to tempPoints for edge
+ for(const temporaryPoint & rTempPoint : aTempPointVectorEdge)
+ {
+ rTempPointsB.emplace_back(rTempPoint.getPoint(), nIndB, rTempPoint.getCut());
+ }
+ }
+
+ void findEdgeCutsTwoBeziers(
+ const B2DCubicBezier& rCubicA,
+ const B2DCubicBezier& rCubicB,
+ sal_uInt32 nIndA, sal_uInt32 nIndB,
+ temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
+ {
+ // find all cuts between the two given bezier segments. Add an entry to the tempPoints
+ // for each common point with the cut value describing the relative position on given
+ // bezier segments.
+ B2DPolygon aTempPolygonA;
+ B2DPolygon aTempPolygonB;
+ temporaryPointVector aTempPointVectorA;
+ temporaryPointVector aTempPointVectorB;
+
+ // create subdivided polygons and find cuts between them
+ // Keep adaptiveSubdivideByCount due to needed quality
+ aTempPolygonA.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
+ aTempPolygonA.append(rCubicA.getStartPoint());
+ rCubicA.adaptiveSubdivideByCount(aTempPolygonA, SUBDIVIDE_FOR_CUT_TEST_COUNT);
+ aTempPolygonB.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
+ aTempPolygonB.append(rCubicB.getStartPoint());
+ rCubicB.adaptiveSubdivideByCount(aTempPolygonB, SUBDIVIDE_FOR_CUT_TEST_COUNT);
+
+ // #i76891# using findCuts recursively is not sufficient here
+ findCutsAndTouchesAndCommonForBezier(aTempPolygonA, aTempPolygonB, aTempPointVectorA, aTempPointVectorB);
+
+ if(!aTempPointVectorA.empty())
+ {
+ // adapt tempVector entries to segment
+ adaptAndTransferCutsWithBezierSegment(aTempPointVectorA, aTempPolygonA, nIndA, rTempPointsA);
+ }
+
+ if(!aTempPointVectorB.empty())
+ {
+ // adapt tempVector entries to segment
+ adaptAndTransferCutsWithBezierSegment(aTempPointVectorB, aTempPolygonB, nIndB, rTempPointsB);
+ }
+ }
+
+ void findEdgeCutsOneBezier(
+ const B2DCubicBezier& rCubicA,
+ sal_uInt32 nInd, temporaryPointVector& rTempPoints)
+ {
+ // avoid expensive part of this method if possible
+ // TODO: use hasAnyExtremum() method instead when it becomes available
+ double fDummy;
+ const bool bHasAnyExtremum = rCubicA.getMinimumExtremumPosition( fDummy );
+ if( !bHasAnyExtremum )
+ return;
+
+ // find all self-intersections on the given bezier segment. Add an entry to the tempPoints
+ // for each self intersection point with the cut value describing the relative position on given
+ // bezier segment.
+ B2DPolygon aTempPolygon;
+ temporaryPointVector aTempPointVector;
+
+ // create subdivided polygon and find cuts on it
+ // Keep adaptiveSubdivideByCount due to needed quality
+ aTempPolygon.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
+ aTempPolygon.append(rCubicA.getStartPoint());
+ rCubicA.adaptiveSubdivideByCount(aTempPolygon, SUBDIVIDE_FOR_CUT_TEST_COUNT);
+ findCuts(aTempPolygon, aTempPointVector);
+
+ if(!aTempPointVector.empty())
+ {
+ // adapt tempVector entries to segment
+ adaptAndTransferCutsWithBezierSegment(aTempPointVector, aTempPolygon, nInd, rTempPoints);
+ }
+ }
+
+ void findCuts(const B2DPolygon& rCandidate, temporaryPointVector& rTempPoints, size_t* pPointLimit)
+ {
+ // find out if there are edges with intersections (self-cuts). If yes, add
+ // entries to rTempPoints accordingly
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(!nPointCount)
+ return;
+
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+
+ if(!nEdgeCount)
+ return;
+
+ const bool bCurvesInvolved(rCandidate.areControlPointsUsed());
+
+ if(bCurvesInvolved)
+ {
+ B2DCubicBezier aCubicA;
+ B2DCubicBezier aCubicB;
+
+ for(sal_uInt32 a(0); a < nEdgeCount - 1; a++)
+ {
+ rCandidate.getBezierSegment(a, aCubicA);
+ aCubicA.testAndSolveTrivialBezier();
+ const bool bEdgeAIsCurve(aCubicA.isBezier());
+ const B2DRange aRangeA(aCubicA.getRange());
+
+ if(bEdgeAIsCurve)
+ {
+ // curved segments may have self-intersections, do not forget those (!)
+ findEdgeCutsOneBezier(aCubicA, a, rTempPoints);
+ }
+
+ for(sal_uInt32 b(a + 1); b < nEdgeCount; b++)
+ {
+ rCandidate.getBezierSegment(b, aCubicB);
+ aCubicB.testAndSolveTrivialBezier();
+ const B2DRange aRangeB(aCubicB.getRange());
+
+ // only overlapping segments need to be tested
+ // consecutive segments touch of course
+ bool bOverlap = false;
+ if( b > a+1)
+ bOverlap = aRangeA.overlaps(aRangeB);
+ else
+ bOverlap = aRangeA.overlapsMore(aRangeB);
+ if( bOverlap)
+ {
+ const bool bEdgeBIsCurve(aCubicB.isBezier());
+ if(bEdgeAIsCurve && bEdgeBIsCurve)
+ {
+ // test for bezier-bezier cuts
+ findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, rTempPoints, rTempPoints);
+ }
+ else if(bEdgeAIsCurve)
+ {
+ // test for bezier-edge cuts
+ findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, rTempPoints, rTempPoints);
+ }
+ else if(bEdgeBIsCurve)
+ {
+ // test for bezier-edge cuts
+ findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, rTempPoints, rTempPoints);
+ }
+ else
+ {
+ // test for simple edge-edge cuts
+ findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(),
+ a, b, rTempPoints, rTempPoints);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ B2DPoint aCurrA(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nEdgeCount - 1; a++)
+ {
+ const B2DPoint aNextA(rCandidate.getB2DPoint(a + 1 == nPointCount ? 0 : a + 1));
+ const B2DRange aRangeA(aCurrA, aNextA);
+ B2DPoint aCurrB(rCandidate.getB2DPoint(a + 1));
+
+ for(sal_uInt32 b(a + 1); b < nEdgeCount; b++)
+ {
+ const B2DPoint aNextB(rCandidate.getB2DPoint(b + 1 == nPointCount ? 0 : b + 1));
+ const B2DRange aRangeB(aCurrB, aNextB);
+
+ // consecutive segments touch of course
+ bool bOverlap = false;
+ if( b > a+1)
+ bOverlap = aRangeA.overlaps(aRangeB);
+ else
+ bOverlap = aRangeA.overlapsMore(aRangeB);
+ if( bOverlap)
+ {
+ findEdgeCutsTwoEdges(aCurrA, aNextA, aCurrB, aNextB, a, b, rTempPoints, rTempPoints);
+ }
+
+ if (pPointLimit && rTempPoints.size() > *pPointLimit)
+ break;
+
+ // prepare next step
+ aCurrB = aNextB;
+ }
+
+ // prepare next step
+ aCurrA = aNextA;
+ }
+ }
+
+ if (pPointLimit)
+ {
+ if (rTempPoints.size() > *pPointLimit)
+ *pPointLimit = 0;
+ else
+ *pPointLimit -= rTempPoints.size();
+ }
+ }
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+namespace basegfx
+{
+ namespace
+ {
+
+ void findTouchesOnEdge(
+ const B2DPoint& rCurr, const B2DPoint& rNext, const B2DPolygon& rPointPolygon,
+ sal_uInt32 nInd, temporaryPointVector& rTempPoints)
+ {
+ // find out if points from rPointPolygon are positioned on given edge. If Yes, add
+ // points there to represent touches (which may be enter or leave nodes later).
+ const sal_uInt32 nPointCount(rPointPolygon.count());
+
+ if(!nPointCount)
+ return;
+
+ const B2DRange aRange(rCurr, rNext);
+ const B2DVector aEdgeVector(rNext - rCurr);
+ bool bTestUsingX(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY()));
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B2DPoint aTestPoint(rPointPolygon.getB2DPoint(a));
+
+ if(aRange.isInside(aTestPoint))
+ {
+ if(!aTestPoint.equal(rCurr) && !aTestPoint.equal(rNext))
+ {
+ const B2DVector aTestVector(aTestPoint - rCurr);
+
+ if(areParallel(aEdgeVector, aTestVector))
+ {
+ const double fCut(bTestUsingX
+ ? aTestVector.getX() / aEdgeVector.getX()
+ : aTestVector.getY() / aEdgeVector.getY());
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ if(fTools::more(fCut, fZero) && fTools::less(fCut, fOne))
+ {
+ rTempPoints.emplace_back(aTestPoint, nInd, fCut);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void findTouchesOnCurve(
+ const B2DCubicBezier& rCubicA, const B2DPolygon& rPointPolygon,
+ sal_uInt32 nInd, temporaryPointVector& rTempPoints)
+ {
+ // find all points from rPointPolygon which touch the given bezier segment. Add an entry
+ // for each touch to the given pointVector. The cut for that entry is the relative position on
+ // the given bezier segment.
+ B2DPolygon aTempPolygon;
+ temporaryPointVector aTempPointVector;
+
+ // create subdivided polygon and find cuts on it
+ // Keep adaptiveSubdivideByCount due to needed quality
+ aTempPolygon.reserve(SUBDIVIDE_FOR_CUT_TEST_COUNT + 8);
+ aTempPolygon.append(rCubicA.getStartPoint());
+ rCubicA.adaptiveSubdivideByCount(aTempPolygon, SUBDIVIDE_FOR_CUT_TEST_COUNT);
+ findTouches(aTempPolygon, rPointPolygon, aTempPointVector);
+
+ if(!aTempPointVector.empty())
+ {
+ // adapt tempVector entries to segment
+ adaptAndTransferCutsWithBezierSegment(aTempPointVector, aTempPolygon, nInd, rTempPoints);
+ }
+ }
+
+ void findTouches(const B2DPolygon& rEdgePolygon, const B2DPolygon& rPointPolygon, temporaryPointVector& rTempPoints)
+ {
+ // find out if points from rPointPolygon touch edges from rEdgePolygon. If yes,
+ // add entries to rTempPoints
+ const sal_uInt32 nPointCount(rPointPolygon.count());
+ const sal_uInt32 nEdgePointCount(rEdgePolygon.count());
+
+ if(!(nPointCount && nEdgePointCount))
+ return;
+
+ const sal_uInt32 nEdgeCount(rEdgePolygon.isClosed() ? nEdgePointCount : nEdgePointCount - 1);
+ B2DPoint aCurr(rEdgePolygon.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nEdgePointCount);
+ const B2DPoint aNext(rEdgePolygon.getB2DPoint(nNextIndex));
+
+ if(!aCurr.equal(aNext))
+ {
+ bool bHandleAsSimpleEdge(true);
+
+ if(rEdgePolygon.areControlPointsUsed())
+ {
+ const B2DPoint aNextControlPoint(rEdgePolygon.getNextControlPoint(a));
+ const B2DPoint aPrevControlPoint(rEdgePolygon.getPrevControlPoint(nNextIndex));
+ const bool bEdgeIsCurve(!aNextControlPoint.equal(aCurr) || !aPrevControlPoint.equal(aNext));
+
+ if(bEdgeIsCurve)
+ {
+ bHandleAsSimpleEdge = false;
+ const B2DCubicBezier aCubicA(aCurr, aNextControlPoint, aPrevControlPoint, aNext);
+ findTouchesOnCurve(aCubicA, rPointPolygon, a, rTempPoints);
+ }
+ }
+
+ if(bHandleAsSimpleEdge)
+ {
+ findTouchesOnEdge(aCurr, aNext, rPointPolygon, a, rTempPoints);
+ }
+ }
+
+ // next step
+ aCurr = aNext;
+ }
+ }
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+namespace basegfx
+{
+ namespace
+ {
+
+ void findCuts(const B2DPolygon& rCandidateA, const B2DPolygon& rCandidateB, temporaryPointVector& rTempPointsA, temporaryPointVector& rTempPointsB)
+ {
+ // find out if edges from both polygons cut. If so, add entries to rTempPoints which
+ // should be added to the polygons accordingly
+ const sal_uInt32 nPointCountA(rCandidateA.count());
+ const sal_uInt32 nPointCountB(rCandidateB.count());
+
+ if(!(nPointCountA && nPointCountB))
+ return;
+
+ const sal_uInt32 nEdgeCountA(rCandidateA.isClosed() ? nPointCountA : nPointCountA - 1);
+ const sal_uInt32 nEdgeCountB(rCandidateB.isClosed() ? nPointCountB : nPointCountB - 1);
+
+ if(!(nEdgeCountA && nEdgeCountB))
+ return;
+
+ const bool bCurvesInvolved(rCandidateA.areControlPointsUsed() || rCandidateB.areControlPointsUsed());
+
+ if(bCurvesInvolved)
+ {
+ B2DCubicBezier aCubicA;
+ B2DCubicBezier aCubicB;
+
+ for(sal_uInt32 a(0); a < nEdgeCountA; a++)
+ {
+ rCandidateA.getBezierSegment(a, aCubicA);
+ aCubicA.testAndSolveTrivialBezier();
+ const bool bEdgeAIsCurve(aCubicA.isBezier());
+ const B2DRange aRangeA(aCubicA.getRange());
+
+ for(sal_uInt32 b(0); b < nEdgeCountB; b++)
+ {
+ rCandidateB.getBezierSegment(b, aCubicB);
+ aCubicB.testAndSolveTrivialBezier();
+ const B2DRange aRangeB(aCubicB.getRange());
+
+ // consecutive segments touch of course
+ bool bOverlap = false;
+ if( b > a+1)
+ bOverlap = aRangeA.overlaps(aRangeB);
+ else
+ bOverlap = aRangeA.overlapsMore(aRangeB);
+ if( bOverlap)
+ {
+ const bool bEdgeBIsCurve(aCubicB.isBezier());
+ if(bEdgeAIsCurve && bEdgeBIsCurve)
+ {
+ // test for bezier-bezier cuts
+ findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, rTempPointsA, rTempPointsB);
+ }
+ else if(bEdgeAIsCurve)
+ {
+ // test for bezier-edge cuts
+ findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, rTempPointsA, rTempPointsB);
+ }
+ else if(bEdgeBIsCurve)
+ {
+ // test for bezier-edge cuts
+ findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, rTempPointsB, rTempPointsA);
+ }
+ else
+ {
+ // test for simple edge-edge cuts
+ findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(),
+ a, b, rTempPointsA, rTempPointsB);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ B2DPoint aCurrA(rCandidateA.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nEdgeCountA; a++)
+ {
+ const B2DPoint aNextA(rCandidateA.getB2DPoint(a + 1 == nPointCountA ? 0 : a + 1));
+ const B2DRange aRangeA(aCurrA, aNextA);
+ B2DPoint aCurrB(rCandidateB.getB2DPoint(0));
+
+ for(sal_uInt32 b(0); b < nEdgeCountB; b++)
+ {
+ const B2DPoint aNextB(rCandidateB.getB2DPoint(b + 1 == nPointCountB ? 0 : b + 1));
+ const B2DRange aRangeB(aCurrB, aNextB);
+
+ // consecutive segments touch of course
+ bool bOverlap = false;
+ if( b > a+1)
+ bOverlap = aRangeA.overlaps(aRangeB);
+ else
+ bOverlap = aRangeA.overlapsMore(aRangeB);
+ if( bOverlap)
+ {
+ // test for simple edge-edge cuts
+ findEdgeCutsTwoEdges(aCurrA, aNextA, aCurrB, aNextB, a, b, rTempPointsA, rTempPointsB);
+ }
+
+ // prepare next step
+ aCurrB = aNextB;
+ }
+
+ // prepare next step
+ aCurrA = aNextA;
+ }
+ }
+ }
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+namespace basegfx::utils
+{
+
+ B2DPolygon addPointsAtCutsAndTouches(const B2DPolygon& rCandidate, size_t* pPointLimit)
+ {
+ if(rCandidate.count())
+ {
+ temporaryPointVector aTempPoints;
+
+ findTouches(rCandidate, rCandidate, aTempPoints);
+ findCuts(rCandidate, aTempPoints, pPointLimit);
+ if (pPointLimit && !*pPointLimit)
+ {
+ SAL_WARN("basegfx", "addPointsAtCutsAndTouches hit point limit");
+ return rCandidate;
+ }
+
+ return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints);
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolyPolygon addPointsAtCutsAndTouches(const B2DPolyPolygon& rCandidate, size_t* pPointLimit)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nCount)
+ {
+ B2DPolyPolygon aRetval;
+
+ if(nCount == 1)
+ {
+ // remove self intersections
+ aRetval.append(addPointsAtCutsAndTouches(rCandidate.getB2DPolygon(0), pPointLimit));
+ }
+ else
+ {
+ // first solve self cuts and self touches for all contained single polygons
+ std::unique_ptr<temporaryPolygonData[]> pTempData(new temporaryPolygonData[nCount]);
+ sal_uInt32 a, b;
+
+ for(a = 0; a < nCount; a++)
+ {
+ // use polygons with solved self intersections
+ pTempData[a].setPolygon(addPointsAtCutsAndTouches(rCandidate.getB2DPolygon(a), pPointLimit));
+ }
+
+ if (pPointLimit && !*pPointLimit)
+ {
+ SAL_WARN("basegfx", "addPointsAtCutsAndTouches hit point limit");
+ return rCandidate;
+ }
+
+ // now cuts and touches between the polygons
+ for(a = 0; a < nCount; a++)
+ {
+ for(b = 0; b < nCount; b++)
+ {
+ if(a != b)
+ {
+ // look for touches, compare each edge polygon to all other points
+ if(pTempData[a].getRange().overlaps(pTempData[b].getRange()))
+ {
+ findTouches(pTempData[a].getPolygon(), pTempData[b].getPolygon(), pTempData[a].getTemporaryPointVector());
+ }
+ }
+
+ if(a < b)
+ {
+ // look for cuts, compare each edge polygon to following ones
+ if(pTempData[a].getRange().overlaps(pTempData[b].getRange()))
+ {
+ findCuts(pTempData[a].getPolygon(), pTempData[b].getPolygon(), pTempData[a].getTemporaryPointVector(), pTempData[b].getTemporaryPointVector());
+ }
+ }
+ }
+ }
+
+ // consolidate the result
+ for(a = 0; a < nCount; a++)
+ {
+ aRetval.append(mergeTemporaryPointsAndPolygon(pTempData[a].getPolygon(), pTempData[a].getTemporaryPointVector()));
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate, const B2DPoint& rStart, const B2DPoint& rEnd)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nCount && !rStart.equal(rEnd))
+ {
+ const B2DRange aPolygonRange(rCandidate.getB2DRange());
+ const B2DRange aEdgeRange(rStart, rEnd);
+
+ if(aPolygonRange.overlaps(aEdgeRange))
+ {
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nCount : nCount - 1);
+ temporaryPointVector aTempPoints;
+ temporaryPointVector aUnusedTempPoints;
+ B2DCubicBezier aCubic;
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ rCandidate.getBezierSegment(a, aCubic);
+ B2DRange aCubicRange(aCubic.getStartPoint(), aCubic.getEndPoint());
+
+ if(aCubic.isBezier())
+ {
+ aCubicRange.expand(aCubic.getControlPointA());
+ aCubicRange.expand(aCubic.getControlPointB());
+
+ if(aCubicRange.overlaps(aEdgeRange))
+ {
+ findEdgeCutsBezierAndEdge(aCubic, rStart, rEnd, a, 0, aTempPoints, aUnusedTempPoints);
+ }
+ }
+ else
+ {
+ if(aCubicRange.overlaps(aEdgeRange))
+ {
+ findEdgeCutsTwoEdges(aCubic.getStartPoint(), aCubic.getEndPoint(), rStart, rEnd, a, 0, aTempPoints, aUnusedTempPoints);
+ }
+ }
+ }
+
+ return mergeTemporaryPointsAndPolygon(rCandidate, aTempPoints);
+ }
+ }
+
+ return rCandidate;
+ }
+
+ B2DPolygon addPointsAtCuts(const B2DPolygon& rCandidate, const B2DPolyPolygon& rPolyMask)
+ {
+ const sal_uInt32 nCountA(rCandidate.count());
+ const sal_uInt32 nCountM(rPolyMask.count());
+
+ if(nCountA && nCountM)
+ {
+ const B2DRange aRangeA(rCandidate.getB2DRange());
+ const B2DRange aRangeM(rPolyMask.getB2DRange());
+
+ if(aRangeA.overlaps(aRangeM))
+ {
+ const sal_uInt32 nEdgeCountA(rCandidate.isClosed() ? nCountA : nCountA - 1);
+ temporaryPointVector aTempPointsA;
+ temporaryPointVector aUnusedTempPointsB;
+
+ for(sal_uInt32 m(0); m < nCountM; m++)
+ {
+ const B2DPolygon& aMask(rPolyMask.getB2DPolygon(m));
+ const sal_uInt32 nCountB(aMask.count());
+
+ if(nCountB)
+ {
+ B2DCubicBezier aCubicA;
+ B2DCubicBezier aCubicB;
+
+ for(sal_uInt32 a(0); a < nEdgeCountA; a++)
+ {
+ rCandidate.getBezierSegment(a, aCubicA);
+ const bool bCubicAIsCurve(aCubicA.isBezier());
+ B2DRange aCubicRangeA(aCubicA.getStartPoint(), aCubicA.getEndPoint());
+
+ if(bCubicAIsCurve)
+ {
+ aCubicRangeA.expand(aCubicA.getControlPointA());
+ aCubicRangeA.expand(aCubicA.getControlPointB());
+ }
+
+ for(sal_uInt32 b(0); b < nCountB; b++)
+ {
+ aMask.getBezierSegment(b, aCubicB);
+ const bool bCubicBIsCurve(aCubicB.isBezier());
+ B2DRange aCubicRangeB(aCubicB.getStartPoint(), aCubicB.getEndPoint());
+
+ if(bCubicBIsCurve)
+ {
+ aCubicRangeB.expand(aCubicB.getControlPointA());
+ aCubicRangeB.expand(aCubicB.getControlPointB());
+ }
+
+ if(aCubicRangeA.overlaps(aCubicRangeB))
+ {
+ if(bCubicAIsCurve && bCubicBIsCurve)
+ {
+ findEdgeCutsTwoBeziers(aCubicA, aCubicB, a, b, aTempPointsA, aUnusedTempPointsB);
+ }
+ else if(bCubicAIsCurve)
+ {
+ findEdgeCutsBezierAndEdge(aCubicA, aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, aTempPointsA, aUnusedTempPointsB);
+ }
+ else if(bCubicBIsCurve)
+ {
+ findEdgeCutsBezierAndEdge(aCubicB, aCubicA.getStartPoint(), aCubicA.getEndPoint(), b, a, aUnusedTempPointsB, aTempPointsA);
+ }
+ else
+ {
+ findEdgeCutsTwoEdges(aCubicA.getStartPoint(), aCubicA.getEndPoint(), aCubicB.getStartPoint(), aCubicB.getEndPoint(), a, b, aTempPointsA, aUnusedTempPointsB);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return mergeTemporaryPointsAndPolygon(rCandidate, aTempPointsA);
+ }
+ }
+
+ return rCandidate;
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dpolygontools.cxx b/basegfx/source/polygon/b2dpolygontools.cxx
new file mode 100644
index 0000000000..b3f43669dd
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolygontools.cxx
@@ -0,0 +1,3702 @@
+/* -*- 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 <numeric>
+#include <algorithm>
+#include <stack>
+
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <sal/log.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/point/b3dpoint.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/curve/b2dbeziertools.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+// #i37443#
+#define ANGLE_BOUND_START_VALUE (2.25)
+#define ANGLE_BOUND_MINIMUM_VALUE (0.1)
+#define STEPSPERQUARTER (3)
+
+namespace basegfx::utils
+{
+ void openWithGeometryChange(B2DPolygon& rCandidate)
+ {
+ if(!rCandidate.isClosed())
+ return;
+
+ if(rCandidate.count())
+ {
+ rCandidate.append(rCandidate.getB2DPoint(0));
+
+ if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(0))
+ {
+ rCandidate.setPrevControlPoint(rCandidate.count() - 1, rCandidate.getPrevControlPoint(0));
+ rCandidate.resetPrevControlPoint(0);
+ }
+ }
+
+ rCandidate.setClosed(false);
+ }
+
+ void closeWithGeometryChange(B2DPolygon& rCandidate)
+ {
+ if(rCandidate.isClosed())
+ return;
+
+ while(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
+ {
+ if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(rCandidate.count() - 1))
+ {
+ rCandidate.setPrevControlPoint(0, rCandidate.getPrevControlPoint(rCandidate.count() - 1));
+ }
+
+ rCandidate.remove(rCandidate.count() - 1);
+ }
+
+ rCandidate.setClosed(true);
+ }
+
+ void checkClosed(B2DPolygon& rCandidate)
+ {
+ // #i80172# Removed unnecessary assertion
+ // OSL_ENSURE(!rCandidate.isClosed(), "checkClosed: already closed (!)");
+
+ if(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
+ {
+ closeWithGeometryChange(rCandidate);
+ }
+ }
+
+ // Get successor and predecessor indices. Returning the same index means there
+ // is none. Same for successor.
+ sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
+
+ if(nIndex)
+ {
+ return nIndex - 1;
+ }
+ else if(rCandidate.count())
+ {
+ return rCandidate.count() - 1;
+ }
+ else
+ {
+ return nIndex;
+ }
+ }
+
+ sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
+
+ if(nIndex + 1 < rCandidate.count())
+ {
+ return nIndex + 1;
+ }
+ else if(nIndex + 1 == rCandidate.count())
+ {
+ return 0;
+ }
+ else
+ {
+ return nIndex;
+ }
+ }
+
+ B2VectorOrientation getOrientation(const B2DPolygon& rCandidate)
+ {
+ B2VectorOrientation eRetval(B2VectorOrientation::Neutral);
+
+ if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
+ {
+ const double fSignedArea(getSignedArea(rCandidate));
+
+ if(fTools::equalZero(fSignedArea))
+ {
+ // B2VectorOrientation::Neutral, already set
+ }
+ if(fSignedArea > 0.0)
+ {
+ eRetval = B2VectorOrientation::Positive;
+ }
+ else if(fSignedArea < 0.0)
+ {
+ eRetval = B2VectorOrientation::Negative;
+ }
+ }
+
+ return eRetval;
+ }
+
+ B2VectorContinuity getContinuityInPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ return rCandidate.getContinuityInPoint(nIndex);
+ }
+
+ B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound, int nRecurseLimit)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ B2DPolygon aRetval;
+
+ if(nPointCount)
+ {
+ // prepare edge-oriented loop
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rCandidate.getB2DPoint(0));
+
+ // perf: try to avoid too many reallocations by guessing the result's pointcount
+ aRetval.reserve(nPointCount*4);
+
+ // add start point (always)
+ aRetval.append(aBezier.getStartPoint());
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // get next and control points
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+ aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
+ aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aBezier.testAndSolveTrivialBezier();
+
+ if(aBezier.isBezier())
+ {
+ // add curved edge and generate DistanceBound
+ double fBound(0.0);
+
+ if(fDistanceBound == 0.0)
+ {
+ // If not set, use B2DCubicBezier functionality to guess a rough value
+ const double fRoughLength((aBezier.getEdgeLength() + aBezier.getControlPolygonLength()) / 2.0);
+
+ // take 1/100th of the rough curve length
+ fBound = fRoughLength * 0.01;
+ }
+ else
+ {
+ // use given bound value
+ fBound = fDistanceBound;
+ }
+
+ // make sure bound value is not too small. The base units are 1/100th mm, thus
+ // just make sure it's not smaller then 1/100th of that
+ if(fBound < 0.01)
+ {
+ fBound = 0.01;
+ }
+
+ // call adaptive subdivide which adds edges to aRetval accordingly
+ aBezier.adaptiveSubdivideByDistance(aRetval, fBound, nRecurseLimit);
+ }
+ else
+ {
+ // add non-curved edge
+ aRetval.append(aBezier.getEndPoint());
+ }
+
+ // prepare next step
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(rCandidate.isClosed())
+ {
+ // set closed flag and correct last point (which is added double now).
+ closeWithGeometryChange(aRetval);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolygon adaptiveSubdivideByAngle(const B2DPolygon& rCandidate, double fAngleBound)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ B2DPolygon aRetval;
+
+ if(nPointCount)
+ {
+ // prepare edge-oriented loop
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rCandidate.getB2DPoint(0));
+
+ // perf: try to avoid too many reallocations by guessing the result's pointcount
+ aRetval.reserve(nPointCount*4);
+
+ // add start point (always)
+ aRetval.append(aBezier.getStartPoint());
+
+ // #i37443# prepare convenient AngleBound if none was given
+ if(fAngleBound == 0.0)
+ {
+ fAngleBound = ANGLE_BOUND_START_VALUE;
+ }
+ else if(fTools::less(fAngleBound, ANGLE_BOUND_MINIMUM_VALUE))
+ {
+ fAngleBound = 0.1;
+ }
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // get next and control points
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+ aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
+ aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aBezier.testAndSolveTrivialBezier();
+
+ if(aBezier.isBezier())
+ {
+ // call adaptive subdivide
+ aBezier.adaptiveSubdivideByAngle(aRetval, fAngleBound);
+ }
+ else
+ {
+ // add non-curved edge
+ aRetval.append(aBezier.getEndPoint());
+ }
+
+ // prepare next step
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(rCandidate.isClosed())
+ {
+ // set closed flag and correct last point (which is added double now).
+ closeWithGeometryChange(aRetval);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ bool isInside(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder)
+ {
+ const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
+
+ if(bWithBorder && isPointOnPolygon(aCandidate, rPoint))
+ {
+ return true;
+ }
+ else
+ {
+ bool bRetval(false);
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(nPointCount)
+ {
+ B2DPoint aCurrentPoint(aCandidate.getB2DPoint(nPointCount - 1));
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B2DPoint aPreviousPoint(aCurrentPoint);
+ aCurrentPoint = aCandidate.getB2DPoint(a);
+
+ // cross-over in Y? tdf#130150 use full precision, no need for epsilon
+ const bool bCompYA(aPreviousPoint.getY() > rPoint.getY());
+ const bool bCompYB(aCurrentPoint.getY() > rPoint.getY());
+
+ if(bCompYA != bCompYB)
+ {
+ // cross-over in X? tdf#130150 use full precision, no need for epsilon
+ const bool bCompXA(aPreviousPoint.getX() > rPoint.getX());
+ const bool bCompXB(aCurrentPoint.getX() > rPoint.getX());
+
+ if(bCompXA == bCompXB)
+ {
+ if(bCompXA)
+ {
+ bRetval = !bRetval;
+ }
+ }
+ else
+ {
+ const double fCompare(
+ aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) *
+ (aPreviousPoint.getX() - aCurrentPoint.getX()) /
+ (aPreviousPoint.getY() - aCurrentPoint.getY()));
+
+ // tdf#130150 use full precision, no need for epsilon
+ if(fCompare > rPoint.getX())
+ {
+ bRetval = !bRetval;
+ }
+ }
+ }
+ }
+ }
+
+ return bRetval;
+ }
+ }
+
+ bool isInside(const B2DPolygon& rCandidate, const B2DPolygon& rPolygon, bool bWithBorder)
+ {
+ const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
+ const B2DPolygon aPolygon(rPolygon.areControlPointsUsed() ? rPolygon.getDefaultAdaptiveSubdivision() : rPolygon);
+ const sal_uInt32 nPointCount(aPolygon.count());
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B2DPoint aTestPoint(aPolygon.getB2DPoint(a));
+
+ if(!isInside(aCandidate, aTestPoint, bWithBorder))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ B2DRange getRange(const B2DPolygon& rCandidate)
+ {
+ // changed to use internally buffered version at B2DPolygon
+ return rCandidate.getB2DRange();
+ }
+
+ double getSignedArea(const B2DPolygon& rCandidate)
+ {
+ const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
+ double fRetval(0.0);
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(nPointCount > 2)
+ {
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B2DPoint aPreviousPoint(aCandidate.getB2DPoint((!a) ? nPointCount - 1 : a - 1));
+ const B2DPoint aCurrentPoint(aCandidate.getB2DPoint(a));
+
+ fRetval += aPreviousPoint.getX() * aCurrentPoint.getY();
+ fRetval -= aPreviousPoint.getY() * aCurrentPoint.getX();
+ }
+
+ // correct to zero if small enough. Also test the quadratic
+ // of the result since the precision is near quadratic due to
+ // the algorithm
+ if(fTools::equalZero(fRetval) || fTools::equalZero(fRetval * fRetval))
+ {
+ fRetval = 0.0;
+ }
+ }
+
+ return fRetval;
+ }
+
+ double getArea(const B2DPolygon& rCandidate)
+ {
+ double fRetval(0.0);
+
+ if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
+ {
+ fRetval = getSignedArea(rCandidate);
+ const double fZero(0.0);
+
+ if(fTools::less(fRetval, fZero))
+ {
+ fRetval = -fRetval;
+ }
+ }
+
+ return fRetval;
+ }
+
+ double getEdgeLength(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ OSL_ENSURE(nIndex < nPointCount, "getEdgeLength: Access to polygon out of range (!)");
+ double fRetval(0.0);
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ B2DCubicBezier aEdge;
+
+ aEdge.setStartPoint(rCandidate.getB2DPoint(nIndex));
+ aEdge.setControlPointA(rCandidate.getNextControlPoint(nIndex));
+ aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+
+ fRetval = aEdge.getLength();
+ }
+ else
+ {
+ const B2DPoint aCurrent(rCandidate.getB2DPoint(nIndex));
+ const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex));
+
+ fRetval = B2DVector(aNext - aCurrent).getLength();
+ }
+ }
+
+ return fRetval;
+ }
+
+ double getLength(const B2DPolygon& rCandidate, bool bApproximateBezierLength)
+ {
+ double fRetval(0.0);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ if (bApproximateBezierLength)
+ {
+ B2DPoint aStartPoint = rCandidate.getB2DPoint(0);
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // An approximate length of a cubic Bezier curve is the average
+ // of its chord length and the sum of the lengths of its control net sides.
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B2DPoint& aControlPoint1 = rCandidate.getNextControlPoint(a);
+ const B2DPoint& aControlPoint2 = rCandidate.getPrevControlPoint(nNextIndex);
+ const B2DPoint& aEndPoint = rCandidate.getB2DPoint(nNextIndex);
+
+ double chord_length = B2DVector(aEndPoint - aStartPoint).getLength();
+ double control_net_length = B2DVector(aStartPoint - aControlPoint1).getLength()
+ + B2DVector(aControlPoint2 - aControlPoint1).getLength()
+ + B2DVector(aEndPoint - aControlPoint2).getLength();
+ double approximate_arc_length = (control_net_length + chord_length) / 2;
+
+ fRetval += approximate_arc_length;
+ aStartPoint = aEndPoint;
+ }
+
+ }
+ else
+ {
+ B2DCubicBezier aEdge;
+ aEdge.setStartPoint(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
+ aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+
+ fRetval += aEdge.getLength();
+ aEdge.setStartPoint(aEdge.getEndPoint());
+ }
+ }
+ }
+ else
+ {
+ B2DPoint aCurrent(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex));
+
+ fRetval += B2DVector(aNext - aCurrent).getLength();
+ aCurrent = aNext;
+ }
+ }
+ }
+
+ return fRetval;
+ }
+
+ B2DPoint getPositionAbsolute(const B2DPolygon& rCandidate, double fDistance, double fLength)
+ {
+ B2DPoint aRetval;
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if( nPointCount == 1 )
+ {
+ // only one point (i.e. no edge) - simply take that point
+ aRetval = rCandidate.getB2DPoint(0);
+ }
+ else if(nPointCount > 1)
+ {
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ sal_uInt32 nIndex(0);
+ bool bIndexDone(false);
+
+ // get length if not given
+ if(fTools::equalZero(fLength))
+ {
+ fLength = getLength(rCandidate);
+ }
+
+ if(fTools::less(fDistance, 0.0))
+ {
+ // handle fDistance < 0.0
+ if(rCandidate.isClosed())
+ {
+ // if fDistance < 0.0 increment with multiple of fLength
+ sal_uInt32 nCount(sal_uInt32(-fDistance / fLength));
+ fDistance += double(nCount + 1) * fLength;
+ }
+ else
+ {
+ // crop to polygon start
+ fDistance = 0.0;
+ bIndexDone = true;
+ }
+ }
+ else if(fTools::moreOrEqual(fDistance, fLength))
+ {
+ // handle fDistance >= fLength
+ if(rCandidate.isClosed())
+ {
+ // if fDistance >= fLength decrement with multiple of fLength
+ sal_uInt32 nCount(sal_uInt32(fDistance / fLength));
+ fDistance -= static_cast<double>(nCount) * fLength;
+ }
+ else
+ {
+ // crop to polygon end
+ fDistance = 0.0;
+ nIndex = nEdgeCount;
+ bIndexDone = true;
+ }
+ }
+
+ // look for correct index. fDistance is now [0.0 .. fLength[
+ double fEdgeLength(getEdgeLength(rCandidate, nIndex));
+
+ while(!bIndexDone)
+ {
+ // edge found must be on the half-open range
+ // [0,fEdgeLength).
+ // Note that in theory, we cannot move beyond
+ // the last polygon point, since fDistance>=fLength
+ // is checked above. Unfortunately, with floating-
+ // point calculations, this case might happen.
+ // Handled by nIndex check below
+ if (nIndex+1 < nEdgeCount && fTools::moreOrEqual(fDistance, fEdgeLength))
+ {
+ // go to next edge
+ fDistance -= fEdgeLength;
+ fEdgeLength = getEdgeLength(rCandidate, ++nIndex);
+ }
+ else
+ {
+ // it's on this edge, stop
+ bIndexDone = true;
+ }
+ }
+
+ // get the point using nIndex
+ aRetval = rCandidate.getB2DPoint(nIndex);
+
+ // if fDistance != 0.0, move that length on the edge. The edge
+ // length is in fEdgeLength.
+ if(!fTools::equalZero(fDistance))
+ {
+ if(fTools::moreOrEqual(fDistance, fEdgeLength))
+ {
+ // end point of chosen edge
+ const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+ aRetval = rCandidate.getB2DPoint(nNextIndex);
+ }
+ else if(fTools::equalZero(fDistance))
+ {
+ // start point of chosen edge
+ }
+ else
+ {
+ // inside edge
+ const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+ const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
+ bool bDone(false);
+
+ // add calculated average value to the return value
+ if(rCandidate.areControlPointsUsed())
+ {
+ // get as bezier segment
+ const B2DCubicBezier aBezierSegment(
+ aRetval, rCandidate.getNextControlPoint(nIndex),
+ rCandidate.getPrevControlPoint(nNextIndex), aNextPoint);
+
+ if(aBezierSegment.isBezier())
+ {
+ // use B2DCubicBezierHelper to bridge the non-linear gap between
+ // length and bezier distances
+ const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
+ const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fDistance));
+
+ aRetval = aBezierSegment.interpolatePoint(fBezierDistance);
+ bDone = true;
+ }
+ }
+
+ if(!bDone)
+ {
+ const double fRelativeInEdge(fDistance / fEdgeLength);
+ aRetval = interpolate(aRetval, aNextPoint, fRelativeInEdge);
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPoint getPositionRelative(const B2DPolygon& rCandidate, double fDistance, double fLength)
+ {
+ // get length if not given
+ if(fTools::equalZero(fLength))
+ {
+ fLength = getLength(rCandidate);
+ }
+
+ // multiply fDistance with real length to get absolute position and
+ // use getPositionAbsolute
+ return getPositionAbsolute(rCandidate, fDistance * fLength, fLength);
+ }
+
+ B2DPolygon getSnippetAbsolute(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ // get length if not given
+ if(fTools::equalZero(fLength))
+ {
+ fLength = getLength(rCandidate);
+ }
+
+ // test and correct fFrom
+ if(fTools::less(fFrom, 0.0))
+ {
+ fFrom = 0.0;
+ }
+
+ // test and correct fTo
+ if(fTools::more(fTo, fLength))
+ {
+ fTo = fLength;
+ }
+
+ // test and correct relationship of fFrom, fTo
+ if(fTools::more(fFrom, fTo))
+ {
+ fFrom = fTo = (fFrom + fTo) / 2.0;
+ }
+
+ if(fTools::equalZero(fFrom) && fTools::equal(fTo, fLength))
+ {
+ // no change, result is the whole polygon
+ return rCandidate;
+ }
+ else
+ {
+ B2DPolygon aRetval;
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ double fPositionOfStart(0.0);
+ bool bStartDone(false);
+ bool bEndDone(false);
+
+ for(sal_uInt32 a(0); !(bStartDone && bEndDone) && a < nEdgeCount; a++)
+ {
+ const double fEdgeLength(getEdgeLength(rCandidate, a));
+
+ if(!bStartDone)
+ {
+ if(fTools::equalZero(fFrom))
+ {
+ aRetval.append(rCandidate.getB2DPoint(a));
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a));
+ }
+
+ bStartDone = true;
+ }
+ else if(fTools::moreOrEqual(fFrom, fPositionOfStart) && fTools::less(fFrom, fPositionOfStart + fEdgeLength))
+ {
+ // calculate and add start point
+ if(fTools::equalZero(fEdgeLength))
+ {
+ aRetval.append(rCandidate.getB2DPoint(a));
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a));
+ }
+ }
+ else
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B2DPoint aStart(rCandidate.getB2DPoint(a));
+ const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex));
+ bool bDone(false);
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ const B2DCubicBezier aBezierSegment(
+ aStart, rCandidate.getNextControlPoint(a),
+ rCandidate.getPrevControlPoint(nNextIndex), aEnd);
+
+ if(aBezierSegment.isBezier())
+ {
+ // use B2DCubicBezierHelper to bridge the non-linear gap between
+ // length and bezier distances
+ const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
+ const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fFrom - fPositionOfStart));
+ B2DCubicBezier aRight;
+
+ aBezierSegment.split(fBezierDistance, nullptr, &aRight);
+ aRetval.append(aRight.getStartPoint());
+ aRetval.setNextControlPoint(aRetval.count() - 1, aRight.getControlPointA());
+ bDone = true;
+ }
+ }
+
+ if(!bDone)
+ {
+ const double fRelValue((fFrom - fPositionOfStart) / fEdgeLength);
+ aRetval.append(interpolate(aStart, aEnd, fRelValue));
+ }
+ }
+
+ bStartDone = true;
+
+ // if same point, end is done, too.
+ if(rtl::math::approxEqual(fFrom, fTo))
+ {
+ bEndDone = true;
+ }
+ }
+ }
+
+ if(!bEndDone && fTools::moreOrEqual(fTo, fPositionOfStart) && fTools::less(fTo, fPositionOfStart + fEdgeLength))
+ {
+ // calculate and add end point
+ if(fTools::equalZero(fEdgeLength))
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aRetval.append(rCandidate.getB2DPoint(nNextIndex));
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
+ }
+ }
+ else
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B2DPoint aStart(rCandidate.getB2DPoint(a));
+ const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex));
+ bool bDone(false);
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ const B2DCubicBezier aBezierSegment(
+ aStart, rCandidate.getNextControlPoint(a),
+ rCandidate.getPrevControlPoint(nNextIndex), aEnd);
+
+ if(aBezierSegment.isBezier())
+ {
+ // use B2DCubicBezierHelper to bridge the non-linear gap between
+ // length and bezier distances
+ const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
+ const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fTo - fPositionOfStart));
+ B2DCubicBezier aLeft;
+
+ aBezierSegment.split(fBezierDistance, &aLeft, nullptr);
+ aRetval.append(aLeft.getEndPoint());
+ aRetval.setPrevControlPoint(aRetval.count() - 1, aLeft.getControlPointB());
+ bDone = true;
+ }
+ }
+
+ if(!bDone)
+ {
+ const double fRelValue((fTo - fPositionOfStart) / fEdgeLength);
+ aRetval.append(interpolate(aStart, aEnd, fRelValue));
+ }
+ }
+
+ bEndDone = true;
+ }
+
+ if(!bEndDone)
+ {
+ if(bStartDone)
+ {
+ // add segments end point
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aRetval.append(rCandidate.getB2DPoint(nNextIndex));
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
+ aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(nNextIndex));
+ }
+ }
+
+ // increment fPositionOfStart
+ fPositionOfStart += fEdgeLength;
+ }
+ }
+ return aRetval;
+ }
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ CutFlagValue findCut(
+ const B2DPoint& rEdge1Start, const B2DVector& rEdge1Delta,
+ const B2DPoint& rEdge2Start, const B2DVector& rEdge2Delta,
+ CutFlagValue aCutFlags,
+ double* pCut1, double* pCut2)
+ {
+ CutFlagValue aRetval(CutFlagValue::NONE);
+ double fCut1(0.0);
+ double fCut2(0.0);
+ bool bFinished(!static_cast<bool>(aCutFlags & CutFlagValue::ALL));
+
+ // test for same points?
+ if(!bFinished
+ && (aCutFlags & (CutFlagValue::START1|CutFlagValue::END1))
+ && (aCutFlags & (CutFlagValue::START2|CutFlagValue::END2)))
+ {
+ // same startpoint?
+ if((aCutFlags & (CutFlagValue::START1|CutFlagValue::START2)) == (CutFlagValue::START1|CutFlagValue::START2))
+ {
+ if(rEdge1Start.equal(rEdge2Start))
+ {
+ bFinished = true;
+ aRetval = (CutFlagValue::START1|CutFlagValue::START2);
+ }
+ }
+
+ // same endpoint?
+ if(!bFinished && (aCutFlags & (CutFlagValue::END1|CutFlagValue::END2)) == (CutFlagValue::END1|CutFlagValue::END2))
+ {
+ const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
+ const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
+
+ if(aEnd1.equal(aEnd2))
+ {
+ bFinished = true;
+ aRetval = (CutFlagValue::END1|CutFlagValue::END2);
+ fCut1 = fCut2 = 1.0;
+ }
+ }
+
+ // startpoint1 == endpoint2?
+ if(!bFinished && (aCutFlags & (CutFlagValue::START1|CutFlagValue::END2)) == (CutFlagValue::START1|CutFlagValue::END2))
+ {
+ const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
+
+ if(rEdge1Start.equal(aEnd2))
+ {
+ bFinished = true;
+ aRetval = (CutFlagValue::START1|CutFlagValue::END2);
+ fCut1 = 0.0;
+ fCut2 = 1.0;
+ }
+ }
+
+ // startpoint2 == endpoint1?
+ if(!bFinished&& (aCutFlags & (CutFlagValue::START2|CutFlagValue::END1)) == (CutFlagValue::START2|CutFlagValue::END1))
+ {
+ const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
+
+ if(rEdge2Start.equal(aEnd1))
+ {
+ bFinished = true;
+ aRetval = (CutFlagValue::START2|CutFlagValue::END1);
+ fCut1 = 1.0;
+ fCut2 = 0.0;
+ }
+ }
+ }
+
+ if(!bFinished && (aCutFlags & CutFlagValue::LINE))
+ {
+ if(aCutFlags & CutFlagValue::START1)
+ {
+ // start1 on line 2 ?
+ if(isPointOnEdge(rEdge1Start, rEdge2Start, rEdge2Delta, &fCut2))
+ {
+ bFinished = true;
+ aRetval = (CutFlagValue::LINE|CutFlagValue::START1);
+ }
+ }
+
+ if(!bFinished && (aCutFlags & CutFlagValue::START2))
+ {
+ // start2 on line 1 ?
+ if(isPointOnEdge(rEdge2Start, rEdge1Start, rEdge1Delta, &fCut1))
+ {
+ bFinished = true;
+ aRetval = (CutFlagValue::LINE|CutFlagValue::START2);
+ }
+ }
+
+ if(!bFinished && (aCutFlags & CutFlagValue::END1))
+ {
+ // end1 on line 2 ?
+ const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
+
+ if(isPointOnEdge(aEnd1, rEdge2Start, rEdge2Delta, &fCut2))
+ {
+ bFinished = true;
+ aRetval = (CutFlagValue::LINE|CutFlagValue::END1);
+ }
+ }
+
+ if(!bFinished && (aCutFlags & CutFlagValue::END2))
+ {
+ // end2 on line 1 ?
+ const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
+
+ if(isPointOnEdge(aEnd2, rEdge1Start, rEdge1Delta, &fCut1))
+ {
+ bFinished = true;
+ aRetval = (CutFlagValue::LINE|CutFlagValue::END2);
+ }
+ }
+
+ if(!bFinished)
+ {
+ // cut in line1, line2 ?
+ fCut1 = (rEdge1Delta.getX() * rEdge2Delta.getY()) - (rEdge1Delta.getY() * rEdge2Delta.getX());
+
+ if(!fTools::equalZero(fCut1))
+ {
+ fCut1 = (rEdge2Delta.getY() * (rEdge2Start.getX() - rEdge1Start.getX())
+ + rEdge2Delta.getX() * (rEdge1Start.getY() - rEdge2Start.getY())) / fCut1;
+
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ // inside parameter range edge1 AND fCut2 is calculable
+ if(fTools::more(fCut1, fZero) && fTools::less(fCut1, fOne)
+ && (!fTools::equalZero(rEdge2Delta.getX()) || !fTools::equalZero(rEdge2Delta.getY())))
+ {
+ // take the more precise calculation of the two possible
+ if(fabs(rEdge2Delta.getX()) > fabs(rEdge2Delta.getY()))
+ {
+ fCut2 = (rEdge1Start.getX() + fCut1
+ * rEdge1Delta.getX() - rEdge2Start.getX()) / rEdge2Delta.getX();
+ }
+ else
+ {
+ fCut2 = (rEdge1Start.getY() + fCut1
+ * rEdge1Delta.getY() - rEdge2Start.getY()) / rEdge2Delta.getY();
+ }
+
+ // inside parameter range edge2, too
+ if(fTools::more(fCut2, fZero) && fTools::less(fCut2, fOne))
+ {
+ aRetval = CutFlagValue::LINE;
+ }
+ }
+ }
+ }
+ }
+
+ // copy values if wanted
+ if(pCut1)
+ {
+ *pCut1 = fCut1;
+ }
+
+ if(pCut2)
+ {
+ *pCut2 = fCut2;
+ }
+
+ return aRetval;
+ }
+
+ bool isPointOnEdge(
+ const B2DPoint& rPoint,
+ const B2DPoint& rEdgeStart,
+ const B2DVector& rEdgeDelta,
+ double* pCut)
+ {
+ bool bDeltaXIsZero(fTools::equalZero(rEdgeDelta.getX()));
+ bool bDeltaYIsZero(fTools::equalZero(rEdgeDelta.getY()));
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ if(bDeltaXIsZero && bDeltaYIsZero)
+ {
+ // no line, just a point
+ return false;
+ }
+ else if(bDeltaXIsZero)
+ {
+ // vertical line
+ if(fTools::equal(rPoint.getX(), rEdgeStart.getX()))
+ {
+ double fValue = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
+
+ if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
+ {
+ if(pCut)
+ {
+ *pCut = fValue;
+ }
+
+ return true;
+ }
+ }
+ }
+ else if(bDeltaYIsZero)
+ {
+ // horizontal line
+ if(fTools::equal(rPoint.getY(), rEdgeStart.getY()))
+ {
+ double fValue = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
+
+ if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
+ {
+ if(pCut)
+ {
+ *pCut = fValue;
+ }
+
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // any angle line
+ double fTOne = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
+ double fTTwo = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
+
+ if(fTools::equal(fTOne, fTTwo))
+ {
+ // same parameter representation, point is on line. Take
+ // middle value for better results
+ double fValue = (fTOne + fTTwo) / 2.0;
+
+ if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
+ {
+ // point is inside line bounds, too
+ if(pCut)
+ {
+ *pCut = fValue;
+ }
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ void applyLineDashing(
+ const B2DPolygon& rCandidate,
+ const std::vector<double>& rDotDashArray,
+ B2DPolyPolygon* pLineTarget,
+ B2DPolyPolygon* pGapTarget,
+ double fDotDashLength)
+ {
+ // clear targets in any case
+ if(pLineTarget)
+ {
+ pLineTarget->clear();
+ }
+
+ if(pGapTarget)
+ {
+ pGapTarget->clear();
+ }
+
+ // provide callbacks as lambdas
+ auto aLineCallback(
+ nullptr == pLineTarget
+ ? std::function<void(const basegfx::B2DPolygon&)>()
+ : [&pLineTarget](const basegfx::B2DPolygon& rSnippet){ pLineTarget->append(rSnippet); });
+ auto aGapCallback(
+ nullptr == pGapTarget
+ ? std::function<void(const basegfx::B2DPolygon&)>()
+ : [&pGapTarget](const basegfx::B2DPolygon& rSnippet){ pGapTarget->append(rSnippet); });
+
+ // call version that uses callbacks
+ applyLineDashing(
+ rCandidate,
+ rDotDashArray,
+ aLineCallback,
+ aGapCallback,
+ fDotDashLength);
+ }
+
+ static void implHandleSnippet(
+ const B2DPolygon& rSnippet,
+ const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rTargetCallback,
+ B2DPolygon& rFirst,
+ B2DPolygon& rLast)
+ {
+ if(rSnippet.isClosed())
+ {
+ if(!rFirst.count())
+ {
+ rFirst = rSnippet;
+ }
+ else
+ {
+ if(rLast.count())
+ {
+ rTargetCallback(rLast);
+ }
+
+ rLast = rSnippet;
+ }
+ }
+ else
+ {
+ rTargetCallback(rSnippet);
+ }
+ }
+
+ static void implHandleFirstLast(
+ const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rTargetCallback,
+ B2DPolygon& rFirst,
+ B2DPolygon& rLast)
+ {
+ if(rFirst.count() && rLast.count()
+ && rFirst.getB2DPoint(0).equal(rLast.getB2DPoint(rLast.count() - 1)))
+ {
+ // start of first and end of last are the same -> merge them
+ rLast.append(rFirst);
+ rLast.removeDoublePoints();
+ rFirst.clear();
+ }
+
+ if(rLast.count())
+ {
+ rTargetCallback(rLast);
+ }
+
+ if(rFirst.count())
+ {
+ rTargetCallback(rFirst);
+ }
+ }
+
+ void applyLineDashing(
+ const B2DPolygon& rCandidate,
+ const std::vector<double>& rDotDashArray,
+ const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rLineTargetCallback,
+ const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rGapTargetCallback,
+ double fDotDashLength)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ const sal_uInt32 nDotDashCount(rDotDashArray.size());
+
+ if(fTools::lessOrEqual(fDotDashLength, 0.0))
+ {
+ fDotDashLength = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
+ }
+
+ if(fTools::lessOrEqual(fDotDashLength, 0.0) || (!rLineTargetCallback && !rGapTargetCallback) || !nPointCount)
+ {
+ // parameters make no sense, just add source to targets
+ if (rLineTargetCallback)
+ rLineTargetCallback(rCandidate);
+
+ if (rGapTargetCallback)
+ rGapTargetCallback(rCandidate);
+
+ return;
+ }
+
+ // precalculate maximal acceptable length of candidate polygon assuming
+ // we want to create a maximum of fNumberOfAllowedSnippets. For
+ // fNumberOfAllowedSnippets use ca. 65536, double due to line & gap.
+ static const double fNumberOfAllowedSnippets(100.0 * 2.0);
+ const double fAllowedLength((fNumberOfAllowedSnippets * fDotDashLength) / double(rDotDashArray.size()));
+ const double fCandidateLength(basegfx::utils::getLength(rCandidate, /*bApproximateBezierLength*/true));
+ std::vector<double> aDotDashArray(rDotDashArray);
+
+ if(fCandidateLength > fAllowedLength)
+ {
+ // we would produce more than fNumberOfAllowedSnippets, so
+ // adapt aDotDashArray to exactly produce assumed number. Also
+ // assert this to let the caller know about it.
+ // If this asserts: Please think about checking your DotDashArray
+ // before calling this function or evtl. use the callback version
+ // to *not* produce that much of data. Even then, you may still
+ // think about producing too much runtime (!)
+ assert(true && "applyLineDashing: potentially too expensive to do the requested dismantle - please consider stretched LineDash pattern (!)");
+
+ // calculate correcting factor, apply to aDotDashArray and fDotDashLength
+ // to enlarge these as needed
+ const double fFactor(fCandidateLength / fAllowedLength);
+ std::for_each(aDotDashArray.begin(), aDotDashArray.end(), [&fFactor](double &f){ f *= fFactor; });
+ }
+
+ // prepare current edge's start
+ B2DCubicBezier aCurrentEdge;
+ const bool bIsClosed(rCandidate.isClosed());
+ const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
+ aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0));
+
+ // prepare DotDashArray iteration and the line/gap switching bool
+ sal_uInt32 nDotDashIndex(0);
+ bool bIsLine(true);
+ double fDotDashMovingLength(aDotDashArray[0]);
+ B2DPolygon aSnippet;
+
+ // remember 1st and last snippets to try to merge after execution
+ // is complete and hand to callback
+ B2DPolygon aFirstLine, aLastLine;
+ B2DPolygon aFirstGap, aLastGap;
+
+ // iterate over all edges
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // update current edge (fill in C1, C2 and end point)
+ double fLastDotDashMovingLength(0.0);
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a));
+ aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+
+ // check if we have a trivial bezier segment -> possible fallback to edge
+ aCurrentEdge.testAndSolveTrivialBezier();
+
+ if(aCurrentEdge.isBezier())
+ {
+ // bezier segment
+ const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge);
+ const double fEdgeLength(aCubicBezierHelper.getLength());
+
+ if(!fTools::equalZero(fEdgeLength))
+ {
+ while(fTools::less(fDotDashMovingLength, fEdgeLength))
+ {
+ // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
+ const bool bHandleLine(bIsLine && rLineTargetCallback);
+ const bool bHandleGap(!bIsLine && rGapTargetCallback);
+
+ if(bHandleLine || bHandleGap)
+ {
+ const double fBezierSplitStart(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
+ const double fBezierSplitEnd(aCubicBezierHelper.distanceToRelative(fDotDashMovingLength));
+ B2DCubicBezier aBezierSnippet(aCurrentEdge.snippet(fBezierSplitStart, fBezierSplitEnd));
+
+ if(!aSnippet.count())
+ {
+ aSnippet.append(aBezierSnippet.getStartPoint());
+ }
+
+ aSnippet.appendBezierSegment(aBezierSnippet.getControlPointA(), aBezierSnippet.getControlPointB(), aBezierSnippet.getEndPoint());
+
+ if(bHandleLine)
+ {
+ implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
+ }
+
+ if(bHandleGap)
+ {
+ implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap);
+ }
+
+ aSnippet.clear();
+ }
+
+ // prepare next DotDashArray step and flip line/gap flag
+ fLastDotDashMovingLength = fDotDashMovingLength;
+ fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
+ bIsLine = !bIsLine;
+ }
+
+ // append closing snippet [fLastDotDashMovingLength, fEdgeLength]
+ const bool bHandleLine(bIsLine && rLineTargetCallback);
+ const bool bHandleGap(!bIsLine && rGapTargetCallback);
+
+ if(bHandleLine || bHandleGap)
+ {
+ B2DCubicBezier aRight;
+ const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
+
+ aCurrentEdge.split(fBezierSplit, nullptr, &aRight);
+
+ if(!aSnippet.count())
+ {
+ aSnippet.append(aRight.getStartPoint());
+ }
+
+ aSnippet.appendBezierSegment(aRight.getControlPointA(), aRight.getControlPointB(), aRight.getEndPoint());
+ }
+
+ // prepare move to next edge
+ fDotDashMovingLength -= fEdgeLength;
+ }
+ }
+ else
+ {
+ // simple edge
+ const double fEdgeLength(aCurrentEdge.getEdgeLength());
+
+ if(!fTools::equalZero(fEdgeLength))
+ {
+ while(fTools::less(fDotDashMovingLength, fEdgeLength))
+ {
+ // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
+ const bool bHandleLine(bIsLine && rLineTargetCallback);
+ const bool bHandleGap(!bIsLine && rGapTargetCallback);
+
+ if(bHandleLine || bHandleGap)
+ {
+ if(!aSnippet.count())
+ {
+ aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
+ }
+
+ aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength));
+
+ if(bHandleLine)
+ {
+ implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
+ }
+
+ if(bHandleGap)
+ {
+ implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap);
+ }
+
+ aSnippet.clear();
+ }
+
+ // prepare next DotDashArray step and flip line/gap flag
+ fLastDotDashMovingLength = fDotDashMovingLength;
+ fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
+ bIsLine = !bIsLine;
+ }
+
+ // append snippet [fLastDotDashMovingLength, fEdgeLength]
+ const bool bHandleLine(bIsLine && rLineTargetCallback);
+ const bool bHandleGap(!bIsLine && rGapTargetCallback);
+
+ if(bHandleLine || bHandleGap)
+ {
+ if(!aSnippet.count())
+ {
+ aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
+ }
+
+ aSnippet.append(aCurrentEdge.getEndPoint());
+ }
+
+ // prepare move to next edge
+ fDotDashMovingLength -= fEdgeLength;
+ }
+ }
+
+ // prepare next edge step (end point gets new start point)
+ aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint());
+ }
+
+ // append last intermediate results (if exists)
+ if(aSnippet.count())
+ {
+ const bool bHandleLine(bIsLine && rLineTargetCallback);
+ const bool bHandleGap(!bIsLine && rGapTargetCallback);
+
+ if(bHandleLine)
+ {
+ implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
+ }
+
+ if(bHandleGap)
+ {
+ implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap);
+ }
+ }
+
+ if(bIsClosed && rLineTargetCallback)
+ {
+ implHandleFirstLast(rLineTargetCallback, aFirstLine, aLastLine);
+ }
+
+ if(bIsClosed && rGapTargetCallback)
+ {
+ implHandleFirstLast(rGapTargetCallback, aFirstGap, aLastGap);
+ }
+ }
+
+ // test if point is inside epsilon-range around an edge defined
+ // by the two given points. Can be used for HitTesting. The epsilon-range
+ // is defined to be the rectangle centered to the given edge, using height
+ // 2 x fDistance, and the circle around both points with radius fDistance.
+ bool isInEpsilonRange(const B2DPoint& rEdgeStart, const B2DPoint& rEdgeEnd, const B2DPoint& rTestPosition, double fDistance)
+ {
+ // build edge vector
+ const B2DVector aEdge(rEdgeEnd - rEdgeStart);
+ bool bDoDistanceTestStart(false);
+ bool bDoDistanceTestEnd(false);
+
+ if(aEdge.equalZero())
+ {
+ // no edge, just a point. Do one of the distance tests.
+ bDoDistanceTestStart = true;
+ }
+ else
+ {
+ // edge has a length. Create perpendicular vector.
+ const B2DVector aPerpend(getPerpendicular(aEdge));
+ double fCut(
+ (aPerpend.getY() * (rTestPosition.getX() - rEdgeStart.getX())
+ + aPerpend.getX() * (rEdgeStart.getY() - rTestPosition.getY())) /
+ (aEdge.getX() * aEdge.getX() + aEdge.getY() * aEdge.getY()));
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ if(fTools::less(fCut, fZero))
+ {
+ // left of rEdgeStart
+ bDoDistanceTestStart = true;
+ }
+ else if(fTools::more(fCut, fOne))
+ {
+ // right of rEdgeEnd
+ bDoDistanceTestEnd = true;
+ }
+ else
+ {
+ // inside line [0.0 .. 1.0]
+ const B2DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut));
+ const B2DVector aDelta(rTestPosition - aCutPoint);
+ const double fDistanceSquare(aDelta.scalar(aDelta));
+
+ return fDistanceSquare <= fDistance * fDistance;
+ }
+ }
+
+ if(bDoDistanceTestStart)
+ {
+ const B2DVector aDelta(rTestPosition - rEdgeStart);
+ const double fDistanceSquare(aDelta.scalar(aDelta));
+
+ if(fDistanceSquare <= fDistance * fDistance)
+ {
+ return true;
+ }
+ }
+ else if(bDoDistanceTestEnd)
+ {
+ const B2DVector aDelta(rTestPosition - rEdgeEnd);
+ const double fDistanceSquare(aDelta.scalar(aDelta));
+
+ if(fDistanceSquare <= fDistance * fDistance)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // test if point is inside epsilon-range around the given Polygon. Can be used
+ // for HitTesting. The epsilon-range is defined to be the tube around the polygon
+ // with distance fDistance and rounded edges (start and end point).
+ bool isInEpsilonRange(const B2DPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance)
+ {
+ // force to non-bezier polygon
+ const B2DPolygon& aCandidate(rCandidate.getDefaultAdaptiveSubdivision());
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(!nPointCount)
+ return false;
+
+ const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DPoint aCurrent(aCandidate.getB2DPoint(0));
+
+ if(nEdgeCount)
+ {
+ // edges
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
+
+ if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance))
+ {
+ return true;
+ }
+
+ // prepare next step
+ aCurrent = aNext;
+ }
+ }
+ else
+ {
+ // no edges, but points -> not closed. Check single point. Just
+ // use isInEpsilonRange with twice the same point, it handles those well
+ if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Calculates distance of curve point to its control point for a Bézier curve, that
+ // approximates a unit circle arc. fAngle is the center angle of the circle arc. The
+ // constrain 0<=fAngle<=pi/2 must not be violated to give a useful accuracy. For details
+ // and alternatives read document "ApproxCircleInfo.odt", attachment of bug tdf#121425.
+ static double impDistanceBezierPointToControl(double fAngle)
+ {
+ SAL_WARN_IF(fAngle < 0 || fAngle > M_PI_2,"basegfx","angle not suitable for approximate circle");
+ if (0 <= fAngle && fAngle <= M_PI_2)
+ {
+ return 4.0/3.0 * ( tan(fAngle/4.0));
+ }
+ else
+ return 0;
+ }
+
+ B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadiusX, double fRadiusY )
+ {
+ const double fZero(0.0);
+ const double fOne(1.0);
+
+ fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
+ fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
+
+ if(rtl::math::approxEqual(fZero, fRadiusX) || rtl::math::approxEqual(fZero, fRadiusY))
+ {
+ // at least in one direction no radius, use rectangle.
+ // Do not use createPolygonFromRect() here since original
+ // creator (historical reasons) still creates a start point at the
+ // bottom center, so do the same here to get the same line patterns.
+ // Due to this the order of points is different, too.
+ const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
+ B2DPolygon aPolygon {
+ aBottomCenter,
+ { rRect.getMinX(), rRect.getMaxY() },
+ { rRect.getMinX(), rRect.getMinY() },
+ { rRect.getMaxX(), rRect.getMinY() },
+ { rRect.getMaxX(), rRect.getMaxY() }
+ };
+
+ // close
+ aPolygon.setClosed( true );
+
+ return aPolygon;
+ }
+ else if(rtl::math::approxEqual(fOne, fRadiusX) && rtl::math::approxEqual(fOne, fRadiusY))
+ {
+ // in both directions full radius, use ellipse
+ const B2DPoint aCenter(rRect.getCenter());
+ const double fRectRadiusX(rRect.getWidth() / 2.0);
+ const double fRectRadiusY(rRect.getHeight() / 2.0);
+
+ return createPolygonFromEllipse( aCenter, fRectRadiusX, fRectRadiusY );
+ }
+ else
+ {
+ B2DPolygon aRetval;
+ const double fBowX((rRect.getWidth() / 2.0) * fRadiusX);
+ const double fBowY((rRect.getHeight() / 2.0) * fRadiusY);
+ const double fKappa(impDistanceBezierPointToControl(M_PI_2));
+
+ // create start point at bottom center
+ if(!rtl::math::approxEqual(fOne, fRadiusX))
+ {
+ const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
+ aRetval.append(aBottomCenter);
+ }
+
+ // create first bow
+ {
+ const B2DPoint aBottomRight(rRect.getMaxX(), rRect.getMaxY());
+ const B2DPoint aStart(aBottomRight + B2DPoint(-fBowX, 0.0));
+ const B2DPoint aStop(aBottomRight + B2DPoint(0.0, -fBowY));
+ aRetval.append(aStart);
+ aRetval.appendBezierSegment(interpolate(aStart, aBottomRight, fKappa), interpolate(aStop, aBottomRight, fKappa), aStop);
+ }
+
+ // create second bow
+ {
+ const B2DPoint aTopRight(rRect.getMaxX(), rRect.getMinY());
+ const B2DPoint aStart(aTopRight + B2DPoint(0.0, fBowY));
+ const B2DPoint aStop(aTopRight + B2DPoint(-fBowX, 0.0));
+ aRetval.append(aStart);
+ aRetval.appendBezierSegment(interpolate(aStart, aTopRight, fKappa), interpolate(aStop, aTopRight, fKappa), aStop);
+ }
+
+ // create third bow
+ {
+ const B2DPoint aTopLeft(rRect.getMinX(), rRect.getMinY());
+ const B2DPoint aStart(aTopLeft + B2DPoint(fBowX, 0.0));
+ const B2DPoint aStop(aTopLeft + B2DPoint(0.0, fBowY));
+ aRetval.append(aStart);
+ aRetval.appendBezierSegment(interpolate(aStart, aTopLeft, fKappa), interpolate(aStop, aTopLeft, fKappa), aStop);
+ }
+
+ // create forth bow
+ {
+ const B2DPoint aBottomLeft(rRect.getMinX(), rRect.getMaxY());
+ const B2DPoint aStart(aBottomLeft + B2DPoint(0.0, -fBowY));
+ const B2DPoint aStop(aBottomLeft + B2DPoint(fBowX, 0.0));
+ aRetval.append(aStart);
+ aRetval.appendBezierSegment(interpolate(aStart, aBottomLeft, fKappa), interpolate(aStop, aBottomLeft, fKappa), aStop);
+ }
+
+ // close
+ aRetval.setClosed( true );
+
+ // remove double created points if there are extreme radii involved
+ if(rtl::math::approxEqual(fOne, fRadiusX) || rtl::math::approxEqual(fOne, fRadiusY))
+ {
+ aRetval.removeDoublePoints();
+ }
+
+ return aRetval;
+ }
+ }
+
+ B2DPolygon createPolygonFromRect( const B2DRectangle& rRect )
+ {
+ B2DPolygon aPolygon {
+ { rRect.getMinX(), rRect.getMinY() },
+ { rRect.getMaxX(), rRect.getMinY() },
+ { rRect.getMaxX(), rRect.getMaxY() },
+ { rRect.getMinX(), rRect.getMaxY() }
+ };
+
+ // close
+ aPolygon.setClosed( true );
+
+ return aPolygon;
+ }
+
+ B2DPolygon const & createUnitPolygon()
+ {
+ static auto const singleton = [] {
+ B2DPolygon aPolygon {
+ { 0.0, 0.0 },
+ { 1.0, 0.0 },
+ { 1.0, 1.0 },
+ { 0.0, 1.0 }
+ };
+
+ // close
+ aPolygon.setClosed( true );
+
+ return aPolygon;
+ }();
+ return singleton;
+ }
+
+ B2DPolygon createPolygonFromCircle( const B2DPoint& rCenter, double fRadius )
+ {
+ return createPolygonFromEllipse( rCenter, fRadius, fRadius );
+ }
+
+ static B2DPolygon impCreateUnitCircle(sal_uInt32 nStartQuadrant)
+ {
+ B2DPolygon aUnitCircle;
+ const double fSegmentKappa = impDistanceBezierPointToControl(M_PI_2 / STEPSPERQUARTER);
+ const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(M_PI_2 / STEPSPERQUARTER));
+
+ B2DPoint aPoint(1.0, 0.0);
+ B2DPoint aForward(1.0, fSegmentKappa);
+ B2DPoint aBackward(1.0, -fSegmentKappa);
+
+ if(nStartQuadrant != 0)
+ {
+ const B2DHomMatrix aQuadrantMatrix(createRotateB2DHomMatrix(M_PI_2 * (nStartQuadrant % 4)));
+ aPoint *= aQuadrantMatrix;
+ aBackward *= aQuadrantMatrix;
+ aForward *= aQuadrantMatrix;
+ }
+
+ aUnitCircle.append(aPoint);
+
+ for(sal_uInt32 a(0); a < STEPSPERQUARTER * 4; a++)
+ {
+ aPoint *= aRotateMatrix;
+ aBackward *= aRotateMatrix;
+ aUnitCircle.appendBezierSegment(aForward, aBackward, aPoint);
+ aForward *= aRotateMatrix;
+ }
+
+ aUnitCircle.setClosed(true);
+ aUnitCircle.removeDoublePoints();
+
+ return aUnitCircle;
+ }
+
+ B2DPolygon const & createHalfUnitCircle()
+ {
+ static auto const singleton = [] {
+ B2DPolygon aUnitHalfCircle;
+ const double fSegmentKappa(impDistanceBezierPointToControl(M_PI_2 / STEPSPERQUARTER));
+ const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(M_PI_2 / STEPSPERQUARTER));
+ B2DPoint aPoint(1.0, 0.0);
+ B2DPoint aForward(1.0, fSegmentKappa);
+ B2DPoint aBackward(1.0, -fSegmentKappa);
+
+ aUnitHalfCircle.append(aPoint);
+
+ for(sal_uInt32 a(0); a < STEPSPERQUARTER * 2; a++)
+ {
+ aPoint *= aRotateMatrix;
+ aBackward *= aRotateMatrix;
+ aUnitHalfCircle.appendBezierSegment(aForward, aBackward, aPoint);
+ aForward *= aRotateMatrix;
+ }
+ return aUnitHalfCircle;
+ }();
+ return singleton;
+ }
+
+ B2DPolygon const & createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant)
+ {
+ switch(nStartQuadrant % 4)
+ {
+ case 1 :
+ {
+ static auto const singleton = impCreateUnitCircle(1);
+ return singleton;
+ }
+
+ case 2 :
+ {
+ static auto const singleton = impCreateUnitCircle(2);
+ return singleton;
+ }
+
+ case 3 :
+ {
+ static auto const singleton = impCreateUnitCircle(3);
+ return singleton;
+ }
+
+ default : // case 0 :
+ {
+ static auto const singleton = impCreateUnitCircle(0);
+ return singleton;
+ }
+ }
+ }
+
+ B2DPolygon createPolygonFromEllipse( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, sal_uInt32 nStartQuadrant)
+ {
+ B2DPolygon aRetval(createPolygonFromUnitCircle(nStartQuadrant));
+ const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
+
+ aRetval.transform(aMatrix);
+
+ return aRetval;
+ }
+
+ B2DPolygon createPolygonFromUnitEllipseSegment( double fStart, double fEnd )
+ {
+ B2DPolygon aRetval;
+
+ // truncate fStart, fEnd to a range of [0.0 .. 2PI[ where 2PI
+ // falls back to 0.0 to ensure a unique definition
+ if(fTools::less(fStart, 0.0))
+ {
+ fStart = 0.0;
+ }
+
+ if(fTools::moreOrEqual(fStart, 2 * M_PI))
+ {
+ fStart = 0.0;
+ }
+
+ if(fTools::less(fEnd, 0.0))
+ {
+ fEnd = 0.0;
+ }
+
+ if(fTools::moreOrEqual(fEnd, 2 * M_PI))
+ {
+ fEnd = 0.0;
+ }
+
+ if(fTools::equal(fStart, fEnd))
+ {
+ // same start and end angle, add single point
+ aRetval.append(B2DPoint(cos(fStart), sin(fStart)));
+ }
+ else
+ {
+ const sal_uInt32 nSegments(STEPSPERQUARTER * 4);
+ const double fAnglePerSegment(M_PI_2 / STEPSPERQUARTER);
+ const sal_uInt32 nStartSegment(sal_uInt32(fStart / fAnglePerSegment) % nSegments);
+ const sal_uInt32 nEndSegment(sal_uInt32(fEnd / fAnglePerSegment) % nSegments);
+ const double fSegmentKappa(impDistanceBezierPointToControl(fAnglePerSegment));
+
+ B2DPoint aSegStart(cos(fStart), sin(fStart));
+ aRetval.append(aSegStart);
+
+ if(nStartSegment == nEndSegment && fTools::more(fEnd, fStart))
+ {
+ // start and end in one sector and in the right order, create in one segment
+ const B2DPoint aSegEnd(cos(fEnd), sin(fEnd));
+ const double fFactor(impDistanceBezierPointToControl(fEnd - fStart));
+
+ aRetval.appendBezierSegment(
+ aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
+ aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
+ aSegEnd);
+ }
+ else
+ {
+ double fSegEndRad((nStartSegment + 1) * fAnglePerSegment);
+ double fFactor(impDistanceBezierPointToControl(fSegEndRad - fStart));
+ B2DPoint aSegEnd(cos(fSegEndRad), sin(fSegEndRad));
+
+ aRetval.appendBezierSegment(
+ aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
+ aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
+ aSegEnd);
+
+ sal_uInt32 nSegment((nStartSegment + 1) % nSegments);
+ aSegStart = aSegEnd;
+
+ while(nSegment != nEndSegment)
+ {
+ // No end in this sector, add full sector.
+ fSegEndRad = (nSegment + 1) * fAnglePerSegment;
+ aSegEnd = B2DPoint(cos(fSegEndRad), sin(fSegEndRad));
+
+ aRetval.appendBezierSegment(
+ aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fSegmentKappa),
+ aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fSegmentKappa),
+ aSegEnd);
+
+ nSegment = (nSegment + 1) % nSegments;
+ aSegStart = aSegEnd;
+ }
+
+ // End in this sector
+ const double fSegStartRad(nSegment * fAnglePerSegment);
+ fFactor= impDistanceBezierPointToControl(fEnd - fSegStartRad);
+ aSegEnd = B2DPoint(cos(fEnd), sin(fEnd));
+
+ aRetval.appendBezierSegment(
+ aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
+ aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
+ aSegEnd);
+ }
+ }
+
+ // remove double points between segments created by segmented creation
+ aRetval.removeDoublePoints();
+
+ return aRetval;
+ }
+
+ B2DPolygon createPolygonFromEllipseSegment( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd )
+ {
+ B2DPolygon aRetval(createPolygonFromUnitEllipseSegment(fStart, fEnd));
+ const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
+
+ aRetval.transform(aMatrix);
+
+ return aRetval;
+ }
+
+ bool hasNeutralPoints(const B2DPolygon& rCandidate)
+ {
+ OSL_ENSURE(!rCandidate.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)");
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount <= 2)
+ return false;
+
+ B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
+ B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
+ const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
+ const B2DVector aNextVec(aNextPoint - aCurrPoint);
+ const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
+
+ if(aOrientation == B2VectorOrientation::Neutral)
+ {
+ // current has neutral orientation
+ return true;
+ }
+ else
+ {
+ // prepare next
+ aPrevPoint = aCurrPoint;
+ aCurrPoint = aNextPoint;
+ }
+ }
+
+ return false;
+ }
+
+ B2DPolygon removeNeutralPoints(const B2DPolygon& rCandidate)
+ {
+ if(hasNeutralPoints(rCandidate))
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ B2DPolygon aRetval;
+ B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
+ B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
+ const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
+ const B2DVector aNextVec(aNextPoint - aCurrPoint);
+ const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
+
+ if(aOrientation == B2VectorOrientation::Neutral)
+ {
+ // current has neutral orientation, leave it out and prepare next
+ aCurrPoint = aNextPoint;
+ }
+ else
+ {
+ // add current point
+ aRetval.append(aCurrPoint);
+
+ // prepare next
+ aPrevPoint = aCurrPoint;
+ aCurrPoint = aNextPoint;
+ }
+ }
+
+ while(aRetval.count() && getOrientationForIndex(aRetval, 0) == B2VectorOrientation::Neutral)
+ {
+ aRetval.remove(0);
+ }
+
+ // copy closed state
+ aRetval.setClosed(rCandidate.isClosed());
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ bool isConvex(const B2DPolygon& rCandidate)
+ {
+ OSL_ENSURE(!rCandidate.areControlPointsUsed(), "isConvex: ATM works not for curves (!)");
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount <= 2)
+ return true;
+
+ const B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
+ B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
+ B2DVector aCurrVec(aPrevPoint - aCurrPoint);
+ B2VectorOrientation aOrientation(B2VectorOrientation::Neutral);
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
+ const B2DVector aNextVec(aNextPoint - aCurrPoint);
+ const B2VectorOrientation aCurrentOrientation(getOrientation(aNextVec, aCurrVec));
+
+ if(aOrientation == B2VectorOrientation::Neutral)
+ {
+ // set start value, maybe neutral again
+ aOrientation = aCurrentOrientation;
+ }
+ else
+ {
+ if(aCurrentOrientation != B2VectorOrientation::Neutral && aCurrentOrientation != aOrientation)
+ {
+ // different orientations found, that's it
+ return false;
+ }
+ }
+
+ // prepare next
+ aCurrPoint = aNextPoint;
+ aCurrVec = -aNextVec;
+ }
+
+ return true;
+ }
+
+ B2VectorOrientation getOrientationForIndex(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "getOrientationForIndex: index out of range (!)");
+ const B2DPoint aPrev(rCandidate.getB2DPoint(getIndexOfPredecessor(nIndex, rCandidate)));
+ const B2DPoint aCurr(rCandidate.getB2DPoint(nIndex));
+ const B2DPoint aNext(rCandidate.getB2DPoint(getIndexOfSuccessor(nIndex, rCandidate)));
+ const B2DVector aBack(aPrev - aCurr);
+ const B2DVector aForw(aNext - aCurr);
+
+ return getOrientation(aForw, aBack);
+ }
+
+ bool isPointOnLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidate, bool bWithPoints)
+ {
+ if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
+ {
+ // candidate is in epsilon around start or end -> inside
+ return bWithPoints;
+ }
+ else if(rStart.equal(rEnd))
+ {
+ // start and end are equal, but candidate is outside their epsilon -> outside
+ return false;
+ }
+ else
+ {
+ const B2DVector aEdgeVector(rEnd - rStart);
+ const B2DVector aTestVector(rCandidate - rStart);
+
+ if(areParallel(aEdgeVector, aTestVector))
+ {
+ const double fZero(0.0);
+ const double fOne(1.0);
+ const double fParamTestOnCurr(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY())
+ ? aTestVector.getX() / aEdgeVector.getX()
+ : aTestVector.getY() / aEdgeVector.getY());
+
+ if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ bool isPointOnPolygon(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithPoints)
+ {
+ const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(nPointCount > 1)
+ {
+ const sal_uInt32 nLoopCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DPoint aCurrentPoint(aCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nLoopCount; a++)
+ {
+ const B2DPoint aNextPoint(aCandidate.getB2DPoint((a + 1) % nPointCount));
+
+ if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints))
+ {
+ return true;
+ }
+
+ aCurrentPoint = aNextPoint;
+ }
+ }
+ else if(nPointCount && bWithPoints)
+ {
+ return rPoint.equal(aCandidate.getB2DPoint(0));
+ }
+
+ return false;
+ }
+
+ bool isPointInTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC, const B2DPoint& rCandidate, bool bWithBorder)
+ {
+ if(arePointsOnSameSideOfLine(rA, rB, rC, rCandidate, bWithBorder))
+ {
+ if(arePointsOnSameSideOfLine(rB, rC, rA, rCandidate, bWithBorder))
+ {
+ if(arePointsOnSameSideOfLine(rC, rA, rB, rCandidate, bWithBorder))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ bool arePointsOnSameSideOfLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidateA, const B2DPoint& rCandidateB, bool bWithLine)
+ {
+ const B2DVector aLineVector(rEnd - rStart);
+ const B2DVector aVectorToA(rEnd - rCandidateA);
+ const double fCrossA(aLineVector.cross(aVectorToA));
+
+ // tdf#88352 increase numerical correctness and use rtl::math::approxEqual
+ // instead of fTools::equalZero which compares with a fixed small value
+ if(fCrossA == 0.0)
+ {
+ // one point on the line
+ return bWithLine;
+ }
+
+ const B2DVector aVectorToB(rEnd - rCandidateB);
+ const double fCrossB(aLineVector.cross(aVectorToB));
+
+ // increase numerical correctness
+ if(fCrossB == 0.0)
+ {
+ // one point on the line
+ return bWithLine;
+ }
+
+ // return true if they both have the same sign
+ return ((fCrossA > 0.0) == (fCrossB > 0.0));
+ }
+
+ void addTriangleFan(
+ const B2DPolygon& rCandidate,
+ triangulator::B2DTriangleVector& rTarget)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nCount <= 2)
+ return;
+
+ const B2DPoint aStart(rCandidate.getB2DPoint(0));
+ B2DPoint aLast(rCandidate.getB2DPoint(1));
+
+ for(sal_uInt32 a(2); a < nCount; a++)
+ {
+ const B2DPoint aCurrent(rCandidate.getB2DPoint(a));
+ rTarget.emplace_back(
+ aStart,
+ aLast,
+ aCurrent);
+
+ // prepare next
+ aLast = aCurrent;
+ }
+ }
+
+ namespace
+ {
+ /// return 0 for input of 0, -1 for negative and 1 for positive input
+ int lcl_sgn( const double n )
+ {
+ return n == 0.0 ? 0 : 1 - 2*int(std::signbit(n));
+ }
+ }
+
+ bool isRectangle( const B2DPolygon& rPoly )
+ {
+ // polygon must be closed to resemble a rect, and contain
+ // at least four points.
+ if( !rPoly.isClosed() ||
+ rPoly.count() < 4 ||
+ rPoly.areControlPointsUsed() )
+ {
+ return false;
+ }
+
+ // number of 90 degree turns the polygon has taken
+ int nNumTurns(0);
+
+ int nVerticalEdgeType=0;
+ int nHorizontalEdgeType=0;
+ bool bNullVertex(true);
+ bool bCWPolygon(false); // when true, polygon is CW
+ // oriented, when false, CCW
+ bool bOrientationSet(false); // when false, polygon
+ // orientation has not yet
+ // been determined.
+
+ // scan all _edges_ (which involves coming back to point 0
+ // for the last edge - thus the modulo operation below)
+ const sal_Int32 nCount( rPoly.count() );
+ for( sal_Int32 i=0; i<nCount; ++i )
+ {
+ const B2DPoint& rPoint0( rPoly.getB2DPoint(i % nCount) );
+ const B2DPoint& rPoint1( rPoly.getB2DPoint((i+1) % nCount) );
+
+ // is 0 for zero direction vector, 1 for south edge and -1
+ // for north edge (standard screen coordinate system)
+ int nCurrVerticalEdgeType( lcl_sgn( rPoint1.getY() - rPoint0.getY() ) );
+
+ // is 0 for zero direction vector, 1 for east edge and -1
+ // for west edge (standard screen coordinate system)
+ int nCurrHorizontalEdgeType( lcl_sgn(rPoint1.getX() - rPoint0.getX()) );
+
+ if( nCurrVerticalEdgeType && nCurrHorizontalEdgeType )
+ return false; // oblique edge - for sure no rect
+
+ const bool bCurrNullVertex( !nCurrVerticalEdgeType && !nCurrHorizontalEdgeType );
+
+ // current vertex is equal to previous - just skip,
+ // until we have a real edge
+ if( bCurrNullVertex )
+ continue;
+
+ // if previous edge has two identical points, because
+ // no previous edge direction was available, simply
+ // take this first non-null edge as the start
+ // direction. That's what will happen here, if
+ // bNullVertex is false
+ if( !bNullVertex )
+ {
+ // 2D cross product - is 1 for CW and -1 for CCW turns
+ const int nCrossProduct( nHorizontalEdgeType*nCurrVerticalEdgeType -
+ nVerticalEdgeType*nCurrHorizontalEdgeType );
+
+ if( !nCrossProduct )
+ continue; // no change in orientation -
+ // collinear edges - just go on
+
+ // if polygon orientation is not set, we'll
+ // determine it now
+ if( !bOrientationSet )
+ {
+ bCWPolygon = nCrossProduct == 1;
+ bOrientationSet = true;
+ }
+ else
+ {
+ // if current turn orientation is not equal
+ // initial orientation, this is not a
+ // rectangle (as rectangles have consistent
+ // orientation).
+ if( (nCrossProduct == 1) != bCWPolygon )
+ return false;
+ }
+
+ ++nNumTurns;
+
+ // More than four 90 degree turns are an
+ // indication that this must not be a rectangle.
+ if( nNumTurns > 4 )
+ return false;
+ }
+
+ // store current state for the next turn
+ nVerticalEdgeType = nCurrVerticalEdgeType;
+ nHorizontalEdgeType = nCurrHorizontalEdgeType;
+ bNullVertex = false; // won't reach this line,
+ // if bCurrNullVertex is
+ // true - see above
+ }
+
+ return true;
+ }
+
+ B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon& rCandidate, double fZCoordinate)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ // call myself recursively with subdivided input
+ const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
+ return createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate);
+ }
+ else
+ {
+ B3DPolygon aRetval;
+
+ for(sal_uInt32 a(0); a < rCandidate.count(); a++)
+ {
+ B2DPoint aPoint(rCandidate.getB2DPoint(a));
+ aRetval.append(B3DPoint(aPoint.getX(), aPoint.getY(), fZCoordinate));
+ }
+
+ // copy closed state
+ aRetval.setClosed(rCandidate.isClosed());
+
+ return aRetval;
+ }
+ }
+
+ B2DPolygon createB2DPolygonFromB3DPolygon(const B3DPolygon& rCandidate, const B3DHomMatrix& rMat)
+ {
+ B2DPolygon aRetval;
+ const sal_uInt32 nCount(rCandidate.count());
+ const bool bIsIdentity(rMat.isIdentity());
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ B3DPoint aCandidate(rCandidate.getB3DPoint(a));
+
+ if(!bIsIdentity)
+ {
+ aCandidate *= rMat;
+ }
+
+ aRetval.append(B2DPoint(aCandidate.getX(), aCandidate.getY()));
+ }
+
+ // copy closed state
+ aRetval.setClosed(rCandidate.isClosed());
+
+ return aRetval;
+ }
+
+ double getSmallestDistancePointToEdge(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut)
+ {
+ if(rPointA.equal(rPointB))
+ {
+ rCut = 0.0;
+ const B2DVector aVector(rTestPoint - rPointA);
+ return aVector.getLength();
+ }
+ else
+ {
+ // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint
+ const B2DVector aVector1(rPointB - rPointA);
+ const B2DVector aVector2(rTestPoint - rPointA);
+ const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY()));
+ const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY()));
+ const double fCut(fDividend / fDivisor);
+
+ if(fCut < 0.0)
+ {
+ // not in line range, get distance to PointA
+ rCut = 0.0;
+ return aVector2.getLength();
+ }
+ else if(fCut > 1.0)
+ {
+ // not in line range, get distance to PointB
+ rCut = 1.0;
+ const B2DVector aVector(rTestPoint - rPointB);
+ return aVector.getLength();
+ }
+ else
+ {
+ // in line range
+ const B2DPoint aCutPoint(rPointA + fCut * aVector1);
+ const B2DVector aVector(rTestPoint - aCutPoint);
+ rCut = fCut;
+ return aVector.getLength();
+ }
+ }
+ }
+
+ double getSmallestDistancePointToPolygon(const B2DPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rEdgeIndex, double& rCut)
+ {
+ double fRetval(DBL_MAX);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1)
+ {
+ const double fZero(0.0);
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+ double fEdgeDist;
+ double fNewCut(0.0);
+ bool bEdgeIsCurve(false);
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
+ aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aBezier.testAndSolveTrivialBezier();
+ bEdgeIsCurve = aBezier.isBezier();
+ }
+
+ if(bEdgeIsCurve)
+ {
+ fEdgeDist = aBezier.getSmallestDistancePointToBezierSegment(rTestPoint, fNewCut);
+ }
+ else
+ {
+ fEdgeDist = getSmallestDistancePointToEdge(aBezier.getStartPoint(), aBezier.getEndPoint(), rTestPoint, fNewCut);
+ }
+
+ if(fRetval == DBL_MAX || fEdgeDist < fRetval)
+ {
+ fRetval = fEdgeDist;
+ rEdgeIndex = a;
+ rCut = fNewCut;
+
+ if(fTools::equal(fRetval, fZero))
+ {
+ // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
+ fRetval = 0.0;
+ break;
+ }
+ }
+
+ // prepare next step
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(rtl::math::approxEqual(1.0, rCut))
+ {
+ // correct rEdgeIndex when not last point
+ if(rCandidate.isClosed())
+ {
+ rEdgeIndex = getIndexOfSuccessor(rEdgeIndex, rCandidate);
+ rCut = 0.0;
+ }
+ else
+ {
+ if(rEdgeIndex != nEdgeCount - 1)
+ {
+ rEdgeIndex++;
+ rCut = 0.0;
+ }
+ }
+ }
+ }
+
+ return fRetval;
+ }
+
+ B2DPoint distort(const B2DPoint& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
+ {
+ if(fTools::equalZero(rOriginal.getWidth()) || fTools::equalZero(rOriginal.getHeight()))
+ {
+ return rCandidate;
+ }
+ else
+ {
+ const double fRelativeX((rCandidate.getX() - rOriginal.getMinX()) / rOriginal.getWidth());
+ const double fRelativeY((rCandidate.getY() - rOriginal.getMinY()) / rOriginal.getHeight());
+ const double fOneMinusRelativeX(1.0 - fRelativeX);
+ const double fOneMinusRelativeY(1.0 - fRelativeY);
+ const double fNewX(fOneMinusRelativeY * (fOneMinusRelativeX * rTopLeft.getX() + fRelativeX * rTopRight.getX()) +
+ fRelativeY * (fOneMinusRelativeX * rBottomLeft.getX() + fRelativeX * rBottomRight.getX()));
+ const double fNewY(fOneMinusRelativeX * (fOneMinusRelativeY * rTopLeft.getY() + fRelativeY * rBottomLeft.getY()) +
+ fRelativeX * (fOneMinusRelativeY * rTopRight.getY() + fRelativeY * rBottomRight.getY()));
+
+ return B2DPoint(fNewX, fNewY);
+ }
+ }
+
+ B2DPolygon distort(const B2DPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount && rOriginal.getWidth() != 0.0 && rOriginal.getHeight() != 0.0)
+ {
+ B2DPolygon aRetval;
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ aRetval.append(distort(rCandidate.getB2DPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ if(!rCandidate.getPrevControlPoint(a).equalZero())
+ {
+ aRetval.setPrevControlPoint(a, distort(rCandidate.getPrevControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
+ }
+
+ if(!rCandidate.getNextControlPoint(a).equalZero())
+ {
+ aRetval.setNextControlPoint(a, distort(rCandidate.getNextControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
+ }
+ }
+ }
+
+ aRetval.setClosed(rCandidate.isClosed());
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolygon expandToCurve(const B2DPolygon& rCandidate)
+ {
+ B2DPolygon aRetval(rCandidate);
+
+ for(sal_uInt32 a(0); a < rCandidate.count(); a++)
+ {
+ expandToCurveInPoint(aRetval, a);
+ }
+
+ return aRetval;
+ }
+
+ bool expandToCurveInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "expandToCurveInPoint: Access to polygon out of range (!)");
+ bool bRetval(false);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ // predecessor
+ if(!rCandidate.isPrevControlPointUsed(nIndex))
+ {
+ if(!rCandidate.isClosed() && nIndex == 0)
+ {
+ // do not create previous vector for start point of open polygon
+ }
+ else
+ {
+ const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
+ rCandidate.setPrevControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
+ bRetval = true;
+ }
+ }
+
+ // successor
+ if(!rCandidate.isNextControlPointUsed(nIndex))
+ {
+ if(!rCandidate.isClosed() && nIndex + 1 == nPointCount)
+ {
+ // do not create next vector for end point of open polygon
+ }
+ else
+ {
+ const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+ rCandidate.setNextControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
+ bRetval = true;
+ }
+ }
+ }
+
+ return bRetval;
+ }
+
+ bool setContinuityInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "setContinuityInPoint: Access to polygon out of range (!)");
+ bool bRetval(false);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(nIndex));
+
+ switch(eContinuity)
+ {
+ case B2VectorContinuity::NONE :
+ {
+ if(rCandidate.isPrevControlPointUsed(nIndex))
+ {
+ if(!rCandidate.isClosed() && nIndex == 0)
+ {
+ // remove existing previous vector for start point of open polygon
+ rCandidate.resetPrevControlPoint(nIndex);
+ }
+ else
+ {
+ const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
+ rCandidate.setPrevControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
+ }
+
+ bRetval = true;
+ }
+
+ if(rCandidate.isNextControlPointUsed(nIndex))
+ {
+ if(!rCandidate.isClosed() && nIndex == nPointCount + 1)
+ {
+ // remove next vector for end point of open polygon
+ rCandidate.resetNextControlPoint(nIndex);
+ }
+ else
+ {
+ const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+ rCandidate.setNextControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
+ }
+
+ bRetval = true;
+ }
+
+ break;
+ }
+ case B2VectorContinuity::C1 :
+ {
+ if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
+ {
+ // lengths both exist since both are used
+ B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
+ B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
+ const double fLenPrev(aVectorPrev.getLength());
+ const double fLenNext(aVectorNext.getLength());
+ aVectorPrev.normalize();
+ aVectorNext.normalize();
+ const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
+
+ if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
+ {
+ // parallel and opposite direction; check length
+ if(fTools::equal(fLenPrev, fLenNext))
+ {
+ // this would be even C2, but we want C1. Use the lengths of the corresponding edges.
+ const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
+ const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+ const double fLenPrevEdge(B2DVector(rCandidate.getB2DPoint(nPrevIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
+ const double fLenNextEdge(B2DVector(rCandidate.getB2DPoint(nNextIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
+
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint + (aVectorPrev * fLenPrevEdge),
+ aCurrentPoint + (aVectorNext * fLenNextEdge));
+ bRetval = true;
+ }
+ }
+ else
+ {
+ // not parallel or same direction, set vectors and length
+ const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
+
+ if(aOrientation == B2VectorOrientation::Positive)
+ {
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint - (aNormalizedPerpendicular * fLenPrev),
+ aCurrentPoint + (aNormalizedPerpendicular * fLenNext));
+ }
+ else
+ {
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint + (aNormalizedPerpendicular * fLenPrev),
+ aCurrentPoint - (aNormalizedPerpendicular * fLenNext));
+ }
+
+ bRetval = true;
+ }
+ }
+ break;
+ }
+ case B2VectorContinuity::C2 :
+ {
+ if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
+ {
+ // lengths both exist since both are used
+ B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
+ B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
+ const double fCommonLength((aVectorPrev.getLength() + aVectorNext.getLength()) / 2.0);
+ aVectorPrev.normalize();
+ aVectorNext.normalize();
+ const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
+
+ if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
+ {
+ // parallel and opposite direction; set length. Use one direction for better numerical correctness
+ const B2DVector aScaledDirection(aVectorPrev * fCommonLength);
+
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint + aScaledDirection,
+ aCurrentPoint - aScaledDirection);
+ }
+ else
+ {
+ // not parallel or same direction, set vectors and length
+ const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
+ const B2DVector aPerpendicular(aNormalizedPerpendicular * fCommonLength);
+
+ if(aOrientation == B2VectorOrientation::Positive)
+ {
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint - aPerpendicular,
+ aCurrentPoint + aPerpendicular);
+ }
+ else
+ {
+ rCandidate.setControlPoints(nIndex,
+ aCurrentPoint + aPerpendicular,
+ aCurrentPoint - aPerpendicular);
+ }
+ }
+
+ bRetval = true;
+ }
+ break;
+ }
+ }
+ }
+
+ return bRetval;
+ }
+
+ B2DPolygon growInNormalDirection(const B2DPolygon& rCandidate, double fValue)
+ {
+ if(fValue != 0.0)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ // call myself recursively with subdivided input
+ const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
+ return growInNormalDirection(aCandidate, fValue);
+ }
+ else
+ {
+ B2DPolygon aRetval;
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ B2DPoint aPrev(rCandidate.getB2DPoint(nPointCount - 1));
+ B2DPoint aCurrent(rCandidate.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B2DPoint aNext(rCandidate.getB2DPoint(a + 1 == nPointCount ? 0 : a + 1));
+ const B2DVector aBack(aPrev - aCurrent);
+ const B2DVector aForw(aNext - aCurrent);
+ const B2DVector aPerpBack(getNormalizedPerpendicular(aBack));
+ const B2DVector aPerpForw(getNormalizedPerpendicular(aForw));
+ B2DVector aDirection(aPerpBack - aPerpForw);
+ aDirection.normalize();
+ aDirection *= fValue;
+ aRetval.append(aCurrent + aDirection);
+
+ // prepare next step
+ aPrev = aCurrent;
+ aCurrent = aNext;
+ }
+ }
+
+ // copy closed state
+ aRetval.setClosed(rCandidate.isClosed());
+
+ return aRetval;
+ }
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolygon reSegmentPolygon(const B2DPolygon& rCandidate, sal_uInt32 nSegments)
+ {
+ B2DPolygon aRetval;
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount && nSegments)
+ {
+ // get current segment count
+ const sal_uInt32 nSegmentCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+
+ if(nSegmentCount == nSegments)
+ {
+ aRetval = rCandidate;
+ }
+ else
+ {
+ const double fLength(getLength(rCandidate));
+ const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nSegments : nSegments + 1);
+
+ for(sal_uInt32 a(0); a < nLoopCount; a++)
+ {
+ const double fRelativePos(static_cast<double>(a) / static_cast<double>(nSegments)); // 0.0 .. 1.0
+ const B2DPoint aNewPoint(getPositionRelative(rCandidate, fRelativePos, fLength));
+ aRetval.append(aNewPoint);
+ }
+
+ // copy closed flag
+ aRetval.setClosed(rCandidate.isClosed());
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolygon interpolate(const B2DPolygon& rOld1, const B2DPolygon& rOld2, double t)
+ {
+ OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolygon interpolate: Different geometry (!)");
+
+ if(fTools::lessOrEqual(t, 0.0) || rOld1 == rOld2)
+ {
+ return rOld1;
+ }
+ else if(fTools::moreOrEqual(t, 1.0))
+ {
+ return rOld2;
+ }
+ else
+ {
+ B2DPolygon aRetval;
+ const bool bInterpolateVectors(rOld1.areControlPointsUsed() || rOld2.areControlPointsUsed());
+ aRetval.setClosed(rOld1.isClosed() && rOld2.isClosed());
+
+ for(sal_uInt32 a(0); a < rOld1.count(); a++)
+ {
+ aRetval.append(interpolate(rOld1.getB2DPoint(a), rOld2.getB2DPoint(a), t));
+
+ if(bInterpolateVectors)
+ {
+ aRetval.setPrevControlPoint(a, interpolate(rOld1.getPrevControlPoint(a), rOld2.getPrevControlPoint(a), t));
+ aRetval.setNextControlPoint(a, interpolate(rOld1.getNextControlPoint(a), rOld2.getNextControlPoint(a), t));
+ }
+ }
+
+ return aRetval;
+ }
+ }
+
+ // #i76891#
+ B2DPolygon simplifyCurveSegments(const B2DPolygon& rCandidate)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount && rCandidate.areControlPointsUsed())
+ {
+ // prepare loop
+ const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DPolygon aRetval;
+ B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rCandidate.getB2DPoint(0));
+
+ // try to avoid costly reallocations
+ aRetval.reserve( nEdgeCount+1);
+
+ // add start point
+ aRetval.append(aBezier.getStartPoint());
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // get values for edge
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
+ aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
+ aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
+ aBezier.testAndSolveTrivialBezier();
+
+ // still bezier?
+ if(aBezier.isBezier())
+ {
+ // add edge with control vectors
+ aRetval.appendBezierSegment(aBezier.getControlPointA(), aBezier.getControlPointB(), aBezier.getEndPoint());
+ }
+ else
+ {
+ // add edge
+ aRetval.append(aBezier.getEndPoint());
+ }
+
+ // next point
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(rCandidate.isClosed())
+ {
+ // set closed flag, rescue control point and correct last double point
+ closeWithGeometryChange(aRetval);
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ // makes the given indexed point the new polygon start point. To do that, the points in the
+ // polygon will be rotated. This is only valid for closed polygons, for non-closed ones
+ // an assertion will be triggered
+ B2DPolygon makeStartPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndexOfNewStatPoint)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 2 && nIndexOfNewStatPoint != 0 && nIndexOfNewStatPoint < nPointCount)
+ {
+ OSL_ENSURE(rCandidate.isClosed(), "makeStartPoint: only valid for closed polygons (!)");
+ B2DPolygon aRetval;
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const sal_uInt32 nSourceIndex((a + nIndexOfNewStatPoint) % nPointCount);
+ aRetval.append(rCandidate.getB2DPoint(nSourceIndex));
+
+ if(rCandidate.areControlPointsUsed())
+ {
+ aRetval.setPrevControlPoint(a, rCandidate.getPrevControlPoint(nSourceIndex));
+ aRetval.setNextControlPoint(a, rCandidate.getNextControlPoint(nSourceIndex));
+ }
+ }
+
+ return aRetval;
+ }
+
+ return rCandidate;
+ }
+
+ B2DPolygon createEdgesOfGivenLength(const B2DPolygon& rCandidate, double fLength, double fStart, double fEnd)
+ {
+ B2DPolygon aRetval;
+
+ if(fLength < 0.0)
+ {
+ fLength = 0.0;
+ }
+
+ if(!fTools::equalZero(fLength))
+ {
+ if(fStart < 0.0)
+ {
+ fStart = 0.0;
+ }
+
+ if(fEnd < 0.0)
+ {
+ fEnd = 0.0;
+ }
+
+ if(fEnd < fStart)
+ {
+ fEnd = fStart;
+ }
+
+ // iterate and consume pieces with fLength. First subdivide to reduce input to line segments
+ const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
+ const sal_uInt32 nPointCount(aCandidate.count());
+
+ if(nPointCount > 1)
+ {
+ const bool bEndActive(!fTools::equalZero(fEnd));
+ const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B2DPoint aCurrent(aCandidate.getB2DPoint(0));
+ double fPositionInEdge(fStart);
+ double fAbsolutePosition(fStart);
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
+ const B2DVector aEdge(aNext - aCurrent);
+ double fEdgeLength(aEdge.getLength());
+
+ if(!fTools::equalZero(fEdgeLength))
+ {
+ while(fTools::less(fPositionInEdge, fEdgeLength))
+ {
+ // move position on edge forward as long as on edge
+ const double fScalar(fPositionInEdge / fEdgeLength);
+ aRetval.append(aCurrent + (aEdge * fScalar));
+ fPositionInEdge += fLength;
+
+ if(bEndActive)
+ {
+ fAbsolutePosition += fLength;
+
+ if(fTools::more(fAbsolutePosition, fEnd))
+ {
+ break;
+ }
+ }
+ }
+
+ // subtract length of current edge
+ fPositionInEdge -= fEdgeLength;
+ }
+
+ if(bEndActive && fTools::more(fAbsolutePosition, fEnd))
+ {
+ break;
+ }
+
+ // prepare next step
+ aCurrent = aNext;
+ }
+
+ // keep closed state
+ aRetval.setClosed(aCandidate.isClosed());
+ }
+ else
+ {
+ // source polygon has only one point, return unchanged
+ aRetval = aCandidate;
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolygon createWaveline(const B2DPolygon& rCandidate, double fWaveWidth, double fWaveHeight)
+ {
+ B2DPolygon aRetval;
+
+ if(fWaveWidth < 0.0)
+ {
+ fWaveWidth = 0.0;
+ }
+
+ if(fWaveHeight < 0.0)
+ {
+ fWaveHeight = 0.0;
+ }
+
+ const bool bHasWidth(!fTools::equalZero(fWaveWidth));
+
+ if(bHasWidth)
+ {
+ const bool bHasHeight(!fTools::equalZero(fWaveHeight));
+ if(bHasHeight)
+ {
+ // width and height, create waveline. First subdivide to reduce input to line segments
+ // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it
+ // may be added here again using the original last point from rCandidate. It may
+ // also be the case that rCandidate was closed. To simplify things it is handled here
+ // as if it was opened.
+ // Result from createEdgesOfGivenLength contains no curved segments, handle as straight
+ // edges.
+ const B2DPolygon aEqualLenghEdges(createEdgesOfGivenLength(rCandidate, fWaveWidth));
+ const sal_uInt32 nPointCount(aEqualLenghEdges.count());
+
+ if(nPointCount > 1)
+ {
+ // iterate over straight edges, add start point
+ B2DPoint aCurrent(aEqualLenghEdges.getB2DPoint(0));
+ aRetval.append(aCurrent);
+
+ for(sal_uInt32 a(0); a < nPointCount - 1; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B2DPoint aNext(aEqualLenghEdges.getB2DPoint(nNextIndex));
+ const B2DVector aEdge(aNext - aCurrent);
+ const B2DVector aPerpendicular(getNormalizedPerpendicular(aEdge));
+ const B2DVector aControlOffset((aEdge * 0.467308) - (aPerpendicular * fWaveHeight));
+
+ // add curve segment
+ aRetval.appendBezierSegment(
+ aCurrent + aControlOffset,
+ aNext - aControlOffset,
+ aNext);
+
+ // prepare next step
+ aCurrent = aNext;
+ }
+ }
+ }
+ else
+ {
+ // width but no height -> return original polygon
+ aRetval = rCandidate;
+ }
+ }
+ else
+ {
+ // no width -> no waveline, stay empty and return
+ }
+
+ return aRetval;
+ }
+
+ // snap points of horizontal or vertical edges to discrete values
+ B2DPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon& rCandidate)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1)
+ {
+ // Start by copying the source polygon to get a writeable copy. The closed state is
+ // copied by aRetval's initialisation, too, so no need to copy it in this method
+ B2DPolygon aRetval(rCandidate);
+
+ // prepare geometry data. Get rounded from original
+ B2ITuple aPrevTuple(basegfx::fround(rCandidate.getB2DPoint(nPointCount - 1)));
+ B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
+ B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
+
+ // loop over all points. This will also snap the implicit closing edge
+ // even when not closed, but that's no problem here
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ // get next point. Get rounded from original
+ const bool bLastRun(a + 1 == nPointCount);
+ const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
+ const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
+ const B2ITuple aNextTuple(basegfx::fround(aNextPoint));
+
+ // get the states
+ const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
+ const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
+ const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
+ const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
+ const bool bSnapX(bPrevVertical || bNextVertical);
+ const bool bSnapY(bPrevHorizontal || bNextHorizontal);
+
+ if(bSnapX || bSnapY)
+ {
+ const B2DPoint aSnappedPoint(
+ bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
+ bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
+
+ aRetval.setB2DPoint(a, aSnappedPoint);
+ }
+
+ // prepare next point
+ if(!bLastRun)
+ {
+ aPrevTuple = aCurrTuple;
+ aCurrPoint = aNextPoint;
+ aCurrTuple = aNextTuple;
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DVector getTangentEnteringPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ B2DVector aRetval(0.0, 0.0);
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nIndex >= nCount)
+ {
+ // out of range
+ return aRetval;
+ }
+
+ // start immediately at prev point compared to nIndex
+ const bool bClosed(rCandidate.isClosed());
+ sal_uInt32 nPrev(bClosed ? (nIndex + nCount - 1) % nCount : nIndex ? nIndex - 1 : nIndex);
+
+ if(nPrev == nIndex)
+ {
+ // no previous, done
+ return aRetval;
+ }
+
+ B2DCubicBezier aSegment;
+
+ // go backward in the polygon; if closed, maximal back to start index (nIndex); if not closed,
+ // until zero. Use nIndex as stop criteria
+ while(nPrev != nIndex)
+ {
+ // get BezierSegment and tangent at the *end* of segment
+ rCandidate.getBezierSegment(nPrev, aSegment);
+ aRetval = aSegment.getTangent(1.0);
+
+ if(!aRetval.equalZero())
+ {
+ // if we have a tangent, return it
+ return aRetval;
+ }
+
+ // prepare index before checked one
+ nPrev = bClosed ? (nPrev + nCount - 1) % nCount : nPrev ? nPrev - 1 : nIndex;
+ }
+
+ return aRetval;
+ }
+
+ B2DVector getTangentLeavingPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
+ {
+ B2DVector aRetval(0.0, 0.0);
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nIndex >= nCount)
+ {
+ // out of range
+ return aRetval;
+ }
+
+ // start at nIndex
+ const bool bClosed(rCandidate.isClosed());
+ sal_uInt32 nCurrent(nIndex);
+ B2DCubicBezier aSegment;
+
+ // go forward; if closed, do this until once around and back at start index (nIndex); if not
+ // closed, until last point (nCount - 1). Use nIndex as stop criteria
+ do
+ {
+ // get BezierSegment and tangent at the *beginning* of segment
+ rCandidate.getBezierSegment(nCurrent, aSegment);
+ aRetval = aSegment.getTangent(0.0);
+
+ if(!aRetval.equalZero())
+ {
+ // if we have a tangent, return it
+ return aRetval;
+ }
+
+ // prepare next index
+ nCurrent = bClosed ? (nCurrent + 1) % nCount : nCurrent + 1 < nCount ? nCurrent + 1 : nIndex;
+ }
+ while(nCurrent != nIndex);
+
+ return aRetval;
+ }
+
+ // converters for css::drawing::PointSequence
+
+ B2DPolygon UnoPointSequenceToB2DPolygon(
+ const css::drawing::PointSequence& rPointSequenceSource)
+ {
+ B2DPolygon aRetval;
+ const sal_uInt32 nLength(rPointSequenceSource.getLength());
+
+ if(nLength)
+ {
+ aRetval.reserve(nLength);
+ const css::awt::Point* pArray = rPointSequenceSource.getConstArray();
+ const css::awt::Point* pArrayEnd = pArray + rPointSequenceSource.getLength();
+
+ for(;pArray != pArrayEnd; pArray++)
+ {
+ aRetval.append(B2DPoint(pArray->X, pArray->Y));
+ }
+
+ // check for closed state flag
+ utils::checkClosed(aRetval);
+ }
+
+ return aRetval;
+ }
+
+ void B2DPolygonToUnoPointSequence(
+ const B2DPolygon& rPolygon,
+ css::drawing::PointSequence& rPointSequenceRetval)
+ {
+ B2DPolygon aPolygon(rPolygon);
+
+ if(aPolygon.areControlPointsUsed())
+ {
+ OSL_ENSURE(false, "B2DPolygonToUnoPointSequence: Source contains bezier segments, wrong UNO API data type may be used (!)");
+ aPolygon = aPolygon.getDefaultAdaptiveSubdivision();
+ }
+
+ const sal_uInt32 nPointCount(aPolygon.count());
+
+ if(nPointCount)
+ {
+ // Take closed state into account, the API polygon still uses the old closed definition
+ // with last/first point are identical (cannot hold information about open polygons with identical
+ // first and last point, though)
+ const bool bIsClosed(aPolygon.isClosed());
+
+ rPointSequenceRetval.realloc(bIsClosed ? nPointCount + 1 : nPointCount);
+ css::awt::Point* pSequence = rPointSequenceRetval.getArray();
+
+ for(sal_uInt32 b(0); b < nPointCount; b++)
+ {
+ const B2DPoint aPoint(aPolygon.getB2DPoint(b));
+ const css::awt::Point aAPIPoint(fround(aPoint.getX()), fround(aPoint.getY()));
+
+ *pSequence = aAPIPoint;
+ pSequence++;
+ }
+
+ // copy first point if closed
+ if(bIsClosed)
+ {
+ *pSequence = *rPointSequenceRetval.getConstArray();
+ }
+ }
+ else
+ {
+ rPointSequenceRetval.realloc(0);
+ }
+ }
+
+ // converters for css::drawing::PointSequence and
+ // css::drawing::FlagSequence to B2DPolygon (curved polygons)
+
+ B2DPolygon UnoPolygonBezierCoordsToB2DPolygon(
+ const css::drawing::PointSequence& rPointSequenceSource,
+ const css::drawing::FlagSequence& rFlagSequenceSource)
+ {
+ const sal_uInt32 nCount(static_cast<sal_uInt32>(rPointSequenceSource.getLength()));
+ OSL_ENSURE(nCount == static_cast<sal_uInt32>(rFlagSequenceSource.getLength()),
+ "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)");
+
+ // prepare new polygon
+ B2DPolygon aRetval;
+
+ if(0 != nCount)
+ {
+ aRetval.reserve(nCount);
+
+ const css::awt::Point* pPointSequence = rPointSequenceSource.getConstArray();
+ const css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceSource.getConstArray();
+
+ // get first point and flag
+ B2DPoint aNewCoordinatePair(pPointSequence->X, pPointSequence->Y); pPointSequence++;
+ css::drawing::PolygonFlags ePolygonFlag(*pFlagSequence); pFlagSequence++;
+ B2DPoint aControlA;
+ B2DPoint aControlB;
+
+ // first point is not allowed to be a control point
+ OSL_ENSURE(ePolygonFlag != css::drawing::PolygonFlags_CONTROL,
+ "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)");
+
+ // add first point as start point
+ aRetval.append(aNewCoordinatePair);
+
+ for(sal_uInt32 b(1); b < nCount;)
+ {
+ // prepare loop
+ bool bControlA(false);
+ bool bControlB(false);
+
+ // get next point and flag
+ aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
+ ePolygonFlag = *pFlagSequence;
+ pPointSequence++; pFlagSequence++; b++;
+
+ if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
+ {
+ aControlA = aNewCoordinatePair;
+ bControlA = true;
+
+ // get next point and flag
+ aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
+ ePolygonFlag = *pFlagSequence;
+ pPointSequence++; pFlagSequence++; b++;
+ }
+
+ if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
+ {
+ aControlB = aNewCoordinatePair;
+ bControlB = true;
+
+ // get next point and flag
+ aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
+ ePolygonFlag = *pFlagSequence;
+ pPointSequence++; pFlagSequence++; b++;
+ }
+
+ // two or no control points are consumed, another one would be an error.
+ // It's also an error if only one control point was read
+ SAL_WARN_IF(ePolygonFlag == css::drawing::PolygonFlags_CONTROL || bControlA != bControlB,
+ "basegfx", "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)");
+
+ // the previous writes used the B2DPolyPolygon -> utils::PolyPolygon converter
+ // which did not create minimal PolyPolygons, but created all control points
+ // as null vectors (identical points). Because of the former P(CA)(CB)-norm of
+ // B2DPolygon and it's unused sign of being the zero-vector and CA and CB being
+ // relative to P, an empty edge was exported as P == CA == CB. Luckily, the new
+ // export format can be read without errors by the old OOo-versions, so we need only
+ // to correct here at read and do not need to export a wrong but compatible version
+ // for the future.
+ if(bControlA
+ && aControlA.equal(aControlB)
+ && aControlA.equal(aRetval.getB2DPoint(aRetval.count() - 1)))
+ {
+ bControlA = false;
+ }
+
+ if(bControlA)
+ {
+ // add bezier edge
+ aRetval.appendBezierSegment(aControlA, aControlB, aNewCoordinatePair);
+ }
+ else
+ {
+ // add edge
+ aRetval.append(aNewCoordinatePair);
+ }
+ }
+
+ // #i72807# API import uses old line start/end-equal definition for closed,
+ // so we need to correct this to closed state here
+ checkClosed(aRetval);
+ }
+
+ return aRetval;
+ }
+
+ void B2DPolygonToUnoPolygonBezierCoords(
+ const B2DPolygon& rPolygon,
+ css::drawing::PointSequence& rPointSequenceRetval,
+ css::drawing::FlagSequence& rFlagSequenceRetval)
+ {
+ const sal_uInt32 nPointCount(rPolygon.count());
+
+ if(nPointCount)
+ {
+ const bool bCurve(rPolygon.areControlPointsUsed());
+ const bool bClosed(rPolygon.isClosed());
+
+ if(bCurve)
+ {
+ // calculate target point count
+ const sal_uInt32 nLoopCount(bClosed ? nPointCount : nPointCount - 1);
+
+ if(nLoopCount)
+ {
+ // prepare target data. The real needed number of target points (and flags)
+ // could only be calculated by using two loops, so use dynamic memory
+ std::vector< css::awt::Point > aCollectPoints;
+ std::vector< css::drawing::PolygonFlags > aCollectFlags;
+
+ // reserve maximum creatable points
+ const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1);
+ aCollectPoints.reserve(nMaxTargetCount);
+ aCollectFlags.reserve(nMaxTargetCount);
+
+ // prepare current bezier segment by setting start point
+ B2DCubicBezier aBezierSegment;
+ aBezierSegment.setStartPoint(rPolygon.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nLoopCount; a++)
+ {
+ // add current point (always) and remember StartPointIndex for evtl. later corrections
+ const sal_uInt32 nStartPointIndex(aCollectPoints.size());
+ aCollectPoints.emplace_back(
+ fround(aBezierSegment.getStartPoint().getX()),
+ fround(aBezierSegment.getStartPoint().getY()));
+ aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
+
+ // prepare next segment
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ aBezierSegment.setEndPoint(rPolygon.getB2DPoint(nNextIndex));
+ aBezierSegment.setControlPointA(rPolygon.getNextControlPoint(a));
+ aBezierSegment.setControlPointB(rPolygon.getPrevControlPoint(nNextIndex));
+
+ if(aBezierSegment.isBezier())
+ {
+ // if bezier is used, add always two control points due to the old schema
+ aCollectPoints.emplace_back(
+ fround(aBezierSegment.getControlPointA().getX()),
+ fround(aBezierSegment.getControlPointA().getY()));
+ aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
+
+ aCollectPoints.emplace_back(
+ fround(aBezierSegment.getControlPointB().getX()),
+ fround(aBezierSegment.getControlPointB().getY()));
+ aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
+ }
+
+ // test continuity with previous control point to set flag value
+ if(aBezierSegment.getControlPointA() != aBezierSegment.getStartPoint() && (bClosed || a))
+ {
+ const B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a));
+
+ if(eCont == B2VectorContinuity::C1)
+ {
+ aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SMOOTH;
+ }
+ else if(eCont == B2VectorContinuity::C2)
+ {
+ aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SYMMETRIC;
+ }
+ }
+
+ // prepare next loop
+ aBezierSegment.setStartPoint(aBezierSegment.getEndPoint());
+ }
+
+ if(bClosed)
+ {
+ // add first point again as closing point due to old definition
+ aCollectPoints.push_back(aCollectPoints[0]);
+ aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
+ }
+ else
+ {
+ // add last point as closing point
+ const B2DPoint aClosingPoint(rPolygon.getB2DPoint(nPointCount - 1));
+ aCollectPoints.emplace_back(
+ fround(aClosingPoint.getX()),
+ fround(aClosingPoint.getY()));
+ aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
+ }
+
+ // copy collected data to target arrays
+ const sal_uInt32 nTargetCount(aCollectPoints.size());
+ OSL_ENSURE(nTargetCount == aCollectFlags.size(), "Unequal Point and Flag count (!)");
+
+ rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
+ rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
+ css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
+ css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
+
+ for(sal_uInt32 a(0); a < nTargetCount; a++)
+ {
+ *pPointSequence = aCollectPoints[a];
+ *pFlagSequence = aCollectFlags[a];
+ pPointSequence++;
+ pFlagSequence++;
+ }
+ }
+ }
+ else
+ {
+ // straightforward point list creation
+ const sal_uInt32 nTargetCount(nPointCount + (bClosed ? 1 : 0));
+
+ rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
+ rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
+
+ css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
+ css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B2DPoint aB2DPoint(rPolygon.getB2DPoint(a));
+ const css::awt::Point aAPIPoint(
+ fround(aB2DPoint.getX()),
+ fround(aB2DPoint.getY()));
+
+ *pPointSequence = aAPIPoint;
+ *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
+ pPointSequence++;
+ pFlagSequence++;
+ }
+
+ if(bClosed)
+ {
+ // add first point as closing point
+ *pPointSequence = *rPointSequenceRetval.getConstArray();
+ *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
+ }
+ }
+ }
+ else
+ {
+ rPointSequenceRetval.realloc(0);
+ rFlagSequenceRetval.realloc(0);
+ }
+ }
+
+B2DPolygon createSimplifiedPolygon(const B2DPolygon& rCandidate, const double fTolerance)
+{
+ const sal_uInt32 nPointCount(rCandidate.count());
+ if (nPointCount < 3 || rCandidate.areControlPointsUsed())
+ return rCandidate;
+
+ // The solution does not use recursion directly, since this could lead to a stack overflow if
+ // nPointCount is very large. Instead, an own stack is used. This does not contain points, but
+ // pairs of low and high index of a range in rCandidate. A parallel boolean vector is used to note
+ // whether a point of rCandidate belongs to the output polygon or not.
+ std::vector<bool> bIsKeptVec(nPointCount, false);
+ bIsKeptVec[0] = true;
+ bIsKeptVec[nPointCount - 1] = true;
+ sal_uInt32 nKept = 2;
+ std::stack<std::pair<sal_uInt32, sal_uInt32>> aUnfinishedRangesStack;
+
+ // The RDP algorithm draws a straight line from the first point to the last point of a range.
+ // Then, from the inner points of the range, the point that has the largest distance to the line
+ // is determined. If the distance is greater than the tolerance, this point is kept and the lower
+ // and upper sub-segments of the range are treated in the same way. If the distance is smaller
+ // than the tolerance, none of the inner points will be kept.
+ sal_uInt32 nLowIndex = 0;
+ sal_uInt32 nHighIndex = nPointCount - 1;
+ bool bContinue = true;
+ do
+ {
+ bContinue = false;
+ if (nHighIndex - nLowIndex < 2) // maximal two points, range is finished.
+ {
+ // continue with sibling upper range if any
+ if (!aUnfinishedRangesStack.empty())
+ {
+ std::pair<sal_uInt32, sal_uInt32> aIndexPair = aUnfinishedRangesStack.top();
+ aUnfinishedRangesStack.pop();
+ nLowIndex = aIndexPair.first;
+ nHighIndex = aIndexPair.second;
+ bContinue = true;
+ }
+ }
+ else // the range needs examine the max distance
+ {
+ // Get maximal distance of inner points of the range to the straight line from start to
+ // end point of the range.
+ // For calculation of the distance we use the Hesse normal form of the straight line.
+ B2DPoint aLowPoint = rCandidate.getB2DPoint(nLowIndex);
+ B2DPoint aHighPoint = rCandidate.getB2DPoint(nHighIndex);
+ B2DVector aNormalVec
+ = basegfx::getNormalizedPerpendicular(B2DVector(aHighPoint) - B2DVector(aLowPoint));
+ double fLineOriginDistance = aNormalVec.scalar(B2DVector(aLowPoint));
+ double fMaxDist = 0;
+ sal_uInt32 nMaxPointIndex = nLowIndex;
+ for (sal_uInt32 i = nLowIndex + 1; i < nHighIndex; i++)
+ {
+ double fDistance
+ = aNormalVec.scalar(B2DVector(rCandidate.getB2DPoint(i))) - fLineOriginDistance;
+ if (std::fabs(fDistance) > fMaxDist)
+ {
+ fMaxDist = std::fabs(fDistance);
+ nMaxPointIndex = i;
+ }
+ }
+
+ if (fMaxDist >= fTolerance)
+ {
+ // We need to divide the current range into two sub ranges.
+ bIsKeptVec[nMaxPointIndex] = true;
+ nKept++;
+ // continue with lower sub range and push upper sub range to stack
+ aUnfinishedRangesStack.push(std::make_pair(nMaxPointIndex, nHighIndex));
+ nHighIndex = nMaxPointIndex;
+ bContinue = true;
+ }
+ else
+ {
+ // We do not keep any of the inner points of the current range.
+ // continue with sibling upper range if any
+ if (!aUnfinishedRangesStack.empty())
+ {
+ std::pair<sal_uInt32, sal_uInt32> aIndexPair = aUnfinishedRangesStack.top();
+ aUnfinishedRangesStack.pop();
+ nLowIndex = aIndexPair.first;
+ nHighIndex = aIndexPair.second;
+ bContinue = true;
+ }
+ }
+ }
+ } while (bContinue);
+
+ // Put all points which are marked as "keep" into the result polygon
+ B2DPolygon aResultPolygon;
+ aResultPolygon.reserve(nKept);
+ for (sal_uInt32 i = 0; i < nPointCount; i++)
+ {
+ if (bIsKeptVec[i])
+ aResultPolygon.append(rCandidate.getB2DPoint(i));
+ }
+ return aResultPolygon;
+}
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dpolygontriangulator.cxx b/basegfx/source/polygon/b2dpolygontriangulator.cxx
new file mode 100644
index 0000000000..8742ade70e
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolygontriangulator.cxx
@@ -0,0 +1,434 @@
+/* -*- 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 <basegfx/polygon/b2dpolygontriangulator.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/numeric/ftools.hxx>
+
+#include <algorithm>
+
+namespace basegfx
+{
+ namespace
+ {
+ class EdgeEntry
+ {
+ EdgeEntry* mpNext;
+ B2DPoint maStart;
+ B2DPoint maEnd;
+ double mfAtan2;
+
+ public:
+ EdgeEntry(const B2DPoint& rStart, const B2DPoint& rEnd)
+ : mpNext(nullptr),
+ maStart(rStart),
+ maEnd(rEnd),
+ mfAtan2(0.0)
+ {
+ // make sure edge goes down. If horizontal, let it go to the right (left-handed).
+ bool bSwap(false);
+
+ if(::basegfx::fTools::equal(maStart.getY(), maEnd.getY()))
+ {
+ if(maStart.getX() > maEnd.getX())
+ {
+ bSwap = true;
+ }
+ }
+ else if(maStart.getY() > maEnd.getY())
+ {
+ bSwap = true;
+ }
+
+ if(bSwap)
+ {
+ maStart = rEnd;
+ maEnd = rStart;
+ }
+
+ mfAtan2 = atan2(maEnd.getY() - maStart.getY(), maEnd.getX() - maStart.getX());
+ }
+
+ bool operator<(const EdgeEntry& rComp) const
+ {
+ if(::basegfx::fTools::equal(maStart.getY(), rComp.maStart.getY()))
+ {
+ if(::basegfx::fTools::equal(maStart.getX(), rComp.maStart.getX()))
+ {
+ // same in x and y -> same start point. Sort emitting vectors from left to right.
+ return (mfAtan2 > rComp.mfAtan2);
+ }
+
+ return (maStart.getX() < rComp.maStart.getX());
+ }
+
+ return (maStart.getY() < rComp.maStart.getY());
+ }
+
+ bool operator==(const EdgeEntry& rComp) const
+ {
+ return (maStart.equal(rComp.maStart) && maEnd.equal(rComp.maEnd));
+ }
+
+ bool operator!=(const EdgeEntry& rComp) const
+ {
+ return !(*this == rComp);
+ }
+
+ const B2DPoint& getStart() const { return maStart; }
+ const B2DPoint& getEnd() const { return maEnd; }
+
+ EdgeEntry* getNext() const { return mpNext; }
+ void setNext(EdgeEntry* pNext) { mpNext = pNext; }
+ };
+
+ typedef std::vector< EdgeEntry > EdgeEntries;
+
+ class Triangulator
+ {
+ EdgeEntry* mpList;
+ EdgeEntries maStartEntries;
+ std::vector< std::unique_ptr<EdgeEntry> > maNewEdgeEntries;
+ triangulator::B2DTriangleVector maResult;
+
+ void handleClosingEdge(const B2DPoint& rStart, const B2DPoint& rEnd);
+ bool CheckPointInTriangle(EdgeEntry* pEdgeA, EdgeEntry const * pEdgeB, const B2DPoint& rTestPoint);
+ void createTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC);
+
+ public:
+ explicit Triangulator(const B2DPolyPolygon& rCandidate);
+
+ const triangulator::B2DTriangleVector& getResult() const { return maResult; }
+ };
+
+ void Triangulator::handleClosingEdge(const B2DPoint& rStart, const B2DPoint& rEnd)
+ {
+ // create an entry, else the comparison might use the wrong edges
+ EdgeEntry aNew(rStart, rEnd);
+ EdgeEntry* pCurr = mpList;
+ EdgeEntry* pPrev = nullptr;
+
+ while(pCurr
+ && pCurr->getStart().getY() <= aNew.getStart().getY()
+ && *pCurr != aNew)
+ {
+ pPrev = pCurr;
+ pCurr = pCurr->getNext();
+ }
+
+ if(pCurr && *pCurr == aNew)
+ {
+ // found closing edge, remove
+ if(pPrev)
+ {
+ pPrev->setNext(pCurr->getNext());
+ }
+ else
+ {
+ mpList = pCurr->getNext();
+ }
+ }
+ else
+ {
+ // insert closing edge
+ EdgeEntry* pNew = new EdgeEntry(aNew);
+ maNewEdgeEntries.emplace_back(pNew);
+ pCurr = mpList;
+ pPrev = nullptr;
+
+ while(pCurr && *pCurr < *pNew)
+ {
+ pPrev = pCurr;
+ pCurr = pCurr->getNext();
+ }
+
+ if(pPrev)
+ {
+ pNew->setNext(pPrev->getNext());
+ pPrev->setNext(pNew);
+ }
+ else
+ {
+ pNew->setNext(mpList);
+ mpList = pNew;
+ }
+ }
+ }
+
+ bool Triangulator::CheckPointInTriangle(EdgeEntry* pEdgeA, EdgeEntry const * pEdgeB, const B2DPoint& rTestPoint)
+ {
+ // inside triangle or on edge?
+ if(!utils::isPointInTriangle(pEdgeA->getStart(), pEdgeA->getEnd(), pEdgeB->getEnd(), rTestPoint, true))
+ return true;
+
+ // but not on point
+ if(!rTestPoint.equal(pEdgeA->getEnd()) && !rTestPoint.equal(pEdgeB->getEnd()))
+ {
+ // found point in triangle -> split triangle inserting two edges
+ EdgeEntry* pStart = new EdgeEntry(pEdgeA->getStart(), rTestPoint);
+ EdgeEntry* pEnd = new EdgeEntry(*pStart);
+ maNewEdgeEntries.emplace_back(pStart);
+ maNewEdgeEntries.emplace_back(pEnd);
+
+ pStart->setNext(pEnd);
+ pEnd->setNext(pEdgeA->getNext());
+ pEdgeA->setNext(pStart);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ void Triangulator::createTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC)
+ {
+ maResult.emplace_back(
+ rA,
+ rB,
+ rC);
+ }
+
+ // consume as long as there are edges
+ Triangulator::Triangulator(const B2DPolyPolygon& rCandidate)
+ : mpList(nullptr)
+ {
+ // add all available edges to the single linked local list which will be sorted
+ // by Y,X,atan2 when adding nodes
+ if(rCandidate.count())
+ {
+ for(const auto& rPolygonCandidate : rCandidate)
+ {
+ const sal_uInt32 nCount {rPolygonCandidate.count()};
+
+ if(nCount > 2)
+ {
+ B2DPoint aPrevPnt(rPolygonCandidate.getB2DPoint(nCount - 1));
+
+ for(sal_uInt32 b(0); b < nCount; b++)
+ {
+ B2DPoint aNextPnt(rPolygonCandidate.getB2DPoint(b));
+
+ if( !aPrevPnt.equal(aNextPnt) )
+ {
+ maStartEntries.emplace_back(aPrevPnt, aNextPnt);
+ }
+
+ aPrevPnt = aNextPnt;
+ }
+ }
+ }
+
+ if(!maStartEntries.empty())
+ {
+ // sort initial list
+ std::sort(maStartEntries.begin(), maStartEntries.end());
+
+ // insert to own simply linked list
+ EdgeEntries::iterator aPos(maStartEntries.begin());
+ mpList = &(*aPos++);
+ EdgeEntry* pLast = mpList;
+
+ while(aPos != maStartEntries.end())
+ {
+ EdgeEntry* pEntry = &(*aPos++);
+ pLast->setNext(pEntry);
+ pLast = pEntry;
+ }
+ }
+ }
+
+ while(mpList)
+ {
+ if(mpList->getNext() && mpList->getNext()->getStart().equal(mpList->getStart()))
+ {
+ // next candidate. There are two edges and start point is equal.
+ // Length is not zero.
+ EdgeEntry* pEdgeA = mpList;
+ EdgeEntry* pEdgeB = pEdgeA->getNext();
+
+ if( pEdgeA->getEnd().equal(pEdgeB->getEnd()) )
+ {
+ // start and end equal -> neutral triangle, delete both
+ mpList = pEdgeB->getNext();
+ }
+ else
+ {
+ const B2DVector aLeft(pEdgeA->getEnd() - pEdgeA->getStart());
+ const B2DVector aRight(pEdgeB->getEnd() - pEdgeA->getStart());
+
+ if(getOrientation(aLeft, aRight) == B2VectorOrientation::Neutral)
+ {
+ // edges are parallel and have different length -> neutral triangle,
+ // delete both edges and handle closing edge
+ mpList = pEdgeB->getNext();
+ handleClosingEdge(pEdgeA->getEnd(), pEdgeB->getEnd());
+ }
+ else
+ {
+ // not parallel, look for points inside
+ B2DRange aRange(pEdgeA->getStart(), pEdgeA->getEnd());
+ aRange.expand(pEdgeB->getEnd());
+ EdgeEntry* pTestEdge = pEdgeB->getNext();
+ bool bNoPointInTriangle(true);
+
+ // look for start point in triangle
+ while(bNoPointInTriangle && pTestEdge)
+ {
+ if(aRange.getMaxY() < pTestEdge->getStart().getY())
+ {
+ // edge is below test range and edges are sorted -> stop looking
+ break;
+ }
+ else
+ {
+ // do not look for edges with same start point, they are sorted and cannot end inside.
+ if(!pTestEdge->getStart().equal(pEdgeA->getStart()))
+ {
+ if(aRange.isInside(pTestEdge->getStart()))
+ {
+ bNoPointInTriangle = CheckPointInTriangle(pEdgeA, pEdgeB, pTestEdge->getStart());
+ }
+ }
+ }
+
+ // next candidate
+ pTestEdge = pTestEdge->getNext();
+ }
+
+ if(bNoPointInTriangle)
+ {
+ // look for end point in triangle
+ pTestEdge = pEdgeB->getNext();
+
+ while(bNoPointInTriangle && pTestEdge)
+ {
+ if(aRange.getMaxY() < pTestEdge->getStart().getY())
+ {
+ // edge is below test range and edges are sorted -> stop looking
+ break;
+ }
+ else
+ {
+ // do not look for edges with same end point, they are sorted and cannot end inside.
+ if(!pTestEdge->getEnd().equal(pEdgeA->getStart()))
+ {
+ if(aRange.isInside(pTestEdge->getEnd()))
+ {
+ bNoPointInTriangle = CheckPointInTriangle(pEdgeA, pEdgeB, pTestEdge->getEnd());
+ }
+ }
+ }
+
+ // next candidate
+ pTestEdge = pTestEdge->getNext();
+ }
+ }
+
+ if(bNoPointInTriangle)
+ {
+ // create triangle, remove edges, handle closing edge
+ mpList = pEdgeB->getNext();
+ createTriangle(pEdgeA->getStart(), pEdgeB->getEnd(), pEdgeA->getEnd());
+ handleClosingEdge(pEdgeA->getEnd(), pEdgeB->getEnd());
+ }
+ }
+ }
+ }
+ else
+ {
+ // only one entry at start point, delete it
+ mpList = mpList->getNext();
+ }
+ }
+ }
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+namespace basegfx::triangulator
+{
+ B2DTriangleVector triangulate(const B2DPolygon& rCandidate)
+ {
+ B2DTriangleVector aRetval;
+
+ // subdivide locally (triangulate does not work with beziers), remove double and neutral points
+ B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? utils::adaptiveSubdivideByAngle(rCandidate) : rCandidate);
+ aCandidate.removeDoublePoints();
+ aCandidate = utils::removeNeutralPoints(aCandidate);
+
+ if(aCandidate.count() == 2)
+ {
+ // candidate IS a triangle, just append
+ aRetval.emplace_back(
+ aCandidate.getB2DPoint(0),
+ aCandidate.getB2DPoint(1),
+ aCandidate.getB2DPoint(2));
+ }
+ else if(aCandidate.count() > 2)
+ {
+ if(utils::isConvex(aCandidate))
+ {
+ // polygon is convex, just use a triangle fan
+ utils::addTriangleFan(aCandidate, aRetval);
+ }
+ else
+ {
+ // polygon is concave.
+ const B2DPolyPolygon aCandPolyPoly(aCandidate);
+ Triangulator aTriangulator(aCandPolyPoly);
+
+ aRetval = aTriangulator.getResult();
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DTriangleVector triangulate(const B2DPolyPolygon& rCandidate)
+ {
+ B2DTriangleVector aRetval;
+
+ // subdivide locally (triangulate does not work with beziers)
+ B2DPolyPolygon aCandidate(rCandidate.areControlPointsUsed() ? utils::adaptiveSubdivideByAngle(rCandidate) : rCandidate);
+
+ if(aCandidate.count() == 1)
+ {
+ // single polygon -> single polygon triangulation
+ const B2DPolygon& aSinglePolygon(aCandidate.getB2DPolygon(0));
+
+ aRetval = triangulate(aSinglePolygon);
+ }
+ else
+ {
+ Triangulator aTriangulator(aCandidate);
+
+ aRetval = aTriangulator.getResult();
+ }
+
+ return aRetval;
+ }
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dpolypolygon.cxx b/basegfx/source/polygon/b2dpolypolygon.cxx
new file mode 100644
index 0000000000..547634dc4d
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolypolygon.cxx
@@ -0,0 +1,432 @@
+/* -*- 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 <cassert>
+#include <utility>
+
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+
+namespace basegfx
+{
+
+class ImplB2DPolyPolygon
+{
+ basegfx::B2DPolygonVector maPolygons;
+ // we do not want to 'modify' the ImplB2DPolyPolygon,
+ // but add buffered data that is valid for all referencing instances
+ mutable std::unique_ptr<basegfx::SystemDependentDataHolder> mpSystemDependentDataHolder;
+
+public:
+ ImplB2DPolyPolygon()
+ {
+ }
+
+ explicit ImplB2DPolyPolygon(const ImplB2DPolyPolygon& rSource)
+ : maPolygons(rSource.maPolygons)
+ {
+ }
+
+ explicit ImplB2DPolyPolygon(const basegfx::B2DPolygon& rToBeCopied)
+ : maPolygons(1,rToBeCopied)
+ {
+ }
+
+ ImplB2DPolyPolygon& operator=(const ImplB2DPolyPolygon& rSource)
+ {
+ if (this != &rSource)
+ {
+ maPolygons = rSource.maPolygons;
+ mpSystemDependentDataHolder.reset();
+ }
+
+ return *this;
+ }
+
+ void addOrReplaceSystemDependentData(basegfx::SystemDependentData_SharedPtr& rData) const
+ {
+ if(!mpSystemDependentDataHolder)
+ {
+ mpSystemDependentDataHolder.reset(new basegfx::SystemDependentDataHolder());
+ }
+
+ mpSystemDependentDataHolder->addOrReplaceSystemDependentData(rData);
+ }
+
+ basegfx::SystemDependentData_SharedPtr getSystemDependentData(size_t hash_code) const
+ {
+ if(!mpSystemDependentDataHolder)
+ {
+ return basegfx::SystemDependentData_SharedPtr();
+ }
+
+ return mpSystemDependentDataHolder->getSystemDependentData(hash_code);
+ }
+
+ bool operator==(const ImplB2DPolyPolygon& rPolygonList) const
+ {
+ return maPolygons == rPolygonList.maPolygons;
+ }
+
+ const basegfx::B2DPolygon& getB2DPolygon(sal_uInt32 nIndex) const
+ {
+ assert(nIndex < maPolygons.size());
+ return maPolygons[nIndex];
+ }
+
+ void setB2DPolygon(sal_uInt32 nIndex, const basegfx::B2DPolygon& rPolygon)
+ {
+ assert(nIndex < maPolygons.size());
+ maPolygons[nIndex] = rPolygon;
+ }
+
+ void insert(sal_uInt32 nIndex, const basegfx::B2DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ assert(nCount > 0);
+ assert(nIndex <= maPolygons.size());
+ // add nCount copies of rPolygon
+ maPolygons.insert(maPolygons.begin() + nIndex, nCount, rPolygon);
+ }
+
+ void append(const basegfx::B2DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ insert(maPolygons.size(), rPolygon, nCount);
+ }
+
+ void reserve(sal_uInt32 nCount)
+ {
+ maPolygons.reserve(nCount);
+ }
+
+ void insert(sal_uInt32 nIndex, const basegfx::B2DPolyPolygon& rPolyPolygon)
+ {
+ assert(nIndex <= maPolygons.size());
+ // add nCount polygons from rPolyPolygon
+ maPolygons.insert(maPolygons.begin() + nIndex, rPolyPolygon.begin(), rPolyPolygon.end());
+ }
+
+ void append(const basegfx::B2DPolyPolygon& rPolyPolygon)
+ {
+ insert(maPolygons.size(), rPolyPolygon);
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ assert(nCount > 0);
+ assert(nIndex + nCount <= maPolygons.size());
+ // remove polygon data
+ auto aStart(maPolygons.begin() + nIndex);
+ auto aEnd(aStart + nCount);
+
+ maPolygons.erase(aStart, aEnd);
+ }
+
+ sal_uInt32 count() const
+ {
+ return maPolygons.size();
+ }
+
+ void setClosed(bool bNew)
+ {
+ for(basegfx::B2DPolygon & rPolygon : maPolygons)
+ {
+ rPolygon.setClosed(bNew);
+ }
+ }
+
+ void flip()
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.flip();
+ }
+
+ void removeDoublePoints()
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.removeDoublePoints();
+ }
+
+ void transform(const basegfx::B2DHomMatrix& rMatrix)
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.transform(rMatrix);
+ }
+
+ void makeUnique()
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.makeUnique();
+ }
+
+ const basegfx::B2DPolygon* begin() const
+ {
+ if (maPolygons.empty())
+ return nullptr;
+ else
+ return maPolygons.data();
+ }
+
+ const basegfx::B2DPolygon* end() const
+ {
+ if (maPolygons.empty())
+ return nullptr;
+ else
+ return maPolygons.data() + maPolygons.size();
+ }
+
+ basegfx::B2DPolygon* begin()
+ {
+ if (maPolygons.empty())
+ return nullptr;
+ else
+ return maPolygons.data();
+ }
+
+ basegfx::B2DPolygon* end()
+ {
+ if (maPolygons.empty())
+ return nullptr;
+ else
+ return maPolygons.data() + maPolygons.size();
+ }
+};
+
+ B2DPolyPolygon::B2DPolyPolygon() = default;
+
+ B2DPolyPolygon::B2DPolyPolygon(const B2DPolyPolygon&) = default;
+
+ B2DPolyPolygon::B2DPolyPolygon(B2DPolyPolygon&&) = default;
+
+ B2DPolyPolygon::B2DPolyPolygon(const B2DPolygon& rPolygon) :
+ mpPolyPolygon( ImplB2DPolyPolygon(rPolygon) )
+ {
+ }
+
+ B2DPolyPolygon::~B2DPolyPolygon() = default;
+
+ B2DPolyPolygon& B2DPolyPolygon::operator=(const B2DPolyPolygon&) = default;
+
+ B2DPolyPolygon& B2DPolyPolygon::operator=(B2DPolyPolygon&&) = default;
+
+ void B2DPolyPolygon::makeUnique()
+ {
+ mpPolyPolygon->makeUnique(); // non-const cow_wrapper::operator-> calls make_unique
+ }
+
+ bool B2DPolyPolygon::operator==(const B2DPolyPolygon& rPolyPolygon) const
+ {
+ if(mpPolyPolygon.same_object(rPolyPolygon.mpPolyPolygon))
+ return true;
+
+ return ((*mpPolyPolygon) == (*rPolyPolygon.mpPolyPolygon));
+ }
+
+ bool B2DPolyPolygon::operator!=(const B2DPolyPolygon& rPolyPolygon) const
+ {
+ return !((*this) == rPolyPolygon);
+ }
+
+ sal_uInt32 B2DPolyPolygon::count() const
+ {
+ return mpPolyPolygon->count();
+ }
+
+ B2DPolygon const & B2DPolyPolygon::getB2DPolygon(sal_uInt32 nIndex) const
+ {
+ return mpPolyPolygon->getB2DPolygon(nIndex);
+ }
+
+ void B2DPolyPolygon::setB2DPolygon(sal_uInt32 nIndex, const B2DPolygon& rPolygon)
+ {
+ if(getB2DPolygon(nIndex) != rPolygon)
+ mpPolyPolygon->setB2DPolygon(nIndex, rPolygon);
+ }
+
+ bool B2DPolyPolygon::areControlPointsUsed() const
+ {
+ for(sal_uInt32 a(0); a < count(); a++)
+ {
+ if(getB2DPolygon(a).areControlPointsUsed())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void B2DPolyPolygon::insert(sal_uInt32 nIndex, const B2DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ if(nCount)
+ mpPolyPolygon->insert(nIndex, rPolygon, nCount);
+ }
+
+ void B2DPolyPolygon::append(const B2DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ if(nCount)
+ mpPolyPolygon->append(rPolygon, nCount);
+ }
+
+ void B2DPolyPolygon::reserve(sal_uInt32 nCount)
+ {
+ if(nCount)
+ mpPolyPolygon->reserve(nCount);
+ }
+
+ B2DPolyPolygon B2DPolyPolygon::getDefaultAdaptiveSubdivision() const
+ {
+ B2DPolyPolygon aRetval;
+ if (count())
+ {
+ ImplB2DPolyPolygon& dest = *aRetval.mpPolyPolygon;
+ dest.reserve(count());
+
+ for (sal_uInt32 a(0); a < count(); a++)
+ {
+ dest.append(getB2DPolygon(a).getDefaultAdaptiveSubdivision(), 1);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DRange B2DPolyPolygon::getB2DRange() const
+ {
+ B2DRange aRetval;
+
+ for(sal_uInt32 a(0); a < count(); a++)
+ {
+ aRetval.expand(getB2DPolygon(a).getB2DRange());
+ }
+
+ return aRetval;
+ }
+
+ void B2DPolyPolygon::insert(sal_uInt32 nIndex, const B2DPolyPolygon& rPolyPolygon)
+ {
+ if(rPolyPolygon.count())
+ mpPolyPolygon->insert(nIndex, rPolyPolygon);
+ }
+
+ void B2DPolyPolygon::append(const B2DPolyPolygon& rPolyPolygon)
+ {
+ if(rPolyPolygon.count())
+ mpPolyPolygon->append(rPolyPolygon);
+ }
+
+ void B2DPolyPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ mpPolyPolygon->remove(nIndex, nCount);
+ }
+
+ void B2DPolyPolygon::clear()
+ {
+ *mpPolyPolygon = ImplB2DPolyPolygon();
+ }
+
+ bool B2DPolyPolygon::isClosed() const
+ {
+ // PolyPOlygon is closed when all contained Polygons are closed or
+ // no Polygon exists.
+ for(sal_uInt32 a(0); a < count(); a++)
+ {
+ if(!getB2DPolygon(a).isClosed())
+ return false;
+ }
+
+ return true;
+ }
+
+ void B2DPolyPolygon::setClosed(bool bNew)
+ {
+ if(bNew != isClosed())
+ mpPolyPolygon->setClosed(bNew);
+ }
+
+ void B2DPolyPolygon::flip()
+ {
+ if(count())
+ {
+ mpPolyPolygon->flip();
+ }
+ }
+
+ bool B2DPolyPolygon::hasDoublePoints() const
+ {
+ for(sal_uInt32 a(0); a < count(); a++)
+ {
+ if(getB2DPolygon(a).hasDoublePoints())
+ return true;
+ }
+
+ return false;
+ }
+
+ void B2DPolyPolygon::removeDoublePoints()
+ {
+ if(hasDoublePoints())
+ mpPolyPolygon->removeDoublePoints();
+ }
+
+ void B2DPolyPolygon::transform(const B2DHomMatrix& rMatrix)
+ {
+ if(count() && !rMatrix.isIdentity())
+ {
+ mpPolyPolygon->transform(rMatrix);
+ }
+ }
+
+ const B2DPolygon* B2DPolyPolygon::begin() const
+ {
+ return mpPolyPolygon->begin();
+ }
+
+ const B2DPolygon* B2DPolyPolygon::end() const
+ {
+ return mpPolyPolygon->end();
+ }
+
+ B2DPolygon* B2DPolyPolygon::begin()
+ {
+ return mpPolyPolygon->begin();
+ }
+
+ B2DPolygon* B2DPolyPolygon::end()
+ {
+ return mpPolyPolygon->end();
+ }
+
+ void B2DPolyPolygon::addOrReplaceSystemDependentDataInternal(SystemDependentData_SharedPtr& rData) const
+ {
+ mpPolyPolygon->addOrReplaceSystemDependentData(rData);
+ }
+
+ SystemDependentData_SharedPtr B2DPolyPolygon::getSystemDependantDataInternal(size_t hash_code) const
+ {
+ return mpPolyPolygon->getSystemDependentData(hash_code);
+ }
+
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dpolypolygoncutter.cxx b/basegfx/source/polygon/b2dpolypolygoncutter.cxx
new file mode 100644
index 0000000000..42cfed615f
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolypolygoncutter.cxx
@@ -0,0 +1,1135 @@
+/* -*- 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 <basegfx/numeric/ftools.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolygoncutandtouch.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <sal/log.hxx>
+#include <utility>
+#include <vector>
+#include <algorithm>
+#include <numeric>
+
+namespace basegfx
+{
+ namespace
+ {
+
+ struct StripHelper
+ {
+ B2DRange maRange;
+ sal_Int32 mnDepth;
+ B2VectorOrientation meOrinetation;
+ };
+
+ struct PN
+ {
+ public:
+ B2DPoint maPoint;
+ sal_uInt32 mnI;
+ sal_uInt32 mnIP;
+ sal_uInt32 mnIN;
+ };
+
+ struct VN
+ {
+ public:
+ B2DVector maPrev;
+ B2DVector maNext;
+
+ // to have the correct curve segments in the crossover checks,
+ // it is necessary to keep the original next vectors, too. Else,
+ // it may happen to use an already switched next vector which
+ // would interpolate the wrong comparison point
+ B2DVector maOriginalNext;
+ };
+
+ struct SN
+ {
+ public:
+ PN* mpPN;
+
+ bool operator<(const SN& rComp) const
+ {
+ if(fTools::equal(mpPN->maPoint.getX(), rComp.mpPN->maPoint.getX()))
+ {
+ if(fTools::equal(mpPN->maPoint.getY(), rComp.mpPN->maPoint.getY()))
+ {
+ return (mpPN->mnI < rComp.mpPN->mnI);
+ }
+ else
+ {
+ return fTools::less(mpPN->maPoint.getY(), rComp.mpPN->maPoint.getY());
+ }
+ }
+ else
+ {
+ return fTools::less(mpPN->maPoint.getX(), rComp.mpPN->maPoint.getX());
+ }
+ }
+ };
+
+ typedef std::vector< PN > PNV;
+ typedef std::vector< VN > VNV;
+ typedef std::vector< SN > SNV;
+ typedef std::pair< basegfx::B2DPoint /*orig*/, basegfx::B2DPoint /*repl*/ > CorrectionPair;
+
+ class solver
+ {
+ private:
+ const B2DPolyPolygon maOriginal;
+ PNV maPNV;
+ VNV maVNV;
+ SNV maSNV;
+ std::vector< CorrectionPair >
+ maCorrectionTable;
+
+ bool mbIsCurve : 1;
+ bool mbChanged : 1;
+
+ void impAddPolygon(const sal_uInt32 aPos, const B2DPolygon& rGeometry)
+ {
+ const sal_uInt32 nCount(rGeometry.count());
+ PN aNewPN;
+ VN aNewVN;
+ SN aNewSN;
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ const B2DPoint aPoint(rGeometry.getB2DPoint(a));
+ aNewPN.maPoint = aPoint;
+ aNewPN.mnI = aPos + a;
+ aNewPN.mnIP = aPos + ((a != 0) ? a - 1 : nCount - 1);
+ aNewPN.mnIN = aPos + ((a + 1 == nCount) ? 0 : a + 1);
+ maPNV.push_back(aNewPN);
+
+ if(mbIsCurve)
+ {
+ aNewVN.maPrev = rGeometry.getPrevControlPoint(a) - aPoint;
+ aNewVN.maNext = rGeometry.getNextControlPoint(a) - aPoint;
+ aNewVN.maOriginalNext = aNewVN.maNext;
+ maVNV.push_back(aNewVN);
+ }
+
+ aNewSN.mpPN = &maPNV[maPNV.size() - 1];
+ maSNV.push_back(aNewSN);
+ }
+ }
+
+ static bool impLeftOfEdges(const B2DVector& rVecA, const B2DVector& rVecB, const B2DVector& rTest)
+ {
+ // tests if rTest is left of both directed line segments along the line -rVecA, rVecB. Test is
+ // with border.
+ if(rVecA.cross(rVecB) > 0.0)
+ {
+ // b is left turn seen from a, test if Test is left of both and so inside (left is seen as inside)
+ const bool bBoolA(fTools::moreOrEqual(rVecA.cross(rTest), 0.0));
+ const bool bBoolB(fTools::lessOrEqual(rVecB.cross(rTest), 0.0));
+
+ return (bBoolA && bBoolB);
+ }
+ else
+ {
+ // b is right turn seen from a, test if Test is right of both and so outside (left is seen as inside)
+ const bool bBoolA(fTools::lessOrEqual(rVecA.cross(rTest), 0.0));
+ const bool bBoolB(fTools::moreOrEqual(rVecB.cross(rTest), 0.0));
+
+ return (!(bBoolA && bBoolB));
+ }
+ }
+
+ void impSwitchNext(PN& rPNa, PN& rPNb)
+ {
+ std::swap(rPNa.mnIN, rPNb.mnIN);
+
+ if(mbIsCurve)
+ {
+ VN& rVNa = maVNV[rPNa.mnI];
+ VN& rVNb = maVNV[rPNb.mnI];
+
+ std::swap(rVNa.maNext, rVNb.maNext);
+ }
+
+ if(!mbChanged)
+ {
+ mbChanged = true;
+ }
+ }
+
+ B2DCubicBezier createSegment(const PN& rPN, bool bPrev) const
+ {
+ const B2DPoint& rStart(rPN.maPoint);
+ const B2DPoint& rEnd(maPNV[bPrev ? rPN.mnIP : rPN.mnIN].maPoint);
+ const B2DVector& rCPA(bPrev ? maVNV[rPN.mnI].maPrev : maVNV[rPN.mnI].maNext);
+ // Use maOriginalNext, not maNext to create the original (yet unchanged)
+ // curve segment. Otherwise, this segment would NOT ne correct.
+ const B2DVector& rCPB(bPrev ? maVNV[maPNV[rPN.mnIP].mnI].maOriginalNext : maVNV[maPNV[rPN.mnIN].mnI].maPrev);
+
+ return B2DCubicBezier(rStart, rStart + rCPA, rEnd + rCPB, rEnd);
+ }
+
+ void impHandleCommon(PN& rPNa, PN& rPNb)
+ {
+ if(mbIsCurve)
+ {
+ const B2DCubicBezier aNextA(createSegment(rPNa, false));
+ const B2DCubicBezier aPrevA(createSegment(rPNa, true));
+
+ if(aNextA.equal(aPrevA))
+ {
+ // deadend on A (identical edge)
+ return;
+ }
+
+ const B2DCubicBezier aNextB(createSegment(rPNb, false));
+ const B2DCubicBezier aPrevB(createSegment(rPNb, true));
+
+ if(aNextB.equal(aPrevB))
+ {
+ // deadend on B (identical edge)
+ return;
+ }
+
+ if(aPrevA.equal(aPrevB))
+ {
+ // common edge in same direction
+ return;
+ }
+ else if(aPrevA.equal(aNextB))
+ {
+ // common edge in opposite direction
+ if(aNextA.equal(aPrevB))
+ {
+ // common edge in opposite direction continues
+ return;
+ }
+ else
+ {
+ // common edge in opposite direction leave
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ else if(aNextA.equal(aNextB))
+ {
+ // common edge in same direction enter
+ // search leave edge
+ PN* pPNa2 = &maPNV[rPNa.mnIN];
+ PN* pPNb2 = &maPNV[rPNb.mnIN];
+ bool bOnEdge(true);
+
+ do
+ {
+ const B2DCubicBezier aNextA2(createSegment(*pPNa2, false));
+ const B2DCubicBezier aNextB2(createSegment(*pPNb2, false));
+
+ if(aNextA2.equal(aNextB2))
+ {
+ pPNa2 = &maPNV[pPNa2->mnIN];
+ pPNb2 = &maPNV[pPNb2->mnIN];
+ }
+ else
+ {
+ bOnEdge = false;
+ }
+ }
+ while(bOnEdge && pPNa2 != &rPNa && pPNb2 != &rPNb);
+
+ if(bOnEdge)
+ {
+ // loop over two identical polygon paths
+ return;
+ }
+ else
+ {
+ // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges
+ // at enter/leave. Check for crossover.
+ const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint());
+ const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint());
+ const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint());
+ const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB));
+
+ const B2DCubicBezier aNextA2(createSegment(*pPNa2, false));
+ const B2DCubicBezier aPrevA2(createSegment(*pPNa2, true));
+ const B2DCubicBezier aNextB2(createSegment(*pPNb2, false));
+ const B2DVector aPrevCA2(aPrevA2.interpolatePoint(0.5) - aPrevA2.getStartPoint());
+ const B2DVector aNextCA2(aNextA2.interpolatePoint(0.5) - aNextA2.getStartPoint());
+ const B2DVector aNextCB2(aNextB2.interpolatePoint(0.5) - aNextB2.getStartPoint());
+ const bool bLeave(impLeftOfEdges(aPrevCA2, aNextCA2, aNextCB2));
+
+ if(bEnter != bLeave)
+ {
+ // crossover
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ }
+ else if(aNextA.equal(aPrevB))
+ {
+ // common edge in opposite direction enter
+ impSwitchNext(rPNa, rPNb);
+ }
+ else
+ {
+ // no common edges, check for crossover
+ const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint());
+ const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint());
+ const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint());
+ const B2DVector aNextCB(aNextB.interpolatePoint(0.5) - aNextB.getStartPoint());
+
+ const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB));
+ const bool bLeave(impLeftOfEdges(aPrevCA, aNextCA, aNextCB));
+
+ if(bEnter != bLeave)
+ {
+ // crossover
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ }
+ else
+ {
+ const B2DPoint& rNextA(maPNV[rPNa.mnIN].maPoint);
+ const B2DPoint& rPrevA(maPNV[rPNa.mnIP].maPoint);
+
+ if(rNextA.equal(rPrevA))
+ {
+ // deadend on A
+ return;
+ }
+
+ const B2DPoint& rNextB(maPNV[rPNb.mnIN].maPoint);
+ const B2DPoint& rPrevB(maPNV[rPNb.mnIP].maPoint);
+
+ if(rNextB.equal(rPrevB))
+ {
+ // deadend on B
+ return;
+ }
+
+ if(rPrevA.equal(rPrevB))
+ {
+ // common edge in same direction
+ return;
+ }
+ else if(rPrevA.equal(rNextB))
+ {
+ // common edge in opposite direction
+ if(rNextA.equal(rPrevB))
+ {
+ // common edge in opposite direction continues
+ return;
+ }
+ else
+ {
+ // common edge in opposite direction leave
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ else if(rNextA.equal(rNextB))
+ {
+ // common edge in same direction enter
+ // search leave edge
+ PN* pPNa2 = &maPNV[rPNa.mnIN];
+ PN* pPNb2 = &maPNV[rPNb.mnIN];
+ bool bOnEdge(true);
+
+ do
+ {
+ const B2DPoint& rNextA2(maPNV[pPNa2->mnIN].maPoint);
+ const B2DPoint& rNextB2(maPNV[pPNb2->mnIN].maPoint);
+
+ if(rNextA2.equal(rNextB2))
+ {
+ pPNa2 = &maPNV[pPNa2->mnIN];
+ pPNb2 = &maPNV[pPNb2->mnIN];
+ }
+ else
+ {
+ bOnEdge = false;
+ }
+ }
+ while(bOnEdge && pPNa2 != &rPNa && pPNb2 != &rPNb);
+
+ if(bOnEdge)
+ {
+ // loop over two identical polygon paths
+ return;
+ }
+ else
+ {
+ // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges
+ // at enter/leave. Check for crossover.
+ const B2DPoint& aPointE(rPNa.maPoint);
+ const B2DVector aPrevAE(rPrevA - aPointE);
+ const B2DVector aNextAE(rNextA - aPointE);
+ const B2DVector aPrevBE(rPrevB - aPointE);
+
+ const B2DPoint& aPointL(pPNa2->maPoint);
+ const B2DVector aPrevAL(maPNV[pPNa2->mnIP].maPoint - aPointL);
+ const B2DVector aNextAL(maPNV[pPNa2->mnIN].maPoint - aPointL);
+ const B2DVector aNextBL(maPNV[pPNb2->mnIN].maPoint - aPointL);
+
+ const bool bEnter(impLeftOfEdges(aPrevAE, aNextAE, aPrevBE));
+ const bool bLeave(impLeftOfEdges(aPrevAL, aNextAL, aNextBL));
+
+ if(bEnter != bLeave)
+ {
+ // crossover; switch start or end
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ }
+ else if(rNextA.equal(rPrevB))
+ {
+ // common edge in opposite direction enter
+ impSwitchNext(rPNa, rPNb);
+ }
+ else
+ {
+ // no common edges, check for crossover
+ const B2DPoint& aPoint(rPNa.maPoint);
+ const B2DVector aPrevA(rPrevA - aPoint);
+ const B2DVector aNextA(rNextA - aPoint);
+ const B2DVector aPrevB(rPrevB - aPoint);
+ const B2DVector aNextB(rNextB - aPoint);
+
+ const bool bEnter(impLeftOfEdges(aPrevA, aNextA, aPrevB));
+ const bool bLeave(impLeftOfEdges(aPrevA, aNextA, aNextB));
+
+ if(bEnter != bLeave)
+ {
+ // crossover
+ impSwitchNext(rPNa, rPNb);
+ }
+ }
+ }
+ }
+
+ void impSolve()
+ {
+ // sort by point to identify common nodes easier
+ std::sort(maSNV.begin(), maSNV.end());
+
+ // handle common nodes
+ const sal_uInt32 nNodeCount(maSNV.size());
+ sal_uInt32 a(0);
+
+ // snap unsharp-equal points
+ if(nNodeCount)
+ {
+ basegfx::B2DPoint* pLast(&maSNV[0].mpPN->maPoint);
+
+ for(const auto& rSN : maSNV)
+ {
+ basegfx::B2DPoint* pCurrent(&rSN.mpPN->maPoint);
+
+ if(pLast->equal(*pCurrent) && (pLast->getX() != pCurrent->getX() || pLast->getY() != pCurrent->getY()))
+ {
+ const basegfx::B2DPoint aMiddle((*pLast + *pCurrent) * 0.5);
+
+ if(pLast->getX() != aMiddle.getX() || pLast->getY() != aMiddle.getY())
+ {
+ maCorrectionTable.emplace_back(*pLast, aMiddle);
+ *pLast = aMiddle;
+ }
+
+ if(pCurrent->getX() != aMiddle.getX() || pCurrent->getY() != aMiddle.getY())
+ {
+ maCorrectionTable.emplace_back(*pCurrent, aMiddle);
+ *pCurrent = aMiddle;
+ }
+ }
+
+ pLast = pCurrent;
+ }
+ }
+
+ for(a = 0; a < nNodeCount - 1; a++)
+ {
+ // test a before using it, not after. Also use nPointCount instead of aSortNodes.size()
+ PN& rPNb = *(maSNV[a].mpPN);
+
+ for(sal_uInt32 b(a + 1); b < nNodeCount && rPNb.maPoint.equal(maSNV[b].mpPN->maPoint); b++)
+ {
+ impHandleCommon(rPNb, *maSNV[b].mpPN);
+ }
+ }
+ }
+
+ public:
+ explicit solver(const B2DPolygon& rOriginal)
+ : maOriginal(B2DPolyPolygon(rOriginal)),
+ mbIsCurve(false),
+ mbChanged(false)
+ {
+ const sal_uInt32 nOriginalCount(rOriginal.count());
+
+ if(!nOriginalCount)
+ return;
+
+ B2DPolygon aGeometry(utils::addPointsAtCutsAndTouches(rOriginal));
+ aGeometry.removeDoublePoints();
+ aGeometry = utils::simplifyCurveSegments(aGeometry);
+ mbIsCurve = aGeometry.areControlPointsUsed();
+
+ const sal_uInt32 nPointCount(aGeometry.count());
+
+ // If it's not a bezier polygon, at least four points are needed to create
+ // a self-intersection. If it's a bezier polygon, the minimum point number
+ // is two, since with a single point You get a curve, but no self-intersection
+ if(!(nPointCount > 3 || (nPointCount > 1 && mbIsCurve)))
+ return;
+
+ // reserve space in point, control and sort vector.
+ maSNV.reserve(nPointCount);
+ maPNV.reserve(nPointCount);
+ maVNV.reserve(mbIsCurve ? nPointCount : 0);
+
+ // fill data
+ impAddPolygon(0, aGeometry);
+
+ // solve common nodes
+ impSolve();
+ }
+
+ explicit solver(B2DPolyPolygon aOriginal, size_t* pPointLimit = nullptr)
+ : maOriginal(std::move(aOriginal)),
+ mbIsCurve(false),
+ mbChanged(false)
+ {
+ sal_uInt32 nOriginalCount(maOriginal.count());
+
+ if(!nOriginalCount)
+ return;
+
+ B2DPolyPolygon aGeometry(utils::addPointsAtCutsAndTouches(maOriginal, pPointLimit));
+ aGeometry.removeDoublePoints();
+ aGeometry = utils::simplifyCurveSegments(aGeometry);
+ mbIsCurve = aGeometry.areControlPointsUsed();
+ nOriginalCount = aGeometry.count();
+
+ if(!nOriginalCount)
+ return;
+
+ // If it's not a bezier curve, at least three points would be needed to have a
+ // topological relevant (not empty) polygon. Since it's not known here if trivial
+ // edges (dead ends) will be kept or sorted out, add non-bezier polygons with
+ // more than one point.
+ // For bezier curves, the minimum for defining an area is also one.
+ sal_uInt32 nPointCount = std::accumulate( aGeometry.begin(), aGeometry.end(), sal_uInt32(0),
+ [](sal_uInt32 a, const basegfx::B2DPolygon& b){return a + b.count();});
+
+ if(!nPointCount)
+ return;
+
+ // reserve space in point, control and sort vector.
+ maSNV.reserve(nPointCount);
+ maPNV.reserve(nPointCount);
+ maVNV.reserve(mbIsCurve ? nPointCount : 0);
+
+ // fill data
+ sal_uInt32 nInsertIndex(0);
+
+ for(const auto& rCandidate : aGeometry )
+ {
+ const sal_uInt32 nCandCount(rCandidate.count());
+
+ // use same condition as above, the data vector is
+ // pre-allocated
+ if(nCandCount)
+ {
+ impAddPolygon(nInsertIndex, rCandidate);
+ nInsertIndex += nCandCount;
+ }
+ }
+
+ // solve common nodes
+ impSolve();
+ }
+
+ B2DPolyPolygon getB2DPolyPolygon()
+ {
+ if(mbChanged)
+ {
+ B2DPolyPolygon aRetval;
+ const sal_uInt32 nCount(maPNV.size());
+ sal_uInt32 nCountdown(nCount);
+
+ for(sal_uInt32 a(0); nCountdown && a < nCount; a++)
+ {
+ PN& rPN = maPNV[a];
+
+ if(rPN.mnI != SAL_MAX_UINT32)
+ {
+ // unused node, start new part polygon
+ B2DPolygon aNewPart;
+ PN* pPNCurr = &rPN;
+
+ do
+ {
+ const B2DPoint& rPoint = pPNCurr->maPoint;
+ aNewPart.append(rPoint);
+
+ if(mbIsCurve)
+ {
+ const VN& rVNCurr = maVNV[pPNCurr->mnI];
+
+ if(!rVNCurr.maPrev.equalZero())
+ {
+ aNewPart.setPrevControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maPrev);
+ }
+
+ if(!rVNCurr.maNext.equalZero())
+ {
+ aNewPart.setNextControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maNext);
+ }
+ }
+
+ pPNCurr->mnI = SAL_MAX_UINT32;
+ nCountdown--;
+ pPNCurr = &(maPNV[pPNCurr->mnIN]);
+ }
+ while(pPNCurr != &rPN && pPNCurr->mnI != SAL_MAX_UINT32);
+
+ // close and add
+ aNewPart.setClosed(true);
+ aRetval.append(aNewPart);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ const sal_uInt32 nCorrectionSize(maCorrectionTable.size());
+
+ // no change, return original
+ if(!nCorrectionSize)
+ {
+ return maOriginal;
+ }
+
+ // apply coordinate corrections to ensure inside/outside correctness after solving
+ const sal_uInt32 nPolygonCount(maOriginal.count());
+ basegfx::B2DPolyPolygon aRetval(maOriginal);
+
+ for(sal_uInt32 a(0); a < nPolygonCount; a++)
+ {
+ basegfx::B2DPolygon aTemp(aRetval.getB2DPolygon(a));
+ const sal_uInt32 nPointCount(aTemp.count());
+ bool bChanged(false);
+
+ for(sal_uInt32 b(0); b < nPointCount; b++)
+ {
+ const basegfx::B2DPoint aCandidate(aTemp.getB2DPoint(b));
+
+ for(sal_uInt32 c(0); c < nCorrectionSize; c++)
+ {
+ if(maCorrectionTable[c].first.getX() == aCandidate.getX() && maCorrectionTable[c].first.getY() == aCandidate.getY())
+ {
+ aTemp.setB2DPoint(b, maCorrectionTable[c].second);
+ bChanged = true;
+ }
+ }
+ }
+
+ if(bChanged)
+ {
+ aRetval.setB2DPolygon(a, aTemp);
+ }
+ }
+
+ return aRetval;
+ }
+ }
+ };
+
+ } // end of anonymous namespace
+} // end of namespace basegfx
+
+namespace basegfx::utils
+{
+
+ B2DPolyPolygon solveCrossovers(const B2DPolyPolygon& rCandidate, size_t* pPointLimit)
+ {
+ if(rCandidate.count() > 0)
+ {
+ solver aSolver(rCandidate, pPointLimit);
+ return aSolver.getB2DPolyPolygon();
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolyPolygon solveCrossovers(const B2DPolygon& rCandidate)
+ {
+ solver aSolver(rCandidate);
+ return aSolver.getB2DPolyPolygon();
+ }
+
+ B2DPolyPolygon stripNeutralPolygons(const B2DPolyPolygon& rCandidate)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(const auto& rPolygon : rCandidate)
+ {
+ if(utils::getOrientation(rPolygon) != B2VectorOrientation::Neutral)
+ {
+ aRetval.append(rPolygon);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon createNonzeroConform(const B2DPolyPolygon& rCandidate)
+ {
+ if (rCandidate.count() > 1000)
+ {
+ SAL_WARN("basegfx", "this poly is too large, " << rCandidate.count() << " elements, to be able to process timeously, falling back to ignoring the winding rule, which is likely to cause rendering artifacts");
+ return rCandidate;
+ }
+
+ B2DPolyPolygon aCandidate;
+
+ // remove all self-intersections and intersections
+ if(rCandidate.count() == 1)
+ {
+ aCandidate = basegfx::utils::solveCrossovers(rCandidate.getB2DPolygon(0));
+ }
+ else
+ {
+ aCandidate = basegfx::utils::solveCrossovers(rCandidate);
+ }
+
+ // cleanup evtl. neutral polygons
+ aCandidate = basegfx::utils::stripNeutralPolygons(aCandidate);
+
+ // remove all polygons which have the same orientation as the polygon they are directly contained in
+ const sal_uInt32 nCount(aCandidate.count());
+
+ if(nCount > 1)
+ {
+ sal_uInt32 a, b;
+ std::vector< StripHelper > aHelpers;
+ aHelpers.resize(nCount);
+
+ for(a = 0; a < nCount; a++)
+ {
+ const B2DPolygon& aCand(aCandidate.getB2DPolygon(a));
+ StripHelper* pNewHelper = &(aHelpers[a]);
+ pNewHelper->maRange = utils::getRange(aCand);
+ pNewHelper->meOrinetation = utils::getOrientation(aCand);
+
+ // initialize with own orientation
+ pNewHelper->mnDepth = (pNewHelper->meOrinetation == B2VectorOrientation::Negative ? -1 : 1);
+ }
+
+ for(a = 0; a < nCount - 1; a++)
+ {
+ const B2DPolygon& aCandA(aCandidate.getB2DPolygon(a));
+ StripHelper& rHelperA = aHelpers[a];
+
+ for(b = a + 1; b < nCount; b++)
+ {
+ const B2DPolygon& aCandB(aCandidate.getB2DPolygon(b));
+ StripHelper& rHelperB = aHelpers[b];
+ const bool bAInB(rHelperB.maRange.isInside(rHelperA.maRange) && utils::isInside(aCandB, aCandA, true));
+
+ if(bAInB)
+ {
+ // A is inside B, add orientation of B to A
+ rHelperA.mnDepth += (rHelperB.meOrinetation == B2VectorOrientation::Negative ? -1 : 1);
+ }
+
+ const bool bBInA(rHelperA.maRange.isInside(rHelperB.maRange) && utils::isInside(aCandA, aCandB, true));
+
+ if(bBInA)
+ {
+ // B is inside A, add orientation of A to B
+ rHelperB.mnDepth += (rHelperA.meOrinetation == B2VectorOrientation::Negative ? -1 : 1);
+ }
+ }
+ }
+
+ const B2DPolyPolygon aSource(aCandidate);
+ aCandidate.clear();
+
+ for(a = 0; a < nCount; a++)
+ {
+ const StripHelper& rHelper = aHelpers[a];
+ // for contained unequal oriented polygons sum will be 0
+ // for contained equal it will be >=2 or <=-2
+ // for free polygons (not contained) it will be 1 or -1
+ // -> accept all which are >=-1 && <= 1
+ bool bAcceptEntry(rHelper.mnDepth >= -1 && rHelper.mnDepth <= 1);
+
+ if(bAcceptEntry)
+ {
+ aCandidate.append(aSource.getB2DPolygon(a));
+ }
+ }
+ }
+
+ return aCandidate;
+ }
+
+ B2DPolyPolygon stripDispensablePolygons(const B2DPolyPolygon& rCandidate, bool bKeepAboveZero)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+ B2DPolyPolygon aRetval;
+
+ if(nCount)
+ {
+ if(nCount == 1)
+ {
+ if(!bKeepAboveZero && utils::getOrientation(rCandidate.getB2DPolygon(0)) == B2VectorOrientation::Positive)
+ {
+ aRetval = rCandidate;
+ }
+ }
+ else
+ {
+ sal_uInt32 a, b;
+ std::vector< StripHelper > aHelpers;
+ aHelpers.resize(nCount);
+
+ for(a = 0; a < nCount; a++)
+ {
+ const B2DPolygon& aCandidate(rCandidate.getB2DPolygon(a));
+ StripHelper* pNewHelper = &(aHelpers[a]);
+ pNewHelper->maRange = utils::getRange(aCandidate);
+ pNewHelper->meOrinetation = utils::getOrientation(aCandidate);
+ pNewHelper->mnDepth = (pNewHelper->meOrinetation == B2VectorOrientation::Negative ? -1 : 0);
+ }
+
+ for(a = 0; a < nCount - 1; a++)
+ {
+ const B2DPolygon& aCandA(rCandidate.getB2DPolygon(a));
+ StripHelper& rHelperA = aHelpers[a];
+
+ for(b = a + 1; b < nCount; b++)
+ {
+ const B2DPolygon& aCandB(rCandidate.getB2DPolygon(b));
+ StripHelper& rHelperB = aHelpers[b];
+ const bool bAInB(rHelperB.maRange.isInside(rHelperA.maRange) && utils::isInside(aCandB, aCandA, true));
+ const bool bBInA(rHelperA.maRange.isInside(rHelperB.maRange) && utils::isInside(aCandA, aCandB, true));
+
+ if(bAInB && bBInA)
+ {
+ // congruent
+ if(rHelperA.meOrinetation == rHelperB.meOrinetation)
+ {
+ // two polys or two holes. Lower one of them to get one of them out of the way.
+ // Since each will be contained in the other one, both will be increased, too.
+ // So, for lowering, increase only one of them
+ rHelperA.mnDepth++;
+ }
+ else
+ {
+ // poly and hole. They neutralize, so get rid of both. Move securely below zero.
+ rHelperA.mnDepth = - static_cast<sal_Int32>(nCount);
+ rHelperB.mnDepth = - static_cast<sal_Int32>(nCount);
+ }
+ }
+ else
+ {
+ if(bAInB)
+ {
+ if(rHelperB.meOrinetation == B2VectorOrientation::Negative)
+ {
+ rHelperA.mnDepth--;
+ }
+ else
+ {
+ rHelperA.mnDepth++;
+ }
+ }
+ else if(bBInA)
+ {
+ if(rHelperA.meOrinetation == B2VectorOrientation::Negative)
+ {
+ rHelperB.mnDepth--;
+ }
+ else
+ {
+ rHelperB.mnDepth++;
+ }
+ }
+ }
+ }
+ }
+
+ for(a = 0; a < nCount; a++)
+ {
+ const StripHelper& rHelper = aHelpers[a];
+ bool bAcceptEntry(bKeepAboveZero ? 1 <= rHelper.mnDepth : rHelper.mnDepth == 0);
+
+ if(bAcceptEntry)
+ {
+ aRetval.append(rCandidate.getB2DPolygon(a));
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon prepareForPolygonOperation(const B2DPolygon& rCandidate)
+ {
+ solver aSolver(rCandidate);
+ B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon()));
+
+ return correctOrientations(aRetval);
+ }
+
+ B2DPolyPolygon prepareForPolygonOperation(const B2DPolyPolygon& rCandidate)
+ {
+ solver aSolver(rCandidate);
+ B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon()));
+
+ return correctOrientations(aRetval);
+ }
+
+ B2DPolyPolygon solvePolygonOperationOr(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
+ {
+ if(!rCandidateA.count())
+ {
+ return rCandidateB;
+ }
+ else if(!rCandidateB.count())
+ {
+ return rCandidateA;
+ }
+ else
+ {
+ // concatenate polygons, solve crossovers and throw away all sub-polygons
+ // which have a depth other than 0.
+ B2DPolyPolygon aRetval(rCandidateA);
+
+ aRetval.append(rCandidateB);
+ aRetval = solveCrossovers(aRetval);
+ aRetval = stripNeutralPolygons(aRetval);
+
+ return stripDispensablePolygons(aRetval);
+ }
+ }
+
+ B2DPolyPolygon solvePolygonOperationXor(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
+ {
+ if(!rCandidateA.count())
+ {
+ return rCandidateB;
+ }
+ else if(!rCandidateB.count())
+ {
+ return rCandidateA;
+ }
+ else
+ {
+ // XOR is pretty simple: By definition it is the simple concatenation of
+ // the single polygons since we imply XOR fill rule. Make it intersection-free
+ // and correct orientations
+ B2DPolyPolygon aRetval(rCandidateA);
+
+ aRetval.append(rCandidateB);
+ aRetval = solveCrossovers(aRetval);
+ aRetval = stripNeutralPolygons(aRetval);
+
+ return correctOrientations(aRetval);
+ }
+ }
+
+ B2DPolyPolygon solvePolygonOperationAnd(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
+ {
+ if(!rCandidateA.count())
+ {
+ return B2DPolyPolygon();
+ }
+ else if(!rCandidateB.count())
+ {
+ return B2DPolyPolygon();
+ }
+ else
+ {
+ // tdf#130150 shortcut & precision: If both are simple ranges,
+ // solve based on ranges
+ if(basegfx::utils::isRectangle(rCandidateA) && basegfx::utils::isRectangle(rCandidateB))
+ {
+ // *if* both are ranges, AND always can be solved
+ const basegfx::B2DRange aRangeA(rCandidateA.getB2DRange());
+ const basegfx::B2DRange aRangeB(rCandidateB.getB2DRange());
+
+ if(aRangeA.isInside(aRangeB))
+ {
+ // 2nd completely inside 1st -> 2nd is result of AND
+ return rCandidateB;
+ }
+
+ if(aRangeB.isInside(aRangeA))
+ {
+ // 2nd completely inside 1st -> 2nd is result of AND
+ return rCandidateA;
+ }
+
+ // solve by intersection
+ basegfx::B2DRange aIntersect(aRangeA);
+ aIntersect.intersect(aRangeB);
+
+ if(aIntersect.isEmpty())
+ {
+ // no overlap -> empty polygon as result of AND
+ return B2DPolyPolygon();
+ }
+
+ // create polygon result
+ return B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(
+ aIntersect));
+ }
+
+ // concatenate polygons, solve crossovers and throw away all sub-polygons
+ // with a depth of < 1. This means to keep all polygons where at least two
+ // polygons do overlap.
+ B2DPolyPolygon aRetval(rCandidateA);
+
+ aRetval.append(rCandidateB);
+ aRetval = solveCrossovers(aRetval);
+ aRetval = stripNeutralPolygons(aRetval);
+
+ return stripDispensablePolygons(aRetval, true);
+ }
+ }
+
+ B2DPolyPolygon solvePolygonOperationDiff(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
+ {
+ if(!rCandidateA.count())
+ {
+ return B2DPolyPolygon();
+ }
+ else if(!rCandidateB.count())
+ {
+ return rCandidateA;
+ }
+ else
+ {
+ // Make B topologically to holes and append to A
+ B2DPolyPolygon aRetval(rCandidateB);
+
+ aRetval.flip();
+ aRetval.append(rCandidateA);
+
+ // solve crossovers and throw away all sub-polygons which have a
+ // depth other than 0.
+ aRetval = basegfx::utils::solveCrossovers(aRetval);
+ aRetval = basegfx::utils::stripNeutralPolygons(aRetval);
+
+ return basegfx::utils::stripDispensablePolygons(aRetval);
+ }
+ }
+
+ B2DPolyPolygon mergeToSinglePolyPolygon(const B2DPolyPolygonVector& rInput)
+ {
+ if(rInput.empty())
+ return B2DPolyPolygon();
+
+ // first step: prepareForPolygonOperation and simple merge of non-overlapping
+ // PolyPolygons for speedup; this is possible for the wanted OR-operation
+ B2DPolyPolygonVector aResult;
+ aResult.reserve(rInput.size());
+
+ for(const basegfx::B2DPolyPolygon & a : rInput)
+ {
+ const basegfx::B2DPolyPolygon aCandidate(prepareForPolygonOperation(a));
+
+ if(!aResult.empty())
+ {
+ const B2DRange aCandidateRange(aCandidate.getB2DRange());
+ bool bCouldMergeSimple(false);
+
+ for(auto & b: aResult)
+ {
+ basegfx::B2DPolyPolygon aTarget(b);
+ const B2DRange aTargetRange(aTarget.getB2DRange());
+
+ if(!aCandidateRange.overlaps(aTargetRange))
+ {
+ aTarget.append(aCandidate);
+ b = aTarget;
+ bCouldMergeSimple = true;
+ break;
+ }
+ }
+
+ if(!bCouldMergeSimple)
+ {
+ aResult.push_back(aCandidate);
+ }
+ }
+ else
+ {
+ aResult.push_back(aCandidate);
+ }
+ }
+
+ // second step: melt pairwise to a single PolyPolygon
+ while(aResult.size() > 1)
+ {
+ B2DPolyPolygonVector aResult2;
+ aResult2.reserve((aResult.size() / 2) + 1);
+
+ for(size_t a(0); a < aResult.size(); a += 2)
+ {
+ if(a + 1 < aResult.size())
+ {
+ // a pair for processing
+ aResult2.push_back(solvePolygonOperationOr(aResult[a], aResult[a + 1]));
+ }
+ else
+ {
+ // last single PolyPolygon; copy to target to not lose it
+ aResult2.push_back(aResult[a]);
+ }
+ }
+
+ aResult = aResult2;
+ }
+
+ // third step: get result
+ if(aResult.size() == 1)
+ {
+ return aResult[0];
+ }
+
+ return B2DPolyPolygon();
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dpolypolygontools.cxx b/basegfx/source/polygon/b2dpolypolygontools.cxx
new file mode 100644
index 0000000000..faf734f6e7
--- /dev/null
+++ b/basegfx/source/polygon/b2dpolypolygontools.cxx
@@ -0,0 +1,657 @@
+/* -*- 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 <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <osl/diagnose.h>
+#include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <rtl/math.hxx>
+
+#include <algorithm>
+#include <numeric>
+
+namespace basegfx::utils
+{
+ B2DPolyPolygon correctOrientations(const B2DPolyPolygon& rCandidate)
+ {
+ B2DPolyPolygon aRetval(rCandidate);
+ const sal_uInt32 nCount(aRetval.count());
+
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ const B2DPolygon& aCandidate(rCandidate.getB2DPolygon(a));
+ const B2VectorOrientation aOrientation(utils::getOrientation(aCandidate));
+ sal_uInt32 nDepth(0);
+
+ for(sal_uInt32 b(0); b < nCount; b++)
+ {
+ if(b != a)
+ {
+ const B2DPolygon& aCompare(rCandidate.getB2DPolygon(b));
+
+ if(utils::isInside(aCompare, aCandidate, true))
+ {
+ nDepth++;
+ }
+ }
+ }
+
+ const bool bShallBeHole((nDepth & 0x00000001) == 1);
+ const bool bIsHole(aOrientation == B2VectorOrientation::Negative);
+
+ if(bShallBeHole != bIsHole && aOrientation != B2VectorOrientation::Neutral)
+ {
+ B2DPolygon aFlipped(aCandidate);
+ aFlipped.flip();
+ aRetval.setB2DPolygon(a, aFlipped);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon correctOutmostPolygon(const B2DPolyPolygon& rCandidate)
+ {
+ const sal_uInt32 nCount(rCandidate.count());
+
+ if(nCount > 1)
+ {
+ for(sal_uInt32 a(0); a < nCount; a++)
+ {
+ const B2DPolygon& aCandidate(rCandidate.getB2DPolygon(a));
+ sal_uInt32 nDepth(0);
+
+ for(sal_uInt32 b(0); b < nCount; b++)
+ {
+ if(b != a)
+ {
+ const B2DPolygon& aCompare(rCandidate.getB2DPolygon(b));
+
+ if(utils::isInside(aCompare, aCandidate, true))
+ {
+ nDepth++;
+ }
+ }
+ }
+
+ if(!nDepth)
+ {
+ B2DPolyPolygon aRetval(rCandidate);
+
+ if(a != 0)
+ {
+ // exchange polygon a and polygon 0
+ aRetval.setB2DPolygon(0, aCandidate);
+ aRetval.setB2DPolygon(a, rCandidate.getB2DPolygon(0));
+ }
+
+ // exit
+ return aRetval;
+ }
+ }
+ }
+
+ return rCandidate;
+ }
+
+ B2DPolyPolygon adaptiveSubdivideByDistance(const B2DPolyPolygon& rCandidate, double fDistanceBound, int nRecurseLimit)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ B2DPolyPolygon aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ if(rPolygon.areControlPointsUsed())
+ {
+ aRetval.append(utils::adaptiveSubdivideByDistance(rPolygon, fDistanceBound, nRecurseLimit));
+ }
+ else
+ {
+ aRetval.append(rPolygon);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolyPolygon adaptiveSubdivideByAngle(const B2DPolyPolygon& rCandidate, double fAngleBound)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ B2DPolyPolygon aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ if(rPolygon.areControlPointsUsed())
+ {
+ aRetval.append(utils::adaptiveSubdivideByAngle(rPolygon, fAngleBound));
+ }
+ else
+ {
+ aRetval.append(rPolygon);
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ bool isInside(const B2DPolyPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder)
+ {
+ if(rCandidate.count() == 1)
+ {
+ return isInside(rCandidate.getB2DPolygon(0), rPoint, bWithBorder);
+ }
+ else
+ {
+ sal_Int32 nInsideCount = std::count_if(rCandidate.begin(), rCandidate.end(), [rPoint, bWithBorder](B2DPolygon polygon){ return isInside(polygon, rPoint, bWithBorder); });
+
+ return (nInsideCount % 2);
+ }
+ }
+
+ B2DRange getRange(const B2DPolyPolygon& rCandidate)
+ {
+ B2DRange aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ aRetval.expand(utils::getRange(rPolygon));
+ }
+
+ return aRetval;
+ }
+
+ double getSignedArea(const B2DPolyPolygon& rCandidate)
+ {
+ double fRetval(0.0);
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ fRetval += utils::getSignedArea(rPolygon);
+ }
+
+ return fRetval;
+ }
+
+ double getArea(const B2DPolyPolygon& rCandidate)
+ {
+ return fabs(getSignedArea(rCandidate));
+ }
+
+ void applyLineDashing(const B2DPolyPolygon& rCandidate, const std::vector<double>& rDotDashArray, B2DPolyPolygon* pLineTarget, double fFullDashDotLen)
+ {
+ if(fFullDashDotLen == 0.0 && !rDotDashArray.empty())
+ {
+ // calculate fFullDashDotLen from rDotDashArray
+ fFullDashDotLen = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
+ }
+
+ if(!(rCandidate.count() && fFullDashDotLen > 0.0))
+ return;
+
+ B2DPolyPolygon aLineTarget;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ applyLineDashing(
+ rPolygon,
+ rDotDashArray,
+ pLineTarget ? &aLineTarget : nullptr,
+ nullptr,
+ fFullDashDotLen);
+
+ if(pLineTarget)
+ {
+ pLineTarget->append(aLineTarget);
+ }
+ }
+ }
+
+ bool isInEpsilonRange(const B2DPolyPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance)
+ {
+ for(auto const& rPolygon : rCandidate)
+ {
+ if(isInEpsilonRange(rPolygon, rTestPosition, fDistance))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ B3DPolyPolygon createB3DPolyPolygonFromB2DPolyPolygon(const B2DPolyPolygon& rCandidate, double fZCoordinate)
+ {
+ B3DPolyPolygon aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ aRetval.append(createB3DPolygonFromB2DPolygon(rPolygon, fZCoordinate));
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon createB2DPolyPolygonFromB3DPolyPolygon(const B3DPolyPolygon& rCandidate, const B3DHomMatrix& rMat)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ aRetval.append(createB2DPolygonFromB3DPolygon(rPolygon, rMat));
+ }
+
+ return aRetval;
+ }
+
+ double getSmallestDistancePointToPolyPolygon(const B2DPolyPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rPolygonIndex, sal_uInt32& rEdgeIndex, double& rCut)
+ {
+ double fRetval(DBL_MAX);
+ const double fZero(0.0);
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+
+ for(sal_uInt32 a(0); a < nPolygonCount; a++)
+ {
+ const B2DPolygon& aCandidate(rCandidate.getB2DPolygon(a));
+ sal_uInt32 nNewEdgeIndex;
+ double fNewCut(0.0);
+ const double fNewDistance(getSmallestDistancePointToPolygon(aCandidate, rTestPoint, nNewEdgeIndex, fNewCut));
+
+ if(fRetval == DBL_MAX || fNewDistance < fRetval)
+ {
+ fRetval = fNewDistance;
+ rPolygonIndex = a;
+ rEdgeIndex = nNewEdgeIndex;
+ rCut = fNewCut;
+
+ if(fTools::equal(fRetval, fZero))
+ {
+ // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
+ fRetval = 0.0;
+ break;
+ }
+ }
+ }
+
+ return fRetval;
+ }
+
+ B2DPolyPolygon distort(const B2DPolyPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ aRetval.append(distort(rPolygon, rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon expandToCurve(const B2DPolyPolygon& rCandidate)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ aRetval.append(expandToCurve(rPolygon));
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon growInNormalDirection(const B2DPolyPolygon& rCandidate, double fValue)
+ {
+ if(fValue != 0.0)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ aRetval.append(growInNormalDirection(rPolygon, fValue));
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolyPolygon reSegmentPolyPolygon(const B2DPolyPolygon& rCandidate, sal_uInt32 nSegments)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ aRetval.append(reSegmentPolygon(rPolygon, nSegments));
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon interpolate(const B2DPolyPolygon& rOld1, const B2DPolyPolygon& rOld2, double t)
+ {
+ OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolyPolygon interpolate: Different geometry (!)");
+ B2DPolyPolygon aRetval;
+
+ for(sal_uInt32 a(0); a < rOld1.count(); a++)
+ {
+ aRetval.append(interpolate(rOld1.getB2DPolygon(a), rOld2.getB2DPolygon(a), t));
+ }
+
+ return aRetval;
+ }
+
+ bool isRectangle( const B2DPolyPolygon& rPoly )
+ {
+ // exclude some cheap cases first
+ if( rPoly.count() != 1 )
+ return false;
+
+ return isRectangle( rPoly.getB2DPolygon(0) );
+ }
+
+ // #i76891#
+ B2DPolyPolygon simplifyCurveSegments(const B2DPolyPolygon& rCandidate)
+ {
+ if(rCandidate.areControlPointsUsed())
+ {
+ B2DPolyPolygon aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ aRetval.append(simplifyCurveSegments(rPolygon));
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+ B2DPolyPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolyPolygon& rCandidate)
+ {
+ B2DPolyPolygon aRetval;
+
+ for(auto const& rPolygon : rCandidate)
+ {
+ aRetval.append(snapPointsOfHorizontalOrVerticalEdges(rPolygon));
+ }
+
+ return aRetval;
+ }
+
+ B2DPolyPolygon createSevenSegmentPolyPolygon(char nNumber, bool bLitSegments)
+ {
+ // config here
+ // {
+ const double fTotalSize=1.0;
+ const double fPosMiddleSegment=0.6;
+ const double fSegmentEndChopHoriz=0.08;
+ const double fSegmentEndChopVert =0.04;
+ // }
+ // config here
+
+ const double fLeft=0.0;
+ const double fRight=fTotalSize;
+ const double fTop=0.0;
+ const double fMiddle=fPosMiddleSegment;
+ const double fBottom=fTotalSize;
+
+ // from 0 to 5: pair of segment corner coordinates
+
+ // segment corner indices are these:
+
+ // 0 - 1
+ // | |
+ // 2 - 3
+ // | |
+ // 4 - 5
+
+ static const double corners[] =
+ {
+ fLeft, fTop,
+ fRight, fTop,
+ fLeft, fMiddle,
+ fRight, fMiddle,
+ fLeft, fBottom,
+ fRight, fBottom
+ };
+
+ // from 0 to 9: which segments are 'lit' for this number?
+
+ // array denotes graph edges to traverse, with -1 means
+ // stop (the vertices are the corner indices from above):
+ // 0
+ // -
+ // 1 | | 2
+ // - 3
+ // 4 | | 5
+ // -
+ // 6
+
+ static const int numbers[] =
+ {
+ 1, 1, 1, 0, 1, 1, 1, // 0
+ 0, 0, 1, 0, 0, 1, 0, // 1
+ 1, 0, 1, 1, 1, 0, 1, // 2
+ 1, 0, 1, 1, 0, 1, 1, // 3
+ 0, 1, 1, 1, 0, 1, 0, // 4
+ 1, 1, 0, 1, 0, 1, 1, // 5
+ 1, 1, 0, 1, 1, 1, 1, // 6
+ 1, 0, 1, 0, 0, 1, 0, // 1
+ 1, 1, 1, 1, 1, 1, 1, // 8
+ 1, 1, 1, 1, 0, 1, 1, // 9
+ 0, 0, 0, 1, 0, 0, 0, // '-'
+ 1, 1, 0, 1, 1, 0, 1, // 'E'
+ };
+
+ // maps segment index to two corner ids:
+ static const int index2corner[] =
+ {
+ 0, 2, // 0
+ 0, 4, // 1
+ 2, 6, // 2
+ 4, 6, // 3
+ 4, 8, // 4
+ 6, 10, // 5
+ 8, 10, // 6
+ };
+
+ B2DPolyPolygon aRes;
+ if( nNumber == '-' )
+ {
+ nNumber = 10;
+ }
+ else if( nNumber == 'E' )
+ {
+ nNumber = 11;
+ }
+ else if( nNumber == '.' )
+ {
+ if( bLitSegments )
+ aRes.append(createPolygonFromCircle(B2DPoint(fTotalSize/2, fTotalSize),
+ fSegmentEndChopHoriz));
+ return aRes;
+ }
+ else
+ {
+ nNumber=std::clamp<sal_uInt32>(nNumber,'0','9') - '0';
+ }
+
+ B2DPolygon aCurrSegment;
+ const size_t sliceSize=std::size(numbers)/12;
+ const int* pCurrSegment=numbers + nNumber*sliceSize;
+ for( size_t i=0; i<sliceSize; i++, pCurrSegment++)
+ {
+ if( !(*pCurrSegment ^ int(bLitSegments)) )
+ {
+ const size_t j=2*i;
+ aCurrSegment.clear();
+ B2DPoint start(corners[index2corner[j]],
+ corners[index2corner[j]+1] );
+ B2DPoint end (corners[index2corner[j+1]],
+ corners[index2corner[j+1]+1]);
+
+ if( rtl::math::approxEqual(start.getX(), end.getX()) )
+ {
+ start.setY(start.getY()+fSegmentEndChopVert);
+ end.setY(end.getY()-fSegmentEndChopVert);
+ }
+ else
+ {
+ start.setX(start.getX()+fSegmentEndChopHoriz);
+ end.setX(end.getX()-fSegmentEndChopHoriz);
+ }
+
+ aCurrSegment.append(start);
+ aCurrSegment.append(end);
+ }
+ aRes.append(aCurrSegment);
+ }
+
+ return aRes;
+ }
+
+ // converters for css::drawing::PointSequence
+
+ B2DPolyPolygon UnoPointSequenceSequenceToB2DPolyPolygon(
+ const css::drawing::PointSequenceSequence& rPointSequenceSequenceSource)
+ {
+ B2DPolyPolygon aRetval;
+ aRetval.reserve(rPointSequenceSequenceSource.getLength());
+ const css::drawing::PointSequence* pPointSequence = rPointSequenceSequenceSource.getConstArray();
+ const css::drawing::PointSequence* pPointSeqEnd = pPointSequence + rPointSequenceSequenceSource.getLength();
+
+ for(;pPointSequence != pPointSeqEnd; pPointSequence++)
+ {
+ const B2DPolygon aNewPolygon = UnoPointSequenceToB2DPolygon(*pPointSequence);
+ aRetval.append(aNewPolygon);
+ }
+
+ return aRetval;
+ }
+
+ void B2DPolyPolygonToUnoPointSequenceSequence(
+ const B2DPolyPolygon& rPolyPolygon,
+ css::drawing::PointSequenceSequence& rPointSequenceSequenceRetval)
+ {
+ const sal_uInt32 nCount(rPolyPolygon.count());
+
+ if(nCount)
+ {
+ rPointSequenceSequenceRetval.realloc(nCount);
+ css::drawing::PointSequence* pPointSequence = rPointSequenceSequenceRetval.getArray();
+
+ for(auto const& rPolygon : rPolyPolygon)
+ {
+ B2DPolygonToUnoPointSequence(rPolygon, *pPointSequence);
+ pPointSequence++;
+ }
+ }
+ else
+ {
+ rPointSequenceSequenceRetval.realloc(0);
+ }
+ }
+
+ // converters for css::drawing::PolyPolygonBezierCoords (curved polygons)
+
+ B2DPolyPolygon UnoPolyPolygonBezierCoordsToB2DPolyPolygon(
+ const css::drawing::PolyPolygonBezierCoords& rPolyPolygonBezierCoordsSource)
+ {
+ B2DPolyPolygon aRetval;
+ const sal_uInt32 nSequenceCount(static_cast<sal_uInt32>(rPolyPolygonBezierCoordsSource.Coordinates.getLength()));
+
+ if(nSequenceCount)
+ {
+ OSL_ENSURE(nSequenceCount == static_cast<sal_uInt32>(rPolyPolygonBezierCoordsSource.Flags.getLength()),
+ "UnoPolyPolygonBezierCoordsToB2DPolyPolygon: unequal number of Points and Flags (!)");
+ const css::drawing::PointSequence* pPointSequence = rPolyPolygonBezierCoordsSource.Coordinates.getConstArray();
+ const css::drawing::FlagSequence* pFlagSequence = rPolyPolygonBezierCoordsSource.Flags.getConstArray();
+
+ for(sal_uInt32 a(0); a < nSequenceCount; a++)
+ {
+ const B2DPolygon aNewPolygon(UnoPolygonBezierCoordsToB2DPolygon(
+ *pPointSequence,
+ *pFlagSequence));
+
+ pPointSequence++;
+ pFlagSequence++;
+ aRetval.append(aNewPolygon);
+ }
+ }
+
+ return aRetval;
+ }
+
+ void B2DPolyPolygonToUnoPolyPolygonBezierCoords(
+ const B2DPolyPolygon& rPolyPolygon,
+ css::drawing::PolyPolygonBezierCoords& rPolyPolygonBezierCoordsRetval)
+ {
+ const sal_uInt32 nCount(rPolyPolygon.count());
+
+ if(nCount)
+ {
+ // prepare return value memory
+ rPolyPolygonBezierCoordsRetval.Coordinates.realloc(static_cast<sal_Int32>(nCount));
+ rPolyPolygonBezierCoordsRetval.Flags.realloc(static_cast<sal_Int32>(nCount));
+
+ // get pointers to arrays
+ css::drawing::PointSequence* pPointSequence = rPolyPolygonBezierCoordsRetval.Coordinates.getArray();
+ css::drawing::FlagSequence* pFlagSequence = rPolyPolygonBezierCoordsRetval.Flags.getArray();
+
+ for(auto const& rSource : rPolyPolygon)
+ {
+ B2DPolygonToUnoPolygonBezierCoords(
+ rSource,
+ *pPointSequence,
+ *pFlagSequence);
+ pPointSequence++;
+ pFlagSequence++;
+ }
+ }
+ else
+ {
+ rPolyPolygonBezierCoordsRetval.Coordinates.realloc(0);
+ rPolyPolygonBezierCoordsRetval.Flags.realloc(0);
+ }
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dsvgpolypolygon.cxx b/basegfx/source/polygon/b2dsvgpolypolygon.cxx
new file mode 100644
index 0000000000..fe4f646eb3
--- /dev/null
+++ b/basegfx/source/polygon/b2dsvgpolypolygon.cxx
@@ -0,0 +1,931 @@
+/* -*- 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 <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <rtl/math.hxx>
+#include <rtl/character.hxx>
+#include <stringconversiontools.hxx>
+
+namespace
+{
+
+void putCommandChar(OUStringBuffer& rBuffer,sal_Unicode& rLastSVGCommand, sal_Unicode aChar, bool bToLower,bool bVerbose)
+{
+ const sal_Unicode aCommand = bToLower ? rtl::toAsciiLowerCase(aChar) : aChar;
+
+ if (bVerbose && rBuffer.getLength())
+ rBuffer.append(' ');
+
+ if (bVerbose || rLastSVGCommand != aCommand)
+ {
+ rBuffer.append(aCommand);
+ rLastSVGCommand = aCommand;
+ }
+}
+
+void putNumberChar(OUStringBuffer& rStr,double fValue, double fOldValue, bool bUseRelativeCoordinates,bool bVerbose)
+{
+ if (bUseRelativeCoordinates)
+ fValue -= fOldValue;
+
+ const sal_Int32 aLen(rStr.getLength());
+ if (bVerbose || (aLen && basegfx::internal::isOnNumberChar(rStr[aLen - 1], false) && fValue >= 0.0))
+ rStr.append(' ');
+
+ rStr.append(fValue);
+}
+
+}
+
+namespace basegfx::utils
+{
+ bool PointIndex::operator<(const PointIndex& rComp) const
+ {
+ if(rComp.getPolygonIndex() == getPolygonIndex())
+ {
+ return rComp.getPointIndex() < getPointIndex();
+ }
+
+ return rComp.getPolygonIndex() < getPolygonIndex();
+ }
+
+ bool importFromSvgD(
+ B2DPolyPolygon& o_rPolyPolygon,
+ std::u16string_view rSvgDStatement,
+ bool bHandleRelativeNextPointCompatible,
+ PointIndexSet* pHelpPointIndexSet)
+ {
+ o_rPolyPolygon.clear();
+ const sal_Int32 nLen(rSvgDStatement.size());
+ sal_Int32 nPos(0);
+ double nLastX( 0.0 );
+ double nLastY( 0.0 );
+ B2DPolygon aCurrPoly;
+
+ // skip initial whitespace
+ basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen)
+ {
+ bool bRelative(false);
+ const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
+
+ if(o_rPolyPolygon.count() && !aCurrPoly.count() && aCurrChar != 'm' && aCurrChar != 'M')
+ {
+ // we have a new sub-polygon starting, but without a 'moveto' command.
+ // this requires to add the current point as start point to the polygon
+ // (see SVG1.1 8.3.3 The "closepath" command)
+ aCurrPoly.append(B2DPoint(nLastX, nLastY));
+ }
+
+ switch(aCurrChar)
+ {
+ case 'z' :
+ case 'Z' :
+ {
+ // consume CurrChar and whitespace
+ nPos++;
+ basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
+
+ // create closed polygon and reset import values
+ if(aCurrPoly.count())
+ {
+ if(!bHandleRelativeNextPointCompatible)
+ {
+ // SVG defines that "the next subpath starts at the
+ // same initial point as the current subpath", so set the
+ // current point if we do not need to be compatible
+ nLastX = aCurrPoly.getB2DPoint(0).getX();
+ nLastY = aCurrPoly.getB2DPoint(0).getY();
+ }
+
+ aCurrPoly.setClosed(true);
+ o_rPolyPolygon.append(aCurrPoly);
+ aCurrPoly.clear();
+ }
+
+ break;
+ }
+
+ case 'm' :
+ case 'M' :
+ {
+ // create non-closed polygon and reset import values
+ if(aCurrPoly.count())
+ {
+ o_rPolyPolygon.append(aCurrPoly);
+ aCurrPoly.clear();
+ }
+ [[fallthrough]]; // to add coordinate data as 1st point of new polygon
+ }
+ case 'l' :
+ case 'L' :
+ {
+ if(aCurrChar == 'm' || aCurrChar == 'l')
+ {
+ bRelative = true;
+ }
+
+ // consume CurrChar and whitespace
+ nPos++;
+ basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+
+ if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+
+ // add point
+ aCurrPoly.append(B2DPoint(nX, nY));
+ }
+ break;
+ }
+
+ case 'h' :
+ {
+ bRelative = true;
+ [[fallthrough]];
+ }
+ case 'H' :
+ {
+ nPos++;
+ basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY(nLastY);
+
+ if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX += nLastX;
+ }
+
+ // set last position
+ nLastX = nX;
+
+ // add point
+ aCurrPoly.append(B2DPoint(nX, nY));
+ }
+ break;
+ }
+
+ case 'v' :
+ {
+ bRelative = true;
+ [[fallthrough]];
+ }
+ case 'V' :
+ {
+ nPos++;
+ basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX(nLastX), nY;
+
+ if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nY += nLastY;
+ }
+
+ // set last position
+ nLastY = nY;
+
+ // add point
+ aCurrPoly.append(B2DPoint(nX, nY));
+ }
+ break;
+ }
+
+ case 's' :
+ {
+ bRelative = true;
+ [[fallthrough]];
+ }
+ case 'S' :
+ {
+ nPos++;
+ basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+ double nX2, nY2;
+
+ if(!basegfx::internal::importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX2 += nLastX;
+ nY2 += nLastY;
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ // ensure existence of start point
+ if(!aCurrPoly.count())
+ {
+ aCurrPoly.append(B2DPoint(nLastX, nLastY));
+ }
+
+ // get first control point. It's the reflection of the PrevControlPoint
+ // of the last point. If not existent, use current point (see SVG)
+ B2DPoint aPrevControl(nLastX, nLastY);
+ const sal_uInt32 nIndex(aCurrPoly.count() - 1);
+
+ if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
+ {
+ const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
+ const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
+
+ // use mirrored previous control point
+ aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
+ aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
+ }
+
+ // append curved edge
+ aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+ }
+ break;
+ }
+
+ case 'c' :
+ {
+ bRelative = true;
+ [[fallthrough]];
+ }
+ case 'C' :
+ {
+ nPos++;
+ basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+ double nX1, nY1;
+ double nX2, nY2;
+
+ if(!basegfx::internal::importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX1 += nLastX;
+ nY1 += nLastY;
+ nX2 += nLastX;
+ nY2 += nLastY;
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ // ensure existence of start point
+ if(!aCurrPoly.count())
+ {
+ aCurrPoly.append(B2DPoint(nLastX, nLastY));
+ }
+
+ // append curved edge
+ aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+ }
+ break;
+ }
+
+ // #100617# quadratic beziers are imported as cubic ones
+ case 'q' :
+ {
+ bRelative = true;
+ [[fallthrough]];
+ }
+ case 'Q' :
+ {
+ nPos++;
+ basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+ double nX1, nY1;
+
+ if(!basegfx::internal::importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX1 += nLastX;
+ nY1 += nLastY;
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ // ensure existence of start point
+ if(!aCurrPoly.count())
+ {
+ aCurrPoly.append(B2DPoint(nLastX, nLastY));
+ }
+
+ // append curved edge
+ aCurrPoly.appendQuadraticBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX, nY));
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+ }
+ break;
+ }
+
+ // #100617# relative quadratic beziers are imported as cubic
+ case 't' :
+ {
+ bRelative = true;
+ [[fallthrough]];
+ }
+ case 'T' :
+ {
+ nPos++;
+ basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+
+ if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ // ensure existence of start point
+ if(!aCurrPoly.count())
+ {
+ aCurrPoly.append(B2DPoint(nLastX, nLastY));
+ }
+
+ // get first control point. It's the reflection of the PrevControlPoint
+ // of the last point. If not existent, use current point (see SVG)
+ B2DPoint aPrevControl(nLastX, nLastY);
+ const sal_uInt32 nIndex(aCurrPoly.count() - 1);
+ const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
+
+ if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
+ {
+ const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
+
+ // use mirrored previous control point
+ aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
+ aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
+ }
+
+ if(!aPrevControl.equal(aPrevPoint))
+ {
+ // there is a prev control point, and we have the already mirrored one
+ // in aPrevControl. We also need the quadratic control point for this
+ // new quadratic segment to calculate the 2nd cubic control point
+ const B2DPoint aQuadControlPoint(
+ ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0,
+ ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0);
+
+ // calculate the cubic bezier coefficients from the quadratic ones.
+ const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0);
+ const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0);
+
+ // append curved edge, use mirrored cubic control point directly
+ aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
+ }
+ else
+ {
+ // when no previous control, SVG says to use current point -> straight line.
+ // Just add end point
+ aCurrPoly.append(B2DPoint(nX, nY));
+ }
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+ }
+ break;
+ }
+
+ case 'a' :
+ {
+ bRelative = true;
+ [[fallthrough]];
+ }
+ case 'A' :
+ {
+ nPos++;
+ basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
+
+ while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
+ {
+ double nX, nY;
+ double fRX, fRY, fPhi;
+ sal_Int32 bLargeArcFlag, bSweepFlag;
+
+ if(!basegfx::internal::importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importFlagAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importFlagAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
+
+ if(bRelative)
+ {
+ nX += nLastX;
+ nY += nLastY;
+ }
+
+ if( rtl::math::approxEqual(nX, nLastX) && rtl::math::approxEqual(nY, nLastY) )
+ continue; // start==end -> skip according to SVG spec
+
+ if( fRX == 0.0 || fRY == 0.0 )
+ {
+ // straight line segment according to SVG spec
+ aCurrPoly.append(B2DPoint(nX, nY));
+ }
+ else
+ {
+ // normalize according to SVG spec
+ fRX=fabs(fRX); fRY=fabs(fRY);
+
+ // from the SVG spec, appendix F.6.4
+
+ // |x1'| |cos phi sin phi| |(x1 - x2)/2|
+ // |y1'| = |-sin phi cos phi| |(y1 - y2)/2|
+ const B2DPoint p1(nLastX, nLastY);
+ const B2DPoint p2(nX, nY);
+ B2DHomMatrix aTransform(basegfx::utils::createRotateB2DHomMatrix(
+ -deg2rad(fPhi)));
+
+ const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
+
+ // ______________________________________ rx y1'
+ // |cx'| + / rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2 ry
+ // |cy'| =-/ rx^2y1'^2 + ry^2 x1'^2 - ry x1'
+ // rx
+ // chose + if f_A != f_S
+ // chose - if f_A = f_S
+ B2DPoint aCenter_prime;
+ const double fRadicant(
+ (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/
+ (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX()));
+ if( fRadicant < 0.0 )
+ {
+ // no solution - according to SVG
+ // spec, scale up ellipse
+ // uniformly such that it passes
+ // through end points (denominator
+ // of radicant solved for fRY,
+ // with s=fRX/fRY)
+ const double fRatio(fRX/fRY);
+ const double fRadicant2(
+ p1_prime.getY()*p1_prime.getY() +
+ p1_prime.getX()*p1_prime.getX()/(fRatio*fRatio));
+ if( fRadicant2 < 0.0 )
+ {
+ // only trivial solution, one
+ // of the axes 0 -> straight
+ // line segment according to
+ // SVG spec
+ aCurrPoly.append(B2DPoint(nX, nY));
+ continue;
+ }
+
+ fRY=sqrt(fRadicant2);
+ fRX=fRatio*fRY;
+
+ // keep center_prime forced to (0,0)
+ }
+ else
+ {
+ const double fFactor(
+ (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
+ sqrt(fRadicant));
+
+ // actually calculate center_prime
+ aCenter_prime = B2DPoint(
+ fFactor*fRX*p1_prime.getY()/fRY,
+ -fFactor*fRY*p1_prime.getX()/fRX);
+ }
+
+ // + u - v
+ // angle(u,v) = arccos( ------------ ) (take the sign of (ux vy - uy vx))
+ // - ||u|| ||v||
+
+ // 1 | (x1' - cx')/rx |
+ // theta1 = angle(( ), | | )
+ // 0 | (y1' - cy')/ry |
+ const B2DPoint aRadii(fRX,fRY);
+ double fTheta1(
+ B2DVector(1.0,0.0).angle(
+ (p1_prime-aCenter_prime)/aRadii));
+
+ // |1| | (-x1' - cx')/rx |
+ // theta2 = angle( | | , | | )
+ // |0| | (-y1' - cy')/ry |
+ double fTheta2(
+ B2DVector(1.0,0.0).angle(
+ (-p1_prime-aCenter_prime)/aRadii));
+
+ // map both angles to [0,2pi)
+ fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI);
+ fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI);
+
+ // make sure the large arc is taken
+ // (since
+ // createPolygonFromEllipseSegment()
+ // normalizes to e.g. cw arc)
+ if( !bSweepFlag )
+ std::swap(fTheta1,fTheta2);
+
+ // finally, create bezier polygon from this
+ B2DPolygon aSegment(
+ utils::createPolygonFromUnitEllipseSegment(
+ fTheta1, fTheta2 ));
+
+ // transform ellipse by rotation & move to final center
+ aTransform = basegfx::utils::createScaleB2DHomMatrix(fRX, fRY);
+ aTransform.translate(aCenter_prime.getX(),
+ aCenter_prime.getY());
+ aTransform.rotate(deg2rad(fPhi));
+ const B2DPoint aOffset((p1+p2)/2.0);
+ aTransform.translate(aOffset.getX(),
+ aOffset.getY());
+ aSegment.transform(aTransform);
+
+ // createPolygonFromEllipseSegment()
+ // always creates arcs that are
+ // positively oriented - flip polygon
+ // if we swapped angles above
+ if( !bSweepFlag )
+ aSegment.flip();
+
+ // remember PointIndex of evtl. added pure helper points
+ sal_uInt32 nPointIndex(aCurrPoly.count() + 1);
+ aCurrPoly.append(aSegment);
+
+ // if asked for, mark pure helper points by adding them to the index list of
+ // helper points
+ if(pHelpPointIndexSet && aCurrPoly.count() > 1)
+ {
+ const sal_uInt32 nPolyIndex(o_rPolyPolygon.count());
+
+ for(;nPointIndex + 1 < aCurrPoly.count(); nPointIndex++)
+ {
+ pHelpPointIndexSet->insert(PointIndex(nPolyIndex, nPointIndex));
+ }
+ }
+ }
+
+ // set last position
+ nLastX = nX;
+ nLastY = nY;
+ }
+ break;
+ }
+
+ default:
+ {
+ SAL_WARN("basegfx", "importFromSvgD(): skipping tags in svg:d element (unknown: \""
+ << OUString(aCurrChar)
+ << "\")!");
+ ++nPos;
+ break;
+ }
+ }
+ }
+
+ // if there is polygon data, create non-closed polygon
+ if(aCurrPoly.count())
+ {
+ o_rPolyPolygon.append(aCurrPoly);
+ }
+
+ return true;
+ }
+
+ bool importFromSvgPoints( B2DPolygon& o_rPoly,
+ std::u16string_view rSvgPointsAttribute )
+ {
+ o_rPoly.clear();
+ const sal_Int32 nLen(rSvgPointsAttribute.size());
+ sal_Int32 nPos(0);
+ double nX, nY;
+
+ // skip initial whitespace
+ basegfx::internal::skipSpaces(nPos, rSvgPointsAttribute, nLen);
+
+ while(nPos < nLen)
+ {
+ if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
+ if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
+
+ // add point
+ o_rPoly.append(B2DPoint(nX, nY));
+
+ // skip to next number, or finish
+ basegfx::internal::skipSpaces(nPos, rSvgPointsAttribute, nLen);
+ }
+
+ return true;
+ }
+
+ OUString exportToSvgPoints( const B2DPolygon& rPoly )
+ {
+ SAL_WARN_IF(rPoly.areControlPointsUsed(), "basegfx", "exportToSvgPoints: Only non-bezier polygons allowed (!)");
+ const sal_uInt32 nPointCount(rPoly.count());
+ OUStringBuffer aResult;
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const basegfx::B2DPoint aPoint(rPoly.getB2DPoint(a));
+
+ if(a)
+ {
+ aResult.append(' ');
+ }
+
+ aResult.append(OUString::number(aPoint.getX())
+ + ","
+ + OUString::number(aPoint.getY()));
+ }
+
+ return aResult.makeStringAndClear();
+ }
+
+ OUString exportToSvgD(
+ const B2DPolyPolygon& rPolyPolygon,
+ bool bUseRelativeCoordinates,
+ bool bDetectQuadraticBeziers,
+ bool bHandleRelativeNextPointCompatible,
+ bool bOOXMLMotionPath)
+ {
+ const sal_uInt32 nCount(rPolyPolygon.count());
+ sal_uInt32 nCombinedPointCount = 0;
+ for(sal_uInt32 i(0); i < nCount; i++)
+ {
+ const B2DPolygon& aPolygon(rPolyPolygon.getB2DPolygon(i));
+ nCombinedPointCount += aPolygon.count();
+ }
+
+ OUStringBuffer aResult(std::max<int>(nCombinedPointCount * 32,512));
+ B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
+
+ for(sal_uInt32 i(0); i < nCount; i++)
+ {
+ const B2DPolygon& aPolygon(rPolyPolygon.getB2DPolygon(i));
+ const sal_uInt32 nPointCount(aPolygon.count());
+
+ if(nPointCount)
+ {
+ const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
+ const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
+ sal_Unicode aLastSVGCommand(' '); // last SVG command char
+ B2DPoint aLeft, aRight; // for quadratic bezier test
+
+ // handle polygon start point
+ B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
+ bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates);
+
+ if(bHandleRelativeNextPointCompatible)
+ {
+ // To get around the error that the start point for the next polygon is the
+ // start point of the current one (and not the last as it was handled up to now)
+ // do force to write an absolute 'M' command as start for the next polygon
+ bUseRelativeCoordinatesForFirstPoint = false;
+ }
+
+ // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto'
+ putCommandChar(aResult, aLastSVGCommand, 'M', bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
+ putNumberChar(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
+ putNumberChar(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
+ aLastSVGCommand = bUseRelativeCoordinatesForFirstPoint ? 'l' : 'L';
+ aCurrentSVGPosition = aEdgeStart;
+
+ for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
+ {
+ // prepare access to next point
+ const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+ const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
+
+ // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
+ const bool bEdgeIsBezier(bPolyUsesControlPoints
+ && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
+
+ if(bEdgeIsBezier)
+ {
+ // handle bezier edge
+ const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
+ const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
+ bool bIsQuadraticBezier(false);
+
+ // check continuity at current edge's start point. For SVG, do NOT use an
+ // existing continuity since no 'S' or 's' statement should be written. At
+ // import, that 'previous' control vector is not available. SVG documentation
+ // says for interpretation:
+
+ // "(If there is no previous command or if the previous command was
+ // not a C, c, S or s, assume the first control point is coincident
+ // with the current point.)"
+
+ // That's what is done from our import, so avoid exporting it as first statement
+ // is necessary.
+ const bool bSymmetricAtEdgeStart(
+ !bOOXMLMotionPath && nIndex != 0
+ && aPolygon.getContinuityInPoint(nIndex) == B2VectorContinuity::C2);
+
+ if(bDetectQuadraticBeziers)
+ {
+ // check for quadratic beziers - that's
+ // the case if both control points are in
+ // the same place when they are prolonged
+ // to the common quadratic control point
+
+ // Left: P = (3P1 - P0) / 2
+ // Right: P = (3P2 - P3) / 2
+ aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
+ aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
+ bIsQuadraticBezier = aLeft.equal(aRight);
+ }
+
+ if(bIsQuadraticBezier)
+ {
+ // approximately equal, export as quadratic bezier
+ if(bSymmetricAtEdgeStart)
+ {
+ putCommandChar(aResult, aLastSVGCommand, 'T', bUseRelativeCoordinates, bOOXMLMotionPath);
+
+ putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ else
+ {
+ putCommandChar(aResult, aLastSVGCommand, 'Q', bUseRelativeCoordinates, bOOXMLMotionPath);
+
+ putNumberChar(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ }
+ else
+ {
+ // export as cubic bezier
+ if(bSymmetricAtEdgeStart)
+ {
+ putCommandChar(aResult, aLastSVGCommand, 'S', bUseRelativeCoordinates, bOOXMLMotionPath);
+
+ putNumberChar(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ else
+ {
+ putCommandChar(aResult, aLastSVGCommand, 'C', bUseRelativeCoordinates, bOOXMLMotionPath);
+
+ putNumberChar(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ }
+ }
+ else
+ {
+ // straight edge
+ if(nNextIndex == 0)
+ {
+ // it's a closed polygon's last edge and it's not a bezier edge, so there is
+ // no need to write it
+ }
+ else
+ {
+ const bool bXEqual(rtl::math::approxEqual(aEdgeStart.getX(), aEdgeEnd.getX()));
+ const bool bYEqual(rtl::math::approxEqual(aEdgeStart.getY(), aEdgeEnd.getY()));
+
+ if(bXEqual && bYEqual)
+ {
+ // point is a double point; do not export at all
+ }
+ else if(bXEqual && !bOOXMLMotionPath)
+ {
+ // export as vertical line
+ putCommandChar(aResult, aLastSVGCommand, 'V', bUseRelativeCoordinates, bOOXMLMotionPath);
+
+ putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ else if(bYEqual && !bOOXMLMotionPath)
+ {
+ // export as horizontal line
+ putCommandChar(aResult, aLastSVGCommand, 'H', bUseRelativeCoordinates, bOOXMLMotionPath);
+
+ putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ else
+ {
+ // export as line
+ putCommandChar(aResult, aLastSVGCommand, 'L', bUseRelativeCoordinates, bOOXMLMotionPath);
+
+ putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
+ aCurrentSVGPosition = aEdgeEnd;
+ }
+ }
+ }
+
+ // prepare edge start for next loop step
+ aEdgeStart = aEdgeEnd;
+ }
+
+ // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
+ if(aPolygon.isClosed())
+ {
+ putCommandChar(aResult, aLastSVGCommand, 'Z', bUseRelativeCoordinates, bOOXMLMotionPath);
+ }
+ else if (bOOXMLMotionPath)
+ {
+ putCommandChar(aResult, aLastSVGCommand, 'E', bUseRelativeCoordinates, bOOXMLMotionPath);
+ }
+
+ if(!bHandleRelativeNextPointCompatible)
+ {
+ // SVG defines that "the next subpath starts at the same initial point as the current subpath",
+ // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path
+ aCurrentSVGPosition = aPolygon.getB2DPoint(0);
+ }
+ }
+ }
+
+ return aResult.makeStringAndClear();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b2dtrapezoid.cxx b/basegfx/source/polygon/b2dtrapezoid.cxx
new file mode 100644
index 0000000000..2870c46d82
--- /dev/null
+++ b/basegfx/source/polygon/b2dtrapezoid.cxx
@@ -0,0 +1,1163 @@
+/* -*- 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 <basegfx/polygon/b2dtrapezoid.hxx>
+#include <basegfx/range/b1drange.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+#include <osl/diagnose.h>
+
+#include <list>
+
+namespace basegfx::trapezoidhelper
+{
+
+ // helper class to hold a simple edge. This is only used for horizontal edges
+ // currently, thus the YPositions will be equal. I did not create a special
+ // class for this since holding the pointers is more effective and also can be
+ // used as baseclass for the traversing edges
+
+ namespace {
+
+ class TrDeSimpleEdge
+ {
+ protected:
+ // pointers to start and end point
+ const B2DPoint* mpStart;
+ const B2DPoint* mpEnd;
+
+ public:
+ // constructor
+ TrDeSimpleEdge(
+ const B2DPoint* pStart,
+ const B2DPoint* pEnd)
+ : mpStart(pStart),
+ mpEnd(pEnd)
+ {
+ }
+
+ // data read access
+ const B2DPoint& getStart() const { return *mpStart; }
+ const B2DPoint& getEnd() const { return *mpEnd; }
+ };
+
+ }
+
+ // define vector of simple edges
+
+ typedef std::vector< TrDeSimpleEdge > TrDeSimpleEdges;
+
+ // helper class for holding a traversing edge. It will always have some
+ // distance in YPos. The slope (in a numerically useful form, see comments) is
+ // hold and used in SortValue to allow sorting traversing edges by Y, X and slope
+ // (in that order)
+
+ namespace {
+
+ class TrDeEdgeEntry : public TrDeSimpleEdge
+ {
+ private:
+ // the slope in a numerical useful form for sorting
+ sal_uInt32 mnSortValue;
+
+ public:
+ // convenience data read access
+ double getDeltaX() const { return mpEnd->getX() - mpStart->getX(); }
+ double getDeltaY() const { return mpEnd->getY() - mpStart->getY(); }
+
+ // convenience data read access. SortValue is created on demand since
+ // it is not always used
+ sal_uInt32 getSortValue() const
+ {
+ if(mnSortValue != 0)
+ return mnSortValue;
+
+ // get radiant; has to be in the range ]0.0 .. pi[, thus scale to full
+ // sal_uInt32 range for maximum precision
+ const double fRadiant(atan2(getDeltaY(), getDeltaX()) * (SAL_MAX_UINT32 / M_PI));
+
+ // convert to sal_uInt32 value
+ const_cast< TrDeEdgeEntry* >(this)->mnSortValue = sal_uInt32(fRadiant);
+
+ return mnSortValue;
+ }
+
+ // constructor. SortValue can be given when known, use zero otherwise
+ TrDeEdgeEntry(
+ const B2DPoint* pStart,
+ const B2DPoint* pEnd,
+ sal_uInt32 nSortValue)
+ : TrDeSimpleEdge(pStart, pEnd),
+ mnSortValue(nSortValue)
+ {
+ // force traversal of deltaY downward
+ if(mpEnd->getY() < mpStart->getY())
+ {
+ std::swap(mpStart, mpEnd);
+ }
+
+ // no horizontal edges allowed, all need to traverse vertically
+ OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
+ }
+
+ // data write access to StartPoint
+ void setStart( const B2DPoint* pNewStart)
+ {
+ OSL_ENSURE(pNewStart != nullptr, "No null pointer allowed here (!)");
+
+ if(mpStart != pNewStart)
+ {
+ mpStart = pNewStart;
+
+ // no horizontal edges allowed, all need to traverse vertically
+ OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
+ }
+ }
+
+ // data write access to EndPoint
+ void setEnd( const B2DPoint* pNewEnd)
+ {
+ OSL_ENSURE(pNewEnd != nullptr, "No null pointer allowed here (!)");
+
+ if(mpEnd != pNewEnd)
+ {
+ mpEnd = pNewEnd;
+
+ // no horizontal edges allowed, all need to traverse vertically
+ OSL_ENSURE(mpEnd->getY() > mpStart->getY(), "Illegal TrDeEdgeEntry constructed (!)");
+ }
+ }
+
+ // operator for sort support. Sort by Y, X and slope (in that order)
+ bool operator<(const TrDeEdgeEntry& rComp) const
+ {
+ if(fTools::equal(getStart().getY(), rComp.getStart().getY()))
+ {
+ if(fTools::equal(getStart().getX(), rComp.getStart().getX()))
+ {
+ // when start points are equal, use the direction the edge is pointing
+ // to. That value is created on demand and derived from atan2 in the
+ // range ]0.0 .. pi[ (without extremas, we always have a deltaY in this
+ // class) and scaled to sal_uInt32 range for best precision. 0 means no angle,
+ // while SAL_MAX_UINT32 means pi. Thus, the higher the value, the more left
+ // the edge traverses.
+ return (getSortValue() > rComp.getSortValue());
+ }
+ else
+ {
+ return fTools::less(getStart().getX(), rComp.getStart().getX());
+ }
+ }
+ else
+ {
+ return fTools::less(getStart().getY(), rComp.getStart().getY());
+ }
+ }
+
+ // method for cut support
+ B2DPoint getCutPointForGivenY(double fGivenY) const
+ {
+ // avoid div/0
+ if (getDeltaY() == 0)
+ return B2DPoint(getStart().getX(), fGivenY);
+ // Calculate cut point locally (do not use interpolate) since it is numerically
+ // necessary to guarantee the new, equal Y-coordinate
+ const double fFactor((fGivenY - getStart().getY()) / getDeltaY());
+ const double fDeltaXNew(fFactor * getDeltaX());
+
+ return B2DPoint(getStart().getX() + fDeltaXNew, fGivenY);
+ }
+ };
+
+ }
+
+ // define double linked list of edges (for fast random insert)
+
+ typedef std::list< TrDeEdgeEntry > TrDeEdgeEntries;
+
+
+
+ // FIXME: templatize this and use it for TrDeEdgeEntries too ...
+
+ namespace {
+
+ /// Class to allow efficient allocation and release of B2DPoints
+ class PointBlockAllocator
+ {
+ static const size_t nBlockSize = 32;
+ size_t nCurPoint;
+ B2DPoint *mpPointBase;
+ /// Special case the first allocation to avoid it.
+ B2DPoint maFirstStackBlock[nBlockSize];
+ std::vector< B2DPoint * > maBlocks;
+ public:
+ PointBlockAllocator() :
+ nCurPoint( nBlockSize ),
+ mpPointBase( maFirstStackBlock )
+ {
+ }
+
+ ~PointBlockAllocator()
+ {
+ while(!maBlocks.empty())
+ {
+ delete [] maBlocks.back();
+ maBlocks.pop_back();
+ }
+ }
+
+ B2DPoint *allocatePoint()
+ {
+ if(nCurPoint >= nBlockSize)
+ {
+ mpPointBase = new B2DPoint[nBlockSize];
+ maBlocks.push_back(mpPointBase);
+ nCurPoint = 0;
+ }
+ return mpPointBase + nCurPoint++;
+ }
+
+ B2DPoint *allocatePoint(const B2DTuple &rPoint)
+ {
+ B2DPoint *pPoint = allocatePoint();
+ *pPoint = rPoint;
+ return pPoint;
+ }
+
+ /// This is a very uncommon case but why not ...
+ void freeIfLast(B2DPoint const *pPoint)
+ {
+ // just re-use the last point if we can.
+ if ( nCurPoint > 0 && pPoint == mpPointBase + nCurPoint - 1 )
+ nCurPoint--;
+ }
+ };
+
+ // helper class to handle the complete trapezoid subdivision of a PolyPolygon
+ class TrapezoidSubdivider
+ {
+ private:
+ // local data
+ TrDeEdgeEntries maTrDeEdgeEntries;
+ std::vector< B2DPoint > maPoints;
+ /// new points allocated for cuts
+ PointBlockAllocator maNewPoints;
+
+ void addEdgeSorted(
+ TrDeEdgeEntries::iterator aCurrent,
+ const TrDeEdgeEntry& rNewEdge)
+ {
+ // Loop while new entry is bigger, use operator<
+ while(aCurrent != maTrDeEdgeEntries.end() && (*aCurrent) < rNewEdge)
+ {
+ ++aCurrent;
+ }
+
+ // Insert before first which is smaller or equal or at end
+ maTrDeEdgeEntries.insert(aCurrent, rNewEdge);
+ }
+
+ bool splitEdgeAtGivenPoint(
+ TrDeEdgeEntries::reference aEdge,
+ const B2DPoint& rCutPoint,
+ const TrDeEdgeEntries::iterator& aCurrent)
+ {
+ // do not create edges without deltaY: do not split when start is identical
+ if(aEdge.getStart().equal(rCutPoint))
+ {
+ return false;
+ }
+
+ // do not create edges without deltaY: do not split when end is identical
+ if(aEdge.getEnd().equal(rCutPoint))
+ {
+ return false;
+ }
+
+ const double fOldDeltaYStart(rCutPoint.getY() - aEdge.getStart().getY());
+
+ if(fTools::lessOrEqual(fOldDeltaYStart, 0.0))
+ {
+ // do not split: the resulting edge would be horizontal
+ // correct it to new start point
+ aEdge.setStart(&rCutPoint);
+ return false;
+ }
+
+ const double fNewDeltaYStart(aEdge.getEnd().getY() - rCutPoint.getY());
+
+ if(fTools::lessOrEqual(fNewDeltaYStart, 0.0))
+ {
+ // do not split: the resulting edge would be horizontal
+ // correct it to new end point
+ aEdge.setEnd(&rCutPoint);
+ return false;
+ }
+
+ // Create new entry
+ const TrDeEdgeEntry aNewEdge(
+ &rCutPoint,
+ &aEdge.getEnd(),
+ aEdge.getSortValue());
+
+ // Correct old entry
+ aEdge.setEnd(&rCutPoint);
+
+ // Insert sorted (to avoid new sort)
+ addEdgeSorted(aCurrent, aNewEdge);
+
+ return true;
+ }
+
+ bool testAndCorrectEdgeIntersection(
+ TrDeEdgeEntries::reference aEdgeA,
+ TrDeEdgeEntries::reference aEdgeB,
+ const TrDeEdgeEntries::iterator& aCurrent)
+ {
+ // Exclude simple cases: same start or end point
+ if(aEdgeA.getStart().equal(aEdgeB.getStart()))
+ {
+ return false;
+ }
+
+ if(aEdgeA.getStart().equal(aEdgeB.getEnd()))
+ {
+ return false;
+ }
+
+ if(aEdgeA.getEnd().equal(aEdgeB.getStart()))
+ {
+ return false;
+ }
+
+ if(aEdgeA.getEnd().equal(aEdgeB.getEnd()))
+ {
+ return false;
+ }
+
+ // Exclude simple cases: one of the edges has no length anymore
+ if(aEdgeA.getStart().equal(aEdgeA.getEnd()))
+ {
+ return false;
+ }
+
+ if(aEdgeB.getStart().equal(aEdgeB.getEnd()))
+ {
+ return false;
+ }
+
+ // check if one point is on the other edge (a touch, not a cut)
+ const B2DVector aDeltaB(aEdgeB.getDeltaX(), aEdgeB.getDeltaY());
+
+ if(utils::isPointOnEdge(aEdgeA.getStart(), aEdgeB.getStart(), aDeltaB))
+ {
+ return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getStart(), aCurrent);
+ }
+
+ if(utils::isPointOnEdge(aEdgeA.getEnd(), aEdgeB.getStart(), aDeltaB))
+ {
+ return splitEdgeAtGivenPoint(aEdgeB, aEdgeA.getEnd(), aCurrent);
+ }
+
+ const B2DVector aDeltaA(aEdgeA.getDeltaX(), aEdgeA.getDeltaY());
+
+ if(utils::isPointOnEdge(aEdgeB.getStart(), aEdgeA.getStart(), aDeltaA))
+ {
+ return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getStart(), aCurrent);
+ }
+
+ if(utils::isPointOnEdge(aEdgeB.getEnd(), aEdgeA.getStart(), aDeltaA))
+ {
+ return splitEdgeAtGivenPoint(aEdgeA, aEdgeB.getEnd(), aCurrent);
+ }
+
+ // check for cut inside edges. Use both t-values to choose the more precise
+ // one later
+ double fCutA(0.0);
+ double fCutB(0.0);
+
+ if(utils::findCut(
+ aEdgeA.getStart(), aDeltaA,
+ aEdgeB.getStart(), aDeltaB,
+ CutFlagValue::LINE,
+ &fCutA,
+ &fCutB) == CutFlagValue::NONE)
+ return false;
+
+ // use a simple metric (length criteria) for choosing the numerically
+ // better cut
+ const double fSimpleLengthA(aDeltaA.getX() + aDeltaA.getY());
+ const double fSimpleLengthB(aDeltaB.getX() + aDeltaB.getY());
+ const bool bAIsLonger(fSimpleLengthA > fSimpleLengthB);
+ B2DPoint* pNewPoint = bAIsLonger
+ ? maNewPoints.allocatePoint(aEdgeA.getStart() + (fCutA * aDeltaA))
+ : maNewPoints.allocatePoint(aEdgeB.getStart() + (fCutB * aDeltaB));
+
+ // try to split both edges
+ bool bRetval = splitEdgeAtGivenPoint(aEdgeA, *pNewPoint, aCurrent);
+ bRetval |= splitEdgeAtGivenPoint(aEdgeB, *pNewPoint, aCurrent);
+
+ if(!bRetval)
+ maNewPoints.freeIfLast(pNewPoint);
+
+ return bRetval;
+ }
+
+ void solveHorizontalEdges(TrDeSimpleEdges& rTrDeSimpleEdges)
+ {
+ if(rTrDeSimpleEdges.empty() || maTrDeEdgeEntries.empty())
+ return;
+
+ // there were horizontal edges. These can be excluded, but
+ // cuts with other edges need to be solved and added before
+ // ignoring them
+ for(const TrDeSimpleEdge & rHorEdge : rTrDeSimpleEdges)
+ {
+ // get horizontal edge as candidate; prepare its range and fixed Y
+ const B1DRange aRange(rHorEdge.getStart().getX(), rHorEdge.getEnd().getX());
+ const double fFixedY(rHorEdge.getStart().getY());
+
+ // loop over traversing edges
+ TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin());
+
+ do
+ {
+ // get compare edge
+ TrDeEdgeEntries::reference aCompare(*aCurrent++);
+
+ if(fTools::lessOrEqual(aCompare.getEnd().getY(), fFixedY))
+ {
+ // edge ends above horizontal edge, continue
+ continue;
+ }
+
+ if(fTools::moreOrEqual(aCompare.getStart().getY(), fFixedY))
+ {
+ // edge starts below horizontal edge, continue
+ continue;
+ }
+
+ // vertical overlap, get horizontal range
+ const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX());
+
+ if(aRange.overlaps(aCompareRange))
+ {
+ // possible cut, get cut point
+ const B2DPoint aSplit(aCompare.getCutPointForGivenY(fFixedY));
+
+ if(fTools::more(aSplit.getX(), aRange.getMinimum())
+ && fTools::less(aSplit.getX(), aRange.getMaximum()))
+ {
+ // cut is in XRange of horizontal edge, potentially needed cut
+ B2DPoint* pNewPoint = maNewPoints.allocatePoint(aSplit);
+
+ if(!splitEdgeAtGivenPoint(aCompare, *pNewPoint, aCurrent))
+ {
+ maNewPoints.freeIfLast(pNewPoint);
+ }
+ }
+ }
+ }
+ while(aCurrent != maTrDeEdgeEntries.end()
+ && fTools::less(aCurrent->getStart().getY(), fFixedY));
+ }
+ }
+
+ public:
+ explicit TrapezoidSubdivider(
+ const B2DPolyPolygon& rSourcePolyPolygon)
+ {
+ B2DPolyPolygon aSource(rSourcePolyPolygon);
+ TrDeSimpleEdges aTrDeSimpleEdges;
+ sal_uInt32 nAllPointCount(0);
+
+ // ensure there are no curves used
+ if(aSource.areControlPointsUsed())
+ {
+ aSource = aSource.getDefaultAdaptiveSubdivision();
+ }
+
+ for(const auto& aPolygonCandidate : std::as_const(aSource))
+ {
+ // 1st run: count points
+ const sal_uInt32 nCount(aPolygonCandidate.count());
+
+ if(nCount > 2)
+ {
+ nAllPointCount += nCount;
+ }
+ }
+
+ if(nAllPointCount)
+ {
+ // reserve needed points. CAUTION: maPoints size is NOT to be changed anymore
+ // after 2nd loop since pointers to it are used in the edges
+ maPoints.reserve(nAllPointCount);
+
+ for(const auto& aPolygonCandidate : std::as_const(aSource))
+ {
+ // 2nd run: add points
+ const sal_uInt32 nCount(aPolygonCandidate.count());
+
+ if(nCount > 2)
+ {
+ for(sal_uInt32 b = 0; b < nCount; b++)
+ {
+ maPoints.push_back(aPolygonCandidate.getB2DPoint(b));
+ }
+ }
+ }
+
+ // Moved the edge construction to a 3rd run: doing it in the 2nd run is
+ // possible (and I used it), but requires a working vector::reserve()
+ // implementation, else the vector will be reallocated and the pointers
+ // in the edges may be wrong. Security first here.
+ sal_uInt32 nStartIndex(0);
+
+ for(const auto& aPolygonCandidate : std::as_const(aSource))
+ {
+ const sal_uInt32 nCount(aPolygonCandidate.count());
+
+ if(nCount > 2)
+ {
+ // get the last point of the current polygon
+ B2DPoint* pPrev(&maPoints[nCount + nStartIndex - 1]);
+
+ for(sal_uInt32 b = 0; b < nCount; b++)
+ {
+ // get next point
+ B2DPoint* pCurr(&maPoints[nStartIndex++]);
+
+ if(fTools::equal(pPrev->getY(), pCurr->getY()))
+ {
+ // horizontal edge, check for single point
+ if(!fTools::equal(pPrev->getX(), pCurr->getX()))
+ {
+ // X-order not needed, just add
+ aTrDeSimpleEdges.emplace_back(pPrev, pCurr);
+
+ const double fMiddle((pPrev->getY() + pCurr->getY()) * 0.5);
+ pPrev->setY(fMiddle);
+ pCurr->setY(fMiddle);
+ }
+ }
+ else
+ {
+ // vertical edge. Positive Y-direction is guaranteed by the
+ // TrDeEdgeEntry constructor
+ maTrDeEdgeEntries.emplace_back(pPrev, pCurr, 0);
+ }
+
+ // prepare next step
+ pPrev = pCurr;
+ }
+ }
+ }
+ }
+
+ if(!maTrDeEdgeEntries.empty())
+ {
+ // single and initial sort of traversing edges
+ maTrDeEdgeEntries.sort();
+
+ // solve horizontal edges if there are any detected
+ solveHorizontalEdges(aTrDeSimpleEdges);
+ }
+ }
+
+ void Subdivide(B2DTrapezoidVector& ro_Result)
+ {
+ // This is the central subdivider. The strategy is to use the first two entries
+ // from the traversing edges as a potential trapezoid and do the needed corrections
+ // and adaptations on the way.
+
+ // There always must be two edges with the same YStart value: When adding the polygons
+ // in the constructor, there is always a topmost point from which two edges start; when
+ // the topmost is an edge, there is a start and end of this edge from which two edges
+ // start. All cases have two edges with same StartY (QED).
+
+ // Based on this these edges get corrected when:
+ // - one is longer than the other
+ // - they intersect
+ // - they intersect with other edges
+ // - another edge starts inside the thought trapezoid
+
+ // All this cases again produce a valid state so that the first two edges have a common
+ // Ystart again. Some cases lead to a restart of the process, some allow consuming the
+ // edges and create the intended trapezoid.
+
+ // Be careful when doing changes here: it is essential to keep all possible paths
+ // in valid states and to be numerically correct. This is especially needed e.g.
+ // by using fTools::equal(..) in the more robust small-value incarnation.
+ B1DRange aLeftRange;
+ B1DRange aRightRange;
+
+ if(!maTrDeEdgeEntries.empty())
+ {
+ // measuring shows that the relation between edges and created trapezoids is
+ // mostly in the 1:1 range, thus reserve as much trapezoids as edges exist.
+ ro_Result.reserve(ro_Result.size() + maTrDeEdgeEntries.size());
+ }
+
+ while(!maTrDeEdgeEntries.empty())
+ {
+ // Prepare current operator and get first edge
+ TrDeEdgeEntries::iterator aCurrent(maTrDeEdgeEntries.begin());
+ TrDeEdgeEntries::reference aLeft(*aCurrent++);
+
+ if(aCurrent == maTrDeEdgeEntries.end())
+ {
+ // Should not happen: No 2nd edge; consume the single edge
+ // to not have an endless loop and start next. During development
+ // I constantly had breakpoints here, so I am sure enough to add an
+ // assertion here
+ OSL_FAIL("Trapezoid decomposer in illegal state (!)");
+ maTrDeEdgeEntries.pop_front();
+ continue;
+ }
+
+ // get second edge
+ TrDeEdgeEntries::reference aRight(*aCurrent++);
+
+ if(!fTools::equal(aLeft.getStart().getY(), aRight.getStart().getY()))
+ {
+ // Should not happen: We have a 2nd edge, but YStart is on another
+ // line; consume the single edge to not have an endless loop and start
+ // next. During development I constantly had breakpoints here, so I am
+ // sure enough to add an assertion here
+ OSL_FAIL("Trapezoid decomposer in illegal state (!)");
+ maTrDeEdgeEntries.pop_front();
+ continue;
+ }
+
+ // aLeft and aRight build a thought trapezoid now. They have a common
+ // start line (same Y for start points). Potentially, one of the edges
+ // is longer than the other. It is only needed to look at the shorter
+ // length which build the potential trapezoid. To do so, get the end points
+ // locally and adapt the evtl. longer one. Use only aLeftEnd and aRightEnd
+ // from here on, not the aLeft.getEnd() or aRight.getEnd() accesses.
+ B2DPoint aLeftEnd(aLeft.getEnd());
+ B2DPoint aRightEnd(aRight.getEnd());
+
+ // check if end points are on the same line. If yes, no adaptation
+ // needs to be prepared. Also remember which one actually is longer.
+ const bool bEndOnSameLine(fTools::equal(aLeftEnd.getY(), aRightEnd.getY()));
+ bool bLeftIsLonger(false);
+
+ if(!bEndOnSameLine)
+ {
+ // check which edge is longer and correct accordingly
+ bLeftIsLonger = fTools::more(aLeftEnd.getY(), aRightEnd.getY());
+
+ if(bLeftIsLonger)
+ {
+ aLeftEnd = aLeft.getCutPointForGivenY(aRightEnd.getY());
+ }
+ else
+ {
+ aRightEnd = aRight.getCutPointForGivenY(aLeftEnd.getY());
+ }
+ }
+
+ // check for same start and end points
+ const bool bSameStartPoint(aLeft.getStart().equal(aRight.getStart()));
+ const bool bSameEndPoint(aLeftEnd.equal(aRightEnd));
+
+ // check the simple case that the edges form a 'blind' edge (deadend)
+ if(bSameStartPoint && bSameEndPoint)
+ {
+ // correct the longer edge if prepared
+ if(!bEndOnSameLine)
+ {
+ if(bLeftIsLonger)
+ {
+ B2DPoint* pNewPoint = maNewPoints.allocatePoint(aLeftEnd);
+
+ if(!splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent))
+ {
+ maNewPoints.freeIfLast(pNewPoint);
+ }
+ }
+ else
+ {
+ B2DPoint* pNewPoint = maNewPoints.allocatePoint(aRightEnd);
+
+ if(!splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent))
+ {
+ maNewPoints.freeIfLast(pNewPoint);
+ }
+ }
+ }
+
+ // consume both edges and start next run
+ maTrDeEdgeEntries.pop_front();
+ maTrDeEdgeEntries.pop_front();
+
+ continue;
+ }
+
+ // check if the edges self-intersect. This can only happen when
+ // start and end point are different
+ bool bRangesSet(false);
+
+ if(!(bSameStartPoint || bSameEndPoint))
+ {
+ // get XRanges of edges
+ aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX());
+ aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX());
+ bRangesSet = true;
+
+ // use fast range test first
+ if(aLeftRange.overlaps(aRightRange))
+ {
+ // real cut test and correction. If correction was needed,
+ // start new run
+ if(testAndCorrectEdgeIntersection(aLeft, aRight, aCurrent))
+ {
+ continue;
+ }
+ }
+ }
+
+ // now we need to check if there are intersections with other edges
+ // or if other edges start inside the candidate trapezoid
+ if(aCurrent != maTrDeEdgeEntries.end()
+ && fTools::less(aCurrent->getStart().getY(), aLeftEnd.getY()))
+ {
+ // get XRanges of edges
+ if(!bRangesSet)
+ {
+ aLeftRange = B1DRange(aLeft.getStart().getX(), aLeftEnd.getX());
+ aRightRange = B1DRange(aRight.getStart().getX(), aRightEnd.getX());
+ }
+
+ // build full XRange for fast check
+ B1DRange aAllRange(aLeftRange);
+ aAllRange.expand(aRightRange);
+
+ // prepare loop iterator; aCurrent needs to stay unchanged for
+ // possibly sorted insertions of new EdgeNodes. Also prepare stop flag
+ TrDeEdgeEntries::iterator aLoop(aCurrent);
+ bool bDone(false);
+
+ do
+ {
+ // get compare edge and its XRange
+ TrDeEdgeEntries::reference aCompare(*aLoop++);
+
+ // avoid edges using the same start point as one of
+ // the edges. These can neither have their start point
+ // in the thought trapezoid nor cut with one of the edges
+ if(aCompare.getStart().equal(aRight.getStart()))
+ {
+ continue;
+ }
+
+ // get compare XRange
+ const B1DRange aCompareRange(aCompare.getStart().getX(), aCompare.getEnd().getX());
+
+ // use fast range test first
+ if(aAllRange.overlaps(aCompareRange))
+ {
+ // check for start point inside thought trapezoid
+ if(fTools::more(aCompare.getStart().getY(), aLeft.getStart().getY()))
+ {
+ // calculate the two possible split points at compare's Y
+ const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aCompare.getStart().getY()));
+ const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aCompare.getStart().getY()));
+
+ // check for start point of aCompare being inside thought
+ // trapezoid
+ if(aCompare.getStart().getX() >= aSplitLeft.getX() &&
+ aCompare.getStart().getX() <= aSplitRight.getX())
+ {
+ // is inside, correct and restart loop
+ B2DPoint* pNewLeft = maNewPoints.allocatePoint(aSplitLeft);
+
+ if(splitEdgeAtGivenPoint(aLeft, *pNewLeft, aCurrent))
+ {
+ bDone = true;
+ }
+ else
+ {
+ maNewPoints.freeIfLast(pNewLeft);
+ }
+
+ B2DPoint* pNewRight = maNewPoints.allocatePoint(aSplitRight);
+
+ if(splitEdgeAtGivenPoint(aRight, *pNewRight, aCurrent))
+ {
+ bDone = true;
+ }
+ else
+ {
+ maNewPoints.freeIfLast(pNewRight);
+ }
+ }
+ }
+
+ if(!bDone && aLeftRange.overlaps(aCompareRange))
+ {
+ // test for concrete cut of compare edge with left edge
+ bDone = testAndCorrectEdgeIntersection(aLeft, aCompare, aCurrent);
+ }
+
+ if(!bDone && aRightRange.overlaps(aCompareRange))
+ {
+ // test for concrete cut of compare edge with Right edge
+ bDone = testAndCorrectEdgeIntersection(aRight, aCompare, aCurrent);
+ }
+ }
+ }
+ while(!bDone
+ && aLoop != maTrDeEdgeEntries.end()
+ && fTools::less(aLoop->getStart().getY(), aLeftEnd.getY()));
+
+ if(bDone)
+ {
+ // something needed to be changed; start next loop
+ continue;
+ }
+ }
+
+ // when we get here, the intended trapezoid can be used. It needs to
+ // be corrected possibly (if prepared); but this is no reason not to
+ // use it in the same loop iteration
+ if(!bEndOnSameLine)
+ {
+ if(bLeftIsLonger)
+ {
+ B2DPoint* pNewPoint = maNewPoints.allocatePoint(aLeftEnd);
+
+ if(!splitEdgeAtGivenPoint(aLeft, *pNewPoint, aCurrent))
+ {
+ maNewPoints.freeIfLast(pNewPoint);
+ }
+ }
+ else
+ {
+ B2DPoint* pNewPoint = maNewPoints.allocatePoint(aRightEnd);
+
+ if(!splitEdgeAtGivenPoint(aRight, *pNewPoint, aCurrent))
+ {
+ maNewPoints.freeIfLast(pNewPoint);
+ }
+ }
+ }
+
+ // the two edges start at the same Y, they use the same DeltaY, they
+ // do not cut themselves and not any other edge in range. Create a
+ // B2DTrapezoid and consume both edges
+ ro_Result.emplace_back(
+ aLeft.getStart().getX(),
+ aRight.getStart().getX(),
+ aLeft.getStart().getY(),
+ aLeftEnd.getX(),
+ aRightEnd.getX(),
+ aLeftEnd.getY());
+
+ maTrDeEdgeEntries.pop_front();
+ maTrDeEdgeEntries.pop_front();
+ }
+ }
+ };
+
+ }
+} // end of namespace
+
+namespace basegfx
+{
+ B2DTrapezoid::B2DTrapezoid(
+ const double& rfTopXLeft,
+ const double& rfTopXRight,
+ const double& rfTopY,
+ const double& rfBottomXLeft,
+ const double& rfBottomXRight,
+ const double& rfBottomY)
+ : mfTopXLeft(rfTopXLeft),
+ mfTopXRight(rfTopXRight),
+ mfTopY(rfTopY),
+ mfBottomXLeft(rfBottomXLeft),
+ mfBottomXRight(rfBottomXRight),
+ mfBottomY(rfBottomY)
+ {
+ // guarantee mfTopXRight >= mfTopXLeft
+ if(mfTopXLeft > mfTopXRight)
+ {
+ std::swap(mfTopXLeft, mfTopXRight);
+ }
+
+ // guarantee mfBottomXRight >= mfBottomXLeft
+ if(mfBottomXLeft > mfBottomXRight)
+ {
+ std::swap(mfBottomXLeft, mfBottomXRight);
+ }
+
+ // guarantee mfBottomY >= mfTopY
+ if(mfTopY > mfBottomY)
+ {
+ std::swap(mfTopY, mfBottomY);
+ std::swap(mfTopXLeft, mfBottomXLeft);
+ std::swap(mfTopXRight, mfBottomXRight);
+ }
+ }
+
+ B2DPolygon B2DTrapezoid::getB2DPolygon() const
+ {
+ B2DPolygon aRetval;
+
+ aRetval.append(B2DPoint(getTopXLeft(), getTopY()));
+ aRetval.append(B2DPoint(getTopXRight(), getTopY()));
+ aRetval.append(B2DPoint(getBottomXRight(), getBottomY()));
+ aRetval.append(B2DPoint(getBottomXLeft(), getBottomY()));
+ aRetval.setClosed(true);
+
+ return aRetval;
+ }
+} // end of namespace basegfx
+
+namespace basegfx::utils
+{
+ // convert Source utils::PolyPolygon to trapezoids
+ void trapezoidSubdivide(B2DTrapezoidVector& ro_Result, const B2DPolyPolygon& rSourcePolyPolygon)
+ {
+ trapezoidhelper::TrapezoidSubdivider aTrapezoidSubdivider(rSourcePolyPolygon);
+
+ aTrapezoidSubdivider.Subdivide(ro_Result);
+ }
+
+ void createLineTrapezoidFromEdge(
+ B2DTrapezoidVector& ro_Result,
+ const B2DPoint& rPointA,
+ const B2DPoint& rPointB,
+ double fLineWidth)
+ {
+ if(fTools::lessOrEqual(fLineWidth, 0.0))
+ {
+ // no line width
+ return;
+ }
+
+ if(rPointA.equal(rPointB))
+ {
+ // points are equal, no edge
+ return;
+ }
+
+ const double fHalfLineWidth(0.5 * fLineWidth);
+
+ if(fTools::equal(rPointA.getX(), rPointB.getX()))
+ {
+ // vertical line
+ const double fLeftX(rPointA.getX() - fHalfLineWidth);
+ const double fRightX(rPointA.getX() + fHalfLineWidth);
+
+ ro_Result.emplace_back(
+ fLeftX,
+ fRightX,
+ std::min(rPointA.getY(), rPointB.getY()),
+ fLeftX,
+ fRightX,
+ std::max(rPointA.getY(), rPointB.getY()));
+ }
+ else if(fTools::equal(rPointA.getY(), rPointB.getY()))
+ {
+ // horizontal line
+ const double fLeftX(std::min(rPointA.getX(), rPointB.getX()));
+ const double fRightX(std::max(rPointA.getX(), rPointB.getX()));
+
+ ro_Result.emplace_back(
+ fLeftX,
+ fRightX,
+ rPointA.getY() - fHalfLineWidth,
+ fLeftX,
+ fRightX,
+ rPointA.getY() + fHalfLineWidth);
+ }
+ else
+ {
+ // diagonal line
+ // create perpendicular vector
+ const B2DVector aDelta(rPointB - rPointA);
+ B2DVector aPerpendicular(-aDelta.getY(), aDelta.getX());
+ aPerpendicular.setLength(fHalfLineWidth);
+
+ // create StartLow, StartHigh, EndLow and EndHigh
+ const B2DPoint aStartLow(rPointA + aPerpendicular);
+ const B2DPoint aStartHigh(rPointA - aPerpendicular);
+ const B2DPoint aEndHigh(rPointB - aPerpendicular);
+ const B2DPoint aEndLow(rPointB + aPerpendicular);
+
+ // create EdgeEntries
+ basegfx::trapezoidhelper::TrDeEdgeEntries aTrDeEdgeEntries;
+
+ aTrDeEdgeEntries.emplace_back(&aStartLow, &aStartHigh, 0);
+ aTrDeEdgeEntries.emplace_back(&aStartHigh, &aEndHigh, 0);
+ aTrDeEdgeEntries.emplace_back(&aEndHigh, &aEndLow, 0);
+ aTrDeEdgeEntries.emplace_back(&aEndLow, &aStartLow, 0);
+ aTrDeEdgeEntries.sort();
+
+ // here we know we have exactly four edges, and they do not cut, touch or
+ // intersect. This makes processing much easier. Get the first two as start
+ // edges for the thought trapezoid
+ basegfx::trapezoidhelper::TrDeEdgeEntries::iterator aCurrent(aTrDeEdgeEntries.begin());
+ basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft(*aCurrent++);
+ basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight(*aCurrent++);
+ const bool bEndOnSameLine(fTools::equal(aLeft.getEnd().getY(), aRight.getEnd().getY()));
+
+ if(bEndOnSameLine)
+ {
+ // create two triangle trapezoids
+ ro_Result.emplace_back(
+ aLeft.getStart().getX(),
+ aRight.getStart().getX(),
+ aLeft.getStart().getY(),
+ aLeft.getEnd().getX(),
+ aRight.getEnd().getX(),
+ aLeft.getEnd().getY());
+
+ basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
+ basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
+
+ ro_Result.emplace_back(
+ aLeft2.getStart().getX(),
+ aRight2.getStart().getX(),
+ aLeft2.getStart().getY(),
+ aLeft2.getEnd().getX(),
+ aRight2.getEnd().getX(),
+ aLeft2.getEnd().getY());
+ }
+ else
+ {
+ // create three trapezoids. Check which edge is longer and
+ // correct accordingly
+ const bool bLeftIsLonger(fTools::more(aLeft.getEnd().getY(), aRight.getEnd().getY()));
+
+ if(bLeftIsLonger)
+ {
+ basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
+ basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
+ const B2DPoint aSplitLeft(aLeft.getCutPointForGivenY(aRight.getEnd().getY()));
+ const B2DPoint aSplitRight(aRight2.getCutPointForGivenY(aLeft.getEnd().getY()));
+
+ ro_Result.emplace_back(
+ aLeft.getStart().getX(),
+ aRight.getStart().getX(),
+ aLeft.getStart().getY(),
+ aSplitLeft.getX(),
+ aRight.getEnd().getX(),
+ aRight.getEnd().getY());
+
+ ro_Result.emplace_back(
+ aSplitLeft.getX(),
+ aRight.getEnd().getX(),
+ aRight.getEnd().getY(),
+ aLeft2.getStart().getX(),
+ aSplitRight.getX(),
+ aLeft2.getStart().getY());
+
+ ro_Result.emplace_back(
+ aLeft2.getStart().getX(),
+ aSplitRight.getX(),
+ aLeft2.getStart().getY(),
+ aLeft2.getEnd().getX(),
+ aRight2.getEnd().getX(),
+ aLeft2.getEnd().getY());
+ }
+ else
+ {
+ basegfx::trapezoidhelper::TrDeEdgeEntries::reference aLeft2(*aCurrent++);
+ basegfx::trapezoidhelper::TrDeEdgeEntries::reference aRight2(*aCurrent++);
+ const B2DPoint aSplitRight(aRight.getCutPointForGivenY(aLeft.getEnd().getY()));
+ const B2DPoint aSplitLeft(aLeft2.getCutPointForGivenY(aRight.getEnd().getY()));
+
+ ro_Result.emplace_back(
+ aLeft.getStart().getX(),
+ aRight.getStart().getX(),
+ aLeft.getStart().getY(),
+ aLeft.getEnd().getX(),
+ aSplitRight.getX(),
+ aLeft.getEnd().getY());
+
+ ro_Result.emplace_back(
+ aLeft.getEnd().getX(),
+ aSplitRight.getX(),
+ aLeft.getEnd().getY(),
+ aSplitLeft.getX(),
+ aRight.getEnd().getX(),
+ aRight2.getStart().getY());
+
+ ro_Result.emplace_back(
+ aSplitLeft.getX(),
+ aRight.getEnd().getX(),
+ aRight2.getStart().getY(),
+ aLeft2.getEnd().getX(),
+ aRight2.getEnd().getX(),
+ aLeft2.getEnd().getY());
+ }
+ }
+ }
+ }
+
+ void createLineTrapezoidFromB2DPolygon(
+ B2DTrapezoidVector& ro_Result,
+ const B2DPolygon& rPolygon,
+ double fLineWidth)
+ {
+ if(fTools::lessOrEqual(fLineWidth, 0.0))
+ {
+ return;
+ }
+
+ // ensure there are no curves used
+ B2DPolygon aSource(rPolygon);
+
+ if(aSource.areControlPointsUsed())
+ {
+ const double fPrecisionFactor = 0.25;
+ aSource = adaptiveSubdivideByDistance( aSource, fLineWidth * fPrecisionFactor );
+ }
+
+ const sal_uInt32 nPointCount(aSource.count());
+
+ if(!nPointCount)
+ {
+ return;
+ }
+
+ const sal_uInt32 nEdgeCount(aSource.isClosed() ? nPointCount : nPointCount - 1);
+ B2DPoint aCurrent(aSource.getB2DPoint(0));
+
+ ro_Result.reserve(ro_Result.size() + (3 * nEdgeCount));
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B2DPoint aNext(aSource.getB2DPoint(nNextIndex));
+
+ createLineTrapezoidFromEdge(ro_Result, aCurrent, aNext, fLineWidth);
+ aCurrent = aNext;
+ }
+ }
+
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b3dpolygon.cxx b/basegfx/source/polygon/b3dpolygon.cxx
new file mode 100644
index 0000000000..ebd9e3f4f7
--- /dev/null
+++ b/basegfx/source/polygon/b3dpolygon.cxx
@@ -0,0 +1,1612 @@
+/* -*- 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 <osl/diagnose.h>
+#include <basegfx/polygon/b3dpolygon.hxx>
+#include <basegfx/point/b3dpoint.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/color/bcolor.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <cassert>
+#include <memory>
+#include <utility>
+#include <vector>
+#include <algorithm>
+
+namespace {
+
+class CoordinateData3D
+{
+ basegfx::B3DPoint maPoint;
+
+public:
+ CoordinateData3D()
+ {
+ }
+
+ explicit CoordinateData3D(const basegfx::B3DPoint& rData)
+ : maPoint(rData)
+ {
+ }
+
+ const basegfx::B3DPoint& getCoordinate() const
+ {
+ return maPoint;
+ }
+
+ void setCoordinate(const basegfx::B3DPoint& rValue)
+ {
+ if(rValue != maPoint)
+ maPoint = rValue;
+ }
+
+ bool operator==(const CoordinateData3D& rData) const
+ {
+ return (maPoint == rData.getCoordinate());
+ }
+
+ void transform(const basegfx::B3DHomMatrix& rMatrix)
+ {
+ maPoint *= rMatrix;
+ }
+};
+
+class CoordinateDataArray3D
+{
+ typedef std::vector< CoordinateData3D > CoordinateData3DVector;
+
+ CoordinateData3DVector maVector;
+
+public:
+ explicit CoordinateDataArray3D(sal_uInt32 nCount)
+ : maVector(nCount)
+ {
+ }
+
+ CoordinateDataArray3D(const CoordinateDataArray3D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maVector(rOriginal.maVector.begin() + nIndex, rOriginal.maVector.begin() + (nIndex + nCount))
+ {
+ }
+
+ ::basegfx::B3DVector getNormal() const
+ {
+ ::basegfx::B3DVector aRetval;
+ const sal_uInt32 nPointCount(maVector.size());
+
+ if(nPointCount > 2)
+ {
+ sal_uInt32 nISmallest(0);
+ sal_uInt32 a(0);
+ const basegfx::B3DPoint* pSmallest(&maVector[0].getCoordinate());
+ const basegfx::B3DPoint* pNext(nullptr);
+ const basegfx::B3DPoint* pPrev(nullptr);
+
+ // To guarantee a correctly oriented point, choose an outmost one
+ // which then cannot be concave
+ for(a = 1; a < nPointCount; a++)
+ {
+ const basegfx::B3DPoint& rCandidate = maVector[a].getCoordinate();
+
+ if((rCandidate.getX() < pSmallest->getX())
+ || (rCandidate.getX() == pSmallest->getX() && rCandidate.getY() < pSmallest->getY())
+ || (rCandidate.getX() == pSmallest->getX() && rCandidate.getY() == pSmallest->getY() && rCandidate.getZ() < pSmallest->getZ()))
+ {
+ nISmallest = a;
+ pSmallest = &rCandidate;
+ }
+ }
+
+ // look for a next point different from minimal one
+ for(a = (nISmallest + 1) % nPointCount; a != nISmallest; a = (a + 1) % nPointCount)
+ {
+ const basegfx::B3DPoint& rCandidate = maVector[a].getCoordinate();
+
+ if(!rCandidate.equal(*pSmallest))
+ {
+ pNext = &rCandidate;
+ break;
+ }
+ }
+
+ // look for a previous point different from minimal one
+ for(a = (nISmallest + nPointCount - 1) % nPointCount; a != nISmallest; a = (a + nPointCount - 1) % nPointCount)
+ {
+ const basegfx::B3DPoint& rCandidate = maVector[a].getCoordinate();
+
+ if(!rCandidate.equal(*pSmallest))
+ {
+ pPrev = &rCandidate;
+ break;
+ }
+ }
+
+ // we always have a minimal point. If we also have a different next and previous,
+ // we can calculate the normal
+ if(pNext && pPrev)
+ {
+ const basegfx::B3DVector aPrev(*pPrev - *pSmallest);
+ const basegfx::B3DVector aNext(*pNext - *pSmallest);
+
+ aRetval = cross(aPrev, aNext);
+ aRetval.normalize();
+ }
+ }
+
+ return aRetval;
+ }
+
+ sal_uInt32 count() const
+ {
+ return maVector.size();
+ }
+
+ bool operator==(const CoordinateDataArray3D& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ const basegfx::B3DPoint& getCoordinate(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex].getCoordinate();
+ }
+
+ void setCoordinate(sal_uInt32 nIndex, const basegfx::B3DPoint& rValue)
+ {
+ maVector[nIndex].setCoordinate(rValue);
+ }
+
+ void insert(sal_uInt32 nIndex, const CoordinateData3D& rValue, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rValue
+ CoordinateData3DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ maVector.insert(aIndex, nCount, rValue);
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const CoordinateDataArray3D& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maVector.size());
+
+ if(nCount)
+ {
+ // insert data
+ CoordinateData3DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ CoordinateData3DVector::const_iterator aStart(rSource.maVector.begin());
+ CoordinateData3DVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // remove point data
+ CoordinateData3DVector::iterator aStart(maVector.begin());
+ aStart += nIndex;
+ const CoordinateData3DVector::iterator aEnd(aStart + nCount);
+ maVector.erase(aStart, aEnd);
+ }
+ }
+
+ void flip()
+ {
+ if(maVector.size() <= 1)
+ return;
+
+ const sal_uInt32 nHalfSize(maVector.size() >> 1);
+ CoordinateData3DVector::iterator aStart(maVector.begin());
+ CoordinateData3DVector::iterator aEnd(maVector.end() - 1);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ std::swap(*aStart, *aEnd);
+ ++aStart;
+ --aEnd;
+ }
+ }
+
+ void transform(const ::basegfx::B3DHomMatrix& rMatrix)
+ {
+ for (auto & elem : maVector)
+ {
+ elem.transform(rMatrix);
+ }
+ }
+};
+
+class BColorArray
+{
+ typedef std::vector< ::basegfx::BColor > BColorDataVector;
+
+ BColorDataVector maVector;
+ sal_uInt32 mnUsedEntries;
+
+public:
+ explicit BColorArray(sal_uInt32 nCount)
+ : maVector(nCount),
+ mnUsedEntries(0)
+ {
+ }
+
+ BColorArray(const BColorArray& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : mnUsedEntries(0)
+ {
+ BColorDataVector::const_iterator aStart(rOriginal.maVector.begin());
+ aStart += nIndex;
+ BColorDataVector::const_iterator aEnd(aStart);
+ assert(nCount <= rOriginal.maVector.size());
+ aEnd += nCount;
+ maVector.reserve(nCount);
+
+ for(; aStart != aEnd; ++aStart)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries++;
+
+ maVector.push_back(*aStart);
+ }
+ }
+
+ bool operator==(const BColorArray& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ bool isUsed() const
+ {
+ return (mnUsedEntries != 0);
+ }
+
+ const ::basegfx::BColor& getBColor(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex];
+ }
+
+ void setBColor(sal_uInt32 nIndex, const ::basegfx::BColor& rValue)
+ {
+ bool bWasUsed(mnUsedEntries && !maVector[nIndex].equalZero());
+ bool bIsUsed(!rValue.equalZero());
+
+ if(bWasUsed)
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ }
+ else
+ {
+ maVector[nIndex] = ::basegfx::BColor::getEmptyBColor();
+ mnUsedEntries--;
+ }
+ }
+ else
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ mnUsedEntries++;
+ }
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::BColor& rValue, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rValue
+ BColorDataVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ maVector.insert(aIndex, nCount, rValue);
+
+ if(!rValue.equalZero())
+ mnUsedEntries += nCount;
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const BColorArray& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maVector.size());
+
+ if(nCount)
+ {
+ // insert data
+ BColorDataVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ BColorDataVector::const_iterator aStart(rSource.maVector.begin());
+ BColorDataVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+
+ mnUsedEntries += std::count_if(aStart, aEnd,
+ [](BColorDataVector::const_reference rData) { return !rData.equalZero(); });
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ const BColorDataVector::iterator aDeleteStart(maVector.begin() + nIndex);
+ const BColorDataVector::iterator aDeleteEnd(aDeleteStart + nCount);
+
+ auto nDeleteUsed = std::count_if(aDeleteStart, aDeleteEnd,
+ [](BColorDataVector::const_reference rData) { return !rData.equalZero(); });
+ mnUsedEntries -= std::min(mnUsedEntries, static_cast<sal_uInt32>(nDeleteUsed));
+
+ // remove point data
+ maVector.erase(aDeleteStart, aDeleteEnd);
+ }
+ }
+
+ void flip()
+ {
+ if(maVector.size() <= 1)
+ return;
+
+ const sal_uInt32 nHalfSize(maVector.size() >> 1);
+ BColorDataVector::iterator aStart(maVector.begin());
+ BColorDataVector::iterator aEnd(maVector.end() - 1);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ std::swap(*aStart, *aEnd);
+ ++aStart;
+ --aEnd;
+ }
+ }
+};
+
+class NormalsArray3D
+{
+ typedef std::vector< ::basegfx::B3DVector > NormalsData3DVector;
+
+ NormalsData3DVector maVector;
+ sal_uInt32 mnUsedEntries;
+
+public:
+ explicit NormalsArray3D(sal_uInt32 nCount)
+ : maVector(nCount),
+ mnUsedEntries(0)
+ {
+ }
+
+ NormalsArray3D(const NormalsArray3D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : mnUsedEntries(0)
+ {
+ NormalsData3DVector::const_iterator aStart(rOriginal.maVector.begin());
+ aStart += nIndex;
+ NormalsData3DVector::const_iterator aEnd(aStart);
+ aEnd += nCount;
+ maVector.reserve(nCount);
+
+ for(; aStart != aEnd; ++aStart)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries++;
+
+ maVector.push_back(*aStart);
+ }
+ }
+
+ bool operator==(const NormalsArray3D& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ bool isUsed() const
+ {
+ return (mnUsedEntries != 0);
+ }
+
+ const ::basegfx::B3DVector& getNormal(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex];
+ }
+
+ void setNormal(sal_uInt32 nIndex, const ::basegfx::B3DVector& rValue)
+ {
+ bool bWasUsed(mnUsedEntries && !maVector[nIndex].equalZero());
+ bool bIsUsed(!rValue.equalZero());
+
+ if(bWasUsed)
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ }
+ else
+ {
+ maVector[nIndex] = ::basegfx::B3DVector::getEmptyVector();
+ mnUsedEntries--;
+ }
+ }
+ else
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ mnUsedEntries++;
+ }
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::B3DVector& rValue, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rValue
+ NormalsData3DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ maVector.insert(aIndex, nCount, rValue);
+
+ if(!rValue.equalZero())
+ mnUsedEntries += nCount;
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const NormalsArray3D& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maVector.size());
+
+ if(nCount)
+ {
+ // insert data
+ NormalsData3DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ NormalsData3DVector::const_iterator aStart(rSource.maVector.begin());
+ NormalsData3DVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+
+ mnUsedEntries += std::count_if(aStart, aEnd,
+ [](NormalsData3DVector::const_reference rData) { return !rData.equalZero(); });
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ const NormalsData3DVector::iterator aDeleteStart(maVector.begin() + nIndex);
+ const NormalsData3DVector::iterator aDeleteEnd(aDeleteStart + nCount);
+
+ auto nDeleteUsed = std::count_if(aDeleteStart, aDeleteEnd,
+ [](NormalsData3DVector::const_reference rData) { return !rData.equalZero(); });
+ mnUsedEntries -= std::min(mnUsedEntries, static_cast<sal_uInt32>(nDeleteUsed));
+
+ // remove point data
+ maVector.erase(aDeleteStart, aDeleteEnd);
+ }
+ }
+
+ void flip()
+ {
+ if(maVector.size() <= 1)
+ return;
+
+ const sal_uInt32 nHalfSize(maVector.size() >> 1);
+ NormalsData3DVector::iterator aStart(maVector.begin());
+ NormalsData3DVector::iterator aEnd(maVector.end() - 1);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ std::swap(*aStart, *aEnd);
+ ++aStart;
+ --aEnd;
+ }
+ }
+
+ void transform(const basegfx::B3DHomMatrix& rMatrix)
+ {
+ for (auto & elem : maVector)
+ {
+ elem *= rMatrix;
+ }
+ }
+};
+
+class TextureCoordinate2D
+{
+ typedef std::vector< ::basegfx::B2DPoint > TextureData2DVector;
+
+ TextureData2DVector maVector;
+ sal_uInt32 mnUsedEntries;
+
+public:
+ explicit TextureCoordinate2D(sal_uInt32 nCount)
+ : maVector(nCount),
+ mnUsedEntries(0)
+ {
+ }
+
+ TextureCoordinate2D(const TextureCoordinate2D& rOriginal, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : mnUsedEntries(0)
+ {
+ TextureData2DVector::const_iterator aStart(rOriginal.maVector.begin());
+ aStart += nIndex;
+ TextureData2DVector::const_iterator aEnd(aStart);
+ aEnd += nCount;
+ maVector.reserve(nCount);
+
+ for(; aStart != aEnd; ++aStart)
+ {
+ if(!aStart->equalZero())
+ mnUsedEntries++;
+
+ maVector.push_back(*aStart);
+ }
+ }
+
+ bool operator==(const TextureCoordinate2D& rCandidate) const
+ {
+ return (maVector == rCandidate.maVector);
+ }
+
+ bool isUsed() const
+ {
+ return (mnUsedEntries != 0);
+ }
+
+ const ::basegfx::B2DPoint& getTextureCoordinate(sal_uInt32 nIndex) const
+ {
+ return maVector[nIndex];
+ }
+
+ void setTextureCoordinate(sal_uInt32 nIndex, const ::basegfx::B2DPoint& rValue)
+ {
+ bool bWasUsed(mnUsedEntries && !maVector[nIndex].equalZero());
+ bool bIsUsed(!rValue.equalZero());
+
+ if(bWasUsed)
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ }
+ else
+ {
+ maVector[nIndex] = ::basegfx::B2DPoint::getEmptyPoint();
+ mnUsedEntries--;
+ }
+ }
+ else
+ {
+ if(bIsUsed)
+ {
+ maVector[nIndex] = rValue;
+ mnUsedEntries++;
+ }
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::B2DPoint& rValue, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rValue
+ TextureData2DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ maVector.insert(aIndex, nCount, rValue);
+
+ if(!rValue.equalZero())
+ mnUsedEntries += nCount;
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const TextureCoordinate2D& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maVector.size());
+
+ if(nCount)
+ {
+ // insert data
+ TextureData2DVector::iterator aIndex(maVector.begin());
+ aIndex += nIndex;
+ TextureData2DVector::const_iterator aStart(rSource.maVector.begin());
+ TextureData2DVector::const_iterator aEnd(rSource.maVector.end());
+ maVector.insert(aIndex, aStart, aEnd);
+
+ mnUsedEntries += std::count_if(aStart, aEnd,
+ [](TextureData2DVector::const_reference rData) { return !rData.equalZero(); });
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ const TextureData2DVector::iterator aDeleteStart(maVector.begin() + nIndex);
+ const TextureData2DVector::iterator aDeleteEnd(aDeleteStart + nCount);
+
+ auto nDeleteUsed = std::count_if(aDeleteStart, aDeleteEnd,
+ [](TextureData2DVector::const_reference rData) { return !rData.equalZero(); });
+ mnUsedEntries -= std::min(mnUsedEntries, static_cast<sal_uInt32>(nDeleteUsed));
+
+ // remove point data
+ maVector.erase(aDeleteStart, aDeleteEnd);
+ }
+ }
+
+ void flip()
+ {
+ if(maVector.size() <= 1)
+ return;
+
+ const sal_uInt32 nHalfSize(maVector.size() >> 1);
+ TextureData2DVector::iterator aStart(maVector.begin());
+ TextureData2DVector::iterator aEnd(maVector.end() - 1);
+
+ for(sal_uInt32 a(0); a < nHalfSize; a++)
+ {
+ std::swap(*aStart, *aEnd);
+ ++aStart;
+ --aEnd;
+ }
+ }
+
+ void transform(const ::basegfx::B2DHomMatrix& rMatrix)
+ {
+ for (auto & elem : maVector)
+ {
+ elem *= rMatrix;
+ }
+ }
+};
+
+}
+
+class ImplB3DPolygon
+{
+ // The point vector. This vector exists always and defines the
+ // count of members.
+ CoordinateDataArray3D maPoints;
+
+ // The BColor vector. This vectors are created on demand
+ // and may be zero.
+ std::unique_ptr<BColorArray> mpBColors;
+
+ // The Normals vector. This vectors are created on demand
+ // and may be zero.
+ std::unique_ptr<NormalsArray3D> mpNormals;
+
+ // The TextureCoordinates vector. This vectors are created on demand
+ // and may be zero.
+ std::unique_ptr<TextureCoordinate2D> mpTextureCoordinates;
+
+ // The calculated plane normal. mbPlaneNormalValid says if it's valid.
+ ::basegfx::B3DVector maPlaneNormal;
+
+ // flag which decides if this polygon is opened or closed
+ bool mbIsClosed : 1;
+
+ // flag which says if maPlaneNormal is up-to-date
+ bool mbPlaneNormalValid : 1;
+
+protected:
+ void invalidatePlaneNormal()
+ {
+ if(mbPlaneNormalValid)
+ {
+ mbPlaneNormalValid = false;
+ }
+ }
+
+public:
+ // This constructor is only used from the static identity polygon, thus
+ // the RefCount is set to 1 to never 'delete' this static incarnation.
+ ImplB3DPolygon()
+ : maPoints(0),
+ maPlaneNormal(::basegfx::B3DVector::getEmptyVector()),
+ mbIsClosed(false),
+ mbPlaneNormalValid(true)
+ {
+ // complete initialization with defaults
+ }
+
+ ImplB3DPolygon(const ImplB3DPolygon& rToBeCopied)
+ : maPoints(rToBeCopied.maPoints),
+ maPlaneNormal(rToBeCopied.maPlaneNormal),
+ mbIsClosed(rToBeCopied.mbIsClosed),
+ mbPlaneNormalValid(rToBeCopied.mbPlaneNormalValid)
+ {
+ // complete initialization using copy
+ if(rToBeCopied.mpBColors && rToBeCopied.mpBColors->isUsed())
+ {
+ mpBColors.reset( new BColorArray(*rToBeCopied.mpBColors) );
+ }
+
+ if(rToBeCopied.mpNormals && rToBeCopied.mpNormals->isUsed())
+ {
+ mpNormals.reset( new NormalsArray3D(*rToBeCopied.mpNormals) );
+ }
+
+ if(rToBeCopied.mpTextureCoordinates && rToBeCopied.mpTextureCoordinates->isUsed())
+ {
+ mpTextureCoordinates.reset( new TextureCoordinate2D(*rToBeCopied.mpTextureCoordinates) );
+ }
+ }
+
+ ImplB3DPolygon(const ImplB3DPolygon& rToBeCopied, sal_uInt32 nIndex, sal_uInt32 nCount)
+ : maPoints(rToBeCopied.maPoints, nIndex, nCount),
+ maPlaneNormal(::basegfx::B3DVector::getEmptyVector()),
+ mbIsClosed(rToBeCopied.mbIsClosed),
+ mbPlaneNormalValid(false)
+ {
+ // complete initialization using partly copy
+ if(rToBeCopied.mpBColors && rToBeCopied.mpBColors->isUsed())
+ {
+ mpBColors.reset( new BColorArray(*rToBeCopied.mpBColors, nIndex, nCount) );
+
+ if(!mpBColors->isUsed())
+ {
+ mpBColors.reset();
+ }
+ }
+
+ if(rToBeCopied.mpNormals && rToBeCopied.mpNormals->isUsed())
+ {
+ mpNormals.reset( new NormalsArray3D(*rToBeCopied.mpNormals, nIndex, nCount) );
+
+ if(!mpNormals->isUsed())
+ {
+ mpNormals.reset();
+ }
+ }
+
+ if(rToBeCopied.mpTextureCoordinates && rToBeCopied.mpTextureCoordinates->isUsed())
+ {
+ mpTextureCoordinates.reset( new TextureCoordinate2D(*rToBeCopied.mpTextureCoordinates, nIndex, nCount) );
+
+ if(!mpTextureCoordinates->isUsed())
+ {
+ mpTextureCoordinates.reset();
+ }
+ }
+ }
+
+ sal_uInt32 count() const
+ {
+ return maPoints.count();
+ }
+
+ bool isClosed() const
+ {
+ return mbIsClosed;
+ }
+
+ void setClosed(bool bNew)
+ {
+ if(bNew != mbIsClosed)
+ {
+ mbIsClosed = bNew;
+ }
+ }
+
+ bool impBColorsAreEqual(const ImplB3DPolygon& rCandidate) const
+ {
+ bool bBColorsAreEqual(true);
+
+ if(mpBColors)
+ {
+ if(rCandidate.mpBColors)
+ {
+ bBColorsAreEqual = (*mpBColors == *rCandidate.mpBColors);
+ }
+ else
+ {
+ // candidate has no BColors, so it's assumed all unused.
+ bBColorsAreEqual = !mpBColors->isUsed();
+ }
+ }
+ else
+ {
+ if(rCandidate.mpBColors)
+ {
+ // we have no TextureCoordinates, so it's assumed all unused.
+ bBColorsAreEqual = !rCandidate.mpBColors->isUsed();
+ }
+ }
+
+ return bBColorsAreEqual;
+ }
+
+ bool impNormalsAreEqual(const ImplB3DPolygon& rCandidate) const
+ {
+ bool bNormalsAreEqual(true);
+
+ if(mpNormals)
+ {
+ if(rCandidate.mpNormals)
+ {
+ bNormalsAreEqual = (*mpNormals == *rCandidate.mpNormals);
+ }
+ else
+ {
+ // candidate has no normals, so it's assumed all unused.
+ bNormalsAreEqual = !mpNormals->isUsed();
+ }
+ }
+ else
+ {
+ if(rCandidate.mpNormals)
+ {
+ // we have no normals, so it's assumed all unused.
+ bNormalsAreEqual = !rCandidate.mpNormals->isUsed();
+ }
+ }
+
+ return bNormalsAreEqual;
+ }
+
+ bool impTextureCoordinatesAreEqual(const ImplB3DPolygon& rCandidate) const
+ {
+ bool bTextureCoordinatesAreEqual(true);
+
+ if(mpTextureCoordinates)
+ {
+ if(rCandidate.mpTextureCoordinates)
+ {
+ bTextureCoordinatesAreEqual = (*mpTextureCoordinates == *rCandidate.mpTextureCoordinates);
+ }
+ else
+ {
+ // candidate has no TextureCoordinates, so it's assumed all unused.
+ bTextureCoordinatesAreEqual = !mpTextureCoordinates->isUsed();
+ }
+ }
+ else
+ {
+ if(rCandidate.mpTextureCoordinates)
+ {
+ // we have no TextureCoordinates, so it's assumed all unused.
+ bTextureCoordinatesAreEqual = !rCandidate.mpTextureCoordinates->isUsed();
+ }
+ }
+
+ return bTextureCoordinatesAreEqual;
+ }
+
+ bool operator==(const ImplB3DPolygon& rCandidate) const
+ {
+ if(mbIsClosed == rCandidate.mbIsClosed)
+ {
+ if(maPoints == rCandidate.maPoints)
+ {
+ if(impBColorsAreEqual(rCandidate))
+ {
+ if(impNormalsAreEqual(rCandidate))
+ {
+ if(impTextureCoordinatesAreEqual(rCandidate))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ const ::basegfx::B3DPoint& getPoint(sal_uInt32 nIndex) const
+ {
+ return maPoints.getCoordinate(nIndex);
+ }
+
+ void setPoint(sal_uInt32 nIndex, const ::basegfx::B3DPoint& rValue)
+ {
+ maPoints.setCoordinate(nIndex, rValue);
+ invalidatePlaneNormal();
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::B3DPoint& rPoint, sal_uInt32 nCount)
+ {
+ if(!nCount)
+ return;
+
+ CoordinateData3D aCoordinate(rPoint);
+ maPoints.insert(nIndex, aCoordinate, nCount);
+ invalidatePlaneNormal();
+
+ if(mpBColors)
+ {
+ mpBColors->insert(nIndex, ::basegfx::BColor::getEmptyBColor(), nCount);
+ }
+
+ if(mpNormals)
+ {
+ mpNormals->insert(nIndex, ::basegfx::B3DVector::getEmptyVector(), nCount);
+ }
+
+ if(mpTextureCoordinates)
+ {
+ mpTextureCoordinates->insert(nIndex, ::basegfx::B2DPoint::getEmptyPoint(), nCount);
+ }
+ }
+
+ const ::basegfx::BColor& getBColor(sal_uInt32 nIndex) const
+ {
+ if(mpBColors)
+ {
+ return mpBColors->getBColor(nIndex);
+ }
+ else
+ {
+ return ::basegfx::BColor::getEmptyBColor();
+ }
+ }
+
+ void setBColor(sal_uInt32 nIndex, const ::basegfx::BColor& rValue)
+ {
+ if(!mpBColors)
+ {
+ if(!rValue.equalZero())
+ {
+ mpBColors.reset( new BColorArray(maPoints.count()) );
+ mpBColors->setBColor(nIndex, rValue);
+ }
+ }
+ else
+ {
+ mpBColors->setBColor(nIndex, rValue);
+
+ if(!mpBColors->isUsed())
+ {
+ mpBColors.reset();
+ }
+ }
+ }
+
+ bool areBColorsUsed() const
+ {
+ return (mpBColors && mpBColors->isUsed());
+ }
+
+ void clearBColors()
+ {
+ mpBColors.reset();
+ }
+
+ const ::basegfx::B3DVector& getNormal() const
+ {
+ if(!mbPlaneNormalValid)
+ {
+ const_cast< ImplB3DPolygon* >(this)->maPlaneNormal = maPoints.getNormal();
+ const_cast< ImplB3DPolygon* >(this)->mbPlaneNormalValid = true;
+ }
+
+ return maPlaneNormal;
+ }
+
+ const ::basegfx::B3DVector& getNormal(sal_uInt32 nIndex) const
+ {
+ if(mpNormals)
+ {
+ return mpNormals->getNormal(nIndex);
+ }
+ else
+ {
+ return ::basegfx::B3DVector::getEmptyVector();
+ }
+ }
+
+ void setNormal(sal_uInt32 nIndex, const ::basegfx::B3DVector& rValue)
+ {
+ if(!mpNormals)
+ {
+ if(!rValue.equalZero())
+ {
+ mpNormals.reset( new NormalsArray3D(maPoints.count()) );
+ mpNormals->setNormal(nIndex, rValue);
+ }
+ }
+ else
+ {
+ mpNormals->setNormal(nIndex, rValue);
+
+ if(!mpNormals->isUsed())
+ {
+ mpNormals.reset();
+ }
+ }
+ }
+
+ void transformNormals(const ::basegfx::B3DHomMatrix& rMatrix)
+ {
+ if(mpNormals)
+ {
+ mpNormals->transform(rMatrix);
+ }
+ }
+
+ bool areNormalsUsed() const
+ {
+ return (mpNormals && mpNormals->isUsed());
+ }
+
+ void clearNormals()
+ {
+ mpNormals.reset();
+ }
+
+ const ::basegfx::B2DPoint& getTextureCoordinate(sal_uInt32 nIndex) const
+ {
+ if(mpTextureCoordinates)
+ {
+ return mpTextureCoordinates->getTextureCoordinate(nIndex);
+ }
+ else
+ {
+ return ::basegfx::B2DPoint::getEmptyPoint();
+ }
+ }
+
+ void setTextureCoordinate(sal_uInt32 nIndex, const ::basegfx::B2DPoint& rValue)
+ {
+ if(!mpTextureCoordinates)
+ {
+ if(!rValue.equalZero())
+ {
+ mpTextureCoordinates.reset( new TextureCoordinate2D(maPoints.count()) );
+ mpTextureCoordinates->setTextureCoordinate(nIndex, rValue);
+ }
+ }
+ else
+ {
+ mpTextureCoordinates->setTextureCoordinate(nIndex, rValue);
+
+ if(!mpTextureCoordinates->isUsed())
+ {
+ mpTextureCoordinates.reset();
+ }
+ }
+ }
+
+ bool areTextureCoordinatesUsed() const
+ {
+ return (mpTextureCoordinates && mpTextureCoordinates->isUsed());
+ }
+
+ void clearTextureCoordinates()
+ {
+ mpTextureCoordinates.reset();
+ }
+
+ void transformTextureCoordinates(const ::basegfx::B2DHomMatrix& rMatrix)
+ {
+ if(mpTextureCoordinates)
+ {
+ mpTextureCoordinates->transform(rMatrix);
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ImplB3DPolygon& rSource)
+ {
+ const sal_uInt32 nCount(rSource.maPoints.count());
+
+ if(!nCount)
+ return;
+
+ maPoints.insert(nIndex, rSource.maPoints);
+ invalidatePlaneNormal();
+
+ if(rSource.mpBColors && rSource.mpBColors->isUsed())
+ {
+ if(!mpBColors)
+ {
+ mpBColors.reset( new BColorArray(maPoints.count()) );
+ }
+
+ mpBColors->insert(nIndex, *rSource.mpBColors);
+ }
+ else
+ {
+ if(mpBColors)
+ {
+ mpBColors->insert(nIndex, ::basegfx::BColor::getEmptyBColor(), nCount);
+ }
+ }
+
+ if(rSource.mpNormals && rSource.mpNormals->isUsed())
+ {
+ if(!mpNormals)
+ {
+ mpNormals.reset( new NormalsArray3D(maPoints.count()) );
+ }
+
+ mpNormals->insert(nIndex, *rSource.mpNormals);
+ }
+ else
+ {
+ if(mpNormals)
+ {
+ mpNormals->insert(nIndex, ::basegfx::B3DVector::getEmptyVector(), nCount);
+ }
+ }
+
+ if(rSource.mpTextureCoordinates && rSource.mpTextureCoordinates->isUsed())
+ {
+ if(!mpTextureCoordinates)
+ {
+ mpTextureCoordinates.reset( new TextureCoordinate2D(maPoints.count()) );
+ }
+
+ mpTextureCoordinates->insert(nIndex, *rSource.mpTextureCoordinates);
+ }
+ else
+ {
+ if(mpTextureCoordinates)
+ {
+ mpTextureCoordinates->insert(nIndex, ::basegfx::B2DPoint::getEmptyPoint(), nCount);
+ }
+ }
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(!nCount)
+ return;
+
+ maPoints.remove(nIndex, nCount);
+ invalidatePlaneNormal();
+
+ if(mpBColors)
+ {
+ mpBColors->remove(nIndex, nCount);
+
+ if(!mpBColors->isUsed())
+ {
+ mpBColors.reset();
+ }
+ }
+
+ if(mpNormals)
+ {
+ mpNormals->remove(nIndex, nCount);
+
+ if(!mpNormals->isUsed())
+ {
+ mpNormals.reset();
+ }
+ }
+
+ if(mpTextureCoordinates)
+ {
+ mpTextureCoordinates->remove(nIndex, nCount);
+
+ if(!mpTextureCoordinates->isUsed())
+ {
+ mpTextureCoordinates.reset();
+ }
+ }
+ }
+
+ void flip()
+ {
+ if(maPoints.count() <= 1)
+ return;
+
+ maPoints.flip();
+
+ if(mbPlaneNormalValid)
+ {
+ // mirror plane normal
+ maPlaneNormal = -maPlaneNormal;
+ }
+
+ if(mpBColors)
+ {
+ mpBColors->flip();
+ }
+
+ if(mpNormals)
+ {
+ mpNormals->flip();
+ }
+
+ if(mpTextureCoordinates)
+ {
+ mpTextureCoordinates->flip();
+ }
+ }
+
+ bool hasDoublePoints() const
+ {
+ if(mbIsClosed)
+ {
+ // check for same start and end point
+ const sal_uInt32 nIndex(maPoints.count() - 1);
+
+ if(maPoints.getCoordinate(0) == maPoints.getCoordinate(nIndex))
+ {
+ const bool bBColorEqual(!mpBColors || (mpBColors->getBColor(0) == mpBColors->getBColor(nIndex)));
+
+ if(bBColorEqual)
+ {
+ const bool bNormalsEqual(!mpNormals || (mpNormals->getNormal(0) == mpNormals->getNormal(nIndex)));
+
+ if(bNormalsEqual)
+ {
+ const bool bTextureCoordinatesEqual(!mpTextureCoordinates || (mpTextureCoordinates->getTextureCoordinate(0) == mpTextureCoordinates->getTextureCoordinate(nIndex)));
+
+ if(bTextureCoordinatesEqual)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ // test for range
+ for(sal_uInt32 a(0); a < maPoints.count() - 1; a++)
+ {
+ if(maPoints.getCoordinate(a) == maPoints.getCoordinate(a + 1))
+ {
+ const bool bBColorEqual(!mpBColors || (mpBColors->getBColor(a) == mpBColors->getBColor(a + 1)));
+
+ if(bBColorEqual)
+ {
+ const bool bNormalsEqual(!mpNormals || (mpNormals->getNormal(a) == mpNormals->getNormal(a + 1)));
+
+ if(bNormalsEqual)
+ {
+ const bool bTextureCoordinatesEqual(!mpTextureCoordinates || (mpTextureCoordinates->getTextureCoordinate(a) == mpTextureCoordinates->getTextureCoordinate(a + 1)));
+
+ if(bTextureCoordinatesEqual)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ void removeDoublePointsAtBeginEnd()
+ {
+ // Only remove DoublePoints at Begin and End when poly is closed
+ if(!mbIsClosed)
+ return;
+
+ bool bRemove;
+
+ do
+ {
+ bRemove = false;
+
+ if(maPoints.count() > 1)
+ {
+ const sal_uInt32 nIndex(maPoints.count() - 1);
+ bRemove = (maPoints.getCoordinate(0) == maPoints.getCoordinate(nIndex));
+
+ if(bRemove && mpBColors && mpBColors->getBColor(0) != mpBColors->getBColor(nIndex))
+ {
+ bRemove = false;
+ }
+
+ if(bRemove && mpNormals && mpNormals->getNormal(0) != mpNormals->getNormal(nIndex))
+ {
+ bRemove = false;
+ }
+
+ if(bRemove && mpTextureCoordinates && mpTextureCoordinates->getTextureCoordinate(0) != mpTextureCoordinates->getTextureCoordinate(nIndex))
+ {
+ bRemove = false;
+ }
+ }
+
+ if(bRemove)
+ {
+ const sal_uInt32 nIndex(maPoints.count() - 1);
+ remove(nIndex, 1);
+ }
+ } while(bRemove);
+ }
+
+ void removeDoublePointsWholeTrack()
+ {
+ sal_uInt32 nIndex(0);
+
+ // test as long as there are at least two points and as long as the index
+ // is smaller or equal second last point
+ while((maPoints.count() > 1) && (nIndex <= maPoints.count() - 2))
+ {
+ const sal_uInt32 nNextIndex(nIndex + 1);
+ bool bRemove(maPoints.getCoordinate(nIndex) == maPoints.getCoordinate(nNextIndex));
+
+ if(bRemove && mpBColors && mpBColors->getBColor(nIndex) != mpBColors->getBColor(nNextIndex))
+ {
+ bRemove = false;
+ }
+
+ if(bRemove && mpNormals && mpNormals->getNormal(nIndex) != mpNormals->getNormal(nNextIndex))
+ {
+ bRemove = false;
+ }
+
+ if(bRemove && mpTextureCoordinates && mpTextureCoordinates->getTextureCoordinate(nIndex) != mpTextureCoordinates->getTextureCoordinate(nNextIndex))
+ {
+ bRemove = false;
+ }
+
+ if(bRemove)
+ {
+ // if next is same as index and the control vectors are unused, delete index
+ remove(nIndex, 1);
+ }
+ else
+ {
+ // if different, step forward
+ nIndex++;
+ }
+ }
+ }
+
+ void transform(const ::basegfx::B3DHomMatrix& rMatrix)
+ {
+ maPoints.transform(rMatrix);
+
+ // Here, it seems to be possible to transform a valid plane normal and to avoid
+ // invalidation, but it's not true. If the transformation contains shears or e.g.
+ // perspective projection, the orthogonality to the transformed plane will not
+ // be preserved. It may be possible to test that at the matrix to not invalidate in
+ // all cases or to extract a matrix which does not 'shear' the vector which is
+ // a normal in this case. As long as this is not sure, i will just invalidate.
+ invalidatePlaneNormal();
+ }
+};
+
+namespace basegfx
+{
+ namespace {
+
+ B3DPolygon::ImplType const & getDefaultPolygon() {
+ static B3DPolygon::ImplType const singleton;
+ return singleton;
+ }
+
+ }
+
+ B3DPolygon::B3DPolygon() :
+ mpPolygon(getDefaultPolygon())
+ {
+ }
+
+ B3DPolygon::B3DPolygon(const B3DPolygon&) = default;
+
+ B3DPolygon::B3DPolygon(B3DPolygon&&) = default;
+
+ B3DPolygon::~B3DPolygon() = default;
+
+ B3DPolygon& B3DPolygon::operator=(const B3DPolygon&) = default;
+
+ B3DPolygon& B3DPolygon::operator=(B3DPolygon&&) = default;
+
+ bool B3DPolygon::operator==(const B3DPolygon& rPolygon) const
+ {
+ if(mpPolygon.same_object(rPolygon.mpPolygon))
+ return true;
+
+ return (*mpPolygon == *rPolygon.mpPolygon);
+ }
+
+ sal_uInt32 B3DPolygon::count() const
+ {
+ return mpPolygon->count();
+ }
+
+ basegfx::B3DPoint const & B3DPolygon::getB3DPoint(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ return mpPolygon->getPoint(nIndex);
+ }
+
+ void B3DPolygon::setB3DPoint(sal_uInt32 nIndex, const basegfx::B3DPoint& rValue)
+ {
+ OSL_ENSURE(nIndex < std::as_const(mpPolygon)->count(), "B3DPolygon access outside range (!)");
+
+ if(getB3DPoint(nIndex) != rValue)
+ mpPolygon->setPoint(nIndex, rValue);
+ }
+
+ BColor const & B3DPolygon::getBColor(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ return mpPolygon->getBColor(nIndex);
+ }
+
+ void B3DPolygon::setBColor(sal_uInt32 nIndex, const BColor& rValue)
+ {
+ OSL_ENSURE(nIndex < std::as_const(mpPolygon)->count(), "B3DPolygon access outside range (!)");
+
+ if(std::as_const(mpPolygon)->getBColor(nIndex) != rValue)
+ mpPolygon->setBColor(nIndex, rValue);
+ }
+
+ bool B3DPolygon::areBColorsUsed() const
+ {
+ return mpPolygon->areBColorsUsed();
+ }
+
+ void B3DPolygon::clearBColors()
+ {
+ if(std::as_const(mpPolygon)->areBColorsUsed())
+ mpPolygon->clearBColors();
+ }
+
+ B3DVector const & B3DPolygon::getNormal() const
+ {
+ return mpPolygon->getNormal();
+ }
+
+ B3DVector const & B3DPolygon::getNormal(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ return mpPolygon->getNormal(nIndex);
+ }
+
+ void B3DPolygon::setNormal(sal_uInt32 nIndex, const B3DVector& rValue)
+ {
+ OSL_ENSURE(nIndex < std::as_const(mpPolygon)->count(), "B3DPolygon access outside range (!)");
+
+ if(std::as_const(mpPolygon)->getNormal(nIndex) != rValue)
+ mpPolygon->setNormal(nIndex, rValue);
+ }
+
+ void B3DPolygon::transformNormals(const B3DHomMatrix& rMatrix)
+ {
+ if(std::as_const(mpPolygon)->areNormalsUsed() && !rMatrix.isIdentity())
+ mpPolygon->transformNormals(rMatrix);
+ }
+
+ bool B3DPolygon::areNormalsUsed() const
+ {
+ return mpPolygon->areNormalsUsed();
+ }
+
+ void B3DPolygon::clearNormals()
+ {
+ if(std::as_const(mpPolygon)->areNormalsUsed())
+ mpPolygon->clearNormals();
+ }
+
+ B2DPoint const & B3DPolygon::getTextureCoordinate(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolygon->count(), "B3DPolygon access outside range (!)");
+
+ return mpPolygon->getTextureCoordinate(nIndex);
+ }
+
+ void B3DPolygon::setTextureCoordinate(sal_uInt32 nIndex, const B2DPoint& rValue)
+ {
+ OSL_ENSURE(nIndex < std::as_const(mpPolygon)->count(), "B3DPolygon access outside range (!)");
+
+ if(std::as_const(mpPolygon)->getTextureCoordinate(nIndex) != rValue)
+ mpPolygon->setTextureCoordinate(nIndex, rValue);
+ }
+
+ void B3DPolygon::transformTextureCoordinates(const B2DHomMatrix& rMatrix)
+ {
+ if(std::as_const(mpPolygon)->areTextureCoordinatesUsed() && !rMatrix.isIdentity())
+ mpPolygon->transformTextureCoordinates(rMatrix);
+ }
+
+ bool B3DPolygon::areTextureCoordinatesUsed() const
+ {
+ return mpPolygon->areTextureCoordinatesUsed();
+ }
+
+ void B3DPolygon::clearTextureCoordinates()
+ {
+ if(std::as_const(mpPolygon)->areTextureCoordinatesUsed())
+ mpPolygon->clearTextureCoordinates();
+ }
+
+ void B3DPolygon::append(const basegfx::B3DPoint& rPoint, sal_uInt32 nCount)
+ {
+ if(nCount)
+ mpPolygon->insert(std::as_const(mpPolygon)->count(), rPoint, nCount);
+ }
+
+ void B3DPolygon::append(const B3DPolygon& rPoly, sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(!rPoly.count())
+ return;
+
+ if(!nCount)
+ {
+ nCount = rPoly.count();
+ }
+
+ if(nIndex == 0 && nCount == rPoly.count())
+ {
+ mpPolygon->insert(std::as_const(mpPolygon)->count(), *rPoly.mpPolygon);
+ }
+ else
+ {
+ OSL_ENSURE(nIndex + nCount <= rPoly.mpPolygon->count(), "B3DPolygon Append outside range (!)");
+ ImplB3DPolygon aTempPoly(*rPoly.mpPolygon, nIndex, nCount);
+ mpPolygon->insert(std::as_const(mpPolygon)->count(), aTempPoly);
+ }
+ }
+
+ void B3DPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex + nCount <= std::as_const(mpPolygon)->count(), "B3DPolygon Remove outside range (!)");
+
+ if(nCount)
+ mpPolygon->remove(nIndex, nCount);
+ }
+
+ void B3DPolygon::clear()
+ {
+ mpPolygon = getDefaultPolygon();
+ }
+
+ bool B3DPolygon::isClosed() const
+ {
+ return mpPolygon->isClosed();
+ }
+
+ void B3DPolygon::setClosed(bool bNew)
+ {
+ if(isClosed() != bNew)
+ mpPolygon->setClosed(bNew);
+ }
+
+ void B3DPolygon::flip()
+ {
+ if(count() > 1)
+ mpPolygon->flip();
+ }
+
+ bool B3DPolygon::hasDoublePoints() const
+ {
+ return (mpPolygon->count() > 1 && mpPolygon->hasDoublePoints());
+ }
+
+ void B3DPolygon::removeDoublePoints()
+ {
+ if(hasDoublePoints())
+ {
+ mpPolygon->removeDoublePointsAtBeginEnd();
+ mpPolygon->removeDoublePointsWholeTrack();
+ }
+ }
+
+ void B3DPolygon::transform(const basegfx::B3DHomMatrix& rMatrix)
+ {
+ if(std::as_const(mpPolygon)->count() && !rMatrix.isIdentity())
+ {
+ mpPolygon->transform(rMatrix);
+ }
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b3dpolygontools.cxx b/basegfx/source/polygon/b3dpolygontools.cxx
new file mode 100644
index 0000000000..7c92f5ddce
--- /dev/null
+++ b/basegfx/source/polygon/b3dpolygontools.cxx
@@ -0,0 +1,826 @@
+/* -*- 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 <osl/diagnose.h>
+#include <basegfx/polygon/b3dpolygontools.hxx>
+#include <basegfx/polygon/b3dpolygon.hxx>
+#include <basegfx/polygon/b3dpolypolygon.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/range/b3drange.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/tuple/b3ituple.hxx>
+#include <cassert>
+#include <numeric>
+
+namespace basegfx::utils
+{
+ // B3DPolygon tools
+ void checkClosed(B3DPolygon& rCandidate)
+ {
+ while(rCandidate.count() > 1
+ && rCandidate.getB3DPoint(0).equal(rCandidate.getB3DPoint(rCandidate.count() - 1)))
+ {
+ rCandidate.setClosed(true);
+ rCandidate.remove(rCandidate.count() - 1);
+ }
+ }
+
+ sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B3DPolygon& rCandidate)
+ {
+ OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
+
+ if(nIndex + 1 < rCandidate.count())
+ {
+ return nIndex + 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ B3DRange getRange(const B3DPolygon& rCandidate)
+ {
+ B3DRange aRetval;
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B3DPoint aTestPoint(rCandidate.getB3DPoint(a));
+ aRetval.expand(aTestPoint);
+ }
+
+ return aRetval;
+ }
+
+ double getLength(const B3DPolygon& rCandidate)
+ {
+ double fRetval(0.0);
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1)
+ {
+ const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+
+ for(sal_uInt32 a(0); a < nLoopCount; a++)
+ {
+ const sal_uInt32 nNextIndex(getIndexOfSuccessor(a, rCandidate));
+ const B3DPoint aCurrentPoint(rCandidate.getB3DPoint(a));
+ const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
+ const B3DVector aVector(aNextPoint - aCurrentPoint);
+ fRetval += aVector.getLength();
+ }
+ }
+
+ return fRetval;
+ }
+
+ void applyLineDashing(
+ const B3DPolygon& rCandidate,
+ const std::vector<double>& rDotDashArray,
+ B3DPolyPolygon* pLineTarget,
+ double fDotDashLength)
+ {
+ // clear targets in any case
+ if(pLineTarget)
+ {
+ pLineTarget->clear();
+ }
+
+ // provide callback as lambda
+ auto aLineCallback(
+ nullptr == pLineTarget
+ ? std::function<void(const basegfx::B3DPolygon&)>()
+ : [&pLineTarget](const basegfx::B3DPolygon& rSnippet){ pLineTarget->append(rSnippet); });
+
+ // call version that uses callbacks
+ applyLineDashing(
+ rCandidate,
+ rDotDashArray,
+ aLineCallback,
+ fDotDashLength);
+ }
+
+ static void implHandleSnippet(
+ const B3DPolygon& rSnippet,
+ const std::function<void(const basegfx::B3DPolygon& rSnippet)>& rTargetCallback,
+ B3DPolygon& rFirst,
+ B3DPolygon& rLast)
+ {
+ if(rSnippet.isClosed())
+ {
+ if(!rFirst.count())
+ {
+ rFirst = rSnippet;
+ }
+ else
+ {
+ if(rLast.count())
+ {
+ rTargetCallback(rLast);
+ }
+
+ rLast = rSnippet;
+ }
+ }
+ else
+ {
+ rTargetCallback(rSnippet);
+ }
+ }
+
+ static void implHandleFirstLast(
+ const std::function<void(const basegfx::B3DPolygon& rSnippet)>& rTargetCallback,
+ B3DPolygon& rFirst,
+ B3DPolygon& rLast)
+ {
+ if(rFirst.count() && rLast.count()
+ && rFirst.getB3DPoint(0).equal(rLast.getB3DPoint(rLast.count() - 1)))
+ {
+ // start of first and end of last are the same -> merge them
+ rLast.append(rFirst);
+ rLast.removeDoublePoints();
+ rFirst.clear();
+ }
+
+ if(rLast.count())
+ {
+ rTargetCallback(rLast);
+ }
+
+ if(rFirst.count())
+ {
+ rTargetCallback(rFirst);
+ }
+ }
+
+ void applyLineDashing(
+ const B3DPolygon& rCandidate,
+ const std::vector<double>& rDotDashArray,
+ const std::function<void(const basegfx::B3DPolygon& rSnippet)>& rLineTargetCallback,
+ double fDotDashLength)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+ const sal_uInt32 nDotDashCount(rDotDashArray.size());
+
+ if(fTools::lessOrEqual(fDotDashLength, 0.0))
+ {
+ fDotDashLength = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
+ }
+
+ if(fTools::lessOrEqual(fDotDashLength, 0.0) || !rLineTargetCallback || !nPointCount)
+ {
+ // parameters make no sense, just add source to targets
+ if (rLineTargetCallback)
+ rLineTargetCallback(rCandidate);
+ return;
+ }
+
+ // precalculate maximal acceptable length of candidate polygon assuming
+ // we want to create a maximum of fNumberOfAllowedSnippets. In 3D
+ // use less for fNumberOfAllowedSnippets, ca. 6553.6, double due to line & gap.
+ // Less in 3D due to potentially blowing up to rounded line segments.
+ static const double fNumberOfAllowedSnippets(6553.5 * 2.0);
+ const double fAllowedLength((fNumberOfAllowedSnippets * fDotDashLength) / double(rDotDashArray.size()));
+ const double fCandidateLength(basegfx::utils::getLength(rCandidate));
+ std::vector<double> aDotDashArray(rDotDashArray);
+
+ if(fCandidateLength > fAllowedLength)
+ {
+ // we would produce more than fNumberOfAllowedSnippets, so
+ // adapt aDotDashArray to exactly produce assumed number. Also
+ // assert this to let the caller know about it.
+ // If this asserts: Please think about checking your DotDashArray
+ // before calling this function or evtl. use the callback version
+ // to *not* produce that much of data. Even then, you may still
+ // think about producing too much runtime (!)
+ assert(true && "applyLineDashing: potentially too expensive to do the requested dismantle - please consider stretched LineDash pattern (!)");
+
+ // calculate correcting factor, apply to aDotDashArray and fDotDashLength
+ // to enlarge these as needed
+ const double fFactor(fCandidateLength / fAllowedLength);
+ std::for_each(aDotDashArray.begin(), aDotDashArray.end(), [&fFactor](double &f){ f *= fFactor; });
+ }
+
+ // prepare current edge's start
+ B3DPoint aCurrentPoint(rCandidate.getB3DPoint(0));
+ const bool bIsClosed(rCandidate.isClosed());
+ const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
+
+ // prepare DotDashArray iteration and the line/gap switching bool
+ sal_uInt32 nDotDashIndex(0);
+ bool bIsLine(true);
+ double fDotDashMovingLength(aDotDashArray[0]);
+ B3DPolygon aSnippet;
+
+ // remember 1st and last snippets to try to merge after execution
+ // is complete and hand to callback
+ B3DPolygon aFirstLine, aLastLine;
+
+ // iterate over all edges
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ // update current edge
+ const sal_uInt32 nNextIndex((a + 1) % nPointCount);
+ const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
+ const double fEdgeLength(B3DVector(aNextPoint - aCurrentPoint).getLength());
+
+ if(!fTools::equalZero(fEdgeLength))
+ {
+ double fLastDotDashMovingLength(0.0);
+ while(fTools::less(fDotDashMovingLength, fEdgeLength))
+ {
+ // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
+ if(bIsLine)
+ {
+ if(!aSnippet.count())
+ {
+ aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fLastDotDashMovingLength / fEdgeLength));
+ }
+
+ aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fDotDashMovingLength / fEdgeLength));
+
+ implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
+
+ aSnippet.clear();
+ }
+
+ // prepare next DotDashArray step and flip line/gap flag
+ fLastDotDashMovingLength = fDotDashMovingLength;
+ fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
+ bIsLine = !bIsLine;
+ }
+
+ // append snippet [fLastDotDashMovingLength, fEdgeLength]
+ if(bIsLine)
+ {
+ if(!aSnippet.count())
+ {
+ aSnippet.append(interpolate(aCurrentPoint, aNextPoint, fLastDotDashMovingLength / fEdgeLength));
+ }
+
+ aSnippet.append(aNextPoint);
+ }
+
+ // prepare move to next edge
+ fDotDashMovingLength -= fEdgeLength;
+ }
+
+ // prepare next edge step (end point gets new start point)
+ aCurrentPoint = aNextPoint;
+ }
+
+ // append last intermediate results (if exists)
+ if(aSnippet.count())
+ {
+ if(bIsLine)
+ {
+ implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
+ }
+ }
+
+ if(bIsClosed)
+ {
+ implHandleFirstLast(rLineTargetCallback, aFirstLine, aLastLine);
+ }
+ }
+
+ B3DPolygon applyDefaultNormalsSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter)
+ {
+ B3DPolygon aRetval(rCandidate);
+
+ for(sal_uInt32 a(0); a < aRetval.count(); a++)
+ {
+ B3DVector aVector(aRetval.getB3DPoint(a) - rCenter);
+ aVector.normalize();
+ aRetval.setNormal(a, aVector);
+ }
+
+ return aRetval;
+ }
+
+ B3DPolygon invertNormals( const B3DPolygon& rCandidate)
+ {
+ B3DPolygon aRetval(rCandidate);
+
+ if(aRetval.areNormalsUsed())
+ {
+ for(sal_uInt32 a(0); a < aRetval.count(); a++)
+ {
+ aRetval.setNormal(a, -aRetval.getNormal(a));
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPolygon applyDefaultTextureCoordinatesParallel( const B3DPolygon& rCandidate, const B3DRange& rRange, bool bChangeX, bool bChangeY)
+ {
+ B3DPolygon aRetval(rCandidate);
+
+ if(bChangeX || bChangeY)
+ {
+ // create projection of standard texture coordinates in (X, Y) onto
+ // the 3d coordinates straight
+ const double fWidth(rRange.getWidth());
+ const double fHeight(rRange.getHeight());
+ const bool bWidthSet(!fTools::equalZero(fWidth));
+ const bool bHeightSet(!fTools::equalZero(fHeight));
+ const double fOne(1.0);
+
+ for(sal_uInt32 a(0); a < aRetval.count(); a++)
+ {
+ const B3DPoint aPoint(aRetval.getB3DPoint(a));
+ B2DPoint aTextureCoordinate(aRetval.getTextureCoordinate(a));
+
+ if(bChangeX)
+ {
+ if(bWidthSet)
+ {
+ aTextureCoordinate.setX((aPoint.getX() - rRange.getMinX()) / fWidth);
+ }
+ else
+ {
+ aTextureCoordinate.setX(0.0);
+ }
+ }
+
+ if(bChangeY)
+ {
+ if(bHeightSet)
+ {
+ aTextureCoordinate.setY(fOne - ((aPoint.getY() - rRange.getMinY()) / fHeight));
+ }
+ else
+ {
+ aTextureCoordinate.setY(fOne);
+ }
+ }
+
+ aRetval.setTextureCoordinate(a, aTextureCoordinate);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPolygon applyDefaultTextureCoordinatesSphere( const B3DPolygon& rCandidate, const B3DPoint& rCenter, bool bChangeX, bool bChangeY)
+ {
+ B3DPolygon aRetval(rCandidate);
+
+ if(bChangeX || bChangeY)
+ {
+ // create texture coordinates using sphere projection to cartesian coordinates,
+ // use object's center as base
+ const double fOne(1.0);
+ const sal_uInt32 nPointCount(aRetval.count());
+ bool bPolarPoints(false);
+ sal_uInt32 a;
+
+ // create center cartesian coordinates to have a possibility to decide if on boundary
+ // transitions which value to choose
+ const B3DRange aPlaneRange(getRange(rCandidate));
+ const B3DPoint aPlaneCenter(aPlaneRange.getCenter() - rCenter);
+ const double fXCenter(fOne - ((atan2(aPlaneCenter.getZ(), aPlaneCenter.getX()) + M_PI) / (2 * M_PI)));
+
+ for(a = 0; a < nPointCount; a++)
+ {
+ const B3DVector aVector(aRetval.getB3DPoint(a) - rCenter);
+ const double fY(fOne - ((atan2(aVector.getY(), aVector.getXZLength()) + M_PI_2) / M_PI));
+ B2DPoint aTexCoor(aRetval.getTextureCoordinate(a));
+
+ if(fTools::equalZero(fY))
+ {
+ // point is a north polar point, no useful X-coordinate can be created.
+ if(bChangeY)
+ {
+ aTexCoor.setY(0.0);
+
+ if(bChangeX)
+ {
+ bPolarPoints = true;
+ }
+ }
+ }
+ else if(fTools::equal(fY, fOne))
+ {
+ // point is a south polar point, no useful X-coordinate can be created. Set
+ // Y-coordinate, though
+ if(bChangeY)
+ {
+ aTexCoor.setY(fOne);
+
+ if(bChangeX)
+ {
+ bPolarPoints = true;
+ }
+ }
+ }
+ else
+ {
+ double fX(fOne - ((atan2(aVector.getZ(), aVector.getX()) + M_PI) / (2 * M_PI)));
+
+ // correct cartesian point coordinate dependent from center value
+ if(fX > fXCenter + 0.5)
+ {
+ fX -= fOne;
+ }
+ else if(fX < fXCenter - 0.5)
+ {
+ fX += fOne;
+ }
+
+ if(bChangeX)
+ {
+ aTexCoor.setX(fX);
+ }
+
+ if(bChangeY)
+ {
+ aTexCoor.setY(fY);
+ }
+ }
+
+ aRetval.setTextureCoordinate(a, aTexCoor);
+ }
+
+ if(bPolarPoints)
+ {
+ // correct X-texture coordinates if polar points are contained. Those
+ // coordinates cannot be correct, so use prev or next X-coordinate
+ for(a = 0; a < nPointCount; a++)
+ {
+ B2DPoint aTexCoor(aRetval.getTextureCoordinate(a));
+
+ if(fTools::equalZero(aTexCoor.getY()) || fTools::equal(aTexCoor.getY(), fOne))
+ {
+ // get prev, next TexCoor and test for pole
+ const B2DPoint aPrevTexCoor(aRetval.getTextureCoordinate(a ? a - 1 : nPointCount - 1));
+ const B2DPoint aNextTexCoor(aRetval.getTextureCoordinate((a + 1) % nPointCount));
+ const bool bPrevPole(fTools::equalZero(aPrevTexCoor.getY()) || fTools::equal(aPrevTexCoor.getY(), fOne));
+ const bool bNextPole(fTools::equalZero(aNextTexCoor.getY()) || fTools::equal(aNextTexCoor.getY(), fOne));
+
+ if(!bPrevPole && !bNextPole)
+ {
+ // both no poles, mix them
+ aTexCoor.setX((aPrevTexCoor.getX() + aNextTexCoor.getX()) / 2.0);
+ }
+ else if(!bNextPole)
+ {
+ // copy next
+ aTexCoor.setX(aNextTexCoor.getX());
+ }
+ else
+ {
+ // copy prev, even if it's a pole, hopefully it is already corrected
+ aTexCoor.setX(aPrevTexCoor.getX());
+ }
+
+ aRetval.setTextureCoordinate(a, aTexCoor);
+ }
+ }
+ }
+ }
+
+ return aRetval;
+ }
+
+ bool isInside(const B3DPolygon& rCandidate, const B3DPoint& rPoint, bool bWithBorder)
+ {
+ if(bWithBorder && isPointOnPolygon(rCandidate, rPoint))
+ {
+ return true;
+ }
+ else
+ {
+ bool bRetval(false);
+ const B3DVector aPlaneNormal(rCandidate.getNormal());
+
+ if(!aPlaneNormal.equalZero())
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount)
+ {
+ B3DPoint aCurrentPoint(rCandidate.getB3DPoint(nPointCount - 1));
+ const double fAbsX(fabs(aPlaneNormal.getX()));
+ const double fAbsY(fabs(aPlaneNormal.getY()));
+ const double fAbsZ(fabs(aPlaneNormal.getZ()));
+
+ if(fAbsX > fAbsY && fAbsX > fAbsZ)
+ {
+ // normal points mostly in X-Direction, use YZ-Polygon projection for check
+ // x -> y, y -> z
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B3DPoint aPreviousPoint(aCurrentPoint);
+ aCurrentPoint = rCandidate.getB3DPoint(a);
+
+ // cross-over in Z?
+ const bool bCompZA(fTools::more(aPreviousPoint.getZ(), rPoint.getZ()));
+ const bool bCompZB(fTools::more(aCurrentPoint.getZ(), rPoint.getZ()));
+
+ if(bCompZA != bCompZB)
+ {
+ // cross-over in Y?
+ const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY()));
+ const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY()));
+
+ if(bCompYA == bCompYB)
+ {
+ if(bCompYA)
+ {
+ bRetval = !bRetval;
+ }
+ }
+ else
+ {
+ const double fCompare(
+ aCurrentPoint.getY() - (aCurrentPoint.getZ() - rPoint.getZ()) *
+ (aPreviousPoint.getY() - aCurrentPoint.getY()) /
+ (aPreviousPoint.getZ() - aCurrentPoint.getZ()));
+
+ if(fTools::more(fCompare, rPoint.getY()))
+ {
+ bRetval = !bRetval;
+ }
+ }
+ }
+ }
+ }
+ else if(fAbsY > fAbsX && fAbsY > fAbsZ)
+ {
+ // normal points mostly in Y-Direction, use XZ-Polygon projection for check
+ // x -> x, y -> z
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B3DPoint aPreviousPoint(aCurrentPoint);
+ aCurrentPoint = rCandidate.getB3DPoint(a);
+
+ // cross-over in Z?
+ const bool bCompZA(fTools::more(aPreviousPoint.getZ(), rPoint.getZ()));
+ const bool bCompZB(fTools::more(aCurrentPoint.getZ(), rPoint.getZ()));
+
+ if(bCompZA != bCompZB)
+ {
+ // cross-over in X?
+ const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX()));
+ const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX()));
+
+ if(bCompXA == bCompXB)
+ {
+ if(bCompXA)
+ {
+ bRetval = !bRetval;
+ }
+ }
+ else
+ {
+ const double fCompare(
+ aCurrentPoint.getX() - (aCurrentPoint.getZ() - rPoint.getZ()) *
+ (aPreviousPoint.getX() - aCurrentPoint.getX()) /
+ (aPreviousPoint.getZ() - aCurrentPoint.getZ()));
+
+ if(fTools::more(fCompare, rPoint.getX()))
+ {
+ bRetval = !bRetval;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // normal points mostly in Z-Direction, use XY-Polygon projection for check
+ // x -> x, y -> y
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const B3DPoint aPreviousPoint(aCurrentPoint);
+ aCurrentPoint = rCandidate.getB3DPoint(a);
+
+ // cross-over in Y?
+ const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY()));
+ const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY()));
+
+ if(bCompYA != bCompYB)
+ {
+ // cross-over in X?
+ const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX()));
+ const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX()));
+
+ if(bCompXA == bCompXB)
+ {
+ if(bCompXA)
+ {
+ bRetval = !bRetval;
+ }
+ }
+ else
+ {
+ const double fCompare(
+ aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) *
+ (aPreviousPoint.getX() - aCurrentPoint.getX()) /
+ (aPreviousPoint.getY() - aCurrentPoint.getY()));
+
+ if(fTools::more(fCompare, rPoint.getX()))
+ {
+ bRetval = !bRetval;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return bRetval;
+ }
+ }
+
+ bool isPointOnLine(const B3DPoint& rStart, const B3DPoint& rEnd, const B3DPoint& rCandidate, bool bWithPoints)
+ {
+ if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
+ {
+ // candidate is in epsilon around start or end -> inside
+ return bWithPoints;
+ }
+ else if(rStart.equal(rEnd))
+ {
+ // start and end are equal, but candidate is outside their epsilon -> outside
+ return false;
+ }
+ else
+ {
+ const B3DVector aEdgeVector(rEnd - rStart);
+ const B3DVector aTestVector(rCandidate - rStart);
+
+ if(areParallel(aEdgeVector, aTestVector))
+ {
+ double fParamTestOnCurr(0.0);
+
+ if(aEdgeVector.getX() > aEdgeVector.getY())
+ {
+ if(aEdgeVector.getX() > aEdgeVector.getZ())
+ {
+ // X is biggest
+ fParamTestOnCurr = aTestVector.getX() / aEdgeVector.getX();
+ }
+ else
+ {
+ // Z is biggest
+ fParamTestOnCurr = aTestVector.getZ() / aEdgeVector.getZ();
+ }
+ }
+ else
+ {
+ if(aEdgeVector.getY() > aEdgeVector.getZ())
+ {
+ // Y is biggest
+ fParamTestOnCurr = aTestVector.getY() / aEdgeVector.getY();
+ }
+ else
+ {
+ // Z is biggest
+ fParamTestOnCurr = aTestVector.getZ() / aEdgeVector.getZ();
+ }
+ }
+
+ if(fTools::more(fParamTestOnCurr, 0.0) && fTools::less(fParamTestOnCurr, 1.0))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ bool isPointOnPolygon(const B3DPolygon& rCandidate, const B3DPoint& rPoint)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1)
+ {
+ const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
+ B3DPoint aCurrentPoint(rCandidate.getB3DPoint(0));
+
+ for(sal_uInt32 a(0); a < nLoopCount; a++)
+ {
+ const B3DPoint aNextPoint(rCandidate.getB3DPoint((a + 1) % nPointCount));
+
+ if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, true/*bWithPoints*/))
+ {
+ return true;
+ }
+
+ aCurrentPoint = aNextPoint;
+ }
+ }
+ else if(nPointCount)
+ {
+ return rPoint.equal(rCandidate.getB3DPoint(0));
+ }
+
+ return false;
+ }
+
+ bool getCutBetweenLineAndPlane(const B3DVector& rPlaneNormal, const B3DPoint& rPlanePoint, const B3DPoint& rEdgeStart, const B3DPoint& rEdgeEnd, double& fCut)
+ {
+ if(!rPlaneNormal.equalZero() && !rEdgeStart.equal(rEdgeEnd))
+ {
+ const B3DVector aTestEdge(rEdgeEnd - rEdgeStart);
+ const double fScalarEdge(rPlaneNormal.scalar(aTestEdge));
+
+ if(!fTools::equalZero(fScalarEdge))
+ {
+ const B3DVector aCompareEdge(rPlanePoint - rEdgeStart);
+ const double fScalarCompare(rPlaneNormal.scalar(aCompareEdge));
+
+ fCut = fScalarCompare / fScalarEdge;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // snap points of horizontal or vertical edges to discrete values
+ B3DPolygon snapPointsOfHorizontalOrVerticalEdges(const B3DPolygon& rCandidate)
+ {
+ const sal_uInt32 nPointCount(rCandidate.count());
+
+ if(nPointCount > 1)
+ {
+ // Start by copying the source polygon to get a writeable copy. The closed state is
+ // copied by aRetval's initialisation, too, so no need to copy it in this method
+ B3DPolygon aRetval(rCandidate);
+
+ // prepare geometry data. Get rounded from original
+ B3ITuple aPrevTuple(basegfx::fround(rCandidate.getB3DPoint(nPointCount - 1)));
+ B3DPoint aCurrPoint(rCandidate.getB3DPoint(0));
+ B3ITuple aCurrTuple(basegfx::fround(aCurrPoint));
+
+ // loop over all points. This will also snap the implicit closing edge
+ // even when not closed, but that's no problem here
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ // get next point. Get rounded from original
+ const bool bLastRun(a + 1 == nPointCount);
+ const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
+ const B3DPoint aNextPoint(rCandidate.getB3DPoint(nNextIndex));
+ const B3ITuple aNextTuple(basegfx::fround(aNextPoint));
+
+ // get the states
+ const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
+ const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
+ const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
+ const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
+ const bool bSnapX(bPrevVertical || bNextVertical);
+ const bool bSnapY(bPrevHorizontal || bNextHorizontal);
+
+ if(bSnapX || bSnapY)
+ {
+ const B3DPoint aSnappedPoint(
+ bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
+ bSnapY ? aCurrTuple.getY() : aCurrPoint.getY(),
+ aCurrPoint.getZ());
+
+ aRetval.setB3DPoint(a, aSnappedPoint);
+ }
+
+ // prepare next point
+ if(!bLastRun)
+ {
+ aPrevTuple = aCurrTuple;
+ aCurrPoint = aNextPoint;
+ aCurrTuple = aNextTuple;
+ }
+ }
+
+ return aRetval;
+ }
+ else
+ {
+ return rCandidate;
+ }
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b3dpolypolygon.cxx b/basegfx/source/polygon/b3dpolypolygon.cxx
new file mode 100644
index 0000000000..017906eef5
--- /dev/null
+++ b/basegfx/source/polygon/b3dpolypolygon.cxx
@@ -0,0 +1,400 @@
+/* -*- 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 <osl/diagnose.h>
+#include <basegfx/polygon/b3dpolypolygon.hxx>
+#include <basegfx/polygon/b3dpolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <utility>
+#include <vector>
+
+class ImplB3DPolyPolygon
+{
+ typedef std::vector< ::basegfx::B3DPolygon > PolygonVector;
+
+ PolygonVector maPolygons;
+
+public:
+ ImplB3DPolyPolygon()
+ {
+ }
+
+ explicit ImplB3DPolyPolygon(const ::basegfx::B3DPolygon& rToBeCopied) :
+ maPolygons(1,rToBeCopied)
+ {
+ }
+
+ bool operator==(const ImplB3DPolyPolygon& rPolygonList) const
+ {
+ // same polygon count?
+ if(maPolygons.size() != rPolygonList.maPolygons.size())
+ return false;
+
+ // compare polygon content
+ if(maPolygons != rPolygonList.maPolygons)
+ return false;
+
+ return true;
+ }
+
+ const ::basegfx::B3DPolygon& getB3DPolygon(sal_uInt32 nIndex) const
+ {
+ return maPolygons[nIndex];
+ }
+
+ void setB3DPolygon(sal_uInt32 nIndex, const ::basegfx::B3DPolygon& rPolygon)
+ {
+ maPolygons[nIndex] = rPolygon;
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::B3DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // add nCount copies of rPolygon
+ PolygonVector::iterator aIndex(maPolygons.begin());
+ if( nIndex )
+ aIndex += nIndex;
+ maPolygons.insert(aIndex, nCount, rPolygon);
+ }
+ }
+
+ void insert(sal_uInt32 nIndex, const ::basegfx::B3DPolyPolygon& rPolyPolygon)
+ {
+ // add all polygons from rPolyPolygon
+ PolygonVector::iterator aIndex(maPolygons.begin());
+ if( nIndex )
+ aIndex += nIndex;
+ maPolygons.insert(aIndex, rPolyPolygon.begin(), rPolyPolygon.end());
+ }
+
+ void remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ if(nCount)
+ {
+ // remove polygon data
+ PolygonVector::iterator aStart(maPolygons.begin());
+ aStart += nIndex;
+ const PolygonVector::iterator aEnd(aStart + nCount);
+
+ maPolygons.erase(aStart, aEnd);
+ }
+ }
+
+ sal_uInt32 count() const
+ {
+ return maPolygons.size();
+ }
+
+ void flip()
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.flip();
+ }
+
+ void removeDoublePoints()
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.removeDoublePoints();
+ }
+
+ void transform(const ::basegfx::B3DHomMatrix& rMatrix)
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.transform(rMatrix);
+ }
+
+ void clearBColors()
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.clearBColors();
+ }
+
+ void transformNormals(const ::basegfx::B3DHomMatrix& rMatrix)
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.transformNormals(rMatrix);
+ }
+
+ void clearNormals()
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.clearNormals();
+ }
+
+ void transformTextureCoordinates(const ::basegfx::B2DHomMatrix& rMatrix)
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.transformTextureCoordinates(rMatrix);
+ }
+
+ void clearTextureCoordinates()
+ {
+ for (auto& aPolygon : maPolygons)
+ aPolygon.clearTextureCoordinates();
+ }
+
+ const basegfx::B3DPolygon* begin() const
+ {
+ if (maPolygons.empty())
+ return nullptr;
+ else
+ return maPolygons.data();
+ }
+
+ const basegfx::B3DPolygon* end() const
+ {
+ if (maPolygons.empty())
+ return nullptr;
+ else
+ return maPolygons.data() + maPolygons.size();
+ }
+
+ basegfx::B3DPolygon* begin()
+ {
+ if (maPolygons.empty())
+ return nullptr;
+ else
+ return maPolygons.data();
+ }
+
+ basegfx::B3DPolygon* end()
+ {
+ if (maPolygons.empty())
+ return nullptr;
+ else
+ return maPolygons.data() + maPolygons.size();
+ }
+};
+
+namespace basegfx
+{
+ namespace {
+
+ B3DPolyPolygon::ImplType const & getDefaultPolyPolygon() {
+ static B3DPolyPolygon::ImplType const singleton;
+ return singleton;
+ }
+
+ }
+
+ B3DPolyPolygon::B3DPolyPolygon() :
+ mpPolyPolygon(getDefaultPolyPolygon())
+ {
+ }
+
+ B3DPolyPolygon::B3DPolyPolygon(const B3DPolyPolygon&) = default;
+
+ B3DPolyPolygon::B3DPolyPolygon(B3DPolyPolygon&&) = default;
+
+ B3DPolyPolygon::B3DPolyPolygon(const B3DPolygon& rPolygon) :
+ mpPolyPolygon( ImplB3DPolyPolygon(rPolygon) )
+ {
+ }
+
+ B3DPolyPolygon::~B3DPolyPolygon() = default;
+
+ B3DPolyPolygon& B3DPolyPolygon::operator=(const B3DPolyPolygon&) = default;
+
+ B3DPolyPolygon& B3DPolyPolygon::operator=(B3DPolyPolygon&&) = default;
+
+ bool B3DPolyPolygon::operator==(const B3DPolyPolygon& rPolyPolygon) const
+ {
+ if(mpPolyPolygon.same_object(rPolyPolygon.mpPolyPolygon))
+ return true;
+
+ return ((*mpPolyPolygon) == (*rPolyPolygon.mpPolyPolygon));
+ }
+
+ bool B3DPolyPolygon::operator!=(const B3DPolyPolygon& rPolyPolygon) const
+ {
+ return !(*this == rPolyPolygon);
+ }
+
+ sal_uInt32 B3DPolyPolygon::count() const
+ {
+ return mpPolyPolygon->count();
+ }
+
+ B3DPolygon const & B3DPolyPolygon::getB3DPolygon(sal_uInt32 nIndex) const
+ {
+ OSL_ENSURE(nIndex < mpPolyPolygon->count(), "B3DPolyPolygon access outside range (!)");
+
+ return mpPolyPolygon->getB3DPolygon(nIndex);
+ }
+
+ void B3DPolyPolygon::setB3DPolygon(sal_uInt32 nIndex, const B3DPolygon& rPolygon)
+ {
+ OSL_ENSURE(nIndex < std::as_const(mpPolyPolygon)->count(), "B3DPolyPolygon access outside range (!)");
+
+ if(getB3DPolygon(nIndex) != rPolygon)
+ mpPolyPolygon->setB3DPolygon(nIndex, rPolygon);
+ }
+
+ bool B3DPolyPolygon::areBColorsUsed() const
+ {
+ for(sal_uInt32 a(0); a < mpPolyPolygon->count(); a++)
+ {
+ if(mpPolyPolygon->getB3DPolygon(a).areBColorsUsed())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void B3DPolyPolygon::clearBColors()
+ {
+ if(areBColorsUsed())
+ mpPolyPolygon->clearBColors();
+ }
+
+ void B3DPolyPolygon::transformNormals(const B3DHomMatrix& rMatrix)
+ {
+ if(!rMatrix.isIdentity())
+ mpPolyPolygon->transformNormals(rMatrix);
+ }
+
+ bool B3DPolyPolygon::areNormalsUsed() const
+ {
+ for(sal_uInt32 a(0); a < mpPolyPolygon->count(); a++)
+ {
+ if(mpPolyPolygon->getB3DPolygon(a).areNormalsUsed())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void B3DPolyPolygon::clearNormals()
+ {
+ if(areNormalsUsed())
+ mpPolyPolygon->clearNormals();
+ }
+
+ void B3DPolyPolygon::transformTextureCoordinates(const B2DHomMatrix& rMatrix)
+ {
+ if(!rMatrix.isIdentity())
+ mpPolyPolygon->transformTextureCoordinates(rMatrix);
+ }
+
+ bool B3DPolyPolygon::areTextureCoordinatesUsed() const
+ {
+ for(sal_uInt32 a(0); a < mpPolyPolygon->count(); a++)
+ {
+ if(mpPolyPolygon->getB3DPolygon(a).areTextureCoordinatesUsed())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void B3DPolyPolygon::clearTextureCoordinates()
+ {
+ if(areTextureCoordinatesUsed())
+ mpPolyPolygon->clearTextureCoordinates();
+ }
+
+ void B3DPolyPolygon::append(const B3DPolygon& rPolygon, sal_uInt32 nCount)
+ {
+ if(nCount)
+ mpPolyPolygon->insert(std::as_const(mpPolyPolygon)->count(), rPolygon, nCount);
+ }
+
+ void B3DPolyPolygon::append(const B3DPolyPolygon& rPolyPolygon)
+ {
+ if(rPolyPolygon.count())
+ mpPolyPolygon->insert(std::as_const(mpPolyPolygon)->count(), rPolyPolygon);
+ }
+
+ void B3DPolyPolygon::remove(sal_uInt32 nIndex, sal_uInt32 nCount)
+ {
+ OSL_ENSURE(nIndex + nCount <= std::as_const(mpPolyPolygon)->count(), "B3DPolyPolygon Remove outside range (!)");
+
+ if(nCount)
+ mpPolyPolygon->remove(nIndex, nCount);
+ }
+
+ void B3DPolyPolygon::clear()
+ {
+ mpPolyPolygon = getDefaultPolyPolygon();
+ }
+
+ void B3DPolyPolygon::flip()
+ {
+ mpPolyPolygon->flip();
+ }
+
+ bool B3DPolyPolygon::hasDoublePoints() const
+ {
+ bool bRetval(false);
+
+ for(sal_uInt32 a(0); !bRetval && a < mpPolyPolygon->count(); a++)
+ {
+ if(mpPolyPolygon->getB3DPolygon(a).hasDoublePoints())
+ {
+ bRetval = true;
+ }
+ }
+
+ return bRetval;
+ }
+
+ void B3DPolyPolygon::removeDoublePoints()
+ {
+ if(hasDoublePoints())
+ mpPolyPolygon->removeDoublePoints();
+ }
+
+ void B3DPolyPolygon::transform(const B3DHomMatrix& rMatrix)
+ {
+ if(std::as_const(mpPolyPolygon)->count() && !rMatrix.isIdentity())
+ {
+ mpPolyPolygon->transform(rMatrix);
+ }
+ }
+
+ const B3DPolygon* B3DPolyPolygon::begin() const
+ {
+ return mpPolyPolygon->begin();
+ }
+
+ const B3DPolygon* B3DPolyPolygon::end() const
+ {
+ return mpPolyPolygon->end();
+ }
+
+ B3DPolygon* B3DPolyPolygon::begin()
+ {
+ return mpPolyPolygon->begin();
+ }
+
+ B3DPolygon* B3DPolyPolygon::end()
+ {
+ return mpPolyPolygon->end();
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/polygon/b3dpolypolygontools.cxx b/basegfx/source/polygon/b3dpolypolygontools.cxx
new file mode 100644
index 0000000000..ffd8bded4d
--- /dev/null
+++ b/basegfx/source/polygon/b3dpolypolygontools.cxx
@@ -0,0 +1,585 @@
+/* -*- 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 <basegfx/polygon/b3dpolypolygontools.hxx>
+#include <basegfx/range/b3drange.hxx>
+#include <basegfx/polygon/b3dpolypolygon.hxx>
+#include <basegfx/polygon/b3dpolygon.hxx>
+#include <basegfx/polygon/b3dpolygontools.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <com/sun/star/drawing/DoubleSequence.hpp>
+#include <com/sun/star/drawing/PolyPolygonShape3D.hpp>
+
+// predefines
+#define nMinSegments sal_uInt32(1)
+#define nMaxSegments sal_uInt32(512)
+
+namespace basegfx::utils
+{
+ // B3DPolyPolygon tools
+ B3DRange getRange(const B3DPolyPolygon& rCandidate)
+ {
+ B3DRange aRetval;
+
+ for(const auto& rPolygon : rCandidate )
+ {
+ aRetval.expand(getRange(rPolygon));
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon const & createUnitCubePolyPolygon()
+ {
+ static auto const singleton = [] {
+ B3DPolyPolygon aRetval;
+ B3DPolygon aTemp;
+ aTemp.append(B3DPoint(0.0, 0.0, 1.0));
+ aTemp.append(B3DPoint(0.0, 1.0, 1.0));
+ aTemp.append(B3DPoint(1.0, 1.0, 1.0));
+ aTemp.append(B3DPoint(1.0, 0.0, 1.0));
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ aTemp.clear();
+ aTemp.append(B3DPoint(0.0, 0.0, 0.0));
+ aTemp.append(B3DPoint(0.0, 1.0, 0.0));
+ aTemp.append(B3DPoint(1.0, 1.0, 0.0));
+ aTemp.append(B3DPoint(1.0, 0.0, 0.0));
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ aTemp.clear();
+ aTemp.append(B3DPoint(0.0, 0.0, 0.0));
+ aTemp.append(B3DPoint(0.0, 0.0, 1.0));
+ aRetval.append(aTemp);
+
+ aTemp.clear();
+ aTemp.append(B3DPoint(0.0, 1.0, 0.0));
+ aTemp.append(B3DPoint(0.0, 1.0, 1.0));
+ aRetval.append(aTemp);
+
+ aTemp.clear();
+ aTemp.append(B3DPoint(1.0, 1.0, 0.0));
+ aTemp.append(B3DPoint(1.0, 1.0, 1.0));
+ aRetval.append(aTemp);
+
+ aTemp.clear();
+ aTemp.append(B3DPoint(1.0, 0.0, 0.0));
+ aTemp.append(B3DPoint(1.0, 0.0, 1.0));
+ aRetval.append(aTemp);
+ return aRetval;
+ }();
+ return singleton;
+ }
+
+ B3DPolyPolygon const & createUnitCubeFillPolyPolygon()
+ {
+ static auto const singleton = [] {
+ B3DPolyPolygon aRetval;
+ B3DPolygon aTemp;
+
+ // all points
+ const B3DPoint A(0.0, 0.0, 0.0);
+ const B3DPoint B(0.0, 1.0, 0.0);
+ const B3DPoint C(1.0, 1.0, 0.0);
+ const B3DPoint D(1.0, 0.0, 0.0);
+ const B3DPoint E(0.0, 0.0, 1.0);
+ const B3DPoint F(0.0, 1.0, 1.0);
+ const B3DPoint G(1.0, 1.0, 1.0);
+ const B3DPoint H(1.0, 0.0, 1.0);
+
+ // create bottom
+ aTemp.append(D);
+ aTemp.append(A);
+ aTemp.append(E);
+ aTemp.append(H);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ // create front
+ aTemp.clear();
+ aTemp.append(B);
+ aTemp.append(A);
+ aTemp.append(D);
+ aTemp.append(C);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ // create left
+ aTemp.clear();
+ aTemp.append(E);
+ aTemp.append(A);
+ aTemp.append(B);
+ aTemp.append(F);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ // create top
+ aTemp.clear();
+ aTemp.append(C);
+ aTemp.append(G);
+ aTemp.append(F);
+ aTemp.append(B);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ // create right
+ aTemp.clear();
+ aTemp.append(H);
+ aTemp.append(G);
+ aTemp.append(C);
+ aTemp.append(D);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+
+ // create back
+ aTemp.clear();
+ aTemp.append(F);
+ aTemp.append(G);
+ aTemp.append(H);
+ aTemp.append(E);
+ aTemp.setClosed(true);
+ aRetval.append(aTemp);
+ return aRetval;
+ }();
+ return singleton;
+ }
+
+ B3DPolyPolygon createCubePolyPolygonFromB3DRange( const B3DRange& rRange)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(!rRange.isEmpty())
+ {
+ aRetval = createUnitCubePolyPolygon();
+ B3DHomMatrix aTrans;
+ aTrans.scale(rRange.getWidth(), rRange.getHeight(), rRange.getDepth());
+ aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ());
+ aRetval.transform(aTrans);
+ aRetval.removeDoublePoints();
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon createCubeFillPolyPolygonFromB3DRange( const B3DRange& rRange)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(!rRange.isEmpty())
+ {
+ aRetval = createUnitCubeFillPolyPolygon();
+ B3DHomMatrix aTrans;
+ aTrans.scale(rRange.getWidth(), rRange.getHeight(), rRange.getDepth());
+ aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ());
+ aRetval.transform(aTrans);
+ aRetval.removeDoublePoints();
+ }
+
+ return aRetval;
+ }
+
+ // helper for getting the 3D Point from given cartesian coordinates. fHor is defined from
+ // [M_PI_2 .. -M_PI_2], fVer from [0.0 .. 2PI]
+ static B3DPoint getPointFromCartesian(double fHor, double fVer)
+ {
+ const double fCosVer(cos(fVer));
+ return B3DPoint(fCosVer * cos(fHor), sin(fVer), fCosVer * -sin(fHor));
+ }
+
+ B3DPolyPolygon createUnitSpherePolyPolygon(
+ sal_uInt32 nHorSeg, sal_uInt32 nVerSeg,
+ double fVerStart, double fVerStop,
+ double fHorStart, double fHorStop)
+ {
+ B3DPolyPolygon aRetval;
+ sal_uInt32 a, b;
+
+ if(!nHorSeg)
+ {
+ nHorSeg = fround(fabs(fHorStop - fHorStart) / (M_PI / 12.0));
+ }
+
+ // min/max limitations
+ nHorSeg = std::clamp(nHorSeg, nMinSegments, nMaxSegments);
+
+ if(!nVerSeg)
+ {
+ nVerSeg = fround(fabs(fVerStop - fVerStart) / (M_PI / 12.0));
+ }
+
+ // min/max limitations
+ nVerSeg = std::clamp(nVerSeg, nMinSegments, nMaxSegments);
+
+ // create constants
+ const double fVerDiffPerStep((fVerStop - fVerStart) / static_cast<double>(nVerSeg));
+ const double fHorDiffPerStep((fHorStop - fHorStart) / static_cast<double>(nHorSeg));
+ bool bHorClosed(fTools::equal(fHorStop - fHorStart, 2 * M_PI));
+ bool bVerFromTop(fTools::equal(fVerStart, M_PI_2));
+ bool bVerToBottom(fTools::equal(fVerStop, -M_PI_2));
+
+ // create horizontal rings
+ const sal_uInt32 nLoopVerInit(bVerFromTop ? 1 : 0);
+ const sal_uInt32 nLoopVerLimit(bVerToBottom ? nVerSeg : nVerSeg + 1);
+ const sal_uInt32 nLoopHorLimit(bHorClosed ? nHorSeg : nHorSeg + 1);
+
+ for(a = nLoopVerInit; a < nLoopVerLimit; a++)
+ {
+ const double fVer(fVerStart + (static_cast<double>(a) * fVerDiffPerStep));
+ B3DPolygon aNew;
+
+ for(b = 0; b < nLoopHorLimit; b++)
+ {
+ const double fHor(fHorStart + (static_cast<double>(b) * fHorDiffPerStep));
+ aNew.append(getPointFromCartesian(fHor, fVer));
+ }
+
+ aNew.setClosed(bHorClosed);
+ aRetval.append(aNew);
+ }
+
+ // create vertical half-rings
+ for(a = 0; a < nLoopHorLimit; a++)
+ {
+ const double fHor(fHorStart + (static_cast<double>(a) * fHorDiffPerStep));
+ B3DPolygon aNew;
+
+ if(bVerFromTop)
+ {
+ aNew.append(B3DPoint(0.0, 1.0, 0.0));
+ }
+
+ for(b = nLoopVerInit; b < nLoopVerLimit; b++)
+ {
+ const double fVer(fVerStart + (static_cast<double>(b) * fVerDiffPerStep));
+ aNew.append(getPointFromCartesian(fHor, fVer));
+ }
+
+ if(bVerToBottom)
+ {
+ aNew.append(B3DPoint(0.0, -1.0, 0.0));
+ }
+
+ aRetval.append(aNew);
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon createSpherePolyPolygonFromB3DRange( const B3DRange& rRange,
+ sal_uInt32 nHorSeg, sal_uInt32 nVerSeg,
+ double fVerStart, double fVerStop,
+ double fHorStart, double fHorStop)
+ {
+ B3DPolyPolygon aRetval(createUnitSpherePolyPolygon(nHorSeg, nVerSeg, fVerStart, fVerStop, fHorStart, fHorStop));
+
+ if(aRetval.count())
+ {
+ // move and scale whole construct which is now in [-1.0 .. 1.0] in all directions
+ B3DHomMatrix aTrans;
+ aTrans.translate(1.0, 1.0, 1.0);
+ aTrans.scale(rRange.getWidth() / 2.0, rRange.getHeight() / 2.0, rRange.getDepth() / 2.0);
+ aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ());
+ aRetval.transform(aTrans);
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon createUnitSphereFillPolyPolygon(
+ sal_uInt32 nHorSeg, sal_uInt32 nVerSeg,
+ bool bNormals,
+ double fVerStart, double fVerStop,
+ double fHorStart, double fHorStop)
+ {
+ B3DPolyPolygon aRetval;
+
+ if(!nHorSeg)
+ {
+ nHorSeg = fround(fabs(fHorStop - fHorStart) / (M_PI / 12.0));
+ }
+
+ // min/max limitations
+ nHorSeg = std::clamp(nHorSeg, nMinSegments, nMaxSegments);
+
+ if(!nVerSeg)
+ {
+ nVerSeg = fround(fabs(fVerStop - fVerStart) / (M_PI / 12.0));
+ }
+
+ // min/max limitations
+ nVerSeg = std::clamp(nVerSeg, nMinSegments, nMaxSegments);
+
+ // vertical loop
+ for(sal_uInt32 a(0); a < nVerSeg; a++)
+ {
+ const double fVer1(fVerStart + (((fVerStop - fVerStart) * a) / nVerSeg));
+ const double fVer2(fVerStart + (((fVerStop - fVerStart) * (a + 1)) / nVerSeg));
+
+ // horizontal loop
+ for(sal_uInt32 b(0); b < nHorSeg; b++)
+ {
+ const double fHor1(fHorStart + (((fHorStop - fHorStart) * b) / nHorSeg));
+ const double fHor2(fHorStart + (((fHorStop - fHorStart) * (b + 1)) / nHorSeg));
+ B3DPolygon aNew;
+
+ aNew.append(getPointFromCartesian(fHor1, fVer1));
+ aNew.append(getPointFromCartesian(fHor2, fVer1));
+ aNew.append(getPointFromCartesian(fHor2, fVer2));
+ aNew.append(getPointFromCartesian(fHor1, fVer2));
+
+ if(bNormals)
+ {
+ for(sal_uInt32 c(0); c < aNew.count(); c++)
+ {
+ aNew.setNormal(c, ::basegfx::B3DVector(aNew.getB3DPoint(c)));
+ }
+ }
+
+ aNew.setClosed(true);
+ aRetval.append(aNew);
+ }
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon createSphereFillPolyPolygonFromB3DRange( const B3DRange& rRange,
+ sal_uInt32 nHorSeg, sal_uInt32 nVerSeg,
+ bool bNormals,
+ double fVerStart, double fVerStop,
+ double fHorStart, double fHorStop)
+ {
+ B3DPolyPolygon aRetval(createUnitSphereFillPolyPolygon(nHorSeg, nVerSeg, bNormals, fVerStart, fVerStop, fHorStart, fHorStop));
+
+ if(aRetval.count())
+ {
+ // move and scale whole construct which is now in [-1.0 .. 1.0] in all directions
+ B3DHomMatrix aTrans;
+ aTrans.translate(1.0, 1.0, 1.0);
+ aTrans.scale(rRange.getWidth() / 2.0, rRange.getHeight() / 2.0, rRange.getDepth() / 2.0);
+ aTrans.translate(rRange.getMinX(), rRange.getMinY(), rRange.getMinZ());
+ aRetval.transform(aTrans);
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon applyDefaultNormalsSphere( const B3DPolyPolygon& rCandidate, const B3DPoint& rCenter)
+ {
+ B3DPolyPolygon aRetval;
+
+ for( const auto& rB3DPolygon : rCandidate)
+ {
+ aRetval.append(applyDefaultNormalsSphere(rB3DPolygon, rCenter));
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon invertNormals( const B3DPolyPolygon& rCandidate)
+ {
+ B3DPolyPolygon aRetval;
+
+ for( const auto& rB3DPolygon : rCandidate )
+ {
+ aRetval.append(invertNormals(rB3DPolygon));
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon applyDefaultTextureCoordinatesParallel( const B3DPolyPolygon& rCandidate, const B3DRange& rRange, bool bChangeX, bool bChangeY)
+ {
+ B3DPolyPolygon aRetval;
+
+ for( const auto& rB3DPolygon : rCandidate)
+ {
+ aRetval.append(applyDefaultTextureCoordinatesParallel(rB3DPolygon, rRange, bChangeX, bChangeY));
+ }
+
+ return aRetval;
+ }
+
+ B3DPolyPolygon applyDefaultTextureCoordinatesSphere( const B3DPolyPolygon& rCandidate, const B3DPoint& rCenter, bool bChangeX, bool bChangeY)
+ {
+ B3DPolyPolygon aRetval;
+
+ for( const auto& rB3DPolygon : rCandidate )
+ {
+ aRetval.append(applyDefaultTextureCoordinatesSphere(rB3DPolygon, rCenter, bChangeX, bChangeY));
+ }
+
+ return aRetval;
+ }
+
+ bool isInside(const B3DPolyPolygon& rCandidate, const B3DPoint& rPoint)
+ {
+ const sal_uInt32 nPolygonCount(rCandidate.count());
+
+ if(nPolygonCount == 1)
+ {
+ return isInside(rCandidate.getB3DPolygon(0), rPoint, false/*bWithBorder*/);
+ }
+ else
+ {
+ sal_Int32 nInsideCount(0);
+
+ for(const auto& rPolygon : rCandidate )
+ {
+ const bool bInside(isInside(rPolygon, rPoint, false/*bWithBorder*/));
+
+ if(bInside)
+ {
+ nInsideCount++;
+ }
+ }
+
+ return (nInsideCount % 2);
+ }
+ }
+
+/// converters for css::drawing::PolyPolygonShape3D
+ B3DPolyPolygon UnoPolyPolygonShape3DToB3DPolyPolygon(
+ const css::drawing::PolyPolygonShape3D& rPolyPolygonShape3DSource)
+ {
+ B3DPolyPolygon aRetval;
+ const sal_Int32 nOuterSequenceCount(rPolyPolygonShape3DSource.SequenceX.getLength());
+
+ if(nOuterSequenceCount)
+ {
+ assert(nOuterSequenceCount == rPolyPolygonShape3DSource.SequenceY.getLength()
+ && nOuterSequenceCount
+ == rPolyPolygonShape3DSource.SequenceZ.getLength()&&
+ "UnoPolyPolygonShape3DToB3DPolygon: Not all double sequences have the same "
+ "length (!)" );
+
+ const css::drawing::DoubleSequence* pInnerSequenceX = rPolyPolygonShape3DSource.SequenceX.getConstArray();
+ const css::drawing::DoubleSequence* pInnerSequenceY = rPolyPolygonShape3DSource.SequenceY.getConstArray();
+ const css::drawing::DoubleSequence* pInnerSequenceZ = rPolyPolygonShape3DSource.SequenceZ.getConstArray();
+
+ for(sal_Int32 a(0); a < nOuterSequenceCount; a++)
+ {
+ basegfx::B3DPolygon aNewPolygon;
+ const sal_Int32 nInnerSequenceCount(pInnerSequenceX->getLength());
+ assert(nInnerSequenceCount == pInnerSequenceY->getLength()
+ && nInnerSequenceCount == pInnerSequenceZ->getLength()
+ && "UnoPolyPolygonShape3DToB3DPolygon: Not all double sequences have "
+ "the same length (!)");
+
+ const double* pArrayX = pInnerSequenceX->getConstArray();
+ const double* pArrayY = pInnerSequenceY->getConstArray();
+ const double* pArrayZ = pInnerSequenceZ->getConstArray();
+
+ for(sal_Int32 b(0); b < nInnerSequenceCount; b++)
+ {
+ aNewPolygon.append(basegfx::B3DPoint(*pArrayX++,*pArrayY++,*pArrayZ++));
+ }
+
+ pInnerSequenceX++;
+ pInnerSequenceY++;
+ pInnerSequenceZ++;
+
+ // #i101520# correction is needed for imported polygons of old format,
+ // see callers
+ basegfx::utils::checkClosed(aNewPolygon);
+
+ aRetval.append(aNewPolygon);
+ }
+ }
+
+ return aRetval;
+ }
+
+ void B3DPolyPolygonToUnoPolyPolygonShape3D(
+ const B3DPolyPolygon& rPolyPolygonSource,
+ css::drawing::PolyPolygonShape3D& rPolyPolygonShape3DRetval)
+ {
+ const sal_uInt32 nPolygonCount(rPolyPolygonSource.count());
+
+ if(nPolygonCount)
+ {
+ rPolyPolygonShape3DRetval.SequenceX.realloc(nPolygonCount);
+ rPolyPolygonShape3DRetval.SequenceY.realloc(nPolygonCount);
+ rPolyPolygonShape3DRetval.SequenceZ.realloc(nPolygonCount);
+
+ css::drawing::DoubleSequence* pOuterSequenceX = rPolyPolygonShape3DRetval.SequenceX.getArray();
+ css::drawing::DoubleSequence* pOuterSequenceY = rPolyPolygonShape3DRetval.SequenceY.getArray();
+ css::drawing::DoubleSequence* pOuterSequenceZ = rPolyPolygonShape3DRetval.SequenceZ.getArray();
+
+ for(sal_uInt32 a(0); a < nPolygonCount; a++)
+ {
+ const basegfx::B3DPolygon& aPoly(rPolyPolygonSource.getB3DPolygon(a));
+ const sal_uInt32 nPointCount(aPoly.count());
+
+ if(nPointCount)
+ {
+ const bool bIsClosed(aPoly.isClosed());
+ const sal_uInt32 nTargetCount(bIsClosed ? nPointCount + 1 : nPointCount);
+ pOuterSequenceX->realloc(nTargetCount);
+ pOuterSequenceY->realloc(nTargetCount);
+ pOuterSequenceZ->realloc(nTargetCount);
+
+ double* pInnerSequenceX = pOuterSequenceX->getArray();
+ double* pInnerSequenceY = pOuterSequenceY->getArray();
+ double* pInnerSequenceZ = pOuterSequenceZ->getArray();
+
+ for(sal_uInt32 b(0); b < nPointCount; b++)
+ {
+ const basegfx::B3DPoint aPoint(aPoly.getB3DPoint(b));
+
+ *pInnerSequenceX++ = aPoint.getX();
+ *pInnerSequenceY++ = aPoint.getY();
+ *pInnerSequenceZ++ = aPoint.getZ();
+ }
+
+ if(bIsClosed)
+ {
+ const basegfx::B3DPoint aPoint(aPoly.getB3DPoint(0));
+
+ *pInnerSequenceX++ = aPoint.getX();
+ *pInnerSequenceY++ = aPoint.getY();
+ *pInnerSequenceZ++ = aPoint.getZ();
+ }
+ }
+ else
+ {
+ pOuterSequenceX->realloc(0);
+ pOuterSequenceY->realloc(0);
+ pOuterSequenceZ->realloc(0);
+ }
+
+ pOuterSequenceX++;
+ pOuterSequenceY++;
+ pOuterSequenceZ++;
+ }
+ }
+ else
+ {
+ rPolyPolygonShape3DRetval.SequenceX.realloc(0);
+ rPolyPolygonShape3DRetval.SequenceY.realloc(0);
+ rPolyPolygonShape3DRetval.SequenceZ.realloc(0);
+ }
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/range/b2dpolyrange.cxx b/basegfx/source/range/b2dpolyrange.cxx
new file mode 100644
index 0000000000..beb506fc8e
--- /dev/null
+++ b/basegfx/source/range/b2dpolyrange.cxx
@@ -0,0 +1,153 @@
+/* -*- 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 <basegfx/range/b2dpolyrange.hxx>
+
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/range/b2drangeclipper.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+#include <algorithm>
+#include <vector>
+
+namespace basegfx
+{
+ class ImplB2DPolyRange
+ {
+ public:
+ ImplB2DPolyRange()
+ {}
+
+ bool operator==(const ImplB2DPolyRange& rRHS) const
+ {
+ return maRanges == rRHS.maRanges && maOrient == rRHS.maOrient;
+ }
+
+ sal_uInt32 count() const
+ {
+ return maRanges.size();
+ }
+
+ B2DPolyRange::ElementType getElement(sal_uInt32 nIndex) const
+ {
+ return std::make_tuple(maRanges[nIndex], maOrient[nIndex]);
+ }
+
+ void appendElement(const B2DRange& rRange, B2VectorOrientation eOrient)
+ {
+ maRanges.push_back(rRange);
+ maOrient.push_back(eOrient);
+ maBounds.expand(rRange);
+ }
+
+ void clear()
+ {
+ std::vector<B2DRange>().swap(maRanges);
+ std::vector<B2VectorOrientation>().swap(maOrient);
+
+ maBounds.reset();
+ }
+
+ bool overlaps( const B2DRange& rRange ) const
+ {
+ if( !maBounds.overlaps( rRange ) )
+ return false;
+
+ const std::vector<B2DRange>::const_iterator aEnd( maRanges.end() );
+ return std::any_of( maRanges.begin(),
+ aEnd,
+ [&rRange](const B2DRange& aRange) { return aRange.overlaps(rRange); } );
+ }
+
+ B2DPolyPolygon solveCrossovers() const
+ {
+ return utils::solveCrossovers(maRanges,maOrient);
+ }
+
+ void transform(const basegfx::B2DHomMatrix& rTranslate)
+ {
+ maBounds.transform(rTranslate);
+ for (auto &a : maRanges)
+ a.transform(rTranslate);
+ }
+
+ private:
+ B2DRange maBounds;
+ std::vector<B2DRange> maRanges;
+ std::vector<B2VectorOrientation> maOrient;
+ };
+
+ B2DPolyRange::B2DPolyRange() = default;
+
+ B2DPolyRange::~B2DPolyRange() = default;
+
+ B2DPolyRange::B2DPolyRange( const B2DPolyRange& ) = default;
+
+ B2DPolyRange& B2DPolyRange::operator=( const B2DPolyRange& ) = default;
+
+ bool B2DPolyRange::operator==(const B2DPolyRange& rRange) const
+ {
+ if(mpImpl.same_object(rRange.mpImpl))
+ return true;
+
+ return ((*mpImpl) == (*rRange.mpImpl));
+ }
+
+ bool B2DPolyRange::operator!=(const B2DPolyRange& rRange) const
+ {
+ return !(*this == rRange);
+ }
+
+ sal_uInt32 B2DPolyRange::count() const
+ {
+ return mpImpl->count();
+ }
+
+ B2DPolyRange::ElementType B2DPolyRange::getElement(sal_uInt32 nIndex) const
+ {
+ return mpImpl->getElement(nIndex);
+ }
+
+ void B2DPolyRange::appendElement(const B2DRange& rRange, B2VectorOrientation eOrient)
+ {
+ mpImpl->appendElement(rRange, eOrient);
+ }
+
+ void B2DPolyRange::clear()
+ {
+ mpImpl->clear();
+ }
+
+ bool B2DPolyRange::overlaps( const B2DRange& rRange ) const
+ {
+ return mpImpl->overlaps(rRange);
+ }
+
+ void B2DPolyRange::transform(const basegfx::B2DHomMatrix& rTranslate)
+ {
+ mpImpl->transform(rTranslate);
+ }
+
+ B2DPolyPolygon B2DPolyRange::solveCrossovers() const
+ {
+ return mpImpl->solveCrossovers();
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/range/b2drange.cxx b/basegfx/source/range/b2drange.cxx
new file mode 100644
index 0000000000..1f20cce90a
--- /dev/null
+++ b/basegfx/source/range/b2drange.cxx
@@ -0,0 +1,81 @@
+/* -*- 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 <basegfx/range/b2drange.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace basegfx
+{
+ B2DRange::B2DRange(const B2IRange& rRange)
+ {
+ if (!rRange.isEmpty())
+ {
+ maRangeX = basegfx::BasicRange<ValueType, TraitsType>(rRange.getMinX());
+ maRangeY = basegfx::BasicRange<ValueType, TraitsType>(rRange.getMinY());
+
+ maRangeX.expand(rRange.getMaxX());
+ maRangeY.expand(rRange.getMaxY());
+ }
+ }
+
+ void B2DRange::transform(const B2DHomMatrix& rMatrix)
+ {
+ if(!isEmpty() && !rMatrix.isIdentity())
+ {
+ const B2DRange aSource(*this);
+ reset();
+ expand(rMatrix * B2DPoint(aSource.getMinX(), aSource.getMinY()));
+ expand(rMatrix * B2DPoint(aSource.getMaxX(), aSource.getMinY()));
+ expand(rMatrix * B2DPoint(aSource.getMinX(), aSource.getMaxY()));
+ expand(rMatrix * B2DPoint(aSource.getMaxX(), aSource.getMaxY()));
+ }
+ }
+
+ B2DRange& B2DRange::operator*=(const basegfx::B2DHomMatrix& rMatrix)
+ {
+ transform(rMatrix);
+ return *this;
+ }
+
+ const B2DRange& B2DRange::getUnitB2DRange()
+ {
+ static const B2DRange aUnitB2DRange(0.0, 0.0, 1.0, 1.0);
+
+ return aUnitB2DRange;
+ }
+
+ B2IRange fround(const B2DRange& rRange)
+ {
+ return rRange.isEmpty() ?
+ B2IRange() :
+ B2IRange(fround(rRange.getMinimum()),
+ fround(rRange.getMaximum()));
+ }
+
+ B2DRange operator*( const ::basegfx::B2DHomMatrix& rMat, const B2DRange& rB2DRange )
+ {
+ B2DRange aRes( rB2DRange );
+ aRes *= rMat;
+ return aRes;
+ }
+
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/range/b2drangeclipper.cxx b/basegfx/source/range/b2drangeclipper.cxx
new file mode 100644
index 0000000000..401ff20caa
--- /dev/null
+++ b/basegfx/source/range/b2drangeclipper.cxx
@@ -0,0 +1,877 @@
+/* -*- 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 <osl/diagnose.h>
+
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/range/b2drangeclipper.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+
+#include <o3tl/vector_pool.hxx>
+
+#include <algorithm>
+#include <cassert>
+#include <list>
+#include <iterator>
+
+namespace basegfx
+{
+ namespace
+ {
+ // Generating a poly-polygon from a bunch of rectangles
+
+ // Helper functionality for sweep-line algorithm
+ // ====================================================
+
+ class ImplPolygon;
+ typedef o3tl::vector_pool<ImplPolygon> VectorOfPolygons;
+
+ /** This class represents an active edge
+
+ As the sweep line traverses across the overall area,
+ rectangle edges parallel to it generate events, and
+ rectangle edges orthogonal to it generate active
+ edges. This class represents the latter.
+ */
+ class ActiveEdge
+ {
+ public:
+
+ enum EdgeDirection {
+ /// edge proceeds to the left
+ PROCEED_LEFT=0,
+ /// edge proceeds to the right
+ PROCEED_RIGHT=1
+ };
+
+ /** Create active edge
+
+ @param rRect
+ Rectangle this edge is part of
+
+ @param fInvariantCoord
+ The invariant coordinate value of this edge
+
+ @param eEdgeType
+ Is fInvariantCoord the lower or the higher value, for
+ this rect?
+ */
+ ActiveEdge( const B2DRectangle& rRect,
+ const double& fInvariantCoord,
+ std::ptrdiff_t nPolyIdx,
+ EdgeDirection eEdgeDirection ) :
+ mfInvariantCoord(fInvariantCoord),
+ mpAssociatedRect( &rRect ),
+ mnPolygonIdx( nPolyIdx ),
+ meEdgeDirection( eEdgeDirection )
+ {}
+
+ double getInvariantCoord() const { return mfInvariantCoord; }
+ const B2DRectangle& getRect() const { return *mpAssociatedRect; }
+ std::ptrdiff_t getTargetPolygonIndex() const { return mnPolygonIdx; }
+ void setTargetPolygonIndex( std::ptrdiff_t nIdx ) { mnPolygonIdx = nIdx; }
+ EdgeDirection getEdgeDirection() const { return meEdgeDirection; }
+
+ private:
+ /** The invariant coordinate value of this edge (e.g. the
+ common y value, for a horizontal edge)
+ */
+ double mfInvariantCoord;
+
+ /** Associated rectangle
+
+ This on the one hand saves some storage space (the
+ vector of rectangles is persistent, anyway), and on
+ the other hand provides an identifier to match active
+ edges and x events (see below)
+
+ Ptr because class needs to be assignable
+ */
+ const B2DRectangle* mpAssociatedRect;
+
+ /** Index of the polygon this edge is currently involved
+ with.
+
+ Note that this can change for some kinds of edge
+ intersection, as the algorithm tends to swap
+ associated polygons there.
+
+ -1 denotes no assigned polygon
+ */
+ std::ptrdiff_t mnPolygonIdx;
+
+ /// 'left' or 'right'
+ EdgeDirection meEdgeDirection;
+ };
+
+ // Needs to be list - various places hold ptrs to elements
+ typedef std::list< ActiveEdge > ListOfEdges;
+
+ /** Element of the sweep line event list
+
+ As the sweep line traverses across the overall area,
+ rectangle edges parallel to it generate events, and
+ rectangle edges orthogonal to it generate active
+ edges. This class represents the former.
+
+ The class defines an element of the sweep line list. The
+ sweep line's position jumps in steps defined by the
+ coordinates of the sorted SweepLineEvent entries.
+ */
+ class SweepLineEvent
+ {
+ public:
+ /** The two possible sweep line rectangle edges differ by
+ one coordinate value - the starting edge has the
+ lower, the finishing edge the higher value.
+ */
+ enum EdgeType {
+ /// edge with lower coordinate value
+ STARTING_EDGE=0,
+ /// edge with higher coordinate value
+ FINISHING_EDGE=1
+ };
+
+ /** The two possible sweep line directions
+ */
+ enum EdgeDirection {
+ PROCEED_UP=0,
+ PROCEED_DOWN=1
+ };
+
+ /** Create sweep line event
+
+ @param fPos
+ Coordinate position of the event
+
+ @param rRect
+ Rectangle this event is generated for.
+
+ @param eEdgeType
+ Is fPos the lower or the higher value, for the
+ rectangle this event is generated for?
+ */
+ SweepLineEvent( double fPos,
+ const B2DRectangle& rRect,
+ EdgeType eEdgeType,
+ EdgeDirection eDirection) :
+ mfPos( fPos ),
+ mpAssociatedRect( &rRect ),
+ meEdgeType( eEdgeType ),
+ meEdgeDirection( eDirection )
+ {}
+
+ double getPos() const { return mfPos; }
+ const B2DRectangle& getRect() const { return *mpAssociatedRect; }
+ EdgeType getEdgeType() const { return meEdgeType; }
+ EdgeDirection getEdgeDirection() const { return meEdgeDirection; }
+
+ /// For STL sort
+ bool operator<( const SweepLineEvent& rRHS ) const { return mfPos < rRHS.mfPos; }
+
+ private:
+ /// position of the event, in the direction of the line sweep
+ double mfPos;
+
+ /** Rectangle this event is generated for
+
+ This on the one hand saves some storage space (the
+ vector of rectangles is persistent, anyway), and on
+ the other hand provides an identifier to match active
+ edges and events (see below)
+
+ Ptr because class needs to be assignable
+ */
+ const B2DRectangle* mpAssociatedRect;
+
+ /// 'upper' or 'lower' edge of original rectangle.
+ EdgeType meEdgeType;
+
+ /// 'up' or 'down'
+ EdgeDirection meEdgeDirection;
+ };
+
+ typedef std::vector< SweepLineEvent > VectorOfEvents;
+
+ /** Smart point container for B2DMultiRange::getPolyPolygon()
+
+ This class provides methods needed only here, and is used
+ as a place to store some additional information per
+ polygon. Also, most of the intersection logic is
+ implemented here.
+ */
+ class ImplPolygon
+ {
+ public:
+ /** Create polygon
+ */
+ ImplPolygon() :
+ mpLeadingRightEdge(nullptr),
+ mnIdx(-1),
+ mbIsFinished(false)
+ {
+ // completely ad-hoc. but what the hell.
+ maPoints.reserve(11);
+ }
+
+ void setPolygonPoolIndex( std::ptrdiff_t nIdx ) { mnIdx = nIdx; }
+
+ /// Add point to the end of the existing points
+ void append( const B2DPoint& rPoint )
+ {
+ OSL_PRECOND( maPoints.empty() ||
+ maPoints.back().getX() == rPoint.getX() ||
+ maPoints.back().getY() == rPoint.getY(),
+ "ImplPolygon::append(): added point violates 90 degree line angle constraint!" );
+
+ if( maPoints.empty() ||
+ maPoints.back() != rPoint )
+ {
+ // avoid duplicate points
+ maPoints.push_back( rPoint );
+ }
+ }
+
+ /** Perform the intersection of this polygon with an
+ active edge.
+
+ @param rEvent
+ The vertical line event that generated the
+ intersection
+
+ @param rActiveEdge
+ The active edge that generated the intersection
+
+ @param rPolygonPool
+ Polygon pool, we sometimes need to allocate a new one
+
+ @param bIsFinishingEdge
+ True, when this is hitting the last edge of the
+ vertical sweep - every vertical sweep starts and ends
+ with upper and lower edge of the _same_ rectangle.
+
+ @return the new current polygon (that's the one
+ processing must proceed with, when going through the
+ list of upcoming active edges).
+ */
+ std::ptrdiff_t intersect( SweepLineEvent const & rEvent,
+ ActiveEdge& rActiveEdge,
+ VectorOfPolygons& rPolygonPool,
+ B2DPolyPolygon& rRes,
+ bool isFinishingEdge )
+ {
+ OSL_PRECOND( !mbIsFinished,
+ "ImplPolygon::intersect(): called on already finished polygon!" );
+ OSL_PRECOND( !isFinishingEdge || &rEvent.getRect() == &rActiveEdge.getRect(),
+ "ImplPolygon::intersect(): inconsistent ending!" );
+
+ const B2DPoint aIntersectionPoint( rEvent.getPos(),
+ rActiveEdge.getInvariantCoord() );
+
+ // intersection point, goes to our polygon
+ // unconditionally
+ append(aIntersectionPoint);
+
+ if( isFinishingEdge )
+ {
+ // isSweepLineEnteringRect ?
+ if( rEvent.getEdgeType() == SweepLineEvent::STARTING_EDGE)
+ handleFinalOwnRightEdge(rActiveEdge);
+ else
+ handleFinalOwnLeftEdge(rActiveEdge,
+ rPolygonPool,
+ rRes);
+
+ // we're done with this rect & sweep line
+ return -1;
+ }
+ else if( metOwnEdge(rEvent,rActiveEdge) )
+ {
+ handleInitialOwnEdge(rEvent, rActiveEdge);
+
+ // point already added, all init done, continue
+ // with same poly
+ return mnIdx;
+ }
+ else
+ {
+ OSL_ENSURE( rActiveEdge.getTargetPolygonIndex() != -1,
+ "ImplPolygon::intersect(): non-trivial intersection hit empty polygon!" );
+
+ const bool isHittingLeftEdge(
+ rActiveEdge.getEdgeDirection() == ActiveEdge::PROCEED_LEFT);
+
+ if( isHittingLeftEdge )
+ return handleComplexLeftEdge(rActiveEdge,
+ aIntersectionPoint,
+ rPolygonPool,
+ rRes);
+ else
+ return handleComplexRightEdge(rActiveEdge,
+ aIntersectionPoint,
+ rPolygonPool);
+ }
+ }
+
+ private:
+ void handleInitialOwnEdge(SweepLineEvent const & rEvent,
+ ActiveEdge const & rActiveEdge) const
+ {
+ const bool isActiveEdgeProceedLeft(
+ rActiveEdge.getEdgeDirection() == ActiveEdge::PROCEED_LEFT);
+ const bool isSweepLineEnteringRect(
+ rEvent.getEdgeType() == SweepLineEvent::STARTING_EDGE);
+
+ OSL_ENSURE( isSweepLineEnteringRect == isActiveEdgeProceedLeft,
+ "ImplPolygon::intersect(): sweep initial own edge hit: wrong polygon order" );
+
+ OSL_ENSURE( isSweepLineEnteringRect ||
+ mpLeadingRightEdge == &rActiveEdge,
+ "ImplPolygon::intersect(): sweep initial own edge hit: wrong leading edge" );
+ }
+
+ void handleFinalOwnRightEdge(ActiveEdge& rActiveEdge)
+ {
+ OSL_ENSURE( rActiveEdge.getEdgeDirection() == ActiveEdge::PROCEED_RIGHT,
+ "ImplPolygon::handleInitialOwnRightEdge(): start edge wrong polygon order" );
+
+ rActiveEdge.setTargetPolygonIndex(mnIdx);
+ mpLeadingRightEdge = &rActiveEdge;
+ }
+
+ void handleFinalOwnLeftEdge(ActiveEdge const & rActiveEdge,
+ VectorOfPolygons& rPolygonPool,
+ B2DPolyPolygon& rRes)
+ {
+ OSL_ENSURE( rActiveEdge.getEdgeDirection() == ActiveEdge::PROCEED_LEFT,
+ "ImplPolygon::handleFinalOwnLeftEdge(): end edge wrong polygon order" );
+
+ const bool isHittingOurTail(
+ rActiveEdge.getTargetPolygonIndex() == mnIdx);
+
+ if( isHittingOurTail )
+ finish(rRes); // just finish. no fuss.
+ else
+ {
+ // temp poly hits final left edge
+ const std::ptrdiff_t nTmpIdx=rActiveEdge.getTargetPolygonIndex();
+ ImplPolygon& rTmp=rPolygonPool.get(nTmpIdx);
+
+ // active edge's polygon has points
+ // already. ours need to go in front of them.
+ maPoints.insert(maPoints.end(),
+ rTmp.maPoints.begin(),
+ rTmp.maPoints.end());
+
+ // adjust leading edges, we're switching the polygon
+ ActiveEdge* const pFarEdge=rTmp.mpLeadingRightEdge;
+
+ mpLeadingRightEdge = pFarEdge;
+ pFarEdge->setTargetPolygonIndex(mnIdx);
+
+ // nTmpIdx is an empty shell, get rid of it
+ rPolygonPool.free(nTmpIdx);
+ }
+ }
+
+ std::ptrdiff_t handleComplexLeftEdge(ActiveEdge& rActiveEdge,
+ const B2DPoint& rIntersectionPoint,
+ VectorOfPolygons& rPolygonPool,
+ B2DPolyPolygon& rRes)
+ {
+ const bool isHittingOurTail(
+ rActiveEdge.getTargetPolygonIndex() == mnIdx);
+ if( isHittingOurTail )
+ {
+ finish(rRes);
+
+ // so "this" is done - need new polygon to collect
+ // further points
+ const std::ptrdiff_t nIdxNewPolygon=rPolygonPool.alloc();
+ rPolygonPool.get(nIdxNewPolygon).setPolygonPoolIndex(nIdxNewPolygon);
+ rPolygonPool.get(nIdxNewPolygon).append(rIntersectionPoint);
+
+ rActiveEdge.setTargetPolygonIndex(nIdxNewPolygon);
+
+ return nIdxNewPolygon;
+ }
+ else
+ {
+ const std::ptrdiff_t nTmpIdx=rActiveEdge.getTargetPolygonIndex();
+ ImplPolygon& rTmp=rPolygonPool.get(nTmpIdx);
+
+ // active edge's polygon has points
+ // already. ours need to go in front of them.
+ maPoints.insert(maPoints.end(),
+ rTmp.maPoints.begin(),
+ rTmp.maPoints.end());
+
+ rTmp.maPoints.clear();
+ rTmp.append(rIntersectionPoint);
+
+ // adjust leading edges, we're switching the polygon
+ ActiveEdge* const pFarEdge=rTmp.mpLeadingRightEdge;
+ ActiveEdge* const pNearEdge=&rActiveEdge;
+
+ rTmp.mpLeadingRightEdge = nullptr;
+ pNearEdge->setTargetPolygonIndex(nTmpIdx);
+
+ mpLeadingRightEdge = pFarEdge;
+ pFarEdge->setTargetPolygonIndex(mnIdx);
+
+ return nTmpIdx;
+ }
+ }
+
+ std::ptrdiff_t handleComplexRightEdge(ActiveEdge& rActiveEdge,
+ const B2DPoint& rIntersectionPoint,
+ VectorOfPolygons& rPolygonPool)
+ {
+ const std::ptrdiff_t nTmpIdx=rActiveEdge.getTargetPolygonIndex();
+ ImplPolygon& rTmp=rPolygonPool.get(nTmpIdx);
+
+ rTmp.append(rIntersectionPoint);
+
+ rActiveEdge.setTargetPolygonIndex(mnIdx);
+ mpLeadingRightEdge = &rActiveEdge;
+
+ rTmp.mpLeadingRightEdge = nullptr;
+
+ return nTmpIdx;
+ }
+
+ /// True when sweep line hits our own active edge
+ static bool metOwnEdge(SweepLineEvent const & rEvent,
+ ActiveEdge const & rActiveEdge)
+ {
+ const bool bHitOwnEdge=&rEvent.getRect() == &rActiveEdge.getRect();
+ return bHitOwnEdge;
+ }
+
+ /// Retrieve B2DPolygon from this object
+ B2DPolygon getPolygon() const
+ {
+ B2DPolygon aRes;
+ for (auto const& aPoint : maPoints)
+ aRes.append(aPoint, 1);
+ aRes.setClosed( true );
+ return aRes;
+ }
+
+ /** Finish this polygon, push to result set.
+ */
+ void finish(B2DPolyPolygon& rRes)
+ {
+ OSL_PRECOND( maPoints.empty() ||
+ maPoints.front().getX() == maPoints.back().getX() ||
+ maPoints.front().getY() == maPoints.back().getY(),
+ "ImplPolygon::finish(): first and last point violate 90 degree line angle constraint!" );
+
+ mbIsFinished = true;
+ mpLeadingRightEdge = nullptr;
+
+ rRes.append(getPolygon());
+ }
+
+ /** Refers to the current leading edge element of this
+ polygon, or NULL. The leading edge denotes the 'front'
+ of the polygon vertex sequence, i.e. the coordinates
+ at the polygon's leading edge are returned from
+ maPoints.front()
+ */
+ ActiveEdge* mpLeadingRightEdge;
+
+ /// current index into vector pool
+ std::ptrdiff_t mnIdx;
+
+ /// Container for the actual polygon points
+ std::vector<B2DPoint> maPoints;
+
+ /// When true, this polygon is 'done', i.e. nothing must be added anymore.
+ bool mbIsFinished;
+ };
+
+ /** Init sweep line event list
+
+ This method fills the event list with the sweep line
+ events generated from the input rectangles, and sorts them
+ with increasing x.
+ */
+ void setupSweepLineEventListFromRanges( VectorOfEvents& o_rEventVector,
+ const std::vector<B2DRange>& rRanges,
+ const std::vector<B2VectorOrientation>& rOrientations )
+ {
+ // we need exactly 2*rectVec.size() events: one for the
+ // left, and one for the right edge of each rectangle
+ o_rEventVector.clear();
+ o_rEventVector.reserve( 2*rRanges.size() );
+
+ // generate events
+ // ===============
+
+ // first pass: add all left edges in increasing order
+ std::vector<B2DRange>::const_iterator aCurrRect=rRanges.begin();
+ std::vector<B2VectorOrientation>::const_iterator aCurrOrientation=rOrientations.begin();
+ const std::vector<B2DRange>::const_iterator aEnd=rRanges.end();
+ const std::vector<B2VectorOrientation>::const_iterator aEndOrientation=rOrientations.end();
+ while( aCurrRect != aEnd && aCurrOrientation != aEndOrientation )
+ {
+ const B2DRectangle& rCurrRect( *aCurrRect++ );
+
+ o_rEventVector.emplace_back( rCurrRect.getMinX(),
+ rCurrRect,
+ SweepLineEvent::STARTING_EDGE,
+ (*aCurrOrientation++) == B2VectorOrientation::Positive ?
+ SweepLineEvent::PROCEED_UP : SweepLineEvent::PROCEED_DOWN );
+ }
+
+ // second pass: add all right edges in reversed order
+ std::vector<B2DRange>::const_reverse_iterator aCurrRectR=rRanges.rbegin();
+ std::vector<B2VectorOrientation>::const_reverse_iterator aCurrOrientationR=rOrientations.rbegin();
+ const std::vector<B2DRange>::const_reverse_iterator aEndR=rRanges.rend();
+ while( aCurrRectR != aEndR )
+ {
+ const B2DRectangle& rCurrRect( *aCurrRectR++ );
+
+ o_rEventVector.emplace_back( rCurrRect.getMaxX(),
+ rCurrRect,
+ SweepLineEvent::FINISHING_EDGE,
+ (*aCurrOrientationR++) == B2VectorOrientation::Positive ?
+ SweepLineEvent::PROCEED_DOWN : SweepLineEvent::PROCEED_UP );
+ }
+
+ // sort events
+ // ===========
+
+ // since we use stable_sort, the order of events with the
+ // same x value will not change. The elaborate two-pass
+ // add above thus ensures, that for each two rectangles
+ // with similar left and right x coordinates, the
+ // rectangle whose left event comes first will have its
+ // right event come last. This is advantageous for the
+ // clip algorithm below, see handleRightEdgeCrossing().
+
+ std::stable_sort( o_rEventVector.begin(),
+ o_rEventVector.end() );
+ }
+
+ /** Insert two active edge segments for the given rectangle.
+
+ This method creates two active edge segments from the
+ given rect, and inserts them into the active edge list,
+ such that this stays sorted (if it was before).
+
+ @param io_rEdgeList
+ Active edge list to insert into
+
+ @param io_rPolygons
+ Vector of polygons. Each rectangle added creates one
+ tentative result polygon in this vector, and the edge list
+ entries holds a reference to that polygon (this _requires_
+ that the polygon vector does not reallocate, i.e. it must
+ have at least the maximal number of rectangles reserved)
+
+ @param o_CurrentPolygon
+ The then-current polygon when processing this sweep line
+ event
+
+ @param rCurrEvent
+ The actual event that caused this call
+ */
+ void createActiveEdgesFromStartEvent( ListOfEdges & io_rEdgeList,
+ VectorOfPolygons & io_rPolygonPool,
+ SweepLineEvent const & rCurrEvent )
+ {
+ ListOfEdges aNewEdges;
+ const B2DRectangle& rRect=rCurrEvent.getRect();
+ const bool bGoesDown=rCurrEvent.getEdgeDirection() == SweepLineEvent::PROCEED_DOWN;
+
+ // start event - new rect starts here, needs polygon to
+ // collect points into
+ const std::ptrdiff_t nIdxPolygon=io_rPolygonPool.alloc();
+ io_rPolygonPool.get(nIdxPolygon).setPolygonPoolIndex(nIdxPolygon);
+
+ // upper edge
+ aNewEdges.emplace_back(
+ rRect,
+ rRect.getMinY(),
+ bGoesDown ? nIdxPolygon : -1,
+ bGoesDown ? ActiveEdge::PROCEED_LEFT : ActiveEdge::PROCEED_RIGHT );
+ // lower edge
+ aNewEdges.emplace_back(
+ rRect,
+ rRect.getMaxY(),
+ bGoesDown ? -1 : nIdxPolygon,
+ bGoesDown ? ActiveEdge::PROCEED_RIGHT : ActiveEdge::PROCEED_LEFT );
+
+ // furthermore, have to respect a special tie-breaking
+ // rule here, for edges which share the same y value:
+ // newly added upper edges must be inserted _before_ any
+ // other edge with the same y value, and newly added lower
+ // edges must be _after_ all other edges with the same
+ // y. This ensures that the left vertical edge processing
+ // below encounters the upper edge of the current rect
+ // first, and the lower edge last, which automatically
+ // starts and finishes this rect correctly (as only then,
+ // the polygon will have their associated active edges
+ // set).
+ const double nMinY( rRect.getMinY() );
+ const double nMaxY( rRect.getMaxY() );
+ ListOfEdges::iterator aCurr( io_rEdgeList.begin() );
+ const ListOfEdges::iterator aEnd ( io_rEdgeList.end() );
+ while( aCurr != aEnd )
+ {
+ const double nCurrY( aCurr->getInvariantCoord() );
+
+ if( nCurrY >= nMinY &&
+ aNewEdges.size() == 2 ) // only add, if not yet done.
+ {
+ // insert upper edge _before_ aCurr. Thus, it will
+ // be the first entry for a range of equal y
+ // values. Using splice here, since we hold
+ // references to the moved list element!
+ io_rEdgeList.splice( aCurr,
+ aNewEdges,
+ aNewEdges.begin() );
+ }
+
+ if( nCurrY > nMaxY )
+ {
+ // insert lower edge _before_ aCurr. Thus, it will
+ // be the last entry for a range of equal y values
+ // (aCurr is the first entry strictly larger than
+ // nMaxY). Using splice here, since we hold
+ // references to the moved list element!
+ io_rEdgeList.splice( aCurr,
+ aNewEdges,
+ aNewEdges.begin() );
+ // done with insertion, can early-exit here.
+ return;
+ }
+
+ ++aCurr;
+ }
+
+ // append remainder of aNewList (might still contain 2 or
+ // 1 elements, depending of the contents of io_rEdgeList).
+ io_rEdgeList.splice( aCurr,
+ aNewEdges );
+ }
+
+ bool isSameRect(ActiveEdge const & rEdge,
+ basegfx::B2DRange const & rRect)
+ {
+ return &rEdge.getRect() == &rRect;
+ }
+
+ // wow what a hack. necessary because stl's list::erase does
+ // not eat reverse_iterator
+ template<typename Cont, typename Iter> Iter eraseFromList(Cont&, const Iter&);
+ template<> ListOfEdges::iterator eraseFromList(
+ ListOfEdges& rList, const ListOfEdges::iterator& aIter)
+ {
+ return rList.erase(aIter);
+ }
+ template<> ListOfEdges::reverse_iterator eraseFromList(
+ ListOfEdges& rList, const ListOfEdges::reverse_iterator& aIter)
+ {
+ return ListOfEdges::reverse_iterator(
+ rList.erase(std::prev(aIter.base())));
+ }
+
+ template<int bPerformErase,
+ typename Iterator> void processActiveEdges(
+ Iterator first,
+ Iterator last,
+ ListOfEdges& rActiveEdgeList,
+ SweepLineEvent const & rCurrEvent,
+ VectorOfPolygons& rPolygonPool,
+ B2DPolyPolygon& rRes )
+ {
+ const basegfx::B2DRange& rCurrRect=rCurrEvent.getRect();
+
+ // fast-forward to rCurrEvent's first active edge (holds
+ // for both starting and finishing sweep line events, a
+ // rect is regarded _outside_ any rects whose events have
+ // started earlier
+ first = std::find_if(first, last,
+ [&rCurrRect](ActiveEdge& anEdge) { return isSameRect(anEdge, rCurrRect); });
+
+ if(first == last)
+ return;
+
+ int nCount=0;
+ std::ptrdiff_t nCurrPolyIdx=-1;
+ while(first != last)
+ {
+ if( nCurrPolyIdx == -1 )
+ nCurrPolyIdx=first->getTargetPolygonIndex();
+
+ assert(nCurrPolyIdx != -1);
+
+ // second encounter of my rect -> second edge
+ // encountered, done
+ const bool bExit=
+ nCount &&
+ isSameRect(*first,
+ rCurrRect);
+
+ // deal with current active edge
+ nCurrPolyIdx =
+ rPolygonPool.get(nCurrPolyIdx).intersect(
+ rCurrEvent,
+ *first,
+ rPolygonPool,
+ rRes,
+ bExit);
+
+ // prune upper & lower active edges, if requested
+ if( bPerformErase && (bExit || !nCount) )
+ first = eraseFromList(rActiveEdgeList,first);
+ else
+ ++first;
+
+ // delayed exit, had to prune first
+ if( bExit )
+ return;
+
+ ++nCount;
+ }
+ }
+
+ template<int bPerformErase> void processActiveEdgesTopDown(
+ SweepLineEvent& rCurrEvent,
+ ListOfEdges& rActiveEdgeList,
+ VectorOfPolygons& rPolygonPool,
+ B2DPolyPolygon& rRes )
+ {
+ processActiveEdges<bPerformErase>(
+ rActiveEdgeList. begin(),
+ rActiveEdgeList. end(),
+ rActiveEdgeList,
+ rCurrEvent,
+ rPolygonPool,
+ rRes);
+ }
+
+ template<int bPerformErase> void processActiveEdgesBottomUp(
+ SweepLineEvent& rCurrEvent,
+ ListOfEdges& rActiveEdgeList,
+ VectorOfPolygons& rPolygonPool,
+ B2DPolyPolygon& rRes )
+ {
+ processActiveEdges<bPerformErase>(
+ rActiveEdgeList. rbegin(),
+ rActiveEdgeList. rend(),
+ rActiveEdgeList,
+ rCurrEvent,
+ rPolygonPool,
+ rRes);
+ }
+
+ enum{ NoErase=0, PerformErase=1 };
+
+ void handleStartingEdge( SweepLineEvent& rCurrEvent,
+ ListOfEdges& rActiveEdgeList,
+ VectorOfPolygons& rPolygonPool,
+ B2DPolyPolygon& rRes)
+ {
+ // inject two new active edges for rect
+ createActiveEdgesFromStartEvent( rActiveEdgeList,
+ rPolygonPool,
+ rCurrEvent );
+
+ if( rCurrEvent.getEdgeDirection() == SweepLineEvent::PROCEED_DOWN )
+ processActiveEdgesTopDown<NoErase>(
+ rCurrEvent, rActiveEdgeList, rPolygonPool, rRes);
+ else
+ processActiveEdgesBottomUp<NoErase>(
+ rCurrEvent, rActiveEdgeList, rPolygonPool, rRes);
+ }
+
+ void handleFinishingEdge( SweepLineEvent& rCurrEvent,
+ ListOfEdges& rActiveEdgeList,
+ VectorOfPolygons& rPolygonPool,
+ B2DPolyPolygon& rRes)
+ {
+ if( rCurrEvent.getEdgeDirection() == SweepLineEvent::PROCEED_DOWN )
+ processActiveEdgesTopDown<PerformErase>(
+ rCurrEvent, rActiveEdgeList, rPolygonPool, rRes);
+ else
+ processActiveEdgesBottomUp<PerformErase>(
+ rCurrEvent, rActiveEdgeList, rPolygonPool, rRes);
+ }
+
+ void handleSweepLineEvent( SweepLineEvent& rCurrEvent,
+ ListOfEdges& rActiveEdgeList,
+ VectorOfPolygons& rPolygonPool,
+ B2DPolyPolygon& rRes)
+ {
+ if( rCurrEvent.getEdgeType() == SweepLineEvent::STARTING_EDGE )
+ handleStartingEdge(rCurrEvent,rActiveEdgeList,rPolygonPool,rRes);
+ else
+ handleFinishingEdge(rCurrEvent,rActiveEdgeList,rPolygonPool,rRes);
+ }
+ }
+
+ namespace utils
+ {
+ B2DPolyPolygon solveCrossovers(const std::vector<B2DRange>& rRanges,
+ const std::vector<B2VectorOrientation>& rOrientations)
+ {
+ // sweep-line algorithm to generate a poly-polygon
+ // from a bunch of rectangles
+ // ===============================================
+
+ // This algorithm uses the well-known sweep line
+ // concept, explained in every good text book about
+ // computational geometry.
+
+ // We start with creating two structures for every
+ // rectangle, one representing the left x coordinate,
+ // one representing the right x coordinate (and both
+ // referencing the original rect). These structs are
+ // sorted with increasing x coordinates.
+
+ // Then, we start processing the resulting list from
+ // the beginning. Every entry in the list defines a
+ // point in time of the line sweeping from left to
+ // right across all rectangles.
+ VectorOfEvents aSweepLineEvents;
+ setupSweepLineEventListFromRanges( aSweepLineEvents,
+ rRanges,
+ rOrientations );
+
+ B2DPolyPolygon aRes;
+ VectorOfPolygons aPolygonPool;
+ ListOfEdges aActiveEdgeList;
+
+ // sometimes not enough, but a usable compromise
+ aPolygonPool.reserve( rRanges.size() );
+
+ for (auto& aSweepLineEvent : aSweepLineEvents)
+ handleSweepLineEvent(aSweepLineEvent, aActiveEdgeList, aPolygonPool, aRes);
+
+ return aRes;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/range/b2xrange.cxx b/basegfx/source/range/b2xrange.cxx
new file mode 100644
index 0000000000..3f04a95af5
--- /dev/null
+++ b/basegfx/source/range/b2xrange.cxx
@@ -0,0 +1,120 @@
+/* -*- 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 <basegfx/range/b2drange.hxx>
+#include <basegfx/range/b2irange.hxx>
+
+namespace basegfx
+{
+ namespace
+ {
+ /** Generic implementation of the difference set computation
+
+ @tpl RangeType
+ Type to operate on. Must provide ValueType and TraitsType
+ nested types.
+ */
+ template< class RangeType > void doComputeSetDifference(
+ std::vector< RangeType >& o_rRanges,
+ const RangeType& a,
+ const RangeType& b )
+ {
+ o_rRanges.clear();
+
+ // special-casing the empty rect case (this will fail most
+ // of the times below, because of the DBL_MIN/MAX special
+ // values denoting emptiness in the rectangle.
+ if( a.isEmpty() )
+ {
+ o_rRanges.push_back( b );
+ return;
+ }
+ if( b.isEmpty() )
+ {
+ o_rRanges.push_back( a );
+ return;
+ }
+
+ const typename RangeType::ValueType ax(a.getMinX());
+ const typename RangeType::ValueType ay(a.getMinY());
+ const typename RangeType::TraitsType::DifferenceType aw(a.getWidth());
+ const typename RangeType::TraitsType::DifferenceType ah(a.getHeight());
+ const typename RangeType::ValueType bx(b.getMinX());
+ const typename RangeType::ValueType by(b.getMinY());
+ const typename RangeType::TraitsType::DifferenceType bw(b.getWidth());
+ const typename RangeType::TraitsType::DifferenceType bh(b.getHeight());
+
+ const typename RangeType::TraitsType::DifferenceType h0( (by > ay) ? by - ay : 0 );
+ const typename RangeType::TraitsType::DifferenceType h3( (by + bh < ay + ah) ? ay + ah - by - bh : 0 );
+ const typename RangeType::TraitsType::DifferenceType w1( (bx > ax) ? bx - ax : 0 );
+ const typename RangeType::TraitsType::DifferenceType w2( (ax + aw > bx + bw) ? ax + aw - bx - bw : 0 );
+ const typename RangeType::TraitsType::DifferenceType h12( (h0 + h3 < ah) ? ah - h0 - h3 : 0 );
+
+ // TODO(E2): Use numeric_cast instead of static_cast here,
+ // need range checks!
+ if (h0 > 0)
+ o_rRanges.push_back(
+ RangeType(ax,ay,
+ static_cast<typename RangeType::ValueType>(ax+aw),
+ static_cast<typename RangeType::ValueType>(ay+h0)) );
+
+ if (w1 > 0 && h12 > 0)
+ o_rRanges.push_back(
+ RangeType(ax,
+ static_cast<typename RangeType::ValueType>(ay+h0),
+ static_cast<typename RangeType::ValueType>(ax+w1),
+ static_cast<typename RangeType::ValueType>(ay+h0+h12)) );
+
+ if (w2 > 0 && h12 > 0)
+ o_rRanges.push_back(
+ RangeType(static_cast<typename RangeType::ValueType>(bx+bw),
+ static_cast<typename RangeType::ValueType>(ay+h0),
+ static_cast<typename RangeType::ValueType>(bx+bw+w2),
+ static_cast<typename RangeType::ValueType>(ay+h0+h12)) );
+
+ if (h3 > 0)
+ o_rRanges.push_back(
+ RangeType(ax,
+ static_cast<typename RangeType::ValueType>(ay+h0+h12),
+ static_cast<typename RangeType::ValueType>(ax+aw),
+ static_cast<typename RangeType::ValueType>(ay+h0+h12+h3)) );
+ }
+ }
+
+ std::vector< B2IRange >& computeSetDifference( std::vector< B2IRange >& o_rResult,
+ const B2IRange& rFirst,
+ const B2IRange& rSecond )
+ {
+ doComputeSetDifference( o_rResult, rFirst, rSecond );
+
+ return o_rResult;
+ }
+
+ std::vector< B2DRange >& computeSetDifference( std::vector< B2DRange >& o_rResult,
+ const B2DRange& rFirst,
+ const B2DRange& rSecond )
+ {
+ doComputeSetDifference( o_rResult, rFirst, rSecond );
+
+ return o_rResult;
+ }
+
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/range/b3drange.cxx b/basegfx/source/range/b3drange.cxx
new file mode 100644
index 0000000000..89bb98a1e1
--- /dev/null
+++ b/basegfx/source/range/b3drange.cxx
@@ -0,0 +1,64 @@
+/* -*- 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 <basegfx/range/b3drange.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+
+namespace basegfx
+{
+ void B3DRange::transform(const B3DHomMatrix& rMatrix)
+ {
+ if(isEmpty() || rMatrix.isIdentity())
+ return;
+
+ const B3DRange aSource(*this);
+ reset();
+ expand(rMatrix * B3DPoint(aSource.getMinX(), aSource.getMinY(), aSource.getMinZ()));
+ expand(rMatrix * B3DPoint(aSource.getMaxX(), aSource.getMinY(), aSource.getMinZ()));
+ expand(rMatrix * B3DPoint(aSource.getMinX(), aSource.getMaxY(), aSource.getMinZ()));
+ expand(rMatrix * B3DPoint(aSource.getMaxX(), aSource.getMaxY(), aSource.getMinZ()));
+ expand(rMatrix * B3DPoint(aSource.getMinX(), aSource.getMinY(), aSource.getMaxZ()));
+ expand(rMatrix * B3DPoint(aSource.getMaxX(), aSource.getMinY(), aSource.getMaxZ()));
+ expand(rMatrix * B3DPoint(aSource.getMinX(), aSource.getMaxY(), aSource.getMaxZ()));
+ expand(rMatrix * B3DPoint(aSource.getMaxX(), aSource.getMaxY(), aSource.getMaxZ()));
+ }
+
+ B3DRange& B3DRange::operator*=( const ::basegfx::B3DHomMatrix& rMat )
+ {
+ transform(rMat);
+ return *this;
+ }
+
+ const B3DRange& B3DRange::getUnitB3DRange()
+ {
+ static const B3DRange aUnitB3DRange(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
+
+ return aUnitB3DRange;
+ }
+
+ B3DRange operator*( const ::basegfx::B3DHomMatrix& rMat, const B3DRange& rB3DRange )
+ {
+ B3DRange aRes( rB3DRange );
+ aRes *= rMat;
+ return aRes;
+ }
+
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/raster/rasterconvert3d.cxx b/basegfx/source/raster/rasterconvert3d.cxx
new file mode 100644
index 0000000000..e998eb9938
--- /dev/null
+++ b/basegfx/source/raster/rasterconvert3d.cxx
@@ -0,0 +1,337 @@
+/* -*- 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 <basegfx/raster/rasterconvert3d.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/polygon/b3dpolygon.hxx>
+#include <basegfx/polygon/b3dpolypolygon.hxx>
+#include <basegfx/point/b3dpoint.hxx>
+
+// implementations of the 3D raster converter
+
+namespace basegfx
+{
+ void RasterConverter3D::addArea(const B3DPolygon& rFill, const B3DHomMatrix* pViewToEye)
+ {
+ const sal_uInt32 nPointCount(rFill.count());
+
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ addEdge(rFill, a, (a + 1) % nPointCount, pViewToEye);
+ }
+ }
+
+ void RasterConverter3D::addArea(const B3DPolyPolygon& rFill, const B3DHomMatrix* pViewToEye)
+ {
+ const sal_uInt32 nPolyCount(rFill.count());
+
+ for(sal_uInt32 a(0); a < nPolyCount; a++)
+ {
+ addArea(rFill.getB3DPolygon(a), pViewToEye);
+ }
+ }
+
+ RasterConverter3D::RasterConverter3D()
+ {}
+
+ RasterConverter3D::~RasterConverter3D()
+ {}
+
+ void RasterConverter3D::rasterconvertB3DArea(sal_Int32 nStartLine, sal_Int32 nStopLine)
+ {
+ if(maLineEntries.empty())
+ return;
+
+ OSL_ENSURE(nStopLine >= nStartLine, "nStopLine is bigger than nStartLine (!)");
+
+ // sort global entries by Y, X once. After this, the vector
+ // is seen as frozen. Pointers to its entries will be used in the following code.
+ std::sort(maLineEntries.begin(), maLineEntries.end());
+
+ // local parameters
+ std::vector< RasterConversionLineEntry3D >::iterator aCurrentEntry(maLineEntries.begin());
+ std::vector< RasterConversionLineEntry3D* > aCurrentLine;
+ std::vector< RasterConversionLineEntry3D* > aNextLine;
+ std::vector< RasterConversionLineEntry3D* >::iterator aRasterConversionLineEntry3D;
+
+ // get scanlines first LineNumber as start
+ sal_Int32 nLineNumber(std::max(aCurrentEntry->getY(), nStartLine));
+
+ while((!aCurrentLine.empty() || aCurrentEntry != maLineEntries.end()) && (nLineNumber < nStopLine))
+ {
+ // add all entries which start at current line to current scanline
+ while(aCurrentEntry != maLineEntries.end())
+ {
+ const sal_Int32 nCurrentLineNumber(aCurrentEntry->getY());
+
+ if(nCurrentLineNumber > nLineNumber)
+ {
+ // line is below current one, done (since array is sorted)
+ break;
+ }
+ else
+ {
+ // less or equal. Line is above or at current one. Advance it exactly to
+ // current line
+ const sal_uInt32 nStep(nLineNumber - nCurrentLineNumber);
+
+ if(!nStep || aCurrentEntry->decrementRasterConversionLineEntry3D(nStep))
+ {
+ // add when exactly on current line or when increment to it did not
+ // completely consume it
+ if(nStep)
+ {
+ aCurrentEntry->incrementRasterConversionLineEntry3D(nStep, *this);
+ }
+
+ aCurrentLine.push_back(&(*aCurrentEntry));
+ }
+ }
+
+ ++aCurrentEntry;
+ }
+
+ // sort current scanline using comparator. Only X is used there
+ // since all entries are already in one processed line. This needs to be done
+ // every time since not only new spans may have benn added or old removed,
+ // but incrementing may also have changed the order
+ std::sort(aCurrentLine.begin(), aCurrentLine.end(), lineComparator());
+
+ // process current scanline
+ aRasterConversionLineEntry3D = aCurrentLine.begin();
+ aNextLine.clear();
+ sal_uInt32 nPairCount(0);
+
+ while(aRasterConversionLineEntry3D != aCurrentLine.end())
+ {
+ RasterConversionLineEntry3D& rPrevScanRasterConversionLineEntry3D(**aRasterConversionLineEntry3D++);
+
+ // look for 2nd span
+ if(aRasterConversionLineEntry3D != aCurrentLine.end())
+ {
+ // work on span from rPrevScanRasterConversionLineEntry3D to aRasterConversionLineEntry3D, fLineNumber is valid
+ processLineSpan(rPrevScanRasterConversionLineEntry3D, **aRasterConversionLineEntry3D, nLineNumber, nPairCount++);
+ }
+
+ // increment to next line
+ if(rPrevScanRasterConversionLineEntry3D.decrementRasterConversionLineEntry3D(1))
+ {
+ rPrevScanRasterConversionLineEntry3D.incrementRasterConversionLineEntry3D(1, *this);
+ aNextLine.push_back(&rPrevScanRasterConversionLineEntry3D);
+ }
+ }
+
+ // copy back next scanline if count has changed
+ if(aNextLine.size() != aCurrentLine.size())
+ {
+ aCurrentLine = aNextLine;
+ }
+
+ // increment fLineNumber
+ nLineNumber++;
+ }
+ }
+
+ void RasterConverter3D::addEdge(const B3DPolygon& rFill, sal_uInt32 a, sal_uInt32 b, const B3DHomMatrix* pViewToEye)
+ {
+ B3DPoint aStart(rFill.getB3DPoint(a));
+ B3DPoint aEnd(rFill.getB3DPoint(b));
+ sal_Int32 nYStart(fround(aStart.getY()));
+ sal_Int32 nYEnd(fround(aEnd.getY()));
+
+ if(nYStart == nYEnd)
+ return;
+
+ if(nYStart > nYEnd)
+ {
+ std::swap(aStart, aEnd);
+ std::swap(nYStart, nYEnd);
+ std::swap(a, b);
+ }
+
+ const sal_uInt32 nYDelta(nYEnd - nYStart);
+ const double fInvYDelta(1.0 / nYDelta);
+ maLineEntries.emplace_back(
+ aStart.getX(), (aEnd.getX() - aStart.getX()) * fInvYDelta,
+ aStart.getZ(), (aEnd.getZ() - aStart.getZ()) * fInvYDelta,
+ nYStart, nYDelta);
+
+ // if extra interpolation data is used, add it to the last created entry
+ RasterConversionLineEntry3D& rEntry = maLineEntries[maLineEntries.size() - 1];
+
+ if(rFill.areBColorsUsed())
+ {
+ rEntry.setColorIndex(addColorInterpolator(rFill.getBColor(a), rFill.getBColor(b), fInvYDelta));
+ }
+
+ if(rFill.areNormalsUsed())
+ {
+ rEntry.setNormalIndex(addNormalInterpolator(rFill.getNormal(a), rFill.getNormal(b), fInvYDelta));
+ }
+
+ if(!rFill.areTextureCoordinatesUsed())
+ return;
+
+ if(pViewToEye)
+ {
+ const double fEyeA(((*pViewToEye) * aStart).getZ());
+ const double fEyeB(((*pViewToEye) * aEnd).getZ());
+
+ rEntry.setInverseTextureIndex(addInverseTextureInterpolator(
+ rFill.getTextureCoordinate(a),
+ rFill.getTextureCoordinate(b),
+ fEyeA, fEyeB, fInvYDelta));
+ }
+ else
+ {
+ rEntry.setTextureIndex(addTextureInterpolator(
+ rFill.getTextureCoordinate(a),
+ rFill.getTextureCoordinate(b),
+ fInvYDelta));
+ }
+ }
+
+ void RasterConverter3D::rasterconvertB3DEdge(const B3DPolygon& rLine, sal_uInt32 nA, sal_uInt32 nB, sal_Int32 nStartLine, sal_Int32 nStopLine, sal_uInt16 nLineWidth)
+ {
+ B3DPoint aStart(rLine.getB3DPoint(nA));
+ B3DPoint aEnd(rLine.getB3DPoint(nB));
+ const double fZBufferLineAdd(0x00ff);
+
+ if(nLineWidth > 1)
+ {
+ // this is not a hairline anymore, in most cases since it's an oversampled
+ // hairline to get e.g. AA for Z-Buffering. Create fill geometry.
+ if(!aStart.equal(aEnd))
+ {
+ reset();
+ maLineEntries.clear();
+
+ B2DVector aVector(aEnd.getX() - aStart.getX(), aEnd.getY() - aStart.getY());
+ aVector.normalize();
+ const B2DVector aPerpend(getPerpendicular(aVector) * ((static_cast<double>(nLineWidth) + 0.5) * 0.5));
+ const double fZStartWithAdd(aStart.getZ() + fZBufferLineAdd);
+ const double fZEndWithAdd(aEnd.getZ() + fZBufferLineAdd);
+
+ B3DPolygon aPolygon;
+ aPolygon.append(B3DPoint(aStart.getX() + aPerpend.getX(), aStart.getY() + aPerpend.getY(), fZStartWithAdd));
+ aPolygon.append(B3DPoint(aEnd.getX() + aPerpend.getX(), aEnd.getY() + aPerpend.getY(), fZEndWithAdd));
+ aPolygon.append(B3DPoint(aEnd.getX() - aPerpend.getX(), aEnd.getY() - aPerpend.getY(), fZEndWithAdd));
+ aPolygon.append(B3DPoint(aStart.getX() - aPerpend.getX(), aStart.getY() - aPerpend.getY(), fZStartWithAdd));
+ aPolygon.setClosed(true);
+
+ addArea(aPolygon, nullptr);
+ }
+ }
+ else
+ {
+ // it's a hairline. Use direct RasterConversionLineEntry creation to
+ // rasterconvert lines as similar to areas as possible to avoid Z-Fighting
+ sal_Int32 nYStart(fround(aStart.getY()));
+ sal_Int32 nYEnd(fround(aEnd.getY()));
+
+ if(nYStart == nYEnd)
+ {
+ // horizontal line, check X
+ const sal_Int32 nXStart(static_cast<sal_Int32>(aStart.getX()));
+ const sal_Int32 nXEnd(static_cast<sal_Int32>(aEnd.getX()));
+
+ if(nXStart != nXEnd)
+ {
+ reset();
+ maLineEntries.clear();
+
+ // horizontal line, create vertical entries. These will be sorted by
+ // X anyways, so no need to distinguish the case here
+ maLineEntries.emplace_back(
+ aStart.getX(), 0.0,
+ aStart.getZ() + fZBufferLineAdd, 0.0,
+ nYStart, 1);
+ maLineEntries.emplace_back(
+ aEnd.getX(), 0.0,
+ aEnd.getZ() + fZBufferLineAdd, 0.0,
+ nYStart, 1);
+ }
+ }
+ else
+ {
+ reset();
+ maLineEntries.clear();
+
+ if(nYStart > nYEnd)
+ {
+ std::swap(aStart, aEnd);
+ std::swap(nYStart, nYEnd);
+ }
+
+ const sal_uInt32 nYDelta(static_cast<sal_uInt32>(nYEnd - nYStart));
+ const double fInvYDelta(1.0 / nYDelta);
+
+ // non-horizontal line, create two parallel entries. These will be sorted by
+ // X anyways, so no need to distinguish the case here
+ maLineEntries.emplace_back(
+ aStart.getX(), (aEnd.getX() - aStart.getX()) * fInvYDelta,
+ aStart.getZ() + fZBufferLineAdd, (aEnd.getZ() - aStart.getZ()) * fInvYDelta,
+ nYStart, nYDelta);
+
+ RasterConversionLineEntry3D& rEntry = maLineEntries[maLineEntries.size() - 1];
+
+ // need to choose a X-Distance for the 2nd edge which guarantees all pixels
+ // of the line to be set. This is exactly the X-Increment for one Y-Step.
+ // Same is true for Z, so in both cases, add one increment to them. To also
+ // guarantee one pixel per line, add a minimum of one for X.
+ const double fDistanceX(fabs(rEntry.getX().getInc()) >= 1.0 ? rEntry.getX().getInc() : 1.0);
+
+ maLineEntries.emplace_back(
+ rEntry.getX().getVal() + fDistanceX, rEntry.getX().getInc(),
+ rEntry.getZ().getVal() + rEntry.getZ().getInc(), rEntry.getZ().getInc(),
+ nYStart, nYDelta);
+ }
+ }
+
+ if(!maLineEntries.empty())
+ {
+ rasterconvertB3DArea(nStartLine, nStopLine);
+ }
+ }
+
+ void RasterConverter3D::rasterconvertB3DPolyPolygon(const B3DPolyPolygon& rFill, const B3DHomMatrix* pViewToEye, sal_Int32 nStartLine, sal_Int32 nStopLine)
+ {
+ reset();
+ maLineEntries.clear();
+ addArea(rFill, pViewToEye);
+ rasterconvertB3DArea(nStartLine, nStopLine);
+ }
+
+ void RasterConverter3D::rasterconvertB3DPolygon(const B3DPolygon& rLine, sal_Int32 nStartLine, sal_Int32 nStopLine, sal_uInt16 nLineWidth)
+ {
+ const sal_uInt32 nPointCount(rLine.count());
+
+ if(nPointCount)
+ {
+ const sal_uInt32 nEdgeCount(rLine.isClosed() ? nPointCount : nPointCount - 1);
+
+ for(sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ rasterconvertB3DEdge(rLine, a, (a + 1) % nPointCount, nStartLine, nStopLine, nLineWidth);
+ }
+ }
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/b2dclipstate.cxx b/basegfx/source/tools/b2dclipstate.cxx
new file mode 100644
index 0000000000..126235699b
--- /dev/null
+++ b/basegfx/source/tools/b2dclipstate.cxx
@@ -0,0 +1,476 @@
+/* -*- 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 <basegfx/utils/b2dclipstate.hxx>
+
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/range/b2dpolyrange.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>
+#include <utility>
+
+
+namespace basegfx::utils
+{
+ class ImplB2DClipState
+ {
+ public:
+ enum Operation {UNION, INTERSECT, XOR, SUBTRACT};
+
+ ImplB2DClipState() :
+ mePendingOps(UNION)
+ {}
+
+ explicit ImplB2DClipState( B2DPolyPolygon aPoly ) :
+ maClipPoly(std::move(aPoly)),
+ mePendingOps(UNION)
+ {}
+
+ bool isCleared() const
+ {
+ return !maClipPoly.count()
+ && !maPendingPolygons.count()
+ && !maPendingRanges.count();
+ }
+
+ 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,
+ B2VectorOrientation::Positive);
+ }
+
+ void addPolyPolygon(const B2DPolyPolygon& aPoly, Operation eOp)
+ {
+ commitPendingRanges();
+ if( mePendingOps != eOp )
+ commitPendingPolygons();
+
+ mePendingOps = eOp;
+ maPendingPolygons.append(aPoly);
+ }
+
+ void unionRange(const B2DRange& rRange)
+ {
+ if( isCleared() )
+ return;
+
+ addRange(rRange,UNION);
+ }
+
+ void unionPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ if( isCleared() )
+ return;
+
+ addPolyPolygon(rPolyPoly,UNION);
+ }
+
+ void intersectRange(const B2DRange& rRange)
+ {
+ if( isNull() )
+ return;
+
+ addRange(rRange,INTERSECT);
+ }
+
+ void intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ if( isNull() )
+ return;
+
+ addPolyPolygon(rPolyPoly,INTERSECT);
+ }
+
+ void subtractRange(const B2DRange& rRange )
+ {
+ if( isNull() )
+ return;
+
+ addRange(rRange,SUBTRACT);
+ }
+
+ void subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ if( isNull() )
+ return;
+
+ addPolyPolygon(rPolyPoly,SUBTRACT);
+ }
+
+ void xorRange(const B2DRange& rRange)
+ {
+ addRange(rRange,XOR);
+ }
+
+ void xorPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ addPolyPolygon(rPolyPoly,XOR);
+ }
+
+ void transform(const basegfx::B2DHomMatrix& rTranslate)
+ {
+ maPendingRanges.transform(rTranslate);
+ maPendingPolygons.transform(rTranslate);
+ maClipPoly.transform(rTranslate);
+ }
+
+ B2DPolyPolygon const & 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 = utils::prepareForPolygonOperation(maPendingPolygons);
+ const bool bIsEmpty=isNullClipPoly();
+ const bool bIsCleared=!maClipPoly.count();
+ switch(mePendingOps)
+ {
+ case UNION:
+ assert( !bIsCleared );
+
+ if( bIsEmpty )
+ maClipPoly = maPendingPolygons;
+ else
+ maClipPoly = utils::solvePolygonOperationOr(
+ maClipPoly,
+ maPendingPolygons);
+ break;
+ case INTERSECT:
+ assert( !bIsEmpty );
+
+ if( bIsCleared )
+ maClipPoly = maPendingPolygons;
+ else
+ maClipPoly = utils::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 = utils::solvePolygonOperationXor(
+ B2DPolyPolygon(
+ utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
+ maPendingPolygons);
+ }
+ else
+ maClipPoly = utils::solvePolygonOperationXor(
+ maClipPoly,
+ maPendingPolygons);
+ break;
+ case SUBTRACT:
+ assert( !bIsEmpty );
+
+ // first union all pending ones, subtract en bloc then
+ maPendingPolygons = solveCrossovers(maPendingPolygons);
+ maPendingPolygons = stripNeutralPolygons(maPendingPolygons);
+ maPendingPolygons = stripDispensablePolygons(maPendingPolygons);
+
+ 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 = utils::solvePolygonOperationDiff(
+ B2DPolyPolygon(
+ utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
+ maPendingPolygons);
+ }
+ else
+ maClipPoly = utils::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:
+ assert( !bIsCleared );
+
+ aCollectedRanges = maPendingRanges.solveCrossovers();
+ aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
+ aCollectedRanges = stripDispensablePolygons(aCollectedRanges);
+ if( bIsEmpty )
+ maClipPoly = aCollectedRanges;
+ else
+ maClipPoly = utils::solvePolygonOperationOr(
+ maClipPoly,
+ aCollectedRanges);
+ break;
+ case INTERSECT:
+ assert( !bIsEmpty );
+
+ aCollectedRanges = maPendingRanges.solveCrossovers();
+ aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
+ if( maPendingRanges.count() > 1 )
+ aCollectedRanges = stripDispensablePolygons(aCollectedRanges, true);
+
+ if( bIsCleared )
+ maClipPoly = aCollectedRanges;
+ else
+ maClipPoly = utils::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 = utils::solvePolygonOperationXor(
+ B2DPolyPolygon(
+ utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
+ aCollectedRanges);
+ }
+ else
+ maClipPoly = utils::solvePolygonOperationXor(
+ maClipPoly,
+ aCollectedRanges);
+ break;
+ case SUBTRACT:
+ assert( !bIsEmpty );
+
+ // first union all pending ranges, subtract en bloc then
+ aCollectedRanges = maPendingRanges.solveCrossovers();
+ aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
+ aCollectedRanges = stripDispensablePolygons(aCollectedRanges);
+
+ 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 = utils::solvePolygonOperationDiff(
+ B2DPolyPolygon(
+ utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
+ aCollectedRanges);
+ }
+ else
+ maClipPoly = utils::solvePolygonOperationDiff(
+ maClipPoly,
+ aCollectedRanges);
+ break;
+ }
+
+ maPendingRanges.clear();
+ mePendingOps = UNION;
+ }
+
+ mutable B2DPolyPolygon maPendingPolygons;
+ mutable B2DPolyRange maPendingRanges;
+ mutable B2DPolyPolygon maClipPoly;
+ mutable Operation mePendingOps;
+ };
+
+ B2DClipState::B2DClipState() = default;
+
+ B2DClipState::~B2DClipState() = default;
+
+ B2DClipState::B2DClipState( const B2DClipState& ) = default;
+
+ B2DClipState::B2DClipState( B2DClipState&& ) = default;
+
+ B2DClipState::B2DClipState( const B2DPolyPolygon& rPolyPoly ) :
+ mpImpl( ImplB2DClipState(rPolyPoly) )
+ {}
+
+ B2DClipState& B2DClipState::operator=( const B2DClipState& ) = default;
+
+ B2DClipState& B2DClipState::operator=( B2DClipState&& ) = default;
+
+ void B2DClipState::makeNull()
+ {
+ mpImpl->makeNull();
+ }
+
+ 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::unionPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ mpImpl->unionPolyPolygon(rPolyPoly);
+ }
+
+ void B2DClipState::intersectRange(const B2DRange& rRange)
+ {
+ mpImpl->intersectRange(rRange);
+ }
+
+ void B2DClipState::intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ mpImpl->intersectPolyPolygon(rPolyPoly);
+ }
+
+ void B2DClipState::subtractRange(const B2DRange& rRange)
+ {
+ mpImpl->subtractRange(rRange);
+ }
+
+ void B2DClipState::subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ mpImpl->subtractPolyPolygon(rPolyPoly);
+ }
+
+ void B2DClipState::xorRange(const B2DRange& rRange)
+ {
+ mpImpl->xorRange(rRange);
+ }
+
+ void B2DClipState::xorPolyPolygon(const B2DPolyPolygon& rPolyPoly)
+ {
+ mpImpl->xorPolyPolygon(rPolyPoly);
+ }
+
+ B2DPolyPolygon const & B2DClipState::getClipPoly() const
+ {
+ return mpImpl->getClipPoly();
+ }
+
+ void B2DClipState::transform(const basegfx::B2DHomMatrix& rTranslate)
+ {
+ return mpImpl->transform(rTranslate);
+ }
+
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/bgradient.cxx b/basegfx/source/tools/bgradient.cxx
new file mode 100644
index 0000000000..86e1812d21
--- /dev/null
+++ b/basegfx/source/tools/bgradient.cxx
@@ -0,0 +1,997 @@
+/* -*- 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/.
+ */
+
+#include <basegfx/utils/bgradient.hxx>
+#include <basegfx/utils/gradienttools.hxx>
+#include <com/sun/star/awt/Gradient2.hpp>
+#include <boost/property_tree/json_parser.hpp>
+#include <map>
+
+typedef std::map<OUString, OUString> StringMap;
+
+namespace
+{
+css::awt::GradientStyle lcl_getStyleFromString(std::u16string_view rStyle)
+{
+ if (rStyle == u"LINEAR")
+ return css::awt::GradientStyle_LINEAR;
+ else if (rStyle == u"AXIAL")
+ return css::awt::GradientStyle_AXIAL;
+ else if (rStyle == u"RADIAL")
+ return css::awt::GradientStyle_RADIAL;
+ else if (rStyle == u"ELLIPTICAL")
+ return css::awt::GradientStyle_ELLIPTICAL;
+ else if (rStyle == u"SQUARE")
+ return css::awt::GradientStyle_SQUARE;
+ else if (rStyle == u"RECT")
+ return css::awt::GradientStyle_RECT;
+
+ return css::awt::GradientStyle_LINEAR;
+}
+
+StringMap lcl_jsonToStringMap(std::u16string_view rJSON)
+{
+ StringMap aArgs;
+ if (rJSON.size() && rJSON[0] != '\0')
+ {
+ std::stringstream aStream(std::string(OUStringToOString(rJSON, RTL_TEXTENCODING_ASCII_US)));
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+
+ for (const auto& rPair : aTree)
+ {
+ aArgs[OUString::fromUtf8(rPair.first)]
+ = OUString::fromUtf8(rPair.second.get_value<std::string>("."));
+ }
+ }
+ return aArgs;
+}
+
+basegfx::BGradient lcl_buildGradientFromStringMap(StringMap& rMap)
+{
+ basegfx::BGradient aGradient(
+ basegfx::BColorStops(ColorToBColorConverter(rMap["startcolor"].toInt32(16)).getBColor(),
+ ColorToBColorConverter(rMap["endcolor"].toInt32(16)).getBColor()));
+
+ aGradient.SetGradientStyle(lcl_getStyleFromString(rMap["style"]));
+ aGradient.SetAngle(Degree10(rMap["angle"].toInt32()));
+
+ return aGradient;
+}
+}
+
+namespace basegfx
+{
+// constructor with two colors to explicitly create a
+// BColorStops for a single StartColor @0.0 & EndColor @1.0
+BColorStops::BColorStops(const BColor& rStart, const BColor& rEnd)
+{
+ emplace_back(0.0, rStart);
+ emplace_back(1.0, rEnd);
+}
+
+/* Helper to grep the correct ColorStop out of
+ ColorStops and interpolate as needed for given
+ relative value in fPosition in the range of [0.0 .. 1.0].
+ It also takes care of evtl. given RequestedSteps.
+ */
+BColor BColorStops::getInterpolatedBColor(double fPosition, sal_uInt32 nRequestedSteps,
+ BColorStopRange& rLastColorStopRange) const
+{
+ // no color at all, done
+ if (empty())
+ return BColor();
+
+ // outside range -> at start
+ const double fMin(front().getStopOffset());
+ if (fPosition < fMin)
+ return front().getStopColor();
+
+ // outside range -> at end
+ const double fMax(back().getStopOffset());
+ if (fPosition > fMax)
+ return back().getStopColor();
+
+ // special case for the 'classic' case with just two colors:
+ // we can optimize that and keep the speed/resources low
+ // by avoiding some calculations and an O(log(N)) array access
+ if (2 == size())
+ {
+ // if same StopOffset use front color
+ if (fTools::equal(fMin, fMax))
+ return front().getStopColor();
+
+ const basegfx::BColor aCStart(front().getStopColor());
+ const basegfx::BColor aCEnd(back().getStopColor());
+
+ // if colors are equal just return one
+ if (aCStart == aCEnd)
+ return aCStart;
+
+ // calculate Steps
+ const sal_uInt32 nSteps(
+ basegfx::utils::calculateNumberOfSteps(nRequestedSteps, aCStart, aCEnd));
+
+ // we need to extend the interpolation to the local
+ // range of ColorStops. Despite having two ColorStops
+ // these are not necessarily at 0.0 and 1.0, so may be
+ // not the classical Start/EndColor (what is allowed)
+ fPosition = (fPosition - fMin) / (fMax - fMin);
+ return basegfx::interpolate(aCStart, aCEnd,
+ nSteps > 1 ? floor(fPosition * nSteps) / double(nSteps - 1)
+ : fPosition);
+ }
+
+ // check if we need to newly populate the needed interpolation data
+ // or if we can re-use from last time.
+ // If this scope is not entered, we do not need the binary search. It's
+ // only a single buffered entry, and only used when more than three
+ // ColorStops exist, but makes a huge difference compared with accessing
+ // the sorted ColorStop vector each time.
+ // NOTE: with this simple change I get very high hit rates, e.g. rotating
+ // a donut with gradient test '1' hit rate is at 0.99909440357755486
+ if (rLastColorStopRange.mfOffsetStart == rLastColorStopRange.mfOffsetEnd
+ || fPosition < rLastColorStopRange.mfOffsetStart
+ || fPosition > rLastColorStopRange.mfOffsetEnd)
+ {
+ // access needed spot in sorted array using binary search
+ // NOTE: This *seems* slow(er) when developing compared to just
+ // looping/accessing, but that's just due to the extensive
+ // debug test code created by the stl. In a pro version,
+ // all is good/fast as expected
+ const auto upperBound(std::upper_bound(begin(), end(), BColorStop(fPosition),
+ [](const BColorStop& x, const BColorStop& y) {
+ return x.getStopOffset() < y.getStopOffset();
+ }));
+
+ // no upper bound, done
+ if (end() == upperBound)
+ return back().getStopColor();
+
+ // lower bound is one entry back, access that
+ const auto lowerBound(upperBound - 1);
+
+ // no lower bound, done
+ if (end() == lowerBound)
+ return back().getStopColor();
+
+ // we have lower and upper bound, get colors and offsets
+ rLastColorStopRange.maColorStart = lowerBound->getStopColor();
+ rLastColorStopRange.maColorEnd = upperBound->getStopColor();
+ rLastColorStopRange.mfOffsetStart = lowerBound->getStopOffset();
+ rLastColorStopRange.mfOffsetEnd = upperBound->getStopOffset();
+ }
+
+ // when there are just two color steps this cannot happen, but when using
+ // a range of colors this *may* be used inside the range to represent
+ // single-colored regions inside a ColorRange. Use that color & done
+ if (rLastColorStopRange.maColorStart == rLastColorStopRange.maColorEnd)
+ return rLastColorStopRange.maColorStart;
+
+ // calculate number of steps and adapted proportional
+ // range for scaler in [0.0 .. 1.0]
+ const double fAdaptedScaler(
+ (fPosition - rLastColorStopRange.mfOffsetStart)
+ / (rLastColorStopRange.mfOffsetEnd - rLastColorStopRange.mfOffsetStart));
+ const sal_uInt32 nSteps(basegfx::utils::calculateNumberOfSteps(
+ nRequestedSteps, rLastColorStopRange.maColorStart, rLastColorStopRange.maColorEnd));
+
+ // interpolate & evtl. apply steps
+ return interpolate(rLastColorStopRange.maColorStart, rLastColorStopRange.maColorEnd,
+ nSteps > 1 ? floor(fAdaptedScaler * nSteps) / double(nSteps - 1)
+ : fAdaptedScaler);
+}
+
+/* Tooling method that allows to replace the StartColor in a
+ vector of ColorStops. A vector in 'ordered state' is expected,
+ so you may use/have used sortAndCorrect.
+ This method is for convenience & backwards compatibility, please
+ think about handling multi-colored gradients directly.
+ */
+void BColorStops::replaceStartColor(const BColor& rStart)
+{
+ BColorStops::iterator a1stNonStartColor(begin());
+
+ // search for highest existing non-StartColor - CAUTION,
+ // there might be none, one or multiple with StopOffset 0.0
+ while (a1stNonStartColor != end()
+ && basegfx::fTools::lessOrEqual(a1stNonStartColor->getStopOffset(), 0.0))
+ a1stNonStartColor++;
+
+ // create new ColorStops by 1st adding new one and then all
+ // non-StartColor entries
+ BColorStops aNewColorStops;
+
+ aNewColorStops.reserve(size() + 1);
+ aNewColorStops.emplace_back(0.0, rStart);
+ aNewColorStops.insert(aNewColorStops.end(), a1stNonStartColor, end());
+
+ // assign & done
+ *this = aNewColorStops;
+}
+
+/* Tooling method that allows to replace the EndColor in a
+ vector of ColorStops. A vector in 'ordered state' is expected,
+ so you may use/have used sortAndCorrectColorStops.
+ This method is for convenience & backwards compatibility, please
+ think about handling multi-colored gradients directly.
+ */
+void BColorStops::replaceEndColor(const BColor& rEnd)
+{
+ // erase all evtl. existing EndColor(s)
+ while (!empty() && basegfx::fTools::moreOrEqual(back().getStopOffset(), 1.0))
+ pop_back();
+
+ // add at the end of existing ColorStops
+ emplace_back(1.0, rEnd);
+}
+
+/* Tooling method to linearly blend the Colors contained in
+ a given ColorStop vector against a given Color using the
+ given intensity values.
+ The intensity values fStartIntensity, fEndIntensity are
+ in the range of [0.0 .. 1.0] and describe how much the
+ blend is supposed to be done at the start color position
+ and the end color position respectively, where 0.0 means
+ to fully use the given BlendColor, 1.0 means to not change
+ the existing color in the ColorStop.
+ Every color entry in the given ColorStop is blended
+ relative to it's StopPosition, interpolating the
+ given intensities with the range [0.0 .. 1.0] to do so.
+ */
+void BColorStops::blendToIntensity(double fStartIntensity, double fEndIntensity,
+ const BColor& rBlendColor)
+{
+ // no entries, done
+ if (empty())
+ return;
+
+ // correct intensities (maybe assert when input was wrong)
+ fStartIntensity = std::max(std::min(1.0, fStartIntensity), 0.0);
+ fEndIntensity = std::max(std::min(1.0, fEndIntensity), 0.0);
+
+ // all 100%, no real blend, done
+ if (basegfx::fTools::equal(fStartIntensity, 1.0) && basegfx::fTools::equal(fEndIntensity, 1.0))
+ return;
+
+ // blend relative to StopOffset position
+ for (auto& candidate : *this)
+ {
+ const double fOffset(candidate.getStopOffset());
+ const double fIntensity((fStartIntensity * (1.0 - fOffset)) + (fEndIntensity * fOffset));
+ candidate = basegfx::BColorStop(
+ fOffset, basegfx::interpolate(rBlendColor, candidate.getStopColor(), fIntensity));
+ }
+}
+
+/* Tooling method to guarantee sort and correctness for
+ the given ColorStops vector.
+ A vector fulfilling these conditions is called to be
+ in 'ordered state'.
+
+ At return, the following conditions are guaranteed:
+ - contains no ColorStops with offset < 0.0 (will
+ be removed)
+ - contains no ColorStops with offset > 1.0 (will
+ be removed)
+ - ColorStops with identical offsets are now allowed
+ - will be sorted from lowest offset to highest
+
+ Some more notes:
+ - It can happen that the result is empty
+ - It is allowed to have consecutive entries with
+ the same color, this represents single-color
+ regions inside the gradient
+ - A entry with 0.0 is not required or forced, so
+ no 'StartColor' is technically required
+ - A entry with 1.0 is not required or forced, so
+ no 'EndColor' is technically required
+
+ All this is done in one run (sort + O(N)) without
+ creating a copy of the data in any form
+ */
+void BColorStops::sortAndCorrect()
+{
+ // no content, we are done
+ if (empty())
+ return;
+
+ if (1 == size())
+ {
+ // no gradient at all, but preserve given color
+ // evtl. correct offset to be in valid range [0.0 .. 1.0]
+ // NOTE: This does not move it to 0.0 or 1.0, it *can* still
+ // be somewhere in-between what is allowed
+ const BColorStop aEntry(front());
+ clear();
+ emplace_back(std::max(0.0, std::min(1.0, aEntry.getStopOffset())), aEntry.getStopColor());
+
+ // done
+ return;
+ }
+
+ // start with sorting the input data. Remember that
+ // this preserves the order of equal entries, where
+ // equal is defined here by offset (see use operator==)
+ std::sort(begin(), end());
+
+ // prepare status values
+ size_t write(0);
+
+ // use the paradigm of a band machine with two heads, read
+ // and write with write <= read all the time. Step over the
+ // data using read and check for valid entry. If valid, decide
+ // how to keep it
+ for (size_t read(0); read < size(); read++)
+ {
+ // get offset of entry at read position
+ double fOff((*this)[read].getStopOffset());
+
+ if (basegfx::fTools::less(fOff, 0.0) && read + 1 < size())
+ {
+ // value < 0.0 and we have a next entry. check for gradient snippet
+ // containing 0.0 resp. StartColor
+ const double fOff2((*this)[read + 1].getStopOffset());
+
+ if (basegfx::fTools::more(fOff2, 0.0))
+ {
+ // read is the start of a gradient snippet containing 0.0. Correct
+ // entry to StartColor, interpolate to correct StartColor
+ (*this)[read]
+ = BColorStop(0.0, basegfx::interpolate((*this)[read].getStopColor(),
+ (*this)[read + 1].getStopColor(),
+ (0.0 - fOff) / (fOff2 - fOff)));
+
+ // adapt fOff
+ fOff = 0.0;
+ }
+ }
+
+ // step over < 0 values, these are outside and will be removed
+ if (basegfx::fTools::less(fOff, 0.0))
+ {
+ continue;
+ }
+
+ if (basegfx::fTools::less(fOff, 1.0) && read + 1 < size())
+ {
+ // value < 1.0 and we have a next entry. check for gradient snippet
+ // containing 1.0 resp. EndColor
+ const double fOff2((*this)[read + 1].getStopOffset());
+
+ if (basegfx::fTools::more(fOff2, 1.0))
+ {
+ // read is the start of a gradient snippet containing 1.0. Correct
+ // next entry to EndColor, interpolate to correct EndColor
+ (*this)[read + 1]
+ = BColorStop(1.0, basegfx::interpolate((*this)[read].getStopColor(),
+ (*this)[read + 1].getStopColor(),
+ (1.0 - fOff) / (fOff2 - fOff)));
+
+ // adapt fOff
+ fOff = 1.0;
+ }
+ }
+
+ // step over > 1 values; even break, since all following
+ // entries will also be bigger due to being sorted, so done
+ if (basegfx::fTools::more(fOff, 1.0))
+ {
+ break;
+ }
+
+ // entry is valid value at read position
+ // copy if write target is empty (write at start) or when
+ // write target is different to read in color or offset
+ if (0 == write || !((*this)[read] == (*this)[write - 1]))
+ {
+ if (write != read)
+ {
+ // copy read to write backwards to close gaps
+ (*this)[write] = (*this)[read];
+ }
+
+ // always forward write position
+ write++;
+ }
+ }
+
+ // correct size when length is reduced. write is always at
+ // last used position + 1
+ if (size() > write)
+ {
+ if (0 == write)
+ {
+ // no valid entries at all, but not empty. This can only happen
+ // when all entries are below 0.0 or above 1.0 (else a gradient
+ // snippet spawning over both would have been detected)
+ if (basegfx::fTools::less(back().getStopOffset(), 0.0))
+ {
+ // all outside too low, rescue last due to being closest to content
+ const BColor aBackColor(back().getStopColor());
+ clear();
+ emplace_back(0.0, aBackColor);
+ }
+ else // if (basegfx::fTools::more(front().getStopOffset(), 1.0))
+ {
+ // all outside too high, rescue first due to being closest to content
+ const BColor aFrontColor(front().getStopColor());
+ clear();
+ emplace_back(1.0, aFrontColor);
+ }
+ }
+ else
+ {
+ resize(write);
+ }
+ }
+}
+
+bool BColorStops::checkPenultimate() const
+{
+ // not needed when no ColorStops
+ if (empty())
+ return false;
+
+ // not needed when last ColorStop at the end or outside
+ if (basegfx::fTools::moreOrEqual(back().getStopOffset(), 1.0))
+ return false;
+
+ // get penultimate entry
+ const auto penultimate(rbegin() + 1);
+
+ // if there is none, we need no correction and are done
+ if (penultimate == rend())
+ return false;
+
+ // not needed when the last two ColorStops have different offset, then
+ // a visible range will be processed already
+ if (!basegfx::fTools::equal(back().getStopOffset(), penultimate->getStopOffset()))
+ return false;
+
+ // not needed when the last two ColorStops have the same Color, then the
+ // range before solves the problem
+ if (back().getStopColor() == penultimate->getStopColor())
+ return false;
+
+ return true;
+}
+
+/* Tooling method to check if a ColorStop vector is defined
+ by a single color. It returns true if this is the case.
+ If true is returned, rSingleColor contains that single
+ color for convenience.
+ NOTE: If no ColorStop is defined, a fallback to BColor-default
+ (which is black) and true will be returned
+ */
+bool BColorStops::isSingleColor(BColor& rSingleColor) const
+{
+ if (empty())
+ {
+ rSingleColor = BColor();
+ return true;
+ }
+
+ if (1 == size())
+ {
+ rSingleColor = front().getStopColor();
+ return true;
+ }
+
+ rSingleColor = front().getStopColor();
+
+ for (auto const& rCandidate : *this)
+ {
+ if (rCandidate.getStopColor() != rSingleColor)
+ return false;
+ }
+
+ return true;
+}
+
+/* Tooling method to reverse ColorStops, including offsets.
+ When also mirroring offsets a valid sort keeps valid.
+ */
+void BColorStops::reverseColorStops()
+{
+ // can use std::reverse, but also need to adapt offset(s)
+ std::reverse(begin(), end());
+ for (auto& candidate : *this)
+ candidate = BColorStop(1.0 - candidate.getStopOffset(), candidate.getStopColor());
+}
+
+// createSpaceAtStart creates fOffset space at start by
+// translating/scaling all entries to the right
+void BColorStops::createSpaceAtStart(double fOffset)
+{
+ // nothing to do if empty
+ if (empty())
+ return;
+
+ // correct offset to [0.0 .. 1.0]
+ fOffset = std::max(std::min(1.0, fOffset), 0.0);
+
+ // nothing to do if 0.0 == offset
+ if (basegfx::fTools::equalZero(fOffset))
+ return;
+
+ BColorStops aNewStops;
+
+ for (const auto& candidate : *this)
+ {
+ aNewStops.emplace_back(fOffset + (candidate.getStopOffset() * (1.0 - fOffset)),
+ candidate.getStopColor());
+ }
+
+ *this = aNewStops;
+}
+
+// removeSpaceAtStart removes fOffset space from start by
+// translating/scaling entries more or equal to fOffset
+// to the left. Entries less than fOffset will be removed
+void BColorStops::removeSpaceAtStart(double fOffset)
+{
+ // nothing to do if empty
+ if (empty())
+ return;
+
+ // correct factor to [0.0 .. 1.0]
+ fOffset = std::max(std::min(1.0, fOffset), 0.0);
+
+ // nothing to do if fOffset == 0.0
+ if (basegfx::fTools::equalZero(fOffset))
+ return;
+
+ BColorStops aNewStops;
+ const double fMul(basegfx::fTools::equal(fOffset, 1.0) ? 1.0 : 1.0 / (1.0 - fOffset));
+
+ for (const auto& candidate : *this)
+ {
+ if (basegfx::fTools::moreOrEqual(candidate.getStopOffset(), fOffset))
+ {
+ aNewStops.emplace_back((candidate.getStopOffset() - fOffset) * fMul,
+ candidate.getStopColor());
+ }
+ }
+
+ *this = aNewStops;
+}
+
+// try to detect if an empty/no-color-change area exists
+// at the start and return offset to it. Returns 0.0 if not.
+double BColorStops::detectPossibleOffsetAtStart() const
+{
+ BColor aSingleColor;
+ const bool bSingleColor(isSingleColor(aSingleColor));
+
+ // no useful offset for single color
+ if (bSingleColor)
+ return 0.0;
+
+ // here we know that we have at least two colors, so we have a
+ // color change. Find colors left and right of that first color change
+ BColorStops::const_iterator aColorR(begin());
+ BColorStops::const_iterator aColorL(aColorR++);
+
+ // aColorR would 1st get equal to end(), so no need to also check aColorL
+ // for end(). Loop as long as same color. Since we *have* a color change
+ // not even aColorR can get equal to end() before color inequality, but
+ // keep for safety
+ while (aColorR != end() && aColorL->getStopColor() == aColorR->getStopColor())
+ {
+ aColorL++;
+ aColorR++;
+ }
+
+ // also for safety: access values at aColorL below *only*
+ // if not equal to end(), but can theoretically not happen
+ if (aColorL == end())
+ {
+ return 0.0;
+ }
+
+ // return offset (maybe 0.0 what is OK)
+ return aColorL->getStopOffset();
+}
+
+// checks whether the color stops are symmetrical in color and offset.
+bool BColorStops::isSymmetrical() const
+{
+ if (empty())
+ return false;
+ if (1 == size())
+ return basegfx::fTools::equal(0.5, front().getStopOffset());
+
+ BColorStops::const_iterator aIter(begin()); // for going forward
+ BColorStops::const_iterator aRIter(end()); // for going backward
+ --aRIter;
+ // We have at least two elements, so aIter <= aRIter fails before iterators no longer point to
+ // an element.
+ while (aIter <= aRIter && aIter->getStopColor().equal(aRIter->getStopColor())
+ && basegfx::fTools::equal(aIter->getStopOffset(), 1.0 - aRIter->getStopOffset()))
+ {
+ ++aIter;
+ --aRIter;
+ }
+ return aIter > aRIter;
+}
+
+void BColorStops::doApplyAxial()
+{
+ // prepare new ColorStops
+ basegfx::BColorStops aNewColorStops;
+
+ // add gradient stops in reverse order, scaled to [0.0 .. 0.5]
+ basegfx::BColorStops::const_reverse_iterator aRevCurrColor(rbegin());
+
+ while (aRevCurrColor != rend())
+ {
+ aNewColorStops.emplace_back((1.0 - aRevCurrColor->getStopOffset()) * 0.5,
+ aRevCurrColor->getStopColor());
+ aRevCurrColor++;
+ }
+
+ // prepare non-reverse run
+ basegfx::BColorStops::const_iterator aCurrColor(begin());
+
+ if (basegfx::fTools::equalZero(aCurrColor->getStopOffset()))
+ {
+ // Caution: do not add 1st entry again, that would be double since it was
+ // already added as last element of the inverse run above. But only if
+ // the gradient has a start entry for 0.0 aka StartColor, else it is correct.
+ aCurrColor++;
+ }
+
+ // add gradient stops in non-reverse order, translated and scaled to [0.5 .. 1.0]
+ while (aCurrColor != end())
+ {
+ aNewColorStops.emplace_back((aCurrColor->getStopOffset() * 0.5) + 0.5,
+ aCurrColor->getStopColor());
+ aCurrColor++;
+ }
+
+ // apply color stops
+ *this = aNewColorStops;
+}
+
+void BColorStops::doApplySteps(sal_uInt16 nStepCount)
+{
+ // check for zero or invalid steps setting -> done
+ if (0 == nStepCount || nStepCount > 100)
+ return;
+
+ // no change needed if single color
+ BColor aSingleColor;
+ if (isSingleColor(aSingleColor))
+ return;
+
+ // prepare new color stops, get L/R iterators for segments
+ basegfx::BColorStops aNewColorStops;
+ basegfx::BColorStops::const_iterator aColorR(begin());
+ basegfx::BColorStops::const_iterator aColorL(aColorR++);
+
+ while (aColorR != end())
+ {
+ // get start/end color for segment
+ const double fStart(aColorL->getStopOffset());
+ const double fDelta(aColorR->getStopOffset() - fStart);
+
+ if (aNewColorStops.empty() || aNewColorStops.back() != *aColorL)
+ {
+ // add start color, but check if it is already there - which is the
+ // case from the 2nd segment on due to a new segment starting with
+ // the same color as the previous one ended
+ aNewColorStops.push_back(*aColorL);
+ }
+ if (!basegfx::fTools::equalZero(fDelta))
+ {
+ // create in-between steps, always two at the same position to
+ // define a 'hard' color stop. Get start/end color for the segment
+ const basegfx::BColor& rStartColor(aColorL->getStopColor());
+ const basegfx::BColor& rEndColor(aColorR->getStopColor());
+
+ if (rStartColor != rEndColor)
+ {
+ // get relative single-step width
+ // tdf155852 Use same method for the color as in rendering.
+ const double fSingleStep(1.0 / static_cast<double>(nStepCount - 1));
+ const double fOffsetStep(fDelta / static_cast<double>(nStepCount));
+
+ for (sal_uInt16 a(1); a < nStepCount; a++)
+ {
+ // calculate stop position since being used twice
+ const double fPosition(fStart + fOffsetStep * static_cast<double>(a));
+
+ // add end color of previous sub-segment
+ aNewColorStops.emplace_back(
+ fPosition, basegfx::interpolate(rStartColor, rEndColor,
+ static_cast<double>(a - 1) * fSingleStep));
+
+ // add start color of current sub-segment
+ aNewColorStops.emplace_back(
+ fPosition, basegfx::interpolate(rStartColor, rEndColor,
+ static_cast<double>(a) * fSingleStep));
+ }
+ }
+ }
+
+ // always add end color of segment
+ aNewColorStops.push_back(*aColorR);
+
+ // next segment
+ aColorL++;
+ aColorR++;
+ }
+
+ // apply the change to color stops
+ *this = aNewColorStops;
+}
+
+std::string BGradient::GradientStyleToString(css::awt::GradientStyle eStyle)
+{
+ switch (eStyle)
+ {
+ case css::awt::GradientStyle::GradientStyle_LINEAR:
+ return "LINEAR";
+
+ case css::awt::GradientStyle::GradientStyle_AXIAL:
+ return "AXIAL";
+
+ case css::awt::GradientStyle::GradientStyle_RADIAL:
+ return "RADIAL";
+
+ case css::awt::GradientStyle::GradientStyle_ELLIPTICAL:
+ return "ELLIPTICAL";
+
+ case css::awt::GradientStyle::GradientStyle_SQUARE:
+ return "SQUARE";
+
+ case css::awt::GradientStyle::GradientStyle_RECT:
+ return "RECT";
+
+ case css::awt::GradientStyle::GradientStyle_MAKE_FIXED_SIZE:
+ return "MAKE_FIXED_SIZE";
+ }
+
+ return "";
+}
+
+BGradient BGradient::fromJSON(std::u16string_view rJSON)
+{
+ StringMap aMap(lcl_jsonToStringMap(rJSON));
+ return lcl_buildGradientFromStringMap(aMap);
+}
+
+BGradient::BGradient()
+ : eStyle(css::awt::GradientStyle_LINEAR)
+ , aColorStops()
+ , nAngle(0)
+ , nBorder(0)
+ , nOfsX(50)
+ , nOfsY(50)
+ , nIntensStart(100)
+ , nIntensEnd(100)
+ , nStepCount(0)
+{
+ aColorStops.emplace_back(0.0, BColor(0.0, 0.0, 0.0)); // COL_BLACK
+ aColorStops.emplace_back(1.0, BColor(1.0, 1.0, 1.0)); // COL_WHITE
+}
+
+BGradient::BGradient(const basegfx::BColorStops& rColorStops, css::awt::GradientStyle eTheStyle,
+ Degree10 nTheAngle, sal_uInt16 nXOfs, sal_uInt16 nYOfs, sal_uInt16 nTheBorder,
+ sal_uInt16 nStartIntens, sal_uInt16 nEndIntens, sal_uInt16 nSteps)
+ : eStyle(eTheStyle)
+ , aColorStops(rColorStops)
+ , nAngle(nTheAngle)
+ , nBorder(nTheBorder)
+ , nOfsX(nXOfs)
+ , nOfsY(nYOfs)
+ , nIntensStart(nStartIntens)
+ , nIntensEnd(nEndIntens)
+ , nStepCount(nSteps)
+{
+ SetColorStops(aColorStops);
+}
+
+bool BGradient::operator==(const BGradient& rGradient) const
+{
+ return (eStyle == rGradient.eStyle && aColorStops == rGradient.aColorStops
+ && nAngle == rGradient.nAngle && nBorder == rGradient.nBorder
+ && nOfsX == rGradient.nOfsX && nOfsY == rGradient.nOfsY
+ && nIntensStart == rGradient.nIntensStart && nIntensEnd == rGradient.nIntensEnd
+ && nStepCount == rGradient.nStepCount);
+}
+
+void BGradient::SetColorStops(const basegfx::BColorStops& rSteps)
+{
+ aColorStops = rSteps;
+ aColorStops.sortAndCorrect();
+ if (aColorStops.empty())
+ aColorStops.emplace_back(0.0, basegfx::BColor());
+}
+
+namespace
+{
+OUString AsRGBHexString(const ColorToBColorConverter& rVal)
+{
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0') << std::setw(6) << sal_uInt32(rVal);
+ return OUString::createFromAscii(ss.str());
+}
+}
+
+boost::property_tree::ptree BGradient::dumpAsJSON() const
+{
+ boost::property_tree::ptree aTree;
+
+ aTree.put("style", BGradient::GradientStyleToString(eStyle));
+ const ColorToBColorConverter aStart(GetColorStops().front().getStopColor());
+ aTree.put("startcolor", AsRGBHexString(aStart.GetRGBColor()));
+ const ColorToBColorConverter aEnd(GetColorStops().back().getStopColor());
+ aTree.put("endcolor", AsRGBHexString(aEnd.GetRGBColor()));
+ aTree.put("angle", std::to_string(nAngle.get()));
+ aTree.put("border", std::to_string(nBorder));
+ aTree.put("x", std::to_string(nOfsX));
+ aTree.put("y", std::to_string(nOfsY));
+ aTree.put("intensstart", std::to_string(nIntensStart));
+ aTree.put("intensend", std::to_string(nIntensEnd));
+ aTree.put("stepcount", std::to_string(nStepCount));
+
+ return aTree;
+}
+
+void BGradient::tryToRecreateBorder(basegfx::BColorStops* pAssociatedTransparencyStops)
+{
+ // border already set, do not try to recreate
+ if (0 != GetBorder())
+ return;
+
+ BColor aSingleColor;
+ const bool bSingleColor(GetColorStops().isSingleColor(aSingleColor));
+
+ // no need to recreate with single color
+ if (bSingleColor)
+ return;
+
+ const bool bIsAxial(css::awt::GradientStyle_AXIAL == GetGradientStyle());
+
+ if (bIsAxial)
+ {
+ // for axial due to reverse used gradient work reversed
+ aColorStops.reverseColorStops();
+ if (nullptr != pAssociatedTransparencyStops)
+ pAssociatedTransparencyStops->reverseColorStops();
+ }
+
+ // check if we have space at start of range [0.0 .. 1.0] that
+ // may be interpreted as 'border' -> same color. That may involve
+ // different scenarios, e.g. 1st index > 0.0, but also a non-zero
+ // number of same color entries, or a combination of both
+ const double fOffset(aColorStops.detectPossibleOffsetAtStart());
+
+ if (!basegfx::fTools::equalZero(fOffset))
+ {
+ // we have a border area, indeed re-create
+ aColorStops.removeSpaceAtStart(fOffset);
+ if (nullptr != pAssociatedTransparencyStops)
+ pAssociatedTransparencyStops->removeSpaceAtStart(fOffset);
+
+ // ...and create border value
+ SetBorder(static_cast<sal_uInt16>(std::lround(fOffset * 100.0)));
+ }
+
+ if (bIsAxial)
+ {
+ // take back reverse
+ aColorStops.reverseColorStops();
+ if (nullptr != pAssociatedTransparencyStops)
+ pAssociatedTransparencyStops->reverseColorStops();
+ }
+}
+
+void BGradient::tryToApplyBorder()
+{
+ // no border to apply, done
+ if (0 == GetBorder())
+ return;
+
+ // NOTE: no new start node is added. The new ColorStop
+ // mechanism does not need entries at 0.0 and 1.0.
+ // In case this is needed, do that in the caller
+ const double fOffset(GetBorder() * 0.01);
+
+ if (css::awt::GradientStyle_AXIAL == GetGradientStyle())
+ {
+ // for axial due to reverse used gradient work reversed
+ aColorStops.reverseColorStops();
+ aColorStops.createSpaceAtStart(fOffset);
+ aColorStops.reverseColorStops();
+ }
+ else
+ {
+ // apply border to GradientSteps
+ aColorStops.createSpaceAtStart(fOffset);
+ }
+
+ // set changed values
+ SetBorder(0);
+}
+
+void BGradient::tryToApplyStartEndIntensity()
+{
+ // already on default, nothing to apply
+ if (100 == GetStartIntens() && 100 == GetEndIntens())
+ return;
+
+ // apply 'old' blend stuff, blend against black
+ aColorStops.blendToIntensity(GetStartIntens() * 0.01, GetEndIntens() * 0.01,
+ BColor()); // COL_BLACK
+
+ // set values to default
+ SetStartIntens(100);
+ SetEndIntens(100);
+}
+
+void BGradient::tryToConvertToAxial()
+{
+ if (css::awt::GradientStyle_LINEAR != GetGradientStyle() || 0 != GetBorder()
+ || GetColorStops().empty())
+ return;
+
+ if (!GetColorStops().isSymmetrical())
+ return;
+
+ SetGradientStyle(css::awt::GradientStyle_AXIAL);
+
+ // Stretch the first half of the color stops to double width
+ // and collect them in a new color stops vector.
+ BColorStops aAxialColorStops;
+ aAxialColorStops.reserve(std::ceil(GetColorStops().size() / 2.0));
+ BColorStops::const_iterator aIter(GetColorStops().begin());
+ while (basegfx::fTools::lessOrEqual(aIter->getStopOffset(), 0.5))
+ {
+ BColorStop aNextStop(std::clamp((*aIter).getStopOffset() * 2.0, 0.0, 1.0),
+ (*aIter).getStopColor());
+ aAxialColorStops.push_back(aNextStop);
+ ++aIter;
+ }
+ // Axial gradients have outmost color as last color stop.
+ aAxialColorStops.reverseColorStops();
+
+ SetColorStops(aAxialColorStops);
+}
+
+void BGradient::tryToApplyAxial()
+{
+ // only need to do something if css::awt::GradientStyle_AXIAL, else done
+ if (GetGradientStyle() != css::awt::GradientStyle_AXIAL)
+ return;
+
+ // apply the change to color stops
+ aColorStops.doApplyAxial();
+
+ // set style to GradientStyle_LINEAR
+ SetGradientStyle(css::awt::GradientStyle_LINEAR);
+}
+
+void BGradient::tryToApplySteps()
+{
+ // check for zero or invalid steps setting -> done
+ if (0 == GetSteps() || GetSteps() > 100)
+ return;
+
+ // do the action
+ aColorStops.doApplySteps(GetSteps());
+
+ // set value to default
+ SetSteps(0);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/canvastools.cxx b/basegfx/source/tools/canvastools.cxx
new file mode 100644
index 0000000000..70f4787710
--- /dev/null
+++ b/basegfx/source/tools/canvastools.cxx
@@ -0,0 +1,474 @@
+/* -*- 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 <com/sun/star/geometry/RealSize2D.hpp>
+#include <com/sun/star/geometry/RealPoint2D.hpp>
+#include <com/sun/star/geometry/RealRectangle2D.hpp>
+#include <com/sun/star/geometry/RealRectangle3D.hpp>
+#include <com/sun/star/geometry/RealBezierSegment2D.hpp>
+#include <com/sun/star/geometry/AffineMatrix2D.hpp>
+#include <com/sun/star/geometry/AffineMatrix3D.hpp>
+#include <com/sun/star/geometry/IntegerSize2D.hpp>
+#include <com/sun/star/geometry/IntegerRectangle2D.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/rendering/XPolyPolygon2D.hpp>
+#include <com/sun/star/rendering/XGraphicDevice.hpp>
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <basegfx/utils/unopolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dsize.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <basegfx/range/b3drange.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/utils/canvastools.hxx>
+
+using namespace ::com::sun::star;
+
+namespace basegfx::unotools
+{
+ namespace
+ {
+ uno::Sequence< geometry::RealBezierSegment2D > bezierSequenceFromB2DPolygon(const ::basegfx::B2DPolygon& rPoly)
+ {
+ const sal_uInt32 nPointCount(rPoly.count());
+ uno::Sequence< geometry::RealBezierSegment2D > outputSequence(nPointCount);
+ geometry::RealBezierSegment2D* pOutput = outputSequence.getArray();
+
+ // fill sequences and imply closed polygon on this implementation layer
+ for(sal_uInt32 a(0); a < nPointCount; a++)
+ {
+ const basegfx::B2DPoint aStart(rPoly.getB2DPoint(a));
+ const basegfx::B2DPoint aControlA(rPoly.getNextControlPoint(a));
+ const basegfx::B2DPoint aControlB(rPoly.getPrevControlPoint((a + 1) % nPointCount));
+
+ pOutput[a] = geometry::RealBezierSegment2D(
+ aStart.getX(), aStart.getY(),
+ aControlA.getX(), aControlA.getY(),
+ aControlB.getX(), aControlB.getY());
+ }
+
+ return outputSequence;
+ }
+
+ uno::Sequence< geometry::RealPoint2D > pointSequenceFromB2DPolygon( const ::basegfx::B2DPolygon& rPoly )
+ {
+ const sal_uInt32 nNumPoints( rPoly.count() );
+
+ uno::Sequence< geometry::RealPoint2D > outputSequence( nNumPoints );
+ geometry::RealPoint2D* pOutput = outputSequence.getArray();
+
+ // fill sequence from polygon
+ sal_uInt32 i;
+ for( i=0; i<nNumPoints; ++i )
+ {
+ const ::basegfx::B2DPoint aPoint( rPoly.getB2DPoint(i) );
+
+ pOutput[i] = geometry::RealPoint2D( aPoint.getX(),
+ aPoint.getY() );
+ }
+
+ return outputSequence;
+ }
+ }
+
+ uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > bezierSequenceSequenceFromB2DPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly )
+ {
+ const sal_uInt32 nNumPolies( rPolyPoly.count() );
+ sal_uInt32 i;
+
+ uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > outputSequence( nNumPolies );
+ uno::Sequence< geometry::RealBezierSegment2D >* pOutput = outputSequence.getArray();
+
+ for( i=0; i<nNumPolies; ++i )
+ {
+ pOutput[i] = bezierSequenceFromB2DPolygon( rPolyPoly.getB2DPolygon(i) );
+ }
+
+ return outputSequence;
+ }
+
+ uno::Sequence< uno::Sequence< geometry::RealPoint2D > > pointSequenceSequenceFromB2DPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly )
+ {
+ const sal_uInt32 nNumPolies( rPolyPoly.count() );
+ sal_uInt32 i;
+
+ uno::Sequence< uno::Sequence< geometry::RealPoint2D > > outputSequence( nNumPolies );
+ uno::Sequence< geometry::RealPoint2D >* pOutput = outputSequence.getArray();
+
+ for( i=0; i<nNumPolies; ++i )
+ {
+ pOutput[i] = pointSequenceFromB2DPolygon( rPolyPoly.getB2DPolygon(i) );
+ }
+
+ return outputSequence;
+ }
+
+ uno::Reference< rendering::XPolyPolygon2D > xPolyPolygonFromB2DPolygon( const uno::Reference< rendering::XGraphicDevice >& xGraphicDevice,
+ const ::basegfx::B2DPolygon& rPoly )
+ {
+ uno::Reference< rendering::XPolyPolygon2D > xRes;
+
+ if( !xGraphicDevice.is() )
+ return xRes;
+
+ if( rPoly.areControlPointsUsed() )
+ {
+ uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > outputSequence{ bezierSequenceFromB2DPolygon( rPoly )};
+
+ xRes = xGraphicDevice->createCompatibleBezierPolyPolygon( outputSequence );
+ }
+ else
+ {
+ uno::Sequence< uno::Sequence< geometry::RealPoint2D > > outputSequence{
+ pointSequenceFromB2DPolygon( rPoly )};
+
+ xRes = xGraphicDevice->createCompatibleLinePolyPolygon( outputSequence );
+ }
+
+ if( xRes.is() && rPoly.isClosed() )
+ xRes->setClosed( 0, true );
+
+ return xRes;
+ }
+
+ uno::Reference< rendering::XPolyPolygon2D > xPolyPolygonFromB2DPolyPolygon( const uno::Reference< rendering::XGraphicDevice >& xGraphicDevice,
+ const ::basegfx::B2DPolyPolygon& rPolyPoly )
+ {
+ uno::Reference< rendering::XPolyPolygon2D > xRes;
+
+ if( !xGraphicDevice.is() )
+ return xRes;
+
+ const sal_uInt32 nNumPolies( rPolyPoly.count() );
+ sal_uInt32 i;
+
+ if( rPolyPoly.areControlPointsUsed() )
+ {
+ xRes = xGraphicDevice->createCompatibleBezierPolyPolygon(
+ bezierSequenceSequenceFromB2DPolyPolygon( rPolyPoly ) );
+ }
+ else
+ {
+ xRes = xGraphicDevice->createCompatibleLinePolyPolygon(
+ pointSequenceSequenceFromB2DPolyPolygon( rPolyPoly ) );
+ }
+
+ for( i=0; i<nNumPolies; ++i )
+ {
+ xRes->setClosed( i, rPolyPoly.getB2DPolygon(i).isClosed() );
+ }
+
+ return xRes;
+ }
+
+ ::basegfx::B2DPolygon polygonFromPoint2DSequence( const uno::Sequence< geometry::RealPoint2D >& points )
+ {
+ const sal_Int32 nCurrSize( points.getLength() );
+
+ ::basegfx::B2DPolygon aPoly;
+
+ for( sal_Int32 nCurrPoint=0; nCurrPoint<nCurrSize; ++nCurrPoint )
+ aPoly.append( b2DPointFromRealPoint2D( points[nCurrPoint] ) );
+
+ return aPoly;
+ }
+
+ ::basegfx::B2DPolyPolygon polyPolygonFromPoint2DSequenceSequence( const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points )
+ {
+ ::basegfx::B2DPolyPolygon aRes;
+
+ for( const auto & p : points )
+ {
+ aRes.append( polygonFromPoint2DSequence( p ) );
+ }
+
+ return aRes;
+ }
+
+ ::basegfx::B2DPolygon polygonFromBezier2DSequence( const uno::Sequence< geometry::RealBezierSegment2D >& curves )
+ {
+ const sal_Int32 nSize(curves.getLength());
+ basegfx::B2DPolygon aRetval;
+
+ if(nSize)
+ {
+ // prepare start with providing a start point. Use the first point from
+ // the sequence for this
+ const geometry::RealBezierSegment2D& rFirstSegment(curves[0]); // #i79917# first segment, not last
+ aRetval.append(basegfx::B2DPoint(rFirstSegment.Px, rFirstSegment.Py));
+
+ for(sal_Int32 a(0); a < nSize; a++)
+ {
+ const geometry::RealBezierSegment2D& rCurrSegment(curves[a]);
+ const geometry::RealBezierSegment2D& rNextSegment(curves[(a + 1) % nSize]);
+
+ // append curved edge with the control points and the next point
+ aRetval.appendBezierSegment(
+ basegfx::B2DPoint(rCurrSegment.C1x, rCurrSegment.C1y),
+ basegfx::B2DPoint(rCurrSegment.C2x, rCurrSegment.C2y), // #i79917# Argh! An x for an y!!
+ basegfx::B2DPoint(rNextSegment.Px, rNextSegment.Py));
+ }
+
+ // rescue the control point and remove the now double-added point
+ aRetval.setPrevControlPoint(0, aRetval.getPrevControlPoint(aRetval.count() - 1));
+ aRetval.remove(aRetval.count() - 1);
+ }
+
+ return aRetval;
+ }
+
+ ::basegfx::B2DPolyPolygon polyPolygonFromBezier2DSequenceSequence( const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& curves )
+ {
+ ::basegfx::B2DPolyPolygon aRes;
+
+ for( const auto & c : curves )
+ {
+ aRes.append( polygonFromBezier2DSequence( c ) );
+ }
+
+ return aRes;
+ }
+
+ ::basegfx::B2DPolyPolygon b2DPolyPolygonFromXPolyPolygon2D( const uno::Reference< rendering::XPolyPolygon2D >& xPoly )
+ {
+ ::basegfx::unotools::UnoPolyPolygon* pPolyImpl =
+ dynamic_cast< ::basegfx::unotools::UnoPolyPolygon* >( xPoly.get() );
+
+ if( pPolyImpl )
+ {
+ return pPolyImpl->getPolyPolygon();
+ }
+ else
+ {
+ // not a known implementation object - try data source
+ // interfaces
+ const sal_Int32 nPolys( xPoly->getNumberOfPolygons() );
+
+ uno::Reference< rendering::XBezierPolyPolygon2D > xBezierPoly(
+ xPoly,
+ uno::UNO_QUERY );
+
+ if( xBezierPoly.is() )
+ {
+ return ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence(
+ xBezierPoly->getBezierSegments( 0,
+ nPolys,
+ 0,
+ -1 ) );
+ }
+ else
+ {
+ uno::Reference< rendering::XLinePolyPolygon2D > xLinePoly(
+ xPoly,
+ uno::UNO_QUERY );
+
+ // no implementation class and no data provider
+ // found - contract violation.
+ if( !xLinePoly.is() )
+ {
+ throw lang::IllegalArgumentException(
+ "basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(): Invalid input"
+ "poly-polygon, cannot retrieve vertex data",
+ uno::Reference< uno::XInterface >(),
+ 0 );
+ }
+
+ return ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence(
+ xLinePoly->getPoints( 0,
+ nPolys,
+ 0,
+ -1 ));
+ }
+ }
+ }
+
+ ::basegfx::B2DHomMatrix& homMatrixFromAffineMatrix( ::basegfx::B2DHomMatrix& output,
+ const geometry::AffineMatrix2D& input )
+ {
+ // ensure last row is [0,0,1] (and optimized away)
+ output.identity();
+
+ output.set(0,0, input.m00);
+ output.set(0,1, input.m01);
+ output.set(0,2, input.m02);
+ output.set(1,0, input.m10);
+ output.set(1,1, input.m11);
+ output.set(1,2, input.m12);
+
+ return output;
+ }
+
+ ::basegfx::B3DHomMatrix homMatrixFromAffineMatrix3D( const ::css::geometry::AffineMatrix3D& input )
+ {
+ ::basegfx::B3DHomMatrix output;
+
+ output.set(0,0, input.m00);
+ output.set(0,1, input.m01);
+ output.set(0,2, input.m02);
+ output.set(0,3, input.m03);
+
+ output.set(1,0, input.m10);
+ output.set(1,1, input.m11);
+ output.set(1,2, input.m12);
+ output.set(1,3, input.m13);
+
+ output.set(2,0, input.m20);
+ output.set(2,1, input.m21);
+ output.set(2,2, input.m22);
+ output.set(2,3, input.m23);
+
+ return output;
+ }
+
+ geometry::AffineMatrix2D& affineMatrixFromHomMatrix( geometry::AffineMatrix2D& output,
+ const ::basegfx::B2DHomMatrix& input)
+ {
+ output.m00 = input.get(0,0);
+ output.m01 = input.get(0,1);
+ output.m02 = input.get(0,2);
+ output.m10 = input.get(1,0);
+ output.m11 = input.get(1,1);
+ output.m12 = input.get(1,2);
+
+ return output;
+ }
+
+ geometry::AffineMatrix3D& affineMatrixFromHomMatrix3D(
+ geometry::AffineMatrix3D& output,
+ const ::basegfx::B3DHomMatrix& input)
+ {
+ output.m00 = input.get(0,0);
+ output.m01 = input.get(0,1);
+ output.m02 = input.get(0,2);
+ output.m03 = input.get(0,3);
+
+ output.m10 = input.get(1,0);
+ output.m11 = input.get(1,1);
+ output.m12 = input.get(1,2);
+ output.m13 = input.get(1,3);
+
+ output.m20 = input.get(2,0);
+ output.m21 = input.get(2,1);
+ output.m22 = input.get(2,2);
+ output.m23 = input.get(2,3);
+
+ return output;
+ }
+
+ geometry::RealSize2D size2DFromB2DSize(const ::basegfx::B2DSize& rSize)
+ {
+ return geometry::RealSize2D(rSize.getWidth(), rSize.getHeight());
+ }
+
+ geometry::RealPoint2D point2DFromB2DPoint( const ::basegfx::B2DPoint& rPoint )
+ {
+ return geometry::RealPoint2D( rPoint.getX(),
+ rPoint.getY() );
+ }
+
+ geometry::RealRectangle2D rectangle2DFromB2DRectangle( const ::basegfx::B2DRange& rRect )
+ {
+ return geometry::RealRectangle2D( rRect.getMinX(),
+ rRect.getMinY(),
+ rRect.getMaxX(),
+ rRect.getMaxY() );
+ }
+
+ geometry::RealRectangle3D rectangle3DFromB3DRectangle( const ::basegfx::B3DRange& rRect )
+ {
+ return geometry::RealRectangle3D( rRect.getMinX(),
+ rRect.getMinY(),
+ rRect.getMinZ(),
+ rRect.getMaxX(),
+ rRect.getMaxY(),
+ rRect.getMaxZ());
+ }
+
+ ::basegfx::B2DPoint b2DPointFromRealPoint2D( const geometry::RealPoint2D& rPoint )
+ {
+ return ::basegfx::B2DPoint( rPoint.X,
+ rPoint.Y );
+ }
+
+ ::basegfx::B2DRange b2DRectangleFromRealRectangle2D( const geometry::RealRectangle2D& rRect )
+ {
+ return ::basegfx::B2DRange( rRect.X1,
+ rRect.Y1,
+ rRect.X2,
+ rRect.Y2 );
+ }
+
+ ::basegfx::B3DRange b3DRectangleFromRealRectangle3D( const geometry::RealRectangle3D& rRect )
+ {
+ return ::basegfx::B3DRange( rRect.X1,
+ rRect.Y1,
+ rRect.Z1,
+ rRect.X2,
+ rRect.Y2,
+ rRect.Z2);
+ }
+
+ geometry::IntegerSize2D integerSize2DFromB2ISize( const ::basegfx::B2ISize& rSize )
+ {
+ return geometry::IntegerSize2D( rSize.getWidth(),
+ rSize.getHeight() );
+ }
+
+ basegfx::B2ISize b2ISizeFromIntegerSize2D( const geometry::IntegerSize2D& rSize )
+ {
+ return basegfx::B2ISize(rSize.Width, rSize.Height);
+ }
+
+ ::basegfx::B2IRange b2IRectangleFromIntegerRectangle2D( const geometry::IntegerRectangle2D& rRectangle )
+ {
+ return ::basegfx::B2IRange( rRectangle.X1, rRectangle.Y1,
+ rRectangle.X2, rRectangle.Y2 );
+ }
+
+ ::basegfx::B2IRange b2IRectangleFromAwtRectangle( const awt::Rectangle& rRect )
+ {
+ return ::basegfx::B2IRange( rRect.X,
+ rRect.Y,
+ rRect.X + rRect.Width,
+ rRect.Y + rRect.Height );
+ }
+
+ ::basegfx::B2IRange b2ISurroundingRangeFromB2DRange( const ::basegfx::B2DRange& rRange )
+ {
+ return ::basegfx::B2IRange( static_cast<sal_Int32>( floor(rRange.getMinX()) ),
+ static_cast<sal_Int32>( floor(rRange.getMinY()) ),
+ static_cast<sal_Int32>( ceil(rRange.getMaxX()) ),
+ static_cast<sal_Int32>( ceil(rRange.getMaxY()) ) );
+ }
+
+ ::basegfx::B2DRange b2DSurroundingIntegerRangeFromB2DRange( const ::basegfx::B2DRange& rRange )
+ {
+ return ::basegfx::B2DRange( floor(rRange.getMinX()),
+ floor(rRange.getMinY()),
+ ceil(rRange.getMaxX()),
+ ceil(rRange.getMaxY()) );
+ }
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/gradienttools.cxx b/basegfx/source/tools/gradienttools.cxx
new file mode 100644
index 0000000000..8f3e8ae83c
--- /dev/null
+++ b/basegfx/source/tools/gradienttools.cxx
@@ -0,0 +1,756 @@
+/* -*- 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 <basegfx/utils/gradienttools.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <com/sun/star/awt/Gradient2.hpp>
+#include <osl/endian.h>
+
+#include <algorithm>
+#include <cmath>
+
+namespace basegfx
+{
+ bool ODFGradientInfo::operator==(const ODFGradientInfo& rODFGradientInfo) const
+ {
+ return getTextureTransform() == rODFGradientInfo.getTextureTransform()
+ && getAspectRatio() == rODFGradientInfo.getAspectRatio()
+ && getRequestedSteps() == rODFGradientInfo.getRequestedSteps();
+ }
+
+ const B2DHomMatrix& ODFGradientInfo::getBackTextureTransform() const
+ {
+ if(maBackTextureTransform.isIdentity())
+ {
+ const_cast< ODFGradientInfo* >(this)->maBackTextureTransform = getTextureTransform();
+ const_cast< ODFGradientInfo* >(this)->maBackTextureTransform.invert();
+ }
+
+ return maBackTextureTransform;
+ }
+
+ /** Most of the setup for linear & axial gradient is the same, except
+ for the border treatment. Factored out here.
+ */
+ static ODFGradientInfo init1DGradientInfo(
+ const B2DRange& rTargetRange,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle,
+ bool bAxial)
+ {
+ B2DHomMatrix aTextureTransform;
+
+ fAngle = -fAngle;
+
+ double fTargetSizeX(rTargetRange.getWidth());
+ double fTargetSizeY(rTargetRange.getHeight());
+ double fTargetOffsetX(rTargetRange.getMinX());
+ double fTargetOffsetY(rTargetRange.getMinY());
+
+ // add object expansion
+ const bool bAngleUsed(!fTools::equalZero(fAngle));
+
+ if(bAngleUsed)
+ {
+ const double fAbsCos(fabs(cos(fAngle)));
+ const double fAbsSin(fabs(sin(fAngle)));
+ const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
+ const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
+
+ fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
+ fTargetSizeX = fNewX;
+ fTargetSizeY = fNewY;
+ }
+
+ const double fSizeWithoutBorder(1.0 - fBorder);
+
+ if(bAxial)
+ {
+ aTextureTransform.scale(1.0, fSizeWithoutBorder * 0.5);
+ aTextureTransform.translate(0.0, 0.5);
+ }
+ else
+ {
+ if(!fTools::equal(fSizeWithoutBorder, 1.0))
+ {
+ aTextureTransform.scale(1.0, fSizeWithoutBorder);
+ aTextureTransform.translate(0.0, fBorder);
+ }
+ }
+
+ aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
+
+ // add texture rotate after scale to keep perpendicular angles
+ if(bAngleUsed)
+ {
+ const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
+
+ aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
+ }
+
+ // add object translate
+ aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
+
+ // prepare aspect for texture
+ const double fAspectRatio(fTools::equalZero(fTargetSizeY) ? 1.0 : fTargetSizeX / fTargetSizeY);
+
+ return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
+ }
+
+ /** Most of the setup for radial & ellipsoidal gradient is the same,
+ except for the border treatment. Factored out here.
+ */
+ static ODFGradientInfo initEllipticalGradientInfo(
+ const B2DRange& rTargetRange,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle,
+ bool bCircular)
+ {
+ B2DHomMatrix aTextureTransform;
+
+ fAngle = -fAngle;
+
+ double fTargetSizeX(rTargetRange.getWidth());
+ double fTargetSizeY(rTargetRange.getHeight());
+ double fTargetOffsetX(rTargetRange.getMinX());
+ double fTargetOffsetY(rTargetRange.getMinY());
+
+ // add object expansion
+ if(bCircular)
+ {
+ const double fOriginalDiag(std::hypot(fTargetSizeX, fTargetSizeY));
+
+ fTargetOffsetX -= (fOriginalDiag - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fOriginalDiag - fTargetSizeY) / 2.0;
+ fTargetSizeX = fOriginalDiag;
+ fTargetSizeY = fOriginalDiag;
+ }
+ else
+ {
+ fTargetOffsetX -= ((M_SQRT2 - 1) / 2.0 ) * fTargetSizeX;
+ fTargetOffsetY -= ((M_SQRT2 - 1) / 2.0 ) * fTargetSizeY;
+ fTargetSizeX = M_SQRT2 * fTargetSizeX;
+ fTargetSizeY = M_SQRT2 * fTargetSizeY;
+ }
+
+ const double fHalfBorder((1.0 - fBorder) * 0.5);
+
+ aTextureTransform.scale(fHalfBorder, fHalfBorder);
+ aTextureTransform.translate(0.5, 0.5);
+ aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
+
+ // add texture rotate after scale to keep perpendicular angles
+ if(!bCircular && !fTools::equalZero(fAngle))
+ {
+ const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
+
+ aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
+ }
+
+ // add defined offsets after rotation
+ if(!fTools::equal(0.5, rOffset.getX()) || !fTools::equal(0.5, rOffset.getY()))
+ {
+ // use original target size
+ fTargetOffsetX += (rOffset.getX() - 0.5) * rTargetRange.getWidth();
+ fTargetOffsetY += (rOffset.getY() - 0.5) * rTargetRange.getHeight();
+ }
+
+ // add object translate
+ aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
+
+ // prepare aspect for texture
+ const double fAspectRatio(fTargetSizeY == 0.0 ? 1.0 : (fTargetSizeX / fTargetSizeY));
+
+ return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
+ }
+
+ /** Setup for rect & square gradient is exactly the same. Factored out
+ here.
+ */
+ static ODFGradientInfo initRectGradientInfo(
+ const B2DRange& rTargetRange,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle,
+ bool bSquare)
+ {
+ B2DHomMatrix aTextureTransform;
+
+ fAngle = -fAngle;
+
+ double fTargetSizeX(rTargetRange.getWidth());
+ double fTargetSizeY(rTargetRange.getHeight());
+ double fTargetOffsetX(rTargetRange.getMinX());
+ double fTargetOffsetY(rTargetRange.getMinY());
+
+ // add object expansion
+ if(bSquare)
+ {
+ const double fSquareWidth(std::max(fTargetSizeX, fTargetSizeY));
+
+ fTargetOffsetX -= (fSquareWidth - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fSquareWidth - fTargetSizeY) / 2.0;
+ fTargetSizeX = fTargetSizeY = fSquareWidth;
+ }
+
+ // add object expansion
+ const bool bAngleUsed(!fTools::equalZero(fAngle));
+
+ if(bAngleUsed)
+ {
+ const double fAbsCos(fabs(cos(fAngle)));
+ const double fAbsSin(fabs(sin(fAngle)));
+ const double fNewX(fTargetSizeX * fAbsCos + fTargetSizeY * fAbsSin);
+ const double fNewY(fTargetSizeY * fAbsCos + fTargetSizeX * fAbsSin);
+
+ fTargetOffsetX -= (fNewX - fTargetSizeX) / 2.0;
+ fTargetOffsetY -= (fNewY - fTargetSizeY) / 2.0;
+ fTargetSizeX = fNewX;
+ fTargetSizeY = fNewY;
+ }
+
+ const double fHalfBorder((1.0 - fBorder) * 0.5);
+
+ aTextureTransform.scale(fHalfBorder, fHalfBorder);
+ aTextureTransform.translate(0.5, 0.5);
+ aTextureTransform.scale(fTargetSizeX, fTargetSizeY);
+
+ // add texture rotate after scale to keep perpendicular angles
+ if(bAngleUsed)
+ {
+ const B2DPoint aCenter(0.5 * fTargetSizeX, 0.5 * fTargetSizeY);
+
+ aTextureTransform *= basegfx::utils::createRotateAroundPoint(aCenter, fAngle);
+ }
+
+ // add defined offsets after rotation
+ if(!fTools::equal(0.5, rOffset.getX()) || !fTools::equal(0.5, rOffset.getY()))
+ {
+ // use original target size
+ fTargetOffsetX += (rOffset.getX() - 0.5) * rTargetRange.getWidth();
+ fTargetOffsetY += (rOffset.getY() - 0.5) * rTargetRange.getHeight();
+ }
+
+ // add object translate
+ aTextureTransform.translate(fTargetOffsetX, fTargetOffsetY);
+
+ // prepare aspect for texture
+ const double fAspectRatio(fTargetSizeY == 0.0 ? 1.0 : (fTargetSizeX / fTargetSizeY));
+
+ return ODFGradientInfo(aTextureTransform, fAspectRatio, nSteps);
+ }
+
+ namespace utils
+ {
+ /* Tooling method to extract data from given BGradient
+ to ColorStops, doing some corrections, partially based
+ on given SingleColor */
+ void prepareColorStops(
+ const basegfx::BGradient& rGradient,
+ BColorStops& rColorStops,
+ BColor& rSingleColor)
+ {
+ if (rGradient.GetColorStops().isSingleColor(rSingleColor))
+ {
+ // when single color, preserve value in rSingleColor
+ // and clear the ColorStops, done.
+ rColorStops.clear();
+ return;
+ }
+
+ const bool bAdaptStartEndIntensity(100 != rGradient.GetStartIntens() || 100 != rGradient.GetEndIntens());
+ const bool bAdaptBorder(0 != rGradient.GetBorder());
+
+ if (!bAdaptStartEndIntensity && !bAdaptBorder)
+ {
+ // copy unchanged ColorStops & done
+ rColorStops = rGradient.GetColorStops();
+ return;
+ }
+
+ // prepare a copy to work on
+ basegfx::BGradient aWorkCopy(rGradient);
+
+ if (bAdaptStartEndIntensity)
+ {
+ aWorkCopy.tryToApplyStartEndIntensity();
+
+ // this can again lead to single color (e.g. both zero, so
+ // all black), so check again for it
+ if (aWorkCopy.GetColorStops().isSingleColor(rSingleColor))
+ {
+ rColorStops.clear();
+ return;
+ }
+ }
+
+ if (bAdaptBorder)
+ {
+ aWorkCopy.tryToApplyBorder();
+ }
+
+ // extract ColorStops, that's all we need here
+ rColorStops = aWorkCopy.GetColorStops();
+ }
+
+ /* Tooling method to synchronize the given ColorStops.
+ The intention is that a color GradientStops and an
+ alpha/transparence GradientStops gets synchronized
+ for export. */
+ void synchronizeColorStops(
+ BColorStops& rColorStops,
+ BColorStops& rAlphaStops,
+ const BColor& rSingleColor,
+ const BColor& rSingleAlpha)
+ {
+ if (rColorStops.empty())
+ {
+ if (rAlphaStops.empty())
+ {
+ // no AlphaStops and no ColorStops
+ // create two-stop fallbacks for both
+ rColorStops = BColorStops {
+ BColorStop(0.0, rSingleColor),
+ BColorStop(1.0, rSingleColor) };
+ rAlphaStops = BColorStops {
+ BColorStop(0.0, rSingleAlpha),
+ BColorStop(1.0, rSingleAlpha) };
+ }
+ else
+ {
+ // AlphaStops but no ColorStops
+ // create fallback synched with existing AlphaStops
+ for (const auto& cand : rAlphaStops)
+ {
+ rColorStops.emplace_back(cand.getStopOffset(), rSingleColor);
+ }
+ }
+
+ // preparations complete, we are done
+ return;
+ }
+ else if (rAlphaStops.empty())
+ {
+ // ColorStops but no AlphaStops
+ // create fallback AlphaStops synched with existing ColorStops using SingleAlpha
+ for (const auto& cand : rColorStops)
+ {
+ rAlphaStops.emplace_back(cand.getStopOffset(), rSingleAlpha);
+ }
+
+ // preparations complete, we are done
+ return;
+ }
+
+ // here we have ColorStops and AlphaStops not empty. Check if we need to
+ // synchronize both or if they are already usable/in a synched state so
+ // that they have same count and same StopOffsets
+ bool bNeedToSyncronize(rColorStops.size() != rAlphaStops.size());
+
+ if (!bNeedToSyncronize)
+ {
+ // check for same StopOffsets
+ BColorStops::const_iterator aCurrColor(rColorStops.begin());
+ BColorStops::const_iterator aCurrAlpha(rAlphaStops.begin());
+
+ while (!bNeedToSyncronize &&
+ aCurrColor != rColorStops.end() &&
+ aCurrAlpha != rAlphaStops.end())
+ {
+ if (fTools::equal(aCurrColor->getStopOffset(), aCurrAlpha->getStopOffset()))
+ {
+ aCurrColor++;
+ aCurrAlpha++;
+ }
+ else
+ {
+ bNeedToSyncronize = true;
+ }
+ }
+ }
+
+ if (bNeedToSyncronize)
+ {
+ // synchronize sizes & StopOffsets
+ BColorStops::const_iterator aCurrColor(rColorStops.begin());
+ BColorStops::const_iterator aCurrAlpha(rAlphaStops.begin());
+ BColorStops aNewColor;
+ BColorStops aNewAlpha;
+ BColorStops::BColorStopRange aColorStopRange;
+ BColorStops::BColorStopRange aAlphaStopRange;
+ bool bRealChange(false);
+
+ do {
+ const bool bColor(aCurrColor != rColorStops.end());
+ const bool bAlpha(aCurrAlpha != rAlphaStops.end());
+
+ if (bColor && bAlpha)
+ {
+ const double fColorOff(aCurrColor->getStopOffset());
+ const double fAlphaOff(aCurrAlpha->getStopOffset());
+
+ if (fTools::less(fColorOff, fAlphaOff))
+ {
+ // copy color, create alpha
+ aNewColor.emplace_back(fColorOff, aCurrColor->getStopColor());
+ aNewAlpha.emplace_back(fColorOff, rAlphaStops.getInterpolatedBColor(fColorOff, 0, aAlphaStopRange));
+ bRealChange = true;
+ aCurrColor++;
+ }
+ else if (fTools::more(fColorOff, fAlphaOff))
+ {
+ // copy alpha, create color
+ aNewColor.emplace_back(fAlphaOff, rColorStops.getInterpolatedBColor(fAlphaOff, 0, aColorStopRange));
+ aNewAlpha.emplace_back(fAlphaOff, aCurrAlpha->getStopColor());
+ bRealChange = true;
+ aCurrAlpha++;
+ }
+ else
+ {
+ // equal: copy both, advance
+ aNewColor.emplace_back(fColorOff, aCurrColor->getStopColor());
+ aNewAlpha.emplace_back(fAlphaOff, aCurrAlpha->getStopColor());
+ aCurrColor++;
+ aCurrAlpha++;
+ }
+ }
+ else if (bColor)
+ {
+ const double fColorOff(aCurrColor->getStopOffset());
+ aNewAlpha.emplace_back(fColorOff, rAlphaStops.getInterpolatedBColor(fColorOff, 0, aAlphaStopRange));
+ aNewColor.emplace_back(fColorOff, aCurrColor->getStopColor());
+ bRealChange = true;
+ aCurrColor++;
+ }
+ else if (bAlpha)
+ {
+ const double fAlphaOff(aCurrAlpha->getStopOffset());
+ aNewColor.emplace_back(fAlphaOff, rColorStops.getInterpolatedBColor(fAlphaOff, 0, aColorStopRange));
+ aNewAlpha.emplace_back(fAlphaOff, aCurrAlpha->getStopColor());
+ bRealChange = true;
+ aCurrAlpha++;
+ }
+ else
+ {
+ // no more input, break do..while loop
+ break;
+ }
+ }
+ while(true);
+
+ if (bRealChange)
+ {
+ // copy on 'real' change, that means data was added.
+ // This should always be the cease and should have been
+ // detected as such above, see bNeedToSyncronize
+ rColorStops = aNewColor;
+ rAlphaStops = aNewAlpha; // MCGR: tdf#155537 used wrong result here
+ }
+ }
+ }
+
+ sal_uInt32 calculateNumberOfSteps(
+ sal_uInt32 nRequestedSteps,
+ const BColor& rStart,
+ const BColor& rEnd)
+ {
+ const sal_uInt32 nMaxSteps(sal_uInt32((rStart.getMaximumDistance(rEnd) * 127.5) + 0.5));
+
+ if (0 == nRequestedSteps)
+ {
+ nRequestedSteps = nMaxSteps;
+ }
+
+ if(nRequestedSteps > nMaxSteps)
+ {
+ nRequestedSteps = nMaxSteps;
+ }
+
+ return std::max(sal_uInt32(1), nRequestedSteps);
+ }
+
+ ODFGradientInfo createLinearODFGradientInfo(
+ const B2DRange& rTargetArea,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return init1DGradientInfo(
+ rTargetArea,
+ nSteps,
+ fBorder,
+ fAngle,
+ false);
+ }
+
+ ODFGradientInfo createAxialODFGradientInfo(
+ const B2DRange& rTargetArea,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return init1DGradientInfo(
+ rTargetArea,
+ nSteps,
+ fBorder,
+ fAngle,
+ true);
+ }
+
+ ODFGradientInfo createRadialODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder)
+ {
+ return initEllipticalGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ 0.0,
+ true);
+ }
+
+ ODFGradientInfo createEllipticalODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return initEllipticalGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ fAngle,
+ false);
+ }
+
+ ODFGradientInfo createSquareODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return initRectGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ fAngle,
+ true);
+ }
+
+ ODFGradientInfo createRectangularODFGradientInfo(
+ const B2DRange& rTargetArea,
+ const B2DVector& rOffset,
+ sal_uInt32 nSteps,
+ double fBorder,
+ double fAngle)
+ {
+ return initRectGradientInfo(
+ rTargetArea,
+ rOffset,
+ nSteps,
+ fBorder,
+ fAngle,
+ false);
+ }
+
+ double getLinearGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+
+ // Ignore X, this is not needed at all for Y-Oriented gradients
+ // if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
+ // {
+ // return 0.0;
+ // }
+
+ if(aCoor.getY() <= 0.0)
+ {
+ return 0.0; // start value for inside
+ }
+
+ if(aCoor.getY() >= 1.0)
+ {
+ return 1.0; // end value for outside
+ }
+
+ return aCoor.getY();
+ }
+
+ double getAxialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+
+ // Ignore X, this is not needed at all for Y-Oriented gradients
+ //if(aCoor.getX() < 0.0 || aCoor.getX() > 1.0)
+ //{
+ // return 0.0;
+ //}
+
+ const double fAbsY(fabs(aCoor.getY()));
+
+ if(fAbsY >= 1.0)
+ {
+ return 1.0; // use end value when outside in Y
+ }
+
+ return fAbsY;
+ }
+
+ double getRadialGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+
+ if(aCoor.getX() < -1.0 || aCoor.getX() > 1.0 || aCoor.getY() < -1.0 || aCoor.getY() > 1.0)
+ {
+ return 0.0;
+ }
+
+ return 1.0 - std::hypot(aCoor.getX(), aCoor.getY());
+ }
+
+ double getEllipticalGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+
+ if(aCoor.getX() < -1.0 || aCoor.getX() > 1.0 || aCoor.getY() < -1.0 || aCoor.getY() > 1.0)
+ {
+ return 0.0;
+ }
+
+ double fAspectRatio(rGradInfo.getAspectRatio());
+ double t(1.0);
+
+ // MCGR: Similar to getRectangularGradientAlpha (please
+ // see there) we need to use aspect ratio here. Due to
+ // initEllipticalGradientInfo using M_SQRT2 to make this
+ // gradient look 'nicer' this correction seems not 100%
+ // correct, but is close enough for now
+ if(fAspectRatio > 1.0)
+ {
+ t = 1.0 - std::hypot(aCoor.getX() / fAspectRatio, aCoor.getY());
+ }
+ else if(fAspectRatio > 0.0)
+ {
+ t = 1.0 - std::hypot(aCoor.getX(), aCoor.getY() * fAspectRatio);
+ }
+
+ return t;
+ }
+
+ double getSquareGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+ const double fAbsX(fabs(aCoor.getX()));
+
+ if(fAbsX >= 1.0)
+ {
+ return 0.0;
+ }
+
+ const double fAbsY(fabs(aCoor.getY()));
+
+ if(fAbsY >= 1.0)
+ {
+ return 0.0;
+ }
+
+ return 1.0 - std::max(fAbsX, fAbsY);
+ }
+
+ double getRectangularGradientAlpha(const B2DPoint& rUV, const ODFGradientInfo& rGradInfo)
+ {
+ const B2DPoint aCoor(rGradInfo.getBackTextureTransform() * rUV);
+ double fAbsX(fabs(aCoor.getX()));
+
+ if(fAbsX >= 1.0)
+ {
+ return 0.0;
+ }
+
+ double fAbsY(fabs(aCoor.getY()));
+
+ if(fAbsY >= 1.0)
+ {
+ return 0.0;
+ }
+
+ // MCGR: Visualizations using the texturing method for
+ // displaying gradients (getBackTextureTransform is
+ // involved) show wrong results for GradientElliptical
+ // and GradientRect, this can be best seen when using
+ // less steps, e.g. just four. This thus has influence
+ // on cppcanvas (slideshow) and 3D textures, so needs
+ // to be corrected.
+ // Missing is to use the aspect ratio of the object
+ // in this [-1, -1, 1, 1] unified coordinate space
+ // after getBackTextureTransform is applied. Optically
+ // in the larger direction of the texturing the color
+ // step distances are too big *because* we are in that
+ // unit range now.
+ // To correct that, a kind of 'limo stretching' needs to
+ // be applied, adding space around the center
+ // proportional to the aspect ratio, so the intuitive
+ // idea would be to do
+ //
+ // fAbsX' = ((fAspectRatio - 1) + fAbsX) / fAspectRatio
+ //
+ // which scales from the center. This does not work, and
+ // after some thoughts it's clear why: It's not the
+ // position that needs to be moved (this cannot be
+ // changed), but the position *before* that scale has
+ // to be determined to get the correct, shifted color
+ // for the already 'new' position. Thus, turn around
+ // the expression as
+ //
+ // fAbsX' * fAspectRatio = fAspectRatio - 1 + fAbsX
+ // fAbsX' * fAspectRatio - fAspectRatio + 1 = fAbsX
+ // fAbsX = (fAbsX' - 1) * fAspectRatio + 1
+ //
+ // This works and can even be simply adapted for
+ // fAspectRatio < 1.0 aka vertical is bigger.
+ double fAspectRatio(rGradInfo.getAspectRatio());
+ if(fAspectRatio > 1.0)
+ {
+ fAbsX = ((fAbsX - 1) * fAspectRatio) + 1;
+ }
+ else if(fAspectRatio > 0.0)
+ {
+ fAbsY = ((fAbsY - 1) / fAspectRatio) + 1;
+ }
+
+ return 1.0 - std::max(fAbsX, fAbsY);
+ }
+ } // namespace utils
+} // namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/keystoplerp.cxx b/basegfx/source/tools/keystoplerp.cxx
new file mode 100644
index 0000000000..e5d0d76304
--- /dev/null
+++ b/basegfx/source/tools/keystoplerp.cxx
@@ -0,0 +1,90 @@
+/* -*- 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 <basegfx/utils/keystoplerp.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <osl/diagnose.h>
+
+#include <algorithm>
+
+static void validateInput(const std::vector<double>& rKeyStops)
+{
+#ifdef DBG_UTIL
+ OSL_ENSURE( rKeyStops.size() > 1,
+ "KeyStopLerp::KeyStopLerp(): key stop vector must have two entries or more" );
+
+ // rKeyStops must be sorted in ascending order
+ for( std::size_t i=1, len=rKeyStops.size(); i<len; ++i )
+ {
+ if( rKeyStops[i-1] > rKeyStops[i] )
+ OSL_FAIL( "KeyStopLerp::KeyStopLerp(): time vector is not sorted in ascending order!" );
+ }
+#else
+ (void)rKeyStops;
+#endif
+}
+
+namespace basegfx::utils
+{
+ KeyStopLerp::KeyStopLerp( std::vector<double>&& rKeyStops ) :
+ maKeyStops(std::move(rKeyStops)),
+ mnLastIndex(0)
+ {
+ validateInput(maKeyStops);
+ }
+
+ KeyStopLerp::KeyStopLerp( const ::css::uno::Sequence<double>& rKeyStops ) :
+ maKeyStops(rKeyStops.begin(), rKeyStops.end()),
+ mnLastIndex(0)
+ {
+ validateInput(maKeyStops);
+ }
+
+ KeyStopLerp::ResultType KeyStopLerp::lerp(double fAlpha) const
+ {
+ // cached value still okay?
+ if( maKeyStops.at(mnLastIndex) < fAlpha ||
+ maKeyStops.at(mnLastIndex+1) >= fAlpha )
+ {
+ // nope, find new index
+ mnLastIndex = std::min<std::ptrdiff_t>(
+ maKeyStops.size()-2,
+ // range is ensured by max below
+ std::max<std::ptrdiff_t>(
+ 0,
+ std::distance( maKeyStops.begin(),
+ std::lower_bound( maKeyStops.begin(),
+ maKeyStops.end(),
+ fAlpha )) - 1 ));
+ }
+
+ // lerp between stop and stop+1
+ const double fRawLerp=
+ (fAlpha-maKeyStops.at(mnLastIndex)) /
+ (maKeyStops.at(mnLastIndex+1) - maKeyStops.at(mnLastIndex));
+
+ // clamp to permissible range (input fAlpha might be
+ // everything)
+ return ResultType(
+ mnLastIndex,
+ std::clamp(fRawLerp,0.0,1.0));
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/numbertools.cxx b/basegfx/source/tools/numbertools.cxx
new file mode 100644
index 0000000000..4248068b7d
--- /dev/null
+++ b/basegfx/source/tools/numbertools.cxx
@@ -0,0 +1,58 @@
+/* -*- 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/.
+ */
+
+#include <basegfx/utils/tools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/math.hxx>
+
+namespace basegfx::utils
+{
+ B2DPolyPolygon number2PolyPolygon(double fValue, sal_Int32 nTotalDigits, sal_Int32 nDecPlaces, bool bLitSegments)
+ {
+ // config here
+ // {
+ const double fSpace=0.2;
+ // }
+ // config here
+
+ OStringBuffer aNum;
+ rtl::math::doubleToStringBuffer(aNum,
+ fValue,
+ rtl_math_StringFormat_F,
+ nDecPlaces, '.',
+ nullptr, ',');
+
+ B2DPolyPolygon aRes;
+ B2DHomMatrix aMat;
+ double fCurrX=std::max(nTotalDigits-aNum.getLength(),
+ sal_Int32(0)) * (1.0+fSpace);
+ for( sal_Int32 i=0; i<aNum.getLength(); ++i )
+ {
+ B2DPolyPolygon aCurr=createSevenSegmentPolyPolygon(aNum[i],
+ bLitSegments);
+
+ aMat.identity();
+ aMat.translate(fCurrX,0.0);
+ aCurr.transform(aMat);
+
+ fCurrX += 1.0+fSpace;
+
+ aRes.append(aCurr);
+ }
+
+ return aRes;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/stringconversiontools.cxx b/basegfx/source/tools/stringconversiontools.cxx
new file mode 100644
index 0000000000..79b6d60466
--- /dev/null
+++ b/basegfx/source/tools/stringconversiontools.cxx
@@ -0,0 +1,158 @@
+/* -*- 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 <stringconversiontools.hxx>
+#include <rtl/math.hxx>
+
+namespace basegfx::internal
+{
+ void skipSpaces(sal_Int32& io_rPos,
+ std::u16string_view rStr,
+ const sal_Int32 nLen)
+ {
+ while( io_rPos < nLen &&
+ rStr[io_rPos] == ' ' )
+ {
+ ++io_rPos;
+ }
+ }
+
+ static void skipSpacesAndCommas(sal_Int32& io_rPos,
+ std::u16string_view rStr,
+ const sal_Int32 nLen)
+ {
+ while(io_rPos < nLen
+ && (rStr[io_rPos] == ' ' || rStr[io_rPos] == ','))
+ {
+ ++io_rPos;
+ }
+ }
+
+ static bool getDoubleChar(double& o_fRetval,
+ sal_Int32& io_rPos,
+ std::u16string_view rStr)
+ {
+ const sal_Int64 nStrSize = rStr.size();
+ sal_Unicode aChar = io_rPos < nStrSize ? rStr[io_rPos] : 0;
+ const sal_Int32 nStartPos = io_rPos;
+
+ // sign
+ if(aChar == '+' || aChar == '-')
+ {
+ aChar = rStr[++io_rPos];
+ }
+
+ // numbers before point
+ while('0' <= aChar && '9' >= aChar)
+ {
+ io_rPos++;
+ aChar = io_rPos < nStrSize ? rStr[io_rPos] : 0;
+ }
+
+ // point
+ if(aChar == '.')
+ {
+ io_rPos++;
+ aChar = io_rPos < nStrSize ? rStr[io_rPos] : 0;
+ }
+
+ // numbers after point
+ while ('0' <= aChar && '9' >= aChar)
+ {
+ io_rPos++;
+ aChar = io_rPos < nStrSize ? rStr[io_rPos] : 0;
+ }
+
+ // 'e'
+ if(aChar == 'e' || aChar == 'E')
+ {
+ io_rPos++;
+ aChar = io_rPos < nStrSize ? rStr[io_rPos] : 0;
+
+ // sign for 'e'
+ if(aChar == '+' || aChar == '-')
+ {
+ io_rPos++;
+ aChar = io_rPos < nStrSize ? rStr[io_rPos] : 0;
+ }
+
+ // number for 'e'
+ while('0' <= aChar && '9' >= aChar)
+ {
+ io_rPos++;
+ aChar = io_rPos < nStrSize ? rStr[io_rPos] : 0;
+ }
+ }
+
+ const sal_Int32 nLen = io_rPos - nStartPos;
+ if(nLen)
+ {
+ rStr = rStr.substr(nStartPos, nLen);
+ rtl_math_ConversionStatus eStatus;
+ o_fRetval = ::rtl::math::stringToDouble( rStr,
+ '.',
+ ',',
+ &eStatus );
+ return ( eStatus == rtl_math_ConversionStatus_Ok );
+ }
+
+ return false;
+ }
+
+ bool importDoubleAndSpaces(double& o_fRetval,
+ sal_Int32& io_rPos,
+ std::u16string_view rStr,
+ const sal_Int32 nLen )
+ {
+ if( !getDoubleChar(o_fRetval, io_rPos, rStr) )
+ return false;
+
+ skipSpacesAndCommas(io_rPos, rStr, nLen);
+
+ return true;
+ }
+
+ bool importFlagAndSpaces(sal_Int32& o_nRetval,
+ sal_Int32& io_rPos,
+ std::u16string_view rStr,
+ const sal_Int32 nLen)
+ {
+ sal_Unicode aChar( rStr[io_rPos] );
+
+ if(aChar == '0')
+ {
+ o_nRetval = 0;
+ ++io_rPos;
+ }
+ else if (aChar == '1')
+ {
+ o_nRetval = 1;
+ ++io_rPos;
+ }
+ else
+ return false;
+
+ skipSpacesAndCommas(io_rPos, rStr, nLen);
+
+ return true;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/systemdependentdata.cxx b/basegfx/source/tools/systemdependentdata.cxx
new file mode 100644
index 0000000000..0d64d9982c
--- /dev/null
+++ b/basegfx/source/tools/systemdependentdata.cxx
@@ -0,0 +1,166 @@
+/* -*- 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/.
+ */
+
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <config_fuzzers.h>
+#include <math.h>
+
+namespace basegfx
+{
+ SystemDependentDataManager::SystemDependentDataManager()
+ {
+ }
+
+ SystemDependentDataManager::~SystemDependentDataManager()
+ {
+ }
+} // namespace basegfx
+
+namespace basegfx
+{
+ SystemDependentData::SystemDependentData(
+ SystemDependentDataManager& rSystemDependentDataManager)
+ : mrSystemDependentDataManager(rSystemDependentDataManager),
+ mnCalculatedCycles(0)
+ {
+ }
+
+ SystemDependentData::~SystemDependentData()
+ {
+ }
+
+ sal_uInt32 SystemDependentData::calculateCombinedHoldCyclesInSeconds() const
+ {
+#if ENABLE_FUZZERS
+ return 0;
+#endif
+
+ if(0 == mnCalculatedCycles)
+ {
+ const sal_Int64 nBytes(estimateUsageInBytes());
+
+ // tdf#129845 as indicator for no need to buffer trivial data, stay at and
+ // return zero. As border, use 450 bytes. For polygons, this means to buffer
+ // starting with ca. 50 points (GDIPLUS uses 9 bytes per coordinate). For
+ // Bitmap data this means to more or less always buffer (as it was before).
+ // For the future, a more sophisticated differentiation may be added
+ if(nBytes > 450)
+ {
+ // HoldCyclesInSeconds
+ const sal_uInt32 nSeconds = 60;
+
+ // default is Seconds (minimal is one)
+ sal_uInt32 nResult(0 == nSeconds ? 1 : nSeconds);
+
+ if(0 != nBytes)
+ {
+ // use sqrt to get some curved shape. With a default of 60s we get
+ // a single second at 3600 byte. To get close to 10mb, multiply by
+ // a corresponding scaling factor
+ const double fScaleToMB(3600.0 / (1024.0 * 1024.0 * 10.0));
+
+ // also use a multiplier to move the start point higher
+ const double fMultiplierSeconds(10.0);
+
+ // calculate
+ nResult = static_cast<sal_uInt32>((fMultiplierSeconds * nSeconds) / sqrt(nBytes * fScaleToMB));
+
+ // minimal value is 1
+ if(nResult < 1)
+ {
+ nResult = 1;
+ }
+
+ // maximal value is nSeconds
+ if(nResult > nSeconds)
+ {
+ nResult = nSeconds;
+ }
+ }
+
+ // set locally (once, on-demand created, non-zero)
+ const_cast<SystemDependentData*>(this)->mnCalculatedCycles = nResult;
+ }
+ }
+
+ return mnCalculatedCycles;
+ }
+
+ sal_Int64 SystemDependentData::estimateUsageInBytes() const
+ {
+ // default implementation has no idea
+ return 0;
+ }
+} // namespace basegfx
+
+namespace basegfx
+{
+ SystemDependentDataHolder::SystemDependentDataHolder()
+ {
+ }
+
+ SystemDependentDataHolder::~SystemDependentDataHolder()
+ {
+ for(const auto& candidate : maSystemDependentReferences)
+ {
+ basegfx::SystemDependentData_SharedPtr aData(candidate.second.lock());
+
+ if(aData)
+ {
+ aData->getSystemDependentDataManager().endUsage(aData);
+ }
+ }
+ }
+
+ void SystemDependentDataHolder::addOrReplaceSystemDependentData(basegfx::SystemDependentData_SharedPtr& rData)
+ {
+ const size_t hash_code(typeid(*rData).hash_code());
+ auto result(maSystemDependentReferences.find(hash_code));
+
+ if(result != maSystemDependentReferences.end())
+ {
+ basegfx::SystemDependentData_SharedPtr aData(result->second.lock());
+
+ if(aData)
+ {
+ aData->getSystemDependentDataManager().endUsage(aData);
+ }
+
+ maSystemDependentReferences.erase(result);
+ result = maSystemDependentReferences.end();
+ }
+
+ maSystemDependentReferences[hash_code] = rData;
+ rData->getSystemDependentDataManager().startUsage(rData);
+ }
+
+ SystemDependentData_SharedPtr SystemDependentDataHolder::getSystemDependentData(size_t hash_code) const
+ {
+ basegfx::SystemDependentData_SharedPtr aRetval;
+ auto result(maSystemDependentReferences.find(hash_code));
+
+ if(result != maSystemDependentReferences.end())
+ {
+ aRetval = result->second.lock();
+
+ if(aRetval)
+ {
+ aRetval->getSystemDependentDataManager().touchUsage(aRetval);
+ }
+ else
+ {
+ const_cast< SystemDependentDataHolder* >(this)->maSystemDependentReferences.erase(result);
+ }
+ }
+
+ return aRetval;
+ }
+} // namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/tools.cxx b/basegfx/source/tools/tools.cxx
new file mode 100644
index 0000000000..4a7f2c962a
--- /dev/null
+++ b/basegfx/source/tools/tools.cxx
@@ -0,0 +1,111 @@
+/* -*- 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 <basegfx/utils/tools.hxx>
+#include <basegfx/range/b2drange.hxx>
+
+#include <algorithm>
+
+namespace basegfx::utils
+{
+ namespace
+ {
+ double distance( const double& nX,
+ const double& nY,
+ const ::basegfx::B2DVector& rNormal,
+ const double& nC )
+ {
+ return nX*rNormal.getX() + nY*rNormal.getY() - nC;
+ }
+
+ void moveLineOutsideRect( ::basegfx::B2DPoint& io_rStart,
+ ::basegfx::B2DPoint& io_rEnd,
+ const ::basegfx::B2DVector& rMoveDirection,
+ const ::basegfx::B2DRange& rFitTarget )
+ {
+ // calc c for normal line form equation n x - c = 0
+ const double nC( rMoveDirection.scalar( io_rStart ) );
+
+ // calc maximum orthogonal distance for all four bound
+ // rect corners to the line
+ const double nMaxDistance( std::max(
+ 0.0,
+ std::max(
+ distance(rFitTarget.getMinX(),
+ rFitTarget.getMinY(),
+ rMoveDirection,
+ nC),
+ std::max(
+ distance(rFitTarget.getMinX(),
+ rFitTarget.getMaxY(),
+ rMoveDirection,
+ nC),
+ std::max(
+ distance(rFitTarget.getMaxX(),
+ rFitTarget.getMinY(),
+ rMoveDirection,
+ nC),
+ distance(rFitTarget.getMaxX(),
+ rFitTarget.getMaxY(),
+ rMoveDirection,
+ nC) ) ) ) ) );
+
+ // now move line points, such that the bound rect
+ // points are all either 'on' or on the negative side
+ // of the half-plane
+ io_rStart += nMaxDistance*rMoveDirection;
+ io_rEnd += nMaxDistance*rMoveDirection;
+ }
+ }
+
+ void infiniteLineFromParallelogram( ::basegfx::B2DPoint& io_rLeftTop,
+ ::basegfx::B2DPoint& io_rLeftBottom,
+ ::basegfx::B2DPoint& io_rRightTop,
+ ::basegfx::B2DPoint& io_rRightBottom,
+ const ::basegfx::B2DRange& rFitTarget )
+ {
+ // For the top and bottom border line of the
+ // parallelogram, we determine the distance to all four
+ // corner points of the bound rect (tl, tr, bl, br). When
+ // using the unit normal form for lines (n x - c = 0), and
+ // choosing n to point 'outwards' the parallelogram, then
+ // all bound rect corner points having positive distance
+ // to the line lie outside the extended gradient rect, and
+ // thus, the corresponding border line must be moved the
+ // maximum distance outwards.
+
+ // don't use the top and bottom border line direction, and
+ // calculate the normal from them. Instead, use the
+ // vertical lines (lt - lb or rt - rb), as they more
+ // faithfully represent the direction of the
+ // to-be-generated infinite line
+ ::basegfx::B2DVector aDirectionVertical( io_rLeftTop - io_rLeftBottom );
+ aDirectionVertical.normalize();
+
+ const ::basegfx::B2DVector aNormalTop( aDirectionVertical );
+ const ::basegfx::B2DVector aNormalBottom( -aDirectionVertical );
+
+ // now extend parallelogram, such that the bound rect
+ // point are included
+ moveLineOutsideRect( io_rLeftTop, io_rRightTop, aNormalTop, rFitTarget );
+ moveLineOutsideRect( io_rLeftBottom, io_rRightBottom, aNormalBottom, rFitTarget );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/unopolypolygon.cxx b/basegfx/source/tools/unopolypolygon.cxx
new file mode 100644
index 0000000000..41f1497319
--- /dev/null
+++ b/basegfx/source/tools/unopolypolygon.cxx
@@ -0,0 +1,446 @@
+/* -*- 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 <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/utils/unopolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+namespace basegfx::unotools
+{
+ UnoPolyPolygon::UnoPolyPolygon( B2DPolyPolygon aPolyPoly ) :
+ maPolyPoly(std::move( aPolyPoly )),
+ meFillRule( rendering::FillRule_EVEN_ODD )
+ {
+ }
+
+ void SAL_CALL UnoPolyPolygon::addPolyPolygon(
+ const geometry::RealPoint2D& position,
+ const uno::Reference< rendering::XPolyPolygon2D >& polyPolygon )
+ {
+ std::unique_lock const guard( m_aMutex );
+ modifying();
+
+ // TODO(F1): Correctly fulfill the UNO API
+ // specification. This will probably result in a vector of
+ // poly-polygons to be stored in this object.
+
+ const sal_Int32 nPolys( polyPolygon->getNumberOfPolygons() );
+
+ if( !polyPolygon.is() || !nPolys )
+ {
+ // invalid or empty polygon - nothing to do.
+ return;
+ }
+
+ B2DPolyPolygon aSrcPoly;
+ const UnoPolyPolygon* pSrc( dynamic_cast< UnoPolyPolygon* >(polyPolygon.get()) );
+
+ // try to extract polygon data from interface. First,
+ // check whether it's the same implementation object,
+ // which we can tunnel then.
+ if( pSrc )
+ {
+ aSrcPoly = pSrc->getPolyPolygon();
+ }
+ else
+ {
+ // not a known implementation object - try data source
+ // interfaces
+ uno::Reference< rendering::XBezierPolyPolygon2D > xBezierPoly(
+ polyPolygon,
+ uno::UNO_QUERY );
+
+ if( xBezierPoly.is() )
+ {
+ aSrcPoly = unotools::polyPolygonFromBezier2DSequenceSequence(
+ xBezierPoly->getBezierSegments( 0,
+ nPolys,
+ 0,
+ -1 ) );
+ }
+ else
+ {
+ uno::Reference< rendering::XLinePolyPolygon2D > xLinePoly(
+ polyPolygon,
+ uno::UNO_QUERY );
+
+ // no implementation class and no data provider
+ // found - contract violation.
+ if( !xLinePoly.is() )
+ throw lang::IllegalArgumentException(
+ "UnoPolyPolygon::addPolyPolygon(): Invalid input "
+ "poly-polygon, cannot retrieve vertex data",
+ getXWeak(), 1);
+
+ aSrcPoly = unotools::polyPolygonFromPoint2DSequenceSequence(
+ xLinePoly->getPoints( 0,
+ nPolys,
+ 0,
+ -1 ) );
+ }
+ }
+
+ const B2DRange aBounds( utils::getRange( aSrcPoly ) );
+ const B2DVector aOffset( unotools::b2DPointFromRealPoint2D( position ) -
+ aBounds.getMinimum() );
+
+ if( !aOffset.equalZero() )
+ {
+ const B2DHomMatrix aTranslate(utils::createTranslateB2DHomMatrix(aOffset));
+ aSrcPoly.transform( aTranslate );
+ }
+
+ maPolyPoly.append( aSrcPoly );
+ }
+
+ sal_Int32 SAL_CALL UnoPolyPolygon::getNumberOfPolygons()
+ {
+ std::unique_lock const guard( m_aMutex );
+ return maPolyPoly.count();
+ }
+
+ sal_Int32 SAL_CALL UnoPolyPolygon::getNumberOfPolygonPoints(
+ sal_Int32 polygon )
+ {
+ std::unique_lock const guard( m_aMutex );
+ checkIndex( polygon );
+
+ return maPolyPoly.getB2DPolygon(polygon).count();
+ }
+
+ rendering::FillRule SAL_CALL UnoPolyPolygon::getFillRule()
+ {
+ std::unique_lock const guard( m_aMutex );
+ return meFillRule;
+ }
+
+ void SAL_CALL UnoPolyPolygon::setFillRule(
+ rendering::FillRule fillRule )
+ {
+ std::unique_lock const guard( m_aMutex );
+ modifying();
+
+ meFillRule = fillRule;
+ }
+
+ sal_Bool SAL_CALL UnoPolyPolygon::isClosed(
+ sal_Int32 index )
+ {
+ std::unique_lock const guard( m_aMutex );
+ checkIndex( index );
+
+ return maPolyPoly.getB2DPolygon(index).isClosed();
+ }
+
+ void SAL_CALL UnoPolyPolygon::setClosed(
+ sal_Int32 index,
+ sal_Bool closedState )
+ {
+ std::unique_lock const guard( m_aMutex );
+ modifying();
+
+ if( index == -1 )
+ {
+ // set all
+ maPolyPoly.setClosed( closedState );
+ }
+ else
+ {
+ checkIndex( index );
+
+ // fetch referenced polygon, change state
+ B2DPolygon aTmp( maPolyPoly.getB2DPolygon(index) );
+ aTmp.setClosed( closedState );
+
+ // set back to container
+ maPolyPoly.setB2DPolygon( index, aTmp );
+ }
+ }
+
+ uno::Sequence< uno::Sequence< geometry::RealPoint2D > > SAL_CALL UnoPolyPolygon::getPoints(
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nNumberOfPolygons,
+ sal_Int32 nPointIndex,
+ sal_Int32 nNumberOfPoints )
+ {
+ return unotools::pointSequenceSequenceFromB2DPolyPolygon(
+ getSubsetPolyPolygon( nPolygonIndex,
+ nNumberOfPolygons,
+ nPointIndex,
+ nNumberOfPoints ) );
+ }
+
+ void SAL_CALL UnoPolyPolygon::setPoints(
+ const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points,
+ sal_Int32 nPolygonIndex )
+ {
+ std::unique_lock const guard( m_aMutex );
+ modifying();
+
+ const B2DPolyPolygon& rNewPolyPoly(
+ unotools::polyPolygonFromPoint2DSequenceSequence( points ) );
+
+ if( nPolygonIndex == -1 )
+ {
+ maPolyPoly = rNewPolyPoly;
+ }
+ else
+ {
+ checkIndex( nPolygonIndex );
+
+ maPolyPoly.insert( nPolygonIndex, rNewPolyPoly );
+ }
+ }
+
+ geometry::RealPoint2D SAL_CALL UnoPolyPolygon::getPoint(
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nPointIndex )
+ {
+ std::unique_lock const guard( m_aMutex );
+ checkIndex( nPolygonIndex );
+
+ const B2DPolygon& rPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) );
+
+ if( nPointIndex < 0 || o3tl::make_unsigned(nPointIndex) >= rPoly.count() )
+ throw lang::IndexOutOfBoundsException();
+
+ return unotools::point2DFromB2DPoint( rPoly.getB2DPoint( nPointIndex ) );
+ }
+
+ void SAL_CALL UnoPolyPolygon::setPoint(
+ const geometry::RealPoint2D& point,
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nPointIndex )
+ {
+ std::unique_lock const guard( m_aMutex );
+ checkIndex( nPolygonIndex );
+ modifying();
+
+ B2DPolygon aPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) );
+
+ if( nPointIndex < 0 || o3tl::make_unsigned(nPointIndex) >= aPoly.count() )
+ throw lang::IndexOutOfBoundsException();
+
+ aPoly.setB2DPoint( nPointIndex,
+ unotools::b2DPointFromRealPoint2D( point ) );
+ maPolyPoly.setB2DPolygon( nPolygonIndex, aPoly );
+ }
+
+ uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > > SAL_CALL UnoPolyPolygon::getBezierSegments(
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nNumberOfPolygons,
+ sal_Int32 nPointIndex,
+ sal_Int32 nNumberOfPoints )
+ {
+ return unotools::bezierSequenceSequenceFromB2DPolyPolygon(
+ getSubsetPolyPolygon( nPolygonIndex,
+ nNumberOfPolygons,
+ nPointIndex,
+ nNumberOfPoints ) );
+ }
+
+ void SAL_CALL UnoPolyPolygon::setBezierSegments(
+ const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& points,
+ sal_Int32 nPolygonIndex )
+ {
+ std::unique_lock const guard( m_aMutex );
+ modifying();
+ const B2DPolyPolygon& rNewPolyPoly(
+ unotools::polyPolygonFromBezier2DSequenceSequence( points ) );
+
+ if( nPolygonIndex == -1 )
+ {
+ maPolyPoly = rNewPolyPoly;
+ }
+ else
+ {
+ checkIndex( nPolygonIndex );
+
+ maPolyPoly.insert( nPolygonIndex, rNewPolyPoly );
+ }
+ }
+
+ geometry::RealBezierSegment2D SAL_CALL UnoPolyPolygon::getBezierSegment( sal_Int32 nPolygonIndex,
+ sal_Int32 nPointIndex )
+ {
+ std::unique_lock const guard( m_aMutex );
+ checkIndex( nPolygonIndex );
+
+ const B2DPolygon& rPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) );
+ const sal_uInt32 nPointCount(rPoly.count());
+
+ if( nPointIndex < 0 || o3tl::make_unsigned(nPointIndex) >= nPointCount )
+ throw lang::IndexOutOfBoundsException();
+
+ const B2DPoint& rPt( rPoly.getB2DPoint( nPointIndex ) );
+ const B2DPoint& rCtrl0( rPoly.getNextControlPoint(nPointIndex) );
+ const B2DPoint& rCtrl1( rPoly.getPrevControlPoint((nPointIndex + 1) % nPointCount) );
+
+ return geometry::RealBezierSegment2D( rPt.getX(),
+ rPt.getY(),
+ rCtrl0.getX(),
+ rCtrl0.getY(),
+ rCtrl1.getX(),
+ rCtrl1.getY() );
+ }
+
+ void SAL_CALL UnoPolyPolygon::setBezierSegment( const geometry::RealBezierSegment2D& segment,
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nPointIndex )
+ {
+ std::unique_lock const guard( m_aMutex );
+ checkIndex( nPolygonIndex );
+ modifying();
+
+ B2DPolygon aPoly( maPolyPoly.getB2DPolygon( nPolygonIndex ) );
+ const sal_uInt32 nPointCount(aPoly.count());
+
+ if( nPointIndex < 0 || o3tl::make_unsigned(nPointIndex) >= nPointCount )
+ throw lang::IndexOutOfBoundsException();
+
+ aPoly.setB2DPoint( nPointIndex,
+ B2DPoint( segment.Px,
+ segment.Py ) );
+ aPoly.setNextControlPoint(nPointIndex,
+ B2DPoint(segment.C1x, segment.C1y));
+ aPoly.setPrevControlPoint((nPointIndex + 1) % nPointCount,
+ B2DPoint(segment.C2x, segment.C2y));
+
+ maPolyPoly.setB2DPolygon( nPolygonIndex, aPoly );
+ }
+
+ B2DPolyPolygon UnoPolyPolygon::getSubsetPolyPolygon(
+ sal_Int32 nPolygonIndex,
+ sal_Int32 nNumberOfPolygons,
+ sal_Int32 nPointIndex,
+ sal_Int32 nNumberOfPoints ) const
+ {
+ std::unique_lock const guard( m_aMutex );
+ checkIndex( nPolygonIndex );
+
+ const sal_Int32 nPolyCount( maPolyPoly.count() );
+
+ // check for "full polygon" case
+ if( !nPolygonIndex &&
+ !nPointIndex &&
+ nNumberOfPolygons == nPolyCount &&
+ nNumberOfPoints == -1 )
+ {
+ return maPolyPoly;
+ }
+
+ B2DPolyPolygon aSubsetPoly;
+
+ // create temporary polygon (as an extract from maPoly,
+ // which contains the requested subset)
+ for( sal_Int32 i=nPolygonIndex; i<nNumberOfPolygons; ++i )
+ {
+ checkIndex(i);
+
+ const B2DPolygon& rCurrPoly( maPolyPoly.getB2DPolygon(i) );
+
+ sal_Int32 nFirstPoint(0);
+ sal_Int32 nLastPoint(nPolyCount-1);
+
+ if( nPointIndex && i==nPolygonIndex )
+ {
+ // very first polygon - respect nPointIndex, if
+ // not zero
+
+ // empty polygon - impossible to specify _any_
+ // legal value except 0 here!
+ if( !nPolyCount)
+ throw lang::IndexOutOfBoundsException();
+
+ nFirstPoint = nPointIndex;
+ }
+
+ if( i==nNumberOfPolygons-1 && nNumberOfPoints != -1 )
+ {
+ // very last polygon - respect nNumberOfPoints
+
+ // empty polygon - impossible to specify _any_
+ // legal value except -1 here!
+ if( !nPolyCount )
+ throw lang::IndexOutOfBoundsException();
+
+ nLastPoint = nFirstPoint+nNumberOfPoints;
+ }
+
+ if( !nPolyCount )
+ {
+ // empty polygon - index checks already performed
+ // above, now simply append empty polygon
+ aSubsetPoly.append( rCurrPoly );
+ }
+ else
+ {
+ if( nFirstPoint < 0 || nFirstPoint >= nPolyCount )
+ throw lang::IndexOutOfBoundsException();
+
+ if( nLastPoint < 0 || nLastPoint >= nPolyCount )
+ throw lang::IndexOutOfBoundsException();
+
+ B2DPolygon aTmp;
+ for( sal_Int32 j=nFirstPoint; j<nLastPoint; ++j )
+ aTmp.append( rCurrPoly.getB2DPoint(j) );
+
+ aSubsetPoly.append( aTmp );
+ }
+ }
+
+ return aSubsetPoly;
+ }
+
+ OUString SAL_CALL UnoPolyPolygon::getImplementationName()
+ {
+ return "gfx::internal::UnoPolyPolygon";
+ }
+
+ sal_Bool SAL_CALL UnoPolyPolygon::supportsService( const OUString& ServiceName )
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ uno::Sequence< OUString > SAL_CALL UnoPolyPolygon::getSupportedServiceNames()
+ {
+ return { "com.sun.star.rendering.PolyPolygon2D" };
+ }
+
+ B2DPolyPolygon UnoPolyPolygon::getPolyPolygon() const
+ {
+ std::unique_lock const guard( m_aMutex );
+
+ return maPolyPoly;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/zoomtools.cxx b/basegfx/source/tools/zoomtools.cxx
new file mode 100644
index 0000000000..dd4c7a6cbb
--- /dev/null
+++ b/basegfx/source/tools/zoomtools.cxx
@@ -0,0 +1,113 @@
+/* -*- 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/.
+ */
+
+#include <basegfx/utils/zoomtools.hxx>
+
+namespace basegfx::zoomtools
+{
+
+/** 2^(1/6) as the default step
+
+ This ensures (unless the rounding is used) that 6 steps lead
+ to double / half zoom level.
+*/
+const double ZOOM_FACTOR = 1.12246205;
+
+/**
+* Round a value against a specified multiple. Values below half
+* of the multiple are rounded down and all others are rounded up.
+*
+* @param nCurrent current value
+* @param nMultiple multiple against which the current value is rounded
+*/
+static sal_uInt16 roundMultiple(sal_uInt16 nCurrent, int nMultiple)
+{
+ // round zoom to a multiple of nMultiple
+ return (( nCurrent + nMultiple / 2 ) - ( nCurrent + nMultiple / 2 ) % nMultiple);
+}
+
+/**
+* Convert geometric progression results into more common values by
+* rounding them against certain multiples depending on the size.
+* Beginning with 50 the multiple is 5, with 100, 10, and so on.
+*
+* @param nCurrent current zoom factor
+*/
+static sal_uInt16 roundZoom(double nCurrent)
+{
+ // convert nCurrent properly to int
+ sal_uInt16 nNew = nCurrent + 0.5;
+
+ // round to more common numbers above 50
+ if (nNew > 1000) {
+ nNew = roundMultiple(nNew, 100);
+ } else if ( nNew > 500 ) {
+ nNew = roundMultiple(nNew, 50);
+ } else if ( nNew > 100 ) {
+ nNew = roundMultiple(nNew, 10);
+ } else if ( nNew > 50 ) {
+ nNew = roundMultiple(nNew, 5);
+ }
+
+ return nNew;
+}
+
+/**
+* Make sure that a certain step isn't skipped during the zooming
+* progress.
+*
+* @param nCurrent current zoom factor
+* @param nPrevious previous zoom factor
+* @param nStep step which shouldn't be skipped
+*/
+static sal_uInt16 enforceStep(sal_uInt16 nCurrent, sal_uInt16 nPrevious, unsigned int nStep)
+{
+ if ((( nCurrent > nStep ) && ( nPrevious < nStep ))
+ || (( nCurrent < nStep ) && ( nPrevious > nStep )))
+ return nStep;
+ else
+ return nCurrent;
+}
+
+/**
+* Increasing the zoom level.
+*
+* @param nCurrent current zoom factor
+*/
+sal_uInt16 zoomIn(sal_uInt16 nCurrent)
+{
+ sal_uInt16 nNew = roundZoom( nCurrent * ZOOM_FACTOR );
+ // make sure some values are not skipped
+ nNew = enforceStep(nNew, nCurrent, 200);
+ nNew = enforceStep(nNew, nCurrent, 100);
+ nNew = enforceStep(nNew, nCurrent, 75);
+ nNew = enforceStep(nNew, nCurrent, 50);
+ nNew = enforceStep(nNew, nCurrent, 25);
+ return nNew;
+}
+
+/**
+* Decreasing the zoom level.
+*
+* @param nCurrent current zoom factor
+*/
+sal_uInt16 zoomOut(sal_uInt16 nCurrent)
+{
+ sal_uInt16 nNew = roundZoom( nCurrent / ZOOM_FACTOR );
+ // make sure some values are not skipped
+ nNew = enforceStep(nNew, nCurrent, 200);
+ nNew = enforceStep(nNew, nCurrent, 100);
+ nNew = enforceStep(nNew, nCurrent, 75);
+ nNew = enforceStep(nNew, nCurrent, 50);
+ nNew = enforceStep(nNew, nCurrent, 25);
+ return nNew;
+}
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tuple/b2dtuple.cxx b/basegfx/source/tuple/b2dtuple.cxx
new file mode 100644
index 0000000000..18a93f8d73
--- /dev/null
+++ b/basegfx/source/tuple/b2dtuple.cxx
@@ -0,0 +1,43 @@
+/* -*- 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 <basegfx/tuple/b2dtuple.hxx>
+#include <basegfx/tuple/b2ituple.hxx>
+#include <basegfx/numeric/ftools.hxx>
+
+namespace basegfx
+{
+ const B2DTuple& B2DTuple::getEmptyTuple()
+ {
+ static B2DTuple const singleton;
+ return singleton;
+ }
+
+ B2DTuple::B2DTuple(const B2ITuple& rTup)
+ : Tuple2D(rTup.getX(), rTup.getY())
+ {}
+
+ B2ITuple fround(const B2DTuple& rTup)
+ {
+ return B2ITuple(fround(rTup.getX()), fround(rTup.getY()));
+ }
+
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tuple/b3dtuple.cxx b/basegfx/source/tuple/b3dtuple.cxx
new file mode 100644
index 0000000000..73e9fd042a
--- /dev/null
+++ b/basegfx/source/tuple/b3dtuple.cxx
@@ -0,0 +1,37 @@
+/* -*- 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 <basegfx/tuple/b3dtuple.hxx>
+#include <basegfx/tuple/b3ituple.hxx>
+
+namespace basegfx
+{
+ const B3DTuple& B3DTuple::getEmptyTuple()
+ {
+ static B3DTuple const singleton;
+ return singleton;
+ }
+
+ B3ITuple fround(const B3DTuple& rTup)
+ {
+ return B3ITuple(fround(rTup.getX()), fround(rTup.getY()), fround(rTup.getZ()));
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/vector/b2dvector.cxx b/basegfx/source/vector/b2dvector.cxx
new file mode 100644
index 0000000000..85ec6ca811
--- /dev/null
+++ b/basegfx/source/vector/b2dvector.cxx
@@ -0,0 +1,184 @@
+/* -*- 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 <basegfx/vector/b2dvector.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+
+namespace basegfx
+{
+ B2DVector& B2DVector::normalize()
+ {
+ double fLen(scalar(*this));
+
+ if(fTools::equalZero(fLen))
+ {
+ mnX = 0.0;
+ mnY = 0.0;
+ }
+ else
+ {
+ const double fOne(1.0);
+
+ if(!fTools::equal(fOne, fLen))
+ {
+ fLen = sqrt(fLen);
+
+ if(!fTools::equalZero(fLen))
+ {
+ mnX /= fLen;
+ mnY /= fLen;
+ }
+ }
+ }
+
+ return *this;
+ }
+
+ double B2DVector::getLength() const
+ {
+ if(fTools::equalZero(mnX))
+ {
+ return fabs(mnY);
+ }
+ else if(fTools::equalZero(mnY))
+ {
+ return fabs(mnX);
+ }
+
+ return hypot( mnX, mnY );
+ }
+
+ double B2DVector::angle( const B2DVector& rVec ) const
+ {
+ return atan2(mnX * rVec.getY() - mnY * rVec.getX(),
+ mnX * rVec.getX() + mnY * rVec.getY());
+ }
+
+ const B2DVector& B2DVector::getEmptyVector()
+ {
+ return static_cast<const B2DVector&>( B2DTuple::getEmptyTuple() );
+ }
+
+ B2DVector& B2DVector::operator*=( const B2DHomMatrix& rMat )
+ {
+ const double fTempX( rMat.get(0,0)*mnX +
+ rMat.get(0,1)*mnY );
+ const double fTempY( rMat.get(1,0)*mnX +
+ rMat.get(1,1)*mnY );
+ mnX = fTempX;
+ mnY = fTempY;
+
+ return *this;
+ }
+
+ B2DVector& B2DVector::setLength(double fLen)
+ {
+ double fLenNow(scalar(*this));
+
+ if(!fTools::equalZero(fLenNow))
+ {
+ const double fOne(1.0);
+
+ if(!fTools::equal(fOne, fLenNow))
+ {
+ fLen /= sqrt(fLenNow);
+ }
+
+ mnX *= fLen;
+ mnY *= fLen;
+ }
+
+ return *this;
+ }
+
+ bool areParallel( const B2DVector& rVecA, const B2DVector& rVecB )
+ {
+ const double fValA(rVecA.getX() * rVecB.getY());
+ const double fValB(rVecA.getY() * rVecB.getX());
+
+ return fTools::equal(fValA, fValB);
+ }
+
+ B2VectorOrientation getOrientation( const B2DVector& rVecA, const B2DVector& rVecB )
+ {
+ double fVal(rVecA.getX() * rVecB.getY() - rVecA.getY() * rVecB.getX());
+
+ if(fTools::equalZero(fVal))
+ {
+ return B2VectorOrientation::Neutral;
+ }
+
+ if(fVal > 0.0)
+ {
+ return B2VectorOrientation::Positive;
+ }
+ else
+ {
+ return B2VectorOrientation::Negative;
+ }
+ }
+
+ B2DVector getPerpendicular( const B2DVector& rNormalizedVec )
+ {
+ B2DVector aPerpendicular(-rNormalizedVec.getY(), rNormalizedVec.getX());
+ return aPerpendicular;
+ }
+
+ B2DVector getNormalizedPerpendicular( const B2DVector& rVec )
+ {
+ B2DVector aPerpendicular(rVec);
+ aPerpendicular.normalize();
+ const double aTemp(-aPerpendicular.getY());
+ aPerpendicular.setY(aPerpendicular.getX());
+ aPerpendicular.setX(aTemp);
+ return aPerpendicular;
+ }
+
+ B2DVector operator*( const B2DHomMatrix& rMat, const B2DVector& rVec )
+ {
+ B2DVector aRes( rVec );
+ aRes *= rMat;
+ return aRes;
+ }
+
+ B2VectorContinuity getContinuity(const B2DVector& rBackVector, const B2DVector& rForwardVector )
+ {
+ if(rBackVector.equalZero() || rForwardVector.equalZero())
+ {
+ return B2VectorContinuity::NONE;
+ }
+
+ if(fTools::equal(rBackVector.getX(), -rForwardVector.getX()) && fTools::equal(rBackVector.getY(), -rForwardVector.getY()))
+ {
+ // same direction and same length -> C2
+ return B2VectorContinuity::C2;
+ }
+
+ if(areParallel(rBackVector, rForwardVector) && rBackVector.scalar(rForwardVector) < 0.0)
+ {
+ // parallel and opposite direction -> C1
+ return B2VectorContinuity::C1;
+ }
+
+ return B2VectorContinuity::NONE;
+ }
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/vector/b2ivector.cxx b/basegfx/source/vector/b2ivector.cxx
new file mode 100644
index 0000000000..5b45871a39
--- /dev/null
+++ b/basegfx/source/vector/b2ivector.cxx
@@ -0,0 +1,65 @@
+/* -*- 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 <basegfx/vector/b2ivector.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+
+namespace basegfx
+{
+ B2IVector& B2IVector::operator=( const ::basegfx::B2ITuple& rVec )
+ {
+ mnX = rVec.getX();
+ mnY = rVec.getY();
+ return *this;
+ }
+
+ B2IVector& B2IVector::operator*=( const B2DHomMatrix& rMat )
+ {
+ mnX = fround( rMat.get(0,0)*mnX +
+ rMat.get(0,1)*mnY );
+ mnY = fround( rMat.get(1,0)*mnX +
+ rMat.get(1,1)*mnY );
+
+ return *this;
+ }
+
+ B2IVector& B2IVector::setLength(double fLen)
+ {
+ double fLenNow(scalar(*this));
+
+ if(!::basegfx::fTools::equalZero(fLenNow))
+ {
+ const double fOne(1.0);
+
+ if(!::basegfx::fTools::equal(fOne, fLenNow))
+ {
+ fLen /= sqrt(fLenNow);
+ }
+
+ mnX = fround( mnX*fLen );
+ mnY = fround( mnY*fLen );
+ }
+
+ return *this;
+ }
+
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/vector/b3dvector.cxx b/basegfx/source/vector/b3dvector.cxx
new file mode 100644
index 0000000000..89bc33c2bd
--- /dev/null
+++ b/basegfx/source/vector/b3dvector.cxx
@@ -0,0 +1,86 @@
+/* -*- 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 <basegfx/vector/b3dvector.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+
+namespace basegfx
+{
+ B3DVector& B3DVector::normalize()
+ {
+ double fLen(scalar(*this));
+
+ if(!::basegfx::fTools::equalZero(fLen))
+ {
+ const double fOne(1.0);
+
+ if(!::basegfx::fTools::equal(fOne, fLen))
+ {
+ fLen = sqrt(fLen);
+
+ mnX /= fLen;
+ mnY /= fLen;
+ mnZ /= fLen;
+ }
+ }
+
+ return *this;
+ }
+
+ B3DVector B3DVector::getPerpendicular(const B3DVector& rNormalizedVec) const
+ {
+ B3DVector aNew = cross(*this, rNormalizedVec);
+ aNew.normalize();
+ return aNew;
+ }
+
+ B3DVector& B3DVector::operator*=( const ::basegfx::B3DHomMatrix& rMat )
+ {
+ const double fTempX( rMat.get(0,0)*mnX + rMat.get(0,1)*mnY + rMat.get(0,2)*mnZ );
+ const double fTempY( rMat.get(1,0)*mnX + rMat.get(1,1)*mnY + rMat.get(1,2)*mnZ );
+ const double fTempZ( rMat.get(2,0)*mnX + rMat.get(2,1)*mnY + rMat.get(2,2)*mnZ );
+ mnX = fTempX;
+ mnY = fTempY;
+ mnZ = fTempZ;
+
+ return *this;
+ }
+
+ B3DVector operator*( const ::basegfx::B3DHomMatrix& rMat, const B3DVector& rVec )
+ {
+ B3DVector aRes( rVec );
+ aRes *= rMat;
+ return aRes;
+ }
+
+ bool areParallel( const B3DVector& rVecA, const B3DVector& rVecB )
+ {
+ // i think fastest is to compare relations, need no square or division
+ if(!fTools::equal(rVecA.getX() * rVecB.getY(), rVecA.getY() * rVecB.getX()))
+ return false;
+
+ if(!fTools::equal(rVecA.getX() * rVecB.getZ(), rVecA.getZ() * rVecB.getX()))
+ return false;
+
+ return fTools::equal(rVecA.getY() * rVecB.getZ(), rVecA.getZ() * rVecB.getY());
+ }
+
+} // end of namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/workbench/Makefile b/basegfx/source/workbench/Makefile
new file mode 100644
index 0000000000..6218141da1
--- /dev/null
+++ b/basegfx/source/workbench/Makefile
@@ -0,0 +1,23 @@
+#
+# 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 .
+#
+
+prog : bezierclip.cxx convexhull.cxx
+ g++ -I. -Wall -g bezierclip.cxx convexhull.cxx -o bezierclip
+
+.cxx.o:
+ g++ -c $(LOCALDEFINES) $(CCFLAGS) $<
diff --git a/basegfx/source/workbench/bezierclip.cxx b/basegfx/source/workbench/bezierclip.cxx
new file mode 100644
index 0000000000..676f239efd
--- /dev/null
+++ b/basegfx/source/workbench/bezierclip.cxx
@@ -0,0 +1,2006 @@
+/* -*- 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 <iostream>
+#include <cassert>
+#include <algorithm>
+#include <iterator>
+#include <vector>
+#include <utility>
+
+#include <math.h>
+
+#include <bezierclip.hxx>
+#include <gauss.hxx>
+
+// what to test
+#define WITH_ASSERTIONS
+//#define WITH_CONVEXHULL_TEST
+//#define WITH_MULTISUBDIVIDE_TEST
+//#define WITH_FATLINE_TEST
+//#define WITH_CALCFOCUS_TEST
+//#define WITH_SAFEPARAMBASE_TEST
+//#define WITH_SAFEPARAMS_TEST
+//#define WITH_SAFEPARAM_DETAILED_TEST
+//#define WITH_SAFEFOCUSPARAM_CALCFOCUS
+//#define WITH_SAFEFOCUSPARAM_TEST
+//#define WITH_SAFEFOCUSPARAM_DETAILED_TEST
+#define WITH_BEZIERCLIP_TEST
+
+/* Implementation of the so-called 'Fat-Line Bezier Clipping Algorithm' by Sederberg et al.
+ *
+ * Actual reference is: T. W. Sederberg and T Nishita: Curve
+ * intersection using Bezier clipping. In Computer Aided Design, 22
+ * (9), 1990, pp. 538--549
+ */
+
+/* Misc helper
+ * ===========
+ */
+int fallFac( int n, int k )
+{
+#ifdef WITH_ASSERTIONS
+ assert(n>=k); // "For factorials, n must be greater or equal k"
+ assert(n>=0); // "For factorials, n must be positive"
+ assert(k>=0); // "For factorials, k must be positive"
+#endif
+
+ int res( 1 );
+
+ while( k-- && n ) res *= n--;
+
+ return res;
+}
+
+int fac( int n )
+{
+ return fallFac(n, n);
+}
+
+/* Bezier fat line clipping part
+ * =============================
+ */
+
+void Impl_calcFatLine( FatLine& line, const Bezier& c )
+{
+ // Prepare normalized implicit line
+ // ================================
+
+ // calculate vector orthogonal to p1-p4:
+ line.a = -(c.p0.y - c.p3.y);
+ line.b = (c.p0.x - c.p3.x);
+
+ // normalize
+ const double len(std::hypot(line.a, line.b));
+ if( !tolZero(len) )
+ {
+ line.a /= len;
+ line.b /= len;
+ }
+
+ line.c = -(line.a*c.p0.x + line.b*c.p0.y);
+
+ // Determine bounding fat line from it
+ // ===================================
+
+ // calc control point distances
+ const double dP2( calcLineDistance(line.a, line.b, line.c, c.p1.x, c.p1.y ) );
+ const double dP3( calcLineDistance(line.a, line.b, line.c, c.p2.x, c.p2.y ) );
+
+ // calc approximate bounding lines to curve (tight bounds are
+ // possible here, but more expensive to calculate and thus not
+ // worth the overhead)
+ if( dP2 * dP3 > 0.0 )
+ {
+ line.dMin = 3.0/4.0 * std::min(0.0, std::min(dP2, dP3));
+ line.dMax = 3.0/4.0 * std::max(0.0, std::max(dP2, dP3));
+ }
+ else
+ {
+ line.dMin = 4.0/9.0 * std::min(0.0, std::min(dP2, dP3));
+ line.dMax = 4.0/9.0 * std::max(0.0, std::max(dP2, dP3));
+ }
+}
+
+void Impl_calcBounds( Point2D& leftTop,
+ Point2D& rightBottom,
+ const Bezier& c1 )
+{
+ leftTop.x = std::min( c1.p0.x, std::min( c1.p1.x, std::min( c1.p2.x, c1.p3.x ) ) );
+ leftTop.y = std::min( c1.p0.y, std::min( c1.p1.y, std::min( c1.p2.y, c1.p3.y ) ) );
+ rightBottom.x = std::max( c1.p0.x, std::max( c1.p1.x, std::max( c1.p2.x, c1.p3.x ) ) );
+ rightBottom.y = std::max( c1.p0.y, std::max( c1.p1.y, std::max( c1.p2.y, c1.p3.y ) ) );
+}
+
+bool Impl_doBBoxIntersect( const Bezier& c1,
+ const Bezier& c2 )
+{
+ // calc rectangular boxes from c1 and c2
+ Point2D lt1;
+ Point2D rb1;
+ Point2D lt2;
+ Point2D rb2;
+
+ Impl_calcBounds( lt1, rb1, c1 );
+ Impl_calcBounds( lt2, rb2, c2 );
+
+ if( std::min(rb1.x, rb2.x) < std::max(lt1.x, lt2.x) ||
+ std::min(rb1.y, rb2.y) < std::max(lt1.y, lt2.y) )
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+/* calculates two t's for the given bernstein control polygon: the first is
+ * the intersection of the min value line with the convex hull from
+ * the left, the second is the intersection of the max value line with
+ * the convex hull from the right.
+ */
+bool Impl_calcSafeParams( double& t1,
+ double& t2,
+ const Polygon2D& rPoly,
+ double lowerYBound,
+ double upperYBound )
+{
+ // need the convex hull of the control polygon, as this is
+ // guaranteed to completely bound the curve
+ Polygon2D convHull( convexHull(rPoly) );
+
+ // init min and max buffers
+ t1 = 0.0 ;
+ double currLowerT( 1.0 );
+
+ t2 = 1.0;
+ double currHigherT( 0.0 );
+
+ if( convHull.size() <= 1 )
+ return false; // only one point? Then we're done with clipping
+
+ /* now, clip against lower and higher bounds */
+ Point2D p0;
+ Point2D p1;
+
+ bool bIntersection( false );
+
+ for( Polygon2D::size_type i=0; i<convHull.size(); ++i )
+ {
+ // have to check against convHull.size() segments, as the
+ // convex hull is, by definition, closed. Thus, for the
+ // last point, we take the first point as partner.
+ if( i+1 == convHull.size() )
+ {
+ // close the polygon
+ p0 = convHull[i];
+ p1 = convHull[0];
+ }
+ else
+ {
+ p0 = convHull[i];
+ p1 = convHull[i+1];
+ }
+
+ // is the segment in question within or crossing the
+ // horizontal band spanned by lowerYBound and upperYBound? If
+ // not, we've got no intersection. If yes, we maybe don't have
+ // an intersection, but we've got to update the permissible
+ // range, nevertheless. This is because inside lying segments
+ // leads to full range forbidden.
+ if( (tolLessEqual(p0.y, upperYBound) || tolLessEqual(p1.y, upperYBound)) &&
+ (tolGreaterEqual(p0.y, lowerYBound) || tolGreaterEqual(p1.y, lowerYBound)) )
+ {
+ // calc intersection of convex hull segment with
+ // one of the horizontal bounds lines
+ // to optimize a bit, r_x is calculated only in else case
+ const double r_y( p1.y - p0.y );
+
+ if( tolZero(r_y) )
+ {
+ // r_y is virtually zero, thus we've got a horizontal
+ // line. Now check whether we maybe coincide with lower or
+ // upper horizontal bound line.
+ if( tolEqual(p0.y, lowerYBound) ||
+ tolEqual(p0.y, upperYBound) )
+ {
+ // yes, simulate intersection then
+ currLowerT = std::min(currLowerT, std::min(p0.x, p1.x));
+ currHigherT = std::max(currHigherT, std::max(p0.x, p1.x));
+ }
+ }
+ else
+ {
+ // check against lower and higher bounds
+ // =====================================
+ const double r_x( p1.x - p0.x );
+
+ // calc intersection with horizontal dMin line
+ const double currTLow( (lowerYBound - p0.y) * r_x / r_y + p0.x );
+
+ // calc intersection with horizontal dMax line
+ const double currTHigh( (upperYBound - p0.y) * r_x / r_y + p0.x );
+
+ currLowerT = std::min(currLowerT, std::min(currTLow, currTHigh));
+ currHigherT = std::max(currHigherT, std::max(currTLow, currTHigh));
+ }
+
+ // set flag that at least one segment is contained or
+ // intersects given horizontal band.
+ bIntersection = true;
+ }
+ }
+
+#ifndef WITH_SAFEPARAMBASE_TEST
+ // limit intersections found to permissible t parameter range
+ t1 = std::max(0.0, currLowerT);
+ t2 = std::min(1.0, currHigherT);
+#endif
+
+ return bIntersection;
+}
+
+/* calculates two t's for the given bernstein polynomial: the first is
+ * the intersection of the min value line with the convex hull from
+ * the left, the second is the intersection of the max value line with
+ * the convex hull from the right.
+ *
+ * The polynomial coefficients c0 to c3 given to this method
+ * must correspond to t values of 0, 1/3, 2/3 and 1, respectively.
+ */
+bool Impl_calcSafeParams_clip( double& t1,
+ double& t2,
+ const FatLine& bounds,
+ double c0,
+ double c1,
+ double c2,
+ double c3 )
+{
+ /* first of all, determine convex hull of c0-c3 */
+ Polygon2D poly(4);
+ poly[0] = Point2D(0, c0);
+ poly[1] = Point2D(1.0/3.0, c1);
+ poly[2] = Point2D(2.0/3.0, c2);
+ poly[3] = Point2D(1, c3);
+
+#ifndef WITH_SAFEPARAM_DETAILED_TEST
+
+ return Impl_calcSafeParams( t1, t2, poly, bounds.dMin, bounds.dMax );
+
+#else
+ bool bRet( Impl_calcSafeParams( t1, t2, poly, bounds.dMin, bounds.dMax ) );
+
+ Polygon2D convHull( convexHull( poly ) );
+
+ std::cout << "# convex hull testing" << std::endl
+ << "plot [t=0:1] ";
+ std::cout << " bez("
+ << poly[0].x << ","
+ << poly[1].x << ","
+ << poly[2].x << ","
+ << poly[3].x << ",t),bez("
+ << poly[0].y << ","
+ << poly[1].y << ","
+ << poly[2].y << ","
+ << poly[3].y << ",t), "
+ << "t, " << bounds.dMin << ", "
+ << "t, " << bounds.dMax << ", "
+ << t1 << ", t, "
+ << t2 << ", t, "
+ << "'-' using ($1):($2) title \"control polygon\" with lp, "
+ << "'-' using ($1):($2) title \"convex hull\" with lp" << std::endl;
+
+ unsigned int k;
+ for( k=0; k<poly.size(); ++k )
+ {
+ std::cout << poly[k].x << " " << poly[k].y << std::endl;
+ }
+ std::cout << poly[0].x << " " << poly[0].y << std::endl;
+ std::cout << "e" << std::endl;
+
+ for( k=0; k<convHull.size(); ++k )
+ {
+ std::cout << convHull[k].x << " " << convHull[k].y << std::endl;
+ }
+ std::cout << convHull[0].x << " " << convHull[0].y << std::endl;
+ std::cout << "e" << std::endl;
+
+ return bRet;
+#endif
+}
+
+void Impl_deCasteljauAt( Bezier& part1,
+ Bezier& part2,
+ const Bezier& input,
+ double t )
+{
+ // deCasteljau bezier arc, scheme is:
+
+ // First row is C_0^n,C_1^n,...,C_n^n
+ // Second row is P_1^n,...,P_n^n
+ // etc.
+ // with P_k^r = (1 - x_s)P_{k-1}^{r-1} + x_s P_k{r-1}
+
+ // this results in:
+
+ // P1 P2 P3 P4
+ // L1 P2 P3 R4
+ // L2 H R3
+ // L3 R2
+ // L4/R1
+ if( tolZero(t) )
+ {
+ // t is zero -> part2 is input curve, part1 is empty (input.p0, that is)
+ part1.p0.x = part1.p1.x = part1.p2.x = part1.p3.x = input.p0.x;
+ part1.p0.y = part1.p1.y = part1.p2.y = part1.p3.y = input.p0.y;
+ part2 = input;
+ }
+ else if( tolEqual(t, 1.0) )
+ {
+ // t is one -> part1 is input curve, part2 is empty (input.p3, that is)
+ part1 = input;
+ part2.p0.x = part2.p1.x = part2.p2.x = part2.p3.x = input.p3.x;
+ part2.p0.y = part2.p1.y = part2.p2.y = part2.p3.y = input.p3.y;
+ }
+ else
+ {
+ part1.p0.x = input.p0.x; part1.p0.y = input.p0.y;
+ part1.p1.x = (1.0 - t)*part1.p0.x + t*input.p1.x; part1.p1.y = (1.0 - t)*part1.p0.y + t*input.p1.y;
+ const double Hx ( (1.0 - t)*input.p1.x + t*input.p2.x ), Hy ( (1.0 - t)*input.p1.y + t*input.p2.y );
+ part1.p2.x = (1.0 - t)*part1.p1.x + t*Hx; part1.p2.y = (1.0 - t)*part1.p1.y + t*Hy;
+ part2.p3.x = input.p3.x; part2.p3.y = input.p3.y;
+ part2.p2.x = (1.0 - t)*input.p2.x + t*input.p3.x; part2.p2.y = (1.0 - t)*input.p2.y + t*input.p3.y;
+ part2.p1.x = (1.0 - t)*Hx + t*part2.p2.x; part2.p1.y = (1.0 - t)*Hy + t*part2.p2.y;
+ part2.p0.x = (1.0 - t)*part1.p2.x + t*part2.p1.x; part2.p0.y = (1.0 - t)*part1.p2.y + t*part2.p1.y;
+ part1.p3.x = part2.p0.x; part1.p3.y = part2.p0.y;
+ }
+}
+
+void printCurvesWithSafeRange( const Bezier& c1, const Bezier& c2, double t1_c1, double t2_c1,
+ const Bezier& c2_part, const FatLine& bounds_c2 )
+{
+ static int offset = 0;
+
+ std::cout << "# safe param range testing" << std::endl
+ << "plot [t=0.0:1.0] ";
+
+ // clip safe ranges off c1
+ Bezier c1_part1;
+ Bezier c1_part2;
+ Bezier c1_part3;
+
+ // subdivide at t1_c1
+ Impl_deCasteljauAt( c1_part1, c1_part2, c1, t1_c1 );
+ // subdivide at t2_c1
+ Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, t2_c1 );
+
+ // output remaining segment (c1_part1)
+
+ std::cout << "bez("
+ << c1.p0.x+offset << ","
+ << c1.p1.x+offset << ","
+ << c1.p2.x+offset << ","
+ << c1.p3.x+offset << ",t),bez("
+ << c1.p0.y << ","
+ << c1.p1.y << ","
+ << c1.p2.y << ","
+ << c1.p3.y << ",t), bez("
+ << c2.p0.x+offset << ","
+ << c2.p1.x+offset << ","
+ << c2.p2.x+offset << ","
+ << c2.p3.x+offset << ",t),bez("
+ << c2.p0.y << ","
+ << c2.p1.y << ","
+ << c2.p2.y << ","
+ << c2.p3.y << ",t), "
+#if 1
+ << "bez("
+ << c1_part1.p0.x+offset << ","
+ << c1_part1.p1.x+offset << ","
+ << c1_part1.p2.x+offset << ","
+ << c1_part1.p3.x+offset << ",t),bez("
+ << c1_part1.p0.y << ","
+ << c1_part1.p1.y << ","
+ << c1_part1.p2.y << ","
+ << c1_part1.p3.y << ",t), "
+#endif
+#if 1
+ << "bez("
+ << c2_part.p0.x+offset << ","
+ << c2_part.p1.x+offset << ","
+ << c2_part.p2.x+offset << ","
+ << c2_part.p3.x+offset << ",t),bez("
+ << c2_part.p0.y << ","
+ << c2_part.p1.y << ","
+ << c2_part.p2.y << ","
+ << c2_part.p3.y << ",t), "
+#endif
+ << "linex("
+ << bounds_c2.a << ","
+ << bounds_c2.b << ","
+ << bounds_c2.c << ",t)+" << offset << ", liney("
+ << bounds_c2.a << ","
+ << bounds_c2.b << ","
+ << bounds_c2.c << ",t) title \"fat line (center)\", linex("
+ << bounds_c2.a << ","
+ << bounds_c2.b << ","
+ << bounds_c2.c-bounds_c2.dMin << ",t)+" << offset << ", liney("
+ << bounds_c2.a << ","
+ << bounds_c2.b << ","
+ << bounds_c2.c-bounds_c2.dMin << ",t) title \"fat line (min) \", linex("
+ << bounds_c2.a << ","
+ << bounds_c2.b << ","
+ << bounds_c2.c-bounds_c2.dMax << ",t)+" << offset << ", liney("
+ << bounds_c2.a << ","
+ << bounds_c2.b << ","
+ << bounds_c2.c-bounds_c2.dMax << ",t) title \"fat line (max) \"" << std::endl;
+
+ offset += 1;
+}
+
+void printResultWithFinalCurves( const Bezier& c1, const Bezier& c1_part,
+ const Bezier& c2, const Bezier& c2_part,
+ double t1_c1, double t2_c1 )
+{
+ static int offset = 0;
+
+ std::cout << "# final result" << std::endl
+ << "plot [t=0.0:1.0] ";
+
+ std::cout << "bez("
+ << c1.p0.x+offset << ","
+ << c1.p1.x+offset << ","
+ << c1.p2.x+offset << ","
+ << c1.p3.x+offset << ",t),bez("
+ << c1.p0.y << ","
+ << c1.p1.y << ","
+ << c1.p2.y << ","
+ << c1.p3.y << ",t), bez("
+ << c1_part.p0.x+offset << ","
+ << c1_part.p1.x+offset << ","
+ << c1_part.p2.x+offset << ","
+ << c1_part.p3.x+offset << ",t),bez("
+ << c1_part.p0.y << ","
+ << c1_part.p1.y << ","
+ << c1_part.p2.y << ","
+ << c1_part.p3.y << ",t), "
+ << " pointmarkx(bez("
+ << c1.p0.x+offset << ","
+ << c1.p1.x+offset << ","
+ << c1.p2.x+offset << ","
+ << c1.p3.x+offset << ","
+ << t1_c1 << "),t), "
+ << " pointmarky(bez("
+ << c1.p0.y << ","
+ << c1.p1.y << ","
+ << c1.p2.y << ","
+ << c1.p3.y << ","
+ << t1_c1 << "),t), "
+ << " pointmarkx(bez("
+ << c1.p0.x+offset << ","
+ << c1.p1.x+offset << ","
+ << c1.p2.x+offset << ","
+ << c1.p3.x+offset << ","
+ << t2_c1 << "),t), "
+ << " pointmarky(bez("
+ << c1.p0.y << ","
+ << c1.p1.y << ","
+ << c1.p2.y << ","
+ << c1.p3.y << ","
+ << t2_c1 << "),t), "
+
+ << "bez("
+ << c2.p0.x+offset << ","
+ << c2.p1.x+offset << ","
+ << c2.p2.x+offset << ","
+ << c2.p3.x+offset << ",t),bez("
+ << c2.p0.y << ","
+ << c2.p1.y << ","
+ << c2.p2.y << ","
+ << c2.p3.y << ",t), "
+ << "bez("
+ << c2_part.p0.x+offset << ","
+ << c2_part.p1.x+offset << ","
+ << c2_part.p2.x+offset << ","
+ << c2_part.p3.x+offset << ",t),bez("
+ << c2_part.p0.y << ","
+ << c2_part.p1.y << ","
+ << c2_part.p2.y << ","
+ << c2_part.p3.y << ",t)" << std::endl;
+
+ offset += 1;
+}
+
+/** determine parameter ranges [0,t1) and (t2,1] on c1, where c1 is guaranteed to lie outside c2.
+ Returns false, if the two curves don't even intersect.
+
+ @param t1
+ Range [0,t1) on c1 is guaranteed to lie outside c2
+
+ @param t2
+ Range (t2,1] on c1 is guaranteed to lie outside c2
+
+ @param c1_orig
+ Original curve c1
+
+ @param c1_part
+ Subdivided current part of c1
+
+ @param c2_orig
+ Original curve c2
+
+ @param c2_part
+ Subdivided current part of c2
+ */
+bool Impl_calcClipRange( double& t1,
+ double& t2,
+ const Bezier& c1_orig,
+ const Bezier& c1_part,
+ const Bezier& c2_orig,
+ const Bezier& c2_part )
+{
+ // TODO: Maybe also check fat line orthogonal to P0P3, having P0
+ // and P3 as the extremal points
+
+ if( Impl_doBBoxIntersect(c1_part, c2_part) )
+ {
+ // Calculate fat lines around c1
+ FatLine bounds_c2;
+
+ // must use the subdivided version of c2, since the fat line
+ // algorithm works implicitly with the convex hull bounding
+ // box.
+ Impl_calcFatLine(bounds_c2, c2_part);
+
+ // determine clip positions on c2. Can use original c1 (which
+ // is necessary anyway, to get the t's on the original curve),
+ // since the distance calculations work directly in the
+ // Bernstein polynomial parameter domain.
+ if( Impl_calcSafeParams_clip( t1, t2, bounds_c2,
+ calcLineDistance( bounds_c2.a,
+ bounds_c2.b,
+ bounds_c2.c,
+ c1_orig.p0.x,
+ c1_orig.p0.y ),
+ calcLineDistance( bounds_c2.a,
+ bounds_c2.b,
+ bounds_c2.c,
+ c1_orig.p1.x,
+ c1_orig.p1.y ),
+ calcLineDistance( bounds_c2.a,
+ bounds_c2.b,
+ bounds_c2.c,
+ c1_orig.p2.x,
+ c1_orig.p2.y ),
+ calcLineDistance( bounds_c2.a,
+ bounds_c2.b,
+ bounds_c2.c,
+ c1_orig.p3.x,
+ c1_orig.p3.y ) ) )
+ {
+ //printCurvesWithSafeRange(c1_orig, c2_orig, t1, t2, c2_part, bounds_c2);
+
+ // they do intersect
+ return true;
+ }
+ }
+
+ // they don't intersect: nothing to do
+ return false;
+}
+
+/* Tangent intersection part
+ * =========================
+ */
+
+void Impl_calcFocus( Bezier& res, const Bezier& c )
+{
+ // arbitrary small value, for now
+ // TODO: find meaningful value
+ const double minPivotValue( 1.0e-20 );
+
+ Point2D::value_type fMatrix[6];
+ Point2D::value_type fRes[2];
+
+ // calc new curve from hodograph, c and linear blend
+
+ // Coefficients for derivative of c are (C_i=n(C_{i+1} - C_i)):
+
+ // 3(P1 - P0), 3(P2 - P1), 3(P3 - P2) (bezier curve of degree 2)
+
+ // The hodograph is then (bezier curve of 2nd degree is P0(1-t)^2 + 2P1(1-t)t + P2t^2):
+
+ // 3(P1 - P0)(1-t)^2 + 6(P2 - P1)(1-t)t + 3(P3 - P2)t^2
+
+ // rotate by 90 degrees: x=-y, y=x and you get the normal vector function N(t):
+
+ // x(t) = -(3(P1.y - P0.y)(1-t)^2 + 6(P2.y - P1.y)(1-t)t + 3(P3.y - P2.y)t^2)
+ // y(t) = 3(P1.x - P0.x)(1-t)^2 + 6(P2.x - P1.x)(1-t)t + 3(P3.x - P2.x)t^2
+
+ // Now, the focus curve is defined to be F(t)=P(t) + c(t)N(t),
+ // where P(t) is the original curve, and c(t)=c0(1-t) + c1 t
+
+ // This results in the following expression for F(t):
+
+ // x(t) = P0.x (1-t)^3 + 3 P1.x (1-t)^2t + 3 P2.x (1.t)t^2 + P3.x t^3 -
+ // (c0(1-t) + c1 t)(3(P1.y - P0.y)(1-t)^2 + 6(P2.y - P1.y)(1-t)t + 3(P3.y - P2.y)t^2)
+
+ // y(t) = P0.y (1-t)^3 + 3 P1.y (1-t)^2t + 3 P2.y (1.t)t^2 + P3.y t^3 +
+ // (c0(1-t) + c1 t)(3(P1.x - P0.x)(1-t)^2 + 6(P2.x - P1.x)(1-t)t + 3(P3.x - P2.x)t^2)
+
+ // As a heuristic, we set F(0)=F(1) (thus, the curve is closed and _tends_ to be small):
+
+ // For F(0), the following results:
+
+ // x(0) = P0.x - c0 3(P1.y - P0.y)
+ // y(0) = P0.y + c0 3(P1.x - P0.x)
+
+ // For F(1), the following results:
+
+ // x(1) = P3.x - c1 3(P3.y - P2.y)
+ // y(1) = P3.y + c1 3(P3.x - P2.x)
+
+ // Reorder, collect and substitute into F(0)=F(1):
+
+ // P0.x - c0 3(P1.y - P0.y) = P3.x - c1 3(P3.y - P2.y)
+ // P0.y + c0 3(P1.x - P0.x) = P3.y + c1 3(P3.x - P2.x)
+
+ // which yields
+
+ // (P0.y - P1.y)c0 + (P3.y - P2.y)c1 = (P3.x - P0.x)/3
+ // (P1.x - P0.x)c0 + (P2.x - P3.x)c1 = (P3.y - P0.y)/3
+
+ // so, this is what we calculate here (determine c0 and c1):
+ fMatrix[0] = c.p1.x - c.p0.x;
+ fMatrix[1] = c.p2.x - c.p3.x;
+ fMatrix[2] = (c.p3.y - c.p0.y)/3.0;
+ fMatrix[3] = c.p0.y - c.p1.y;
+ fMatrix[4] = c.p3.y - c.p2.y;
+ fMatrix[5] = (c.p3.x - c.p0.x)/3.0;
+
+ // TODO: determine meaningful value for
+ if( !solve(fMatrix, 2, 3, fRes, minPivotValue) )
+ {
+ // TODO: generate meaningful values here
+ // singular or nearly singular system -- use arbitrary
+ // values for res
+ fRes[0] = 0.0;
+ fRes[1] = 1.0;
+
+ std::cerr << "Matrix singular!" << std::endl;
+ }
+
+ // now, the reordered and per-coefficient collected focus curve is
+ // the following third degree bezier curve F(t):
+
+ // x(t) = P0.x (1-t)^3 + 3 P1.x (1-t)^2t + 3 P2.x (1.t)t^2 + P3.x t^3 -
+ // (c0(1-t) + c1 t)(3(P1.y - P0.y)(1-t)^2 + 6(P2.y - P1.y)(1-t)t + 3(P3.y - P2.y)t^2)
+ // = P0.x (1-t)^3 + 3 P1.x (1-t)^2t + 3 P2.x (1.t)t^2 + P3.x t^3 -
+ // (3c0P1.y(1-t)^3 - 3c0P0.y(1-t)^3 + 6c0P2.y(1-t)^2t - 6c0P1.y(1-t)^2t +
+ // 3c0P3.y(1-t)t^2 - 3c0P2.y(1-t)t^2 +
+ // 3c1P1.y(1-t)^2t - 3c1P0.y(1-t)^2t + 6c1P2.y(1-t)t^2 - 6c1P1.y(1-t)t^2 +
+ // 3c1P3.yt^3 - 3c1P2.yt^3)
+ // = (P0.x - 3 c0 P1.y + 3 c0 P0.y)(1-t)^3 +
+ // 3(P1.x - c1 P1.y + c1 P0.y - 2 c0 P2.y + 2 c0 P1.y)(1-t)^2t +
+ // 3(P2.x - 2 c1 P2.y + 2 c1 P1.y - c0 P3.y + c0 P2.y)(1-t)t^2 +
+ // (P3.x - 3 c1 P3.y + 3 c1 P2.y)t^3
+ // = (P0.x - 3 c0(P1.y - P0.y))(1-t)^3 +
+ // 3(P1.x - c1(P1.y - P0.y) - 2c0(P2.y - P1.y))(1-t)^2t +
+ // 3(P2.x - 2 c1(P2.y - P1.y) - c0(P3.y - P2.y))(1-t)t^2 +
+ // (P3.x - 3 c1(P3.y - P2.y))t^3
+
+ // y(t) = P0.y (1-t)^3 + 3 P1.y (1-t)^2t + 3 P2.y (1-t)t^2 + P3.y t^3 +
+ // (c0(1-t) + c1 t)(3(P1.x - P0.x)(1-t)^2 + 6(P2.x - P1.x)(1-t)t + 3(P3.x - P2.x)t^2)
+ // = P0.y (1-t)^3 + 3 P1.y (1-t)^2t + 3 P2.y (1-t)t^2 + P3.y t^3 +
+ // 3c0(P1.x - P0.x)(1-t)^3 + 6c0(P2.x - P1.x)(1-t)^2t + 3c0(P3.x - P2.x)(1-t)t^2 +
+ // 3c1(P1.x - P0.x)(1-t)^2t + 6c1(P2.x - P1.x)(1-t)t^2 + 3c1(P3.x - P2.x)t^3
+ // = (P0.y + 3 c0 (P1.x - P0.x))(1-t)^3 +
+ // 3(P1.y + 2 c0 (P2.x - P1.x) + c1 (P1.x - P0.x))(1-t)^2t +
+ // 3(P2.y + c0 (P3.x - P2.x) + 2 c1 (P2.x - P1.x))(1-t)t^2 +
+ // (P3.y + 3 c1 (P3.x - P2.x))t^3
+
+ // Therefore, the coefficients F0 to F3 of the focus curve are:
+
+ // F0.x = (P0.x - 3 c0(P1.y - P0.y)) F0.y = (P0.y + 3 c0 (P1.x - P0.x))
+ // F1.x = (P1.x - c1(P1.y - P0.y) - 2c0(P2.y - P1.y)) F1.y = (P1.y + 2 c0 (P2.x - P1.x) + c1 (P1.x - P0.x))
+ // F2.x = (P2.x - 2 c1(P2.y - P1.y) - c0(P3.y - P2.y)) F2.y = (P2.y + c0 (P3.x - P2.x) + 2 c1 (P2.x - P1.x))
+ // F3.x = (P3.x - 3 c1(P3.y - P2.y)) F3.y = (P3.y + 3 c1 (P3.x - P2.x))
+
+ res.p0.x = c.p0.x - 3*fRes[0]*(c.p1.y - c.p0.y);
+ res.p1.x = c.p1.x - fRes[1]*(c.p1.y - c.p0.y) - 2*fRes[0]*(c.p2.y - c.p1.y);
+ res.p2.x = c.p2.x - 2*fRes[1]*(c.p2.y - c.p1.y) - fRes[0]*(c.p3.y - c.p2.y);
+ res.p3.x = c.p3.x - 3*fRes[1]*(c.p3.y - c.p2.y);
+
+ res.p0.y = c.p0.y + 3*fRes[0]*(c.p1.x - c.p0.x);
+ res.p1.y = c.p1.y + 2*fRes[0]*(c.p2.x - c.p1.x) + fRes[1]*(c.p1.x - c.p0.x);
+ res.p2.y = c.p2.y + fRes[0]*(c.p3.x - c.p2.x) + 2*fRes[1]*(c.p2.x - c.p1.x);
+ res.p3.y = c.p3.y + 3*fRes[1]*(c.p3.x - c.p2.x);
+}
+
+bool Impl_calcSafeParams_focus( double& t1,
+ double& t2,
+ const Bezier& curve,
+ const Bezier& focus )
+{
+ // now, we want to determine which normals of the original curve
+ // P(t) intersect with the focus curve F(t). The condition for
+ // this statement is P'(t)(P(t) - F) = 0, i.e. hodograph P'(t) and
+ // line through P(t) and F are perpendicular.
+ // If you expand this equation, you end up with something like
+
+ // (\sum_{i=0}^n (P_i - F)B_i^n(t))^T (\sum_{j=0}^{n-1} n(P_{j+1} - P_j)B_j^{n-1}(t))
+
+ // Multiplying that out (as the scalar product is linear, we can
+ // extract some terms) yields:
+
+ // (P_i - F)^T n(P_{j+1} - P_j) B_i^n(t)B_j^{n-1}(t) + ...
+
+ // If we combine the B_i^n(t)B_j^{n-1}(t) product, we arrive at a
+ // Bernstein polynomial of degree 2n-1, as
+
+ // \binom{n}{i}(1-t)^{n-i}t^i) \binom{n-1}{j}(1-t)^{n-1-j}t^j) =
+ // \binom{n}{i}\binom{n-1}{j}(1-t)^{2n-1-i-j}t^{i+j}
+
+ // Thus, with the defining equation for a 2n-1 degree Bernstein
+ // polynomial
+
+ // \sum_{i=0}^{2n-1} d_i B_i^{2n-1}(t)
+
+ // the d_i are calculated as follows:
+
+ // d_i = \sum_{j+k=i, j\in\{0,...,n\}, k\in\{0,...,n-1\}} \frac{\binom{n}{j}\binom{n-1}{k}}{\binom{2n-1}{i}} n (P_{k+1} - P_k)^T(P_j - F)
+
+ // Okay, but F is now not a single point, but itself a curve
+ // F(u). Thus, for every value of u, we get a different 2n-1
+ // bezier curve from the above equation. Therefore, we have a
+ // tensor product bezier patch, with the following defining
+ // equation:
+
+ // d(t,u) = \sum_{i=0}^{2n-1} \sum_{j=0}^m B_i^{2n-1}(t) B_j^{m}(u) d_{ij}, where
+ // d_{ij} = \sum_{k+l=i, l\in\{0,...,n\}, k\in\{0,...,n-1\}} \frac{\binom{n}{l}\binom{n-1}{k}}{\binom{2n-1}{i}} n (P_{k+1} - P_k)^T(P_l - F_j)
+
+ // as above, only that now F is one of the focus' control points.
+
+ // Note the difference in the binomial coefficients to the
+ // reference paper, these formulas most probably contained a typo.
+
+ // To determine, where D(t,u) is _not_ zero (these are the parts
+ // of the curve that don't share normals with the focus and can
+ // thus be safely clipped away), we project D(u,t) onto the
+ // (d(t,u), t) plane, determine the convex hull there and proceed
+ // as for the curve intersection part (projection is orthogonal to
+ // u axis, thus simply throw away u coordinate).
+
+ // \fallfac are so-called falling factorials (see Concrete
+ // Mathematics, p. 47 for a definition).
+
+ // now, for tensor product bezier curves, the convex hull property
+ // holds, too. Thus, we simply project the control points (t_{ij},
+ // u_{ij}, d_{ij}) onto the (t,d) plane and calculate the
+ // intersections of the convex hull with the t axis, as for the
+ // bezier clipping case.
+
+ // calc polygon of control points (t_{ij}, d_{ij}):
+
+ const int n( 3 ); // cubic bezier curves, as a matter of fact
+ const int i_card( 2*n );
+ const int j_card( n + 1 );
+ const int k_max( n-1 );
+ Polygon2D controlPolygon( i_card*j_card ); // vector of (t_{ij}, d_{ij}) in row-major order
+
+ int i, j, k, l; // variable notation from formulas above and Sederberg article
+ Point2D::value_type d;
+ for( i=0; i<i_card; ++i )
+ {
+ for( j=0; j<j_card; ++j )
+ {
+ // calc single d_{ij} sum:
+ for( d=0.0, k=std::max(0,i-n); k<=k_max && k<=i; ++k )
+ {
+ l = i - k; // invariant: k + l = i
+ assert(k>=0 && k<=n-1); // k \in {0,...,n-1}
+ assert(l>=0 && l<=n); // l \in {0,...,n}
+
+ // TODO: find, document and assert proper limits for n and int's max_val.
+ // This becomes important should anybody wants to use
+ // this code for higher-than-cubic beziers
+ d += static_cast<double>(fallFac(n,l)*fallFac(n-1,k)*fac(i)) /
+ static_cast<double>(fac(l)*fac(k) * fallFac(2*n-1,i)) * n *
+ ( (curve[k+1].x - curve[k].x)*(curve[l].x - focus[j].x) + // dot product here
+ (curve[k+1].y - curve[k].y)*(curve[l].y - focus[j].y) );
+ }
+
+ // Note that the t_{ij} values are evenly spaced on the
+ // [0,1] interval, thus t_{ij}=i/(2n-1)
+ controlPolygon[ i*j_card + j ] = Point2D( i/(2.0*n-1.0), d );
+ }
+ }
+
+#ifndef WITH_SAFEFOCUSPARAM_DETAILED_TEST
+
+ // calc safe parameter range, to determine [0,t1] and [t2,1] where
+ // no zero crossing is guaranteed.
+ return Impl_calcSafeParams( t1, t2, controlPolygon, 0.0, 0.0 );
+
+#else
+ bool bRet( Impl_calcSafeParams( t1, t2, controlPolygon, 0.0, 0.0 ) );
+
+ Polygon2D convHull( convexHull( controlPolygon ) );
+
+ std::cout << "# convex hull testing (focus)" << std::endl
+ << "plot [t=0:1] ";
+ std::cout << "'-' using ($1):($2) title \"control polygon\" with lp, "
+ << "'-' using ($1):($2) title \"convex hull\" with lp" << std::endl;
+
+ unsigned int count;
+ for( count=0; count<controlPolygon.size(); ++count )
+ {
+ std::cout << controlPolygon[count].x << " " << controlPolygon[count].y << std::endl;
+ }
+ std::cout << controlPolygon[0].x << " " << controlPolygon[0].y << std::endl;
+ std::cout << "e" << std::endl;
+
+ for( count=0; count<convHull.size(); ++count )
+ {
+ std::cout << convHull[count].x << " " << convHull[count].y << std::endl;
+ }
+ std::cout << convHull[0].x << " " << convHull[0].y << std::endl;
+ std::cout << "e" << std::endl;
+
+ return bRet;
+#endif
+}
+
+/** Calc all values t_i on c1, for which safeRanges functor does not
+ give a safe range on c1 and c2.
+
+ This method is the workhorse of the bezier clipping. Because c1
+ and c2 must be alternatingly tested against each other (first
+ determine safe parameter interval on c1 with regard to c2, then
+ the other way around), we call this method recursively with c1 and
+ c2 swapped.
+
+ @param result
+ Output iterator where the final t values are added to. If curves
+ don't intersect, nothing is added.
+
+ @param delta
+ Maximal allowed distance to true critical point (measured in the
+ original curve's coordinate system)
+
+ @param safeRangeFunctor
+ Functor object, that must provide the following operator():
+ bool safeRangeFunctor( double& t1,
+ double& t2,
+ const Bezier& c1_orig,
+ const Bezier& c1_part,
+ const Bezier& c2_orig,
+ const Bezier& c2_part );
+ This functor must calculate the safe ranges [0,t1] and [t2,1] on
+ c1_orig, where c1_orig is 'safe' from c2_part. If the whole
+ c1_orig is safe, false must be returned, true otherwise.
+ */
+template <class Functor> void Impl_applySafeRanges_rec( std::back_insert_iterator< std::vector< std::pair<double, double> > >& result,
+ double delta,
+ const Functor& safeRangeFunctor,
+ int recursionLevel,
+ const Bezier& c1_orig,
+ const Bezier& c1_part,
+ double last_t1_c1,
+ double last_t2_c1,
+ const Bezier& c2_orig,
+ const Bezier& c2_part,
+ double last_t1_c2,
+ double last_t2_c2 )
+{
+ // check end condition
+ // ===================
+
+ // TODO: tidy up recursion handling. maybe put everything in a
+ // struct and swap that here at method entry
+
+ // TODO: Implement limit on recursion depth. Should that limit be
+ // reached, chances are that we're on a higher-order tangency. For
+ // this case, AW proposed to take the middle of the current
+ // interval, and to correct both curve's tangents at that new
+ // endpoint to be equal. That virtually generates a first-order
+ // tangency, and justifies to return a single intersection
+ // point. Otherwise, inside/outside test might fail here.
+
+ for( int i=0; i<recursionLevel; ++i ) std::cerr << " ";
+ if( recursionLevel % 2 )
+ {
+ std::cerr << std::endl << "level: " << recursionLevel
+ << " t: "
+ << last_t1_c2 + (last_t2_c2 - last_t1_c2)/2.0
+ << ", c1: " << last_t1_c2 << " " << last_t2_c2
+ << ", c2: " << last_t1_c1 << " " << last_t2_c1
+ << std::endl;
+ }
+ else
+ {
+ std::cerr << std::endl << "level: " << recursionLevel
+ << " t: "
+ << last_t1_c1 + (last_t2_c1 - last_t1_c1)/2.0
+ << ", c1: " << last_t1_c1 << " " << last_t2_c1
+ << ", c2: " << last_t1_c2 << " " << last_t2_c2
+ << std::endl;
+ }
+
+ // refine solution
+ // ===============
+
+ double t1_c1, t2_c1;
+
+ // Note: we first perform the clipping and only test for precision
+ // sufficiency afterwards, since we want to exploit the fact that
+ // Impl_calcClipRange returns false if the curves don't
+ // intersect. We would have to check that separately for the end
+ // condition, otherwise.
+
+ // determine safe range on c1_orig
+ if( safeRangeFunctor( t1_c1, t2_c1, c1_orig, c1_part, c2_orig, c2_part ) )
+ {
+ // now, t1 and t2 are calculated on the original curve
+ // (but against a fat line calculated from the subdivided
+ // c2, namely c2_part). If the [t1,t2] range is outside
+ // our current [last_t1,last_t2] range, we're done in this
+ // branch - the curves no longer intersect.
+ if( tolLessEqual(t1_c1, last_t2_c1) && tolGreaterEqual(t2_c1, last_t1_c1) )
+ {
+ // As noted above, t1 and t2 are calculated on the
+ // original curve, but against a fat line
+ // calculated from the subdivided c2, namely
+ // c2_part. Our domain to work on is
+ // [last_t1,last_t2], on the other hand, so values
+ // of [t1,t2] outside that range are irrelevant
+ // here. Clip range appropriately.
+ t1_c1 = std::max(t1_c1, last_t1_c1);
+ t2_c1 = std::min(t2_c1, last_t2_c1);
+
+ // TODO: respect delta
+ // for now, end condition is just a fixed threshold on the t's
+
+ // check end condition
+ // ===================
+
+#if 1
+ if( fabs(last_t2_c1 - last_t1_c1) < 0.0001 &&
+ fabs(last_t2_c2 - last_t1_c2) < 0.0001 )
+#else
+ if( fabs(last_t2_c1 - last_t1_c1) < 0.01 &&
+ fabs(last_t2_c2 - last_t1_c2) < 0.01 )
+#endif
+ {
+ // done. Add to result
+ if( recursionLevel % 2 )
+ {
+ // uneven level: have to swap the t's, since curves are swapped, too
+ *result++ = std::make_pair( last_t1_c2 + (last_t2_c2 - last_t1_c2)/2.0,
+ last_t1_c1 + (last_t2_c1 - last_t1_c1)/2.0 );
+ }
+ else
+ {
+ *result++ = std::make_pair( last_t1_c1 + (last_t2_c1 - last_t1_c1)/2.0,
+ last_t1_c2 + (last_t2_c2 - last_t1_c2)/2.0 );
+ }
+
+#if 0
+ //printResultWithFinalCurves( c1_orig, c1_part, c2_orig, c2_part, last_t1_c1, last_t2_c1 );
+ printResultWithFinalCurves( c1_orig, c1_part, c2_orig, c2_part, t1_c1, t2_c1 );
+#else
+ // calc focus curve of c2
+ Bezier focus;
+ Impl_calcFocus(focus, c2_part); // need to use subdivided c2
+
+ safeRangeFunctor( t1_c1, t2_c1, c1_orig, c1_part, c2_orig, c2_part );
+
+ //printResultWithFinalCurves( c1_orig, c1_part, c2_orig, focus, t1_c1, t2_c1 );
+ printResultWithFinalCurves( c1_orig, c1_part, c2_orig, focus, last_t1_c1, last_t2_c1 );
+#endif
+ }
+ else
+ {
+ // heuristic: if parameter range is not reduced by at least
+ // 20%, subdivide longest curve, and clip shortest against
+ // both parts of longest
+// if( (last_t2_c1 - last_t1_c1 - t2_c1 + t1_c1) / (last_t2_c1 - last_t1_c1) < 0.2 )
+ if( false )
+ {
+ // subdivide and descend
+ // =====================
+
+ Bezier part1;
+ Bezier part2;
+
+ double intervalMiddle;
+
+ if( last_t2_c1 - last_t1_c1 > last_t2_c2 - last_t1_c2 )
+ {
+ // subdivide c1
+ // ============
+
+ intervalMiddle = last_t1_c1 + (last_t2_c1 - last_t1_c1)/2.0;
+
+ // subdivide at the middle of the interval (as
+ // we're not subdividing on the original
+ // curve, this simply amounts to subdivision
+ // at 0.5)
+ Impl_deCasteljauAt( part1, part2, c1_part, 0.5 );
+
+ // and descend recursively with swapped curves
+ Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1,
+ c2_orig, c2_part, last_t1_c2, last_t2_c2,
+ c1_orig, part1, last_t1_c1, intervalMiddle );
+
+ Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1,
+ c2_orig, c2_part, last_t1_c2, last_t2_c2,
+ c1_orig, part2, intervalMiddle, last_t2_c1 );
+ }
+ else
+ {
+ // subdivide c2
+ // ============
+
+ intervalMiddle = last_t1_c2 + (last_t2_c2 - last_t1_c2)/2.0;
+
+ // subdivide at the middle of the interval (as
+ // we're not subdividing on the original
+ // curve, this simply amounts to subdivision
+ // at 0.5)
+ Impl_deCasteljauAt( part1, part2, c2_part, 0.5 );
+
+ // and descend recursively with swapped curves
+ Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1,
+ c2_orig, part1, last_t1_c2, intervalMiddle,
+ c1_orig, c1_part, last_t1_c1, last_t2_c1 );
+
+ Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1,
+ c2_orig, part2, intervalMiddle, last_t2_c2,
+ c1_orig, c1_part, last_t1_c1, last_t2_c1 );
+ }
+ }
+ else
+ {
+ // apply calculated clip
+ // =====================
+
+ // clip safe ranges off c1_orig
+ Bezier c1_part1;
+ Bezier c1_part2;
+ Bezier c1_part3;
+
+ // subdivide at t1_c1
+ Impl_deCasteljauAt( c1_part1, c1_part2, c1_orig, t1_c1 );
+
+ // subdivide at t2_c1. As we're working on
+ // c1_part2 now, we have to adapt t2_c1 since
+ // we're no longer in the original parameter
+ // interval. This is based on the following
+ // assumption: t2_new = (t2-t1)/(1-t1), which
+ // relates the t2 value into the new parameter
+ // range [0,1] of c1_part2.
+ Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (t2_c1-t1_c1)/(1.0-t1_c1) );
+
+ // descend with swapped curves and c1_part1 as the
+ // remaining (middle) segment
+ Impl_applySafeRanges_rec( result, delta, safeRangeFunctor, recursionLevel+1,
+ c2_orig, c2_part, last_t1_c2, last_t2_c2,
+ c1_orig, c1_part1, t1_c1, t2_c1 );
+ }
+ }
+ }
+ }
+}
+
+struct ClipBezierFunctor
+{
+ bool operator()( double& t1_c1,
+ double& t2_c1,
+ const Bezier& c1_orig,
+ const Bezier& c1_part,
+ const Bezier& c2_orig,
+ const Bezier& c2_part ) const
+ {
+ return Impl_calcClipRange( t1_c1, t2_c1, c1_orig, c1_part, c2_orig, c2_part );
+ }
+};
+
+struct BezierTangencyFunctor
+{
+ bool operator()( double& t1_c1,
+ double& t2_c1,
+ const Bezier& c1_orig,
+ const Bezier& c1_part,
+ const Bezier& c2_orig,
+ const Bezier& c2_part ) const
+ {
+ // calc focus curve of c2
+ Bezier focus;
+ Impl_calcFocus(focus, c2_part); // need to use subdivided c2
+ // here, as the whole curve is
+ // used for focus calculation
+
+ // determine safe range on c1_orig
+ bool bRet( Impl_calcSafeParams_focus( t1_c1, t2_c1,
+ c1_orig, // use orig curve here, need t's on original curve
+ focus ) );
+
+ std::cerr << "range: " << t2_c1 - t1_c1 << ", ret: " << bRet << std::endl;
+
+ return bRet;
+ }
+};
+
+/** Perform a bezier clip (curve against curve)
+
+ @param result
+ Output iterator where the final t values are added to. This
+ iterator will remain empty, if there are no intersections.
+
+ @param delta
+ Maximal allowed distance to true intersection (measured in the
+ original curve's coordinate system)
+ */
+void clipBezier( std::back_insert_iterator< std::vector< std::pair<double, double> > >& result,
+ double delta,
+ const Bezier& c1,
+ const Bezier& c2 )
+{
+#if 0
+ // first of all, determine list of collinear normals. Collinear
+ // normals typically separate two intersections, thus, subdivide
+ // at all collinear normal's t values beforehand. This will cater
+ // for tangent intersections, where two or more intersections are
+ // infinitesimally close together.
+
+ // TODO: evaluate effects of higher-than-second-order
+ // tangencies. Sederberg et al. state that collinear normal
+ // algorithm then degrades quickly.
+
+ std::vector< std::pair<double,double> > results;
+ std::back_insert_iterator< std::vector< std::pair<double, double> > > ii(results);
+
+ Impl_calcCollinearNormals( ii, delta, 0, c1, c1, 0.0, 1.0, c2, c2, 0.0, 1.0 );
+
+ // As Sederberg's collinear normal theorem is only sufficient, not
+ // necessary for two intersections left and right, we've to test
+ // all segments generated by the collinear normal algorithm
+ // against each other. In other words, if the two curves are both
+ // divided in a left and a right part, the collinear normal
+ // theorem does _not_ state that the left part of curve 1 does not
+ // e.g. intersect with the right part of curve 2.
+
+ // divide c1 and c2 at collinear normal intersection points
+ std::vector< Bezier > c1_segments( results.size()+1 );
+ std::vector< Bezier > c2_segments( results.size()+1 );
+ Bezier c1_remainder( c1 );
+ Bezier c2_remainder( c2 );
+ unsigned int i;
+ for( i=0; i<results.size(); ++i )
+ {
+ Bezier c1_part2;
+ Impl_deCasteljauAt( c1_segments[i], c1_part2, c1_remainder, results[i].first );
+ c1_remainder = c1_part2;
+
+ Bezier c2_part2;
+ Impl_deCasteljauAt( c2_segments[i], c2_part2, c2_remainder, results[i].second );
+ c2_remainder = c2_part2;
+ }
+ c1_segments[i] = c1_remainder;
+ c2_segments[i] = c2_remainder;
+
+ // now, c1/c2_segments contain all segments, then
+ // clip every resulting segment against every other
+ unsigned int c1_curr, c2_curr;
+ for( c1_curr=0; c1_curr<c1_segments.size(); ++c1_curr )
+ {
+ for( c2_curr=0; c2_curr<c2_segments.size(); ++c2_curr )
+ {
+ if( c1_curr != c2_curr )
+ {
+ Impl_clipBezier_rec(result, delta, 0,
+ c1_segments[c1_curr], c1_segments[c1_curr],
+ 0.0, 1.0,
+ c2_segments[c2_curr], c2_segments[c2_curr],
+ 0.0, 1.0);
+ }
+ }
+ }
+#else
+ Impl_applySafeRanges_rec( result, delta, BezierTangencyFunctor(), 0, c1, c1, 0.0, 1.0, c2, c2, 0.0, 1.0 );
+ //Impl_applySafeRanges_rec( result, delta, ClipBezierFunctor(), 0, c1, c1, 0.0, 1.0, c2, c2, 0.0, 1.0 );
+#endif
+ // that's it, boys'n'girls!
+}
+
+int main(int argc, const char *argv[])
+{
+ double curr_Offset( 0 );
+ unsigned int i,j,k;
+
+ Bezier someCurves[] =
+ {
+// {Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0),Point2D(1.0,0.0)},
+// {Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0),Point2D(1.0,0.5)},
+// {Point2D(1.0,0.0),Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0)}
+// {Point2D(0.25+1,0.5),Point2D(0.25+1,0.708333),Point2D(0.423611+1,0.916667),Point2D(0.770833+1,0.980324)},
+// {Point2D(0.0+1,0.0),Point2D(0.0+1,1.0),Point2D(1.0+1,1.0),Point2D(1.0+1,0.5)}
+
+// tangency1
+// {Point2D(0.627124+1,0.828427),Point2D(0.763048+1,0.828507),Point2D(0.885547+1,0.77312),Point2D(0.950692+1,0.67325)},
+// {Point2D(0.0,1.0),Point2D(0.1,1.0),Point2D(0.4,1.0),Point2D(0.5,1.0)}
+
+// {Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0),Point2D(1.0,0.5)},
+// {Point2D(0.60114,0.933091),Point2D(0.69461,0.969419),Point2D(0.80676,0.992976),Point2D(0.93756,0.998663)}
+// {Point2D(1.0,0.0),Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0)},
+// {Point2D(0.62712,0.828427),Point2D(0.76305,0.828507),Point2D(0.88555,0.77312),Point2D(0.95069,0.67325)}
+
+// clipping1
+// {Point2D(0.0,0.0),Point2D(0.0,3.5),Point2D(1.0,-2.5),Point2D(1.0,1.0)},
+// {Point2D(0.0,1.0),Point2D(3.5,1.0),Point2D(-2.5,0.0),Point2D(1.0,0.0)}
+
+// tangency2
+// {Point2D(0.0,1.0),Point2D(3.5,1.0),Point2D(-2.5,0.0),Point2D(1.0,0.0)},
+// {Point2D(15.3621,0.00986464),Point2D(15.3683,0.0109389),Point2D(15.3682,0.0109315),Point2D(15.3621,0.00986464)}
+
+// tangency3
+// {Point2D(1.0,0.0),Point2D(0.0,0.0),Point2D(0.0,1.0),Point2D(1.0,1.0)},
+// {Point2D(-0.5,0.0),Point2D(0.5,0.0),Point2D(0.5,1.0),Point2D(-0.5,1.0)}
+
+// tangency4
+// {Point2D(-0.5,0.0),Point2D(0.5,0.0),Point2D(0.5,1.0),Point2D(-0.5,1.0)},
+// {Point2D(0.26,0.4),Point2D(0.25,0.5),Point2D(0.25,0.5),Point2D(0.26,0.6)}
+
+// tangency5
+// {Point2D(0.0,0.0),Point2D(0.0,3.5),Point2D(1.0,-2.5),Point2D(1.0,1.0)},
+// {Point2D(15.3621,0.00986464),Point2D(15.3683,0.0109389),Point2D(15.3682,0.0109315),Point2D(15.3621,0.00986464)}
+
+// tangency6
+// {Point2D(0.0,0.0),Point2D(0.0,3.5),Point2D(1.0,-2.5),Point2D(1.0,1.0)},
+// {Point2D(15.3621,10.00986464),Point2D(15.3683,10.0109389),Point2D(15.3682,10.0109315),Point2D(15.3621,10.00986464)}
+
+// tangency7
+// {Point2D(2.505,0.0),Point2D(2.505+4.915,4.300),Point2D(2.505+3.213,10.019),Point2D(2.505-2.505,10.255)},
+// {Point2D(15.3621,10.00986464),Point2D(15.3683,10.0109389),Point2D(15.3682,10.0109315),Point2D(15.3621,10.00986464)}
+
+// tangency Sederberg example
+ {Point2D(2.505,0.0),Point2D(2.505+4.915,4.300),Point2D(2.505+3.213,10.019),Point2D(2.505-2.505,10.255)},
+ {Point2D(5.33+9.311,0.0),Point2D(5.33+9.311-13.279,4.205),Point2D(5.33+9.311-10.681,9.119),Point2D(5.33+9.311-2.603,10.254)}
+
+// clipping2
+// {Point2D(-0.5,0.0),Point2D(0.5,0.0),Point2D(0.5,1.0),Point2D(-0.5,1.0)},
+// {Point2D(0.2575,0.4),Point2D(0.2475,0.5),Point2D(0.2475,0.5),Point2D(0.2575,0.6)}
+
+// {Point2D(0.0,0.1),Point2D(0.2,3.5),Point2D(1.0,-2.5),Point2D(1.1,1.2)},
+// {Point2D(0.0,1.0),Point2D(3.5,0.9),Point2D(-2.5,0.1),Point2D(1.1,0.2)}
+// {Point2D(0.0,0.1),Point2D(0.2,3.0),Point2D(1.0,-2.0),Point2D(1.1,1.2)},
+// {Point2D(0.627124+1,0.828427),Point2D(0.763048+1,0.828507),Point2D(0.885547+1,0.77312),Point2D(0.950692+1,0.67325)}
+// {Point2D(0.0,1.0),Point2D(3.0,0.9),Point2D(-2.0,0.1),Point2D(1.1,0.2)}
+// {Point2D(0.0,4.0),Point2D(0.1,5.0),Point2D(0.9,5.0),Point2D(1.0,4.0)},
+// {Point2D(0.0,0.0),Point2D(0.1,0.5),Point2D(0.9,0.5),Point2D(1.0,0.0)},
+// {Point2D(0.0,0.1),Point2D(0.1,1.5),Point2D(0.9,1.5),Point2D(1.0,0.1)},
+// {Point2D(0.0,-4.0),Point2D(0.1,-5.0),Point2D(0.9,-5.0),Point2D(1.0,-4.0)}
+ };
+
+ // output gnuplot setup
+ std::cout << "#!/usr/bin/gnuplot -persist" << std::endl
+ << "#" << std::endl
+ << "# automatically generated by bezierclip, don't change!" << std::endl
+ << "#" << std::endl
+ << "set parametric" << std::endl
+ << "bez(p,q,r,s,t) = p*(1-t)**3+q*3*(1-t)**2*t+r*3*(1-t)*t**2+s*t**3" << std::endl
+ << "bezd(p,q,r,s,t) = 3*(q-p)*(1-t)**2+6*(r-q)*(1-t)*t+3*(s-r)*t**2" << std::endl
+ << "pointmarkx(c,t) = c-0.03*t" << std::endl
+ << "pointmarky(c,t) = c+0.03*t" << std::endl
+ << "linex(a,b,c,t) = a*-c + t*-b" << std::endl
+ << "liney(a,b,c,t) = b*-c + t*a" << std::endl << std::endl
+ << "# end of setup" << std::endl << std::endl;
+
+#ifdef WITH_CONVEXHULL_TEST
+ // test convex hull algorithm
+ const double convHull_xOffset( curr_Offset );
+ curr_Offset += 20;
+ std::cout << "# convex hull testing" << std::endl
+ << "plot [t=0:1] ";
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ Polygon2D aTestPoly(4);
+ aTestPoly[0] = someCurves[i].p0;
+ aTestPoly[1] = someCurves[i].p1;
+ aTestPoly[2] = someCurves[i].p2;
+ aTestPoly[3] = someCurves[i].p3;
+
+ aTestPoly[0].x += convHull_xOffset;
+ aTestPoly[1].x += convHull_xOffset;
+ aTestPoly[2].x += convHull_xOffset;
+ aTestPoly[3].x += convHull_xOffset;
+
+ std::cout << " bez("
+ << aTestPoly[0].x << ","
+ << aTestPoly[1].x << ","
+ << aTestPoly[2].x << ","
+ << aTestPoly[3].x << ",t),bez("
+ << aTestPoly[0].y << ","
+ << aTestPoly[1].y << ","
+ << aTestPoly[2].y << ","
+ << aTestPoly[3].y << ",t), '-' using ($1):($2) title \"convex hull " << i << "\" with lp";
+
+ if( i+1<sizeof(someCurves)/sizeof(Bezier) )
+ std::cout << ",\\" << std::endl;
+ else
+ std::cout << std::endl;
+ }
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ Polygon2D aTestPoly(4);
+ aTestPoly[0] = someCurves[i].p0;
+ aTestPoly[1] = someCurves[i].p1;
+ aTestPoly[2] = someCurves[i].p2;
+ aTestPoly[3] = someCurves[i].p3;
+
+ aTestPoly[0].x += convHull_xOffset;
+ aTestPoly[1].x += convHull_xOffset;
+ aTestPoly[2].x += convHull_xOffset;
+ aTestPoly[3].x += convHull_xOffset;
+
+ Polygon2D convHull( convexHull(aTestPoly) );
+
+ for( k=0; k<convHull.size(); ++k )
+ {
+ std::cout << convHull[k].x << " " << convHull[k].y << std::endl;
+ }
+ std::cout << convHull[0].x << " " << convHull[0].y << std::endl;
+ std::cout << "e" << std::endl;
+ }
+#endif
+
+#ifdef WITH_MULTISUBDIVIDE_TEST
+ // test convex hull algorithm
+ const double multiSubdivide_xOffset( curr_Offset );
+ curr_Offset += 20;
+ std::cout << "# multi subdivide testing" << std::endl
+ << "plot [t=0:1] ";
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ Bezier c( someCurves[i] );
+ Bezier c1_part1;
+ Bezier c1_part2;
+ Bezier c1_part3;
+
+ c.p0.x += multiSubdivide_xOffset;
+ c.p1.x += multiSubdivide_xOffset;
+ c.p2.x += multiSubdivide_xOffset;
+ c.p3.x += multiSubdivide_xOffset;
+
+ const double t1( 0.1+i/(3.0*sizeof(someCurves)/sizeof(Bezier)) );
+ const double t2( 0.9-i/(3.0*sizeof(someCurves)/sizeof(Bezier)) );
+
+ // subdivide at t1
+ Impl_deCasteljauAt( c1_part1, c1_part2, c, t1 );
+
+ // subdivide at t2_c1. As we're working on
+ // c1_part2 now, we have to adapt t2_c1 since
+ // we're no longer in the original parameter
+ // interval. This is based on the following
+ // assumption: t2_new = (t2-t1)/(1-t1), which
+ // relates the t2 value into the new parameter
+ // range [0,1] of c1_part2.
+ Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (t2-t1)/(1.0-t1) );
+
+ // subdivide at t2
+ Impl_deCasteljauAt( c1_part3, c1_part2, c, t2 );
+
+ std::cout << " bez("
+ << c1_part1.p0.x << ","
+ << c1_part1.p1.x << ","
+ << c1_part1.p2.x << ","
+ << c1_part1.p3.x << ",t), bez("
+ << c1_part1.p0.y+0.01 << ","
+ << c1_part1.p1.y+0.01 << ","
+ << c1_part1.p2.y+0.01 << ","
+ << c1_part1.p3.y+0.01 << ",t) title \"middle " << i << "\", "
+ << " bez("
+ << c1_part2.p0.x << ","
+ << c1_part2.p1.x << ","
+ << c1_part2.p2.x << ","
+ << c1_part2.p3.x << ",t), bez("
+ << c1_part2.p0.y << ","
+ << c1_part2.p1.y << ","
+ << c1_part2.p2.y << ","
+ << c1_part2.p3.y << ",t) title \"right " << i << "\", "
+ << " bez("
+ << c1_part3.p0.x << ","
+ << c1_part3.p1.x << ","
+ << c1_part3.p2.x << ","
+ << c1_part3.p3.x << ",t), bez("
+ << c1_part3.p0.y << ","
+ << c1_part3.p1.y << ","
+ << c1_part3.p2.y << ","
+ << c1_part3.p3.y << ",t) title \"left " << i << "\"";
+
+ if( i+1<sizeof(someCurves)/sizeof(Bezier) )
+ std::cout << ",\\" << std::endl;
+ else
+ std::cout << std::endl;
+ }
+#endif
+
+#ifdef WITH_FATLINE_TEST
+ // test fatline algorithm
+ const double fatLine_xOffset( curr_Offset );
+ curr_Offset += 20;
+ std::cout << "# fat line testing" << std::endl
+ << "plot [t=0:1] ";
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ Bezier c( someCurves[i] );
+
+ c.p0.x += fatLine_xOffset;
+ c.p1.x += fatLine_xOffset;
+ c.p2.x += fatLine_xOffset;
+ c.p3.x += fatLine_xOffset;
+
+ FatLine line;
+
+ Impl_calcFatLine(line, c);
+
+ std::cout << " bez("
+ << c.p0.x << ","
+ << c.p1.x << ","
+ << c.p2.x << ","
+ << c.p3.x << ",t), bez("
+ << c.p0.y << ","
+ << c.p1.y << ","
+ << c.p2.y << ","
+ << c.p3.y << ",t) title \"bezier " << i << "\", linex("
+ << line.a << ","
+ << line.b << ","
+ << line.c << ",t), liney("
+ << line.a << ","
+ << line.b << ","
+ << line.c << ",t) title \"fat line (center) on " << i << "\", linex("
+ << line.a << ","
+ << line.b << ","
+ << line.c-line.dMin << ",t), liney("
+ << line.a << ","
+ << line.b << ","
+ << line.c-line.dMin << ",t) title \"fat line (min) on " << i << "\", linex("
+ << line.a << ","
+ << line.b << ","
+ << line.c-line.dMax << ",t), liney("
+ << line.a << ","
+ << line.b << ","
+ << line.c-line.dMax << ",t) title \"fat line (max) on " << i << "\"";
+
+ if( i+1<sizeof(someCurves)/sizeof(Bezier) )
+ std::cout << ",\\" << std::endl;
+ else
+ std::cout << std::endl;
+ }
+#endif
+
+#ifdef WITH_CALCFOCUS_TEST
+ // test focus curve algorithm
+ const double focus_xOffset( curr_Offset );
+ curr_Offset += 20;
+ std::cout << "# focus line testing" << std::endl
+ << "plot [t=0:1] ";
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ Bezier c( someCurves[i] );
+
+ c.p0.x += focus_xOffset;
+ c.p1.x += focus_xOffset;
+ c.p2.x += focus_xOffset;
+ c.p3.x += focus_xOffset;
+
+ // calc focus curve
+ Bezier focus;
+ Impl_calcFocus(focus, c);
+
+ std::cout << " bez("
+ << c.p0.x << ","
+ << c.p1.x << ","
+ << c.p2.x << ","
+ << c.p3.x << ",t), bez("
+ << c.p0.y << ","
+ << c.p1.y << ","
+ << c.p2.y << ","
+ << c.p3.y << ",t) title \"bezier " << i << "\", bez("
+ << focus.p0.x << ","
+ << focus.p1.x << ","
+ << focus.p2.x << ","
+ << focus.p3.x << ",t), bez("
+ << focus.p0.y << ","
+ << focus.p1.y << ","
+ << focus.p2.y << ","
+ << focus.p3.y << ",t) title \"focus " << i << "\"";
+
+ if( i+1<sizeof(someCurves)/sizeof(Bezier) )
+ std::cout << ",\\" << std::endl;
+ else
+ std::cout << std::endl;
+ }
+#endif
+
+#ifdef WITH_SAFEPARAMBASE_TEST
+ // test safe params base method
+ double safeParamsBase_xOffset( curr_Offset );
+ std::cout << "# safe param base method testing" << std::endl
+ << "plot [t=0:1] ";
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ Bezier c( someCurves[i] );
+
+ c.p0.x += safeParamsBase_xOffset;
+ c.p1.x += safeParamsBase_xOffset;
+ c.p2.x += safeParamsBase_xOffset;
+ c.p3.x += safeParamsBase_xOffset;
+
+ Polygon2D poly(4);
+ poly[0] = c.p0;
+ poly[1] = c.p1;
+ poly[2] = c.p2;
+ poly[3] = c.p3;
+
+ double t1, t2;
+
+ bool bRet( Impl_calcSafeParams( t1, t2, poly, 0, 1 ) );
+
+ Polygon2D convHull( convexHull( poly ) );
+
+ std::cout << " bez("
+ << poly[0].x << ","
+ << poly[1].x << ","
+ << poly[2].x << ","
+ << poly[3].x << ",t),bez("
+ << poly[0].y << ","
+ << poly[1].y << ","
+ << poly[2].y << ","
+ << poly[3].y << ",t), "
+ << "t+" << safeParamsBase_xOffset << ", 0, "
+ << "t+" << safeParamsBase_xOffset << ", 1, ";
+ if( bRet )
+ {
+ std::cout << t1+safeParamsBase_xOffset << ", t, "
+ << t2+safeParamsBase_xOffset << ", t, ";
+ }
+ std::cout << "'-' using ($1):($2) title \"control polygon\" with lp, "
+ << "'-' using ($1):($2) title \"convex hull\" with lp";
+
+ if( i+1<sizeof(someCurves)/sizeof(Bezier) )
+ std::cout << ",\\" << std::endl;
+ else
+ std::cout << std::endl;
+
+ safeParamsBase_xOffset += 2;
+ }
+
+ safeParamsBase_xOffset = curr_Offset;
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ Bezier c( someCurves[i] );
+
+ c.p0.x += safeParamsBase_xOffset;
+ c.p1.x += safeParamsBase_xOffset;
+ c.p2.x += safeParamsBase_xOffset;
+ c.p3.x += safeParamsBase_xOffset;
+
+ Polygon2D poly(4);
+ poly[0] = c.p0;
+ poly[1] = c.p1;
+ poly[2] = c.p2;
+ poly[3] = c.p3;
+
+ double t1, t2;
+
+ Impl_calcSafeParams( t1, t2, poly, 0, 1 );
+
+ Polygon2D convHull( convexHull( poly ) );
+
+ unsigned int k;
+ for( k=0; k<poly.size(); ++k )
+ {
+ std::cout << poly[k].x << " " << poly[k].y << std::endl;
+ }
+ std::cout << poly[0].x << " " << poly[0].y << std::endl;
+ std::cout << "e" << std::endl;
+
+ for( k=0; k<convHull.size(); ++k )
+ {
+ std::cout << convHull[k].x << " " << convHull[k].y << std::endl;
+ }
+ std::cout << convHull[0].x << " " << convHull[0].y << std::endl;
+ std::cout << "e" << std::endl;
+
+ safeParamsBase_xOffset += 2;
+ }
+ curr_Offset += 20;
+#endif
+
+#ifdef WITH_SAFEPARAMS_TEST
+ // test safe parameter range algorithm
+ const double safeParams_xOffset( curr_Offset );
+ curr_Offset += 20;
+ std::cout << "# safe param range testing" << std::endl
+ << "plot [t=0.0:1.0] ";
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ for( j=i+1; j<sizeof(someCurves)/sizeof(Bezier); ++j )
+ {
+ Bezier c1( someCurves[i] );
+ Bezier c2( someCurves[j] );
+
+ c1.p0.x += safeParams_xOffset;
+ c1.p1.x += safeParams_xOffset;
+ c1.p2.x += safeParams_xOffset;
+ c1.p3.x += safeParams_xOffset;
+ c2.p0.x += safeParams_xOffset;
+ c2.p1.x += safeParams_xOffset;
+ c2.p2.x += safeParams_xOffset;
+ c2.p3.x += safeParams_xOffset;
+
+ double t1, t2;
+
+ if( Impl_calcClipRange(t1, t2, c1, c1, c2, c2) )
+ {
+ // clip safe ranges off c1
+ Bezier c1_part1;
+ Bezier c1_part2;
+ Bezier c1_part3;
+
+ // subdivide at t1_c1
+ Impl_deCasteljauAt( c1_part1, c1_part2, c1, t1 );
+ // subdivide at t2_c1
+ Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (t2-t1)/(1.0-t1) );
+
+ // output remaining segment (c1_part1)
+
+ std::cout << " bez("
+ << c1.p0.x << ","
+ << c1.p1.x << ","
+ << c1.p2.x << ","
+ << c1.p3.x << ",t),bez("
+ << c1.p0.y << ","
+ << c1.p1.y << ","
+ << c1.p2.y << ","
+ << c1.p3.y << ",t), bez("
+ << c2.p0.x << ","
+ << c2.p1.x << ","
+ << c2.p2.x << ","
+ << c2.p3.x << ",t),bez("
+ << c2.p0.y << ","
+ << c2.p1.y << ","
+ << c2.p2.y << ","
+ << c2.p3.y << ",t), bez("
+ << c1_part1.p0.x << ","
+ << c1_part1.p1.x << ","
+ << c1_part1.p2.x << ","
+ << c1_part1.p3.x << ",t),bez("
+ << c1_part1.p0.y << ","
+ << c1_part1.p1.y << ","
+ << c1_part1.p2.y << ","
+ << c1_part1.p3.y << ",t)";
+
+ if( i+2<sizeof(someCurves)/sizeof(Bezier) )
+ std::cout << ",\\" << std::endl;
+ else
+ std::cout << std::endl;
+ }
+ }
+ }
+#endif
+
+#ifdef WITH_SAFEPARAM_DETAILED_TEST
+ // test safe parameter range algorithm
+ const double safeParams2_xOffset( curr_Offset );
+ curr_Offset += 20;
+ if( sizeof(someCurves)/sizeof(Bezier) > 1 )
+ {
+ Bezier c1( someCurves[0] );
+ Bezier c2( someCurves[1] );
+
+ c1.p0.x += safeParams2_xOffset;
+ c1.p1.x += safeParams2_xOffset;
+ c1.p2.x += safeParams2_xOffset;
+ c1.p3.x += safeParams2_xOffset;
+ c2.p0.x += safeParams2_xOffset;
+ c2.p1.x += safeParams2_xOffset;
+ c2.p2.x += safeParams2_xOffset;
+ c2.p3.x += safeParams2_xOffset;
+
+ double t1, t2;
+
+ // output happens here
+ Impl_calcClipRange(t1, t2, c1, c1, c2, c2);
+ }
+#endif
+
+#ifdef WITH_SAFEFOCUSPARAM_TEST
+ // test safe parameter range from focus algorithm
+ const double safeParamsFocus_xOffset( curr_Offset );
+ curr_Offset += 20;
+ std::cout << "# safe param range from focus testing" << std::endl
+ << "plot [t=0.0:1.0] ";
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ for( j=i+1; j<sizeof(someCurves)/sizeof(Bezier); ++j )
+ {
+ Bezier c1( someCurves[i] );
+ Bezier c2( someCurves[j] );
+
+ c1.p0.x += safeParamsFocus_xOffset;
+ c1.p1.x += safeParamsFocus_xOffset;
+ c1.p2.x += safeParamsFocus_xOffset;
+ c1.p3.x += safeParamsFocus_xOffset;
+ c2.p0.x += safeParamsFocus_xOffset;
+ c2.p1.x += safeParamsFocus_xOffset;
+ c2.p2.x += safeParamsFocus_xOffset;
+ c2.p3.x += safeParamsFocus_xOffset;
+
+ double t1, t2;
+
+ Bezier focus;
+#ifdef WITH_SAFEFOCUSPARAM_CALCFOCUS
+#if 0
+ {
+ // clip safe ranges off c1_orig
+ Bezier c1_part1;
+ Bezier c1_part2;
+ Bezier c1_part3;
+
+ // subdivide at t1_c1
+ Impl_deCasteljauAt( c1_part1, c1_part2, c2, 0.30204 );
+
+ // subdivide at t2_c1. As we're working on
+ // c1_part2 now, we have to adapt t2_c1 since
+ // we're no longer in the original parameter
+ // interval. This is based on the following
+ // assumption: t2_new = (t2-t1)/(1-t1), which
+ // relates the t2 value into the new parameter
+ // range [0,1] of c1_part2.
+ Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (0.57151-0.30204)/(1.0-0.30204) );
+
+ c2 = c1_part1;
+ Impl_calcFocus( focus, c2 );
+ }
+#else
+ Impl_calcFocus( focus, c2 );
+#endif
+#else
+ focus = c2;
+#endif
+ // determine safe range on c1
+ bool bRet( Impl_calcSafeParams_focus( t1, t2,
+ c1, focus ) );
+
+ std::cerr << "t1: " << t1 << ", t2: " << t2 << std::endl;
+
+ // clip safe ranges off c1
+ Bezier c1_part1;
+ Bezier c1_part2;
+ Bezier c1_part3;
+
+ // subdivide at t1_c1
+ Impl_deCasteljauAt( c1_part1, c1_part2, c1, t1 );
+ // subdivide at t2_c1
+ Impl_deCasteljauAt( c1_part1, c1_part3, c1_part2, (t2-t1)/(1.0-t1) );
+
+ // output remaining segment (c1_part1)
+
+ std::cout << " bez("
+ << c1.p0.x << ","
+ << c1.p1.x << ","
+ << c1.p2.x << ","
+ << c1.p3.x << ",t),bez("
+ << c1.p0.y << ","
+ << c1.p1.y << ","
+ << c1.p2.y << ","
+ << c1.p3.y << ",t) title \"c1\", "
+#ifdef WITH_SAFEFOCUSPARAM_CALCFOCUS
+ << "bez("
+ << c2.p0.x << ","
+ << c2.p1.x << ","
+ << c2.p2.x << ","
+ << c2.p3.x << ",t),bez("
+ << c2.p0.y << ","
+ << c2.p1.y << ","
+ << c2.p2.y << ","
+ << c2.p3.y << ",t) title \"c2\", "
+ << "bez("
+ << focus.p0.x << ","
+ << focus.p1.x << ","
+ << focus.p2.x << ","
+ << focus.p3.x << ",t),bez("
+ << focus.p0.y << ","
+ << focus.p1.y << ","
+ << focus.p2.y << ","
+ << focus.p3.y << ",t) title \"focus\"";
+#else
+ << "bez("
+ << c2.p0.x << ","
+ << c2.p1.x << ","
+ << c2.p2.x << ","
+ << c2.p3.x << ",t),bez("
+ << c2.p0.y << ","
+ << c2.p1.y << ","
+ << c2.p2.y << ","
+ << c2.p3.y << ",t) title \"focus\"";
+#endif
+ if( bRet )
+ {
+ std::cout << ", bez("
+ << c1_part1.p0.x << ","
+ << c1_part1.p1.x << ","
+ << c1_part1.p2.x << ","
+ << c1_part1.p3.x << ",t),bez("
+ << c1_part1.p0.y+0.01 << ","
+ << c1_part1.p1.y+0.01 << ","
+ << c1_part1.p2.y+0.01 << ","
+ << c1_part1.p3.y+0.01 << ",t) title \"part\"";
+ }
+
+ if( i+2<sizeof(someCurves)/sizeof(Bezier) )
+ std::cout << ",\\" << std::endl;
+ else
+ std::cout << std::endl;
+ }
+ }
+#endif
+
+#ifdef WITH_SAFEFOCUSPARAM_DETAILED_TEST
+ // test safe parameter range algorithm
+ const double safeParams3_xOffset( curr_Offset );
+ curr_Offset += 20;
+ if( sizeof(someCurves)/sizeof(Bezier) > 1 )
+ {
+ Bezier c1( someCurves[0] );
+ Bezier c2( someCurves[1] );
+
+ c1.p0.x += safeParams3_xOffset;
+ c1.p1.x += safeParams3_xOffset;
+ c1.p2.x += safeParams3_xOffset;
+ c1.p3.x += safeParams3_xOffset;
+ c2.p0.x += safeParams3_xOffset;
+ c2.p1.x += safeParams3_xOffset;
+ c2.p2.x += safeParams3_xOffset;
+ c2.p3.x += safeParams3_xOffset;
+
+ double t1, t2;
+
+ Bezier focus;
+#ifdef WITH_SAFEFOCUSPARAM_CALCFOCUS
+ Impl_calcFocus( focus, c2 );
+#else
+ focus = c2;
+#endif
+
+ // determine safe range on c1, output happens here
+ Impl_calcSafeParams_focus( t1, t2,
+ c1, focus );
+ }
+#endif
+
+#ifdef WITH_BEZIERCLIP_TEST
+ std::vector< std::pair<double, double> > result;
+ std::back_insert_iterator< std::vector< std::pair<double, double> > > ii(result);
+
+ // test full bezier clipping
+ const double bezierClip_xOffset( curr_Offset );
+ std::cout << std::endl << std::endl << "# bezier clip testing" << std::endl
+ << "plot [t=0:1] ";
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ for( j=i+1; j<sizeof(someCurves)/sizeof(Bezier); ++j )
+ {
+ Bezier c1( someCurves[i] );
+ Bezier c2( someCurves[j] );
+
+ c1.p0.x += bezierClip_xOffset;
+ c1.p1.x += bezierClip_xOffset;
+ c1.p2.x += bezierClip_xOffset;
+ c1.p3.x += bezierClip_xOffset;
+ c2.p0.x += bezierClip_xOffset;
+ c2.p1.x += bezierClip_xOffset;
+ c2.p2.x += bezierClip_xOffset;
+ c2.p3.x += bezierClip_xOffset;
+
+ std::cout << " bez("
+ << c1.p0.x << ","
+ << c1.p1.x << ","
+ << c1.p2.x << ","
+ << c1.p3.x << ",t),bez("
+ << c1.p0.y << ","
+ << c1.p1.y << ","
+ << c1.p2.y << ","
+ << c1.p3.y << ",t), bez("
+ << c2.p0.x << ","
+ << c2.p1.x << ","
+ << c2.p2.x << ","
+ << c2.p3.x << ",t),bez("
+ << c2.p0.y << ","
+ << c2.p1.y << ","
+ << c2.p2.y << ","
+ << c2.p3.y << ",t), '-' using (bez("
+ << c1.p0.x << ","
+ << c1.p1.x << ","
+ << c1.p2.x << ","
+ << c1.p3.x
+ << ",$1)):(bez("
+ << c1.p0.y << ","
+ << c1.p1.y << ","
+ << c1.p2.y << ","
+ << c1.p3.y << ",$1)) title \"bezier " << i << " clipped against " << j << " (t on " << i << ")\", "
+ << " '-' using (bez("
+ << c2.p0.x << ","
+ << c2.p1.x << ","
+ << c2.p2.x << ","
+ << c2.p3.x
+ << ",$1)):(bez("
+ << c2.p0.y << ","
+ << c2.p1.y << ","
+ << c2.p2.y << ","
+ << c2.p3.y << ",$1)) title \"bezier " << i << " clipped against " << j << " (t on " << j << ")\"";
+
+ if( i+2<sizeof(someCurves)/sizeof(Bezier) )
+ std::cout << ",\\" << std::endl;
+ else
+ std::cout << std::endl;
+ }
+ }
+ for( i=0; i<sizeof(someCurves)/sizeof(Bezier); ++i )
+ {
+ for( j=i+1; j<sizeof(someCurves)/sizeof(Bezier); ++j )
+ {
+ result.clear();
+ Bezier c1( someCurves[i] );
+ Bezier c2( someCurves[j] );
+
+ c1.p0.x += bezierClip_xOffset;
+ c1.p1.x += bezierClip_xOffset;
+ c1.p2.x += bezierClip_xOffset;
+ c1.p3.x += bezierClip_xOffset;
+ c2.p0.x += bezierClip_xOffset;
+ c2.p1.x += bezierClip_xOffset;
+ c2.p2.x += bezierClip_xOffset;
+ c2.p3.x += bezierClip_xOffset;
+
+ clipBezier( ii, 0.00001, c1, c2 );
+
+ for( k=0; k<result.size(); ++k )
+ {
+ std::cout << result[k].first << std::endl;
+ }
+ std::cout << "e" << std::endl;
+
+ for( k=0; k<result.size(); ++k )
+ {
+ std::cout << result[k].second << std::endl;
+ }
+ std::cout << "e" << std::endl;
+ }
+ }
+#endif
+
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/workbench/bezierclip.hxx b/basegfx/source/workbench/bezierclip.hxx
new file mode 100644
index 0000000000..54cceb414f
--- /dev/null
+++ b/basegfx/source/workbench/bezierclip.hxx
@@ -0,0 +1,84 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <vector>
+
+struct Point2D
+{
+ typedef double value_type;
+ Point2D( double _x, double _y ) : x(_x), y(_y) {}
+ Point2D() : x(), y() {}
+ double x;
+ double y;
+};
+
+struct Bezier
+{
+ Point2D p0;
+ Point2D p1;
+ Point2D p2;
+ Point2D p3;
+
+ Point2D& operator[]( int i ) { return reinterpret_cast<Point2D*>(this)[i]; }
+ const Point2D& operator[]( int i ) const { return reinterpret_cast<const Point2D*>(this)[i]; }
+};
+
+struct FatLine
+{
+ // line L through p1 and p4 in normalized implicit form
+ double a;
+ double b;
+ double c;
+
+ // the upper and lower distance from this line
+ double dMin;
+ double dMax;
+};
+
+template <typename DataType> DataType calcLineDistance( const DataType& a,
+ const DataType& b,
+ const DataType& c,
+ const DataType& x,
+ const DataType& y )
+{
+ return a*x + b*y + c;
+}
+
+typedef std::vector< Point2D > Polygon2D;
+
+/* little abs template */
+template <typename NumType> NumType absval( NumType x )
+{
+ return x<0 ? -x : x;
+}
+
+Polygon2D convexHull( const Polygon2D& rPoly );
+
+// TODO: find proper epsilon here (try std::numeric_limits<NumType>::epsilon()?)!
+constexpr auto DBL_EPSILON = 1.0e-100;
+
+/* little approximate comparisons */
+template <typename NumType> bool tolZero( NumType n ) { return fabs(n) < DBL_EPSILON; }
+template <typename NumType> bool tolEqual( NumType n1, NumType n2 ) { return tolZero(n1-n2); }
+template <typename NumType> bool tolLessEqual( NumType n1, NumType n2 ) { return tolEqual(n1,n2) || n1<n2; }
+template <typename NumType> bool tolGreaterEqual( NumType n1, NumType n2 ) { return tolEqual(n1,n2) || n1>n2; }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/workbench/convexhull.cxx b/basegfx/source/workbench/convexhull.cxx
new file mode 100644
index 0000000000..ddcb040465
--- /dev/null
+++ b/basegfx/source/workbench/convexhull.cxx
@@ -0,0 +1,200 @@
+/* -*- 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 <algorithm>
+#include <vector>
+
+#include <bezierclip.hxx>
+
+/* Implements the theta function from Sedgewick: Algorithms in XXX, chapter 24 */
+template <class PointType> double theta( const PointType& p1, const PointType& p2 )
+{
+ typename PointType::value_type dx, dy, ax, ay;
+ double t;
+
+ dx = p2.x - p1.x; ax = absval( dx );
+ dy = p2.y - p1.y; ay = absval( dy );
+ t = (ax+ay == 0) ? 0 : (double) dy/(ax+ay);
+ if( dx < 0 )
+ t = 2-t;
+ else if( dy < 0 )
+ t = 4+t;
+
+ return t*90.0;
+}
+
+/* Model of LessThanComparable for theta sort.
+ * Uses the theta function from Sedgewick: Algorithms in XXX, chapter 24
+ */
+template <class PointType> class ThetaCompare
+{
+public:
+ explicit ThetaCompare( const PointType& rRefPoint ) : maRefPoint( rRefPoint ) {}
+
+ bool operator() ( const PointType& p1, const PointType& p2 )
+ {
+ // return true, if p1 precedes p2 in the angle relative to maRefPoint
+ return theta(maRefPoint, p1) < theta(maRefPoint, p2);
+ }
+
+ double operator() ( const PointType& p ) const
+ {
+ return theta(maRefPoint, p);
+ }
+
+private:
+ PointType maRefPoint;
+};
+
+/* Implementation of the predicate 'counter-clockwise' for three points, from Sedgewick: Algorithms in XXX, chapter 24 */
+template <class PointType, class CmpFunctor> typename PointType::value_type ccw( const PointType& p0, const PointType& p1, const PointType& p2, CmpFunctor& thetaCmp )
+{
+ typename PointType::value_type dx1, dx2, dy1, dy2;
+ typename PointType::value_type theta0( thetaCmp(p0) );
+ typename PointType::value_type theta1( thetaCmp(p1) );
+ typename PointType::value_type theta2( thetaCmp(p2) );
+
+#if 0
+ if( theta0 == theta1 ||
+ theta0 == theta2 ||
+ theta1 == theta2 )
+ {
+ // cannot reliably compare, as at least two points are
+ // theta-equal. See bug description below
+ return 0;
+ }
+#endif
+
+ dx1 = p1.x - p0.x; dy1 = p1.y - p0.y;
+ dx2 = p2.x - p0.x; dy2 = p2.y - p0.y;
+
+ if( dx1*dy2 > dy1*dx2 )
+ return +1;
+
+ if( dx1*dy2 < dy1*dx2 )
+ return -1;
+
+ if( (dx1*dx2 < 0) || (dy1*dy2 < 0) )
+ return -1;
+
+ if( (dx1*dx1 + dy1*dy1) < (dx2*dx2 + dy2*dy2) )
+ return +1;
+
+ return 0;
+}
+
+/*
+ Bug
+ ===
+
+ Sometimes, the resulting polygon is not the convex hull (see below
+ for an edge configuration to reproduce that problem)
+
+ Problem analysis:
+ =================
+
+ The root cause of this bug is the fact that the second part of
+ the algorithm (the 'wrapping' of the point set) relies on the
+ previous theta sorting. Namely, it is required that the
+ generated point ordering, when interpreted as a polygon, is not
+ self-intersecting. This property, although, cannot be
+ guaranteed due to limited floating point accuracy. For example,
+ for two points very close together, and at the same time very
+ far away from the theta reference point p1, can appear on the
+ same theta value (because floating point accuracy is limited),
+ although on different rays to p1 when inspected locally.
+
+ Example:
+
+ /
+ P3 /
+ |\ /
+ | /
+ |/ \
+ P2 \
+ \
+ ...____________\
+ P1
+
+ Here, P2 and P3 are theta-equal relative to P1, but the local
+ ccw measure always says 'left turn'. Thus, the convex hull is
+ wrong at this place.
+
+ Solution:
+ =========
+
+ If two points are theta-equal and checked via ccw, ccw must
+ also classify them as 'equal'. Thus, the second stage of the
+ convex hull algorithm sorts the first one out, effectively
+ reducing a cluster of theta-equal points to only one. This
+ single point can then be treated correctly.
+*/
+
+/* Implementation of Graham's convex hull algorithm, see Sedgewick: Algorithms in XXX, chapter 25 */
+Polygon2D convexHull( const Polygon2D& rPoly )
+{
+ const Polygon2D::size_type N( rPoly.size() );
+ Polygon2D result( N + 1 );
+ std::copy(rPoly.begin(), rPoly.end(), result.begin()+1 );
+ Polygon2D::size_type min, i;
+
+ // determine safe point on hull (smallest y value)
+ for( min=1, i=2; i<=N; ++i )
+ {
+ if( result[i].y < result[min].y )
+ min = i;
+ }
+
+ // determine safe point on hull (largest x value)
+ for( i=1; i<=N; ++i )
+ {
+ if( result[i].y == result[min].y &&
+ result[i].x > result[min].x )
+ {
+ min = i;
+ }
+ }
+
+ // TODO: add inner elimination optimization from Sedgewick: Algorithms in XXX, chapter 25
+ // TODO: use radix sort instead of std::sort(), calc theta only once and store
+
+ // setup first point and sort
+ std::swap( result[1], result[min] );
+ ThetaCompare<Point2D> cmpFunc(result[1]);
+ std::sort( result.begin()+1, result.end(), cmpFunc );
+
+ // setup sentinel
+ result[0] = result[N];
+
+ // generate convex hull
+ Polygon2D::size_type M;
+ for( M=3, i=4; i<=N; ++i )
+ {
+ while( ccw(result[M], result[M-1], result[i], cmpFunc) >= 0 )
+ --M;
+
+ ++M;
+ std::swap( result[M], result[i] );
+ }
+
+ // copy range [1,M] to output
+ return Polygon2D( result.begin()+1, result.begin()+M+1 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/workbench/gauss.hxx b/basegfx/source/workbench/gauss.hxx
new file mode 100644
index 0000000000..4ef050ccbc
--- /dev/null
+++ b/basegfx/source/workbench/gauss.hxx
@@ -0,0 +1,163 @@
+/* -*- 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 .
+ */
+
+/** This method eliminates elements below main diagonal in the given
+ matrix by gaussian elimination.
+
+ @param matrix
+ The matrix to operate on. Last column is the result vector (right
+ hand side of the linear equation). After successful termination,
+ the matrix is upper triangular. The matrix is expected to be in
+ row major order.
+
+ @param rows
+ Number of rows in matrix
+
+ @param cols
+ Number of columns in matrix
+
+ @param minPivot
+ If the pivot element gets lesser than minPivot, this method fails,
+ otherwise, elimination succeeds and true is returned.
+
+ @return true, if elimination succeeded.
+ */
+
+#pragma once
+
+template <class Matrix, typename BaseType>
+bool eliminate( Matrix& matrix,
+ int rows,
+ int cols,
+ const BaseType& minPivot )
+{
+ /* i, j, k *must* be signed, when looping like: j>=0 ! */
+ /* eliminate below main diagonal */
+ for(int i=0; i<cols-1; ++i)
+ {
+ /* find best pivot */
+ int max = i;
+ for(int j=i+1; j<rows; ++j)
+ if( fabs(matrix[ j*cols + i ]) > fabs(matrix[ max*cols + i ]) )
+ max = j;
+
+ /* check pivot value */
+ if( fabs(matrix[ max*cols + i ]) < minPivot )
+ return false; /* pivot too small! */
+
+ /* interchange rows 'max' and 'i' */
+ for(int k=0; k<cols; ++k)
+ {
+ std::swap(matrix[ i*cols + k ], matrix[ max*cols + k ]);
+ }
+
+ /* eliminate column */
+ for(int j=i+1; j<rows; ++j)
+ for(int k=cols-1; k>=i; --k)
+ matrix[ j*cols + k ] -= matrix[ i*cols + k ] *
+ matrix[ j*cols + i ] / matrix[ i*cols + i ];
+ }
+
+ /* everything went well */
+ return true;
+}
+
+/** Retrieve solution vector of linear system by substituting backwards.
+
+ This operation _relies_ on the previous successful
+ application of eliminate()!
+
+ @param matrix
+ Matrix in upper diagonal form, as e.g. generated by eliminate()
+
+ @param rows
+ Number of rows in matrix
+
+ @param cols
+ Number of columns in matrix
+
+ @param result
+ Result vector. Given matrix must have space for one column (rows entries).
+
+ @return true, if back substitution was possible (i.e. no division
+ by zero occurred).
+ */
+template <class Matrix, class Vector, typename BaseType>
+bool substitute( const Matrix& matrix,
+ int rows,
+ int cols,
+ Vector& result )
+{
+ BaseType temp;
+
+ /* j, k *must* be signed, when looping like: j>=0 ! */
+ /* substitute backwards */
+ for(int j=rows-1; j>=0; --j)
+ {
+ temp = 0.0;
+ for(int k=j+1; k<cols-1; ++k)
+ temp += matrix[ j*cols + k ] * result[k];
+
+ if( matrix[ j*cols + j ] == 0.0 )
+ return false; /* imminent division by zero! */
+
+ result[j] = (matrix[ j*cols + cols-1 ] - temp) / matrix[ j*cols + j ];
+ }
+
+ /* everything went well */
+ return true;
+}
+
+/** This method determines solution of given linear system, if any
+
+ This is a wrapper for eliminate and substitute, given matrix must
+ contain right side of equation as the last column.
+
+ @param matrix
+ The matrix to operate on. Last column is the result vector (right
+ hand side of the linear equation). After successful termination,
+ the matrix is upper triangular. The matrix is expected to be in
+ row major order.
+
+ @param rows
+ Number of rows in matrix
+
+ @param cols
+ Number of columns in matrix
+
+ @param minPivot
+ If the pivot element gets lesser than minPivot, this method fails,
+ otherwise, elimination succeeds and true is returned.
+
+ @return true, if elimination succeeded.
+ */
+template <class Matrix, class Vector, typename BaseType>
+bool solve( Matrix& matrix,
+ int rows,
+ int cols,
+ Vector& result,
+ BaseType minPivot )
+{
+ if( eliminate<Matrix,BaseType>(matrix, rows, cols, minPivot) )
+ return substitute<Matrix,Vector,BaseType>(matrix, rows, cols, result);
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */