comphelper/qa/unit/test_hash.cxx | 26 --- comphelper/source/misc/hash.cxx | 13 + include/comphelper/hash.hxx | 2 sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_DifferentKindOfTextBox.odp |binary sd/qa/unit/tiledrendering/tiledrendering.cxx | 82 ++++++++++ sd/source/ui/tools/SlideshowLayerRenderer.cxx | 25 +-- 6 files changed, 118 insertions(+), 30 deletions(-)
New commits: commit 9fef2827aa234c893ec5cc2d608181890e24cdd1 Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Wed Oct 16 09:30:11 2024 +0200 Commit: Szymon Kłos <szymon.k...@collabora.com> CommitDate: Mon Oct 21 10:33:30 2024 +0200 slideshow: fix para. rendering when using SdrBlockTextPrimitive2D If using an empty ViewInformation, it is possible that the prim. decomposition is redone, when a different ViewInformation is used. This removes all visibility flags for the paragraphs, so the whole text is rendered. This fixes the issue by using the same instance of the ViewInformation that is also used later for rendering the primitives to the VirtualDevice, so the buffered decomposition is used. Also a test is added - it checks the hash of the buffer, which should be different for all 3 layers. For this the tostring has to be moved to the common hash.hxx, so we can reuse it in out tests. Change-Id: I4c951215a52e302d3b7b60a30c1b995002e53a4b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/174989 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Szymon Kłos <szymon.k...@collabora.com> diff --git a/comphelper/qa/unit/test_hash.cxx b/comphelper/qa/unit/test_hash.cxx index 64815ee56dc8..d5e8be2646b0 100644 --- a/comphelper/qa/unit/test_hash.cxx +++ b/comphelper/qa/unit/test_hash.cxx @@ -13,7 +13,6 @@ #include <comphelper/docpasswordhelper.hxx> #include <rtl/ustring.hxx> -#include <iomanip> #include <cppunit/TestFixture.h> #include <cppunit/extensions/HelperMacros.h> @@ -48,21 +47,6 @@ public: CPPUNIT_TEST_SUITE_END(); }; -namespace { - -std::string tostring(const std::vector<unsigned char>& a) -{ - std::stringstream aStrm; - for (auto& i:a) - { - aStrm << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(i); - } - - return aStrm.str(); -} - -} - void TestHash::testMD5() { comphelper::Hash aHash(comphelper::HashType::MD5); @@ -70,7 +54,7 @@ void TestHash::testMD5() aHash.update(reinterpret_cast<const unsigned char*>(pInput), 0); std::vector<unsigned char> calculate_hash = aHash.finalize(); CPPUNIT_ASSERT_EQUAL(size_t(16), calculate_hash.size()); - CPPUNIT_ASSERT_EQUAL(std::string("d41d8cd98f00b204e9800998ecf8427e"), tostring(calculate_hash)); + CPPUNIT_ASSERT_EQUAL(std::string("d41d8cd98f00b204e9800998ecf8427e"), comphelper::hashToString(calculate_hash)); } void TestHash::testSHA1() @@ -80,7 +64,7 @@ void TestHash::testSHA1() aHash.update(reinterpret_cast<const unsigned char*>(pInput), 0); std::vector<unsigned char> calculate_hash = aHash.finalize(); CPPUNIT_ASSERT_EQUAL(size_t(20), calculate_hash.size()); - CPPUNIT_ASSERT_EQUAL(std::string("da39a3ee5e6b4b0d3255bfef95601890afd80709"), tostring(calculate_hash)); + CPPUNIT_ASSERT_EQUAL(std::string("da39a3ee5e6b4b0d3255bfef95601890afd80709"), comphelper::hashToString(calculate_hash)); } void TestHash::testSHA256() @@ -90,7 +74,7 @@ void TestHash::testSHA256() aHash.update(reinterpret_cast<const unsigned char*>(pInput), 0); std::vector<unsigned char> calculate_hash = aHash.finalize(); CPPUNIT_ASSERT_EQUAL(size_t(32), calculate_hash.size()); - CPPUNIT_ASSERT_EQUAL(std::string("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), tostring(calculate_hash)); + CPPUNIT_ASSERT_EQUAL(std::string("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), comphelper::hashToString(calculate_hash)); } void TestHash::testSHA512() @@ -101,7 +85,7 @@ void TestHash::testSHA512() std::vector<unsigned char> calculate_hash = aHash.finalize(); CPPUNIT_ASSERT_EQUAL(size_t(64), calculate_hash.size()); std::string aStr("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); - CPPUNIT_ASSERT_EQUAL(aStr, tostring(calculate_hash)); + CPPUNIT_ASSERT_EQUAL(aStr, comphelper::hashToString(calculate_hash)); } // Must be identical to testSHA512() @@ -113,7 +97,7 @@ void TestHash::testSHA512_NoSaltNoSpin() nullptr, 0, 0, comphelper::Hash::IterCount::NONE, comphelper::HashType::SHA512); CPPUNIT_ASSERT_EQUAL(size_t(64), calculate_hash.size()); std::string aStr("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); - CPPUNIT_ASSERT_EQUAL(aStr, tostring(calculate_hash)); + CPPUNIT_ASSERT_EQUAL(aStr, comphelper::hashToString(calculate_hash)); } // Password, salt, hash and spin count taken from OOXML sheetProtection of diff --git a/comphelper/source/misc/hash.cxx b/comphelper/source/misc/hash.cxx index 25b93ad87e54..96e125cac23d 100644 --- a/comphelper/source/misc/hash.cxx +++ b/comphelper/source/misc/hash.cxx @@ -15,6 +15,8 @@ #include <rtl/alloc.h> #include <osl/endian.h> #include <config_oox.h> +#include <sstream> +#include <iomanip> #if USE_TLS_NSS #include <nss.h> @@ -27,6 +29,17 @@ namespace comphelper { +std::string hashToString(const std::vector<unsigned char>& rHash) +{ + std::stringstream aStringStream; + for (auto& i: rHash) + { + aStringStream << std::setw(2) << std::setfill('0') << std::hex << int(i); + } + + return aStringStream.str(); +} + struct HashImpl { diff --git a/include/comphelper/hash.hxx b/include/comphelper/hash.hxx index a3ad468d3eb5..1007e8dfa464 100644 --- a/include/comphelper/hash.hxx +++ b/include/comphelper/hash.hxx @@ -39,6 +39,8 @@ const sal_uInt32 SHA512_HASH_LENGTH = 64; struct HashImpl; +COMPHELPER_DLLPUBLIC std::string hashToString(const std::vector<unsigned char>& rHash); + class COMPHELPER_DLLPUBLIC Hash { private: diff --git a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_DifferentKindOfTextBox.odp b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_DifferentKindOfTextBox.odp new file mode 100644 index 000000000000..47777662805f Binary files /dev/null and b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_DifferentKindOfTextBox.odp differ diff --git a/sd/qa/unit/tiledrendering/tiledrendering.cxx b/sd/qa/unit/tiledrendering/tiledrendering.cxx index 0003f5378d9a..8442f9332c79 100644 --- a/sd/qa/unit/tiledrendering/tiledrendering.cxx +++ b/sd/qa/unit/tiledrendering/tiledrendering.cxx @@ -18,6 +18,7 @@ #include <comphelper/propertysequence.hxx> #include <comphelper/propertyvalue.hxx> #include <comphelper/string.hxx> +#include <comphelper/hash.hxx> #include <editeng/eeitem.hxx> #include <editeng/editids.hrc> #include <editeng/editobj.hxx> @@ -3801,6 +3802,87 @@ CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering_Animati pXImpressDocument->postSlideshowCleanup(); } +namespace +{ +template <typename T> +bool is_unique(std::vector<T> vec) +{ + std::sort(vec.begin(), vec.end()); + return std::unique(vec.begin(), vec.end()) == vec.end(); +} +} + +CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering_Animation_DifferentKindOfTextBox) +{ + SdXImpressDocument* pXImpressDocument = createDoc("SlideRenderingTest_Animations_DifferentKindOfTextBox.odp"); + pXImpressDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); + CPPUNIT_ASSERT(pViewShell); + SdPage* pPage = pViewShell->GetActualPage(); + CPPUNIT_ASSERT(pPage); + sal_Int32 nViewWidth = 2000; + sal_Int32 nViewHeight = 2000; + + std::string sHash = GetInterfaceHash(GetXDrawPageForSdrPage(pPage)); + CPPUNIT_ASSERT(pXImpressDocument->createSlideRenderer(sHash.c_str(), 0, nViewWidth, nViewHeight, true, true)); + CPPUNIT_ASSERT_EQUAL(2000, nViewWidth); + CPPUNIT_ASSERT_EQUAL(1500, nViewHeight); + + std::vector<std::string> aHashes; + + { + std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4); + bool bIsBitmapLayer = false; + OUString rJsonMsg; + CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); + + // Remember the hash of the buffer for uniqueness check + auto aBufferHash = comphelper::Hash::calculateHash(pBuffer.data(), pBuffer.size(), comphelper::HashType::SHA1); + aHashes.push_back(comphelper::hashToString(aBufferHash)); + + debugWriteImageToFile(1, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); + } + + { + std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4); + bool bIsBitmapLayer = false; + OUString rJsonMsg; + CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); + + // Remember the hash of the buffer for uniqueness check + auto aBufferHash = comphelper::Hash::calculateHash(pBuffer.data(), pBuffer.size(), comphelper::HashType::SHA1); + aHashes.push_back(comphelper::hashToString(aBufferHash)); + + debugWriteImageToFile(2, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); + } + + { + std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4); + bool bIsBitmapLayer = false; + OUString rJsonMsg; + CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); + + // Remember the hash of the buffer for uniqueness check + auto aBufferHash = comphelper::Hash::calculateHash(pBuffer.data(), pBuffer.size(), comphelper::HashType::SHA1); + aHashes.push_back(comphelper::hashToString(aBufferHash)); + + debugWriteImageToFile(3, pBuffer, nViewWidth, nViewHeight, rJsonMsg.toUtf8().getStr()); + } + + // check if all hashes are unique + CPPUNIT_ASSERT(is_unique(aHashes)); + + // Check we are done + { + std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4); + bool bIsBitmapLayer = false; + OUString rJsonMsg; + CPPUNIT_ASSERT(pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); + } + + pXImpressDocument->postSlideshowCleanup(); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/tools/SlideshowLayerRenderer.cxx b/sd/source/ui/tools/SlideshowLayerRenderer.cxx index 39adde615331..6ec95a2030be 100644 --- a/sd/source/ui/tools/SlideshowLayerRenderer.cxx +++ b/sd/source/ui/tools/SlideshowLayerRenderer.cxx @@ -139,7 +139,8 @@ void changeBackground(drawinglayer::primitive2d::Primitive2DContainer const& rCo } drawinglayer::primitive2d::TextHierarchyBlockPrimitive2D* -findTextBlock(drawinglayer::primitive2d::Primitive2DContainer const& rContainer) +findTextBlock(drawinglayer::primitive2d::Primitive2DContainer const& rContainer, + drawinglayer::geometry::ViewInformation2D const& rViewInformation2D) { for (size_t i = 0; i < rContainer.size(); i++) { @@ -158,7 +159,8 @@ findTextBlock(drawinglayer::primitive2d::Primitive2DContainer const& rContainer) = dynamic_cast<drawinglayer::primitive2d::GroupPrimitive2D*>(pBasePrimitive); if (pGroupPrimitive) { - auto* pTextBlock = findTextBlock(pGroupPrimitive->getChildren()); + auto* pTextBlock + = findTextBlock(pGroupPrimitive->getChildren(), rViewInformation2D); if (pTextBlock) return pTextBlock; } @@ -170,9 +172,8 @@ findTextBlock(drawinglayer::primitive2d::Primitive2DContainer const& rContainer) { // try to decompose drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer; - pBasePrimitive->get2DDecomposition(aPrimitiveContainer, - drawinglayer::geometry::ViewInformation2D()); - auto* pTextBlock = findTextBlock(aPrimitiveContainer); + pBasePrimitive->get2DDecomposition(aPrimitiveContainer, rViewInformation2D); + auto* pTextBlock = findTextBlock(aPrimitiveContainer, rViewInformation2D); if (pTextBlock) return pTextBlock; } @@ -182,9 +183,10 @@ findTextBlock(drawinglayer::primitive2d::Primitive2DContainer const& rContainer) } void modifyParagraphs(drawinglayer::primitive2d::Primitive2DContainer& rContainer, + drawinglayer::geometry::ViewInformation2D const& rViewInformation2D, std::deque<sal_Int32> const& rPreserveIndices, bool bRenderObject) { - auto* pTextBlock = findTextBlock(rContainer); + auto* pTextBlock = findTextBlock(rContainer, rViewInformation2D); if (pTextBlock) { @@ -244,6 +246,10 @@ public: return; SdrObject* pObject = rOriginal.GetViewContact().TryToGetSdrObject(); + + drawinglayer::geometry::ViewInformation2D const& rViewInformation2D + = rOriginal.GetObjectContact().getViewInformation2D(); + // Check if we are rendering an object that is valid to render (exists, and not empty) if (pObject == nullptr || pObject->IsEmptyPresObj()) return; @@ -339,7 +345,8 @@ public: if (bRenderOtherParagraphs) { - modifyParagraphs(rContainer, nOtherParagraphs, true); // render the object + modifyParagraphs(rContainer, rViewInformation2D, nOtherParagraphs, + true); // render the object mrRenderState.mnCurrentTargetParagraph = -1; } else @@ -349,8 +356,8 @@ public: std::deque<sal_Int32> aPreserveParagraphs{ nParagraph }; mrRenderState.mnCurrentTargetParagraph = nParagraph; - modifyParagraphs(rContainer, aPreserveParagraphs, - false); // render only the paragraphs + // render only the paragraphs + modifyParagraphs(rContainer, rViewInformation2D, aPreserveParagraphs, false); } }