1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
19 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
20 #include <LibreOfficeKit/LibreOfficeKitInit.h>
21 #include <LibreOfficeKit/LibreOfficeKit.hxx>
24 #include <vcl/svapp.hxx>
27 #include <boost/property_tree/json_parser.hpp>
31 static int help( const char *error
= nullptr )
34 fprintf (stderr
, "Error: %s\n\n", error
);
35 fprintf( stderr
, "Usage: tilebench <absolute-path-to-libreoffice-install> [path to document] [--preinit] [--save <path>] <options>\n");
36 fprintf( stderr
, "\trenders a selection of small tiles from the document, checksums them and times the process based on options:\n" );
37 fprintf( stderr
, "\t--tile\t[max parts|-1] [max tiles|-1]\n" );
38 fprintf( stderr
, "\t--dialog\t<.uno:Command>\n" );
39 fprintf( stderr
, "\t--join\trun tile joining tests\n" );
43 static double getTimeNow()
46 osl_getSystemTime(&aValue
);
47 return static_cast<double>(aValue
.Seconds
) +
48 static_cast<double>(aValue
.Nanosec
) / (1000*1000*1000);
59 TimeRecord() : mpName(nullptr), mfTime(getTimeNow()) { }
60 explicit TimeRecord(const char *pName
) :
61 mpName(pName
), mfTime(getTimeNow())
63 fprintf(stderr
, "%3.3fs - %s\n", (mfTime
- origin
), mpName
);
69 static std::vector
< TimeRecord
> aTimes
;
71 /// Dump an array (or sub-array) of RGBA or BGRA to an RGB PPM file.
72 static void dumpTile(const char *pNameStem
,
73 const int nWidth
, const int nHeight
,
74 const int mode
, const unsigned char* pBufferU
,
75 const int nOffX
= 0, const int nOffY
= 0,
81 auto pBuffer
= reinterpret_cast<const char *>(pBufferU
);
82 static int counter
= 0;
83 std::string aName
= "/tmp/dump_tile";
85 aName
+= "_" + std::to_string(counter
);
88 std::ofstream
ofs(aName
);
90 NSArray
*paths
= NSSearchPathForDirectoriesInDomains(NSDocumentDirectory
, NSUserDomainMask
, YES
);
91 NSString
*documentsDirectory
= [paths objectAtIndex
:0];
92 NSString
*path
= [NSString stringWithFormat
:@
"%@/dump_tile_%d.ppm", documentsDirectory
, counter
];
93 std::ofstream
ofs([path UTF8String
]);
94 std::cerr
<< "---> Dumping tile\n";
102 const bool dumpText
= false;
105 fprintf(stderr
, "Stream %s - %dx%d:\n", pNameStem
, nWidth
, nHeight
);
107 for (int y
= 0; y
< nHeight
; ++y
)
109 const char* row
= pBuffer
+ (y
+ nOffY
) * nTotalWidth
* 4 + nOffX
* 4;
110 for (int x
= 0; x
< nWidth
; ++x
)
112 const char* pixel
= row
+ x
* 4;
114 const int alpha
= *(pixel
+ 3);
126 case LOK_TILEMODE_RGBA
:
127 buf
[0] = (*(pixel
+ 0) * 255 + alpha
/ 2) / alpha
;
128 buf
[1] = (*(pixel
+ 1) * 255 + alpha
/ 2) / alpha
;
129 buf
[2] = (*(pixel
+ 2) * 255 + alpha
/ 2) / alpha
;
131 case LOK_TILEMODE_BGRA
:
132 buf
[0] = (*(pixel
+ 2) * 255 + alpha
/ 2) / alpha
;
133 buf
[1] = (*(pixel
+ 1) * 255 + alpha
/ 2) / alpha
;
134 buf
[2] = (*(pixel
+ 0) * 255 + alpha
/ 2) / alpha
;
137 assert(false && "unhandled LibreOfficeKitTileMode");
145 int lowResI
= (pixel
[0] + pixel
[1] + pixel
[2])/(3*16);
146 fprintf(stderr
,"%1x", lowResI
);
150 fprintf(stderr
,"\n");
155 static void testTile( Document
*pDocument
, int max_parts
,
156 int max_tiles
, bool dump
)
158 const int mode
= pDocument
->getTileMode();
160 aTimes
.emplace_back("getparts");
161 const int nOriginalPart
= (pDocument
->getDocumentType() == LOK_DOCTYPE_TEXT
? 1 : pDocument
->getPart());
162 // Writer really has 1 part (the full doc).
163 const int nTotalParts
= (pDocument
->getDocumentType() == LOK_DOCTYPE_TEXT
? 1 : pDocument
->getParts());
164 const int nParts
= (max_parts
< 0 ? nTotalParts
: std::min(max_parts
, nTotalParts
));
165 aTimes
.emplace_back();
167 aTimes
.emplace_back("get size of parts");
170 for (int n
= 0; n
< nParts
; ++n
)
172 const int nPart
= (nOriginalPart
+ n
) % nTotalParts
;
173 char* pName
= pDocument
->getPartName(nPart
);
174 pDocument
->setPart(nPart
);
175 pDocument
->getDocumentSize(&nWidth
, &nHeight
);
176 fprintf (stderr
, " '%s' -> %ld, %ld\n", pName
, nWidth
, nHeight
);
179 aTimes
.emplace_back();
181 // Use realistic dimensions, similar to the Online client.
182 long const nTilePixelWidth
= 512;
183 long const nTilePixelHeight
= 512;
184 long const nTileTwipWidth
= 3840;
185 long const nTileTwipHeight
= 3840;
187 // Estimate the maximum tiles based on the number of parts requested, if Writer.
188 if (pDocument
->getDocumentType() == LOK_DOCTYPE_TEXT
)
189 max_tiles
= static_cast<int>(ceil(max_parts
* 16128. / nTilePixelHeight
) * ceil(static_cast<double>(nWidth
) / nTilePixelWidth
));
190 fprintf(stderr
, "Parts to render: %d, Total Parts: %d, Max parts: %d, Max tiles: %d\n", nParts
, nTotalParts
, max_parts
, max_tiles
);
192 std::vector
<unsigned char> vBuffer(nTilePixelWidth
* nTilePixelHeight
* 4);
193 unsigned char* pPixels
= vBuffer
.data();
195 for (int n
= 0; n
< nParts
; ++n
)
197 const int nPart
= (nOriginalPart
+ n
) % nTotalParts
;
198 char* pName
= pDocument
->getPartName(nPart
);
199 pDocument
->setPart(nPart
);
200 pDocument
->getDocumentSize(&nWidth
, &nHeight
);
201 fprintf (stderr
, "render '%s' -> %ld, %ld\n", pName
, nWidth
, nHeight
);
204 if (dump
|| pDocument
->getDocumentType() != LOK_DOCTYPE_TEXT
)
206 // whole part; meaningful only for non-writer documents.
207 aTimes
.emplace_back("render whole part");
208 pDocument
->paintTile(pPixels
, nTilePixelWidth
, nTilePixelHeight
,
209 nWidth
/2, 2000, 1000, 1000);
210 aTimes
.emplace_back();
212 dumpTile("tile", nTilePixelWidth
, nTilePixelHeight
, mode
, pPixels
);
216 aTimes
.emplace_back("render sub-region at 1:1");
217 // Estimate the maximum tiles based on the number of parts requested, if Writer.
218 int nMaxTiles
= max_tiles
;
220 for (long nY
= 0; nY
< nHeight
- 1; nY
+= nTilePixelHeight
)
222 for (long nX
= 0; nX
< nWidth
- 1; nX
+= nTilePixelWidth
)
224 if (nMaxTiles
>= 0 && nTiles
>= nMaxTiles
)
229 pDocument
->paintTile(pPixels
, nTilePixelWidth
, nTilePixelHeight
,
230 nX
, nY
, nTilePixelWidth
, nTilePixelHeight
);
232 fprintf (stderr
, " rendered 1:1 tile %d at %ld, %ld\n",
236 aTimes
.emplace_back();
240 aTimes
.emplace_back("render sub-regions at scale");
241 int nMaxTiles
= max_tiles
;
242 if (pDocument
->getDocumentType() == LOK_DOCTYPE_TEXT
)
243 nMaxTiles
= static_cast<int>(ceil(max_parts
* 16128. / nTileTwipHeight
) * ceil(static_cast<double>(nWidth
) / nTileTwipWidth
));
245 for (long nY
= 0; nY
< nHeight
- 1; nY
+= nTileTwipHeight
)
247 for (long nX
= 0; nX
< nWidth
- 1; nX
+= nTileTwipWidth
)
249 if (nMaxTiles
>= 0 && nTiles
>= nMaxTiles
)
254 pDocument
->paintTile(pPixels
, nTilePixelWidth
, nTilePixelHeight
,
255 nX
, nY
, nTileTwipWidth
, nTileTwipHeight
);
257 fprintf (stderr
, " rendered scaled tile %d at %ld, %ld\n",
261 aTimes
.emplace_back();
266 static uint32_t fade(uint32_t col
)
268 uint8_t a
= (col
>> 24) & 0xff;
269 uint8_t b
= (col
>> 16) & 0xff;
270 uint8_t g
= (col
>> 8) & 0xff;
271 uint8_t r
= (col
>> 0) & 0xff;
272 uint8_t grey
= (r
+g
+b
)/6;
273 return (a
<<24) + (grey
<<16) + (grey
<<8) + grey
;
276 static bool sloppyEqual(uint32_t pixA
, uint32_t pixB
)
280 a
[0] = (pixA
>> 24) & 0xff;
281 a
[1] = (pixA
>> 16) & 0xff;
282 a
[2] = (pixA
>> 8) & 0xff;
283 a
[3] = (pixA
>> 0) & 0xff;
285 b
[0] = (pixB
>> 24) & 0xff;
286 b
[1] = (pixB
>> 16) & 0xff;
287 b
[2] = (pixB
>> 8) & 0xff;
288 b
[3] = (pixB
>> 0) & 0xff;
290 for (int i
= 0; i
< 4; ++i
)
294 // tolerate small differences
295 if (delta
< -4 || delta
> 4)
301 // Count and build a picture of any differences into rDiff
302 static int diffTiles( const std::vector
<unsigned char> &vBase
,
303 long nBaseRowPixelWidth
,
304 const std::vector
<unsigned char> &vCompare
,
305 long nCompareRowPixelWidth
,
306 long nTilePixelHeight
,
307 long nPosX
, long nPosY
,
308 std::vector
<unsigned char> &rDiff
)
311 const uint32_t *pBase
= reinterpret_cast<const uint32_t *>(vBase
.data());
312 const uint32_t *pCompare
= reinterpret_cast<const uint32_t *>(vCompare
.data());
313 uint32_t *pDiff
= reinterpret_cast<uint32_t *>(rDiff
.data());
314 long left
= 0, mid
= nCompareRowPixelWidth
, right
= nCompareRowPixelWidth
*2;
315 for (long y
= 0; y
< nTilePixelHeight
; ++y
)
317 long nBaseOffset
= nBaseRowPixelWidth
* (y
+ nPosY
) + nPosX
* nCompareRowPixelWidth
;
318 long nCompareOffset
= nCompareRowPixelWidth
* y
;
319 long nDiffRowStart
= nCompareOffset
* 3;
320 for (long x
= 0; x
< nCompareRowPixelWidth
; ++x
)
322 pDiff
[nDiffRowStart
+ left
+ x
] = pBase
[nBaseOffset
+ x
];
323 pDiff
[nDiffRowStart
+ mid
+ x
] = pCompare
[nCompareOffset
+ x
];
324 pDiff
[nDiffRowStart
+ right
+ x
] = fade(pBase
[nBaseOffset
+ x
]);
325 if (!sloppyEqual(pBase
[nBaseOffset
+ x
], pCompare
[nCompareOffset
+ x
]))
327 pDiff
[nDiffRowStart
+ right
+ x
] = 0xffff00ff;
329 fprintf (stderr
, "First mismatching pixel at %ld (pixels) into row %ld\n", x
, y
);
337 static std::vector
<unsigned char> paintTile( Document
*pDocument
,
339 long const nTilePixelWidth
,
340 long const nTilePixelHeight
,
341 long const nTileTwipWidth
,
342 long const nTileTwipHeight
)
344 // long e = 0; // tweak if we suspect an overlap / visibility issue.
345 // pDocument->setClientVisibleArea( nX - e, nY - e, nTileTwipWidth + e, nTileTwipHeight + e );
346 std::vector
<unsigned char> vData( nTilePixelWidth
* nTilePixelHeight
* 4 );
347 pDocument
->paintTile( vData
.data(), nTilePixelWidth
, nTilePixelHeight
,
348 nX
, nY
, nTileTwipWidth
, nTileTwipHeight
);
352 static int testJoinsAt( Document
*pDocument
, long nX
, long nY
,
353 long const nTilePixelSize
,
354 long const nTileTwipSize
)
356 const int mode
= pDocument
->getTileMode();
358 long const nTilePixelWidth
= nTilePixelSize
;
359 long const nTilePixelHeight
= nTilePixelSize
;
360 long const nTileTwipWidth
= nTileTwipSize
;
361 long const nTileTwipHeight
= nTileTwipSize
;
363 long initPosX
= nX
* nTileTwipWidth
, initPosY
= nY
* nTileTwipHeight
;
365 // Calc has to do significant work on changing zoom ...
366 pDocument
->setClientZoom( nTilePixelWidth
, nTilePixelHeight
,
367 nTileTwipWidth
, nTileTwipHeight
);
369 // Unfortunately without getting this nothing renders ...
370 std::stringstream aForceHeaders
;
371 aForceHeaders
<< ".uno:ViewRowColumnHeaders?x=" << initPosX
<< "&y=" << initPosY
<<
372 "&width=" << (nTileTwipWidth
* 2) << "&height=" << (nTileTwipHeight
* 2);
373 std::string cmd
= aForceHeaders
.str();
374 char* pJSON
= pDocument
->getCommandValues(cmd
.c_str());
375 fprintf(stderr
, "command: '%s' values '%s'\n", cmd
.c_str(), pJSON
);
378 // Get a base image 4x the size
379 std::vector
<unsigned char> vBase(
380 paintTile(pDocument
, initPosX
, initPosY
,
381 nTilePixelWidth
* 2, nTilePixelHeight
* 2,
382 nTileTwipWidth
* 2, nTileTwipHeight
* 2));
394 int nDifferences
= 0;
395 // Compare each of the 4x tiles with a sub-tile of the larger image
396 for( auto &rPos
: aCompare
)
398 std::vector
<unsigned char> vCompare(
400 initPosX
+ rPos
.X
* nTileTwipWidth
,
401 initPosY
+ rPos
.Y
* nTileTwipHeight
,
402 nTilePixelWidth
, nTilePixelHeight
,
403 nTileTwipWidth
, nTileTwipHeight
));
405 std::vector
<unsigned char> vDiff( nTilePixelWidth
* 3 * nTilePixelHeight
* 4 );
406 int nDiffs
= diffTiles( vBase
, nTilePixelWidth
* 2,
407 vCompare
, nTilePixelWidth
,
409 rPos
.X
, rPos
.Y
* nTilePixelHeight
,
413 fprintf( stderr
, " %d differences in sub-tile pixel mismatch at %ld, %ld at offset %ld, %ld (twips) size %ld\n",
414 nDiffs
, rPos
.X
, rPos
.Y
, initPosX
, initPosY
,
416 dumpTile("_base", nTilePixelWidth
* 2, nTilePixelHeight
* 2,
418 /* dumpTile("_sub", nTilePixelWidth, nTilePixelHeight,
420 rPos.X*nTilePixelWidth, rPos.Y*nTilePixelHeight,
421 nTilePixelWidth * 2);
422 dumpTile("_compare", nTilePixelWidth, nTilePixelHeight,
423 mode, vCompare.data());*/
424 dumpTile("_diff", nTilePixelWidth
* 3, nTilePixelHeight
, mode
, vDiff
.data());
426 nDifferences
+= nDiffs
;
432 // Check that our tiles join nicely ...
433 static int testJoin( Document
*pDocument
)
435 // Ignore parts - just the first for now ...
436 long nWidth
= 0, nHeight
= 0;
437 pDocument
->getDocumentSize(&nWidth
, &nHeight
);
438 fprintf (stderr
, "Width is %ld, %ld (twips)\n", nWidth
, nHeight
);
440 // Use realistic dimensions, similar to the Online client.
441 long const nTilePixelSize
= 256;
442 long const nTileTwipSize
= 3840;
451 std::stringstream results
;
453 for( auto z
: fZooms
)
456 long nDifferences
= 0;
457 for( long y
= 0; y
< 8; ++y
)
459 for( long x
= 0; x
< 8; ++x
)
461 int nDiffs
= testJoinsAt( pDocument
, x
, y
, nTilePixelSize
, nTileTwipSize
* z
);
464 nDifferences
+= nDiffs
;
468 results
<< "\tZoom " << z
<< " bad tiles: " << nBad
<< " with " << nDifferences
<< " mismatching pixels\n";
473 fprintf( stderr
, "Failed %ld joins\n", nFails
);
475 fprintf( stderr
, "All joins compared correctly\n" );
477 fprintf(stderr
, "%s\n", results
.str().c_str());
482 static std::atomic
<bool> bDialogRendered(false);
483 static std::atomic
<int> nDialogId(-1);
485 static void kitCallback(int nType
, const char* pPayload
, void* pData
)
487 Document
*pDocument
= static_cast<Document
*>(pData
);
489 if (nType
!= LOK_CALLBACK_WINDOW
)
492 std::stringstream
aStream(pPayload
);
493 boost::property_tree::ptree aRoot
;
494 boost::property_tree::read_json(aStream
, aRoot
);
495 nDialogId
= aRoot
.get
<unsigned>("id");
496 const std::string aAction
= aRoot
.get
<std::string
>("action");
498 if (aAction
!= "created")
501 const std::string aType
= aRoot
.get
<std::string
>("type");
502 const std::string aSize
= aRoot
.get
<std::string
>("size");
503 int nWidth
= atoi(aSize
.c_str());
505 const char *pComma
= strstr(aSize
.c_str(), ", ");
507 nHeight
= atoi(pComma
+ 2);
508 std::cerr
<< "Size " << aSize
<< " is " << nWidth
<< ", " << nHeight
<< "\n";
510 if (aType
!= "dialog")
513 aTimes
.emplace_back(); // complete wait for dialog
515 unsigned char *pBuffer
= new unsigned char[nWidth
* nHeight
* 4];
517 aTimes
.emplace_back("render dialog");
518 pDocument
->paintWindow(nDialogId
, pBuffer
, 0, 0, nWidth
, nHeight
);
519 dumpTile("dialog", nWidth
, nHeight
, pDocument
->getTileMode(), pBuffer
);
520 aTimes
.emplace_back();
524 bDialogRendered
= true;
527 static void testDialog( Document
*pDocument
, const char *uno_cmd
)
529 int view
= pDocument
->createView();
530 pDocument
->setView(view
);
531 pDocument
->registerCallback(kitCallback
, pDocument
);
533 aTimes
.emplace_back("open dialog");
534 pDocument
->postUnoCommand(uno_cmd
, nullptr, true);
535 aTimes
.emplace_back();
537 aTimes
.emplace_back("wait for dialog");
538 while (!bDialogRendered
)
543 aTimes
.emplace_back("post close dialog");
544 pDocument
->postWindow(nDialogId
, LOK_WINDOW_CLOSE
);
545 aTimes
.emplace_back();
547 pDocument
->destroyView(view
);
550 static void documentCallback(const int type
, const char* p
, void*)
552 std::cerr
<< "Document callback " << type
<< ": " << (p
? p
: "(null)") << "\n";
555 // Avoid excessive dbgutil churn.
556 static void ignoreCallback(const int /*type*/, const char* /*p*/, void* /*data*/)
560 int main( int argc
, char* argv
[] )
563 origin
= getTimeNow();
566 // avoid X oddness etc.
570 ( argc
> 1 && ( !strcmp( argv
[1], "--help" ) || !strcmp( argv
[1], "-h" ) ) ) )
573 if ( argv
[1][0] != '/' )
575 fprintf(stderr
, "Absolute path required to libreoffice install\n");
579 const char *doc_url
= argv
[arg
++];
580 const char *mode
= argv
[arg
++];
582 bool pre_init
= false;
583 if (!strcmp(mode
, "--preinit"))
589 const char *saveToPath
= nullptr;
590 if (!strcmp (mode
, "--save"))
593 saveToPath
= argv
[arg
++];
597 std::string
user_url("file:///");
598 user_url
.append(argv
[1]);
599 user_url
.append("../user");
603 aTimes
.emplace_back("pre-initialization");
604 setenv("LOK_ALLOWLIST_LANGUAGES", "en_US", 0);
605 // coverity[tainted_string] - build time test tool
606 lok_preinit(argv
[1], user_url
.c_str());
607 aTimes
.emplace_back();
609 const char *install_path
= argv
[1];
610 const char *user_profile
= user_url
.c_str();
612 const char *install_path
= nullptr;
613 const char *user_profile
= nullptr;
614 const char *doc_url
= strdup([[[[[NSBundle mainBundle
] bundleURL
] absoluteString
] stringByAppendingString
:@
"/test.odt"] UTF8String
]);
615 const char *mode
= "--tile";
616 const char *saveToPath
= nullptr;
619 aTimes
.emplace_back("initialization");
620 // coverity[tainted_string] - build time test tool
621 std::unique_ptr
<Office
> pOffice( lok_cpp_init(install_path
, user_profile
) );
622 if (pOffice
== nullptr)
624 fprintf(stderr
, "Failed to initialize Office from %s\n", argv
[1]);
627 aTimes
.emplace_back();
628 pOffice
->registerCallback(ignoreCallback
, nullptr);
630 std::unique_ptr
<Document
> pDocument
;
632 pOffice
->setOptionalFeatures(LOK_FEATURE_NO_TILED_ANNOTATIONS
);
634 aTimes
.emplace_back("load document");
635 if (doc_url
!= nullptr)
636 pDocument
.reset(pOffice
->documentLoad(doc_url
));
637 aTimes
.emplace_back();
641 pDocument
->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"Local Host #0\"}}");
642 pDocument
->registerCallback(documentCallback
, nullptr);
643 if (!strcmp(mode
, "--tile"))
645 const int max_parts
= (argc
> arg
? atoi(argv
[arg
++]) : -1);
646 int max_tiles
= (argc
> arg
? atoi(argv
[arg
++]) : -1);
647 const bool dump
= true;
649 // coverity[tainted_data] - we trust the contents of this variable
650 testTile (pDocument
.get(), max_parts
, max_tiles
, dump
);
652 else if (!strcmp(mode
, "--join"))
654 return testJoin (pDocument
.get());
656 else if (!strcmp (mode
, "--dialog"))
658 const char *uno_cmd
= argc
> arg
? argv
[arg
++] : nullptr;
661 switch (pDocument
->getDocumentType())
663 case LOK_DOCTYPE_SPREADSHEET
:
664 uno_cmd
= ".uno:FormatCellDialog";
666 case LOK_DOCTYPE_TEXT
:
667 case LOK_DOCTYPE_PRESENTATION
:
668 case LOK_DOCTYPE_DRAWING
:
669 case LOK_DOCTYPE_OTHER
:
670 return help("missing argument to --dialog and no default");
673 testDialog (pDocument
.get(), uno_cmd
);
676 return help ("unknown parameter");
678 if (saveToPath
!= nullptr)
680 aTimes
.emplace_back("save");
681 pDocument
->saveAs(saveToPath
);
682 aTimes
.emplace_back();
685 fprintf(stderr
, "Failed to load document '%s'\n",
686 (doc_url
? doc_url
: "<null>"));
691 aTimes
.emplace_back("destroy document");
693 aTimes
.emplace_back();
698 fprintf (stderr
, "profile run:\n");
699 for (size_t i
= 0; i
< aTimes
.size() - 1; i
++)
701 const double nDelta
= aTimes
[i
+1].mfTime
- aTimes
[i
].mfTime
;
702 fprintf (stderr
, " %s - %2.4f(ms)\n", aTimes
[i
].mpName
, nDelta
* 1000.0);
703 if (aTimes
[i
+1].mpName
== nullptr)
707 fprintf (stderr
, "Total: %2.4f(s)\n", nTotal
);
711 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */