summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/editeng/unoprnms.hxx1
-rw-r--r--include/svx/svddef.hxx6
-rw-r--r--include/svx/svdoedge.hxx8
-rw-r--r--include/svx/unoshprp.hxx3
-rw-r--r--offapi/com/sun/star/drawing/ConnectorProperties.idl7
-rw-r--r--oox/inc/drawingml/connectorhelper.hxx33
-rw-r--r--oox/qa/unit/data/WPC_CurvedConnector2.docxbin0 -> 20635 bytes
-rw-r--r--oox/qa/unit/data/WPC_CurvedConnector5.docxbin0 -> 21262 bytes
-rw-r--r--oox/qa/unit/wpc_drawing_canvas.cxx32
-rw-r--r--oox/source/drawingml/connectorhelper.cxx89
-rw-r--r--oox/source/export/shapes.cxx52
-rw-r--r--oox/source/shape/ShapeContextHandler.cxx13
-rw-r--r--svx/source/sdr/properties/connectorproperties.cxx1
-rw-r--r--svx/source/svdraw/svdattr.cxx1
-rw-r--r--svx/source/svdraw/svdoedge.cxx83
-rw-r--r--xmloff/source/draw/ximpshap.cxx54
-rw-r--r--xmloff/source/draw/ximpshap.hxx5
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
new file mode 100644
index 000000000000..3f914fdf5cb0
--- /dev/null
+++ b/oox/qa/unit/data/WPC_CurvedConnector2.docx
Binary files 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
--- /dev/null
+++ b/oox/qa/unit/data/WPC_CurvedConnector5.docx
Binary files 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,