xref: /core/desktop/source/lib/init.cxx (revision d5fbbc75)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  */
9 
10 #include <sfx2/lokhelper.hxx>
11 #include <sal/types.h>
12 #include <svx/sdr/contact/viewcontact.hxx>
13 #include <svx/svdpage.hxx>
14 #include <svx/svdpagv.hxx>
15 #include <config_buildconfig.h>
16 #include <config_cairo_rgba.h>
17 #include <config_features.h>
18 
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 
23 #ifdef IOS
24 #include <sys/mman.h>
25 #include <sys/stat.h>
26 #include <unicode/udata.h>
27 #include <unicode/ucnv.h>
28 #include <premac.h>
29 #import <Foundation/Foundation.h>
30 #import <CoreGraphics/CoreGraphics.h>
31 #include <postmac.h>
32 #endif
33 
34 #undef HAVE_MALLOC_TRIM
35 
36 #ifdef LINUX
37 #include <fcntl.h>
38 #if defined __GLIBC__
39 #  include <malloc.h>
40 #  define HAVE_MALLOC_TRIM
41 #endif
42 #endif
43 
44 #ifdef ANDROID
45 #include <osl/detail/android-bootstrap.h>
46 #endif
47 
48 #ifdef EMSCRIPTEN
49 #include <osl/detail/emscripten-bootstrap.h>
50 #endif
51 
52 #include <algorithm>
53 #include <memory>
54 #include <iostream>
55 #include <string_view>
56 #include <queue>
57 
58 #include <boost/property_tree/json_parser.hpp>
59 #include <boost/algorithm/string.hpp>
60 
61 #include <LibreOfficeKit/LibreOfficeKit.h>
62 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
63 
64 #include <sal/log.hxx>
65 #include <utility>
66 #include <vcl/errinf.hxx>
67 #include <vcl/lok.hxx>
68 #include <o3tl/any.hxx>
69 #include <o3tl/unit_conversion.hxx>
70 #include <o3tl/string_view.hxx>
71 #include <osl/file.hxx>
72 #include <osl/process.h>
73 #include <osl/thread.h>
74 #include <rtl/bootstrap.hxx>
75 #include <rtl/strbuf.hxx>
76 #include <rtl/uri.hxx>
77 #include <svl/zforlist.hxx>
78 #include <linguistic/misc.hxx>
79 #include <cppuhelper/bootstrap.hxx>
80 #include <comphelper/base64.hxx>
81 #include <comphelper/dispatchcommand.hxx>
82 #include <comphelper/lok.hxx>
83 #include <comphelper/processfactory.hxx>
84 #include <comphelper/string.hxx>
85 #include <comphelper/profilezone.hxx>
86 #include <comphelper/propertysequence.hxx>
87 #include <comphelper/propertyvalue.hxx>
88 #include <comphelper/scopeguard.hxx>
89 #include <comphelper/threadpool.hxx>
90 #include <comphelper/types.hxx>
91 #include <comphelper/servicehelper.hxx>
92 #include <comphelper/sequenceashashmap.hxx>
93 
94 #include <com/sun/star/connection/XConnection.hpp>
95 #include <com/sun/star/document/MacroExecMode.hpp>
96 #include <com/sun/star/beans/XPropertySet.hpp>
97 #include <com/sun/star/container/XNameAccess.hpp>
98 #include <com/sun/star/document/XDocumentLanguages.hpp>
99 #include <com/sun/star/frame/Desktop.hpp>
100 #include <com/sun/star/frame/DispatchResultEvent.hpp>
101 #include <com/sun/star/frame/DispatchResultState.hpp>
102 #include <com/sun/star/frame/XDispatchProvider.hpp>
103 #include <com/sun/star/frame/XDispatchResultListener.hpp>
104 #include <com/sun/star/frame/XSynchronousDispatch.hpp>
105 #include <com/sun/star/frame/XStorable.hpp>
106 #include <com/sun/star/lang/Locale.hpp>
107 #include <com/sun/star/lang/XComponent.hpp>
108 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
109 #include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
110 #include <com/sun/star/util/URLTransformer.hpp>
111 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
112 #include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
113 #include <com/sun/star/datatransfer/XTransferable2.hpp>
114 #include <com/sun/star/text/TextContentAnchorType.hpp>
115 #include <com/sun/star/document/XRedlinesSupplier.hpp>
116 #include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp>
117 #include <com/sun/star/bridge/BridgeFactory.hpp>
118 #include <com/sun/star/bridge/XBridgeFactory.hpp>
119 #include <com/sun/star/bridge/XBridge.hpp>
120 #include <com/sun/star/uno/XNamingService.hpp>
121 
122 #include <com/sun/star/xml/crypto/SEInitializer.hpp>
123 #include <com/sun/star/xml/crypto/XSEInitializer.hpp>
124 #include <com/sun/star/xml/crypto/XSecurityEnvironment.hpp>
125 #include <com/sun/star/xml/crypto/XCertificateCreator.hpp>
126 #include <com/sun/star/security/XCertificate.hpp>
127 
128 #include <com/sun/star/linguistic2/DictionaryList.hpp>
129 #include <com/sun/star/linguistic2/LanguageGuessing.hpp>
130 #include <com/sun/star/linguistic2/LinguServiceManager.hpp>
131 #include <com/sun/star/linguistic2/XSpellChecker.hpp>
132 #include <com/sun/star/linguistic2/XProofreader.hpp>
133 #include <com/sun/star/i18n/LocaleCalendar2.hpp>
134 #include <com/sun/star/i18n/ScriptType.hpp>
135 #include <com/sun/star/lang/DisposedException.hpp>
136 #include <com/sun/star/view/XSelectionSupplier.hpp>
137 
138 #include <editeng/flstitem.hxx>
139 #ifdef IOS
140 #include <sfx2/app.hxx>
141 #endif
142 #include <sfx2/objsh.hxx>
143 #include <sfx2/docfilt.hxx>
144 #include <sfx2/docfile.hxx>
145 #include <sfx2/viewsh.hxx>
146 #include <sfx2/viewfrm.hxx>
147 #include <sfx2/msgpool.hxx>
148 #include <sfx2/dispatch.hxx>
149 #include <sfx2/lokcomponenthelpers.hxx>
150 #include <sfx2/DocumentSigner.hxx>
151 #include <sfx2/sidebar/SidebarDockingWindow.hxx>
152 #include <sfx2/sidebar/SidebarController.hxx>
153 #include <svl/numformat.hxx>
154 #include <svx/dialmgr.hxx>
155 #include <svx/strings.hrc>
156 #include <svx/svdview.hxx>
157 #include <svx/svxids.hrc>
158 #include <svx/ucsubset.hxx>
159 #include <vcl/vclevent.hxx>
160 #include <vcl/GestureEventPan.hxx>
161 #include <vcl/svapp.hxx>
162 #include <unotools/resmgr.hxx>
163 #include <tools/fract.hxx>
164 #include <tools/json_writer.hxx>
165 #include <svtools/ctrltool.hxx>
166 #include <svtools/langtab.hxx>
167 #include <vcl/fontcharmap.hxx>
168 #ifdef IOS
169 #include <vcl/sysdata.hxx>
170 #endif
171 #include <vcl/virdev.hxx>
172 #include <vcl/ImageTree.hxx>
173 #include <vcl/ITiledRenderable.hxx>
174 #include <vcl/dialoghelper.hxx>
175 #ifdef _WIN32
176 #include <vcl/BitmapReadAccess.hxx>
177 #endif
178 #include <unicode/uchar.h>
179 #include <unotools/securityoptions.hxx>
180 #include <unotools/confignode.hxx>
181 #include <unotools/syslocaleoptions.hxx>
182 #include <unotools/mediadescriptor.hxx>
183 #include <unotools/pathoptions.hxx>
184 #include <unotools/tempfile.hxx>
185 #include <unotools/streamwrap.hxx>
186 #include <osl/module.hxx>
187 #include <comphelper/sequence.hxx>
188 #include <sfx2/sfxbasemodel.hxx>
189 #include <svl/undo.hxx>
190 #include <unotools/datetime.hxx>
191 #include <i18nlangtag/mslangid.hxx>
192 #include <i18nlangtag/languagetag.hxx>
193 #include <vcl/abstdlg.hxx>
194 #include <comphelper/diagnose_ex.hxx>
195 #include <vcl/uitest/uiobject.hxx>
196 #include <vcl/jsdialog/executor.hxx>
197 
198 // Needed for getUndoManager()
199 #include <com/sun/star/document/XUndoManager.hpp>
200 #include <com/sun/star/document/XUndoManagerSupplier.hpp>
201 #include <com/sun/star/document/XLinkTargetSupplier.hpp>
202 #include <editeng/sizeitem.hxx>
203 #include <svx/rulritem.hxx>
204 #include <svx/pageitem.hxx>
205 
206 #include <app.hxx>
207 
208 #include "../app/cmdlineargs.hxx"
209 // We also need to hackily be able to start the main libreoffice thread:
210 #include "../app/sofficemain.h"
211 #include "../app/officeipcthread.hxx"
212 #include <lib/init.hxx>
213 
214 #include "lokinteractionhandler.hxx"
215 #include "lokclipboard.hxx"
216 #include <officecfg/Office/Common.hxx>
217 #include <officecfg/Office/Impress.hxx>
218 #include <officecfg/Office/Linguistic.hxx>
219 #include <officecfg/Office/UI/ToolbarMode.hxx>
220 #include <unotools/optionsdlg.hxx>
221 #include <svl/ctloptions.hxx>
222 #include <svtools/colorcfg.hxx>
223 #include <svtools/miscopt.hxx>
224 #include <unotools/cmdoptions.hxx>
225 #include <unotools/lingucfg.hxx>
226 #include <unotools/moduleoptions.hxx>
227 #include <unotools/searchopt.hxx>
228 #include <unotools/useroptions.hxx>
229 #include <unotools/viewoptions.hxx>
230 #include <vcl/settings.hxx>
231 
232 #include <officecfg/Setup.hxx>
233 #include <com/sun/star/ui/XAcceleratorConfiguration.hpp>
234 #include <svtools/acceleratorexecute.hxx>
235 
236 #include <tools/hostfilter.hxx>
237 
238 using namespace css;
239 using namespace vcl;
240 using namespace desktop;
241 using namespace utl;
242 using namespace bridge;
243 using namespace uno;
244 using namespace lang;
245 
246 using LanguageToolCfg = officecfg::Office::Linguistic::GrammarChecking::LanguageTool;
247 
248 static LibLibreOffice_Impl *gImpl = nullptr;
249 static bool lok_preinit_2_called = false;
250 static std::weak_ptr< LibreOfficeKitClass > gOfficeClass;
251 static std::weak_ptr< LibreOfficeKitDocumentClass > gDocumentClass;
252 
253 static void SetLastExceptionMsg(const OUString& s = OUString())
254 {
255     SAL_WARN_IF(!s.isEmpty(), "lok", "lok exception '" + s + "'");
256     if (gImpl)
257         gImpl->maLastExceptionMsg = s;
258 }
259 
260 namespace {
261 
262 struct ExtensionMap
263 {
264     std::string_view extn;
265     OUString filterName;
266 };
267 
268 class TraceEventDumper : public AutoTimer
269 {
270     static const int dumpTimeoutMS = 5000;
271 
272 public:
273     TraceEventDumper() : AutoTimer( "Trace Event dumper" )
274     {
275         SetTimeout(dumpTimeoutMS);
276         Start();
277     }
278 
279     virtual void Invoke() override
280     {
281         flushRecordings();
282     }
283 
284     static void flushRecordings()
285     {
286         const css::uno::Sequence<OUString> aEvents =
287             comphelper::TraceEvent::getRecordingAndClear();
288         OStringBuffer aOutput;
289         for (const auto &s : aEvents)
290         {
291             aOutput.append(OUStringToOString(s, RTL_TEXTENCODING_UTF8)
292                 + "\n");
293         }
294         if (aOutput.getLength() > 0)
295         {
296             OString aChunk = aOutput.makeStringAndClear();
297             if (gImpl && gImpl->mpCallback)
298                 gImpl->mpCallback(LOK_CALLBACK_PROFILE_FRAME, aChunk.getStr(), gImpl->mpCallbackData);
299         }
300     }
301 };
302 
303 TraceEventDumper *traceEventDumper = nullptr;
304 
305 constexpr ExtensionMap aWriterExtensionMap[] =
306 {
307     { "doc",   u"MS Word 97"_ustr },
308     { "docm",  u"MS Word 2007 XML VBA"_ustr },
309     { "docx",  u"MS Word 2007 XML"_ustr },
310     { "fodt",  u"OpenDocument Text Flat XML"_ustr },
311     { "html",  u"HTML (StarWriter)"_ustr },
312     { "odt",   u"writer8"_ustr },
313     { "ott",   u"writer8_template"_ustr },
314     { "pdf",   u"writer_pdf_Export"_ustr },
315     { "epub",  u"EPUB"_ustr },
316     { "rtf",   u"Rich Text Format"_ustr },
317     { "txt",   u"Text"_ustr },
318     { "xhtml", u"XHTML Writer File"_ustr },
319     { "png",   u"writer_png_Export"_ustr },
320     { "xml",   u"writer_indexing_export"_ustr },
321 };
322 
323 constexpr ExtensionMap aCalcExtensionMap[] =
324 {
325     { "csv",   u"Text - txt - csv (StarCalc)"_ustr },
326     { "fods",  u"OpenDocument Spreadsheet Flat XML"_ustr },
327     { "html",  u"HTML (StarCalc)"_ustr },
328     { "ods",   u"calc8"_ustr },
329     { "ots",   u"calc8_template"_ustr },
330     { "pdf",   u"calc_pdf_Export"_ustr },
331     { "xhtml", u"XHTML Calc File"_ustr },
332     { "xls",   u"MS Excel 97"_ustr },
333     { "xlsm",  u"Calc MS Excel 2007 VBA XML"_ustr },
334     { "xlsx",  u"Calc MS Excel 2007 XML"_ustr },
335     { "png",   u"calc_png_Export"_ustr },
336 };
337 
338 constexpr ExtensionMap aImpressExtensionMap[] =
339 {
340     { "fodp",  u"OpenDocument Presentation Flat XML"_ustr },
341     { "html",  u"impress_html_Export"_ustr },
342     { "odg",   u"impress8_draw"_ustr },
343     { "odp",   u"impress8"_ustr },
344     { "otp",   u"impress8_template"_ustr },
345     { "pdf",   u"impress_pdf_Export"_ustr },
346     { "potm",  u"Impress MS PowerPoint 2007 XML Template"_ustr },
347     { "pot",   u"MS PowerPoint 97 Vorlage"_ustr },
348     { "pptm",  u"Impress MS PowerPoint 2007 XML VBA"_ustr },
349     { "pptx",  u"Impress MS PowerPoint 2007 XML"_ustr },
350     { "pps",   u"MS PowerPoint 97 Autoplay"_ustr },
351     { "ppt",   u"MS PowerPoint 97"_ustr },
352     { "svg",   u"impress_svg_Export"_ustr },
353     { "xhtml", u"XHTML Impress File"_ustr },
354     { "png",   u"impress_png_Export"_ustr },
355 };
356 
357 constexpr ExtensionMap aDrawExtensionMap[] =
358 {
359     { "fodg",  u"draw_ODG_FlatXML"_ustr },
360     { "html",  u"draw_html_Export"_ustr },
361     { "odg",   u"draw8"_ustr },
362     { "pdf",   u"draw_pdf_Export"_ustr },
363     { "svg",   u"draw_svg_Export"_ustr },
364     { "xhtml", u"XHTML Draw File"_ustr },
365     { "png",   u"draw_png_Export"_ustr },
366 };
367 
368 OUString getUString(const char* pString)
369 {
370     if (pString == nullptr)
371         return OUString();
372 
373     return OStringToOUString(pString, RTL_TEXTENCODING_UTF8);
374 }
375 
376 // Tolerate embedded \0s etc.
377 char *convertOString(const OString &rStr)
378 {
379     char* pMemory = static_cast<char*>(malloc(rStr.getLength() + 1));
380     assert(pMemory); // don't tolerate failed allocations.
381     memcpy(pMemory, rStr.getStr(), rStr.getLength() + 1);
382     return pMemory;
383 }
384 
385 char *convertOUString(std::u16string_view aStr)
386 {
387     return convertOString(OUStringToOString(aStr, RTL_TEXTENCODING_UTF8));
388 }
389 
390 /// Try to convert a relative URL to an absolute one, unless it already looks like a URL.
391 OUString getAbsoluteURL(const char* pURL)
392 {
393     OUString aURL(getUString(pURL));
394     if (aURL.isEmpty())
395         return aURL;
396 
397     // convert relative paths to absolute ones
398     OUString aWorkingDir;
399     osl_getProcessWorkingDir(&aWorkingDir.pData);
400     if (!aWorkingDir.endsWith("/"))
401         aWorkingDir += "/";
402 
403     try
404     {
405         return rtl::Uri::convertRelToAbs(aWorkingDir, aURL);
406     }
407     catch (const rtl::MalformedUriException &)
408     {
409     }
410 
411     return OUString();
412 }
413 
414 } // unnamed namespace
415 
416 std::vector<beans::PropertyValue> desktop::jsonToPropertyValuesVector(const char* pJSON)
417 {
418     std::vector<beans::PropertyValue> aArguments;
419     if (pJSON && pJSON[0] != '\0')
420     {
421         aArguments = comphelper::JsonToPropertyValues(pJSON);
422     }
423     return aArguments;
424 }
425 
426 static void extractLinks(const uno::Reference< container::XNameAccess >& xLinks, bool subcontent, tools::JsonWriter& aJson)
427 {
428     for (const OUString& aLink : xLinks->getElementNames())
429     {
430         uno::Any aAny;
431 
432         try
433         {
434             aAny = xLinks->getByName( aLink );
435         }
436         catch(const uno::Exception&)
437         {
438             // if the name of the target was invalid (like empty headings)
439             // no object can be provided
440             continue;
441         }
442 
443         uno::Reference< beans::XPropertySet > xTarget;
444         if( aAny >>= xTarget )
445         {
446             try
447             {
448                 // get name to display
449                 aAny = xTarget->getPropertyValue(u"LinkDisplayName"_ustr);
450                 OUString aStrDisplayname;
451                 aAny >>= aStrDisplayname;
452 
453                 if (subcontent)
454                 {
455                     aJson.put(aStrDisplayname, aLink);
456                 }
457                 else
458                 {
459                     uno::Reference<lang::XServiceInfo> xSI(xTarget, uno::UNO_QUERY_THROW);
460                     if (xSI->supportsService(u"com.sun.star.document.LinkTarget"_ustr))
461                     {
462                         aJson.put(aStrDisplayname, aLink);
463                         continue;
464                     }
465                     else
466                     {
467                         auto aNode = aJson.startNode(
468                             OUStringToOString(aStrDisplayname, RTL_TEXTENCODING_UTF8));
469 
470                         uno::Reference< document::XLinkTargetSupplier > xLTS( xTarget, uno::UNO_QUERY );
471                         if( xLTS.is() )
472                             extractLinks(xLTS->getLinks(), true, aJson);
473                     }
474                 }
475             }
476             catch(...)
477             {
478                 SAL_WARN("lok", "extractLinks: Exception");
479             }
480         }
481     }
482 }
483 
484 static void unoAnyToJson(tools::JsonWriter& rJson, std::string_view pNodeName, const uno::Any& anyItem)
485 {
486     auto aNode = rJson.startNode(pNodeName);
487     OUString aType = anyItem.getValueTypeName();
488     rJson.put("type", aType);
489 
490     if (aType == "string")
491         rJson.put("value", anyItem.get<OUString>());
492     else if (aType == "unsigned long")
493         rJson.put("value", OString::number(anyItem.get<sal_uInt32>()));
494     else if (aType == "long")
495         rJson.put("value", OString::number(anyItem.get<sal_Int32>()));
496     else if (aType == "[]any")
497     {
498         uno::Sequence<uno::Any> aSeq;
499         if (anyItem >>= aSeq)
500         {
501             auto valueNode = rJson.startNode("value");
502 
503             for (auto i = 0; i < aSeq.getLength(); ++i)
504             {
505                 unoAnyToJson(rJson, OString::number(i), aSeq[i]);
506             }
507         }
508     }
509 }
510 
511 static int lcl_getViewId(std::string_view payload);
512 
513 namespace desktop {
514 
515 RectangleAndPart RectangleAndPart::Create(const OString& rPayload)
516 {
517     RectangleAndPart aRet;
518     if (rPayload.startsWith("EMPTY")) // payload starts with "EMPTY"
519     {
520         aRet.m_aRectangle = tools::Rectangle(0, 0, SfxLokHelper::MaxTwips, SfxLokHelper::MaxTwips);
521         if (comphelper::LibreOfficeKit::isPartInInvalidation())
522         {
523             int nSeparatorPos = rPayload.indexOf(',', 6);
524             bool bHasMode = nSeparatorPos > 0;
525             if (bHasMode)
526             {
527                 aRet.m_nPart = o3tl::toInt32(rPayload.subView(6, nSeparatorPos - 6));
528                 assert(rPayload.getLength() > nSeparatorPos);
529                 aRet.m_nMode = o3tl::toInt32(rPayload.subView(nSeparatorPos + 1));
530             }
531             else
532             {
533                 aRet.m_nPart = o3tl::toInt32(rPayload.subView(6));
534                 aRet.m_nMode = 0;
535             }
536         }
537 
538         return aRet;
539     }
540 
541     // Read '<left>, <top>, <width>, <height>[, <part>, <mode>]'. C++ streams are simpler but slower.
542     const char* pos = rPayload.getStr();
543     const char* end = rPayload.getStr() + rPayload.getLength();
544     tools::Long nLeft = rtl_str_toInt64_WithLength(pos, 10, end - pos);
545     while (pos < end && *pos != ',')
546         ++pos;
547     if (pos < end)
548         ++pos;
549     assert(pos < end);
550     tools::Long nTop = rtl_str_toInt64_WithLength(pos, 10, end - pos);
551     while (pos < end && *pos != ',')
552         ++pos;
553     if (pos < end)
554         ++pos;
555     assert(pos < end);
556     tools::Long nWidth = rtl_str_toInt64_WithLength(pos, 10, end - pos);
557     while (pos < end && *pos != ',')
558         ++pos;
559     if (pos < end)
560         ++pos;
561     assert(pos < end);
562     tools::Long nHeight = rtl_str_toInt64_WithLength(pos, 10, end - pos);
563     tools::Long nPart = INT_MIN;
564     tools::Long nMode = 0;
565     if (comphelper::LibreOfficeKit::isPartInInvalidation())
566     {
567         while (pos < end && *pos != ',')
568             ++pos;
569         if (pos < end)
570             ++pos;
571         assert(pos < end);
572         nPart = rtl_str_toInt64_WithLength(pos, 10, end - pos);
573 
574         while (pos < end && *pos != ',')
575             ++pos;
576         if (pos < end)
577         {
578             ++pos;
579             assert(pos < end);
580             nMode = rtl_str_toInt64_WithLength(pos, 10, end - pos);
581         }
582     }
583 
584     aRet.m_aRectangle = SanitizedRectangle(nLeft, nTop, nWidth, nHeight);
585     aRet.m_nPart = nPart;
586     aRet.m_nMode = nMode;
587     return aRet;
588 }
589 
590 tools::Rectangle RectangleAndPart::SanitizedRectangle(tools::Long nLeft, tools::Long nTop, tools::Long nWidth, tools::Long nHeight)
591 {
592     if (nWidth <= 0 || nHeight <= 0)
593         return tools::Rectangle();
594 
595     // The top-left corner starts at (0, 0).
596     // Anything negative is invalid.
597     if (nLeft < 0)
598     {
599         nWidth += nLeft;
600         nLeft = 0;
601     }
602 
603     if (nTop < 0)
604     {
605         nHeight += nTop;
606         nTop = 0;
607     }
608 
609     if (nWidth > 0 && nHeight > 0)
610         return tools::Rectangle(nLeft, nTop, nLeft + nWidth, nTop + nHeight);
611     // Else set empty rect.
612     return tools::Rectangle();
613 }
614 
615 tools::Rectangle RectangleAndPart::SanitizedRectangle(const tools::Rectangle& rect)
616 {
617     return SanitizedRectangle(rect.Left(), rect.Top(), rect.getOpenWidth(), rect.getOpenHeight());
618 }
619 
620 const OString& CallbackFlushHandler::CallbackData::getPayload() const
621 {
622     if(PayloadString.isEmpty())
623     {
624         // Do to-string conversion on demand, as many calls will get dropped without
625         // needing the string.
626         if(PayloadObject.which() == 1)
627             PayloadString = getRectangleAndPart().toString();
628     }
629     return PayloadString;
630 }
631 
632 void CallbackFlushHandler::CallbackData::updateRectangleAndPart(const RectangleAndPart& rRectAndPart)
633 {
634     PayloadObject = rRectAndPart;
635     PayloadString.clear(); // will be set on demand if needed
636 }
637 
638 const RectangleAndPart& CallbackFlushHandler::CallbackData::getRectangleAndPart() const
639 {
640     // TODO: In case of unittests, they do not pass invalidations in binary but as text messages.
641     // LO core should preferably always pass binary for performance.
642     if(PayloadObject.which() != 1)
643         PayloadObject = RectangleAndPart::Create(PayloadString);
644     return boost::get<RectangleAndPart>(PayloadObject);
645 }
646 
647 boost::property_tree::ptree& CallbackFlushHandler::CallbackData::setJson(const std::string& payload)
648 {
649     boost::property_tree::ptree aTree;
650     std::stringstream aStream(payload);
651     boost::property_tree::read_json(aStream, aTree);
652 
653     // Let boost normalize the payload so it always matches the cache.
654     setJson(aTree);
655 
656     // Return reference to the cached object.
657     return boost::get<boost::property_tree::ptree>(PayloadObject);
658 }
659 
660 void CallbackFlushHandler::CallbackData::setJson(const boost::property_tree::ptree& rTree)
661 {
662     std::stringstream aJSONStream;
663     constexpr bool bPretty = false; // Don't waste time and bloat logs.
664     boost::property_tree::write_json(aJSONStream, rTree, bPretty);
665     PayloadString = OString(o3tl::trim(aJSONStream.str()));
666 
667     PayloadObject = rTree;
668 }
669 
670 const boost::property_tree::ptree& CallbackFlushHandler::CallbackData::getJson() const
671 {
672     assert(PayloadObject.which() == 2);
673     return boost::get<boost::property_tree::ptree>(PayloadObject);
674 }
675 
676 int CallbackFlushHandler::CallbackData::getViewId() const
677 {
678     if (isCached())
679     {
680         assert(PayloadObject.which() == 3);
681         return boost::get<int>(PayloadObject);
682     }
683     return lcl_getViewId(getPayload());
684 }
685 
686 bool CallbackFlushHandler::CallbackData::validate() const
687 {
688     switch (PayloadObject.which())
689     {
690         // Not cached.
691         case 0:
692             return true;
693 
694         // RectangleAndPart.
695         case 1:
696             return getRectangleAndPart().toString().getStr() == getPayload();
697 
698         // Json.
699         case 2:
700         {
701             std::stringstream aJSONStream;
702             boost::property_tree::write_json(aJSONStream, getJson(), false);
703             const std::string aExpected = boost::trim_copy(aJSONStream.str());
704             return getPayload() == std::string_view(aExpected);
705         }
706 
707         // View id.
708         case 3:
709             return getViewId() == lcl_getViewId( getPayload());
710 
711         default:
712             assert(!"Unknown variant type; please add an entry to validate.");
713     }
714 
715     return false;
716 }
717 
718 } // namespace desktop
719 
720 static bool lcl_isViewCallbackType(const int type)
721 {
722     switch (type)
723     {
724         case LOK_CALLBACK_CELL_VIEW_CURSOR:
725         case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
726         case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
727         case LOK_CALLBACK_TEXT_VIEW_SELECTION:
728         case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
729             return true;
730 
731         default:
732             return false;
733     }
734 }
735 
736 static bool isUpdatedType(int type)
737 {
738     switch (type)
739     {
740         case LOK_CALLBACK_TEXT_SELECTION:
741         case LOK_CALLBACK_TEXT_SELECTION_START:
742         case LOK_CALLBACK_TEXT_SELECTION_END:
743             return true;
744         default:
745             return false;
746     }
747 }
748 
749 static bool isUpdatedTypePerViewId(int type)
750 {
751     switch (type)
752     {
753         case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
754         case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
755         case LOK_CALLBACK_TEXT_VIEW_SELECTION:
756             return true;
757         default:
758             return false;
759     }
760 }
761 
762 static int lcl_getViewId(std::string_view payload)
763 {
764     // this is a cheap way how to get the viewId from a JSON message; proper
765     // parsing is terribly expensive, and we just need the viewId here
766     size_t viewIdPos = payload.find("viewId");
767     if (viewIdPos == std::string::npos)
768         return 0;
769 
770     size_t numberPos = payload.find(":", viewIdPos + 6);
771     if (numberPos == std::string::npos)
772         return 0;
773 
774     for (++numberPos; numberPos < payload.length(); ++numberPos)
775     {
776         if (payload[numberPos] == ',' || payload[numberPos] == '}' || (payload[numberPos] >= '0' && payload[numberPos] <= '9'))
777             break;
778     }
779 
780     if (numberPos < payload.length() && payload[numberPos] >= '0' && payload[numberPos] <= '9')
781         return o3tl::toInt32(payload.substr(numberPos));
782 
783     return 0;
784 }
785 
786 namespace {
787 
788 std::string extractCertificate(const std::string & certificate)
789 {
790     static constexpr std::string_view header("-----BEGIN CERTIFICATE-----");
791     static constexpr std::string_view footer("-----END CERTIFICATE-----");
792 
793     std::string result;
794 
795     size_t pos1 = certificate.find(header);
796     if (pos1 == std::string::npos)
797         return result;
798 
799     size_t pos2 = certificate.find(footer, pos1 + 1);
800     if (pos2 == std::string::npos)
801         return result;
802 
803     pos1 = pos1 + header.length();
804     pos2 = pos2 - pos1;
805 
806     return certificate.substr(pos1, pos2);
807 }
808 
809 std::string extractPrivateKey(const std::string & privateKey)
810 {
811     static constexpr std::string_view header("-----BEGIN PRIVATE KEY-----");
812     static constexpr std::string_view footer("-----END PRIVATE KEY-----");
813 
814     std::string result;
815 
816     size_t pos1 = privateKey.find(header);
817     if (pos1 == std::string::npos)
818         return result;
819 
820     size_t pos2 = privateKey.find(footer, pos1 + 1);
821     if (pos2 == std::string::npos)
822         return result;
823 
824     pos1 = pos1 + header.length();
825     pos2 = pos2 - pos1;
826 
827     return privateKey.substr(pos1, pos2);
828 }
829 
830 OUString lcl_getCurrentDocumentMimeType(const LibLODocument_Impl* pDocument)
831 {
832     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
833     if (!pBaseModel)
834         return "";
835 
836     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
837     if (!pObjectShell)
838         return "";
839 
840     SfxMedium* pMedium = pObjectShell->GetMedium();
841     if (!pMedium)
842         return "";
843 
844     auto pFilter = pMedium->GetFilter();
845     if (!pFilter)
846         return "";
847 
848     return pFilter->GetMimeType();
849 }
850 
851 // Gets an undo manager to enter and exit undo context. Needed by ToggleOrientation
852 css::uno::Reference< css::document::XUndoManager > getUndoManager( const css::uno::Reference< css::frame::XFrame >& rxFrame )
853 {
854     const css::uno::Reference< css::frame::XController >& xController = rxFrame->getController();
855     if ( xController.is() )
856     {
857         const css::uno::Reference< css::frame::XModel >& xModel = xController->getModel();
858         if ( xModel.is() )
859         {
860             const css::uno::Reference< css::document::XUndoManagerSupplier > xSuppUndo( xModel, css::uno::UNO_QUERY_THROW );
861             return css::uno::Reference< css::document::XUndoManager >( xSuppUndo->getUndoManager(), css::uno::UNO_SET_THROW );
862         }
863     }
864 
865     return css::uno::Reference< css::document::XUndoManager > ();
866 }
867 
868 // Adjusts page margins for Writer doc. Needed by ToggleOrientation
869 void ExecuteMarginLRChange(
870     const tools::Long nPageLeftMargin,
871     const tools::Long nPageRightMargin,
872     SvxLongLRSpaceItem* pPageLRMarginItem)
873 {
874     pPageLRMarginItem->SetLeft( nPageLeftMargin );
875     pPageLRMarginItem->SetRight( nPageRightMargin );
876     SfxViewShell::Current()->GetDispatcher()->ExecuteList(SID_ATTR_PAGE_LRSPACE,
877             SfxCallMode::RECORD, { pPageLRMarginItem });
878 }
879 
880 // Adjusts page margins for Writer doc. Needed by ToggleOrientation
881 void ExecuteMarginULChange(
882         const tools::Long nPageTopMargin,
883         const tools::Long nPageBottomMargin,
884         SvxLongULSpaceItem* pPageULMarginItem)
885 {
886     pPageULMarginItem->SetUpper( nPageTopMargin );
887     pPageULMarginItem->SetLower( nPageBottomMargin );
888     SfxViewShell::Current()->GetDispatcher()->ExecuteList(SID_ATTR_PAGE_ULSPACE,
889                                                           SfxCallMode::RECORD, { pPageULMarginItem });
890 }
891 
892 // Main function which toggles page orientation of the Writer doc. Needed by ToggleOrientation
893 void ExecuteOrientationChange()
894 {
895     SfxViewFrame* pViewFrm = SfxViewFrame::Current();
896     if (!pViewFrm)
897         return;
898 
899     std::unique_ptr<SvxPageItem> pPageItem(new SvxPageItem(SID_ATTR_PAGE));
900 
901     // 1mm in twips rounded
902     // This should be in sync with MINBODY in sw/source/uibase/sidebar/PageMarginControl.hxx
903     constexpr tools::Long MINBODY = o3tl::toTwips(1, o3tl::Length::mm);
904 
905     css::uno::Reference< css::document::XUndoManager > mxUndoManager(
906                 getUndoManager( pViewFrm->GetFrame().GetFrameInterface() ) );
907 
908     if ( mxUndoManager.is() )
909         mxUndoManager->enterUndoContext( "" );
910 
911     SfxPoolItemHolder aResult;
912     pViewFrm->GetBindings().GetDispatcher()->QueryState(SID_ATTR_PAGE_SIZE, aResult);
913     std::unique_ptr<SvxSizeItem> pPageSizeItem(static_cast<const SvxSizeItem*>(aResult.getItem())->Clone());
914 
915     pViewFrm->GetBindings().GetDispatcher()->QueryState(SID_ATTR_PAGE_LRSPACE, aResult);
916     std::unique_ptr<SvxLongLRSpaceItem> pPageLRMarginItem(static_cast<const SvxLongLRSpaceItem*>(aResult.getItem())->Clone());
917 
918     pViewFrm->GetBindings().GetDispatcher()->QueryState(SID_ATTR_PAGE_ULSPACE, aResult);
919     std::unique_ptr<SvxLongULSpaceItem> pPageULMarginItem(static_cast<const SvxLongULSpaceItem*>(aResult.getItem())->Clone());
920 
921     {
922         bool bIsLandscape = false;
923         if ( pPageSizeItem->GetSize().Width() > pPageSizeItem->GetSize().Height())
924             bIsLandscape = true;
925 
926         // toggle page orientation
927         pPageItem->SetLandscape(!bIsLandscape);
928 
929 
930         // swap the width and height of the page size
931         const tools::Long nRotatedWidth = pPageSizeItem->GetSize().Height();
932         const tools::Long nRotatedHeight = pPageSizeItem->GetSize().Width();
933         pPageSizeItem->SetSize(Size(nRotatedWidth, nRotatedHeight));
934 
935 
936         // apply changed attributes
937         if (SfxViewShell::Current())
938         {
939             SfxViewShell::Current()->GetDispatcher()->ExecuteList(SID_ATTR_PAGE_SIZE,
940                 SfxCallMode::RECORD, { pPageSizeItem.get(), pPageItem.get() });
941         }
942     }
943 
944 
945     // check, if margin values still fit to the changed page size.
946     // if not, adjust margin values
947     {
948         const tools::Long nML = pPageLRMarginItem->GetLeft();
949         const tools::Long nMR = pPageLRMarginItem->GetRight();
950         const tools::Long nTmpPW = nML + nMR + MINBODY;
951 
952         const tools::Long nPW  = pPageSizeItem->GetSize().Width();
953 
954         if ( nTmpPW > nPW )
955         {
956             if ( nML <= nMR )
957             {
958                 ExecuteMarginLRChange( pPageLRMarginItem->GetLeft(), nMR - (nTmpPW - nPW ), pPageLRMarginItem.get() );
959             }
960             else
961             {
962                 ExecuteMarginLRChange( nML - (nTmpPW - nPW ), pPageLRMarginItem->GetRight(), pPageLRMarginItem.get() );
963             }
964         }
965 
966         const tools::Long nMT = pPageULMarginItem->GetUpper();
967         const tools::Long nMB = pPageULMarginItem->GetLower();
968         const tools::Long nTmpPH = nMT + nMB + MINBODY;
969 
970         const tools::Long nPH  = pPageSizeItem->GetSize().Height();
971 
972         if ( nTmpPH > nPH )
973         {
974             if ( nMT <= nMB )
975             {
976                 ExecuteMarginULChange( pPageULMarginItem->GetUpper(), nMB - ( nTmpPH - nPH ), pPageULMarginItem.get() );
977             }
978             else
979             {
980                 ExecuteMarginULChange( nMT - ( nTmpPH - nPH ), pPageULMarginItem->GetLower(), pPageULMarginItem.get() );
981             }
982         }
983     }
984 
985     if ( mxUndoManager.is() )
986         mxUndoManager->leaveUndoContext();
987 }
988 
989 void setupSidebar(std::u16string_view sidebarDeckId = u"")
990 {
991     SfxViewShell* pViewShell = SfxViewShell::Current();
992     SfxViewFrame* pViewFrame = pViewShell ? &pViewShell->GetViewFrame() : nullptr;
993     if (pViewFrame)
994     {
995         if (!pViewFrame->GetChildWindow(SID_SIDEBAR))
996             pViewFrame->SetChildWindow(SID_SIDEBAR, false /* create it */, true /* focus */);
997 
998         pViewFrame->ShowChildWindow(SID_SIDEBAR, true);
999 
1000         // Force synchronous population of panels
1001         SfxChildWindow *pChild = pViewFrame->GetChildWindow(SID_SIDEBAR);
1002         if (!pChild)
1003             return;
1004 
1005         auto pDockingWin = dynamic_cast<sfx2::sidebar::SidebarDockingWindow *>(pChild->GetWindow());
1006         if (!pDockingWin)
1007             return;
1008 
1009         pViewFrame->ShowChildWindow( SID_SIDEBAR );
1010 
1011         const rtl::Reference<sfx2::sidebar::SidebarController>& xController
1012             = pDockingWin->GetOrCreateSidebarController();
1013 
1014         xController->FadeIn();
1015         xController->RequestOpenDeck();
1016 
1017         if (!sidebarDeckId.empty())
1018         {
1019             xController->SwitchToDeck(sidebarDeckId);
1020         }
1021         else
1022         {
1023             xController->SwitchToDefaultDeck();
1024         }
1025 
1026         pDockingWin->SyncUpdate();
1027     }
1028     else
1029         SetLastExceptionMsg(u"No view shell or sidebar"_ustr);
1030 }
1031 
1032 void hideSidebar()
1033 {
1034     SfxViewShell* pViewShell = SfxViewShell::Current();
1035     SfxViewFrame* pViewFrame = pViewShell ? &pViewShell->GetViewFrame() : nullptr;
1036     if (pViewFrame)
1037         pViewFrame->SetChildWindow(SID_SIDEBAR, false , false );
1038     else
1039         SetLastExceptionMsg(u"No view shell or sidebar"_ustr);
1040 }
1041 
1042 }  // end anonymous namespace
1043 
1044 // Could be anonymous in principle, but for the unit testing purposes, we
1045 // declare it in init.hxx.
1046 OUString desktop::extractParameter(OUString& rOptions, std::u16string_view rName)
1047 {
1048     OUString aValue;
1049 
1050     OUString aNameEquals(OUString::Concat(rName) + "=");
1051     OUString aCommaNameEquals(OUString::Concat(",") + rName + "=");
1052 
1053     int nIndex = -1;
1054     if (rOptions.startsWith(aNameEquals))
1055     {
1056         size_t nLen = aNameEquals.getLength();
1057         int nComma = rOptions.indexOf(",", nLen);
1058         if (nComma >= 0)
1059         {
1060             aValue = rOptions.copy(nLen, nComma - nLen);
1061             rOptions = rOptions.copy(nComma + 1);
1062         }
1063         else
1064         {
1065             aValue = rOptions.copy(nLen);
1066             rOptions.clear();
1067         }
1068     }
1069     else if ((nIndex = rOptions.indexOf(aCommaNameEquals)) >= 0)
1070     {
1071         size_t nLen = aCommaNameEquals.getLength();
1072         int nComma = rOptions.indexOf(",", nIndex + nLen);
1073         if (nComma >= 0)
1074         {
1075             aValue = rOptions.copy(nIndex + nLen, nComma - nIndex - nLen);
1076             rOptions = OUString::Concat(rOptions.subView(0, nIndex)) + rOptions.subView(nComma);
1077         }
1078         else
1079         {
1080             aValue = rOptions.copy(nIndex + nLen);
1081             rOptions = rOptions.copy(0, nIndex);
1082         }
1083     }
1084 
1085     return aValue;
1086 }
1087 
1088 extern "C"
1089 {
1090 
1091 static void doc_destroy(LibreOfficeKitDocument* pThis);
1092 static int doc_saveAs(LibreOfficeKitDocument* pThis, const char* pUrl, const char* pFormat, const char* pFilterOptions);
1093 static int doc_getDocumentType(LibreOfficeKitDocument* pThis);
1094 static int doc_getParts(LibreOfficeKitDocument* pThis);
1095 static char* doc_getPartPageRectangles(LibreOfficeKitDocument* pThis);
1096 static int doc_getPart(LibreOfficeKitDocument* pThis);
1097 static void doc_setPart(LibreOfficeKitDocument* pThis, int nPart);
1098 static void doc_selectPart(LibreOfficeKitDocument* pThis, int nPart, int nSelect);
1099 static void doc_moveSelectedParts(LibreOfficeKitDocument* pThis, int nPosition, bool bDuplicate);
1100 static char* doc_getPartName(LibreOfficeKitDocument* pThis, int nPart);
1101 static void doc_setPartMode(LibreOfficeKitDocument* pThis, int nPartMode);
1102 static int doc_getEditMode(LibreOfficeKitDocument* pThis);
1103 static void doc_paintTile(LibreOfficeKitDocument* pThis,
1104                           unsigned char* pBuffer,
1105                           const int nCanvasWidth, const int nCanvasHeight,
1106                           const int nTilePosX, const int nTilePosY,
1107                           const int nTileWidth, const int nTileHeight);
1108 static void doc_paintPartTile(LibreOfficeKitDocument* pThis,
1109                               unsigned char* pBuffer,
1110                               const int nPart,
1111                               const int nMode,
1112                               const int nCanvasWidth, const int nCanvasHeight,
1113                               const int nTilePosX, const int nTilePosY,
1114                               const int nTileWidth, const int nTileHeight);
1115 static int doc_getTileMode(LibreOfficeKitDocument* pThis);
1116 static void doc_getDocumentSize(LibreOfficeKitDocument* pThis,
1117                                 long* pWidth,
1118                                 long* pHeight);
1119 static void doc_getDataArea(LibreOfficeKitDocument* pThis,
1120                             long nTab,
1121                             long* pCol,
1122                             long* pRow);
1123 static void doc_initializeForRendering(LibreOfficeKitDocument* pThis,
1124                                        const char* pArguments);
1125 
1126 static void doc_registerCallback(LibreOfficeKitDocument* pThis,
1127                                 LibreOfficeKitCallback pCallback,
1128                                 void* pData);
1129 static void doc_postKeyEvent(LibreOfficeKitDocument* pThis,
1130                              int nType,
1131                              int nCharCode,
1132                              int nKeyCode);
1133 static void doc_setBlockedCommandList(LibreOfficeKitDocument* pThis,
1134                                 int nViewId,
1135                                 const char* blockedCommandList);
1136 
1137 static void doc_postWindowExtTextInputEvent(LibreOfficeKitDocument* pThis,
1138                                             unsigned nWindowId,
1139                                             int nType,
1140                                             const char* pText);
1141 
1142 static char* doc_hyperlinkInfoAtPosition(LibreOfficeKitDocument *pThis, int x, int y);
1143 
1144 static void doc_removeTextContext(LibreOfficeKitDocument* pThis,
1145                                   unsigned nLOKWindowId,
1146                                   int nCharBefore,
1147                                   int nCharAfter);
1148 static void doc_sendDialogEvent(LibreOfficeKitDocument* pThis,
1149                                unsigned long long int nLOKWindowId,
1150                                const char* pArguments);
1151 static void doc_postWindowKeyEvent(LibreOfficeKitDocument* pThis,
1152                                    unsigned nLOKWindowId,
1153                                    int nType,
1154                                    int nCharCode,
1155                                    int nKeyCode);
1156 static void doc_postMouseEvent (LibreOfficeKitDocument* pThis,
1157                                 int nType,
1158                                 int nX,
1159                                 int nY,
1160                                 int nCount,
1161                                 int nButtons,
1162                                 int nModifier);
1163 static void doc_postWindowMouseEvent (LibreOfficeKitDocument* pThis,
1164                                       unsigned nLOKWindowId,
1165                                       int nType,
1166                                       int nX,
1167                                       int nY,
1168                                       int nCount,
1169                                       int nButtons,
1170                                       int nModifier);
1171 static void doc_postWindowGestureEvent(LibreOfficeKitDocument* pThis,
1172                                       unsigned nLOKWindowId,
1173                                       const char* pType,
1174                                       int nX,
1175                                       int nY,
1176                                       int nOffset);
1177 static void doc_postUnoCommand(LibreOfficeKitDocument* pThis,
1178                                const char* pCommand,
1179                                const char* pArguments,
1180                                bool bNotifyWhenFinished);
1181 static void doc_setWindowTextSelection(LibreOfficeKitDocument* pThis,
1182                                        unsigned nLOKWindowId,
1183                                        bool swap,
1184                                        int nX,
1185                                        int nY);
1186 static void doc_setTextSelection (LibreOfficeKitDocument* pThis,
1187                                   int nType,
1188                                   int nX,
1189                                   int nY);
1190 static char* doc_getTextSelection(LibreOfficeKitDocument* pThis,
1191                                   const char* pMimeType,
1192                                   char** pUsedMimeType);
1193 static int doc_getSelectionType(LibreOfficeKitDocument* pThis);
1194 static int doc_getSelectionTypeAndText(LibreOfficeKitDocument* pThis,
1195                                        const char* pMimeType,
1196                                        char** pText,
1197                                        char** pUsedMimeType);
1198 static int doc_getClipboard (LibreOfficeKitDocument* pThis,
1199                              const char **pMimeTypes,
1200                              size_t      *pOutCount,
1201                              char      ***pOutMimeTypes,
1202                              size_t     **pOutSizes,
1203                              char      ***pOutStreams);
1204 static int doc_setClipboard (LibreOfficeKitDocument* pThis,
1205                              const size_t   nInCount,
1206                              const char   **pInMimeTypes,
1207                              const size_t  *pInSizes,
1208                              const char   **pInStreams);
1209 static bool doc_paste(LibreOfficeKitDocument* pThis,
1210                       const char* pMimeType,
1211                       const char* pData,
1212                       size_t nSize);
1213 static void doc_setGraphicSelection (LibreOfficeKitDocument* pThis,
1214                                   int nType,
1215                                   int nX,
1216                                   int nY);
1217 static void doc_resetSelection (LibreOfficeKitDocument* pThis);
1218 static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCommand);
1219 static void doc_setClientZoom(LibreOfficeKitDocument* pThis,
1220                                     int nTilePixelWidth,
1221                                     int nTilePixelHeight,
1222                                     int nTileTwipWidth,
1223                                     int nTileTwipHeight);
1224 static void doc_setClientVisibleArea(LibreOfficeKitDocument* pThis, int nX, int nY, int nWidth, int nHeight);
1225 static void doc_setOutlineState(LibreOfficeKitDocument* pThis, bool bColumn, int nLevel, int nIndex, bool bHidden);
1226 static int doc_createView(LibreOfficeKitDocument* pThis);
1227 static int doc_createViewWithOptions(LibreOfficeKitDocument* pThis, const char* pOptions);
1228 static void doc_destroyView(LibreOfficeKitDocument* pThis, int nId);
1229 static void doc_setView(LibreOfficeKitDocument* pThis, int nId);
1230 static int doc_getView(LibreOfficeKitDocument* pThis);
1231 static int doc_getViewsCount(LibreOfficeKitDocument* pThis);
1232 static bool doc_getViewIds(LibreOfficeKitDocument* pThis, int* pArray, size_t nSize);
1233 static void doc_setViewLanguage(LibreOfficeKitDocument* pThis, int nId, const char* language);
1234 static unsigned char* doc_renderFontOrientation(LibreOfficeKitDocument* pThis,
1235                           const char *pFontName,
1236                           const char *pChar,
1237                           int* pFontWidth,
1238                           int* pFontHeight,
1239                           int pOrientation);
1240 static unsigned char* doc_renderFont(LibreOfficeKitDocument* pThis,
1241                           const char *pFontName,
1242                           const char *pChar,
1243                           int* pFontWidth,
1244                           int* pFontHeight);
1245 static char* doc_getPartHash(LibreOfficeKitDocument* pThis, int nPart);
1246 
1247 static void doc_paintWindow(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, unsigned char* pBuffer,
1248                             const int nX, const int nY,
1249                             const int nWidth, const int nHeight);
1250 
1251 static void doc_paintWindowDPI(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, unsigned char* pBuffer,
1252                                const int nX, const int nY,
1253                                const int nWidth, const int nHeight,
1254                                const double fDPIScale);
1255 
1256 static void doc_paintWindowForView(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, unsigned char* pBuffer,
1257                                    const int nX, const int nY,
1258                                    const int nWidth, const int nHeight,
1259                                    const double fDPIScale, int viewId);
1260 
1261 static void doc_postWindow(LibreOfficeKitDocument* pThis, unsigned
1262  nLOKWindowId, int nAction, const char* pData);
1263 
1264 static char* doc_getPartInfo(LibreOfficeKitDocument* pThis, int nPart);
1265 
1266 static bool doc_insertCertificate(LibreOfficeKitDocument* pThis,
1267                                   const unsigned char* pCertificateBinary,
1268                                   const int nCertificateBinarySize,
1269                                   const unsigned char* pPrivateKeyBinary,
1270                                   const int nPrivateKeyBinarySize);
1271 
1272 static bool doc_addCertificate(LibreOfficeKitDocument* pThis,
1273                                  const unsigned char* pCertificateBinary,
1274                                  const int nCertificateBinarySize);
1275 
1276 static int doc_getSignatureState(LibreOfficeKitDocument* pThis);
1277 
1278 static size_t doc_renderShapeSelection(LibreOfficeKitDocument* pThis, char** pOutput);
1279 
1280 static void doc_resizeWindow(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId,
1281                              const int nWidth, const int nHeight);
1282 
1283 static void doc_completeFunction(LibreOfficeKitDocument* pThis, const char*);
1284 
1285 
1286 static void doc_sendFormFieldEvent(LibreOfficeKitDocument* pThis,
1287                                    const char* pArguments);
1288 
1289 static bool doc_renderSearchResult(LibreOfficeKitDocument* pThis,
1290                                  const char* pSearchResult, unsigned char** pBitmapBuffer,
1291                                  int* pWidth, int* pHeight, size_t* pByteSize);
1292 
1293 static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const char* pArguments);
1294 
1295 static void doc_setViewTimezone(LibreOfficeKitDocument* pThis, int nId, const char* timezone);
1296 
1297 static void doc_setAccessibilityState(LibreOfficeKitDocument* pThis, int nId, bool bEnabled);
1298 
1299 static char* doc_getA11yFocusedParagraph(LibreOfficeKitDocument* pThis);
1300 
1301 static int doc_getA11yCaretPosition(LibreOfficeKitDocument* pThis);
1302 } // extern "C"
1303 
1304 namespace {
1305 ITiledRenderable* getTiledRenderable(LibreOfficeKitDocument* pThis)
1306 {
1307     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
1308     return dynamic_cast<ITiledRenderable*>(pDocument->mxComponent.get());
1309 }
1310 
1311 #ifndef IOS
1312 
1313 /*
1314  * Unfortunately clipboard creation using UNO is insanely baroque.
1315  * we also need to ensure that this works for the first view which
1316  * has no clear 'createView' called for it (unfortunately).
1317  */
1318 rtl::Reference<LOKClipboard> forceSetClipboardForCurrentView(LibreOfficeKitDocument *pThis)
1319 {
1320     ITiledRenderable* pDoc = getTiledRenderable(pThis);
1321     rtl::Reference<LOKClipboard> xClip(LOKClipboardFactory::getClipboardForCurView());
1322 
1323     SAL_INFO("lok", "Set to clipboard for view " << xClip.get());
1324     // FIXME: using a hammer here - should not be necessary if all tests used createView.
1325     pDoc->setClipboard(uno::Reference<datatransfer::clipboard::XClipboard>(xClip->getXI(), UNO_QUERY));
1326 
1327     return xClip;
1328 }
1329 
1330 #endif
1331 
1332 const vcl::Font* FindFont(std::u16string_view rFontName)
1333 {
1334     SfxObjectShell* pDocSh = SfxObjectShell::Current();
1335     if (!pDocSh)
1336         return nullptr;
1337     const SvxFontListItem* pFonts
1338         = static_cast<const SvxFontListItem*>(pDocSh->GetItem(SID_ATTR_CHAR_FONTLIST));
1339     const FontList* pList = pFonts ? pFonts->GetFontList() : nullptr;
1340     if (pList && !rFontName.empty())
1341         if (sal_Handle hMetric = pList->GetFirstFontMetric(rFontName))
1342             return &FontList::GetFontMetric(hMetric);
1343     return nullptr;
1344 }
1345 
1346 vcl::Font FindFont_FallbackToDefault(std::u16string_view rFontName)
1347 {
1348     if (auto pFound = FindFont(rFontName))
1349         return *pFound;
1350 
1351     return OutputDevice::GetDefaultFont(DefaultFontType::SANS_UNICODE, LANGUAGE_NONE,
1352                                         GetDefaultFontFlags::NONE);
1353 }
1354 
1355 int getDocumentType (LibreOfficeKitDocument* pThis)
1356 {
1357     SetLastExceptionMsg();
1358 
1359     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
1360 
1361     try
1362     {
1363         uno::Reference<lang::XServiceInfo> xDocument(pDocument->mxComponent, uno::UNO_QUERY_THROW);
1364 
1365         if (xDocument->supportsService(u"com.sun.star.sheet.SpreadsheetDocument"_ustr))
1366         {
1367             return LOK_DOCTYPE_SPREADSHEET;
1368         }
1369         else if (xDocument->supportsService(u"com.sun.star.presentation.PresentationDocument"_ustr))
1370         {
1371             return LOK_DOCTYPE_PRESENTATION;
1372         }
1373         else if (xDocument->supportsService(u"com.sun.star.drawing.DrawingDocument"_ustr))
1374         {
1375             return LOK_DOCTYPE_DRAWING;
1376         }
1377         else if (xDocument->supportsService(u"com.sun.star.text.TextDocument"_ustr) || xDocument->supportsService(u"com.sun.star.text.WebDocument"_ustr))
1378         {
1379             return LOK_DOCTYPE_TEXT;
1380         }
1381         else
1382         {
1383             SetLastExceptionMsg(u"unknown document type"_ustr);
1384         }
1385     }
1386     catch (const uno::Exception& exception)
1387     {
1388         SetLastExceptionMsg("exception: " + exception.Message);
1389     }
1390     return LOK_DOCTYPE_OTHER;
1391 }
1392 
1393 } // anonymous namespace
1394 
1395 LibLODocument_Impl::LibLODocument_Impl(uno::Reference <css::lang::XComponent> xComponent, int nDocumentId)
1396     : mxComponent(std::move(xComponent))
1397     , mnDocumentId(nDocumentId)
1398 {
1399     assert(nDocumentId != -1 && "Cannot set mnDocumentId to -1");
1400 
1401     m_pDocumentClass = gDocumentClass.lock();
1402     if (!m_pDocumentClass)
1403     {
1404         m_pDocumentClass = std::make_shared<LibreOfficeKitDocumentClass>();
1405 
1406         m_pDocumentClass->nSize = sizeof(LibreOfficeKitDocumentClass);
1407 
1408         m_pDocumentClass->destroy = doc_destroy;
1409         m_pDocumentClass->saveAs = doc_saveAs;
1410         m_pDocumentClass->getDocumentType = doc_getDocumentType;
1411         m_pDocumentClass->getParts = doc_getParts;
1412         m_pDocumentClass->getPartPageRectangles = doc_getPartPageRectangles;
1413         m_pDocumentClass->getPart = doc_getPart;
1414         m_pDocumentClass->setPart = doc_setPart;
1415         m_pDocumentClass->selectPart = doc_selectPart;
1416         m_pDocumentClass->moveSelectedParts = doc_moveSelectedParts;
1417         m_pDocumentClass->getPartName = doc_getPartName;
1418         m_pDocumentClass->setPartMode = doc_setPartMode;
1419         m_pDocumentClass->getEditMode = doc_getEditMode;
1420         m_pDocumentClass->paintTile = doc_paintTile;
1421         m_pDocumentClass->paintPartTile = doc_paintPartTile;
1422         m_pDocumentClass->getTileMode = doc_getTileMode;
1423         m_pDocumentClass->getDocumentSize = doc_getDocumentSize;
1424         m_pDocumentClass->getDataArea = doc_getDataArea;
1425         m_pDocumentClass->initializeForRendering = doc_initializeForRendering;
1426         m_pDocumentClass->registerCallback = doc_registerCallback;
1427         m_pDocumentClass->postKeyEvent = doc_postKeyEvent;
1428         m_pDocumentClass->postWindowExtTextInputEvent = doc_postWindowExtTextInputEvent;
1429         m_pDocumentClass->hyperlinkInfoAtPosition = doc_hyperlinkInfoAtPosition;
1430         m_pDocumentClass->removeTextContext = doc_removeTextContext;
1431         m_pDocumentClass->postWindowKeyEvent = doc_postWindowKeyEvent;
1432         m_pDocumentClass->postMouseEvent = doc_postMouseEvent;
1433         m_pDocumentClass->postWindowMouseEvent = doc_postWindowMouseEvent;
1434         m_pDocumentClass->sendDialogEvent = doc_sendDialogEvent;
1435         m_pDocumentClass->postUnoCommand = doc_postUnoCommand;
1436         m_pDocumentClass->setTextSelection = doc_setTextSelection;
1437         m_pDocumentClass->setWindowTextSelection = doc_setWindowTextSelection;
1438         m_pDocumentClass->getTextSelection = doc_getTextSelection;
1439         m_pDocumentClass->getSelectionType = doc_getSelectionType;
1440         m_pDocumentClass->getSelectionTypeAndText = doc_getSelectionTypeAndText;
1441         m_pDocumentClass->getClipboard = doc_getClipboard;
1442         m_pDocumentClass->setClipboard = doc_setClipboard;
1443         m_pDocumentClass->paste = doc_paste;
1444         m_pDocumentClass->setGraphicSelection = doc_setGraphicSelection;
1445         m_pDocumentClass->resetSelection = doc_resetSelection;
1446         m_pDocumentClass->getCommandValues = doc_getCommandValues;
1447         m_pDocumentClass->setClientZoom = doc_setClientZoom;
1448         m_pDocumentClass->setClientVisibleArea = doc_setClientVisibleArea;
1449         m_pDocumentClass->setOutlineState = doc_setOutlineState;
1450 
1451         m_pDocumentClass->createView = doc_createView;
1452         m_pDocumentClass->destroyView = doc_destroyView;
1453         m_pDocumentClass->setView = doc_setView;
1454         m_pDocumentClass->getView = doc_getView;
1455         m_pDocumentClass->getViewsCount = doc_getViewsCount;
1456         m_pDocumentClass->getViewIds = doc_getViewIds;
1457 
1458         m_pDocumentClass->renderFont = doc_renderFont;
1459         m_pDocumentClass->renderFontOrientation = doc_renderFontOrientation;
1460         m_pDocumentClass->getPartHash = doc_getPartHash;
1461 
1462         m_pDocumentClass->paintWindow = doc_paintWindow;
1463         m_pDocumentClass->paintWindowDPI = doc_paintWindowDPI;
1464         m_pDocumentClass->paintWindowForView = doc_paintWindowForView;
1465         m_pDocumentClass->postWindow = doc_postWindow;
1466         m_pDocumentClass->resizeWindow = doc_resizeWindow;
1467 
1468         m_pDocumentClass->setViewLanguage = doc_setViewLanguage;
1469 
1470         m_pDocumentClass->getPartInfo = doc_getPartInfo;
1471 
1472         m_pDocumentClass->insertCertificate = doc_insertCertificate;
1473         m_pDocumentClass->addCertificate = doc_addCertificate;
1474         m_pDocumentClass->getSignatureState = doc_getSignatureState;
1475 
1476         m_pDocumentClass->renderShapeSelection = doc_renderShapeSelection;
1477         m_pDocumentClass->postWindowGestureEvent = doc_postWindowGestureEvent;
1478 
1479         m_pDocumentClass->createViewWithOptions = doc_createViewWithOptions;
1480         m_pDocumentClass->completeFunction = doc_completeFunction;
1481 
1482         m_pDocumentClass->sendFormFieldEvent = doc_sendFormFieldEvent;
1483         m_pDocumentClass->renderSearchResult = doc_renderSearchResult;
1484 
1485         m_pDocumentClass->setBlockedCommandList = doc_setBlockedCommandList;
1486 
1487         m_pDocumentClass->sendContentControlEvent = doc_sendContentControlEvent;
1488 
1489         m_pDocumentClass->setViewTimezone = doc_setViewTimezone;
1490 
1491         m_pDocumentClass->setAccessibilityState = doc_setAccessibilityState;
1492 
1493         m_pDocumentClass->getA11yFocusedParagraph = doc_getA11yFocusedParagraph;
1494         m_pDocumentClass->getA11yCaretPosition = doc_getA11yCaretPosition;
1495 
1496         gDocumentClass = m_pDocumentClass;
1497     }
1498     pClass = m_pDocumentClass.get();
1499 
1500 #ifndef IOS
1501     forceSetClipboardForCurrentView(this);
1502 #endif
1503 }
1504 
1505 LibLODocument_Impl::~LibLODocument_Impl()
1506 {
1507     try
1508     {
1509         mxComponent->dispose();
1510     }
1511     catch (const css::lang::DisposedException&)
1512     {
1513         TOOLS_WARN_EXCEPTION("lok", "failed to dispose document");
1514     }
1515 }
1516 
1517 static OUString getGenerator()
1518 {
1519     OUString sGenerator(
1520         Translate::ExpandVariables(u"%PRODUCTNAME %PRODUCTVERSION%PRODUCTEXTENSION (%1)"_ustr));
1521     OUString os(u"$_OS"_ustr);
1522     ::rtl::Bootstrap::expandMacros(os);
1523     return sGenerator.replaceFirst("%1", os);
1524 }
1525 
1526 extern "C" {
1527 
1528 CallbackFlushHandler::TimeoutIdle::TimeoutIdle( CallbackFlushHandler* handler )
1529     : Timer( "lokit timer callback" )
1530     , mHandler( handler )
1531 {
1532     // A second timer with higher priority, it'll ensure we flush in reasonable time if we get too busy
1533     // to get POST_PAINT priority processing. Otherwise it could take a long time to flush.
1534     SetPriority(TaskPriority::DEFAULT);
1535     SetTimeout( 100 ); // 100 ms
1536 }
1537 
1538 void CallbackFlushHandler::TimeoutIdle::Invoke()
1539 {
1540     mHandler->Invoke();
1541 }
1542 
1543 // One of these is created per view to handle events cf. doc_registerCallback
1544 CallbackFlushHandler::CallbackFlushHandler(LibreOfficeKitDocument* pDocument, LibreOfficeKitCallback pCallback, void* pData)
1545     : Idle( "lokit idle callback" ),
1546       m_pDocument(pDocument),
1547       m_pCallback(pCallback),
1548       m_pData(pData),
1549       m_nDisableCallbacks(0),
1550       m_TimeoutIdle( this )
1551 {
1552     SetPriority(TaskPriority::POST_PAINT);
1553 
1554     // Add the states that are safe to skip duplicates on, even when
1555     // not consequent (i.e. do no emit them if unchanged from last).
1556     m_states.emplace(LOK_CALLBACK_TEXT_SELECTION, "NIL"_ostr);
1557     m_states.emplace(LOK_CALLBACK_GRAPHIC_SELECTION, "NIL"_ostr);
1558     m_states.emplace(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, "NIL"_ostr);
1559     m_states.emplace(LOK_CALLBACK_STATE_CHANGED, "NIL"_ostr);
1560     m_states.emplace(LOK_CALLBACK_MOUSE_POINTER, "NIL"_ostr);
1561     m_states.emplace(LOK_CALLBACK_CELL_CURSOR, "NIL"_ostr);
1562     m_states.emplace(LOK_CALLBACK_CELL_FORMULA, "NIL"_ostr);
1563     m_states.emplace(LOK_CALLBACK_CELL_ADDRESS, "NIL"_ostr);
1564     m_states.emplace(LOK_CALLBACK_CURSOR_VISIBLE, "NIL"_ostr);
1565     m_states.emplace(LOK_CALLBACK_SET_PART, "NIL"_ostr);
1566     m_states.emplace(LOK_CALLBACK_TABLE_SELECTED, "NIL"_ostr);
1567     m_states.emplace(LOK_CALLBACK_TAB_STOP_LIST, "NIL"_ostr);
1568     m_states.emplace(LOK_CALLBACK_RULER_UPDATE, "NIL"_ostr);
1569     m_states.emplace(LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE, "NIL"_ostr);
1570 }
1571 
1572 CallbackFlushHandler::~CallbackFlushHandler()
1573 {
1574     Stop();
1575 }
1576 
1577 CallbackFlushHandler::queue_type2::iterator CallbackFlushHandler::toQueue2(CallbackFlushHandler::queue_type1::iterator pos)
1578 {
1579     int delta = std::distance(m_queue1.begin(), pos);
1580     return m_queue2.begin() + delta;
1581 }
1582 
1583 CallbackFlushHandler::queue_type2::reverse_iterator CallbackFlushHandler::toQueue2(CallbackFlushHandler::queue_type1::reverse_iterator pos)
1584 {
1585     int delta = std::distance(m_queue1.rbegin(), pos);
1586     return m_queue2.rbegin() + delta;
1587 }
1588 
1589 void CallbackFlushHandler::setUpdatedType( int nType, bool value )
1590 {
1591     assert(isUpdatedType(nType));
1592     if( m_updatedTypes.size() <= o3tl::make_unsigned( nType ))
1593         m_updatedTypes.resize( nType + 1 ); // new are default-constructed, i.e. false
1594     m_updatedTypes[ nType ] = value;
1595     if(value)
1596         startTimer();
1597 }
1598 
1599 void CallbackFlushHandler::resetUpdatedType( int nType )
1600 {
1601     setUpdatedType( nType, false );
1602 }
1603 
1604 void CallbackFlushHandler::setUpdatedTypePerViewId( int nType, int nViewId, int nSourceViewId, bool value )
1605 {
1606     assert(isUpdatedTypePerViewId(nType));
1607     std::vector<PerViewIdData>& types = m_updatedTypesPerViewId[ nViewId ];
1608     if( types.size() <= o3tl::make_unsigned( nType ))
1609         types.resize( nType + 1 ); // new are default-constructed, i.e. 'set' is false
1610     types[ nType ] = PerViewIdData{ value, nSourceViewId };
1611     if(value)
1612         startTimer();
1613 }
1614 
1615 void CallbackFlushHandler::resetUpdatedTypePerViewId( int nType, int nViewId )
1616 {
1617     assert(isUpdatedTypePerViewId(nType));
1618     bool allViewIds = false;
1619     // Handle specially messages that do not have viewId for backwards compatibility.
1620     if( nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR && !comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
1621         allViewIds = true;
1622     if( !allViewIds )
1623     {
1624         setUpdatedTypePerViewId( nType, nViewId, -1, false );
1625         return;
1626     }
1627     for( auto& it : m_updatedTypesPerViewId )
1628     {
1629         std::vector<PerViewIdData>& types = it.second;
1630         if( types.size() >= o3tl::make_unsigned( nType ))
1631             types[ nType ].set = false;
1632     }
1633 }
1634 
1635 void CallbackFlushHandler::libreOfficeKitViewCallback(int nType, const OString& pPayload)
1636 {
1637     CallbackData callbackData(pPayload);
1638     queue(nType, callbackData);
1639 }
1640 
1641 void CallbackFlushHandler::libreOfficeKitViewCallbackWithViewId(int nType, const OString& pPayload, int nViewId)
1642 {
1643     CallbackData callbackData(pPayload, nViewId);
1644     queue(nType, callbackData);
1645 }
1646 
1647 void CallbackFlushHandler::libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart, int nMode)
1648 {
1649     CallbackData callbackData(pRect, nPart, nMode);
1650     queue(LOK_CALLBACK_INVALIDATE_TILES, callbackData);
1651 }
1652 
1653 void CallbackFlushHandler::libreOfficeKitViewUpdatedCallback(int nType)
1654 {
1655     assert(isUpdatedType( nType ));
1656     std::unique_lock<std::recursive_mutex> lock(m_mutex);
1657     SAL_INFO("lok", "Updated: [" << nType << "]");
1658     setUpdatedType(nType, true);
1659 }
1660 
1661 void CallbackFlushHandler::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId)
1662 {
1663     assert(isUpdatedTypePerViewId( nType ));
1664     std::unique_lock<std::recursive_mutex> lock(m_mutex);
1665     SAL_INFO("lok", "Updated: [" << nType << "]");
1666     setUpdatedTypePerViewId(nType, nViewId, nSourceViewId, true);
1667 }
1668 
1669 void CallbackFlushHandler::dumpState(rtl::OStringBuffer &rState)
1670 {
1671     // NB. no locking
1672     rState.append("\nView:\t");
1673     rState.append(static_cast<sal_Int32>(m_viewId));
1674     rState.append("\n\tDisableCallbacks:\t");
1675     rState.append(static_cast<sal_Int32>(m_nDisableCallbacks));
1676     rState.append("\n\tStates:\n");
1677     for (const auto &i : m_states)
1678     {
1679         rState.append("\n\t\t");
1680         rState.append(static_cast<sal_Int32>(i.first));
1681         rState.append("\t");
1682         rState.append(i.second);
1683     }
1684 }
1685 
1686 void CallbackFlushHandler::libreOfficeKitViewAddPendingInvalidateTiles()
1687 {
1688     // Invoke() will call flushPendingLOKInvalidateTiles(), so just make sure the timer is active.
1689     startTimer();
1690 }
1691 
1692 void CallbackFlushHandler::queue(const int type, const OString& data)
1693 {
1694     CallbackData callbackData(data);
1695     queue(type, callbackData);
1696 }
1697 
1698 void CallbackFlushHandler::queue(const int type, CallbackData& aCallbackData)
1699 {
1700     comphelper::ProfileZone aZone("CallbackFlushHandler::queue");
1701 
1702     SAL_INFO("lok", "Queue: [" << type << "]: [" << aCallbackData.getPayload() << "] on " << m_queue1.size() << " entries.");
1703 
1704     bool bIsChartActive = false;
1705     bool bIsComment = false;
1706     if (type == LOK_CALLBACK_GRAPHIC_SELECTION)
1707     {
1708         LokChartHelper aChartHelper(SfxViewShell::Current());
1709         bIsChartActive = aChartHelper.GetWindow() != nullptr;
1710     }
1711     else if (type == LOK_CALLBACK_COMMENT)
1712     {
1713         bIsComment = true;
1714     }
1715 
1716     if (callbacksDisabled() && !bIsChartActive && !bIsComment)
1717     {
1718         // We drop notifications when this is set, except for important ones.
1719         // When we issue a complex command (such as .uno:InsertAnnotation)
1720         // there will be multiple notifications. On the first invalidation
1721         // we will start painting, but other events will get fired
1722         // while the complex command in question executes.
1723         // We don't want to suppress everything here on the wrong assumption
1724         // that no new events are fired during painting.
1725         if (type != LOK_CALLBACK_STATE_CHANGED &&
1726             type != LOK_CALLBACK_INVALIDATE_TILES &&
1727             type != LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR &&
1728             type != LOK_CALLBACK_CURSOR_VISIBLE &&
1729             type != LOK_CALLBACK_VIEW_CURSOR_VISIBLE &&
1730             type != LOK_CALLBACK_TEXT_SELECTION &&
1731             type != LOK_CALLBACK_TEXT_SELECTION_START &&
1732             type != LOK_CALLBACK_TEXT_SELECTION_END &&
1733             type != LOK_CALLBACK_MEDIA_SHAPE &&
1734             type != LOK_CALLBACK_REFERENCE_MARKS)
1735         {
1736             SAL_INFO("lok", "Skipping while painting [" << type << "]: [" << aCallbackData.getPayload() << "].");
1737             return;
1738         }
1739 
1740         // In Writer we drop all notifications during painting.
1741         if (doc_getDocumentType(m_pDocument) == LOK_DOCTYPE_TEXT)
1742             return;
1743     }
1744 
1745     // Suppress invalid payloads.
1746     if (type == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR &&
1747         aCallbackData.getPayload().indexOf(", 0, 0, ") != -1 &&
1748         aCallbackData.getPayload().indexOf("\"hyperlink\":\"\"") == -1 &&
1749         aCallbackData.getPayload().indexOf("\"hyperlink\": {}") == -1)
1750     {
1751         // The cursor position is often the relative coordinates of the widget
1752         // issuing it, instead of the absolute one that we expect.
1753         // This is temporary however, and, once the control is created and initialized
1754         // correctly, it eventually emits the correct absolute coordinates.
1755         SAL_INFO("lok", "Skipping invalid event [" << type << "]: [" << aCallbackData.getPayload() << "].");
1756         return;
1757     }
1758 
1759     std::unique_lock<std::recursive_mutex> lock(m_mutex);
1760 
1761     // Update types should be received via the updated callbacks for performance,
1762     // getting them as normal callbacks is technically not wrong, but probably should be avoided.
1763     // Reset the updated flag if we get a normal message.
1764     if(isUpdatedType(type))
1765     {
1766         SAL_INFO("lok", "Received event with updated type [" << type << "] as normal callback");
1767         resetUpdatedType(type);
1768     }
1769     if(isUpdatedTypePerViewId(type))
1770     {
1771         SAL_INFO("lok", "Received event with updated type [" << type << "] as normal callback");
1772         resetUpdatedTypePerViewId(type, aCallbackData.getViewId());
1773     }
1774 
1775     // drop duplicate callbacks for the listed types
1776     switch (type)
1777     {
1778         case LOK_CALLBACK_TEXT_SELECTION_START:
1779         case LOK_CALLBACK_TEXT_SELECTION_END:
1780         case LOK_CALLBACK_TEXT_SELECTION:
1781         case LOK_CALLBACK_GRAPHIC_SELECTION:
1782         case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
1783         case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
1784         case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
1785         case LOK_CALLBACK_STATE_CHANGED:
1786         case LOK_CALLBACK_MOUSE_POINTER:
1787         case LOK_CALLBACK_CELL_CURSOR:
1788         case LOK_CALLBACK_CELL_VIEW_CURSOR:
1789         case LOK_CALLBACK_CELL_FORMULA:
1790         case LOK_CALLBACK_CELL_ADDRESS:
1791         case LOK_CALLBACK_CELL_SELECTION_AREA:
1792         case LOK_CALLBACK_CURSOR_VISIBLE:
1793         case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
1794         case LOK_CALLBACK_SET_PART:
1795         case LOK_CALLBACK_TEXT_VIEW_SELECTION:
1796         case LOK_CALLBACK_INVALIDATE_HEADER:
1797         case LOK_CALLBACK_WINDOW:
1798         case LOK_CALLBACK_CALC_FUNCTION_LIST:
1799         case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY:
1800         case LOK_CALLBACK_REFERENCE_MARKS:
1801         case LOK_CALLBACK_CELL_AUTO_FILL_AREA:
1802         case LOK_CALLBACK_A11Y_FOCUS_CHANGED:
1803         case LOK_CALLBACK_A11Y_CARET_CHANGED:
1804         case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED:
1805         case LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED:
1806         case LOK_CALLBACK_COLOR_PALETTES:
1807         case LOK_CALLBACK_A11Y_EDITING_IN_SELECTION_STATE:
1808         case LOK_CALLBACK_A11Y_SELECTION_CHANGED:
1809         {
1810             const auto& pos = std::find(m_queue1.rbegin(), m_queue1.rend(), type);
1811             auto pos2 = toQueue2(pos);
1812             if (pos != m_queue1.rend() && pos2->getPayload() == aCallbackData.getPayload())
1813             {
1814                 SAL_INFO("lok", "Skipping queue duplicate [" << type << + "]: [" << aCallbackData.getPayload() << "].");
1815                 return;
1816             }
1817         }
1818         break;
1819     }
1820 
1821     if (type == LOK_CALLBACK_TEXT_SELECTION && aCallbackData.isEmpty())
1822     {
1823         const auto& posStart = std::find(m_queue1.rbegin(), m_queue1.rend(), LOK_CALLBACK_TEXT_SELECTION_START);
1824         auto posStart2 = toQueue2(posStart);
1825         if (posStart != m_queue1.rend())
1826             posStart2->clear();
1827 
1828         const auto& posEnd = std::find(m_queue1.rbegin(), m_queue1.rend(), LOK_CALLBACK_TEXT_SELECTION_END);
1829         auto posEnd2 = toQueue2(posEnd);
1830         if (posEnd != m_queue1.rend())
1831             posEnd2->clear();
1832     }
1833 
1834     // When payload is empty discards any previous state.
1835     if (aCallbackData.isEmpty())
1836     {
1837         switch (type)
1838         {
1839             case LOK_CALLBACK_TEXT_SELECTION_START:
1840             case LOK_CALLBACK_TEXT_SELECTION_END:
1841             case LOK_CALLBACK_TEXT_SELECTION:
1842             case LOK_CALLBACK_GRAPHIC_SELECTION:
1843             case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
1844             case LOK_CALLBACK_INVALIDATE_TILES:
1845             case LOK_CALLBACK_TOOLTIP:
1846                 if (removeAll(type))
1847                     SAL_INFO("lok", "Removed dups of [" << type << "]: [" << aCallbackData.getPayload() << "].");
1848                 break;
1849         }
1850     }
1851     else
1852     {
1853         switch (type)
1854         {
1855             // These are safe to use the latest state and ignore previous
1856             // ones (if any) since the last overrides previous ones.
1857             case LOK_CALLBACK_TEXT_SELECTION_START:
1858             case LOK_CALLBACK_TEXT_SELECTION_END:
1859             case LOK_CALLBACK_TEXT_SELECTION:
1860             case LOK_CALLBACK_MOUSE_POINTER:
1861             case LOK_CALLBACK_CELL_CURSOR:
1862             case LOK_CALLBACK_CELL_FORMULA:
1863             case LOK_CALLBACK_CELL_ADDRESS:
1864             case LOK_CALLBACK_CURSOR_VISIBLE:
1865             case LOK_CALLBACK_SET_PART:
1866             case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
1867             case LOK_CALLBACK_RULER_UPDATE:
1868             case LOK_CALLBACK_A11Y_FOCUS_CHANGED:
1869             case LOK_CALLBACK_A11Y_CARET_CHANGED:
1870             case LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED:
1871             case LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED:
1872             case LOK_CALLBACK_COLOR_PALETTES:
1873             case LOK_CALLBACK_TOOLTIP:
1874             {
1875                 if (removeAll(type))
1876                     SAL_INFO("lok", "Removed dups of [" << type << "]: [" << aCallbackData.getPayload() << "].");
1877             }
1878             break;
1879 
1880             // These are safe to use the latest state and ignore previous
1881             // ones (if any) since the last overrides previous ones,
1882             // but only if the view is the same.
1883             case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
1884                 // deleting the duplicate of visible cursor message can cause hyperlink popup not to show up on second/or more click on the same place.
1885                 // If the hyperlink is not empty we can bypass that to show the popup
1886                 if (aCallbackData.getPayload().indexOf("\"hyperlink\":\"\"") == -1
1887                     && aCallbackData.getPayload().indexOf("\"hyperlink\": {}") == -1)
1888                     break;
1889                 [[fallthrough]];
1890             case LOK_CALLBACK_CELL_VIEW_CURSOR:
1891             case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
1892             case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR:
1893             case LOK_CALLBACK_TEXT_VIEW_SELECTION:
1894             case LOK_CALLBACK_VIEW_CURSOR_VISIBLE:
1895             case LOK_CALLBACK_CALC_FUNCTION_LIST:
1896             case LOK_CALLBACK_FORM_FIELD_BUTTON:
1897             {
1898                 const int nViewId = aCallbackData.getViewId();
1899                 removeAll(type, [nViewId] (const CallbackData& elemData) {
1900                         return (nViewId == elemData.getViewId());
1901                     }
1902                 );
1903             }
1904             break;
1905 
1906             case LOK_CALLBACK_INVALIDATE_TILES:
1907                 if (processInvalidateTilesEvent(type, aCallbackData))
1908                     return;
1909             break;
1910 
1911             // State changes with same name override previous ones with a different value.
1912             // Ex. ".uno:PageStatus=Slide 20 of 83" overwrites any previous PageStatus.
1913             case LOK_CALLBACK_STATE_CHANGED:
1914             {
1915                 // Compare the state name=value and overwrite earlier entries with same name.
1916                 const auto pos = aCallbackData.getPayload().indexOf('=');
1917                 if (pos != -1)
1918                 {
1919                     const std::string_view name = aCallbackData.getPayload().subView(0, pos + 1);
1920                     // This is needed because otherwise it creates some problems when
1921                     // a save occurs while a cell is still edited in Calc.
1922                     if (name != ".uno:ModifiedStatus=")
1923                     {
1924                         removeAll(type, [&name] (const CallbackData& elemData) {
1925                                 return elemData.getPayload().startsWith(name);
1926                             }
1927                         );
1928                     }
1929                 }
1930             }
1931             break;
1932 
1933             case LOK_CALLBACK_WINDOW:
1934                 if (processWindowEvent(type, aCallbackData))
1935                     return;
1936             break;
1937 
1938             case LOK_CALLBACK_GRAPHIC_SELECTION:
1939             {
1940                 // remove only selection ranges and 'EMPTY' messages
1941                 // always send 'INPLACE' and 'INPLACE EXIT' messages
1942                 removeAll(type, [] (const CallbackData& elemData)
1943                     { return (elemData.getPayload().indexOf("INPLACE") == -1); });
1944             }
1945             break;
1946         }
1947     }
1948 
1949     // Validate that the cached data and the payload string are identical.
1950     assert(aCallbackData.validate() && "Cached callback payload object and string mismatch!");
1951     m_queue1.emplace_back(type);
1952     m_queue2.emplace_back(aCallbackData);
1953     SAL_INFO("lok", "Queued #" << (m_queue1.size() - 1) <<
1954              " [" << type << "]: [" << aCallbackData.getPayload() << "] to have " << m_queue1.size() << " entries.");
1955 
1956 #ifdef DBG_UTIL
1957     {
1958         // Dump the queue state and validate cached data.
1959         int i = 1;
1960         std::ostringstream oss;
1961         if (m_queue1.empty())
1962             oss << "Empty";
1963         else
1964             oss << m_queue1.size() << " items\n";
1965         auto it1 = m_queue1.begin();
1966         auto it2 = m_queue2.begin();
1967         for (; it1 != m_queue1.end(); ++it1, ++it2)
1968             oss << i++ << ": [" << *it1 << "] [" << it2->getPayload() << "].\n";
1969         SAL_INFO("lok", "Current Queue: " << oss.str());
1970         assert(
1971             std::all_of(
1972                 m_queue2.begin(), m_queue2.end(),
1973                 [](const CallbackData& c) { return c.validate(); }));
1974     }
1975 #endif
1976 
1977     lock.unlock();
1978     startTimer();
1979 }
1980 
1981 bool CallbackFlushHandler::processInvalidateTilesEvent(int type, CallbackData& aCallbackData)
1982 {
1983     RectangleAndPart rcNew = aCallbackData.getRectangleAndPart();
1984     if (rcNew.isEmpty())
1985     {
1986         SAL_INFO("lok", "Skipping invalid event [" << type << "]: [" << aCallbackData.getPayload() << "].");
1987         return true;
1988     }
1989 
1990     // If we have to invalidate all tiles, we can skip any new tile invalidation.
1991     // Find the last INVALIDATE_TILES entry, if any to see if it's invalidate-all.
1992     const auto& pos
1993         = std::find(m_queue1.rbegin(), m_queue1.rend(), LOK_CALLBACK_INVALIDATE_TILES);
1994     if (pos != m_queue1.rend())
1995     {
1996         auto pos2 = toQueue2(pos);
1997         const RectangleAndPart& rcOld = pos2->getRectangleAndPart();
1998         if (rcOld.isInfinite() && (rcOld.m_nPart == -1 || rcOld.m_nPart == rcNew.m_nPart) &&
1999             (rcOld.m_nMode == rcNew.m_nMode))
2000         {
2001             SAL_INFO("lok", "Skipping queue [" << type << "]: [" << aCallbackData.getPayload()
2002                                                << "] since all tiles need to be invalidated.");
2003             return true;
2004         }
2005 
2006         if ((rcOld.m_nPart == -1 || rcOld.m_nPart == rcNew.m_nPart) && (rcOld.m_nMode == rcNew.m_nMode))
2007         {
2008             // If fully overlapping.
2009             if (rcOld.m_aRectangle.Contains(rcNew.m_aRectangle))
2010             {
2011                 SAL_INFO("lok", "Skipping queue [" << type << "]: [" << aCallbackData.getPayload()
2012                                                    << "] since overlaps existing all-parts.");
2013                 return true;
2014             }
2015         }
2016     }
2017 
2018     if (rcNew.isInfinite())
2019     {
2020         SAL_INFO("lok", "Have Empty [" << type << "]: [" << aCallbackData.getPayload()
2021                                        << "] so removing all with part " << rcNew.m_nPart << ".");
2022         removeAll(LOK_CALLBACK_INVALIDATE_TILES, [&rcNew](const CallbackData& elemData) {
2023             // Remove exiting if new is all-encompassing, or if of the same part.
2024             return ((rcNew.m_nPart == -1 || rcNew.m_nPart == elemData.getRectangleAndPart().m_nPart)
2025                 && (rcNew.m_nMode == elemData.getRectangleAndPart().m_nMode));
2026         });
2027     }
2028     else
2029     {
2030         const auto rcOrig = rcNew;
2031 
2032         SAL_INFO("lok", "Have [" << type << "]: [" << aCallbackData.getPayload() << "] so merging overlapping.");
2033         removeAll(LOK_CALLBACK_INVALIDATE_TILES,[&rcNew](const CallbackData& elemData) {
2034             const RectangleAndPart& rcOld = elemData.getRectangleAndPart();
2035             if (rcNew.m_nPart != -1 && rcOld.m_nPart != -1 &&
2036                 (rcOld.m_nPart != rcNew.m_nPart || rcOld.m_nMode != rcNew.m_nMode))
2037             {
2038                 SAL_INFO("lok", "Nothing to merge between new: "
2039                                     << rcNew.toString() << ", and old: " << rcOld.toString());
2040                 return false;
2041             }
2042 
2043             if (rcNew.m_nPart == -1)
2044             {
2045                 // Don't merge unless fully overlapped.
2046                 SAL_INFO("lok", "New " << rcNew.toString() << " has " << rcOld.toString()
2047                                        << "?");
2048                 if (rcNew.m_aRectangle.Contains(rcOld.m_aRectangle) && rcOld.m_nMode == rcNew.m_nMode)
2049                 {
2050                     SAL_INFO("lok", "New " << rcNew.toString() << " engulfs old "
2051                                            << rcOld.toString() << ".");
2052                     return true;
2053                 }
2054             }
2055             else if (rcOld.m_nPart == -1)
2056             {
2057                 // Don't merge unless fully overlapped.
2058                 SAL_INFO("lok", "Old " << rcOld.toString() << " has " << rcNew.toString()
2059                                        << "?");
2060                 if (rcOld.m_aRectangle.Contains(rcNew.m_aRectangle) && rcOld.m_nMode == rcNew.m_nMode)
2061                 {
2062                     SAL_INFO("lok", "New " << rcNew.toString() << " engulfs old "
2063                                            << rcOld.toString() << ".");
2064                     return true;
2065                 }
2066             }
2067             else
2068             {
2069                 const tools::Rectangle rcOverlap
2070                     = rcNew.m_aRectangle.GetIntersection(rcOld.m_aRectangle);
2071                 const bool bOverlap = !rcOverlap.IsEmpty() && rcOld.m_nMode == rcNew.m_nMode;
2072                 SAL_INFO("lok", "Merging " << rcNew.toString() << " & " << rcOld.toString()
2073                                            << " => " << rcOverlap.toString()
2074                                            << " Overlap: " << bOverlap);
2075                 if (bOverlap)
2076                 {
2077                     rcNew.m_aRectangle.Union(rcOld.m_aRectangle);
2078                     SAL_INFO("lok", "Merged: " << rcNew.toString());
2079                     return true;
2080                 }
2081             }
2082 
2083             // Keep others.
2084             return false;
2085         });
2086 
2087         if (rcNew.m_aRectangle != rcOrig.m_aRectangle)
2088         {
2089             SAL_INFO("lok", "Replacing: " << rcOrig.toString() << " by " << rcNew.toString());
2090             if (rcNew.m_aRectangle.GetWidth() < rcOrig.m_aRectangle.GetWidth()
2091                 || rcNew.m_aRectangle.GetHeight() < rcOrig.m_aRectangle.GetHeight())
2092             {
2093                 SAL_WARN("lok", "Error: merged rect smaller.");
2094             }
2095         }
2096     }
2097 
2098     aCallbackData.updateRectangleAndPart(rcNew);
2099     // Queue this one.
2100     return false;
2101 }
2102 
2103 bool CallbackFlushHandler::processWindowEvent(int type, CallbackData& aCallbackData)
2104 {
2105     const OString& payload = aCallbackData.getPayload();
2106 
2107     boost::property_tree::ptree& aTree = aCallbackData.setJson(std::string(payload));
2108     const unsigned nLOKWindowId = aTree.get<unsigned>("id", 0);
2109     const std::string aAction = aTree.get<std::string>("action", "");
2110     if (aAction == "invalidate")
2111     {
2112         std::string aRectStr = aTree.get<std::string>("rectangle", "");
2113         // no 'rectangle' field => invalidate all of the window =>
2114         // remove all previous window part invalidations
2115         if (aRectStr.empty())
2116         {
2117             removeAll(LOK_CALLBACK_WINDOW,[&nLOKWindowId](const CallbackData& elemData) {
2118                 const boost::property_tree::ptree& aOldTree = elemData.getJson();
2119                 if (nLOKWindowId == aOldTree.get<unsigned>("id", 0)
2120                     && aOldTree.get<std::string>("action", "") == "invalidate")
2121                 {
2122                     return true;
2123                 }
2124                 return false;
2125             });
2126         }
2127         else
2128         {
2129             // if we have to invalidate all of the window, ignore
2130             // any part invalidation message
2131             bool invAllExist = false;
2132             auto it1 = m_queue1.rbegin();
2133             auto it2 = m_queue2.rbegin();
2134             for (;it1 != m_queue1.rend(); ++it1, ++it2)
2135             {
2136                 if (*it1 != LOK_CALLBACK_WINDOW)
2137                     continue;
2138                 const boost::property_tree::ptree& aOldTree = it2->getJson();
2139                 if (nLOKWindowId == aOldTree.get<unsigned>("id", 0)
2140                      && aOldTree.get<std::string>("action", "") == "invalidate"
2141                      && aOldTree.get<std::string>("rectangle", "").empty())
2142                 {
2143                     invAllExist = true;
2144                     break;
2145                 }
2146             }
2147 
2148             // we found a invalidate-all window callback
2149             if (invAllExist)
2150             {
2151                 SAL_INFO("lok.dialog", "Skipping queue ["
2152                                            << type << "]: [" << payload
2153                                            << "] since whole window needs to be invalidated.");
2154                 return true;
2155             }
2156 
2157             std::istringstream aRectStream(aRectStr);
2158             tools::Long nLeft, nTop, nWidth, nHeight;
2159             char nComma;
2160             aRectStream >> nLeft >> nComma >> nTop >> nComma >> nWidth >> nComma >> nHeight;
2161             tools::Rectangle aNewRect(nLeft, nTop, nLeft + nWidth, nTop + nHeight);
2162             bool currentIsRedundant = false;
2163             removeAll(LOK_CALLBACK_WINDOW, [&aNewRect, &nLOKWindowId,
2164                        &currentIsRedundant](const CallbackData& elemData) {
2165                 const boost::property_tree::ptree& aOldTree = elemData.getJson();
2166                 if (aOldTree.get<std::string>("action", "") == "invalidate")
2167                 {
2168                     // Not possible that we encounter an empty rectangle here; we already handled this case above.
2169                     std::istringstream aOldRectStream(aOldTree.get<std::string>("rectangle", ""));
2170                     tools::Long nOldLeft, nOldTop, nOldWidth, nOldHeight;
2171                     char nOldComma;
2172                     aOldRectStream >> nOldLeft >> nOldComma >> nOldTop >> nOldComma >> nOldWidth
2173                         >> nOldComma >> nOldHeight;
2174                     const tools::Rectangle aOldRect = tools::Rectangle(
2175                         nOldLeft, nOldTop, nOldLeft + nOldWidth, nOldTop + nOldHeight);
2176 
2177                     if (nLOKWindowId == aOldTree.get<unsigned>("id", 0))
2178                     {
2179                         if (aNewRect == aOldRect)
2180                         {
2181                             SAL_INFO("lok.dialog", "Duplicate rect [" << aNewRect.toString()
2182                                                                       << "]. Skipping new.");
2183                             // We have a rectangle in the queue already that makes the current Callback useless.
2184                             currentIsRedundant = true;
2185                             return false;
2186                         }
2187                         // new one engulfs the old one?
2188                         else if (aNewRect.Contains(aOldRect))
2189                         {
2190                             SAL_INFO("lok.dialog",
2191                                      "New rect [" << aNewRect.toString() << "] engulfs old ["
2192                                                   << aOldRect.toString() << "]. Replacing old.");
2193                             return true;
2194                         }
2195                         // old one engulfs the new one?
2196                         else if (aOldRect.Contains(aNewRect))
2197                         {
2198                             SAL_INFO("lok.dialog",
2199                                      "Old rect [" << aOldRect.toString() << "] engulfs new ["
2200                                                   << aNewRect.toString() << "]. Skipping new.");
2201                             // We have a rectangle in the queue already that makes the current Callback useless.
2202                             currentIsRedundant = true;
2203                             return false;
2204                         }
2205                         else
2206                         {
2207                             // Overlapping rects.
2208                             const tools::Rectangle aPreMergeRect = aNewRect;
2209                             aNewRect.Union(aOldRect);
2210                             SAL_INFO("lok.dialog", "Merging rects ["
2211                                                        << aPreMergeRect.toString() << "] & ["
2212                                                        << aOldRect.toString() << "] = ["
2213                                                        << aNewRect.toString()
2214                                                        << "]. Replacing old.");
2215                             return true;
2216                         }
2217                     }
2218                 }
2219 
2220                 // keep rest
2221                 return false;
2222             });
2223 
2224             // Do not enqueue if redundant.
2225             if (currentIsRedundant)
2226                 return true;
2227 
2228             aTree.put("rectangle", aNewRect.toString().getStr());
2229             aCallbackData.setJson(aTree);
2230             assert(aCallbackData.validate() && "Validation after setJson failed!");
2231         }
2232     }
2233     else if (aAction == "created")
2234     {
2235         // Remove all previous actions on same dialog, if we are creating it anew.
2236         removeAll(LOK_CALLBACK_WINDOW,[&nLOKWindowId](const CallbackData& elemData) {
2237             const boost::property_tree::ptree& aOldTree = elemData.getJson();
2238             if (nLOKWindowId == aOldTree.get<unsigned>("id", 0))
2239                 return true;
2240             return false;
2241         });
2242 
2243         VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
2244         if (!pWindow)
2245         {
2246             SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
2247             return false;
2248         }
2249 
2250 #ifndef IOS
2251         auto xClip = forceSetClipboardForCurrentView(m_pDocument);
2252 
2253         uno::Reference<datatransfer::clipboard::XClipboard> xClipboard(xClip);
2254         pWindow->SetClipboard(xClipboard);
2255 #endif
2256     }
2257     else if (aAction == "size_changed")
2258     {
2259         // A size change is practically re-creation of the window.
2260         // But at a minimum it's a full invalidation.
2261         removeAll(LOK_CALLBACK_WINDOW, [&nLOKWindowId](const CallbackData& elemData) {
2262             const boost::property_tree::ptree& aOldTree = elemData.getJson();
2263             if (nLOKWindowId == aOldTree.get<unsigned>("id", 0))
2264             {
2265                 const std::string aOldAction = aOldTree.get<std::string>("action", "");
2266                 if (aOldAction == "invalidate")
2267                     return true;
2268             }
2269             return false;
2270         });
2271     }
2272 
2273     // Queue this one.
2274     return false;
2275 }
2276 
2277 void CallbackFlushHandler::enqueueUpdatedTypes()
2278 {
2279     if( m_updatedTypes.empty() && m_updatedTypesPerViewId.empty())
2280         return;
2281     assert(m_viewId >= 0);
2282     SfxViewShell* viewShell = SfxViewShell::GetFirst( false,
2283         [this](const SfxViewShell& shell) { return shell.GetViewShellId().get() == m_viewId; } );
2284     assert(viewShell != nullptr);
2285 
2286     // First move data to local structures, so that callbacks don't possibly modify it.
2287     std::vector<bool> updatedTypes;
2288     std::swap(updatedTypes, m_updatedTypes);
2289     boost::container::flat_map<int, std::vector<PerViewIdData>> updatedTypesPerViewId;
2290     std::swap(updatedTypesPerViewId, m_updatedTypesPerViewId);
2291 
2292     // Some types must always precede other types, for example
2293     // LOK_CALLBACK_TEXT_SELECTION_START and LOK_CALLBACK_TEXT_SELECTION_END
2294     // must always precede LOK_CALLBACK_TEXT_SELECTION if present.
2295     // Only these types should be present (see isUpdatedType()) and should be processed in this order.
2296     static const int orderedUpdatedTypes[] = {
2297         LOK_CALLBACK_TEXT_SELECTION_START, LOK_CALLBACK_TEXT_SELECTION_END, LOK_CALLBACK_TEXT_SELECTION };
2298     // Only these types should be present (see isUpdatedTypePerViewId()) and (as of now)
2299     // the order doesn't matter.
2300     static const int orderedUpdatedTypesPerViewId[] = {
2301         LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR,
2302         LOK_CALLBACK_INVALIDATE_VIEW_CURSOR,
2303         LOK_CALLBACK_TEXT_VIEW_SELECTION };
2304 
2305     for( int type : orderedUpdatedTypes )
2306     {
2307         if(o3tl::make_unsigned( type ) < updatedTypes.size() && updatedTypes[ type ])
2308         {
2309             enqueueUpdatedType( type, viewShell, m_viewId );
2310         }
2311     }
2312     for( const auto& it : updatedTypesPerViewId )
2313     {
2314         int viewId = it.first;
2315         const std::vector<PerViewIdData>& types = it.second;
2316         for( int type : orderedUpdatedTypesPerViewId )
2317         {
2318             if(o3tl::make_unsigned( type ) < types.size() && types[ type ].set)
2319             {
2320                 SfxViewShell* sourceViewShell = viewShell;
2321                 const int sourceViewId = types[ type ].sourceViewId;
2322                 if( sourceViewId != m_viewId )
2323                 {
2324                     assert(sourceViewId >= 0);
2325                     sourceViewShell = SfxViewShell::GetFirst( false,
2326                     [sourceViewId](const SfxViewShell& shell) { return shell.GetViewShellId().get() == sourceViewId; } );
2327                 }
2328                 if(sourceViewShell == nullptr)
2329                 {
2330                     SAL_INFO("lok", "View #" << sourceViewId << " no longer found for updated event [" << type << "]");
2331                     continue; // View removed, probably cleaning up.
2332                 }
2333                 enqueueUpdatedType( type, sourceViewShell, viewId );
2334             }
2335         }
2336     }
2337 }
2338 
2339 void CallbackFlushHandler::enqueueUpdatedType( int type, const SfxViewShell* viewShell, int viewId )
2340 {
2341     if (type == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR)
2342     {
2343         if (const SfxViewShell* viewShell2 = LokStarMathHelper(viewShell).GetSmViewShell())
2344             viewShell = viewShell2;
2345     }
2346     std::optional<OString> payload = viewShell->getLOKPayload( type, viewId );
2347     if(!payload)
2348         return; // No actual payload to send.
2349     CallbackData callbackData(*payload, viewId);
2350     m_queue1.emplace_back(type);
2351     m_queue2.emplace_back(callbackData);
2352     SAL_INFO("lok", "Queued updated [" << type << "]: [" << callbackData.getPayload()
2353         << "] to have " << m_queue1.size() << " entries.");
2354 }
2355 
2356 void CallbackFlushHandler::Invoke()
2357 {
2358     comphelper::ProfileZone aZone("CallbackFlushHandler::Invoke");
2359 
2360     if (!m_pCallback)
2361         return;
2362 
2363     // Get any pending invalidate tile events. This will call our callbacks,
2364     // so it must be done before taking the mutex.
2365     assert(m_viewId >= 0);
2366     if(SfxViewShell* viewShell = SfxViewShell::GetFirst( false,
2367         [this](const SfxViewShell& shell) { return shell.GetViewShellId().get() == m_viewId; } ))
2368     {
2369         viewShell->flushPendingLOKInvalidateTiles();
2370     }
2371 
2372     std::unique_lock<std::recursive_mutex> lock(m_mutex);
2373 
2374     // Append messages for updated types, fetch them only now.
2375     enqueueUpdatedTypes();
2376 
2377     SAL_INFO("lok", "Flushing " << m_queue1.size() << " elements.");
2378     auto it1 = m_queue1.begin();
2379     auto it2 = m_queue2.begin();
2380     for (; it1 != m_queue1.end(); ++it1, ++it2)
2381     {
2382         const int type = *it1;
2383         const auto& payload = it2->getPayload();
2384         const int viewId = lcl_isViewCallbackType(type) ? it2->getViewId() : -1;
2385 
2386         SAL_INFO("lok", "processing event: [" << type << ',' << viewId << "]: [" << payload << "].");
2387 
2388         // common code-path for events on this view:
2389         if (viewId == -1)
2390         {
2391             sal_Int32 idx;
2392             // key-value pairs
2393             if (type == LOK_CALLBACK_STATE_CHANGED &&
2394                 (idx = payload.indexOf('=')) != -1)
2395             {
2396                 OString key = payload.copy(0, idx);
2397                 OString value = payload.copy(idx+1);
2398                 const auto stateIt = m_lastStateChange.find(key);
2399                 if (stateIt != m_lastStateChange.end())
2400                 {
2401                     // If the value didn't change, it's safe to ignore.
2402                     if (stateIt->second == value)
2403                     {
2404                         SAL_INFO("lok", "Skipping new state duplicate: [" << type << "]: [" << payload << "].");
2405                         continue;
2406                     }
2407                     SAL_INFO("lok", "Replacing a state element [" << type << "]: [" << payload << "].");
2408                     stateIt->second = value;
2409                 }
2410                 else
2411                 {
2412                     SAL_INFO("lok", "Inserted a new state element: [" << type << "]: [" << payload << "]");
2413                     m_lastStateChange.emplace(key, value);
2414                 }
2415             }
2416             else
2417             {
2418                 const auto stateIt = m_states.find(type);
2419                 if (stateIt != m_states.end())
2420                 {
2421                     // If the state didn't change, it's safe to ignore.
2422                     if (stateIt->second == payload)
2423                     {
2424                         SAL_INFO("lok", "Skipping duplicate [" << type << "]: [" << payload << "].");
2425                         continue;
2426                     }
2427                     stateIt->second = payload;
2428                 }
2429             }
2430         }
2431         else // less common path for events relating to other views
2432         {
2433             const auto statesIt = m_viewStates.find(viewId);
2434             if (statesIt != m_viewStates.end())
2435             {
2436                 auto& states = statesIt->second;
2437                 const auto stateIt = states.find(type);
2438                 if (stateIt != states.end())
2439                 {
2440                     // If the state didn't change, it's safe to ignore.
2441                     if (stateIt->second == payload)
2442                     {
2443                         SAL_INFO("lok", "Skipping view duplicate [" << type << ',' << viewId << "]: [" << payload << "].");
2444                         continue;
2445                     }
2446 
2447                     SAL_INFO("lok", "Replacing an element in view states [" << type << ',' << viewId << "]: [" << payload << "].");
2448                     stateIt->second = payload;
2449                 }
2450                 else
2451                 {
2452                     SAL_INFO("lok", "Inserted a new element in view states: [" << type << ',' << viewId << "]: [" << payload << "]");
2453                     states.emplace(type, payload);
2454 
2455                 }
2456             }
2457         }
2458 
2459         m_pCallback(type, payload.getStr(), m_pData);
2460     }
2461 
2462     m_queue1.clear();
2463     m_queue2.clear();
2464     Stop();
2465     m_TimeoutIdle.Stop();
2466 }
2467 
2468 void CallbackFlushHandler::startTimer()
2469 {
2470     if (!IsActive())
2471         Start();
2472     if (!m_TimeoutIdle.IsActive())
2473         m_TimeoutIdle.Start();
2474 }
2475 
2476 bool CallbackFlushHandler::removeAll(int type)
2477 {
2478     bool bErased = false;
2479     auto it1 = m_queue1.begin();
2480     for(;;)
2481     {
2482         it1 = std::find(it1, m_queue1.end(), type);
2483         if(it1 == m_queue1.end())
2484             break;
2485         m_queue2.erase(toQueue2(it1));
2486         it1 = m_queue1.erase(it1);
2487         bErased = true;
2488     }
2489     return bErased;
2490 }
2491 
2492 bool CallbackFlushHandler::removeAll(int type, const std::function<bool (const CallbackData&)>& rTestFunc)
2493 {
2494     bool bErased = false;
2495     auto it1 = m_queue1.begin();
2496     for(;;)
2497     {
2498         it1 = std::find(it1, m_queue1.end(), type);
2499         if(it1 == m_queue1.end())
2500             break;
2501         auto it2 = toQueue2(it1);
2502         if (rTestFunc(*it2))
2503         {
2504             m_queue2.erase(it2);
2505             it1 = m_queue1.erase(it1);
2506             bErased = true;
2507         }
2508         else
2509             ++it1;
2510     }
2511     return bErased;
2512 }
2513 
2514 void CallbackFlushHandler::addViewStates(int viewId)
2515 {
2516     const auto& result = m_viewStates.emplace(viewId, decltype(m_viewStates)::mapped_type());
2517     if (!result.second && result.first != m_viewStates.end())
2518     {
2519         result.first->second.clear();
2520     }
2521 }
2522 
2523 void CallbackFlushHandler::removeViewStates(int viewId)
2524 {
2525     m_viewStates.erase(viewId);
2526 }
2527 
2528 
2529 static void doc_destroy(LibreOfficeKitDocument *pThis)
2530 {
2531     comphelper::ProfileZone aZone("doc_destroy");
2532 
2533     SolarMutexGuard aGuard;
2534 
2535 #ifndef IOS
2536     LOKClipboardFactory::releaseClipboardForView(-1);
2537 #endif
2538 
2539     LibLODocument_Impl *pDocument = static_cast<LibLODocument_Impl*>(pThis);
2540     delete pDocument;
2541 }
2542 
2543 static void                    lo_destroy       (LibreOfficeKit* pThis);
2544 static int                     lo_initialize    (LibreOfficeKit* pThis, const char* pInstallPath, const char* pUserProfilePath);
2545 static LibreOfficeKitDocument* lo_documentLoad  (LibreOfficeKit* pThis, const char* pURL);
2546 static char *                  lo_getError      (LibreOfficeKit* pThis);
2547 static void                    lo_freeError     (char* pFree);
2548 static LibreOfficeKitDocument* lo_documentLoadWithOptions  (LibreOfficeKit* pThis,
2549                                                            const char* pURL,
2550                                                            const char* pOptions);
2551 static void                    lo_registerCallback (LibreOfficeKit* pThis,
2552                                                     LibreOfficeKitCallback pCallback,
2553                                                     void* pData);
2554 static char* lo_getFilterTypes(LibreOfficeKit* pThis);
2555 static void                    lo_setOptionalFeatures(LibreOfficeKit* pThis, unsigned long long features);
2556 static void                    lo_setDocumentPassword(LibreOfficeKit* pThis,
2557                                                        const char* pURL,
2558                                                        const char* pPassword);
2559 static char*                   lo_getVersionInfo(LibreOfficeKit* pThis);
2560 static int                     lo_runMacro      (LibreOfficeKit* pThis, const char* pURL);
2561 
2562 static bool lo_signDocument(LibreOfficeKit* pThis,
2563                                    const char* pUrl,
2564                                    const unsigned char* pCertificateBinary,
2565                                    const int nCertificateBinarySize,
2566                                    const unsigned char* pPrivateKeyBinary,
2567                                    const int nPrivateKeyBinarySize);
2568 
2569 static char* lo_extractRequest(LibreOfficeKit* pThis,
2570                                    const char* pFilePath);
2571 
2572 static void lo_trimMemory(LibreOfficeKit* pThis, int nTarget);
2573 
2574 static void*
2575 lo_startURP(LibreOfficeKit* pThis, void* pReceiveURPFromLOContext, void* pSendURPToLOContext,
2576             int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen),
2577             int (*fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen));
2578 
2579 static void lo_stopURP(LibreOfficeKit* pThis, void* pSendURPToLOContext);
2580 
2581 static int lo_joinThreads(LibreOfficeKit* pThis);
2582 
2583 static void lo_runLoop(LibreOfficeKit* pThis,
2584                        LibreOfficeKitPollCallback pPollCallback,
2585                        LibreOfficeKitWakeCallback pWakeCallback,
2586                        void* pData);
2587 
2588 static void lo_sendDialogEvent(LibreOfficeKit* pThis,
2589                                unsigned long long int nLOKWindowId,
2590                                const char* pArguments);
2591 
2592 static void lo_setOption(LibreOfficeKit* pThis, const char* pOption, const char* pValue);
2593 
2594 static void lo_dumpState(LibreOfficeKit* pThis, const char* pOptions, char** pState);
2595 
2596 LibLibreOffice_Impl::LibLibreOffice_Impl()
2597     : m_pOfficeClass( gOfficeClass.lock() )
2598     , maThread(nullptr)
2599     , mpCallback(nullptr)
2600     , mpCallbackData(nullptr)
2601     , mOptionalFeatures(0)
2602 {
2603     if(!m_pOfficeClass) {
2604         m_pOfficeClass = std::make_shared<LibreOfficeKitClass>();
2605         m_pOfficeClass->nSize = sizeof(LibreOfficeKitClass);
2606 
2607         m_pOfficeClass->destroy = lo_destroy;
2608         m_pOfficeClass->documentLoad = lo_documentLoad;
2609         m_pOfficeClass->getError = lo_getError;
2610         m_pOfficeClass->freeError = lo_freeError;
2611         m_pOfficeClass->documentLoadWithOptions = lo_documentLoadWithOptions;
2612         m_pOfficeClass->registerCallback = lo_registerCallback;
2613         m_pOfficeClass->getFilterTypes = lo_getFilterTypes;
2614         m_pOfficeClass->setOptionalFeatures = lo_setOptionalFeatures;
2615         m_pOfficeClass->setDocumentPassword = lo_setDocumentPassword;
2616         m_pOfficeClass->getVersionInfo = lo_getVersionInfo;
2617         m_pOfficeClass->runMacro = lo_runMacro;
2618         m_pOfficeClass->signDocument = lo_signDocument;
2619         m_pOfficeClass->runLoop = lo_runLoop;
2620         m_pOfficeClass->sendDialogEvent = lo_sendDialogEvent;
2621         m_pOfficeClass->setOption = lo_setOption;
2622         m_pOfficeClass->dumpState = lo_dumpState;
2623         m_pOfficeClass->extractRequest = lo_extractRequest;
2624         m_pOfficeClass->trimMemory = lo_trimMemory;
2625         m_pOfficeClass->startURP = lo_startURP;
2626         m_pOfficeClass->stopURP = lo_stopURP;
2627         m_pOfficeClass->joinThreads = lo_joinThreads;
2628 
2629         gOfficeClass = m_pOfficeClass;
2630     }
2631 
2632     pClass = m_pOfficeClass.get();
2633 }
2634 
2635 LibLibreOffice_Impl::~LibLibreOffice_Impl()
2636 {
2637 }
2638 
2639 namespace
2640 {
2641 
2642 void setLanguageAndLocale(OUString const & aLangISO)
2643 {
2644     SvtSysLocaleOptions aLocalOptions;
2645     aLocalOptions.SetLocaleConfigString(aLangISO);
2646     aLocalOptions.SetUILocaleConfigString(aLangISO);
2647     aLocalOptions.Commit();
2648 }
2649 
2650 void setFormatSpecificFilterData(std::u16string_view sFormat, comphelper::SequenceAsHashMap & rFilterDataMap)
2651 {
2652     if (sFormat == u"pdf")
2653     {
2654         // always export bookmarks, which is needed for annotations
2655         rFilterDataMap[u"ExportBookmarks"_ustr] <<= true;
2656     }
2657 }
2658 
2659 } // anonymous namespace
2660 
2661 // Wonder global state ...
2662 static uno::Reference<css::uno::XComponentContext> xContext;
2663 static uno::Reference<css::lang::XMultiServiceFactory> xSFactory;
2664 static uno::Reference<css::lang::XMultiComponentFactory> xFactory;
2665 
2666 static LibreOfficeKitDocument* lo_documentLoad(LibreOfficeKit* pThis, const char* pURL)
2667 {
2668     return lo_documentLoadWithOptions(pThis, pURL, nullptr);
2669 }
2670 
2671 static LibreOfficeKitDocument* lo_documentLoadWithOptions(LibreOfficeKit* pThis, const char* pURL, const char* pOptions)
2672 {
2673     comphelper::ProfileZone aZone("lo_documentLoadWithOptions");
2674 
2675     SolarMutexGuard aGuard;
2676 
2677     static int nDocumentIdCounter = 0;
2678 
2679     LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
2680     pLib->maLastExceptionMsg.clear();
2681 
2682     const OUString aURL(getAbsoluteURL(pURL));
2683     if (aURL.isEmpty())
2684     {
2685         pLib->maLastExceptionMsg = u"Filename to load was not provided."_ustr;
2686         SAL_INFO("lok", "URL for load is empty");
2687         return nullptr;
2688     }
2689 
2690     pLib->maLastExceptionMsg.clear();
2691 
2692     if (!xContext.is())
2693     {
2694         pLib->maLastExceptionMsg = u"ComponentContext is not available"_ustr;
2695         SAL_INFO("lok", "ComponentContext is not available");
2696         return nullptr;
2697     }
2698 
2699     uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext);
2700 
2701     if (!xComponentLoader.is())
2702     {
2703         pLib->maLastExceptionMsg = u"ComponentLoader is not available"_ustr;
2704         SAL_INFO("lok", "ComponentLoader is not available");
2705         return nullptr;
2706     }
2707 
2708     try
2709     {
2710         // 'Language=...' is an option that LOK consumes by itself, and does
2711         // not pass it as a parameter to the filter
2712         OUString aOptions = getUString(pOptions);
2713         const OUString aLanguage = extractParameter(aOptions, u"Language");
2714 
2715         if (!aLanguage.isEmpty() && LanguageTag::isValidBcp47(aLanguage, nullptr))
2716         {
2717             static bool isLoading = true;
2718             if (isLoading)
2719             {
2720                 // Capture the language used to load the document.
2721                 SfxLokHelper::setLoadLanguage(aLanguage);
2722                 isLoading = false;
2723             }
2724 
2725             SfxLokHelper::setDefaultLanguage(aLanguage);
2726             // Set the LOK language tag, used for dialog tunneling.
2727             comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLanguage));
2728             comphelper::LibreOfficeKit::setLocale(LanguageTag(aLanguage));
2729 
2730             SAL_INFO("lok", "Set document language to " << aLanguage);
2731             // use with care - it sets it for the entire core, not just the
2732             // document
2733             setLanguageAndLocale(aLanguage);
2734             // Need to reset the static initialized values
2735             SvNumberFormatter::resetTheCurrencyTable();
2736         }
2737 
2738         // Set the timezone, if not empty.
2739         const OUString aTimezone = extractParameter(aOptions, u"Timezone");
2740         if (!aTimezone.isEmpty())
2741         {
2742             SfxLokHelper::setDefaultTimezone(true, aTimezone);
2743         }
2744         else
2745         {
2746             // Default to the TZ envar, if set.
2747             const char* tz = ::getenv("TZ");
2748             if (tz)
2749             {
2750                 SfxLokHelper::setDefaultTimezone(true,
2751                                                  OStringToOUString(tz, RTL_TEXTENCODING_UTF8));
2752             }
2753             else
2754             {
2755                 SfxLokHelper::setDefaultTimezone(false, OUString());
2756             }
2757         }
2758 
2759         const OUString aDeviceFormFactor = extractParameter(aOptions, u"DeviceFormFactor");
2760         SfxLokHelper::setDeviceFormFactor(aDeviceFormFactor);
2761 
2762         const OUString aBatch = extractParameter(aOptions, u"Batch");
2763         if (!aBatch.isEmpty())
2764         {
2765              Application::SetDialogCancelMode(DialogCancelMode::LOKSilent);
2766         }
2767 
2768         rtl::Reference<LOKInteractionHandler> const pInteraction(
2769             new LOKInteractionHandler("load"_ostr, pLib));
2770         auto const pair(pLib->mInteractionMap.insert(std::make_pair(aURL.toUtf8(), pInteraction)));
2771         comphelper::ScopeGuard const g([&] () {
2772                 if (pair.second)
2773                 {
2774                     pLib->mInteractionMap.erase(aURL.toUtf8());
2775                 }
2776             });
2777         uno::Reference<task::XInteractionHandler2> const xInteraction(pInteraction);
2778 
2779         int nMacroSecurityLevel = 1;
2780         const OUString aMacroSecurityLevel = extractParameter(aOptions, u"MacroSecurityLevel");
2781         if (!aMacroSecurityLevel.isEmpty())
2782         {
2783             double nNumber;
2784             sal_uInt32 nFormat = 1;
2785             SvNumberFormatter aFormatter(::comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US);
2786             if (aFormatter.IsNumberFormat(aMacroSecurityLevel, nFormat, nNumber))
2787                 nMacroSecurityLevel = static_cast<int>(nNumber);
2788         }
2789         SvtSecurityOptions::SetMacroSecurityLevel(nMacroSecurityLevel);
2790 
2791 #if defined(ANDROID) && HAVE_FEATURE_ANDROID_LOK
2792         sal_Int16 nMacroExecMode = document::MacroExecMode::USE_CONFIG;
2793 #else
2794         const OUString aEnableMacrosExecution = extractParameter(aOptions, u"EnableMacrosExecution");
2795         sal_Int16 nMacroExecMode = aEnableMacrosExecution == "true" ? document::MacroExecMode::USE_CONFIG :
2796             document::MacroExecMode::NEVER_EXECUTE;
2797 #endif
2798 
2799         // set AsTemplate explicitly false to be able to load template files
2800         // as regular files, otherwise we cannot save them; it will try
2801         // to bring saveas dialog which cannot work with LOK case
2802         uno::Sequence<css::beans::PropertyValue> aFilterOptions{
2803             comphelper::makePropertyValue(u"FilterOptions"_ustr, aOptions),
2804             comphelper::makePropertyValue(u"InteractionHandler"_ustr, xInteraction),
2805             comphelper::makePropertyValue(u"MacroExecutionMode"_ustr, nMacroExecMode),
2806             comphelper::makePropertyValue(u"AsTemplate"_ustr, false),
2807             comphelper::makePropertyValue(u"Silent"_ustr, !aBatch.isEmpty())
2808         };
2809 
2810         /* TODO
2811         sal_Int16 nUpdateDoc = document::UpdateDocMode::ACCORDING_TO_CONFIG;
2812         aFilterOptions[3].Name = "UpdateDocMode";
2813         aFilterOptions[3].Value <<= nUpdateDoc;
2814         */
2815 
2816         OutputDevice::StartTrackingFontMappingUse();
2817 
2818         const int nThisDocumentId = nDocumentIdCounter++;
2819         SfxViewShell::SetCurrentDocId(ViewShellDocId(nThisDocumentId));
2820         uno::Reference<lang::XComponent> xComponent = xComponentLoader->loadComponentFromURL(
2821                                             aURL, u"_blank"_ustr, 0,
2822                                             aFilterOptions);
2823 
2824         assert(!xComponent.is() || pair.second); // concurrent loading of same URL ought to fail
2825 
2826         if (!xComponent.is())
2827         {
2828             pLib->maLastExceptionMsg = u"loadComponentFromURL returned an empty reference"_ustr;
2829             SAL_INFO("lok", "Document can't be loaded - " << pLib->maLastExceptionMsg);
2830             return nullptr;
2831         }
2832 
2833         LibLODocument_Impl* pDocument = new LibLODocument_Impl(xComponent, nThisDocumentId);
2834 
2835         // After loading the document, its initial view is the "current" view.
2836         if (pLib->mpCallback)
2837         {
2838             int nState = doc_getSignatureState(pDocument);
2839             pLib->mpCallback(LOK_CALLBACK_SIGNATURE_STATUS, OString::number(nState).getStr(), pLib->mpCallbackData);
2840         }
2841 
2842         auto aFontMappingUseData = OutputDevice::FinishTrackingFontMappingUse();
2843 
2844         if (aFontMappingUseData.size() > 0)
2845         {
2846             SAL_INFO("lok.fontsubst", "================ Original substitutions:");
2847             for (const auto &i : aFontMappingUseData)
2848             {
2849                 SAL_INFO("lok.fontsubst", i.mOriginalFont);
2850                 for (const auto &j : i.mUsedFonts)
2851                     SAL_INFO("lok.fontsubst", "    " << j);
2852             }
2853         }
2854 
2855         // Filter out font substitutions that actually aren't any substitutions, like "Liberation
2856         // Serif" -> "Liberation Serif/Regular". If even one of the "substitutions" of a font is to
2857         // the same font, don't count that as a missing font.
2858 
2859         std::erase_if
2860             (aFontMappingUseData,
2861                             [](OutputDevice::FontMappingUseItem x)
2862                             {
2863                                 // If the original font had an empty style and one of its
2864                                 // replacement fonts has the same family name, we assume the font is
2865                                 // present. The root problem here is that the code that collects
2866                                 // font substitutions tends to get just empty styles for the font
2867                                 // that is being substituted, as vcl::Font::GetStyleName() tends to
2868                                 // return an empty string. (Italicness is instead indicated by what
2869                                 // vcl::Font::GetItalic() returns and boldness by what
2870                                 // vcl::Font::GetWeight() returns.)
2871 
2872                                 if (x.mOriginalFont.indexOf('/') == -1)
2873                                     for (const auto &j : x.mUsedFonts)
2874                                         if (j == x.mOriginalFont ||
2875                                             j.startsWith(Concat2View(x.mOriginalFont + "/")))
2876                                             return true;
2877 
2878                                 return false;
2879                             });
2880 
2881         // Filter out substitutions where a proprietary font has been substituted by a
2882         // metric-compatible one. Obviously this is just a heuristic and implemented only for some
2883         // well-known cases.
2884 
2885         std::erase_if
2886             (aFontMappingUseData,
2887                             [](OutputDevice::FontMappingUseItem x)
2888                             {
2889                                 // Again, handle only cases where the original font does not include
2890                                 // a style. Unclear whether there ever will be a style part included
2891                                 // in the mOriginalFont.
2892 
2893                                 if (x.mOriginalFont.indexOf('/') == -1)
2894                                     for (const auto &j : x.mUsedFonts)
2895                                         if ((x.mOriginalFont == "Arial" &&
2896                                              j.startsWith("Liberation Sans/")) ||
2897                                             (x.mOriginalFont == "Times New Roman" &&
2898                                              j.startsWith("Liberation Serif/")) ||
2899                                             (x.mOriginalFont == "Courier New" &&
2900                                              j.startsWith("Liberation Mono/")) ||
2901                                             (x.mOriginalFont == "Arial Narrow" &&
2902                                              j.startsWith("Liberation Sans Narrow/")) ||
2903                                             (x.mOriginalFont == "Cambria" &&
2904                                              j.startsWith("Caladea/")) ||
2905                                             (x.mOriginalFont == "Calibri" &&
2906                                              j.startsWith("Carlito/")) ||
2907                                             (x.mOriginalFont == "Palatino Linotype" &&
2908                                              j.startsWith("P052/")) ||
2909                                             // Perhaps a risky heuristic? If some glyphs from Symbol
2910                                             // have been mapped to ones in OpenSymbol, don't warn
2911                                             // that Symbol is missing.
2912                                             (x.mOriginalFont == "Symbol" &&
2913                                              j.startsWith("OpenSymbol/")))
2914                                         {
2915                                             return true;
2916                                         }
2917 
2918                                 return false;
2919                             });
2920 
2921         if (aFontMappingUseData.size() > 0)
2922         {
2923             SAL_INFO("lok.fontsubst", "================ Pruned substitutions:");
2924             for (const auto &i : aFontMappingUseData)
2925             {
2926                 SAL_INFO("lok.fontsubst", i.mOriginalFont);
2927                 for (const auto &j : i.mUsedFonts)
2928                     SAL_INFO("lok.fontsubst", "    " << j);
2929             }
2930         }
2931 
2932         for (std::size_t i = 0; i < aFontMappingUseData.size(); ++i)
2933         {
2934             pDocument->maFontsMissing.insert(aFontMappingUseData[i].mOriginalFont);
2935         }
2936 
2937         return pDocument;
2938     }
2939     catch (const uno::Exception& exception)
2940     {
2941         pLib->maLastExceptionMsg = exception.Message;
2942         TOOLS_INFO_EXCEPTION("lok", "Document can't be loaded");
2943     }
2944 
2945     return nullptr;
2946 }
2947 
2948 static int lo_runMacro(LibreOfficeKit* pThis, const char *pURL)
2949 {
2950     comphelper::ProfileZone aZone("lo_runMacro");
2951 
2952     SolarMutexGuard aGuard;
2953 
2954     LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
2955     pLib->maLastExceptionMsg.clear();
2956 
2957     OUString sURL( pURL, strlen(pURL), RTL_TEXTENCODING_UTF8 );
2958     if (sURL.isEmpty())
2959     {
2960         pLib->maLastExceptionMsg = u"Macro to run was not provided."_ustr;
2961         SAL_INFO("lok", "Macro URL is empty");
2962         return false;
2963     }
2964 
2965     if (!sURL.startsWith("macro://"))
2966     {
2967         pLib->maLastExceptionMsg = u"This doesn't look like macro URL"_ustr;
2968         SAL_INFO("lok", "Macro URL is invalid");
2969         return false;
2970     }
2971 
2972     pLib->maLastExceptionMsg.clear();
2973 
2974     if (!xContext.is())
2975     {
2976         pLib->maLastExceptionMsg = u"ComponentContext is not available"_ustr;
2977         SAL_INFO("lok", "ComponentContext is not available");
2978         return false;
2979     }
2980 
2981     util::URL aURL;
2982     aURL.Complete = sURL;
2983 
2984     uno::Reference < util::XURLTransformer > xParser( util::URLTransformer::create( xContext ) );
2985 
2986     if( xParser.is() )
2987         xParser->parseStrict( aURL );
2988 
2989     uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext);
2990 
2991     if (!xComponentLoader.is())
2992     {
2993         pLib->maLastExceptionMsg = u"ComponentLoader is not available"_ustr;
2994         SAL_INFO("lok", "ComponentLoader is not available");
2995         return false;
2996     }
2997 
2998     xFactory = xContext->getServiceManager();
2999 
3000     if (!xFactory)
3001         return false;
3002 
3003     uno::Reference<frame::XDispatchProvider> xDP;
3004     xSFactory.set(xFactory, uno::UNO_QUERY_THROW);
3005     xDP.set( xSFactory->createInstance(u"com.sun.star.comp.sfx2.SfxMacroLoader"_ustr), uno::UNO_QUERY );
3006     uno::Reference<frame::XDispatch> xD = xDP->queryDispatch( aURL, OUString(), 0);
3007 
3008     if (!xD.is())
3009     {
3010         pLib->maLastExceptionMsg = u"Macro loader is not available"_ustr;
3011         SAL_INFO("lok", "Macro loader is not available");
3012         return false;
3013     }
3014 
3015     uno::Reference < frame::XSynchronousDispatch > xSyncDisp( xD, uno::UNO_QUERY_THROW );
3016     uno::Sequence<css::beans::PropertyValue> aEmpty;
3017     css::beans::PropertyValue aErr;
3018     uno::Any aRet = xSyncDisp->dispatchWithReturnValue( aURL, aEmpty );
3019     aRet >>= aErr;
3020 
3021     if (aErr.Name == "ErrorCode")
3022     {
3023         sal_uInt32 nErrCode = 0; // ERRCODE_NONE
3024         aErr.Value >>= nErrCode;
3025 
3026         pLib->maLastExceptionMsg = "An error occurred running macro (error code: " + OUString::number( nErrCode ) + ")";
3027         SAL_INFO("lok", "Macro execution terminated with error code " << nErrCode);
3028 
3029         return false;
3030     }
3031 
3032     return true;
3033 }
3034 
3035 static bool lo_signDocument(LibreOfficeKit* /*pThis*/,
3036                             const char* pURL,
3037                             const unsigned char* pCertificateBinary,
3038                             const int nCertificateBinarySize,
3039                             const unsigned char* pPrivateKeyBinary,
3040                             const int nPrivateKeyBinarySize)
3041 {
3042     comphelper::ProfileZone aZone("lo_signDocument");
3043 
3044     OUString aURL(getAbsoluteURL(pURL));
3045     if (aURL.isEmpty())
3046        return false;
3047 
3048     if (!xContext.is())
3049         return false;
3050 
3051     uno::Sequence<sal_Int8> aCertificateSequence;
3052 
3053     std::string aCertificateString(reinterpret_cast<const char*>(pCertificateBinary), nCertificateBinarySize);
3054     std::string aCertificateBase64String = extractCertificate(aCertificateString);
3055     if (!aCertificateBase64String.empty())
3056     {
3057         OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String);
3058         comphelper::Base64::decode(aCertificateSequence, aBase64OUString);
3059     }
3060     else
3061     {
3062         aCertificateSequence.realloc(nCertificateBinarySize);
3063         std::copy(pCertificateBinary, pCertificateBinary + nCertificateBinarySize, aCertificateSequence.getArray());
3064     }
3065 
3066     uno::Sequence<sal_Int8> aPrivateKeySequence;
3067     std::string aPrivateKeyString(reinterpret_cast<const char*>(pPrivateKeyBinary), nPrivateKeyBinarySize);
3068     std::string aPrivateKeyBase64String = extractPrivateKey(aPrivateKeyString);
3069     if (!aPrivateKeyBase64String.empty())
3070     {
3071         OUString aBase64OUString = OUString::createFromAscii(aPrivateKeyBase64String);
3072         comphelper::Base64::decode(aPrivateKeySequence, aBase64OUString);
3073     }
3074     else
3075     {
3076         aPrivateKeySequence.realloc(nPrivateKeyBinarySize);
3077         std::copy(pPrivateKeyBinary, pPrivateKeyBinary + nPrivateKeyBinarySize, aPrivateKeySequence.getArray());
3078     }
3079 
3080     uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
3081     uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
3082     if (!xSecurityContext.is())
3083         return false;
3084 
3085     uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment();
3086     uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY);
3087 
3088     if (!xCertificateCreator.is())
3089         return false;
3090 
3091     uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->createDERCertificateWithPrivateKey(aCertificateSequence, aPrivateKeySequence);
3092 
3093     if (!xCertificate.is())
3094         return false;
3095 
3096     sfx2::DocumentSigner aDocumentSigner(aURL);
3097     if (!aDocumentSigner.signDocument(xCertificate))
3098         return false;
3099 
3100     return true;
3101 }
3102 
3103 
3104 static char* lo_extractRequest(LibreOfficeKit* /*pThis*/, const char* pFilePath)
3105 {
3106     uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext);
3107     uno::Reference< css::lang::XComponent > xComp;
3108     OUString aURL(getAbsoluteURL(pFilePath));
3109     if (!aURL.isEmpty())
3110     {
3111         if (xComponentLoader.is())
3112         {
3113             try
3114             {
3115                 uno::Sequence<css::beans::PropertyValue> aFilterOptions(comphelper::InitPropertySequence(
3116                 {
3117                     {u"Hidden"_ustr, css::uno::Any(true)},
3118                     {u"ReadOnly"_ustr, css::uno::Any(true)}
3119                 }));
3120                 xComp = xComponentLoader->loadComponentFromURL( aURL, u"_blank"_ustr, 0, aFilterOptions );
3121             }
3122             catch ( const lang::IllegalArgumentException& ex )
3123             {
3124                 SAL_WARN("lok", "lo_extractRequest: IllegalArgumentException: " << ex.Message);
3125             }
3126             catch (...)
3127             {
3128                 SAL_WARN("lok", "lo_extractRequest: Exception on loadComponentFromURL, url= " << aURL);
3129             }
3130 
3131             if (xComp.is())
3132             {
3133                 uno::Reference< document::XLinkTargetSupplier > xLTS( xComp, uno::UNO_QUERY );
3134 
3135                 if( xLTS.is() )
3136                 {
3137                     tools::JsonWriter aJson;
3138                     {
3139                         auto aNode = aJson.startNode("Targets");
3140                         extractLinks(xLTS->getLinks(), false, aJson);
3141                     }
3142                     return convertOString(aJson.finishAndGetAsOString());
3143                 }
3144                 xComp->dispose();
3145             }
3146         }
3147     }
3148     return strdup("{ }");
3149 }
3150 
3151 static void lo_trimMemory(LibreOfficeKit* /* pThis */, int nTarget)
3152 {
3153     vcl::lok::trimMemory(nTarget);
3154 
3155     if (nTarget > 2000)
3156     {
3157         SolarMutexGuard aGuard;
3158 
3159         // Flush all buffered VOC primitives from the pages.
3160         SfxViewShell* pViewShell = SfxViewShell::Current();
3161         if (pViewShell)
3162         {
3163             const SdrView* pView = pViewShell->GetDrawView();
3164             if (pView)
3165             {
3166                 SdrPageView* pPageView = pView->GetSdrPageView();
3167                 if (pPageView)
3168                 {
3169                     SdrPage* pCurPage = pPageView->GetPage();
3170                     if (pCurPage)
3171                     {
3172                         SdrModel& sdrModel = pCurPage->getSdrModelFromSdrPage();
3173                         for (sal_uInt16 i = 0; i < sdrModel.GetPageCount(); ++i)
3174                         {
3175                             SdrPage* pPage = sdrModel.GetPage(i);
3176                             if (pPage)
3177                                 pPage->GetViewContact().flushViewObjectContacts();
3178                         }
3179                     }
3180                 }
3181             }
3182         }
3183     }
3184 
3185     if (nTarget > 1000)
3186     {
3187 #ifdef HAVE_MALLOC_TRIM
3188         malloc_trim(0);
3189 #endif
3190     }
3191 }
3192 
3193 namespace
3194 {
3195 class FunctionBasedURPInstanceProvider
3196     : public ::cppu::WeakImplHelper<css::bridge::XInstanceProvider>
3197 {
3198 private:
3199     css::uno::Reference<css::uno::XComponentContext> m_rContext;
3200 
3201 public:
3202     FunctionBasedURPInstanceProvider(
3203         const css::uno::Reference<css::uno::XComponentContext>& rxContext);
3204 
3205     // XInstanceProvider
3206     virtual css::uno::Reference<css::uno::XInterface>
3207         SAL_CALL getInstance(const OUString& aName) override;
3208 };
3209 
3210 // InstanceProvider
3211 FunctionBasedURPInstanceProvider::FunctionBasedURPInstanceProvider(
3212     const Reference<XComponentContext>& rxContext)
3213     : m_rContext(rxContext)
3214 {
3215 }
3216 
3217 Reference<XInterface> FunctionBasedURPInstanceProvider::getInstance(const OUString& aName)
3218 {
3219     Reference<XInterface> rInstance;
3220 
3221     if (aName == "StarOffice.ServiceManager")
3222     {
3223         rInstance.set(m_rContext->getServiceManager());
3224     }
3225     else if (aName == "StarOffice.ComponentContext")
3226     {
3227         rInstance = m_rContext;
3228     }
3229     else if (aName == "StarOffice.NamingService")
3230     {
3231         Reference<XNamingService> rNamingService(
3232             m_rContext->getServiceManager()->createInstanceWithContext(
3233                 u"com.sun.star.uno.NamingService"_ustr, m_rContext),
3234             UNO_QUERY);
3235         if (rNamingService.is())
3236         {
3237             rNamingService->registerObject(u"StarOffice.ServiceManager"_ustr,
3238                                            m_rContext->getServiceManager());
3239             rNamingService->registerObject(u"StarOffice.ComponentContext"_ustr, m_rContext);
3240             rInstance = rNamingService;
3241         }
3242     }
3243     return rInstance;
3244 }
3245 
3246 class FunctionBasedURPConnection : public cppu::WeakImplHelper<css::connection::XConnection>
3247 {
3248 public:
3249     explicit FunctionBasedURPConnection(void*, int (*)(void* pContext, const signed char* pBuffer, int nLen),
3250                                         void*, int (*)(void* pContext, signed char* pBuffer, int nLen));
3251     ~FunctionBasedURPConnection();
3252 
3253     // These overridden member functions use "read" and "write" from the point of view of LO,
3254     // i.e. the opposite to how startURP() uses them.
3255     virtual sal_Int32 SAL_CALL read(Sequence<sal_Int8>& rReadBytes,
3256                                     sal_Int32 nBytesToRead) override;
3257     virtual void SAL_CALL write(const Sequence<sal_Int8>& aData) override;
3258     virtual void SAL_CALL flush() override;
3259     virtual void SAL_CALL close() override;
3260     virtual OUString SAL_CALL getDescription() override;
3261     void setBridge(const Reference<XBridge>&);
3262     void* getContext();
3263     inline static int g_connectionCount = 0;
3264 
3265 private:
3266     void* m_pRecieveFromLOContext;
3267     void* m_pSendURPToLOContext;
3268     int (*m_fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen);
3269     int (*m_fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen);
3270     Reference<XBridge> m_URPBridge;
3271 };
3272 
3273 FunctionBasedURPConnection::FunctionBasedURPConnection(
3274     void* pRecieveFromLOContext,
3275     int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen),
3276     void* pSendURPToLOContext,
3277     int (*fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen))
3278     : m_pRecieveFromLOContext(pRecieveFromLOContext)
3279     , m_pSendURPToLOContext(pSendURPToLOContext)
3280     , m_fnReceiveURPFromLO(fnReceiveURPFromLO)
3281     , m_fnSendURPToLO(fnSendURPToLO)
3282 {
3283     g_connectionCount++;
3284 }
3285 
3286 FunctionBasedURPConnection::~FunctionBasedURPConnection()
3287 {
3288     Reference<XComponent> xComp(m_URPBridge, UNO_QUERY_THROW);
3289     xComp->dispose(); // TODO: check this doesn't deadlock
3290 }
3291 
3292 void* FunctionBasedURPConnection::getContext() { return this; }
3293 
3294 sal_Int32 FunctionBasedURPConnection::read(Sequence<sal_Int8>& rReadBytes, sal_Int32 nBytesToRead)
3295 {
3296     if (nBytesToRead < 0)
3297         return 0;
3298 
3299     if (rReadBytes.getLength() != nBytesToRead)
3300         rReadBytes.realloc(nBytesToRead);
3301 
3302     // As with osl::StreamPipe, we must always read nBytesToRead...
3303     return m_fnSendURPToLO(m_pSendURPToLOContext, rReadBytes.getArray(), nBytesToRead);
3304 }
3305 
3306 void FunctionBasedURPConnection::write(const Sequence<sal_Int8>& rData)
3307 {
3308     m_fnReceiveURPFromLO(m_pRecieveFromLOContext, rData.getConstArray(), rData.getLength());
3309 }
3310 
3311 void FunctionBasedURPConnection::flush() {}
3312 
3313 void FunctionBasedURPConnection::close()
3314 {
3315     SAL_INFO("lok.urp", "Requested to close FunctionBasedURPConnection");
3316 }
3317 
3318 OUString FunctionBasedURPConnection::getDescription() { return ""; }
3319 
3320 void FunctionBasedURPConnection::setBridge(const Reference<XBridge>& xBridge) { m_URPBridge = xBridge; }
3321 }
3322 
3323 static void*
3324 lo_startURP(LibreOfficeKit* /* pThis */, void* pRecieveFromLOContext, void* pSendToLOContext,
3325             int (*fnReceiveURPFromLO)(void* pContext, const signed char* pBuffer, int nLen),
3326             int (*fnSendURPToLO)(void* pContext, signed char* pBuffer, int nLen))
3327 {
3328     // Here we will roughly do what desktop LO does when one passes a command-line switch like
3329     // --accept=socket,port=nnnn;urp;StarOffice.ServiceManager. Except that no listening socket will
3330     // be created. The communication to the URP will be through the nReceiveURPFromLO and nSendURPToLO
3331     // functions.
3332 
3333     rtl::Reference<FunctionBasedURPConnection> connection(
3334         new FunctionBasedURPConnection(pRecieveFromLOContext, fnReceiveURPFromLO,
3335                                        pSendToLOContext, fnSendURPToLO));
3336 
3337     Reference<XBridgeFactory> xBridgeFactory = css::bridge::BridgeFactory::create(xContext);
3338 
3339     Reference<XInstanceProvider> xInstanceProvider(new FunctionBasedURPInstanceProvider(xContext));
3340 
3341     Reference<XBridge> xBridge(xBridgeFactory->createBridge(
3342         "functionurp" + OUString::number(FunctionBasedURPConnection::g_connectionCount), u"urp"_ustr,
3343         connection, xInstanceProvider));
3344 
3345     connection->setBridge(std::move(xBridge));
3346 
3347     return connection->getContext();
3348 }
3349 
3350 /**
3351  * Stop a function based URP connection that you started with lo_startURP above
3352  *
3353  * @param pSendToLOContext a pointer to the context returned by lo_startURP */
3354 static void lo_stopURP(LibreOfficeKit* /* pThis */,
3355                        void* pFunctionBasedURPConnection/* FunctionBasedURPConnection* */)
3356 {
3357     static_cast<FunctionBasedURPConnection*>(pFunctionBasedURPConnection)->close();
3358 }
3359 
3360 
3361 static int lo_joinThreads(LibreOfficeKit* /* pThis */)
3362 {
3363     comphelper::ThreadPool &pool = comphelper::ThreadPool::getSharedOptimalPool();
3364     pool.joinThreadsIfIdle();
3365 
3366 //    if (comphelper::getWorkerCount() > 0)
3367 //        return 0;
3368 
3369     // Grammar checker thread
3370     css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv =
3371         css::linguistic2::LinguServiceManager::create(xContext);
3372 
3373     auto joinable = dynamic_cast<comphelper::LibreOfficeKit::ThreadJoinable *>(xLangSrv.get());
3374     if (joinable && !joinable->joinThreads())
3375         return 0;
3376 
3377     return 1;
3378 }
3379 
3380 static void lo_registerCallback (LibreOfficeKit* pThis,
3381                                  LibreOfficeKitCallback pCallback,
3382                                  void* pData)
3383 {
3384     SolarMutexGuard aGuard;
3385 
3386     Application* pApp = GetpApp();
3387     assert(pApp);
3388 
3389     LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
3390     pLib->maLastExceptionMsg.clear();
3391 
3392     pApp->m_pCallback = pLib->mpCallback = pCallback;
3393     pApp->m_pCallbackData = pLib->mpCallbackData = pData;
3394 }
3395 
3396 static int doc_saveAs(LibreOfficeKitDocument* pThis, const char* sUrl, const char* pFormat, const char* pFilterOptions)
3397 {
3398     comphelper::ProfileZone aZone("doc_saveAs");
3399 
3400     SolarMutexGuard aGuard;
3401     SetLastExceptionMsg();
3402 
3403     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
3404 
3405     OUString sFormat = getUString(pFormat);
3406     OUString aURL(getAbsoluteURL(sUrl));
3407 
3408     uno::Reference<frame::XStorable> xStorable(pDocument->mxComponent, uno::UNO_QUERY_THROW);
3409 
3410     if (aURL.isEmpty())
3411     {
3412         SetLastExceptionMsg(u"Filename to save to was not provided."_ustr);
3413         SAL_INFO("lok", "URL for save is empty");
3414         return false;
3415     }
3416 
3417     try
3418     {
3419         std::span<const ExtensionMap> pMap;
3420 
3421         switch (doc_getDocumentType(pThis))
3422         {
3423         case LOK_DOCTYPE_SPREADSHEET:
3424             pMap = aCalcExtensionMap;
3425             break;
3426         case LOK_DOCTYPE_PRESENTATION:
3427             pMap = aImpressExtensionMap;
3428             break;
3429         case LOK_DOCTYPE_DRAWING:
3430             pMap = aDrawExtensionMap;
3431             break;
3432         case LOK_DOCTYPE_TEXT:
3433             pMap = aWriterExtensionMap;
3434             break;
3435         case LOK_DOCTYPE_OTHER:
3436         default:
3437             SAL_INFO("lok", "Can't save document - unsupported document type.");
3438             return false;
3439         }
3440 
3441         if (pFormat == nullptr)
3442         {
3443             // sniff from the extension
3444             sal_Int32 idx = aURL.lastIndexOf(".");
3445             if( idx > 0 )
3446             {
3447                 sFormat = aURL.copy( idx + 1 );
3448             }
3449             else
3450             {
3451                 SetLastExceptionMsg("input URL '" + aURL + "' lacks a suffix");
3452                 return false;
3453             }
3454         }
3455 
3456         OUString aFilterName;
3457         for (const auto& item : pMap)
3458         {
3459             if (sFormat.equalsIgnoreAsciiCaseAscii(item.extn))
3460             {
3461                 aFilterName = item.filterName;
3462                 break;
3463             }
3464         }
3465         if (aFilterName.isEmpty())
3466         {
3467             SetLastExceptionMsg(u"no output filter found for provided suffix"_ustr);
3468             return false;
3469         }
3470 
3471         OUString aFilterOptions = getUString(pFilterOptions);
3472 
3473         // Check if watermark for pdf is passed by filteroptions...
3474         // It is not a real filter option so it must be filtered out.
3475         OUString watermarkText;
3476         std::u16string_view sFullSheetPreview;
3477         int aIndex = -1;
3478         if ((aIndex = aFilterOptions.indexOf(",Watermark=")) >= 0)
3479         {
3480             int bIndex = aFilterOptions.indexOf("WATERMARKEND");
3481             watermarkText = aFilterOptions.subView(aIndex+11, bIndex-(aIndex+11));
3482             aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + aFilterOptions.subView(bIndex+12);
3483         }
3484 
3485         if ((aIndex = aFilterOptions.indexOf(",FullSheetPreview=")) >= 0)
3486         {
3487             int bIndex = aFilterOptions.indexOf("FULLSHEETPREVEND");
3488             sFullSheetPreview = aFilterOptions.subView(aIndex+18, bIndex-(aIndex+18));
3489             aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + aFilterOptions.subView(bIndex+16);
3490         }
3491 
3492         bool bFullSheetPreview = sFullSheetPreview == u"true";
3493 
3494         OUString filePassword;
3495         if ((aIndex = aFilterOptions.indexOf(",Password=")) >= 0)
3496         {
3497             int bIndex = aFilterOptions.indexOf("PASSWORDEND");
3498             filePassword = aFilterOptions.subView(aIndex + 10, bIndex - (aIndex + 10));
3499             aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex))
3500                              + aFilterOptions.subView(bIndex + 11);
3501         }
3502         OUString filePasswordToModify;
3503         if ((aIndex = aFilterOptions.indexOf(",PasswordToModify=")) >= 0)
3504         {
3505             int bIndex = aFilterOptions.indexOf("PASSWORDTOMODIFYEND");
3506             filePassword = aFilterOptions.subView(aIndex + 18, bIndex - (aIndex + 18));
3507             aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex))
3508                              + aFilterOptions.subView(bIndex + 19);
3509         }
3510 
3511         // Select a pdf version if specified a valid one. If not specified then ignore.
3512         // If invalid then fail.
3513         sal_Int32 pdfVer = 0;
3514         if ((aIndex = aFilterOptions.indexOf(",PDFVer=")) >= 0)
3515         {
3516             int bIndex = aFilterOptions.indexOf("PDFVEREND");
3517             std::u16string_view sPdfVer = aFilterOptions.subView(aIndex+8, bIndex-(aIndex+8));
3518             aFilterOptions = OUString::Concat(aFilterOptions.subView(0, aIndex)) + aFilterOptions.subView(bIndex+9);
3519 
3520             if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF/A-1b"))
3521                 pdfVer = 1;
3522             else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF/A-2b"))
3523                 pdfVer = 2;
3524             else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF/A-3b"))
3525                 pdfVer = 3;
3526             else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF-1.5"))
3527                 pdfVer = 15;
3528             else if (o3tl::equalsIgnoreAsciiCase(sPdfVer, u"PDF-1.6"))
3529                 pdfVer = 16;
3530             else
3531             {
3532                 SetLastExceptionMsg(u"wrong PDF version"_ustr);
3533                 return false;
3534             }
3535         }
3536 
3537         // 'TakeOwnership' == this is a 'real' SaveAs (that is, the document
3538         // gets a new name).  When this is not provided, the meaning of
3539         // saveAs() is more like save-a-copy, which allows saving to any
3540         // random format like PDF or PNG.
3541         // It is not a real filter option, so we have to filter it out.
3542         const uno::Sequence<OUString> aOptionSeq = comphelper::string::convertCommaSeparated(aFilterOptions);
3543         std::vector<OUString> aFilteredOptionVec;
3544         bool bTakeOwnership = false;
3545         MediaDescriptor aSaveMediaDescriptor;
3546         for (const auto& rOption : aOptionSeq)
3547         {
3548             if (rOption == "TakeOwnership")
3549                 bTakeOwnership = true;
3550             else if (rOption == "NoFileSync")
3551                 aSaveMediaDescriptor[u"NoFileSync"_ustr] <<= true;
3552             else
3553                 aFilteredOptionVec.push_back(rOption);
3554         }
3555 
3556         aSaveMediaDescriptor[u"Overwrite"_ustr] <<= true;
3557         aSaveMediaDescriptor[u"FilterName"_ustr] <<= aFilterName;
3558 
3559         auto aFilteredOptionSeq = comphelper::containerToSequence<OUString>(aFilteredOptionVec);
3560         aFilterOptions = comphelper::string::convertCommaSeparated(aFilteredOptionSeq);
3561         aSaveMediaDescriptor[MediaDescriptor::PROP_FILTEROPTIONS] <<= aFilterOptions;
3562 
3563         comphelper::SequenceAsHashMap aFilterDataMap;
3564 
3565         // If filter options is JSON string, then make sure aFilterDataMap stays empty, otherwise we
3566         // would ignore the filter options.
3567         if (!aFilterOptions.startsWith("{"))
3568         {
3569             setFormatSpecificFilterData(sFormat, aFilterDataMap);
3570         }
3571 
3572         if (!watermarkText.isEmpty())
3573             aFilterDataMap[u"TiledWatermark"_ustr] <<= watermarkText;
3574 
3575         if (bFullSheetPreview)
3576             aFilterDataMap[u"SinglePageSheets"_ustr] <<= true;
3577 
3578         if (pdfVer)
3579             aFilterDataMap[u"SelectPdfVersion"_ustr] <<= pdfVer;
3580 
3581         if (!aFilterDataMap.empty())
3582         {
3583             aSaveMediaDescriptor[u"FilterData"_ustr] <<= aFilterDataMap.getAsConstPropertyValueList();
3584         }
3585         if (!filePassword.isEmpty())
3586             aSaveMediaDescriptor[u"Password"_ustr] <<= filePassword;
3587         if (!filePasswordToModify.isEmpty())
3588             aSaveMediaDescriptor[u"PasswordToModify"_ustr] <<= filePasswordToModify;
3589 
3590         // add interaction handler too
3591         if (gImpl)
3592         {
3593             // gImpl does not have to exist when running from a unit test
3594             rtl::Reference<LOKInteractionHandler> const pInteraction(
3595                     new LOKInteractionHandler("saveas"_ostr, gImpl, pDocument));
3596             uno::Reference<task::XInteractionHandler2> const xInteraction(pInteraction);
3597 
3598             aSaveMediaDescriptor[MediaDescriptor::PROP_INTERACTIONHANDLER] <<= xInteraction;
3599         }
3600 
3601 
3602         if (bTakeOwnership)
3603             xStorable->storeAsURL(aURL, aSaveMediaDescriptor.getAsConstPropertyValueList());
3604         else
3605             xStorable->storeToURL(aURL, aSaveMediaDescriptor.getAsConstPropertyValueList());
3606 
3607         return true;
3608     }
3609     catch (const uno::Exception& exception)
3610     {
3611         SetLastExceptionMsg("exception: " + exception.Message);
3612     }
3613     return false;
3614 }
3615 
3616 /**
3617  * Initialize UNO commands, in the sense that from now on, the LOK client gets updates for status
3618  * changes of these commands. This is necessary, because (unlike in the desktop case) there are no
3619  * toolbars hosting widgets these UNO commands, so no such status updates would be sent to the
3620  * headless LOK clients out of the box.
3621  */
3622 static void doc_iniUnoCommands ()
3623 {
3624     SolarMutexGuard aGuard;
3625     SetLastExceptionMsg();
3626 
3627     static constexpr OUString sUnoCommands[] =
3628     {
3629         u".uno:AlignLeft"_ustr,
3630         u".uno:AlignHorizontalCenter"_ustr,
3631         u".uno:AlignRight"_ustr,
3632         u".uno:BackColor"_ustr,
3633         u".uno:BackgroundColor"_ustr,
3634         u".uno:TableCellBackgroundColor"_ustr,
3635         u".uno:Bold"_ustr,
3636         u".uno:CenterPara"_ustr,
3637         u".uno:CharBackColor"_ustr,
3638         u".uno:CharBackgroundExt"_ustr,
3639         u".uno:CharFontName"_ustr,
3640         u".uno:Color"_ustr,
3641         u".uno:ControlCodes"_ustr,
3642         u".uno:DecrementIndent"_ustr,
3643         u".uno:DefaultBullet"_ustr,
3644         u".uno:DefaultNumbering"_ustr,
3645         u".uno:FontColor"_ustr,
3646         u".uno:FontHeight"_ustr,
3647         u".uno:IncrementIndent"_ustr,
3648         u".uno:Italic"_ustr,
3649         u".uno:JustifyPara"_ustr,
3650         u".uno:JumpToMark"_ustr,
3651         u".uno:OutlineFont"_ustr,
3652         u".uno:LeftPara"_ustr,
3653         u".uno:LanguageStatus"_ustr,
3654         u".uno:RightPara"_ustr,
3655         u".uno:Shadowed"_ustr,
3656         u".uno:SubScript"_ustr,
3657         u".uno:SuperScript"_ustr,
3658         u".uno:Strikeout"_ustr,
3659         u".uno:StyleApply"_ustr,
3660         u".uno:Underline"_ustr,
3661         u".uno:ModifiedStatus"_ustr,
3662         u".uno:Undo"_ustr,
3663         u".uno:Redo"_ustr,
3664         u".uno:InsertPage"_ustr,
3665         u".uno:DeletePage"_ustr,
3666         u".uno:DuplicatePage"_ustr,
3667         u".uno:InsertSlide"_ustr,
3668         u".uno:DeleteSlide"_ustr,
3669         u".uno:DuplicateSlide"_ustr,
3670         u".uno:ChangeTheme"_ustr,
3671         u".uno:Cut"_ustr,
3672         u".uno:Copy"_ustr,
3673         u".uno:Paste"_ustr,
3674         u".uno:SelectAll"_ustr,
3675         u".uno:ReplyComment"_ustr,
3676         u".uno:ResolveComment"_ustr,
3677         u".uno:ResolveCommentThread"_ustr,
3678         u".uno:InsertRowsBefore"_ustr,
3679         u".uno:InsertRowsAfter"_ustr,
3680         u".uno:InsertColumnsBefore"_ustr,
3681         u".uno:InsertColumnsAfter"_ustr,
3682         u".uno:DeleteRows"_ustr,
3683         u".uno:DeleteColumns"_ustr,
3684         u".uno:DeleteTable"_ustr,
3685         u".uno:SelectTable"_ustr,
3686         u".uno:EntireRow"_ustr,
3687         u".uno:EntireColumn"_ustr,
3688         u".uno:EntireCell"_ustr,
3689         u".uno:AssignLayout"_ustr,
3690         u".uno:StatusDocPos"_ustr,
3691         u".uno:RowColSelCount"_ustr,
3692         u".uno:StatusPageStyle"_ustr,
3693         u".uno:InsertMode"_ustr,
3694         u".uno:SpellOnline"_ustr,
3695         u".uno:StatusSelectionMode"_ustr,
3696         u".uno:StateTableCell"_ustr,
3697         u".uno:StatusBarFunc"_ustr,
3698         u".uno:StatePageNumber"_ustr,
3699         u".uno:StateWordCount"_ustr,
3700         u".uno:SelectionMode"_ustr,
3701         u".uno:PageStatus"_ustr,
3702         u".uno:LayoutStatus"_ustr,
3703         u".uno:Scale"_ustr,
3704         u".uno:Context"_ustr,
3705         u".uno:WrapText"_ustr,
3706         u".uno:ToggleMergeCells"_ustr,
3707         u".uno:NumberFormatCurrency"_ustr,
3708         u".uno:NumberFormatPercent"_ustr,
3709         u".uno:NumberFormatDecimal"_ustr,
3710         u".uno:NumberFormatIncDecimals"_ustr,
3711         u".uno:NumberFormatDecDecimals"_ustr,
3712         u".uno:NumberFormatDate"_ustr,
3713         u".uno:EditHeaderAndFooter"_ustr,
3714         u".uno:FrameLineColor"_ustr,
3715         u".uno:SortAscending"_ustr,
3716         u".uno:SortDescending"_ustr,
3717         u".uno:TrackChanges"_ustr,
3718         u".uno:ShowTrackedChanges"_ustr,
3719         u".uno:NextTrackedChange"_ustr,
3720         u".uno:PreviousTrackedChange"_ustr,
3721         u".uno:AcceptAllTrackedChanges"_ustr,
3722         u".uno:RejectAllTrackedChanges"_ustr,
3723         u".uno:TableDialog"_ustr,
3724         u".uno:FormatCellDialog"_ustr,
3725         u".uno:FontDialog"_ustr,
3726         u".uno:ParagraphDialog"_ustr,
3727         u".uno:OutlineBullet"_ustr,
3728         u".uno:InsertIndexesEntry"_ustr,
3729         u".uno:DocumentRepair"_ustr,
3730         u".uno:TransformDialog"_ustr,
3731         u".uno:InsertPageHeader"_ustr,
3732         u".uno:InsertPageFooter"_ustr,
3733         u".uno:OnlineAutoFormat"_ustr,
3734         u".uno:InsertObjectChart"_ustr,
3735         u".uno:InsertSection"_ustr,
3736         u".uno:InsertAnnotation"_ustr,
3737         u".uno:DeleteAnnotation"_ustr,
3738         u".uno:InsertPagebreak"_ustr,
3739         u".uno:InsertColumnBreak"_ustr,
3740         u".uno:HyperlinkDialog"_ustr,
3741         u".uno:InsertSymbol"_ustr,
3742         u".uno:EditRegion"_ustr,
3743         u".uno:ThesaurusDialog"_ustr,
3744         u".uno:FormatArea"_ustr,
3745         u".uno:FormatLine"_ustr,
3746         u".uno:FormatColumns"_ustr,
3747         u".uno:Watermark"_ustr,
3748         u".uno:ResetAttributes"_ustr,
3749         u".uno:Orientation"_ustr,
3750         u".uno:ObjectAlignLeft"_ustr,
3751         u".uno:ObjectAlignRight"_ustr,
3752         u".uno:AlignCenter"_ustr,
3753         u".uno:TransformPosX"_ustr,
3754         u".uno:TransformPosY"_ustr,
3755         u".uno:TransformWidth"_ustr,
3756         u".uno:TransformHeight"_ustr,
3757         u".uno:ObjectBackOne"_ustr,
3758         u".uno:SendToBack"_ustr,
3759         u".uno:ObjectForwardOne"_ustr,
3760         u".uno:BringToFront"_ustr,
3761         u".uno:WrapRight"_ustr,
3762         u".uno:WrapThrough"_ustr,
3763         u".uno:WrapLeft"_ustr,
3764         u".uno:WrapIdeal"_ustr,
3765         u".uno:WrapOn"_ustr,
3766         u".uno:WrapOff"_ustr,
3767         u".uno:UpdateCurIndex"_ustr,
3768         u".uno:InsertCaptionDialog"_ustr,
3769         u".uno:FormatGroup"_ustr,
3770         u".uno:SplitTable"_ustr,
3771         u".uno:SplitCell"_ustr,
3772         u".uno:MergeCells"_ustr,
3773         u".uno:DeleteNote"_ustr,
3774         u".uno:AcceptChanges"_ustr,
3775         u".uno:FormatPaintbrush"_ustr,
3776         u".uno:SetDefault"_ustr,
3777         u".uno:ParaLeftToRight"_ustr,
3778         u".uno:ParaRightToLeft"_ustr,
3779         u".uno:ParaspaceIncrease"_ustr,
3780         u".uno:ParaspaceDecrease"_ustr,
3781         u".uno:AcceptTrackedChange"_ustr,
3782         u".uno:RejectTrackedChange"_ustr,
3783         u".uno:ShowResolvedAnnotations"_ustr,
3784         u".uno:InsertBreak"_ustr,
3785         u".uno:InsertEndnote"_ustr,
3786         u".uno:InsertFootnote"_ustr,
3787         u".uno:InsertReferenceField"_ustr,
3788         u".uno:InsertBookmark"_ustr,
3789         u".uno:InsertAuthoritiesEntry"_ustr,
3790         u".uno:InsertMultiIndex"_ustr,
3791         u".uno:InsertField"_ustr,
3792         u".uno:PageNumberWizard"_ustr,
3793         u".uno:InsertPageNumberField"_ustr,
3794         u".uno:InsertPageCountField"_ustr,
3795         u".uno:InsertDateField"_ustr,
3796         u".uno:InsertTitleField"_ustr,
3797         u".uno:InsertFieldCtrl"_ustr,
3798         u".uno:CharmapControl"_ustr,
3799         u".uno:EnterGroup"_ustr,
3800         u".uno:LeaveGroup"_ustr,
3801         u".uno:AlignUp"_ustr,
3802         u".uno:AlignMiddle"_ustr,
3803         u".uno:AlignDown"_ustr,
3804         u".uno:TraceChangeMode"_ustr,
3805         u".uno:Combine"_ustr,
3806         u".uno:Merge"_ustr,
3807         u".uno:Dismantle"_ustr,
3808         u".uno:Substract"_ustr,
3809         u".uno:DistributeSelection"_ustr,
3810         u".uno:Intersect"_ustr,
3811         u".uno:BorderInner"_ustr,
3812         u".uno:BorderOuter"_ustr,
3813         u".uno:FreezePanes"_ustr,
3814         u".uno:FreezePanesColumn"_ustr,
3815         u".uno:FreezePanesRow"_ustr,
3816         u".uno:Sidebar"_ustr,
3817         u".uno:SheetRightToLeft"_ustr,
3818         u".uno:RunMacro"_ustr,
3819         u".uno:SpacePara1"_ustr,
3820         u".uno:SpacePara15"_ustr,
3821         u".uno:SpacePara2"_ustr,
3822         u".uno:InsertSparkline"_ustr,
3823         u".uno:DeleteSparkline"_ustr,
3824         u".uno:DeleteSparklineGroup"_ustr,
3825         u".uno:EditSparklineGroup"_ustr,
3826         u".uno:EditSparkline"_ustr,
3827         u".uno:GroupSparklines"_ustr,
3828         u".uno:UngroupSparklines"_ustr,
3829         u".uno:FormatSparklineMenu"_ustr,
3830         u".uno:DataDataPilotRun"_ustr,
3831         u".uno:RecalcPivotTable"_ustr,
3832         u".uno:DeletePivotTable"_ustr,
3833         u".uno:Protect"_ustr,
3834         u".uno:UnsetCellsReadOnly"_ustr,
3835         u".uno:ContentControlProperties"_ustr,
3836         u".uno:InsertCheckboxContentControl"_ustr,
3837         u".uno:InsertContentControl"_ustr,
3838         u".uno:InsertDateContentControl"_ustr,
3839         u".uno:InsertDropdownContentControl"_ustr,
3840         u".uno:InsertPlainTextContentControl"_ustr,
3841         u".uno:InsertPictureContentControl"_ustr,
3842         u".uno:DataFilterAutoFilter"_ustr,
3843         u".uno:CellProtection"_ustr,
3844         u".uno:MoveKeepInsertMode"_ustr
3845     };
3846 
3847     util::URL aCommandURL;
3848     SfxViewShell* pViewShell = SfxViewShell::Current();
3849     SfxViewFrame* pViewFrame = pViewShell ? &pViewShell->GetViewFrame() : nullptr;
3850 
3851     // check if Frame-Controller were created.
3852     if (!pViewFrame)
3853     {
3854         SAL_WARN("lok", "iniUnoCommands: No Frame-Controller created.");
3855         return;
3856     }
3857 
3858     if (!xContext.is())
3859         xContext = comphelper::getProcessComponentContext();
3860     if (!xContext.is())
3861     {
3862         SAL_WARN("lok", "iniUnoCommands: Component context is not available");
3863         return;
3864     }
3865 
3866 #if !defined IOS && !defined ANDROID && !defined __EMSCRIPTEN__
3867     uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
3868     if (!xSEInitializer.is())
3869     {
3870         SAL_WARN("lok", "iniUnoCommands: XSEInitializer is not available");
3871         return;
3872     }
3873 
3874     uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext =
3875         xSEInitializer->createSecurityContext(OUString());
3876     if (!xSecurityContext.is())
3877     {
3878         SAL_WARN("lok", "iniUnoCommands: failed to create security context");
3879     }
3880 #endif
3881 
3882     SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool(pViewFrame);
3883     uno::Reference<util::XURLTransformer> xParser(util::URLTransformer::create(xContext));
3884 
3885     for (const auto & sUnoCommand : sUnoCommands)
3886     {
3887         aCommandURL.Complete = sUnoCommand;
3888         xParser->parseStrict(aCommandURL);
3889 
3890         // when null, this command is not supported by the given component
3891         // (like eg. Calc does not have ".uno:DefaultBullet" etc.)
3892         if (const SfxSlot* pSlot = rSlotPool.GetUnoSlot(aCommandURL.Path))
3893         {
3894             // Initialize slot to dispatch .uno: Command.
3895             pViewFrame->GetBindings().GetDispatch(pSlot, aCommandURL, false);
3896         }
3897     }
3898 }
3899 
3900 static int doc_getDocumentType (LibreOfficeKitDocument* pThis)
3901 {
3902     comphelper::ProfileZone aZone("doc_getDocumentType");
3903 
3904     SolarMutexGuard aGuard;
3905     return getDocumentType(pThis);
3906 }
3907 
3908 static int doc_getParts (LibreOfficeKitDocument* pThis)
3909 {
3910     comphelper::ProfileZone aZone("doc_getParts");
3911 
3912     SolarMutexGuard aGuard;
3913 
3914     ITiledRenderable* pDoc = getTiledRenderable(pThis);
3915     if (!pDoc)
3916     {
3917         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
3918         return 0;
3919     }
3920 
3921     return pDoc->getParts();
3922 }
3923 
3924 static int doc_getPart (LibreOfficeKitDocument* pThis)
3925 {
3926     comphelper::ProfileZone aZone("doc_getPart");
3927 
3928     SolarMutexGuard aGuard;
3929     SetLastExceptionMsg();
3930 
3931     ITiledRenderable* pDoc = getTiledRenderable(pThis);
3932     if (!pDoc)
3933     {
3934         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
3935         return 0;
3936     }
3937 
3938     return pDoc->getPart();
3939 }
3940 
3941 static void doc_setPartImpl(LibreOfficeKitDocument* pThis, int nPart, bool bAllowChangeFocus = true)
3942 {
3943     comphelper::ProfileZone aZone("doc_setPart");
3944 
3945     SolarMutexGuard aGuard;
3946     SetLastExceptionMsg();
3947 
3948     ITiledRenderable* pDoc = getTiledRenderable(pThis);
3949     if (!pDoc)
3950     {
3951         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
3952         return;
3953     }
3954 
3955     pDoc->setPart( nPart, bAllowChangeFocus );
3956 }
3957 
3958 static void doc_setPart(LibreOfficeKitDocument* pThis, int nPart)
3959 {
3960     doc_setPartImpl(pThis, nPart, true);
3961 }
3962 
3963 static char* doc_getPartInfo(LibreOfficeKitDocument* pThis, int nPart)
3964 {
3965     comphelper::ProfileZone aZone("doc_getPartInfo");
3966 
3967     SolarMutexGuard aGuard;
3968     ITiledRenderable* pDoc = getTiledRenderable(pThis);
3969     if (!pDoc)
3970     {
3971         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
3972         return nullptr;
3973     }
3974 
3975     return convertOUString(pDoc->getPartInfo(nPart));
3976 }
3977 
3978 static void doc_selectPart(LibreOfficeKitDocument* pThis, int nPart, int nSelect)
3979 {
3980     SolarMutexGuard aGuard;
3981     SetLastExceptionMsg();
3982 
3983     ITiledRenderable* pDoc = getTiledRenderable(pThis);
3984     if (!pDoc)
3985     {
3986         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
3987         return;
3988     }
3989 
3990     pDoc->selectPart( nPart, nSelect );
3991 }
3992 
3993 static void doc_moveSelectedParts(LibreOfficeKitDocument* pThis, int nPosition, bool bDuplicate)
3994 {
3995     SolarMutexGuard aGuard;
3996     SetLastExceptionMsg();
3997 
3998     ITiledRenderable* pDoc = getTiledRenderable(pThis);
3999     if (!pDoc)
4000     {
4001         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4002         return;
4003     }
4004 
4005     pDoc->moveSelectedParts(nPosition, bDuplicate);
4006 }
4007 
4008 static char* doc_getPartPageRectangles(LibreOfficeKitDocument* pThis)
4009 {
4010     comphelper::ProfileZone aZone("doc_getPartPageRectangles");
4011 
4012     SolarMutexGuard aGuard;
4013     SetLastExceptionMsg();
4014 
4015     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4016     if (!pDoc)
4017     {
4018         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4019         return nullptr;
4020     }
4021 
4022     return convertOUString(pDoc->getPartPageRectangles());
4023 }
4024 
4025 static char* doc_getA11yFocusedParagraph(LibreOfficeKitDocument* pThis)
4026 {
4027     SolarMutexGuard aGuard;
4028     SetLastExceptionMsg();
4029 
4030     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4031     if (!pDoc)
4032     {
4033         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4034         return nullptr;
4035     }
4036 
4037     if (SfxViewShell* pViewShell = SfxViewShell::Current())
4038     {
4039         return convertOUString(pViewShell->getA11yFocusedParagraph());
4040 
4041     }
4042     return nullptr;
4043 }
4044 
4045 static int  doc_getA11yCaretPosition(LibreOfficeKitDocument* pThis)
4046 {
4047     SolarMutexGuard aGuard;
4048     SetLastExceptionMsg();
4049 
4050     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4051     if (!pDoc)
4052     {
4053         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4054         return -1;
4055     }
4056     if (SfxViewShell* pViewShell = SfxViewShell::Current())
4057     {
4058         return pViewShell->getA11yCaretPosition();
4059 
4060     }
4061     return -1;
4062 
4063 }
4064 
4065 static char* doc_getPartName(LibreOfficeKitDocument* pThis, int nPart)
4066 {
4067     comphelper::ProfileZone aZone("doc_getPartName");
4068 
4069     SolarMutexGuard aGuard;
4070     SetLastExceptionMsg();
4071 
4072     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4073     if (!pDoc)
4074     {
4075         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4076         return nullptr;
4077     }
4078 
4079     return convertOUString(pDoc->getPartName(nPart));
4080 }
4081 
4082 static char* doc_getPartHash(LibreOfficeKitDocument* pThis, int nPart)
4083 {
4084     comphelper::ProfileZone aZone("doc_getPartHash");
4085 
4086     SolarMutexGuard aGuard;
4087     SetLastExceptionMsg();
4088 
4089     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4090     if (!pDoc)
4091     {
4092         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4093         return nullptr;
4094     }
4095 
4096     return convertOUString(pDoc->getPartHash(nPart));
4097 }
4098 
4099 static void doc_setPartMode(LibreOfficeKitDocument* pThis,
4100                             int nPartMode)
4101 {
4102     comphelper::ProfileZone aZone("doc_setPartMode");
4103 
4104     SolarMutexGuard aGuard;
4105     SetLastExceptionMsg();
4106 
4107     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4108     if (!pDoc)
4109     {
4110         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4111         return;
4112     }
4113 
4114 
4115     int nCurrentPart = pDoc->getPart();
4116 
4117     pDoc->setPartMode(nPartMode);
4118 
4119     // We need to make sure the internal state is updated, just changing the mode
4120     // might not update the relevant shells (i.e. impress will keep rendering the
4121     // previous mode unless we do this).
4122     // TODO: we might want to do this within the relevant components rather than
4123     // here, but that's also dependent on how we implement embedded object
4124     // rendering I guess?
4125     // TODO: we could be clever and e.g. set to 0 when we change to/from
4126     // embedded object mode, and not when changing between slide/notes/combined
4127     // modes?
4128     if ( nCurrentPart < pDoc->getParts() )
4129     {
4130         pDoc->setPart( nCurrentPart );
4131     }
4132     else
4133     {
4134         pDoc->setPart( 0 );
4135     }
4136 }
4137 
4138 static int doc_getEditMode(LibreOfficeKitDocument* pThis)
4139 {
4140     comphelper::ProfileZone aZone("doc_getEditMode");
4141 
4142     SolarMutexGuard aGuard;
4143     SetLastExceptionMsg();
4144 
4145     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4146     if (!pDoc)
4147     {
4148         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4149         return 0;
4150     }
4151 
4152     return pDoc->getEditMode();
4153 }
4154 
4155 static void doc_paintTile(LibreOfficeKitDocument* pThis,
4156                           unsigned char* pBuffer,
4157                           const int nCanvasWidth, const int nCanvasHeight,
4158                           const int nTilePosX, const int nTilePosY,
4159                           const int nTileWidth, const int nTileHeight)
4160 {
4161     comphelper::ProfileZone aZone("doc_paintTile");
4162 
4163     SolarMutexGuard aGuard;
4164     SetLastExceptionMsg();
4165 
4166     SAL_INFO( "lok.tiledrendering", "paintTile: painting [" << nTileWidth << "x" << nTileHeight <<
4167               "]@(" << nTilePosX << ", " << nTilePosY << ") to [" <<
4168               nCanvasWidth << "x" << nCanvasHeight << "]px" );
4169 
4170     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4171     if (!pDoc)
4172     {
4173         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4174         return;
4175     }
4176 
4177 #if defined(UNX) && !defined(MACOSX) || defined(_WIN32)
4178 
4179     // Painting of zoomed or HiDPI spreadsheets is special, we actually draw everything at 100%,
4180     // and only set cairo's (or CoreGraphic's, in the iOS case) scale factor accordingly, so that
4181     // everything is painted bigger or smaller. This is different to what Calc's internal scaling
4182     // would do - because that one is trying to fit the lines between cells to integer multiples of
4183     // pixels.
4184     comphelper::ScopeGuard dpiScaleGuard([]() { comphelper::LibreOfficeKit::setDPIScale(1.0); });
4185 
4186 #if defined(IOS)
4187     double fDPIScale = 1.0;
4188 
4189     // Onine uses the LOK_TILEMODE_RGBA by default so flip the normal flags
4190     // to kCGImageAlphaPremultipliedLast | kCGImageByteOrder32Big
4191     CGContextRef pCGContext = CGBitmapContextCreate(pBuffer, nCanvasWidth, nCanvasHeight, 8,
4192                                                     nCanvasWidth * 4, CGColorSpaceCreateDeviceRGB(),
4193                                                     kCGImageAlphaPremultipliedLast | kCGImageByteOrder32Big);
4194 
4195     CGContextTranslateCTM(pCGContext, 0, nCanvasHeight);
4196     CGContextScaleCTM(pCGContext, fDPIScale, -fDPIScale);
4197 
4198     SAL_INFO( "lok.tiledrendering", "doc_paintTile: painting [" << nTileWidth << "x" << nTileHeight <<
4199               "]@(" << nTilePosX << ", " << nTilePosY << ") to [" <<
4200               nCanvasWidth << "x" << nCanvasHeight << "]px" );
4201 
4202     Size aCanvasSize(nCanvasWidth, nCanvasHeight);
4203 
4204     SystemGraphicsData aData;
4205     aData.rCGContext = reinterpret_cast<CGContextRef>(pCGContext);
4206 
4207     ScopedVclPtrInstance<VirtualDevice> pDevice(aData, Size(1, 1), DeviceFormat::WITHOUT_ALPHA);
4208     pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
4209     pDevice->SetOutputSizePixel(aCanvasSize);
4210     pDoc->paintTile(*pDevice, aCanvasSize.Width(), aCanvasSize.Height(),
4211                     nTilePosX, nTilePosY, nTileWidth, nTileHeight);
4212 
4213     CGContextRelease(pCGContext);
4214 #else
4215     ScopedVclPtrInstance< VirtualDevice > pDevice(DeviceFormat::WITHOUT_ALPHA);
4216 
4217     // Set background to transparent by default.
4218     pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
4219 
4220     pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(
4221                 Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(),
4222                 pBuffer);
4223 
4224     pDoc->paintTile(*pDevice, nCanvasWidth, nCanvasHeight,
4225                     nTilePosX, nTilePosY, nTileWidth, nTileHeight);
4226 
4227     static bool bDebug = getenv("LOK_DEBUG_TILES") != nullptr;
4228     if (bDebug)
4229     {
4230         // Draw a small red rectangle in the top left corner so that it's easy to see where a new tile begins.
4231         tools::Rectangle aRect(0, 0, 5, 5);
4232         aRect = pDevice->PixelToLogic(aRect);
4233         pDevice->Push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
4234         pDevice->SetFillColor(COL_LIGHTRED);
4235         pDevice->SetLineColor();
4236         pDevice->DrawRect(aRect);
4237         pDevice->Pop();
4238     }
4239 
4240 #ifdef _WIN32
4241     // pBuffer was not used there
4242     pDevice->EnableMapMode(false);
4243     BitmapEx aBmpEx = pDevice->GetBitmapEx({ 0, 0 }, { nCanvasWidth, nCanvasHeight });
4244     Bitmap aBmp = aBmpEx.GetBitmap();
4245     AlphaMask aAlpha = aBmpEx.GetAlphaMask();
4246     BitmapScopedReadAccess sraBmp(aBmp);
4247     BitmapScopedReadAccess sraAlpha(aAlpha);
4248 
4249     assert(sraBmp->Height() == nCanvasHeight);
4250     assert(sraBmp->Width() == nCanvasWidth);
4251     assert(!sraAlpha || sraBmp->Height() == sraAlpha->Height());
4252     assert(!sraAlpha || sraBmp->Width() == sraAlpha->Width());
4253     auto p = pBuffer;
4254     for (tools::Long y = 0; y < sraBmp->Height(); ++y)
4255     {
4256         Scanline dataBmp = sraBmp->GetScanline(y);
4257         Scanline dataAlpha = sraAlpha ? sraAlpha->GetScanline(y) : nullptr;
4258         for (tools::Long x = 0; x < sraBmp->Width(); ++x)
4259         {
4260             BitmapColor color = sraBmp->GetPixelFromData(dataBmp, x);
4261             sal_uInt8 alpha = dataAlpha ? sraAlpha->GetPixelFromData(dataAlpha, x).GetBlue() : 255;
4262             *p++ = color.GetBlue();
4263             *p++ = color.GetGreen();
4264             *p++ = color.GetRed();
4265             *p++ = alpha;
4266         }
4267     }
4268 #endif
4269 #endif
4270 
4271 #else
4272     (void) pBuffer;
4273 #endif
4274 }
4275 
4276 static void doc_paintPartTile(LibreOfficeKitDocument* pThis,
4277                               unsigned char* pBuffer,
4278                               const int nPart,
4279                               const int nMode,
4280                               const int nCanvasWidth, const int nCanvasHeight,
4281                               const int nTilePosX, const int nTilePosY,
4282                               const int nTileWidth, const int nTileHeight)
4283 {
4284     comphelper::ProfileZone aZone("doc_paintPartTile");
4285 
4286     SolarMutexGuard aGuard;
4287     SetLastExceptionMsg();
4288 
4289     SAL_INFO( "lok.tiledrendering", "paintPartTile: painting @ " << nPart << " : " << nMode << " ["
4290                << nTileWidth << "x" << nTileHeight << "]@("
4291                << nTilePosX << ", " << nTilePosY << ") to ["
4292                << nCanvasWidth << "x" << nCanvasHeight << "]px" );
4293 
4294     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
4295     int nOrigViewId = doc_getView(pThis);
4296 
4297     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4298     if (!pDoc)
4299     {
4300         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4301         return;
4302     }
4303 
4304     if (nOrigViewId < 0)
4305     {
4306         // tile painting always needs a SfxViewShell::Current(), but actually
4307         // it does not really matter which one - all of them should paint the
4308         // same thing. It's important to get a view for the correct document,
4309         // though.
4310         // doc_getViewsCount() returns the count of views for the document in the current view.
4311         int viewCount = doc_getViewsCount(pThis);
4312         if (viewCount == 0)
4313             return;
4314 
4315         std::vector<int> viewIds(viewCount);
4316         doc_getViewIds(pThis, viewIds.data(), viewCount);
4317 
4318         nOrigViewId = viewIds[0];
4319         doc_setView(pThis, nOrigViewId);
4320     }
4321 
4322     // Disable callbacks while we are painting.
4323     if (nOrigViewId >= 0)
4324     {
4325         const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nOrigViewId);
4326         if (handlerIt != pDocument->mpCallbackFlushHandlers.end())
4327             handlerIt->second->disableCallbacks();
4328     }
4329 
4330     try
4331     {
4332         // Text documents have a single coordinate system; don't change part.
4333         int nOrigPart = 0;
4334         const int aType = doc_getDocumentType(pThis);
4335         const bool isText = (aType == LOK_DOCTYPE_TEXT);
4336         const bool isCalc = (aType == LOK_DOCTYPE_SPREADSHEET);
4337         int nOrigEditMode = 0;
4338         bool bPaintTextEdit = true;
4339         int nViewId = nOrigViewId;
4340         int nLastNonEditorView = -1;
4341         int nViewMatchingMode = -1;
4342         SfxViewShell* pCurrentViewShell = SfxViewShell::Current();
4343 
4344         if (!isText)
4345         {
4346             // Check if just switching to another view is enough, that has
4347             // less side-effects.
4348             if (nPart != doc_getPart(pThis) || nMode != pDoc->getEditMode())
4349             {
4350                 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
4351                 while (pViewShell)
4352                 {
4353                     bool bIsInEdit = pViewShell->GetDrawView() &&
4354                         pViewShell->GetDrawView()->GetTextEditOutliner();
4355 
4356                     OString sCurrentViewRenderState = pDoc->getViewRenderState(pCurrentViewShell);
4357                     OString sNewRenderState = pDoc->getViewRenderState(pViewShell);
4358 
4359                     if (sCurrentViewRenderState == sNewRenderState && !bIsInEdit)
4360                         nLastNonEditorView = pViewShell->GetViewShellId().get();
4361 
4362                     if (pViewShell->getPart() == nPart &&
4363                         pViewShell->getEditMode() == nMode &&
4364                         sCurrentViewRenderState == sNewRenderState &&
4365                         !bIsInEdit)
4366                     {
4367                         nViewId = pViewShell->GetViewShellId().get();
4368                         nViewMatchingMode = nViewId;
4369                         nLastNonEditorView = nViewId;
4370                         doc_setView(pThis, nViewId);
4371                         break;
4372                     }
4373                     else if (pViewShell->getEditMode() == nMode && sCurrentViewRenderState == sNewRenderState && !bIsInEdit)
4374                     {
4375                         nViewMatchingMode = pViewShell->GetViewShellId().get();
4376                     }
4377 
4378                     pViewShell = SfxViewShell::GetNext(*pViewShell);
4379                 }
4380             }
4381 
4382             // if not found view with correct part
4383             // - at least avoid rendering active textbox, This is for Impress.
4384             // - prefer view with the same mode
4385             if (nViewMatchingMode >= 0 && nViewMatchingMode != nViewId)
4386             {
4387                 nViewId = nViewMatchingMode;
4388                 doc_setView(pThis, nViewId);
4389             }
4390             else if (!isCalc && nLastNonEditorView >= 0 && nLastNonEditorView != nViewId &&
4391                 pCurrentViewShell && pCurrentViewShell->GetDrawView() &&
4392                 pCurrentViewShell->GetDrawView()->GetTextEditOutliner())
4393             {
4394                 nViewId = nLastNonEditorView;
4395                 doc_setView(pThis, nViewId);
4396             }
4397 
4398             // Disable callbacks while we are painting - after setting the view
4399             if (nViewId != nOrigViewId && nViewId >= 0)
4400             {
4401                 const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nViewId);
4402                 if (handlerIt != pDocument->mpCallbackFlushHandlers.end())
4403                     handlerIt->second->disableCallbacks();
4404             }
4405 
4406             nOrigPart = doc_getPart(pThis);
4407             if (nPart != nOrigPart)
4408             {
4409                 doc_setPartImpl(pThis, nPart, false);
4410             }
4411 
4412             nOrigEditMode = pDoc->getEditMode();
4413             if (nOrigEditMode != nMode)
4414             {
4415                 SfxLokHelper::setEditMode(nMode, pDoc);
4416             }
4417 
4418             bPaintTextEdit = (nPart == nOrigPart && nMode == nOrigEditMode);
4419             pDoc->setPaintTextEdit(bPaintTextEdit);
4420         }
4421 
4422         doc_paintTile(pThis, pBuffer, nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight);
4423 
4424         if (!isText)
4425         {
4426             pDoc->setPaintTextEdit(true);
4427 
4428             if (nMode != nOrigEditMode)
4429             {
4430                 SfxLokHelper::setEditMode(nOrigEditMode, pDoc);
4431             }
4432 
4433             if (nPart != nOrigPart)
4434             {
4435                 doc_setPartImpl(pThis, nOrigPart, false);
4436             }
4437 
4438             if (nViewId != nOrigViewId)
4439             {
4440                 if (nViewId >= 0)
4441                 {
4442                     const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nViewId);
4443                     if (handlerIt != pDocument->mpCallbackFlushHandlers.end())
4444                         handlerIt->second->enableCallbacks();
4445                 }
4446 
4447                 doc_setView(pThis, nOrigViewId);
4448             }
4449         }
4450     }
4451     catch (const std::exception&)
4452     {
4453         // Nothing to do but restore the PartTilePainting flag.
4454     }
4455 
4456     if (nOrigViewId >= 0)
4457     {
4458         const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nOrigViewId);
4459         if (handlerIt != pDocument->mpCallbackFlushHandlers.end())
4460             handlerIt->second->enableCallbacks();
4461     }
4462 }
4463 
4464 static int doc_getTileMode(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/)
4465 {
4466     SetLastExceptionMsg();
4467 #if ENABLE_CAIRO_RGBA || defined IOS
4468     return LOK_TILEMODE_RGBA;
4469 #else
4470     return LOK_TILEMODE_BGRA;
4471 #endif
4472 }
4473 
4474 static void doc_getDocumentSize(LibreOfficeKitDocument* pThis,
4475                                 long* pWidth,
4476                                 long* pHeight)
4477 {
4478     comphelper::ProfileZone aZone("doc_getDocumentSize");
4479 
4480     SolarMutexGuard aGuard;
4481     SetLastExceptionMsg();
4482 
4483     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4484     if (pDoc)
4485     {
4486         Size aDocumentSize = pDoc->getDocumentSize();
4487         *pWidth = aDocumentSize.Width();
4488         *pHeight = aDocumentSize.Height();
4489     }
4490     else
4491     {
4492         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4493     }
4494 }
4495 
4496 static void doc_getDataArea(LibreOfficeKitDocument* pThis,
4497                             long nTab,
4498                             long* pCol,
4499                             long* pRow)
4500 {
4501     comphelper::ProfileZone aZone("doc_getDataArea");
4502 
4503     SolarMutexGuard aGuard;
4504     SetLastExceptionMsg();
4505 
4506     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4507     if (pDoc)
4508     {
4509         Size aDocumentSize = pDoc->getDataArea(nTab);
4510         *pCol = aDocumentSize.Width();
4511         *pRow = aDocumentSize.Height();
4512     }
4513     else
4514     {
4515         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4516     }
4517 }
4518 
4519 static void doc_initializeForRendering(LibreOfficeKitDocument* pThis,
4520                                        const char* pArguments)
4521 {
4522     comphelper::ProfileZone aZone("doc_initializeForRendering");
4523 
4524     SolarMutexGuard aGuard;
4525     SetLastExceptionMsg();
4526 
4527     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4528     if (pDoc)
4529     {
4530         doc_iniUnoCommands();
4531         pDoc->initializeForTiledRendering(
4532                 comphelper::containerToSequence(jsonToPropertyValuesVector(pArguments)));
4533     }
4534 }
4535 
4536 static void doc_registerCallback(LibreOfficeKitDocument* pThis,
4537                                  LibreOfficeKitCallback pCallback,
4538                                  void* pData)
4539 {
4540     SolarMutexGuard aGuard;
4541     SetLastExceptionMsg();
4542 
4543     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
4544 
4545     const int nView = SfxLokHelper::getView();
4546     if (nView < 0)
4547         return;
4548 
4549     const size_t nId = nView;
4550     if (pCallback != nullptr)
4551     {
4552         for (auto& pair : pDocument->mpCallbackFlushHandlers)
4553         {
4554             if (pair.first == nId)
4555                 continue;
4556 
4557             pair.second->addViewStates(nView);
4558         }
4559     }
4560     else
4561     {
4562         for (auto& pair : pDocument->mpCallbackFlushHandlers)
4563         {
4564             if (pair.first == nId)
4565                 continue;
4566 
4567             pair.second->removeViewStates(nView);
4568         }
4569     }
4570 
4571     pDocument->mpCallbackFlushHandlers[nView] = std::make_shared<CallbackFlushHandler>(pThis, pCallback, pData);
4572 
4573     if (pCallback != nullptr)
4574     {
4575         for (const auto& pair : pDocument->mpCallbackFlushHandlers)
4576         {
4577             if (pair.first == nId)
4578                 continue;
4579 
4580             pDocument->mpCallbackFlushHandlers[nView]->addViewStates(pair.first);
4581         }
4582 
4583         if (SfxViewShell* pViewShell = SfxViewShell::Current())
4584         {
4585             pDocument->mpCallbackFlushHandlers[nView]->setViewId(pViewShell->GetViewShellId().get());
4586             pViewShell->setLibreOfficeKitViewCallback(pDocument->mpCallbackFlushHandlers[nView].get());
4587         }
4588 
4589         if (!pDocument->maFontsMissing.empty())
4590         {
4591             OString sPayload = "{ \"fontsmissing\": [ "_ostr;
4592             bool bFirst = true;
4593             for (const auto &f : pDocument->maFontsMissing)
4594             {
4595                 if (bFirst)
4596                     bFirst = false;
4597                 else
4598                     sPayload += ", ";
4599                 sPayload += "\"" + f.toUtf8() + "\"";
4600             }
4601             sPayload += " ] }";
4602             pCallback(LOK_CALLBACK_FONTS_MISSING, sPayload.getStr(), pData);
4603             pDocument->maFontsMissing.clear();
4604         }
4605     }
4606     else
4607     {
4608         if (SfxViewShell* pViewShell = SfxViewShell::Current())
4609         {
4610             pViewShell->setLibreOfficeKitViewCallback(nullptr);
4611             pDocument->mpCallbackFlushHandlers[nView]->setViewId(-1);
4612         }
4613     }
4614 }
4615 
4616 /// Returns the JSON representation of all the comments in the document
4617 static char* getPostIts(LibreOfficeKitDocument* pThis)
4618 {
4619     SetLastExceptionMsg();
4620     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4621     if (!pDoc)
4622     {
4623         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4624         return nullptr;
4625     }
4626     tools::JsonWriter aJsonWriter;
4627     pDoc->getPostIts(aJsonWriter);
4628     return convertOString(aJsonWriter.finishAndGetAsOString());
4629 }
4630 
4631 /// Returns the JSON representation of the positions of all the comments in the document
4632 static char* getPostItsPos(LibreOfficeKitDocument* pThis)
4633 {
4634     SetLastExceptionMsg();
4635     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4636     if (!pDoc)
4637     {
4638         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4639         return nullptr;
4640     }
4641     tools::JsonWriter aJsonWriter;
4642     pDoc->getPostItsPos(aJsonWriter);
4643     return convertOString(aJsonWriter.finishAndGetAsOString());
4644 }
4645 
4646 static char* getRulerState(LibreOfficeKitDocument* pThis)
4647 {
4648     SetLastExceptionMsg();
4649     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4650     if (!pDoc)
4651     {
4652         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4653         return nullptr;
4654     }
4655     tools::JsonWriter aJsonWriter;
4656     pDoc->getRulerState(aJsonWriter);
4657     return convertOString(aJsonWriter.finishAndGetAsOString());
4658 }
4659 
4660 static void doc_postKeyEvent(LibreOfficeKitDocument* pThis, int nType, int nCharCode, int nKeyCode)
4661 {
4662     comphelper::ProfileZone aZone("doc_postKeyEvent");
4663 
4664     SolarMutexGuard aGuard;
4665     SetLastExceptionMsg();
4666 
4667     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4668     if (!pDoc)
4669     {
4670         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4671         return;
4672     }
4673 
4674     try
4675     {
4676         pDoc->postKeyEvent(nType, nCharCode, nKeyCode);
4677     }
4678     catch (const uno::Exception& exception)
4679     {
4680         SetLastExceptionMsg(exception.Message);
4681         SAL_INFO("lok", "Failed to postKeyEvent " << exception.Message);
4682     }
4683 }
4684 
4685 static void doc_setBlockedCommandList(LibreOfficeKitDocument* /*pThis*/, int nViewId, const char* blockedCommandList)
4686 {
4687     SolarMutexGuard aGuard;
4688     SfxLokHelper::setBlockedCommandList(nViewId, blockedCommandList);
4689 }
4690 
4691 static void doc_postWindowExtTextInputEvent(LibreOfficeKitDocument* pThis, unsigned nWindowId, int nType, const char* pText)
4692 {
4693     comphelper::ProfileZone aZone("doc_postWindowExtTextInputEvent");
4694 
4695     SolarMutexGuard aGuard;
4696     VclPtr<vcl::Window> pWindow;
4697     if (nWindowId == 0)
4698     {
4699         ITiledRenderable* pDoc = getTiledRenderable(pThis);
4700         if (!pDoc)
4701         {
4702             SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4703             return;
4704         }
4705         pWindow = pDoc->getDocWindow();
4706     }
4707     else
4708     {
4709         pWindow = vcl::Window::FindLOKWindow(nWindowId);
4710     }
4711 
4712     if (!pWindow)
4713     {
4714         SetLastExceptionMsg("No window found for window id: " + OUString::number(nWindowId));
4715         return;
4716     }
4717 
4718     SfxLokHelper::postExtTextEventAsync(pWindow, nType, OUString::fromUtf8(std::string_view(pText, strlen(pText))));
4719 }
4720 
4721 static char* doc_hyperlinkInfoAtPosition(LibreOfficeKitDocument* pThis, int x, int y)
4722 {
4723     SolarMutexGuard aGuard;
4724 
4725     ITiledRenderable* pDoc = getTiledRenderable(pThis);
4726     if (!pDoc)
4727     {
4728         SetLastExceptionMsg("Document doesn't support tiled rendering");
4729         return nullptr;
4730     }
4731 
4732     return convertOUString(pDoc->hyperlinkInfoAtPosition(x, y));
4733 }
4734 
4735 static void doc_removeTextContext(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId, int nCharBefore, int nCharAfter)
4736 {
4737     SolarMutexGuard aGuard;
4738     VclPtr<vcl::Window> pWindow;
4739     if (nLOKWindowId == 0)
4740     {
4741         ITiledRenderable* pDoc = getTiledRenderable(pThis);
4742         if (!pDoc)
4743         {
4744             SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
4745             return;
4746         }
4747         pWindow = pDoc->getDocWindow();
4748     }
4749     else
4750     {
4751         pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
4752     }
4753 
4754     if (!pWindow)
4755     {
4756         SetLastExceptionMsg("No window found for window id: " + OUString::number(nLOKWindowId));
4757         return;
4758     }
4759 
4760     // Annoyingly - backspace and delete are handled in the apps via an accelerator
4761     // which are PostMessage'd by SfxViewShell::ExecKey_Impl so to stay in the same
4762     // order we do this synchronously here, unless we're in a dialog.
4763     if (nCharBefore > 0)
4764     {
4765         // backspace
4766         if (nLOKWindowId == 0)
4767         {
4768             KeyEvent aEvt(8, KEY_BACKSPACE);
4769             for (int i = 0; i < nCharBefore; ++i)
4770                 pWindow->KeyInput(aEvt);
4771         }
4772         else
4773             SfxLokHelper::postKeyEventAsync(pWindow, LOK_KEYEVENT_KEYINPUT, 8, KEY_BACKSPACE, nCharBefore - 1);
4774     }
4775 
4776     if (nCharAfter > 0)
4777     {
4778         // delete (forward)
4779         if (nLOKWindowId == 0)
4780         {
4781             KeyEvent aEvt(46, KEY_DELETE);
4782             for (int i = 0; i < nCharAfter; ++i)
4783                 pWindow->KeyInput(aEvt);
4784         }
4785         else
4786             SfxLokHelper::postKeyEventAsync(pWindow, LOK_KEYEVENT_KEYINPUT, 46, KEY_DELETE, nCharAfter - 1);
4787     }
4788 }
4789 
4790 static void doc_postWindowKeyEvent(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, int nType, int nCharCode, int nKeyCode)
4791 {
4792     comphelper::ProfileZone aZone("doc_postWindowKeyEvent");
4793 
4794     SolarMutexGuard aGuard;
4795     SetLastExceptionMsg();
4796 
4797     VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
4798     if (!pWindow)
4799     {
4800         SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
4801         return;
4802     }
4803 
4804     KeyEvent aEvent(nCharCode, nKeyCode, 0);
4805 
4806     switch (nType)
4807     {
4808         case LOK_KEYEVENT_KEYINPUT:
4809             Application::PostKeyEvent(VclEventId::WindowKeyInput, pWindow, &aEvent);
4810             break;
4811         case LOK_KEYEVENT_KEYUP:
4812             Application::PostKeyEvent(VclEventId::WindowKeyUp, pWindow, &aEvent);
4813             break;
4814         default:
4815             assert(false);
4816             break;
4817     }
4818 }
4819 
4820 // To be an exportable selection, there must be something selected and that
4821 // selection can't be "ScCellObj" which doesn't can't provide a svg.
4822 //
4823 // Typically a problem arises when double clicking a shape in calc. The 1st
4824 // click selects the shape, triggering generation of a preview, but the second
4825 // shape enters into edit mode before doc_renderShapeSelection has a chance to
4826 // fire, at which point the shape is no longer selected. Rather than generate
4827 // an error just return a 0 length result if there is no shape selected, so we
4828 // continue to generate an error if a shape is selected, but could not provide
4829 // an svg.
4830 static bool doc_hasShapeSelection(const css::uno::Reference<css::lang::XComponent>& rComponent)
4831 {
4832     uno::Reference<frame::XModel> xModel(rComponent, uno::UNO_QUERY);
4833     if (!xModel.is())
4834         return false;
4835 
4836     uno::Reference<frame::XController> xController(xModel->getCurrentController());
4837     if (!xController.is())
4838         return false;
4839 
4840     uno::Reference<view::XSelectionSupplier> xSelectionSupplier(xController, uno::UNO_QUERY);
4841     if (!xSelectionSupplier.is())
4842         return false;
4843 
4844     Any selection = xSelectionSupplier->getSelection();
4845     uno::Reference<lang::XServiceInfo> xSelection;
4846     selection >>= xSelection;
4847 
4848     return xSelection && xSelection->getImplementationName() != "ScCellObj";
4849 }
4850 
4851 static size_t doc_renderShapeSelection(LibreOfficeKitDocument* pThis, char** pOutput)
4852 {
4853     comphelper::ProfileZone aZone("doc_renderShapeSelection");
4854 
4855     SolarMutexGuard aGuard;
4856     SetLastExceptionMsg();
4857 
4858     LokChartHelper aChartHelper(SfxViewShell::Current());
4859 
4860     if (aChartHelper.GetWindow())
4861         return 0;
4862 
4863     try
4864     {
4865         LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
4866 
4867         if (!doc_hasShapeSelection(pDocument->mxComponent))
4868             return 0;
4869 
4870         uno::Reference<frame::XStorable> xStorable(pDocument->mxComponent, uno::UNO_QUERY_THROW);
4871 
4872         SvMemoryStream aOutStream;
4873         uno::Reference<io::XOutputStream> xOut = new utl::OOutputStreamWrapper(aOutStream);
4874 
4875         utl::MediaDescriptor aMediaDescriptor;
4876         switch (doc_getDocumentType(pThis))
4877         {
4878             case LOK_DOCTYPE_PRESENTATION:
4879                 aMediaDescriptor[u"FilterName"_ustr] <<= u"impress_svg_Export"_ustr;
4880                 break;
4881             case LOK_DOCTYPE_DRAWING:
4882                 aMediaDescriptor[u"FilterName"_ustr] <<= u"draw_svg_Export"_ustr;
4883                 break;
4884             case LOK_DOCTYPE_TEXT:
4885                 aMediaDescriptor[u"FilterName"_ustr] <<= u"writer_svg_Export"_ustr;
4886                 break;
4887             case LOK_DOCTYPE_SPREADSHEET:
4888                 aMediaDescriptor[u"FilterName"_ustr] <<= u"calc_svg_Export"_ustr;
4889                 break;
4890             default:
4891                 SAL_WARN("lok", "Failed to render shape selection: Document type is not supported");
4892         }
4893         aMediaDescriptor[u"SelectionOnly"_ustr] <<= true;
4894         aMediaDescriptor[u"OutputStream"_ustr] <<= xOut;
4895         aMediaDescriptor[u"IsPreview"_ustr] <<= true; // will down-scale graphics
4896 
4897         xStorable->storeToURL(u"private:stream"_ustr, aMediaDescriptor.getAsConstPropertyValueList());
4898 
4899         if (pOutput)
4900         {
4901             const size_t nOutputSize = aOutStream.GetEndOfData();
4902             *pOutput = static_cast<char*>(malloc(nOutputSize));
4903             if (*pOutput)
4904             {
4905                 std::memcpy(*pOutput, aOutStream.GetData(), nOutputSize);
4906                 return nOutputSize;
4907             }
4908         }
4909     }
4910     catch (const uno::Exception& exception)
4911     {
4912         css::uno::Any exAny( cppu::getCaughtException() );
4913         SetLastExceptionMsg(exception.Message);
4914         SAL_WARN("lok", "Failed to render shape selection: " << exceptionToString(exAny));
4915     }
4916 
4917     return 0;
4918 }
4919 
4920 namespace {
4921 
4922 /** Class to react on finishing of a dispatched command.
4923 
4924     This will call a LOK_COMMAND_FINISHED callback when postUnoCommand was
4925     called with the parameter requesting the notification.
4926 
4927     @see LibreOfficeKitCallbackType::LOK_CALLBACK_UNO_COMMAND_RESULT.
4928 */
4929 class DispatchResultListener : public cppu::WeakImplHelper<css::frame::XDispatchResultListener>
4930 {
4931     const OString maCommand; ///< Command for which this is the result.
4932     const std::shared_ptr<CallbackFlushHandler> mpCallback; ///< Callback to call.
4933     const std::chrono::steady_clock::time_point mSaveTime; //< The time we started saving.
4934     const bool mbWasModified; //< Whether or not the document was modified before saving.
4935 
4936 public:
4937     DispatchResultListener(const char* pCommand, std::shared_ptr<CallbackFlushHandler> pCallback)
4938         : maCommand(pCommand)
4939         , mpCallback(std::move(pCallback))
4940         , mSaveTime(std::chrono::steady_clock::now())
4941         , mbWasModified(SfxObjectShell::Current()->IsModified())
4942     {
4943         assert(mpCallback);
4944     }
4945 
4946     virtual void SAL_CALL dispatchFinished(const css::frame::DispatchResultEvent& rEvent) override
4947     {
4948         tools::JsonWriter aJson;
4949         aJson.put("commandName", maCommand);
4950 
4951         if (rEvent.State != frame::DispatchResultState::DONTKNOW)
4952         {
4953             bool bSuccess = (rEvent.State == frame::DispatchResultState::SUCCESS);
4954             aJson.put("success", bSuccess);
4955         }
4956 
4957         unoAnyToJson(aJson, "result", rEvent.Result);
4958         aJson.put("wasModified", mbWasModified);
4959         aJson.put("startUnixTimeMics",
4960                   std::chrono::time_point_cast<std::chrono::microseconds>(mSaveTime)
4961                       .time_since_epoch()
4962                       .count());
4963         aJson.put("saveDurationMics", std::chrono::duration_cast<std::chrono::microseconds>(
4964                                           std::chrono::steady_clock::now() - mSaveTime)
4965                                           .count());
4966         mpCallback->queue(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
4967     }
4968 
4969     virtual void SAL_CALL disposing(const css::lang::EventObject&) override {}
4970 };
4971 
4972 } // anonymous namespace
4973 
4974 
4975 static void lcl_sendDialogEvent(unsigned long long int nWindowId, const char* pArguments)
4976 {
4977     SolarMutexGuard aGuard;
4978 
4979     StringMap aMap(jsdialog::jsonToStringMap(pArguments));
4980 
4981     if (aMap.find(u"id"_ustr) == aMap.end())
4982         return;
4983 
4984     sal_uInt64 nCurrentShellId = reinterpret_cast<sal_uInt64>(SfxViewShell::Current());
4985 
4986     try
4987     {
4988         OUString sControlId = aMap[u"id"_ustr];
4989         OUString sWindowId = OUString::number(nWindowId);
4990         OUString sCurrentShellId = OUString::number(nCurrentShellId);
4991 
4992         // special values for window id
4993         if (nWindowId == static_cast<unsigned long long int>(-1))
4994             sWindowId = sCurrentShellId + "sidebar";
4995         if (nWindowId == static_cast<unsigned long long int>(-2))
4996             sWindowId = sCurrentShellId + "notebookbar";
4997         if (nWindowId == static_cast<unsigned long long int>(-3))
4998             sWindowId = sCurrentShellId + "formulabar";
4999 
5000         // dialogs send own id but notebookbar and sidebar controls are remembered by SfxViewShell id
5001         if (jsdialog::ExecuteAction(sWindowId, sControlId, aMap))
5002             return;
5003 
5004         if (jsdialog::ExecuteAction(sCurrentShellId + "sidebar", sControlId, aMap))
5005             return;
5006         if (jsdialog::ExecuteAction(sCurrentShellId + "notebookbar", sControlId, aMap))
5007             return;
5008         if (jsdialog::ExecuteAction(sCurrentShellId + "formulabar", sControlId, aMap))
5009             return;
5010         // this is needed for dialogs shown before document is loaded: MacroWarning dialog, etc...
5011         // these dialogs are created with WindowId "0"
5012         if (!SfxViewShell::Current() && jsdialog::ExecuteAction(u"0"_ustr, sControlId, aMap))
5013             return;
5014 
5015         // force resend - used in mobile-wizard
5016         jsdialog::SendFullUpdate(sCurrentShellId + "sidebar", u"Panel"_ustr);
5017 
5018     } catch(...) {}
5019 }
5020 
5021 
5022 static void doc_sendDialogEvent(LibreOfficeKitDocument* /*pThis*/, unsigned long long int nWindowId, const char* pArguments)
5023 {
5024     lcl_sendDialogEvent(nWindowId, pArguments);
5025 }
5026 
5027 static void lo_sendDialogEvent(LibreOfficeKit* /*pThis*/, unsigned long long int nWindowId, const char* pArguments)
5028 {
5029     lcl_sendDialogEvent(nWindowId, pArguments);
5030 }
5031 
5032 static void lo_setOption(LibreOfficeKit* /*pThis*/, const char *pOption, const char* pValue)
5033 {
5034     static char* pCurrentSalLogOverride = nullptr;
5035 
5036     if (strcmp(pOption, "traceeventrecording") == 0)
5037     {
5038         if (strcmp(pValue, "start") == 0)
5039         {
5040             comphelper::TraceEvent::setBufferSizeAndCallback(100, TraceEventDumper::flushRecordings);
5041             comphelper::TraceEvent::startRecording();
5042             if (traceEventDumper == nullptr)
5043                 traceEventDumper = new TraceEventDumper();
5044         }
5045         else if (strcmp(pValue, "stop") == 0)
5046             comphelper::TraceEvent::stopRecording();
5047     }
5048     else if (strcmp(pOption, "sallogoverride") == 0)
5049     {
5050         if (pCurrentSalLogOverride != nullptr)
5051             free(pCurrentSalLogOverride);
5052         if (pValue == nullptr)
5053             pCurrentSalLogOverride = nullptr;
5054         else
5055             pCurrentSalLogOverride = strdup(pValue);
5056 
5057         if (pCurrentSalLogOverride == nullptr || pCurrentSalLogOverride[0] == '\0')
5058             sal_detail_set_log_selector(nullptr);
5059         else
5060             sal_detail_set_log_selector(pCurrentSalLogOverride);
5061     }
5062 #ifdef LINUX
5063     else if (strcmp(pOption, "addfont") == 0)
5064     {
5065         if (memcmp(pValue, "file://", 7) == 0)
5066             pValue += 7;
5067 
5068         int fd = open(pValue, O_RDONLY);
5069         if (fd == -1)
5070         {
5071             std::cerr << "Could not open font file '" << pValue << "': " << strerror(errno) << std::endl;
5072             return;
5073         }
5074 
5075         OUString sMagicFileName = "file:///:FD:/" + OUString::number(fd);
5076 
5077         OutputDevice *pDevice = Application::GetDefaultDevice();
5078         OutputDevice::ImplClearAllFontData(false);
5079         pDevice->AddTempDevFont(sMagicFileName, "");
5080         OutputDevice::ImplRefreshAllFontData(false);
5081     }
5082 #endif
5083 }
5084 
5085 static void lo_dumpState (LibreOfficeKit* pThis, const char* /* pOptions */, char** pState)
5086 {
5087     if (!pState)
5088         return;
5089 
5090     // NB. no SolarMutexGuard since this may be caused in some extremis / deadlock
5091     SetLastExceptionMsg();
5092 
5093     *pState = nullptr;
5094     OStringBuffer aState(4096*256);
5095 
5096     LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
5097 
5098     pLib->dumpState(aState);
5099 
5100     *pState = convertOString(aState.makeStringAndClear());
5101 }
5102 
5103 void LibLibreOffice_Impl::dumpState(rtl::OStringBuffer &rState)
5104 {
5105     rState.append("LibreOfficeKit state:"
5106                   "\n\tLastExceptionMsg:\t");
5107     rState.append(rtl::OUStringToOString(maLastExceptionMsg, RTL_TEXTENCODING_UTF8));
5108     rState.append("\n\tUnipoll:\t");
5109     rState.append(vcl::lok::isUnipoll() ? "yes" : "no: events on thread");
5110     rState.append("\n\tOptionalFeatures:\t0x");
5111     rState.append(static_cast<sal_Int64>(mOptionalFeatures), 16);
5112     rState.append("\n\tCallbackData:\t0x");
5113     rState.append(reinterpret_cast<sal_Int64>(mpCallback), 16);
5114     // TODO: dump mInteractionMap
5115     SfxLokHelper::dumpState(rState);
5116     vcl::lok::dumpState(rState);
5117 }
5118 
5119 static void doc_postUnoCommand(LibreOfficeKitDocument* pThis, const char* pCommand, const char* pArguments, bool bNotifyWhenFinished)
5120 {
5121     comphelper::ProfileZone aZone("doc_postUnoCommand");
5122 
5123     SolarMutexGuard aGuard;
5124     SetLastExceptionMsg();
5125 
5126     SfxObjectShell* pDocSh = SfxObjectShell::Current();
5127     OUString aCommand(pCommand, strlen(pCommand), RTL_TEXTENCODING_UTF8);
5128     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
5129 
5130     std::vector<beans::PropertyValue> aPropertyValuesVector(jsonToPropertyValuesVector(pArguments));
5131 
5132     if (!vcl::lok::isUnipoll())
5133     {
5134         beans::PropertyValue aSynchronMode;
5135         aSynchronMode.Name = u"SynchronMode"_ustr;
5136         aSynchronMode.Value <<= false;
5137         aPropertyValuesVector.push_back(aSynchronMode);
5138     }
5139 
5140     int nView = SfxLokHelper::getView();
5141     if (nView < 0)
5142         return;
5143 
5144     if (gImpl && aCommand == ".uno:ToggleOrientation")
5145     {
5146         ExecuteOrientationChange();
5147         return;
5148     }
5149 
5150     // handle potential interaction
5151     if (gImpl && aCommand == ".uno:Save")
5152     {
5153         // Check if saving a PDF file
5154         OUString aMimeType = lcl_getCurrentDocumentMimeType(pDocument);
5155         if (pDocSh && pDocSh->IsModified() && aMimeType == "application/pdf")
5156         {
5157             // If we have a PDF file (for saving annotations for example), we need
5158             // to run save-as to the same file as the opened document. Plain save
5159             // doesn't work as the PDF is not a "native" format.
5160             uno::Reference<frame::XStorable> xStorable(pDocument->mxComponent, uno::UNO_QUERY_THROW);
5161             OUString aURL = xStorable->getLocation();
5162             OString aURLUtf8 = OUStringToOString(aURL, RTL_TEXTENCODING_UTF8);
5163             bool bResult = doc_saveAs(pThis, aURLUtf8.getStr(), "pdf", nullptr);
5164 
5165             // Send the result of save
5166             tools::JsonWriter aJson;
5167             aJson.put("commandName", pCommand);
5168             aJson.put("success", bResult);
5169             pDocument->mpCallbackFlushHandlers[nView]->queue(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
5170             return;
5171         }
5172 
5173 
5174         rtl::Reference<LOKInteractionHandler> const pInteraction(
5175             new LOKInteractionHandler("save"_ostr, gImpl, pDocument));
5176         uno::Reference<task::XInteractionHandler2> const xInteraction(pInteraction);
5177 
5178         beans::PropertyValue aValue;
5179         aValue.Name = u"InteractionHandler"_ustr;
5180         aValue.Value <<= xInteraction;
5181         aPropertyValuesVector.push_back(aValue);
5182 
5183         bool bDontSaveIfUnmodified = false;
5184         std::erase_if(aPropertyValuesVector,
5185                                                    [&bDontSaveIfUnmodified](const beans::PropertyValue& aItem){
5186                                                        if (aItem.Name == "DontSaveIfUnmodified")
5187                                                        {
5188                                                            bDontSaveIfUnmodified = aItem.Value.get<bool>();
5189                                                            return true;
5190                                                        }
5191                                                        return false;
5192                                                    });
5193 
5194         // skip saving and tell the result via UNO_COMMAND_RESULT
5195         if (bDontSaveIfUnmodified && (!pDocSh || !pDocSh->IsModified()))
5196         {
5197             tools::JsonWriter aJson;
5198             aJson.put("commandName", pCommand);
5199             aJson.put("success", false);
5200             // Add the reason for not saving
5201             {
5202                 auto resultNode = aJson.startNode("result");
5203                 aJson.put("type", "string");
5204                 aJson.put("value", "unmodified");
5205             }
5206             pDocument->mpCallbackFlushHandlers[nView]->queue(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
5207             return;
5208         }
5209     }
5210     else if (gImpl && aCommand == ".uno:TransformDialog")
5211     {
5212         bool bNeedConversion = false;
5213         SfxViewShell* pViewShell = SfxViewShell::Current();
5214         LokChartHelper aChartHelper(pViewShell);
5215 
5216         if (aChartHelper.GetWindow() )
5217         {
5218             bNeedConversion = true;
5219         }
5220         else if (const SdrView* pView = pViewShell->GetDrawView())
5221         {
5222             if (OutputDevice* pOutputDevice = pView->GetFirstOutputDevice())
5223             {
5224                 bNeedConversion = (pOutputDevice->GetMapMode().GetMapUnit() == MapUnit::Map100thMM);
5225             }
5226         }
5227 
5228         if (bNeedConversion)
5229         {
5230             sal_Int32 value;
5231             for (beans::PropertyValue& rPropValue: aPropertyValuesVector)
5232             {
5233                 if (rPropValue.Name == "TransformPosX"
5234                         || rPropValue.Name == "TransformPosY"
5235                         || rPropValue.Name == "TransformWidth"
5236                         || rPropValue.Name == "TransformHeight"
5237                         || rPropValue.Name == "TransformRotationX"
5238                         || rPropValue.Name == "TransformRotationY")
5239                 {
5240                     rPropValue.Value >>= value;
5241                     value = o3tl::convert(value, o3tl::Length::twip, o3tl::Length::mm100);
5242                     rPropValue.Value <<= value;
5243                 }
5244             }
5245         }
5246 
5247         if (aChartHelper.GetWindow() && aPropertyValuesVector.size() > 0)
5248         {
5249             if (aPropertyValuesVector[0].Name != "Action")
5250             {
5251                 tools::Rectangle aChartBB = aChartHelper.GetChartBoundingBox();
5252 
5253                 int nLeft = o3tl::convert(aChartBB.Left(), o3tl::Length::twip, o3tl::Length::mm100);
5254                 int nTop = o3tl::convert(aChartBB.Top(), o3tl::Length::twip, o3tl::Length::mm100);
5255 
5256                 for (beans::PropertyValue& rPropValue: aPropertyValuesVector)
5257                 {
5258                     if (rPropValue.Name == "TransformPosX" || rPropValue.Name == "TransformRotationX")
5259                     {
5260                         auto const value = *o3tl::doAccess<sal_Int32>(rPropValue.Value);
5261                         rPropValue.Value <<= value - nLeft;
5262                     }
5263                     else if (rPropValue.Name == "TransformPosY" || rPropValue.Name == "TransformRotationY")
5264                     {
5265                         auto const value = *o3tl::doAccess<sal_Int32>(rPropValue.Value);
5266                         rPropValue.Value <<= value - nTop;
5267                     }
5268                 }
5269             }
5270             util::URL aCommandURL;
5271             aCommandURL.Path = u"LOKTransform"_ustr;
5272             css::uno::Reference<css::frame::XDispatch>& aChartDispatcher = aChartHelper.GetXDispatcher();
5273             aChartDispatcher->dispatch(aCommandURL, comphelper::containerToSequence(aPropertyValuesVector));
5274             return;
5275         }
5276     }
5277     else if (gImpl && aCommand == ".uno:LOKSidebarWriterPage")
5278     {
5279         setupSidebar(u"WriterPageDeck");
5280         return;
5281     }
5282     else if (gImpl && aCommand == ".uno:SidebarShow")
5283     {
5284         setupSidebar();
5285         return;
5286     }
5287     else if (gImpl && aCommand == ".uno:SidebarHide")
5288     {
5289         hideSidebar();
5290         return;
5291     }
5292 
5293     bool bResult = false;
5294     LokChartHelper aChartHelper(SfxViewShell::Current());
5295 
5296     if (aChartHelper.GetWindow() && aCommand != ".uno:Save" )
5297     {
5298         util::URL aCommandURL;
5299         aCommandURL.Path = aCommand.copy(5);
5300         css::uno::Reference<css::frame::XDispatch>& aChartDispatcher = aChartHelper.GetXDispatcher();
5301         aChartDispatcher->dispatch(aCommandURL, comphelper::containerToSequence(aPropertyValuesVector));
5302         return;
5303     }
5304     if (LokStarMathHelper aMathHelper(SfxViewShell::Current());
5305         aMathHelper.GetGraphicWindow() && aCommand != ".uno:Save")
5306     {
5307         aMathHelper.Dispatch(aCommand, comphelper::containerToSequence(aPropertyValuesVector));
5308         return;
5309     }
5310     if (bNotifyWhenFinished && pDocument->mpCallbackFlushHandlers.count(nView))
5311     {
5312         bResult = comphelper::dispatchCommand(aCommand, comphelper::containerToSequence(aPropertyValuesVector),
5313                 new DispatchResultListener(pCommand, pDocument->mpCallbackFlushHandlers[nView]));
5314     }
5315     else
5316         bResult = comphelper::dispatchCommand(aCommand, comphelper::containerToSequence(aPropertyValuesVector));
5317 
5318     if (!bResult)
5319     {
5320         SetLastExceptionMsg("Failed to dispatch " + aCommand);
5321     }
5322 }
5323 
5324 static void doc_postMouseEvent(LibreOfficeKitDocument* pThis, int nType, int nX, int nY, int nCount, int nButtons, int nModifier)
5325 {
5326     comphelper::ProfileZone aZone("doc_postMouseEvent");
5327 
5328     SolarMutexGuard aGuard;
5329     SetLastExceptionMsg();
5330 
5331     ITiledRenderable* pDoc = getTiledRenderable(pThis);
5332     if (!pDoc)
5333     {
5334         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
5335         return;
5336     }
5337     try
5338     {
5339         pDoc->postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier);
5340     }
5341     catch (const uno::Exception& exception)
5342     {
5343         SetLastExceptionMsg(exception.Message);
5344         SAL_INFO("lok", "Failed to postMouseEvent " << exception.Message);
5345     }
5346 }
5347 
5348 static void doc_postWindowMouseEvent(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, int nType, int nX, int nY, int nCount, int nButtons, int nModifier)
5349 {
5350     comphelper::ProfileZone aZone("doc_postWindowMouseEvent");
5351 
5352     SolarMutexGuard aGuard;
5353     SetLastExceptionMsg();
5354 
5355     VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
5356     if (!pWindow)
5357     {
5358         SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
5359         return;
5360     }
5361 
5362     const Point aPos(nX, nY);
5363 
5364     MouseEvent aEvent(aPos, nCount, MouseEventModifiers::SIMPLECLICK, nButtons, nModifier);
5365 
5366     vcl::EnableDialogInput(pWindow);
5367 
5368     switch (nType)
5369     {
5370         case LOK_MOUSEEVENT_MOUSEBUTTONDOWN:
5371             Application::PostMouseEvent(VclEventId::WindowMouseButtonDown, pWindow, &aEvent);
5372             break;
5373         case LOK_MOUSEEVENT_MOUSEBUTTONUP:
5374             Application::PostMouseEvent(VclEventId::WindowMouseButtonUp, pWindow, &aEvent);
5375             break;
5376         case LOK_MOUSEEVENT_MOUSEMOVE:
5377             Application::PostMouseEvent(VclEventId::WindowMouseMove, pWindow, &aEvent);
5378             break;
5379         default:
5380             assert(false);
5381             break;
5382     }
5383 }
5384 
5385 static void doc_postWindowGestureEvent(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, const char* pType, int nX, int nY, int nOffset)
5386 {
5387     comphelper::ProfileZone aZone("doc_postWindowGestureEvent");
5388 
5389     SolarMutexGuard aGuard;
5390     SetLastExceptionMsg();
5391 
5392     VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
5393     if (!pWindow)
5394     {
5395         SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
5396         return;
5397     }
5398 
5399     OString aType(pType);
5400     GestureEventPanType eEventType = GestureEventPanType::Update;
5401 
5402     if (aType == "panBegin")
5403         eEventType = GestureEventPanType::Begin;
5404     else if (aType == "panEnd")
5405         eEventType = GestureEventPanType::End;
5406 
5407     GestureEventPan aEvent {
5408         sal_Int32(nX),
5409         sal_Int32(nY),
5410         eEventType,
5411         sal_Int32(nOffset),
5412         PanningOrientation::Vertical,
5413     };
5414 
5415     vcl::EnableDialogInput(pWindow);
5416 
5417     Application::PostGestureEvent(VclEventId::WindowGestureEvent, pWindow, &aEvent);
5418 }
5419 
5420 static void doc_setTextSelection(LibreOfficeKitDocument* pThis, int nType, int nX, int nY)
5421 {
5422     comphelper::ProfileZone aZone("doc_setTextSelection");
5423 
5424     SolarMutexGuard aGuard;
5425     SetLastExceptionMsg();
5426 
5427     ITiledRenderable* pDoc = getTiledRenderable(pThis);
5428     if (!pDoc)
5429     {
5430         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
5431         return;
5432     }
5433 
5434     pDoc->setTextSelection(nType, nX, nY);
5435 }
5436 
5437 static void doc_setWindowTextSelection(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, bool swap, int nX, int nY)
5438 {
5439     comphelper::ProfileZone aZone("doc_setWindowTextSelection");
5440 
5441     SolarMutexGuard aGuard;
5442     SetLastExceptionMsg();
5443 
5444     VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
5445     if (!pWindow)
5446     {
5447         SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
5448         return;
5449     }
5450 
5451 
5452     Size aOffset(pWindow->GetOutOffXPixel(), pWindow->GetOutOffYPixel());
5453     Point aCursorPos(nX, nY);
5454     aCursorPos.Move(aOffset);
5455     sal_uInt16 nModifier = swap ? KEY_MOD1 + KEY_MOD2 : KEY_SHIFT;
5456 
5457     MouseEvent aCursorEvent(aCursorPos, 1, MouseEventModifiers::SIMPLECLICK, 0, nModifier);
5458     Application::PostMouseEvent(VclEventId::WindowMouseButtonDown, pWindow, &aCursorEvent);
5459     Application::PostMouseEvent(VclEventId::WindowMouseButtonUp, pWindow, &aCursorEvent);
5460 }
5461 
5462 static bool getFromTransferable(
5463     const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable,
5464     const OString &aInMimeType, OString &aRet);
5465 
5466 static bool encodeImageAsHTML(
5467     const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable,
5468     const OString &aMimeType, OString &aRet)
5469 {
5470     if (!getFromTransferable(xTransferable, aMimeType, aRet))
5471         return false;
5472 
5473     // Encode in base64.
5474     auto aSeq = Sequence<sal_Int8>(reinterpret_cast<const sal_Int8*>(aRet.getStr()),
5475                                    aRet.getLength());
5476     OStringBuffer aBase64Data;
5477     comphelper::Base64::encode(aBase64Data, aSeq);
5478 
5479     // Embed in HTML.
5480     aRet = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"
5481         "<html><head>"
5482         "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/><meta "
5483         "name=\"generator\" content=\""
5484         + getGenerator().toUtf8()
5485         + "\"/>"
5486         "</head><body><img src=\"data:" + aMimeType + ";base64,"
5487         + aBase64Data + "\"/></body></html>";
5488 
5489     return true;
5490 }
5491 
5492 static bool encodeTextAsHTML(
5493     const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable,
5494     const OString &aMimeType, OString &aRet)
5495 {
5496     if (!getFromTransferable(xTransferable, aMimeType, aRet))
5497         return false;
5498 
5499     // Embed in HTML - FIXME: needs some escaping.
5500     aRet = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"
5501         "<html><head>"
5502         "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/><meta "
5503         "name=\"generator\" content=\""
5504         + getGenerator().toUtf8()
5505         + "\"/></head><body><pre>" + aRet + "</pre></body></html>";
5506 
5507     return true;
5508 }
5509 
5510 static bool getFromTransferable(
5511     const css::uno::Reference<css::datatransfer::XTransferable> &xTransferable,
5512     const OString &aInMimeType, OString &aRet)
5513 {
5514     OString aMimeType(aInMimeType);
5515 
5516     // Take care of UTF-8 text here.
5517     bool bConvert = false;
5518     sal_Int32 nIndex = 0;
5519     if (o3tl::getToken(aMimeType, 0, ';', nIndex) == "text/plain")
5520     {
5521         if (o3tl::getToken(aMimeType, 0, ';', nIndex) == "charset=utf-8")
5522         {
5523             aMimeType = "text/plain;charset=utf-16"_ostr;
5524             bConvert = true;
5525         }
5526     }
5527 
5528     datatransfer::DataFlavor aFlavor;
5529     aFlavor.MimeType = OUString::fromUtf8(aMimeType);
5530     if (aMimeType == "text/plain;charset=utf-16")
5531         aFlavor.DataType = cppu::UnoType<OUString>::get();
5532     else
5533         aFlavor.DataType = cppu::UnoType< uno::Sequence<sal_Int8> >::get();
5534 
5535     if (!xTransferable->isDataFlavorSupported(aFlavor))
5536     {
5537         // Try harder for HTML it is our copy/paste meta-file format
5538         if (aInMimeType == "text/html")
5539         {
5540             // Desperate measures - convert text to HTML instead.
5541             if (encodeTextAsHTML(xTransferable, "text/plain;charset=utf-8"_ostr, aRet))
5542                 return true;
5543             // If html is not supported, might be a graphic-selection,
5544             if (encodeImageAsHTML(xTransferable, "image/png"_ostr, aRet))
5545                 return true;
5546         }
5547 
5548         SetLastExceptionMsg("Flavor " + aFlavor.MimeType + " is not supported");
5549         return false;
5550     }
5551 
5552     uno::Any aAny;
5553     try
5554     {
5555         aAny = xTransferable->getTransferData(aFlavor);
5556     }
5557     catch (const css::datatransfer::UnsupportedFlavorException& e)
5558     {
5559         SetLastExceptionMsg("Unsupported flavor " + aFlavor.MimeType + " exception " + e.Message);
5560         return false;
5561     }
5562     catch (const css::uno::Exception& e)
5563     {
5564         SetLastExceptionMsg("Exception getting " + aFlavor.MimeType + " exception " + e.Message);
5565         return false;
5566     }
5567 
5568     if (aFlavor.DataType == cppu::UnoType<OUString>::get())
5569     {
5570         OUString aString;
5571         aAny >>= aString;
5572         if (bConvert)
5573             aRet = OUStringToOString(aString, RTL_TEXTENCODING_UTF8);
5574         else
5575             aRet = OString(reinterpret_cast<const char *>(aString.getStr()), aString.getLength() * sizeof(sal_Unicode));
5576     }
5577     else
5578     {
5579         uno::Sequence<sal_Int8> aSequence;
5580         aAny >>= aSequence;
5581         aRet = OString(reinterpret_cast<const char*>(aSequence.getConstArray()), aSequence.getLength());
5582     }
5583 
5584     return true;
5585 }
5586 
5587 static char* doc_getTextSelection(LibreOfficeKitDocument* pThis, const char* pMimeType, char** pUsedMimeType)
5588 {
5589     comphelper::ProfileZone aZone("doc_getTextSelection");
5590 
5591     SolarMutexGuard aGuard;
5592     SetLastExceptionMsg();
5593 
5594     ITiledRenderable* pDoc = getTiledRenderable(pThis);
5595     if (!pDoc)
5596     {
5597         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
5598         return nullptr;
5599     }
5600 
5601     css::uno::Reference<css::datatransfer::XTransferable> xTransferable = pDoc->getSelection();
5602     if (!xTransferable)
5603     {
5604         SetLastExceptionMsg(u"No selection available"_ustr);
5605         return nullptr;
5606     }
5607 
5608     OString aType
5609         = pMimeType && pMimeType[0] != '\0' ? OString(pMimeType) : "text/plain;charset=utf-8"_ostr;
5610 
5611     OString aRet;
5612     bool bSuccess = getFromTransferable(xTransferable, aType, aRet);
5613     if (!bSuccess)
5614         return nullptr;
5615 
5616     if (pUsedMimeType) // legacy
5617     {
5618         if (pMimeType)
5619             *pUsedMimeType = strdup(pMimeType);
5620         else
5621             *pUsedMimeType = nullptr;
5622     }
5623 
5624     return convertOString(aRet);
5625 }
5626 
5627 static int doc_getSelectionType(LibreOfficeKitDocument* pThis)
5628 {
5629     comphelper::ProfileZone aZone("doc_getSelectionType");
5630 
5631     SolarMutexGuard aGuard;
5632     SetLastExceptionMsg();
5633 
5634     ITiledRenderable* pDoc = getTiledRenderable(pThis);
5635     if (!pDoc)
5636     {
5637         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
5638         return LOK_SELTYPE_NONE;
5639     }
5640 
5641     css::uno::Reference<css::datatransfer::XTransferable> xTransferable = pDoc->getSelection();
5642     if (!xTransferable)
5643     {
5644         SetLastExceptionMsg(u"No selection available"_ustr);
5645         return LOK_SELTYPE_NONE;
5646     }
5647 
5648     css::uno::Reference<css::datatransfer::XTransferable2> xTransferable2(xTransferable, css::uno::UNO_QUERY);
5649     if (xTransferable2.is() && xTransferable2->isComplex())
5650         return LOK_SELTYPE_COMPLEX;
5651 
5652     OString aRet;
5653     bool bSuccess = getFromTransferable(xTransferable, "text/plain;charset=utf-8"_ostr, aRet);
5654     if (!bSuccess)
5655         return LOK_SELTYPE_NONE;
5656 
5657     if (aRet.getLength() > 10000)
5658         return LOK_SELTYPE_COMPLEX;
5659 
5660     return !aRet.isEmpty() ? LOK_SELTYPE_TEXT : LOK_SELTYPE_NONE;
5661 }
5662 
5663 static int doc_getSelectionTypeAndText(LibreOfficeKitDocument* pThis, const char* pMimeType, char** pText, char** pUsedMimeType)
5664 {
5665     // The purpose of this function is to avoid double call to pDoc->getSelection(),
5666     // which may be expensive.
5667     comphelper::ProfileZone aZone("doc_getSelectionTypeAndText");
5668 
5669     SolarMutexGuard aGuard;
5670     SetLastExceptionMsg();
5671 
5672     ITiledRenderable* pDoc = getTiledRenderable(pThis);
5673     if (!pDoc)
5674     {
5675         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
5676         return LOK_SELTYPE_NONE;
5677     }
5678 
5679     css::uno::Reference<css::datatransfer::XTransferable> xTransferable = pDoc->getSelection();
5680     if (!xTransferable)
5681     {
5682         SetLastExceptionMsg(u"No selection available"_ustr);
5683         return LOK_SELTYPE_NONE;
5684     }
5685 
5686     css::uno::Reference<css::datatransfer::XTransferable2> xTransferable2(xTransferable, css::uno::UNO_QUERY);
5687     if (xTransferable2.is() && xTransferable2->isComplex())
5688         return LOK_SELTYPE_COMPLEX;
5689 
5690     OString aType
5691         = pMimeType && pMimeType[0] != '\0' ? OString(pMimeType) : "text/plain;charset=utf-8"_ostr;
5692 
5693     OString aRet;
5694     bool bSuccess = getFromTransferable(xTransferable, aType, aRet);
5695     if (!bSuccess)
5696         return LOK_SELTYPE_NONE;
5697 
5698     if (aRet.getLength() > 10000)
5699         return LOK_SELTYPE_COMPLEX;
5700 
5701     if (aRet.isEmpty())
5702         return LOK_SELTYPE_NONE;
5703 
5704     if (pText)
5705         *pText = convertOString(aRet);
5706 
5707     if (pUsedMimeType) // legacy
5708     {
5709         if (pMimeType)
5710             *pUsedMimeType = strdup(pMimeType);
5711         else
5712             *pUsedMimeType = nullptr;
5713     }
5714 
5715     return LOK_SELTYPE_TEXT;
5716 }
5717 
5718 static int doc_getClipboard(LibreOfficeKitDocument* pThis,
5719                             const char **pMimeTypes,
5720                             size_t      *pOutCount,
5721                             char      ***pOutMimeTypes,
5722                             size_t     **pOutSizes,
5723                             char      ***pOutStreams)
5724 {
5725 #ifdef IOS
5726     (void) pThis;
5727     (void) pMimeTypes;
5728     (void) pOutCount;
5729     (void) pOutMimeTypes;
5730     (void) pOutSizes;
5731     (void) pOutStreams;
5732 
5733     assert(!"doc_getClipboard should not be called on iOS");
5734 
5735     return 0;
5736 #else
5737     comphelper::ProfileZone aZone("doc_getClipboard");
5738 
5739     SolarMutexGuard aGuard;
5740     SetLastExceptionMsg();
5741 
5742     assert (pOutCount);
5743     assert (pOutMimeTypes);
5744     assert (pOutSizes);
5745     assert (pOutStreams);
5746 
5747     *pOutCount = 0;
5748     *pOutMimeTypes = nullptr;
5749     *pOutSizes = nullptr;
5750     *pOutStreams = nullptr;
5751 
5752     ITiledRenderable* pDoc = getTiledRenderable(pThis);
5753     if (!pDoc)
5754     {
5755         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
5756         return 0;
5757     }
5758 
5759     rtl::Reference<LOKClipboard> xClip(LOKClipboardFactory::getClipboardForCurView());
5760 
5761     css::uno::Reference<css::datatransfer::XTransferable> xTransferable = xClip->getContents();
5762     SAL_INFO("lok", "Got from clip: " << xClip.get() << " transferable: " << xTransferable);
5763     if (!xTransferable)
5764     {
5765         SetLastExceptionMsg(u"No clipboard content available"_ustr);
5766         return 0;
5767     }
5768 
5769     std::vector<OString> aMimeTypes;
5770     if (!pMimeTypes) // everything
5771     {
5772         const uno::Sequence< css::datatransfer::DataFlavor > flavors = xTransferable->getTransferDataFlavors();
5773         if (!flavors.getLength())
5774         {
5775             SetLastExceptionMsg(u"Flavourless selection"_ustr);
5776             return 0;
5777         }
5778         for (const auto &it : flavors)
5779             aMimeTypes.push_back(OUStringToOString(it.MimeType, RTL_TEXTENCODING_UTF8));
5780     }
5781     else
5782     {
5783         for (size_t i = 0; pMimeTypes[i]; ++i)
5784             aMimeTypes.push_back(OString(pMimeTypes[i]));
5785     }
5786 
5787     *pOutCount = aMimeTypes.size();
5788     *pOutSizes = static_cast<size_t *>(malloc(*pOutCount * sizeof(size_t)));
5789     *pOutMimeTypes = static_cast<char **>(malloc(*pOutCount * sizeof(char *)));
5790     *pOutStreams = static_cast<char **>(malloc(*pOutCount * sizeof(char *)));
5791     for (size_t i = 0; i < aMimeTypes.size(); ++i)
5792     {
5793         if (aMimeTypes[i] == "text/plain;charset=utf-16")
5794             (*pOutMimeTypes)[i] = strdup("text/plain;charset=utf-8");
5795         else
5796             (*pOutMimeTypes)[i] = convertOString(aMimeTypes[i]);
5797 
5798         OString aRet;
5799         bool bSuccess = getFromTransferable(xTransferable, (*pOutMimeTypes)[i], aRet);
5800         if (!bSuccess || aRet.getLength() < 1)
5801         {
5802             (*pOutSizes)[i] = 0;
5803             (*pOutStreams)[i] = nullptr;
5804         }
5805         else
5806         {
5807             (*pOutSizes)[i] = aRet.getLength();
5808             (*pOutStreams)[i] = convertOString(aRet);
5809         }
5810     }
5811 
5812     return 1;
5813 #endif
5814 }
5815 
5816 static int doc_setClipboard(LibreOfficeKitDocument* pThis,
5817                             const size_t   nInCount,
5818                             const char   **pInMimeTypes,
5819                             const size_t  *pInSizes,
5820                             const char   **pInStreams)
5821 {
5822 #ifdef IOS
5823     (void) pThis;
5824     (void) nInCount;
5825     (void) pInMimeTypes;
5826     (void) pInSizes;
5827     (void) pInStreams;
5828 #else
5829     comphelper::ProfileZone aZone("doc_setClipboard");
5830 
5831     SolarMutexGuard aGuard;
5832     SetLastExceptionMsg();
5833 
5834     ITiledRenderable* pDoc = getTiledRenderable(pThis);
5835     if (!pDoc)
5836     {
5837         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
5838         return false;
5839     }
5840 
5841     uno::Reference<datatransfer::XTransferable> xTransferable(new LOKTransferable(nInCount, pInMimeTypes, pInSizes, pInStreams));
5842 
5843     auto xClip = forceSetClipboardForCurrentView(pThis);
5844     xClip->setContents(xTransferable, uno::Reference<datatransfer::clipboard::XClipboardOwner>());
5845 
5846     SAL_INFO("lok", "Set clip: " << xClip.get() << " to: " << xTransferable);
5847 
5848     if (!pDoc->isMimeTypeSupported())
5849     {
5850         SetLastExceptionMsg(u"Document doesn't support this mime type"_ustr);
5851         return false;
5852     }
5853 #endif
5854     return true;
5855 }
5856 
5857 static bool doc_paste(LibreOfficeKitDocument* pThis, const char* pMimeType, const char* pData, size_t nSize)
5858 {
5859     comphelper::ProfileZone aZone("doc_paste");
5860 
5861     SolarMutexGuard aGuard;
5862 
5863     const char *pInMimeTypes[1];
5864     const char *pInStreams[1];
5865     size_t pInSizes[1];
5866     pInMimeTypes[0] = pMimeType;
5867     pInSizes[0] = nSize;
5868     pInStreams[0] = pData;
5869 
5870     if (!doc_setClipboard(pThis, 1, pInMimeTypes, pInSizes, pInStreams))
5871         return false;
5872 
5873     uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
5874     {
5875         {"AnchorType", uno::Any(static_cast<sal_uInt16>(css::text::TextContentAnchorType_AS_CHARACTER))},
5876         {"IgnoreComments", uno::Any(true)},
5877     }));
5878     if (!comphelper::dispatchCommand(u".uno:Paste"_ustr, aPropertyValues))
5879     {
5880         SetLastExceptionMsg(u"Failed to dispatch the .uno: command"_ustr);
5881         return false;
5882     }
5883 
5884     return true;
5885 }
5886 
5887 static void doc_setGraphicSelection(LibreOfficeKitDocument* pThis, int nType, int nX, int nY)
5888 {
5889     comphelper::ProfileZone aZone("doc_setGraphicSelection");
5890 
5891     SolarMutexGuard aGuard;
5892     SetLastExceptionMsg();
5893 
5894     ITiledRenderable* pDoc = getTiledRenderable(pThis);
5895     if (!pDoc)
5896     {
5897         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
5898         return;
5899     }
5900 
5901     pDoc->setGraphicSelection(nType, nX, nY);
5902 }
5903 
5904 static void doc_resetSelection(LibreOfficeKitDocument* pThis)
5905 {
5906     comphelper::ProfileZone aZone("doc_resetSelection");
5907 
5908     SolarMutexGuard aGuard;
5909     SetLastExceptionMsg();
5910 
5911     ITiledRenderable* pDoc = getTiledRenderable(pThis);
5912     if (!pDoc)
5913     {
5914         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
5915         return;
5916     }
5917 
5918     pDoc->resetSelection();
5919 }
5920 
5921 static char* getDocReadOnly(LibreOfficeKitDocument* pThis)
5922 {
5923     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
5924     if (!pDocument)
5925         return nullptr;
5926 
5927     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
5928     if (!pBaseModel)
5929         return nullptr;
5930 
5931     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
5932     if (!pObjectShell)
5933         return nullptr;
5934 
5935     boost::property_tree::ptree aTree;
5936     aTree.put("commandName", ".uno:ReadOnly");
5937     aTree.put("success", pObjectShell->IsLoadReadonly());
5938 
5939     std::stringstream aStream;
5940     boost::property_tree::write_json(aStream, aTree);
5941     char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1));
5942     if (!pJson)
5943         return nullptr;
5944 
5945     strcpy(pJson, aStream.str().c_str());
5946     pJson[aStream.str().size()] = '\0';
5947     return pJson;
5948 }
5949 
5950 static void addLocale(boost::property_tree::ptree& rValues, css::lang::Locale const & rLocale)
5951 {
5952     boost::property_tree::ptree aChild;
5953     const LanguageTag aLanguageTag( rLocale );
5954     OUString sLanguage = SvtLanguageTable::GetLanguageString(aLanguageTag.getLanguageType());
5955     if (sLanguage.endsWith("}"))
5956         return;
5957 
5958     sLanguage += ";" + aLanguageTag.getBcp47(false);
5959     aChild.put("", sLanguage.toUtf8());
5960     rValues.push_back(std::make_pair("", aChild));
5961 }
5962 
5963 static char* getLanguages(const char* pCommand)
5964 {
5965     css::uno::Sequence< css::lang::Locale > aLocales;
5966     css::uno::Sequence< css::lang::Locale > aGrammarLocales;
5967 
5968     if (xContext.is())
5969     {
5970         // SpellChecker
5971         css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv = css::linguistic2::LinguServiceManager::create(xContext);
5972         if (xLangSrv.is())
5973         {
5974             css::uno::Reference<css::linguistic2::XSpellChecker> xSpell = xLangSrv->getSpellChecker();
5975             if (xSpell.is())
5976                 aLocales = xSpell->getLocales();
5977         }
5978 
5979         // LanguageTool
5980         if (LanguageToolCfg::IsEnabled::get())
5981         {
5982             uno::Reference< linguistic2::XProofreader > xGC(
5983                     xContext->getServiceManager()->createInstanceWithContext(u"org.openoffice.lingu.LanguageToolGrammarChecker"_ustr, xContext),
5984                     uno::UNO_QUERY_THROW );
5985             uno::Reference< linguistic2::XSupportedLocales > xSuppLoc( xGC, uno::UNO_QUERY_THROW );
5986             aGrammarLocales = xSuppLoc->getLocales();
5987         }
5988     }
5989 
5990     boost::property_tree::ptree aTree;
5991     aTree.put("commandName", pCommand);
5992     boost::property_tree::ptree aValues;
5993     for (css::lang::Locale const& rLocale : aLocales)
5994         addLocale(aValues, rLocale);
5995     for (css::lang::Locale const& rLocale : aGrammarLocales)
5996         addLocale(aValues, rLocale);
5997     aTree.add_child("commandValues", aValues);
5998     std::stringstream aStream;
5999     boost::property_tree::write_json(aStream, aTree);
6000     char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1));
6001     assert(pJson); // Don't handle OOM conditions
6002     strcpy(pJson, aStream.str().c_str());
6003     pJson[aStream.str().size()] = '\0';
6004     return pJson;
6005 }
6006 
6007 static char* getFonts (const char* pCommand)
6008 {
6009     SfxObjectShell* pDocSh = SfxObjectShell::Current();
6010     if (!pDocSh)
6011         return nullptr;
6012     const SvxFontListItem* pFonts = static_cast<const SvxFontListItem*>(
6013         pDocSh->GetItem(SID_ATTR_CHAR_FONTLIST));
6014     const FontList* pList = pFonts ? pFonts->GetFontList() : nullptr;
6015 
6016     boost::property_tree::ptree aTree;
6017     aTree.put("commandName", pCommand);
6018     boost::property_tree::ptree aValues;
6019     if ( pList )
6020     {
6021         sal_uInt16 nFontCount = pList->GetFontNameCount();
6022         for (sal_uInt16 i = 0; i < nFontCount; ++i)
6023         {
6024             boost::property_tree::ptree aChildren;
6025             const FontMetric& rFontMetric = pList->GetFontName(i);
6026             const int* pAry = FontList::GetStdSizeAry();
6027             sal_uInt16 nSizeCount = 0;
6028             while (pAry[nSizeCount])
6029             {
6030                 boost::property_tree::ptree aChild;
6031                 aChild.put("", static_cast<float>(pAry[nSizeCount]) / 10);
6032                 aChildren.push_back(std::make_pair("", aChild));
6033                 nSizeCount++;
6034             }
6035             aValues.add_child(rFontMetric.GetFamilyName().toUtf8().getStr(), aChildren);
6036         }
6037     }
6038     aTree.add_child("commandValues", aValues);
6039     std::stringstream aStream;
6040     boost::property_tree::write_json(aStream, aTree);
6041     char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1));
6042     assert(pJson); // Don't handle OOM conditions
6043     strcpy(pJson, aStream.str().c_str());
6044     pJson[aStream.str().size()] = '\0';
6045     return pJson;
6046 }
6047 
6048 static char* getFontSubset (std::string_view aFontName)
6049 {
6050     OUString aFoundFont(::rtl::Uri::decode(OStringToOUString(aFontName, RTL_TEXTENCODING_UTF8), rtl_UriDecodeStrict, RTL_TEXTENCODING_UTF8));
6051 
6052     boost::property_tree::ptree aTree;
6053     aTree.put("commandName", ".uno:FontSubset");
6054     boost::property_tree::ptree aValues;
6055 
6056     if (const vcl::Font* pFont = FindFont(aFoundFont))
6057     {
6058         FontCharMapRef xFontCharMap (new FontCharMap());
6059         auto aDevice(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA));
6060 
6061         aDevice->SetFont(*pFont);
6062         aDevice->GetFontCharMap(xFontCharMap);
6063         SubsetMap aSubMap(xFontCharMap);
6064 
6065         for (auto const& subset : aSubMap.GetSubsetMap())
6066         {
6067             boost::property_tree::ptree aChild;
6068             aChild.put("", static_cast<int>(ublock_getCode(subset.GetRangeMin())));
6069             aValues.push_back(std::make_pair("", aChild));
6070         }
6071     }
6072 
6073     aTree.add_child("commandValues", aValues);
6074     std::stringstream aStream;
6075     boost::property_tree::write_json(aStream, aTree);
6076     char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1));
6077     assert(pJson); // Don't handle OOM conditions
6078     strcpy(pJson, aStream.str().c_str());
6079     pJson[aStream.str().size()] = '\0';
6080     return pJson;
6081 }
6082 
6083 static char* getStyles(LibreOfficeKitDocument* pThis, const char* pCommand)
6084 {
6085     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
6086 
6087     boost::property_tree::ptree aTree;
6088     aTree.put("commandName", pCommand);
6089     uno::Reference<css::style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(pDocument->mxComponent, uno::UNO_QUERY);
6090     const uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
6091     const uno::Sequence<OUString> aStyleFamilies = xStyleFamilies->getElementNames();
6092 
6093     static constexpr OUString aWriterStyles[] =
6094     {
6095         u"Text body"_ustr,
6096         u"Quotations"_ustr,
6097         u"Title"_ustr,
6098         u"Subtitle"_ustr,
6099         u"Heading 1"_ustr,
6100         u"Heading 2"_ustr,
6101         u"Heading 3"_ustr,
6102     };
6103 
6104     // We need to keep a list of the default style names
6105     // in order to filter these out later when processing
6106     // the full list of styles.
6107     std::set<OUString> aDefaultStyleNames;
6108 
6109     boost::property_tree::ptree aValues;
6110     for (OUString const & sStyleFam : aStyleFamilies)
6111     {
6112         boost::property_tree::ptree aChildren;
6113         uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName(sStyleFam), uno::UNO_QUERY);
6114 
6115         // Writer provides a huge number of styles, we have a list of 7 "default" styles which
6116         // should be shown in the normal dropdown, which we should add to the start of the list
6117         // to simplify their selection.
6118         if (sStyleFam == "ParagraphStyles"
6119             && doc_getDocumentType(pThis) == LOK_DOCTYPE_TEXT)
6120         {
6121             for (const OUString& rStyle: aWriterStyles)
6122             {
6123                 aDefaultStyleNames.insert( rStyle );
6124 
6125                 boost::property_tree::ptree aChild;
6126                 aChild.put("", rStyle.toUtf8());
6127                 aChildren.push_back(std::make_pair("", aChild));
6128             }
6129         }
6130 
6131         const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();
6132         for (const OUString& rStyle: aStyles )
6133         {
6134             // Filter out the default styles - they are already at the top
6135             // of the list
6136             if (aDefaultStyleNames.find(rStyle) == aDefaultStyleNames.end() ||
6137                 (sStyleFam != "ParagraphStyles" || doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT) )
6138             {
6139                 boost::property_tree::ptree aChild;
6140                 aChild.put("", rStyle.toUtf8());
6141                 aChildren.push_back(std::make_pair("", aChild));
6142             }
6143         }
6144         aValues.add_child(sStyleFam.toUtf8().getStr(), aChildren);
6145     }
6146 
6147     // Header & Footer Styles
6148     {
6149         boost::property_tree::ptree aChild;
6150         boost::property_tree::ptree aChildren;
6151         static constexpr OUString sPageStyles(u"PageStyles"_ustr);
6152         uno::Reference<beans::XPropertySet> xProperty;
6153         uno::Reference<container::XNameContainer> xContainer;
6154 
6155         if (xStyleFamilies->hasByName(sPageStyles) && (xStyleFamilies->getByName(sPageStyles) >>= xContainer))
6156         {
6157             const uno::Sequence<OUString> aSeqNames = xContainer->getElementNames();
6158             for (OUString const & sName : aSeqNames)
6159             {
6160                 bool bIsPhysical;
6161                 xProperty.set(xContainer->getByName(sName), uno::UNO_QUERY);
6162                 if (xProperty.is() && (xProperty->getPropertyValue(u"IsPhysical"_ustr) >>= bIsPhysical) && bIsPhysical)
6163                 {
6164                     OUString displayName;
6165                     xProperty->getPropertyValue(u"DisplayName"_ustr) >>= displayName;
6166                     aChild.put("", displayName.toUtf8());
6167                     aChildren.push_back(std::make_pair("", aChild));
6168                 }
6169             }
6170             aValues.add_child("HeaderFooter", aChildren);
6171         }
6172     }
6173 
6174     {
6175         boost::property_tree::ptree aCommandList;
6176 
6177         {
6178             boost::property_tree::ptree aChild;
6179 
6180             OUString sClearFormat = SvxResId(RID_SVXSTR_CLEARFORM);
6181 
6182             boost::property_tree::ptree aName;
6183             aName.put("", sClearFormat.toUtf8());
6184             aChild.push_back(std::make_pair("text", aName));
6185 
6186             boost::property_tree::ptree aCommand;
6187             aCommand.put("", ".uno:ResetAttributes");
6188             aChild.push_back(std::make_pair("id", aCommand));
6189 
6190             aCommandList.push_back(std::make_pair("", aChild));
6191         }
6192 
6193         aValues.add_child("Commands", aCommandList);
6194     }
6195 
6196     aTree.add_child("commandValues", aValues);
6197     std::stringstream aStream;
6198     boost::property_tree::write_json(aStream, aTree);
6199     char* pJson = static_cast<char*>(malloc(aStream.str().size() + 1));
6200     assert(pJson); // Don't handle OOM conditions
6201     strcpy(pJson, aStream.str().c_str());
6202     pJson[aStream.str().size()] = '\0';
6203     return pJson;
6204 }
6205 
6206 namespace {
6207 
6208 enum class UndoOrRedo
6209 {
6210     UNDO,
6211     REDO
6212 };
6213 
6214 }
6215 
6216 /// Returns the JSON representation of either an undo or a redo stack.
6217 static char* getUndoOrRedo(LibreOfficeKitDocument* pThis, UndoOrRedo eCommand)
6218 {
6219     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
6220 
6221     auto pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
6222     if (!pBaseModel)
6223         return nullptr;
6224 
6225     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
6226     if (!pObjectShell)
6227         return nullptr;
6228 
6229     SfxUndoManager* pUndoManager = pObjectShell->GetUndoManager();
6230     if (!pUndoManager)
6231         return nullptr;
6232 
6233     OUString aString;
6234     if (eCommand == UndoOrRedo::UNDO)
6235         aString = pUndoManager->GetUndoActionsInfo();
6236     else
6237         aString = pUndoManager->GetRedoActionsInfo();
6238     char* pJson = convertOUString(aString);
6239     return pJson;
6240 }
6241 
6242 /// Returns the JSON representation of the redline stack.
6243 static char* getTrackedChanges(LibreOfficeKitDocument* pThis)
6244 {
6245     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
6246 
6247     uno::Reference<document::XRedlinesSupplier> xRedlinesSupplier(pDocument->mxComponent, uno::UNO_QUERY);
6248     tools::JsonWriter aJson;
6249     // We want positions of the track changes also which is not possible from
6250     // UNO. Enable positioning information for text documents only for now, so
6251     // construct the tracked changes JSON from inside the sw/, not here using UNO
6252     if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT && xRedlinesSupplier.is())
6253     {
6254         auto redlinesNode = aJson.startArray("redlines");
6255         uno::Reference<container::XEnumeration> xRedlines = xRedlinesSupplier->getRedlines()->createEnumeration();
6256         for (size_t nIndex = 0; xRedlines->hasMoreElements(); ++nIndex)
6257         {
6258             uno::Reference<beans::XPropertySet> xRedline(xRedlines->nextElement(), uno::UNO_QUERY);
6259             auto redlineNode = aJson.startStruct();
6260             aJson.put("index", static_cast<sal_Int32>(nIndex));
6261 
6262             OUString sAuthor;
6263             xRedline->getPropertyValue(u"RedlineAuthor"_ustr) >>= sAuthor;
6264             aJson.put("author", sAuthor);
6265 
6266             OUString sType;
6267             xRedline->getPropertyValue(u"RedlineType"_ustr) >>= sType;
6268             aJson.put("type", sType);
6269 
6270             OUString sComment;
6271             xRedline->getPropertyValue(u"RedlineComment"_ustr) >>= sComment;
6272             aJson.put("comment", sComment);
6273 
6274             OUString sDescription;
6275             xRedline->getPropertyValue(u"RedlineDescription"_ustr) >>= sDescription;
6276             aJson.put("description", sDescription);
6277 
6278             util::DateTime aDateTime;
6279             xRedline->getPropertyValue(u"RedlineDateTime"_ustr) >>= aDateTime;
6280             OUString sDateTime = utl::toISO8601(aDateTime);
6281             aJson.put("dateTime", sDateTime);
6282         }
6283     }
6284     else
6285     {
6286         ITiledRenderable* pDoc = getTiledRenderable(pThis);
6287         if (!pDoc)
6288         {
6289             SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
6290             return nullptr;
6291         }
6292         pDoc->getTrackedChanges(aJson);
6293     }
6294 
6295     return convertOString(aJson.finishAndGetAsOString());
6296 }
6297 
6298 
6299 /// Returns the JSON representation of the redline author table.
6300 static char* getTrackedChangeAuthors(LibreOfficeKitDocument* pThis)
6301 {
6302     ITiledRenderable* pDoc = getTiledRenderable(pThis);
6303     if (!pDoc)
6304     {
6305         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
6306         return nullptr;
6307     }
6308     tools::JsonWriter aJsonWriter;
6309     pDoc->getTrackedChangeAuthors(aJsonWriter);
6310     return convertOString(aJsonWriter.finishAndGetAsOString());
6311 }
6312 
6313 static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCommand)
6314 {
6315     comphelper::ProfileZone aZone("doc_getCommandValues");
6316 
6317     SolarMutexGuard aGuard;
6318     SetLastExceptionMsg();
6319 
6320     const std::string_view aCommand(pCommand);
6321     static constexpr std::string_view aViewRowColumnHeaders(".uno:ViewRowColumnHeaders");
6322     static constexpr std::string_view aSheetGeometryData(".uno:SheetGeometryData");
6323     static constexpr std::string_view aFontSubset(".uno:FontSubset&name=");
6324 
6325     ITiledRenderable* pDoc = getTiledRenderable(pThis);
6326     if (!pDoc)
6327     {
6328         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
6329         return nullptr;
6330     }
6331 
6332     if (aCommand == ".uno:ReadOnly")
6333     {
6334         return getDocReadOnly(pThis);
6335     }
6336     else if (aCommand == ".uno:LanguageStatus")
6337     {
6338         return getLanguages(pCommand);
6339     }
6340     else if (aCommand == ".uno:CharFontName")
6341     {
6342         return getFonts(pCommand);
6343     }
6344     else if (aCommand == ".uno:StyleApply")
6345     {
6346         return getStyles(pThis, pCommand);
6347     }
6348     else if (aCommand == ".uno:Undo")
6349     {
6350         return getUndoOrRedo(pThis, UndoOrRedo::UNDO);
6351     }
6352     else if (aCommand == ".uno:Redo")
6353     {
6354         return getUndoOrRedo(pThis, UndoOrRedo::REDO);
6355     }
6356     else if (aCommand == ".uno:AcceptTrackedChanges")
6357     {
6358         return getTrackedChanges(pThis);
6359     }
6360     else if (aCommand == ".uno:TrackedChangeAuthors")
6361     {
6362         return getTrackedChangeAuthors(pThis);
6363     }
6364     else if (aCommand == ".uno:ViewAnnotations")
6365     {
6366         return getPostIts(pThis);
6367     }
6368     else if (aCommand == ".uno:ViewAnnotationsPosition")
6369     {
6370         return getPostItsPos(pThis);
6371     }
6372     else if (aCommand == ".uno:RulerState")
6373     {
6374         return getRulerState(pThis);
6375     }
6376     else if (aCommand == ".uno:ViewRenderState")
6377     {
6378         return convertOString(pDoc->getViewRenderState());
6379     }
6380     else if (aCommand.starts_with(aViewRowColumnHeaders))
6381     {
6382         tools::Rectangle aRectangle;
6383         if (aCommand.size() > aViewRowColumnHeaders.size())
6384         {
6385             // Command has parameters.
6386             int nX = 0;
6387             int nY = 0;
6388             int nWidth = 0;
6389             int nHeight = 0;
6390             std::string_view aArguments = aCommand.substr(aViewRowColumnHeaders.size() + 1);
6391             sal_Int32 nParamIndex = 0;
6392             do
6393             {
6394                 std::string_view aParamToken = o3tl::getToken(aArguments, 0, '&', nParamIndex);
6395                 sal_Int32 nIndex = 0;
6396                 std::string_view aKey;
6397                 std::string_view aValue;
6398                 do
6399                 {
6400                     std::string_view aToken = o3tl::getToken(aParamToken, 0, '=', nIndex);
6401                     if (aKey.empty())
6402                         aKey = aToken;
6403                     else
6404                         aValue = aToken;
6405                 }
6406                 while (nIndex >= 0);
6407                 if (aKey == "x")
6408                     nX = o3tl::toInt32(aValue);
6409                 else if (aKey == "y")
6410                     nY = o3tl::toInt32(aValue);
6411                 else if (aKey == "width")
6412                     nWidth = o3tl::toInt32(aValue);
6413                 else if (aKey == "height")
6414                     nHeight = o3tl::toInt32(aValue);
6415             }
6416             while (nParamIndex >= 0);
6417 
6418             aRectangle = tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight);
6419         }
6420 
6421         tools::JsonWriter aJsonWriter;
6422         pDoc->getRowColumnHeaders(aRectangle, aJsonWriter);
6423         return convertOString(aJsonWriter.finishAndGetAsOString());
6424     }
6425     else if (aCommand.starts_with(aSheetGeometryData))
6426     {
6427         bool bColumns = true;
6428         bool bRows = true;
6429         bool bSizes = true;
6430         bool bHidden = true;
6431         bool bFiltered = true;
6432         bool bGroups = true;
6433         if (aCommand.size() > aSheetGeometryData.size())
6434         {
6435             bColumns = bRows = bSizes = bHidden = bFiltered = bGroups = false;
6436 
6437             std::string_view aArguments = aCommand.substr(aSheetGeometryData.size() + 1);
6438             sal_Int32 nParamIndex = 0;
6439             do
6440             {
6441                 std::string_view aParamToken = o3tl::getToken(aArguments, 0, '&', nParamIndex);
6442                 sal_Int32 nIndex = 0;
6443                 std::string_view aKey;
6444                 std::string_view aValue;
6445                 do
6446                 {
6447                     std::string_view aToken = o3tl::getToken(aParamToken, 0, '=', nIndex);
6448                     if (aKey.empty())
6449                         aKey = aToken;
6450                     else
6451                         aValue = aToken;
6452 
6453                 } while (nIndex >= 0);
6454 
6455                 bool bEnableFlag = aValue.empty() ||
6456                     o3tl::equalsIgnoreAsciiCase(aValue, "true") || o3tl::toInt32(aValue) > 0;
6457                 if (!bEnableFlag)
6458                     continue;
6459 
6460                 if (aKey == "columns")
6461                     bColumns = true;
6462                 else if (aKey == "rows")
6463                     bRows = true;
6464                 else if (aKey == "sizes")
6465                     bSizes = true;
6466                 else if (aKey == "hidden")
6467                     bHidden = true;
6468                 else if (aKey == "filtered")
6469                     bFiltered = true;
6470                 else if (aKey == "groups")
6471                     bGroups = true;
6472 
6473             } while (nParamIndex >= 0);
6474         }
6475 
6476         OString aGeomDataStr
6477             = pDoc->getSheetGeometryData(bColumns, bRows, bSizes, bHidden, bFiltered, bGroups);
6478 
6479         if (aGeomDataStr.isEmpty())
6480             return nullptr;
6481 
6482         return convertOString(aGeomDataStr);
6483     }
6484     else if (aCommand.starts_with(".uno:CellCursor"))
6485     {
6486         // Ignore command's deprecated parameters.
6487         tools::JsonWriter aJsonWriter;
6488         pDoc->getCellCursor(aJsonWriter);
6489         return convertOString(aJsonWriter.finishAndGetAsOString());
6490     }
6491     else if (aCommand.starts_with(aFontSubset))
6492     {
6493         return getFontSubset(aCommand.substr(aFontSubset.size()));
6494     }
6495     else if (pDoc->supportsCommand(INetURLObject(OUString::fromUtf8(aCommand)).GetURLPath()))
6496     {
6497         tools::JsonWriter aJsonWriter;
6498         pDoc->getCommandValues(aJsonWriter, aCommand);
6499         return convertOString(aJsonWriter.finishAndGetAsOString());
6500     }
6501     else
6502     {
6503         SetLastExceptionMsg(u"Unknown command, no values returned"_ustr);
6504         return nullptr;
6505     }
6506 }
6507 
6508 static void doc_setClientZoom(LibreOfficeKitDocument* pThis, int nTilePixelWidth, int nTilePixelHeight,
6509         int nTileTwipWidth, int nTileTwipHeight)
6510 {
6511     comphelper::ProfileZone aZone("doc_setClientZoom");
6512 
6513     SolarMutexGuard aGuard;
6514     SetLastExceptionMsg();
6515 
6516     ITiledRenderable* pDoc = getTiledRenderable(pThis);
6517     if (!pDoc)
6518     {
6519         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
6520         return;
6521     }
6522 
6523     pDoc->setClientZoom(nTilePixelWidth, nTilePixelHeight, nTileTwipWidth, nTileTwipHeight);
6524 }
6525 
6526 static void doc_setClientVisibleArea(LibreOfficeKitDocument* pThis, int nX, int nY, int nWidth, int nHeight)
6527 {
6528     comphelper::ProfileZone aZone("doc_setClientVisibleArea");
6529 
6530     SolarMutexGuard aGuard;
6531     SetLastExceptionMsg();
6532 
6533     ITiledRenderable* pDoc = getTiledRenderable(pThis);
6534     if (!pDoc)
6535     {
6536         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
6537         return;
6538     }
6539 
6540     tools::Rectangle aRectangle(Point(nX, nY), Size(nWidth, nHeight));
6541     pDoc->setClientVisibleArea(aRectangle);
6542 }
6543 
6544 static void doc_setOutlineState(LibreOfficeKitDocument* pThis, bool bColumn, int nLevel, int nIndex, bool bHidden)
6545 {
6546     comphelper::ProfileZone aZone("doc_setOutlineState");
6547 
6548     SolarMutexGuard aGuard;
6549     SetLastExceptionMsg();
6550 
6551     ITiledRenderable* pDoc = getTiledRenderable(pThis);
6552     if (!pDoc)
6553     {
6554         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
6555         return;
6556     }
6557 
6558     pDoc->setOutlineState(bColumn, nLevel, nIndex, bHidden);
6559 }
6560 
6561 static int doc_createViewWithOptions(LibreOfficeKitDocument* pThis,
6562                                      const char* pOptions)
6563 {
6564     comphelper::ProfileZone aZone("doc_createView");
6565 
6566     SolarMutexGuard aGuard;
6567     SetLastExceptionMsg();
6568 
6569     OUString aOptions = getUString(pOptions);
6570     const OUString aLanguage = extractParameter(aOptions, u"Language");
6571 
6572     if (!aLanguage.isEmpty())
6573     {
6574         // Set the LOK language tag, used for dialog tunneling.
6575         comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLanguage));
6576         comphelper::LibreOfficeKit::setLocale(LanguageTag(aLanguage));
6577     }
6578 
6579     const OUString aDeviceFormFactor = extractParameter(aOptions, u"DeviceFormFactor");
6580     SfxLokHelper::setDeviceFormFactor(aDeviceFormFactor);
6581 
6582     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
6583     const int nId = SfxLokHelper::createView(pDocument->mnDocumentId);
6584 
6585     vcl::lok::numberOfViewsChanged(SfxLokHelper::getViewsCount(pDocument->mnDocumentId));
6586 
6587 #ifdef IOS
6588     (void) pThis;
6589 #else
6590     forceSetClipboardForCurrentView(pThis);
6591 #endif
6592 
6593     return nId;
6594 }
6595 
6596 static int doc_createView(LibreOfficeKitDocument* pThis)
6597 {
6598     return doc_createViewWithOptions(pThis, nullptr); // No options.
6599 }
6600 
6601 static void doc_destroyView(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int nId)
6602 {
6603     comphelper::ProfileZone aZone("doc_destroyView");
6604 
6605     SolarMutexGuard aGuard;
6606     SetLastExceptionMsg();
6607 
6608 #ifndef IOS
6609     LOKClipboardFactory::releaseClipboardForView(nId);
6610 #endif
6611 
6612     SfxLokHelper::destroyView(nId);
6613 
6614     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
6615     vcl::lok::numberOfViewsChanged(SfxLokHelper::getViewsCount(pDocument->mnDocumentId));
6616 }
6617 
6618 static void doc_setView(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId)
6619 {
6620     comphelper::ProfileZone aZone("doc_setView");
6621 
6622     SolarMutexGuard aGuard;
6623     SetLastExceptionMsg();
6624 
6625     SfxLokHelper::setView(nId);
6626 }
6627 
6628 static int doc_getView(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/)
6629 {
6630     comphelper::ProfileZone aZone("doc_getView");
6631 
6632     SolarMutexGuard aGuard;
6633     SetLastExceptionMsg();
6634 
6635     return SfxLokHelper::getView();
6636 }
6637 
6638 static int doc_getViewsCount(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis)
6639 {
6640     comphelper::ProfileZone aZone("doc_getViewsCount");
6641 
6642     SolarMutexGuard aGuard;
6643     SetLastExceptionMsg();
6644 
6645     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
6646     return SfxLokHelper::getViewsCount(pDocument->mnDocumentId);
6647 }
6648 
6649 static bool doc_getViewIds(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int* pArray, size_t nSize)
6650 {
6651     comphelper::ProfileZone aZone("doc_getViewsIds");
6652 
6653     SolarMutexGuard aGuard;
6654     SetLastExceptionMsg();
6655 
6656     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
6657     return SfxLokHelper::getViewIds(pDocument->mnDocumentId, pArray, nSize);
6658 }
6659 
6660 static void doc_setViewLanguage(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId, const char* language)
6661 {
6662     comphelper::ProfileZone aZone("doc_setViewLanguage");
6663 
6664     SolarMutexGuard aGuard;
6665     SetLastExceptionMsg();
6666 
6667     OUString sLanguage = OStringToOUString(language, RTL_TEXTENCODING_UTF8);
6668     SfxLokHelper::setViewLanguage(nId, sLanguage);
6669     SfxLokHelper::setViewLocale(nId, sLanguage);
6670 }
6671 
6672 unsigned char* doc_renderFont(LibreOfficeKitDocument* pThis,
6673                               const char* pFontName,
6674                               const char* pChar,
6675                               int* pFontWidth,
6676                               int* pFontHeight)
6677 {
6678     return doc_renderFontOrientation(pThis, pFontName, pChar, pFontWidth, pFontHeight, 0);
6679 }
6680 
6681 unsigned char* doc_renderFontOrientation(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/,
6682                               const char* pFontName,
6683                               const char* pChar,
6684                               int* pFontWidth,
6685                               int* pFontHeight,
6686                               int pOrientation)
6687 {
6688     comphelper::ProfileZone aZone("doc_renderFont");
6689 
6690     SolarMutexGuard aGuard;
6691     SetLastExceptionMsg();
6692 
6693     const int nDefaultFontSize = 25;
6694 
6695     auto aFont = FindFont_FallbackToDefault(OStringToOUString(pFontName, RTL_TEXTENCODING_UTF8));
6696 
6697     OUString aText(OStringToOUString(pChar, RTL_TEXTENCODING_UTF8));
6698     if (aText.isEmpty())
6699         aText = aFont.GetFamilyName();
6700 
6701     auto aDevice(VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA));
6702     ::tools::Rectangle aRect;
6703     aFont.SetFontSize(Size(0, nDefaultFontSize));
6704     aFont.SetOrientation(Degree10(pOrientation));
6705     aDevice->SetFont(aFont);
6706     aDevice->GetTextBoundRect(aRect, aText);
6707     if (aRect.IsEmpty())
6708         return nullptr;
6709 
6710     int nFontWidth = aRect.Right() + 1;
6711     int nFontHeight = aRect.Bottom() + 1;
6712 
6713     if (nFontWidth <= 0 || nFontHeight <= 0)
6714         return nullptr;
6715 
6716     if (*pFontWidth > 0 && *pFontHeight > 0)
6717     {
6718         double fScaleX = *pFontWidth / static_cast<double>(nFontWidth) / 1.5;
6719         double fScaleY = *pFontHeight / static_cast<double>(nFontHeight) / 1.5;
6720 
6721         double fScale = std::min(fScaleX, fScaleY);
6722 
6723         if (fScale >= 1.0)
6724         {
6725             int nFontSize = fScale * nDefaultFontSize;
6726             aFont.SetFontSize(Size(0, nFontSize));
6727             aDevice->SetFont(aFont);
6728         }
6729 
6730         aRect = tools::Rectangle(0, 0, *pFontWidth, *pFontHeight);
6731 
6732         nFontWidth = *pFontWidth;
6733         nFontHeight = *pFontHeight;
6734 
6735     }
6736 
6737     unsigned char* pBuffer = static_cast<unsigned char*>(malloc(4 * nFontWidth * nFontHeight));
6738     if (!pBuffer)
6739         return nullptr;
6740 
6741     memset(pBuffer, 0, nFontWidth * nFontHeight * 4);
6742     aDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
6743     aDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(
6744                 Size(nFontWidth, nFontHeight), Fraction(1.0), Point(),
6745                 pBuffer);
6746 
6747     if (*pFontWidth > 0 && *pFontHeight > 0)
6748     {
6749         DrawTextFlags const nStyle =
6750                 DrawTextFlags::Center
6751                 | DrawTextFlags::VCenter
6752                 | DrawTextFlags::MultiLine
6753                 | DrawTextFlags::WordBreak;// | DrawTextFlags::WordBreakHyphenation ;
6754 
6755         aDevice->DrawText(aRect, aText, nStyle);
6756     }
6757     else
6758     {
6759         *pFontWidth = nFontWidth;
6760         *pFontHeight = nFontHeight;
6761 
6762         aDevice->DrawText(Point(0,0), aText);
6763     }
6764 
6765 
6766     return pBuffer;
6767 }
6768 
6769 
6770 static void doc_paintWindow(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId,
6771                             unsigned char* pBuffer,
6772                             const int nX, const int nY,
6773                             const int nWidth, const int nHeight)
6774 {
6775     doc_paintWindowDPI(pThis, nLOKWindowId, pBuffer, nX, nY, nWidth, nHeight, 1.0);
6776 }
6777 
6778 static void doc_paintWindowDPI(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId,
6779                                unsigned char* pBuffer,
6780                                const int nX, const int nY,
6781                                const int nWidth, const int nHeight,
6782                                const double fDPIScale)
6783 {
6784     doc_paintWindowForView(pThis, nLOKWindowId, pBuffer, nX, nY, nWidth, nHeight, fDPIScale, -1);
6785 }
6786 
6787 static void doc_paintWindowForView(LibreOfficeKitDocument* pThis, unsigned nLOKWindowId,
6788                                    unsigned char* pBuffer, const int nX, const int nY,
6789                                    const int nWidth, const int nHeight,
6790                                    const double fDPIScale, int viewId)
6791 {
6792     comphelper::ProfileZone aZone("doc_paintWindowDPI");
6793 
6794     SolarMutexGuard aGuard;
6795     SetLastExceptionMsg();
6796 
6797     VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
6798     if (!pWindow)
6799     {
6800         SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
6801         return;
6802     }
6803 
6804     // Used to avoid work in setView if set.
6805     comphelper::LibreOfficeKit::setDialogPainting(true);
6806 
6807     if (viewId >= 0)
6808         doc_setView(pThis, viewId);
6809 
6810     // Setup cairo (or CoreGraphics, in the iOS case) to draw with the changed DPI scale (and return
6811     // back to 1.0 when the painting finishes)
6812     comphelper::ScopeGuard dpiScaleGuard([]() { comphelper::LibreOfficeKit::setDPIScale(1.0); });
6813     comphelper::LibreOfficeKit::setDPIScale(fDPIScale);
6814 
6815 #if defined(IOS)
6816     // Onine uses the LOK_TILEMODE_RGBA by default so flip the normal flags
6817     // to kCGImageAlphaNoneSkipLast | kCGImageByteOrder32Big
6818     CGContextRef cgc = CGBitmapContextCreate(pBuffer, nWidth, nHeight, 8, nWidth*4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipLast | kCGImageByteOrder32Big);
6819 
6820     CGContextTranslateCTM(cgc, 0, nHeight);
6821     CGContextScaleCTM(cgc, fDPIScale, -fDPIScale);
6822 
6823     SystemGraphicsData aData;
6824     aData.rCGContext = cgc;
6825 
6826     ScopedVclPtrInstance<VirtualDevice> pDevice(aData, Size(1, 1), DeviceFormat::WITHOUT_ALPHA);
6827     pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
6828 
6829     pDevice->SetOutputSizePixel(Size(nWidth, nHeight));
6830 
6831     MapMode aMapMode(pDevice->GetMapMode());
6832     aMapMode.SetOrigin(Point(-(nX / fDPIScale), -(nY / fDPIScale)));
6833     pDevice->SetMapMode(aMapMode);
6834 
6835     pWindow->PaintToDevice(pDevice.get(), Point(0, 0));
6836 
6837     CGContextRelease(cgc);
6838 
6839 #else
6840 
6841     ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
6842     pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
6843 
6844     pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nWidth, nHeight), Fraction(1.0), Point(), pBuffer);
6845 
6846     MapMode aMapMode(pDevice->GetMapMode());
6847     aMapMode.SetOrigin(Point(-(nX / fDPIScale), -(nY / fDPIScale)));
6848     pDevice->SetMapMode(aMapMode);
6849 
6850     pWindow->PaintToDevice(pDevice.get(), Point(0, 0));
6851 #endif
6852 
6853     comphelper::LibreOfficeKit::setDialogPainting(false);
6854 }
6855 
6856 static void doc_postWindow(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId, int nAction, const char* pData)
6857 {
6858     comphelper::ProfileZone aZone("doc_postWindow");
6859 
6860     SolarMutexGuard aGuard;
6861     SetLastExceptionMsg();
6862 
6863     VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
6864     if (!pWindow)
6865     {
6866         SetLastExceptionMsg(u"Document doesn't support dialog rendering, or window not found."_ustr);
6867         return;
6868     }
6869 
6870     if (nAction == LOK_WINDOW_CLOSE)
6871     {
6872         vcl::CloseTopLevel(pWindow);
6873     }
6874     else if (nAction == LOK_WINDOW_PASTE)
6875     {
6876 #ifndef IOS
6877         OUString aMimeType;
6878         css::uno::Sequence<sal_Int8> aData;
6879         std::vector<beans::PropertyValue> aArgs(jsonToPropertyValuesVector(pData));
6880         {
6881             aArgs.size() == 2 &&
6882             aArgs[0].Name == "MimeType" && (aArgs[0].Value >>= aMimeType) &&
6883             aArgs[1].Name == "Data" && (aArgs[1].Value >>= aData);
6884         }
6885 
6886         if (!aMimeType.isEmpty() && aData.hasElements())
6887         {
6888             uno::Reference<datatransfer::XTransferable> xTransferable(new LOKTransferable(aMimeType, aData));
6889             uno::Reference<datatransfer::clipboard::XClipboard> xClipboard(new LOKClipboard);
6890             xClipboard->setContents(xTransferable, uno::Reference<datatransfer::clipboard::XClipboardOwner>());
6891             pWindow->SetClipboard(xClipboard);
6892 
6893             KeyEvent aEvent(0, KEY_PASTE, 0);
6894             Application::PostKeyEvent(VclEventId::WindowKeyInput, pWindow, &aEvent);
6895         }
6896         else
6897             SetLastExceptionMsg(u"Window command 'paste': wrong parameters."_ustr);
6898 #else
6899         (void) pData;
6900         assert(!"doc_postWindow() with LOK_WINDOW_PASTE should not be called on iOS");
6901 #endif
6902     }
6903 }
6904 
6905 // CERTIFICATE AND DOCUMENT SIGNING
6906 static bool doc_insertCertificate(LibreOfficeKitDocument* pThis,
6907                                   const unsigned char* pCertificateBinary, const int nCertificateBinarySize,
6908                                   const unsigned char* pPrivateKeyBinary, const int nPrivateKeySize)
6909 {
6910     comphelper::ProfileZone aZone("doc_insertCertificate");
6911 
6912     if (!xContext.is())
6913         return false;
6914 
6915     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
6916 
6917     if (!pDocument->mxComponent.is())
6918         return false;
6919 
6920     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
6921     if (!pBaseModel)
6922         return false;
6923 
6924     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
6925 
6926     if (!pObjectShell)
6927         return false;
6928 
6929     uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
6930     uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
6931     if (!xSecurityContext.is())
6932         return false;
6933 
6934     uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment();
6935     uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY);
6936 
6937     if (!xCertificateCreator.is())
6938         return false;
6939 
6940     uno::Sequence<sal_Int8> aCertificateSequence;
6941 
6942     std::string aCertificateString(reinterpret_cast<const char*>(pCertificateBinary), nCertificateBinarySize);
6943     std::string aCertificateBase64String = extractCertificate(aCertificateString);
6944     if (!aCertificateBase64String.empty())
6945     {
6946         OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String);
6947         comphelper::Base64::decode(aCertificateSequence, aBase64OUString);
6948     }
6949     else
6950     {
6951         aCertificateSequence.realloc(nCertificateBinarySize);
6952         std::copy(pCertificateBinary, pCertificateBinary + nCertificateBinarySize, aCertificateSequence.getArray());
6953     }
6954 
6955     uno::Sequence<sal_Int8> aPrivateKeySequence;
6956     std::string aPrivateKeyString(reinterpret_cast<const char*>(pPrivateKeyBinary), nPrivateKeySize);
6957     std::string aPrivateKeyBase64String = extractPrivateKey(aPrivateKeyString);
6958     if (!aPrivateKeyBase64String.empty())
6959     {
6960         OUString aBase64OUString = OUString::createFromAscii(aPrivateKeyBase64String);
6961         comphelper::Base64::decode(aPrivateKeySequence, aBase64OUString);
6962     }
6963     else
6964     {
6965         aPrivateKeySequence.realloc(nPrivateKeySize);
6966         std::copy(pPrivateKeyBinary, pPrivateKeyBinary + nPrivateKeySize, aPrivateKeySequence.getArray());
6967     }
6968 
6969     uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->createDERCertificateWithPrivateKey(aCertificateSequence, aPrivateKeySequence);
6970 
6971     if (!xCertificate.is())
6972         return false;
6973 
6974     SolarMutexGuard aGuard;
6975 
6976     return pObjectShell->SignDocumentContentUsingCertificate(xCertificate);
6977 }
6978 
6979 static bool doc_addCertificate(LibreOfficeKitDocument* pThis,
6980                                   const unsigned char* pCertificateBinary, const int nCertificateBinarySize)
6981 {
6982     comphelper::ProfileZone aZone("doc_addCertificate");
6983 
6984     if (!xContext.is())
6985         return false;
6986 
6987     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
6988 
6989     if (!pDocument->mxComponent.is())
6990         return false;
6991 
6992     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
6993     if (!pBaseModel)
6994         return false;
6995 
6996     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
6997 
6998     if (!pObjectShell)
6999         return false;
7000 
7001     uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
7002     uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
7003     if (!xSecurityContext.is())
7004         return false;
7005 
7006     uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment();
7007     uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY);
7008 
7009     if (!xCertificateCreator.is())
7010         return false;
7011 
7012     uno::Sequence<sal_Int8> aCertificateSequence;
7013 
7014     std::string aCertificateString(reinterpret_cast<const char*>(pCertificateBinary), nCertificateBinarySize);
7015     std::string aCertificateBase64String = extractCertificate(aCertificateString);
7016     if (!aCertificateBase64String.empty())
7017     {
7018         OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String);
7019         comphelper::Base64::decode(aCertificateSequence, aBase64OUString);
7020     }
7021     else
7022     {
7023         aCertificateSequence.realloc(nCertificateBinarySize);
7024         std::copy(pCertificateBinary, pCertificateBinary + nCertificateBinarySize, aCertificateSequence.getArray());
7025     }
7026 
7027     uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->addDERCertificateToTheDatabase(aCertificateSequence, u"TCu,Cu,Tu"_ustr);
7028 
7029     if (!xCertificate.is())
7030         return false;
7031 
7032     SAL_INFO("lok", "Certificate Added = IssuerName: " << xCertificate->getIssuerName() << " SubjectName: " << xCertificate->getSubjectName());
7033 
7034     return true;
7035 }
7036 
7037 static int doc_getSignatureState(LibreOfficeKitDocument* pThis)
7038 {
7039     comphelper::ProfileZone aZone("doc_getSignatureState");
7040 
7041     LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis);
7042 
7043     if (!pDocument->mxComponent.is())
7044         return int(SignatureState::UNKNOWN);
7045 
7046     SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(pDocument->mxComponent.get());
7047     if (!pBaseModel)
7048         return int(SignatureState::UNKNOWN);
7049 
7050     SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell();
7051     if (!pObjectShell)
7052         return int(SignatureState::UNKNOWN);
7053 
7054     SolarMutexGuard aGuard;
7055 
7056     pObjectShell->RecheckSignature(false);
7057 
7058     return int(pObjectShell->GetDocumentSignatureState());
7059 }
7060 
7061 static void doc_resizeWindow(LibreOfficeKitDocument* /*pThis*/, unsigned nLOKWindowId,
7062                              const int nWidth, const int nHeight)
7063 {
7064     SolarMutexGuard aGuard;
7065     SetLastExceptionMsg();
7066 
7067     VclPtr<Window> pWindow = vcl::Window::FindLOKWindow(nLOKWindowId);
7068     if (!pWindow)
7069     {
7070         SetLastExceptionMsg(u"Document doesn't support dialog resizing, or window not found."_ustr);
7071         return;
7072     }
7073 
7074     pWindow->SetSizePixel(Size(nWidth, nHeight));
7075 }
7076 
7077 static void doc_completeFunction(LibreOfficeKitDocument* pThis, const char* pFunctionName)
7078 {
7079     SolarMutexGuard aGuard;
7080     SetLastExceptionMsg();
7081 
7082     ITiledRenderable* pDoc = getTiledRenderable(pThis);
7083     if (!pDoc)
7084     {
7085         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
7086         return;
7087     }
7088 
7089     pDoc->completeFunction(OUString::fromUtf8(pFunctionName));
7090 }
7091 
7092 
7093 static void doc_sendFormFieldEvent(LibreOfficeKitDocument* pThis, const char* pArguments)
7094 {
7095     SolarMutexGuard aGuard;
7096 
7097     // Supported in Writer only
7098     if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT)
7099             return;
7100 
7101     StringMap aMap(jsdialog::jsonToStringMap(pArguments));
7102     ITiledRenderable* pDoc = getTiledRenderable(pThis);
7103     if (!pDoc)
7104     {
7105         SetLastExceptionMsg(u"Document doesn't support tiled rendering!"_ustr);
7106         return;
7107     }
7108 
7109     // Sanity check
7110     if (aMap.find(u"type"_ustr) == aMap.end() || aMap.find(u"cmd"_ustr) == aMap.end())
7111     {
7112         SetLastExceptionMsg(u"Wrong arguments for sendFormFieldEvent!"_ustr);
7113         return;
7114     }
7115 
7116     pDoc->executeFromFieldEvent(aMap);
7117 }
7118 
7119 static bool doc_renderSearchResult(LibreOfficeKitDocument* pThis,
7120                                      const char* pSearchResult, unsigned char** pBitmapBuffer,
7121                                      int* pWidth, int* pHeight, size_t* pByteSize)
7122 {
7123     if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT)
7124         return false;
7125 
7126     if (pBitmapBuffer == nullptr)
7127         return false;
7128 
7129     if (!pSearchResult || pSearchResult[0] == '\0')
7130         return false;
7131 
7132     ITiledRenderable* pDoc = getTiledRenderable(pThis);
7133     if (!pDoc)
7134     {
7135         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
7136         return false;
7137     }
7138 
7139     auto aRectangleVector = pDoc->getSearchResultRectangles(pSearchResult);
7140 
7141     // combine into a rectangle union
7142     basegfx::B2DRange aRangeUnion;
7143     for (basegfx::B2DRange const & rRange : aRectangleVector)
7144     {
7145         aRangeUnion.expand(rRange);
7146     }
7147 
7148     int aPixelWidth = o3tl::convert(aRangeUnion.getWidth(), o3tl::Length::twip, o3tl::Length::px);
7149     int aPixelHeight = o3tl::convert(aRangeUnion.getHeight(), o3tl::Length::twip, o3tl::Length::px);
7150 
7151     size_t nByteSize = aPixelWidth * aPixelHeight * 4;
7152 
7153     *pWidth = aPixelWidth;
7154     *pHeight = aPixelHeight;
7155     *pByteSize = nByteSize;
7156 
7157     auto* pBuffer = static_cast<unsigned char*>(std::malloc(nByteSize));
7158 
7159     doc_paintTile(pThis, pBuffer,
7160         aPixelWidth, aPixelHeight,
7161         aRangeUnion.getMinX(), aRangeUnion.getMinY(),
7162         aRangeUnion.getWidth(), aRangeUnion.getHeight());
7163 
7164     *pBitmapBuffer = pBuffer;
7165 
7166     return true;
7167 }
7168 
7169 static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const char* pArguments)
7170 {
7171     SolarMutexGuard aGuard;
7172 
7173     // Supported in Writer only
7174     if (doc_getDocumentType(pThis) != LOK_DOCTYPE_TEXT)
7175     {
7176         return;
7177     }
7178 
7179     StringMap aMap(jsdialog::jsonToStringMap(pArguments));
7180     ITiledRenderable* pDoc = getTiledRenderable(pThis);
7181     if (!pDoc)
7182     {
7183         SetLastExceptionMsg(u"Document doesn't support tiled rendering"_ustr);
7184         return;
7185     }
7186 
7187     // Sanity check
7188     if (aMap.find(u"type"_ustr) == aMap.end())
7189     {
7190         SetLastExceptionMsg(u"Missing 'type' argument for sendContentControlEvent"_ustr);
7191         return;
7192     }
7193 
7194     pDoc->executeContentControlEvent(aMap);
7195 }
7196 
7197 static void doc_setViewTimezone(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId,
7198                                 const char* pTimezone)
7199 {
7200     comphelper::ProfileZone aZone("doc_setViewTimezone");
7201 
7202     SolarMutexGuard aGuard;
7203     SetLastExceptionMsg();
7204 
7205     // Leave the default if we get a null timezone.
7206     if (pTimezone)
7207     {
7208         OUString sTimezone = OStringToOUString(pTimezone, RTL_TEXTENCODING_UTF8);
7209         SfxLokHelper::setViewTimezone(nId, true, sTimezone);
7210     }
7211 }
7212 
7213 static void doc_setAccessibilityState(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* pThis, int nId, bool nEnabled)
7214 {
7215     SolarMutexGuard aGuard;
7216 
7217     int nDocType = getDocumentType(pThis);
7218     if (!(nDocType == LOK_DOCTYPE_TEXT || nDocType == LOK_DOCTYPE_PRESENTATION || nDocType == LOK_DOCTYPE_SPREADSHEET))
7219         return;
7220 
7221     SfxLokHelper::setAccessibilityState(nId, nEnabled);
7222 }
7223 
7224 static char* lo_getError (LibreOfficeKit *pThis)
7225 {
7226     comphelper::ProfileZone aZone("lo_getError");
7227 
7228     SolarMutexGuard aGuard;
7229 
7230     LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
7231     return convertOUString(pLib->maLastExceptionMsg);
7232 }
7233 
7234 static void lo_freeError(char* pFree)
7235 {
7236     free(pFree);
7237 }
7238 
7239 static char* lo_getFilterTypes(LibreOfficeKit* pThis)
7240 {
7241     SolarMutexGuard aGuard;
7242     SetLastExceptionMsg();
7243 
7244     LibLibreOffice_Impl* pImpl = static_cast<LibLibreOffice_Impl*>(pThis);
7245 
7246     if (!xSFactory.is())
7247         xSFactory = comphelper::getProcessServiceFactory();
7248 
7249     if (!xSFactory.is())
7250     {
7251         pImpl->maLastExceptionMsg = u"Service factory is not available"_ustr;
7252         return nullptr;
7253     }
7254 
7255     uno::Reference<container::XNameAccess> xTypeDetection(xSFactory->createInstance(u"com.sun.star.document.TypeDetection"_ustr), uno::UNO_QUERY);
7256     const uno::Sequence<OUString> aTypes = xTypeDetection->getElementNames();
7257     tools::JsonWriter aJson;
7258     for (const OUString& rType : aTypes)
7259     {
7260         uno::Sequence<beans::PropertyValue> aValues;
7261         if (xTypeDetection->getByName(rType) >>= aValues)
7262         {
7263             auto it = std::find_if(std::cbegin(aValues), std::cend(aValues), [](const beans::PropertyValue& rValue) { return rValue.Name == "MediaType"; });
7264             OUString aValue;
7265             if (it != std::cend(aValues) && (it->Value >>= aValue) && !aValue.isEmpty())
7266             {
7267                 auto typeNode = aJson.startNode(rType.toUtf8());
7268                 aJson.put("MediaType", aValue.toUtf8());
7269             }
7270         }
7271     }
7272 
7273     return convertOString(aJson.finishAndGetAsOString());
7274 }
7275 
7276 static void lo_setOptionalFeatures(LibreOfficeKit* pThis, unsigned long long const features)
7277 {
7278     comphelper::ProfileZone aZone("lo_setOptionalFeatures");
7279 
7280     SolarMutexGuard aGuard;
7281     SetLastExceptionMsg();
7282 
7283     LibLibreOffice_Impl *const pLib = static_cast<LibLibreOffice_Impl*>(pThis);
7284     pLib->mOptionalFeatures = features;
7285     if (features & LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK)
7286         comphelper::LibreOfficeKit::setPartInInvalidation(true);
7287     if (features & LOK_FEATURE_NO_TILED_ANNOTATIONS)
7288         comphelper::LibreOfficeKit::setTiledAnnotations(false);
7289     if (features & LOK_FEATURE_RANGE_HEADERS)
7290         comphelper::LibreOfficeKit::setRangeHeaders(true);
7291     if (features & LOK_FEATURE_VIEWID_IN_VISCURSOR_INVALIDATION_CALLBACK)
7292         comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true);
7293 }
7294 
7295 static void lo_setDocumentPassword(LibreOfficeKit* pThis,
7296         const char* pURL, const char* pPassword)
7297 {
7298     comphelper::ProfileZone aZone("lo_setDocumentPassword");
7299 
7300     SolarMutexGuard aGuard;
7301     SetLastExceptionMsg();
7302 
7303     assert(pThis);
7304     assert(pURL);
7305     LibLibreOffice_Impl *const pLib = static_cast<LibLibreOffice_Impl*>(pThis);
7306     assert(pLib->mInteractionMap.find(OString(pURL)) != pLib->mInteractionMap.end());
7307     pLib->mInteractionMap.find(OString(pURL))->second->SetPassword(pPassword);
7308 }
7309 
7310 static char* lo_getVersionInfo(SAL_UNUSED_PARAMETER LibreOfficeKit* /*pThis*/)
7311 {
7312     SetLastExceptionMsg();
7313     return convertOUString(ReplaceStringHookProc(
7314         u"{ "
7315         "\"ProductName\": \"%PRODUCTNAME\", "
7316         "\"ProductVersion\": \"%PRODUCTVERSION\", "
7317         "\"ProductExtension\": \"%PRODUCTEXTENSION\", "
7318         "\"BuildId\": \"%BUILDID\""
7319 #if BUILDCONFIG_RECORDED
7320         ", \"BuildConfig\": \"" BUILDCONFIG "\""
7321 #endif
7322         " }"_ustr));
7323 }
7324 
7325 static void aBasicErrorFunc(const OUString& rError, const OUString& rAction)
7326 {
7327     OString aBuffer = "Unexpected dialog: " +
7328         OUStringToOString(rAction, RTL_TEXTENCODING_ASCII_US) +
7329         " Error: " +
7330         OUStringToOString(rError, RTL_TEXTENCODING_ASCII_US);
7331 
7332     fprintf(stderr, "Unexpected basic error dialog '%s'\n", aBuffer.getStr());
7333 }
7334 
7335 static bool initialize_uno(const OUString& aAppProgramURL)
7336 {
7337 #ifdef IOS
7338     // For iOS we already hardcode the inifile as "rc" in the .app directory.
7339     rtl::Bootstrap::setIniFilename(aAppProgramURL + "/" SAL_CONFIGFILE("fundamental"));
7340     xContext = cppu::defaultBootstrap_InitialComponentContext(aAppProgramURL + "/rc");
7341 #elif defined MACOSX
7342     rtl::Bootstrap::setIniFilename(aAppProgramURL + "/../Resources/" SAL_CONFIGFILE("soffice"));
7343     xContext = cppu::defaultBootstrap_InitialComponentContext();
7344 #else
7345     rtl::Bootstrap::setIniFilename(aAppProgramURL + "/" SAL_CONFIGFILE("soffice"));
7346     xContext = cppu::defaultBootstrap_InitialComponentContext();
7347 #endif
7348 
7349     if (!xContext.is())
7350     {
7351         SetLastExceptionMsg(u"XComponentContext could not be created"_ustr);
7352         SAL_INFO("lok", "XComponentContext could not be created");
7353         return false;
7354     }
7355 
7356     xFactory = xContext->getServiceManager();
7357     if (!xFactory.is())
7358     {
7359         SetLastExceptionMsg(u"XMultiComponentFactory could not be created"_ustr);
7360         SAL_INFO("lok", "XMultiComponentFactory could not be created");
7361         return false;
7362     }
7363 
7364     xSFactory.set(xFactory, uno::UNO_QUERY_THROW);
7365     comphelper::setProcessServiceFactory(xSFactory);
7366 
7367     SAL_INFO("lok", "Uno initialized  - " <<  xContext.is());
7368 
7369     // set UserInstallation to user profile dir in test/user-template
7370 //    rtl::Bootstrap aDefaultVars;
7371 //    aDefaultVars.set(OUString("UserInstallation"), aAppProgramURL + "../registry" );
7372     // configmgr setup ?
7373 
7374     return true;
7375 }
7376 
7377 // pre-unipoll version.
7378 static void lo_startmain(void*)
7379 {
7380     osl_setThreadName("lo_startmain");
7381 
7382     if (comphelper::SolarMutex::get())
7383         Application::GetSolarMutex().tryToAcquire();
7384 
7385     Application::UpdateMainThread();
7386 
7387     soffice_main();
7388 
7389     Application::ReleaseSolarMutex();
7390 }
7391 
7392 // unipoll version.
7393 static void lo_runLoop(LibreOfficeKit* /*pThis*/,
7394                        LibreOfficeKitPollCallback pPollCallback,
7395                        LibreOfficeKitWakeCallback pWakeCallback,
7396                        void* pData)
7397 {
7398 #if defined(IOS) || defined(ANDROID) || defined(__EMSCRIPTEN__)
7399     Application::GetSolarMutex().acquire();
7400 #endif
7401 
7402     {
7403         SolarMutexGuard aGuard;
7404 
7405         vcl::lok::registerPollCallbacks(pPollCallback, pWakeCallback, pData);
7406         Application::UpdateMainThread();
7407         soffice_main();
7408     }
7409 #if defined(IOS) || defined(ANDROID) || defined(__EMSCRIPTEN__)
7410     vcl::lok::unregisterPollCallbacks();
7411     Application::ReleaseSolarMutex();
7412 #endif
7413 }
7414 
7415 static bool bInitialized = false;
7416 
7417 static void lo_status_indicator_callback(void *data, comphelper::LibreOfficeKit::statusIndicatorCallbackType type, int percent, const char* pText)
7418 {
7419     LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(data);
7420 
7421     if (!pLib->mpCallback)
7422         return;
7423 
7424     switch (type)
7425     {
7426     case comphelper::LibreOfficeKit::statusIndicatorCallbackType::Start:
7427         pLib->mpCallback(LOK_CALLBACK_STATUS_INDICATOR_START, pText, pLib->mpCallbackData);
7428         break;
7429     case comphelper::LibreOfficeKit::statusIndicatorCallbackType::SetValue:
7430         pLib->mpCallback(LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE,
7431             OUString(OUString::number(percent)).toUtf8().getStr(), pLib->mpCallbackData);
7432         break;
7433     case comphelper::LibreOfficeKit::statusIndicatorCallbackType::Finish:
7434         pLib->mpCallback(LOK_CALLBACK_STATUS_INDICATOR_FINISH, nullptr, pLib->mpCallbackData);
7435         break;
7436     }
7437 }
7438 
7439 /// Used by preloadData (LibreOfficeKit) for providing different shortcuts for different languages.
7440 static void preLoadShortCutAccelerators()
7441 {
7442     std::unordered_map<OUString, css::uno::Reference<com::sun::star::ui::XAcceleratorConfiguration>>& acceleratorConfs = SfxLokHelper::getAcceleratorConfs();
7443     css::uno::Sequence<OUString> installedLocales(officecfg::Setup::Office::InstalledLocales::get()->getElementNames());
7444     OUString actualLang = officecfg::Setup::L10N::ooLocale::get();
7445 
7446     for (sal_Int32 i = 0; i < installedLocales.getLength(); i++)
7447     {
7448         // Set the UI language to current one, before creating the accelerator.
7449         std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
7450         officecfg::Setup::L10N::ooLocale::set(installedLocales[i], batch);
7451         batch->commit();
7452 
7453         // Supported module names: Writer, Calc, Draw, Impress
7454         static constexpr OUString supportedModuleNames[] = {
7455             u"com.sun.star.text.TextDocument"_ustr,
7456             u"com.sun.star.sheet.SpreadsheetDocument"_ustr,
7457             u"com.sun.star.drawing.DrawingDocument"_ustr,
7458             u"com.sun.star.presentation.PresentationDocument"_ustr,
7459         };
7460         // Create the accelerators.
7461         for (const OUString& supportedModuleName : supportedModuleNames)
7462         {
7463             OUString key = supportedModuleName + installedLocales[i];
7464             acceleratorConfs[key] = svt::AcceleratorExecute::lok_createNewAcceleratorConfiguration(::comphelper::getProcessComponentContext(), supportedModuleName);
7465         }
7466     }
7467 
7468     // Set the UI language back to default one.
7469     std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
7470     officecfg::Setup::L10N::ooLocale::set(actualLang, batch);
7471     batch->commit();
7472 }
7473 
7474 void setLanguageToolConfig();
7475 
7476 /// Used only by LibreOfficeKit when used by Online to pre-initialize
7477 static void preloadData()
7478 {
7479     comphelper::ProfileZone aZone("preload data");
7480 
7481     // Create user profile in the temp directory for loading the dictionaries
7482     OUString sUserPath;
7483     rtl::Bootstrap::get(u"UserInstallation"_ustr, sUserPath);
7484     utl::TempFileNamed aTempDir(nullptr, true);
7485     aTempDir.EnableKillingFile();
7486     rtl::Bootstrap::set(u"UserInstallation"_ustr, aTempDir.GetURL());
7487 
7488     // Register the bundled extensions
7489     desktop::Desktop::SynchronizeExtensionRepositories(true);
7490     bool bAbort = desktop::Desktop::CheckExtensionDependencies();
7491     if(bAbort)
7492         std::cerr << "CheckExtensionDependencies failed" << std::endl;
7493 
7494     std::cerr << "Preload textencodings"; // sal_textenc
7495     // Use RTL_TEXTENCODING_MS_1250 to trigger Impl_getTextEncodingData
7496     // to dlopen sal_textenclo
7497     (void)OUStringToOString(u"arbitrary string", RTL_TEXTENCODING_MS_1250);
7498     std::cerr << "\n";
7499 
7500     // setup LanguageTool config before spell checking init
7501     setLanguageToolConfig();
7502 
7503     // preload all available dictionaries
7504     linguistic2::DictionaryList::create(comphelper::getProcessComponentContext());
7505     css::uno::Reference<css::linguistic2::XLinguServiceManager> xLngSvcMgr =
7506         css::linguistic2::LinguServiceManager::create(comphelper::getProcessComponentContext());
7507     css::uno::Reference<linguistic2::XSpellChecker> xSpellChecker(xLngSvcMgr->getSpellChecker());
7508 
7509     std::cerr << "Preloading dictionaries: ";
7510     css::uno::Reference<linguistic2::XSupportedLocales> xSpellLocales(xSpellChecker, css::uno::UNO_QUERY_THROW);
7511     uno::Sequence< css::lang::Locale > aLocales = xSpellLocales->getLocales();
7512     for (auto& it : aLocales)
7513     {
7514         std::cerr << LanguageTag::convertToBcp47(it) << " ";
7515         css::beans::PropertyValues aNone;
7516         xSpellChecker->isValid(u"forcefed"_ustr, it, aNone);
7517     }
7518     std::cerr << "\n";
7519 
7520     // Hack to load and cache the module liblocaledata_others.so which is not loaded normally
7521     // (when loading dictionaries of just non-Asian locales). Creating a XCalendar4 of one Asian locale
7522     // will cheaply load this missing "others" locale library. Appending an Asian locale in
7523     // LOK_ALLOWLIST_LANGUAGES env-var also works but at the cost of loading that dictionary.
7524     css::uno::Reference< css::i18n::XCalendar4 > xCal = css::i18n::LocaleCalendar2::create(comphelper::getProcessComponentContext());
7525     css::lang::Locale aAsianLocale = { u"hi"_ustr, u"IN"_ustr, {} };
7526     xCal->loadDefaultCalendar(aAsianLocale);
7527 
7528     // preload all available thesauri
7529     css::uno::Reference<linguistic2::XThesaurus> xThesaurus(xLngSvcMgr->getThesaurus());
7530     css::uno::Reference<linguistic2::XSupportedLocales> xThesLocales(xSpellChecker, css::uno::UNO_QUERY_THROW);
7531     aLocales = xThesLocales->getLocales();
7532     std::cerr << "Preloading thesauri: ";
7533     for (auto& it : aLocales)
7534     {
7535         std::cerr << LanguageTag::convertToBcp47(it) << " ";
7536         css::beans::PropertyValues aNone;
7537         xThesaurus->queryMeanings(u"forcefed"_ustr, it, aNone);
7538     }
7539     std::cerr << "\n";
7540 
7541     css::uno::Reference< css::ui::XAcceleratorConfiguration > xGlobalCfg = css::ui::GlobalAcceleratorConfiguration::create(
7542         comphelper::getProcessComponentContext());
7543     xGlobalCfg->getAllKeyEvents();
7544 
7545     std::cerr << "Preload icons\n";
7546     ImageTree &images = ImageTree::get();
7547     images.getImageUrl(u"forcefed.png"_ustr, u"style"_ustr, u"FO_oo"_ustr);
7548 
7549     std::cerr << "Preload short cut accelerators\n";
7550     preLoadShortCutAccelerators();
7551 
7552     std::cerr << "Preload languages\n";
7553 
7554     // force load language singleton
7555     SvtLanguageTable::HasLanguageType(LANGUAGE_SYSTEM);
7556     (void)LanguageTag::isValidBcp47(u"foo"_ustr, nullptr);
7557 
7558     std::cerr << "Preload fonts\n";
7559 
7560     // Initialize fonts.
7561     css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv = css::linguistic2::LinguServiceManager::create(xContext);
7562     if (xLangSrv.is())
7563     {
7564         css::uno::Reference<css::linguistic2::XSpellChecker> xSpell = xLangSrv->getSpellChecker();
7565         if (xSpell.is())
7566             aLocales = xSpell->getLocales();
7567     }
7568 
7569     for (const auto& aLocale : aLocales)
7570     {
7571         //TODO: Add more types and cache more aggressively. For now this initializes the fontcache.
7572         using namespace ::com::sun::star::i18n::ScriptType;
7573         LanguageType nLang;
7574         nLang = MsLangId::resolveSystemLanguageByScriptType(LanguageTag::convertToLanguageType(aLocale, false), LATIN);
7575         OutputDevice::GetDefaultFont(DefaultFontType::LATIN_SPREADSHEET, nLang, GetDefaultFontFlags::OnlyOne);
7576         nLang = MsLangId::resolveSystemLanguageByScriptType(LanguageTag::convertToLanguageType(aLocale, false), ASIAN);
7577         OutputDevice::GetDefaultFont(DefaultFontType::CJK_SPREADSHEET, nLang, GetDefaultFontFlags::OnlyOne);
7578         nLang = MsLangId::resolveSystemLanguageByScriptType(LanguageTag::convertToLanguageType(aLocale, false), COMPLEX);
7579         OutputDevice::GetDefaultFont(DefaultFontType::CTL_SPREADSHEET, nLang, GetDefaultFontFlags::OnlyOne);
7580     }
7581 
7582     std::cerr << "Preload config\n";
7583 #if defined __GNUC__ || defined __clang__
7584 #pragma GCC diagnostic push
7585 #pragma GCC diagnostic ignored "-Wunused-variable"
7586 #endif
7587     static SvtOptionsDialogOptions aDialogOptions;
7588     static SvtCTLOptions aSvtCTLOptions;
7589     static svtools::ColorConfig aColorConfig;
7590     static SvtMiscOptions aSvtMiscOptions;
7591     static SvtCommandOptions aSvtCommandOptions;
7592     static SvtLinguConfig aSvtLinguConfig;
7593     static SvtModuleOptions aSvtModuleOptions;
7594     static SvtPathOptions aSvtPathOptions;
7595     static SvtSearchOptions aSvtSearchOptions;
7596     static SvtSysLocaleOptions aSvtSysLocaleOptions;
7597     static SvtUserOptions aSvtUserOptions;
7598     //static SvtViewOptions aSvtViewOptions;
7599     static MouseSettings aMouseSettings;
7600     static StyleSettings aStyleSettings;
7601     static MiscSettings aMiscSettings;
7602     static HelpSettings aHelpSettings;
7603     static AllSettings aAllSettings;
7604 #if defined __GNUC__ || defined __clang__
7605 #pragma GCC diagnostic pop
7606 #endif
7607 
7608     static constexpr OUString preloadComponents[] = {
7609         u"private:factory/swriter"_ustr,
7610         u"private:factory/scalc"_ustr,
7611         u"private:factory/simpress"_ustr,
7612         u"private:factory/sdraw"_ustr
7613     };
7614     // getting the remote LibreOffice service manager
7615     uno::Reference<frame::XDesktop2> xCompLoader(frame::Desktop::create(xContext));
7616 
7617     // Preload and close each of the main components once to initialize global state
7618     uno::Sequence<css::beans::PropertyValue> szEmptyArgs(0);
7619     for (const auto& component : preloadComponents)
7620     {
7621         auto xComp = xCompLoader->loadComponentFromURL(component, "_blank", 0, szEmptyArgs);
7622         xComp->dispose();
7623     }
7624 
7625     // Set user profile's path back to the original one
7626     rtl::Bootstrap::set(u"UserInstallation"_ustr, sUserPath);
7627 }
7628 
7629 namespace {
7630 
7631 static void activateNotebookbar(std::u16string_view rApp)
7632 {
7633     OUString aPath = OUString::Concat("org.openoffice.Office.UI.ToolbarMode/Applications/") + rApp;
7634 
7635     const utl::OConfigurationTreeRoot aAppNode(xContext, aPath, true);
7636 
7637     if (aAppNode.isValid())
7638     {
7639         static constexpr OUString sNoteBookbarName(u"notebookbar_online.ui"_ustr);
7640         aAppNode.setNodeValue(u"Active"_ustr, Any(sNoteBookbarName));
7641 
7642         const utl::OConfigurationNode aImplsNode = aAppNode.openNode(u"Modes"_ustr);
7643         const Sequence<OUString> aModeNodeNames( aImplsNode.getNodeNames() );
7644 
7645         for (const auto& rModeNodeName : aModeNodeNames)
7646         {
7647             const utl::OConfigurationNode aImplNode(aImplsNode.openNode(rModeNodeName));
7648             if (!aImplNode.isValid())
7649                 continue;
7650 
7651             OUString aCommandArg = comphelper::getString(aImplNode.getNodeValue(u"CommandArg"_ustr));
7652             if (aCommandArg == "notebookbar.ui")
7653                 aImplNode.setNodeValue(u"CommandArg"_ustr, Any(sNoteBookbarName));
7654         }
7655 
7656         aAppNode.commit();
7657     }
7658 }
7659 
7660 void setHelpRootURL()
7661 {
7662     const char* pHelpRootURL = ::getenv("LOK_HELP_URL");
7663     if (pHelpRootURL)
7664     {
7665         OUString aHelpRootURL = OStringToOUString(pHelpRootURL, RTL_TEXTENCODING_UTF8);
7666         try
7667         {
7668             std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
7669             officecfg::Office::Common::Help::HelpRootURL::set(aHelpRootURL, batch);
7670             batch->commit();
7671         }
7672         catch (uno::Exception const& rException)
7673         {
7674             SAL_WARN("lok", "Failed to set the help root URL: " << rException.Message);
7675         }
7676     }
7677 }
7678 
7679 void setCertificateDir()
7680 {
7681     const char* pEnvVarString = ::getenv("LO_CERTIFICATE_DATABASE_PATH");
7682     if (pEnvVarString)
7683     {
7684         OUString aCertificateDatabasePath = OStringToOUString(pEnvVarString, RTL_TEXTENCODING_UTF8);
7685         try
7686         {
7687             std::shared_ptr<comphelper::ConfigurationChanges> pBatch(comphelper::ConfigurationChanges::create());
7688             officecfg::Office::Common::Security::Scripting::CertDir::set(aCertificateDatabasePath, pBatch);
7689             officecfg::Office::Common::Security::Scripting::ManualCertDir::set(aCertificateDatabasePath, pBatch);
7690             pBatch->commit();
7691         }
7692         catch (uno::Exception const& rException)
7693         {
7694             SAL_WARN("lok", "Failed to set the NSS certificate database directory: " << rException.Message);
7695         }
7696     }
7697 }
7698 
7699 void setDeeplConfig()
7700 {
7701     const char* pAPIUrlString = ::getenv("DEEPL_API_URL");
7702     const char* pAuthKeyString = ::getenv("DEEPL_AUTH_KEY");
7703     if (pAPIUrlString && pAuthKeyString)
7704     {
7705         OUString aAPIUrl = OStringToOUString(pAPIUrlString, RTL_TEXTENCODING_UTF8);
7706         OUString aAuthKey = OStringToOUString(pAuthKeyString, RTL_TEXTENCODING_UTF8);
7707         try
7708         {
7709             std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
7710             officecfg::Office::Linguistic::Translation::Deepl::ApiURL::set(aAPIUrl, batch);
7711             officecfg::Office::Linguistic::Translation::Deepl::AuthKey::set(aAuthKey, batch);
7712             batch->commit();
7713         }
7714         catch(uno::Exception const& rException)
7715         {
7716             SAL_WARN("lok", "Failed to set Deepl API settings: " << rException.Message);
7717         }
7718     }
7719 }
7720 
7721 void setLanguageToolConfig()
7722 {
7723     const char* pEnabled = ::getenv("LANGUAGETOOL_ENABLED");
7724     const char* pBaseUrlString = ::getenv("LANGUAGETOOL_BASEURL");
7725 
7726     if (pEnabled && pBaseUrlString)
7727     {
7728         const char* pUsername = ::getenv("LANGUAGETOOL_USERNAME");
7729         const char* pApikey = ::getenv("LANGUAGETOOL_APIKEY");
7730         const char* pSSLVerification = ::getenv("LANGUAGETOOL_SSL_VERIFICATION");
7731         const char* pRestProtocol = ::getenv("LANGUAGETOOL_RESTPROTOCOL");
7732 
7733         OUString aEnabled = OStringToOUString(pEnabled, RTL_TEXTENCODING_UTF8);
7734         if (aEnabled != "true")
7735             return;
7736         OUString aBaseUrl = OStringToOUString(pBaseUrlString, RTL_TEXTENCODING_UTF8);
7737         try
7738         {
7739             using LanguageToolCfg = officecfg::Office::Linguistic::GrammarChecking::LanguageTool;
7740             auto batch(comphelper::ConfigurationChanges::create());
7741 
7742             LanguageToolCfg::BaseURL::set(aBaseUrl, batch);
7743             LanguageToolCfg::IsEnabled::set(true, batch);
7744             if (pSSLVerification)
7745             {
7746                 OUString aSSLVerification = OStringToOUString(pSSLVerification, RTL_TEXTENCODING_UTF8);
7747                 LanguageToolCfg::SSLCertVerify::set(aSSLVerification == "true", batch);
7748             }
7749             if (pRestProtocol)
7750             {
7751                 OUString aRestProtocol = OStringToOUString(pRestProtocol, RTL_TEXTENCODING_UTF8);
7752                 LanguageToolCfg::RestProtocol::set(aRestProtocol, batch);
7753             }
7754             if (pUsername && pApikey)
7755             {
7756                 OUString aUsername = OStringToOUString(pUsername, RTL_TEXTENCODING_UTF8);
7757                 OUString aApiKey = OStringToOUString(pApikey, RTL_TEXTENCODING_UTF8);
7758                 LanguageToolCfg::Username::set(aUsername, batch);
7759                 LanguageToolCfg::ApiKey::set(aApiKey, batch);
7760             }
7761             batch->commit();
7762 
7763             css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv =
7764                 css::linguistic2::LinguServiceManager::create(xContext);
7765             if (xLangSrv.is())
7766             {
7767                 css::uno::Reference<css::linguistic2::XSpellChecker> xSpell = xLangSrv->getSpellChecker();
7768                 if (xSpell.is())
7769                 {
7770                     Sequence<OUString> aEmpty;
7771                     Sequence<css::lang::Locale> aLocales = xSpell->getLocales();
7772 
7773                     uno::Reference<linguistic2::XProofreader> xGC(
7774                         xContext->getServiceManager()->createInstanceWithContext(u"org.openoffice.lingu.LanguageToolGrammarChecker"_ustr, xContext),
7775                         uno::UNO_QUERY_THROW);
7776                     uno::Reference<linguistic2::XSupportedLocales> xSuppLoc(xGC, uno::UNO_QUERY_THROW);
7777 
7778                     for (int itLocale = 0; itLocale < aLocales.getLength(); itLocale++)
7779                     {
7780                         // turn off spell checker if LanguageTool supports the locale already
7781                         if (xSuppLoc->hasLocale(aLocales[itLocale]))
7782                             xLangSrv->setConfiguredServices(
7783                                 SN_SPELLCHECKER, aLocales[itLocale], aEmpty);
7784                     }
7785                 }
7786             }
7787         }
7788         catch(uno::Exception const& rException)
7789         {
7790             SAL_WARN("lok", "Failed to set LanguageTool API settings: " << rException.Message);
7791         }
7792     }
7793 }
7794 
7795 }
7796 
7797 static int lo_initialize(LibreOfficeKit* pThis, const char* pAppPath, const char* pUserProfileUrl)
7798 {
7799     enum {
7800         PRE_INIT,     // setup shared data in master process
7801         SECOND_INIT,  // complete init. after fork
7802         FULL_INIT     // do a standard complete init.
7803     } eStage;
7804 
7805     // Did we do a pre-initialize
7806     static bool bPreInited = false;
7807     static bool bUnipoll = false;
7808     static bool bProfileZones = false;
7809     static bool bNotebookbar = false;
7810 
7811     { // cf. string lifetime for preinit
7812         std::vector<OUString> aOpts;
7813 
7814         // ':' delimited options - avoiding ABI change for new parameters
7815         const char *pOptions = getenv("SAL_LOK_OPTIONS");
7816         if (pOptions)
7817             aOpts = comphelper::string::split(OUString(pOptions, strlen(pOptions), RTL_TEXTENCODING_UTF8), ':');
7818         for (const auto &it : aOpts)
7819         {
7820             if (it == "unipoll")
7821                 bUnipoll = true;
7822             else if (it == "profile_events")
7823                 bProfileZones = true;
7824             else if (it == "sc_no_grid_bg")
7825                 comphelper::LibreOfficeKit::setCompatFlag(
7826                     comphelper::LibreOfficeKit::Compat::scNoGridBackground);
7827             else if (it == "sc_print_twips_msgs")
7828                 comphelper::LibreOfficeKit::setCompatFlag(
7829                     comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
7830             else if (it == "notebookbar")
7831                 bNotebookbar = true;
7832         }
7833     }
7834 
7835     char* pAllowlist = ::getenv("LOK_HOST_ALLOWLIST");
7836     if (pAllowlist)
7837     {
7838         HostFilter::setAllowedHostsRegex(pAllowlist);
7839     }
7840 
7841     // What stage are we at ?
7842     if (pThis == nullptr)
7843     {
7844         eStage = PRE_INIT;
7845         if (lok_preinit_2_called)
7846         {
7847             SAL_INFO("lok", "Create libreoffice object");
7848             gImpl = new LibLibreOffice_Impl();
7849         }
7850     }
7851     else if (bPreInited)
7852         eStage = SECOND_INIT;
7853     else
7854         eStage = FULL_INIT;
7855 
7856     LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
7857 
7858     if (bInitialized)
7859         return 1;
7860 
7861     // Turn profile zones on early
7862     if (bProfileZones && eStage == SECOND_INIT)
7863     {
7864         comphelper::TraceEvent::startRecording();
7865         traceEventDumper = new TraceEventDumper();
7866     }
7867 
7868     comphelper::ProfileZone aZone("lok-init");
7869 
7870     if (eStage == PRE_INIT)
7871     {
7872         rtl_alloc_preInit(true);
7873 
7874         // Set the default timezone to the TZ envar, if set.
7875         const char* tz = ::getenv("TZ");
7876         SfxLokHelper::setDefaultTimezone(!!tz, tz ? OStringToOUString(tz, RTL_TEXTENCODING_UTF8)
7877                                                   : OUString());
7878     }
7879 
7880     if (eStage != SECOND_INIT)
7881         comphelper::LibreOfficeKit::setActive();
7882 
7883     if (eStage != PRE_INIT)
7884         comphelper::LibreOfficeKit::setStatusIndicatorCallback(lo_status_indicator_callback, pLib);
7885 
7886     if (pUserProfileUrl && eStage != PRE_INIT)
7887     {
7888         OUString url(
7889             pUserProfileUrl, strlen(pUserProfileUrl), RTL_TEXTENCODING_UTF8);
7890         OUString path;
7891         if (url.startsWithIgnoreAsciiCase("vnd.sun.star.pathname:", &path))
7892         {
7893             OUString url2;
7894             osl::FileBase::RC e = osl::FileBase::getFileURLFromSystemPath(
7895                 path, url2);
7896             if (e == osl::FileBase::E_None)
7897                 url = url2;
7898             else
7899                 SAL_WARN("lok", "resolving <" << url << "> failed with " << +e);
7900         }
7901         rtl::Bootstrap::set(u"UserInstallation"_ustr, url);
7902         if (eStage == SECOND_INIT)
7903             utl::Bootstrap::reloadData();
7904     }
7905 
7906     OUString aAppPath;
7907     if (pAppPath)
7908     {
7909         aAppPath = OUString(pAppPath, strlen(pAppPath), RTL_TEXTENCODING_UTF8);
7910     }
7911     else
7912     {
7913 #if defined ANDROID || defined EMSCRIPTEN
7914         aAppPath = OUString::fromUtf8(lo_get_app_data_dir()) + "/program";
7915 #else
7916         // Fun conversion dance back and forth between URLs and system paths...
7917         OUString aAppURL;
7918         ::osl::Module::getUrlFromAddress( reinterpret_cast< oslGenericFunction >(lo_initialize),
7919                                           aAppURL);
7920         osl::FileBase::getSystemPathFromFileURL( aAppURL, aAppPath );
7921 #endif
7922 
7923 #ifdef IOS
7924         // The above gives something like
7925         // "/private/var/containers/Bundle/Application/953AA851-CC15-4C60-A2CB-C2C6F24E6F71/Foo.app/Foo",
7926         // and we want to drop the final component (the binary name).
7927         sal_Int32 lastSlash = aAppPath.lastIndexOf('/');
7928         assert(lastSlash > 0);
7929         aAppPath = aAppPath.copy(0, lastSlash);
7930 #endif
7931     }
7932 
7933     OUString aAppURL;
7934     if (osl::FileBase::getFileURLFromSystemPath(aAppPath, aAppURL) != osl::FileBase::E_None)
7935         return 0;
7936 
7937 #ifdef IOS
7938     // A LibreOffice-using iOS app should have the ICU data file in the app bundle. Initialize ICU
7939     // to use that.
7940     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
7941 
7942     int fd = open([[bundlePath stringByAppendingPathComponent:@"ICU.dat"] UTF8String], O_RDONLY);
7943     if (fd == -1)
7944         NSLog(@"Could not open ICU data file %s", [[bundlePath stringByAppendingPathComponent:@"ICU.dat"] UTF8String]);
7945     else
7946     {
7947         struct stat st;
7948         if (fstat(fd, &st) == -1)
7949             NSLog(@"fstat on ICU data file failed: %s", strerror(errno));
7950         else
7951         {
7952             void *icudata = mmap(0, (size_t) st.st_size, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0);
7953             if (icudata == MAP_FAILED)
7954                 NSLog(@"mmap failed: %s", strerror(errno));
7955             else
7956             {
7957                 UErrorCode icuStatus = U_ZERO_ERROR;
7958                 udata_setCommonData(icudata, &icuStatus);
7959                 if (U_FAILURE(icuStatus))
7960                     NSLog(@"udata_setCommonData failed");
7961                 else
7962                 {
7963                     // Quick test that ICU works...
7964                     UConverter *cnv = ucnv_open("iso-8859-3", &icuStatus);
7965                     if (U_SUCCESS(icuStatus))
7966                         ucnv_close(cnv);
7967                     else
7968                         NSLog(@"ucnv_open() failed: %s", u_errorName(icuStatus));
7969                 }
7970             }
7971         }
7972         close(fd);
7973     }
7974 #endif
7975 
7976     try
7977     {
7978         if (eStage != SECOND_INIT)
7979         {
7980             SAL_INFO("lok", "Attempting to initialize UNO");
7981 
7982             if (!initialize_uno(aAppURL))
7983                 return false;
7984 
7985             // Force headless -- this is only for bitmap rendering.
7986             rtl::Bootstrap::set(u"SAL_USE_VCLPLUGIN"_ustr, u"svp"_ustr);
7987 
7988             // We specifically need to make sure we have the "headless"
7989             // command arg set (various code specifically checks via
7990             // CommandLineArgs):
7991             desktop::Desktop::GetCommandLineArgs().setHeadless();
7992 
7993 #ifdef IOS
7994             if (InitVCL() && [NSThread isMainThread])
7995             {
7996                 static bool bFirstTime = true;
7997                 if (bFirstTime)
7998                 {
7999                     Application::GetSolarMutex().release();
8000                     bFirstTime = false;
8001                 }
8002             }
8003             SfxApplication::GetOrCreate();
8004 #endif
8005 
8006 #if HAVE_FEATURE_ANDROID_LOK
8007             // Register the bundled extensions - so that the dictionaries work
8008             desktop::Desktop::SynchronizeExtensionRepositories(false);
8009             bool bFailed = desktop::Desktop::CheckExtensionDependencies();
8010             if (bFailed)
8011                 SAL_INFO("lok", "CheckExtensionDependencies failed");
8012 #endif
8013 
8014             if (eStage == PRE_INIT)
8015             {
8016                 {
8017                     comphelper::ProfileZone aInit("Init vcl");
8018                     std::cerr << "Init vcl\n";
8019                     InitVCL();
8020                 }
8021 
8022                 // pre-load all component libraries.
8023                 if (!xContext.is())
8024                     throw css::uno::DeploymentException(u"preInit: XComponentContext is not created"_ustr);
8025 
8026                 css::uno::Reference< css::uno::XInterface > xService;
8027                 xContext->getValueByName(u"/singletons/com.sun.star.lang.theServiceManager"_ustr) >>= xService;
8028                 if (!xService.is())
8029                     throw css::uno::DeploymentException(u"preInit: XMultiComponentFactory is not created"_ustr);
8030 
8031                 css::uno::Reference<css::lang::XInitialization> aService(
8032                     xService, css::uno::UNO_QUERY_THROW);
8033 
8034                 // pre-requisites:
8035                 // In order to load implementations and invoke
8036                 // component factory it is required:
8037                 // 1) defaultBootstrap_InitialComponentContext()
8038                 // 2) comphelper::setProcessServiceFactory(xSFactory);
8039                 // 3) InitVCL()
8040                 {
8041                     comphelper::ProfileZone aInit("preload");
8042                     aService->initialize({css::uno::Any(u"preload"_ustr)});
8043                 }
8044                 { // Force load some modules
8045                     comphelper::ProfileZone aInit("preload modules");
8046                     VclBuilderPreload();
8047                     VclAbstractDialogFactory::Create();
8048                 }
8049 
8050                 preloadData();
8051 
8052                 // Release Solar Mutex, lo_startmain thread should acquire it.
8053                 Application::ReleaseSolarMutex();
8054             }
8055 
8056             setLanguageAndLocale(u"en-US"_ustr);
8057         }
8058 
8059         if (eStage != PRE_INIT)
8060         {
8061             SAL_INFO("lok", "Re-initialize temp paths");
8062             SvtPathOptions aOptions;
8063             OUString aNewTemp;
8064             osl::FileBase::getTempDirURL(aNewTemp);
8065             aOptions.SetTempPath(aNewTemp);
8066             {
8067                 const char *pWorkPath = getenv("LOK_WORKDIR");
8068                 if (pWorkPath)
8069                 {
8070                     OString sWorkPath(pWorkPath);
8071                     aOptions.SetWorkPath(OStringToOUString(sWorkPath, RTL_TEXTENCODING_UTF8));
8072                 }
8073             }
8074             desktop::Desktop::CreateTemporaryDirectory();
8075 
8076             // The RequestHandler is specifically set to be ready when all the other
8077             // init in Desktop::Main (run from soffice_main) is done. We can enable
8078             // the RequestHandler here (without starting any IPC thread;
8079             // shortcutting the invocation in Desktop::Main that would start the IPC
8080             // thread), and can then use it to wait until we're definitely ready to
8081             // continue.
8082 
8083             SAL_INFO("lok", "Enabling RequestHandler");
8084             RequestHandler::Enable(false);
8085             SAL_INFO("lok", "Starting soffice_main");
8086             RequestHandler::SetReady(false);
8087             if (!bUnipoll)
8088             {
8089                 // Start the main thread only in non-unipoll mode (i.e. multithreaded).
8090                 pLib->maThread = osl_createThread(lo_startmain, nullptr);
8091                 SAL_INFO("lok", "Waiting for RequestHandler");
8092                 RequestHandler::WaitForReady();
8093                 SAL_INFO("lok", "RequestHandler ready -- continuing");
8094             }
8095             else
8096                 InitVCL();
8097         }
8098 
8099         if (eStage != SECOND_INIT)
8100             ErrorRegistry::RegisterDisplay(aBasicErrorFunc);
8101 
8102         SAL_INFO("lok", "LOK Initialized");
8103         if (eStage == PRE_INIT)
8104             bPreInited = true;
8105         else
8106             bInitialized = true;
8107     }
8108     catch (css::uno::Exception& exception)
8109     {
8110         fprintf(stderr, "Bootstrapping exception '%s'\n",
8111                  OUStringToOString(exception.Message, RTL_TEXTENCODING_UTF8).getStr());
8112     }
8113 
8114     if (eStage == PRE_INIT)
8115     {
8116         comphelper::ThreadPool::getSharedOptimalPool().shutdown();
8117     }
8118 
8119 // Turn off quick editing on iOS, Android and Emscripten
8120 #if defined IOS || defined ANDROID || defined __EMSCRIPTEN__
8121     if (officecfg::Office::Impress::Misc::TextObject::QuickEditing::get())
8122     {
8123         std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
8124         officecfg::Office::Impress::Misc::TextObject::QuickEditing::set(false, batch);
8125         batch->commit();
8126     }
8127 #endif
8128 
8129 
8130     setHelpRootURL();
8131     setCertificateDir();
8132     setDeeplConfig();
8133     setLanguageToolConfig();
8134 
8135     if (bNotebookbar)
8136     {
8137         std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
8138         officecfg::Office::UI::ToolbarMode::ActiveWriter::set(u"notebookbar_online.ui"_ustr, batch);
8139         officecfg::Office::UI::ToolbarMode::ActiveCalc::set(u"notebookbar_online.ui"_ustr, batch);
8140         officecfg::Office::UI::ToolbarMode::ActiveImpress::set(u"notebookbar_online.ui"_ustr, batch);
8141         officecfg::Office::UI::ToolbarMode::ActiveDraw::set(u"notebookbar_online.ui"_ustr, batch);
8142         batch->commit();
8143 
8144         activateNotebookbar(u"Writer");
8145         activateNotebookbar(u"Calc");
8146         activateNotebookbar(u"Impress");
8147         activateNotebookbar(u"Draw");
8148     }
8149 
8150     // staticize all strings.
8151     if (eStage == PRE_INIT)
8152         rtl_alloc_preInit(false);
8153 
8154     return bInitialized;
8155 }
8156 
8157 SAL_JNI_EXPORT
8158 LibreOfficeKit *libreofficekit_hook_2(const char* install_path, const char* user_profile_url)
8159 {
8160     static bool alreadyCalled = false;
8161 
8162     if ((!lok_preinit_2_called && !gImpl) || (lok_preinit_2_called && !alreadyCalled))
8163     {
8164         alreadyCalled = true;
8165 
8166         if (!lok_preinit_2_called)
8167         {
8168             SAL_INFO("lok", "Create libreoffice object");
8169             gImpl = new LibLibreOffice_Impl();
8170         }
8171 
8172         if (!lo_initialize(gImpl, install_path, user_profile_url))
8173         {
8174             lo_destroy(gImpl);
8175         }
8176     }
8177     return static_cast<LibreOfficeKit*>(gImpl);
8178 }
8179 
8180 SAL_JNI_EXPORT
8181 LibreOfficeKit *libreofficekit_hook(const char* install_path)
8182 {
8183     return libreofficekit_hook_2(install_path, nullptr);
8184 }
8185 
8186 SAL_JNI_EXPORT
8187 int lok_preinit(const char* install_path, const char* user_profile_url)
8188 {
8189     return lo_initialize(nullptr, install_path, user_profile_url);
8190 }
8191 
8192 SAL_JNI_EXPORT
8193 int lok_preinit_2(const char* install_path, const char* user_profile_url, LibreOfficeKit** kit)
8194 {
8195     lok_preinit_2_called = true;
8196     int result = lo_initialize(nullptr, install_path, user_profile_url);
8197     if (kit != nullptr)
8198         *kit = gImpl;
8199     return result;
8200 }
8201 
8202 static void lo_destroy(LibreOfficeKit* pThis)
8203 {
8204     SolarMutexClearableGuard aGuard;
8205 
8206     LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis);
8207     gImpl = nullptr;
8208 
8209     SAL_INFO("lok", "LO Destroy");
8210 
8211     comphelper::LibreOfficeKit::setStatusIndicatorCallback(nullptr, nullptr);
8212     uno::Reference <frame::XDesktop2> xDesktop = frame::Desktop::create ( ::comphelper::getProcessComponentContext() );
8213     // FIXME: the terminate() call here is a no-op because it detects
8214     // that LibreOfficeKit::isActive() and then returns early!
8215     bool bSuccess = xDesktop.is() && xDesktop->terminate();
8216 
8217     if (!bSuccess)
8218     {
8219         bSuccess = GetpApp() && GetpApp()->QueryExit();
8220     }
8221 
8222     if (!bSuccess)
8223     {
8224         Application::Quit();
8225     }
8226 
8227     aGuard.clear();
8228 
8229     osl_joinWithThread(pLib->maThread);
8230     osl_destroyThread(pLib->maThread);
8231 
8232     delete pLib;
8233     bInitialized = false;
8234     SAL_INFO("lok", "LO Destroy Done");
8235 }
8236 
8237 } // extern "C"
8238 
8239 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
8240