diff options
-rw-r--r-- | include/editeng/unoprnms.hxx | 1 | ||||
-rw-r--r-- | include/svx/svddef.hxx | 6 | ||||
-rw-r--r-- | include/svx/svdoedge.hxx | 8 | ||||
-rw-r--r-- | include/svx/unoshprp.hxx | 3 | ||||
-rw-r--r-- | offapi/com/sun/star/drawing/ConnectorProperties.idl | 7 | ||||
-rw-r--r-- | oox/inc/drawingml/connectorhelper.hxx | 33 | ||||
-rw-r--r-- | oox/qa/unit/data/WPC_CurvedConnector2.docx | bin | 0 -> 20635 bytes | |||
-rw-r--r-- | oox/qa/unit/data/WPC_CurvedConnector5.docx | bin | 0 -> 21262 bytes | |||
-rw-r--r-- | oox/qa/unit/wpc_drawing_canvas.cxx | 32 | ||||
-rw-r--r-- | oox/source/drawingml/connectorhelper.cxx | 89 | ||||
-rw-r--r-- | oox/source/export/shapes.cxx | 52 | ||||
-rw-r--r-- | oox/source/shape/ShapeContextHandler.cxx | 13 | ||||
-rw-r--r-- | svx/source/sdr/properties/connectorproperties.cxx | 1 | ||||
-rw-r--r-- | svx/source/svdraw/svdattr.cxx | 1 | ||||
-rw-r--r-- | svx/source/svdraw/svdoedge.cxx | 83 | ||||
-rw-r--r-- | xmloff/source/draw/ximpshap.cxx | 54 | ||||
-rw-r--r-- | xmloff/source/draw/ximpshap.hxx | 5 |
17 files changed, 338 insertions, 50 deletions
diff --git a/include/editeng/unoprnms.hxx b/include/editeng/unoprnms.hxx index 1300effe179f..7f742f549833 100644 --- a/include/editeng/unoprnms.hxx +++ b/include/editeng/unoprnms.hxx @@ -56,6 +56,7 @@ inline constexpr OUString UNO_NAME_EDGENODE1HORZDIST = u"EdgeNode1HorzDist"_ustr inline constexpr OUString UNO_NAME_EDGENODE1VERTDIST = u"EdgeNode1VertDist"_ustr; inline constexpr OUString UNO_NAME_EDGENODE2HORZDIST = u"EdgeNode2HorzDist"_ustr; inline constexpr OUString UNO_NAME_EDGENODE2VERTDIST = u"EdgeNode2VertDist"_ustr; +inline constexpr OUString UNO_NAME_EDGEOOXMLCURVE = u"EdgeOOXMLCurve"_ustr; inline constexpr OUString UNO_NAME_FILLBMP_OFFSET_X = u"FillBitmapOffsetX"_ustr; inline constexpr OUString UNO_NAME_FILLBMP_OFFSET_Y = u"FillBitmapOffsetY"_ustr; diff --git a/include/svx/svddef.hxx b/include/svx/svddef.hxx index fa57a8aa0bfc..500d68fc3916 100644 --- a/include/svx/svddef.hxx +++ b/include/svx/svddef.hxx @@ -441,7 +441,11 @@ constexpr sal_uInt16 SDRATTR_WRITINGMODE2_FIRST(SDRATTR constexpr TypedWhichId<SvxFrameDirectionItem> SDRATTR_WRITINGMODE2(SDRATTR_WRITINGMODE2_FIRST+0); // 1248 constexpr sal_uInt16 SDRATTR_WRITINGMODE2_LAST(SDRATTR_WRITINGMODE2); // 1248 -constexpr sal_uInt16 SDRATTR_END (SDRATTR_WRITINGMODE2_LAST); // 1248 +constexpr sal_uInt16 SDRATTR_EDGEOOXMLCURVE_FIRST(SDRATTR_WRITINGMODE2_LAST+1);// 1249 +constexpr TypedWhichId<SfxBoolItem> SDRATTR_EDGEOOXMLCURVE(SDRATTR_EDGEOOXMLCURVE_FIRST+0); // 1249 +constexpr sal_uInt16 SDRATTR_EDGEOOXMLCURVE_LAST(SDRATTR_EDGEOOXMLCURVE); // 1249 + +constexpr sal_uInt16 SDRATTR_END (SDRATTR_EDGEOOXMLCURVE_LAST); // 1249 #endif // INCLUDED_SVX_SVDDEF_HXX diff --git a/include/svx/svdoedge.hxx b/include/svx/svdoedge.hxx index 94bb89a00a3e..9c987e259ea3 100644 --- a/include/svx/svdoedge.hxx +++ b/include/svx/svdoedge.hxx @@ -91,13 +91,19 @@ public: sal_uInt16 m_nObj2Lines; // 1..3 sal_uInt16 m_nMiddleLine; // 0xFFFF=none, otherwise point number of the beginning of the line + // The value determines how curved connectors are routed. With value 'true' it is routed + // compatible to OOXML, with value 'false' LO routing is used. + // The value is set/get via property SDRATTR_EDGEOOXMLCURVE. + bool m_bUseOOXMLCurve; + public: SdrEdgeInfoRec() : m_nAngle1(0), m_nAngle2(0), m_nObj1Lines(0), m_nObj2Lines(0), - m_nMiddleLine(0xFFFF) + m_nMiddleLine(0xFFFF), + m_bUseOOXMLCurve(false) {} Point& ImpGetLineOffsetPoint(SdrEdgeLineCode eLineCode); diff --git a/include/svx/unoshprp.hxx b/include/svx/unoshprp.hxx index 0dd723f08e77..53561015804b 100644 --- a/include/svx/unoshprp.hxx +++ b/include/svx/unoshprp.hxx @@ -403,7 +403,8 @@ { u"EdgeEndConnection"_ustr, OWN_ATTR_EDGE_END_OBJ, cppu::UnoType<css::drawing::XShape>::get(), css::beans::PropertyAttribute::MAYBEVOID, 0}, \ { u"EdgeEndPoint"_ustr, OWN_ATTR_EDGE_END_POS, ::cppu::UnoType<css::awt::Point>::get(), css::beans::PropertyAttribute::READONLY, 0}, \ \ - { UNO_NAME_POLYPOLYGONBEZIER, OWN_ATTR_EDGE_POLYPOLYGONBEZIER, ::cppu::UnoType<css::drawing::PolyPolygonBezierCoords>::get(), 0, 0}, + { UNO_NAME_POLYPOLYGONBEZIER, OWN_ATTR_EDGE_POLYPOLYGONBEZIER, ::cppu::UnoType<css::drawing::PolyPolygonBezierCoords>::get(), 0, 0}, \ + { UNO_NAME_EDGEOOXMLCURVE, SDRATTR_EDGEOOXMLCURVE, ::cppu::UnoType<bool>::get(), 0, 0}, #define SPECIAL_DIMENSIONING_PROPERTIES_DEFAULTS \ { UNO_NAME_MEASUREBELOWREFEDGE, SDRATTR_MEASUREBELOWREFEDGE, cppu::UnoType<bool>::get(), 0, 0}, \ diff --git a/offapi/com/sun/star/drawing/ConnectorProperties.idl b/offapi/com/sun/star/drawing/ConnectorProperties.idl index c7d5eb62eae4..92547f73f1df 100644 --- a/offapi/com/sun/star/drawing/ConnectorProperties.idl +++ b/offapi/com/sun/star/drawing/ConnectorProperties.idl @@ -51,6 +51,13 @@ published service ConnectorProperties */ [property] long EdgeNode2VertDist; + + /** If 'TRUE' a curved connector is routed compatible to OOXML. + The default value for new connectors is 'FALSE'. + The property is only evaluated in case EdgeKind CURVE. + @since LibreOffice 24.2 + */ + [property, optional] boolean EdgeOOXMLCurve; }; diff --git a/oox/inc/drawingml/connectorhelper.hxx b/oox/inc/drawingml/connectorhelper.hxx index f5409d635270..f353decc15ab 100644 --- a/oox/inc/drawingml/connectorhelper.hxx +++ b/oox/inc/drawingml/connectorhelper.hxx @@ -56,7 +56,7 @@ void getOOXHandlePositionsHmm(const oox::drawingml::ShapePtr& pConnector, * rotation of the connector shape. This method collects these transformations into a * B2DHomMatrix. - * @param [in] pConnector is pointer to a oox::drawing::Shape. + * @param [in] pConnector is pointer to a oox::drawing::Shape that represents a connector shape. * @return a newly created B2DHomMatrix. It might be the unit matrix. */ basegfx::B2DHomMatrix getConnectorTransformMatrix(const oox::drawingml::ShapePtr& pConnector); @@ -69,20 +69,34 @@ basegfx::B2DHomMatrix getConnectorTransformMatrix(const oox::drawingml::ShapePtr * The vector rHandlePositions is cleaned and then filled with the actual handle positions. It * is empty if the geometry does not use handles. - * @param [in] rXShape interface of a connector shape. + * @param [in] pConnector is pointer to a oox::drawing::Shape that represents a connector shape. * @param [in,out] rHandlePositions contains the calculated handle positions. */ void getLOBentHandlePositionsHmm(const oox::drawingml::ShapePtr& pConnector, std::vector<basegfx::B2DPoint>& rHandlePositions); /** + * Calulates the handle positions of a connector of type ConnectorType_CURVE for which OOXML + * compatible routing is enabled. Such connector corresponds to the OOXML curvedConnector shapes. The + * calculation is based on the actual polygon of the connector. The coordinates are always returned + * in Hmm, even for shapes on a text document draw page. + * The vector rHandlePositions is cleaned and then filled with the actual handle positions. It + * is empty if the geometry does not use handles. + + * @param [in] pConnector is pointer to a oox::drawing::Shape that represents a connector shape. + * @param [in,out] rHandlePositions contains the calculated handle positions. +*/ +void getLOCurvedHandlePositionsHmm(const oox::drawingml::ShapePtr& pConnector, + std::vector<basegfx::B2DPoint>& rHandlePositions); + +/** * Sets the properties "StartShape", "EndShape", "StartGluePointIndex" and "EndGluePointIndex". Thus * it actually connects the shapes. Connecting generates the default connector path. - * @param rConnector The connector shape + * @param pConnector is pointer to a oox::drawing::Shape that represents a connector shape. * @param [in] A flat map of target shape candidates, indexed by their msId. */ -void applyConnections(oox::drawingml::ShapePtr& rConnector, oox::drawingml::ShapeIdMap& rShapeMap); +void applyConnections(oox::drawingml::ShapePtr& pConnector, oox::drawingml::ShapeIdMap& rShapeMap); /** * Calculates the difference between handle positions in OOXML and the default handle positions in @@ -95,6 +109,17 @@ void applyConnections(oox::drawingml::ShapePtr& rConnector, oox::drawingml::Shap */ void applyBentHandleAdjustments(oox::drawingml::ShapePtr pConnector); +/** + * Calculates the difference between handle positions in OOXML and the default handle positions in + * LibreOffice. The difference is written to "EdgeLine1Delta", "EdgeLine2Delta" and "EdgeLine3Delta" + * properties. It uses the connector polygon. + + * @pre The referenced connector has type ConnectorType_CURVE, OOXML compatible routing is enabled, + and the connector has the default connector path. + * @param pConnector refers to the shape whose handles are adapted. +*/ +void applyCurvedHandleAdjustments(oox::drawingml::ShapePtr pConnector); + } // end namespace ConnectorHelper /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/oox/qa/unit/data/WPC_CurvedConnector2.docx b/oox/qa/unit/data/WPC_CurvedConnector2.docx Binary files differnew file mode 100644 index 000000000000..3f914fdf5cb0 --- /dev/null +++ b/oox/qa/unit/data/WPC_CurvedConnector2.docx diff --git a/oox/qa/unit/data/WPC_CurvedConnector5.docx b/oox/qa/unit/data/WPC_CurvedConnector5.docx Binary files differnew file mode 100644 index 000000000000..e92f9ecc21ac --- /dev/null +++ b/oox/qa/unit/data/WPC_CurvedConnector5.docx diff --git a/oox/qa/unit/wpc_drawing_canvas.cxx b/oox/qa/unit/wpc_drawing_canvas.cxx index d1fde534034c..ba347925d317 100644 --- a/oox/qa/unit/wpc_drawing_canvas.cxx +++ b/oox/qa/unit/wpc_drawing_canvas.cxx @@ -17,6 +17,7 @@ #include <com/sun/star/awt/Rectangle.hpp> #include <com/sun/star/beans/XPropertySet.hpp> #include <com/sun/star/drawing/ConnectorType.hpp> +#include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp> #include <com/sun/star/drawing/XDrawPagesSupplier.hpp> #include <com/sun/star/drawing/XShape.hpp> #include <com/sun/star/lang/XServiceInfo.hpp> @@ -26,7 +27,6 @@ #include <com/sun/star/text/XTextTable.hpp> #include <com/sun/star/text/XTextTablesSupplier.hpp> #include <com/sun/star/util/XComplexColor.hpp> - using namespace ::com::sun::star; namespace @@ -306,6 +306,36 @@ CPPUNIT_TEST_FIXTURE(TestWPC, WPC_tdf158348_shape_text_in_table_cell) // The string had started with "Inside shape" without fix. CPPUNIT_ASSERT(xCellA1->getString().startsWith("Inside table")); } + +CPPUNIT_TEST_FIXTURE(TestWPC, WPC_CurvedConnector2) +{ + // The document has two shapes connected with a curvedConnector2 on a drawing canvas. + // This connector is a single Bezier segment without handles. + loadFromURL(u"WPC_CurvedConnector2.docx"); + + // LO and OOXML differ in the position of the control points. LibreOffice uses 2/3 but OOXML + // uses 1/2 of width or height. The path by LO looks more round. + uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0), + uno::UNO_QUERY); + uno::Reference<drawing::XShapes> xGroup(xDrawPage->getByIndex(0), uno::UNO_QUERY); + uno::Reference<lang::XServiceInfo> xInfo(xGroup->getByIndex(3), uno::UNO_QUERY); + CPPUNIT_ASSERT(xInfo->supportsService("com.sun.star.drawing.ConnectorShape")); + + uno::Reference<beans::XPropertySet> xShapeProps(xGroup->getByIndex(3), uno::UNO_QUERY); + com::sun::star::drawing::ConnectorType eEdgeKind; + xShapeProps->getPropertyValue(UNO_NAME_EDGEKIND) >>= eEdgeKind; + CPPUNIT_ASSERT_EQUAL(drawing::ConnectorType::ConnectorType_CURVE, eEdgeKind); + + // Make sure the path is OOXML compatible + drawing::PolyPolygonBezierCoords aPolyPolygonBezierCoords; + xShapeProps->getPropertyValue("PolyPolygonBezier") >>= aPolyPolygonBezierCoords; + drawing::PointSequence aPolygon = aPolyPolygonBezierCoords.Coordinates[0]; + // First control point. LO routing would generate point (4372|5584). + CPPUNIT_ASSERT_EQUAL(sal_Int32(5149), aPolygon[1].Y); + // Second control point. LO routing would generate point (5887|6458). + CPPUNIT_ASSERT_EQUAL(sal_Int32(6645), aPolygon[2].X); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/oox/source/drawingml/connectorhelper.cxx b/oox/source/drawingml/connectorhelper.cxx index ff2c1fed42b9..e54b586c2365 100644 --- a/oox/source/drawingml/connectorhelper.cxx +++ b/oox/source/drawingml/connectorhelper.cxx @@ -245,11 +245,55 @@ void ConnectorHelper::getLOBentHandlePositionsHmm(const oox::drawingml::ShapePtr } } -// This is similar to SlidePersist::createConnectorShapeConnection() -void ConnectorHelper::applyConnections(oox::drawingml::ShapePtr& rConnector, +void ConnectorHelper::getLOCurvedHandlePositionsHmm( + const oox::drawingml::ShapePtr& pConnector, std::vector<basegfx::B2DPoint>& rHandlePositions) +{ + // This method is intended for Edgekind css::drawing::ConnectorType_Curve for which OoXML + // compatible routing is enabled. + rHandlePositions.clear(); + + if (!pConnector) + return; + uno::Reference<drawing::XShape> xConnector(pConnector->getXShape()); + if (!xConnector.is()) + return; + + // Get the EdgeTrack polygon. We cannot use UNO "PolyPolygonBezier" because that includes + // the yet not known anchor position in Writer. Thus get the polygon directly from the object. + SdrEdgeObj* pEdgeObj = dynamic_cast<SdrEdgeObj*>(SdrObject::getSdrObjectFromXShape(xConnector)); + if (!pEdgeObj) + return; + basegfx::B2DPolyPolygon aB2DPolyPolygon(pEdgeObj->GetEdgeTrackPath()); + if (aB2DPolyPolygon.count() == 0) + return; + + basegfx::B2DPolygon aEdgePolygon = aB2DPolyPolygon.getB2DPolygon(0); + if (aEdgePolygon.count() < 3 || !aEdgePolygon.areControlPointsUsed()) + return; + + // We need Hmm, the polygon might be e.g. in Twips, in Writer for example + MapUnit eMapUnit = pEdgeObj->getSdrModelFromSdrObject().GetItemPool().GetMetric(0); + if (eMapUnit != MapUnit::Map100thMM) + { + const auto eFrom = MapToO3tlLength(eMapUnit); + if (eFrom == o3tl::Length::invalid) + return; + const double fConvert(o3tl::convert(1.0, eFrom, o3tl::Length::mm100)); + aEdgePolygon.transform(basegfx::B2DHomMatrix(fConvert, 0.0, 0.0, 0.0, fConvert, 0.0)); + } + + // The OOXML compatible routing has the handles as polygon points, but not start or + // end point. + for (sal_uInt32 i = 1; i < aEdgePolygon.count() - 1; i++) + { + rHandlePositions.push_back(aEdgePolygon.getB2DPoint(i)); + } +} + +void ConnectorHelper::applyConnections(oox::drawingml::ShapePtr& pConnector, oox::drawingml::ShapeIdMap& rShapeMap) { - uno::Reference<drawing::XShape> xConnector(rConnector->getXShape()); + uno::Reference<drawing::XShape> xConnector(pConnector->getXShape()); if (!xConnector.is()) return; uno::Reference<beans::XPropertySet> xPropSet(xConnector, uno::UNO_QUERY); @@ -262,8 +306,13 @@ void ConnectorHelper::applyConnections(oox::drawingml::ShapePtr& rConnector, xPropSet->setPropertyValue("EdgeNode2HorzDist", uno::Any(sal_Int32(0))); xPropSet->setPropertyValue("EdgeNode2VertDist", uno::Any(sal_Int32(0))); + // A OOXML curvedConnector uses a routing method which is basically incompatible with the + // traditional way of LibreOffice. A compatible way was added and needs to be enabled before + // connections are set, so that the method is used in the default routing. + xPropSet->setPropertyValue("EdgeOOXMLCurve", uno::Any(true)); + oox::drawingml::ConnectorShapePropertiesList aConnectorShapeProperties - = rConnector->getConnectorShapeProperties(); + = pConnector->getConnectorShapeProperties(); // It contains maximal two items, each a struct with mbStartShape, maDestShapeId, mnDestGlueId for (const auto& aIt : aConnectorShapeProperties) { @@ -356,4 +405,36 @@ void ConnectorHelper::applyBentHandleAdjustments(oox::drawingml::ShapePtr pConne } } +void ConnectorHelper::applyCurvedHandleAdjustments(oox::drawingml::ShapePtr pConnector) +{ + uno::Reference<drawing::XShape> xConnector(pConnector->getXShape(), uno::UNO_QUERY); + if (!xConnector.is()) + return; + uno::Reference<beans::XPropertySet> xPropSet(xConnector, uno::UNO_QUERY); + if (!xPropSet.is()) + return; + + std::vector<basegfx::B2DPoint> aOOXMLHandles; + ConnectorHelper::getOOXHandlePositionsHmm(pConnector, aOOXMLHandles); + std::vector<basegfx::B2DPoint> aLODefaultHandles; + ConnectorHelper::getLOCurvedHandlePositionsHmm(pConnector, aLODefaultHandles); + + if (aOOXMLHandles.size() == aLODefaultHandles.size()) + { + bool bUseYforHori + = basegfx::fTools::equalZero(getConnectorTransformMatrix(pConnector).get(0, 0)); + for (size_t i = 0; i < aOOXMLHandles.size(); i++) + { + basegfx::B2DVector aDiff(aOOXMLHandles[i] - aLODefaultHandles[i]); + sal_Int32 nDiff; + if ((i == 1 && !bUseYforHori) || (i != 1 && bUseYforHori)) + nDiff = basegfx::fround(aDiff.getY()); + else + nDiff = basegfx::fround(aDiff.getX()); + xPropSet->setPropertyValue("EdgeLine" + OUString::number(i + 1) + "Delta", + uno::Any(nDiff)); + } + } +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx index 67a61974d926..87f2cfb7277b 100644 --- a/oox/source/export/shapes.cxx +++ b/oox/source/export/shapes.cxx @@ -1607,18 +1607,25 @@ static void lcl_GetConnectorAdjustValue(const Reference<XShape>& xShape, tools:: ConnectorType eConnectorType, std::vector<std::pair<sal_Int32, sal_Int32>>& rAvList) { + Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY); + bool bIsOOXMLCurve(false); + xShapeProps->getPropertyValue("EdgeOOXMLCurve") >>= bIsOOXMLCurve; sal_Int32 nAdjCount = 0; if (eConnectorType == ConnectorType_CURVE) { - if (aPoly.GetSize() == 4) + if (bIsOOXMLCurve) + { + nAdjCount = (aPoly.GetSize() - 4) / 3; + } + else if (aPoly.GetSize() == 4) { if ((aPoly[0].X() == aPoly[1].X() && aPoly[2].X() == aPoly[3].X()) || (aPoly[0].Y() == aPoly[1].Y() && aPoly[2].Y() == aPoly[3].Y())) { - nAdjCount = 1; // curvedConnector3 + nAdjCount = 1; // curvedConnector3, control vectors parallel } else - nAdjCount = 0; // curvedConnector2 + nAdjCount = 0; // curvedConnector2, control vectors orthogonal } else if (aPoly.GetSize() > 4) { @@ -1672,27 +1679,34 @@ static void lcl_GetConnectorAdjustValue(const Reference<XShape>& xShape, tools:: if (eConnectorType == ConnectorType_CURVE) { - awt::Size aSize = xShape->getSize(); - awt::Point aShapePosition = xShape->getPosition(); - tools::Rectangle aBoundRect = aPoly.GetBoundRect(); - - if (bVertical) + if (bIsOOXMLCurve) { - if ((aBoundRect.GetSize().Height() - aSize.Height) == 1) - aPt.setY(aPoly[i + 1].Y()); - else if (aStart.Y() > aPt.Y()) - aPt.setY(aShapePosition.Y); - else - aPt.setY(aShapePosition.Y + aSize.Height); + aPt = aPoly[3 * i]; } else { - if ((aBoundRect.GetSize().Width() - aSize.Width) == 1) - aPt.setX(aPoly[i + 1].X()); - else if (aStart.X() > aPt.X()) - aPt.setX(aShapePosition.X); + awt::Size aSize = xShape->getSize(); + awt::Point aShapePosition = xShape->getPosition(); + tools::Rectangle aBoundRect = aPoly.GetBoundRect(); + + if (bVertical) + { + if ((aBoundRect.GetSize().Height() - aSize.Height) == 1) + aPt.setY(aPoly[i + 1].Y()); + else if (aStart.Y() > aPt.Y()) + aPt.setY(aShapePosition.Y); + else + aPt.setY(aShapePosition.Y + aSize.Height); + } else - aPt.setX(aShapePosition.X + aSize.Width); + { + if ((aBoundRect.GetSize().Width() - aSize.Width) == 1) + aPt.setX(aPoly[i + 1].X()); + else if (aStart.X() > aPt.X()) + aPt.setX(aShapePosition.X); + else + aPt.setX(aShapePosition.X + aSize.Width); + } } } diff --git a/oox/source/shape/ShapeContextHandler.cxx b/oox/source/shape/ShapeContextHandler.cxx index 19c2deb71f57..c012097004e9 100644 --- a/oox/source/shape/ShapeContextHandler.cxx +++ b/oox/source/shape/ShapeContextHandler.cxx @@ -537,11 +537,14 @@ ShapeContextHandler::getShape() { ConnectorHelper::applyBentHandleAdjustments(rIt.second); } - // else use the default path of LibreOffice - // curvedConnector2 and bentConnector2 do not have handles. - // ToDo: OOXML defines a path for curveConnector3, curveConnector4 and - // curveConnector5 that is basically incompatible with the way LibreOffice - // creates the path. + else if (rIt.second->getConnectorName() == u"curvedConnector3"_ustr + || rIt.second->getConnectorName() == u"curvedConnector4"_ustr + || rIt.second->getConnectorName() == u"curvedConnector5"_ustr) + { + ConnectorHelper::applyCurvedHandleAdjustments(rIt.second); + } + // else use the default path of LibreOffice. + // curveConnector2 and bentConnector2 do not have handles. } } xResult = pShape->getXShape(); diff --git a/svx/source/sdr/properties/connectorproperties.cxx b/svx/source/sdr/properties/connectorproperties.cxx index 8ae3f0ef3965..4d3542d67894 100644 --- a/svx/source/sdr/properties/connectorproperties.cxx +++ b/svx/source/sdr/properties/connectorproperties.cxx @@ -40,6 +40,7 @@ namespace sdr::properties SDRATTR_MISC_FIRST, SDRATTR_EDGE_LAST, SDRATTR_TEXTDIRECTION, SDRATTR_TEXTDIRECTION, SDRATTR_TEXTCOLUMNS_FIRST, SDRATTR_TEXTCOLUMNS_LAST, + SDRATTR_EDGEOOXMLCURVE_FIRST, SDRATTR_EDGEOOXMLCURVE_LAST, // Range from SdrTextObj: EE_ITEMS_START, EE_ITEMS_END>); } diff --git a/svx/source/svdraw/svdattr.cxx b/svx/source/svdraw/svdattr.cxx index 6ba680e5d520..c518900e6f03 100644 --- a/svx/source/svdraw/svdattr.cxx +++ b/svx/source/svdraw/svdattr.cxx @@ -194,6 +194,7 @@ SdrItemPool::SdrItemPool( rPoolDefaults[SDRATTR_EDGELINE1DELTA -SDRATTR_START]=new SdrMetricItem(SDRATTR_EDGELINE1DELTA, 0); rPoolDefaults[SDRATTR_EDGELINE2DELTA -SDRATTR_START]=new SdrMetricItem(SDRATTR_EDGELINE2DELTA, 0); rPoolDefaults[SDRATTR_EDGELINE3DELTA -SDRATTR_START]=new SdrMetricItem(SDRATTR_EDGELINE3DELTA, 0); + rPoolDefaults[SDRATTR_EDGEOOXMLCURVE -SDRATTR_START]=new SfxBoolItem(SDRATTR_EDGEOOXMLCURVE, false); rPoolDefaults[SDRATTR_MEASUREKIND -SDRATTR_START]=new SdrMeasureKindItem; rPoolDefaults[SDRATTR_MEASURETEXTHPOS -SDRATTR_START]=new SdrMeasureTextHPosItem; rPoolDefaults[SDRATTR_MEASURETEXTVPOS -SDRATTR_START]=new SdrMeasureTextVPosItem; diff --git a/svx/source/svdraw/svdoedge.cxx b/svx/source/svdraw/svdoedge.cxx index f70e1f924f6b..33b6a6b82a1a 100644 --- a/svx/source/svdraw/svdoedge.cxx +++ b/svx/source/svdraw/svdoedge.cxx @@ -252,6 +252,11 @@ void SdrEdgeObj::ImpSetAttrToEdgeInfo() m_aEdgeInfo.ImpSetLineOffset(SdrEdgeLineCode::Obj2Line2, *m_pEdgeTrack, nVals[n]); n++; } + + // Do not overwrite existing value with default. ImpSetAttrToEdgeInfo() is called several + // times with a set, that does not have SDRATTR_EDGEOOXMLCURVE item. + if (rSet.HasItem(SDRATTR_EDGEOOXMLCURVE)) + m_aEdgeInfo.m_bUseOOXMLCurve = rSet.Get(SDRATTR_EDGEOOXMLCURVE).GetValue(); } else if(eKind == SdrEdgeKind::ThreeLines) { @@ -371,6 +376,9 @@ void SdrEdgeObj::ImpSetEdgeInfoToAttr() { GetProperties().ClearObjectItemDirect(SDRATTR_EDGELINE1DELTA); } + + GetProperties().SetObjectItemDirect( + SfxBoolItem(SDRATTR_EDGEOOXMLCURVE, m_aEdgeInfo.m_bUseOOXMLCurve)); } void SdrEdgeObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const @@ -1501,7 +1509,45 @@ XPolygon SdrEdgeObj::ImpCalcEdgeTrack(const Point& rPt1, tools::Long nAngle1, co } } // make the connector a bezier curve, if appropriate - if (eKind==SdrEdgeKind::Bezier && nPointCount>2) { + if (eKind != SdrEdgeKind::Bezier || nPointCount <= 2) + return aXP1; + + if (pInfo->m_bUseOOXMLCurve) // Routing method OOXML + { + // The additional points needed are located on the segments of the path of the + // corresponding bentConnector as calculated above. + auto SegmentPoint = [&aXP1](const sal_uInt16& nEnd, const double& fFactor) { + return Point( + aXP1[nEnd - 1].X() + FRound(fFactor * (aXP1[nEnd].X() - aXP1[nEnd - 1].X())), + aXP1[nEnd - 1].Y() + FRound(fFactor * (aXP1[nEnd].Y() - aXP1[nEnd - 1].Y()))); + }; + + // We change the path going from end to start. Thus inserting points does not affect the index + // of the preciding points. + // The end point has index nPointCount-1 and is a normal point and kept. + // Insert new control point in the middle of last segments. + Point aControl = SegmentPoint(nPointCount - 1, 0.5); + // Insert happens before specified index. + aXP1.Insert(nPointCount - 1, aControl, PolyFlags::Control); + for (sal_uInt16 nSegment = nPointCount - 2; nSegment > 1; --nSegment) + { + // We need a normal point at center of segment and control points at 1/4 and 3/4 of + // segment. At center and 1/4 are new points, at 3/4 will be replacement for the end + // point of the segment. + aControl = SegmentPoint(nSegment, 0.25); + Point aNormal = SegmentPoint(nSegment, 0.5); + aXP1.SetFlags(nSegment, PolyFlags::Control); + aXP1[nSegment] = SegmentPoint(nSegment, 0.75); + aXP1.Insert(nSegment, aNormal, PolyFlags::Normal); + aXP1.Insert(nSegment, aControl, PolyFlags::Control); + } + // The first segments needs a control point in the middle. It is replacement for the + // second point. + aXP1.SetFlags(1, PolyFlags::Control); + aXP1[1] = SegmentPoint(1, 0.5); + } + else // Routing method LO + { Point* pPt1=&aXP1[0]; Point* pPt2=&aXP1[1]; Point* pPt3=&aXP1[nPointCount-2]; @@ -1796,35 +1842,48 @@ void SdrEdgeObj::AddToHdlList(SdrHdlList& rHdlList) const sal_uInt32 nO1(m_aEdgeInfo.m_nObj1Lines > 0 ? m_aEdgeInfo.m_nObj1Lines - 1 : 0); sal_uInt32 nO2(m_aEdgeInfo.m_nObj2Lines > 0 ? m_aEdgeInfo.m_nObj2Lines - 1 : 0); sal_uInt32 nM(m_aEdgeInfo.m_nMiddleLine != 0xFFFF ? 1 : 0); + bool bOOXMLCurve = m_aEdgeInfo.m_bUseOOXMLCurve && eKind == SdrEdgeKind::Bezier; for(sal_uInt32 i = 0; i < (nO1 + nO2 + nM); ++i) { sal_Int32 nPt(0); sal_uInt32 nNum = i; std::unique_ptr<ImpEdgeHdl> pHdl(new ImpEdgeHdl(Point(),SdrHdlKind::Poly)); - if (nNum<nO1) { - nPt=nNum+1; + if (nNum<nO1) + { + nPt = bOOXMLCurve ? (nNum + 1) * 3 : nNum + 1; if (nNum==0) pHdl->SetLineCode(SdrEdgeLineCode::Obj1Line2); if (nNum==1) pHdl->SetLineCode(SdrEdgeLineCode::Obj1Line3); } else { nNum=nNum-nO1; - if (nNum<nO2) { - nPt=nPointCount-3-nNum; + if (nNum<nO2) + { + nPt = bOOXMLCurve ? nPointCount - 4 - nNum * 3 : nPointCount - 3 - nNum; if (nNum==0) pHdl->SetLineCode(SdrEdgeLineCode::Obj2Line2); if (nNum==1) pHdl->SetLineCode(SdrEdgeLineCode::Obj2Line3); } else { nNum=nNum-nO2; if (nNum<nM) { - nPt=m_aEdgeInfo.m_nMiddleLine; + nPt = bOOXMLCurve ? m_aEdgeInfo.m_nMiddleLine * 3 + : m_aEdgeInfo.m_nMiddleLine; pHdl->SetLineCode(SdrEdgeLineCode::MiddleLine); } } } - if (nPt>0) { - Point aPos((*m_pEdgeTrack)[static_cast<sal_uInt16>(nPt)]); - aPos+=(*m_pEdgeTrack)[static_cast<sal_uInt16>(nPt)+1]; - aPos.setX( aPos.X() / 2 ); - aPos.setY( aPos.Y() / 2 ); - pHdl->SetPos(aPos); + if (nPt>0) + { + if (bOOXMLCurve) + { + Point aPos((*m_pEdgeTrack)[static_cast<sal_uInt16>(nPt)]); + pHdl->SetPos(aPos); + } + else + { + Point aPos((*m_pEdgeTrack)[static_cast<sal_uInt16>(nPt)]); + aPos+=(*m_pEdgeTrack)[static_cast<sal_uInt16>(nPt)+1]; + aPos.setX( aPos.X() / 2 ); + aPos.setY( aPos.Y() / 2 ); + pHdl->SetPos(aPos); + } pHdl->SetPointNum(i + 2); rHdlList.AddHdl(std::move(pHdl)); } diff --git a/xmloff/source/draw/ximpshap.cxx b/xmloff/source/draw/ximpshap.cxx index d228b729521c..6cf226274115 100644 --- a/xmloff/source/draw/ximpshap.cxx +++ b/xmloff/source/draw/ximpshap.cxx @@ -1749,7 +1749,8 @@ SdXMLConnectorShapeContext::SdXMLConnectorShapeContext( mnEndGlueId(-1), mnDelta1(0), mnDelta2(0), - mnDelta3(0) + mnDelta3(0), + mbLikelyOOXMLCurve(true) { } @@ -1774,6 +1775,50 @@ bool SvXMLImport::needFixPositionAfterZ() const return bWrongPositionAfterZ; } +namespace +{ +bool lcl_IsLikelyOOXMLCurve(const basegfx::B2DPolygon& rPolygon) +{ + sal_uInt32 nCount = rPolygon.count(); + if (!rPolygon.areControlPointsUsed() or nCount < 2) + return false; // no curve at all + + basegfx::B2DVector aStartVec(rPolygon.getNextControlPoint(0) - rPolygon.getB2DPoint(0)); + basegfx::B2DVector aEndVec(rPolygon.getPrevControlPoint(nCount-1) - rPolygon.getB2DPoint(nCount - 1)); + // LibreOffice uses one point less than OOXML for the same underlaying bentConnector or + // STANDARD connector, respectively. A deeper inspection is only needed in case of 2 resulting + // points. Those connector paths look like a quarter ellipse. + switch (nCount) + { + case 2: + { + // In case start and end direction are parallel, it cannot be OOXML because that case + // introduces a handle on the path and the curve has three points then. + if (basegfx::areParallel(aStartVec, aEndVec)) + return false; + // OOXML sets the control point at 1/2, LibreOffice at 2/3 of width or height. + // A tolerance is used because +-1 deviations due to integer arithmetic in many places. + basegfx::B2DRange aRect(rPolygon.getB2DPoint(0), rPolygon.getB2DPoint(1)); + if ((basegfx::fTools::equalZero(aStartVec.getX()) + && basegfx::fTools::equal(aStartVec.getLength() * 2.0, aRect.getHeight(), 2.0)) + || (basegfx::fTools::equalZero(aStartVec.getY()) + && basegfx::fTools::equal(aStartVec.getLength() * 2.0, aRect.getWidth(), 2.0))) + return true; + } + break; + case 3: + case 5: + return basegfx::areParallel(aStartVec, aEndVec); + break; + case 4: // start and end direction are orthogonal + return basegfx::fTools::equalZero(aStartVec.scalar( aEndVec)); + break; + default: + return false; + } + return false; +} +} // end namespace // this is called from the parent group for each unparsed attribute in the attribute list bool SdXMLConnectorShapeContext::processAttribute( const sax_fastparser::FastAttributeList::FastAttributeIter & aIter ) @@ -1859,6 +1904,8 @@ bool SdXMLConnectorShapeContext::processAttribute( const sax_fastparser::FastAtt aPolyPolygon, aSourcePolyPolygon); maPath <<= aSourcePolyPolygon; + + mbLikelyOOXMLCurve = lcl_IsLikelyOOXMLCurve(aPolyPolygon.getB2DPolygon(0)); } } break; @@ -1921,13 +1968,16 @@ void SdXMLConnectorShapeContext::startFastElement (sal_Int32 nElement, } } + uno::Reference< beans::XPropertySet > xProps( mxShape, uno::UNO_QUERY ); + if (xProps.is()) + xProps->setPropertyValue("EdgeOOXMLCurve", Any(mbLikelyOOXMLCurve)); + // add connection ids if( !maStartShapeId.isEmpty() ) GetImport().GetShapeImport()->addShapeConnection( mxShape, true, maStartShapeId, mnStartGlueId ); if( !maEndShapeId.isEmpty() ) GetImport().GetShapeImport()->addShapeConnection( mxShape, false, maEndShapeId, mnEndGlueId ); - uno::Reference< beans::XPropertySet > xProps( mxShape, uno::UNO_QUERY ); if( xProps.is() ) { xProps->setPropertyValue("StartPosition", Any(maStart)); diff --git a/xmloff/source/draw/ximpshap.hxx b/xmloff/source/draw/ximpshap.hxx index 64b28df579a0..3450a16e5780 100644 --- a/xmloff/source/draw/ximpshap.hxx +++ b/xmloff/source/draw/ximpshap.hxx @@ -296,6 +296,11 @@ private: css::uno::Any maPath; + // Guess from the svg:d attribute whether the shape was rendered using OOXML definition. The + // default value is true to cover files exported to ODF by MS Office, which does not write a + // svg:d attribute. LibreOffice has always written a svg:d attribute. + bool mbLikelyOOXMLCurve; + public: SdXMLConnectorShapeContext( SvXMLImport& rImport, |