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 /oox | |
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>
Diffstat (limited to 'oox')
-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 |
7 files changed, 186 insertions, 33 deletions
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(); |