summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/oox/export/drawingml.hxx13
-rw-r--r--oox/qa/unit/data/tdf147978_endsubpath.odpbin0 -> 14505 bytes
-rw-r--r--oox/qa/unit/data/tdf147978_enhancedPath_commandA.odpbin0 -> 12755 bytes
-rw-r--r--oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odpbin0 -> 14922 bytes
-rw-r--r--oox/qa/unit/data/tdf147978_enhancedPath_commandT.odpbin0 -> 12869 bytes
-rw-r--r--oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odpbin0 -> 13166 bytes
-rw-r--r--oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptxbin0 -> 15040 bytes
-rw-r--r--oox/qa/unit/export.cxx155
-rw-r--r--oox/source/export/drawingml.cxx919
-rw-r--r--oox/source/export/shapes.cxx31
-rw-r--r--sd/qa/unit/data/odp/tdf147978_enhancedPath_viewBox.odpbin0 -> 12728 bytes
-rw-r--r--sd/qa/unit/data/xml/tdf92001_0.xml42
-rw-r--r--sd/qa/unit/export-tests-ooxml2.cxx32
-rw-r--r--sd/qa/unit/export-tests-ooxml3.cxx16
-rw-r--r--sw/qa/extras/ooxmlexport/data/tdf147978_enhancedPath_commandABVW.odtbin0 -> 11581 bytes
-rw-r--r--sw/qa/extras/ooxmlexport/ooxmlexport17.cxx16
-rw-r--r--sw/qa/extras/ooxmlexport/ooxmlexport7.cxx24
-rw-r--r--sw/source/filter/ww8/docxsdrexport.cxx20
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
new file mode 100644
index 000000000000..2dfd55de1be3
--- /dev/null
+++ b/oox/qa/unit/data/tdf147978_endsubpath.odp
Binary files differ
diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandA.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandA.odp
new file mode 100644
index 000000000000..99ddda7c132e
--- /dev/null
+++ b/oox/qa/unit/data/tdf147978_enhancedPath_commandA.odp
Binary files differ
diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odp
new file mode 100644
index 000000000000..49e01bc0933a
--- /dev/null
+++ b/oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odp
Binary files differ
diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandT.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandT.odp
new file mode 100644
index 000000000000..3dcd0d567545
--- /dev/null
+++ b/oox/qa/unit/data/tdf147978_enhancedPath_commandT.odp
Binary files differ
diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odp
new file mode 100644
index 000000000000..6112251783e1
--- /dev/null
+++ b/oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odp
Binary files differ
diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptx b/oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptx
new file mode 100644
index 000000000000..bbedc7ab98f5
--- /dev/null
+++ b/oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptx
Binary files differ
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
new file mode 100644
index 000000000000..15d7046811c8
--- /dev/null
+++ b/sd/qa/unit/data/odp/tdf147978_enhancedPath_viewBox.odp
Binary files differ
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
new file mode 100644
index 000000000000..d9ef07db5920
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/tdf147978_enhancedPath_commandABVW.odt
Binary files differ
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);