Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / vcl / unx / generic / fontmanager / fontconfig.cxx
blob75343a41b771e3cd62f7188f28b33a1a60559c7c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <memory>
21 #include <unx/fontmanager.hxx>
22 #include <unx/helper.hxx>
23 #include <comphelper/sequence.hxx>
24 #include <vcl/svapp.hxx>
25 #include <vcl/vclenum.hxx>
26 #include <fontselect.hxx>
27 #include <i18nlangtag/languagetag.hxx>
28 #include <i18nutil/unicode.hxx>
29 #include <rtl/strbuf.hxx>
30 #include <sal/log.hxx>
31 #include <tools/diagnose_ex.h>
32 #include <unicode/uchar.h>
33 #include <unicode/uscript.h>
34 #include <officecfg/Office/Common.hxx>
35 #include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp>
37 using namespace psp;
39 #include <fontconfig/fontconfig.h>
41 #include <cstdio>
43 #include <unotools/configmgr.hxx>
45 #include <osl/process.h>
47 #include <utility>
48 #include <algorithm>
50 using namespace osl;
52 namespace
54 typedef std::pair<FcChar8*, FcChar8*> lang_and_element;
57 class FontCfgWrapper
59 FcFontSet* m_pFontSet;
61 void addFontSet( FcSetName );
63 FontCfgWrapper();
64 ~FontCfgWrapper();
66 public:
67 static FontCfgWrapper& get();
68 static void release();
70 FcFontSet* getFontSet();
72 void clear();
74 public:
75 FcResult LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **family,
76 const char *elementtype, const char *elementlangtype);
77 //to-do, make private and add some cleaner accessor methods
78 std::unordered_map< OString, OString > m_aFontNameToLocalized;
79 std::unordered_map< OString, OString > m_aLocalizedToCanonical;
80 private:
81 void cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, const std::vector< lang_and_element > &lang_and_elements);
83 std::unique_ptr<LanguageTag> m_pLanguageTag;
86 FontCfgWrapper::FontCfgWrapper()
87 : m_pFontSet( nullptr )
89 FcInit();
92 void FontCfgWrapper::addFontSet( FcSetName eSetName )
94 // Add only acceptable fonts to our config, for future fontconfig use.
95 FcFontSet* pOrig = FcConfigGetFonts( FcConfigGetCurrent(), eSetName );
96 if( !pOrig )
97 return;
99 // filter the font sets to remove obsolete faces
100 for( int i = 0; i < pOrig->nfont; ++i )
102 FcPattern* pPattern = pOrig->fonts[i];
103 // #i115131# ignore non-scalable fonts
104 // Scalable fonts are usually outline fonts, but some bitmaps fonts
105 // (like Noto Color Emoji) are also scalable.
106 FcBool bScalable = FcFalse;
107 FcResult eScalableRes = FcPatternGetBool(pPattern, FC_SCALABLE, 0, &bScalable);
108 if ((eScalableRes != FcResultMatch) || (bScalable == FcFalse))
109 continue;
111 // Ignore Type 1 fonts, too.
112 FcChar8* pFormat = nullptr;
113 FcResult eFormatRes = FcPatternGetString(pPattern, FC_FONTFORMAT, 0, &pFormat);
114 if ((eFormatRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pFormat), "Type 1") == 0))
115 continue;
117 FcPatternReference( pPattern );
118 FcFontSetAdd( m_pFontSet, pPattern );
121 // TODO?: FcFontSetDestroy( pOrig );
124 namespace
126 int compareFontNames(const FcPattern *a, const FcPattern *b)
128 FcChar8 *pNameA=nullptr, *pNameB=nullptr;
130 bool bHaveA = FcPatternGetString(a, FC_FAMILY, 0, &pNameA) == FcResultMatch;
131 bool bHaveB = FcPatternGetString(b, FC_FAMILY, 0, &pNameB) == FcResultMatch;
133 if (bHaveA && bHaveB)
134 return strcmp(reinterpret_cast<const char*>(pNameA), reinterpret_cast<const char*>(pNameB));
136 return int(bHaveA) - int(bHaveB);
139 //Sort fonts so that fonts with the same family name are side-by-side, with
140 //those with higher version numbers first
141 class SortFont
143 public:
144 bool operator()(const FcPattern *a, const FcPattern *b)
146 int comp = compareFontNames(a, b);
147 if (comp != 0)
148 return comp < 0;
150 int nVersionA=0, nVersionB=0;
152 bool bHaveA = FcPatternGetInteger(a, FC_FONTVERSION, 0, &nVersionA) == FcResultMatch;
153 bool bHaveB = FcPatternGetInteger(b, FC_FONTVERSION, 0, &nVersionB) == FcResultMatch;
155 if (bHaveA && bHaveB)
156 return nVersionA > nVersionB;
158 return bHaveA > bHaveB;
162 //See fdo#30729 for where an old opensymbol installed system-wide can
163 //clobber the new opensymbol installed locally
165 //See if this font is a duplicate with equal attributes which has already been
166 //inserted, or if it an older version of an inserted fonts. Depends on FcFontSet
167 //on being sorted with SortFont
168 bool isPreviouslyDuplicateOrObsoleted(FcFontSet const *pFSet, int i)
170 const FcPattern *a = pFSet->fonts[i];
172 FcPattern* pTestPatternA = FcPatternDuplicate(a);
173 FcPatternDel(pTestPatternA, FC_FILE);
174 FcPatternDel(pTestPatternA, FC_CHARSET);
175 FcPatternDel(pTestPatternA, FC_CAPABILITY);
176 FcPatternDel(pTestPatternA, FC_FONTVERSION);
177 FcPatternDel(pTestPatternA, FC_LANG);
179 bool bIsDup(false);
181 // fdo#66715: loop for case of several font files for same font
182 for (int j = i - 1; 0 <= j && !bIsDup; --j)
184 const FcPattern *b = pFSet->fonts[j];
186 if (compareFontNames(a, b) != 0)
187 break;
189 FcPattern* pTestPatternB = FcPatternDuplicate(b);
190 FcPatternDel(pTestPatternB, FC_FILE);
191 FcPatternDel(pTestPatternB, FC_CHARSET);
192 FcPatternDel(pTestPatternB, FC_CAPABILITY);
193 FcPatternDel(pTestPatternB, FC_FONTVERSION);
194 FcPatternDel(pTestPatternB, FC_LANG);
196 bIsDup = FcPatternEqual(pTestPatternA, pTestPatternB);
198 FcPatternDestroy(pTestPatternB);
201 FcPatternDestroy(pTestPatternA);
203 return bIsDup;
207 FcFontSet* FontCfgWrapper::getFontSet()
209 if( !m_pFontSet )
211 m_pFontSet = FcFontSetCreate();
212 addFontSet( FcSetSystem );
213 addFontSet( FcSetApplication );
215 ::std::sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont());
218 return m_pFontSet;
221 FontCfgWrapper::~FontCfgWrapper()
223 clear();
224 //To-Do: get gtk vclplug smoketest to pass
225 //FcFini();
228 static FontCfgWrapper* pOneInstance = nullptr;
230 FontCfgWrapper& FontCfgWrapper::get()
232 if( ! pOneInstance )
233 pOneInstance = new FontCfgWrapper();
234 return *pOneInstance;
237 void FontCfgWrapper::release()
239 if( pOneInstance )
241 delete pOneInstance;
242 pOneInstance = nullptr;
246 namespace
248 FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag);
250 FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag)
252 FcChar8* candidate = elements.begin()->second;
253 /* FIXME-BCP47: once fontconfig supports language tags this
254 * language-territory stuff needs to be changed! */
255 SAL_INFO_IF( !rLangTag.isIsoLocale(), "vcl.fonts", "localizedsorter::bestname - not an ISO locale");
256 OString sLangMatch(OUStringToOString(rLangTag.getLanguage().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8));
257 OString sFullMatch = sLangMatch +
258 "-" +
259 OUStringToOString(rLangTag.getCountry().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8);
261 bool alreadyclosematch = false;
262 bool found_fallback_englishname = false;
263 for (auto const& element : elements)
265 const char *pLang = reinterpret_cast<const char*>(element.first);
266 if( sFullMatch == pLang)
268 // both language and country match
269 candidate = element.second;
270 break;
272 else if( alreadyclosematch )
274 // current candidate matches lang of lang-TERRITORY
275 // override candidate only if there is a full match
276 continue;
278 else if( sLangMatch == pLang)
280 // just the language matches
281 candidate = element.second;
282 alreadyclosematch = true;
284 else if( found_fallback_englishname )
286 // already found an english fallback, don't override candidate
287 // unless there is a better language match
288 continue;
290 else if( rtl_str_compare( pLang, "en") == 0)
292 // select a fallback candidate of the first english element
293 // name
294 candidate = element.second;
295 found_fallback_englishname = true;
298 return candidate;
302 //Set up maps to quickly map between a fonts best UI name and all the rest of its names, and vice versa
303 void FontCfgWrapper::cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname,
304 const std::vector< lang_and_element > &lang_and_elements)
306 for (auto const& element : lang_and_elements)
308 const char *candidate = reinterpret_cast<const char*>(element.second);
309 if (rtl_str_compare(candidate, reinterpret_cast<const char*>(bestfontname)) != 0)
310 m_aFontNameToLocalized[OString(candidate)] = OString(reinterpret_cast<const char*>(bestfontname));
312 if (rtl_str_compare(reinterpret_cast<const char*>(origfontname), reinterpret_cast<const char*>(bestfontname)) != 0)
313 m_aLocalizedToCanonical[OString(reinterpret_cast<const char*>(bestfontname))] = OString(reinterpret_cast<const char*>(origfontname));
316 FcResult FontCfgWrapper::LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **element,
317 const char *elementtype, const char *elementlangtype)
318 { /* e. g.: ^ FC_FAMILY ^ FC_FAMILYLANG */
319 FcChar8 *origelement;
320 FcResult eElementRes = FcPatternGetString( pPattern, elementtype, 0, &origelement );
321 *element = origelement;
323 if( eElementRes == FcResultMatch)
325 FcChar8* elementlang = nullptr;
326 if (FcPatternGetString( pPattern, elementlangtype, 0, &elementlang ) == FcResultMatch)
328 std::vector< lang_and_element > lang_and_elements;
329 lang_and_elements.emplace_back(elementlang, *element);
330 int k = 1;
331 while (true)
333 if (FcPatternGetString( pPattern, elementlangtype, k, &elementlang ) != FcResultMatch)
334 break;
335 if (FcPatternGetString( pPattern, elementtype, k, element ) != FcResultMatch)
336 break;
337 lang_and_elements.emplace_back(elementlang, *element);
338 ++k;
341 //possible to-do, sort by UILocale instead of process locale
342 if (!m_pLanguageTag)
344 rtl_Locale* pLoc = nullptr;
345 osl_getProcessLocale(&pLoc);
346 m_pLanguageTag.reset( new LanguageTag(*pLoc) );
348 *element = bestname(lang_and_elements, *m_pLanguageTag);
350 //if this element is a fontname, map the other names to this best-name
351 if (rtl_str_compare(elementtype, FC_FAMILY) == 0)
352 cacheLocalizedFontNames(origelement, *element, lang_and_elements);
356 return eElementRes;
359 void FontCfgWrapper::clear()
361 m_aFontNameToLocalized.clear();
362 m_aLocalizedToCanonical.clear();
363 if( m_pFontSet )
365 FcFontSetDestroy( m_pFontSet );
366 m_pFontSet = nullptr;
368 m_pLanguageTag.reset();
372 * PrintFontManager::initFontconfig
374 void PrintFontManager::initFontconfig()
376 FontCfgWrapper& rWrapper = FontCfgWrapper::get();
377 rWrapper.clear();
380 namespace
382 FontWeight convertWeight(int weight)
384 // set weight
385 if( weight <= FC_WEIGHT_THIN )
386 return WEIGHT_THIN;
387 else if( weight <= FC_WEIGHT_ULTRALIGHT )
388 return WEIGHT_ULTRALIGHT;
389 else if( weight <= FC_WEIGHT_LIGHT )
390 return WEIGHT_LIGHT;
391 else if( weight <= FC_WEIGHT_BOOK )
392 return WEIGHT_SEMILIGHT;
393 else if( weight <= FC_WEIGHT_NORMAL )
394 return WEIGHT_NORMAL;
395 else if( weight <= FC_WEIGHT_MEDIUM )
396 return WEIGHT_MEDIUM;
397 else if( weight <= FC_WEIGHT_SEMIBOLD )
398 return WEIGHT_SEMIBOLD;
399 else if( weight <= FC_WEIGHT_BOLD )
400 return WEIGHT_BOLD;
401 else if( weight <= FC_WEIGHT_ULTRABOLD )
402 return WEIGHT_ULTRABOLD;
403 return WEIGHT_BLACK;
406 FontItalic convertSlant(int slant)
408 // set italic
409 if( slant == FC_SLANT_ITALIC )
410 return ITALIC_NORMAL;
411 else if( slant == FC_SLANT_OBLIQUE )
412 return ITALIC_OBLIQUE;
413 return ITALIC_NONE;
416 FontPitch convertSpacing(int spacing)
418 // set pitch
419 if( spacing == FC_MONO || spacing == FC_CHARCELL )
420 return PITCH_FIXED;
421 return PITCH_VARIABLE;
424 // translation: fontconfig enum -> vcl enum
425 FontWidth convertWidth(int width)
427 if (width == FC_WIDTH_ULTRACONDENSED)
428 return WIDTH_ULTRA_CONDENSED;
429 else if (width == FC_WIDTH_EXTRACONDENSED)
430 return WIDTH_EXTRA_CONDENSED;
431 else if (width == FC_WIDTH_CONDENSED)
432 return WIDTH_CONDENSED;
433 else if (width == FC_WIDTH_SEMICONDENSED)
434 return WIDTH_SEMI_CONDENSED;
435 else if (width == FC_WIDTH_SEMIEXPANDED)
436 return WIDTH_SEMI_EXPANDED;
437 else if (width == FC_WIDTH_EXPANDED)
438 return WIDTH_EXPANDED;
439 else if (width == FC_WIDTH_EXTRAEXPANDED)
440 return WIDTH_EXTRA_EXPANDED;
441 else if (width == FC_WIDTH_ULTRAEXPANDED)
442 return WIDTH_ULTRA_EXPANDED;
443 return WIDTH_NORMAL;
447 //FontConfig doesn't come with a way to remove an element from a FontSet as far
448 //as I can see
449 static void lcl_FcFontSetRemove(FcFontSet* pFSet, int i)
451 FcPatternDestroy(pFSet->fonts[i]);
453 int nTail = pFSet->nfont - (i + 1);
454 --pFSet->nfont;
455 if (!nTail)
456 return;
457 memmove(pFSet->fonts + i, pFSet->fonts + i + 1, nTail*sizeof(FcPattern*));
460 namespace
462 // for variable fonts, FC_INDEX has been changed such that the lower half is now the
463 // index of the font within the collection, and the upper half has been repurposed
464 // as the index within the variations
465 unsigned int GetCollectionIndex(unsigned int nEntryId)
467 return nEntryId & 0xFFFF;
470 unsigned int GetVariationIndex(unsigned int nEntryId)
472 return nEntryId >> 16;
476 void PrintFontManager::countFontconfigFonts( std::unordered_map<OString, int>& o_rVisitedPaths )
478 int nFonts = 0;
479 FontCfgWrapper& rWrapper = FontCfgWrapper::get();
481 FcFontSet* pFSet = rWrapper.getFontSet();
482 const bool bMinimalFontset = utl::ConfigManager::IsFuzzing();
483 if( pFSet )
485 SAL_INFO("vcl.fonts", "found " << pFSet->nfont << " entries in fontconfig fontset");
486 for( int i = 0; i < pFSet->nfont; i++ )
488 FcChar8* file = nullptr;
489 FcChar8* family = nullptr;
490 FcChar8* style = nullptr;
491 FcChar8* format = nullptr;
492 int slant = 0;
493 int weight = 0;
494 int width = 0;
495 int spacing = 0;
496 int nEntryId = -1;
497 FcBool scalable = false;
499 FcResult eFileRes = FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file);
500 FcResult eFamilyRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &family, FC_FAMILY, FC_FAMILYLANG );
501 if (bMinimalFontset && strncmp(reinterpret_cast<char*>(family), "Liberation", strlen("Liberation")))
502 continue;
503 FcResult eStyleRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &style, FC_STYLE, FC_STYLELANG );
504 FcResult eSlantRes = FcPatternGetInteger(pFSet->fonts[i], FC_SLANT, 0, &slant);
505 FcResult eWeightRes = FcPatternGetInteger(pFSet->fonts[i], FC_WEIGHT, 0, &weight);
506 FcResult eWidthRes = FcPatternGetInteger(pFSet->fonts[i], FC_WIDTH, 0, &width);
507 FcResult eSpacRes = FcPatternGetInteger(pFSet->fonts[i], FC_SPACING, 0, &spacing);
508 FcResult eScalableRes = FcPatternGetBool(pFSet->fonts[i], FC_SCALABLE, 0, &scalable);
509 FcResult eIndexRes = FcPatternGetInteger(pFSet->fonts[i], FC_INDEX, 0, &nEntryId);
510 FcResult eFormatRes = FcPatternGetString(pFSet->fonts[i], FC_FONTFORMAT, 0, &format);
512 if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eScalableRes != FcResultMatch )
513 continue;
515 SAL_INFO(
516 "vcl.fonts.detail",
517 "found font \"" << family << "\" in file " << file << ", weight = "
518 << (eWeightRes == FcResultMatch ? weight : -1) << ", slant = "
519 << (eSpacRes == FcResultMatch ? slant : -1) << ", style = \""
520 << (eStyleRes == FcResultMatch ? reinterpret_cast<const char*>(style) : "<nil>")
521 << "\", width = " << (eWeightRes == FcResultMatch ? width : -1) << ", spacing = "
522 << (eSpacRes == FcResultMatch ? spacing : -1) << ", scalable = "
523 << (eScalableRes == FcResultMatch ? scalable : -1) << ", format "
524 << (eFormatRes == FcResultMatch
525 ? reinterpret_cast<const char*>(format) : "<unknown>"));
527 // OSL_ASSERT(eScalableRes != FcResultMatch || scalable);
529 // only scalable fonts are usable to psprint anyway
530 if( eScalableRes == FcResultMatch && ! scalable )
531 continue;
533 if (isPreviouslyDuplicateOrObsoleted(pFSet, i))
535 SAL_INFO("vcl.fonts.detail", "Ditching " << file << " as duplicate/obsolete");
536 continue;
539 // see if this font is already cached
540 // update attributes
541 OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
542 splitPath( aOrgPath, aDir, aBase );
544 o_rVisitedPaths[aDir] = 1;
546 int nDirID = getDirectoryAtom( aDir );
547 SAL_INFO("vcl.fonts.detail", "file " << aBase << " not cached");
548 // not known, analyze font file to get attributes
549 // not described by fontconfig (e.g. alias names, PSName)
550 if (eFormatRes != FcResultMatch)
551 format = nullptr;
552 std::vector<std::unique_ptr<PrintFont>> aFonts = analyzeFontFile( nDirID, aBase, reinterpret_cast<char*>(format) );
553 if(aFonts.empty())
555 SAL_INFO(
556 "vcl.fonts", "Warning: file \"" << aOrgPath << "\" is unusable to psprint");
557 //remove font, reuse index
558 //we want to remove unusable fonts here, in case there is a usable font
559 //which duplicates the properties of the unusable one
561 //not removing the unusable font will risk the usable font being rejected
562 //as a duplicate by isPreviouslyDuplicateOrObsoleted
563 lcl_FcFontSetRemove(pFSet, i--);
564 continue;
567 std::unique_ptr<PrintFont> xUpdate;
569 if (aFonts.size() == 1) // one font
570 xUpdate = std::move(aFonts.front());
571 else // more than one font
573 // a collection entry, get the correct index
574 if( eIndexRes == FcResultMatch && nEntryId != -1 )
576 int nCollectionEntry = GetCollectionIndex(nEntryId);
577 for (auto & font : aFonts)
579 if( font->m_nCollectionEntry == nCollectionEntry )
581 xUpdate = std::move(font);
582 break;
587 if (xUpdate)
589 // update collection entry
590 // additional entries will be created in the cache
591 // if this is a new index (that is if the loop above
592 // ran to the end of the list)
593 xUpdate->m_nCollectionEntry = GetCollectionIndex(nEntryId);
595 else
597 SAL_INFO(
598 "vcl.fonts",
599 "multiple fonts for file, but no index in fontconfig pattern ! (index res ="
600 << eIndexRes << " collection entry = " << nEntryId
601 << "; file will not be used");
602 // we have found more than one font in this file
603 // but fontconfig will not tell us which index is meant
604 // -> something is in disorder, do not use this font
608 if (xUpdate)
610 // set family name
611 if( eWeightRes == FcResultMatch )
612 xUpdate->m_eWeight = convertWeight(weight);
613 if( eWidthRes == FcResultMatch )
614 xUpdate->m_eWidth = convertWidth(width);
615 if( eSpacRes == FcResultMatch )
616 xUpdate->m_ePitch = convertSpacing(spacing);
617 if( eSlantRes == FcResultMatch )
618 xUpdate->m_eItalic = convertSlant(slant);
619 if( eStyleRes == FcResultMatch )
620 xUpdate->m_aStyleName = OStringToOUString( OString( reinterpret_cast<char*>(style) ), RTL_TEXTENCODING_UTF8 );
621 if( eIndexRes == FcResultMatch )
622 xUpdate->m_nVariationEntry = GetVariationIndex(nEntryId);
624 // sort into known fonts
625 fontID aFont = m_nNextFontID++;
626 m_aFonts[ aFont ] = std::move(xUpdate);
627 m_aFontFileToFontID[ aBase ].insert( aFont );
628 nFonts++;
629 SAL_INFO("vcl.fonts.detail", "inserted font " << family << " as fontID " << aFont);
634 // how does one get rid of the config ?
635 SAL_INFO("vcl.fonts", "inserted " << nFonts << " fonts from fontconfig");
638 void PrintFontManager::deinitFontconfig()
640 FontCfgWrapper::release();
643 void PrintFontManager::addFontconfigDir( const OString& rDirName )
645 const char* pDirName = rDirName.getStr();
646 bool bDirOk = (FcConfigAppFontAddDir(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pDirName) ) == FcTrue);
648 SAL_INFO("vcl.fonts", "FcConfigAppFontAddDir( \"" << pDirName << "\") => " << bDirOk);
650 if( !bDirOk )
651 return;
653 // load dir-specific fc-config file too if available
654 const OString aConfFileName = rDirName + "/fc_local.conf";
655 FILE* pCfgFile = fopen( aConfFileName.getStr(), "rb" );
656 if( pCfgFile )
658 fclose( pCfgFile);
659 bool bCfgOk = FcConfigParseAndLoad(FcConfigGetCurrent(),
660 reinterpret_cast<FcChar8 const *>(aConfFileName.getStr()), FcTrue);
661 if( !bCfgOk )
662 fprintf( stderr, "FcConfigParseAndLoad( \"%s\") => %d\n", aConfFileName.getStr(), bCfgOk );
663 } else {
664 SAL_INFO("vcl.fonts", "cannot open " << aConfFileName);
668 static void addtopattern(FcPattern *pPattern,
669 FontItalic eItalic, FontWeight eWeight, FontWidth eWidth, FontPitch ePitch)
671 if( eItalic != ITALIC_DONTKNOW )
673 int nSlant = FC_SLANT_ROMAN;
674 switch( eItalic )
676 case ITALIC_NORMAL:
677 nSlant = FC_SLANT_ITALIC;
678 break;
679 case ITALIC_OBLIQUE:
680 nSlant = FC_SLANT_OBLIQUE;
681 break;
682 default:
683 break;
685 FcPatternAddInteger(pPattern, FC_SLANT, nSlant);
687 if( eWeight != WEIGHT_DONTKNOW )
689 int nWeight = FC_WEIGHT_NORMAL;
690 switch( eWeight )
692 case WEIGHT_THIN: nWeight = FC_WEIGHT_THIN;break;
693 case WEIGHT_ULTRALIGHT: nWeight = FC_WEIGHT_ULTRALIGHT;break;
694 case WEIGHT_LIGHT: nWeight = FC_WEIGHT_LIGHT;break;
695 case WEIGHT_SEMILIGHT: nWeight = FC_WEIGHT_BOOK;break;
696 case WEIGHT_NORMAL: nWeight = FC_WEIGHT_NORMAL;break;
697 case WEIGHT_MEDIUM: nWeight = FC_WEIGHT_MEDIUM;break;
698 case WEIGHT_SEMIBOLD: nWeight = FC_WEIGHT_SEMIBOLD;break;
699 case WEIGHT_BOLD: nWeight = FC_WEIGHT_BOLD;break;
700 case WEIGHT_ULTRABOLD: nWeight = FC_WEIGHT_ULTRABOLD;break;
701 case WEIGHT_BLACK: nWeight = FC_WEIGHT_BLACK;break;
702 default:
703 break;
705 FcPatternAddInteger(pPattern, FC_WEIGHT, nWeight);
707 if( eWidth != WIDTH_DONTKNOW )
709 int nWidth = FC_WIDTH_NORMAL;
710 switch( eWidth )
712 case WIDTH_ULTRA_CONDENSED: nWidth = FC_WIDTH_ULTRACONDENSED;break;
713 case WIDTH_EXTRA_CONDENSED: nWidth = FC_WIDTH_EXTRACONDENSED;break;
714 case WIDTH_CONDENSED: nWidth = FC_WIDTH_CONDENSED;break;
715 case WIDTH_SEMI_CONDENSED: nWidth = FC_WIDTH_SEMICONDENSED;break;
716 case WIDTH_NORMAL: nWidth = FC_WIDTH_NORMAL;break;
717 case WIDTH_SEMI_EXPANDED: nWidth = FC_WIDTH_SEMIEXPANDED;break;
718 case WIDTH_EXPANDED: nWidth = FC_WIDTH_EXPANDED;break;
719 case WIDTH_EXTRA_EXPANDED: nWidth = FC_WIDTH_EXTRAEXPANDED;break;
720 case WIDTH_ULTRA_EXPANDED: nWidth = FC_WIDTH_ULTRAEXPANDED;break;
721 default:
722 break;
724 FcPatternAddInteger(pPattern, FC_WIDTH, nWidth);
726 if( ePitch != PITCH_DONTKNOW )
728 int nSpacing = FC_PROPORTIONAL;
729 switch( ePitch )
731 case PITCH_FIXED: nSpacing = FC_MONO;break;
732 case PITCH_VARIABLE: nSpacing = FC_PROPORTIONAL;break;
733 default:
734 break;
736 FcPatternAddInteger(pPattern, FC_SPACING, nSpacing);
737 if (nSpacing == FC_MONO)
738 FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>("monospace"));
742 namespace
744 //Someday fontconfig will hopefully use bcp47, see fdo#19869
745 //In the meantime try something that will fit to workaround fdo#35118
746 OString mapToFontConfigLangTag(const LanguageTag &rLangTag)
748 #if defined(FC_VERSION) && (FC_VERSION >= 20492)
749 std::shared_ptr<FcStrSet> xLangSet(FcGetLangs(), FcStrSetDestroy);
750 OString sLangAttrib;
752 sLangAttrib = OUStringToOString(rLangTag.getBcp47(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
753 if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
755 return sLangAttrib;
758 sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
759 if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
761 return sLangAttrib;
764 OString sLang = OUStringToOString(rLangTag.getLanguage(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
765 OString sRegion = OUStringToOString(rLangTag.getCountry(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
767 if (!sRegion.isEmpty())
769 sLangAttrib = sLang + "-" + sRegion;
770 if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
772 return sLangAttrib;
776 if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLang.getStr())))
778 return sLang;
781 return OString();
782 #else
783 OString sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
784 if (sLangAttrib.equalsIgnoreAsciiCase("pa-in"))
785 sLangAttrib = "pa";
786 return sLangAttrib;
787 #endif
790 bool isEmoji(sal_uInt32 nCurrentChar)
792 #if U_ICU_VERSION_MAJOR_NUM >= 57
793 return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI);
794 #else
795 return false;
796 #endif
799 //returns true if the given code-point couldn't possibly be in rLangTag.
800 bool isImpossibleCodePointForLang(const LanguageTag &rLangTag, sal_uInt32 currentChar)
802 //a non-default script is set, lets believe it
803 if (rLangTag.hasScript())
804 return false;
806 int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
807 UScriptCode eScript = static_cast<UScriptCode>(script);
808 bool bIsImpossible = false;
809 OUString sLang = rLangTag.getLanguage();
810 switch (eScript)
812 //http://en.wiktionary.org/wiki/Category:Oriya_script_languages
813 case USCRIPT_ORIYA:
814 bIsImpossible =
815 sLang != "or" &&
816 sLang != "kxv";
817 break;
818 //http://en.wiktionary.org/wiki/Category:Telugu_script_languages
819 case USCRIPT_TELUGU:
820 bIsImpossible =
821 sLang != "te" &&
822 sLang != "gon" &&
823 sLang != "kfc";
824 break;
825 //http://en.wiktionary.org/wiki/Category:Bengali_script_languages
826 case USCRIPT_BENGALI:
827 bIsImpossible =
828 sLang != "bn" &&
829 sLang != "as" &&
830 sLang != "bpy" &&
831 sLang != "ctg" &&
832 sLang != "sa";
833 break;
834 default:
835 break;
837 SAL_WARN_IF(bIsImpossible, "vcl.fonts", "In glyph fallback throwing away the language property of "
838 << sLang << " because the detected script for '0x"
839 << OUString::number(currentChar, 16)
840 << "' is " << uscript_getName(eScript)
841 << " and that language doesn't make sense. Autodetecting instead.");
842 return bIsImpossible;
845 OUString getExemplarLangTagForCodePoint(sal_uInt32 currentChar)
847 if (isEmoji(currentChar))
848 return "und-zsye";
849 int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
850 UScriptCode eScript = static_cast<UScriptCode>(script);
851 OStringBuffer aBuf(unicode::getExemplarLanguageForUScriptCode(eScript));
852 if (const char* pScriptCode = uscript_getShortName(eScript))
853 aBuf.append('-').append(pScriptCode);
854 return OStringToOUString(aBuf.makeStringAndClear(), RTL_TEXTENCODING_UTF8);
858 IMPL_LINK_NOARG(PrintFontManager, autoInstallFontLangSupport, Timer *, void)
862 using namespace org::freedesktop::PackageKit;
863 css::uno::Reference<XSyncDbusSessionHelper> xSyncDbusSessionHelper(SyncDbusSessionHelper::create(comphelper::getProcessComponentContext()));
864 xSyncDbusSessionHelper->InstallFontconfigResources(comphelper::containerToSequence(m_aCurrentRequests), "hide-finished");
866 catch (const css::uno::Exception&)
868 TOOLS_INFO_EXCEPTION("vcl.fonts", "InstallFontconfigResources problem");
869 // Disable this method from now on. It's simply not available on some systems
870 // and leads to an error dialog being shown each time this is called tdf#104883
871 std::shared_ptr<comphelper::ConfigurationChanges> batch( comphelper::ConfigurationChanges::create() );
872 officecfg::Office::Common::PackageKit::EnableFontInstallation::set(false, batch);
873 batch->commit();
876 m_aCurrentRequests.clear();
879 void PrintFontManager::Substitute(FontSelectPattern &rPattern, OUString& rMissingCodes)
881 FontCfgWrapper& rWrapper = FontCfgWrapper::get();
883 // build pattern argument for fontconfig query
884 FcPattern* pPattern = FcPatternCreate();
886 // Prefer scalable fonts
887 FcPatternAddBool(pPattern, FC_SCALABLE, FcTrue);
889 const OString aTargetName = OUStringToOString( rPattern.maTargetName, RTL_TEXTENCODING_UTF8 );
890 const FcChar8* pTargetNameUtf8 = reinterpret_cast<FcChar8 const *>(aTargetName.getStr());
891 FcPatternAddString(pPattern, FC_FAMILY, pTargetNameUtf8);
893 LanguageTag aLangTag(rPattern.meLanguage);
894 OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
896 // Add required Unicode characters, if any
897 if ( !rMissingCodes.isEmpty() )
899 FcCharSet *codePoints = FcCharSetCreate();
900 for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
902 // also handle unicode surrogates
903 const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
904 FcCharSetAddChar( codePoints, nCode );
905 //if the codepoint is impossible for this lang tag, then clear it
906 //and autodetect something useful
907 if (!aLangAttrib.isEmpty() && (isImpossibleCodePointForLang(aLangTag, nCode) || isEmoji(nCode)))
908 aLangAttrib.clear();
909 //#i105784#/rhbz#527719 improve selection of fallback font
910 if (aLangAttrib.isEmpty())
912 aLangTag.reset(getExemplarLangTagForCodePoint(nCode));
913 aLangAttrib = mapToFontConfigLangTag(aLangTag);
916 FcPatternAddCharSet(pPattern, FC_CHARSET, codePoints);
917 FcCharSetDestroy(codePoints);
920 if (!aLangAttrib.isEmpty())
921 FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
923 addtopattern(pPattern, rPattern.GetItalic(), rPattern.GetWeight(),
924 rPattern.GetWidthType(), rPattern.GetPitch());
926 // query fontconfig for a substitute
927 FcConfigSubstitute(FcConfigGetCurrent(), pPattern, FcMatchPattern);
928 FcDefaultSubstitute(pPattern);
930 // process the result of the fontconfig query
931 FcResult eResult = FcResultNoMatch;
932 FcFontSet* pFontSet = rWrapper.getFontSet();
933 FcPattern* pResult = FcFontSetMatch(FcConfigGetCurrent(), &pFontSet, 1, pPattern, &eResult);
934 FcPatternDestroy( pPattern );
936 FcFontSet* pSet = nullptr;
937 if( pResult )
939 pSet = FcFontSetCreate();
940 // info: destroying the pSet destroys pResult implicitly
941 // since pResult was "added" to pSet
942 FcFontSetAdd( pSet, pResult );
945 if( pSet )
947 if( pSet->nfont > 0 )
949 bool bRet = false;
951 //extract the closest match
952 FcChar8* file = nullptr;
953 FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
954 int nEntryId = 0;
955 FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
956 if (eIndexRes != FcResultMatch)
957 nEntryId = 0;
958 if( eFileRes == FcResultMatch )
960 OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
961 splitPath( aOrgPath, aDir, aBase );
962 int nDirID = getDirectoryAtom( aDir );
963 fontID aFont = findFontFileID(nDirID, aBase, GetCollectionIndex(nEntryId), GetVariationIndex(nEntryId));
964 if( aFont > 0 )
966 FastPrintFontInfo aInfo;
967 bRet = getFontFastInfo( aFont, aInfo );
968 rPattern.maSearchName = aInfo.m_aFamilyName;
972 SAL_WARN_IF(!bRet, "vcl.fonts", "no FC_FILE found, falling back to name search");
974 if (!bRet)
976 FcChar8* family = nullptr;
977 FcResult eFamilyRes = FcPatternGetString( pSet->fonts[0], FC_FAMILY, 0, &family );
979 // get the family name
980 if( eFamilyRes == FcResultMatch )
982 OString sFamily(reinterpret_cast<char*>(family));
983 std::unordered_map< OString, OString >::const_iterator aI =
984 rWrapper.m_aFontNameToLocalized.find(sFamily);
985 if (aI != rWrapper.m_aFontNameToLocalized.end())
986 sFamily = aI->second;
987 rPattern.maSearchName = OStringToOUString( sFamily, RTL_TEXTENCODING_UTF8 );
988 bRet = true;
992 if (bRet)
994 int val = 0;
995 if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WEIGHT, 0, &val))
996 rPattern.SetWeight( convertWeight(val) );
997 if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SLANT, 0, &val))
998 rPattern.SetItalic( convertSlant(val) );
999 if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SPACING, 0, &val))
1000 rPattern.SetPitch ( convertSpacing(val) );
1001 if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WIDTH, 0, &val))
1002 rPattern.SetWidthType ( convertWidth(val) );
1003 FcBool bEmbolden;
1004 if (FcResultMatch == FcPatternGetBool(pSet->fonts[0], FC_EMBOLDEN, 0, &bEmbolden))
1005 rPattern.mbEmbolden = bEmbolden;
1006 FcMatrix *pMatrix = nullptr;
1007 if (FcResultMatch == FcPatternGetMatrix(pSet->fonts[0], FC_MATRIX, 0, &pMatrix))
1009 rPattern.maItalicMatrix.xx = pMatrix->xx;
1010 rPattern.maItalicMatrix.xy = pMatrix->xy;
1011 rPattern.maItalicMatrix.yx = pMatrix->yx;
1012 rPattern.maItalicMatrix.yy = pMatrix->yy;
1016 // update rMissingCodes by removing resolved code points
1017 if( !rMissingCodes.isEmpty() )
1019 std::unique_ptr<sal_uInt32[]> const pRemainingCodes(new sal_uInt32[rMissingCodes.getLength()]);
1020 int nRemainingLen = 0;
1021 FcCharSet* codePoints;
1022 if (!FcPatternGetCharSet(pSet->fonts[0], FC_CHARSET, 0, &codePoints))
1024 for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
1026 // also handle surrogates
1027 const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
1028 if (FcCharSetHasChar(codePoints, nCode) != FcTrue)
1029 pRemainingCodes[ nRemainingLen++ ] = nCode;
1032 OUString sStillMissing(pRemainingCodes.get(), nRemainingLen);
1033 if (!Application::IsHeadlessModeEnabled() && officecfg::Office::Common::PackageKit::EnableFontInstallation::get())
1035 if (sStillMissing == rMissingCodes) //replaced nothing
1037 //It'd be better if we could ask packagekit using the
1038 //missing codepoints or some such rather than using
1039 //"language" as a proxy to how fontconfig considers
1040 //scripts to default to a given language.
1041 for (sal_Int32 i = 0; i < nRemainingLen; ++i)
1043 LanguageTag aOurTag(getExemplarLangTagForCodePoint(pRemainingCodes[i]));
1044 OString sTag = OUStringToOString(aOurTag.getBcp47(), RTL_TEXTENCODING_UTF8);
1045 if (!m_aPreviousLangSupportRequests.insert(sTag).second)
1046 continue;
1047 sTag = mapToFontConfigLangTag(aOurTag);
1048 if (!sTag.isEmpty() && m_aPreviousLangSupportRequests.find(sTag) == m_aPreviousLangSupportRequests.end())
1050 OString sReq = OStringLiteral(":lang=") + sTag;
1051 m_aCurrentRequests.push_back(OUString::fromUtf8(sReq));
1052 m_aPreviousLangSupportRequests.insert(sTag);
1056 if (!m_aCurrentRequests.empty())
1058 m_aFontInstallerTimer.Stop();
1059 m_aFontInstallerTimer.Start();
1062 rMissingCodes = sStillMissing;
1066 FcFontSetDestroy( pSet );
1069 SAL_INFO("vcl.fonts", "PrintFontManager::Substitute: replacing missing font: '"
1070 << rPattern.maTargetName << "' with '" << rPattern.maSearchName
1071 << "'");
1074 FontConfigFontOptions::~FontConfigFontOptions()
1076 FcPatternDestroy(mpPattern);
1079 FcPattern *FontConfigFontOptions::GetPattern() const
1081 return mpPattern;
1084 void FontConfigFontOptions::SyncPattern(const OString& rFileName, sal_uInt32 nIndex, sal_uInt32 nVariation, bool bEmbolden)
1086 FcPatternDel(mpPattern, FC_FILE);
1087 FcPatternAddString(mpPattern, FC_FILE, reinterpret_cast<FcChar8 const *>(rFileName.getStr()));
1088 FcPatternDel(mpPattern, FC_INDEX);
1089 sal_uInt32 nFcIndex = (nVariation << 16) | nIndex;
1090 FcPatternAddInteger(mpPattern, FC_INDEX, nFcIndex);
1091 FcPatternDel(mpPattern, FC_EMBOLDEN);
1092 FcPatternAddBool(mpPattern, FC_EMBOLDEN, bEmbolden ? FcTrue : FcFalse);
1095 std::unique_ptr<FontConfigFontOptions> PrintFontManager::getFontOptions(const FastPrintFontInfo& rInfo, int nSize)
1097 FontCfgWrapper& rWrapper = FontCfgWrapper::get();
1099 std::unique_ptr<FontConfigFontOptions> pOptions;
1100 FcConfig* pConfig = FcConfigGetCurrent();
1101 FcPattern* pPattern = FcPatternCreate();
1103 OString sFamily = OUStringToOString( rInfo.m_aFamilyName, RTL_TEXTENCODING_UTF8 );
1105 std::unordered_map< OString, OString >::const_iterator aI = rWrapper.m_aLocalizedToCanonical.find(sFamily);
1106 if (aI != rWrapper.m_aLocalizedToCanonical.end())
1107 sFamily = aI->second;
1108 if( !sFamily.isEmpty() )
1109 FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(sFamily.getStr()));
1111 addtopattern(pPattern, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch);
1112 FcPatternAddDouble(pPattern, FC_PIXEL_SIZE, nSize);
1114 int hintstyle = FC_HINT_FULL;
1116 FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
1117 FontConfigFontOptions::cairo_font_options_substitute(pPattern);
1118 FcDefaultSubstitute(pPattern);
1120 FcResult eResult = FcResultNoMatch;
1121 FcFontSet* pFontSet = rWrapper.getFontSet();
1122 FcPattern* pResult = FcFontSetMatch( pConfig, &pFontSet, 1, pPattern, &eResult );
1123 if( pResult )
1125 (void) FcPatternGetInteger(pResult,
1126 FC_HINT_STYLE, 0, &hintstyle);
1128 pOptions.reset(new FontConfigFontOptions(pResult));
1131 // cleanup
1132 FcPatternDestroy( pPattern );
1134 return pOptions;
1137 void PrintFontManager::matchFont( FastPrintFontInfo& rInfo, const css::lang::Locale& rLocale )
1139 FontCfgWrapper& rWrapper = FontCfgWrapper::get();
1141 FcConfig* pConfig = FcConfigGetCurrent();
1142 FcPattern* pPattern = FcPatternCreate();
1144 // populate pattern with font characteristics
1145 const LanguageTag aLangTag(rLocale);
1146 const OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
1147 if (!aLangAttrib.isEmpty())
1148 FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
1150 OString aFamily = OUStringToOString( rInfo.m_aFamilyName, RTL_TEXTENCODING_UTF8 );
1151 if( !aFamily.isEmpty() )
1152 FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr()));
1154 addtopattern(pPattern, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch);
1156 FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
1157 FcDefaultSubstitute(pPattern);
1158 FcResult eResult = FcResultNoMatch;
1159 FcFontSet *pFontSet = rWrapper.getFontSet();
1160 FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult);
1161 if( pResult )
1163 FcFontSet* pSet = FcFontSetCreate();
1164 FcFontSetAdd( pSet, pResult );
1165 if( pSet->nfont > 0 )
1167 //extract the closest match
1168 FcChar8* file = nullptr;
1169 FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
1170 int nEntryId = 0;
1171 FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
1172 if (eIndexRes != FcResultMatch)
1173 nEntryId = 0;
1174 if( eFileRes == FcResultMatch )
1176 OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) );
1177 splitPath( aOrgPath, aDir, aBase );
1178 int nDirID = getDirectoryAtom( aDir );
1179 fontID aFont = findFontFileID(nDirID, aBase,
1180 GetCollectionIndex(nEntryId),
1181 GetVariationIndex(nEntryId));
1182 if( aFont > 0 )
1183 getFontFastInfo( aFont, rInfo );
1186 // info: destroying the pSet destroys pResult implicitly
1187 // since pResult was "added" to pSet
1188 FcFontSetDestroy( pSet );
1191 // cleanup
1192 FcPatternDestroy( pPattern );
1195 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */