diff options
Diffstat (limited to 'oox')
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 Binary files differnew file mode 100644 index 000000000000..27cc978077c4 --- /dev/null +++ b/oox/qa/unit/data/WPC_BentConnector.docx diff --git a/oox/qa/unit/data/WPC_CanvasBackground.docx b/oox/qa/unit/data/WPC_CanvasBackground.docx Binary files differnew file mode 100644 index 000000000000..bc5b3dda8678 --- /dev/null +++ b/oox/qa/unit/data/WPC_CanvasBackground.docx diff --git a/oox/qa/unit/data/WPC_Glow.docx b/oox/qa/unit/data/WPC_Glow.docx Binary files differnew file mode 100644 index 000000000000..29f9a7466a36 --- /dev/null +++ b/oox/qa/unit/data/WPC_Glow.docx diff --git a/oox/qa/unit/data/WPC_MulticolorGradient.docx b/oox/qa/unit/data/WPC_MulticolorGradient.docx Binary files differnew file mode 100644 index 000000000000..4d874fa48053 --- /dev/null +++ b/oox/qa/unit/data/WPC_MulticolorGradient.docx diff --git a/oox/qa/unit/data/WPC_Shadow.docx b/oox/qa/unit/data/WPC_Shadow.docx Binary files differnew file mode 100644 index 000000000000..44e282feddb7 --- /dev/null +++ b/oox/qa/unit/data/WPC_Shadow.docx diff --git a/oox/qa/unit/data/WPC_Textwrap_in_ellipse.docx b/oox/qa/unit/data/WPC_Textwrap_in_ellipse.docx Binary files differnew file mode 100644 index 000000000000..1729db3972ae --- /dev/null +++ b/oox/qa/unit/data/WPC_Textwrap_in_ellipse.docx diff --git a/oox/qa/unit/data/WPC_ThemeColor.docx b/oox/qa/unit/data/WPC_ThemeColor.docx Binary files differnew file mode 100644 index 000000000000..20cc52f6fc04 --- /dev/null +++ b/oox/qa/unit/data/WPC_ThemeColor.docx diff --git a/oox/qa/unit/data/WPC_tdf104671_Cloud.docx b/oox/qa/unit/data/WPC_tdf104671_Cloud.docx Binary files differnew file mode 100644 index 000000000000..5cd1ca53b680 --- /dev/null +++ b/oox/qa/unit/data/WPC_tdf104671_Cloud.docx 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 Binary files differnew file mode 100644 index 000000000000..353ba6aafc8a --- /dev/null +++ b/oox/qa/unit/data/WPC_tdf48610_Textbox_with_table_inside.docx 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 |