diff options
Diffstat (limited to 'svx/qa/unit')
85 files changed, 4086 insertions, 0 deletions
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 Binary files differnew file mode 100644 index 000000000..17349924d --- /dev/null +++ b/svx/qa/unit/data/0-width-text-wrap.pptx diff --git a/svx/qa/unit/data/3d-object-fallback.odp b/svx/qa/unit/data/3d-object-fallback.odp Binary files differnew file mode 100644 index 000000000..5ced0be47 --- /dev/null +++ b/svx/qa/unit/data/3d-object-fallback.odp diff --git a/svx/qa/unit/data/FontWork.odg b/svx/qa/unit/data/FontWork.odg Binary files differnew file mode 100644 index 000000000..e14d2f70b --- /dev/null +++ b/svx/qa/unit/data/FontWork.odg diff --git a/svx/qa/unit/data/GraphicObjectResolverTest.zip b/svx/qa/unit/data/GraphicObjectResolverTest.zip Binary files differnew file mode 100644 index 000000000..4c19bf2b0 --- /dev/null +++ b/svx/qa/unit/data/GraphicObjectResolverTest.zip diff --git a/svx/qa/unit/data/auto-height-multi-col-shape.pptx b/svx/qa/unit/data/auto-height-multi-col-shape.pptx Binary files differnew file mode 100644 index 000000000..12f232b0f --- /dev/null +++ b/svx/qa/unit/data/auto-height-multi-col-shape.pptx diff --git a/svx/qa/unit/data/chart.ods b/svx/qa/unit/data/chart.ods Binary files differnew file mode 100644 index 000000000..d9e9bdf18 --- /dev/null +++ b/svx/qa/unit/data/chart.ods diff --git a/svx/qa/unit/data/graphic.pdf b/svx/qa/unit/data/graphic.pdf Binary files differnew file mode 100644 index 000000000..4b53d2056 --- /dev/null +++ b/svx/qa/unit/data/graphic.pdf diff --git a/svx/qa/unit/data/page-view-draw-layer-clip.docx b/svx/qa/unit/data/page-view-draw-layer-clip.docx Binary files differnew file mode 100644 index 000000000..7136a800f --- /dev/null +++ b/svx/qa/unit/data/page-view-draw-layer-clip.docx diff --git a/svx/qa/unit/data/shadow-scale-origin.pptx b/svx/qa/unit/data/shadow-scale-origin.pptx Binary files differnew file mode 100644 index 000000000..a0a164a3c --- /dev/null +++ b/svx/qa/unit/data/shadow-scale-origin.pptx diff --git a/svx/qa/unit/data/slide-background.odp b/svx/qa/unit/data/slide-background.odp Binary files differnew file mode 100644 index 000000000..ea62bd639 --- /dev/null +++ b/svx/qa/unit/data/slide-background.odp diff --git a/svx/qa/unit/data/slide-background.png b/svx/qa/unit/data/slide-background.png Binary files differnew file mode 100644 index 000000000..3a8c5ceb4 --- /dev/null +++ b/svx/qa/unit/data/slide-background.png 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 Binary files differnew file mode 100644 index 000000000..959940e09 --- /dev/null +++ b/svx/qa/unit/data/table-shadow-blur.pptx diff --git a/svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odp b/svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odp Binary files differnew file mode 100644 index 000000000..9b36d45ee --- /dev/null +++ b/svx/qa/unit/data/tdf103474_commandG_CaseZeroHeight.odp diff --git a/svx/qa/unit/data/tdf103474_commandT_CaseZeroHeight.odp b/svx/qa/unit/data/tdf103474_commandT_CaseZeroHeight.odp Binary files differnew file mode 100644 index 000000000..54a4377ca --- /dev/null +++ b/svx/qa/unit/data/tdf103474_commandT_CaseZeroHeight.odp diff --git a/svx/qa/unit/data/tdf115813_HandleMovementOOXMLPresetShapes.pptx b/svx/qa/unit/data/tdf115813_HandleMovementOOXMLPresetShapes.pptx Binary files differnew file mode 100644 index 000000000..3435fef61 --- /dev/null +++ b/svx/qa/unit/data/tdf115813_HandleMovementOOXMLPresetShapes.pptx diff --git a/svx/qa/unit/data/tdf121761_Accuracy_command_X.odp b/svx/qa/unit/data/tdf121761_Accuracy_command_X.odp Binary files differnew file mode 100644 index 000000000..1de391758 --- /dev/null +++ b/svx/qa/unit/data/tdf121761_Accuracy_command_X.odp diff --git a/svx/qa/unit/data/tdf121845_HalfEllipseVML.doc b/svx/qa/unit/data/tdf121845_HalfEllipseVML.doc Binary files differnew file mode 100644 index 000000000..043e4e15f --- /dev/null +++ b/svx/qa/unit/data/tdf121845_HalfEllipseVML.doc diff --git a/svx/qa/unit/data/tdf121845_Two_commands_U.odg b/svx/qa/unit/data/tdf121845_Two_commands_U.odg Binary files differnew file mode 100644 index 000000000..c0f7ff34f --- /dev/null +++ b/svx/qa/unit/data/tdf121845_Two_commands_U.odg diff --git a/svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg b/svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg Binary files differnew file mode 100644 index 000000000..349c2eb81 --- /dev/null +++ b/svx/qa/unit/data/tdf121845_WidthOrientation_command_U.odg diff --git a/svx/qa/unit/data/tdf121845_start40_swing480.doc b/svx/qa/unit/data/tdf121845_start40_swing480.doc Binary files differnew file mode 100644 index 000000000..ff37aab3f --- /dev/null +++ b/svx/qa/unit/data/tdf121845_start40_swing480.doc diff --git a/svx/qa/unit/data/tdf121952_Toggle_direction_command_X.odp b/svx/qa/unit/data/tdf121952_Toggle_direction_command_X.odp Binary files differnew file mode 100644 index 000000000..fbe1b7d40 --- /dev/null +++ b/svx/qa/unit/data/tdf121952_Toggle_direction_command_X.odp diff --git a/svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptx b/svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptx Binary files differnew file mode 100644 index 000000000..919675ef9 --- /dev/null +++ b/svx/qa/unit/data/tdf122323_swingAngle_larger360deg.pptx diff --git a/svx/qa/unit/data/tdf122964_MultipleMoveTo.odg b/svx/qa/unit/data/tdf122964_MultipleMoveTo.odg Binary files differnew file mode 100644 index 000000000..63d80fd06 --- /dev/null +++ b/svx/qa/unit/data/tdf122964_MultipleMoveTo.odg diff --git a/svx/qa/unit/data/tdf124029_Arc_position.doc b/svx/qa/unit/data/tdf124029_Arc_position.doc Binary files differnew file mode 100644 index 000000000..d5396c375 --- /dev/null +++ b/svx/qa/unit/data/tdf124029_Arc_position.doc diff --git a/svx/qa/unit/data/tdf124212_handle_position.odg b/svx/qa/unit/data/tdf124212_handle_position.odg Binary files differnew file mode 100644 index 000000000..7a4eb0517 --- /dev/null +++ b/svx/qa/unit/data/tdf124212_handle_position.odg diff --git a/svx/qa/unit/data/tdf124740_HandleInOOXMLUserShape.pptx b/svx/qa/unit/data/tdf124740_HandleInOOXMLUserShape.pptx Binary files differnew file mode 100644 index 000000000..dd6df7b03 --- /dev/null +++ b/svx/qa/unit/data/tdf124740_HandleInOOXMLUserShape.pptx diff --git a/svx/qa/unit/data/tdf125782_QuadraticCurveTo.odg b/svx/qa/unit/data/tdf125782_QuadraticCurveTo.odg Binary files differnew file mode 100644 index 000000000..ba7b49536 --- /dev/null +++ b/svx/qa/unit/data/tdf125782_QuadraticCurveTo.odg diff --git a/svx/qa/unit/data/tdf126060_3D_Z_Rotation.pptx b/svx/qa/unit/data/tdf126060_3D_Z_Rotation.pptx Binary files differnew file mode 100644 index 000000000..8c8798f21 --- /dev/null +++ b/svx/qa/unit/data/tdf126060_3D_Z_Rotation.pptx diff --git a/svx/qa/unit/data/tdf126512_OOXMLHandleMovementInODF.odp b/svx/qa/unit/data/tdf126512_OOXMLHandleMovementInODF.odp Binary files differnew file mode 100644 index 000000000..7dd283f88 --- /dev/null +++ b/svx/qa/unit/data/tdf126512_OOXMLHandleMovementInODF.odp diff --git a/svx/qa/unit/data/tdf127785_Mirror.odp b/svx/qa/unit/data/tdf127785_Mirror.odp Binary files differnew file mode 100644 index 000000000..ff867839f --- /dev/null +++ b/svx/qa/unit/data/tdf127785_Mirror.odp diff --git a/svx/qa/unit/data/tdf127785_TextRotateAngle.odp b/svx/qa/unit/data/tdf127785_TextRotateAngle.odp Binary files differnew file mode 100644 index 000000000..742f12d4d --- /dev/null +++ b/svx/qa/unit/data/tdf127785_TextRotateAngle.odp diff --git a/svx/qa/unit/data/tdf127785_asymmetricTextBoxFlipV.odg b/svx/qa/unit/data/tdf127785_asymmetricTextBoxFlipV.odg Binary files differnew file mode 100644 index 000000000..ea700eaf7 --- /dev/null +++ b/svx/qa/unit/data/tdf127785_asymmetricTextBoxFlipV.odg diff --git a/svx/qa/unit/data/tdf128413_tbrl_OnOff.odp b/svx/qa/unit/data/tdf128413_tbrl_OnOff.odp Binary files differnew file mode 100644 index 000000000..f10f98bbe --- /dev/null +++ b/svx/qa/unit/data/tdf128413_tbrl_OnOff.odp diff --git a/svx/qa/unit/data/tdf129532_MatrixFlipV.odg b/svx/qa/unit/data/tdf129532_MatrixFlipV.odg Binary files differnew file mode 100644 index 000000000..eb0c10b3d --- /dev/null +++ b/svx/qa/unit/data/tdf129532_MatrixFlipV.odg diff --git a/svx/qa/unit/data/tdf130076_FlipOnSectorSection.odg b/svx/qa/unit/data/tdf130076_FlipOnSectorSection.odg Binary files differnew file mode 100644 index 000000000..058e7e044 --- /dev/null +++ b/svx/qa/unit/data/tdf130076_FlipOnSectorSection.odg diff --git a/svx/qa/unit/data/tdf136176_rot30_flip.odg b/svx/qa/unit/data/tdf136176_rot30_flip.odg Binary files differnew file mode 100644 index 000000000..75707a0f5 --- /dev/null +++ b/svx/qa/unit/data/tdf136176_rot30_flip.odg diff --git a/svx/qa/unit/data/tdf138945_resizeRotatedShape.odg b/svx/qa/unit/data/tdf138945_resizeRotatedShape.odg Binary files differnew file mode 100644 index 000000000..20a2825af --- /dev/null +++ b/svx/qa/unit/data/tdf138945_resizeRotatedShape.odg diff --git a/svx/qa/unit/data/tdf140321_Matte_import.ppt b/svx/qa/unit/data/tdf140321_Matte_import.ppt Binary files differnew file mode 100644 index 000000000..83e790fd4 --- /dev/null +++ b/svx/qa/unit/data/tdf140321_Matte_import.ppt diff --git a/svx/qa/unit/data/tdf140321_material_specular.odp b/svx/qa/unit/data/tdf140321_material_specular.odp Binary files differnew file mode 100644 index 000000000..03869914f --- /dev/null +++ b/svx/qa/unit/data/tdf140321_material_specular.odp diff --git a/svx/qa/unit/data/tdf140321_metal.odp b/svx/qa/unit/data/tdf140321_metal.odp Binary files differnew file mode 100644 index 000000000..a81ee0dfb --- /dev/null +++ b/svx/qa/unit/data/tdf140321_metal.odp diff --git a/svx/qa/unit/data/tdf140321_phong.odp b/svx/qa/unit/data/tdf140321_phong.odp Binary files differnew file mode 100644 index 000000000..989a08485 --- /dev/null +++ b/svx/qa/unit/data/tdf140321_phong.odp diff --git a/svx/qa/unit/data/tdf141021_ExtrusionNorth.odp b/svx/qa/unit/data/tdf141021_ExtrusionNorth.odp Binary files differnew file mode 100644 index 000000000..559b2c0d5 --- /dev/null +++ b/svx/qa/unit/data/tdf141021_ExtrusionNorth.odp diff --git a/svx/qa/unit/data/tdf141268.odp b/svx/qa/unit/data/tdf141268.odp Binary files differnew file mode 100644 index 000000000..41d0dc446 --- /dev/null +++ b/svx/qa/unit/data/tdf141268.odp diff --git a/svx/qa/unit/data/tdf144988_Fontwork_FontSize.odp b/svx/qa/unit/data/tdf144988_Fontwork_FontSize.odp Binary files differnew file mode 100644 index 000000000..943bc143b --- /dev/null +++ b/svx/qa/unit/data/tdf144988_Fontwork_FontSize.odp diff --git a/svx/qa/unit/data/tdf145004_gap_by_ScaleX.pptx b/svx/qa/unit/data/tdf145004_gap_by_ScaleX.pptx Binary files differnew file mode 100644 index 000000000..900a89675 --- /dev/null +++ b/svx/qa/unit/data/tdf145004_gap_by_ScaleX.pptx diff --git a/svx/qa/unit/data/tdf145111_TL_BL_Fontwork.odp b/svx/qa/unit/data/tdf145111_TL_BL_Fontwork.odp Binary files differnew file mode 100644 index 000000000..257023cfa --- /dev/null +++ b/svx/qa/unit/data/tdf145111_TL_BL_Fontwork.odp diff --git a/svx/qa/unit/data/tdf145245_ExtrusionPosition.odp b/svx/qa/unit/data/tdf145245_ExtrusionPosition.odp Binary files differnew file mode 100644 index 000000000..a356cf9ed --- /dev/null +++ b/svx/qa/unit/data/tdf145245_ExtrusionPosition.odp diff --git a/svx/qa/unit/data/tdf145700_3D_FirstLightHarsh.doc b/svx/qa/unit/data/tdf145700_3D_FirstLightHarsh.doc Binary files differnew file mode 100644 index 000000000..28b8b018d --- /dev/null +++ b/svx/qa/unit/data/tdf145700_3D_FirstLightHarsh.doc diff --git a/svx/qa/unit/data/tdf145700_3D_FrontLightDim.doc b/svx/qa/unit/data/tdf145700_3D_FrontLightDim.doc Binary files differnew file mode 100644 index 000000000..5849e3eac --- /dev/null +++ b/svx/qa/unit/data/tdf145700_3D_FrontLightDim.doc diff --git a/svx/qa/unit/data/tdf145700_3D_NonUI.doc b/svx/qa/unit/data/tdf145700_3D_NonUI.doc Binary files differnew file mode 100644 index 000000000..d62d57cf0 --- /dev/null +++ b/svx/qa/unit/data/tdf145700_3D_NonUI.doc diff --git a/svx/qa/unit/data/tdf145904_center_Y0dot25.doc b/svx/qa/unit/data/tdf145904_center_Y0dot25.doc Binary files differnew file mode 100644 index 000000000..f4f9b28f2 --- /dev/null +++ b/svx/qa/unit/data/tdf145904_center_Y0dot25.doc diff --git a/svx/qa/unit/data/tdf145904_center_Y0dot25.odt b/svx/qa/unit/data/tdf145904_center_Y0dot25.odt Binary files differnew file mode 100644 index 000000000..fcdbbff6c --- /dev/null +++ b/svx/qa/unit/data/tdf145904_center_Y0dot25.odt diff --git a/svx/qa/unit/data/tdf145904_center_Zminus2000.odt b/svx/qa/unit/data/tdf145904_center_Zminus2000.odt Binary files differnew file mode 100644 index 000000000..9a19f4cbd --- /dev/null +++ b/svx/qa/unit/data/tdf145904_center_Zminus2000.odt diff --git a/svx/qa/unit/data/tdf145956_Origin.odp b/svx/qa/unit/data/tdf145956_Origin.odp Binary files differnew file mode 100644 index 000000000..7bba1dadd --- /dev/null +++ b/svx/qa/unit/data/tdf145956_Origin.odp diff --git a/svx/qa/unit/data/tdf147409_GeomItemHash.odg b/svx/qa/unit/data/tdf147409_GeomItemHash.odg Binary files differnew file mode 100644 index 000000000..770b7a6c0 --- /dev/null +++ b/svx/qa/unit/data/tdf147409_GeomItemHash.odg diff --git a/svx/qa/unit/data/tdf148501_OctagonBevel.odp b/svx/qa/unit/data/tdf148501_OctagonBevel.odp Binary files differnew file mode 100644 index 000000000..9dafaf7c2 --- /dev/null +++ b/svx/qa/unit/data/tdf148501_OctagonBevel.odp diff --git a/svx/qa/unit/data/tdf148707_two_commands_B_V.odp b/svx/qa/unit/data/tdf148707_two_commands_B_V.odp Binary files differnew file mode 100644 index 000000000..c26d371ef --- /dev/null +++ b/svx/qa/unit/data/tdf148707_two_commands_B_V.odp diff --git a/svx/qa/unit/data/tdf148714_CurvedArrows.ppt b/svx/qa/unit/data/tdf148714_CurvedArrows.ppt Binary files differnew file mode 100644 index 000000000..23e4ed0ad --- /dev/null +++ b/svx/qa/unit/data/tdf148714_CurvedArrows.ppt diff --git a/svx/qa/unit/data/tdf60684.jpg b/svx/qa/unit/data/tdf60684.jpg Binary files differnew file mode 100644 index 000000000..2218cdd72 --- /dev/null +++ b/svx/qa/unit/data/tdf60684.jpg diff --git a/svx/qa/unit/data/tdf93998.odp b/svx/qa/unit/data/tdf93998.odp Binary files differnew file mode 100644 index 000000000..889aeeb02 --- /dev/null +++ b/svx/qa/unit/data/tdf93998.odp diff --git a/svx/qa/unit/data/tdf98583_ShearHorizontal.odp b/svx/qa/unit/data/tdf98583_ShearHorizontal.odp Binary files differnew file mode 100644 index 000000000..37719e825 --- /dev/null +++ b/svx/qa/unit/data/tdf98583_ShearHorizontal.odp diff --git a/svx/qa/unit/data/tdf98584_ShearVertical.odg b/svx/qa/unit/data/tdf98584_ShearVertical.odg Binary files differnew file mode 100644 index 000000000..457521d50 --- /dev/null +++ b/svx/qa/unit/data/tdf98584_ShearVertical.odg diff --git a/svx/qa/unit/data/theme.pptx b/svx/qa/unit/data/theme.pptx Binary files differnew file mode 100644 index 000000000..08e4a3b6f --- /dev/null +++ b/svx/qa/unit/data/theme.pptx diff --git a/svx/qa/unit/data/unodraw-writer-image.odt b/svx/qa/unit/data/unodraw-writer-image.odt Binary files differnew file mode 100644 index 000000000..5264118a3 --- /dev/null +++ b/svx/qa/unit/data/unodraw-writer-image.odt diff --git a/svx/qa/unit/data/video-snapshot.pptx b/svx/qa/unit/data/video-snapshot.pptx Binary files differnew file mode 100644 index 000000000..76f7c0d50 --- /dev/null +++ b/svx/qa/unit/data/video-snapshot.pptx diff --git a/svx/qa/unit/data/viewBox_positive_twolines_strict.odp b/svx/qa/unit/data/viewBox_positive_twolines_strict.odp Binary files differnew file mode 100644 index 000000000..3425582b8 --- /dev/null +++ b/svx/qa/unit/data/viewBox_positive_twolines_strict.odp diff --git a/svx/qa/unit/gallery/data/galtest1.png b/svx/qa/unit/gallery/data/galtest1.png Binary files differnew file mode 100644 index 000000000..37be81f9d --- /dev/null +++ b/svx/qa/unit/gallery/data/galtest1.png diff --git a/svx/qa/unit/gallery/data/galtest2.png b/svx/qa/unit/gallery/data/galtest2.png Binary files differnew file mode 100644 index 000000000..bf1aac397 --- /dev/null +++ b/svx/qa/unit/gallery/data/galtest2.png diff --git a/svx/qa/unit/gallery/data/galtest3.jpg b/svx/qa/unit/gallery/data/galtest3.jpg Binary files differnew file mode 100644 index 000000000..59f67e9aa --- /dev/null +++ b/svx/qa/unit/gallery/data/galtest3.jpg 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: */ |