1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <memory>
21 #include <string_view>
22 
23 #include <o3tl/lru_map.hxx>
24 #include <unx/fontmanager.hxx>
25 #include <unx/helper.hxx>
26 #include <comphelper/sequence.hxx>
27 #include <vcl/svapp.hxx>
28 #include <vcl/vclenum.hxx>
29 #include <fontselect.hxx>
30 #include <i18nlangtag/languagetag.hxx>
31 #include <i18nutil/unicode.hxx>
32 #include <rtl/strbuf.hxx>
33 #include <sal/log.hxx>
34 #include <tools/diagnose_ex.h>
35 #include <unicode/uchar.h>
36 #include <unicode/uscript.h>
37 #include <officecfg/Office/Common.hxx>
38 #include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp>
39 
40 using namespace psp;
41 
42 #include <fontconfig/fontconfig.h>
43 
44 #include <cstdio>
45 
46 #include <unotools/configmgr.hxx>
47 
48 #include <osl/process.h>
49 
50 #include <o3tl/hash_combine.hxx>
51 #include <utility>
52 #include <algorithm>
53 
54 using namespace osl;
55 
56 namespace
57 {
58 
59 struct FontOptionsKey
60 {
61     OUString m_sFamilyName;
62     int m_nFontSize;
63     FontItalic m_eItalic;
64     FontWeight m_eWeight;
65     FontWidth m_eWidth;
66     FontPitch m_ePitch;
67 
operator ==__anone964c3460111::FontOptionsKey68     bool operator==(const FontOptionsKey& rOther) const
69     {
70         return m_sFamilyName == rOther.m_sFamilyName &&
71                m_nFontSize == rOther.m_nFontSize &&
72                m_eItalic == rOther.m_eItalic &&
73                m_eWeight == rOther.m_eWeight &&
74                m_eWidth == rOther.m_eWidth &&
75                m_ePitch == rOther.m_ePitch;
76     }
77 };
78 
79 }
80 
81 namespace std
82 {
83 
84 template <> struct hash<FontOptionsKey>
85 {
operator ()std::hash86     std::size_t operator()(const FontOptionsKey& k) const noexcept
87     {
88         std::size_t seed = k.m_sFamilyName.hashCode();
89         o3tl::hash_combine(seed, k.m_nFontSize);
90         o3tl::hash_combine(seed, k.m_eItalic);
91         o3tl::hash_combine(seed, k.m_eWeight);
92         o3tl::hash_combine(seed, k.m_eWidth);
93         o3tl::hash_combine(seed, k.m_ePitch);
94         return seed;
95     }
96 };
97 
98 } // end std namespace
99 
100 namespace
101 {
102 
103 struct FcPatternDeleter
104 {
operator ()__anone964c3460211::FcPatternDeleter105     void operator()(FcPattern* pPattern) const
106     {
107         FcPatternDestroy(pPattern);
108     }
109 };
110 
111 typedef std::unique_ptr<FcPattern, FcPatternDeleter> FcPatternUniquePtr;
112 
113 class CachedFontConfigFontOptions
114 {
115 private:
116     o3tl::lru_map<FontOptionsKey, FcPatternUniquePtr> lru_options_cache;
117 
118 public:
CachedFontConfigFontOptions()119     CachedFontConfigFontOptions()
120         : lru_options_cache(10) // arbitrary cache size of 10
121     {
122     }
123 
lookup(const FontOptionsKey & rKey)124     std::unique_ptr<FontConfigFontOptions> lookup(const FontOptionsKey& rKey)
125     {
126         auto it = lru_options_cache.find(rKey);
127         if (it != lru_options_cache.end())
128             return std::make_unique<FontConfigFontOptions>(FcPatternDuplicate(it->second.get()));
129         return nullptr;
130     }
131 
cache(const FontOptionsKey & rKey,const FcPattern * pPattern)132     void cache(const FontOptionsKey& rKey, const FcPattern* pPattern)
133     {
134         lru_options_cache.insert(std::make_pair(rKey, FcPatternUniquePtr(FcPatternDuplicate(pPattern))));
135     }
136 
137 };
138 
139 typedef std::pair<FcChar8*, FcChar8*> lang_and_element;
140 
141 class FontCfgWrapper
142 {
143     FcFontSet* m_pFontSet;
144 
145     void addFontSet( FcSetName );
146 
147     FontCfgWrapper();
148     ~FontCfgWrapper();
149 
150 public:
151     static FontCfgWrapper& get();
152     static void release();
153 
154     FcFontSet* getFontSet();
155 
156     void clear();
157 
158 public:
159     FcResult LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **family,
160                                          const char *elementtype, const char *elementlangtype);
161 //to-do, make private and add some cleaner accessor methods
162     std::unordered_map< OString, OString > m_aFontNameToLocalized;
163     std::unordered_map< OString, OString > m_aLocalizedToCanonical;
164     CachedFontConfigFontOptions m_aCachedFontOptions;
165 private:
166     void cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, const std::vector< lang_and_element > &lang_and_elements);
167 
168     std::unique_ptr<LanguageTag> m_pLanguageTag;
169 };
170 
171 }
172 
FontCfgWrapper()173 FontCfgWrapper::FontCfgWrapper()
174     : m_pFontSet( nullptr )
175 {
176     FcInit();
177 }
178 
addFontSet(FcSetName eSetName)179 void FontCfgWrapper::addFontSet( FcSetName eSetName )
180 {
181     // Add only acceptable fonts to our config, for future fontconfig use.
182     FcFontSet* pOrig = FcConfigGetFonts( FcConfigGetCurrent(), eSetName );
183     if( !pOrig )
184         return;
185 
186     // filter the font sets to remove obsolete faces
187     for( int i = 0; i < pOrig->nfont; ++i )
188     {
189         FcPattern* pPattern = pOrig->fonts[i];
190         // #i115131# ignore non-scalable fonts
191         // Scalable fonts are usually outline fonts, but some bitmaps fonts
192         // (like Noto Color Emoji) are also scalable.
193         FcBool bScalable = FcFalse;
194         FcResult eScalableRes = FcPatternGetBool(pPattern, FC_SCALABLE, 0, &bScalable);
195         if ((eScalableRes != FcResultMatch) || (bScalable == FcFalse))
196             continue;
197 
198         // Ignore Type 1 fonts, too.
199         FcChar8* pFormat = nullptr;
200         FcResult eFormatRes = FcPatternGetString(pPattern, FC_FONTFORMAT, 0, &pFormat);
201         if ((eFormatRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pFormat), "Type 1") == 0))
202             continue;
203 
204         FcPatternReference( pPattern );
205         FcFontSetAdd( m_pFontSet, pPattern );
206     }
207 
208     // TODO?: FcFontSetDestroy( pOrig );
209 }
210 
211 namespace
212 {
compareFontNames(const FcPattern * a,const FcPattern * b)213     int compareFontNames(const FcPattern *a, const FcPattern *b)
214     {
215         FcChar8 *pNameA=nullptr, *pNameB=nullptr;
216 
217         bool bHaveA = FcPatternGetString(a, FC_FAMILY, 0, &pNameA) == FcResultMatch;
218         bool bHaveB = FcPatternGetString(b, FC_FAMILY, 0, &pNameB) == FcResultMatch;
219 
220         if (bHaveA && bHaveB)
221             return strcmp(reinterpret_cast<const char*>(pNameA), reinterpret_cast<const char*>(pNameB));
222 
223         return int(bHaveA) - int(bHaveB);
224     }
225 
226     //Sort fonts so that fonts with the same family name are side-by-side, with
227     //those with higher version numbers first
228     class SortFont
229     {
230     public:
operator ()(const FcPattern * a,const FcPattern * b)231         bool operator()(const FcPattern *a, const FcPattern *b)
232         {
233             int comp = compareFontNames(a, b);
234             if (comp != 0)
235                 return comp < 0;
236 
237             int nVersionA=0, nVersionB=0;
238 
239             bool bHaveA = FcPatternGetInteger(a, FC_FONTVERSION, 0, &nVersionA) == FcResultMatch;
240             bool bHaveB = FcPatternGetInteger(b, FC_FONTVERSION, 0, &nVersionB) == FcResultMatch;
241 
242             if (bHaveA && bHaveB)
243                 return nVersionA > nVersionB;
244 
245             return bHaveA > bHaveB;
246         }
247     };
248 
249     //See fdo#30729 for where an old opensymbol installed system-wide can
250     //clobber the new opensymbol installed locally
251 
252     //See if this font is a duplicate with equal attributes which has already been
253     //inserted, or if it an older version of an inserted fonts. Depends on FcFontSet
254     //on being sorted with SortFont
isPreviouslyDuplicateOrObsoleted(FcFontSet const * pFSet,int i)255     bool isPreviouslyDuplicateOrObsoleted(FcFontSet const *pFSet, int i)
256     {
257         const FcPattern *a = pFSet->fonts[i];
258 
259         FcPattern* pTestPatternA = FcPatternDuplicate(a);
260         FcPatternDel(pTestPatternA, FC_FILE);
261         FcPatternDel(pTestPatternA, FC_CHARSET);
262         FcPatternDel(pTestPatternA, FC_CAPABILITY);
263         FcPatternDel(pTestPatternA, FC_FONTVERSION);
264         FcPatternDel(pTestPatternA, FC_LANG);
265 
266         bool bIsDup(false);
267 
268         // fdo#66715: loop for case of several font files for same font
269         for (int j = i - 1; 0 <= j && !bIsDup; --j)
270         {
271             const FcPattern *b = pFSet->fonts[j];
272 
273             if (compareFontNames(a, b) != 0)
274                 break;
275 
276             FcPattern* pTestPatternB = FcPatternDuplicate(b);
277             FcPatternDel(pTestPatternB, FC_FILE);
278             FcPatternDel(pTestPatternB, FC_CHARSET);
279             FcPatternDel(pTestPatternB, FC_CAPABILITY);
280             FcPatternDel(pTestPatternB, FC_FONTVERSION);
281             FcPatternDel(pTestPatternB, FC_LANG);
282 
283             bIsDup = FcPatternEqual(pTestPatternA, pTestPatternB);
284 
285             FcPatternDestroy(pTestPatternB);
286         }
287 
288         FcPatternDestroy(pTestPatternA);
289 
290         return bIsDup;
291     }
292 }
293 
getFontSet()294 FcFontSet* FontCfgWrapper::getFontSet()
295 {
296     if( !m_pFontSet )
297     {
298         m_pFontSet = FcFontSetCreate();
299         addFontSet( FcSetSystem );
300         addFontSet( FcSetApplication );
301 
302         std::stable_sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont());
303     }
304 
305     return m_pFontSet;
306 }
307 
~FontCfgWrapper()308 FontCfgWrapper::~FontCfgWrapper()
309 {
310     clear();
311     //To-Do: get gtk vclplug smoketest to pass
312     //FcFini();
313 }
314 
315 static FontCfgWrapper* pOneInstance = nullptr;
316 
get()317 FontCfgWrapper& FontCfgWrapper::get()
318 {
319     if( ! pOneInstance )
320         pOneInstance = new FontCfgWrapper();
321     return *pOneInstance;
322 }
323 
release()324 void FontCfgWrapper::release()
325 {
326     if( pOneInstance )
327     {
328         delete pOneInstance;
329         pOneInstance = nullptr;
330     }
331 }
332 
333 namespace
334 {
335     FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag);
336 
bestname(const std::vector<lang_and_element> & elements,const LanguageTag & rLangTag)337     FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag)
338     {
339         FcChar8* candidate = elements.begin()->second;
340         /* FIXME-BCP47: once fontconfig supports language tags this
341          * language-territory stuff needs to be changed! */
342         SAL_INFO_IF( !rLangTag.isIsoLocale(), "vcl.fonts", "localizedsorter::bestname - not an ISO locale");
343         OString sLangMatch(OUStringToOString(rLangTag.getLanguage().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8));
344         OString sFullMatch = sLangMatch +
345             "-" +
346             OUStringToOString(rLangTag.getCountry().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8);
347 
348         bool alreadyclosematch = false;
349         bool found_fallback_englishname = false;
350         for (auto const& element : elements)
351         {
352             const char *pLang = reinterpret_cast<const char*>(element.first);
353             if( sFullMatch == pLang)
354             {
355                 // both language and country match
356                 candidate = element.second;
357                 break;
358             }
359             else if( alreadyclosematch )
360             {
361                 // current candidate matches lang of lang-TERRITORY
362                 // override candidate only if there is a full match
363                 continue;
364             }
365             else if( sLangMatch == pLang)
366             {
367                 // just the language matches
368                 candidate = element.second;
369                 alreadyclosematch = true;
370             }
371             else if( found_fallback_englishname )
372             {
373                 // already found an english fallback, don't override candidate
374                 // unless there is a better language match
375                 continue;
376             }
377             else if( rtl_str_compare( pLang, "en") == 0)
378             {
379                 // select a fallback candidate of the first english element
380                 // name
381                 candidate = element.second;
382                 found_fallback_englishname = true;
383             }
384         }
385         return candidate;
386     }
387 }
388 
389 //Set up maps to quickly map between a fonts best UI name and all the rest of its names, and vice versa
cacheLocalizedFontNames(const FcChar8 * origfontname,const FcChar8 * bestfontname,const std::vector<lang_and_element> & lang_and_elements)390 void FontCfgWrapper::cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname,
391     const std::vector< lang_and_element > &lang_and_elements)
392 {
393     for (auto const& element : lang_and_elements)
394     {
395         const char *candidate = reinterpret_cast<const char*>(element.second);
396         if (rtl_str_compare(candidate, reinterpret_cast<const char*>(bestfontname)) != 0)
397             m_aFontNameToLocalized[OString(candidate)] = OString(reinterpret_cast<const char*>(bestfontname));
398     }
399     if (rtl_str_compare(reinterpret_cast<const char*>(origfontname), reinterpret_cast<const char*>(bestfontname)) != 0)
400         m_aLocalizedToCanonical[OString(reinterpret_cast<const char*>(bestfontname))] = OString(reinterpret_cast<const char*>(origfontname));
401 }
402 
LocalizedElementFromPattern(FcPattern const * pPattern,FcChar8 ** element,const char * elementtype,const char * elementlangtype)403 FcResult FontCfgWrapper::LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **element,
404                                                      const char *elementtype, const char *elementlangtype)
405 {                                                /* e. g.:      ^ FC_FAMILY              ^ FC_FAMILYLANG */
406     FcChar8 *origelement;
407     FcResult eElementRes = FcPatternGetString( pPattern, elementtype, 0, &origelement );
408     *element = origelement;
409 
410     if( eElementRes == FcResultMatch)
411     {
412         FcChar8* elementlang = nullptr;
413         if (FcPatternGetString( pPattern, elementlangtype, 0, &elementlang ) == FcResultMatch)
414         {
415             std::vector< lang_and_element > lang_and_elements;
416             lang_and_elements.emplace_back(elementlang, *element);
417             int k = 1;
418             while (true)
419             {
420                 if (FcPatternGetString( pPattern, elementlangtype, k, &elementlang ) != FcResultMatch)
421                     break;
422                 if (FcPatternGetString( pPattern, elementtype, k, element ) != FcResultMatch)
423                     break;
424                 lang_and_elements.emplace_back(elementlang, *element);
425                 ++k;
426             }
427 
428             //possible to-do, sort by UILocale instead of process locale
429             if (!m_pLanguageTag)
430             {
431                 rtl_Locale* pLoc = nullptr;
432                 osl_getProcessLocale(&pLoc);
433                 m_pLanguageTag.reset( new LanguageTag(*pLoc) );
434             }
435             *element = bestname(lang_and_elements, *m_pLanguageTag);
436 
437             //if this element is a fontname, map the other names to this best-name
438             if (rtl_str_compare(elementtype, FC_FAMILY) == 0)
439                 cacheLocalizedFontNames(origelement, *element, lang_and_elements);
440         }
441     }
442 
443     return eElementRes;
444 }
445 
clear()446 void FontCfgWrapper::clear()
447 {
448     m_aFontNameToLocalized.clear();
449     m_aLocalizedToCanonical.clear();
450     if( m_pFontSet )
451     {
452         FcFontSetDestroy( m_pFontSet );
453         m_pFontSet = nullptr;
454     }
455     m_pLanguageTag.reset();
456 }
457 
458 /*
459  * PrintFontManager::initFontconfig
460  */
initFontconfig()461 void PrintFontManager::initFontconfig()
462 {
463     FontCfgWrapper& rWrapper = FontCfgWrapper::get();
464     rWrapper.clear();
465 }
466 
467 namespace
468 {
convertWeight(int weight)469     FontWeight convertWeight(int weight)
470     {
471         // set weight
472         if( weight <= FC_WEIGHT_THIN )
473             return WEIGHT_THIN;
474         else if( weight <= FC_WEIGHT_ULTRALIGHT )
475             return WEIGHT_ULTRALIGHT;
476         else if( weight <= FC_WEIGHT_LIGHT )
477             return WEIGHT_LIGHT;
478         else if( weight <= FC_WEIGHT_BOOK )
479             return WEIGHT_SEMILIGHT;
480         else if( weight <= FC_WEIGHT_NORMAL )
481             return WEIGHT_NORMAL;
482         else if( weight <= FC_WEIGHT_MEDIUM )
483             return WEIGHT_MEDIUM;
484         else if( weight <= FC_WEIGHT_SEMIBOLD )
485             return WEIGHT_SEMIBOLD;
486         else if( weight <= FC_WEIGHT_BOLD )
487             return WEIGHT_BOLD;
488         else if( weight <= FC_WEIGHT_ULTRABOLD )
489             return WEIGHT_ULTRABOLD;
490         return WEIGHT_BLACK;
491     }
492 
convertSlant(int slant)493     FontItalic convertSlant(int slant)
494     {
495         // set italic
496         if( slant == FC_SLANT_ITALIC )
497             return ITALIC_NORMAL;
498         else if( slant == FC_SLANT_OBLIQUE )
499             return ITALIC_OBLIQUE;
500         return ITALIC_NONE;
501     }
502 
convertSpacing(int spacing)503     FontPitch convertSpacing(int spacing)
504     {
505         // set pitch
506         if( spacing == FC_MONO || spacing == FC_CHARCELL )
507             return PITCH_FIXED;
508         return PITCH_VARIABLE;
509     }
510 
511     // translation: fontconfig enum -> vcl enum
convertWidth(int width)512     FontWidth convertWidth(int width)
513     {
514         if (width == FC_WIDTH_ULTRACONDENSED)
515             return WIDTH_ULTRA_CONDENSED;
516         else if (width == FC_WIDTH_EXTRACONDENSED)
517             return WIDTH_EXTRA_CONDENSED;
518         else if (width == FC_WIDTH_CONDENSED)
519             return WIDTH_CONDENSED;
520         else if (width == FC_WIDTH_SEMICONDENSED)
521             return WIDTH_SEMI_CONDENSED;
522         else if (width == FC_WIDTH_SEMIEXPANDED)
523             return WIDTH_SEMI_EXPANDED;
524         else if (width == FC_WIDTH_EXPANDED)
525             return WIDTH_EXPANDED;
526         else if (width == FC_WIDTH_EXTRAEXPANDED)
527             return WIDTH_EXTRA_EXPANDED;
528         else if (width == FC_WIDTH_ULTRAEXPANDED)
529             return WIDTH_ULTRA_EXPANDED;
530         return WIDTH_NORMAL;
531     }
532 }
533 
534 //FontConfig doesn't come with a way to remove an element from a FontSet as far
535 //as I can see
lcl_FcFontSetRemove(FcFontSet * pFSet,int i)536 static void lcl_FcFontSetRemove(FcFontSet* pFSet, int i)
537 {
538     FcPatternDestroy(pFSet->fonts[i]);
539 
540     int nTail = pFSet->nfont - (i + 1);
541     --pFSet->nfont;
542     if (!nTail)
543         return;
544     memmove(pFSet->fonts + i, pFSet->fonts + i + 1, nTail*sizeof(FcPattern*));
545 }
546 
547 namespace
548 {
549     // for variable fonts, FC_INDEX has been changed such that the lower half is now the
550     // index of the font within the collection, and the upper half has been repurposed
551     // as the index within the variations
GetCollectionIndex(unsigned int nEntryId)552     unsigned int GetCollectionIndex(unsigned int nEntryId)
553     {
554         return nEntryId & 0xFFFF;
555     }
556 
GetVariationIndex(unsigned int nEntryId)557     unsigned int GetVariationIndex(unsigned int nEntryId)
558     {
559         return nEntryId >> 16;
560     }
561 }
562 
countFontconfigFonts(std::unordered_map<OString,int> & o_rVisitedPaths)563 void PrintFontManager::countFontconfigFonts( std::unordered_map<OString, int>& o_rVisitedPaths )
564 {
565     int nFonts = 0;
566     FontCfgWrapper& rWrapper = FontCfgWrapper::get();
567 
568     FcFontSet* pFSet = rWrapper.getFontSet();
569     const bool bMinimalFontset = utl::ConfigManager::IsFuzzing();
570     if( pFSet )
571     {
572         SAL_INFO("vcl.fonts", "found " << pFSet->nfont << " entries in fontconfig fontset");
573         for( int i = 0; i < pFSet->nfont; i++ )
574         {
575             FcChar8* file = nullptr;
576             FcChar8* family = nullptr;
577             FcChar8* style = nullptr;
578             FcChar8* format = nullptr;
579             int slant = 0;
580             int weight = 0;
581             int width = 0;
582             int spacing = 0;
583             int nEntryId = -1;
584             FcBool scalable = false;
585 
586             FcResult eFileRes         = FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file);
587             FcResult eFamilyRes       = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &family, FC_FAMILY, FC_FAMILYLANG );
588             if (bMinimalFontset && strncmp(reinterpret_cast<char*>(family), "Liberation", strlen("Liberation")))
589                 continue;
590             FcResult eStyleRes        = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &style, FC_STYLE, FC_STYLELANG );
591             FcResult eSlantRes        = FcPatternGetInteger(pFSet->fonts[i], FC_SLANT, 0, &slant);
592             FcResult eWeightRes       = FcPatternGetInteger(pFSet->fonts[i], FC_WEIGHT, 0, &weight);
593             FcResult eWidthRes        = FcPatternGetInteger(pFSet->fonts[i], FC_WIDTH, 0, &width);
594             FcResult eSpacRes         = FcPatternGetInteger(pFSet->fonts[i], FC_SPACING, 0, &spacing);
595             FcResult eScalableRes     = FcPatternGetBool(pFSet->fonts[i], FC_SCALABLE, 0, &scalable);
596             FcResult eIndexRes        = FcPatternGetInteger(pFSet->fonts[i], FC_INDEX, 0, &nEntryId);
597             FcResult eFormatRes       = FcPatternGetString(pFSet->fonts[i], FC_FONTFORMAT, 0, &format);
598 
599             if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eScalableRes != FcResultMatch )
600                 continue;
601 
602             SAL_INFO(
603                 "vcl.fonts.detail",
604                 "found font \"" << family << "\" in file " << file << ", weight = "
605                 << (eWeightRes == FcResultMatch ? weight : -1) << ", slant = "
606                 << (eSpacRes == FcResultMatch ? slant : -1) << ", style = \""
607                 << (eStyleRes == FcResultMatch ? reinterpret_cast<const char*>(style) : "<nil>")
608                 << "\",  width = " << (eWeightRes == FcResultMatch ? width : -1) << ", spacing = "
609                 << (eSpacRes == FcResultMatch ? spacing : -1) << ", scalable = "
610                 << (eScalableRes == FcResultMatch ? scalable : -1) << ", format "
611                 << (eFormatRes == FcResultMatch
612                     ? reinterpret_cast<const char*>(format) : "<unknown>"));
613 
614 //            OSL_ASSERT(eScalableRes != FcResultMatch || scalable);
615 
616             // only scalable fonts are usable to psprint anyway
617             if( eScalableRes == FcResultMatch && ! scalable )
618                 continue;
619 
620             if (isPreviouslyDuplicateOrObsoleted(pFSet, i))
621             {
622                 SAL_INFO("vcl.fonts.detail", "Ditching " << file << " as duplicate/obsolete");
623                 continue;
624             }
625 
626             // see if this font is already cached
627             // update attributes
628             OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
629             splitPath( aOrgPath, aDir, aBase );
630 
631             o_rVisitedPaths[aDir] = 1;
632 
633             int nDirID = getDirectoryAtom( aDir );
634             SAL_INFO("vcl.fonts.detail", "file " << aBase << " not cached");
635             // not known, analyze font file to get attributes
636             // not described by fontconfig (e.g. alias names, PSName)
637             if (eFormatRes != FcResultMatch)
638                 format = nullptr;
639             std::vector<PrintFont> aFonts = analyzeFontFile( nDirID, aBase, reinterpret_cast<char*>(format) );
640             if(aFonts.empty())
641             {
642                 SAL_INFO(
643                     "vcl.fonts", "Warning: file \"" << aOrgPath << "\" is unusable to psprint");
644                 //remove font, reuse index
645                 //we want to remove unusable fonts here, in case there is a usable font
646                 //which duplicates the properties of the unusable one
647 
648                 //not removing the unusable font will risk the usable font being rejected
649                 //as a duplicate by isPreviouslyDuplicateOrObsoleted
650                 lcl_FcFontSetRemove(pFSet, i--);
651                 continue;
652             }
653 
654             std::optional<PrintFont> xUpdate;
655 
656             if (aFonts.size() == 1) // one font
657                 xUpdate = aFonts.front();
658             else // more than one font
659             {
660                 // a collection entry, get the correct index
661                 if( eIndexRes == FcResultMatch && nEntryId != -1 )
662                 {
663                     int nCollectionEntry = GetCollectionIndex(nEntryId);
664                     for (auto & font : aFonts)
665                     {
666                         if( font.m_nCollectionEntry == nCollectionEntry )
667                         {
668                             xUpdate = font;
669                             break;
670                         }
671                     }
672                 }
673 
674                 if (xUpdate)
675                 {
676                     // update collection entry
677                     // additional entries will be created in the cache
678                     // if this is a new index (that is if the loop above
679                     // ran to the end of the list)
680                     xUpdate->m_nCollectionEntry = GetCollectionIndex(nEntryId);
681                 }
682                 else
683                 {
684                     SAL_INFO(
685                         "vcl.fonts",
686                         "multiple fonts for file, but no index in fontconfig pattern ! (index res ="
687                         << eIndexRes << " collection entry = " << nEntryId
688                         << "; file will not be used");
689                     // we have found more than one font in this file
690                     // but fontconfig will not tell us which index is meant
691                     // -> something is in disorder, do not use this font
692                 }
693             }
694 
695             if (xUpdate)
696             {
697                 // set family name
698                 if( eWeightRes == FcResultMatch )
699                     xUpdate->m_eWeight = convertWeight(weight);
700                 if( eWidthRes == FcResultMatch )
701                     xUpdate->m_eWidth = convertWidth(width);
702                 if( eSpacRes == FcResultMatch )
703                     xUpdate->m_ePitch = convertSpacing(spacing);
704                 if( eSlantRes == FcResultMatch )
705                     xUpdate->m_eItalic = convertSlant(slant);
706                 if( eStyleRes == FcResultMatch )
707                     xUpdate->m_aStyleName = OStringToOUString( std::string_view( reinterpret_cast<char*>(style) ), RTL_TEXTENCODING_UTF8 );
708                 if( eIndexRes == FcResultMatch )
709                     xUpdate->m_nVariationEntry = GetVariationIndex(nEntryId);
710 
711                 // sort into known fonts
712                 fontID aFont = m_nNextFontID++;
713                 m_aFonts.emplace( aFont, *xUpdate );
714                 m_aFontFileToFontID[ aBase ].insert( aFont );
715                 nFonts++;
716                 SAL_INFO("vcl.fonts.detail", "inserted font " << family << " as fontID " << aFont);
717             }
718         }
719     }
720 
721     // how does one get rid of the config ?
722     SAL_INFO("vcl.fonts", "inserted " << nFonts << " fonts from fontconfig");
723 }
724 
deinitFontconfig()725 void PrintFontManager::deinitFontconfig()
726 {
727     FontCfgWrapper::release();
728 }
729 
addFontconfigDir(const OString & rDirName)730 void PrintFontManager::addFontconfigDir( const OString& rDirName )
731 {
732     const char* pDirName = rDirName.getStr();
733     bool bDirOk = (FcConfigAppFontAddDir(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pDirName) ) == FcTrue);
734 
735     SAL_INFO("vcl.fonts", "FcConfigAppFontAddDir( \"" << pDirName << "\") => " << bDirOk);
736 
737     if( !bDirOk )
738         return;
739 
740     // load dir-specific fc-config file too if available
741     const OString aConfFileName = rDirName + "/fc_local.conf";
742     FILE* pCfgFile = fopen( aConfFileName.getStr(), "rb" );
743     if( pCfgFile )
744     {
745         fclose( pCfgFile);
746         bool bCfgOk = FcConfigParseAndLoad(FcConfigGetCurrent(),
747                         reinterpret_cast<FcChar8 const *>(aConfFileName.getStr()), FcTrue);
748 
749         SAL_INFO_IF(!bCfgOk,
750                 "vcl.fonts", "FcConfigParseAndLoad( \""
751                 << aConfFileName << "\") => " << bCfgOk);
752     } else {
753         SAL_INFO("vcl.fonts", "cannot open " << aConfFileName);
754     }
755 }
756 
addtopattern(FcPattern * pPattern,FontItalic eItalic,FontWeight eWeight,FontWidth eWidth,FontPitch ePitch)757 static void addtopattern(FcPattern *pPattern,
758     FontItalic eItalic, FontWeight eWeight, FontWidth eWidth, FontPitch ePitch)
759 {
760     if( eItalic != ITALIC_DONTKNOW )
761     {
762         int nSlant = FC_SLANT_ROMAN;
763         switch( eItalic )
764         {
765             case ITALIC_NORMAL:
766                 nSlant = FC_SLANT_ITALIC;
767                 break;
768             case ITALIC_OBLIQUE:
769                 nSlant = FC_SLANT_OBLIQUE;
770                 break;
771             default:
772                 break;
773         }
774         FcPatternAddInteger(pPattern, FC_SLANT, nSlant);
775     }
776     if( eWeight != WEIGHT_DONTKNOW )
777     {
778         int nWeight = FC_WEIGHT_NORMAL;
779         switch( eWeight )
780         {
781             case WEIGHT_THIN:           nWeight = FC_WEIGHT_THIN;break;
782             case WEIGHT_ULTRALIGHT:     nWeight = FC_WEIGHT_ULTRALIGHT;break;
783             case WEIGHT_LIGHT:          nWeight = FC_WEIGHT_LIGHT;break;
784             case WEIGHT_SEMILIGHT:      nWeight = FC_WEIGHT_BOOK;break;
785             case WEIGHT_NORMAL:         nWeight = FC_WEIGHT_NORMAL;break;
786             case WEIGHT_MEDIUM:         nWeight = FC_WEIGHT_MEDIUM;break;
787             case WEIGHT_SEMIBOLD:       nWeight = FC_WEIGHT_SEMIBOLD;break;
788             case WEIGHT_BOLD:           nWeight = FC_WEIGHT_BOLD;break;
789             case WEIGHT_ULTRABOLD:      nWeight = FC_WEIGHT_ULTRABOLD;break;
790             case WEIGHT_BLACK:          nWeight = FC_WEIGHT_BLACK;break;
791             default:
792                 break;
793         }
794         FcPatternAddInteger(pPattern, FC_WEIGHT, nWeight);
795     }
796     if( eWidth != WIDTH_DONTKNOW )
797     {
798         int nWidth = FC_WIDTH_NORMAL;
799         switch( eWidth )
800         {
801             case WIDTH_ULTRA_CONDENSED: nWidth = FC_WIDTH_ULTRACONDENSED;break;
802             case WIDTH_EXTRA_CONDENSED: nWidth = FC_WIDTH_EXTRACONDENSED;break;
803             case WIDTH_CONDENSED:       nWidth = FC_WIDTH_CONDENSED;break;
804             case WIDTH_SEMI_CONDENSED:  nWidth = FC_WIDTH_SEMICONDENSED;break;
805             case WIDTH_NORMAL:          nWidth = FC_WIDTH_NORMAL;break;
806             case WIDTH_SEMI_EXPANDED:   nWidth = FC_WIDTH_SEMIEXPANDED;break;
807             case WIDTH_EXPANDED:        nWidth = FC_WIDTH_EXPANDED;break;
808             case WIDTH_EXTRA_EXPANDED:  nWidth = FC_WIDTH_EXTRAEXPANDED;break;
809             case WIDTH_ULTRA_EXPANDED:  nWidth = FC_WIDTH_ULTRAEXPANDED;break;
810             default:
811                 break;
812         }
813         FcPatternAddInteger(pPattern, FC_WIDTH, nWidth);
814     }
815     if( ePitch == PITCH_DONTKNOW )
816         return;
817 
818     int nSpacing = FC_PROPORTIONAL;
819     switch( ePitch )
820     {
821         case PITCH_FIXED:           nSpacing = FC_MONO;break;
822         case PITCH_VARIABLE:        nSpacing = FC_PROPORTIONAL;break;
823         default:
824             break;
825     }
826     FcPatternAddInteger(pPattern, FC_SPACING, nSpacing);
827     if (nSpacing == FC_MONO)
828         FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>("monospace"));
829 }
830 
831 namespace
832 {
833     //Someday fontconfig will hopefully use bcp47, see fdo#19869
834     //In the meantime try something that will fit to workaround fdo#35118
mapToFontConfigLangTag(const LanguageTag & rLangTag)835     OString mapToFontConfigLangTag(const LanguageTag &rLangTag)
836     {
837 #if defined(FC_VERSION) && (FC_VERSION >= 20492)
838         std::shared_ptr<FcStrSet> xLangSet(FcGetLangs(), FcStrSetDestroy);
839         OString sLangAttrib;
840 
841         sLangAttrib = OUStringToOString(rLangTag.getBcp47(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
842         if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
843         {
844             return sLangAttrib;
845         }
846 
847         sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
848         if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
849         {
850             return sLangAttrib;
851         }
852 
853         OString sLang = OUStringToOString(rLangTag.getLanguage(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
854         OString sRegion = OUStringToOString(rLangTag.getCountry(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
855 
856         if (!sRegion.isEmpty())
857         {
858             sLangAttrib = sLang + "-" + sRegion;
859             if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
860             {
861                 return sLangAttrib;
862             }
863         }
864 
865         if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLang.getStr())))
866         {
867             return sLang;
868         }
869 
870         return OString();
871 #else
872         OString sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
873         if (sLangAttrib.equalsIgnoreAsciiCase("pa-in"))
874             sLangAttrib = "pa";
875         return sLangAttrib;
876 #endif
877     }
878 
isEmoji(sal_uInt32 nCurrentChar)879     bool isEmoji(sal_uInt32 nCurrentChar)
880     {
881 #if U_ICU_VERSION_MAJOR_NUM >= 57
882         return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI);
883 #else
884 	return false;
885 #endif
886     }
887 
888     //returns true if the given code-point couldn't possibly be in rLangTag.
isImpossibleCodePointForLang(const LanguageTag & rLangTag,sal_uInt32 currentChar)889     bool isImpossibleCodePointForLang(const LanguageTag &rLangTag, sal_uInt32 currentChar)
890     {
891         //a non-default script is set, lets believe it
892         if (rLangTag.hasScript())
893             return false;
894 
895         int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
896         UScriptCode eScript = static_cast<UScriptCode>(script);
897         bool bIsImpossible = false;
898         OUString sLang = rLangTag.getLanguage();
899         switch (eScript)
900         {
901             //http://en.wiktionary.org/wiki/Category:Oriya_script_languages
902             case USCRIPT_ORIYA:
903                 bIsImpossible =
904                     sLang != "or" &&
905                     sLang != "kxv";
906                 break;
907             //http://en.wiktionary.org/wiki/Category:Telugu_script_languages
908             case USCRIPT_TELUGU:
909                 bIsImpossible =
910                     sLang != "te" &&
911                     sLang != "gon" &&
912                     sLang != "kfc";
913                 break;
914             //http://en.wiktionary.org/wiki/Category:Bengali_script_languages
915             case USCRIPT_BENGALI:
916                 bIsImpossible =
917                     sLang != "bn" &&
918                     sLang != "as" &&
919                     sLang != "bpy" &&
920                     sLang != "ctg" &&
921                     sLang != "sa";
922                 break;
923             default:
924                 break;
925         }
926         SAL_WARN_IF(bIsImpossible, "vcl.fonts", "In glyph fallback throwing away the language property of "
927             << sLang << " because the detected script for '0x"
928             << OUString::number(currentChar, 16)
929             << "' is " << uscript_getName(eScript)
930             << " and that language doesn't make sense. Autodetecting instead.");
931         return bIsImpossible;
932     }
933 
getExemplarLangTagForCodePoint(sal_uInt32 currentChar)934     OUString getExemplarLangTagForCodePoint(sal_uInt32 currentChar)
935     {
936         if (isEmoji(currentChar))
937             return "und-zsye";
938         int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
939         UScriptCode eScript = static_cast<UScriptCode>(script);
940         OStringBuffer aBuf(unicode::getExemplarLanguageForUScriptCode(eScript));
941         if (const char* pScriptCode = uscript_getShortName(eScript))
942             aBuf.append('-').append(pScriptCode);
943         return OStringToOUString(aBuf.makeStringAndClear(), RTL_TEXTENCODING_UTF8);
944     }
945 }
946 
IMPL_LINK_NOARG(PrintFontManager,autoInstallFontLangSupport,Timer *,void)947 IMPL_LINK_NOARG(PrintFontManager, autoInstallFontLangSupport, Timer *, void)
948 {
949     try
950     {
951         using namespace org::freedesktop::PackageKit;
952         css::uno::Reference<XSyncDbusSessionHelper> xSyncDbusSessionHelper(SyncDbusSessionHelper::create(comphelper::getProcessComponentContext()));
953         xSyncDbusSessionHelper->InstallFontconfigResources(comphelper::containerToSequence(m_aCurrentRequests), "hide-finished");
954     }
955     catch (const css::uno::Exception&)
956     {
957         TOOLS_INFO_EXCEPTION("vcl.fonts", "InstallFontconfigResources problem");
958         // Disable this method from now on. It's simply not available on some systems
959         // and leads to an error dialog being shown each time this is called tdf#104883
960         std::shared_ptr<comphelper::ConfigurationChanges> batch( comphelper::ConfigurationChanges::create() );
961         officecfg::Office::Common::PackageKit::EnableFontInstallation::set(false, batch);
962         batch->commit();
963     }
964 
965     m_aCurrentRequests.clear();
966 }
967 
Substitute(FontSelectPattern & rPattern,OUString & rMissingCodes)968 void PrintFontManager::Substitute(FontSelectPattern &rPattern, OUString& rMissingCodes)
969 {
970     FontCfgWrapper& rWrapper = FontCfgWrapper::get();
971 
972     // build pattern argument for fontconfig query
973     FcPattern* pPattern = FcPatternCreate();
974 
975     // Prefer scalable fonts
976     FcPatternAddBool(pPattern, FC_SCALABLE, FcTrue);
977 
978     const OString aTargetName = OUStringToOString( rPattern.maTargetName, RTL_TEXTENCODING_UTF8 );
979     const FcChar8* pTargetNameUtf8 = reinterpret_cast<FcChar8 const *>(aTargetName.getStr());
980     FcPatternAddString(pPattern, FC_FAMILY, pTargetNameUtf8);
981 
982     LanguageTag aLangTag(rPattern.meLanguage);
983     OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
984 
985     // Add required Unicode characters, if any
986     if ( !rMissingCodes.isEmpty() )
987     {
988         FcCharSet *codePoints = FcCharSetCreate();
989         for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
990         {
991             // also handle unicode surrogates
992             const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
993             FcCharSetAddChar( codePoints, nCode );
994             //if the codepoint is impossible for this lang tag, then clear it
995             //and autodetect something useful
996             if (!aLangAttrib.isEmpty() && (isImpossibleCodePointForLang(aLangTag, nCode) || isEmoji(nCode)))
997                 aLangAttrib.clear();
998             //#i105784#/rhbz#527719  improve selection of fallback font
999             if (aLangAttrib.isEmpty())
1000             {
1001                 aLangTag.reset(getExemplarLangTagForCodePoint(nCode));
1002                 aLangAttrib = mapToFontConfigLangTag(aLangTag);
1003             }
1004         }
1005         FcPatternAddCharSet(pPattern, FC_CHARSET, codePoints);
1006         FcCharSetDestroy(codePoints);
1007     }
1008 
1009     if (!aLangAttrib.isEmpty())
1010         FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
1011 
1012     addtopattern(pPattern, rPattern.GetItalic(), rPattern.GetWeight(),
1013         rPattern.GetWidthType(), rPattern.GetPitch());
1014 
1015     // query fontconfig for a substitute
1016     FcConfigSubstitute(FcConfigGetCurrent(), pPattern, FcMatchPattern);
1017     FcDefaultSubstitute(pPattern);
1018 
1019     // process the result of the fontconfig query
1020     FcResult eResult = FcResultNoMatch;
1021     FcFontSet* pFontSet = rWrapper.getFontSet();
1022     FcPattern* pResult = FcFontSetMatch(FcConfigGetCurrent(), &pFontSet, 1, pPattern, &eResult);
1023     FcPatternDestroy( pPattern );
1024 
1025     FcFontSet*  pSet = nullptr;
1026     if( pResult )
1027     {
1028         pSet = FcFontSetCreate();
1029         // info: destroying the pSet destroys pResult implicitly
1030         // since pResult was "added" to pSet
1031         FcFontSetAdd( pSet, pResult );
1032     }
1033 
1034     if( pSet )
1035     {
1036         if( pSet->nfont > 0 )
1037         {
1038             bool bRet = false;
1039 
1040             //extract the closest match
1041             FcChar8* file = nullptr;
1042             FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
1043             int nEntryId = 0;
1044             FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
1045             if (eIndexRes != FcResultMatch)
1046                 nEntryId = 0;
1047             if( eFileRes == FcResultMatch )
1048             {
1049                 OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
1050                 splitPath( aOrgPath, aDir, aBase );
1051                 int nDirID = getDirectoryAtom( aDir );
1052                 fontID aFont = findFontFileID(nDirID, aBase, GetCollectionIndex(nEntryId), GetVariationIndex(nEntryId));
1053                 if( aFont > 0 )
1054                 {
1055                     FastPrintFontInfo aInfo;
1056                     bRet = getFontFastInfo( aFont, aInfo );
1057                     rPattern.maSearchName = aInfo.m_aFamilyName;
1058                 }
1059             }
1060 
1061             SAL_WARN_IF(!bRet, "vcl.fonts", "no FC_FILE found, falling back to name search");
1062 
1063             if (!bRet)
1064             {
1065                 FcChar8* family = nullptr;
1066                 FcResult eFamilyRes = FcPatternGetString( pSet->fonts[0], FC_FAMILY, 0, &family );
1067 
1068                 // get the family name
1069                 if( eFamilyRes == FcResultMatch )
1070                 {
1071                     OString sFamily(reinterpret_cast<char*>(family));
1072                     std::unordered_map< OString, OString >::const_iterator aI =
1073                         rWrapper.m_aFontNameToLocalized.find(sFamily);
1074                     if (aI != rWrapper.m_aFontNameToLocalized.end())
1075                         sFamily = aI->second;
1076                     rPattern.maSearchName = OStringToOUString( sFamily, RTL_TEXTENCODING_UTF8 );
1077                     bRet = true;
1078                 }
1079             }
1080 
1081             if (bRet)
1082             {
1083                 int val = 0;
1084                 if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WEIGHT, 0, &val))
1085                     rPattern.SetWeight( convertWeight(val) );
1086                 if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SLANT, 0, &val))
1087                     rPattern.SetItalic( convertSlant(val) );
1088                 if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SPACING, 0, &val))
1089                     rPattern.SetPitch ( convertSpacing(val) );
1090                 if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WIDTH, 0, &val))
1091                     rPattern.SetWidthType ( convertWidth(val) );
1092                 FcBool bEmbolden;
1093                 if (FcResultMatch == FcPatternGetBool(pSet->fonts[0], FC_EMBOLDEN, 0, &bEmbolden))
1094                     rPattern.mbEmbolden = bEmbolden;
1095                 FcMatrix *pMatrix = nullptr;
1096                 if (FcResultMatch == FcPatternGetMatrix(pSet->fonts[0], FC_MATRIX, 0, &pMatrix))
1097                 {
1098                     rPattern.maItalicMatrix.xx = pMatrix->xx;
1099                     rPattern.maItalicMatrix.xy = pMatrix->xy;
1100                     rPattern.maItalicMatrix.yx = pMatrix->yx;
1101                     rPattern.maItalicMatrix.yy = pMatrix->yy;
1102                 }
1103             }
1104 
1105             // update rMissingCodes by removing resolved code points
1106             if( !rMissingCodes.isEmpty() )
1107             {
1108                 std::unique_ptr<sal_uInt32[]> const pRemainingCodes(new sal_uInt32[rMissingCodes.getLength()]);
1109                 int nRemainingLen = 0;
1110                 FcCharSet* codePoints;
1111                 if (!FcPatternGetCharSet(pSet->fonts[0], FC_CHARSET, 0, &codePoints))
1112                 {
1113                     for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
1114                     {
1115                         // also handle surrogates
1116                         const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
1117                         if (FcCharSetHasChar(codePoints, nCode) != FcTrue)
1118                             pRemainingCodes[ nRemainingLen++ ] = nCode;
1119                     }
1120                 }
1121                 OUString sStillMissing(pRemainingCodes.get(), nRemainingLen);
1122                 if (!Application::IsHeadlessModeEnabled() && officecfg::Office::Common::PackageKit::EnableFontInstallation::get())
1123                 {
1124                     if (sStillMissing == rMissingCodes) //replaced nothing
1125                     {
1126                         //It'd be better if we could ask packagekit using the
1127                         //missing codepoints or some such rather than using
1128                         //"language" as a proxy to how fontconfig considers
1129                         //scripts to default to a given language.
1130                         for (sal_Int32 i = 0; i < nRemainingLen; ++i)
1131                         {
1132                             LanguageTag aOurTag(getExemplarLangTagForCodePoint(pRemainingCodes[i]));
1133                             OString sTag = OUStringToOString(aOurTag.getBcp47(), RTL_TEXTENCODING_UTF8);
1134                             if (!m_aPreviousLangSupportRequests.insert(sTag).second)
1135                                 continue;
1136                             sTag = mapToFontConfigLangTag(aOurTag);
1137                             if (!sTag.isEmpty() && m_aPreviousLangSupportRequests.find(sTag) == m_aPreviousLangSupportRequests.end())
1138                             {
1139                                 OString sReq = OString::Concat(":lang=") + sTag;
1140                                 m_aCurrentRequests.push_back(OUString::fromUtf8(sReq));
1141                                 m_aPreviousLangSupportRequests.insert(sTag);
1142                             }
1143                         }
1144                     }
1145                     if (!m_aCurrentRequests.empty())
1146                     {
1147                         m_aFontInstallerTimer.Stop();
1148                         m_aFontInstallerTimer.Start();
1149                     }
1150                 }
1151                 rMissingCodes = sStillMissing;
1152             }
1153         }
1154 
1155         FcFontSetDestroy( pSet );
1156     }
1157 
1158     SAL_INFO("vcl.fonts", "PrintFontManager::Substitute: replacing missing font: '"
1159                               << rPattern.maTargetName << "' with '" << rPattern.maSearchName
1160                               << "'");
1161 }
1162 
~FontConfigFontOptions()1163 FontConfigFontOptions::~FontConfigFontOptions()
1164 {
1165     FcPatternDestroy(mpPattern);
1166 }
1167 
GetPattern() const1168 FcPattern *FontConfigFontOptions::GetPattern() const
1169 {
1170     return mpPattern;
1171 }
1172 
SyncPattern(const OString & rFileName,sal_uInt32 nIndex,sal_uInt32 nVariation,bool bEmbolden)1173 void FontConfigFontOptions::SyncPattern(const OString& rFileName, sal_uInt32 nIndex, sal_uInt32 nVariation, bool bEmbolden)
1174 {
1175     FcPatternDel(mpPattern, FC_FILE);
1176     FcPatternAddString(mpPattern, FC_FILE, reinterpret_cast<FcChar8 const *>(rFileName.getStr()));
1177     FcPatternDel(mpPattern, FC_INDEX);
1178     sal_uInt32 nFcIndex = (nVariation << 16) | nIndex;
1179     FcPatternAddInteger(mpPattern, FC_INDEX, nFcIndex);
1180     FcPatternDel(mpPattern, FC_EMBOLDEN);
1181     FcPatternAddBool(mpPattern, FC_EMBOLDEN, bEmbolden ? FcTrue : FcFalse);
1182 }
1183 
getFontOptions(const FontAttributes & rInfo,int nSize)1184 std::unique_ptr<FontConfigFontOptions> PrintFontManager::getFontOptions(const FontAttributes& rInfo, int nSize)
1185 {
1186     FontOptionsKey aKey{ rInfo.GetFamilyName(), nSize, rInfo.GetItalic(),
1187                          rInfo.GetWeight(), rInfo.GetWidthType(), rInfo.GetPitch() };
1188 
1189     FontCfgWrapper& rWrapper = FontCfgWrapper::get();
1190 
1191     std::unique_ptr<FontConfigFontOptions> pOptions = rWrapper.m_aCachedFontOptions.lookup(aKey);
1192     if (pOptions)
1193         return pOptions;
1194 
1195     FcConfig* pConfig = FcConfigGetCurrent();
1196     FcPattern* pPattern = FcPatternCreate();
1197 
1198     OString sFamily = OUStringToOString(aKey.m_sFamilyName, RTL_TEXTENCODING_UTF8);
1199 
1200     std::unordered_map< OString, OString >::const_iterator aI = rWrapper.m_aLocalizedToCanonical.find(sFamily);
1201     if (aI != rWrapper.m_aLocalizedToCanonical.end())
1202         sFamily = aI->second;
1203     if( !sFamily.isEmpty() )
1204         FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(sFamily.getStr()));
1205 
1206     addtopattern(pPattern, aKey.m_eItalic, aKey.m_eWeight, aKey.m_eWidth, aKey.m_ePitch);
1207     FcPatternAddDouble(pPattern, FC_PIXEL_SIZE, nSize);
1208 
1209     FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
1210     FontConfigFontOptions::cairo_font_options_substitute(pPattern);
1211     FcDefaultSubstitute(pPattern);
1212 
1213     FcResult eResult = FcResultNoMatch;
1214     FcFontSet* pFontSet = rWrapper.getFontSet();
1215     if (FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult))
1216     {
1217         rWrapper.m_aCachedFontOptions.cache(aKey, pResult);
1218         pOptions.reset(new FontConfigFontOptions(pResult));
1219     }
1220 
1221     // cleanup
1222     FcPatternDestroy( pPattern );
1223 
1224     return pOptions;
1225 }
1226 
1227 
matchFont(FastPrintFontInfo & rInfo,const css::lang::Locale & rLocale)1228 void PrintFontManager::matchFont( FastPrintFontInfo& rInfo, const css::lang::Locale& rLocale )
1229 {
1230     FontCfgWrapper& rWrapper = FontCfgWrapper::get();
1231 
1232     FcConfig* pConfig = FcConfigGetCurrent();
1233     FcPattern* pPattern = FcPatternCreate();
1234 
1235     // populate pattern with font characteristics
1236     const LanguageTag aLangTag(rLocale);
1237     const OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
1238     if (!aLangAttrib.isEmpty())
1239         FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
1240 
1241     OString aFamily = OUStringToOString( rInfo.m_aFamilyName, RTL_TEXTENCODING_UTF8 );
1242     if( !aFamily.isEmpty() )
1243         FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr()));
1244 
1245     addtopattern(pPattern, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch);
1246 
1247     FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
1248     FcDefaultSubstitute(pPattern);
1249     FcResult eResult = FcResultNoMatch;
1250     FcFontSet *pFontSet = rWrapper.getFontSet();
1251     FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult);
1252     if( pResult )
1253     {
1254         FcFontSet* pSet = FcFontSetCreate();
1255         FcFontSetAdd( pSet, pResult );
1256         if( pSet->nfont > 0 )
1257         {
1258             //extract the closest match
1259             FcChar8* file = nullptr;
1260             FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
1261             int nEntryId = 0;
1262             FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
1263             if (eIndexRes != FcResultMatch)
1264                 nEntryId = 0;
1265             if( eFileRes == FcResultMatch )
1266             {
1267                 OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
1268                 splitPath( aOrgPath, aDir, aBase );
1269                 int nDirID = getDirectoryAtom( aDir );
1270                 fontID aFont = findFontFileID(nDirID, aBase,
1271                                               GetCollectionIndex(nEntryId),
1272                                               GetVariationIndex(nEntryId));
1273                 if( aFont > 0 )
1274                     getFontFastInfo( aFont, rInfo );
1275             }
1276         }
1277         // info: destroying the pSet destroys pResult implicitly
1278         // since pResult was "added" to pSet
1279         FcFontSetDestroy( pSet );
1280     }
1281 
1282     // cleanup
1283     FcPatternDestroy( pPattern );
1284 }
1285 
1286 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1287