summaryrefslogtreecommitdiff
path: root/oox
diff options
context:
space:
mode:
authorRegina Henschel <rb.henschel@t-online.de>2022-03-18 18:31:05 +0100
committerMiklos Vajna <vmiklos@collabora.com>2022-03-28 11:38:26 +0200
commit6bd85136efe3d3668b59a596d692f65bf0a4982c (patch)
tree4b08a3ac4f1369665bf9987711a1d83dbbfbdaec /oox
parentcebca5086b42ff9462d8c6f8ed2344a0138b2f4a (diff)
tdf#147978 export subpaths individually in custGeom
...and implement export of all missing commands, use existing viewBox if suitable, use one EnhancedCustomShape2d move WriteCustomGeometryPoint to protected, make GetCustomGeometryPointValue local The fix solves tdf#100390 too. Without the fix the entire enhanced-path was exported as one element <a:path>. The command F was applied to the whole drawing but should affect only the subpath. The implementation is changed so that each subpath gets its own element <a:path> and command F acts only on its subpath. Support for export of handles and equations is still a long way off. Thus there is no reason to tread shapes with and without handles different. Shapes with handles had used method WritePolyPolygon, but that is not able to handle subpaths. So have desided to use method WriteCustomGeometry for all cases. To get shapes exported regardless of path commands I have added the export for the missing commands. I have removed the no longer used method WritePolyPolygon. The special treatment of shapes "moon" and "mso-spt89" (right up arrow) in export is no longer needed. Related code parts are removed. The unit test testFlipAndRotateCustomShape is adapted. In case the method WriteCustomGeometry fails, the enhanced-path is invalid. In that case an minimal custGeom is written in case of docx. Shapes whose drawing does not touch all edges of the snap rectangle were exported with wrong position and size of the drawing inside the snap rectangle. That is fixed by using an existing ViewBox for the OOXML path size. The old way of creating a path size from point coordinates is only used if the shape has no suitable ViewBox. The point values in unit test SdOOXMLExportTest2::testTdf111798 are adapted to path size 21600 x 21600 and traverse direction of the points is corrected. The resulting shape outline is still the same as before. The expected xml is updated for file tdf92001.odp in SdImportTest::testDocumentLayout. The resulting shape outline is the same, because the shape touches the edges of the snap rectangle. The case, that the shape outline does not touch a edge of the snap rectangle is tested in SdOOXMLExportTest3::testEnhancedPathViewBox. Still missing is the case, that ViewBox has other left,top than 0,0. In that case all coordinates would have to be shifted because the path size in OOXML has only width and height but not left,top. That will not be included in this patch. Change-Id: Ib1736d6a08371f4d98411d2769275f0580cd0030 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131837 Tested-by: Jenkins Reviewed-by: Miklos Vajna <vmiklos@collabora.com> (cherry picked from commit 2029b2f6dd0109c5892e5ac5640022b31fe42fd2) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132048 Reviewed-by: Bartosz Kosiorek <gang65@poczta.onet.pl>
Diffstat (limited to 'oox')
-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
9 files changed, 756 insertions, 349 deletions
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"))
{