tdf#121845 rework custom shape path command U and T
The patch covers several errors, see comments in the bug report. Change-Id: I6cdaf3e8951dab21b314ef61e12ffa3b3ee481dc Reviewed-on: https://gerrit.libreoffice.org/68029 Tested-by: Jenkins Reviewed-by: Regina Henschel <rb.henschel@t-online.de>
This commit is contained in:
committed by
Thorsten Behrens
parent
0aa8fbe229
commit
3abe1e83c1
@@ -28,6 +28,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,svx_unit, \
|
||||
))
|
||||
|
||||
$(eval $(call gb_CppunitTest_use_libraries,svx_unit, \
|
||||
basegfx \
|
||||
sal \
|
||||
sfx \
|
||||
svxcore \
|
||||
|
@@ -11,6 +11,11 @@
|
||||
#include <unotest/macros_test.hxx>
|
||||
#include <rtl/ustring.hxx>
|
||||
#include <editeng/unoprnms.hxx>
|
||||
#include <basegfx/polygon/b2dpolypolygon.hxx>
|
||||
#include <svx/EnhancedCustomShape2d.hxx>
|
||||
#include <svx/svdoashp.hxx>
|
||||
#include <svx/svdopath.hxx>
|
||||
#include <svx/unoapi.hxx>
|
||||
|
||||
#include <cppunit/TestAssert.h>
|
||||
|
||||
@@ -24,13 +29,17 @@ using namespace ::com::sun::star;
|
||||
|
||||
namespace
|
||||
{
|
||||
const OUString sDataDirectory("/svx/qa/unit/data/");
|
||||
const OUString sDataDirectory("svx/qa/unit/data/");
|
||||
|
||||
/// Tests for svx/source/customshapes/ code.
|
||||
class CustomshapesTest : public test::BootstrapFixture, public unotest::MacrosTest
|
||||
{
|
||||
uno::Reference<lang::XComponent> mxComponent;
|
||||
|
||||
private:
|
||||
// get shape nShapeIndex from page 0
|
||||
uno::Reference<drawing::XShape> getShape(sal_uInt8 nShapeIndex);
|
||||
|
||||
public:
|
||||
virtual void setUp() override
|
||||
{
|
||||
@@ -51,15 +60,36 @@ public:
|
||||
void testAccuracyCommandX();
|
||||
void testToggleCommandXY();
|
||||
void testMultipleMoveTo();
|
||||
void testWidthOrientationCommandU();
|
||||
void testHalfEllipseVML();
|
||||
void testLargeSwingAngleVML();
|
||||
void testTdf121845_two_commands_U();
|
||||
|
||||
CPPUNIT_TEST_SUITE(CustomshapesTest);
|
||||
CPPUNIT_TEST(testViewBoxLeftTop);
|
||||
CPPUNIT_TEST(testAccuracyCommandX);
|
||||
CPPUNIT_TEST(testToggleCommandXY);
|
||||
CPPUNIT_TEST(testMultipleMoveTo);
|
||||
CPPUNIT_TEST(testWidthOrientationCommandU);
|
||||
CPPUNIT_TEST(testHalfEllipseVML);
|
||||
CPPUNIT_TEST(testLargeSwingAngleVML);
|
||||
CPPUNIT_TEST(testTdf121845_two_commands_U);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
};
|
||||
|
||||
uno::Reference<drawing::XShape> CustomshapesTest::getShape(sal_uInt8 nShapeIndex)
|
||||
{
|
||||
uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
|
||||
uno::UNO_QUERY_THROW);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get XDrawPagesSupplier", xDrawPagesSupplier.is());
|
||||
uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
|
||||
uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get xDrawPage", xDrawPage.is());
|
||||
uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(nShapeIndex), uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get xShape", xShape.is());
|
||||
return xShape;
|
||||
}
|
||||
|
||||
void CustomshapesTest::testViewBoxLeftTop()
|
||||
{
|
||||
// tdf#121890 formula values "left" and "top" are wrongly calculated
|
||||
@@ -68,17 +98,9 @@ void CustomshapesTest::testViewBoxLeftTop()
|
||||
= m_directories.getURLFromSrc(sDataDirectory) + "viewBox_positive_twolines_strict.odp";
|
||||
mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
|
||||
|
||||
uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
|
||||
uno::UNO_QUERY_THROW);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get XDrawPagesSupplier", xDrawPagesSupplier.is());
|
||||
uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
|
||||
uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
|
||||
|
||||
// Get the shape "leftright". Error was, that the identifier "left" was always set to zero, thus
|
||||
// the path was outside the frame rectangle for a viewBox having a positive "left" value.
|
||||
uno::Reference<drawing::XShape> xShapeLR(xDrawPage->getByIndex(0), uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape 'leftright'", xShapeLR.is());
|
||||
uno::Reference<drawing::XShape> xShapeLR(getShape(0));
|
||||
uno::Reference<beans::XPropertySet> xShapeLRProps(xShapeLR, uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape 'leftright' properties", xShapeLRProps.is());
|
||||
awt::Rectangle aFrameRectLR;
|
||||
@@ -90,8 +112,7 @@ void CustomshapesTest::testViewBoxLeftTop()
|
||||
|
||||
// Get the shape "topbottom". Error was, that the identifier "top" was always set to zero, thus
|
||||
// the path was outside the frame rectangle for a viewBox having a positive "top" value.
|
||||
uno::Reference<drawing::XShape> xShapeTB(xDrawPage->getByIndex(1), uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape 'topbottom'", xShapeTB.is());
|
||||
uno::Reference<drawing::XShape> xShapeTB(getShape(1));
|
||||
uno::Reference<beans::XPropertySet> xShapeTBProps(xShapeTB, uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape 'topbottom' properties", xShapeTBProps.is());
|
||||
awt::Rectangle aFrameRectTB;
|
||||
@@ -111,18 +132,10 @@ void CustomshapesTest::testAccuracyCommandX()
|
||||
= m_directories.getURLFromSrc(sDataDirectory) + "tdf121761_Accuracy_command_X.odp";
|
||||
mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
|
||||
|
||||
uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
|
||||
uno::UNO_QUERY_THROW);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get XDrawPagesSupplier", xDrawPagesSupplier.is());
|
||||
uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
|
||||
uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
|
||||
|
||||
// Get the shape "arc_45deg_rotated". Error was, that a Bezier curve with bad parameters
|
||||
// was used, thus the segment height was obviously smaller than for a true circle.
|
||||
// Math: segment height approx 10000 * ( 1 - sqrt(0.5)) + line width
|
||||
uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape", xShape.is());
|
||||
uno::Reference<drawing::XShape> xShape(getShape(0));
|
||||
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
|
||||
awt::Rectangle aBoundRect;
|
||||
@@ -142,18 +155,10 @@ void CustomshapesTest::testToggleCommandXY()
|
||||
= m_directories.getURLFromSrc(sDataDirectory) + "tdf121952_Toggle_direction_command_X.odp";
|
||||
mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
|
||||
|
||||
uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
|
||||
uno::UNO_QUERY_THROW);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get XDrawPagesSupplier", xDrawPagesSupplier.is());
|
||||
uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
|
||||
uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
|
||||
|
||||
// Error was, that the second segment was drawn with same direction as first one. If drawn
|
||||
// correctly, the bounding box height of the segments together is about twice the single
|
||||
// segment height. Math: segment height approx 10000 * ( 1 - sqrt(0.5)) + line width
|
||||
uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape", xShape.is());
|
||||
uno::Reference<drawing::XShape> xShape(getShape(0));
|
||||
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
|
||||
awt::Rectangle aBoundRect;
|
||||
@@ -170,18 +175,10 @@ void CustomshapesTest::testMultipleMoveTo()
|
||||
OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf122964_MultipleMoveTo.odg";
|
||||
mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.drawing.DrawingDocument");
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
|
||||
|
||||
uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
|
||||
uno::UNO_QUERY_THROW);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get XDrawPagesSupplier", xDrawPagesSupplier.is());
|
||||
uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
|
||||
uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
|
||||
|
||||
// Error was, that the second and further parameter pairs were treated as moveTo,
|
||||
// and so the generated path was empty, resulting in zero width and height of the
|
||||
// bounding box. It has to be treated same as "M 0 0 L 5 10 10 0 N".
|
||||
uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape", xShape.is());
|
||||
uno::Reference<drawing::XShape> xShape(getShape(0));
|
||||
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
|
||||
awt::Rectangle aBoundRect;
|
||||
@@ -190,6 +187,91 @@ void CustomshapesTest::testMultipleMoveTo()
|
||||
CPPUNIT_ASSERT_MESSAGE("Path is empty", !bIsZero);
|
||||
}
|
||||
|
||||
void CustomshapesTest::testWidthOrientationCommandU()
|
||||
{
|
||||
// tdf121845 custom shape with command U (angleellipse) is wrongly drawn
|
||||
// Load a document with path "M 750 0 L 750 500 250 500 250 0 U 500 0 500 500 0 180 N"
|
||||
// in viewBox="0 0 1000 500" and width="10cm", height="5cm".
|
||||
const OUString sFileName("tdf121845_WidthOrientation_command_U.odg");
|
||||
const OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + sFileName;
|
||||
mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
|
||||
// Error was, that the width and height of the ellipse was halved and that the ellipse
|
||||
// was not drawn clockwise but counter clockwise.
|
||||
uno::Reference<drawing::XShape> xShape(getShape(0));
|
||||
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
|
||||
awt::Rectangle aBoundRect;
|
||||
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
|
||||
const double fWidth = static_cast<double>(aBoundRect.Width);
|
||||
// Need some tolerance for line width
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong width", 10000.0, fWidth, 40.0);
|
||||
const double fHeight = static_cast<double>(aBoundRect.Height);
|
||||
// Wrong orientation draws segment above the top of the viewBox and so increases 'Height'.
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong orientation", 5000.0, fHeight, 40.0);
|
||||
}
|
||||
|
||||
void CustomshapesTest::testHalfEllipseVML()
|
||||
{
|
||||
// tdf121845 custom shape with command U (angleellipse) is wrongly drawn
|
||||
// Load a document which was converted from VML to doc by Word. It had a VML
|
||||
// path="m750,al500,,500,500,,-11796480e" resulting in a lower half circle.
|
||||
const OUString sFileName("tdf121845_HalfEllipseVML.doc");
|
||||
const OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + sFileName;
|
||||
mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.text.TextDocument");
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
|
||||
// Error was, that a full circle instead of the half circle was draw.
|
||||
uno::Reference<drawing::XShape> xShape(getShape(0));
|
||||
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
|
||||
awt::Rectangle aBoundRect;
|
||||
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
|
||||
const double fDiff2HmW = static_cast<double>(2 * aBoundRect.Height - aBoundRect.Width);
|
||||
// Need some tolerance for line width
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("not a half circle", 0.0, fDiff2HmW, 40.0);
|
||||
}
|
||||
|
||||
void CustomshapesTest::testLargeSwingAngleVML()
|
||||
{
|
||||
// tdf121845 custom shape with command U (angleellipse) is wrongly drawn
|
||||
// Load a document which was converted from VML to doc by Word. It had a VML
|
||||
// path="al50,50,45,45,2621440,31457280e" resulting in a full circle plus 120 deg segment.
|
||||
const OUString sFileName("tdf121845_start40_swing480.doc");
|
||||
const OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + sFileName;
|
||||
mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.text.TextDocument");
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
|
||||
// Error was, that only the 120 deg segment was drawn.
|
||||
uno::Reference<drawing::XShape> xShape(getShape(0));
|
||||
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
|
||||
awt::Rectangle aBoundRect;
|
||||
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
|
||||
const double fDiffWmH = static_cast<double>(aBoundRect.Width - aBoundRect.Height);
|
||||
// Need some tolerance for line width
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Full circle plus segment expected", 0.0, fDiffWmH, 10.0);
|
||||
}
|
||||
void CustomshapesTest::testTdf121845_two_commands_U()
|
||||
{
|
||||
// tdf121845 custom shape with command U (angleellipse) is wrongly drawn
|
||||
// Load a document with path "U 950 250 200 200 90 180 250 250 200 200 180 270 N"
|
||||
// Error was, that the second ellipse segment was interpreted as command T and
|
||||
// thus a line from first to second segment was drawn.
|
||||
const OUString sFileName("tdf121845_Two_commands_U.odg");
|
||||
OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + sFileName;
|
||||
mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
|
||||
uno::Reference<drawing::XShape> xShape(getShape(0));
|
||||
// In case no line is drawn, two polygons are generated; with line only one polygon
|
||||
SdrObjCustomShape& rSdrObjCustomShape(
|
||||
static_cast<SdrObjCustomShape&>(*GetSdrObjectFromXShape(xShape)));
|
||||
EnhancedCustomShape2d aCustomShape2d(rSdrObjCustomShape);
|
||||
SdrPathObj* pPathObj = static_cast<SdrPathObj*>(aCustomShape2d.CreateLineGeometry());
|
||||
CPPUNIT_ASSERT_MESSAGE("Could not convert to SdrPathObj", pPathObj);
|
||||
const basegfx::B2DPolyPolygon aPolyPolygon(pPathObj->GetPathPoly());
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("count polygons", static_cast<sal_uInt32>(2),
|
||||
aPolyPolygon.count());
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(CustomshapesTest);
|
||||
}
|
||||
|
||||
|
BIN
svx/qa/unit/data/tdf121845_HalfEllipseVML.doc
Normal file
BIN
svx/qa/unit/data/tdf121845_HalfEllipseVML.doc
Normal file
Binary file not shown.
BIN
svx/qa/unit/data/tdf121845_Two_commands_U.odg
Normal file
BIN
svx/qa/unit/data/tdf121845_Two_commands_U.odg
Normal file
Binary file not shown.
BIN
svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg
Normal file
BIN
svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg
Normal file
Binary file not shown.
BIN
svx/qa/unit/data/tdf121845_start40_swing480.doc
Normal file
BIN
svx/qa/unit/data/tdf121845_start40_swing480.doc
Normal file
Binary file not shown.
@@ -58,6 +58,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace ::com::sun::star;
|
||||
using namespace ::com::sun::star::uno;
|
||||
@@ -1442,6 +1443,31 @@ static basegfx::B2DPolygon CreateArc( const tools::Rectangle& rRect, const Point
|
||||
return aRetval;
|
||||
}
|
||||
|
||||
static double lcl_getNormalizedCircleAngleRad(const double fWR, const double fHR, const double fEllipseAngleDeg)
|
||||
{
|
||||
double fRet(0.0);
|
||||
double fEAngleDeg(fmod(fEllipseAngleDeg, 360.0));
|
||||
if (fEAngleDeg < 0.0)
|
||||
fEAngleDeg += 360.0;
|
||||
const double fX(fHR * cos(basegfx::deg2rad(fEAngleDeg)));
|
||||
const double fY(fWR * sin(basegfx::deg2rad(fEAngleDeg)));
|
||||
if (fX != 0.0 || fY != 0.0)
|
||||
{
|
||||
fRet = atan2(fY, fX);
|
||||
if (fRet < 0.0)
|
||||
fRet += F_2PI;
|
||||
}
|
||||
return fRet;
|
||||
}
|
||||
|
||||
static double lcl_getNormalizedAngleRad(const double fCircleAngleDeg)
|
||||
{
|
||||
double fRet(fmod(fCircleAngleDeg, 360.0));
|
||||
if (fRet < 0.0)
|
||||
fRet += 360.0;
|
||||
return basegfx::deg2rad(fRet);
|
||||
}
|
||||
|
||||
void EnhancedCustomShape2d::CreateSubPath(
|
||||
sal_Int32& rSrcPt,
|
||||
sal_Int32& rSegmentInd,
|
||||
@@ -1555,142 +1581,154 @@ void EnhancedCustomShape2d::CreateSubPath(
|
||||
}
|
||||
break;
|
||||
|
||||
case ANGLEELLIPSE :
|
||||
case ANGLEELLIPSE: // command U
|
||||
case ANGLEELLIPSETO: // command T
|
||||
{
|
||||
if ( nPntCount )
|
||||
// Some shapes will need special handling, decide on property 'Type'.
|
||||
OUString sShpType;
|
||||
SdrCustomShapeGeometryItem& rGeometryItem = const_cast<SdrCustomShapeGeometryItem&>(mrSdrObjCustomShape.GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY));
|
||||
Any* pAny = rGeometryItem.GetPropertyValueByName("Type");
|
||||
if (pAny)
|
||||
*pAny >>= sShpType;
|
||||
// User defined shapes in MS binary format, which contain command U or T after import
|
||||
// in LibreOffice, starts with "mso".
|
||||
const bool bIsFromBinaryImport(sShpType.startsWith("mso"));
|
||||
// The only own or imported preset shapes with U command are those listed below.
|
||||
// Command T is not used in preset shapes.
|
||||
const std::unordered_set<OUString> aPresetShapesWithU =
|
||||
{ "ellipse", "ring", "smiley", "sun", "forbidden", "flowchart-connector",
|
||||
"flowchart-summing-junction", "flowchart-or", "cloud-callout"};
|
||||
std::unordered_set<OUString>::const_iterator aIter = aPresetShapesWithU.find(sShpType);
|
||||
const bool bIsPresetShapeWithU(aIter != aPresetShapesWithU.end());
|
||||
|
||||
for (sal_uInt16 i = 0; (i < nPntCount) && ((rSrcPt + 2) < nCoordSize); i++)
|
||||
{
|
||||
if(aNewB2DPolygon.count() > 1)
|
||||
// ANGLEELLIPSE is the same as ANGLEELLIPSETO, only that it
|
||||
// makes an implicit MOVETO. That ends the previous subpath.
|
||||
if (ANGLEELLIPSE == nCommand)
|
||||
{
|
||||
// #i76201# Add conversion to closed polygon when first and last points are equal
|
||||
basegfx::utils::checkClosed(aNewB2DPolygon);
|
||||
aNewB2DPolyPolygon.append(aNewB2DPolygon);
|
||||
}
|
||||
aNewB2DPolygon.clear();
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case ANGLEELLIPSETO :
|
||||
{
|
||||
for ( sal_uInt16 i = 0; ( i < nPntCount ) && ( ( rSrcPt + 2 ) < nCoordSize ); i++ )
|
||||
{
|
||||
// create a circle
|
||||
Point _aCenter;
|
||||
double fWidth, fHeight;
|
||||
const mso_CustomShape* pDefCustomShape = GetCustomShapeContent( mso_sptEllipse );
|
||||
bool bIsDefaultViewBox = false;
|
||||
bool bIsDefaultPath = false;
|
||||
bool bIsMSEllipse = false;
|
||||
|
||||
if( ( nCoordWidth == pDefCustomShape->nCoordWidth )
|
||||
&& ( nCoordHeight == pDefCustomShape->nCoordHeight ) )
|
||||
bIsDefaultViewBox = true;
|
||||
sal_Int32 j, nCount = pDefCustomShape->nVertices;//==3
|
||||
std::vector< css::drawing::EnhancedCustomShapeParameterPair> seqCoordinates1, seqCoordinates2;
|
||||
|
||||
seqCoordinates1.resize( nCount );
|
||||
for ( j = 0; j < nCount; j++ )
|
||||
{
|
||||
seqCoordinates1[j] = seqCoordinates[ rSrcPt + j];
|
||||
}
|
||||
|
||||
seqCoordinates2.resize( nCount );
|
||||
for ( j = 0; j < nCount; j++ )
|
||||
{
|
||||
EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( seqCoordinates2[ j ].First, pDefCustomShape->pVertices[ j ].nValA );
|
||||
EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( seqCoordinates2[ j ].Second, pDefCustomShape->pVertices[ j ].nValB );
|
||||
}
|
||||
if(seqCoordinates1 == seqCoordinates2)
|
||||
bIsDefaultPath = true;
|
||||
|
||||
OUString sShpType;
|
||||
SdrCustomShapeGeometryItem& rGeometryItem = const_cast<SdrCustomShapeGeometryItem&>(mrSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ));
|
||||
Any* pAny = rGeometryItem.GetPropertyValueByName( "Type" );
|
||||
if ( pAny )
|
||||
*pAny >>= sShpType;
|
||||
if( sShpType.getLength() > 3 &&
|
||||
sShpType.startsWith( "mso" )){
|
||||
bIsMSEllipse = true;
|
||||
}
|
||||
if( (! bIsDefaultPath && ! bIsDefaultViewBox) || (bIsDefaultViewBox && bIsMSEllipse) /*&& (nGeneratorVersion == SfxObjectShell::Sym_L2)*/ )
|
||||
{
|
||||
_aCenter = GetPoint( seqCoordinates[ rSrcPt ], true, true );
|
||||
GetParameter( fWidth, seqCoordinates[ rSrcPt + 1 ].First, true, false );
|
||||
GetParameter( fHeight, seqCoordinates[ rSrcPt + 1 ].Second, false, true );
|
||||
fWidth /= 2;
|
||||
fHeight /= 2;
|
||||
}else if( bIsDefaultPath && !bIsDefaultViewBox /*&& (nGeneratorVersion == SfxObjectShell::Sym_L2)*/ )
|
||||
{
|
||||
_aCenter.setX( nCoordWidth/2 * fXScale );
|
||||
_aCenter.setY( nCoordHeight/2 * fYScale );
|
||||
fWidth = nCoordWidth/2;
|
||||
fHeight = nCoordHeight/2;
|
||||
const Any* pViewBox = rGeometryItem.GetPropertyValueByName( "ViewBox" );
|
||||
css::awt::Rectangle aViewBox;
|
||||
if ( pViewBox && (*pViewBox >>= aViewBox ) )
|
||||
if (aNewB2DPolygon.count() > 1)
|
||||
{
|
||||
aViewBox.Width = pDefCustomShape->nCoordWidth;
|
||||
aViewBox.Height = pDefCustomShape->nCoordHeight;
|
||||
// #i76201# Add conversion to closed polygon when first and last points are equal
|
||||
basegfx::utils::checkClosed(aNewB2DPolygon);
|
||||
aNewB2DPolyPolygon.append(aNewB2DPolygon);
|
||||
}
|
||||
css::beans::PropertyValue aPropVal;
|
||||
aPropVal.Name = "ViewBox";
|
||||
aPropVal.Value <<= aViewBox;
|
||||
rGeometryItem.SetPropertyValue( aPropVal );
|
||||
mrSdrObjCustomShape.SetMergedItem( rGeometryItem );
|
||||
}else{
|
||||
_aCenter = GetPoint( seqCoordinates[ rSrcPt ], true, true );
|
||||
GetParameter( fWidth, seqCoordinates[ rSrcPt + 1 ].First, true, false);
|
||||
GetParameter( fHeight, seqCoordinates[ rSrcPt + 1 ].Second, false, true );
|
||||
aNewB2DPolygon.clear();
|
||||
}
|
||||
|
||||
fWidth *= fXScale;
|
||||
fHeight*= fYScale;
|
||||
Point aP( static_cast<sal_Int32>( _aCenter.X() - fWidth ), static_cast<sal_Int32>( _aCenter.Y() - fHeight ) );
|
||||
Size aS( static_cast<sal_Int32>( fWidth * 2.0 ), static_cast<sal_Int32>( fHeight * 2.0 ) );
|
||||
tools::Rectangle aRect( aP, aS );
|
||||
if ( aRect.GetWidth() && aRect.GetHeight() )
|
||||
{
|
||||
double fStartAngle, fEndAngle;
|
||||
GetParameter( fStartAngle, seqCoordinates[ rSrcPt + 2 ].First, false, false );
|
||||
GetParameter( fEndAngle , seqCoordinates[ rSrcPt + 2 ].Second, false, false );
|
||||
|
||||
if ( (static_cast<sal_Int32>(fStartAngle) % 360) != (static_cast<sal_Int32>(fEndAngle) % 360) )
|
||||
{
|
||||
if ( static_cast<sal_Int32>(fStartAngle) & 0x7fff0000 ) // SJ: if the angle was imported from our escher import, then the
|
||||
fStartAngle /= 65536.0; // value is shifted by 16. TODO: already change the fixed float to a
|
||||
if ( static_cast<sal_Int32>(fEndAngle) & 0x7fff0000 ) // double in the import filter
|
||||
{
|
||||
fEndAngle /= 65536.0;
|
||||
fEndAngle = fEndAngle + fStartAngle;
|
||||
if ( fEndAngle < 0 )
|
||||
{ // in the binary filter the endangle is the amount
|
||||
double fTemp = fStartAngle;
|
||||
fStartAngle = fEndAngle;
|
||||
fEndAngle = fTemp;
|
||||
}
|
||||
}
|
||||
double fCenterX = aRect.Center().X();
|
||||
double fCenterY = aRect.Center().Y();
|
||||
double fx1 = cos(basegfx::deg2rad(fStartAngle)) * 65536.0 * fXScale
|
||||
+ fCenterX;
|
||||
double fy1 = -sin(basegfx::deg2rad(fStartAngle)) * 65536.0 * fYScale
|
||||
+ fCenterY;
|
||||
double fx2 = cos(basegfx::deg2rad(fEndAngle)) * 65536.0 * fXScale
|
||||
+ fCenterX;
|
||||
double fy2 = -sin(basegfx::deg2rad(fEndAngle)) * 65536.0 * fYScale
|
||||
+ fCenterY;
|
||||
aNewB2DPolygon.append(CreateArc( aRect, Point( static_cast<sal_Int32>(fx1), static_cast<sal_Int32>(fy1) ), Point( static_cast<sal_Int32>(fx2), static_cast<sal_Int32>(fy2) ), false));
|
||||
}
|
||||
else
|
||||
{
|
||||
basegfx::B2DPoint aEllipseCenter(aRect.Center().X(),aRect.Center().Y());
|
||||
double fRadiusX(aRect.GetWidth()/2.0);
|
||||
double fRadiusY(aRect.GetHeight()/2.0);
|
||||
aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipse(aEllipseCenter,fRadiusX,fRadiusY, 3));
|
||||
}
|
||||
}
|
||||
// Read all parameters, but do not finally handle them.
|
||||
basegfx::B2DPoint aCenter(GetPointAsB2DPoint(seqCoordinates[ rSrcPt ], true, true));
|
||||
double fWR; // horizontal ellipse radius
|
||||
double fHR; // vertical ellipse radius
|
||||
GetParameter(fWR, seqCoordinates[rSrcPt + 1].First, true, false);
|
||||
GetParameter(fHR, seqCoordinates[rSrcPt + 1].Second, false, true);
|
||||
double fStartAngle;
|
||||
GetParameter(fStartAngle, seqCoordinates[rSrcPt + 2].First, false, false);
|
||||
double fEndAngle;
|
||||
GetParameter(fEndAngle, seqCoordinates[rSrcPt + 2].Second, false, false);
|
||||
// Increasing here allows flat case differentiation tree by using 'continue'.
|
||||
rSrcPt += 3;
|
||||
}
|
||||
}
|
||||
|
||||
double fScaledWR(fWR * fXScale);
|
||||
double fScaledHR(fHR * fYScale);
|
||||
if (fScaledWR == 0.0 && fScaledHR == 0.0)
|
||||
{
|
||||
// degenerated ellipse, add center point
|
||||
aNewB2DPolygon.append(aCenter);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bIsFromBinaryImport)
|
||||
{
|
||||
// If a shape comes from MS binary ('escher') import, the angles are in degrees*2^16
|
||||
// and the second angle is not an end angle, but a swing angle.
|
||||
// MS Word shows this behavior: 0deg right, 90deg top, 180deg left and 270deg
|
||||
// bottom. Third and forth parameter are horizontal and vertical radius, not width
|
||||
// and height as noted in VML spec. A positive swing angle goes counter-clock
|
||||
// wise (in user view). The swing angle might go several times around in case
|
||||
// abs(swing angle) >= 360deg. Stroke accumulates, so that e.g. dash-dot might fill the
|
||||
// gaps of previous turn. Fill does not accumulate but uses even-odd rule, semi-transparent
|
||||
// fill does not become darker. The start and end points of the arc are calculated by
|
||||
// using the angles on a circle and then scaling the circle to the ellipse. Caution, that
|
||||
// is different from angle handling in ARCANGLETO and ODF.
|
||||
// The following implementation generates such rendering. It is only for rendering legacy
|
||||
// MS shapes and independent of the meaning of commands U and T in ODF specification.
|
||||
|
||||
// Convert from fixedfloat to double
|
||||
double fSwingAngle;
|
||||
fStartAngle /= 65536.0;
|
||||
fSwingAngle = fEndAngle / 65536.0;
|
||||
// Convert orientation
|
||||
fStartAngle = -fStartAngle;
|
||||
fSwingAngle = -fSwingAngle;
|
||||
|
||||
fEndAngle = fStartAngle + fSwingAngle;
|
||||
if (fSwingAngle < 0.0)
|
||||
std::swap(fStartAngle, fEndAngle);
|
||||
double fFrom(fStartAngle);
|
||||
double fTo(fFrom + 180.0);
|
||||
basegfx::B2DPolygon aTempB2DPolygon;
|
||||
double fS; // fFrom in radians in [0..2Pi[
|
||||
double fE; // fTo or fEndAngle in radians in [0..2PI[
|
||||
while (fTo < fEndAngle)
|
||||
{
|
||||
fS = lcl_getNormalizedAngleRad(fFrom);
|
||||
fE = lcl_getNormalizedAngleRad(fTo);
|
||||
aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fS,fE));
|
||||
fFrom = fTo;
|
||||
fTo += 180.0;
|
||||
}
|
||||
fS = lcl_getNormalizedAngleRad(fFrom);
|
||||
fE = lcl_getNormalizedAngleRad(fEndAngle);
|
||||
aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR,fS, fE));
|
||||
if (fSwingAngle < 0)
|
||||
aTempB2DPolygon.flip();
|
||||
aNewB2DPolygon.append(aTempB2DPolygon);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The not yet handled shapes are own preset shapes, or preset shapes from MS binary import, or user
|
||||
// defined shapes, or foreign shapes. Shapes from OOXML import do not use ANGLEELLIPSE or
|
||||
// ANGLEELLIPSETO, but use ARCANGLETO.
|
||||
if (bIsPresetShapeWithU)
|
||||
{
|
||||
// Besides "cloud-callout" all preset shapes have angle values '0 360'.
|
||||
// The imported "cloud-callout" has angle values '0 360' too, only our own "cloud-callout"
|
||||
// has values '0 23592960'. But that is fixedfloat and means 360*2^16. Thus all these shapes
|
||||
// have a full ellipse with start at 0deg.
|
||||
aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipse(aCenter, fScaledWR, fScaledHR));
|
||||
continue;
|
||||
}
|
||||
|
||||
// In all other cases, full ODF conform handling is necessary. ODF rules:
|
||||
// Third and forth parameter are horizontal and vertical radius.
|
||||
// An angle determines the start or end point of the segment by intersection of the second angle
|
||||
// leg with the ellipse. The first angle leg is always the positive x-axis. For the position
|
||||
// of the intersection points the angle is used modulo 360deg in range [0deg..360deg[.
|
||||
// The position of range [0deg..360deg[ is the same as in command ARCANGLETO, with 0deg right,
|
||||
// 90deg bottom, 180deg left and 270deg top. Only if abs(end angle - start angle) == 360 deg,
|
||||
// a full ellipse is drawn. The segment is always drawn clock wise (in user view) from start
|
||||
// point to end point. The end point of the segment becomes the new "current" point.
|
||||
|
||||
if (fabs(fabs(fEndAngle - fStartAngle) - 360.0) < 1.0E-15)
|
||||
{
|
||||
// draw full ellipse
|
||||
// Because createPolygonFromEllipseSegment cannot create full ellipse and
|
||||
// createPolygonFromEllipse has no variing starts, we use two half ellipses.
|
||||
const double fS(lcl_getNormalizedCircleAngleRad(fWR, fHR, fStartAngle));
|
||||
const double fH(lcl_getNormalizedCircleAngleRad(fWR, fHR, fStartAngle + 180.0));
|
||||
const double fE(lcl_getNormalizedCircleAngleRad(fWR, fHR, fEndAngle));
|
||||
aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fS, fH));
|
||||
aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fH, fE));
|
||||
continue;
|
||||
}
|
||||
|
||||
// remaining cases with central segment angle < 360
|
||||
double fS(lcl_getNormalizedCircleAngleRad(fWR, fHR, fStartAngle));
|
||||
double fE(lcl_getNormalizedCircleAngleRad(fWR, fHR, fEndAngle));
|
||||
aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fS, fE));
|
||||
} // end for
|
||||
} // end case
|
||||
break;
|
||||
|
||||
case QUADRATICCURVETO :
|
||||
|
Reference in New Issue
Block a user