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