include/editeng/unoprnms.hxx                        |    1 
 include/svx/svddef.hxx                              |    6 +
 include/svx/svdoedge.hxx                            |    8 +
 include/svx/unoshprp.hxx                            |    3 
 offapi/com/sun/star/drawing/ConnectorProperties.idl |    7 +
 oox/inc/drawingml/connectorhelper.hxx               |   33 ++++++-
 oox/qa/unit/data/WPC_CurvedConnector2.docx          |binary
 oox/qa/unit/data/WPC_CurvedConnector5.docx          |binary
 oox/qa/unit/wpc_drawing_canvas.cxx                  |   32 ++++++-
 oox/source/drawingml/connectorhelper.cxx            |   89 +++++++++++++++++++-
 oox/source/export/shapes.cxx                        |   52 +++++++----
 oox/source/shape/ShapeContextHandler.cxx            |   13 +-
 svx/source/sdr/properties/connectorproperties.cxx   |    1 
 svx/source/svdraw/svdattr.cxx                       |    1 
 svx/source/svdraw/svdoedge.cxx                      |   83 +++++++++++++++---
 xmloff/source/draw/ximpshap.cxx                     |   54 +++++++++++-
 xmloff/source/draw/ximpshap.hxx                     |    5 +
 17 files changed, 338 insertions(+), 50 deletions(-)

New commits:
commit 44ee19c99bfb3bf0f550d9656e87bca3e20e5ca0
Author:     Regina Henschel <rb.hensc...@t-online.de>
AuthorDate: Sun Nov 19 00:26:16 2023 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Dec 1 08:44:01 2023 +0100

    [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 <vmik...@collabora.com>

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
new file mode 100644
index 000000000000..3f914fdf5cb0
Binary files /dev/null and b/oox/qa/unit/data/WPC_CurvedConnector2.docx differ
diff --git a/oox/qa/unit/data/WPC_CurvedConnector5.docx 
b/oox/qa/unit/data/WPC_CurvedConnector5.docx
new file mode 100644
index 000000000000..e92f9ecc21ac
Binary files /dev/null and b/oox/qa/unit/data/WPC_CurvedConnector5.docx differ
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,

Reply via email to