diff options
-rw-r--r-- | include/oox/export/drawingml.hxx | 6 | ||||
-rw-r--r-- | oox/qa/unit/data/tdf148784_StretchCommandQ.odp | bin | 0 -> 15311 bytes | |||
-rw-r--r-- | oox/qa/unit/data/tdf148784_StretchCommandVW.odp | bin | 0 -> 15218 bytes | |||
-rw-r--r-- | oox/qa/unit/data/tdf148784_StretchXY.odp | bin | 0 -> 11512 bytes | |||
-rw-r--r-- | oox/qa/unit/export.cxx | 113 | ||||
-rw-r--r-- | oox/source/export/drawingml.cxx | 108 |
6 files changed, 188 insertions, 39 deletions
diff --git a/include/oox/export/drawingml.hxx b/include/oox/export/drawingml.hxx index 65c9112ab497..8e4fe156c0cd 100644 --- a/include/oox/export/drawingml.hxx +++ b/include/oox/export/drawingml.hxx @@ -200,12 +200,14 @@ protected: void WriteGlowEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet); void WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet); void WriteCustomGeometryPoint(const css::drawing::EnhancedCustomShapeParameterPair& rParamPair, - const EnhancedCustomShape2d& rCustomShape2d); + const EnhancedCustomShape2d& rCustomShape2d, + const bool bReplaceGeoWidth, const bool bReplaceGeoHeight); bool WriteCustomGeometrySegment( const sal_Int16 eCommand, const sal_Int32 nCount, const css::uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs, sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid, - const EnhancedCustomShape2d& rCustomShape2d); + const EnhancedCustomShape2d& rCustomShape2d, + const bool bReplaceGeoWidth, const bool bReplaceGeoHeight); public: DrawingML( ::sax_fastparser::FSHelperPtr pFS, ::oox::core::XmlFilterBase* pFB, DocumentType eDocumentType = DOCUMENT_PPTX, DMLTextExport* pTextExport = nullptr ) diff --git a/oox/qa/unit/data/tdf148784_StretchCommandQ.odp b/oox/qa/unit/data/tdf148784_StretchCommandQ.odp Binary files differnew file mode 100644 index 000000000000..3da092b2c598 --- /dev/null +++ b/oox/qa/unit/data/tdf148784_StretchCommandQ.odp diff --git a/oox/qa/unit/data/tdf148784_StretchCommandVW.odp b/oox/qa/unit/data/tdf148784_StretchCommandVW.odp Binary files differnew file mode 100644 index 000000000000..bf1054a20581 --- /dev/null +++ b/oox/qa/unit/data/tdf148784_StretchCommandVW.odp diff --git a/oox/qa/unit/data/tdf148784_StretchXY.odp b/oox/qa/unit/data/tdf148784_StretchXY.odp Binary files differnew file mode 100644 index 000000000000..f9df40570e89 --- /dev/null +++ b/oox/qa/unit/data/tdf148784_StretchXY.odp diff --git a/oox/qa/unit/export.cxx b/oox/qa/unit/export.cxx index a6cf4e7342eb..31f81936684e 100644 --- a/oox/qa/unit/export.cxx +++ b/oox/qa/unit/export.cxx @@ -592,6 +592,119 @@ CPPUNIT_TEST_FIXTURE(Test, testFaultyPathCommandsAWT) assertXPath(pXmlDoc, "//p:spTree/p:sp[4]/p:spPr/a:custGeom/a:pathLst/a:path/a:moveTo"); } +CPPUNIT_TEST_FIXTURE(Test, testTdf148784StretchXY) +{ + // The document has a custom shapes of type "non-primitive" to trigger the custGeom export. + // They use formulas with 'right' and 'bottom'. + // When saving to PPTX the attributes stretchpoint-x and stretchpoint-y were not considered. The + // line at right and bottom edge were positioned inside as if the shape had a square size. + OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf148784_StretchXY.odp"; + 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()); + + // x-position of last segment should be same as path width. It was 21600 without fix. + sal_Int32 nWidth + = getXPathContent(pXmlDoc, "//p:spTree/p:sp[1]/p:spPr/a:custGeom/a:pathLst/a:path/@w") + .toInt32(); + sal_Int32 nPosX + = getXPathContent( + pXmlDoc, "//p:spTree/p:sp[1]/p:spPr/a:custGeom/a:pathLst/a:path/a:moveTo[4]/a:pt/@x") + .toInt32(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("StretchX", nWidth, nPosX); + + // y-position of last segment should be same as path height. It was 21600 without fix. + sal_Int32 nHeight + = getXPathContent(pXmlDoc, "//p:spTree/p:sp[2]/p:spPr/a:custGeom/a:pathLst/a:path/@h") + .toInt32(); + sal_Int32 nPosY + = getXPathContent( + pXmlDoc, "//p:spTree/p:sp[2]/p:spPr/a:custGeom/a:pathLst/a:path/a:moveTo[4]/a:pt/@y") + .toInt32(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("StretchY", nHeight, nPosY); + + // The test reflects the state of Apr 2022. It needs to be adapted when export of handles and + // guides is implemented. +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf148784StretchCommandQ) +{ + // The document has a custom shapes of type "non-primitive" to trigger the custGeom export. + // They use formulas with 'right' and 'bottom'. + // When saving to PPTX the attributes stretchpoint-x and stretchpoint-y were not considered. + // That results in wrong arcs on the right or bottom side of the shape. + OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf148784_StretchCommandQ.odp"; + 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()); + + // x-position of second quadBezTo control should be same as path width. It was 21600 without fix. + sal_Int32 nWidth + = getXPathContent(pXmlDoc, "//p:spTree/p:sp[1]/p:spPr/a:custGeom/a:pathLst/a:path/@w") + .toInt32(); + sal_Int32 nPosX + = getXPathContent( + pXmlDoc, + "//p:spTree/p:sp[1]/p:spPr/a:custGeom/a:pathLst/a:path/a:quadBezTo[2]/a:pt/@x") + .toInt32(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("StretchX", nWidth, nPosX); + + // y-position of third quadBezTo control should be same as path height. It was 21600 without fix. + sal_Int32 nHeight + = getXPathContent(pXmlDoc, "//p:spTree/p:sp[2]/p:spPr/a:custGeom/a:pathLst/a:path/@h") + .toInt32(); + sal_Int32 nPosY + = getXPathContent( + pXmlDoc, + "//p:spTree/p:sp[2]/p:spPr/a:custGeom/a:pathLst/a:path/a:quadBezTo[3]/a:pt/@y") + .toInt32(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("StretchY", nHeight, nPosY); + + // The test reflects the state of Apr 2022. It needs to be adapted when export of handles and + // guides is implemented. +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf148784StretchCommandVW) +{ + // The document has a custom shapes of type "non-primitive" to trigger the custGeom export. + // It should not need adaption when export of handles and guides is implemented because it + // has only fixed values in the path. + // When saving to PPTX the attributes stretchpoint-x and stretchpoint-y were not considered. + // That results in circles instead of ellipses. + OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf148784_StretchCommandVW.odp"; + 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()); + + // wR of first ArcTo in first shape should be same as path width/2. It was 10800 without fix. + sal_Int32 nHalfWidth + = getXPathContent(pXmlDoc, "//p:spTree/p:sp[1]/p:spPr/a:custGeom/a:pathLst/a:path/@w") + .toInt32() + / 2; + sal_Int32 nWR + = getXPathContent(pXmlDoc, + "//p:spTree/p:sp[1]/p:spPr/a:custGeom/a:pathLst/a:path/a:arcTo[1]/@wR") + .toInt32(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("StretchX", nHalfWidth, nWR); + + // hR of first ArcTo in second shape should be same as path height /2. It was 10800 without fix. + sal_Int32 nHalfHeight + = getXPathContent(pXmlDoc, "//p:spTree/p:sp[2]/p:spPr/a:custGeom/a:pathLst/a:path/@h") + .toInt32() + / 2; + sal_Int32 nHR + = getXPathContent(pXmlDoc, + "//p:spTree/p:sp[2]/p:spPr/a:custGeom/a:pathLst/a:path/a:arcTo[1]/@hR") + .toInt32(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("StretchY", nHalfHeight, nHR); +} + CPPUNIT_TEST_FIXTURE(Test, testReferToTheme) { // Given a PPTX file that contains references to a theme: diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx index b8e265882a65..55a9f07e90fe 100644 --- a/oox/source/export/drawingml.cxx +++ b/oox/source/export/drawingml.cxx @@ -3857,10 +3857,11 @@ void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, } sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam, - const EnhancedCustomShape2d& rCustomShape2d) + const EnhancedCustomShape2d& rCustomShape2d, + const bool bReplaceGeoWidth, const bool bReplaceGeoHeight) { double fValue = 0.0; - rCustomShape2d.GetParameter(fValue, rParam, false, false); + rCustomShape2d.GetParameter(fValue, rParam, bReplaceGeoWidth, bReplaceGeoHeight); sal_Int32 nValue(std::lround(fValue)); return nValue; @@ -3881,7 +3882,7 @@ struct Guide }; void prepareTextArea(const EnhancedCustomShape2d& rEnhancedCustomShape2d, - std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect) + std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect) { tools::Rectangle aTextAreaLO(rEnhancedCustomShape2d.GetTextRect()); tools::Rectangle aLogicRectLO(rEnhancedCustomShape2d.GetLogicRect()); @@ -3980,6 +3981,8 @@ bool DrawingML::WriteCustomGeometry( uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs; uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; uno::Sequence<awt::Size> aPathSize; + bool bReplaceGeoWidth = false; + bool bReplaceGeoHeight = false; for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp)) { if (rPathProp.Name == "Coordinates") @@ -3988,6 +3991,10 @@ bool DrawingML::WriteCustomGeometry( rPathProp.Value >>= aSegments; else if (rPathProp.Name == "SubViewSize") rPathProp.Value >>= aPathSize; + else if (rPathProp.Name == "StretchX") + bReplaceGeoWidth = true; + else if (rPathProp.Name == "StretchY") + bReplaceGeoHeight = true; } if ( !aPairs.hasElements() ) @@ -4030,7 +4037,7 @@ bool DrawingML::WriteCustomGeometry( else { mpFS->startElementNS(XML_a, XML_gdLst); - for (auto const& elem: aGuideList) + for (auto const& elem : aGuideList) { mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula); } @@ -4090,8 +4097,10 @@ bool DrawingML::WriteCustomGeometry( for (const auto& rPair : std::as_const(aPairs)) { - sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d); - sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d); + sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d, + bReplaceGeoWidth, false); + sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false, + bReplaceGeoHeight); if (nX < nXMin) nXMin = nX; if (nY < nYMin) @@ -4180,7 +4189,8 @@ bool DrawingML::WriteCustomGeometry( for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k) { bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX, - fCurrentY, bCurrentValid, aCustomShape2d); + fCurrentY, bCurrentValid, aCustomShape2d, + bReplaceGeoWidth, bReplaceGeoHeight); } } // end loop over all commands of subpath // finish this subpath in any case @@ -4204,7 +4214,8 @@ bool DrawingML::WriteCustomGeometrySegment( const sal_Int16 eCommand, const sal_Int32 nCount, const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs, sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid, - const EnhancedCustomShape2d& rCustomShape2d) + const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth, + const bool bReplaceGeoHeight) { switch (eCommand) { @@ -4214,10 +4225,13 @@ bool DrawingML::WriteCustomGeometrySegment( return false; mpFS->startElementNS(XML_a, XML_moveTo); - WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d); + WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); mpFS->endElementNS(XML_a, XML_moveTo); - rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, false, false); - rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false, false); + rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth, + false); + rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false, + bReplaceGeoHeight); rbCurrentValid = true; rnPairIndex++; break; @@ -4232,17 +4246,21 @@ bool DrawingML::WriteCustomGeometrySegment( if (rbCurrentValid) { mpFS->startElementNS(XML_a, XML_lnTo); - WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d); + WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); mpFS->endElementNS(XML_a, XML_lnTo); } else { mpFS->startElementNS(XML_a, XML_moveTo); - WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d); + WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); mpFS->endElementNS(XML_a, XML_moveTo); } - rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, false, false); - rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false, false); + rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth, + false); + rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false, + bReplaceGeoHeight); rbCurrentValid = true; rnPairIndex++; break; @@ -4255,11 +4273,14 @@ bool DrawingML::WriteCustomGeometrySegment( mpFS->startElementNS(XML_a, XML_cubicBezTo); for (sal_uInt8 i = 0; i <= 2; ++i) { - WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d); + WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); } mpFS->endElementNS(XML_a, XML_cubicBezTo); - rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, false, false); - rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false, false); + rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth, + false); + rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false, + bReplaceGeoHeight); rbCurrentValid = true; rnPairIndex += 3; break; @@ -4272,9 +4293,9 @@ bool DrawingML::WriteCustomGeometrySegment( // Read parameters double fCx = 0.0; - rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, false, false); + rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); double fCy = 0.0; - rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, false); + rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); double fWR = 0.0; rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex + 1].First, false, false); double fHR = 0.0; @@ -4340,21 +4361,27 @@ bool DrawingML::WriteCustomGeometrySegment( // read parameters double fX1 = 0.0; - rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, false, false); + rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); double fY1 = 0.0; - rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, false); + rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); double fX2 = 0.0; - rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, false, false); + rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth, + false); double fY2 = 0.0; - rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false, false); + rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false, + bReplaceGeoHeight); double fX3 = 0.0; - rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, false, false); + rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth, + false); double fY3 = 0.0; - rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false, false); + rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false, + bReplaceGeoHeight); double fX4 = 0.0; - rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, false, false); + rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, bReplaceGeoWidth, + false); double fY4 = 0.0; - rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false, false); + rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false, + bReplaceGeoHeight); // calculate ellipse parameter const double fWR = (fX2 - fX1) / 2.0; const double fHR = (fY2 - fY1) / 2.0; @@ -4412,9 +4439,9 @@ bool DrawingML::WriteCustomGeometrySegment( // read parameters double fX = 0.0; - rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, false, false); + rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); double fY = 0.0; - rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, false); + rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); // Prepare parameters for arcTo if (rbCurrentValid) @@ -4452,7 +4479,8 @@ bool DrawingML::WriteCustomGeometrySegment( { // faulty path, but we continue with the target point mpFS->startElementNS(XML_a, XML_moveTo); - WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d); + WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); mpFS->endElementNS(XML_a, XML_moveTo); } rfCurrentX = fX; @@ -4469,11 +4497,14 @@ bool DrawingML::WriteCustomGeometrySegment( mpFS->startElementNS(XML_a, XML_quadBezTo); for (sal_uInt8 i = 0; i < 2; ++i) { - WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d); + WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); } mpFS->endElementNS(XML_a, XML_quadBezTo); - rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, false, false); - rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false, false); + rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth, + false); + rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false, + bReplaceGeoHeight); rbCurrentValid = true; rnPairIndex += 2; break; @@ -4516,10 +4547,13 @@ bool DrawingML::WriteCustomGeometrySegment( void DrawingML::WriteCustomGeometryPoint( const drawing::EnhancedCustomShapeParameterPair& rParamPair, - const EnhancedCustomShape2d& rCustomShape2d) + const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth, + const bool bReplaceGeoHeight) { - sal_Int32 nX = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d); - sal_Int32 nY = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d); + sal_Int32 nX + = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false); + sal_Int32 nY + = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight); mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY)); } |