diff options
author | Regina Henschel <rb.henschel@t-online.de> | 2019-02-19 20:16:45 +0100 |
---|---|---|
committer | Thorsten Behrens <Thorsten.Behrens@CIB.de> | 2019-03-04 09:51:17 +0100 |
commit | 3abe1e83c18c5778d60252092e9cc70c4c63268b (patch) | |
tree | a86b21dcd1e93898e28bb1b4d820b14c45f2ac93 | |
parent | 0aa8fbe22986aba5bf7c24e91cc3f7aff5efc296 (diff) |
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>
-rw-r--r-- | svx/CppunitTest_svx_unit.mk | 1 | ||||
-rw-r--r-- | svx/qa/unit/customshapes.cxx | 160 | ||||
-rw-r--r-- | svx/qa/unit/data/tdf121845_HalfEllipseVML.doc | bin | 0 -> 27648 bytes | |||
-rw-r--r-- | svx/qa/unit/data/tdf121845_Two_commands_U.odg | bin | 0 -> 10751 bytes | |||
-rw-r--r-- | svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg | bin | 0 -> 9908 bytes | |||
-rw-r--r-- | svx/qa/unit/data/tdf121845_start40_swing480.doc | bin | 0 -> 27648 bytes | |||
-rw-r--r-- | svx/source/customshapes/EnhancedCustomShape2d.cxx | 280 |
7 files changed, 281 insertions, 160 deletions
diff --git a/svx/CppunitTest_svx_unit.mk b/svx/CppunitTest_svx_unit.mk index 4ae8fecf2762..c6c30138f4db 100644 --- a/svx/CppunitTest_svx_unit.mk +++ b/svx/CppunitTest_svx_unit.mk @@ -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 \ diff --git a/svx/qa/unit/customshapes.cxx b/svx/qa/unit/customshapes.cxx index a0c256b5d6e0..23d3fb676770 100644 --- a/svx/qa/unit/customshapes.cxx +++ b/svx/qa/unit/customshapes.cxx @@ -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); } diff --git a/svx/qa/unit/data/tdf121845_HalfEllipseVML.doc b/svx/qa/unit/data/tdf121845_HalfEllipseVML.doc Binary files differnew file mode 100644 index 000000000000..043e4e15f91e --- /dev/null +++ b/svx/qa/unit/data/tdf121845_HalfEllipseVML.doc diff --git a/svx/qa/unit/data/tdf121845_Two_commands_U.odg b/svx/qa/unit/data/tdf121845_Two_commands_U.odg Binary files differnew file mode 100644 index 000000000000..c0f7ff34f901 --- /dev/null +++ b/svx/qa/unit/data/tdf121845_Two_commands_U.odg diff --git a/svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg b/svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg Binary files differnew file mode 100644 index 000000000000..349c2eb81086 --- /dev/null +++ b/svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg diff --git a/svx/qa/unit/data/tdf121845_start40_swing480.doc b/svx/qa/unit/data/tdf121845_start40_swing480.doc Binary files differnew file mode 100644 index 000000000000..ff37aab3fa0d --- /dev/null +++ b/svx/qa/unit/data/tdf121845_start40_swing480.doc diff --git a/svx/source/customshapes/EnhancedCustomShape2d.cxx b/svx/source/customshapes/EnhancedCustomShape2d.cxx index ee5a92fe8226..bc143c47c971 100644 --- a/svx/source/customshapes/EnhancedCustomShape2d.cxx +++ b/svx/source/customshapes/EnhancedCustomShape2d.cxx @@ -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]; + if (aNewB2DPolygon.count() > 1) + { + // #i76201# Add conversion to closed polygon when first and last points are equal + basegfx::utils::checkClosed(aNewB2DPolygon); + aNewB2DPolyPolygon.append(aNewB2DPolygon); + } + aNewB2DPolygon.clear(); } - seqCoordinates2.resize( nCount ); - for ( j = 0; j < nCount; j++ ) + // 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) { - EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( seqCoordinates2[ j ].First, pDefCustomShape->pVertices[ j ].nValA ); - EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( seqCoordinates2[ j ].Second, pDefCustomShape->pVertices[ j ].nValB ); + // degenerated ellipse, add center point + aNewB2DPolygon.append(aCenter); + continue; } - 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)*/ ) + + if (bIsFromBinaryImport) { - _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 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) { - aViewBox.Width = pDefCustomShape->nCoordWidth; - aViewBox.Height = pDefCustomShape->nCoordHeight; + fS = lcl_getNormalizedAngleRad(fFrom); + fE = lcl_getNormalizedAngleRad(fTo); + aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fS,fE)); + fFrom = fTo; + fTo += 180.0; } - 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 ); + 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; } - 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() ) + // 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) { - double fStartAngle, fEndAngle; - GetParameter( fStartAngle, seqCoordinates[ rSrcPt + 2 ].First, false, false ); - GetParameter( fEndAngle , seqCoordinates[ rSrcPt + 2 ].Second, false, false ); + // 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; + } - 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)); - } + // 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; } - rSrcPt += 3; - } - } + + // 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 : |