summaryrefslogtreecommitdiff
path: root/svx
diff options
context:
space:
mode:
authorRegina Henschel <rb.henschel@t-online.de>2020-06-10 20:58:54 +0200
committerXisco Fauli <xiscofauli@libreoffice.org>2020-06-16 13:15:16 +0200
commit6eaa80f5c40619d8d27c1e5dc180bd2289bd3559 (patch)
tree35d5462eea38db41b3a7affb1fcfb2be88e734a7 /svx
parentbca5d96335fd3742b721e99b10934502b105259a (diff)
tdf#103474 handle edge cases in ARCANGLETO
The arc-polygon generation in tools does not consider edge cases with zero height or width. That leads to wrong rendering in some ooxml shapes, when the handle is dragged to its extrem position, e.g. in left/right braces/bracket and can. I have switched from tools to basegfx in case ARCANGLETO and added handling for edge cases. Switching to basegfx has the additional advantage, that Bezier curves are used and not polylines. You see the difference, if you convert the shape to curve. ARCANGLETO is not used from our shapes or from import from binary MS Office, but only from OOXML or user-defined custom shapes. tdf#122323 MS Office restricts the swing angle to [-360°,360] in rendering. Such restriction is not in OOXML and not in ODF. Nevertheless, I have added a clamp for ooxml-foo shapes for better interoperability. Change-Id: Ib3233ce14dab950cc521cb8cbac6809a1d3e34a7 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/96068 Tested-by: Jenkins Reviewed-by: Regina Henschel <rb.henschel@t-online.de> (cherry picked from commit 6de8d3109dffa7d4d0cc06f319cca70134f0a8f3) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/96335 Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Diffstat (limited to 'svx')
-rw-r--r--svx/qa/unit/customshapes.cxx53
-rw-r--r--svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odpbin0 -> 12420 bytes
-rw-r--r--svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptxbin0 -> 15580 bytes
-rw-r--r--svx/source/customshapes/EnhancedCustomShape2d.cxx112
4 files changed, 120 insertions, 45 deletions
diff --git a/svx/qa/unit/customshapes.cxx b/svx/qa/unit/customshapes.cxx
index d8f7fb8a5bf9..309c5155d8da 100644
--- a/svx/qa/unit/customshapes.cxx
+++ b/svx/qa/unit/customshapes.cxx
@@ -704,6 +704,59 @@ CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf103474_commandT_CaseZeroHeight)
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aStart y-coordinate", 9999.0, aStart.getY(), 1.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aEnd y-coordinate", 1999.0, aEnd.getY(), 1.0);
}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf103474_commandG_CaseZeroHeight)
+{
+ // Some as above, but with shape with command G.
+ OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf103474_commandG_CaseZeroHeight.odp";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.presentation.PresentationDocument");
+ CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ // The end points of the straight line segment should have the same x-coordinate of left
+ // of shape, and different y-coordinates, one top and the other bottom of the shape.
+ 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>(1),
+ aPolyPolygon.count());
+ const basegfx::B2DPolygon aPolygon(aPolyPolygon.getB2DPolygon(0));
+ // Get the middle points of the polygon. They are the endpoints of the
+ // straight line segment regardless of the quarter ellipse parts, because
+ // the shape is symmetric.
+ const basegfx::B2DPoint aStart(aPolygon.getB2DPoint(aPolygon.count() / 2 - 1));
+ const basegfx::B2DPoint aEnd(aPolygon.getB2DPoint(aPolygon.count() / 2));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aStart x-coordinate", 1999.0, aStart.getX(), 1.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aEnd x-coordinate", 1999.0, aEnd.getX(), 1.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aStart y-coordinate", 9999.0, aStart.getY(), 1.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aEnd y-coordinate", 1999.0, aEnd.getY(), 1.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf122323_largeSwingAngle)
+{
+ // SwingAngles are clamped to [-360;360] in MS Office. Error was, that LO calculated
+ // the end angle and used it modulo 360, no full ellipse was drawn.
+ OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf122323_swingAngle_larger360deg.pptx";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.presentation.PresentationDocument");
+ CPPUNIT_ASSERT_MESSAGE("Could not load document", mxComponent.is());
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ 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());
+ const basegfx::B2DPolygon aPolygon(aPolyPolygon.getB2DPolygon(0));
+ const basegfx::B2DPoint aStart(aPolygon.getB2DPoint(0));
+ // last point comes from line to center, therefore -2 instead of -1
+ const basegfx::B2DPoint aEnd(aPolygon.getB2DPoint(aPolygon.count() - 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Start <> End", aStart, aEnd);
+}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odp b/svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odp
new file mode 100644
index 000000000000..9b36d45eed6a
--- /dev/null
+++ b/svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptx b/svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptx
new file mode 100644
index 000000000000..919675ef9d27
--- /dev/null
+++ b/svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptx
Binary files differ
diff --git a/svx/source/customshapes/EnhancedCustomShape2d.cxx b/svx/source/customshapes/EnhancedCustomShape2d.cxx
index 8b570edd809e..89360b62fc70 100644
--- a/svx/source/customshapes/EnhancedCustomShape2d.cxx
+++ b/svx/source/customshapes/EnhancedCustomShape2d.cxx
@@ -2356,58 +2356,80 @@ void EnhancedCustomShape2d::CreateSubPath(
case ARCANGLETO :
{
- double fWR, fHR, fStartAngle, fSwingAngle;
+ double fWR, fHR; // in Shape coordinate system
+ double fStartAngle, fSwingAngle; // in deg
for ( sal_uInt16 i = 0; ( i < nPntCount ) && ( rSrcPt + 1 < nCoordSize ); i++ )
{
- GetParameter ( fWR, seqCoordinates[ static_cast<sal_uInt16>(rSrcPt) ].First, true, false );
- GetParameter ( fHR, seqCoordinates[ static_cast<sal_uInt16>(rSrcPt) ].Second, false, true );
-
- GetParameter ( fStartAngle, seqCoordinates[ static_cast<sal_uInt16>( rSrcPt + 1) ].First, false, false );
- GetParameter ( fSwingAngle, seqCoordinates[ static_cast<sal_uInt16>( rSrcPt + 1 ) ].Second, false, false );
-
- // Convert angles to radians, but don't do any scaling / translation yet.
-
- fStartAngle = basegfx::deg2rad(fStartAngle);
- fSwingAngle = basegfx::deg2rad(fSwingAngle);
+ basegfx::B2DPoint aTempPair;
+ aTempPair = GetPointAsB2DPoint(seqCoordinates[static_cast<sal_uInt16>(rSrcPt)], false /*bScale*/, false /*bReplaceGeoSize*/);
+ fWR = aTempPair.getX();
+ fHR = aTempPair.getY();
+ aTempPair = GetPointAsB2DPoint(seqCoordinates[static_cast<sal_uInt16>(rSrcPt + 1)], false /*bScale*/, false /*bReplaceGeoSize*/);
+ fStartAngle = aTempPair.getX();
+ fSwingAngle = aTempPair.getY();
+
+ // tdf#122323 MS Office clamps the swing angle to [-360,360]. Such restriction
+ // is neither in OOXML nor in ODF. Nevertheless, to be compatible we do it for
+ // "ooxml-foo" shapes. Those shapes have their origin in MS Office.
+ if (bOOXMLShape)
+ {
+ fSwingAngle = std::clamp(fSwingAngle, -360.0, 360.0);
+ }
SAL_INFO("svx", "ARCANGLETO scale: " << fWR << "x" << fHR << " angles: " << fStartAngle << "," << fSwingAngle);
- bool bClockwise = fSwingAngle >= 0.0;
-
- if (aNewB2DPolygon.count() > 0)
+ if (aNewB2DPolygon.count() > 0) // otherwise no "current point"
{
- basegfx::B2DPoint aStartPointB2D( aNewB2DPolygon.getB2DPoint(aNewB2DPolygon.count() - 1 ) );
- Point aStartPoint( 0, 0 );
-
- double fT = atan2((fWR*sin(fStartAngle)), (fHR*cos(fStartAngle)));
- double fTE = atan2((fWR*sin(fStartAngle + fSwingAngle)), fHR*cos(fStartAngle + fSwingAngle));
-
- SAL_INFO("svx", "ARCANGLETO angles: " << fStartAngle << ", " << fSwingAngle
- << " --> parameters: " << fT <<", " << fTE );
-
- fWR *= fXScale;
- fHR *= fYScale;
-
- tools::Rectangle aRect ( Point ( aStartPoint.getX() - fWR*cos(fT) - fWR, aStartPoint.getY() - fHR*sin(fT) - fHR ),
- Point ( aStartPoint.getX() - fWR*cos(fT) + fWR, aStartPoint.getY() - fHR*sin(fT) + fHR) );
-
- Point aEndPoint ( aStartPoint.getX() - fWR*(cos(fT) - cos(fTE)), aStartPoint.getY() - fHR*(sin(fT) - sin(fTE)) );
-
- SAL_INFO(
- "svx",
- "ARCANGLETO rect: " << aRect.Left() << ", "
- << aRect.Top() << " x " << aRect.Right()
- << ", " << aRect.Bottom() << " start: "
- << aStartPoint.X() << ", "
- << aStartPoint.Y() << " end: "
- << aEndPoint.X() << ", " << aEndPoint.Y()
- << " clockwise: " << int(bClockwise));
- basegfx::B2DPolygon aArc = CreateArc( aRect, bClockwise ? aEndPoint : aStartPoint, bClockwise ? aStartPoint : aEndPoint, bClockwise, aStartPoint == aEndPoint && ((bClockwise && fSwingAngle > F_PI) || (!bClockwise && fSwingAngle < -F_PI)));
- // Now that we have the arc, move it to aStartPointB2D.
- basegfx::B2DHomMatrix aMatrix = basegfx::utils::createTranslateB2DHomMatrix(aStartPointB2D.getX(), aStartPointB2D.getY());
- aArc.transform(aMatrix);
- aNewB2DPolygon.append(aArc);
+ // use similar methods as in command U
+ basegfx::B2DPolygon aTempB2DPolygon;
+
+ if (fWR == 0.0 && fHR == 0.0)
+ {
+ // degenerated ellipse, add this one point
+ aTempB2DPolygon.append(basegfx::B2DPoint(0.0, 0.0));
+ }
+ else
+ {
+ double fEndAngle = fStartAngle + fSwingAngle;
+ // Generate arc with ellipse left|top = 0|0.
+ basegfx::B2DPoint aCenter(fWR, fHR);
+ if (fSwingAngle < 0.0)
+ std::swap(fStartAngle, fEndAngle);
+ double fS; // fFrom in radians in [0..2Pi[
+ double fE; // fTo or fEndAngle in radians in [0..2PI[
+ double fFrom(fStartAngle);
+ // createPolygonFromEllipseSegment expects angles in [0..2PI[.
+ if (fSwingAngle >= 360.0 || fSwingAngle <= -360.0)
+ {
+ double fTo(fFrom + 180.0);
+ while (fTo < fEndAngle)
+ {
+ fS = lcl_getNormalizedCircleAngleRad(fWR, fHR, fFrom);
+ fE = lcl_getNormalizedCircleAngleRad(fWR, fHR, fTo);
+ aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fWR, fHR, fS,fE));
+ fFrom = fTo;
+ fTo += 180.0;
+ }
+ }
+ fS = lcl_getNormalizedCircleAngleRad(fWR, fHR, fFrom);
+ fE = lcl_getNormalizedCircleAngleRad(fWR, fHR, fEndAngle);
+ aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fWR, fHR,fS, fE));
+ if (fSwingAngle < 0)
+ aTempB2DPolygon.flip();
+ aTempB2DPolygon.removeDoublePoints();
+ }
+ // Scale arc to 1/100mm
+ basegfx::B2DHomMatrix aMatrix = basegfx::utils::createScaleB2DHomMatrix(fXScale, fYScale);
+ aTempB2DPolygon.transform(aMatrix);
+
+ // Now that we have the arc, move it to the "current point".
+ basegfx::B2DPoint aCurrentPointB2D( aNewB2DPolygon.getB2DPoint(aNewB2DPolygon.count() - 1 ) );
+ const double fDx(aCurrentPointB2D.getX() - aTempB2DPolygon.getB2DPoint(0).getX());
+ const double fDy(aCurrentPointB2D.getY() - aTempB2DPolygon.getB2DPoint(0).getY());
+ aMatrix = basegfx::utils::createTranslateB2DHomMatrix(fDx, fDy);
+ aTempB2DPolygon.transform(aMatrix);
+ aNewB2DPolygon.append(aTempB2DPolygon);
}
rSrcPt += 2;