diff options
18 files changed, 861 insertions, 407 deletions
diff --git a/include/oox/export/drawingml.hxx b/include/oox/export/drawingml.hxx index 63a32225172b..7fbb015b4ce4 100644 --- a/include/oox/export/drawingml.hxx +++ b/include/oox/export/drawingml.hxx @@ -43,6 +43,7 @@ #include <vcl/checksum.hxx> #include <tools/gen.hxx> #include <vcl/mapmod.hxx> +#include <svx/EnhancedCustomShape2d.hxx> class Graphic; class SdrObjCustomShape; @@ -198,7 +199,8 @@ protected: void WriteGlowEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet); void WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet); - bool HasEnhancedCustomShapeSegmentCommand(const css::uno::Reference<css::drawing::XShape>& rXShape, const sal_Int16 nCommand); + void WriteCustomGeometryPoint(const css::drawing::EnhancedCustomShapeParameterPair& rParamPair, + const EnhancedCustomShape2d& rCustomShape2d); public: DrawingML( ::sax_fastparser::FSHelperPtr pFS, ::oox::core::XmlFilterBase* pFB, DocumentType eDocumentType = DOCUMENT_PPTX, DMLTextExport* pTextExport = nullptr ) @@ -306,14 +308,7 @@ public: bool WriteCustomGeometry( const css::uno::Reference<css::drawing::XShape>& rXShape, const SdrObjCustomShape& rSdrObjCustomShape); - void WriteCustomGeometryPoint( - const css::drawing::EnhancedCustomShapeParameterPair& rParamPair, - const SdrObjCustomShape& rSdrObjCustomShape); - static sal_Int32 GetCustomGeometryPointValue( - const css::drawing::EnhancedCustomShapeParameter& rParam, - const SdrObjCustomShape& rSdrObjCustomShape); - void WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape, - const tools::PolyPolygon& rPolyPolygon, const bool bClosed); + void WriteEmptyCustomGeometry(); void WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape, const bool bClosed); void WriteFill( const css::uno::Reference< css::beans::XPropertySet >& xPropSet ); diff --git a/oox/qa/unit/data/tdf147978_endsubpath.odp b/oox/qa/unit/data/tdf147978_endsubpath.odp Binary files differnew file mode 100644 index 000000000000..2dfd55de1be3 --- /dev/null +++ b/oox/qa/unit/data/tdf147978_endsubpath.odp diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandA.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandA.odp Binary files differnew file mode 100644 index 000000000000..99ddda7c132e --- /dev/null +++ b/oox/qa/unit/data/tdf147978_enhancedPath_commandA.odp diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odp Binary files differnew file mode 100644 index 000000000000..49e01bc0933a --- /dev/null +++ b/oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odp diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandT.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandT.odp Binary files differnew file mode 100644 index 000000000000..3dcd0d567545 --- /dev/null +++ b/oox/qa/unit/data/tdf147978_enhancedPath_commandT.odp diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odp Binary files differnew file mode 100644 index 000000000000..6112251783e1 --- /dev/null +++ b/oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odp diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptx b/oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptx Binary files differnew file mode 100644 index 000000000000..bbedc7ab98f5 --- /dev/null +++ b/oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptx diff --git a/oox/qa/unit/export.cxx b/oox/qa/unit/export.cxx index dd722cd04e79..e104b4effdd7 100644 --- a/oox/qa/unit/export.cxx +++ b/oox/qa/unit/export.cxx @@ -374,6 +374,161 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf146690_endParagraphRunPropertiesNewLinesTextSi assertXPath(pXmlDoc, "//p:sp[1]/p:txBody/a:p[2]/a:endParaRPr", "sz", "500"); assertXPath(pXmlDoc, "//p:sp[1]/p:txBody/a:p[3]/a:endParaRPr", "sz", "500"); } + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_endsubpath) +{ + // Given an odp file that contains a non-primitive custom shape with command N + OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_endsubpath.odp"; + + // When saving that document: + loadAndSave(aURL, "Impress Office Open XML"); + + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // Then make sure the pathLst has two child elements, + // Without the accompanying fix in place, only one element a:path was exported. + assertXPathChildren(pXmlDoc, "//a:pathLst", 2); + // and make sure first path with no stroke, second with no fill + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "stroke", "0"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]", "fill", "none"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_commandA) +{ + // Given an odp file that contains a non-primitive custom shape with command N + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_enhancedPath_commandA.odp"; + + // When saving that document: + loadAndSave(aURL, "Impress Office Open XML"); + + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // Then make sure the path has a child element arcTo. Prior to the fix that part of the curve was + // not exported at all. In odp it is a command A. Such does not exist in OOXML and is therefore + // exported as a:lnTo followed by a:arcTo + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:lnTo", 2); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", 1); + // And assert its attribute values + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "wR", "7200"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "hR", "5400"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "stAng", "7719588"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "swAng", "-5799266"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_commandT) +{ + // The odp file contains a non-primitive custom shape with commands MTZ + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_enhancedPath_commandT.odp"; + + // Export to pptx had only exported the command M and has used a wrong path size + loadAndSave(aURL, "Impress Office Open XML"); + + // Verify the markup: + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // File has draw:viewBox="0 0 216 216" + assertXPath(pXmlDoc, "//a:pathLst/a:path", "w", "216"); + assertXPath(pXmlDoc, "//a:pathLst/a:path", "h", "216"); + // Command T is exported as lnTo followed by arcTo. + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:moveTo", 1); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:lnTo", 1); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", 1); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:close", 1); + // And assert its values + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:moveTo/a:pt", "x", "108"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:moveTo/a:pt", "y", "162"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:lnTo/a:pt", "x", "138"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:lnTo/a:pt", "y", "110"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "wR", "108"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "hR", "54"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "stAng", "18000000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "swAng", "18000000"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_commandXY) +{ + // The odp file contains a non-primitive custom shapes with commands XY + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_enhancedPath_commandXY.odp"; + + // Export to pptx had dropped commands X and Y. + loadAndSave(aURL, "Impress Office Open XML"); + + // Verify the markup: + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // File has draw:viewBox="0 0 10 10" + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "w", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "h", "10"); + // Shape has M 0 5 Y 5 0 10 5 5 10 F Y 0 5 N M 10 10 X 0 0 + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:moveTo/a:pt", "x", "0"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:moveTo/a:pt", "y", "5"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[1]", "wR", "5"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[1]", "hR", "5"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[1]", "stAng", "10800000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[1]", "swAng", "5400000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[2]", "stAng", "16200000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[2]", "swAng", "5400000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[3]", "stAng", "0"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[3]", "swAng", "5400000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[4]", "stAng", "0"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[4]", "swAng", "-5400000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:moveTo/a:pt", "x", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:moveTo/a:pt", "y", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:arcTo", "wR", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:arcTo", "hR", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:arcTo", "stAng", "5400000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:arcTo", "swAng", "5400000"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_commandHIJK) +{ + // The odp file contains a non-primitive custom shapes with commands H,I,J,K + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_enhancedPath_commandHIJK.odp"; + + // Export to pptx had dropped commands X and Y. + loadAndSave(aURL, "Impress Office Open XML"); + + // Verify the markup: + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // File has draw:viewBox="0 0 80 80" + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "w", "80"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "h", "80"); + // File uses from back to front J (lighten), I (lightenLess), normal fill, K (darkenLess), + // H (darken). New feature, old versions did not export these at all. + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "fill", "lighten"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]", "fill", "lightenLess"); + assertXPathNoAttribute(pXmlDoc, "//a:pathLst/a:path[3]", "fill"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[4]", "fill", "darkenLess"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[5]", "fill", "darken"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_subpath) +{ + // The odp file contains a non-primitive custom shapes with commands H,I,J,K + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_enhancedPath_subpath.pptx"; + + // Export to pptx had dropped the subpaths. + loadAndSave(aURL, "Impress Office Open XML"); + + // Verify the markup: + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // File should have four subpaths with increasing path size + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "w", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "h", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]", "w", "20"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]", "h", "20"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[3]", "w", "40"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[3]", "h", "40"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[4]", "w", "80"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[4]", "h", "80"); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx index c457e65ac0c8..708aea6fb29a 100644 --- a/oox/source/export/drawingml.cxx +++ b/oox/source/export/drawingml.cxx @@ -62,6 +62,7 @@ #include <com/sun/star/drawing/ColorMode.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp> #include <com/sun/star/drawing/Hatch.hpp> @@ -137,6 +138,7 @@ using namespace ::css::style; using namespace ::css::text; using namespace ::css::uno; using namespace ::css::container; +using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand; using ::css::io::XOutputStream; using ::sax_fastparser::FSHelperPtr; @@ -3658,6 +3660,86 @@ void DrawingML::WritePresetShape( const char* pShape, MSO_SPT eShapeType, bool b mpFS->endElementNS( XML_a, XML_prstGeom ); } +namespace // helpers for DrawingML::WriteCustomGeometry +{ +sal_Int32 +FindNextCommandEndSubpath(const sal_Int32 nStart, + const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments) +{ + sal_Int32 i = nStart < 0 ? 0 : nStart; + while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH) + i++; + return i; +} + +bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast, + const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments) +{ + for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++) + { + if (rSegments[i].Command == nCommand) + return true; + } + return false; +} + +// Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP +// intersects the ellipse in point S and this point S has angle fAngleDeg in degrees. +void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy, + const double fWR, const double fHR, const double fCx, + const double fCy, const double fRayPx, const double fRayPy) +{ + if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR)) + { + rfSx = fCx; // needed for getting new 'current point' + rfSy = fCy; + } + else + { + // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation + // and get angle + double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx); + // use angle for intersection point on circle and stretch back to ellipse + double fPointMathEllipse_x = fWR * cos(fCircleMathAngle); + double fPointMathEllipse_y = fHR * sin(fCircleMathAngle); + // get angle of intersection point on ellipse + double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x); + // convert from Math to View orientation and shift ellipse back from origin + rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle); + rfSx = fPointMathEllipse_x + fCx; + rfSy = -fPointMathEllipse_y + fCy; + } +} + +void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR, + const double fCx, const double fCy, const double fViewAngleDeg) +{ + if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR)) + { + rfSx = fCx; // needed for getting new 'current point' + rfSy = fCy; + } + else + { + double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR; + double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR; + double fRadius = 1.0 / std::hypot(fX, fY); + rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg)); + rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg)); + } +} + +sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam, + const EnhancedCustomShape2d& rCustomShape2d) +{ + double fValue = 0.0; + rCustomShape2d.GetParameter(fValue, rParam, false, false); + sal_Int32 nValue(std::lround(fValue)); + + return nValue; +} +} + bool DrawingML::WriteCustomGeometry( const Reference< XShape >& rXShape, const SdrObjCustomShape& rSdrObjCustomShape) @@ -3679,349 +3761,574 @@ bool DrawingML::WriteCustomGeometry( return false; } - auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny); + if (!pGeometrySeq) + return false; + + auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), + [](const PropertyValue& rProp) { return rProp.Name == "Path"; }); + if (pPathProp == std::cend(*pGeometrySeq)) + return false; - if ( pGeometrySeq ) + uno::Sequence<beans::PropertyValue> aPathProp; + pPathProp->Value >>= aPathProp; + + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs; + uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; + uno::Sequence<awt::Size> aPathSize; + for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp)) { - for( const beans::PropertyValue& rProp : *pGeometrySeq ) - { - if ( rProp.Name == "Path" ) - { - uno::Sequence<beans::PropertyValue> aPathProp; - rProp.Value >>= aPathProp; + if (rPathProp.Name == "Coordinates") + rPathProp.Value >>= aPairs; + else if (rPathProp.Name == "Segments") + rPathProp.Value >>= aSegments; + else if (rPathProp.Name == "SubViewSize") + rPathProp.Value >>= aPathSize; + } - uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs; - uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; - uno::Sequence<awt::Size> aPathSize; - for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp)) - { - if (rPathProp.Name == "Coordinates") - rPathProp.Value >>= aPairs; - else if (rPathProp.Name == "Segments") - rPathProp.Value >>= aSegments; - else if (rPathProp.Name == "SubViewSize") - rPathProp.Value >>= aPathSize; - } + if ( !aPairs.hasElements() ) + return false; - if ( !aPairs.hasElements() ) - return false; + if ( !aSegments.hasElements() ) + { + aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment> + { + { MOVETO, 1 }, + { LINETO, + static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) }, + { CLOSESUBPATH, 0 }, + { ENDSUBPATH, 0 } + }; + }; - if ( !aSegments.hasElements() ) - { - aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment> - { - { drawing::EnhancedCustomShapeSegmentCommand::MOVETO, 1 }, - { drawing::EnhancedCustomShapeSegmentCommand::LINETO, - static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) }, - { drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH, 0 }, - { drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH, 0 } - }; - }; + int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0, + [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; }); - int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0, - [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; }); + if ( nExpectedPairCount > aPairs.getLength() ) + { + SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs."); + return false; + } - if ( nExpectedPairCount > aPairs.getLength() ) - { - SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs."); - return false; - } + // A EnhancedCustomShape2d caches the equation results. Therefor we use only one of it for the + // entire method. + const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape)); - mpFS->startElementNS(XML_a, XML_custGeom); - mpFS->singleElementNS(XML_a, XML_avLst); - mpFS->singleElementNS(XML_a, XML_gdLst); - mpFS->singleElementNS(XML_a, XML_ahLst); - mpFS->singleElementNS(XML_a, XML_rect, XML_l, "l", XML_t, "t", - XML_r, "r", XML_b, "b"); - mpFS->startElementNS(XML_a, XML_pathLst); + mpFS->startElementNS(XML_a, XML_custGeom); + mpFS->singleElementNS(XML_a, XML_avLst); + mpFS->singleElementNS(XML_a, XML_gdLst); + mpFS->singleElementNS(XML_a, XML_ahLst); + // ToDO: use draw:TextAreas for <a:rect> + mpFS->singleElementNS(XML_a, XML_rect, XML_l, "l", XML_t, "t", XML_r, "r", XML_b, "b"); + mpFS->startElementNS(XML_a, XML_pathLst); - std::optional<OString> sFill; - if (HasEnhancedCustomShapeSegmentCommand(rXShape, css::drawing::EnhancedCustomShapeSegmentCommand::NOFILL)) - sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard + // Prepare width and height for <a:path> + bool bUseGlobalViewBox(false); + + // nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not + // triggered; same for height. + sal_Int32 nViewBoxWidth(0); + sal_Int32 nViewBoxHeight(0); + if (!aPathSize.hasElements()) + { + bUseGlobalViewBox = true; + // If draw:viewBox is missing in draw:enhancedGeometry, then import sets + // viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated + // current file via macro. Author of macro has to fix it. + auto pProp = std::find_if( + std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), + [](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; }); + if (pProp != std::cend(*pGeometrySeq)) + { + css::awt::Rectangle aViewBox; + if (pProp->Value >>= aViewBox) + { + nViewBoxWidth = aViewBox.Width; + nViewBoxHeight = aViewBox.Height; + css::drawing::EnhancedCustomShapeParameter aECSP; + aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; + aECSP.Value <<= nViewBoxWidth; + double fRetValue; + aCustomShape2d.GetParameter(fRetValue, aECSP, true, false); + nViewBoxWidth = basegfx::fround(fRetValue); + aECSP.Value <<= nViewBoxHeight; + aCustomShape2d.GetParameter(fRetValue, aECSP, false, true); + nViewBoxHeight = basegfx::fround(fRetValue); + } + } + // Import from oox or documents, which are imported from oox and saved to strict ODF, might + // have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those + // cases. Even if that is fixed, we need the substitute for old documents. + if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq)) + { + // Generate a substitute based on point coordinates + sal_Int32 nXMin(0); + aPairs[0].First.Value >>= nXMin; + sal_Int32 nXMax = nXMin; + sal_Int32 nYMin(0); + aPairs[0].Second.Value >>= nYMin; + sal_Int32 nYMax = nYMin; - if ( aPathSize.hasElements() ) - { - mpFS->startElementNS( XML_a, XML_path, - XML_fill, sFill, - XML_w, OString::number(aPathSize[0].Width), - XML_h, OString::number(aPathSize[0].Height) ); - } - else + for (const auto& rPair : std::as_const(aPairs)) + { + sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d); + sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d); + if (nX < nXMin) + nXMin = nX; + if (nY < nYMin) + nYMin = nY; + if (nX > nXMax) + nXMax = nX; + if (nY > nYMax) + nYMax = nY; + } + nViewBoxWidth = std::max(nXMax, nXMax - nXMin); + nViewBoxHeight = std::max(nYMax, nYMax - nYMin); + } + // ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a + // shift of the resulting path coordinates. + } + + // Iterate over subpaths + sal_Int32 nPairIndex = 0; // index over "Coordinates" + sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize" + sal_Int32 nSubpathStartIndex(0); // index over "Segments" + do + { + bool bOK(true); // catch faulty paths were commands do not correspond to points + // get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment + sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments); + + // Prepare attributes for a:path start element + // NOFILL or one of the LIGHTEN commands + std::optional<OString> sFill; + if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) + sFill = "none"; + else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) + sFill = "darken"; + else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1, + aSegments)) + sFill = "darkenLess"; + else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1, + aSegments)) + sFill = "lighten"; + else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1, + aSegments)) + sFill = "lightenLess"; + // NOSTROKE + std::optional<OString> sStroke; + if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) + sStroke = "0"; + + // Write a:path start element + mpFS->startElementNS( + XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w, + OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width), + XML_h, + OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height)); + + // Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position + // of the target point in regard to the current point. Therefore we need to track the + // current point. A current point is not defined in the beginning. + double fCurrentX(0.0); + double fCurrentY(0.0); + bool bCurrentValid(false); + // Actually write the subpath + for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex; + ++nSegmentIndex) + { + const auto& rSegment(aSegments[nSegmentIndex]); + if (rSegment.Command == CLOSESUBPATH) + { + mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter + // ODF 1.4 specifies, that the start of the subpath becomes the current point. + // But that is not implemented yet. Currently LO keeps the last current point. + } + for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k) + { + switch (rSegment.Command) { - sal_Int32 nXMin(0); - aPairs[0].First.Value >>= nXMin; - sal_Int32 nXMax = nXMin; - sal_Int32 nYMin(0); - aPairs[0].Second.Value >>= nYMin; - sal_Int32 nYMax = nYMin; - - for ( const auto& rPair : std::as_const(aPairs) ) + case MOVETO: { - sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, rSdrObjCustomShape); - sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, rSdrObjCustomShape); - if (nX < nXMin) - nXMin = nX; - if (nY < nYMin) - nYMin = nY; - if (nX > nXMax) - nXMax = nX; - if (nY > nYMax) - nYMax = nY; + if (nPairIndex >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_moveTo); + WriteCustomGeometryPoint(aPairs[nPairIndex], aCustomShape2d); + mpFS->endElementNS(XML_a, XML_moveTo); + aCustomShape2d.GetParameter(fCurrentX, aPairs[nPairIndex].First, false, + false); + aCustomShape2d.GetParameter(fCurrentY, aPairs[nPairIndex].Second, false, + false); + bCurrentValid = true; + nPairIndex++; + } + break; } - mpFS->startElementNS( XML_a, XML_path, - XML_fill, sFill, - XML_w, OString::number(nXMax - nXMin), - XML_h, OString::number(nYMax - nYMin) ); - } - - - int nPairIndex = 0; - bool bOK = true; - for (const auto& rSegment : std::as_const(aSegments)) - { - if ( rSegment.Command == drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH ) + case LINETO: { - mpFS->singleElementNS(XML_a, XML_close); + if (nPairIndex >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_lnTo); + WriteCustomGeometryPoint(aPairs[nPairIndex], aCustomShape2d); + mpFS->endElementNS(XML_a, XML_lnTo); + aCustomShape2d.GetParameter(fCurrentX, aPairs[nPairIndex].First, false, + false); + aCustomShape2d.GetParameter(fCurrentY, aPairs[nPairIndex].Second, false, + false); + bCurrentValid = true; + nPairIndex++; + } + break; } - for (int k = 0; k < rSegment.Count && bOK; ++k) + case CURVETO: { - switch( rSegment.Command ) + if (nPairIndex + 2 >= aPairs.getLength()) + bOK = false; + else { - case drawing::EnhancedCustomShapeSegmentCommand::MOVETO : + mpFS->startElementNS(XML_a, XML_cubicBezTo); + for (sal_uInt8 l = 0; l <= 2; ++l) { - if (nPairIndex >= aPairs.getLength()) - bOK = false; - else - { - mpFS->startElementNS(XML_a, XML_moveTo); - WriteCustomGeometryPoint(aPairs[nPairIndex], rSdrObjCustomShape); - mpFS->endElementNS( XML_a, XML_moveTo ); - nPairIndex++; - } - break; + WriteCustomGeometryPoint(aPairs[nPairIndex + l], aCustomShape2d); } - case drawing::EnhancedCustomShapeSegmentCommand::LINETO : + mpFS->endElementNS(XML_a, XML_cubicBezTo); + aCustomShape2d.GetParameter(fCurrentX, aPairs[nPairIndex + 2].First, + false, false); + aCustomShape2d.GetParameter(fCurrentY, aPairs[nPairIndex + 2].Second, + false, false); + bCurrentValid = true; + nPairIndex += 3; + } + break; + } + case ANGLEELLIPSETO: + case ANGLEELLIPSE: + { + if (nPairIndex + 2 >= aPairs.getLength()) + bOK = false; + else + { + // Read parameters + double fCx = 0.0; + aCustomShape2d.GetParameter(fCx, aPairs[nPairIndex].First, false, + false); + double fCy = 0.0; + aCustomShape2d.GetParameter(fCy, aPairs[nPairIndex].Second, false, + false); + double fWR = 0.0; + aCustomShape2d.GetParameter(fWR, aPairs[nPairIndex + 1].First, false, + false); + double fHR = 0.0; + aCustomShape2d.GetParameter(fHR, aPairs[nPairIndex + 1].Second, false, + false); + double fStartAngle = 0.0; + aCustomShape2d.GetParameter(fStartAngle, aPairs[nPairIndex + 2].First, + false, false); + double fEndAngle = 0.0; + aCustomShape2d.GetParameter(fEndAngle, aPairs[nPairIndex + 2].Second, + false, false); + + // Prepare start and swing angle + sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + sal_Int32 nSwingAng = 0; + if (basegfx::fTools::equalZero(fStartAngle) + && basegfx::fTools::equalZero(fEndAngle - 360.0)) + nSwingAng = 360 * 60000; // special case full circle + else { - if (nPairIndex >= aPairs.getLength()) - bOK = false; - else - { - mpFS->startElementNS(XML_a, XML_lnTo); - WriteCustomGeometryPoint(aPairs[nPairIndex], rSdrObjCustomShape); - mpFS->endElementNS( XML_a, XML_lnTo ); - nPairIndex++; - } - break; + nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000); + if (nSwingAng < 0) + nSwingAng += 360 * 60000; } - case drawing::EnhancedCustomShapeSegmentCommand::CURVETO : + + // calculate start point on ellipse + double fSx = 0.0; + double fSy = 0.0; + getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle); + + // write markup for going to start point + if (rSegment.Command == ANGLEELLIPSETO) { - if (nPairIndex + 2 >= aPairs.getLength()) - bOK = false; - else - { - mpFS->startElementNS(XML_a, XML_cubicBezTo); - for( sal_uInt8 l = 0; l <= 2; ++l ) - { - WriteCustomGeometryPoint(aPairs[nPairIndex+l], rSdrObjCustomShape); - } - mpFS->endElementNS( XML_a, XML_cubicBezTo ); - nPairIndex += 3; - } - break; + mpFS->startElementNS(XML_a, XML_lnTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, + OString::number(std::lround(fSx)), XML_y, + OString::number(std::lround(fSy))); + mpFS->endElementNS(XML_a, XML_lnTo); } - case drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSETO : - case drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSE : + else { - nPairIndex += 3; - break; + mpFS->startElementNS(XML_a, XML_moveTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, + OString::number(std::lround(fSx)), XML_y, + OString::number(std::lround(fSy))); + mpFS->endElementNS(XML_a, XML_moveTo); } - case drawing::EnhancedCustomShapeSegmentCommand::ARCTO : - case drawing::EnhancedCustomShapeSegmentCommand::ARC : - case drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARCTO : - case drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARC : + // write markup for arcTo + if (!basegfx::fTools::equalZero(fWR) + && !basegfx::fTools::equalZero(fHR)) + mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, + OString::number(std::lround(fWR)), XML_hR, + OString::number(std::lround(fHR)), XML_stAng, + OString::number(nStartAng), XML_swAng, + OString::number(nSwingAng)); + + getEllipsePointFromViewAngle(fCurrentX, fCurrentY, fWR, fHR, fCx, fCy, + fEndAngle); + bCurrentValid = true; + nPairIndex += 3; + } + break; + } + case ARCTO: + case ARC: + case CLOCKWISEARCTO: + case CLOCKWISEARC: + { + if (nPairIndex + 3 >= aPairs.getLength()) + bOK = false; + else + { + // read parameters + double fX1 = 0.0; + aCustomShape2d.GetParameter(fX1, aPairs[nPairIndex].First, false, + false); + double fY1 = 0.0; + aCustomShape2d.GetParameter(fY1, aPairs[nPairIndex].Second, false, + false); + double fX2 = 0.0; + aCustomShape2d.GetParameter(fX2, aPairs[nPairIndex + 1].First, false, + false); + double fY2 = 0.0; + aCustomShape2d.GetParameter(fY2, aPairs[nPairIndex + 1].Second, false, + false); + double fX3 = 0.0; + aCustomShape2d.GetParameter(fX3, aPairs[nPairIndex + 2].First, false, + false); + double fY3 = 0.0; + aCustomShape2d.GetParameter(fY3, aPairs[nPairIndex + 2].Second, false, + false); + double fX4 = 0.0; + aCustomShape2d.GetParameter(fX4, aPairs[nPairIndex + 3].First, false, + false); + double fY4 = 0.0; + aCustomShape2d.GetParameter(fY4, aPairs[nPairIndex + 3].Second, false, + false); + // calculate ellipse parameter + const double fWR = (fX2 - fX1) / 2.0; + const double fHR = (fY2 - fY1) / 2.0; + const double fCx = (fX1 + fX2) / 2.0; + const double fCy = (fY1 + fY2) / 2.0; + // calculate start angle + double fStartAngle = 0.0; + double fPx = 0.0; + double fPy = 0.0; + getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, + fCx, fCy, fX3, fY3); + // markup for going to start point + if (rSegment.Command == ARCTO || rSegment.Command == CLOCKWISEARCTO) { - nPairIndex += 4; - break; + mpFS->startElementNS(XML_a, XML_lnTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, + OString::number(std::lround(fPx)), XML_y, + OString::number(std::lround(fPy))); + mpFS->endElementNS(XML_a, XML_lnTo); } - case drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTX : - case drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTY : + else { - nPairIndex++; - break; + mpFS->startElementNS(XML_a, XML_moveTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, + OString::number(std::lround(fPx)), XML_y, + OString::number(std::lround(fPy))); + mpFS->endElementNS(XML_a, XML_moveTo); } - case drawing::EnhancedCustomShapeSegmentCommand::QUADRATICCURVETO : + // calculate swing angle + double fEndAngle = 0.0; + getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, + fCy, fX4, fY4); + double fSwingAngle(fEndAngle - fStartAngle); + const bool bIsClockwise(rSegment.Command == CLOCKWISEARCTO + || rSegment.Command == CLOCKWISEARC); + if (bIsClockwise && fSwingAngle < 0) + fSwingAngle += 360.0; + else if (!bIsClockwise && fSwingAngle > 0) + fSwingAngle -= 360.0; + // markup for arcTo + // ToDo: write markup for case zero width or height of ellipse + const sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000)); + mpFS->singleElement( + FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), + XML_hR, OString::number(std::lround(fHR)), XML_stAng, + OString::number(nStartAng), XML_swAng, OString::number(nSwingAng)); + fCurrentX = fPx; + fCurrentY = fPy; + bCurrentValid = true; + nPairIndex += 4; + } + break; + } + case ELLIPTICALQUADRANTX: + case ELLIPTICALQUADRANTY: + { + if (nPairIndex >= aPairs.getLength()) + bOK = false; + else + { + // read parameters + double fX = 0.0; + aCustomShape2d.GetParameter(fX, aPairs[nPairIndex].First, false, false); + double fY = 0.0; + aCustomShape2d.GetParameter(fY, aPairs[nPairIndex].Second, false, + false); + + // Prepare parameters for arcTo + if (bCurrentValid) { - if (nPairIndex + 1 >= aPairs.getLength()) - bOK = false; - else + double fWR = std::abs(fCurrentX - fX); + double fHR = std::abs(fCurrentY - fY); + double fStartAngle(0.0); + double fSwingAngle(0.0); + // The starting direction of the arc toggles beween X and Y + if ((rSegment.Command == ELLIPTICALQUADRANTX && !(k % 2)) + || (rSegment.Command == ELLIPTICALQUADRANTY && (k % 2))) { - mpFS->startElementNS(XML_a, XML_quadBezTo); - for( sal_uInt8 l = 0; l < 2; ++l ) - { - WriteCustomGeometryPoint(aPairs[nPairIndex+l], rSdrObjCustomShape); - } - mpFS->endElementNS( XML_a, XML_quadBezTo ); - nPairIndex += 2; + // arc starts horizontal + fStartAngle = fY < fCurrentY ? 90.0 : 270.0; + const bool bClockwise = (fX < fCurrentX && fY < fCurrentY) + || (fX > fCurrentX && fY > fCurrentY); + fSwingAngle = bClockwise ? 90.0 : -90.0; } - break; - } - case drawing::EnhancedCustomShapeSegmentCommand::ARCANGLETO : - { - if (nPairIndex + 1 >= aPairs.getLength()) - bOK = false; else { - const EnhancedCustomShape2d aCustoShape2d( - const_cast<SdrObjCustomShape&>(rSdrObjCustomShape)); - double fWR = 0.0; - aCustoShape2d.GetParameter(fWR, aPairs[nPairIndex].First, false, - false); - double fHR = 0.0; - aCustoShape2d.GetParameter(fHR, aPairs[nPairIndex].Second, - false, false); - double fStartAngle = 0.0; - aCustoShape2d.GetParameter( - fStartAngle, aPairs[nPairIndex + 1].First, false, false); - sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); - double fSwingAng = 0.0; - aCustoShape2d.GetParameter( - fSwingAng, aPairs[nPairIndex + 1].Second, false, false); - sal_Int32 nSwingAng(std::lround(fSwingAng * 60000)); - mpFS->singleElement(FSNS(XML_a, XML_arcTo), - XML_wR, OString::number(fWR), - XML_hR, OString::number(fHR), - XML_stAng, OString::number(nStartAng), - XML_swAng, OString::number(nSwingAng)); - nPairIndex += 2; + // arc starts vertical + fStartAngle = fX < fCurrentX ? 0.0 : 180.0; + const bool bClockwise = (fX < fCurrentX && fY > fCurrentY) + || (fX > fCurrentX && fY < fCurrentY); + fSwingAngle = bClockwise ? 90.0 : -90.0; } - break; + sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000)); + mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, + OString::number(std::lround(fWR)), XML_hR, + OString::number(std::lround(fHR)), XML_stAng, + OString::number(nStartAng), XML_swAng, + OString::number(nSwingAng)); } - default: - // do nothing - break; + else + { + // faulty path, but we continue with the target point + mpFS->startElementNS(XML_a, XML_moveTo); + WriteCustomGeometryPoint(aPairs[nPairIndex], aCustomShape2d); + mpFS->endElementNS(XML_a, XML_moveTo); + } + fCurrentX = fX; + fCurrentY = fY; + bCurrentValid = true; + nPairIndex++; } + break; + } + case QUADRATICCURVETO: + { + if (nPairIndex + 1 >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_quadBezTo); + for( sal_uInt8 l = 0; l < 2; ++l ) + { + WriteCustomGeometryPoint(aPairs[nPairIndex + l], aCustomShape2d); + } + mpFS->endElementNS( XML_a, XML_quadBezTo ); + aCustomShape2d.GetParameter(fCurrentX, aPairs[nPairIndex + 1].First, false, + false); + aCustomShape2d.GetParameter(fCurrentY, aPairs[nPairIndex + 1].Second, false, + false); + bCurrentValid = true; + nPairIndex += 2; + } + break; } - if (!bOK) + case ARCANGLETO: + { + if (nPairIndex + 1 >= aPairs.getLength()) + bOK = false; + else + { + double fWR = 0.0; + aCustomShape2d.GetParameter(fWR, aPairs[nPairIndex].First, false, false); + double fHR = 0.0; + aCustomShape2d.GetParameter(fHR, aPairs[nPairIndex].Second, false, false); + double fStartAngle = 0.0; + aCustomShape2d.GetParameter(fStartAngle, aPairs[nPairIndex + 1].First, + false, false); + sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + double fSwingAng = 0.0; + aCustomShape2d.GetParameter(fSwingAng, aPairs[nPairIndex + 1].Second, + false, false); + sal_Int32 nSwingAng(std::lround(fSwingAng * 60000)); + mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(fWR), + XML_hR, OString::number(fHR), XML_stAng, + OString::number(nStartAng), XML_swAng, + OString::number(nSwingAng)); + double fPx = 0.0; + double fPy = 0.0; + getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle); + double fCx = fCurrentX - fPx; + double fCy = fCurrentY - fPy; + getEllipsePointFromViewAngle(fCurrentX, fCurrentY, fWR, fHR, fCx, fCy, + fStartAngle + fSwingAng); + bCurrentValid = true; + nPairIndex += 2; + } + break; + } + default: + // do nothing break; } - mpFS->endElementNS( XML_a, XML_path ); - mpFS->endElementNS( XML_a, XML_pathLst ); - mpFS->endElementNS( XML_a, XML_custGeom ); - return bOK; - } - } - } - return false; + } // end loop for commands which are repeated + } // end loop over all commands of subpath + // finish this subpath in any case + mpFS->endElementNS(XML_a, XML_path); + + if (!bOK) + break; // exit loop if not enough values in aPairs + + // step forward to next subpath + nSubpathStartIndex = nNextNcommandIndex + 1; + nPathSizeIndex++; + } while (nSubpathStartIndex < aSegments.getLength()); + + mpFS->endElementNS(XML_a, XML_pathLst); + mpFS->endElementNS(XML_a, XML_custGeom); + return true; // We have written custGeom even if path is poorly structured. } void DrawingML::WriteCustomGeometryPoint( const drawing::EnhancedCustomShapeParameterPair& rParamPair, - const SdrObjCustomShape& rSdrObjCustomShape) + const EnhancedCustomShape2d& rCustomShape2d) { - sal_Int32 nX = GetCustomGeometryPointValue(rParamPair.First, rSdrObjCustomShape); - sal_Int32 nY = GetCustomGeometryPointValue(rParamPair.Second, rSdrObjCustomShape); + sal_Int32 nX = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d); + sal_Int32 nY = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d); mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY)); } -sal_Int32 DrawingML::GetCustomGeometryPointValue( - const css::drawing::EnhancedCustomShapeParameter& rParam, - const SdrObjCustomShape& rSdrObjCustomShape) +void DrawingML::WriteEmptyCustomGeometry() { - const EnhancedCustomShape2d aCustoShape2d(const_cast< SdrObjCustomShape& >(rSdrObjCustomShape)); - double fValue = 0.0; - aCustoShape2d.GetParameter(fValue, rParam, false, false); - sal_Int32 nValue(std::lround(fValue)); - - return nValue; -} - -// version for SdrObjCustomShape -void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape, - const tools::PolyPolygon& rPolyPolygon, const bool bClosed) -{ - // In case of Writer, the parent element is <wps:spPr>, and there the - // <a:custGeom> element is not optional. - if (rPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX) - return; - + // This method is used for export to docx in case WriteCustomGeometry fails. mpFS->startElementNS(XML_a, XML_custGeom); mpFS->singleElementNS(XML_a, XML_avLst); mpFS->singleElementNS(XML_a, XML_gdLst); mpFS->singleElementNS(XML_a, XML_ahLst); mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b"); - - mpFS->startElementNS( XML_a, XML_pathLst ); - - const tools::Rectangle aRect( rPolyPolygon.GetBoundRect() ); - - // tdf#101122 - std::optional<OString> sFill; - if (HasEnhancedCustomShapeSegmentCommand(rXShape, css::drawing::EnhancedCustomShapeSegmentCommand::NOFILL)) - sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard - - // Put all polygons of rPolyPolygon in the same path element - // to subtract the overlapped areas. - mpFS->startElementNS( XML_a, XML_path, - XML_fill, sFill, - XML_w, OString::number(aRect.GetWidth()), - XML_h, OString::number(aRect.GetHeight()) ); - - for( sal_uInt16 i = 0; i < rPolyPolygon.Count(); i ++ ) - { - - const tools::Polygon& rPoly = rPolyPolygon[ i ]; - - if( rPoly.GetSize() > 0 ) - { - mpFS->startElementNS(XML_a, XML_moveTo); - - mpFS->singleElementNS( XML_a, XML_pt, - XML_x, OString::number(rPoly[0].X() - aRect.Left()), - XML_y, OString::number(rPoly[0].Y() - aRect.Top()) ); - - mpFS->endElementNS( XML_a, XML_moveTo ); - } - - for( sal_uInt16 j = 1; j < rPoly.GetSize(); j ++ ) - { - PolyFlags flags = rPoly.GetFlags(j); - if( flags == PolyFlags::Control ) - { - // a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this - if( j+2 < rPoly.GetSize() && rPoly.GetFlags(j+1) == PolyFlags::Control && rPoly.GetFlags(j+2) != PolyFlags::Control ) - { - - mpFS->startElementNS(XML_a, XML_cubicBezTo); - for( sal_uInt8 k = 0; k <= 2; ++k ) - { - mpFS->singleElementNS(XML_a, XML_pt, - XML_x, OString::number(rPoly[j+k].X() - aRect.Left()), - XML_y, OString::number(rPoly[j+k].Y() - aRect.Top())); - - } - mpFS->endElementNS( XML_a, XML_cubicBezTo ); - j += 2; - } - } - else if( flags == PolyFlags::Normal ) - { - mpFS->startElementNS(XML_a, XML_lnTo); - mpFS->singleElementNS( XML_a, XML_pt, - XML_x, OString::number(rPoly[j].X() - aRect.Left()), - XML_y, OString::number(rPoly[j].Y() - aRect.Top()) ); - mpFS->endElementNS( XML_a, XML_lnTo ); - } - } - } - if (bClosed) - mpFS->singleElementNS( XML_a, XML_close); - mpFS->endElementNS( XML_a, XML_path ); - - mpFS->endElementNS( XML_a, XML_pathLst ); - - mpFS->endElementNS( XML_a, XML_custGeom ); + mpFS->singleElementNS(XML_a, XML_pathLst); + mpFS->endElementNS(XML_a, XML_custGeom); } // version for SdrPathObj @@ -4647,44 +4954,6 @@ void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPrope WriteShapeEffect(u"softEdge", aProps); } -bool DrawingML::HasEnhancedCustomShapeSegmentCommand( - const css::uno::Reference<css::drawing::XShape>& rXShape, const sal_Int16 nCommand) -{ - try - { - uno::Reference<beans::XPropertySet> xPropSet(rXShape, uno::UNO_QUERY_THROW); - if (!GetProperty(xPropSet, "CustomShapeGeometry")) - return false; - Sequence<PropertyValue> aCustomShapeGeometryProps; - mAny >>= aCustomShapeGeometryProps; - for (const beans::PropertyValue& rGeomProp : std::as_const(aCustomShapeGeometryProps)) - { - if (rGeomProp.Name == "Path") - { - uno::Sequence<beans::PropertyValue> aPathProps; - rGeomProp.Value >>= aPathProps; - for (const beans::PropertyValue& rPathProp : std::as_const(aPathProps)) - { - if (rPathProp.Name == "Segments") - { - uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; - rPathProp.Value >>= aSegments; - for (const auto& rSegment : std::as_const(aSegments)) - { - if (rSegment.Command == nCommand) - return true; - } - } - } - } - } - } - catch (const ::uno::Exception&) - { - } - return false; -} - void DrawingML::Write3DEffects( const Reference< XPropertySet >& xPropSet, bool bIsText ) { // check existence of the grab bag diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx index 466b392c1a0e..7bc505e77481 100644 --- a/oox/source/export/shapes.cxx +++ b/oox/source/export/shapes.cxx @@ -740,7 +740,7 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape ) bool bHasGeometrySeq(false); Sequence< PropertyValue > aGeometrySeq; - OUString sShapeType; + OUString sShapeType("non-primitive"); // default in ODF if (GETA(CustomShapeGeometry)) { SAL_INFO("oox.shape", "got custom shape geometry"); @@ -903,9 +903,6 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape ) // visual shape properties pFS->startElementNS(mnXmlNamespace, XML_spPr); - // moon is flipped in MSO, and mso-spt89 (right up arrow) is mapped to leftUpArrow - if ( sShapeType == "moon" || sShapeType == "mso-spt89" ) - bFlipH = !bFlipH; // we export non-primitive shapes to custom geometry // we also export non-ooxml shapes which have handles/equations to custom geometry, because @@ -925,8 +922,6 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape ) bCustGeom = false; bOnDenylist = true; } - else if( bHasHandles ) - bCustGeom = true; bool bPresetWriteSuccessful = false; // Let the custom shapes what has name and preset information in OOXML, to be written @@ -941,28 +936,16 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape ) // If preset writing has problems try to write the shape as it done before if (bPresetWriteSuccessful) ;// Already written do nothing. - else if (bHasHandles && bCustGeom) - { - WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV, false, true );// do not flip, polypolygon coordinates are flipped already - tools::PolyPolygon aPolyPolygon( rSdrObjCustomShape.GetLineGeometry(true) ); - sal_Int32 nRotation = 0; - // The RotateAngle property's value is independent from any flipping, and that's exactly what we need here. - uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY); - uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); - if (xPropertySetInfo->hasPropertyByName("RotateAngle")) - xPropertySet->getPropertyValue("RotateAngle") >>= nRotation; - // Remove rotation - bool bInvertRotation = bFlipH != bFlipV; - if (nRotation != 0) - aPolyPolygon.Rotate(Point(0,0), Degree10(static_cast<sal_Int16>(bInvertRotation ? nRotation/10 : 3600-nRotation/10))); - WritePolyPolygon(xShape, aPolyPolygon, false); - } else if (bCustGeom) { WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV ); bool bSuccess = WriteCustomGeometry(xShape, rSdrObjCustomShape); - if (!bSuccess) - WritePresetShape( sPresetShape ); + // In case of Writer, the parent element is <wps:spPr>, and there the <a:custGeom> element + // is not optional. + if (!bSuccess && GetDocumentType() == DOCUMENT_DOCX) + { + WriteEmptyCustomGeometry(); + } } else if (bOnDenylist && bHasHandles && nAdjustmentValuesIndex !=-1 && !sShapeType.startsWith("mso-spt")) { diff --git a/sd/qa/unit/data/odp/tdf147978_enhancedPath_viewBox.odp b/sd/qa/unit/data/odp/tdf147978_enhancedPath_viewBox.odp Binary files differnew file mode 100644 index 000000000000..15d7046811c8 --- /dev/null +++ b/sd/qa/unit/data/odp/tdf147978_enhancedPath_viewBox.odp diff --git a/sd/qa/unit/data/xml/tdf92001_0.xml b/sd/qa/unit/data/xml/tdf92001_0.xml index afc1fd6ef10a..1bacf2f2559c 100644 --- a/sd/qa/unit/data/xml/tdf92001_0.xml +++ b/sd/qa/unit/data/xml/tdf92001_0.xml @@ -24,16 +24,52 @@ <PropertyValue name="Path"> <Path> <PropertyValue name="Coordinates"> - <Coordinates/> + <Coordinates> + <EnhancedCustomShapeParameterPair> + <First value="0" type="0"/> + <Second value="5677" type="0"/> + </EnhancedCustomShapeParameterPair> + <EnhancedCustomShapeParameterPair> + <First value="8631" type="0"/> + <Second value="5677" type="0"/> + </EnhancedCustomShapeParameterPair> + <EnhancedCustomShapeParameterPair> + <First value="8631" type="0"/> + <Second value="0" type="0"/> + </EnhancedCustomShapeParameterPair> + <EnhancedCustomShapeParameterPair> + <First value="21600" type="0"/> + <Second value="10800" type="0"/> + </EnhancedCustomShapeParameterPair> + <EnhancedCustomShapeParameterPair> + <First value="8631" type="0"/> + <Second value="21600" type="0"/> + </EnhancedCustomShapeParameterPair> + <EnhancedCustomShapeParameterPair> + <First value="8631" type="0"/> + <Second value="15923" type="0"/> + </EnhancedCustomShapeParameterPair> + <EnhancedCustomShapeParameterPair> + <First value="0" type="0"/> + <Second value="15923" type="0"/> + </EnhancedCustomShapeParameterPair> + </Coordinates> </PropertyValue> <PropertyValue name="Segments"> - <Segments/> + <Segments> + <EnhancedCustomShapeSegment command="1" count="1"/> + <EnhancedCustomShapeSegment command="2" count="6"/> + <EnhancedCustomShapeSegment command="4" count="0"/> + <EnhancedCustomShapeSegment command="5" count="0"/> + </Segments> </PropertyValue> + <PropertyValue name="SubViewSize" handle="0" propertyState="DIRECT_VALUE"/> + <PropertyValue name="TextFrames" handle="0" propertyState="DIRECT_VALUE"/> </Path> </PropertyValue> <PropertyValue name="Type" value="ooxml-non-primitive" handle="0" propertyState="DIRECT_VALUE"/> <PropertyValue name="ViewBox"> - <ViewBox x="0" y="0" width="7040880" height="4663440"/> + <ViewBox x="0" y="0" width="0" height="0"/> </PropertyValue> </CustomShapeGeometry> </XShape> diff --git a/sd/qa/unit/export-tests-ooxml2.cxx b/sd/qa/unit/export-tests-ooxml2.cxx index 1951d2f9eae6..78f43f2160eb 100644 --- a/sd/qa/unit/export-tests-ooxml2.cxx +++ b/sd/qa/unit/export-tests-ooxml2.cxx @@ -1067,29 +1067,27 @@ void SdOOXMLExportTest2::testTdf111798() xDocShRef->DoClose(); xmlDocUniquePtr pXmlDoc = parseExport(tempFile, "ppt/slides/slide1.xml"); - const OUString data[][29] = + const OUString data[][26] = { { "2700000", "2458080", "2414880", "1439640", "1440000", - "moveTo", "0", "3000", - "lnTo[1]", "3000", "3000", - "lnTo[2]", "3000", "4000", - "lnTo[3]", "4000", "2000", - "lnTo[4]", "3000", "0", - "lnTo[5]", "3000", "1000", - "lnTo[6]", "0", "1000", - "lnTo[7]", "0", "3000" + "moveTo", "0", "5400", + "lnTo[1]", "16200", "5400", + "lnTo[2]", "16200", "0", + "lnTo[3]", "21600", "10800", + "lnTo[4]", "16200", "21600", + "lnTo[5]", "16200", "16200", + "lnTo[6]", "0", "16200" }, { "2700000", "6778080", "2414880", "1439640", "1440000", - "moveTo", "3000", "0", - "lnTo[1]", "3000", "3000", - "lnTo[2]", "4000", "3000", - "lnTo[3]", "2000", "4000", - "lnTo[4]", "0", "3000", - "lnTo[5]", "1000", "3000", - "lnTo[6]", "1000", "0", - "lnTo[7]", "3000", "0" + "moveTo", "5400", "0", + "lnTo[1]", "5400", "16200", + "lnTo[2]", "0", "16200", + "lnTo[3]", "10800", "21600", + "lnTo[4]", "21600", "16200", + "lnTo[5]", "16200", "16200", + "lnTo[6]", "16200", "0" } }; diff --git a/sd/qa/unit/export-tests-ooxml3.cxx b/sd/qa/unit/export-tests-ooxml3.cxx index db33ac9b88c4..40031977345d 100644 --- a/sd/qa/unit/export-tests-ooxml3.cxx +++ b/sd/qa/unit/export-tests-ooxml3.cxx @@ -125,6 +125,7 @@ public: void testTdf143315(); void testTdf147121(); void testTdf140912_PicturePlaceholder(); + void testEnhancedPathViewBox(); CPPUNIT_TEST_SUITE(SdOOXMLExportTest3); @@ -200,6 +201,7 @@ public: CPPUNIT_TEST(testTdf143315); CPPUNIT_TEST(testTdf147121); CPPUNIT_TEST(testTdf140912_PicturePlaceholder); + CPPUNIT_TEST(testEnhancedPathViewBox); CPPUNIT_TEST_SUITE_END(); virtual void registerNamespaces(xmlXPathContextPtr& pXmlXPathCtx) override @@ -1896,6 +1898,20 @@ void SdOOXMLExportTest3::testTdf140912_PicturePlaceholder() xDocShRef->DoClose(); } +void SdOOXMLExportTest3::testEnhancedPathViewBox() +{ + auto xDocShRef = loadURL( + m_directories.getURLFromSrc(u"sd/qa/unit/data/odp/tdf147978_enhancedPath_viewBox.odp"), + ODP); + xDocShRef = saveAndReload(xDocShRef.get(), PPTX); + auto xShapeProps(getShapeFromPage(0, 0, xDocShRef)); + awt::Rectangle aBoundRectangle; + xShapeProps->getPropertyValue("BoundRect") >>= aBoundRectangle; + // The shape has a Bézier curve which does not touch the right edge. Prior to the fix the curve + // was stretched to touch the edge, resulting in 5098 curve width instead of 2045. + CPPUNIT_ASSERT_EQUAL(sal_Int32(2045), aBoundRectangle.Width); +} + CPPUNIT_TEST_SUITE_REGISTRATION(SdOOXMLExportTest3); CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/extras/ooxmlexport/data/tdf147978_enhancedPath_commandABVW.odt b/sw/qa/extras/ooxmlexport/data/tdf147978_enhancedPath_commandABVW.odt Binary files differnew file mode 100644 index 000000000000..d9ef07db5920 --- /dev/null +++ b/sw/qa/extras/ooxmlexport/data/tdf147978_enhancedPath_commandABVW.odt diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx index ea1d894f67ad..f23721767fc9 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx @@ -266,6 +266,22 @@ DECLARE_OOXMLEXPORT_TEST(testTdf144668, "tdf144668.odt") CPPUNIT_ASSERT_EQUAL(OUString("[001]"), getProperty<OUString>(xPara2, "ListLabelString")); } +CPPUNIT_TEST_FIXTURE(Test, testTdf147978enhancedPathABVW) +{ + load(DATA_DIRECTORY, "tdf147978_enhancedPath_commandABVW.odt"); + CPPUNIT_ASSERT(mxComponent); + save("Office Open XML Text", maTempFile); + mxComponent->dispose(); + mxComponent = loadFromDesktop(maTempFile.GetURL(), "com.sun.star.text.TextDocument"); + // Make sure the new implemented export for commands A,B,V and W use the correct arc between + // the given two points, here the short one. + for (sal_Int16 i = 1 ; i <= 4; ++i) + { + uno::Reference<drawing::XShape> xShape = getShape(i); + CPPUNIT_ASSERT_EQUAL(sal_Int32(506), getProperty<awt::Rectangle>(xShape, "BoundRect").Height); + } +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx index a16de671a6ba..2960f4a2c6c7 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport7.cxx @@ -1133,24 +1133,30 @@ CPPUNIT_TEST_FIXTURE(Test, testFlipAndRotateCustomShape) CPPUNIT_ASSERT_EQUAL(1, getShapes()); CPPUNIT_ASSERT_EQUAL(1, getPages()); xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); - // there should be no flipH and flipV attributes in this case + // there should be no flipH assertXPathNoAttribute(pXmlDoc, "//a:xfrm", "flipH"); - assertXPathNoAttribute(pXmlDoc, "//a:xfrm", "flipV"); + // flipV should be there + assertXPath(pXmlDoc, "//a:xfrm", "flipV", "1"); // check rotation angle - assertXPath(pXmlDoc, "//a:xfrm", "rot", "13500000"); + assertXPath(pXmlDoc, "//a:xfrm", "rot", "8100000"); + // point values depend on path size, values as of March 2022 + assertXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path", "w", "21600"); + assertXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path", "h", "21600"); // check the first few coordinates of the polygon CPPUNIT_ASSERT_DOUBLES_EQUAL( - 2351, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[1]/a:pt", "x").toInt32(), 1); + 0, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:moveTo/a:pt", "x").toInt32(), 1); CPPUNIT_ASSERT_DOUBLES_EQUAL( - 3171, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[1]/a:pt", "y").toInt32(), 1); + 15831, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:moveTo/a:pt", "y").toInt32(), 1); CPPUNIT_ASSERT_DOUBLES_EQUAL( - 1695, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[2]/a:pt", "x").toInt32(), 1); + 6098, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[1]/a:pt", "x").toInt32(), 1); CPPUNIT_ASSERT_DOUBLES_EQUAL( - 3171, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[2]/a:pt", "y").toInt32(), 1); + 10062, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[1]/a:pt", "y").toInt32(), 1); CPPUNIT_ASSERT_DOUBLES_EQUAL( - 1695, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[3]/a:pt", "x").toInt32(), 1); + 13284, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[4]/a:pt", "x").toInt32(), 1); CPPUNIT_ASSERT_DOUBLES_EQUAL( - 1701, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[3]/a:pt", "y").toInt32(), 1); + 6098, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[4]/a:pt", "y").toInt32(), 1); + // check path is closed + assertXPath(pXmlDoc, "//a:close", 1); } CPPUNIT_TEST_FIXTURE(Test, testTdf92335) diff --git a/sw/source/filter/ww8/docxsdrexport.cxx b/sw/source/filter/ww8/docxsdrexport.cxx index 7166ed49565f..98257391c9cc 100644 --- a/sw/source/filter/ww8/docxsdrexport.cxx +++ b/sw/source/filter/ww8/docxsdrexport.cxx @@ -346,26 +346,6 @@ tools::Polygon lcl_CreateContourPolygon(SdrObject* pSdrObj) basegfx::B2DHomMatrix aScale(basegfx::utils::createScaleB2DHomMatrix(fScaleX, fScaleY)); aPolyPolygon.transform(aScale); - // ToDo: In some cases (see ShapeExport::WriteCustomShape()) flip is suppressed when - // calling WriteShapeTransformation(), because the path in custGeom contains already - // flipped coordinates. In such cases the wrap polygon needs to contain flipped - // coordinates too. That is missing here. - - // "moon" and "msp-spt89" (up-right-arrow) are currently mirrored horizontally. But - // that is removed on export in shapes.cxx. So need to remove it in wrap polygon too. - uno::Reference<drawing::XShape> xShape(pSdrObj->getUnoShape(), uno::UNO_QUERY); - uno::Reference<beans::XPropertySet> xProps(xShape, uno::UNO_QUERY); - comphelper::SequenceAsHashMap aCustomShapeGeometry( - xProps->getPropertyValue("CustomShapeGeometry")); - auto it = aCustomShapeGeometry.find("Type"); - if (it != aCustomShapeGeometry.end() - && (aCustomShapeGeometry["Type"].get<OUString>() == "moon" - || aCustomShapeGeometry["Type"].get<OUString>() == "mso-spt89")) - { - basegfx::B2DHomMatrix aFlipH(basegfx::utils::createScaleB2DHomMatrix(-1.0, 1.0)); - aPolyPolygon.transform(aFlipH); - } - basegfx::B2DHomMatrix aTranslateToCenter( basegfx::utils::createTranslateB2DHomMatrix(10800.0, 10800.0)); aPolyPolygon.transform(aTranslateToCenter); |