summaryrefslogtreecommitdiffstats
path: root/svx/qa
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /svx/qa
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svx/qa')
-rw-r--r--svx/qa/uitest/table/tablecontroller.py76
-rw-r--r--svx/qa/unit/XTableImportExportTest.cxx80
-rw-r--r--svx/qa/unit/classicshapes.cxx211
-rw-r--r--svx/qa/unit/core.cxx128
-rw-r--r--svx/qa/unit/customshapes.cxx1405
-rw-r--r--svx/qa/unit/data/0-width-text-wrap.pptxbin0 -> 26504 bytes
-rw-r--r--svx/qa/unit/data/3d-object-fallback.odpbin0 -> 44589 bytes
-rw-r--r--svx/qa/unit/data/FontWork.odgbin0 -> 10515 bytes
-rw-r--r--svx/qa/unit/data/GraphicObjectResolverTest.zipbin0 -> 740 bytes
-rw-r--r--svx/qa/unit/data/auto-height-multi-col-shape.pptxbin0 -> 16350 bytes
-rw-r--r--svx/qa/unit/data/chart.odsbin0 -> 19734 bytes
-rw-r--r--svx/qa/unit/data/graphic.pdfbin0 -> 7243 bytes
-rw-r--r--svx/qa/unit/data/page-view-draw-layer-clip.docxbin0 -> 20764 bytes
-rw-r--r--svx/qa/unit/data/shadow-scale-origin.pptxbin0 -> 31984 bytes
-rw-r--r--svx/qa/unit/data/slide-background.odpbin0 -> 28090 bytes
-rw-r--r--svx/qa/unit/data/slide-background.pngbin0 -> 18426 bytes
-rw-r--r--svx/qa/unit/data/svx-dialogs-test.txt80
-rw-r--r--svx/qa/unit/data/table-shadow-blur.pptxbin0 -> 33745 bytes
-rw-r--r--svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odpbin0 -> 12420 bytes
-rw-r--r--svx/qa/unit/data/tdf103474_commandT_CaseZeroHeight.odpbin0 -> 12833 bytes
-rw-r--r--svx/qa/unit/data/tdf115813_HandleMovementOOXMLPresetShapes.pptxbin0 -> 34858 bytes
-rw-r--r--svx/qa/unit/data/tdf121761_Accuracy_command_X.odpbin0 -> 11177 bytes
-rw-r--r--svx/qa/unit/data/tdf121845_HalfEllipseVML.docbin0 -> 27648 bytes
-rw-r--r--svx/qa/unit/data/tdf121845_Two_commands_U.odgbin0 -> 10751 bytes
-rw-r--r--svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odgbin0 -> 9908 bytes
-rw-r--r--svx/qa/unit/data/tdf121845_start40_swing480.docbin0 -> 27648 bytes
-rw-r--r--svx/qa/unit/data/tdf121952_Toggle_direction_command_X.odpbin0 -> 12029 bytes
-rw-r--r--svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptxbin0 -> 15580 bytes
-rw-r--r--svx/qa/unit/data/tdf122964_MultipleMoveTo.odgbin0 -> 9666 bytes
-rw-r--r--svx/qa/unit/data/tdf124029_Arc_position.docbin0 -> 19456 bytes
-rw-r--r--svx/qa/unit/data/tdf124212_handle_position.odgbin0 -> 10108 bytes
-rw-r--r--svx/qa/unit/data/tdf124740_HandleInOOXMLUserShape.pptxbin0 -> 14929 bytes
-rw-r--r--svx/qa/unit/data/tdf125782_QuadraticCurveTo.odgbin0 -> 9910 bytes
-rw-r--r--svx/qa/unit/data/tdf126060_3D_Z_Rotation.pptxbin0 -> 32602 bytes
-rw-r--r--svx/qa/unit/data/tdf126512_OOXMLHandleMovementInODF.odpbin0 -> 62627 bytes
-rw-r--r--svx/qa/unit/data/tdf127785_Mirror.odpbin0 -> 13742 bytes
-rw-r--r--svx/qa/unit/data/tdf127785_TextRotateAngle.odpbin0 -> 12841 bytes
-rw-r--r--svx/qa/unit/data/tdf127785_asymmetricTextBoxFlipV.odgbin0 -> 12166 bytes
-rw-r--r--svx/qa/unit/data/tdf128413_tbrl_OnOff.odpbin0 -> 14170 bytes
-rw-r--r--svx/qa/unit/data/tdf129532_MatrixFlipV.odgbin0 -> 10036 bytes
-rw-r--r--svx/qa/unit/data/tdf130076_FlipOnSectorSection.odgbin0 -> 13736 bytes
-rw-r--r--svx/qa/unit/data/tdf136176_rot30_flip.odgbin0 -> 10344 bytes
-rw-r--r--svx/qa/unit/data/tdf138945_resizeRotatedShape.odgbin0 -> 9457 bytes
-rw-r--r--svx/qa/unit/data/tdf140321_Matte_import.pptbin0 -> 13312 bytes
-rw-r--r--svx/qa/unit/data/tdf140321_material_specular.odpbin0 -> 12939 bytes
-rw-r--r--svx/qa/unit/data/tdf140321_metal.odpbin0 -> 16876 bytes
-rw-r--r--svx/qa/unit/data/tdf140321_phong.odpbin0 -> 14088 bytes
-rw-r--r--svx/qa/unit/data/tdf141021_ExtrusionNorth.odpbin0 -> 14261 bytes
-rw-r--r--svx/qa/unit/data/tdf141268.odpbin0 -> 12987 bytes
-rw-r--r--svx/qa/unit/data/tdf144988_Fontwork_FontSize.odpbin0 -> 11169 bytes
-rw-r--r--svx/qa/unit/data/tdf145004_gap_by_ScaleX.pptxbin0 -> 16077 bytes
-rw-r--r--svx/qa/unit/data/tdf145111_TL_BL_Fontwork.odpbin0 -> 13908 bytes
-rw-r--r--svx/qa/unit/data/tdf145245_ExtrusionPosition.odpbin0 -> 12983 bytes
-rw-r--r--svx/qa/unit/data/tdf145700_3D_FirstLightHarsh.docbin0 -> 27136 bytes
-rw-r--r--svx/qa/unit/data/tdf145700_3D_FrontLightDim.docbin0 -> 27136 bytes
-rw-r--r--svx/qa/unit/data/tdf145700_3D_NonUI.docbin0 -> 29184 bytes
-rw-r--r--svx/qa/unit/data/tdf145904_center_Y0dot25.docbin0 -> 27136 bytes
-rw-r--r--svx/qa/unit/data/tdf145904_center_Y0dot25.odtbin0 -> 10618 bytes
-rw-r--r--svx/qa/unit/data/tdf145904_center_Zminus2000.odtbin0 -> 9602 bytes
-rw-r--r--svx/qa/unit/data/tdf145956_Origin.odpbin0 -> 28073 bytes
-rw-r--r--svx/qa/unit/data/tdf147409_GeomItemHash.odgbin0 -> 14274 bytes
-rw-r--r--svx/qa/unit/data/tdf148501_OctagonBevel.odpbin0 -> 15018 bytes
-rw-r--r--svx/qa/unit/data/tdf148707_two_commands_B_V.odpbin0 -> 13389 bytes
-rw-r--r--svx/qa/unit/data/tdf148714_CurvedArrows.pptbin0 -> 99840 bytes
-rw-r--r--svx/qa/unit/data/tdf60684.jpgbin0 -> 35738 bytes
-rw-r--r--svx/qa/unit/data/tdf93998.odpbin0 -> 11308 bytes
-rw-r--r--svx/qa/unit/data/tdf98583_ShearHorizontal.odpbin0 -> 14328 bytes
-rw-r--r--svx/qa/unit/data/tdf98584_ShearVertical.odgbin0 -> 9109 bytes
-rw-r--r--svx/qa/unit/data/theme.pptxbin0 -> 30646 bytes
-rw-r--r--svx/qa/unit/data/unodraw-writer-image.odtbin0 -> 9564 bytes
-rw-r--r--svx/qa/unit/data/video-snapshot.pptxbin0 -> 40331 bytes
-rw-r--r--svx/qa/unit/data/viewBox_positive_twolines_strict.odpbin0 -> 12107 bytes
-rw-r--r--svx/qa/unit/gallery/data/galtest1.pngbin0 -> 172 bytes
-rw-r--r--svx/qa/unit/gallery/data/galtest2.pngbin0 -> 138 bytes
-rw-r--r--svx/qa/unit/gallery/data/galtest3.jpgbin0 -> 762 bytes
-rw-r--r--svx/qa/unit/gallery/test_gallery.cxx482
-rw-r--r--svx/qa/unit/removewhichrange.cxx122
-rw-r--r--svx/qa/unit/sdr.cxx149
-rw-r--r--svx/qa/unit/styles.cxx156
-rw-r--r--svx/qa/unit/svdraw.cxx554
-rw-r--r--svx/qa/unit/svdraw/test_SdrTextObject.cxx46
-rw-r--r--svx/qa/unit/svx-dialogs-test.cxx58
-rw-r--r--svx/qa/unit/table.cxx142
-rw-r--r--svx/qa/unit/unodraw.cxx249
-rw-r--r--svx/qa/unit/xml.cxx68
-rw-r--r--svx/qa/unit/xoutdev.cxx156
-rw-r--r--svx/qa/unoapi/knownissues.xcl108
-rw-r--r--svx/qa/unoapi/svx.sce49
-rw-r--r--svx/qa/unoapi/testdocuments/SvxShape.sxdbin0 -> 6344 bytes
-rw-r--r--svx/qa/unoapi/testdocuments/crazy-blue.jpgbin0 -> 4451 bytes
-rw-r--r--svx/qa/unoapi/testdocuments/space-metal.jpgbin0 -> 4313 bytes
91 files changed, 4319 insertions, 0 deletions
diff --git a/svx/qa/uitest/table/tablecontroller.py b/svx/qa/uitest/table/tablecontroller.py
new file mode 100644
index 000000000..26390cb5c
--- /dev/null
+++ b/svx/qa/uitest/table/tablecontroller.py
@@ -0,0 +1,76 @@
+#
+# 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/.
+#
+
+from uitest.framework import UITestCase
+from uitest.uihelper.common import select_pos
+from libreoffice.uno.propertyvalue import mkPropertyValues
+
+
+# Test for SvxTableController.
+class SvxTableControllerTest(UITestCase):
+
+ def testOnFormatTable(self):
+ # Create an Impress document with a single table in it.
+ with self.ui_test.create_doc_in_start_center("impress") as component:
+ template = self.xUITest.getTopFocusWindow()
+ self.ui_test.close_dialog_through_button(template.getChild("close"))
+ self.xUITest.executeCommand(".uno:SelectAll")
+ self.xUITest.executeCommand(".uno:Delete")
+ self.xUITest.executeCommand(".uno:InsertTable?Columns:short=2&Rows:short=2")
+
+ # Enable shadow.
+ with self.ui_test.execute_dialog_through_command(".uno:TableDialog") as tableDialog:
+ tabs = tableDialog.getChild("tabcontrol")
+ # Select "shadow".
+ select_pos(tabs, "4")
+ shadowCheckbox = tableDialog.getChild("TSB_SHOW_SHADOW")
+ shadowCheckbox.executeAction("CLICK", tuple())
+
+ # Check if the shadow was enabled.
+ drawPage = component.getDrawPages().getByIndex(0)
+ shape = drawPage.getByIndex(0)
+ # Without the accompanying fix in place, this test would have failed with:
+ # AssertionError: False != True
+ # i.e. the table still had no shadow.
+ self.assertEqual(shape.Shadow, True)
+
+ # Close the document.
+
+ def testUndoCrash(self):
+ # Given an Impress document with a single table in it:
+ with self.ui_test.create_doc_in_start_center("impress"):
+ template = self.xUITest.getTopFocusWindow()
+ self.ui_test.close_dialog_through_button(template.getChild("close"))
+ self.xUITest.executeCommand(".uno:SelectAll")
+ self.xUITest.executeCommand(".uno:Delete")
+ self.xUITest.executeCommand(".uno:InsertTable?Columns:short=3&Rows:short=3")
+ self.xUITest.executeCommand(".uno:SelectAll")
+
+ # When enabling shadow on the shape while text edit is active:
+ doc = self.xUITest.getTopFocusWindow()
+ impress = doc.getChild("impress_win")
+ impress.executeAction("TYPE", mkPropertyValues({"TEXT": "A1"}))
+ for i in range(6):
+ impress.executeAction("TYPE", mkPropertyValues({"KEYCODE": "CTRL+TAB"}))
+ impress.executeAction("TYPE", mkPropertyValues({"TEXT": "A3"}))
+ self.xUITest.executeCommand(".uno:SelectAll")
+ with self.ui_test.execute_dialog_through_command(".uno:TableDialog") as tableDialog:
+ tabs = tableDialog.getChild("tabcontrol")
+ # Select "shadow".
+ select_pos(tabs, "4")
+ shadowCheckbox = tableDialog.getChild("TSB_SHOW_SHADOW")
+ shadowCheckbox.executeAction("CLICK", tuple())
+
+ # Then make sure we don't crash:
+ # Without the accompanying fix in place, this test would have failed crashed due to a
+ # use-after-free: text edit ended but an undo action of the text edit remained on the undo
+ # stack.
+ for i in range(2):
+ self.xUITest.executeCommand(".uno:Undo")
+
+ # Close the document.
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/svx/qa/unit/XTableImportExportTest.cxx b/svx/qa/unit/XTableImportExportTest.cxx
new file mode 100644
index 000000000..b97a7c50b
--- /dev/null
+++ b/svx/qa/unit/XTableImportExportTest.cxx
@@ -0,0 +1,80 @@
+/* -*- 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 <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <unotest/bootstrapfixturebase.hxx>
+
+#include <sal/types.h>
+#include <sfx2/app.hxx>
+#include <unotools/tempfile.hxx>
+#include <svx/xtable.hxx>
+#include <vcl/bitmapex.hxx>
+
+#include <com/sun/star/awt/XBitmap.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+
+using namespace css;
+
+class XTableImportExportTest : public CppUnit::TestFixture
+{
+public:
+ virtual void setUp() override
+ {
+ CppUnit::TestFixture::setUp();
+ SfxApplication::GetOrCreate();
+ }
+};
+
+CPPUNIT_TEST_FIXTURE(XTableImportExportTest, testImportExport)
+{
+ utl::TempFile aTempFile(nullptr, true);
+ aTempFile.EnableKillingFile();
+ OUString aTempURL = aTempFile.GetURL();
+ BitmapChecksum aChecksum(0);
+
+ {
+ rtl::Reference<XBitmapList> xBitmapList = new XBitmapList(aTempURL, "REF");
+ uno::Reference<container::XNameContainer> xNameContainer(xBitmapList->createInstance());
+ CPPUNIT_ASSERT(xNameContainer.is());
+
+ Bitmap aBitmap(Size(5, 5), vcl::PixelFormat::N24_BPP);
+ aBitmap.Erase(COL_RED);
+ BitmapEx aBitmapEx(aBitmap);
+ Graphic aGraphic(aBitmapEx);
+ uno::Reference<awt::XBitmap> xBitmap(aGraphic.GetXGraphic(), css::uno::UNO_QUERY);
+
+ xNameContainer->insertByName("SomeBitmap", uno::Any(xBitmap));
+ xBitmapList->Save();
+
+ aChecksum = aBitmap.GetChecksum();
+ }
+
+ {
+ rtl::Reference<XBitmapList> xBitmapList = new XBitmapList(aTempURL, "REF");
+ bool bResult = xBitmapList->Load();
+ CPPUNIT_ASSERT(bResult);
+ uno::Reference<container::XNameContainer> xNameContainer(xBitmapList->createInstance());
+ CPPUNIT_ASSERT(xNameContainer.is());
+
+ uno::Any aAny = xNameContainer->getByName("SomeBitmap");
+ CPPUNIT_ASSERT(aAny.has<uno::Reference<awt::XBitmap>>());
+ auto xBitmap = aAny.get<uno::Reference<awt::XBitmap>>();
+ CPPUNIT_ASSERT(xBitmap.is());
+ uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xGraphic.is());
+ Graphic aGraphic(xGraphic);
+ CPPUNIT_ASSERT(!aGraphic.IsNone());
+ Bitmap aBitmap = aGraphic.GetBitmapEx().GetBitmap();
+ CPPUNIT_ASSERT_EQUAL(aChecksum, aBitmap.GetChecksum());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/classicshapes.cxx b/svx/qa/unit/classicshapes.cxx
new file mode 100644
index 000000000..bb5bea745
--- /dev/null
+++ b/svx/qa/unit/classicshapes.cxx
@@ -0,0 +1,211 @@
+/* -*- 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/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+#include <rtl/ustring.hxx>
+#include <editeng/unoprnms.hxx>
+
+#include <cppunit/TestAssert.h>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawPage.hpp>
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+constexpr OUStringLiteral sDataDirectory(u"svx/qa/unit/data/");
+
+/// Tests not about special features of custom shapes, but about shapes in general.
+class ClassicshapesTest : public test::BootstrapFixture, public unotest::MacrosTest
+{
+protected:
+ uno::Reference<lang::XComponent> mxComponent;
+ uno::Reference<drawing::XShape> getShape(sal_uInt8 nShapeIndex, sal_uInt8 nPageIndex);
+
+public:
+ virtual void setUp() override
+ {
+ test::BootstrapFixture::setUp();
+ mxDesktop.set(frame::Desktop::create(m_xContext));
+ }
+
+ virtual void tearDown() override
+ {
+ if (mxComponent.is())
+ {
+ mxComponent->dispose();
+ }
+ test::BootstrapFixture::tearDown();
+ }
+};
+
+uno::Reference<drawing::XShape> ClassicshapesTest::getShape(sal_uInt8 nShapeIndex,
+ sal_uInt8 nPageIndex)
+{
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
+ uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT_MESSAGE("Could not get XDrawPagesSupplier", xDrawPagesSupplier.is());
+ uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(nPageIndex),
+ uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT_MESSAGE("Could not get xDrawPage", xDrawPage.is());
+ uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(nShapeIndex), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get xShape", xShape.is());
+ return xShape;
+}
+
+CPPUNIT_TEST_FIXTURE(ClassicshapesTest, testTdf98584ShearVertical)
+{
+ // The document contains draw:rect, draw:polygon and draw:path objects.
+ // They are vertical sheared by skewY(-0.927295218002) or by matrix(1 2 0 1 1cm 1cm).
+ // Notice, skewY and matrix are interpreted on file open, but not written on file save.
+ // They are converted to rotate * shear horizontal * scale.
+ // Besides using a wrong sign in shear angle, error was, that TRSetGeometry of SdrPathObj did
+ // not consider the additional scaling (tdf#98565).
+ const OUString sURL(m_directories.getURLFromSrc(sDataDirectory) + "tdf98584_ShearVertical.odg");
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ // Tests skewY
+ for (sal_uInt8 nPageIndex = 0; nPageIndex < 3; ++nPageIndex)
+ {
+ awt::Rectangle aFrameRect;
+ uno::Reference<drawing::XShape> xShape(getShape(0, nPageIndex));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_FRAMERECT) >>= aFrameRect;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ OUString("Incorrect Width on skewY page " + OUString::number(nPageIndex))
+ .toUtf8()
+ .getStr(),
+ 5001.0, aFrameRect.Width, 2.0);
+ double nShearA;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_SHEARANGLE) >>= nShearA;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ OUString("Incorrect Share angle on skewY page " + OUString::number(nPageIndex))
+ .toUtf8()
+ .getStr(),
+ -5313.0, nShearA, 2.0);
+ double nRotA;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nRotA;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ OUString("Incorrect Rotate angle on skewY page " + OUString::number(nPageIndex))
+ .toUtf8()
+ .getStr(),
+ 30687.0, nRotA, 2.0);
+ }
+
+ // Tests matrix
+ for (sal_uInt8 nPageIndex = 3; nPageIndex < 6; ++nPageIndex)
+ {
+ awt::Rectangle aFrameRect;
+ uno::Reference<drawing::XShape> xShape(getShape(0, nPageIndex));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_FRAMERECT) >>= aFrameRect;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ OUString("Incorrect Width on matrix page " + OUString::number(nPageIndex))
+ .toUtf8()
+ .getStr(),
+ 5001.0, aFrameRect.Width, 2.0);
+ double nShearA;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_SHEARANGLE) >>= nShearA;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ OUString("Incorrect Share angle on matrix page " + OUString::number(nPageIndex))
+ .toUtf8()
+ .getStr(),
+ -6343.0, nShearA, 2.0);
+ double nRotA;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nRotA;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ OUString("Incorrect Rotate angle on matrix page " + OUString::number(nPageIndex))
+ .toUtf8()
+ .getStr(),
+ 29657.0, nRotA, 2.0);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(ClassicshapesTest, testTdf98583ShearHorizontal)
+{
+ // The document contains rectangles with LT 3000,5000 and RB 5000,9000.
+ // skewX (-0.78539816339744830961) = skewX(-45deg) is applied on the first page
+ // matrix(1 0 1 1 0cm 0cm) on the second page. Both should result in a parallelogram with
+ // LT 8000,5000 and RB 14000, 9000, which means width 6001, height 4001.
+ // Error was, that not the mathematical matrix was used, but the API matrix, which has
+ // wrong sign in shear angle.
+ const OUString sURL(m_directories.getURLFromSrc(sDataDirectory)
+ + "tdf98583_ShearHorizontal.odp");
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.presentation.PresentationDocument");
+
+ for (sal_uInt8 nPageIndex = 0; nPageIndex < 2; ++nPageIndex)
+ {
+ awt::Rectangle aFrameRect;
+ uno::Reference<drawing::XShape> xShape(getShape(0, nPageIndex));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_FRAMERECT) >>= aFrameRect;
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ OUString("Incorrect Left Position on page " + OUString::number(nPageIndex))
+ .toUtf8()
+ .getStr(),
+ 8000.0, aFrameRect.X, 2.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ OUString("Incorrect Top Position on page " + OUString::number(nPageIndex))
+ .toUtf8()
+ .getStr(),
+ 5000.0, aFrameRect.Y, 2.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ OUString("Incorrect Width on page " + OUString::number(nPageIndex)).toUtf8().getStr(),
+ 6001.0, aFrameRect.Width, 2.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+ OUString("Incorrect Height on page " + OUString::number(nPageIndex)).toUtf8().getStr(),
+ 4001.0, aFrameRect.Height, 2.0);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(ClassicshapesTest, testTdf130076Flip)
+{
+ // The document contains sections of a circle, one of which is scaled
+ // (1, -1), one of which is scaled (-1,1), one of which is transformed
+ // by a matrix equivalent to a vertical flip, and another which is
+ // transformed by a matrix equivalent to a horizontal flip. Error was
+ // that the transformation was made before the CircleKind was set,
+ // resulting in the flip being performed incorrectly.
+ const OUString sURL(m_directories.getURLFromSrc(sDataDirectory)
+ + "tdf130076_FlipOnSectorSection.odg");
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ for (sal_uInt8 nPageIndex = 0; nPageIndex < 2; ++nPageIndex)
+ {
+ double nAngle1(0.0), nAngle2(0.0);
+ uno::Reference<drawing::XShape> xShape(getShape(1, nPageIndex));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ uno::Reference<drawing::XShape> xShape2(getShape(2, nPageIndex));
+ uno::Reference<beans::XPropertySet> xShapeProps2(xShape2, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xShapeProps->getPropertyValue("CircleStartAngle") >>= nAngle1);
+ CPPUNIT_ASSERT(xShapeProps2->getPropertyValue("CircleStartAngle") >>= nAngle2);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(OUString("Incorrect vertical flip starting angle on page "
+ + OUString::number(nPageIndex))
+ .toUtf8()
+ .getStr(),
+ 26000.0, nAngle1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(OUString("Incorrect horizontal flip starting angle on page "
+ + OUString::number(nPageIndex))
+ .toUtf8()
+ .getStr(),
+ 26000.0, nAngle2);
+ }
+}
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/core.cxx b/svx/qa/unit/core.cxx
new file mode 100644
index 000000000..396601001
--- /dev/null
+++ b/svx/qa/unit/core.cxx
@@ -0,0 +1,128 @@
+/* -*- 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/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+
+#include <comphelper/storagehelper.hxx>
+
+#include <svx/graphichelper.hxx>
+#include <svx/xmlgrhlp.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/filter/PDFiumLibrary.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Tests for svx/source/core/ code.
+class Test : public test::BootstrapFixture, public unotest::MacrosTest
+{
+private:
+ uno::Reference<lang::XComponent> mxComponent;
+
+public:
+ void setUp() override;
+ void tearDown() override;
+ uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
+};
+
+void Test::setUp()
+{
+ test::BootstrapFixture::setUp();
+
+ mxDesktop.set(frame::Desktop::create(mxComponentContext));
+}
+
+void Test::tearDown()
+{
+ if (mxComponent.is())
+ mxComponent->dispose();
+
+ test::BootstrapFixture::tearDown();
+}
+
+constexpr OUStringLiteral DATA_DIRECTORY = u"/svx/qa/unit/data/";
+
+CPPUNIT_TEST_FIXTURE(Test, testChartExportToPdf)
+{
+ // Given a Calc document with a chart in it:
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "chart.ods";
+ getComponent() = loadFromDesktop(aURL);
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
+
+ // When exporting that chart to PDF:
+ utl::TempFile aTempFile;
+ GraphicHelper::SaveShapeAsGraphicToPath(getComponent(), xShape, "application/pdf",
+ aTempFile.GetURL());
+
+ // Then make sure we get a valid, non-empty PDF:
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ if (!pPdfium)
+ return;
+ SvMemoryStream aMemory;
+ aMemory.WriteStream(*aTempFile.GetStream(StreamMode::READ));
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
+ = pPdfium->openDocument(aMemory.GetData(), aMemory.GetSize(), OString());
+ // Without the accompanying fix in place, this test would have failed, because the output was
+ // empty (0 bytes).
+ CPPUNIT_ASSERT(pPdfDocument);
+ int nPageCount = pPdfDocument->getPageCount();
+ CPPUNIT_ASSERT_GREATER(0, nPageCount);
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testGraphicObjectResolver)
+{
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "GraphicObjectResolverTest.zip";
+ uno::Reference<embed::XStorage> xStorage
+ = comphelper::OStorageHelper::GetStorageOfFormatFromURL(ZIP_STORAGE_FORMAT_STRING, aURL,
+ embed::ElementModes::READWRITE);
+ CPPUNIT_ASSERT(xStorage.is());
+
+ rtl::Reference<SvXMLGraphicHelper> xGraphicHelper
+ = SvXMLGraphicHelper::Create(xStorage, SvXMLGraphicHelperMode::Read);
+ CPPUNIT_ASSERT(xGraphicHelper.is());
+
+ // Test name in root folder
+ {
+ uno::Reference<graphic::XGraphic> xGraphic = xGraphicHelper->loadGraphic("SomeImage.png");
+ CPPUNIT_ASSERT_EQUAL(true, xGraphic.is());
+ }
+
+ // Test name in sub-folder
+ {
+ uno::Reference<graphic::XGraphic> xGraphic
+ = xGraphicHelper->loadGraphic("Pictures/SomeOtherImage.png");
+ CPPUNIT_ASSERT_EQUAL(true, xGraphic.is());
+ }
+
+ // Test non-existent name
+ {
+ uno::Reference<graphic::XGraphic> xGraphic;
+ try
+ {
+ xGraphic = xGraphicHelper->loadGraphic("NoneExistent.png");
+ }
+ catch (const uno::Exception&)
+ {
+ }
+ CPPUNIT_ASSERT_EQUAL(false, xGraphic.is());
+ }
+}
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/customshapes.cxx b/svx/qa/unit/customshapes.cxx
new file mode 100644
index 000000000..0b1970378
--- /dev/null
+++ b/svx/qa/unit/customshapes.cxx
@@ -0,0 +1,1405 @@
+/* -*- 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 <sal/config.h>
+
+#include <cstdlib>
+
+#include <test/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+#include <rtl/ustring.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <editeng/unoprnms.hxx>
+#include <sfx2/request.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/viewsh.hxx>
+#include <svl/intitem.hxx>
+#include <svx/EnhancedCustomShape2d.hxx>
+#include <svx/extrusionbar.hxx>
+#include <svx/graphichelper.hxx>
+#include <svx/svdoashp.hxx>
+#include <svx/svdopath.hxx>
+#include <svx/svdview.hxx>
+#include <svx/svxids.hrc>
+#include <unotools/mediadescriptor.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+
+#include <cppunit/TestAssert.h>
+
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
+#include <com/sun/star/drawing/GraphicExportFilter.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawPage.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+constexpr OUStringLiteral sDataDirectory(u"svx/qa/unit/data/");
+
+/// Tests for svx/source/customshapes/ code.
+class CustomshapesTest : public test::BootstrapFixture, public unotest::MacrosTest
+{
+protected:
+ uno::Reference<lang::XComponent> mxComponent;
+ // get shape nShapeIndex from page 0
+ uno::Reference<drawing::XShape> getShape(sal_uInt8 nShapeIndex);
+ sal_uInt8 countShapes();
+
+public:
+ virtual void setUp() override
+ {
+ test::BootstrapFixture::setUp();
+ mxDesktop.set(frame::Desktop::create(m_xContext));
+ }
+
+ virtual void tearDown() override
+ {
+ if (mxComponent.is())
+ {
+ mxComponent->dispose();
+ }
+ test::BootstrapFixture::tearDown();
+ }
+};
+
+uno::Reference<drawing::XShape> CustomshapesTest::getShape(sal_uInt8 nShapeIndex)
+{
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
+ uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT_MESSAGE("Could not get XDrawPagesSupplier", xDrawPagesSupplier.is());
+ uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT_MESSAGE("Could not get xDrawPage", xDrawPage.is());
+ uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(nShapeIndex), uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get xShape", xShape.is());
+ return xShape;
+}
+
+sal_uInt8 CustomshapesTest::countShapes()
+{
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
+ uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT_MESSAGE("Could not get XDrawPagesSupplier", xDrawPagesSupplier.is());
+ uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT_MESSAGE("Could not get xDrawPage", xDrawPage.is());
+ return xDrawPage->getCount();
+}
+
+void lcl_AssertRectEqualWithTolerance(std::string_view sInfo, const tools::Rectangle& rExpected,
+ const tools::Rectangle& rActual, const sal_Int32 nTolerance)
+{
+ // Left
+ OString sMsg = OString::Concat(sInfo) + " Left expected " + OString::number(rExpected.Left())
+ + " actual " + OString::number(rActual.Left()) + " Tolerance "
+ + OString::number(nTolerance);
+ CPPUNIT_ASSERT_MESSAGE(sMsg.getStr(),
+ std::abs(rExpected.Left() - rActual.Left()) <= nTolerance);
+
+ // Top
+ sMsg = OString::Concat(sInfo) + " Top expected " + OString::number(rExpected.Top()) + " actual "
+ + OString::number(rActual.Top()) + " Tolerance " + OString::number(nTolerance);
+ CPPUNIT_ASSERT_MESSAGE(sMsg.getStr(), std::abs(rExpected.Top() - rActual.Top()) <= nTolerance);
+
+ // Width
+ sMsg = OString::Concat(sInfo) + " Width expected " + OString::number(rExpected.GetWidth())
+ + " actual " + OString::number(rActual.GetWidth()) + " Tolerance "
+ + OString::number(nTolerance);
+ CPPUNIT_ASSERT_MESSAGE(sMsg.getStr(),
+ std::abs(rExpected.GetWidth() - rActual.GetWidth()) <= nTolerance);
+
+ // Height
+ sMsg = OString::Concat(sInfo) + " Height expected " + OString::number(rExpected.GetHeight())
+ + " actual " + OString::number(rActual.GetHeight()) + " Tolerance "
+ + OString::number(nTolerance);
+ CPPUNIT_ASSERT_MESSAGE(sMsg.getStr(),
+ std::abs(rExpected.GetHeight() - rActual.GetHeight()) <= nTolerance);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf147409_GeomItemHash)
+{
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf147409_GeomItemHash.odg";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.drawing.DrawingDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ SdrObjCustomShape* pSdrCustomShape(
+ static_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)));
+
+ // Mark Object
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ SdrView* pSdrView = pViewShell->GetDrawView();
+ pSdrView->MarkObj(pSdrCustomShape, pSdrView->GetSdrPageView());
+
+ // Apply FontworkSameLetterHeights toggle
+ // Without patch a debug build fails assert in SfxItemPool::PutImpl and so crashes.
+ dispatchCommand(mxComponent, ".uno:FontworkSameLetterHeights", {});
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf146866_GeomItemHash)
+{
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf147409_GeomItemHash.odg";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.drawing.DrawingDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ SdrObjCustomShape* pSdrCustomShape(
+ static_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)));
+
+ // Mark Object
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ SdrView* pSdrView = pViewShell->GetDrawView();
+ pSdrView->MarkObj(pSdrCustomShape, pSdrView->GetSdrPageView());
+
+ // Apply extrusion toggle
+ // Without patch a debug build fails assert in SfxItemPool::PutImpl and so crashes.
+ dispatchCommand(mxComponent, ".uno:ExtrusionToggle", {});
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145700_3D_NonUI)
+{
+ // The document contains first light soft, no ambient color, no second light and shininess 6.
+ // Such settings are not available in the UI. It tests the actual color, not the geometry.
+ // Load document
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145700_3D_NonUI.doc";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument");
+
+ // Generate bitmap from shape
+ uno::Reference<drawing::XShape> xShape = getShape(0);
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ GraphicHelper::SaveShapeAsGraphicToPath(mxComponent, xShape, "image/png", aTempFile.GetURL());
+
+ // Read bitmap and test color
+ // The expected values are taken from an image generated by Word
+ // Without the changed methods the colors were in range RGB(17,11,17) to RGB(87,55,89).
+ SvFileStream aFileStream(aTempFile.GetURL(), StreamMode::READ);
+ vcl::PngImageReader aPNGReader(aFileStream);
+ BitmapEx aBMPEx = aPNGReader.read();
+ Bitmap aBMP = aBMPEx.GetBitmap();
+ Bitmap::ScopedReadAccess pRead(aBMP);
+ Size aSize = aBMP.GetSizePixel();
+ // GetColor(Y,X)
+ Color aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() * 0.125);
+ Color aExpectedColor(107, 67, 109);
+ sal_uInt16 nColorDistance = aExpectedColor.GetColorError(aActualColor);
+ CPPUNIT_ASSERT_LESS(sal_uInt16(6), nColorDistance);
+ // The current solution for soft light still can be improved. nColorDistance is high.
+ aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() * 0.45);
+ aExpectedColor = Color(179, 113, 183);
+ nColorDistance = aExpectedColor.GetColorError(aActualColor);
+ CPPUNIT_ASSERT_LESS(sal_uInt16(54), nColorDistance);
+ // This point tests whether shininess is read and used. With default shininess it would be white.
+ aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() * 0.72);
+ aExpectedColor = Color(255, 231, 255);
+ nColorDistance = aExpectedColor.GetColorError(aActualColor);
+ CPPUNIT_ASSERT_LESS(sal_uInt16(14), nColorDistance);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145700_3D_FrontLightDim)
+{
+ // This tests the actual color, not the geometry.
+ // Load document
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145700_3D_FrontLightDim.doc";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument");
+
+ // Generate bitmap from shape
+ uno::Reference<drawing::XShape> xShape = getShape(0);
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ GraphicHelper::SaveShapeAsGraphicToPath(mxComponent, xShape, "image/png", aTempFile.GetURL());
+
+ // Read bitmap and test color
+ // The expected values are taken from an image generated by Word
+ // Without the changed methods the nColorDistance was 476 and 173 respectively.
+ SvFileStream aFileStream(aTempFile.GetURL(), StreamMode::READ);
+ vcl::PngImageReader aPNGReader(aFileStream);
+ BitmapEx aBMPEx = aPNGReader.read();
+ Bitmap aBMP = aBMPEx.GetBitmap();
+ Bitmap::ScopedReadAccess pRead(aBMP);
+ Size aSize = aBMP.GetSizePixel();
+ // GetColor(Y,X)
+ Color aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() * 0.4);
+ Color aExpectedColor(240, 224, 229);
+ sal_uInt16 nColorDistance = aExpectedColor.GetColorError(aActualColor);
+ CPPUNIT_ASSERT_LESS(sal_uInt16(9), nColorDistance);
+ aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() * 0.9);
+ aExpectedColor = Color(96, 90, 92);
+ nColorDistance = aExpectedColor.GetColorError(aActualColor);
+ CPPUNIT_ASSERT_LESS(sal_uInt16(6), nColorDistance);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145700_3D_FirstLightHarsh)
+{
+ // Load document
+ OUString aURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf145700_3D_FirstLightHarsh.doc";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument");
+
+ // Generate bitmap from shape
+ uno::Reference<drawing::XShape> xShape = getShape(0);
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ GraphicHelper::SaveShapeAsGraphicToPath(mxComponent, xShape, "image/png", aTempFile.GetURL());
+
+ // Read bitmap and test color in center
+ SvFileStream aFileStream(aTempFile.GetURL(), StreamMode::READ);
+ vcl::PngImageReader aPNGReader(aFileStream);
+ BitmapEx aBMPEx = aPNGReader.read();
+ Bitmap aBMP = aBMPEx.GetBitmap();
+ Bitmap::ScopedReadAccess pRead(aBMP);
+ Size aSize = aBMP.GetSizePixel();
+ // GetColor(Y,X)
+ const Color aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() / 2);
+ const Color aExpectedColor(211, 247, 255); // from image generated by Word
+ sal_uInt16 nColorDistance = aExpectedColor.GetColorError(aActualColor);
+ CPPUNIT_ASSERT_LESS(sal_uInt16(3), nColorDistance);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145956_Origin_Relative_BoundRect)
+{
+ // The ViewPoint is relative to point Origin. The coordinates of point Origin are fractions of
+ // the actual (2D) bounding rectangle of the shape, including rotation around z-axis and flip.
+ // Error (among others) was, that the unrotated snap rectangle was used.
+
+ // Load document
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145956_Origin.odp";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.presentation.PresentationDocument");
+
+ // The shape is extruded with 10cm. viewpoint="(0cm 0cm 25cm)", origin="0 0".
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xPropSet(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the properties", xPropSet.is());
+ awt::Rectangle aBoundRect;
+ xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ sal_Int32 nActualTop = aBoundRect.Y;
+
+ // Without the fix it would have failed with top = 9462.
+ // The tolerance 10 is estimated and can be adjusted if required for HiDPI.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("top", 10448, nActualTop, 10);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145904_Extrusion_CenterZ_odt)
+{
+ // The Z-component of the extrusion rotation center specifies the position in Hmm.
+ // Error (among others) was, that the value was interpreted as Twips.
+
+ // Load document
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145904_center_Zminus2000.odt";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument");
+
+ // The shape is extruded and tilt left 60deg. The rotation center is at -2000Hmm on the z-axis.
+ // That is a position behind the back face of the extruded shape.
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xPropSet(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the properties", xPropSet.is());
+ awt::Rectangle aBoundRect;
+ xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ awt::Point aAnchorPosition;
+ xPropSet->getPropertyValue("AnchorPosition") >>= aAnchorPosition;
+ sal_Int32 nActualLeft = aBoundRect.X - aAnchorPosition.X;
+
+ // Without the fix it would have failed with left = 7731.
+ // The tolerance 10 is estimated and can be adjusted if required for HiDPI.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("left", 3501, nActualLeft, 10);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145904_Extrusion_CenterY_odt)
+{
+ // The X- and Y-component of the extrusion rotation center specify the position as fraction of
+ // shape size. Error was, that the relative fraction was handled as absolute value in Hmm.
+
+ // Load document
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145904_center_Y0dot25.odt";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument");
+
+ // The shape is extruded and tilt down 90deg. The rotation center is in the middle between shape
+ // center and bottom shape edge. The bottom edge of the projected solid has roughly the
+ // y-coordinate of the rotation center.
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xPropSet(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the properties", xPropSet.is());
+ awt::Rectangle aBoundRect;
+ xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ awt::Point aAnchorPosition;
+ xPropSet->getPropertyValue("AnchorPosition") >>= aAnchorPosition;
+ sal_Int32 nActualTop = aBoundRect.Y - aAnchorPosition.Y;
+
+ // Without the fix it would have failed with top = 2252.
+ // The tolerance 10 is estimated and can be adjusted if required for HiDPI.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("top", 3208, nActualTop, 10);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145904_Extrusion_CenterY_doc)
+{
+ // The X- and Y-component of the extrusion rotation center specify the position as fraction of
+ // shape size. Error was, that the relative fraction was handled as absolute value in EMU.
+
+ // Load document
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145904_center_Y0dot25.doc";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument");
+
+ // The shape is extruded and tilt down 90deg. The rotation center is in the middle between shape
+ // center and bottom shape edge. The bottom edge of the projected solid has roughly the
+ // y-coordinate of the center.
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xPropSet(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the properties", xPropSet.is());
+ awt::Rectangle aBoundRect;
+ xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ awt::Point aAnchorPosition;
+ xPropSet->getPropertyValue("AnchorPosition") >>= aAnchorPosition;
+ sal_Int32 nActualTop = aBoundRect.Y - aAnchorPosition.Y;
+
+ // Without the fix it would have failed with top = 2330
+ // The tolerance 10 is estimated and can be adjusted if required for HiDPI.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("top", 3208, nActualTop, 10);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145245_ExtrusionPosition)
+{
+ // The second parameter of the extrusion-depth property specifies how much of the extrusion
+ // lies before the shape. The file contains three shapes which have the values 0, 0.5 and 1.
+ // They are rotated around the x-axis so that the extrusion becomes visible. The extrusion
+ // depth itself is 5cm. Y-coordinate of shape is 6cm.
+
+ // Load document
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145245_ExtrusionPosition.odp";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
+
+ // The tolerance 40 is estimated and can be adjusted if required for HiDPI.
+ {
+ // First shape has extrusion behind the shape.
+ uno::Reference<drawing::XShape> xShape0(getShape(0));
+ SdrObjCustomShape& rSdrCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape0)));
+ tools::Rectangle aBoundRect(rSdrCustomShape.GetCurrentBoundRect());
+ tools::Rectangle aExpected(Point(1000, 1000), Size(6002, 5001));
+ lcl_AssertRectEqualWithTolerance("Pos 0.0 extrusion", aExpected, aBoundRect, 40);
+ }
+ {
+ // Second shape has half of extrusion behind the shape.
+ uno::Reference<drawing::XShape> xShape(getShape(1));
+ SdrObjCustomShape& rSdrCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ // Without the fix the height was 1 instead of 5001.
+ tools::Rectangle aBoundRect(rSdrCustomShape.GetCurrentBoundRect());
+ tools::Rectangle aExpected(Point(9000, 3500), Size(6002, 5001));
+ lcl_AssertRectEqualWithTolerance("Pos 0.5 extrusion", aExpected, aBoundRect, 40);
+ }
+ {
+ // Third shape has extrusion before the shape.
+ uno::Reference<drawing::XShape> xShape(getShape(2));
+ SdrObjCustomShape& rSdrCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ // Without the fix the y-coordinate was 1000 instead of 6000.
+ tools::Rectangle aBoundRect(rSdrCustomShape.GetCurrentBoundRect());
+ tools::Rectangle aExpected(Point(18000, 6000), Size(6002, 5001));
+ lcl_AssertRectEqualWithTolerance("Pos 1.0 extrusion", aExpected, aBoundRect, 40);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145111_Fontwork_rendering_font_size)
+{
+ // The tested position and height depend on dpi.
+ if (!IsDefaultDPI())
+ return;
+
+ // tdf#144988 In case ScaleX is true in property TextPath, the rendering font size should be
+ // reduced in case any of the paragraphs would be longer as its sub-path. That was wrong, if
+ // the first paragraph was too long and the second would fit. It resulted in wrong position
+ // and height and overlapping characters.
+
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf144988_Fontwork_FontSize.odp";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ SdrObjCustomShape& rSdrCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+
+ // Without the fix in place left|top, width x height was 1279|1279, 2815 x 2448.
+ // The expected values 1501|1777, 3941 x 1446 are only valid for 96dpi.
+ tools::Rectangle aBoundRect(rSdrCustomShape.GetCurrentBoundRect());
+ tools::Rectangle aExpected(Point(1501, 1777), Size(3941, 1446));
+ lcl_AssertRectEqualWithTolerance("Wrong text rendering", aExpected, aBoundRect, 5);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145111_anchor_in_Fontwork)
+{
+ // The tested positions depend on dpi.
+ if (!IsDefaultDPI())
+ return;
+
+ // tdf#145004 In case ScaleX is true in property TextPath, SDRTEXTVERTADJUST is
+ // evaluated and should shift the Fontwork text. That did not work for
+ // 'Top-Left' and 'Bottom-Left'.
+
+ // Load document
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145111_TL_BL_Fontwork.odp";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
+
+ {
+ // First shape has anchor set to Top-Left, which shifts Fontwork text down.
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ SdrObjCustomShape& rSdrCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+
+ // Without the fix in place top was 2295, but should be 2916 for 96dpi.
+ // Was 2184, should be 2886 for 120dpi.
+ tools::Rectangle aBoundRect(rSdrCustomShape.GetCurrentBoundRect());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(tools::Long(2916), aBoundRect.Top(), 5);
+ }
+ {
+ // Second shape has anchor set to Bottom-Left, which shifts Fontwork text up.
+ uno::Reference<drawing::XShape> xShape(getShape(1));
+ SdrObjCustomShape& rSdrCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+
+ // Without the fix in place top was 10294, but should be 9519 for 96dpi.
+ // Was 10184, should be 9481 for 120dpi.
+ tools::Rectangle aBoundRect(rSdrCustomShape.GetCurrentBoundRect());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(tools::Long(9519), aBoundRect.Top(), 5);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145004_gap_by_ScaleX)
+{
+ if (!IsDefaultDPI())
+ return;
+ // tdf#145004 In case property ScaleX=true was set in property 'TextPath' an additional
+ // padding was added to the scaling factor. That results in a gap at start or/and end of
+ // the text. Such gap should not be there.
+
+ // Load document and get shape. It is a custom shape from pptx import of a WordArt of
+ // kind 'Follow Path'.
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145004_gap_by_ScaleX.pptx";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ SdrObjCustomShape& rSdrCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+
+ // Verify width. Without the fix in place the width was 8231, but should be 8496 for 96dpi.
+ // Was 8328, should be 8527 for 120dpi.
+ tools::Rectangle aBoundRect(rSdrCustomShape.GetCurrentBoundRect());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(tools::Long(8496), aBoundRect.GetWidth(), 5);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf141021ExtrusionNorth)
+{
+ // tdf#141021 Setting extrusion direction in projection method 'perspective' to
+ // 'Extrusion North' had used a wrong origin for the ViewPoint and thus the
+ // side faces were wrong calculated.
+
+ // Load document and get shape. It is a custom shape in 3D mode.
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf141021_ExtrusionNorth.odp";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ SdrObjCustomShape& rSdrCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+
+ // Mark Object
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ SdrView* pSdrView = pViewShell->GetDrawView();
+ pSdrView->MarkObj(&rSdrCustomShape, pSdrView->GetSdrPageView());
+
+ // Set direction
+ SfxRequest aReq(pViewShell->GetViewFrame(), SID_EXTRUSION_DIRECTION);
+ SfxInt32Item aItem(SID_EXTRUSION_DIRECTION, 90);
+ aReq.AppendItem(aItem);
+ svx::ExtrusionBar::execute(pSdrView, aReq, SfxViewFrame::Current()->GetBindings());
+
+ // Verify height. Without the fix in place the height would 4001.
+ tools::Rectangle aBoundRect(rSdrCustomShape.GetCurrentBoundRect());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(5895), aBoundRect.GetHeight());
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testResizeRotatedShape)
+{
+ // tdf#138945 Setting width or height for a rotated or sheared shape in the Position&Size dialog
+ // had resulted in a mismatch of handle position and shape outline. That becomes visible in object
+ // properties as mismatch of frame rectangle and bound rectangle.
+ // Problem was, that fObjectRotation was not updated.
+
+ // Load document and get shape. It is a rectangle custom shape with 45° shear and 330° rotation.
+ OUString aURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf138945_resizeRotatedShape.odg";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+
+ // Change height and mirror vertical
+ {
+ SdrObjCustomShape& rSdrShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ rSdrShape.NbcResize(rSdrShape.GetRelativePos(), Fraction(1.0), Fraction(-0.5));
+ tools::Rectangle aSnapRect(rSdrShape.GetSnapRect());
+ tools::Rectangle aBoundRect(rSdrShape.GetCurrentBoundRect());
+ lcl_AssertRectEqualWithTolerance("height changed, mirror vert", aSnapRect, aBoundRect, 3);
+ }
+
+ // Change height
+ {
+ SdrObjCustomShape& rSdrShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ rSdrShape.NbcResize(rSdrShape.GetRelativePos(), Fraction(1.0), Fraction(2.0));
+ tools::Rectangle aSnapRect(rSdrShape.GetSnapRect());
+ tools::Rectangle aBoundRect(rSdrShape.GetCurrentBoundRect());
+ lcl_AssertRectEqualWithTolerance("height changed", aSnapRect, aBoundRect, 3);
+ }
+
+ // Change width
+ {
+ SdrObjCustomShape& rSdrShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ rSdrShape.NbcResize(rSdrShape.GetRelativePos(), Fraction(2.0), Fraction(1.0));
+ tools::Rectangle aSnapRect(rSdrShape.GetSnapRect());
+ tools::Rectangle aBoundRect(rSdrShape.GetCurrentBoundRect());
+ lcl_AssertRectEqualWithTolerance("width changed", aSnapRect, aBoundRect, 3);
+ }
+
+ // Change width and mirror horizontal
+ {
+ SdrObjCustomShape& rSdrShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ rSdrShape.NbcResize(rSdrShape.GetRelativePos(), Fraction(-0.5), Fraction(1.0));
+ tools::Rectangle aSnapRect(rSdrShape.GetSnapRect());
+ tools::Rectangle aBoundRect(rSdrShape.GetCurrentBoundRect());
+ lcl_AssertRectEqualWithTolerance("width changed, mirror hori", aSnapRect, aBoundRect, 3);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testViewBoxLeftTop)
+{
+ // tdf#121890 formula values "left" and "top" are wrongly calculated
+ // Load a document with two custom shapes of type "non-primitive"
+ OUString aURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "viewBox_positive_twolines_strict.odp";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
+ // Get the shape "leftright". Error was, that the identifier "left" was always set to zero, thus
+ // the path was outside the frame rectangle for a viewBox having a positive "left" value.
+ uno::Reference<drawing::XShape> xShapeLR(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeLRProps(xShapeLR, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape 'leftright' properties", xShapeLRProps.is());
+ awt::Rectangle aFrameRectLR;
+ xShapeLRProps->getPropertyValue(UNO_NAME_MISC_OBJ_FRAMERECT) >>= aFrameRectLR;
+ awt::Rectangle aBoundRectLR;
+ xShapeLRProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRectLR;
+ // difference should be zero, but allow some rounding errors
+ CPPUNIT_ASSERT_LESS(sal_Int32(3), std::abs(aFrameRectLR.X - aBoundRectLR.X));
+
+ // Get the shape "topbottom". Error was, that the identifier "top" was always set to zero, thus
+ // the path was outside the frame rectangle for a viewBox having a positive "top" value.
+ uno::Reference<drawing::XShape> xShapeTB(getShape(1));
+ uno::Reference<beans::XPropertySet> xShapeTBProps(xShapeTB, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape 'topbottom' properties", xShapeTBProps.is());
+ awt::Rectangle aFrameRectTB;
+ xShapeTBProps->getPropertyValue(UNO_NAME_MISC_OBJ_FRAMERECT) >>= aFrameRectTB;
+ awt::Rectangle aBoundRectTB;
+ xShapeTBProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRectTB;
+ // difference should be zero, but allow some rounding errors
+ CPPUNIT_ASSERT_LESS(sal_Int32(3), std::abs(aFrameRectTB.Y - aBoundRectTB.Y));
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testAccuracyCommandX)
+{
+ // 121761 Increase accuracy of quarter circles drawn by command X or Y
+ // The loaded document has a quarter circle with radius 10000 (unit 1/100 mm)
+ // which is rotated by 45deg. The test considers the segment.
+ OUString aURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf121761_Accuracy_command_X.odp";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
+ // Get the shape "arc_45deg_rotated". Error was, that a Bezier curve with bad parameters
+ // was used, thus the segment height was obviously smaller than for a true circle.
+ // Math: segment height approx 10000 * ( 1 - sqrt(0.5)) + line width
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ double fHeight = static_cast<double>(aBoundRect.Height);
+ // The tolerance is a guess, might be smaller.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("segment height out of tolerance", 2942.0, fHeight, 8.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testToggleCommandXY)
+{
+ // 121952 Toggle x- and y-direction if command X has several parameters
+ // The loaded document has a shape with command X and two parameter placed on a diagonal.
+ // The radius of the quarter circles are both 10000 (unit 1/100 mm).
+ // The shape is rotated by 45deg, so you get two segments, one up and one down.
+ OUString aURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf121952_Toggle_direction_command_X.odp";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.presentation.PresentationDocument");
+ // Error was, that the second segment was drawn with same direction as first one. If drawn
+ // correctly, the bounding box height of the segments together is about twice the single
+ // segment height. Math: segment height approx 10000 * ( 1 - sqrt(0.5)) + line width
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ double fHeight = static_cast<double>(aBoundRect.Height);
+ // The tolerance is a guess, might be smaller.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("segment height out of tolerance", 5871.0, fHeight, 16.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testMultipleMoveTo)
+{
+ // tdf122964 Multiple moveTo has to be treated as lineTo in draw:enhanced-path
+ // Load a document with path "M 0 0 5 10 10 0 N"
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf122964_MultipleMoveTo.odg";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.drawing.DrawingDocument");
+ // Error was, that the second and further parameter pairs were treated as moveTo,
+ // and so the generated path was empty, resulting in zero width and height of the
+ // bounding box. It has to be treated same as "M 0 0 L 5 10 10 0 N".
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ bool bIsZero(aBoundRect.Height == 0 && aBoundRect.Width == 0);
+ CPPUNIT_ASSERT_MESSAGE("Path is empty", !bIsZero);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testWidthOrientationCommandU)
+{
+ // tdf121845 custom shape with command U (angleellipse) is wrongly drawn
+ // Load a document with path "M 750 0 L 750 500 250 500 250 0 U 500 0 500 500 0 180 N"
+ // in viewBox="0 0 1000 500" and width="10cm", height="5cm".
+ const OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf121845_WidthOrientation_command_U.odg";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+ // Error was, that the width and height of the ellipse was halved and that the ellipse
+ // was not drawn clockwise but counter clockwise.
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ const double fWidth = static_cast<double>(aBoundRect.Width);
+ // Need some tolerance for line width
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong width", 10000.0, fWidth, 40.0);
+ const double fHeight = static_cast<double>(aBoundRect.Height);
+ // Wrong orientation draws segment above the top of the viewBox and so increases 'Height'.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong orientation", 5000.0, fHeight, 40.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testHalfEllipseVML)
+{
+ // tdf121845 custom shape with command U (angleellipse) is wrongly drawn
+ // Load a document which was converted from VML to doc by Word. It had a VML
+ // path="m750,al500,,500,500,,-11796480e" resulting in a lower half circle.
+ const OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf121845_HalfEllipseVML.doc";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.text.TextDocument");
+ // Error was, that a full circle instead of the half circle was draw.
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ const double fDiff2HmW = static_cast<double>(2 * aBoundRect.Height - aBoundRect.Width);
+ // Need some tolerance for line width
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("not a half circle", 0.0, fDiff2HmW, 40.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testLargeSwingAngleVML)
+{
+ // tdf121845 custom shape with command U (angleellipse) is wrongly drawn
+ // Load a document which was converted from VML to doc by Word. It had a VML
+ // path="al50,50,45,45,2621440,31457280e" resulting in a full circle plus 120 deg segment.
+ const OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf121845_start40_swing480.doc";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.text.TextDocument");
+ // Error was, that only the 120 deg segment was drawn.
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ const double fDiffWmH = static_cast<double>(aBoundRect.Width - aBoundRect.Height);
+ // Need some tolerance for line width
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Full circle plus segment expected", 0.0, fDiffWmH, 10.0);
+}
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf121845_two_commands_U)
+{
+ // tdf121845 custom shape with command U (angleellipse) is wrongly drawn
+ // Load a document with path "U 950 250 200 200 90 180 250 250 200 200 180 270 N"
+ // Error was, that the second ellipse segment was interpreted as command T and
+ // thus a line from first to second segment was drawn.
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf121845_Two_commands_U.odg";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ // In case no line is drawn, two polygons are generated; with line only one polygon
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ EnhancedCustomShape2d aCustomShape2d(rSdrObjCustomShape);
+ SdrPathObjUniquePtr pPathObj(
+ static_cast<SdrPathObj*>(aCustomShape2d.CreateLineGeometry().release()));
+ CPPUNIT_ASSERT_MESSAGE("Could not convert to SdrPathObj", pPathObj);
+ const basegfx::B2DPolyPolygon aPolyPolygon(pPathObj->GetPathPoly());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("count polygons", static_cast<sal_uInt32>(2),
+ aPolyPolygon.count());
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf124212_handle_position)
+{
+ // tdf124212 Adjustment handle reacts wrongly, if custom shape has a non
+ // default viewBox. Load a document with svg:viewBox="10800 0 10800 21600"
+ // Error was, that moving the controller results in a handle position that
+ // does not reflect the movement.
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf124212_handle_position.odg";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ // The shape has one, horizontal adjust handle.
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ EnhancedCustomShape2d aCustomShape2d(rSdrObjCustomShape);
+ Point aInitialPosition;
+ aCustomShape2d.GetHandlePosition(0, aInitialPosition);
+ css::awt::Point aDesiredPosition(aInitialPosition.X() + 1000, aInitialPosition.Y());
+ aCustomShape2d.SetHandleControllerPosition(0, aDesiredPosition);
+ Point aObservedPosition;
+ aCustomShape2d.GetHandlePosition(0, aObservedPosition);
+ sal_Int32 nDesiredX(aDesiredPosition.X); // awt::Point
+ sal_Int32 nObservedX(aObservedPosition.X()); // tools::Point
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("handle X coordinate", nDesiredX, nObservedX);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf124029_arc_position)
+{
+ // tdf121029 MS binary custom shape mso_sptArc has wrong position
+ // MS uses the sector for position reference. Error was, that
+ // LibreOffice has used the underlying ellipse.
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf124029_Arc_position.doc";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.text.TextDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ // The visual wrong position is due to a wrong shape width.
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aFrameRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_FRAMERECT) >>= aFrameRect;
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("shape width", static_cast<sal_uInt32>(1610),
+ static_cast<sal_uInt32>(aFrameRect.Width));
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf124740_handle_path_coordsystem)
+{
+ // tdf124740 OOXML shape with handle and w and h attribute on path has wrong
+ // handle position
+ // The handle position was scaled erroneously twice.
+ OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf124740_HandleInOOXMLUserShape.pptx";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ // The shape has one, horizontal adjust handle. It is about 1/5 of 10cm from left
+ // shape edge, shape is 6cm from left . That results in a position
+ // of 8cm from left page edge, which is 8000 in 1/100 mm unit.
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ EnhancedCustomShape2d aCustomShape2d(rSdrObjCustomShape);
+ Point aPosition;
+ aCustomShape2d.GetHandlePosition(0, aPosition);
+ double fX(aPosition.X());
+ // tolerance for rounding to integer
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("handle X coordinate", 8000.0, fX, 2.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf115813_OOXML_XY_handle)
+{
+ // The test covers all preset shapes with handles. Only these ones are
+ // excluded: arc, blockArc, chord, circularArrow, gear6, gear9, mathNotEqual, pie,
+ // leftCircularArrow, leftRightCircularArrow, swooshArrow.
+ // Connectors are included as ordinary shapes to prevent converting.
+ // Error was, that the handle movement and the changes to the shape did not follow
+ // the mouse movement.
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory)
+ + "tdf115813_HandleMovementOOXMLPresetShapes.pptx";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ // values in vector InteractionsHandles are in 1/100 mm and refer to page
+ for (sal_uInt8 i = 0; i < countShapes(); i++)
+ {
+ uno::Reference<drawing::XShape> xShape(getShape(i));
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ OUString sShapeType("non-primitive"); // default for ODF
+ const SdrCustomShapeGeometryItem& rGeometryItem(
+ rSdrObjCustomShape.GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY));
+ const uno::Any* pAny = rGeometryItem.GetPropertyValueByName("Type");
+ if (pAny)
+ *pAny >>= sShapeType;
+
+ sal_uInt8 nHandlesCount = rSdrObjCustomShape.GetInteractionHandles().size();
+ for (sal_uInt8 j = 0; j < nHandlesCount; j++)
+ {
+ css::awt::Point aInitialPosition(
+ rSdrObjCustomShape.GetInteractionHandles()[j].aPosition);
+ // The handles are initialized in the test document, so that if the handle is moveable in
+ // that direction at all, then it can move at least with an amount of 100.
+ Point aDesiredPosition(aInitialPosition.X + 100, aInitialPosition.Y + 100);
+ rSdrObjCustomShape.DragMoveCustomShapeHdl(aDesiredPosition, j, false);
+ css::awt::Point aObservedPosition(
+ rSdrObjCustomShape.GetInteractionHandles()[j].aPosition);
+ sal_Int32 nDesiredX(aDesiredPosition.X()); // tools::Point
+ sal_Int32 nDesiredY(aDesiredPosition.Y());
+ sal_Int32 nObservedX(aObservedPosition.X); // css::awt::Point
+ sal_Int32 nObservedY(aObservedPosition.Y);
+ // If a handle only moves in one direction, the difference is 100 for the other direction.
+ // There exists some rounding differences, therefore '<= 1' instead of '== 0'.
+ // The condition has the form '!(good cases)'.
+ if (!((abs(nDesiredX - nObservedX) <= 1 && abs(nDesiredY - nObservedY) == 100)
+ || (abs(nDesiredX - nObservedX) == 100 && abs(nDesiredY - nObservedY) <= 1)
+ || (abs(nDesiredX - nObservedX) <= 1 && abs(nDesiredY - nObservedY) <= 1)))
+ {
+ OUString sError
+ = OUString::number(i) + " " + sShapeType + ": " + OUString::number(j) + " X "
+ + OUString::number(nDesiredX) + "|" + OUString::number(nObservedX) + " Y "
+ + OUString::number(nDesiredY) + "|" + OUString::number(nObservedY);
+ CPPUNIT_FAIL(sError.toUtf8().getStr());
+ }
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testQuadraticCurveTo)
+{
+ // tdf125782 command Q (quadraticcurveto) uses wrong 'current point'.
+ // When converting to cubic Bezier curve, this had resulted in a wrong first control point.
+ // The quadraticcurveto segment starts in shape center in the test file. The first control
+ // point should produce a horizontal tangent in the start point.
+ const OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf125782_QuadraticCurveTo.odg";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+ const double fHeight = static_cast<double>(aBoundRect.Height);
+ //Add some tolerance
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("bad height of quadraticcurveto", 3004, fHeight, 10.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf126512_OOXML_handle_in_ODP)
+{
+ // The test covers all preset shapes with handles. Connectors are included as ordinary
+ // shapes to prevent converting. The file was created in PowerPoint 365 and then
+ // opened and exported to ODF format by LibreOffice.
+ // Error was, that for shapes, which were originally imported from OOXML, the handles
+ // could not be moved at all.
+ OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf126512_OOXMLHandleMovementInODF.odp";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ for (sal_uInt8 i = 0; i < countShapes(); i++)
+ {
+ uno::Reference<drawing::XShape> xShape(getShape(i));
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ OUString sShapeType("non-primitive"); // only to initialize, value not used here
+ const SdrCustomShapeGeometryItem& rGeometryItem(
+ rSdrObjCustomShape.GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY));
+ const uno::Any* pAny = rGeometryItem.GetPropertyValueByName("Type");
+ if (pAny)
+ *pAny >>= sShapeType;
+
+ sal_uInt8 nHandlesCount = rSdrObjCustomShape.GetInteractionHandles().size();
+ for (sal_uInt8 j = 0; j < nHandlesCount; j++)
+ {
+ css::awt::Point aInitialPosition(
+ rSdrObjCustomShape.GetInteractionHandles()[j].aPosition);
+ // The handles are initialized in the test document, so that if the handle is moveable
+ // in that direction at all, then it can move at least with an amount of 100.
+ Point aDesiredPosition(aInitialPosition.X + 100, aInitialPosition.Y + 100);
+ rSdrObjCustomShape.DragMoveCustomShapeHdl(aDesiredPosition, j, false);
+ css::awt::Point aObservedPosition(
+ rSdrObjCustomShape.GetInteractionHandles()[j].aPosition);
+ if (aInitialPosition.X == aObservedPosition.X
+ && aInitialPosition.Y == aObservedPosition.Y)
+ {
+ OUString sError
+ = OUString::number(i) + " " + sShapeType + " " + OUString::number(j);
+ CPPUNIT_FAIL(sError.toUtf8().getStr());
+ }
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf127785_Mirror)
+{
+ // The document contains two shapes, one with horizontal flip, the other with vertical
+ // flip. They are diamonds, so their text frame is symmetric to the center of the shape.
+ // The shapes have not stroke and no fill, so that the bounding box surrounds the text
+ // and therefore equals approximately the text frame.
+ // Error was, that because of wrong calculation, the flipped shapes do not use the
+ // text frame but the frame rectangle for their text.
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf127785_Mirror.odp";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ uno::Reference<drawing::XShape> xShapeV(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeVProps(xShapeV, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeVProps.is());
+ awt::Rectangle aBoundRectV;
+ xShapeVProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRectV;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Flip vertical wrong size.", 8000.0, aBoundRectV.Height,
+ 10.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Flip vertical wrong size.", 8000.0, aBoundRectV.Width,
+ 10.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Flip vertical wrong position.", 1000.0, aBoundRectV.X,
+ 10.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Flip vertical wrong position.", 2000.0, aBoundRectV.Y,
+ 10.0);
+
+ uno::Reference<drawing::XShape> xShapeH(getShape(1));
+ uno::Reference<beans::XPropertySet> xShapeHProps(xShapeH, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeHProps.is());
+ awt::Rectangle aBoundRectH;
+ xShapeHProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRectH;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Flip horizontal wrong size.", 8000.0, aBoundRectH.Height,
+ 10.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Flip horizontal wrong size.", 8000.0, aBoundRectH.Width,
+ 10.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Flip horizontal wrong position.", 13000.0, aBoundRectH.X,
+ 10.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Flip horizontal wrong position.", 2000.0, aBoundRectH.Y,
+ 10.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf126060_3D_Z_Rotation)
+{
+ // The document contains one textbox with inside overflowed text
+ // and the text has 3D z rotation. When we open the document we
+ // should see the text vertically and rotated from text bound center not text box.
+
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf126060_3D_Z_Rotation.pptx";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong text camera Z rotation", 90.0,
+ rSdrObjCustomShape.GetCameraZRotation());
+
+ basegfx::B2DHomMatrix aObjectTransform;
+ basegfx::B2DPolyPolygon aObjectPolyPolygon;
+ rSdrObjCustomShape.TRGetBaseGeometry(aObjectTransform, aObjectPolyPolygon);
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong transformation at 0,0 position", 1492.0,
+ aObjectTransform.get(0, 0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong transformation at 0,1 position", 0.0,
+ aObjectTransform.get(0, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong transformation at 0,2 position", 1129.0,
+ aObjectTransform.get(0, 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong transformation at 1,0 position", 0.0,
+ aObjectTransform.get(1, 0));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong transformation at 1,1 position", 2500.0,
+ aObjectTransform.get(1, 1));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong transformation at 1,2 position", 5846.0,
+ aObjectTransform.get(1, 2));
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf127785_Asymmetric)
+{
+ // The document contains a shapes with vertical flip and text frame asymmetrical
+ // to shape. The shape has not stroke and no fill, so that the bounding box surrounds
+ // the text and therefore equals approximately the text frame.
+ // Error was, that the 180deg text rotation was not compensated for the position of
+ // the flipped text box.
+ OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf127785_asymmetricTextBoxFlipV.odg";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong left", 9000.0, aBoundRect.X, 10.0);
+ const double nRight = aBoundRect.X + aBoundRect.Width - 1;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong right", 19000.0, nRight, 10.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong top", 3000.0, aBoundRect.Y, 10.0);
+ const double nBottom = aBoundRect.Y + aBoundRect.Height - 1;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong bottom", 18000.0, nBottom, 10.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf127785_TextRotateAngle)
+{
+ // The document contains a shapes with vertical flip and a text frame with own
+ // rotate angle. The shape has not stroke and no fill, so that the bounding box
+ // surrounds the text and therefore equals approximately the text frame.
+ // Error was, that the compensation for the 180° rotation added for vertical
+ // flip were not made to the text box position but to the text matrix.
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf127785_TextRotateAngle.odp";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aBoundRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong left", 2000.0, aBoundRect.X, 10.0);
+ const double nRight = aBoundRect.X + aBoundRect.Width - 1;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong right", 14000.0, nRight, 10.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong top", 3000.0, aBoundRect.Y, 10.0);
+ const double nBottom = aBoundRect.Y + aBoundRect.Height - 1;
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("wrong bottom", 9000.0, nBottom, 10.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf128413_tbrlOnOff)
+{
+ // The document contains a rotated shape with text. The error was, that switching
+ // tb-rl writing-mode on, changed the shape size and position.
+
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf128413_tbrl_OnOff.odp";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShapeProps.is());
+ awt::Rectangle aOrigRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_FRAMERECT) >>= aOrigRect;
+
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ rSdrObjCustomShape.SetVerticalWriting(true);
+
+ awt::Rectangle aObservedRect;
+ xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_FRAMERECT) >>= aObservedRect;
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Shape has wrong width", aOrigRect.Width, aObservedRect.Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Shape has wrong height", aOrigRect.Height, aObservedRect.Height);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Shape has wrong X position", aOrigRect.X, aObservedRect.X);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Shape has wrong Y position", aOrigRect.Y, aObservedRect.Y);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf129532_MatrixFlipV)
+{
+ // The document contains two rotated shapes with the same geometry. For one of them
+ // "matrix(1 0 0 -1 0cm 0cm)" was manually added to the value of the draw:transform
+ // attribute. That should result in mirroring on the x-axis. Error was, that the lines
+ // which are drawn on the shape rectangle were mirrored, but not the rectangle itself.
+ // The rectangle was only shifted.
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf129532_MatrixFlipV.odg";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ uno::Reference<drawing::XShape> xShape0(getShape(0));
+ uno::Reference<beans::XPropertySet> xShape0Props(xShape0, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShape0Props.is());
+ awt::Rectangle aBoundRect0;
+ xShape0Props->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect0;
+
+ uno::Reference<drawing::XShape> xShape1(getShape(1));
+ uno::Reference<beans::XPropertySet> xShape1Props(xShape1, uno::UNO_QUERY);
+ CPPUNIT_ASSERT_MESSAGE("Could not get the shape properties", xShape1Props.is());
+ awt::Rectangle aBoundRect1;
+ xShape1Props->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect1;
+
+ // The size of the two BoundRect rectangles are the same in case of correct
+ // vertical mirroring.
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect width", aBoundRect0.Width, aBoundRect1.Width);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect height", aBoundRect0.Height, aBoundRect1.Height);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf103474_commandT_CaseZeroHeight)
+{
+ // tdf103474 custom shape with command T to create quarter ellipses in a bracket,
+ // corner case where the ellipse has zero height.
+ // Error was, that the calculation of the circle angle from the ellipse
+ // angle results in a wrong angle for the case 180° and height zero.
+ OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf103474_commandT_CaseZeroHeight.odp";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.presentation.PresentationDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ // The end points of the straight line segment should have the same x-coordinate of left
+ // of shape, and different y-coordinates, one top and the other bottom of the shape.
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ EnhancedCustomShape2d aCustomShape2d(rSdrObjCustomShape);
+ SdrPathObjUniquePtr pPathObj(
+ static_cast<SdrPathObj*>(aCustomShape2d.CreateLineGeometry().release()));
+ CPPUNIT_ASSERT_MESSAGE("Could not convert to SdrPathObj", pPathObj);
+ const basegfx::B2DPolyPolygon aPolyPolygon(pPathObj->GetPathPoly());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("count polygons", static_cast<sal_uInt32>(1),
+ aPolyPolygon.count());
+ const basegfx::B2DPolygon aPolygon(aPolyPolygon.getB2DPolygon(0));
+ // Get the middle points of the polygon. They are the endpoints of the
+ // straight line segment regardless of the quarter ellipse parts, because
+ // the shape is symmetric.
+ const basegfx::B2DPoint aStart(aPolygon.getB2DPoint(aPolygon.count() / 2 - 1));
+ const basegfx::B2DPoint aEnd(aPolygon.getB2DPoint(aPolygon.count() / 2));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aStart x-coordinate", 13999.0, aStart.getX(), 1.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aEnd x-coordinate", 13999.0, aEnd.getX(), 1.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aStart y-coordinate", 9999.0, aStart.getY(), 1.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aEnd y-coordinate", 1999.0, aEnd.getY(), 1.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf103474_commandG_CaseZeroHeight)
+{
+ // Some as above, but with shape with command G.
+ OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf103474_commandG_CaseZeroHeight.odp";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.presentation.PresentationDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ // The end points of the straight line segment should have the same x-coordinate of left
+ // of shape, and different y-coordinates, one top and the other bottom of the shape.
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ EnhancedCustomShape2d aCustomShape2d(rSdrObjCustomShape);
+ SdrPathObjUniquePtr pPathObj(
+ static_cast<SdrPathObj*>(aCustomShape2d.CreateLineGeometry().release()));
+ CPPUNIT_ASSERT_MESSAGE("Could not convert to SdrPathObj", pPathObj);
+ const basegfx::B2DPolyPolygon aPolyPolygon(pPathObj->GetPathPoly());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("count polygons", static_cast<sal_uInt32>(1),
+ aPolyPolygon.count());
+ const basegfx::B2DPolygon aPolygon(aPolyPolygon.getB2DPolygon(0));
+ // Get the middle points of the polygon. They are the endpoints of the
+ // straight line segment regardless of the quarter ellipse parts, because
+ // the shape is symmetric.
+ const basegfx::B2DPoint aStart(aPolygon.getB2DPoint(aPolygon.count() / 2 - 1));
+ const basegfx::B2DPoint aEnd(aPolygon.getB2DPoint(aPolygon.count() / 2));
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aStart x-coordinate", 1999.0, aStart.getX(), 1.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aEnd x-coordinate", 1999.0, aEnd.getX(), 1.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aStart y-coordinate", 9999.0, aStart.getY(), 1.0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("aEnd y-coordinate", 1999.0, aEnd.getY(), 1.0);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf122323_largeSwingAngle)
+{
+ // SwingAngles are clamped to [-360;360] in MS Office. Error was, that LO calculated
+ // the end angle and used it modulo 360, no full ellipse was drawn.
+ OUString sURL
+ = m_directories.getURLFromSrc(sDataDirectory) + "tdf122323_swingAngle_larger360deg.pptx";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.presentation.PresentationDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ EnhancedCustomShape2d aCustomShape2d(rSdrObjCustomShape);
+ SdrPathObjUniquePtr pPathObj(
+ static_cast<SdrPathObj*>(aCustomShape2d.CreateLineGeometry().release()));
+ CPPUNIT_ASSERT_MESSAGE("Could not convert to SdrPathObj", pPathObj);
+ const basegfx::B2DPolyPolygon aPolyPolygon(pPathObj->GetPathPoly());
+ const basegfx::B2DPolygon aPolygon(aPolyPolygon.getB2DPolygon(0));
+ const basegfx::B2DPoint aStart(aPolygon.getB2DPoint(0));
+ // last point comes from line to center, therefore -2 instead of -1
+ const basegfx::B2DPoint aEnd(aPolygon.getB2DPoint(aPolygon.count() - 2));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Start <> End", aStart, aEnd);
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf141268)
+{
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf141268.odp";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.presentation.PresentationDocument");
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ SdrObjCustomShape& rSdrCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+
+ // Check left/bottom of bound rect. Without fix it would be left=6722, bottom=9483.
+ tools::Rectangle aBoundRect(rSdrCustomShape.GetCurrentBoundRect());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(7620), aBoundRect.Left());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(8584), aBoundRect.Bottom());
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf136176)
+{
+ // Error was, that fObjectRotation was not correctly updated after shearing.
+ // The problem becomes visible after save and reload.
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf136176_rot30_flip.odg";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ for (sal_uInt16 i = 0; i < 3; i++)
+ {
+ // get shape
+ uno::Reference<drawing::XShape> xShape(getShape(i));
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ // apply shearing 20deg
+ const Point aCenter = rSdrObjCustomShape.GetSnapRect().Center();
+ rSdrObjCustomShape.Shear(aCenter, 2000_deg100, tan(basegfx::deg2rad(20.0)), false);
+ }
+
+ // Save and reload
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("draw8");
+ xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+ mxComponent->dispose();
+ mxComponent = loadFromDesktop(aTempFile.GetURL());
+
+ // Expected values of point 4 of the shape polygon
+ const OString sTestCase[] = { "FlipH", "FlipV", "FlipHV" };
+ const double fX[] = { 14981.0, 3849.0, 15214.0 };
+ const double fY[] = { 9366.0, 16464.0, 23463.0 };
+
+ // Verify correct positions
+ for (sal_uInt16 i = 0; i < 3; i++)
+ {
+ // Get shape
+ const uno::Reference<drawing::XShape> xShape(getShape(i));
+ const SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ // Create polygon from shape and examine point 4 of the polygon
+ const basegfx::B2DPolyPolygon aLineGeometry = rSdrObjCustomShape.GetLineGeometry(false);
+ const basegfx::B2DPoint aPoint(aLineGeometry.getB2DPolygon(0).getB2DPoint(4));
+ // Allow some tolerance for rounding errors
+ if (fabs(aPoint.getX() - fX[i]) > 2.0 || fabs(aPoint.getY() - fY[i]) > 2.0)
+ {
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(sTestCase[i].getStr(), aPoint,
+ basegfx::B2DPoint(fX[i], fY[i]));
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf148501_OctagonBevel)
+{
+ // The document contains a shape "Octagon Bevel". It should use shadings 40%, 20%, -20%, -40%
+ // from left-top to bottom-right. The test examines actual color, not the geometry.
+ // Load document
+ OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf148501_OctagonBevel.odp";
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.presentation.PresentationDocument");
+
+ // Generate bitmap from shape
+ uno::Reference<drawing::XShape> xShape = getShape(0);
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ GraphicHelper::SaveShapeAsGraphicToPath(mxComponent, xShape, "image/png", aTempFile.GetURL());
+
+ // Read bitmap and test color
+ // expected in order top-left, top, top-right, right, bottom-right:
+ // RGB(165|195|266), RGB(139|176|217), RGB(91|127|166), RGB(68|95|124), RGB(68|95|124)
+ // Without applied patch the colors were:
+ // RGB(193|214,236), RGB(193|214,236), RGB(80|111|145), RGB(23|32|41), RGB(193|214|236)
+ // So we test segments top, right and bottom-right.
+ SvFileStream aFileStream(aTempFile.GetURL(), StreamMode::READ);
+ vcl::PngImageReader aPNGReader(aFileStream);
+ BitmapEx aBMPEx = aPNGReader.read();
+ Bitmap aBMP = aBMPEx.GetBitmap();
+ Bitmap::ScopedReadAccess pRead(aBMP);
+ Size aSize = aBMP.GetSizePixel();
+
+ // GetColor(Y,X). The chosen threshold for the ColorDistance can be adapted if necessary.
+ Color aActualColor = pRead->GetColor(aSize.Height() * 0.17, aSize.Width() * 0.5); // top
+ Color aExpectedColor(139, 176, 217);
+ sal_uInt16 nColorDistance = aExpectedColor.GetColorError(aActualColor);
+ CPPUNIT_ASSERT_LESS(sal_uInt16(6), nColorDistance);
+ aActualColor = pRead->GetColor(aSize.Height() * 0.5, aSize.Width() * 0.83); // right
+ aExpectedColor = Color(68, 95, 124); // same for right and bottom-right
+ nColorDistance = aExpectedColor.GetColorError(aActualColor);
+ CPPUNIT_ASSERT_LESS(sal_uInt16(6), nColorDistance);
+ aActualColor = pRead->GetColor(aSize.Height() * 0.75, aSize.Width() * 0.75); // bottom-right
+ nColorDistance = aExpectedColor.GetColorError(aActualColor);
+ CPPUNIT_ASSERT_LESS(sal_uInt16(6), nColorDistance);
+}
+
+bool lcl_getShapeSegments(uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments,
+ const uno::Reference<drawing::XShape>& xShape)
+{
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY_THROW);
+ uno::Any anotherAny = xShapeProps->getPropertyValue("CustomShapeGeometry");
+ uno::Sequence<beans::PropertyValue> aCustomShapeGeometry;
+ if (!(anotherAny >>= aCustomShapeGeometry))
+ return false;
+ uno::Sequence<beans::PropertyValue> aPathProps;
+ for (beans::PropertyValue const& rProp : std::as_const(aCustomShapeGeometry))
+ {
+ if (rProp.Name == "Path")
+ {
+ rProp.Value >>= aPathProps;
+ break;
+ }
+ }
+
+ for (beans::PropertyValue const& rProp : std::as_const(aPathProps))
+ {
+ if (rProp.Name == "Segments")
+ {
+ rProp.Value >>= rSegments;
+ break;
+ }
+ }
+ if (rSegments.getLength() > 2)
+ return true;
+ else
+ return false;
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf148714_CurvedArrows)
+{
+ // Error was, that the line between 1. and 2. arc was missing.
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf148714_CurvedArrows.ppt";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.presentation.PresentationDocument");
+
+ for (sal_Int32 nShapeIndex = 0; nShapeIndex < 4; nShapeIndex++)
+ {
+ uno::Reference<drawing::XShape> xShape(getShape(nShapeIndex));
+ uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments;
+ CPPUNIT_ASSERT(lcl_getShapeSegments(aSegments, xShape));
+
+ if (nShapeIndex == 0 || nShapeIndex == 3)
+ {
+ // curvedDownArrow or curvedLeftArrow. Segments should start with VW. Without fix it was
+ // V with count 2, which means VV.
+ CPPUNIT_ASSERT_EQUAL(
+ sal_Int16(drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARC),
+ aSegments[0].Command);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(1), aSegments[0].Count);
+ CPPUNIT_ASSERT_EQUAL(
+ sal_Int16(drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARCTO),
+ aSegments[1].Command);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(1), aSegments[1].Count);
+ }
+ else
+ {
+ // curvedUpArrow or curvedRightArrow. Segments should start with BA. Without fix is was
+ // B with count 2, which means BB.
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(drawing::EnhancedCustomShapeSegmentCommand::ARC),
+ aSegments[0].Command);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(1), aSegments[0].Count);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(drawing::EnhancedCustomShapeSegmentCommand::ARCTO),
+ aSegments[1].Command);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(1), aSegments[1].Count);
+ }
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf148707_two_commands_B_V)
+{
+ // tdf148707 custom shape with multiple command B or multiple command V were drawn with a line
+ // between the arcs as if the second command was a A or W respectively.
+ // The test document has a shape with path "V 0 0 50 100 0 50 25 0 50 0 100 100 75 0 100 50 N"
+ // and a shape with path "B 0 0 50 100 0 50 25 100 50 0 100 100 75 100 100 50 N".
+ OUString sURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf148707_two_commands_B_V.odp";
+ mxComponent = loadFromDesktop(sURL, "com.sun.star.comp.drawing.DrawingDocument");
+ for (sal_uInt8 i = 0; i < 2; i++)
+ {
+ uno::Reference<drawing::XShape> xShape(getShape(i));
+ // In case no line is drawn, two polygons are generated; with line only one polygon
+ SdrObjCustomShape& rSdrObjCustomShape(
+ static_cast<SdrObjCustomShape&>(*SdrObject::getSdrObjectFromXShape(xShape)));
+ EnhancedCustomShape2d aCustomShape2d(rSdrObjCustomShape);
+ SdrPathObjUniquePtr pPathObj(
+ static_cast<SdrPathObj*>(aCustomShape2d.CreateLineGeometry().release()));
+ CPPUNIT_ASSERT_MESSAGE("Could not convert to SdrPathObj", pPathObj);
+ const basegfx::B2DPolyPolygon aPolyPolygon(pPathObj->GetPathPoly());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("count polygons", sal_uInt32(2), aPolyPolygon.count());
+ }
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/data/0-width-text-wrap.pptx b/svx/qa/unit/data/0-width-text-wrap.pptx
new file mode 100644
index 000000000..17349924d
--- /dev/null
+++ b/svx/qa/unit/data/0-width-text-wrap.pptx
Binary files differ
diff --git a/svx/qa/unit/data/3d-object-fallback.odp b/svx/qa/unit/data/3d-object-fallback.odp
new file mode 100644
index 000000000..5ced0be47
--- /dev/null
+++ b/svx/qa/unit/data/3d-object-fallback.odp
Binary files differ
diff --git a/svx/qa/unit/data/FontWork.odg b/svx/qa/unit/data/FontWork.odg
new file mode 100644
index 000000000..e14d2f70b
--- /dev/null
+++ b/svx/qa/unit/data/FontWork.odg
Binary files differ
diff --git a/svx/qa/unit/data/GraphicObjectResolverTest.zip b/svx/qa/unit/data/GraphicObjectResolverTest.zip
new file mode 100644
index 000000000..4c19bf2b0
--- /dev/null
+++ b/svx/qa/unit/data/GraphicObjectResolverTest.zip
Binary files differ
diff --git a/svx/qa/unit/data/auto-height-multi-col-shape.pptx b/svx/qa/unit/data/auto-height-multi-col-shape.pptx
new file mode 100644
index 000000000..12f232b0f
--- /dev/null
+++ b/svx/qa/unit/data/auto-height-multi-col-shape.pptx
Binary files differ
diff --git a/svx/qa/unit/data/chart.ods b/svx/qa/unit/data/chart.ods
new file mode 100644
index 000000000..d9e9bdf18
--- /dev/null
+++ b/svx/qa/unit/data/chart.ods
Binary files differ
diff --git a/svx/qa/unit/data/graphic.pdf b/svx/qa/unit/data/graphic.pdf
new file mode 100644
index 000000000..4b53d2056
--- /dev/null
+++ b/svx/qa/unit/data/graphic.pdf
Binary files differ
diff --git a/svx/qa/unit/data/page-view-draw-layer-clip.docx b/svx/qa/unit/data/page-view-draw-layer-clip.docx
new file mode 100644
index 000000000..7136a800f
--- /dev/null
+++ b/svx/qa/unit/data/page-view-draw-layer-clip.docx
Binary files differ
diff --git a/svx/qa/unit/data/shadow-scale-origin.pptx b/svx/qa/unit/data/shadow-scale-origin.pptx
new file mode 100644
index 000000000..a0a164a3c
--- /dev/null
+++ b/svx/qa/unit/data/shadow-scale-origin.pptx
Binary files differ
diff --git a/svx/qa/unit/data/slide-background.odp b/svx/qa/unit/data/slide-background.odp
new file mode 100644
index 000000000..ea62bd639
--- /dev/null
+++ b/svx/qa/unit/data/slide-background.odp
Binary files differ
diff --git a/svx/qa/unit/data/slide-background.png b/svx/qa/unit/data/slide-background.png
new file mode 100644
index 000000000..3a8c5ceb4
--- /dev/null
+++ b/svx/qa/unit/data/slide-background.png
Binary files differ
diff --git a/svx/qa/unit/data/svx-dialogs-test.txt b/svx/qa/unit/data/svx-dialogs-test.txt
new file mode 100644
index 000000000..d9974a384
--- /dev/null
+++ b/svx/qa/unit/data/svx-dialogs-test.txt
@@ -0,0 +1,80 @@
+# -*- 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/.
+#
+
+# This file contains all dialogs that the unit tests in the module
+# will work on if it is in script mode. It will read one-by-one,
+# try to open it and create a screenshot that will be saved in
+# workdir/screenshots using the pattern of the ui-file name.
+#
+# Syntax:
+# - empty lines are allowed
+# - lines starting with '#' are treated as comment
+# - all other lines should contain a *.ui filename in the same
+# notation as in the dialog constructors (see code)
+
+#
+# The 'known' dialogs which have a hard-coded representation
+# in registerKnownDialogsByID/createDialogByID
+#
+
+# No known dialogs in svx for now
+
+#
+# Dialogs without a hard-coded representation. These will
+# be visualized using a fallback based on weld::Builder
+#
+
+# currently deactivated, leads to problems and the test to not work
+# This is typically a hint that these should be hard-coded in the
+# test case since they need some document and model data to work
+#
+# svx/ui/asianphoneticguidedialog.ui <- problems under linux
+
+svx/ui/textcontrolchardialog.ui
+svx/ui/textcontrolparadialog.ui
+svx/ui/datanavigator.ui
+svx/ui/redlineviewpage.ui
+svx/ui/redlinefilterpage.ui
+svx/ui/headfootformatpage.ui
+svx/ui/optgridpage.ui
+svx/ui/xformspage.ui
+svx/ui/compressgraphicdialog.ui
+svx/ui/compressgraphicdialog.ui
+svx/ui/docrecoveryprogressdialog.ui
+svx/ui/docrecoverybrokendialog.ui
+svx/ui/passwd.ui
+svx/ui/adddataitemdialog.ui
+svx/ui/addconditiondialog.ui
+svx/ui/namespacedialog.ui
+svx/ui/addnamespacedialog.ui
+svx/ui/addsubmissiondialog.ui
+svx/ui/addmodeldialog.ui
+svx/ui/addinstancedialog.ui
+svx/ui/extrustiondepthdialog.ui
+svx/ui/fontworkgallerydialog.ui
+svx/ui/fontworkspacingdialog.ui
+svx/ui/chinesedictionary.ui
+svx/ui/chineseconversiondialog.ui
+svx/ui/imapdialog.ui
+svx/ui/findreplacedialog.ui
+svx/ui/crashreportdlg.ui
+svx/ui/docrecoverysavedialog.ui
+svx/ui/docrecoveryrecoverdialog.ui
+svx/ui/querysavecontchangesdialog.ui
+svx/ui/querydeletecontourdialog.ui
+svx/ui/queryunlinkgraphicsdialog.ui
+svx/ui/querynewcontourdialog.ui
+svx/ui/querymodifyimagemapchangesdialog.ui
+svx/ui/querysaveimagemapchangesdialog.ui
+svx/ui/querysaveimagemapchangesdialog.ui
+svx/ui/linkwarndialog.ui
+svx/ui/formlinkwarndialog.ui
+svx/ui/savemodifieddialog.ui
+svx/ui/querydeletethemedialog.ui
+svx/ui/querydeleteobjectdialog.ui
diff --git a/svx/qa/unit/data/table-shadow-blur.pptx b/svx/qa/unit/data/table-shadow-blur.pptx
new file mode 100644
index 000000000..959940e09
--- /dev/null
+++ b/svx/qa/unit/data/table-shadow-blur.pptx
Binary files differ
diff --git a/svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odp b/svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odp
new file mode 100644
index 000000000..9b36d45ee
--- /dev/null
+++ b/svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf103474_commandT_CaseZeroHeight.odp b/svx/qa/unit/data/tdf103474_commandT_CaseZeroHeight.odp
new file mode 100644
index 000000000..54a4377ca
--- /dev/null
+++ b/svx/qa/unit/data/tdf103474_commandT_CaseZeroHeight.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf115813_HandleMovementOOXMLPresetShapes.pptx b/svx/qa/unit/data/tdf115813_HandleMovementOOXMLPresetShapes.pptx
new file mode 100644
index 000000000..3435fef61
--- /dev/null
+++ b/svx/qa/unit/data/tdf115813_HandleMovementOOXMLPresetShapes.pptx
Binary files differ
diff --git a/svx/qa/unit/data/tdf121761_Accuracy_command_X.odp b/svx/qa/unit/data/tdf121761_Accuracy_command_X.odp
new file mode 100644
index 000000000..1de391758
--- /dev/null
+++ b/svx/qa/unit/data/tdf121761_Accuracy_command_X.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf121845_HalfEllipseVML.doc b/svx/qa/unit/data/tdf121845_HalfEllipseVML.doc
new file mode 100644
index 000000000..043e4e15f
--- /dev/null
+++ b/svx/qa/unit/data/tdf121845_HalfEllipseVML.doc
Binary files differ
diff --git a/svx/qa/unit/data/tdf121845_Two_commands_U.odg b/svx/qa/unit/data/tdf121845_Two_commands_U.odg
new file mode 100644
index 000000000..c0f7ff34f
--- /dev/null
+++ b/svx/qa/unit/data/tdf121845_Two_commands_U.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg b/svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg
new file mode 100644
index 000000000..349c2eb81
--- /dev/null
+++ b/svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf121845_start40_swing480.doc b/svx/qa/unit/data/tdf121845_start40_swing480.doc
new file mode 100644
index 000000000..ff37aab3f
--- /dev/null
+++ b/svx/qa/unit/data/tdf121845_start40_swing480.doc
Binary files differ
diff --git a/svx/qa/unit/data/tdf121952_Toggle_direction_command_X.odp b/svx/qa/unit/data/tdf121952_Toggle_direction_command_X.odp
new file mode 100644
index 000000000..fbe1b7d40
--- /dev/null
+++ b/svx/qa/unit/data/tdf121952_Toggle_direction_command_X.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptx b/svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptx
new file mode 100644
index 000000000..919675ef9
--- /dev/null
+++ b/svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptx
Binary files differ
diff --git a/svx/qa/unit/data/tdf122964_MultipleMoveTo.odg b/svx/qa/unit/data/tdf122964_MultipleMoveTo.odg
new file mode 100644
index 000000000..63d80fd06
--- /dev/null
+++ b/svx/qa/unit/data/tdf122964_MultipleMoveTo.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf124029_Arc_position.doc b/svx/qa/unit/data/tdf124029_Arc_position.doc
new file mode 100644
index 000000000..d5396c375
--- /dev/null
+++ b/svx/qa/unit/data/tdf124029_Arc_position.doc
Binary files differ
diff --git a/svx/qa/unit/data/tdf124212_handle_position.odg b/svx/qa/unit/data/tdf124212_handle_position.odg
new file mode 100644
index 000000000..7a4eb0517
--- /dev/null
+++ b/svx/qa/unit/data/tdf124212_handle_position.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf124740_HandleInOOXMLUserShape.pptx b/svx/qa/unit/data/tdf124740_HandleInOOXMLUserShape.pptx
new file mode 100644
index 000000000..dd6df7b03
--- /dev/null
+++ b/svx/qa/unit/data/tdf124740_HandleInOOXMLUserShape.pptx
Binary files differ
diff --git a/svx/qa/unit/data/tdf125782_QuadraticCurveTo.odg b/svx/qa/unit/data/tdf125782_QuadraticCurveTo.odg
new file mode 100644
index 000000000..ba7b49536
--- /dev/null
+++ b/svx/qa/unit/data/tdf125782_QuadraticCurveTo.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf126060_3D_Z_Rotation.pptx b/svx/qa/unit/data/tdf126060_3D_Z_Rotation.pptx
new file mode 100644
index 000000000..8c8798f21
--- /dev/null
+++ b/svx/qa/unit/data/tdf126060_3D_Z_Rotation.pptx
Binary files differ
diff --git a/svx/qa/unit/data/tdf126512_OOXMLHandleMovementInODF.odp b/svx/qa/unit/data/tdf126512_OOXMLHandleMovementInODF.odp
new file mode 100644
index 000000000..7dd283f88
--- /dev/null
+++ b/svx/qa/unit/data/tdf126512_OOXMLHandleMovementInODF.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf127785_Mirror.odp b/svx/qa/unit/data/tdf127785_Mirror.odp
new file mode 100644
index 000000000..ff867839f
--- /dev/null
+++ b/svx/qa/unit/data/tdf127785_Mirror.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf127785_TextRotateAngle.odp b/svx/qa/unit/data/tdf127785_TextRotateAngle.odp
new file mode 100644
index 000000000..742f12d4d
--- /dev/null
+++ b/svx/qa/unit/data/tdf127785_TextRotateAngle.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf127785_asymmetricTextBoxFlipV.odg b/svx/qa/unit/data/tdf127785_asymmetricTextBoxFlipV.odg
new file mode 100644
index 000000000..ea700eaf7
--- /dev/null
+++ b/svx/qa/unit/data/tdf127785_asymmetricTextBoxFlipV.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf128413_tbrl_OnOff.odp b/svx/qa/unit/data/tdf128413_tbrl_OnOff.odp
new file mode 100644
index 000000000..f10f98bbe
--- /dev/null
+++ b/svx/qa/unit/data/tdf128413_tbrl_OnOff.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf129532_MatrixFlipV.odg b/svx/qa/unit/data/tdf129532_MatrixFlipV.odg
new file mode 100644
index 000000000..eb0c10b3d
--- /dev/null
+++ b/svx/qa/unit/data/tdf129532_MatrixFlipV.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf130076_FlipOnSectorSection.odg b/svx/qa/unit/data/tdf130076_FlipOnSectorSection.odg
new file mode 100644
index 000000000..058e7e044
--- /dev/null
+++ b/svx/qa/unit/data/tdf130076_FlipOnSectorSection.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf136176_rot30_flip.odg b/svx/qa/unit/data/tdf136176_rot30_flip.odg
new file mode 100644
index 000000000..75707a0f5
--- /dev/null
+++ b/svx/qa/unit/data/tdf136176_rot30_flip.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf138945_resizeRotatedShape.odg b/svx/qa/unit/data/tdf138945_resizeRotatedShape.odg
new file mode 100644
index 000000000..20a2825af
--- /dev/null
+++ b/svx/qa/unit/data/tdf138945_resizeRotatedShape.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf140321_Matte_import.ppt b/svx/qa/unit/data/tdf140321_Matte_import.ppt
new file mode 100644
index 000000000..83e790fd4
--- /dev/null
+++ b/svx/qa/unit/data/tdf140321_Matte_import.ppt
Binary files differ
diff --git a/svx/qa/unit/data/tdf140321_material_specular.odp b/svx/qa/unit/data/tdf140321_material_specular.odp
new file mode 100644
index 000000000..03869914f
--- /dev/null
+++ b/svx/qa/unit/data/tdf140321_material_specular.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf140321_metal.odp b/svx/qa/unit/data/tdf140321_metal.odp
new file mode 100644
index 000000000..a81ee0dfb
--- /dev/null
+++ b/svx/qa/unit/data/tdf140321_metal.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf140321_phong.odp b/svx/qa/unit/data/tdf140321_phong.odp
new file mode 100644
index 000000000..989a08485
--- /dev/null
+++ b/svx/qa/unit/data/tdf140321_phong.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf141021_ExtrusionNorth.odp b/svx/qa/unit/data/tdf141021_ExtrusionNorth.odp
new file mode 100644
index 000000000..559b2c0d5
--- /dev/null
+++ b/svx/qa/unit/data/tdf141021_ExtrusionNorth.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf141268.odp b/svx/qa/unit/data/tdf141268.odp
new file mode 100644
index 000000000..41d0dc446
--- /dev/null
+++ b/svx/qa/unit/data/tdf141268.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf144988_Fontwork_FontSize.odp b/svx/qa/unit/data/tdf144988_Fontwork_FontSize.odp
new file mode 100644
index 000000000..943bc143b
--- /dev/null
+++ b/svx/qa/unit/data/tdf144988_Fontwork_FontSize.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf145004_gap_by_ScaleX.pptx b/svx/qa/unit/data/tdf145004_gap_by_ScaleX.pptx
new file mode 100644
index 000000000..900a89675
--- /dev/null
+++ b/svx/qa/unit/data/tdf145004_gap_by_ScaleX.pptx
Binary files differ
diff --git a/svx/qa/unit/data/tdf145111_TL_BL_Fontwork.odp b/svx/qa/unit/data/tdf145111_TL_BL_Fontwork.odp
new file mode 100644
index 000000000..257023cfa
--- /dev/null
+++ b/svx/qa/unit/data/tdf145111_TL_BL_Fontwork.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf145245_ExtrusionPosition.odp b/svx/qa/unit/data/tdf145245_ExtrusionPosition.odp
new file mode 100644
index 000000000..a356cf9ed
--- /dev/null
+++ b/svx/qa/unit/data/tdf145245_ExtrusionPosition.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf145700_3D_FirstLightHarsh.doc b/svx/qa/unit/data/tdf145700_3D_FirstLightHarsh.doc
new file mode 100644
index 000000000..28b8b018d
--- /dev/null
+++ b/svx/qa/unit/data/tdf145700_3D_FirstLightHarsh.doc
Binary files differ
diff --git a/svx/qa/unit/data/tdf145700_3D_FrontLightDim.doc b/svx/qa/unit/data/tdf145700_3D_FrontLightDim.doc
new file mode 100644
index 000000000..5849e3eac
--- /dev/null
+++ b/svx/qa/unit/data/tdf145700_3D_FrontLightDim.doc
Binary files differ
diff --git a/svx/qa/unit/data/tdf145700_3D_NonUI.doc b/svx/qa/unit/data/tdf145700_3D_NonUI.doc
new file mode 100644
index 000000000..d62d57cf0
--- /dev/null
+++ b/svx/qa/unit/data/tdf145700_3D_NonUI.doc
Binary files differ
diff --git a/svx/qa/unit/data/tdf145904_center_Y0dot25.doc b/svx/qa/unit/data/tdf145904_center_Y0dot25.doc
new file mode 100644
index 000000000..f4f9b28f2
--- /dev/null
+++ b/svx/qa/unit/data/tdf145904_center_Y0dot25.doc
Binary files differ
diff --git a/svx/qa/unit/data/tdf145904_center_Y0dot25.odt b/svx/qa/unit/data/tdf145904_center_Y0dot25.odt
new file mode 100644
index 000000000..fcdbbff6c
--- /dev/null
+++ b/svx/qa/unit/data/tdf145904_center_Y0dot25.odt
Binary files differ
diff --git a/svx/qa/unit/data/tdf145904_center_Zminus2000.odt b/svx/qa/unit/data/tdf145904_center_Zminus2000.odt
new file mode 100644
index 000000000..9a19f4cbd
--- /dev/null
+++ b/svx/qa/unit/data/tdf145904_center_Zminus2000.odt
Binary files differ
diff --git a/svx/qa/unit/data/tdf145956_Origin.odp b/svx/qa/unit/data/tdf145956_Origin.odp
new file mode 100644
index 000000000..7bba1dadd
--- /dev/null
+++ b/svx/qa/unit/data/tdf145956_Origin.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf147409_GeomItemHash.odg b/svx/qa/unit/data/tdf147409_GeomItemHash.odg
new file mode 100644
index 000000000..770b7a6c0
--- /dev/null
+++ b/svx/qa/unit/data/tdf147409_GeomItemHash.odg
Binary files differ
diff --git a/svx/qa/unit/data/tdf148501_OctagonBevel.odp b/svx/qa/unit/data/tdf148501_OctagonBevel.odp
new file mode 100644
index 000000000..9dafaf7c2
--- /dev/null
+++ b/svx/qa/unit/data/tdf148501_OctagonBevel.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf148707_two_commands_B_V.odp b/svx/qa/unit/data/tdf148707_two_commands_B_V.odp
new file mode 100644
index 000000000..c26d371ef
--- /dev/null
+++ b/svx/qa/unit/data/tdf148707_two_commands_B_V.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf148714_CurvedArrows.ppt b/svx/qa/unit/data/tdf148714_CurvedArrows.ppt
new file mode 100644
index 000000000..23e4ed0ad
--- /dev/null
+++ b/svx/qa/unit/data/tdf148714_CurvedArrows.ppt
Binary files differ
diff --git a/svx/qa/unit/data/tdf60684.jpg b/svx/qa/unit/data/tdf60684.jpg
new file mode 100644
index 000000000..2218cdd72
--- /dev/null
+++ b/svx/qa/unit/data/tdf60684.jpg
Binary files differ
diff --git a/svx/qa/unit/data/tdf93998.odp b/svx/qa/unit/data/tdf93998.odp
new file mode 100644
index 000000000..889aeeb02
--- /dev/null
+++ b/svx/qa/unit/data/tdf93998.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf98583_ShearHorizontal.odp b/svx/qa/unit/data/tdf98583_ShearHorizontal.odp
new file mode 100644
index 000000000..37719e825
--- /dev/null
+++ b/svx/qa/unit/data/tdf98583_ShearHorizontal.odp
Binary files differ
diff --git a/svx/qa/unit/data/tdf98584_ShearVertical.odg b/svx/qa/unit/data/tdf98584_ShearVertical.odg
new file mode 100644
index 000000000..457521d50
--- /dev/null
+++ b/svx/qa/unit/data/tdf98584_ShearVertical.odg
Binary files differ
diff --git a/svx/qa/unit/data/theme.pptx b/svx/qa/unit/data/theme.pptx
new file mode 100644
index 000000000..08e4a3b6f
--- /dev/null
+++ b/svx/qa/unit/data/theme.pptx
Binary files differ
diff --git a/svx/qa/unit/data/unodraw-writer-image.odt b/svx/qa/unit/data/unodraw-writer-image.odt
new file mode 100644
index 000000000..5264118a3
--- /dev/null
+++ b/svx/qa/unit/data/unodraw-writer-image.odt
Binary files differ
diff --git a/svx/qa/unit/data/video-snapshot.pptx b/svx/qa/unit/data/video-snapshot.pptx
new file mode 100644
index 000000000..76f7c0d50
--- /dev/null
+++ b/svx/qa/unit/data/video-snapshot.pptx
Binary files differ
diff --git a/svx/qa/unit/data/viewBox_positive_twolines_strict.odp b/svx/qa/unit/data/viewBox_positive_twolines_strict.odp
new file mode 100644
index 000000000..3425582b8
--- /dev/null
+++ b/svx/qa/unit/data/viewBox_positive_twolines_strict.odp
Binary files differ
diff --git a/svx/qa/unit/gallery/data/galtest1.png b/svx/qa/unit/gallery/data/galtest1.png
new file mode 100644
index 000000000..37be81f9d
--- /dev/null
+++ b/svx/qa/unit/gallery/data/galtest1.png
Binary files differ
diff --git a/svx/qa/unit/gallery/data/galtest2.png b/svx/qa/unit/gallery/data/galtest2.png
new file mode 100644
index 000000000..bf1aac397
--- /dev/null
+++ b/svx/qa/unit/gallery/data/galtest2.png
Binary files differ
diff --git a/svx/qa/unit/gallery/data/galtest3.jpg b/svx/qa/unit/gallery/data/galtest3.jpg
new file mode 100644
index 000000000..59f67e9aa
--- /dev/null
+++ b/svx/qa/unit/gallery/data/galtest3.jpg
Binary files differ
diff --git a/svx/qa/unit/gallery/test_gallery.cxx b/svx/qa/unit/gallery/test_gallery.cxx
new file mode 100644
index 000000000..da224b439
--- /dev/null
+++ b/svx/qa/unit/gallery/test_gallery.cxx
@@ -0,0 +1,482 @@
+/* -*- 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/bootstrapfixture.hxx>
+
+#include <tools/urlobj.hxx>
+#include <sfx2/app.hxx>
+#include <unotools/tempfile.hxx>
+#include <comphelper/DirectoryHelper.hxx>
+
+#include <svx/gallery1.hxx>
+#include <svx/galtheme.hxx>
+#include <svx/gallerybinarystoragelocations.hxx>
+#include <svx/gallerystoragelocations.hxx>
+#include <galobj.hxx>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+class GalleryObjTest : public test::BootstrapFixture
+{
+public:
+ void TestCreateTheme();
+ void TestDeleteTheme();
+ void TestSetThemeName();
+ void TestThemeURLCase();
+ void TestThemeCount();
+ void TestGalleryThemeEntry();
+ void TestInsertGalleryObject();
+ void TestRemoveGalleryObject();
+ void TestChangePositionGalleryObject();
+ void TestGetThemeNameFromGalleryTheme();
+
+ CPPUNIT_TEST_SUITE(GalleryObjTest);
+
+ CPPUNIT_TEST(TestCreateTheme);
+ CPPUNIT_TEST(TestDeleteTheme);
+ CPPUNIT_TEST(TestSetThemeName);
+ CPPUNIT_TEST(TestThemeURLCase);
+ CPPUNIT_TEST(TestThemeCount);
+ CPPUNIT_TEST(TestGalleryThemeEntry);
+ CPPUNIT_TEST(TestInsertGalleryObject);
+ CPPUNIT_TEST(TestRemoveGalleryObject);
+ CPPUNIT_TEST(TestChangePositionGalleryObject);
+ CPPUNIT_TEST(TestGetThemeNameFromGalleryTheme);
+
+ CPPUNIT_TEST_SUITE_END();
+};
+
+// Create and Dereference a theme, check that file exists
+void GalleryObjTest::TestCreateTheme()
+{
+ // Create theme
+ std::unique_ptr<utl::TempFile> pTempDir;
+ pTempDir.reset(new utl::TempFile(nullptr, true));
+ CPPUNIT_ASSERT_MESSAGE("Could not create valid temporary directory", pTempDir->IsValid());
+ pTempDir->EnableKillingFile();
+ const OUString aGalleryURL = pTempDir->GetURL();
+
+ // Check if directory exists
+ CPPUNIT_ASSERT_MESSAGE("Could not create temporary directory",
+ comphelper::DirectoryHelper::dirExists(aGalleryURL));
+
+ std::unique_ptr<Gallery> pGallery(new Gallery(aGalleryURL));
+ CPPUNIT_ASSERT_MESSAGE("Could not create gallery instance", (pGallery != nullptr));
+ static const OUStringLiteral myThemeName = u"addytesttheme";
+ CPPUNIT_ASSERT_MESSAGE("Could not create theme", pGallery->CreateTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find theme", pGallery->HasTheme(myThemeName));
+
+ // check if files exist
+ CPPUNIT_ASSERT_MESSAGE(
+ "Could not find .thm file inside it",
+ comphelper::DirectoryHelper::fileExists(aGalleryURL + "/" + myThemeName + ".thm"));
+ CPPUNIT_ASSERT_MESSAGE(
+ "Could not find .sdv file inside it",
+ comphelper::DirectoryHelper::fileExists(aGalleryURL + "/" + myThemeName + ".sdv"));
+}
+
+// Create and Delete Theme, check the file doesn't exist
+void GalleryObjTest::TestDeleteTheme()
+{
+ // Create theme
+ std::unique_ptr<utl::TempFile> pTempDir;
+ pTempDir.reset(new utl::TempFile(nullptr, true));
+ CPPUNIT_ASSERT_MESSAGE("Could not create valid temporary directory", pTempDir->IsValid());
+ pTempDir->EnableKillingFile();
+ const OUString aGalleryURL = pTempDir->GetURL();
+
+ std::unique_ptr<Gallery> pGallery(new Gallery(aGalleryURL));
+ CPPUNIT_ASSERT_MESSAGE("Could not create gallery instance", (pGallery != nullptr));
+ static const OUStringLiteral myThemeName = u"addytesttheme";
+ CPPUNIT_ASSERT_MESSAGE("Could not create theme", pGallery->CreateTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find theme", pGallery->HasTheme(myThemeName));
+
+ // Delete Theme
+ CPPUNIT_ASSERT_MESSAGE("Could not remove theme", pGallery->RemoveTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not remove theme, theme found even after trying to remove",
+ !pGallery->HasTheme(myThemeName));
+
+ // Check that files do not exist
+ CPPUNIT_ASSERT_MESSAGE(
+ "Found .thm file inside it after deletion",
+ !comphelper::DirectoryHelper::fileExists(aGalleryURL + "/" + myThemeName + ".thm"));
+ CPPUNIT_ASSERT_MESSAGE(
+ "Found .sdv file inside it after deletion",
+ !comphelper::DirectoryHelper::fileExists(aGalleryURL + "/" + myThemeName + ".sdv"));
+ CPPUNIT_ASSERT_MESSAGE(
+ "Found .sdg file inside it after deletion",
+ !comphelper::DirectoryHelper::fileExists(aGalleryURL + "/" + myThemeName + ".sdg"));
+ CPPUNIT_ASSERT_MESSAGE(
+ "Found .str file inside it after deletion",
+ !comphelper::DirectoryHelper::fileExists(aGalleryURL + "/" + myThemeName + ".str"));
+}
+
+// Create theme, set name, assert the name is expected
+void GalleryObjTest::TestSetThemeName()
+{
+ // Create theme
+ std::unique_ptr<utl::TempFile> pTempDir;
+ pTempDir.reset(new utl::TempFile(nullptr, true));
+ CPPUNIT_ASSERT_MESSAGE("Could not create valid temporary directory", pTempDir->IsValid());
+ pTempDir->EnableKillingFile();
+ const OUString aGalleryURL = pTempDir->GetURL();
+
+ std::unique_ptr<Gallery> pGallery(new Gallery(aGalleryURL));
+ CPPUNIT_ASSERT_MESSAGE("Could not create gallery instance", (pGallery != nullptr));
+ static const OUStringLiteral myThemeName = u"addytesttheme";
+ CPPUNIT_ASSERT_MESSAGE("Could not create theme", pGallery->CreateTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find theme", pGallery->HasTheme(myThemeName));
+
+ // Rename theme
+ static const OUStringLiteral myNewThemeName = u"addytestthemenew";
+ pGallery->RenameTheme(myThemeName, myNewThemeName);
+ CPPUNIT_ASSERT_MESSAGE("Could not rename theme because old theme name still exists",
+ !pGallery->HasTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find new renamed theme", pGallery->HasTheme(myNewThemeName));
+
+ // Check that files are not renamed
+ CPPUNIT_ASSERT_MESSAGE(
+ "Could not find .thm file inside it",
+ comphelper::DirectoryHelper::fileExists(aGalleryURL + "/" + myThemeName + ".thm"));
+ CPPUNIT_ASSERT_MESSAGE(
+ "Could not find .sdv file inside it",
+ comphelper::DirectoryHelper::fileExists(aGalleryURL + "/" + myThemeName + ".sdv"));
+}
+
+void GalleryObjTest::TestThemeURLCase()
+{
+ // Create theme
+ std::unique_ptr<utl::TempFile> pTempDir;
+ pTempDir.reset(new utl::TempFile(nullptr, true));
+ CPPUNIT_ASSERT_MESSAGE("Could not create valid temporary directory", pTempDir->IsValid());
+ pTempDir->EnableKillingFile();
+ const OUString aGalleryURL = pTempDir->GetURL();
+
+ // Check if directory exists
+ CPPUNIT_ASSERT_MESSAGE("Could not create temporary directory",
+ comphelper::DirectoryHelper::dirExists(aGalleryURL));
+
+ std::unique_ptr<Gallery> pGallery(new Gallery(aGalleryURL));
+ CPPUNIT_ASSERT_MESSAGE("Could not create gallery instance", (pGallery != nullptr));
+
+ // Mixed Case Theme Name
+ const OUString myThemeName = "AddyTestTheme";
+
+ CPPUNIT_ASSERT_MESSAGE("Could not create theme", pGallery->CreateTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find theme", pGallery->HasTheme(myThemeName));
+
+#if defined(LINUX)
+ CPPUNIT_ASSERT_MESSAGE("[LINUX] Could not find .thm in lowercase",
+ comphelper::DirectoryHelper::fileExists(
+ aGalleryURL + "/" + myThemeName.toAsciiLowerCase() + ".thm"));
+ CPPUNIT_ASSERT_MESSAGE("[LINUX] Could not find .sdv in lowercase",
+ comphelper::DirectoryHelper::fileExists(
+ aGalleryURL + "/" + myThemeName.toAsciiLowerCase() + ".sdv"));
+#else
+ CPPUNIT_ASSERT_MESSAGE(
+ "[WINDOWS] Could not find .thm in mixed case",
+ comphelper::DirectoryHelper::fileExists(aGalleryURL + "/" + myThemeName + ".thm"));
+ CPPUNIT_ASSERT_MESSAGE(
+ "[WINDOWS] Could not find .sdv in mixed case",
+ comphelper::DirectoryHelper::fileExists(aGalleryURL + "/" + myThemeName + ".sdv"));
+#endif
+}
+
+void GalleryObjTest::TestThemeCount()
+{
+ std::unique_ptr<utl::TempFile> pTempDir;
+ pTempDir.reset(new utl::TempFile(nullptr, true));
+ CPPUNIT_ASSERT_MESSAGE("Could not create valid temporary directory", pTempDir->IsValid());
+ pTempDir->EnableKillingFile();
+ const OUString aGalleryURL = pTempDir->GetURL();
+
+ // Check if directory exists
+ CPPUNIT_ASSERT_MESSAGE("Could not create temporary directory",
+ comphelper::DirectoryHelper::dirExists(aGalleryURL));
+
+ std::unique_ptr<Gallery> pGallery(new Gallery(aGalleryURL));
+ CPPUNIT_ASSERT_MESSAGE("Could not create gallery instance", (pGallery != nullptr));
+
+ // Loop through and test theme count in each pass.
+ sal_uInt32 nLimit = 10;
+ for (sal_uInt32 i = 1; i <= nLimit; i++)
+ {
+ OUString myThemeName = "addytesttheme" + OUString::number(i);
+ // Create theme
+ CPPUNIT_ASSERT_MESSAGE("Could not create theme", pGallery->CreateTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find theme", pGallery->HasTheme(myThemeName));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Inconsistent theme count",
+ static_cast<sal_uInt32>(pGallery->GetThemeCount()), i);
+ }
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Inconsistent theme count",
+ static_cast<sal_uInt32>(pGallery->GetThemeCount()), nLimit);
+ for (sal_uInt32 i = nLimit; i > 0; i--)
+ {
+ OUString myThemeName = "addytesttheme" + OUString::number(i);
+ // Delete Theme
+ CPPUNIT_ASSERT_MESSAGE("Could not remove theme", pGallery->RemoveTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not remove theme, theme found even after trying to remove",
+ !pGallery->HasTheme(myThemeName));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Inconsistent theme count",
+ static_cast<sal_uInt32>(pGallery->GetThemeCount()), i - 1);
+ }
+}
+
+void GalleryObjTest::TestGalleryThemeEntry()
+{
+ // Create theme
+ std::unique_ptr<utl::TempFile> pTempDir;
+ pTempDir.reset(new utl::TempFile(nullptr, true));
+ CPPUNIT_ASSERT_MESSAGE("Could not create valid temporary directory", pTempDir->IsValid());
+ pTempDir->EnableKillingFile();
+ const OUString aGalleryURL = pTempDir->GetURL();
+
+ // Check if directory exists
+ CPPUNIT_ASSERT_MESSAGE("Could not create temporary directory",
+ comphelper::DirectoryHelper::dirExists(aGalleryURL));
+
+ std::unique_ptr<Gallery> pGallery(new Gallery(aGalleryURL));
+ CPPUNIT_ASSERT_MESSAGE("Could not create gallery instance", (pGallery != nullptr));
+ const OUString myThemeName = "addytesttheme";
+ CPPUNIT_ASSERT_MESSAGE("Could not create theme", pGallery->CreateTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find theme", pGallery->HasTheme(myThemeName));
+
+ // Get Theme Entry Object
+ const GalleryThemeEntry* mpThemeEntry = pGallery->GetThemeInfo(myThemeName);
+
+ // Check Theme Name
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Theme name doesn't match", mpThemeEntry->GetThemeName(),
+ myThemeName);
+
+ // Check URLs
+ GalleryBinaryStorageLocations& aGalleryBinaryStorageLocations
+ = dynamic_cast<GalleryBinaryStorageLocations&>(mpThemeEntry->getGalleryStorageLocations());
+ INetURLObject aURL(aGalleryURL);
+ aURL.Append(myThemeName);
+ INetURLObject aThemeURL(aURL), aSdvURL(aURL), aSdgURL(aURL), aStrURL(aURL);
+ aThemeURL.setExtension(u"thm");
+ aSdvURL.setExtension(u"sdv");
+ aSdgURL.setExtension(u"sdg");
+ aStrURL.setExtension(u"str");
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Theme URL doesn't match",
+ aGalleryBinaryStorageLocations.GetThmURL().GetMainURL(
+ INetURLObject::DecodeMechanism::Unambiguous),
+ aThemeURL.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Sdv URL doesn't match",
+ aGalleryBinaryStorageLocations.GetSdvURL().GetMainURL(
+ INetURLObject::DecodeMechanism::Unambiguous),
+ aSdvURL.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Sdg URL doesn't match",
+ aGalleryBinaryStorageLocations.GetSdgURL().GetMainURL(
+ INetURLObject::DecodeMechanism::Unambiguous),
+ aSdgURL.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Str URL doesn't match",
+ aGalleryBinaryStorageLocations.GetStrURL().GetMainURL(
+ INetURLObject::DecodeMechanism::Unambiguous),
+ aStrURL.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous));
+}
+
+void GalleryObjTest::TestInsertGalleryObject()
+{
+ // Create theme
+ std::unique_ptr<utl::TempFile> pTempDir;
+ pTempDir.reset(new utl::TempFile(nullptr, true));
+ CPPUNIT_ASSERT_MESSAGE("Could not create valid temporary directory", pTempDir->IsValid());
+ pTempDir->EnableKillingFile();
+ const OUString aGalleryURL = pTempDir->GetURL();
+
+ // Check if directory exists
+ CPPUNIT_ASSERT_MESSAGE("Could not create temporary directory",
+ comphelper::DirectoryHelper::dirExists(aGalleryURL));
+
+ std::unique_ptr<Gallery> pGallery(new Gallery(aGalleryURL));
+ CPPUNIT_ASSERT_MESSAGE("Could not create gallery instance", (pGallery != nullptr));
+ static const OUStringLiteral myThemeName = u"addytesttheme";
+ CPPUNIT_ASSERT_MESSAGE("Could not create theme", pGallery->CreateTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find theme", pGallery->HasTheme(myThemeName));
+
+ // Create Sfx Instance
+ SfxListener aListener;
+ SfxApplication::GetOrCreate();
+
+ // Insert Objects Into Theme
+ GalleryTheme* pGalleryTheme = pGallery->AcquireTheme(myThemeName, aListener);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Object count inconsistent", sal_uInt32(0),
+ pGalleryTheme->GetObjectCount());
+
+ std::vector<OUString> imageList{ "galtest1.png", "galtest2.png", "galtest3.jpg" };
+
+ for (sal_uInt32 i = 0; i < static_cast<sal_uInt32>(imageList.size()); i++)
+ {
+ OUString imageNameFromList(imageList[i]);
+ OUString aURL(m_directories.getURLFromSrc(u"/svx/qa/unit/gallery/data/")
+ + imageNameFromList);
+ CPPUNIT_ASSERT_MESSAGE("Could not insert object into theme",
+ pGalleryTheme->InsertURL(INetURLObject(aURL)));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Inconsistent object Count", pGalleryTheme->GetObjectCount(),
+ i + 1);
+ std::unique_ptr<SgaObject> pObj = pGalleryTheme->AcquireObject(i);
+ CPPUNIT_ASSERT_MESSAGE("Acquired Object Invalid", pObj->IsValid());
+ }
+ pGallery->ReleaseTheme(pGalleryTheme, aListener);
+}
+
+void GalleryObjTest::TestRemoveGalleryObject()
+{
+ // Create theme
+ std::unique_ptr<utl::TempFile> pTempDir;
+ pTempDir.reset(new utl::TempFile(nullptr, true));
+ CPPUNIT_ASSERT_MESSAGE("Could not create valid temporary directory", pTempDir->IsValid());
+ pTempDir->EnableKillingFile();
+ const OUString aGalleryURL = pTempDir->GetURL();
+
+ // Check if directory exists
+ CPPUNIT_ASSERT_MESSAGE("Could not create temporary directory",
+ comphelper::DirectoryHelper::dirExists(aGalleryURL));
+
+ std::unique_ptr<Gallery> pGallery(new Gallery(aGalleryURL));
+ CPPUNIT_ASSERT_MESSAGE("Could not create gallery instance", (pGallery != nullptr));
+ static const OUStringLiteral myThemeName = u"addytesttheme";
+ CPPUNIT_ASSERT_MESSAGE("Could not create theme", pGallery->CreateTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find theme", pGallery->HasTheme(myThemeName));
+
+ // Create Sfx Instance
+ SfxListener aListener;
+ SfxApplication::GetOrCreate();
+
+ // Insert Objects Into Theme
+ GalleryTheme* pGalleryTheme = pGallery->AcquireTheme(myThemeName, aListener);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Object count inconsistent", sal_uInt32(0),
+ pGalleryTheme->GetObjectCount());
+
+ std::vector<OUString> imageList{ "galtest1.png", "galtest2.png", "galtest3.jpg" };
+
+ for (sal_uInt32 i = 0; i < static_cast<sal_uInt32>(imageList.size()); i++)
+ {
+ OUString imageNameFromList(imageList[i]);
+ OUString aURL(m_directories.getURLFromSrc(u"/svx/qa/unit/gallery/data/")
+ + imageNameFromList);
+ CPPUNIT_ASSERT_MESSAGE("Could not insert object into theme",
+ pGalleryTheme->InsertURL(INetURLObject(aURL)));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Inconsistent object Count", pGalleryTheme->GetObjectCount(),
+ i + 1);
+ std::unique_ptr<SgaObject> pObj = pGalleryTheme->AcquireObject(i);
+ CPPUNIT_ASSERT_MESSAGE("Acquired Object Invalid", pObj->IsValid());
+ }
+
+ for (sal_uInt32 i = static_cast<sal_uInt32>(imageList.size()); i > 0; i--)
+ {
+ std::unique_ptr<SgaObject> pObj = pGalleryTheme->AcquireObject(i - 1);
+ CPPUNIT_ASSERT_MESSAGE("Acquired Object Invalid", pObj->IsValid());
+ pGalleryTheme->RemoveObject(i - 1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Inconsistent object Count", pGalleryTheme->GetObjectCount(),
+ i - 1);
+ }
+
+ pGallery->ReleaseTheme(pGalleryTheme, aListener);
+}
+
+void GalleryObjTest::TestChangePositionGalleryObject()
+{
+ // Create theme
+ std::unique_ptr<utl::TempFile> pTempDir;
+ pTempDir.reset(new utl::TempFile(nullptr, true));
+ CPPUNIT_ASSERT_MESSAGE("Could not create valid temporary directory", pTempDir->IsValid());
+ pTempDir->EnableKillingFile();
+ const OUString aGalleryURL = pTempDir->GetURL();
+
+ // Check if directory exists
+ CPPUNIT_ASSERT_MESSAGE("Could not create temporary directory",
+ comphelper::DirectoryHelper::dirExists(aGalleryURL));
+
+ std::unique_ptr<Gallery> pGallery(new Gallery(aGalleryURL));
+ CPPUNIT_ASSERT_MESSAGE("Could not create gallery instance", (pGallery != nullptr));
+ static const OUStringLiteral myThemeName = u"addytesttheme";
+ CPPUNIT_ASSERT_MESSAGE("Could not create theme", pGallery->CreateTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find theme", pGallery->HasTheme(myThemeName));
+
+ // Create Sfx Instance
+ SfxListener aListener;
+ SfxApplication::GetOrCreate();
+
+ // Insert Objects Into Theme
+ GalleryTheme* pGalleryTheme = pGallery->AcquireTheme(myThemeName, aListener);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Object count inconsistent", sal_uInt32(0),
+ pGalleryTheme->GetObjectCount());
+
+ OUString imageList[] = { "galtest1.png", "galtest2.png", "galtest3.jpg" };
+
+ for (sal_uInt32 i = 0; i < (sizeof(imageList) / sizeof(imageList[0])); i++)
+ {
+ OUString imageNameFromList(imageList[i]);
+ OUString aURL(m_directories.getURLFromSrc(u"/svx/qa/unit/gallery/data/")
+ + imageNameFromList);
+ CPPUNIT_ASSERT_MESSAGE("Could not insert object into theme",
+ pGalleryTheme->InsertURL(INetURLObject(aURL)));
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Inconsistent object Count", pGalleryTheme->GetObjectCount(),
+ i + 1);
+ std::unique_ptr<SgaObject> pObj = pGalleryTheme->AcquireObject(i);
+ CPPUNIT_ASSERT_MESSAGE("Acquired Object Invalid", pObj->IsValid());
+ }
+
+ CPPUNIT_ASSERT(pGalleryTheme->ChangeObjectPos(1, 3));
+ std::unique_ptr<SgaObject> pObj = pGalleryTheme->AcquireObject(0);
+ INetURLObject aURL = pObj->GetURL();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Failure to change object position", imageList[0],
+ aURL.GetLastName());
+
+ pObj = pGalleryTheme->AcquireObject(1);
+ aURL = pObj->GetURL();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Failure to change object position", imageList[2],
+ aURL.GetLastName());
+
+ pObj = pGalleryTheme->AcquireObject(2);
+ aURL = pObj->GetURL();
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Failure to change object position", imageList[1],
+ aURL.GetLastName());
+
+ pGallery->ReleaseTheme(pGalleryTheme, aListener);
+}
+
+void GalleryObjTest::TestGetThemeNameFromGalleryTheme()
+{
+ // Create theme
+ std::unique_ptr<utl::TempFile> pTempDir;
+ pTempDir.reset(new utl::TempFile(nullptr, true));
+ CPPUNIT_ASSERT_MESSAGE("Could not create valid temporary directory", pTempDir->IsValid());
+ pTempDir->EnableKillingFile();
+ const OUString aGalleryURL = pTempDir->GetURL();
+
+ // Check if directory exists
+ CPPUNIT_ASSERT_MESSAGE("Could not create temporary directory",
+ comphelper::DirectoryHelper::dirExists(aGalleryURL));
+
+ std::unique_ptr<Gallery> pGallery(new Gallery(aGalleryURL));
+ CPPUNIT_ASSERT_MESSAGE("Could not create gallery instance", (pGallery != nullptr));
+ const OUString myThemeName = "addytesttheme";
+ CPPUNIT_ASSERT_MESSAGE("Could not create theme", pGallery->CreateTheme(myThemeName));
+ CPPUNIT_ASSERT_MESSAGE("Could not find theme", pGallery->HasTheme(myThemeName));
+
+ // Create Sfx Instance
+ SfxListener aListener;
+ SfxApplication::GetOrCreate();
+
+ GalleryTheme* pGalleryTheme = pGallery->AcquireTheme(myThemeName, aListener);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Object count inconsistent", sal_uInt32(0),
+ pGalleryTheme->GetObjectCount());
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Theme name not matching", myThemeName, pGalleryTheme->GetName());
+
+ pGallery->ReleaseTheme(pGalleryTheme, aListener);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GalleryObjTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/svx/qa/unit/removewhichrange.cxx b/svx/qa/unit/removewhichrange.cxx
new file mode 100644
index 000000000..d5856f6c7
--- /dev/null
+++ b/svx/qa/unit/removewhichrange.cxx
@@ -0,0 +1,122 @@
+/* -*- 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 <sal/config.h>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+#include <sal/types.h>
+#include <svx/svdetc.hxx>
+
+namespace
+{
+class TestRemoveWhichRange : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(TestRemoveWhichRange);
+ CPPUNIT_TEST(testRemoveWhichRange);
+ CPPUNIT_TEST_SUITE_END();
+
+ void testRemoveWhichRange()
+ {
+ {
+ WhichRangesContainer in;
+ auto const out = RemoveWhichRange(in, 10, 20);
+ CPPUNIT_ASSERT(out.empty());
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 30, 40>);
+ auto const out = RemoveWhichRange(in, 0, 20);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), out[0].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(40), out[0].second);
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 30, 40>);
+ auto const out = RemoveWhichRange(in, 10, 20);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), out[0].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(40), out[0].second);
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 30, 40>);
+ auto const out = RemoveWhichRange(in, 15, 20);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(10), out[0].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(14), out[0].second);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), out[1].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(40), out[1].second);
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 30, 40>);
+ auto const out = RemoveWhichRange(in, 30, 40);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(10), out[0].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(20), out[0].second);
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 30, 40>);
+ auto const out = RemoveWhichRange(in, 30, 50);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(10), out[0].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(20), out[0].second);
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 30, 40>);
+ auto const out = RemoveWhichRange(in, 30, 35);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(10), out[0].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(20), out[0].second);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(36), out[1].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(40), out[1].second);
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 30, 40>);
+ auto const out = RemoveWhichRange(in, 15, 35);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(10), out[0].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(14), out[0].second);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(36), out[1].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(40), out[1].second);
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 30, 40>);
+ auto const out = RemoveWhichRange(in, 12, 15);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(10), out[0].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(11), out[0].second);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(16), out[1].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(20), out[1].second);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), out[2].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(40), out[2].second);
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 30, 40>);
+ auto const out = RemoveWhichRange(in, 0, 100);
+ CPPUNIT_ASSERT(out.empty());
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 40, 50>);
+ auto const out = RemoveWhichRange(in, 25, 35);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(10), out[0].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(20), out[0].second);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(40), out[1].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(50), out[1].second);
+ }
+ {
+ WhichRangesContainer in(svl::Items<10, 20, 40, 50>);
+ auto const out = RemoveWhichRange(in, 50, 100);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(10), out[0].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(20), out[0].second);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(40), out[1].first);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(49), out[1].second);
+ }
+ }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestRemoveWhichRange);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/svx/qa/unit/sdr.cxx b/svx/qa/unit/sdr.cxx
new file mode 100644
index 000000000..d665987b1
--- /dev/null
+++ b/svx/qa/unit/sdr.cxx
@@ -0,0 +1,149 @@
+/* -*- 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/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+#include <test/xmltesttools.hxx>
+
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawPage.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+
+#include <drawinglayer/tools/primitive2dxmldump.hxx>
+#include <rtl/ustring.hxx>
+#include <svx/sdr/contact/displayinfo.hxx>
+#include <svx/sdr/contact/viewcontact.hxx>
+#include <svx/sdr/contact/viewobjectcontact.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/unopage.hxx>
+#include <vcl/virdev.hxx>
+#include <sdr/contact/objectcontactofobjlistpainter.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Tests for svx/source/sdr/ code.
+class SdrTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools
+{
+protected:
+ uno::Reference<lang::XComponent> mxComponent;
+
+public:
+ virtual void setUp() override
+ {
+ test::BootstrapFixture::setUp();
+ mxDesktop.set(frame::Desktop::create(m_xContext));
+ }
+
+ virtual void tearDown() override
+ {
+ if (mxComponent.is())
+ {
+ mxComponent->dispose();
+ }
+ test::BootstrapFixture::tearDown();
+ }
+ uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
+
+ drawinglayer::primitive2d::Primitive2DContainer
+ renderPageToPrimitives(const uno::Reference<drawing::XDrawPage>& xDrawPage);
+};
+
+drawinglayer::primitive2d::Primitive2DContainer
+SdrTest::renderPageToPrimitives(const uno::Reference<drawing::XDrawPage>& xDrawPage)
+{
+ auto pDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPage.get());
+ CPPUNIT_ASSERT(pDrawPage);
+ SdrPage* pSdrPage = pDrawPage->GetSdrPage();
+ ScopedVclPtrInstance<VirtualDevice> aVirtualDevice;
+ sdr::contact::ObjectContactOfObjListPainter aObjectContact(*aVirtualDevice,
+ { pSdrPage->GetObj(0) }, nullptr);
+ const sdr::contact::ViewObjectContact& rDrawPageVOContact
+ = pSdrPage->GetViewContact().GetViewObjectContact(aObjectContact);
+ sdr::contact::DisplayInfo aDisplayInfo;
+ drawinglayer::primitive2d::Primitive2DContainer aContainer;
+ rDrawPageVOContact.getPrimitive2DSequenceHierarchy(aDisplayInfo, aContainer);
+ return aContainer;
+}
+
+CPPUNIT_TEST_FIXTURE(SdrTest, testShadowScaleOrigin)
+{
+ // Load a document containing a custom shape.
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(u"svx/qa/unit/data/shadow-scale-origin.pptx");
+ getComponent() = loadFromDesktop(aURL);
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ drawinglayer::primitive2d::Primitive2DContainer xPrimitiveSequence
+ = renderPageToPrimitives(xDrawPage);
+
+ // Examine the created primitives.
+ drawinglayer::Primitive2dXmlDump aDumper;
+ xmlDocUniquePtr pDocument = aDumper.dumpAndParse(xPrimitiveSequence);
+ sal_Int32 fShadowX = getXPath(pDocument, "//shadow/transform", "xy13").toInt32();
+ sal_Int32 fShadowY = getXPath(pDocument, "//shadow/transform", "xy23").toInt32();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: -705
+ // - Actual : -158
+ // i.e. the shadow origin was not the top right corner for scaling (larger x position, so it was
+ // visible on the right of the shape as well).
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(-705), fShadowX);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(-684), fShadowY);
+}
+
+CPPUNIT_TEST_FIXTURE(SdrTest, testZeroWidthTextWrap)
+{
+ // Load a document containing a 0-width shape with text.
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(u"svx/qa/unit/data/0-width-text-wrap.pptx");
+ getComponent() = loadFromDesktop(aURL);
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ drawinglayer::primitive2d::Primitive2DContainer xPrimitiveSequence
+ = renderPageToPrimitives(xDrawPage);
+
+ // Examine the created primitives.
+ drawinglayer::Primitive2dXmlDump aDumper;
+ xmlDocUniquePtr pDocument = aDumper.dumpAndParse(xPrimitiveSequence);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 1
+ // - Actual : 12
+ // i.e. the text on the only shape on the slide had 12 lines, not a single one.
+ assertXPath(pDocument, "//textsimpleportion", 1);
+}
+
+CPPUNIT_TEST_FIXTURE(SdrTest, testSlideBackground)
+{
+ // Given a document with a slide what has a linked background image:
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(u"svx/qa/unit/data/slide-background.odp");
+ getComponent() = loadFromDesktop(aURL);
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+
+ // When rendering that document:
+ drawinglayer::primitive2d::Primitive2DContainer xPrimitiveSequence
+ = renderPageToPrimitives(xDrawPage);
+
+ // Then make sure that the background has a bitmap:
+ drawinglayer::Primitive2dXmlDump aDumper;
+ xmlDocUniquePtr pDocument = aDumper.dumpAndParse(xPrimitiveSequence);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 1
+ // - Actual : 0
+ // i.e. the rendering did not find the bitmap.
+ assertXPath(pDocument, "//bitmap", 1);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/styles.cxx b/svx/qa/unit/styles.cxx
new file mode 100644
index 000000000..dd27e24f0
--- /dev/null
+++ b/svx/qa/unit/styles.cxx
@@ -0,0 +1,156 @@
+/* -*- 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/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XMasterPageTarget.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Tests for svx/source/styles/ code.
+class Test : public test::BootstrapFixture, public unotest::MacrosTest
+{
+private:
+ uno::Reference<lang::XComponent> mxComponent;
+
+public:
+ void setUp() override;
+ void tearDown() override;
+ uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
+};
+
+void Test::setUp()
+{
+ test::BootstrapFixture::setUp();
+
+ mxDesktop.set(frame::Desktop::create(mxComponentContext));
+}
+
+void Test::tearDown()
+{
+ if (mxComponent.is())
+ mxComponent->dispose();
+
+ test::BootstrapFixture::tearDown();
+}
+
+constexpr OUStringLiteral DATA_DIRECTORY = u"/svx/qa/unit/data/";
+
+/// Get the character color of the first text portion in xShape.
+sal_Int32 GetShapeTextColor(const uno::Reference<text::XTextRange>& xShape)
+{
+ uno::Reference<container::XEnumerationAccess> xText(xShape->getText(), uno::UNO_QUERY);
+ uno::Reference<container::XEnumerationAccess> xPara(xText->createEnumeration()->nextElement(),
+ uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xPortion(xPara->createEnumeration()->nextElement(),
+ uno::UNO_QUERY);
+ sal_Int32 nColor{};
+ xPortion->getPropertyValue("CharColor") >>= nColor;
+ return nColor;
+}
+
+/// Get the solid fill color of xShape.
+sal_Int32 GetShapeFillColor(const uno::Reference<beans::XPropertySet>& xShape)
+{
+ sal_Int32 nColor{};
+ xShape->getPropertyValue("FillColor") >>= nColor;
+ return nColor;
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testThemeChange)
+{
+ // Given a document, with a first slide and blue shape text from theme:
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "theme.pptx";
+ getComponent() = loadFromDesktop(aURL);
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ // The draw page also contains a group shape to make sure we don't crash on group shapes.
+ uno::Reference<drawing::XMasterPageTarget> xDrawPage(
+ xDrawPagesSupplier->getDrawPages()->getByIndex(0), uno::UNO_QUERY);
+ uno::Reference<drawing::XShapes> xDrawPageShapes(xDrawPage, uno::UNO_QUERY);
+ uno::Reference<text::XTextRange> xShape(xDrawPageShapes->getByIndex(0), uno::UNO_QUERY);
+ // Blue.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0x4472c4), GetShapeTextColor(xShape));
+ uno::Reference<text::XTextRange> xShape2(xDrawPageShapes->getByIndex(1), uno::UNO_QUERY);
+ // Blue, lighter.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0xb4c7e7), GetShapeTextColor(xShape2));
+ uno::Reference<text::XTextRange> xShape3(xDrawPageShapes->getByIndex(2), uno::UNO_QUERY);
+ // Blue, darker.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0x2f5597), GetShapeTextColor(xShape3));
+ // Shape fill:
+ uno::Reference<beans::XPropertySet> xShape4(xDrawPageShapes->getByIndex(4), uno::UNO_QUERY);
+ // Blue.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0x4472c4), GetShapeFillColor(xShape4));
+ // The theme index of this filled shape is set by the PPTX import:
+ sal_Int16 nColorTheme = -1;
+ xShape4->getPropertyValue("FillColorTheme") >>= nColorTheme;
+ // 4 means accent1, this was -1 without the PPTX import bit in place.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(4), nColorTheme);
+ uno::Reference<beans::XPropertySet> xShape5(xDrawPageShapes->getByIndex(5), uno::UNO_QUERY);
+ // Blue, lighter.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0xb4c7e7), GetShapeFillColor(xShape5));
+ // The theme index, and effects (lum mod, lum off) are set by the PPTX import:
+ nColorTheme = -1;
+ xShape5->getPropertyValue("FillColorTheme") >>= nColorTheme;
+ // 4 means accent1, this was -1 without the PPTX import bit in place.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(4), nColorTheme);
+ sal_Int16 nColorLumMod = 10000;
+ xShape5->getPropertyValue("FillColorLumMod") >>= nColorLumMod;
+ // This was 10000 without the PPTX import bit in place.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(4000), nColorLumMod);
+ sal_Int16 nColorLumOff = 0;
+ xShape5->getPropertyValue("FillColorLumOff") >>= nColorLumOff;
+ // This was 0 without the PPTX import bit in place.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(6000), nColorLumOff);
+
+ // When changing the master slide of slide 1 to use the theme of the second master slide:
+ uno::Reference<drawing::XMasterPageTarget> xDrawPage2(
+ xDrawPagesSupplier->getDrawPages()->getByIndex(1), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xMasterPage2(xDrawPage2->getMasterPage(), uno::UNO_QUERY);
+ uno::Any aTheme = xMasterPage2->getPropertyValue("Theme");
+ uno::Reference<beans::XPropertySet> xMasterPage(xDrawPage->getMasterPage(), uno::UNO_QUERY);
+ xMasterPage->setPropertyValue("Theme", aTheme);
+
+ // Then make sure the shape text color is now green:
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 9486886 (#90c226, green)
+ // - Actual : 4485828 (#4472c4, blue)
+ // i.e. shape text was not updated on theme change.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0x90c226), GetShapeTextColor(xShape));
+ // Green, lighter:
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 14020002 (#d5eda2, light green)
+ // - Actual : 9486886 (#90c226, stock green)
+ // i.e. the "light" effect on green was not applied.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0xd5eda2), GetShapeTextColor(xShape2));
+ // Green, darker.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0x6c911d), GetShapeTextColor(xShape3));
+ // Shape fill:
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 9486886 (#90c226, green)
+ // - Actual : 4485828 (#4472c4, blue)
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0x90c226), GetShapeFillColor(xShape4));
+ // Green, lighter:
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 14020002 (#d5eda2, light green)
+ // - Actual : 9486886 (#90c226, green)
+ // i.e. the "light" effect on green was not applied.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0xd5eda2), GetShapeFillColor(xShape5));
+}
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/svdraw.cxx b/svx/qa/unit/svdraw.cxx
new file mode 100644
index 000000000..564998bcf
--- /dev/null
+++ b/svx/qa/unit/svdraw.cxx
@@ -0,0 +1,554 @@
+/* -*- 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/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+#include <test/xmltesttools.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawPage.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp>
+#include <com/sun/star/drawing/HomogenMatrix3.hpp>
+
+#include <drawinglayer/tools/primitive2dxmldump.hxx>
+#include <rtl/ustring.hxx>
+#include <vcl/virdev.hxx>
+#include <svx/sdr/contact/displayinfo.hxx>
+#include <svx/sdr/contact/viewcontact.hxx>
+#include <svx/sdr/contact/viewobjectcontact.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svdorect.hxx>
+#include <svx/unopage.hxx>
+#include <svx/svdview.hxx>
+#include <svx/xlineit0.hxx>
+#include <svx/xlnstwit.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <sfx2/viewsh.hxx>
+#include <svl/itempool.hxx>
+#include <svx/svdomedia.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <vcl/filter/PDFiumLibrary.hxx>
+
+#include <sdr/contact/objectcontactofobjlistpainter.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Tests for svx/source/svdraw/ code.
+class SvdrawTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools
+{
+protected:
+ uno::Reference<lang::XComponent> mxComponent;
+ SdrPage* getFirstDrawPageWithAssert();
+
+public:
+ virtual void setUp() override
+ {
+ test::BootstrapFixture::setUp();
+ mxDesktop.set(frame::Desktop::create(m_xContext));
+ }
+
+ virtual void tearDown() override
+ {
+ if (mxComponent.is())
+ {
+ mxComponent->dispose();
+ }
+ test::BootstrapFixture::tearDown();
+ }
+ uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
+};
+
+SdrPage* SvdrawTest::getFirstDrawPageWithAssert()
+{
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
+ uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT(xDrawPagesSupplier.is());
+ uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT(xDrawPage.is());
+
+ auto pDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPage.get());
+ CPPUNIT_ASSERT(pDrawPage);
+ return pDrawPage->GetSdrPage();
+}
+
+xmlDocUniquePtr lcl_dumpAndParseFirstObjectWithAssert(SdrPage* pSdrPage)
+{
+ ScopedVclPtrInstance<VirtualDevice> aVirtualDevice;
+ sdr::contact::ObjectContactOfObjListPainter aObjectContact(*aVirtualDevice,
+ { pSdrPage->GetObj(0) }, nullptr);
+ const auto& rDrawPageVOContact
+ = pSdrPage->GetViewContact().GetViewObjectContact(aObjectContact);
+ sdr::contact::DisplayInfo aDisplayInfo;
+ drawinglayer::primitive2d::Primitive2DContainer xPrimitiveSequence;
+ rDrawPageVOContact.getPrimitive2DSequenceHierarchy(aDisplayInfo, xPrimitiveSequence);
+
+ drawinglayer::Primitive2dXmlDump aDumper;
+ xmlDocUniquePtr pXmlDoc = aDumper.dumpAndParse(xPrimitiveSequence);
+ CPPUNIT_ASSERT(pXmlDoc);
+ return pXmlDoc;
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testSemiTransparentText)
+{
+ // Create a new Draw document with a rectangle.
+ getComponent() = loadFromDesktop("private:factory/sdraw");
+ uno::Reference<lang::XMultiServiceFactory> xFactory(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XShape> xShape(
+ xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
+ xShape->setSize(awt::Size(10000, 10000));
+ xShape->setPosition(awt::Point(1000, 1000));
+
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ xDrawPage->add(xShape);
+
+ // Add semi-transparent text on the rectangle.
+ uno::Reference<text::XTextRange> xShapeText(xShape, uno::UNO_QUERY);
+ xShapeText->getText()->setString("hello");
+
+ uno::Reference<beans::XPropertySet> xShapeProperties(xShape, uno::UNO_QUERY);
+ xShapeProperties->setPropertyValue("CharColor", uno::Any(COL_RED));
+ sal_Int16 nTransparence = 75;
+ xShapeProperties->setPropertyValue("CharTransparence", uno::Any(nTransparence));
+
+ // Generates drawinglayer primitives for the page.
+ auto pDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPage.get());
+ CPPUNIT_ASSERT(pDrawPage);
+ SdrPage* pSdrPage = pDrawPage->GetSdrPage();
+ xmlDocUniquePtr pDocument = lcl_dumpAndParseFirstObjectWithAssert(pSdrPage);
+
+ // Make sure the text is semi-transparent.
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 1
+ // - Actual : 0
+ // - XPath '//unifiedtransparence' number of nodes is incorrect
+ // i.e. the text was just plain red, not semi-transparent.
+ sal_Int16 fTransparence
+ = getXPath(pDocument, "//unifiedtransparence", "transparence").toInt32();
+ CPPUNIT_ASSERT_EQUAL(nTransparence, fTransparence);
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testHandlePathObjScale)
+{
+ // Given a path object:
+ getComponent() = loadFromDesktop("private:factory/sdraw");
+ uno::Reference<lang::XMultiServiceFactory> xFactory(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XShape> xShape(
+ xFactory->createInstance("com.sun.star.drawing.ClosedBezierShape"), uno::UNO_QUERY);
+
+ // When setting its scale by both using setSize() and scaling in a transform matrix:
+ // Set size and basic properties.
+ xShape->setPosition(awt::Point(2512, 6062));
+ xShape->setSize(awt::Size(112, 112));
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ xShapeProps->setPropertyValue("FillStyle", uno::Any(drawing::FillStyle_SOLID));
+ xShapeProps->setPropertyValue("LineStyle", uno::Any(drawing::LineStyle_SOLID));
+ xShapeProps->setPropertyValue("FillColor", uno::Any(static_cast<sal_Int32>(0)));
+ // Add it to the draw page.
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ xDrawPage->add(xShape);
+ // Set polygon coordinates.
+ drawing::PolyPolygonBezierCoords aPolyPolygonBezierCoords;
+ aPolyPolygonBezierCoords.Coordinates = {
+ {
+ awt::Point(2624, 6118),
+ awt::Point(2624, 6087),
+ awt::Point(2599, 6062),
+ awt::Point(2568, 6062),
+ awt::Point(2537, 6062),
+ awt::Point(2512, 6087),
+ awt::Point(2512, 6118),
+ awt::Point(2512, 6149),
+ awt::Point(2537, 6175),
+ awt::Point(2568, 6174),
+ awt::Point(2599, 6174),
+ awt::Point(2625, 6149),
+ awt::Point(2624, 6118),
+ },
+ };
+ aPolyPolygonBezierCoords.Flags = {
+ {
+ drawing::PolygonFlags_NORMAL,
+ drawing::PolygonFlags_CONTROL,
+ drawing::PolygonFlags_CONTROL,
+ drawing::PolygonFlags_NORMAL,
+ drawing::PolygonFlags_CONTROL,
+ drawing::PolygonFlags_CONTROL,
+ drawing::PolygonFlags_NORMAL,
+ drawing::PolygonFlags_CONTROL,
+ drawing::PolygonFlags_CONTROL,
+ drawing::PolygonFlags_NORMAL,
+ drawing::PolygonFlags_CONTROL,
+ drawing::PolygonFlags_CONTROL,
+ drawing::PolygonFlags_NORMAL,
+ },
+ };
+ xShapeProps->setPropertyValue("PolyPolygonBezier", uno::Any(aPolyPolygonBezierCoords));
+ drawing::HomogenMatrix3 aMatrix;
+ aMatrix.Line1.Column1 = 56;
+ aMatrix.Line2.Column1 = -97;
+ aMatrix.Line3.Column1 = 0;
+ aMatrix.Line1.Column2 = 97;
+ aMatrix.Line2.Column2 = 56;
+ aMatrix.Line3.Column2 = 0;
+ aMatrix.Line1.Column3 = 3317;
+ aMatrix.Line2.Column3 = 5583;
+ aMatrix.Line3.Column3 = 1;
+ xShapeProps->setPropertyValue("Transformation", uno::Any(aMatrix));
+
+ // Then make sure the scaling is only applied once:
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 113
+ // - Actual : 12566
+ // i.e. the scaling was applied twice.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(113), xShape->getSize().Width);
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testTextEditEmptyGrabBag)
+{
+ // Given a document with a groupshape, which has 2 children.
+ getComponent() = loadFromDesktop("private:factory/sdraw");
+ uno::Reference<lang::XMultiServiceFactory> xFactory(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XShape> xRect1(
+ xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
+ xRect1->setPosition(awt::Point(1000, 1000));
+ xRect1->setSize(awt::Size(10000, 10000));
+ uno::Reference<drawing::XShape> xRect2(
+ xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
+ xRect2->setPosition(awt::Point(1000, 1000));
+ xRect2->setSize(awt::Size(10000, 10000));
+ uno::Reference<drawing::XShapes> xGroup(
+ xFactory->createInstance("com.sun.star.drawing.GroupShape"), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ uno::Reference<drawing::XShape> xGroupShape(xGroup, uno::UNO_QUERY);
+ xDrawPage->add(xGroupShape);
+ xGroup->add(xRect1);
+ xGroup->add(xRect2);
+ uno::Reference<text::XTextRange> xRect2Text(xRect2, uno::UNO_QUERY);
+ xRect2Text->setString("x");
+ uno::Sequence<beans::PropertyValue> aGrabBag = {
+ comphelper::makePropertyValue("OOXLayout", true),
+ };
+ uno::Reference<beans::XPropertySet> xGroupProps(xGroup, uno::UNO_QUERY);
+ xGroupProps->setPropertyValue("InteropGrabBag", uno::Any(aGrabBag));
+
+ // When editing the shape text of the 2nd rectangle (insert a char at the start).
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ SdrView* pSdrView = pViewShell->GetDrawView();
+ SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xRect2);
+ pSdrView->SdrBeginTextEdit(pObject);
+ EditView& rEditView = pSdrView->GetTextEditOutlinerView()->GetEditView();
+ rEditView.InsertText("y");
+ pSdrView->SdrEndTextEdit();
+
+ // Then make sure that grab-bag is empty to avoid losing the new text.
+ xGroupProps->getPropertyValue("InteropGrabBag") >>= aGrabBag;
+ // Without the accompanying fix in place, this test would have failed with:
+ // assertion failed
+ // - Expression: !aGrabBag.hasElements()
+ // i.e. the grab-bag was still around after modifying the shape, and that grab-bag contained the
+ // old text.
+ CPPUNIT_ASSERT(!aGrabBag.hasElements());
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testRectangleObject)
+{
+ std::unique_ptr<SdrModel> pModel(new SdrModel(nullptr, nullptr, true));
+ pModel->GetItemPool().FreezeIdRanges();
+
+ rtl::Reference<SdrPage> pPage(new SdrPage(*pModel, false));
+ pPage->SetSize(Size(1000, 1000));
+ pModel->InsertPage(pPage.get(), 0);
+
+ tools::Rectangle aSize(Point(), Size(100, 100));
+ auto* pRectangle = new SdrRectObj(*pModel, aSize);
+ pPage->NbcInsertObject(pRectangle);
+ pRectangle->SetMergedItem(XLineStyleItem(drawing::LineStyle_SOLID));
+ pRectangle->SetMergedItem(XLineStartWidthItem(200));
+
+ ScopedVclPtrInstance<VirtualDevice> aVirtualDevice;
+ aVirtualDevice->SetOutputSize(Size(2000, 2000));
+
+ SdrView aView(*pModel, aVirtualDevice);
+ aView.hideMarkHandles();
+ aView.ShowSdrPage(pPage.get());
+
+ sdr::contact::ObjectContactOfObjListPainter aObjectContact(*aVirtualDevice,
+ { pPage->GetObj(0) }, nullptr);
+ const sdr::contact::ViewObjectContact& rDrawPageVOContact
+ = pPage->GetViewContact().GetViewObjectContact(aObjectContact);
+
+ sdr::contact::DisplayInfo aDisplayInfo;
+ drawinglayer::primitive2d::Primitive2DContainer xPrimitiveSequence;
+ rDrawPageVOContact.getPrimitive2DSequenceHierarchy(aDisplayInfo, xPrimitiveSequence);
+
+ drawinglayer::Primitive2dXmlDump aDumper;
+ xmlDocUniquePtr pXmlDoc = aDumper.dumpAndParse(xPrimitiveSequence);
+
+ assertXPath(pXmlDoc, "/primitive2D", 1);
+
+ OString aBasePath("/primitive2D/sdrrectangle/polypolygoncolor");
+ assertXPath(pXmlDoc, aBasePath, "color", "#729fcf");
+
+ assertXPath(pXmlDoc, aBasePath + "/polypolygon", "height",
+ "99"); // weird Rectangle is created with size 100
+ assertXPath(pXmlDoc, aBasePath + "/polypolygon", "width", "99");
+ assertXPath(pXmlDoc, aBasePath + "/polypolygon", "minx", "0");
+ assertXPath(pXmlDoc, aBasePath + "/polypolygon", "miny", "0");
+ assertXPath(pXmlDoc, aBasePath + "/polypolygon", "maxx", "99");
+ assertXPath(pXmlDoc, aBasePath + "/polypolygon", "maxy", "99");
+
+ aBasePath = "/primitive2D/sdrrectangle/polypolygoncolor/polypolygon/polygon";
+
+ assertXPath(pXmlDoc, aBasePath + "/point", 5);
+ assertXPath(pXmlDoc, aBasePath + "/point[1]", "x", "49.5"); // hmm, weird, why?
+ assertXPath(pXmlDoc, aBasePath + "/point[1]", "y", "99");
+ assertXPath(pXmlDoc, aBasePath + "/point[2]", "x", "0");
+ assertXPath(pXmlDoc, aBasePath + "/point[2]", "y", "99");
+ assertXPath(pXmlDoc, aBasePath + "/point[3]", "x", "0");
+ assertXPath(pXmlDoc, aBasePath + "/point[3]", "y", "0");
+ assertXPath(pXmlDoc, aBasePath + "/point[4]", "x", "99");
+ assertXPath(pXmlDoc, aBasePath + "/point[4]", "y", "0");
+ assertXPath(pXmlDoc, aBasePath + "/point[5]", "x", "99");
+ assertXPath(pXmlDoc, aBasePath + "/point[5]", "y", "99");
+
+ aBasePath = "/primitive2D/sdrrectangle/polygonstroke";
+ assertXPath(pXmlDoc, aBasePath, 1);
+
+ assertXPath(pXmlDoc, aBasePath + "/line", "color", "#3465a4");
+ assertXPath(pXmlDoc, aBasePath + "/line", "width", "0");
+ assertXPath(pXmlDoc, aBasePath + "/line", "linejoin", "Round");
+ assertXPath(pXmlDoc, aBasePath + "/line", "linecap", "BUTT");
+
+ assertXPathContent(pXmlDoc, aBasePath + "/polygon", "49.5,99 0,99 0,0 99,0 99,99");
+
+ // If solid line, then there is no line stroke information
+ assertXPath(pXmlDoc, aBasePath + "/stroke", 0);
+
+ pPage->RemoveObject(0);
+
+ SdrObject* pObject(pRectangle);
+ SdrObject::Free(pObject);
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testAutoHeightMultiColShape)
+{
+ // Given a document containing a shape that has:
+ // 1) automatic height (resize shape to fix text)
+ // 2) multiple columns (2)
+ OUString aURL
+ = m_directories.getURLFromSrc(u"svx/qa/unit/data/auto-height-multi-col-shape.pptx");
+
+ // When loading that document:
+ getComponent().set(loadFromDesktop(aURL, "com.sun.star.presentation.PresentationDocument"));
+
+ // Make sure the in-file shape height is kept, even if nominally the shape height is
+ // automatic:
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 6882
+ // - Actual : 3452
+ // i.e. the shape height was smaller than expected, leading to a 2 columns layout instead of
+ // laying out all the text in the first column.
+ // 2477601 is from slide1.xml, <a:ext cx="4229467" cy="2477601"/>.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(
+ static_cast<sal_Int32>(o3tl::convert(2477601, o3tl::Length::emu, o3tl::Length::mm100)),
+ xShape->getSize().Height, 1);
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testFontWorks)
+{
+ OUString aURL = m_directories.getURLFromSrc(u"svx/qa/unit/data/FontWork.odg");
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.comp.drawing.DrawingDocument");
+
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
+ uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT(xDrawPagesSupplier.is());
+ uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
+ CPPUNIT_ASSERT(xDrawPage.is());
+ uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xShape.is());
+
+ auto pDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPage.get());
+ CPPUNIT_ASSERT(pDrawPage);
+ SdrPage* pSdrPage = pDrawPage->GetSdrPage();
+ xmlDocUniquePtr pXmlDoc = lcl_dumpAndParseFirstObjectWithAssert(pSdrPage);
+
+ assertXPath(pXmlDoc, "/primitive2D", 1);
+
+ assertXPath(pXmlDoc, "//scene", "projectionMode", "Perspective");
+ assertXPath(pXmlDoc, "//scene/extrude3D[1]/fill", "color", "#ff0000");
+ assertXPath(pXmlDoc, "//scene/extrude3D[1]/object3Dattributes/material", "color", "#ff0000");
+ // ODF default 50% is represented by Specular Intensity = 2^5. The relationship is not linear.
+ assertXPath(pXmlDoc, "//scene/extrude3D[1]/object3Dattributes/material", "specularIntensity",
+ "32");
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testSurfaceMetal)
+{
+ OUString aURL = m_directories.getURLFromSrc(u"svx/qa/unit/data/tdf140321_metal.odp");
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.presentation.PresentationDocument");
+
+ SdrPage* pSdrPage = getFirstDrawPageWithAssert();
+
+ xmlDocUniquePtr pXmlDoc = lcl_dumpAndParseFirstObjectWithAssert(pSdrPage);
+
+ // ODF specifies for metal = true specular color as rgb(200,200,200) and adding 15 to specularity
+ // Together with extrusion-first-light-level 67% and extrusion-specularity 80% factor is
+ // 0.67*0.8 * 200/255 = 0.42 and color #6b6b6b
+ assertXPath(pXmlDoc, "(//material)[1]", "specular", "#6b6b6b");
+ // 3D specularIntensity = 2^(50/10) + 15 = 47, with default extrusion-shininess 50%
+ assertXPath(pXmlDoc, "(//material)[1]", "specularIntensity", "47");
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testExtrusionPhong)
+{
+ OUString aURL = m_directories.getURLFromSrc(u"svx/qa/unit/data/tdf140321_phong.odp");
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.presentation.PresentationDocument");
+
+ SdrPage* pSdrPage = getFirstDrawPageWithAssert();
+
+ xmlDocUniquePtr pXmlDoc = lcl_dumpAndParseFirstObjectWithAssert(pSdrPage);
+
+ // The rendering method and normals kind were always 'Flat' without the patch.
+ assertXPath(pXmlDoc, "//scene", "shadeMode", "Phong");
+ assertXPath(pXmlDoc, "//object3Dattributes", "normalsKind", "Specific");
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testSurfaceMattePPT)
+{
+ OUString aURL = m_directories.getURLFromSrc(u"svx/qa/unit/data/tdf140321_Matte_import.ppt");
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.presentation.PresentationDocument");
+
+ SdrPage* pSdrPage = getFirstDrawPageWithAssert();
+
+ xmlDocUniquePtr pXmlDoc = lcl_dumpAndParseFirstObjectWithAssert(pSdrPage);
+
+ // The preset 'matte' sets the specularity of material to 0. But that alone does not make the
+ // rendering 'matte' in LO. To get a 'matte' effect in LO, specularity of the light need to be
+ // false in addition. To get this, first light is set off and values from first light are copied
+ // to forth light, as only first light is specular. Because first and third lights are off, the
+ // forth light is the second one in the dump. The gray color corresponding to
+ // FirstLightLevel = 38000/2^16 is #949494.
+ assertXPath(pXmlDoc, "(//material)[1]", "specular", "#000000");
+ assertXPath(pXmlDoc, "(//light)[2]", "color", "#949494");
+ // To make the second light soft, part of its intensity is moved to lights 5,6,7 and 8.
+ assertXPath(pXmlDoc, "(//light)[1]", "color", "#1e1e1e");
+ assertXPath(pXmlDoc, "(//light)[3]", "color", "#3b3b3b");
+ // The 3D property specularIntensity is not related to 'extrusion-specularity' but to
+ // 'extrusion-shininess'. specularIntensity = 2^(shininess/10), here default 32.
+ assertXPath(pXmlDoc, "(//material)[1]", "specularIntensity", "32");
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testMaterialSpecular)
+{
+ OUString aURL
+ = m_directories.getURLFromSrc(u"svx/qa/unit/data/tdf140321_material_specular.odp");
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.presentation.PresentationDocument");
+
+ SdrPage* pSdrPage = getFirstDrawPageWithAssert();
+
+ xmlDocUniquePtr pXmlDoc = lcl_dumpAndParseFirstObjectWithAssert(pSdrPage);
+ CPPUNIT_ASSERT(pXmlDoc);
+
+ // 3D specular color is derived from properties 'extrusion-specularity' and 'extrusion-first-light
+ // -level'. 3D specularIntensity is derived from property 'draw:extrusion-shininess'. Both are
+ // object properties, not scene properties. Those were wrong in various forms before the patch.
+ // Specularity = 77% * first-light-level 67% = 0.5159, which corresponds to gray color #848484.
+ assertXPath(pXmlDoc, "(//material)[1]", "specular", "#848484");
+ // extrusion-shininess 50% corresponds to 3D specularIntensity 32, use 2^(50/10).
+ assertXPath(pXmlDoc, "(//material)[1]", "specularIntensity", "32");
+ // extrusion-first-light-level 67% corresponds to gray color #ababab, use 255 * 0.67.
+ assertXPath(pXmlDoc, "(//light)[1]", "color", "#ababab");
+ // The first light is harsh, the second light soft. So the 3D scene should have 6 lights (1+1+4).
+ assertXPath(pXmlDoc, "//light", 6);
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testVideoSnapshot)
+{
+ // Given a slide with a media shape, containing a 4 sec video, red-green-blue-black being the 4
+ // seconds:
+ OUString aURL = m_directories.getURLFromSrc(u"svx/qa/unit/data/video-snapshot.pptx");
+ mxComponent = loadFromDesktop(aURL, "com.sun.star.presentation.PresentationDocument");
+ SdrPage* pSdrPage = getFirstDrawPageWithAssert();
+ auto pSdrMediaObj = dynamic_cast<SdrMediaObj*>(pSdrPage->GetObj(0));
+
+ // When getting the red snapshot of the video:
+ Graphic aSnapshot(pSdrMediaObj->getSnapshot());
+
+ // Then make sure the color is correct:
+ const BitmapEx& rBitmap = aSnapshot.GetBitmapExRef();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: rgba[ff0000ff]
+ // - Actual : rgba[000000ff]
+ // i.e. the preview was black, not ~red; since we seeked 3 secs into the video, while PowerPoint
+ // doesn't do that.
+ CPPUNIT_ASSERT_EQUAL(Color(0xfe, 0x0, 0x0), rBitmap.GetPixelColor(0, 0));
+
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 321
+ // - Actual : 640
+ // i.e. ~25% crop from left and right should result in half width, but it was not reduced.
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(321), rBitmap.GetSizePixel().getWidth());
+}
+
+CPPUNIT_TEST_FIXTURE(SvdrawTest, testPageViewDrawLayerClip)
+{
+ // Given a document with 2 pages, first page footer has an off-page line shape:
+ OUString aURL = m_directories.getURLFromSrc(u"svx/qa/unit/data/page-view-draw-layer-clip.docx");
+ mxComponent = loadFromDesktop(aURL);
+
+ // When saving that document to PDF:
+ utl::TempFile aTempFile;
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ xStorable->storeToURL(aTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Then make sure that line shape gets clipped:
+ SvFileStream aFile(aTempFile.GetURL(), StreamMode::READ);
+ SvMemoryStream aMemory;
+ aMemory.WriteStream(aFile);
+ std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
+ if (!pPDFium)
+ {
+ return;
+ }
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pDoc
+ = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize(), OString());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPage1 = pDoc->openPage(0);
+ CPPUNIT_ASSERT_EQUAL(3, pPage1->getObjectCount());
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPage2 = pDoc->openPage(1);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 2
+ // - Actual : 3
+ // i.e. the 2nd page had a line shape from the first page's footer.
+ CPPUNIT_ASSERT_EQUAL(2, pPage2->getObjectCount());
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/svdraw/test_SdrTextObject.cxx b/svx/qa/unit/svdraw/test_SdrTextObject.cxx
new file mode 100644
index 000000000..218db2a52
--- /dev/null
+++ b/svx/qa/unit/svdraw/test_SdrTextObject.cxx
@@ -0,0 +1,46 @@
+/* -*- 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 <svx/svdotext.hxx>
+#include <rtl/ustring.hxx>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+class SdrTextObjTest : public CppUnit::TestFixture
+{
+public:
+ void AllFamiliesCanBeRestoredFromSavedString();
+
+ CPPUNIT_TEST_SUITE(SdrTextObjTest);
+ CPPUNIT_TEST(AllFamiliesCanBeRestoredFromSavedString);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void SdrTextObjTest::AllFamiliesCanBeRestoredFromSavedString()
+{
+ std::vector<SfxStyleFamily> allFamilies{ SfxStyleFamily::Char, SfxStyleFamily::Para,
+ SfxStyleFamily::Page, SfxStyleFamily::Pseudo };
+
+ for (SfxStyleFamily family : allFamilies)
+ {
+ OUString styleName = "styleName";
+ SdrTextObj::AppendFamilyToStyleName(styleName, family);
+ SfxStyleFamily readFamily = SdrTextObj::ReadFamilyFromStyleName(styleName);
+ CPPUNIT_ASSERT_EQUAL(static_cast<int>(family), static_cast<int>(readFamily));
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SdrTextObjTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/svx-dialogs-test.cxx b/svx/qa/unit/svx-dialogs-test.cxx
new file mode 100644
index 000000000..32c632c99
--- /dev/null
+++ b/svx/qa/unit/svx-dialogs-test.cxx
@@ -0,0 +1,58 @@
+/* -*- 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 <sal/config.h>
+#include <test/screenshot_test.hxx>
+#include <vcl/abstdlg.hxx>
+
+using namespace ::com::sun::star;
+
+/// Test opening a dialog in svx
+class SvxDialogsTest : public ScreenshotTest
+{
+private:
+ /// helper method to populate KnownDialogs, called in setUp(). Needs to be
+ /// written and has to add entries to KnownDialogs
+ virtual void registerKnownDialogsByID(mapType& rKnownDialogs) override;
+
+ /// dialog creation for known dialogs by ID. Has to be implemented for
+ /// each registered known dialog
+ virtual VclPtr<VclAbstractDialog> createDialogByID(sal_uInt32 nID) override;
+
+public:
+ SvxDialogsTest();
+
+ // try to open a dialog
+ void openAnyDialog();
+
+ CPPUNIT_TEST_SUITE(SvxDialogsTest);
+ CPPUNIT_TEST(openAnyDialog);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+SvxDialogsTest::SvxDialogsTest() {}
+
+void SvxDialogsTest::registerKnownDialogsByID(mapType& /*rKnownDialogs*/)
+{
+ // fill map of known dialogs
+}
+
+VclPtr<VclAbstractDialog> SvxDialogsTest::createDialogByID(sal_uInt32 /*nID*/) { return nullptr; }
+
+void SvxDialogsTest::openAnyDialog()
+{
+ /// process input file containing the UXMLDescriptions of the dialogs to dump
+ processDialogBatchFile(u"svx/qa/unit/data/svx-dialogs-test.txt");
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SvxDialogsTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/table.cxx b/svx/qa/unit/table.cxx
new file mode 100644
index 000000000..c68abe18b
--- /dev/null
+++ b/svx/qa/unit/table.cxx
@@ -0,0 +1,142 @@
+/* -*- 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/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+#include <test/xmltesttools.hxx>
+
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawPage.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+
+#include <drawinglayer/tools/primitive2dxmldump.hxx>
+#include <rtl/ustring.hxx>
+#include <svx/sdr/contact/displayinfo.hxx>
+#include <svx/sdr/contact/viewcontact.hxx>
+#include <svx/sdr/contact/viewobjectcontact.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/unopage.hxx>
+#include <vcl/virdev.hxx>
+#include <sdr/contact/objectcontactofobjlistpainter.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <sfx2/viewsh.hxx>
+#include <svx/svdview.hxx>
+#include <svx/sdr/table/tablecontroller.hxx>
+#include <editeng/editobj.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Tests for svx/source/table/ code.
+class Test : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools
+{
+protected:
+ uno::Reference<lang::XComponent> mxComponent;
+
+public:
+ virtual void setUp() override
+ {
+ test::BootstrapFixture::setUp();
+ mxDesktop.set(frame::Desktop::create(m_xContext));
+ }
+
+ virtual void tearDown() override
+ {
+ if (mxComponent.is())
+ {
+ mxComponent->dispose();
+ }
+ test::BootstrapFixture::tearDown();
+ }
+ uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
+
+ drawinglayer::primitive2d::Primitive2DContainer
+ renderPageToPrimitives(const uno::Reference<drawing::XDrawPage>& xDrawPage);
+};
+
+drawinglayer::primitive2d::Primitive2DContainer
+Test::renderPageToPrimitives(const uno::Reference<drawing::XDrawPage>& xDrawPage)
+{
+ auto pDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPage.get());
+ CPPUNIT_ASSERT(pDrawPage);
+ SdrPage* pSdrPage = pDrawPage->GetSdrPage();
+ ScopedVclPtrInstance<VirtualDevice> aVirtualDevice;
+ sdr::contact::ObjectContactOfObjListPainter aObjectContact(*aVirtualDevice,
+ { pSdrPage->GetObj(0) }, nullptr);
+ const sdr::contact::ViewObjectContact& rDrawPageVOContact
+ = pSdrPage->GetViewContact().GetViewObjectContact(aObjectContact);
+ sdr::contact::DisplayInfo aDisplayInfo;
+ drawinglayer::primitive2d::Primitive2DContainer aContainer;
+ rDrawPageVOContact.getPrimitive2DSequenceHierarchy(aDisplayInfo, aContainer);
+ return aContainer;
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTableShadowBlur)
+{
+ // Given a document containing a table with a blurry shadow:
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(u"svx/qa/unit/data/table-shadow-blur.pptx");
+ getComponent() = loadFromDesktop(aURL);
+
+ // When rendering the table shadow to primitives:
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ drawinglayer::primitive2d::Primitive2DContainer xPrimitiveSequence
+ = renderPageToPrimitives(xDrawPage);
+
+ // Then make sure that the cell fill part of the shadow is excluded from blurring:
+ drawinglayer::Primitive2dXmlDump aDumper;
+ xmlDocUniquePtr pDocument = aDumper.dumpAndParse(xPrimitiveSequence);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - number of nodes is incorrect
+ // - Expected: 1
+ // - Actual : 0
+ // i.e. the shadow itself was not transparent and that resulted in a non-transparent rendering
+ // as well, while the rendering transparency should be based on the transparency of the shadow
+ // itself and the transparency of the cell fill.
+ assertXPath(pDocument, "//objectinfo/unifiedtransparence[1]", "transparence", "80");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testSvxTableControllerSetAttrToSelectedShape)
+{
+ // Given a document with a table shape, editing cell text:
+ getComponent() = loadFromDesktop("private:factory/simpress",
+ "com.sun.star.presentation.PresentationDocument");
+ uno::Sequence<beans::PropertyValue> aArgs
+ = { comphelper::makePropertyValue("Rows", sal_Int32(2)),
+ comphelper::makePropertyValue("Columns", sal_Int32(2)) };
+ dispatchCommand(mxComponent, ".uno:InsertTable", aArgs);
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ auto pDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPage.get());
+ CPPUNIT_ASSERT(pDrawPage);
+ SdrPage* pSdrPage = pDrawPage->GetSdrPage();
+ auto pSdrObject
+ = dynamic_cast<sdr::table::SdrTableObj*>(pSdrPage->GetObj(pSdrPage->GetObjCount() - 1));
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ SdrView* pSdrView = pViewShell->GetDrawView();
+ pSdrView->SdrBeginTextEdit(pSdrObject);
+ CPPUNIT_ASSERT(pSdrView->IsTextEdit());
+ const EditTextObject& rEdit = pSdrObject->getText(0)->GetOutlinerParaObject()->GetTextObject();
+ SfxItemSet aSet(rEdit.GetParaAttribs(0));
+ auto pTableController
+ = dynamic_cast<sdr::table::SvxTableController*>(pSdrView->getSelectionController().get());
+
+ // When applying attributes which only affect the cell text, not the table shape:
+ pTableController->SetAttrToSelectedShape(aSet);
+
+ // Then make sure the text edit is not ended:
+ CPPUNIT_ASSERT(pSdrView->IsTextEdit());
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/unodraw.cxx b/svx/qa/unit/unodraw.cxx
new file mode 100644
index 000000000..d026e07bc
--- /dev/null
+++ b/svx/qa/unit/unodraw.cxx
@@ -0,0 +1,249 @@
+/* -*- 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 <cppunit/TestAssert.h>
+
+#include <com/sun/star/drawing/GraphicExportFilter.hpp>
+#include <com/sun/star/drawing/XDrawPageSupplier.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/awt/XControlModel.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/table/XCellRange.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+#include <com/sun/star/text/ControlCharacter.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+#include <unotools/tempfile.hxx>
+#include <svx/unopage.hxx>
+#include <vcl/virdev.hxx>
+#include <svx/sdr/contact/displayinfo.hxx>
+#include <drawinglayer/tools/primitive2dxmldump.hxx>
+#include <svx/sdr/contact/viewcontact.hxx>
+#include <svx/sdr/contact/viewobjectcontact.hxx>
+#include <test/xmltesttools.hxx>
+#include <unotools/streamwrap.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+
+#include <sdr/contact/objectcontactofobjlistpainter.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+constexpr OUStringLiteral DATA_DIRECTORY = u"/svx/qa/unit/data/";
+
+/// Tests for svx/source/unodraw/ code.
+class UnodrawTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools
+{
+protected:
+ uno::Reference<lang::XComponent> mxComponent;
+
+public:
+ void setUp() override;
+ void tearDown() override;
+};
+
+void UnodrawTest::setUp()
+{
+ test::BootstrapFixture::setUp();
+
+ mxDesktop.set(frame::Desktop::create(mxComponentContext));
+}
+
+void UnodrawTest::tearDown()
+{
+ if (mxComponent.is())
+ mxComponent->dispose();
+
+ test::BootstrapFixture::tearDown();
+}
+
+CPPUNIT_TEST_FIXTURE(UnodrawTest, testWriterGraphicExport)
+{
+ // Load a document with a Writer picture in it.
+ OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "unodraw-writer-image.odt";
+ mxComponent = loadFromDesktop(aURL);
+ uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
+ uno::Reference<lang::XComponent> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
+
+ // Export it as JPEG.
+ uno::Reference<drawing::XGraphicExportFilter> xExportFilter
+ = drawing::GraphicExportFilter::create(mxComponentContext);
+ // This resulted in a css::lang::IllegalArgumentException for a Writer
+ // picture.
+ xExportFilter->setSourceDocument(xShape);
+
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ uno::Sequence<beans::PropertyValue> aProperties(
+ comphelper::InitPropertySequence({ { "URL", uno::Any(aTempFile.GetURL()) },
+ { "MediaType", uno::Any(OUString("image/jpeg")) } }));
+ CPPUNIT_ASSERT(xExportFilter->filter(aProperties));
+}
+
+CPPUNIT_TEST_FIXTURE(UnodrawTest, testTdf93998)
+{
+ mxComponent = loadFromDesktop(m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf93998.odp");
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xDrawPagesSupplier.is());
+
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xDrawPage.is());
+
+ uno::Reference<beans::XPropertySet> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xShape.is());
+
+ uno::Reference<lang::XMultiServiceFactory> xFactory = comphelper::getProcessServiceFactory();
+ uno::Reference<awt::XControlModel> xModel(
+ xFactory->createInstance("com.sun.star.awt.UnoControlDialogModel"), uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xModel.is());
+
+ uno::Reference<beans::XPropertySet> xModelProps(xModel, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xModelProps.is());
+
+ // This resulted in a uno::RuntimeException, assigning a shape to a dialog model's image was
+ // broken.
+ xModelProps->setPropertyValue("ImageURL", xShape->getPropertyValue("GraphicURL"));
+ uno::Reference<graphic::XGraphic> xGraphic;
+ xModelProps->getPropertyValue("Graphic") >>= xGraphic;
+ CPPUNIT_ASSERT(xGraphic.is());
+}
+
+CPPUNIT_TEST_FIXTURE(UnodrawTest, testTableShadowDirect)
+{
+ // Create an Impress document an insert a table shape.
+ mxComponent = loadFromDesktop("private:factory/simpress",
+ "com.sun.star.presentation.PresentationDocument");
+ uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XShape> xShape(
+ xFactory->createInstance("com.sun.star.drawing.TableShape"), uno::UNO_QUERY);
+ xShape->setPosition(awt::Point(1000, 1000));
+ xShape->setSize(awt::Size(10000, 10000));
+ uno::Reference<drawing::XDrawPagesSupplier> xSupplier(mxComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPages> xDrawPages = xSupplier->getDrawPages();
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY);
+ xDrawPage->add(xShape);
+
+ // Create a red shadow on it without touching its style.
+ uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+ // Without the accompanying fix in place, this test would have failed with throwing a
+ // beans.UnknownPropertyException, as shadow-as-direct-formatting on tables were not possible.
+ xShapeProps->setPropertyValue("Shadow", uno::Any(true));
+ sal_Int32 nRed = 0xff0000;
+ xShapeProps->setPropertyValue("ShadowColor", uno::Any(nRed));
+ CPPUNIT_ASSERT(xShapeProps->getPropertyValue("ShadowColor") >>= nRed);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0xff0000), nRed);
+
+ // Add text.
+ uno::Reference<table::XCellRange> xTable(xShapeProps->getPropertyValue("Model"),
+ uno::UNO_QUERY);
+ uno::Reference<text::XTextRange> xCell(xTable->getCellByPosition(0, 0), uno::UNO_QUERY);
+ xCell->setString("A1");
+
+ // Generates drawinglayer primitives for the shape.
+ auto pDrawPage = dynamic_cast<SvxDrawPage*>(xDrawPage.get());
+ CPPUNIT_ASSERT(pDrawPage);
+ SdrPage* pSdrPage = pDrawPage->GetSdrPage();
+ ScopedVclPtrInstance<VirtualDevice> aVirtualDevice;
+ sdr::contact::ObjectContactOfObjListPainter aObjectContact(*aVirtualDevice,
+ { pSdrPage->GetObj(0) }, nullptr);
+ const sdr::contact::ViewObjectContact& rDrawPageVOContact
+ = pSdrPage->GetViewContact().GetViewObjectContact(aObjectContact);
+ sdr::contact::DisplayInfo aDisplayInfo;
+ drawinglayer::primitive2d::Primitive2DContainer xPrimitiveSequence;
+ rDrawPageVOContact.getPrimitive2DSequenceHierarchy(aDisplayInfo, xPrimitiveSequence);
+
+ // Check the primitives.
+ drawinglayer::Primitive2dXmlDump aDumper;
+ xmlDocUniquePtr pDocument = aDumper.dumpAndParse(xPrimitiveSequence);
+ assertXPath(pDocument, "//shadow", /*nNumberOfNodes=*/1);
+
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 0
+ // - Actual : 1
+ // i.e. there was shadow for the cell text, while here PowerPoint-compatible output is expected,
+ // which has no shadow for cell text (only for cell borders and cell background).
+ assertXPath(pDocument, "//shadow//sdrblocktext", /*nNumberOfNodes=*/0);
+}
+
+CPPUNIT_TEST_FIXTURE(UnodrawTest, testTitleShapeBullets)
+{
+ // Create a title shape with 2 paragraphs in it.
+ mxComponent = loadFromDesktop("private:factory/simpress",
+ "com.sun.star.presentation.PresentationDocument");
+ uno::Reference<drawing::XDrawPagesSupplier> xSupplier(mxComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPages> xDrawPages = xSupplier->getDrawPages();
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY);
+ // A default document contains a title shape and a text shape on the first slide.
+ uno::Reference<drawing::XShape> xTitleShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
+ uno::Reference<lang::XServiceInfo> xTitleShapeInfo(xTitleShape, uno::UNO_QUERY);
+ CPPUNIT_ASSERT(xTitleShapeInfo->supportsService("com.sun.star.presentation.TitleTextShape"));
+ uno::Reference<text::XTextRange> xTitleShapeText(xTitleShape, uno::UNO_QUERY);
+ uno::Reference<text::XText> xText = xTitleShapeText->getText();
+ uno::Reference<text::XTextRange> xCursor = xText->createTextCursor();
+ xText->insertString(xCursor, "foo", /*bAbsorb=*/false);
+ xText->insertControlCharacter(xCursor, text::ControlCharacter::APPEND_PARAGRAPH,
+ /*bAbsorb=*/false);
+ xText->insertString(xCursor, "bar", /*bAbsorb=*/false);
+
+ // Check that the title shape has 2 paragraphs.
+ uno::Reference<container::XEnumerationAccess> xTextEA(xText, uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xTextE = xTextEA->createEnumeration();
+ // Has a first paragraph.
+ CPPUNIT_ASSERT(xTextE->hasMoreElements());
+ xTextE->nextElement();
+ // Has a second paragraph.
+ // Without the accompanying fix in place, this test would have failed, because the 2 paragraphs
+ // were merged together (e.g. 1 bullet instead of 2 bullets for bulleted paragraphs).
+ CPPUNIT_ASSERT(xTextE->hasMoreElements());
+}
+
+CPPUNIT_TEST_FIXTURE(UnodrawTest, testPngExport)
+{
+ // Given an empty Impress document:
+ mxComponent = loadFromDesktop("private:factory/simpress",
+ "com.sun.star.presentation.PresentationDocument");
+
+ // When exporting that document to PNG with a JSON size:
+ uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY_THROW);
+ SvMemoryStream aStream;
+ uno::Reference<io::XOutputStream> xOut = new utl::OOutputStreamWrapper(aStream);
+ utl::MediaDescriptor aMediaDescriptor;
+ aMediaDescriptor["FilterName"] <<= OUString("impress_png_Export");
+ aMediaDescriptor["FilterOptions"]
+ <<= OUString("{\"PixelHeight\":{\"type\":\"long\",\"value\":\"192\"},"
+ "\"PixelWidth\":{\"type\":\"long\",\"value\":\"192\"}}");
+ aMediaDescriptor["OutputStream"] <<= xOut;
+ xStorable->storeToURL("private:stream", aMediaDescriptor.getAsConstPropertyValueList());
+
+ // Then make sure that the size request is handled:
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ vcl::PngImageReader aPngReader(aStream);
+ BitmapEx aBitmapEx;
+ aPngReader.read(aBitmapEx);
+ Size aSize = aBitmapEx.GetSizePixel();
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 192
+ // - Actual : 595
+ // i.e. it was not possible to influence the size from the cmdline.
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(192), aSize.getHeight());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(192), aSize.getWidth());
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/xml.cxx b/svx/qa/unit/xml.cxx
new file mode 100644
index 000000000..de16b39be
--- /dev/null
+++ b/svx/qa/unit/xml.cxx
@@ -0,0 +1,68 @@
+/* -*- 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/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawPage.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+
+#include <rtl/ustring.hxx>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+/// Tests for svx/source/xml/ code.
+class Test : public test::BootstrapFixture, public unotest::MacrosTest
+{
+protected:
+ uno::Reference<lang::XComponent> mxComponent;
+
+public:
+ virtual void setUp() override
+ {
+ test::BootstrapFixture::setUp();
+ mxDesktop.set(frame::Desktop::create(m_xContext));
+ }
+
+ virtual void tearDown() override
+ {
+ if (mxComponent.is())
+ {
+ mxComponent->dispose();
+ }
+ test::BootstrapFixture::tearDown();
+ }
+ uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
+};
+
+CPPUNIT_TEST_FIXTURE(Test, test3DObjectFallback)
+{
+ // Load a document which has a 3D model we don't understand, but has a fallback PNG.
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(u"svx/qa/unit/data/3d-object-fallback.odp");
+ getComponent() = loadFromDesktop(aURL);
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
+ uno::Reference<graphic::XGraphic> xGraphic;
+ xShape->getPropertyValue("Graphic") >>= xGraphic;
+ // Without the accompanying fix in place, this test would have failed, we could not read
+ // Models/Fallbacks/duck.png, as we assumed a format like Pictures/something.png, i.e. a single
+ // slash in the path.
+ CPPUNIT_ASSERT(xGraphic.is());
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unit/xoutdev.cxx b/svx/qa/unit/xoutdev.cxx
new file mode 100644
index 000000000..41b460164
--- /dev/null
+++ b/svx/qa/unit/xoutdev.cxx
@@ -0,0 +1,156 @@
+/* -*- 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/bootstrapfixture.hxx>
+#include <unotest/macros_test.hxx>
+
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+
+#include <sal/types.h>
+#include <sfx2/app.hxx>
+#include <tools/stream.hxx>
+#include <unotest/directories.hxx>
+#include <unotools/tempfile.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <svx/xoutbmp.hxx>
+#include <vcl/filter/PDFiumLibrary.hxx>
+
+using namespace com::sun::star;
+
+class XOutdevTest : public test::BootstrapFixture, public unotest::MacrosTest
+{
+ uno::Reference<lang::XComponent> mxComponent;
+
+public:
+ virtual void setUp() override;
+ void tearDown() override;
+ uno::Reference<lang::XComponent>& getComponent() { return mxComponent; }
+};
+
+void XOutdevTest::setUp()
+{
+ test::BootstrapFixture::setUp();
+
+ mxDesktop.set(frame::Desktop::create(mxComponentContext));
+}
+
+void XOutdevTest::tearDown()
+{
+ if (mxComponent.is())
+ mxComponent->dispose();
+
+ test::BootstrapFixture::tearDown();
+}
+
+CPPUNIT_TEST_FIXTURE(XOutdevTest, testPdfGraphicExport)
+{
+ auto pPdfium = vcl::pdf::PDFiumLibrary::get();
+ if (!pPdfium)
+ {
+ return;
+ }
+
+ // Import the graphic.
+ Graphic aGraphic;
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(u"svx/qa/unit/data/graphic.pdf");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE,
+ GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, aURL, aStream));
+
+ // Export it.
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ XOutFlags const eFlags = XOutFlags::DontExpandFilename | XOutFlags::DontAddExtension
+ | XOutFlags::UseNativeIfPossible;
+ OUString aTempURL = aTempFile.GetURL();
+ XOutBitmap::WriteGraphic(aGraphic, aTempURL, "pdf", eFlags);
+
+ // Assert that the output looks like a PDF.
+ SvStream* pStream = aTempFile.GetStream(StreamMode::READ);
+ CPPUNIT_ASSERT(pStream->TellEnd() > 5);
+ sal_uInt8 sFirstBytes[5];
+ pStream->ReadBytes(sFirstBytes, 5);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>('%'), sFirstBytes[0]);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>('P'), sFirstBytes[1]);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>('D'), sFirstBytes[2]);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>('F'), sFirstBytes[3]);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>('-'), sFirstBytes[4]);
+}
+
+CPPUNIT_TEST_FIXTURE(XOutdevTest, testTdf60684)
+{
+ Graphic aGraphic;
+ test::Directories aDirectories;
+ OUString aURL = aDirectories.getURLFromSrc(u"svx/qa/unit/data/tdf60684.jpg");
+ SvFileStream aStream(aURL, StreamMode::READ);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE,
+ GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, aURL, aStream));
+
+ // Export it.
+ utl::TempFile aTempFile;
+ aTempFile.EnableKillingFile();
+ XOutFlags const eFlags = XOutFlags::DontExpandFilename | XOutFlags::DontAddExtension
+ | XOutFlags::UseNativeIfPossible;
+ OUString aTempURL = aTempFile.GetURL();
+ XOutBitmap::WriteGraphic(aGraphic, aTempURL, "png", eFlags);
+
+ SvStream* pStream = aTempFile.GetStream(StreamMode::READ);
+ CPPUNIT_ASSERT(pStream->TellEnd() > 4);
+ sal_uInt8 sFirstBytes[4];
+ pStream->ReadBytes(sFirstBytes, 4);
+
+ //Checks if the file's header matches a PNG's expected header
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>('P'), sFirstBytes[1]);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>('N'), sFirstBytes[2]);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt8>('G'), sFirstBytes[3]);
+}
+
+CPPUNIT_TEST_FIXTURE(XOutdevTest, testFillColorThemeUnoApi)
+{
+ // Given an empty Impress document with a (title) shape:
+ getComponent() = loadFromDesktop("private:factory/simpress",
+ "com.sun.star.presentation.PresentationDocument");
+
+ // When setting the theme index of the shape's fill color:
+ uno::Reference<drawing::XDrawPagesSupplier> xPagesSupplier(getComponent(), uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPage> xPage(xPagesSupplier->getDrawPages()->getByIndex(0),
+ uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xShape(xPage->getByIndex(0), uno::UNO_QUERY);
+ sal_Int16 nExpected = 4; // Accent 1
+ xShape->setPropertyValue("FillColorTheme", uno::Any(nExpected));
+
+ // 80% lighter
+ sal_Int16 nExpectedLumMod = 2000;
+ xShape->setPropertyValue("FillColorLumMod", uno::Any(nExpectedLumMod));
+ sal_Int16 nExpectedLumOff = 8000;
+ xShape->setPropertyValue("FillColorLumOff", uno::Any(nExpectedLumOff));
+
+ // Then make sure the value we read back is the expected one:
+ sal_Int16 nActual = -1;
+ xShape->getPropertyValue("FillColorTheme") >>= nActual;
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 4
+ // - Actual : -1
+ // i.e. setting the value was broken.
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+ xShape->getPropertyValue("FillColorLumMod") >>= nActual;
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: 2000
+ // - Actual : 8000
+ // i.e. FillColorLumOff was set as FillColor, then getting FillColorLumMod returned FillColor.
+ CPPUNIT_ASSERT_EQUAL(nExpectedLumMod, nActual);
+ xShape->getPropertyValue("FillColorLumOff") >>= nActual;
+ CPPUNIT_ASSERT_EQUAL(nExpectedLumOff, nActual);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/qa/unoapi/knownissues.xcl b/svx/qa/unoapi/knownissues.xcl
new file mode 100644
index 000000000..e5d4b3a17
--- /dev/null
+++ b/svx/qa/unoapi/knownissues.xcl
@@ -0,0 +1,108 @@
+#
+# 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/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+### i85263 ###
+svx.SvxShape::com::sun::star::drawing::TextProperties::TextWritingMode
+svx.SvxShapeConnector::com::sun::star::drawing::TextProperties::TextWritingMode
+# -> disabled in svx.sce
+
+### i46736 ###
+svx.AccessibleImageBullet
+# -> disabled in svx.sce
+
+### i85501 ###
+svx.SvxGraphCtrlAccessibleContext
+# -> disabled in svx.sce
+
+### i85539 ###
+svx.AccessiblePresentationShape
+# -> disabled in svx.sce
+
+### i85476 ###
+svx.SvxUnoText::com::sun::star::text::XTextRangeMover
+
+### i85478 ###
+svx.SvxUnoTextRange::com::sun::star::text::XTextRange
+
+### i35965 ###
+svx.SvxGraphCtrlAccessibleContext::com::sun::star::accessibility::XAccessibleEventBroadcaster
+
+### i85479 ###
+svx.AccessiblePageShape::com::sun::star::accessibility::XAccessibleEventBroadcaster
+
+### i85481 ###
+svx.SvxUnoTextCursor::com::sun::star::beans::XMultiPropertyStates
+
+### i58108 ###
+svx.SvxUnoText::com::sun::star::text::XTextRangeCompare
+
+### i38623 ###
+svx.SvxUnoTextCursor::com::sun::star::text::XTextRange
+
+### i73993 ###
+svx.SvxUnoTextContent::com::sun::star::style::ParagraphProperties
+svx.SvxUnoTextCursor::com::sun::star::style::ParagraphProperties
+svx.SvxUnoTextRange::com::sun::star::style::ParagraphProperties
+svx.SvxGraphicObject::com::sun::star::style::ParagraphProperties
+svx.SvxShape::com::sun::star::style::ParagraphProperties
+svx.SvxShapeCircle::com::sun::star::style::ParagraphProperties
+svx.SvxShapeConnector::com::sun::star::style::ParagraphProperties
+svx.SvxShapeDimensioning::com::sun::star::style::ParagraphProperties
+svx.SvxShapePolyPolygon::com::sun::star::style::ParagraphProperties
+svx.SvxShapePolyPolygonBezier::com::sun::star::style::ParagraphProperties
+
+### i23461 ###
+svx.SvxDrawPage::com::sun::star::drawing::XMasterPageTarget
+
+### i85485 ###
+svx.SvxShapeDimensioning::com::sun::star::drawing::XShape
+
+### i58125 ###
+svx.SvxGraphicObject::com::sun::star::drawing::GraphicObjectShape
+
+### i87698 ###
+svx.SvxShapeDimensioning::com::sun::star::drawing::TextProperties
+
+### i87746 ###
+svx.SvxGraphicObject
+svx.SvxShapeCircle
+svx.SvxShapeControl
+svx.SvxShapeDimensioning
+svx.SvxShapeGroup
+svx.SvxShapePolyPolygon
+svx.SvxShapePolyPolygonBezier
+
+### i88543 ###
+svx.SvxUnoTextRange::com::sun::star::beans::XPropertySet
+
+### i90294 ###
+svx.GraphicExporter
+# -> disabled in svx.sce
+
+### i98339 ###
+svx.AccessibleControlShape
+# -> disabled in svx.sce
+
+### i111114 ###
+svx.AccessiblePresentationOLEShape::com::sun::star::accessibility::XAccessibleComponent
+
+### i111169 ###
+svx.AccessiblePageShape::com::sun::star::accessibility::XAccessibleComponent
+
+### i114642 ###
+svx.SvxUnoTextContent::com::sun::star::style::CharacterProperties
diff --git a/svx/qa/unoapi/svx.sce b/svx/qa/unoapi/svx.sce
new file mode 100644
index 000000000..1c3925f58
--- /dev/null
+++ b/svx/qa/unoapi/svx.sce
@@ -0,0 +1,49 @@
+#
+# 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/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+#i98339 -o svx.AccessibleControlShape
+#i111278 -o svx.AccessibleEditableTextPara
+#i111252 -o svx.AccessibleGraphicShape
+#i46736 -o svx.AccessibleImageBullet
+#i111252 -o svx.AccessibleOLEShape
+#i111252 -o svx.AccessiblePageShape
+#i111216 -o svx.AccessiblePresentationGraphicShape
+#i111216 -o svx.AccessiblePresentationOLEShape
+#i85539 -o svx.AccessiblePresentationShape
+-o svx.AccessibleShape
+#i90294 -o svx.GraphicExporter
+-o svx.SvxDrawPage
+#i85501 -o svx.SvxGraphCtrlAccessibleContext
+#i87746 -o svx.SvxGraphicObject
+#i85263 -o svx.SvxShape
+#i87746 -o svx.SvxShapeCircle
+-o svx.SvxShapeCollection
+#i85263 -o svx.SvxShapeConnector
+#i87746 -o svx.SvxShapeControl
+#i87746 -o svx.SvxShapeDimensioning
+#i87746 -o svx.SvxShapeGroup
+#i87746 -o svx.SvxShapePolyPolygon
+#i87746 -o svx.SvxShapePolyPolygonBezier
+-o svx.SvxUnoNumberingRules
+-o svx.SvxUnoText
+-o svx.SvxUnoTextContent
+-o svx.SvxUnoTextContentEnum
+-o svx.SvxUnoTextCursor
+-o svx.SvxUnoTextField
+-o svx.SvxUnoTextRange
+-o svx.SvxUnoTextRangeEnumeration
diff --git a/svx/qa/unoapi/testdocuments/SvxShape.sxd b/svx/qa/unoapi/testdocuments/SvxShape.sxd
new file mode 100644
index 000000000..27f06d541
--- /dev/null
+++ b/svx/qa/unoapi/testdocuments/SvxShape.sxd
Binary files differ
diff --git a/svx/qa/unoapi/testdocuments/crazy-blue.jpg b/svx/qa/unoapi/testdocuments/crazy-blue.jpg
new file mode 100644
index 000000000..001c88b63
--- /dev/null
+++ b/svx/qa/unoapi/testdocuments/crazy-blue.jpg
Binary files differ
diff --git a/svx/qa/unoapi/testdocuments/space-metal.jpg b/svx/qa/unoapi/testdocuments/space-metal.jpg
new file mode 100644
index 000000000..d23344389
--- /dev/null
+++ b/svx/qa/unoapi/testdocuments/space-metal.jpg
Binary files differ