diff options
author | Regina Henschel <rb.henschel@t-online.de> | 2023-11-19 00:26:16 +0100 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2023-12-01 08:44:01 +0100 |
commit | 44ee19c99bfb3bf0f550d9656e87bca3e20e5ca0 (patch) | |
tree | 3c937a4c86ec15400a12fb1e69ae893fc8541d39 | |
parent | a83088b05f177fb938c2e4ffb06cd19362a5a1ca (diff) |
[API CHANGE] Add OOXML way of curved connector routing
The paths generated for curved connectors are basically incompatible
between LibreOffice and OOXML. Thus it was not possible to render curved
connectors the same way as MS Office. The patch adds an OOXML compatible
method for calculating the path. The new method results in a different
svg:d attribute when saved in ODF, but needs no change to ODF.
The patch introduces the boolean connector property 'EdgeOOXMLCurve' to
switch between the two methods. The property value is determined from
the svg:d attribute in case of import from ODF. In case of missing
svg:d attribute the property value is set to 'true', because Word
currently does not write a svg:d attribute when it exports to ODF.
The property value is set to 'true' for import of connectors on a
drawing canvas in docx. Default value for new connectors is 'false'.
The new property has no UI, but can be used via macro.
Currently the new method is used for import of curved connectors on
drawing canvas in docx documents.
Change-Id: I53d99f44febe4d74c2b611f5fdb9de86628c4519
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/159708
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
-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, |