1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
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>
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>
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>
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
;
89 OUString
convertLcTo32Path(std::u16string_view rPath
)
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);
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
);
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
);
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
151 OSL_ASSERT(rInputStream
.is());
152 std::shared_ptr
<SvMemoryStream
> aMemoryStream(std::make_shared
<SvMemoryStream
>());
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
);
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());
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('/');
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");
240 OUString
ImplImageTree::getImageUrl(OUString
const & rName
, OUString
const & rStyle
, OUString
const & rLang
)
242 OUString
aStyle(rStyle
);
244 while (!aStyle
.isEmpty())
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
)
269 catch (const uno::Exception
&)
271 TOOLS_INFO_EXCEPTION("vcl", "");
274 aStyle
= fallbackStyle(aStyle
);
279 uno::Reference
<io::XInputStream
> ImplImageTree::getImageXInputStream(OUString
const & rName
, OUString
const & rStyle
, OUString
const & rLang
)
281 OUString
aStyle(rStyle
);
283 while (!aStyle
.isEmpty())
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
;
303 (void)ok
; // prevent unused warning in release build
309 catch (const uno::Exception
&)
311 TOOLS_INFO_EXCEPTION("vcl", "");
314 aStyle
= fallbackStyle(aStyle
);
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
);
323 return wrapStream(xStream
);
324 return std::shared_ptr
<SvMemoryStream
>();
327 OUString
ImplImageTree::fallbackStyle(std::u16string_view rsStyle
)
331 if (rsStyle
== u
"colibre" || rsStyle
== u
"helpimg")
333 else if (rsStyle
== u
"sifr" || rsStyle
== u
"breeze_dark")
335 else if (rsStyle
== u
"sifr_dark" )
336 sResult
= "breeze_dark";
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
))
355 catch (uno::RuntimeException
&)
358 aCurrentStyle
= fallbackStyle(aCurrentStyle
);
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
)
379 bool loadDiskCachedVersion(std::u16string_view sVariant
, ImageRequestParameters
& rParameters
)
381 OUString
sUrl(getIconCacheUrl(sVariant
, rParameters
));
382 if (!urlExists(sUrl
))
384 SvFileStream
aFileStream(sUrl
, StreamMode::READ
);
385 vcl::PngImageReader
aPNGReader(aFileStream
);
386 aPNGReader
.read(rParameters
.mrBitmap
);
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
);
404 } // end anonymous namespace
406 bool ImplImageTree::doLoadImage(ImageRequestParameters
& rParameters
)
408 setStyle(rParameters
.msStyle
);
410 if (iconCacheLookup(rParameters
))
413 OUString aVariant
= createVariant(rParameters
);
414 if (loadDiskCachedVersion(aVariant
, rParameters
))
417 if (!rParameters
.mrBitmap
.IsEmpty())
418 rParameters
.mrBitmap
.SetEmpty();
420 LanguageTag aLanguageTag
= Application::GetSettings().GetUILanguageTag();
422 std::vector
<OUString
> aPaths
= getPaths(rParameters
.msName
, aLanguageTag
);
428 bFound
= findImage(aPaths
, rParameters
);
430 catch (uno::RuntimeException
&)
434 catch (const uno::Exception
&)
436 TOOLS_INFO_EXCEPTION("vcl", "ImplImageTree::doLoadImage");
441 if (rParameters
.mbWriteImageToCache
)
443 cacheBitmapToDisk(aVariant
, rParameters
);
445 getIconCache(rParameters
)[rParameters
.msName
] = std::make_pair(rParameters
.mbLocalized
, rParameters
.mrBitmap
);
451 void ImplImageTree::shutdown()
453 maCurrentStyle
.clear();
457 void ImplImageTree::setStyle(OUString
const & style
)
459 assert(!style
.isEmpty());
460 if (style
!= maCurrentStyle
)
462 maCurrentStyle
= style
;
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())
486 if (maCurrentStyle
== "default")
487 sThemeUrl
= "file://" SRC_ROOT
"/icon-themes/colibre-svg";
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
));
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
);
509 sThemeUrl
= aUrl
.GetMainURL(INetURLObject::DecodeMechanism::NONE
) + ".zip";
510 if (urlExists(sThemeUrl
))
515 if (sThemeUrl
.isEmpty())
520 sThemeUrl
+= "images";
521 if (!urlExists(sThemeUrl
))
525 maIconSets
[maCurrentStyle
] = IconSet(sThemeUrl
);
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() )
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
;
555 bool ImplImageTree::findImage(std::vector
<OUString
> const & rPaths
, ImageRequestParameters
& rParameters
)
557 if (!checkPathAccess())
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
;
569 (void)ok
; // prevent unused warning in release build
571 loadImageFromStream(wrapStream(aStream
), rPath
, rParameters
);
579 void ImplImageTree::loadImageLinks()
581 static const OUStringLiteral
aLinkFilename(u
"links.txt");
583 if (!checkPathAccess())
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
;
593 (void)ok
; // prevent unused warning in release build
595 parseLinkFile(wrapStream(xStream
));
600 void ImplImageTree::parseLinkFile(std::shared_ptr
<SvStream
> const & xStream
)
603 OUString aLink
, aOriginal
;
605 while (xStream
->ReadLine(aLine
))
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
);
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
);
640 auto it
= rLinkHash
.find(sNameWithNoExtension
+ ".png");
641 if (it
!= rLinkHash
.end())
644 // also check SVG name
645 it
= rLinkHash
.find(sNameWithNoExtension
+ ".svg");
646 if (it
!= rLinkHash
.end())
649 // neither was found so just return the original name
655 class FolderFileAccess
: public ::cppu::WeakImplHelper
<css::container::XNameAccess
>
658 uno::Reference
< uno::XComponentContext
> mxContext
;
660 FolderFileAccess(uno::Reference
< uno::XComponentContext
> context
, OUString url
)
661 : mxContext(std::move(context
)), maURL(std::move(url
)) {}
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; }
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
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())
694 rNameAccess
= new FolderFileAccess(comphelper::getProcessComponentContext(), rIconSet
.maURL
);
696 rNameAccess
= packages::zip::ZipFileAccess::createWithURL(comphelper::getProcessComponentContext(), rIconSet
.maURL
);
698 catch (const uno::RuntimeException
&)
702 catch (const uno::Exception
&)
704 TOOLS_INFO_EXCEPTION("vcl", "ImplImageTree::zip file location " << rIconSet
.maURL
);
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: */