summaryrefslogtreecommitdiff
path: root/oox
diff options
context:
space:
mode:
Diffstat (limited to 'oox')
-rw-r--r--oox/CppunitTest_oox_wpc_drawing_canvas.mk54
-rw-r--r--oox/Library_oox.mk2
-rw-r--r--oox/Module_oox.mk1
-rw-r--r--oox/inc/drawingml/connectorhelper.hxx108
-rw-r--r--oox/qa/unit/data/WPC_BentConnector.docxbin0 -> 20260 bytes
-rw-r--r--oox/qa/unit/data/WPC_CanvasBackground.docxbin0 -> 18810 bytes
-rw-r--r--oox/qa/unit/data/WPC_Glow.docxbin0 -> 22506 bytes
-rw-r--r--oox/qa/unit/data/WPC_MulticolorGradient.docxbin0 -> 30096 bytes
-rw-r--r--oox/qa/unit/data/WPC_Shadow.docxbin0 -> 21385 bytes
-rw-r--r--oox/qa/unit/data/WPC_Textwrap_in_ellipse.docxbin0 -> 20014 bytes
-rw-r--r--oox/qa/unit/data/WPC_ThemeColor.docxbin0 -> 18941 bytes
-rw-r--r--oox/qa/unit/data/WPC_tdf104671_Cloud.docxbin0 -> 20431 bytes
-rw-r--r--oox/qa/unit/data/WPC_tdf48610_Textbox_with_table_inside.docxbin0 -> 22920 bytes
-rw-r--r--oox/qa/unit/wpc_drawing_canvas.cxx259
-rw-r--r--oox/source/drawingml/connectorhelper.cxx368
-rw-r--r--oox/source/drawingml/connectorshapecontext.cxx17
-rw-r--r--oox/source/drawingml/shape.cxx44
-rw-r--r--oox/source/drawingml/shapegroupcontext.cxx1
-rw-r--r--oox/source/shape/ShapeContextHandler.cxx88
-rw-r--r--oox/source/shape/WordprocessingCanvasContext.cxx106
-rw-r--r--oox/source/shape/WordprocessingCanvasContext.hxx38
-rw-r--r--oox/source/shape/WpsContext.cxx32
-rw-r--r--oox/source/token/namespaces-strict.txt1
-rw-r--r--oox/source/token/namespaces.hxx.tail2
-rw-r--r--oox/source/token/namespaces.txt1
-rw-r--r--oox/source/token/tokens.txt1
26 files changed, 1103 insertions, 20 deletions
diff --git a/oox/CppunitTest_oox_wpc_drawing_canvas.mk b/oox/CppunitTest_oox_wpc_drawing_canvas.mk
new file mode 100644
index 000000000000..78a6beb073fc
--- /dev/null
+++ b/oox/CppunitTest_oox_wpc_drawing_canvas.mk
@@ -0,0 +1,54 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,oox_wpc_drawing_canvas))
+
+$(eval $(call gb_CppunitTest_use_externals,oox_wpc_drawing_canvas,\
+ boost_headers \
+ libxml2 \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,oox_wpc_drawing_canvas, \
+ oox/qa/unit/wpc_drawing_canvas \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,oox_wpc_drawing_canvas, \
+ comphelper \
+ cppu \
+ cppuhelper \
+ docmodel \
+ oox \
+ sal \
+ subsequenttest \
+ test \
+ unotest \
+ utl \
+ tl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,oox_wpc_drawing_canvas))
+
+$(eval $(call gb_CppunitTest_use_ure,oox_wpc_drawing_canvas))
+$(eval $(call gb_CppunitTest_use_vcl,oox_wpc_drawing_canvas))
+
+$(eval $(call gb_CppunitTest_use_rdb,oox_wpc_drawing_canvas,services))
+
+$(eval $(call gb_CppunitTest_use_custom_headers,oox_wpc_drawing_canvas,\
+ officecfg/registry \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,oox_wpc_drawing_canvas))
+
+$(eval $(call gb_CppunitTest_add_arguments,oox_wpc_drawing_canvas, \
+ -env:arg-env=$(gb_Helper_LIBRARY_PATH_VAR)"$$$${$(gb_Helper_LIBRARY_PATH_VAR)+=$$$$$(gb_Helper_LIBRARY_PATH_VAR)}" \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/oox/Library_oox.mk b/oox/Library_oox.mk
index e37df6a393b9..3043d7345f3d 100644
--- a/oox/Library_oox.mk
+++ b/oox/Library_oox.mk
@@ -145,6 +145,7 @@ $(eval $(call gb_Library_add_exception_objects,oox,\
oox/source/drawingml/clrschemecontext \
oox/source/drawingml/clrscheme \
oox/source/drawingml/colorchoicecontext \
+ oox/source/drawingml/connectorhelper \
oox/source/drawingml/connectorshapecontext \
oox/source/drawingml/customshapegeometry \
oox/source/drawingml/customshapepresetdata \
@@ -302,6 +303,7 @@ $(eval $(call gb_Library_add_exception_objects,oox,\
oox/source/shape/ShapeContextHandler \
oox/source/shape/ShapeDrawingFragmentHandler \
oox/source/shape/ShapeFilterBase \
+ oox/source/shape/WordprocessingCanvasContext \
oox/source/shape/WpgContext \
oox/source/shape/WpsContext \
oox/source/token/namespacemap \
diff --git a/oox/Module_oox.mk b/oox/Module_oox.mk
index dc07ab913c56..34efb288d401 100644
--- a/oox/Module_oox.mk
+++ b/oox/Module_oox.mk
@@ -35,6 +35,7 @@ $(eval $(call gb_Module_add_check_targets,oox,\
CppunitTest_oox_shape \
CppunitTest_oox_export \
CppunitTest_oox_mcgr \
+ CppunitTest_oox_wpc_drawing_canvas \
))
endif
diff --git a/oox/inc/drawingml/connectorhelper.hxx b/oox/inc/drawingml/connectorhelper.hxx
new file mode 100644
index 000000000000..dec1deb6a238
--- /dev/null
+++ b/oox/inc/drawingml/connectorhelper.hxx
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <sal/types.h>
+
+#include <basegfx/point/b2dpoint.hxx>
+#include <drawingml/customshapeproperties.hxx>
+#include <oox/drawingml/shape.hxx>
+#include <oox/drawingml/drawingmltypes.hxx>
+#include <rtl/ustring.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+
+#include <vector>
+
+namespace ConnectorHelper
+{
+/* ToDo: Other place? It uses getShapePresetTypeName() and that is only used in shape.cxx in
+ Shape::createAndInsert() for "mso-orig-shape-type" property in GrabBag and for msConnectorName.
+ In both cases it is immediately converted to OUString. So perhaps let
+ getShapePresetTypeName() return an OUString directly?
+*/
+rtl::OUString getShapePresetTypeNameOUString(
+ const oox::drawingml::CustomShapePropertiesPtr& pCustomShapePropertiesPtr);
+
+/**
+ * Some preset shapes use the default connector site but in order right, bottom, left, top.
+ * The function detects this.
+
+ * @param rShapeType The shape type identifier as it is used in the prst attribute in OOXML.
+ * @return true if this shape type has the right, bottom, left, top order of connection site.
+ */
+bool hasClockwiseCxn(const OUString& rShapeType);
+
+/**
+ * Calculates the handle positions based on the definition in presetShapeDefinitions.xml and the
+ * actual size of the connector shape. It transforms the handles positions to the actual used
+ * connector layout. It transforms the handle coordinates to Hmm.
+ * The vector rHandlePositions is cleared and then filled with the actual handle positions. It is
+ * empty if the geometry does not use handles, e.g. "bentConnector2" and "curvedConnector2".
+ * This method works both for bentConnector and curvedConnector.
+
+ * @pre pConnector is not empty and points to a ooxml::drawing::Shape that represents a connector
+ * shape.
+
+ * @param [in] pConnector pointer to a connector shape
+ * @param [in, out] rHandlePositions contains the calculated handle positions.
+*/
+void getOOXHandlePositionsHmm(const oox::drawingml::ShapePtr& pConnector,
+ std::vector<basegfx::B2DPoint>& rHandlePositions);
+
+/**
+ * OOXML defines the connector shapes so, that the start point is top-left and the leaving
+ * direction is horizontal. Other layout is done by flipV, flipH and 90deg, 180deg or 270deg
+ * rotation of the connector shape. This method collects these transformations into a
+ * B2DHomMatrix.
+
+ * @param [in] pConnector is pointer to a oox::drawing::Shape.
+ * @return a newly created B2DHomMatrix. It might be the unit matrix.
+*/
+basegfx::B2DHomMatrix getConnectorTransformMatrix(const oox::drawingml::ShapePtr& pConnector);
+
+/**
+ * Calulates the handle positions of a connector of type ConnectorType_STANDARD. Such connector
+ * corresponds to the OOXML bentConnector shapes, aka "ElbowConnector". 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] rXShape interface of a connector shape.
+ * @param [in,out] rHandlePositions contains the calculated handle positions.
+*/
+void getLOBentHandlePositionsHmm(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 [in] A flat map of target shape candidates, indexed by their msId.
+*/
+void applyConnections(oox::drawingml::ShapePtr& rConnector, oox::drawingml::ShapeIdMap& rShapeMap);
+
+/**
+ * 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_STANDARD and has the default connector path.
+
+ * @param pConnector refers to the shape whose handles are adapted.
+*/
+void applyBentHandleAdjustments(oox::drawingml::ShapePtr pConnector);
+
+} // end namespace ConnecorHelper
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ \ No newline at end of file
diff --git a/oox/qa/unit/data/WPC_BentConnector.docx b/oox/qa/unit/data/WPC_BentConnector.docx
new file mode 100644
index 000000000000..27cc978077c4
--- /dev/null
+++ b/oox/qa/unit/data/WPC_BentConnector.docx
Binary files differ
diff --git a/oox/qa/unit/data/WPC_CanvasBackground.docx b/oox/qa/unit/data/WPC_CanvasBackground.docx
new file mode 100644
index 000000000000..bc5b3dda8678
--- /dev/null
+++ b/oox/qa/unit/data/WPC_CanvasBackground.docx
Binary files differ
diff --git a/oox/qa/unit/data/WPC_Glow.docx b/oox/qa/unit/data/WPC_Glow.docx
new file mode 100644
index 000000000000..29f9a7466a36
--- /dev/null
+++ b/oox/qa/unit/data/WPC_Glow.docx
Binary files differ
diff --git a/oox/qa/unit/data/WPC_MulticolorGradient.docx b/oox/qa/unit/data/WPC_MulticolorGradient.docx
new file mode 100644
index 000000000000..4d874fa48053
--- /dev/null
+++ b/oox/qa/unit/data/WPC_MulticolorGradient.docx
Binary files differ
diff --git a/oox/qa/unit/data/WPC_Shadow.docx b/oox/qa/unit/data/WPC_Shadow.docx
new file mode 100644
index 000000000000..44e282feddb7
--- /dev/null
+++ b/oox/qa/unit/data/WPC_Shadow.docx
Binary files differ
diff --git a/oox/qa/unit/data/WPC_Textwrap_in_ellipse.docx b/oox/qa/unit/data/WPC_Textwrap_in_ellipse.docx
new file mode 100644
index 000000000000..1729db3972ae
--- /dev/null
+++ b/oox/qa/unit/data/WPC_Textwrap_in_ellipse.docx
Binary files differ
diff --git a/oox/qa/unit/data/WPC_ThemeColor.docx b/oox/qa/unit/data/WPC_ThemeColor.docx
new file mode 100644
index 000000000000..20cc52f6fc04
--- /dev/null
+++ b/oox/qa/unit/data/WPC_ThemeColor.docx
Binary files differ
diff --git a/oox/qa/unit/data/WPC_tdf104671_Cloud.docx b/oox/qa/unit/data/WPC_tdf104671_Cloud.docx
new file mode 100644
index 000000000000..5cd1ca53b680
--- /dev/null
+++ b/oox/qa/unit/data/WPC_tdf104671_Cloud.docx
Binary files differ
diff --git a/oox/qa/unit/data/WPC_tdf48610_Textbox_with_table_inside.docx b/oox/qa/unit/data/WPC_tdf48610_Textbox_with_table_inside.docx
new file mode 100644
index 000000000000..353ba6aafc8a
--- /dev/null
+++ b/oox/qa/unit/data/WPC_tdf48610_Textbox_with_table_inside.docx
Binary files differ
diff --git a/oox/qa/unit/wpc_drawing_canvas.cxx b/oox/qa/unit/wpc_drawing_canvas.cxx
new file mode 100644
index 000000000000..7e1af1273d1e
--- /dev/null
+++ b/oox/qa/unit/wpc_drawing_canvas.cxx
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <test/unoapixml_test.hxx>
+
+#include <docmodel/color/ComplexColor.hxx>
+#include <docmodel/uno/UnoComplexColor.hxx>
+#include <editeng/unoprnms.hxx>
+
+#include <com/sun/star/awt/Gradient2.hpp>
+#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/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/text/XTextTablesSupplier.hpp>
+#include <com/sun/star/text/XTextDocument.hpp>
+#include <com/sun/star/util/XComplexColor.hpp>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// The test suite covers tests for import of Word drawing canvas (wpc), available since LO 24.2.
+/// Before its implementation the VML fallback was used. That lost properties because VML is not able
+/// to describe them or the VML import of LO has deficits.
+class TestWPC : public UnoApiXmlTest
+{
+public:
+ TestWPC()
+ : UnoApiXmlTest("/oox/qa/unit/data/")
+ {
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(TestWPC, WPC_Table_inside_Textbox)
+{
+ // The document has a table inside a text box on a drawing canvas.
+ loadFromURL(u"WPC_tdf48610_Textbox_with_table_inside.docx");
+
+ // Make sure the table exists. Without import of drawing canvas, the table was lost.
+ uno::Reference<text::XTextTablesSupplier> xTextDocument(mxComponent, uno::UNO_QUERY);
+ uno::Reference<container::XIndexAccess> xTables(xTextDocument->getTextTables(), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xTables->getCount());
+}
+
+CPPUNIT_TEST_FIXTURE(TestWPC, WPC_Text_in_ellipse)
+{
+ // The document has text in an ellipse on a drawing canvas.
+ loadFromURL(u"WPC_Textwrap_in_ellipse.docx");
+
+ // The VML import creates for an ellipse not a custom shape but a legacy ellipse and that has no
+ // word wrap. Thus the text was in one line and overflows the shape. This overflow becomes visible
+ // in the bounding box. Without fix the rectangle width was 9398 Hmm.
+ 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);
+ // getByIndex(0) gives the background shape, the ellipse is at index 1
+ uno::Reference<beans::XPropertySet> xShapeProps(xGroup->getByIndex(1), uno::UNO_QUERY);
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ // The tolerance 10 is estimated and can be adjusted if required for HiDPI.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(4740, aBoundRect.Width, 10);
+}
+
+CPPUNIT_TEST_FIXTURE(TestWPC, WPC_MulticolorGradient)
+{
+ // The document has a shape with multi color gradient fill on a drawing canvas.
+ loadFromURL(u"WPC_MulticolorGradient.docx");
+
+ // The VML import was not able to import multicolor gradients. Thus only start and end color
+ // were imported, ColorStops had only two elements.
+ 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<beans::XPropertySet> xShapeProps(xGroup->getByIndex(1), uno::UNO_QUERY);
+ awt::Gradient2 aGradient;
+ xShapeProps->getPropertyValue(UNO_NAME_FILLGRADIENT) >>= aGradient;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(4), aGradient.ColorStops.getLength());
+}
+
+CPPUNIT_TEST_FIXTURE(TestWPC, WPC_CanvasBackground)
+{
+ // The document has a drawing canvas with color fill.
+ loadFromURL(u"WPC_CanvasBackground.docx");
+
+ // The VML import displayed the background as if it was transparent. Thus the BoundRect
+ // of the shape which represents the background was zero.
+ 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<beans::XPropertySet> xShapeProps(xGroup->getByIndex(0), uno::UNO_QUERY);
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ CPPUNIT_ASSERT(aBoundRect.Width > 0);
+ CPPUNIT_ASSERT(aBoundRect.Height > 0);
+}
+
+CPPUNIT_TEST_FIXTURE(TestWPC, WPC_Glow)
+{
+ // The document has a shape with glow effect.
+ loadFromURL(u"WPC_Glow.docx");
+
+ // VML does not know any glow effect. Thus it was lost on import.
+ 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<beans::XPropertySet> xShapeProps(xGroup->getByIndex(1), uno::UNO_QUERY);
+
+ // Check glow properties
+ sal_Int32 nGlowEffectRad = 0;
+ xShapeProps->getPropertyValue(u"GlowEffectRadius"_ustr) >>= nGlowEffectRad;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(564), nGlowEffectRad); // 16 pt = 564.444... mm/100
+ Color nGlowEffectColor;
+ xShapeProps->getPropertyValue(u"GlowEffectColor"_ustr) >>= nGlowEffectColor;
+ CPPUNIT_ASSERT_EQUAL(Color(0xFFFF00), nGlowEffectColor); // "Yellow"
+ sal_Int16 nGlowEffectTransparency = 0;
+ xShapeProps->getPropertyValue(u"GlowEffectTransparency"_ustr) >>= nGlowEffectTransparency;
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(10), nGlowEffectTransparency); // 10%
+}
+
+CPPUNIT_TEST_FIXTURE(TestWPC, WPC_BentConnector)
+{
+ // The document has two shapes connected with a bentConnecor on a drawing canvas.
+ loadFromURL(u"WPC_BentConnector.docx");
+
+ // VML has no information about the target shapes of the connector. The connector was imported as
+ // custom shape, not as connector shape
+ 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(2), uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xInfo->supportsService("com.sun.star.drawing.ConnectorShape"));
+
+ uno::Reference<beans::XPropertySet> xShapeProps(xGroup->getByIndex(2), uno::UNO_QUERY);
+ com::sun::star::drawing::ConnectorType eEdgeKind;
+ xShapeProps->getPropertyValue(UNO_NAME_EDGEKIND) >>= eEdgeKind;
+ CPPUNIT_ASSERT_EQUAL(drawing::ConnectorType::ConnectorType_STANDARD, eEdgeKind);
+
+ sal_Int32 nEdgeLineDelta;
+ xShapeProps->getPropertyValue(UNO_NAME_EDGELINE1DELTA) >>= nEdgeLineDelta;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(-635), nEdgeLineDelta);
+ xShapeProps->getPropertyValue(UNO_NAME_EDGELINE2DELTA) >>= nEdgeLineDelta;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1949), nEdgeLineDelta);
+ xShapeProps->getPropertyValue(UNO_NAME_EDGELINE3DELTA) >>= nEdgeLineDelta;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(887), nEdgeLineDelta);
+}
+
+CPPUNIT_TEST_FIXTURE(TestWPC, WPC_ThemeColor)
+{
+ // The document has a shape with color fill used as pseudo background and a 'heart' shape with
+ // color fill and colored line. All colors are theme colors.
+ loadFromURL(u"WPC_ThemeColor.docx");
+
+ // VML has no information about theme colors. Thus ThemeColorType was always 'Unknown'.
+ 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);
+
+ // Check color of shape used for pseudo background
+ {
+ uno::Reference<beans::XPropertySet> xShapeProps(xGroup->getByIndex(0), uno::UNO_QUERY);
+ uno::Reference<util::XComplexColor> xComplexColor;
+ CPPUNIT_ASSERT(xShapeProps->getPropertyValue(UNO_NAME_FILL_COMPLEX_COLOR)
+ >>= xComplexColor);
+ CPPUNIT_ASSERT(xComplexColor.is());
+ auto aComplexColor = model::color::getFromXComplexColor(xComplexColor);
+ CPPUNIT_ASSERT_EQUAL(model::ThemeColorType::Dark2, aComplexColor.getThemeColorType());
+ {
+ auto const& rTrans = aComplexColor.getTransformations();
+ CPPUNIT_ASSERT_EQUAL(size_t(2), rTrans.size());
+ CPPUNIT_ASSERT_EQUAL(model::TransformationType::LumMod, rTrans[0].meType);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(7500), rTrans[1].mnValue);
+ CPPUNIT_ASSERT_EQUAL(model::TransformationType::LumOff, rTrans[1].meType);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(2500), rTrans[0].mnValue);
+ }
+ }
+ // Check colors of 'heart' shape
+ {
+ uno::Reference<beans::XPropertySet> xShapeProps(xGroup->getByIndex(1), uno::UNO_QUERY);
+ uno::Reference<util::XComplexColor> xComplexColor;
+ CPPUNIT_ASSERT(xShapeProps->getPropertyValue(UNO_NAME_FILL_COMPLEX_COLOR)
+ >>= xComplexColor);
+ CPPUNIT_ASSERT(xComplexColor.is());
+ auto aComplexColor = model::color::getFromXComplexColor(xComplexColor);
+ CPPUNIT_ASSERT_EQUAL(model::ThemeColorType::Accent5, aComplexColor.getThemeColorType());
+ }
+ {
+ uno::Reference<beans::XPropertySet> xShapeProps(xGroup->getByIndex(1), uno::UNO_QUERY);
+ uno::Reference<util::XComplexColor> xComplexColor;
+ CPPUNIT_ASSERT(xShapeProps->getPropertyValue(UNO_NAME_LINE_COMPLEX_COLOR)
+ >>= xComplexColor);
+ CPPUNIT_ASSERT(xComplexColor.is());
+ auto aComplexColor = model::color::getFromXComplexColor(xComplexColor);
+ CPPUNIT_ASSERT_EQUAL(model::ThemeColorType::Accent4, aComplexColor.getThemeColorType());
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(TestWPC, WPC_tdf104671_Cloud)
+{
+ // The document has 'cloud' shape on a drawing canvas.
+ loadFromURL(u"WPC_tdf104671_Cloud.docx");
+
+ // MS Office writes the 'cloud' shape without type to the VML fallback. Thus the VLM import uses
+ // ClosedBezierShape with several closed polygons. That produces holes because of the even-odd
+ // rule, and inner lines. The fix uses the mc:Choice alternative which provides the type for a
+ // custom shape.
+ 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(1), uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xInfo->supportsService("com.sun.star.drawing.CustomShape"));
+}
+
+CPPUNIT_TEST_FIXTURE(TestWPC, WPC_Shadow)
+{
+ // The document has a shape with blur shadow on a drawing canvas.
+ loadFromURL(u"WPC_Shadow.docx");
+
+ // The VML fallback contains a block shadow. Blur is not available in VML. The VML import does not
+ // import shadow at all.
+ 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<beans::XPropertySet> xShapeProps(xGroup->getByIndex(1), uno::UNO_QUERY);
+ bool bHasShadow = false;
+ xShapeProps->getPropertyValue(UNO_NAME_SHADOW) >>= bHasShadow;
+ CPPUNIT_ASSERT(bHasShadow);
+ sal_Int32 nValue;
+ xShapeProps->getPropertyValue(UNO_NAME_SHADOWBLUR) >>= nValue;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(282), nValue);
+ xShapeProps->getPropertyValue(UNO_NAME_SHADOWXDIST) >>= nValue;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(224), nValue);
+ xShapeProps->getPropertyValue(UNO_NAME_SHADOWYDIST) >>= nValue;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(224), nValue);
+ Color nColor;
+ xShapeProps->getPropertyValue(UNO_NAME_SHADOWCOLOR) >>= nColor;
+ CPPUNIT_ASSERT_EQUAL(Color(0x808080), nColor);
+}
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/drawingml/connectorhelper.cxx b/oox/source/drawingml/connectorhelper.cxx
new file mode 100644
index 000000000000..82bd92a568b1
--- /dev/null
+++ b/oox/source/drawingml/connectorhelper.cxx
@@ -0,0 +1,368 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <drawingml/connectorhelper.hxx>
+
+#include <sal/config.h>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XIdentifierContainer.hpp>
+#include <com/sun/star/drawing/XGluePointsSupplier.hpp>
+
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <drawingml/customshapeproperties.hxx>
+#include <oox/drawingml/drawingmltypes.hxx>
+#include <oox/drawingml/shape.hxx>
+#include <rtl/ustring.hxx>
+#include <svl/itempool.hxx>
+#include <svx/svdmodel.hxx>
+#include <svx/svdoedge.hxx>
+#include <svx/svdobj.hxx>
+#include <tools/mapunit.hxx>
+#include <tools/UnitConversion.hxx>
+
+#include <map>
+#include <set>
+#include <string_view>
+#include <vector>
+
+using namespace ::com::sun::star;
+
+OUString ConnectorHelper::getShapePresetTypeNameOUString(
+ const oox::drawingml::CustomShapePropertiesPtr& pCustomShapePropertiesPtr)
+{
+ return rtl::OUString(reinterpret_cast<const char*>(
+ pCustomShapePropertiesPtr->getShapePresetTypeName().getConstArray()),
+ pCustomShapePropertiesPtr->getShapePresetTypeName().getLength(),
+ RTL_TEXTENCODING_UTF8);
+}
+
+// These shapes have no gluepoints defined in their mso_CustomShape struct, thus the gluepoint
+// adaption to default gluepoints will be done. Other shapes having no gluepoint defined in the
+// mso_CustomShape struct, have gluepoints in order top-left-bottom-right in OOXML. But the shapes
+// below have order right-bottom-left-top. Adding gluepoints to mso_CustomShape structs does not
+// solve the problem because MS binary gluepoints and OOXML gluepoints are different.
+
+bool ConnectorHelper::hasClockwiseCxn(const OUString& rShapeType)
+{
+ static const std::set<OUString> aWithClockwiseCxnSet({ u"accentBorderCallout1"_ustr,
+ u"accentBorderCallout2"_ustr,
+ u"accentBorderCallout3"_ustr,
+ u"accentCallout1"_ustr,
+ u"accentCallout2"_ustr,
+ u"accentCallout3"_ustr,
+ u"actionButtonBackPrevious"_ustr,
+ u"actionButtonBeginning"_ustr,
+ u"actionButtonBlank"_ustr,
+ u"actionButtonDocument"_ustr,
+ u"actionButtonEnd"_ustr,
+ u"actionButtonForwardNext"_ustr,
+ u"actionButtonHelp"_ustr,
+ u"actionButtonHome"_ustr,
+ u"actionButtonInformation"_ustr,
+ u"actionButtonMovie"_ustr,
+ u"actionButtonReturn"_ustr,
+ u"actionButtonSound"_ustr,
+ u"borderCallout1"_ustr,
+ u"borderCallout2"_ustr,
+ u"borderCallout3"_ustr,
+ u"callout1"_ustr,
+ u"callout2"_ustr,
+ u"callout3"_ustr,
+ u"cloud"_ustr,
+ u"corner"_ustr,
+ u"diagStripe"_ustr,
+ u"flowChartOfflineStorage"_ustr,
+ u"halfFrame"_ustr,
+ u"mathDivide"_ustr,
+ u"mathMinus"_ustr,
+ u"mathPlus"_ustr,
+ u"nonIsoscelesTrapezoid"_ustr,
+ u"pie"_ustr,
+ u"round2DiagRect"_ustr,
+ u"round2SameRect"_ustr,
+ u"snip1Rect"_ustr,
+ u"snip2DiagRect"_ustr,
+ u"snip2SameRect"_ustr,
+ u"snipRoundRect"_ustr });
+ return aWithClockwiseCxnSet.contains(rShapeType);
+}
+
+basegfx::B2DHomMatrix
+ConnectorHelper::getConnectorTransformMatrix(const oox::drawingml::ShapePtr& pConnector)
+{
+ basegfx::B2DHomMatrix aTransform; // ctor generates unit matrix
+ if (!pConnector)
+ return aTransform;
+ if (pConnector->getFlipH())
+ aTransform.scale(-1.0, 1.0);
+ if (pConnector->getFlipV())
+ aTransform.scale(1.0, -1.0);
+ if (pConnector->getRotation() == 0)
+ return aTransform;
+ if (pConnector->getRotation() == 5400000)
+ aTransform *= basegfx::B2DHomMatrix(0, -1, 0, 1, 0, 0);
+ else if (pConnector->getRotation() == 10800000)
+ aTransform *= basegfx::B2DHomMatrix(-1, 0, 0, 0, -1, 0);
+ else if (pConnector->getRotation() == 16200000)
+ aTransform *= basegfx::B2DHomMatrix(0, 1, 0, -1, 0, 0);
+ else
+ SAL_WARN("oox", "tdf#157888 LibreOffice cannot handle such connector rotation");
+ return aTransform;
+}
+
+void ConnectorHelper::getOOXHandlePositionsHmm(const oox::drawingml::ShapePtr& pConnector,
+ std::vector<basegfx::B2DPoint>& rHandlePositions)
+{
+ rHandlePositions.clear();
+
+ if (!pConnector)
+ return;
+
+ if (pConnector->getConnectorName() == u"bentConnector2"_ustr
+ || pConnector->getConnectorName() == u"curvedConnector2"_ustr)
+ return; // These have no handles.
+
+ // Convert string attribute to number. Set default 50000 if missing.
+ std::vector<sal_Int32> aAdjustmentOOXVec; // 1/100000 of shape size
+ for (size_t i = 0; i < 3; i++)
+ {
+ if (i < pConnector->getConnectorAdjustments().size())
+ aAdjustmentOOXVec.push_back(pConnector->getConnectorAdjustments()[i].toInt32());
+ else
+ aAdjustmentOOXVec.push_back(50000);
+ }
+
+ // Handle positions depend on EdgeKind and ShapeSize. bendConnector and curvedConnector use the
+ // same handle positions. The formulas here correspond to guides in the bendConnector in
+ // presetShapeDefinitions.xml.
+ const double fWidth = pConnector->getSize().Width; // EMU
+ const double fHeight = pConnector->getSize().Height; // EMU
+ const double fPosX = pConnector->getPosition().X; // EMU
+ const double fPosY = pConnector->getPosition().Y; // EMU
+
+ if (pConnector->getConnectorName() == u"bentConnector3"_ustr
+ || pConnector->getConnectorName() == u"curvedConnector3"_ustr)
+ {
+ double fAdj1 = aAdjustmentOOXVec[0];
+ double fX1 = fAdj1 / 100000.0 * fWidth;
+ double fY1 = fHeight / 2.0;
+ rHandlePositions.push_back({ fX1, fY1 });
+ }
+ else if (pConnector->getConnectorName() == u"bentConnector4"_ustr
+ || pConnector->getConnectorName() == u"curvedConnector4"_ustr)
+ {
+ double fAdj1 = aAdjustmentOOXVec[0];
+ double fAdj2 = aAdjustmentOOXVec[1];
+ double fX1 = fAdj1 / 100000.0 * fWidth;
+ double fX2 = (fX1 + fWidth) / 2.0;
+ double fY2 = fAdj2 / 100000.0 * fHeight;
+ double fY1 = fY2 / 2.0;
+ rHandlePositions.push_back({ fX1, fY1 });
+ rHandlePositions.push_back({ fX2, fY2 });
+ }
+ else if (pConnector->getConnectorName() == u"bentConnector5"_ustr
+ || pConnector->getConnectorName() == u"curvedConnector5"_ustr)
+ {
+ double fAdj1 = aAdjustmentOOXVec[0];
+ double fAdj2 = aAdjustmentOOXVec[1];
+ double fAdj3 = aAdjustmentOOXVec[2];
+ double fX1 = fAdj1 / 100000.0 * fWidth;
+ double fX3 = fAdj3 / 100000.0 * fWidth;
+ double fX2 = (fX1 + fX3) / 2.0;
+ double fY2 = fAdj2 / 100000.0 * fHeight;
+ double fY1 = fY2 / 2.0;
+ double fY3 = (fHeight + fY2) / 2.0;
+ rHandlePositions.push_back({ fX1, fY1 });
+ rHandlePositions.push_back({ fX2, fY2 });
+ rHandlePositions.push_back({ fX3, fY3 });
+ }
+
+ // The presetGeometry has the first segment horizontal and start point left/top with
+ // coordinates (0|0). Other layouts are done by flipping and rotating.
+ basegfx::B2DHomMatrix aTransform;
+ const basegfx::B2DPoint aB2DCenter(fWidth / 2.0, fHeight / 2.0);
+ aTransform.translate(-aB2DCenter);
+ aTransform *= getConnectorTransformMatrix(pConnector);
+ aTransform.translate(aB2DCenter);
+
+ // Make coordinates absolute
+ aTransform.translate(fPosX, fPosY);
+
+ // Actually transform the handle coordinates
+ for (auto& rElem : rHandlePositions)
+ rElem *= aTransform;
+
+ // Convert EMU -> Hmm
+ for (auto& rElem : rHandlePositions)
+ rElem /= 360.0;
+}
+
+void ConnectorHelper::getLOBentHandlePositionsHmm(const oox::drawingml::ShapePtr& pConnector,
+ std::vector<basegfx::B2DPoint>& rHandlePositions)
+{
+ // This method is intended for Edgekind css::drawing::ConnectorType_STANDARD. Those connectors
+ // correspond to OOX bentConnector, aka "ElbowConnector".
+ 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() < 4 || 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));
+ }
+
+ // LO has the handle in the middle of a segment, but not for first and last segment.
+ for (sal_uInt32 i = 1; i < aEdgePolygon.count() - 2; i++)
+ {
+ const basegfx::B2DPoint aBeforePt(aEdgePolygon.getB2DPoint(i));
+ const basegfx::B2DPoint aAfterPt(aEdgePolygon.getB2DPoint(i + 1));
+ rHandlePositions.push_back((aBeforePt + aAfterPt) / 2.0);
+ }
+}
+
+// This is similar to SlidePersist::createConnectorShapeConnection()
+void ConnectorHelper::applyConnections(oox::drawingml::ShapePtr& rConnector,
+ oox::drawingml::ShapeIdMap& rShapeMap)
+{
+ uno::Reference<drawing::XShape> xConnector(rConnector->getXShape());
+ if (!xConnector.is())
+ return;
+ uno::Reference<beans::XPropertySet> xPropSet(xConnector, uno::UNO_QUERY);
+ if (!xPropSet.is())
+ return;
+
+ // MS Office allows route between shapes with small distance. LO default is 5mm.
+ xPropSet->setPropertyValue("EdgeNode1HorzDist", uno::Any(sal_Int32(0)));
+ xPropSet->setPropertyValue("EdgeNode1VertDist", uno::Any(sal_Int32(0)));
+ xPropSet->setPropertyValue("EdgeNode2HorzDist", uno::Any(sal_Int32(0)));
+ xPropSet->setPropertyValue("EdgeNode2VertDist", uno::Any(sal_Int32(0)));
+
+ oox::drawingml::ConnectorShapePropertiesList aConnectorShapeProperties
+ = rConnector->getConnectorShapeProperties();
+ // It contains maximal two items, each a struct with mbStartShape, maDestShapeId, mnDestGlueId
+ for (const auto& aIt : aConnectorShapeProperties)
+ {
+ const auto& pItem = rShapeMap.find(aIt.maDestShapeId);
+ if (pItem == rShapeMap.end())
+ continue;
+
+ uno::Reference<drawing::XShape> xShape(pItem->second->getXShape(), uno::UNO_QUERY);
+ if (xShape.is())
+ {
+ // Connect to the found shape.
+ if (aIt.mbStartShape)
+ xPropSet->setPropertyValue("StartShape", uno::Any(xShape));
+ else
+ xPropSet->setPropertyValue("EndShape", uno::Any(xShape));
+
+ // The first four glue points are the default glue points, which are set by LibreOffice.
+ // They do not belong to the preset geometry of the shape.
+ // Adapt gluepoint index to LibreOffice
+ uno::Reference<drawing::XGluePointsSupplier> xSupplier(xShape, uno::UNO_QUERY);
+ css::uno::Reference<css::container::XIdentifierContainer> xGluePoints(
+ xSupplier->getGluePoints(), uno::UNO_QUERY);
+ sal_Int32 nCountGluePoints = xGluePoints->getIdentifiers().getLength();
+ sal_Int32 nGlueId = aIt.mnDestGlueId;
+
+ if (nCountGluePoints > 4)
+ nGlueId += 4;
+ else
+ {
+ // In these cases the mso_CustomShape struct defines no gluepoints (Why not?), thus
+ // our default gluepoints are used. The order of the default gluepoints might differ
+ // from the order of the OOXML gluepoints. We try to change nGlueId so, that the
+ // connector attaches to a default gluepoint at the same side as it attaches in OOXML.
+ const OUString sShapeType = ConnectorHelper::getShapePresetTypeNameOUString(
+ pItem->second->getCustomShapeProperties());
+ if (ConnectorHelper::hasClockwiseCxn(sShapeType))
+ nGlueId = (nGlueId + 1) % 4;
+ else
+ {
+ bool bFlipH = pItem->second->getFlipH();
+ bool bFlipV = pItem->second->getFlipV();
+ if (bFlipH == bFlipV)
+ {
+ // change id of the left and right glue points of the bounding box (1 <-> 3)
+ if (nGlueId == 1)
+ nGlueId = 3; // Right
+ else if (nGlueId == 3)
+ nGlueId = 1; // Left
+ }
+ }
+ }
+
+ if (aIt.mbStartShape)
+ xPropSet->setPropertyValue("StartGluePointIndex", uno::Any(nGlueId));
+ else
+ xPropSet->setPropertyValue("EndGluePointIndex", uno::Any(nGlueId));
+ }
+ }
+}
+
+void ConnectorHelper::applyBentHandleAdjustments(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::getLOBentHandlePositionsHmm(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/drawingml/connectorshapecontext.cxx b/oox/source/drawingml/connectorshapecontext.cxx
index 018ca95c648d..18820410dd17 100644
--- a/oox/source/drawingml/connectorshapecontext.cxx
+++ b/oox/source/drawingml/connectorshapecontext.cxx
@@ -36,23 +36,6 @@ using namespace ::com::sun::star::xml::sax;
namespace oox::drawingml {
-namespace
-{
-class ConnectorShapePropertiesContext : public ::oox::core::ContextHandler2
-{
- std::vector<ConnectorShapeProperties>& mrConnectorShapePropertiesList;
- ShapePtr mpConnectorShapePtr;
-
-public:
- ConnectorShapePropertiesContext(
- ::oox::core::ContextHandler2Helper const& rParent, ShapePtr& pShapePtr,
- std::vector<ConnectorShapeProperties>& rConnectorShapePropertiesList);
-
- virtual ::oox::core::ContextHandlerRef onCreateContext(sal_Int32 aElementToken,
- const AttributeList& rAttribs) override;
-};
-}
-
ConnectorShapePropertiesContext::ConnectorShapePropertiesContext(
ContextHandler2Helper const& rParent, ShapePtr& pShapePtr,
std::vector<ConnectorShapeProperties>& rConnectorShapePropertiesList)
diff --git a/oox/source/drawingml/shape.cxx b/oox/source/drawingml/shape.cxx
index c38da7787ec3..01798c344631 100644
--- a/oox/source/drawingml/shape.cxx
+++ b/oox/source/drawingml/shape.cxx
@@ -147,6 +147,7 @@ Shape::Shape( const char* pServiceName, bool bDefaultHeight )
, mbLocked( false )
, mbWPGChild(false)
, mbLockedCanvas( false )
+, mbWordprocessingCanvas(false)
, mbWps( false )
, mbTextBox( false )
, mbHasLinkedTxbx( false )
@@ -191,6 +192,7 @@ Shape::Shape( const ShapePtr& pSourceShape )
, mbLocked( pSourceShape->mbLocked )
, mbWPGChild( pSourceShape->mbWPGChild )
, mbLockedCanvas( pSourceShape->mbLockedCanvas )
+, mbWordprocessingCanvas(pSourceShape->mbWordprocessingCanvas)
, mbWps( pSourceShape->mbWps )
, mbTextBox( pSourceShape->mbTextBox )
, mbHasLinkedTxbx(false)
@@ -374,6 +376,33 @@ void Shape::addShape(
if ( xShapes.is() )
addChildren( rFilterBase, *this, pTheme, xShapes, pShapeMap, aMatrix );
+ if (mbWordprocessingCanvas && !mbWPGChild)
+ {
+ // This is a drawing canvas. In case the canvas has no fill and no stroke, Word does
+ // not render shadow or glow, even if it is set for the canvas. Thus we disable shadow
+ // and glow in this case for the ersatz background shape of the drawing canvas.
+ try
+ {
+ oox::drawingml::ShapePtr pBgShape = getChildren().front();
+ const Reference<css::drawing::XShape>& xBgShape = pBgShape->getXShape();
+ Reference<XPropertySet> xBgProps(xBgShape, uno::UNO_QUERY);
+ drawing::FillStyle eFillStyle = drawing::FillStyle_NONE;
+ xBgProps->getPropertyValue("FillStyle") >>= eFillStyle;
+ drawing::LineStyle eLineStyle = drawing::LineStyle_NONE;
+ xBgProps->getPropertyValue("LineStyle") >>= eLineStyle;
+ if (eFillStyle == drawing::FillStyle_NONE
+ && eLineStyle == drawing::LineStyle_NONE)
+ {
+ xBgProps->setPropertyValue(UNO_NAME_SHADOW, uno::Any(false));
+ xBgProps->setPropertyValue(u"GlowEffectRadius"_ustr, uno::Any(sal_Int32(0)));
+ }
+ }
+ catch (const Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("oox.drawingml", "Shape::addShape mbWordprocessingCanvas");
+ }
+ }
+
if (isWPGChild() && xShape)
{
// This is a wps shape and it is the child of the WPG, now copy the
@@ -464,6 +493,11 @@ void Shape::setLockedCanvas(bool bLockedCanvas)
mbLockedCanvas = bLockedCanvas;
}
+void Shape::setWordprocessingCanvas(bool bWordprocessingCanvas)
+{
+ mbWordprocessingCanvas = bWordprocessingCanvas;
+}
+
void Shape::setWPGChild(bool bWPG)
{
mbWPGChild = bWPG;
@@ -1567,6 +1601,11 @@ Reference< XShape > const & Shape::createAndInsert(
}
}
+ if (mbWordprocessingCanvas)
+ {
+ putPropertyToGrabBag("WordprocessingCanvas", Any(true));
+ }
+
// Store original fill and line colors of the shape and the theme color name to InteropGrabBag
std::vector<beans::PropertyValue> aProperties
{
@@ -1713,6 +1752,11 @@ Reference< XShape > const & Shape::createAndInsert(
//If we have aServiceName as "com.sun.star.drawing.GroupShape" and lockedCanvas
putPropertyToGrabBag( "LockedCanvas", Any( true ) );
}
+ else if (mbWordprocessingCanvas)
+ {
+ putPropertyToGrabBag("WordprocessingCanvas", Any(true));
+ putPropertyToGrabBag("mso-edit-as", Any(OUString("canvas"))); // for export VML Fallback
+ }
// These can have a custom geometry, so position should be set here,
// after creation but before custom shape handling, using the position
diff --git a/oox/source/drawingml/shapegroupcontext.cxx b/oox/source/drawingml/shapegroupcontext.cxx
index d810b0448b9a..ddd31520746a 100644
--- a/oox/source/drawingml/shapegroupcontext.cxx
+++ b/oox/source/drawingml/shapegroupcontext.cxx
@@ -94,6 +94,7 @@ ContextHandlerRef ShapeGroupContext::onCreateContext( sal_Int32 aElementToken, c
{
ShapePtr pShape = std::make_shared<Shape>("com.sun.star.drawing.ConnectorShape");
pShape->setLockedCanvas(mpGroupShapePtr->getLockedCanvas());
+ pShape->setWordprocessingCanvas(mpGroupShapePtr->isInWordprocessingCanvas());
return new ConnectorShapeContext(*this, mpGroupShapePtr, pShape,
pShape->getConnectorShapeProperties());
}
diff --git a/oox/source/shape/ShapeContextHandler.cxx b/oox/source/shape/ShapeContextHandler.cxx
index 7665022c56df..53ddd3f575be 100644
--- a/oox/source/shape/ShapeContextHandler.cxx
+++ b/oox/source/shape/ShapeContextHandler.cxx
@@ -18,12 +18,15 @@
*/
#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/XGluePointsSupplier.hpp>
+#include <com/sun/star/container/XIdentifierContainer.hpp>
#include <com/sun/star/xml/dom/XDocument.hpp>
#include <com/sun/star/xml/sax/XFastSAXSerializable.hpp>
#include <oox/shape/ShapeContextHandler.hxx>
#include <oox/shape/ShapeDrawingFragmentHandler.hxx>
#include "LockedCanvasContext.hxx"
+#include "WordprocessingCanvasContext.hxx"
#include "WpsContext.hxx"
#include "WpgContext.hxx"
#include <basegfx/matrix/b2dhommatrix.hxx>
@@ -35,6 +38,12 @@
#include <oox/token/tokens.hxx>
#include <oox/drawingml/theme.hxx>
#include <oox/drawingml/themefragmenthandler.hxx>
+#include <svx/svdogrp.hxx>
+#include <svx/svdoedge.hxx>
+#include <svx/svdobj.hxx>
+
+#include <drawingml/connectorhelper.hxx>
+
#include <memory>
#include <utility>
@@ -219,6 +228,25 @@ ShapeContextHandler::getDiagramShapeContext()
return mxDiagramShapeContext;
}
+uno::Reference<xml::sax::XFastContextHandler> ShapeContextHandler::getWordprocessingCanvasContext(sal_Int32 nElement)
+{
+ if (!mxWordprocessingCanvasContext.is())
+ {
+ FragmentHandler2Ref rFragmentHandler(new ShapeFragmentHandler(*mxShapeFilterBase, msRelationFragmentPath));
+
+ switch (getBaseToken(nElement))
+ {
+ case XML_wpc:
+ mxWordprocessingCanvasContext.set(new WordprocessingCanvasContext(*rFragmentHandler, maSize));
+ break;
+ default:
+ break;
+ }
+ }
+
+ return static_cast<ContextHandler *>(mxWordprocessingCanvasContext.get());
+}
+
uno::Reference<xml::sax::XFastContextHandler>
ShapeContextHandler::getContextHandler(sal_Int32 nElement)
{
@@ -246,6 +274,9 @@ ShapeContextHandler::getContextHandler(sal_Int32 nElement)
case NMSP_wpg:
xResult.set(getWpgContext(nStartToken));
break;
+ case NMSP_wpc:
+ xResult.set(getWordprocessingCanvasContext(nStartToken));
+ break;
default:
xResult.set(getGraphicShapeContext(nStartToken));
break;
@@ -262,7 +293,8 @@ void SAL_CALL ShapeContextHandler::startFastElement
mxShapeFilterBase->filter(maMediaDescriptor);
if (Element == DGM_TOKEN(relIds) || Element == LC_TOKEN(lockedCanvas) || Element == C_TOKEN(chart) ||
- Element == WPS_TOKEN(wsp) || Element == WPG_TOKEN(wgp) || Element == OOX_TOKEN(dmlPicture, pic))
+ Element == WPS_TOKEN(wsp) || Element == WPG_TOKEN(wgp) || Element == OOX_TOKEN(dmlPicture, pic)
+ || Element == WPC_TOKEN(wpc))
{
// Parse the theme relation, if available; the diagram won't have colors without it.
if (!mpThemePtr && !msRelationFragmentPath.isEmpty())
@@ -392,6 +424,23 @@ void SAL_CALL ShapeContextHandler::characters(const OUString & aChars)
xContextHandler->characters(aChars);
}
+namespace // helpers for case mxWordprocessingCanvasContext
+{
+ void lcl_createShapeMap(oox::drawingml::ShapePtr rShapePtr,
+ oox::drawingml::ShapeIdMap& rShapeMap)
+{
+ std::vector< ShapePtr >& rChildren = rShapePtr->getChildren();
+ if (rChildren.empty())
+ return;
+ for (auto& pIt : rChildren)
+ {
+ rShapeMap[pIt->getId()] = pIt; // add child itself
+ lcl_createShapeMap(pIt, rShapeMap); // and all its descendants
+ }
+}
+
+} // end anonymous namespace
+
uno::Reference< drawing::XShape >
ShapeContextHandler::getShape()
{
@@ -462,6 +511,43 @@ ShapeContextHandler::getShape()
mxLockedCanvasContext.clear();
}
}
+ else if (mxWordprocessingCanvasContext.is())
+ {
+ // group which represents the drawing canvas
+ ShapePtr pShape = mxWordprocessingCanvasContext->getShape();
+ if (pShape)
+ {
+ basegfx::B2DHomMatrix aMatrix;
+ pShape->addShape(*mxShapeFilterBase, mpThemePtr.get(), xShapes, aMatrix, pShape->getFillProperties());
+
+ // create a flat map of all shapes in the drawing canvas group.
+ oox::drawingml::ShapeIdMap aShapeMap;
+ lcl_createShapeMap(pShape, aShapeMap);
+
+ // Travers aShapeMap and generate edge related properties.
+ for (auto& rIt : aShapeMap)
+ {
+ if ((rIt.second)->getServiceName() == "com.sun.star.drawing.ConnectorShape")
+ {
+ ConnectorHelper::applyConnections(rIt.second, aShapeMap);
+
+ if (rIt.second->getConnectorName() == u"bentConnector3"_ustr
+ || rIt.second->getConnectorName() == u"bentConnector4"_ustr
+ || rIt.second->getConnectorName() == u"bentConnector5"_ustr)
+ {
+ ConnectorHelper::applyBentHandleAdjustments(rIt.second);
+ }
+ // else use the default path of LibreOffice
+ // curveConnecto2 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.
+ }
+ }
+ xResult = pShape->getXShape();
+ mxWordprocessingCanvasContext.clear();
+ }
+ }
//NMSP_dmlChart == getNamespace( mnStartToken ) check is introduced to make sure that
//mnStartToken is set as NMSP_dmlChart in setStartToken.
//Only in case it is set then only the below block of code for ChartShapeContext should be executed.
diff --git a/oox/source/shape/WordprocessingCanvasContext.cxx b/oox/source/shape/WordprocessingCanvasContext.cxx
new file mode 100644
index 000000000000..7273a8d23ea6
--- /dev/null
+++ b/oox/source/shape/WordprocessingCanvasContext.cxx
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "WordprocessingCanvasContext.hxx"
+#include "WpsContext.hxx"
+#include "WpgContext.hxx"
+#include <drawingml/customshapeproperties.hxx>
+#include <drawingml/effectpropertiescontext.hxx>
+#include <drawingml/fillproperties.hxx>
+#include <drawingml/shapepropertiescontext.hxx>
+#include <oox/drawingml/connectorshapecontext.hxx>
+#include <oox/drawingml/drawingmltypes.hxx>
+#include <oox/drawingml/graphicshapecontext.hxx>
+#include <oox/drawingml/shape.hxx>
+#include <oox/drawingml/shapecontext.hxx>
+#include <oox/drawingml/shapegroupcontext.hxx>
+#include <oox/helper/attributelist.hxx>
+#include <oox/token/namespaces.hxx>
+#include <oox/token/tokens.hxx>
+#include <sal/log.hxx>
+#include <svx/svdoedge.hxx>
+#include <svx/svdobj.hxx>
+
+using namespace com::sun::star;
+
+namespace oox::shape
+{
+WordprocessingCanvasContext::WordprocessingCanvasContext(FragmentHandler2 const& rParent,
+ css::awt::Size& rSize)
+ : FragmentHandler2(rParent)
+ , m_bFullWPGSupport(true)
+{
+ mpShapePtr = std::make_shared<oox::drawingml::Shape>("com.sun.star.drawing.GroupShape");
+ mpShapePtr->setSize(rSize);
+ mpShapePtr->setWordprocessingCanvas(true); // will be "WordprocessingCanvas" in InteropGrabBag
+ mpShapePtr->setWps(true);
+ oox::drawingml::ShapePtr pBackground
+ = std::make_shared<oox::drawingml::Shape>("com.sun.star.drawing.CustomShape");
+ pBackground->getCustomShapeProperties()->setShapePresetType(XML_rect);
+ pBackground->setSize(rSize);
+ pBackground->setWordprocessingCanvas(true);
+ pBackground->setWPGChild(true);
+ pBackground->setWps(true);
+ // Fill and Line properties will follow in wpc:bg and wpc:whole child elements of wpc element
+ mpShapePtr->addChild(pBackground);
+ mpShapePtr->setChildSize(rSize);
+}
+
+WordprocessingCanvasContext::~WordprocessingCanvasContext() = default;
+
+::oox::core::ContextHandlerRef
+WordprocessingCanvasContext::onCreateContext(sal_Int32 nElementToken,
+ const ::oox::AttributeList& /*rAttribs*/)
+{
+ switch (getBaseToken(nElementToken))
+ {
+ case XML_wpc:
+ SAL_INFO("oox", "WordprocessingCanvasContext::createFastChildContext: wpc: "
+ << getBaseToken(nElementToken));
+ break;
+ case XML_bg: //CT_BackgroundFormatting
+ return new oox::drawingml::ShapePropertiesContext(*this,
+ *(getShape()->getChildren().front()));
+ case XML_whole: // CT_WholeE2oFormatting
+ return new oox::drawingml::ShapePropertiesContext(*this,
+ *(getShape()->getChildren().front()));
+ case XML_wsp: // CT_WordprocessingShape
+ {
+ oox::drawingml::ShapePtr pShape = std::make_shared<oox::drawingml::Shape>(
+ "com.sun.star.drawing.CustomShape", /*bDefaultHeight=*/false);
+ return new oox::shape::WpsContext(*this, uno::Reference<drawing::XShape>(), mpShapePtr,
+ pShape);
+ }
+ case XML_pic: // CT_Picture
+ return new oox::drawingml::GraphicShapeContext(
+ *this, mpShapePtr,
+ std::make_shared<oox::drawingml::Shape>("com.sun.star.drawing.GraphicObjectShape"));
+ break;
+ case XML_graphicFrame: // CT_GraphicFrame
+ SAL_INFO("oox",
+ "WordprocessingCanvasContext::createFastChildContext: ToDo: graphicFrame: "
+ << getBaseToken(nElementToken));
+ break;
+ case XML_wgp: // CT_WordprocessingGroup
+ return new oox::shape::WpgContext(*this, mpShapePtr);
+ default:
+ // includes case XML_contentPart
+ // Word uses this for Ink, as <w14:contentPart r:id="rId4"> for example. Thereby rId4 is
+ // a reference into the 'ink' folder in the docx package. Import of Ink is not
+ // implemented yet. In general it refers to arbitrary XML source.
+ SAL_WARN("oox",
+ "WordprocessingCanvasContext::createFastChildContext: unhandled element:"
+ << getBaseToken(nElementToken));
+ break;
+ }
+ return nullptr;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/shape/WordprocessingCanvasContext.hxx b/oox/source/shape/WordprocessingCanvasContext.hxx
new file mode 100644
index 000000000000..dbb2148967e8
--- /dev/null
+++ b/oox/source/shape/WordprocessingCanvasContext.hxx
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <oox/core/fragmenthandler2.hxx>
+#include <oox/drawingml/drawingmltypes.hxx>
+
+namespace oox::shape
+{
+class WordprocessingCanvasContext final : public oox::core::FragmentHandler2
+{
+public:
+ // mpShapePtr points to the root of the group. rSize is the size of the background shape.
+ explicit WordprocessingCanvasContext(oox::core::FragmentHandler2 const& rParent,
+ css::awt::Size& rSize);
+ ~WordprocessingCanvasContext() override;
+
+ oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElementToken,
+ const ::oox::AttributeList& rAttribs) override;
+
+ oox::drawingml::ShapePtr getShape() { return mpShapePtr; }
+ const bool& isFullWPGSupport() const { return m_bFullWPGSupport; };
+ void setFullWPGSupport(bool bUse) { m_bFullWPGSupport = bUse; };
+
+private:
+ oox::drawingml::ShapePtr mpShapePtr;
+ bool m_bFullWPGSupport;
+};
+} // end namespace oox::shape
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/shape/WpsContext.cxx b/oox/source/shape/WpsContext.cxx
index 1861f5aef7c3..fae704856371 100644
--- a/oox/source/shape/WpsContext.cxx
+++ b/oox/source/shape/WpsContext.cxx
@@ -9,6 +9,7 @@
#include "WpsContext.hxx"
#include "WpgContext.hxx"
+#include "WordprocessingCanvasContext.hxx"
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/tuple/b2dtuple.hxx>
#include <comphelper/propertyvalue.hxx>
@@ -20,6 +21,7 @@
#include <drawingml/textbody.hxx>
#include <drawingml/textbodyproperties.hxx>
#include <oox/drawingml/color.hxx>
+#include <oox/drawingml/connectorshapecontext.hxx>
#include <oox/drawingml/drawingmltypes.hxx>
#include <oox/drawingml/shape.hxx>
#include <oox/drawingml/shapepropertymap.hxx>
@@ -555,8 +557,14 @@ WpsContext::WpsContext(ContextHandler2Helper const& rParent, uno::Reference<draw
if (const auto pParent = dynamic_cast<const WpgContext*>(&rParent))
m_bHasWPGParent = pParent->isFullWPGSupport();
+ else if (dynamic_cast<const WordprocessingCanvasContext*>(&rParent))
+ m_bHasWPGParent = true;
else
m_bHasWPGParent = false;
+
+ if ((pMasterShapePtr && pMasterShapePtr->isInWordprocessingCanvas())
+ || dynamic_cast<const WordprocessingCanvasContext*>(&rParent) != nullptr)
+ pShapePtr->setWordprocessingCanvas(true);
}
WpsContext::~WpsContext() = default;
@@ -567,8 +575,30 @@ oox::core::ContextHandlerRef WpsContext::onCreateContext(sal_Int32 nElementToken
switch (getBaseToken(nElementToken))
{
case XML_wsp:
- case XML_cNvCnPr:
break;
+ case XML_cNvCnPr:
+ {
+ // It might be a connector shape in a wordprocessing canvas
+ // Replace the custom shape with a connector shape.
+ if (!mpShapePtr || !mpShapePtr->isInWordprocessingCanvas() || !mpMasterShapePtr)
+ break;
+ // Generate new shape
+ oox::drawingml::ShapePtr pShape = std::make_shared<oox::drawingml::Shape>(
+ "com.sun.star.drawing.ConnectorShape", false);
+ pShape->setConnectorShape(true);
+ pShape->setWps(true);
+ pShape->setWordprocessingCanvas(true);
+ // ToDo: Can only copy infos from mpShapePtr to pShape for which getter available.
+ pShape->setName(mpShapePtr->getName());
+ pShape->setId(mpShapePtr->getId());
+ pShape->setWPGChild(mpShapePtr->isWPGChild());
+ // And actually replace the shape.
+ mpShapePtr = pShape;
+ mpMasterShapePtr->getChildren().pop_back();
+ mpMasterShapePtr->getChildren().push_back(pShape);
+ return new oox::drawingml::ConnectorShapePropertiesContext(
+ *this, mpShapePtr, mpShapePtr->getConnectorShapeProperties());
+ }
case XML_bodyPr:
if (mxShape.is())
{
diff --git a/oox/source/token/namespaces-strict.txt b/oox/source/token/namespaces-strict.txt
index 59631432eb2f..beed3238ae2d 100644
--- a/oox/source/token/namespaces-strict.txt
+++ b/oox/source/token/namespaces-strict.txt
@@ -81,6 +81,7 @@ wp14 http://schemas.microsoft.com/office/word/2010/wordproces
w14 http://schemas.microsoft.com/office/word/2010/wordml
a14 http://schemas.microsoft.com/office/drawing/2010/main
p14 http://schemas.microsoft.com/office/powerpoint/2010/main
+wpc http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas
# MSO 2012/2013 extensions ---------------------------------------------------------
diff --git a/oox/source/token/namespaces.hxx.tail b/oox/source/token/namespaces.hxx.tail
index 382955683d7d..34513e850fe5 100644
--- a/oox/source/token/namespaces.hxx.tail
+++ b/oox/source/token/namespaces.hxx.tail
@@ -63,7 +63,7 @@ inline sal_Int32 getNamespace( sal_Int32 nToken ) { return nToken & NMSP_MASK; }
#define LOEXT_TOKEN( token ) OOX_TOKEN( loext, token )
#define M_TOKEN(token) OOX_TOKEN(officeMath, token)
#define XR2_TOKEN(token) OOX_TOKEN(xr2, token)
-
+#define WPC_TOKEN(token) OOX_TOKEN(wpc, token)
} // namespace oox
diff --git a/oox/source/token/namespaces.txt b/oox/source/token/namespaces.txt
index 0790c65d8817..dbe29f19a220 100644
--- a/oox/source/token/namespaces.txt
+++ b/oox/source/token/namespaces.txt
@@ -81,6 +81,7 @@ wp14 http://schemas.microsoft.com/office/word/2010/wordproces
w14 http://schemas.microsoft.com/office/word/2010/wordml
a14 http://schemas.microsoft.com/office/drawing/2010/main
p14 http://schemas.microsoft.com/office/powerpoint/2010/main
+wpc http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas
# MSO 2012/2013 extensions ---------------------------------------------------------
diff --git a/oox/source/token/tokens.txt b/oox/source/token/tokens.txt
index eb5239d8a8ac..56e17dc35c22 100644
--- a/oox/source/token/tokens.txt
+++ b/oox/source/token/tokens.txt
@@ -5809,6 +5809,7 @@ wp
wp14
wpJustification
wpSpaceWidth
+wpc
wpg
wps
wrap