bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / source / image / ImplImageTree.cxx
blobbdd4fcedfda50f50c7b8712a9500bf761873cf09
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_folders.h>
22 #include <sal/config.h>
23 #include <sal/log.hxx>
25 #include <deque>
26 #include <string_view>
28 #include <com/sun/star/container/XNameAccess.hpp>
29 #include <com/sun/star/io/XInputStream.hpp>
30 #include <com/sun/star/packages/zip/ZipFileAccess.hpp>
31 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
32 #include <com/sun/star/uno/Exception.hpp>
33 #include <com/sun/star/uno/RuntimeException.hpp>
34 #include <com/sun/star/uno/Sequence.hxx>
35 #include <comphelper/processfactory.hxx>
36 #include <cppuhelper/implbase.hxx>
37 #include <osl/file.hxx>
38 #include <osl/diagnose.h>
39 #include <osl/process.h>
40 #include <rtl/bootstrap.hxx>
41 #include <rtl/uri.hxx>
42 #include <rtl/strbuf.hxx>
44 #include <comphelper/diagnose_ex.hxx>
45 #include <tools/stream.hxx>
46 #include <tools/urlobj.hxx>
47 #include <implimagetree.hxx>
49 #include <utility>
50 #include <vcl/bitmapex.hxx>
51 #include <vcl/dibtools.hxx>
52 #include <vcl/settings.hxx>
53 #include <vcl/svapp.hxx>
54 #include <vcl/BitmapTools.hxx>
55 #include <IconThemeScanner.hxx>
56 #include <vcl/filter/PngImageReader.hxx>
57 #include <vcl/outdev.hxx>
58 #include <vcl/filter/PngImageWriter.hxx>
59 #include <o3tl/string_view.hxx>
60 #include <bitmap/BitmapLightenFilter.hxx>
62 using namespace css;
64 bool ImageRequestParameters::convertToDarkTheme()
66 static bool bIconsForDarkTheme = !!getenv("VCL_ICONS_FOR_DARK_THEME");
68 bool bConvertToDarkTheme = false;
69 if (!(meFlags & ImageLoadFlags::IgnoreDarkTheme))
70 bConvertToDarkTheme = bIconsForDarkTheme;
72 return bConvertToDarkTheme;
75 sal_Int32 ImageRequestParameters::scalePercentage()
77 sal_Int32 aScalePercentage = 100;
78 OutputDevice* pDevice = Application::GetDefaultDevice();
79 if (!(meFlags & ImageLoadFlags::IgnoreScalingFactor) && pDevice != nullptr)
80 aScalePercentage = pDevice->GetDPIScalePercentage();
81 else if (mnScalePercentage > 0)
82 aScalePercentage = mnScalePercentage;
83 return aScalePercentage;
86 namespace
89 OUString convertLcTo32Path(std::u16string_view rPath)
91 OUString aResult;
92 size_t nSlashPos = rPath.rfind('/');
93 if (nSlashPos != std::u16string_view::npos)
95 sal_Int32 nCopyFrom = nSlashPos + 1;
96 std::u16string_view sFile = rPath.substr(nCopyFrom);
97 std::u16string_view sDir = rPath.substr(0, nSlashPos);
98 if (!sFile.empty() && o3tl::starts_with(sFile, u"lc_"))
100 aResult = OUString::Concat(sDir) + "/32/" + sFile.substr(3);
103 return aResult;
106 OUString createPath(std::u16string_view name, sal_Int32 pos, std::u16string_view locale)
108 return OUString::Concat(name.substr(0, pos + 1)) + locale + name.substr(pos);
111 OUString getIconCacheUrl(std::u16string_view sVariant, ImageRequestParameters const & rParameters)
113 // the macro expansion can be expensive in bulk, so cache that
114 static OUString CACHE_DIR = []()
116 OUString sDir = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/";
117 rtl::Bootstrap::expandMacros(sDir);
118 return sDir;
119 }();
120 return CACHE_DIR + rParameters.msStyle + "/" + sVariant + "/" + rParameters.msName;
123 OUString createIconCacheUrl(
124 std::u16string_view sVariant, ImageRequestParameters const & rParameters)
126 OUString sUrl(getIconCacheUrl(sVariant, rParameters));
127 OUString sDir = sUrl.copy(0, sUrl.lastIndexOf('/'));
128 osl::Directory::createPath(sDir);
129 return sUrl;
132 bool urlExists(OUString const & sUrl)
134 osl::File aFile(sUrl);
135 osl::FileBase::RC eRC = aFile.open(osl_File_OpenFlag_Read);
136 return osl::FileBase::E_None == eRC;
139 OUString getNameNoExtension(std::u16string_view sName)
141 size_t nDotPosition = sName.rfind('.');
142 return OUString(sName.substr(0, nDotPosition));
145 std::shared_ptr<SvMemoryStream> wrapStream(uno::Reference<io::XInputStream> const & rInputStream)
147 // This could use SvInputStream instead if that did not have a broken
148 // SeekPos implementation for an XInputStream that is not also XSeekable
149 // (cf. "@@@" at tags/DEV300_m37/svtools/source/misc1/strmadpt.cxx@264807
150 // l. 593):
151 OSL_ASSERT(rInputStream.is());
152 std::shared_ptr<SvMemoryStream> aMemoryStream(std::make_shared<SvMemoryStream>());
153 for (;;)
155 const sal_Int32 nSize(2048);
156 uno::Sequence<sal_Int8> aData(nSize);
157 sal_Int32 nRead = rInputStream->readBytes(aData, nSize);
158 aMemoryStream->WriteBytes(aData.getConstArray(), nRead);
159 if (nRead < nSize)
160 break;
162 aMemoryStream->Seek(0);
163 rInputStream->closeInput();
164 return aMemoryStream;
167 void loadImageFromStream(std::shared_ptr<SvStream> const & xStream, OUString const & rPath, ImageRequestParameters& rParameters)
169 bool bConvertToDarkTheme = rParameters.convertToDarkTheme();
170 sal_Int32 aScalePercentage = rParameters.scalePercentage();
172 if (rPath.endsWith(".png"))
174 vcl::PngImageReader aPNGReader(*xStream);
175 aPNGReader.read(rParameters.mrBitmap);
177 else if (rPath.endsWith(".svg"))
179 rParameters.mbWriteImageToCache = true; // We always want to cache a SVG image
180 vcl::bitmap::loadFromSvg(*xStream, rPath, rParameters.mrBitmap, aScalePercentage / 100.0);
182 if (bConvertToDarkTheme)
183 BitmapFilter::Filter(rParameters.mrBitmap, BitmapLightenFilter());
185 return;
187 else
189 ReadDIBBitmapEx(rParameters.mrBitmap, *xStream);
192 if (bConvertToDarkTheme)
194 rParameters.mbWriteImageToCache = true; // Cache the dark variant
195 BitmapFilter::Filter(rParameters.mrBitmap, BitmapLightenFilter());
198 if (aScalePercentage > 100)
200 rParameters.mbWriteImageToCache = true; // Cache the scaled variant
201 double aScaleFactor(aScalePercentage / 100.0);
202 // when scaling use the full 24bit RGB values
203 rParameters.mrBitmap.Convert(BmpConversion::N24Bit);
204 rParameters.mrBitmap.Scale(aScaleFactor, aScaleFactor, BmpScaleFlag::Fast);
208 } // end anonymous namespace
210 ImplImageTree::ImplImageTree()
214 ImplImageTree::~ImplImageTree()
218 std::vector<OUString> ImplImageTree::getPaths(OUString const & name, LanguageTag const & rLanguageTag)
220 std::vector<OUString> sPaths;
222 sal_Int32 pos = name.lastIndexOf('/');
223 if (pos != -1)
225 for (const OUString& rFallback : rLanguageTag.getFallbackStrings(true))
227 OUString aFallbackName = getNameNoExtension(getRealImageName(createPath(name, pos, rFallback)));
228 sPaths.emplace_back(aFallbackName + ".png");
229 sPaths.emplace_back(aFallbackName + ".svg");
233 OUString aRealName = getNameNoExtension(getRealImageName(name));
234 sPaths.emplace_back(aRealName + ".png");
235 sPaths.emplace_back(aRealName + ".svg");
237 return sPaths;
240 OUString ImplImageTree::getImageUrl(OUString const & rName, OUString const & rStyle, OUString const & rLang)
242 OUString aStyle(rStyle);
244 while (!aStyle.isEmpty())
248 setStyle(aStyle);
250 if (checkPathAccess())
252 IconSet& rIconSet = getCurrentIconSet();
253 const uno::Reference<container::XNameAccess> & rNameAccess = rIconSet.maNameAccess;
255 LanguageTag aLanguageTag(rLang);
257 for (const OUString& rPath: getPaths(rName, aLanguageTag))
259 if (rNameAccess->hasByName(rPath))
261 return "vnd.sun.star.zip://"
262 + rtl::Uri::encode(rIconSet.maURL, rtl_UriCharClassRegName,
263 rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8)
264 + "/" + rPath;
269 catch (const uno::Exception &)
271 TOOLS_INFO_EXCEPTION("vcl", "");
274 aStyle = fallbackStyle(aStyle);
276 return OUString();
279 uno::Reference<io::XInputStream> ImplImageTree::getImageXInputStream(OUString const & rName, OUString const & rStyle, OUString const & rLang)
281 OUString aStyle(rStyle);
283 while (!aStyle.isEmpty())
287 setStyle(aStyle);
289 if (checkPathAccess())
291 IconSet& rIconSet = getCurrentIconSet();
292 const uno::Reference<container::XNameAccess>& rNameAccess = rIconSet.maNameAccess;
294 LanguageTag aLanguageTag(rLang);
296 for (const OUString& rPath: getPaths(rName, aLanguageTag))
298 if (rNameAccess->hasByName(rPath))
300 uno::Reference<io::XInputStream> aStream;
301 bool ok = rNameAccess->getByName(rPath) >>= aStream;
302 assert(ok);
303 (void)ok; // prevent unused warning in release build
304 return aStream;
309 catch (const uno::Exception &)
311 TOOLS_INFO_EXCEPTION("vcl", "");
314 aStyle = fallbackStyle(aStyle);
316 return nullptr;
319 std::shared_ptr<SvMemoryStream> ImplImageTree::getImageStream(OUString const & rName, OUString const & rStyle, OUString const & rLang)
321 uno::Reference<io::XInputStream> xStream = getImageXInputStream(rName, rStyle, rLang);
322 if (xStream)
323 return wrapStream(xStream);
324 return std::shared_ptr<SvMemoryStream>();
327 OUString ImplImageTree::fallbackStyle(std::u16string_view rsStyle)
329 OUString sResult;
331 if (rsStyle == u"colibre" || rsStyle == u"helpimg")
332 sResult = "";
333 else if (rsStyle == u"sifr" || rsStyle == u"breeze_dark")
334 sResult = "breeze";
335 else if (rsStyle == u"sifr_dark" )
336 sResult = "breeze_dark";
337 else
338 sResult = "colibre";
340 return sResult;
343 bool ImplImageTree::loadImage(OUString const & rName, OUString const & rStyle, BitmapEx & rBitmap, bool localized,
344 const ImageLoadFlags eFlags, sal_Int32 nScalePercentage)
346 OUString aCurrentStyle(rStyle);
347 while (!aCurrentStyle.isEmpty())
351 ImageRequestParameters aParameters(rName, aCurrentStyle, rBitmap, localized, eFlags, nScalePercentage);
352 if (doLoadImage(aParameters))
353 return true;
355 catch (uno::RuntimeException &)
358 aCurrentStyle = fallbackStyle(aCurrentStyle);
360 return false;
363 namespace
366 OUString createVariant(ImageRequestParameters& rParameters)
368 bool bConvertToDarkTheme = rParameters.convertToDarkTheme();
369 sal_Int32 aScalePercentage = rParameters.scalePercentage();
371 OUString aVariant = OUString::number(aScalePercentage);
373 if (bConvertToDarkTheme)
374 aVariant += "-dark";
376 return aVariant;
379 bool loadDiskCachedVersion(std::u16string_view sVariant, ImageRequestParameters& rParameters)
381 OUString sUrl(getIconCacheUrl(sVariant, rParameters));
382 if (!urlExists(sUrl))
383 return false;
384 SvFileStream aFileStream(sUrl, StreamMode::READ);
385 vcl::PngImageReader aPNGReader(aFileStream);
386 aPNGReader.read(rParameters.mrBitmap);
387 return true;
390 void cacheBitmapToDisk(std::u16string_view sVariant, ImageRequestParameters const & rParameters)
392 OUString sUrl(createIconCacheUrl(sVariant, rParameters));
395 SvFileStream aStream(sUrl, StreamMode::WRITE);
396 vcl::PngImageWriter aWriter(aStream);
397 aWriter.write(rParameters.mrBitmap);
398 aStream.Close();
400 catch (...)
404 } // end anonymous namespace
406 bool ImplImageTree::doLoadImage(ImageRequestParameters& rParameters)
408 setStyle(rParameters.msStyle);
410 if (iconCacheLookup(rParameters))
411 return true;
413 OUString aVariant = createVariant(rParameters);
414 if (loadDiskCachedVersion(aVariant, rParameters))
415 return true;
417 if (!rParameters.mrBitmap.IsEmpty())
418 rParameters.mrBitmap.SetEmpty();
420 LanguageTag aLanguageTag = Application::GetSettings().GetUILanguageTag();
422 std::vector<OUString> aPaths = getPaths(rParameters.msName, aLanguageTag);
424 bool bFound = false;
428 bFound = findImage(aPaths, rParameters);
430 catch (uno::RuntimeException&)
432 throw;
434 catch (const uno::Exception&)
436 TOOLS_INFO_EXCEPTION("vcl", "ImplImageTree::doLoadImage");
439 if (bFound)
441 if (rParameters.mbWriteImageToCache)
443 cacheBitmapToDisk(aVariant, rParameters);
445 getIconCache(rParameters)[rParameters.msName] = std::make_pair(rParameters.mbLocalized, rParameters.mrBitmap);
448 return bFound;
451 void ImplImageTree::shutdown()
453 maCurrentStyle.clear();
454 maIconSets.clear();
457 void ImplImageTree::setStyle(OUString const & style)
459 assert(!style.isEmpty());
460 if (style != maCurrentStyle)
462 maCurrentStyle = style;
463 createStyle();
468 * The vcldemo app doesn't set up all the config stuff that the main app does, so we need another
469 * way of finding the cursor images.
471 static bool isVclDemo()
473 static const bool bVclDemoOverride = std::getenv("LIBO_VCL_DEMO") != nullptr;
474 return bVclDemoOverride;
477 void ImplImageTree::createStyle()
479 if (maIconSets.find(maCurrentStyle) != maIconSets.end())
480 return;
482 OUString sThemeUrl;
484 if (isVclDemo())
486 if (maCurrentStyle == "default")
487 sThemeUrl = "file://" SRC_ROOT "/icon-themes/colibre-svg";
488 else
489 sThemeUrl = "file://" SRC_ROOT "/icon-themes/" + maCurrentStyle;
491 else if (maCurrentStyle != "default")
493 OUString paths = vcl::IconThemeScanner::GetStandardIconThemePath();
494 std::deque<OUString> aPaths;
495 sal_Int32 nIndex = 0;
498 aPaths.push_front(paths.getToken(0, ';', nIndex));
500 while (nIndex >= 0);
502 for (const auto& path : aPaths)
504 INetURLObject aUrl(path);
505 OSL_ASSERT(!aUrl.HasError());
507 bool ok = aUrl.Append(Concat2View("images_" + maCurrentStyle), INetURLObject::EncodeMechanism::All);
508 OSL_ASSERT(ok);
509 sThemeUrl = aUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE) + ".zip";
510 if (urlExists(sThemeUrl))
511 break;
512 sThemeUrl.clear();
515 if (sThemeUrl.isEmpty())
516 return;
518 else
520 sThemeUrl += "images";
521 if (!urlExists(sThemeUrl))
522 return;
525 maIconSets[maCurrentStyle] = IconSet(sThemeUrl);
527 loadImageLinks();
530 /// Find an icon cache for the right scale factor
531 ImplImageTree::IconCache &ImplImageTree::getIconCache(const ImageRequestParameters& rParameters)
533 IconSet &rSet = getCurrentIconSet();
534 auto it = rSet.maScaledIconCaches.find(rParameters.mnScalePercentage);
535 if ( it != rSet.maScaledIconCaches.end() )
536 return it->second;
537 rSet.maScaledIconCaches.emplace(rParameters.mnScalePercentage, IconCache());
538 return rSet.maScaledIconCaches[rParameters.mnScalePercentage];
541 bool ImplImageTree::iconCacheLookup(ImageRequestParameters& rParameters)
543 IconCache& rIconCache = getIconCache(rParameters);
545 IconCache::iterator i(rIconCache.find(getRealImageName(rParameters.msName)));
546 if (i != rIconCache.end() && i->second.first == rParameters.mbLocalized)
548 rParameters.mrBitmap = i->second.second;
549 return true;
552 return false;
555 bool ImplImageTree::findImage(std::vector<OUString> const & rPaths, ImageRequestParameters& rParameters)
557 if (!checkPathAccess())
558 return false;
560 uno::Reference<container::XNameAccess> const & rNameAccess = getCurrentIconSet().maNameAccess;
562 for (OUString const & rPath : rPaths)
564 if (rNameAccess->hasByName(rPath))
566 uno::Reference<io::XInputStream> aStream;
567 bool ok = rNameAccess->getByName(rPath) >>= aStream;
568 assert(ok);
569 (void)ok; // prevent unused warning in release build
571 loadImageFromStream(wrapStream(aStream), rPath, rParameters);
573 return true;
576 return false;
579 void ImplImageTree::loadImageLinks()
581 static const OUStringLiteral aLinkFilename(u"links.txt");
583 if (!checkPathAccess())
584 return;
586 const uno::Reference<container::XNameAccess> &rNameAccess = getCurrentIconSet().maNameAccess;
588 if (rNameAccess->hasByName(aLinkFilename))
590 uno::Reference<io::XInputStream> xStream;
591 bool ok = rNameAccess->getByName(aLinkFilename) >>= xStream;
592 assert(ok);
593 (void)ok; // prevent unused warning in release build
595 parseLinkFile(wrapStream(xStream));
596 return;
600 void ImplImageTree::parseLinkFile(std::shared_ptr<SvStream> const & xStream)
602 OStringBuffer aLine;
603 OUString aLink, aOriginal;
604 int nLineNo = 0;
605 while (xStream->ReadLine(aLine))
607 ++nLineNo;
608 if (aLine.isEmpty())
609 continue;
611 sal_Int32 nIndex = 0;
612 aLink = OStringToOUString(o3tl::getToken(aLine, 0, ' ', nIndex), RTL_TEXTENCODING_UTF8);
613 aOriginal = OStringToOUString(o3tl::getToken(aLine, 0, ' ', nIndex), RTL_TEXTENCODING_UTF8);
615 // skip comments, or incomplete entries
616 if (aLink.isEmpty() || aLink[0] == '#' || aOriginal.isEmpty())
618 if (aLink.isEmpty() || aOriginal.isEmpty())
619 SAL_WARN("vcl", "ImplImageTree::parseLinkFile: icon links.txt parse error, incomplete link at line " << nLineNo);
620 continue;
623 getCurrentIconSet().maLinkHash[aLink] = aOriginal;
625 OUString aOriginal32 = convertLcTo32Path(aOriginal);
626 OUString aLink32 = convertLcTo32Path(aLink);
628 if (!aOriginal32.isEmpty() && !aLink32.isEmpty())
629 getCurrentIconSet().maLinkHash[aLink32] = aOriginal32;
633 OUString const & ImplImageTree::getRealImageName(OUString const & rIconName)
635 IconLinkHash & rLinkHash = maIconSets[maCurrentStyle].maLinkHash;
637 OUString sNameWithNoExtension = getNameNoExtension(rIconName);
639 // PNG is priority
640 auto it = rLinkHash.find(sNameWithNoExtension + ".png");
641 if (it != rLinkHash.end())
642 return it->second;
644 // also check SVG name
645 it = rLinkHash.find(sNameWithNoExtension + ".svg");
646 if (it != rLinkHash.end())
647 return it->second;
649 // neither was found so just return the original name
650 return rIconName;
653 namespace {
655 class FolderFileAccess : public ::cppu::WeakImplHelper<css::container::XNameAccess>
657 public:
658 uno::Reference< uno::XComponentContext > mxContext;
659 OUString maURL;
660 FolderFileAccess(uno::Reference< uno::XComponentContext > context, OUString url)
661 : mxContext(std::move(context)), maURL(std::move(url)) {}
662 // XElementAccess
663 virtual css::uno::Type SAL_CALL getElementType() override { return cppu::UnoType<io::XInputStream>::get(); }
664 virtual sal_Bool SAL_CALL hasElements() override { return true; }
665 // XNameAccess
666 virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override
668 uno::Reference< io::XInputStream > xInputStream = ucb::SimpleFileAccess::create(mxContext)->openFileRead( maURL + "/" + aName );
669 return css::uno::Any(xInputStream);
671 virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override
673 return {};
675 virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override
677 osl::File aBaseFile(maURL + "/" + aName);
678 return osl::File::E_None == aBaseFile.open(osl_File_OpenFlag_Read);
684 bool ImplImageTree::checkPathAccess()
686 IconSet& rIconSet = getCurrentIconSet();
687 uno::Reference<container::XNameAccess> & rNameAccess = rIconSet.maNameAccess;
688 if (rNameAccess.is())
689 return true;
693 if (isVclDemo())
694 rNameAccess = new FolderFileAccess(comphelper::getProcessComponentContext(), rIconSet.maURL);
695 else
696 rNameAccess = packages::zip::ZipFileAccess::createWithURL(comphelper::getProcessComponentContext(), rIconSet.maURL);
698 catch (const uno::RuntimeException &)
700 throw;
702 catch (const uno::Exception &)
704 TOOLS_INFO_EXCEPTION("vcl", "ImplImageTree::zip file location " << rIconSet.maURL);
705 return false;
707 return rNameAccess.is();
710 uno::Reference<container::XNameAccess> const & ImplImageTree::getNameAccess()
712 (void)checkPathAccess();
713 return getCurrentIconSet().maNameAccess;
716 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */