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/.
21 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
22 #include <LibreOfficeKit/LibreOfficeKitInit.h>
23 #include <LibreOfficeKit/LibreOfficeKit.hxx>
25 #include <boost/property_tree/json_parser.hpp>
26 #include <boost/optional.hpp>
30 static int help( const char *error
= nullptr )
33 fprintf (stderr
, "Error: %s\n\n", error
);
34 fprintf( stderr
, "Usage: tilebench <absolute-path-to-libreoffice-install> [path to document] [--preinit] <options>\n");
35 fprintf( stderr
, "\trenders a selection of small tiles from the document, checksums them and times the process based on options:\n" );
36 fprintf( stderr
, "\t--tile\t[max parts|-1] [max tiles|-1]\n" );
37 fprintf( stderr
, "\t--dialog\t<.uno:Command>\n" );
38 fprintf( stderr
, "\t--join\trun tile joining tests\n" );
42 static double getTimeNow()
45 osl_getSystemTime(&aValue
);
46 return static_cast<double>(aValue
.Seconds
) +
47 static_cast<double>(aValue
.Nanosec
) / (1000*1000*1000);
55 TimeRecord() : mpName(nullptr), mfTime(getTimeNow()) { }
56 explicit TimeRecord(const char *pName
) :
57 mpName(pName
), mfTime(getTimeNow())
59 fprintf(stderr
, "%3.3fs - %s\n", (mfTime
- origin
), mpName
);
62 static std::vector
< TimeRecord
> aTimes
;
64 /// Dump an array (or sub-array) of RGBA or BGRA to an RGB PPM file.
65 static void dumpTile(const char *pNameStem
,
66 const int nWidth
, const int nHeight
,
67 const int mode
, const unsigned char* pBufferU
,
68 const int nOffX
= 0, const int nOffY
= 0,
74 auto pBuffer
= reinterpret_cast<const char *>(pBufferU
);
75 static int counter
= 0;
76 std::string aName
= "/tmp/dump_tile";
78 aName
+= "_" + std::to_string(counter
);
81 std::ofstream
ofs(aName
);
83 NSArray
*paths
= NSSearchPathForDirectoriesInDomains(NSDocumentDirectory
, NSUserDomainMask
, YES
);
84 NSString
*documentsDirectory
= [paths objectAtIndex
:0];
85 NSString
*path
= [NSString stringWithFormat
:@
"%@/dump_tile_%d.ppm", documentsDirectory
, counter
];
86 std::ofstream
ofs([path UTF8String
]);
87 std::cerr
<< "---> Dumping tile\n";
95 const bool dumpText
= false;
98 fprintf(stderr
, "Stream %s - %dx%d:\n", pNameStem
, nWidth
, nHeight
);
100 for (int y
= 0; y
< nHeight
; ++y
)
102 const char* row
= pBuffer
+ (y
+ nOffY
) * nTotalWidth
* 4 + nOffX
* 4;
103 for (int x
= 0; x
< nWidth
; ++x
)
105 const char* pixel
= row
+ x
* 4;
106 if (mode
== LOK_TILEMODE_RGBA
)
108 ofs
.write(pixel
, 3); // Skip alpha
110 else if (mode
== LOK_TILEMODE_BGRA
)
112 const int alpha
= *(pixel
+ 3);
122 buf
[0] = (*(pixel
+ 2) * 255 + alpha
/ 2) / alpha
;
123 buf
[1] = (*(pixel
+ 1) * 255 + alpha
/ 2) / alpha
;
124 buf
[2] = (*(pixel
+ 0) * 255 + alpha
/ 2) / alpha
;
131 int lowResI
= (pixel
[0] + pixel
[1] + pixel
[2])/(3*16);
132 fprintf(stderr
,"%1x", lowResI
);
136 fprintf(stderr
,"\n");
141 static void testTile( Document
*pDocument
, int max_parts
,
142 int max_tiles
, bool dump
)
144 const int mode
= pDocument
->getTileMode();
146 aTimes
.emplace_back("getparts");
147 const int nOriginalPart
= (pDocument
->getDocumentType() == LOK_DOCTYPE_TEXT
? 1 : pDocument
->getPart());
148 // Writer really has 1 part (the full doc).
149 const int nTotalParts
= (pDocument
->getDocumentType() == LOK_DOCTYPE_TEXT
? 1 : pDocument
->getParts());
150 const int nParts
= (max_parts
< 0 ? nTotalParts
: std::min(max_parts
, nTotalParts
));
151 aTimes
.emplace_back();
153 aTimes
.emplace_back("get size of parts");
156 for (int n
= 0; n
< nParts
; ++n
)
158 const int nPart
= (nOriginalPart
+ n
) % nTotalParts
;
159 char* pName
= pDocument
->getPartName(nPart
);
160 pDocument
->setPart(nPart
);
161 pDocument
->getDocumentSize(&nWidth
, &nHeight
);
162 fprintf (stderr
, " '%s' -> %ld, %ld\n", pName
, nWidth
, nHeight
);
165 aTimes
.emplace_back();
167 // Use realistic dimensions, similar to the Online client.
168 long const nTilePixelWidth
= 512;
169 long const nTilePixelHeight
= 512;
170 long const nTileTwipWidth
= 3840;
171 long const nTileTwipHeight
= 3840;
173 // Estimate the maximum tiles based on the number of parts requested, if Writer.
174 if (pDocument
->getDocumentType() == LOK_DOCTYPE_TEXT
)
175 max_tiles
= static_cast<int>(ceil(max_parts
* 16128. / nTilePixelHeight
) * ceil(static_cast<double>(nWidth
) / nTilePixelWidth
));
176 fprintf(stderr
, "Parts to render: %d, Total Parts: %d, Max parts: %d, Max tiles: %d\n", nParts
, nTotalParts
, max_parts
, max_tiles
);
178 std::vector
<unsigned char> vBuffer(nTilePixelWidth
* nTilePixelHeight
* 4);
179 unsigned char* pPixels
= vBuffer
.data();
181 for (int n
= 0; n
< nParts
; ++n
)
183 const int nPart
= (nOriginalPart
+ n
) % nTotalParts
;
184 char* pName
= pDocument
->getPartName(nPart
);
185 pDocument
->setPart(nPart
);
186 pDocument
->getDocumentSize(&nWidth
, &nHeight
);
187 fprintf (stderr
, "render '%s' -> %ld, %ld\n", pName
, nWidth
, nHeight
);
190 if (dump
|| pDocument
->getDocumentType() != LOK_DOCTYPE_TEXT
)
192 // whole part; meaningful only for non-writer documents.
193 aTimes
.emplace_back("render whole part");
194 pDocument
->paintTile(pPixels
, nTilePixelWidth
, nTilePixelHeight
,
195 nWidth
/2, 2000, 1000, 1000);
196 aTimes
.emplace_back();
198 dumpTile("tile", nTilePixelWidth
, nTilePixelHeight
, mode
, pPixels
);
202 aTimes
.emplace_back("render sub-region at 1:1");
203 // Estimate the maximum tiles based on the number of parts requested, if Writer.
204 int nMaxTiles
= max_tiles
;
206 for (long nY
= 0; nY
< nHeight
- 1; nY
+= nTilePixelHeight
)
208 for (long nX
= 0; nX
< nWidth
- 1; nX
+= nTilePixelWidth
)
210 if (nMaxTiles
>= 0 && nTiles
>= nMaxTiles
)
215 pDocument
->paintTile(pPixels
, nTilePixelWidth
, nTilePixelHeight
,
216 nX
, nY
, nTilePixelWidth
, nTilePixelHeight
);
218 fprintf (stderr
, " rendered 1:1 tile %d at %ld, %ld\n",
222 aTimes
.emplace_back();
226 aTimes
.emplace_back("render sub-regions at scale");
227 int nMaxTiles
= max_tiles
;
228 if (pDocument
->getDocumentType() == LOK_DOCTYPE_TEXT
)
229 nMaxTiles
= static_cast<int>(ceil(max_parts
* 16128. / nTileTwipHeight
) * ceil(static_cast<double>(nWidth
) / nTileTwipWidth
));
231 for (long nY
= 0; nY
< nHeight
- 1; nY
+= nTileTwipHeight
)
233 for (long nX
= 0; nX
< nWidth
- 1; nX
+= nTileTwipWidth
)
235 if (nMaxTiles
>= 0 && nTiles
>= nMaxTiles
)
240 pDocument
->paintTile(pPixels
, nTilePixelWidth
, nTilePixelHeight
,
241 nX
, nY
, nTileTwipWidth
, nTileTwipHeight
);
243 fprintf (stderr
, " rendered scaled tile %d at %ld, %ld\n",
247 aTimes
.emplace_back();
252 static uint32_t fade(uint32_t col
)
254 uint8_t a
= (col
>> 24) & 0xff;
255 uint8_t b
= (col
>> 16) & 0xff;
256 uint8_t g
= (col
>> 8) & 0xff;
257 uint8_t r
= (col
>> 0) & 0xff;
258 uint8_t grey
= (r
+g
+b
)/6;
259 return (a
<<24) + (grey
<<16) + (grey
<<8) + grey
;
262 static bool sloppyEqual(uint32_t pixA
, uint32_t pixB
)
266 a
[0] = (pixA
>> 24) & 0xff;
267 a
[1] = (pixA
>> 16) & 0xff;
268 a
[2] = (pixA
>> 8) & 0xff;
269 a
[3] = (pixA
>> 0) & 0xff;
271 b
[0] = (pixB
>> 24) & 0xff;
272 b
[1] = (pixB
>> 16) & 0xff;
273 b
[2] = (pixB
>> 8) & 0xff;
274 b
[3] = (pixB
>> 0) & 0xff;
276 for (int i
= 0; i
< 4; ++i
)
280 // tolerate small differences
281 if (delta
< -4 || delta
> 4)
287 // Count and build a picture of any differences into rDiff
288 static int diffTiles( const std::vector
<unsigned char> &vBase
,
289 long nBaseRowPixelWidth
,
290 const std::vector
<unsigned char> &vCompare
,
291 long nCompareRowPixelWidth
,
292 long nTilePixelHeight
,
293 long nPosX
, long nPosY
,
294 std::vector
<unsigned char> &rDiff
)
297 const uint32_t *pBase
= reinterpret_cast<const uint32_t *>(vBase
.data());
298 const uint32_t *pCompare
= reinterpret_cast<const uint32_t *>(vCompare
.data());
299 uint32_t *pDiff
= reinterpret_cast<uint32_t *>(rDiff
.data());
300 long left
= 0, mid
= nCompareRowPixelWidth
, right
= nCompareRowPixelWidth
*2;
301 for (long y
= 0; y
< nTilePixelHeight
; ++y
)
303 long nBaseOffset
= nBaseRowPixelWidth
* (y
+ nPosY
) + nPosX
* nCompareRowPixelWidth
;
304 long nCompareOffset
= nCompareRowPixelWidth
* y
;
305 long nDiffRowStart
= nCompareOffset
* 3;
306 for (long x
= 0; x
< nCompareRowPixelWidth
; ++x
)
308 pDiff
[nDiffRowStart
+ left
+ x
] = pBase
[nBaseOffset
+ x
];
309 pDiff
[nDiffRowStart
+ mid
+ x
] = pCompare
[nCompareOffset
+ x
];
310 pDiff
[nDiffRowStart
+ right
+ x
] = fade(pBase
[nBaseOffset
+ x
]);
311 if (!sloppyEqual(pBase
[nBaseOffset
+ x
], pCompare
[nCompareOffset
+ x
]))
313 pDiff
[nDiffRowStart
+ right
+ x
] = 0xffff00ff;
315 fprintf (stderr
, "First mismatching pixel at %ld (pixels) into row %ld\n", x
, y
);
323 static std::vector
<unsigned char> paintTile( Document
*pDocument
,
325 long const nTilePixelWidth
,
326 long const nTilePixelHeight
,
327 long const nTileTwipWidth
,
328 long const nTileTwipHeight
)
330 // long e = 0; // tweak if we suspect an overlap / visibility issue.
331 // pDocument->setClientVisibleArea( nX - e, nY - e, nTileTwipWidth + e, nTileTwipHeight + e );
332 std::vector
<unsigned char> vData( nTilePixelWidth
* nTilePixelHeight
* 4 );
333 pDocument
->paintTile( vData
.data(), nTilePixelWidth
, nTilePixelHeight
,
334 nX
, nY
, nTileTwipWidth
, nTileTwipHeight
);
338 static int testJoinsAt( Document
*pDocument
, long nX
, long nY
,
339 long const nTilePixelSize
,
340 long const nTileTwipSize
)
342 const int mode
= pDocument
->getTileMode();
344 long const nTilePixelWidth
= nTilePixelSize
;
345 long const nTilePixelHeight
= nTilePixelSize
;
346 long const nTileTwipWidth
= nTileTwipSize
;
347 long const nTileTwipHeight
= nTileTwipSize
;
349 long initPosX
= nX
* nTileTwipWidth
, initPosY
= nY
* nTileTwipHeight
;
351 // Calc has to do significant work on changing zoom ...
352 pDocument
->setClientZoom( nTilePixelWidth
, nTilePixelHeight
,
353 nTileTwipWidth
, nTileTwipHeight
);
355 // Unfortunately without getting this nothing renders ...
356 std::stringstream aForceHeaders
;
357 aForceHeaders
<< ".uno:ViewRowColumnHeaders?x=" << initPosX
<< "&y=" << initPosY
<<
358 "&width=" << (nTileTwipWidth
* 2) << "&height=" << (nTileTwipHeight
* 2);
359 std::string cmd
= aForceHeaders
.str();
360 char* pJSON
= pDocument
->getCommandValues(cmd
.c_str());
361 fprintf(stderr
, "command: '%s' values '%s'\n", cmd
.c_str(), pJSON
);
364 // Get a base image 4x the size
365 std::vector
<unsigned char> vBase(
366 paintTile(pDocument
, initPosX
, initPosY
,
367 nTilePixelWidth
* 2, nTilePixelHeight
* 2,
368 nTileTwipWidth
* 2, nTileTwipHeight
* 2));
380 int nDifferences
= 0;
381 // Compare each of the 4x tiles with a sub-tile of the larger image
382 for( auto &rPos
: aCompare
)
384 std::vector
<unsigned char> vCompare(
386 initPosX
+ rPos
.X
* nTileTwipWidth
,
387 initPosY
+ rPos
.Y
* nTileTwipHeight
,
388 nTilePixelWidth
, nTilePixelHeight
,
389 nTileTwipWidth
, nTileTwipHeight
));
391 std::vector
<unsigned char> vDiff( nTilePixelWidth
* 3 * nTilePixelHeight
* 4 );
392 int nDiffs
= diffTiles( vBase
, nTilePixelWidth
* 2,
393 vCompare
, nTilePixelWidth
,
395 rPos
.X
, rPos
.Y
* nTilePixelHeight
,
399 fprintf( stderr
, " %d differences in sub-tile pixel mismatch at %ld, %ld at offset %ld, %ld (twips) size %ld\n",
400 nDiffs
, rPos
.X
, rPos
.Y
, initPosX
, initPosY
,
402 dumpTile("_base", nTilePixelWidth
* 2, nTilePixelHeight
* 2,
404 /* dumpTile("_sub", nTilePixelWidth, nTilePixelHeight,
406 rPos.X*nTilePixelWidth, rPos.Y*nTilePixelHeight,
407 nTilePixelWidth * 2);
408 dumpTile("_compare", nTilePixelWidth, nTilePixelHeight,
409 mode, vCompare.data());*/
410 dumpTile("_diff", nTilePixelWidth
* 3, nTilePixelHeight
, mode
, vDiff
.data());
412 nDifferences
+= nDiffs
;
418 // Check that our tiles join nicely ...
419 static int testJoin( Document
*pDocument
)
421 // Ignore parts - just the first for now ...
422 long nWidth
= 0, nHeight
= 0;
423 pDocument
->getDocumentSize(&nWidth
, &nHeight
);
424 fprintf (stderr
, "Width is %ld, %ld (twips)\n", nWidth
, nHeight
);
426 // Use realistic dimensions, similar to the Online client.
427 long const nTilePixelSize
= 256;
428 long const nTileTwipSize
= 3840;
437 std::stringstream results
;
439 for( auto z
: fZooms
)
442 long nDifferences
= 0;
443 for( long y
= 0; y
< 8; ++y
)
445 for( long x
= 0; x
< 8; ++x
)
447 int nDiffs
= testJoinsAt( pDocument
, x
, y
, nTilePixelSize
, nTileTwipSize
* z
);
450 nDifferences
+= nDiffs
;
454 results
<< "\tZoom " << z
<< " bad tiles: " << nBad
<< " with " << nDifferences
<< " mismatching pixels\n";
459 fprintf( stderr
, "Failed %ld joins\n", nFails
);
461 fprintf( stderr
, "All joins compared correctly\n" );
463 fprintf(stderr
, "%s\n", results
.str().c_str());
468 static std::atomic
<bool> bDialogRendered(false);
469 static std::atomic
<int> nDialogId(-1);
471 static void kitCallback(int nType
, const char* pPayload
, void* pData
)
473 Document
*pDocument
= static_cast<Document
*>(pData
);
475 if (nType
!= LOK_CALLBACK_WINDOW
)
478 std::stringstream
aStream(pPayload
);
479 boost::property_tree::ptree aRoot
;
480 boost::property_tree::read_json(aStream
, aRoot
);
481 nDialogId
= aRoot
.get
<unsigned>("id");
482 const std::string aAction
= aRoot
.get
<std::string
>("action");
484 if (aAction
== "created")
486 const std::string aType
= aRoot
.get
<std::string
>("type");
487 const std::string aSize
= aRoot
.get
<std::string
>("size");
488 int nWidth
= atoi(aSize
.c_str());
490 const char *pComma
= strstr(aSize
.c_str(), ", ");
492 nHeight
= atoi(pComma
+ 2);
493 std::cerr
<< "Size " << aSize
<< " is " << nWidth
<< ", " << nHeight
<< "\n";
495 if (aType
== "dialog")
497 aTimes
.emplace_back(); // complete wait for dialog
499 unsigned char *pBuffer
= new unsigned char[nWidth
* nHeight
* 4];
501 aTimes
.emplace_back("render dialog");
502 pDocument
->paintWindow(nDialogId
, pBuffer
, 0, 0, nWidth
, nHeight
);
503 dumpTile("dialog", nWidth
, nHeight
, pDocument
->getTileMode(), pBuffer
);
504 aTimes
.emplace_back();
508 bDialogRendered
= true;
513 static void testDialog( Document
*pDocument
, const char *uno_cmd
)
515 int view
= pDocument
->createView();
516 pDocument
->setView(view
);
517 pDocument
->registerCallback(kitCallback
, pDocument
);
519 aTimes
.emplace_back("open dialog");
520 pDocument
->postUnoCommand(uno_cmd
, nullptr, true);
521 aTimes
.emplace_back();
523 aTimes
.emplace_back("wait for dialog");
524 while (!bDialogRendered
)
529 aTimes
.emplace_back("post close dialog");
530 pDocument
->postWindow(nDialogId
, LOK_WINDOW_CLOSE
);
531 aTimes
.emplace_back();
533 pDocument
->destroyView(view
);
536 static void documentCallback(const int type
, const char* p
, void*)
538 std::cerr
<< "Document callback " << type
<< ": " << (p
? p
: "(null)") << "\n";
541 // Avoid excessive dbgutil churn.
542 static void ignoreCallback(const int /*type*/, const char* /*p*/, void* /*data*/)
546 int main( int argc
, char* argv
[] )
549 origin
= getTimeNow();
552 // avoid X oddness etc.
556 ( argc
> 1 && ( !strcmp( argv
[1], "--help" ) || !strcmp( argv
[1], "-h" ) ) ) )
559 if ( argv
[1][0] != '/' )
561 fprintf(stderr
, "Absolute path required to libreoffice install\n");
565 const char *doc_url
= argv
[arg
++];
566 const char *mode
= argv
[arg
++];
568 bool pre_init
= false;
569 if (!strcmp(mode
, "--preinit"))
575 std::string
user_url("file:///");
576 user_url
.append(argv
[1]);
577 user_url
.append("../user");
581 aTimes
.emplace_back("pre-initialization");
582 setenv("LOK_WHITELIST_LANGUAGES", "en_US", 0);
583 // coverity[tainted_string] - build time test tool
584 lok_preinit(argv
[1], user_url
.c_str());
585 aTimes
.emplace_back();
587 const char *install_path
= argv
[1];
588 const char *user_profile
= user_url
.c_str();
590 const char *install_path
= nullptr;
591 const char *user_profile
= nullptr;
592 const char *doc_url
= strdup([[[[[NSBundle mainBundle
] bundleURL
] absoluteString
] stringByAppendingString
:@
"/test.odt"] UTF8String
]);
593 const char *mode
= "--tile";
596 aTimes
.emplace_back("initialization");
597 // coverity[tainted_string] - build time test tool
598 std::unique_ptr
<Office
> pOffice( lok_cpp_init(install_path
, user_profile
) );
599 if (pOffice
== nullptr)
601 fprintf(stderr
, "Failed to initialize Office from %s\n", argv
[1]);
604 aTimes
.emplace_back();
605 pOffice
->registerCallback(ignoreCallback
, nullptr);
607 std::unique_ptr
<Document
> pDocument
;
609 pOffice
->setOptionalFeatures(LOK_FEATURE_NO_TILED_ANNOTATIONS
);
611 aTimes
.emplace_back("load document");
612 if (doc_url
!= nullptr)
613 pDocument
.reset(pOffice
->documentLoad(doc_url
));
614 aTimes
.emplace_back();
618 pDocument
->initializeForRendering("{\".uno:Author\":{\"type\":\"string\",\"value\":\"Local Host #0\"}}");
619 pDocument
->registerCallback(documentCallback
, nullptr);
620 if (!strcmp(mode
, "--tile"))
622 const int max_parts
= (argc
> arg
? atoi(argv
[arg
++]) : -1);
623 int max_tiles
= (argc
> arg
? atoi(argv
[arg
++]) : -1);
624 const bool dump
= true;
626 testTile (pDocument
.get(), max_parts
, max_tiles
, dump
);
628 else if (!strcmp(mode
, "--join"))
630 return testJoin (pDocument
.get());
632 else if (!strcmp (mode
, "--dialog"))
634 const char *uno_cmd
= argc
> arg
? argv
[arg
++] : nullptr;
637 switch (pDocument
->getDocumentType())
639 case LOK_DOCTYPE_SPREADSHEET
:
640 uno_cmd
= ".uno:FormatCellDialog";
642 case LOK_DOCTYPE_TEXT
:
643 case LOK_DOCTYPE_PRESENTATION
:
644 case LOK_DOCTYPE_DRAWING
:
645 case LOK_DOCTYPE_OTHER
:
646 return help("missing argument to --dialog and no default");
649 testDialog (pDocument
.get(), uno_cmd
);
651 return help ("unknown parameter");
654 aTimes
.emplace_back("destroy document");
656 aTimes
.emplace_back();
661 fprintf (stderr
, "profile run:\n");
662 for (size_t i
= 0; i
< aTimes
.size() - 1; i
++)
664 const double nDelta
= aTimes
[i
+1].mfTime
- aTimes
[i
].mfTime
;
665 fprintf (stderr
, " %s - %2.4f(ms)\n", aTimes
[i
].mpName
, nDelta
* 1000.0);
666 if (aTimes
[i
+1].mpName
== nullptr)
670 fprintf (stderr
, "Total: %2.4f(s)\n", nTotal
);
674 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */