Bump version to 24.04.3.4
[LibreOffice.git] / libreofficekit / qa / unit / tiledrendering.cxx
blob56d789b61e5eff865cb91c575911dd2788e1cd57
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <memory>
11 #include <thread>
12 #include <boost/property_tree/json_parser.hpp>
13 #include <cppunit/TestFixture.h>
14 #include <cppunit/plugin/TestPlugIn.h>
15 #include <cppunit/extensions/HelperMacros.h>
16 #include <cstdlib>
17 #include <string>
18 #include <stdio.h>
20 #include <osl/file.hxx>
21 #include <rtl/bootstrap.hxx>
23 #include <com/sun/star/awt/Key.hpp>
25 #if defined __clang__ && defined __linux__
26 #include <cxxabi.h>
27 #include <config_options.h>
28 #if defined _LIBCPPABI_VERSION || !ENABLE_RUNTIME_OPTIMIZATIONS
29 #define LOK_LOADLIB_GLOBAL
30 #endif
31 #endif
33 #include <LibreOfficeKit/LibreOfficeKitInit.h>
34 #include <LibreOfficeKit/LibreOfficeKit.hxx>
35 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
37 using namespace ::boost;
38 using namespace ::lok;
39 using namespace ::std;
41 namespace {
43 void processEventsToIdle()
45 typedef void (ProcessEventsToIdleFn)(void);
46 static ProcessEventsToIdleFn *processFn = nullptr;
47 if (!processFn)
49 void *me = dlopen(nullptr, RTLD_NOW);
50 processFn = reinterpret_cast<ProcessEventsToIdleFn *>(dlsym(me, "unit_lok_process_events_to_idle"));
53 CPPUNIT_ASSERT(processFn);
55 (*processFn)();
58 void insertString(Document& rDocument, const std::string& s)
60 for (const char c : s)
62 rDocument.postKeyEvent(LOK_KEYEVENT_KEYINPUT, c, 0);
63 rDocument.postKeyEvent(LOK_KEYEVENT_KEYUP, c, 0);
64 processEventsToIdle();
70 static OUString getFileURLFromSystemPath(OUString const & path)
72 OUString url;
73 osl::FileBase::RC e = osl::FileBase::getFileURLFromSystemPath(path, url);
74 CPPUNIT_ASSERT_EQUAL(osl::FileBase::E_None, e);
75 if (!url.endsWith("/"))
76 url += "/";
77 return url;
80 // We specifically don't use the usual BootStrapFixture, as LOK does
81 // all its own setup and bootstrapping, and should be usable in a
82 // raw C++ program.
83 class TiledRenderingTest : public ::CppUnit::TestFixture
85 public:
86 const string m_sSrcRoot;
87 const string m_sInstDir;
88 const string m_sLOPath;
90 std::unique_ptr<Document> loadDocument( Office *pOffice, const string &pName,
91 const char *pFilterOptions = nullptr );
93 TiledRenderingTest()
94 : m_sSrcRoot( getenv( "SRC_ROOT" ) )
95 , m_sInstDir( getenv( "INSTDIR" ) )
96 , m_sLOPath( m_sInstDir + "/program" )
100 // Currently it isn't possible to do multiple startup/shutdown
101 // cycle of LOK in a single process -- hence we run all our tests
102 // as one test, which simply carries out the individual test
103 // components on the one Office instance that we retrieve.
104 void runAllTests();
106 void testDocumentLoadFail( Office* pOffice );
107 void testDocumentTypes( Office* pOffice );
108 void testImpressSlideNames( Office* pOffice );
109 void testCalcSheetNames( Office* pOffice );
110 void testPaintPartTile( Office* pOffice );
111 void testDocumentLoadLanguage(Office* pOffice);
112 void testMultiKeyInput(Office *pOffice);
113 #if 0
114 void testOverlay( Office* pOffice );
115 #endif
117 CPPUNIT_TEST_SUITE(TiledRenderingTest);
118 CPPUNIT_TEST(runAllTests);
119 CPPUNIT_TEST_SUITE_END();
122 void TiledRenderingTest::runAllTests()
124 // set UserInstallation to user profile dir in test/user-template
125 const char* pWorkdirRoot = getenv("WORKDIR_FOR_BUILD");
126 OUString aWorkdirRootPath = OUString::createFromAscii(pWorkdirRoot);
127 OUString aWorkdirRootURL = getFileURLFromSystemPath(aWorkdirRootPath);
128 OUString sUserInstallURL = aWorkdirRootURL + "/unittest";
129 rtl::Bootstrap::set("UserInstallation", sUserInstallURL);
131 std::unique_ptr< Office > pOffice( lok_cpp_init(
132 m_sLOPath.c_str() ) );
133 CPPUNIT_ASSERT( pOffice );
135 testDocumentLoadFail( pOffice.get() );
136 testDocumentTypes( pOffice.get() );
137 testMultiKeyInput(pOffice.get());
138 testImpressSlideNames( pOffice.get() );
139 testCalcSheetNames( pOffice.get() );
140 testPaintPartTile( pOffice.get() );
141 testDocumentLoadLanguage(pOffice.get());
142 #if 0
143 testOverlay( pOffice.get() );
144 #endif
147 void TiledRenderingTest::testDocumentLoadFail( Office* pOffice )
149 const string sDocPath = m_sSrcRoot + "/libreofficekit/qa/data/IDONOTEXIST.odt";
150 std::unique_ptr< Document> pDocument( pOffice->documentLoad( sDocPath.c_str() ) );
151 CPPUNIT_ASSERT( !pDocument );
152 // TODO: we probably want to have some way of returning what
153 // the cause of failure was. getError() will return
154 // something along the lines of:
155 // "Unsupported URL <file:///SRC_ROOT/libreofficekit/qa/data/IDONOTEXIST.odt>: "type detection failed""
158 // Our dumped .png files end up in
159 // workdir/CppunitTest/libreofficekit_tiledrendering.test.core
161 static int getDocumentType( Office* pOffice, const string& rPath )
163 std::unique_ptr< Document> pDocument( pOffice->documentLoad( rPath.c_str() ) );
164 CPPUNIT_ASSERT( pDocument );
165 return pDocument->getDocumentType();
168 std::unique_ptr<Document> TiledRenderingTest::loadDocument( Office *pOffice, const string &pName,
169 const char *pFilterOptions )
171 const string sDocPath = m_sSrcRoot + "/libreofficekit/qa/data/" + pName;
172 const string sLockFile = m_sSrcRoot +"/libreofficekit/qa/data/.~lock." + pName + "#";
174 remove( sLockFile.c_str() );
176 return std::unique_ptr<Document>(pOffice->documentLoad( sDocPath.c_str(), pFilterOptions ));
179 void TiledRenderingTest::testDocumentTypes( Office* pOffice )
181 std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt"));
183 CPPUNIT_ASSERT(pDocument);
184 CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT, static_cast<LibreOfficeKitDocumentType>(pDocument->getDocumentType()));
185 // This crashed.
186 pDocument->postUnoCommand(".uno:Bold");
187 processEventsToIdle();
189 const string sPresentationDocPath = m_sSrcRoot + "/libreofficekit/qa/data/blank_presentation.odp";
190 const string sPresentationLockFile = m_sSrcRoot +"/libreofficekit/qa/data/.~lock.blank_presentation.odp#";
192 // FIXME: same comment as below wrt lockfile removal.
193 remove( sPresentationLockFile.c_str() );
195 CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_PRESENTATION, static_cast<LibreOfficeKitDocumentType>(getDocumentType(pOffice, sPresentationDocPath)));
197 // TODO: do this for all supported document types
200 void TiledRenderingTest::testImpressSlideNames( Office* pOffice )
202 std::unique_ptr<Document> pDocument(loadDocument(pOffice, "impress_slidenames.odp"));
204 CPPUNIT_ASSERT_EQUAL(3, pDocument->getParts());
205 CPPUNIT_ASSERT_EQUAL(std::string("TestText1"), std::string(pDocument->getPartName(0)));
206 CPPUNIT_ASSERT_EQUAL(std::string("TestText2"), std::string(pDocument->getPartName(1)));
207 // The third slide hasn't had a name given to it (i.e. using the rename
208 // context menu in Impress), thus it should (as far as I can determine)
209 // have a localised version of "Slide 3".
212 void TiledRenderingTest::testCalcSheetNames( Office* pOffice )
214 std::unique_ptr<Document> pDocument(loadDocument(pOffice, "calc_sheetnames.ods"));
216 CPPUNIT_ASSERT_EQUAL(3, pDocument->getParts());
217 CPPUNIT_ASSERT_EQUAL(std::string("TestText1"), std::string(pDocument->getPartName(0)));
218 CPPUNIT_ASSERT_EQUAL(std::string("TestText2"), std::string(pDocument->getPartName(1)));
219 CPPUNIT_ASSERT_EQUAL(std::string("Sheet3"), std::string(pDocument->getPartName(2)));
222 void TiledRenderingTest::testPaintPartTile(Office* pOffice)
224 std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt"));
226 CPPUNIT_ASSERT(pDocument);
227 CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT, static_cast<LibreOfficeKitDocumentType>(pDocument->getDocumentType()));
229 // Create two views.
230 pDocument->getView();
231 pDocument->createView();
233 int nView2 = pDocument->getView();
235 // Destroy the current view
236 pDocument->destroyView(nView2);
238 int nCanvasWidth = 256;
239 int nCanvasHeight = 256;
240 std::vector<unsigned char> aBuffer(nCanvasWidth * nCanvasHeight * 4);
242 // And try to paintPartTile() - this used to crash when the current viewId
243 // was destroyed
244 pDocument->paintPartTile(aBuffer.data(), /*nPart=*/0, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/0, /*nTileWidth=*/3840, /*nTileHeight=*/3840);
247 void TiledRenderingTest::testDocumentLoadLanguage(Office* pOffice)
249 std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt", "Language=en-US"));
251 // assert that '.' is the decimal separator
252 insertString(*pDocument, "1.5");
254 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RIGHT);
255 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RIGHT);
256 processEventsToIdle();
258 insertString(*pDocument, "=2*A1");
260 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RETURN);
261 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RETURN);
262 processEventsToIdle();
263 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::UP);
264 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::UP);
265 processEventsToIdle();
267 #if 0
268 // FIXME disabled, as occasionally fails
269 // we've got a meaningful result
270 OString aResult = pDocument->getTextSelection("text/plain;charset=utf-8");
271 CPPUNIT_ASSERT_EQUAL(OString("3\n"), aResult);
273 pDocument.reset();
275 // FIXME: LOK will fail when trying to open a locked file
276 remove(sLockFile.c_str());
278 // load the file again, now in another language
279 pDocument.reset(pOffice->documentLoad(sDocPath.c_str(), "Language=cs-CZ"));
281 // with cs-CZ, the decimal separator is ',' instead, assert that
282 insertString(*pDocument, "1,5");
284 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RIGHT);
285 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RIGHT);
286 processEventsToIdle();
288 insertString(*pDocument, "=2*A1");
290 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::RETURN);
291 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::RETURN);
292 processEventsToIdle();
293 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::UP);
294 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, css::awt::Key::UP);
295 processEventsToIdle();
297 // we've got a meaningful result
298 aResult = pDocument->getTextSelection("text/plain;charset=utf-8");
299 CPPUNIT_ASSERT_EQUAL(OString("3\n"), aResult);
300 #endif
303 #if 0
304 static void dumpRGBABitmap( const OUString& rPath, const unsigned char* pBuffer,
305 const int nWidth, const int nHeight )
307 Bitmap aBitmap( Size( nWidth, nHeight ), 32 );
308 BitmapScopedWriteAccess pWriteAccess( aBitmap );
309 memcpy( pWriteAccess->GetBuffer(), pBuffer, 4*nWidth*nHeight );
311 BitmapEx aBitmapEx( aBitmap );
312 vcl::PNGWriter aWriter( aBitmapEx );
313 SvFileStream sOutput( rPath, StreamMode::WRITE );
314 aWriter.Write( sOutput );
315 sOutput.Close();
318 void TiledRenderingTest::testOverlay( Office* /*pOffice*/ )
320 const string sDocPath = m_sSrcRoot + "/odk/examples/java/DocumentHandling/test/test1.odt";
321 const string sLockFile = m_sSrcRoot + "/odk/examples/java/DocumentHandling/test/.~lock.test1.odt#";
323 // FIXME: this is a temporary hack: LOK will fail when trying to open a
324 // locked file, and since we're reusing the file for a different unit
325 // test it's entirely possible that an unwanted lock file will remain.
326 // Hence forcefully remove it here.
327 remove( sLockFile.c_str() );
328 std::unique_ptr< Office > pOffice( lok_cpp_init(
329 m_sLOPath.c_str() ) );
330 assert( pOffice.get() );
332 std::unique_ptr< Document> pDocument( pOffice->documentLoad(
333 sDocPath.c_str() ) );
335 if ( !pDocument.get() )
337 fprintf( stderr, "documentLoad failed: %s\n", pOffice->getError() );
338 CPPUNIT_FAIL( "Document could not be loaded -- tiled rendering not possible." );
341 // We render one large tile, then subdivide it into 4 and render those parts, and finally
342 // iterate over each smaller tile and check whether their contents match the large
343 // tile.
344 const int nTotalWidthPix = 512;
345 const int nTotalHeightPix = 512;
346 int nRowStride;
348 long nTotalWidthDoc;
349 long nTotalHeightDoc;
350 // pDocument->getDocumentSize( &nTotalWidthDoc, &nTotalHeightDoc );
351 // TODO: make sure we select an actually interesting part of the document
352 // for this comparison, i.e. ideally an image and lots of text, in order
353 // to test as many edge cases as possible.
354 // Alternatively we could rewrite this to actually grab the document size
355 // and iterate over it (subdividing into an arbitrary number of tiles rather
356 // than our less sophisticated test of just 4 sub-tiles).
357 nTotalWidthDoc = 8000;
358 nTotalHeightDoc = 9000;
360 std::unique_ptr< unsigned char []> pLarge( new unsigned char[ 4*nTotalWidthPix*nTotalHeightPix ] );
361 pDocument->paintTile( pLarge.get(), nTotalWidthPix, nTotalHeightPix, &nRowStride,
362 0, 0,
363 nTotalWidthDoc, nTotalHeightDoc );
364 dumpRGBABitmap( "large.png", pLarge.get(), nTotalWidthPix, nTotalHeightPix );
366 std::unique_ptr< unsigned char []> pSmall[4];
367 for ( int i = 0; i < 4; i++ )
369 pSmall[i].reset( new unsigned char[ 4*(nTotalWidthPix/2)*(nTotalHeightPix/2) ] );
370 pDocument->paintTile( pSmall[i].get(), nTotalWidthPix / 2, nTotalHeightPix / 2, &nRowStride,
371 // Tile 0/2: left. Tile 1/3: right. Tile 0/1: top. Tile 2/3: bottom
372 ((i%2 == 0) ? 0 : nTotalWidthDoc / 2), ((i < 2 ) ? 0 : nTotalHeightDoc / 2),
373 nTotalWidthDoc / 2, nTotalHeightDoc / 2);
374 dumpRGBABitmap( "small_" + OUString::number(i) + ".png",
375 pSmall[i].get(), nTotalWidthPix/2, nTotalHeightPix/2 );
378 // Iterate over each pixel of the sub-tile, and compare that pixel for every
379 // tile with the equivalent super-tile pixel.
380 for ( int i = 0; i < 4*nTotalWidthPix / 2 * nTotalHeightPix / 2; i++ )
382 int xSmall = i % (4*nTotalWidthPix/2);
383 int ySmall = i / (4*nTotalWidthPix/2);
384 // Iterate over our array of tiles
385 // However for now we only bother with the top-left
386 // tile as the other ones don't match yet...
387 for ( int x = 0; x < 2; x++ )
389 for ( int y = 0; y < 2; y++ )
391 int xLarge = (x * (4 * nTotalWidthPix / 2)) + xSmall;
392 int yLarge = (y * (nTotalHeightPix / 2)) + ySmall;
393 CPPUNIT_ASSERT( pSmall[2*y+x][i] == pLarge[yLarge*4*nTotalWidthPix + xLarge] );
398 #endif
400 void TiledRenderingTest::testMultiKeyInput(Office *pOffice)
402 std::unique_ptr<Document> pDocument(loadDocument(pOffice, "blank_text.odt"));
404 CPPUNIT_ASSERT(pDocument);
405 CPPUNIT_ASSERT_EQUAL(LOK_DOCTYPE_TEXT, static_cast<LibreOfficeKitDocumentType>(pDocument->getDocumentType()));
407 // Create two views.
408 int nViewA = pDocument->getView();
409 pDocument->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"jill\"}}");
411 pDocument->createView();
412 int nViewB = pDocument->getView();
413 pDocument->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"jack\"}}");
415 // Enable change tracking
416 pDocument->postUnoCommand(".uno:TrackChanges");
418 // First a key-stroke from a
419 pDocument->setView(nViewA);
420 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 97, 0); // a
421 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 512); // 'a
423 // A space on 'a' - force commit
424 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, 0); // ' '
425 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 1284); // '' '
427 // Another 'a'
428 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 97, 0); // a
429 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 512); // 'a
431 // FIXME: Wait for writer input handler to commit that.
432 // without this we fall foul of edtwin's KeyInputFlushTimer
433 std::this_thread::sleep_for(std::chrono::milliseconds(300));
435 // Quickly a new key-stroke from b
436 pDocument->setView(nViewB);
437 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 98, 0); // b
438 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 514); // 'b
440 // A space on 'b' - force commit
441 pDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 32, 0); // ' '
442 pDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, 1284); // '' '
444 // Wait for writer input handler to commit that.
445 std::this_thread::sleep_for(std::chrono::milliseconds(300));
447 // get track changes ?
448 char *values = pDocument->getCommandValues(".uno:AcceptTrackedChanges");
449 std::cerr << "Values: '" << values << "'\n";
452 CPPUNIT_TEST_SUITE_REGISTRATION(TiledRenderingTest);
454 CPPUNIT_PLUGIN_IMPLEMENT();
456 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */