1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
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>
39 #include <fontconfig/fontconfig.h>
43 #include <unotools/configmgr.hxx>
45 #include <osl/process.h>
54 typedef std::pair
<FcChar8
*, FcChar8
*> lang_and_element
;
59 FcFontSet
* m_pFontSet
;
61 void addFontSet( FcSetName
);
67 static FontCfgWrapper
& get();
68 static void release();
70 FcFontSet
* getFontSet();
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
;
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 )
92 void FontCfgWrapper::addFontSet( FcSetName eSetName
)
94 // Add only acceptable fonts to our config, for future fontconfig use.
95 FcFontSet
* pOrig
= FcConfigGetFonts( FcConfigGetCurrent(), eSetName
);
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
))
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))
117 FcPatternReference( pPattern
);
118 FcFontSetAdd( m_pFontSet
, pPattern
);
121 // TODO?: FcFontSetDestroy( pOrig );
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
144 bool operator()(const FcPattern
*a
, const FcPattern
*b
)
146 int comp
= compareFontNames(a
, b
);
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
);
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)
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
);
207 FcFontSet
* FontCfgWrapper::getFontSet()
211 m_pFontSet
= FcFontSetCreate();
212 addFontSet( FcSetSystem
);
213 addFontSet( FcSetApplication
);
215 ::std::sort(m_pFontSet
->fonts
,m_pFontSet
->fonts
+m_pFontSet
->nfont
,SortFont());
221 FontCfgWrapper::~FontCfgWrapper()
224 //To-Do: get gtk vclplug smoketest to pass
228 static FontCfgWrapper
* pOneInstance
= nullptr;
230 FontCfgWrapper
& FontCfgWrapper::get()
233 pOneInstance
= new FontCfgWrapper();
234 return *pOneInstance
;
237 void FontCfgWrapper::release()
242 pOneInstance
= nullptr;
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
+
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
;
272 else if( alreadyclosematch
)
274 // current candidate matches lang of lang-TERRITORY
275 // override candidate only if there is a full match
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
290 else if( rtl_str_compare( pLang
, "en") == 0)
292 // select a fallback candidate of the first english element
294 candidate
= element
.second
;
295 found_fallback_englishname
= true;
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
);
333 if (FcPatternGetString( pPattern
, elementlangtype
, k
, &elementlang
) != FcResultMatch
)
335 if (FcPatternGetString( pPattern
, elementtype
, k
, element
) != FcResultMatch
)
337 lang_and_elements
.emplace_back(elementlang
, *element
);
341 //possible to-do, sort by UILocale instead of process locale
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
);
359 void FontCfgWrapper::clear()
361 m_aFontNameToLocalized
.clear();
362 m_aLocalizedToCanonical
.clear();
365 FcFontSetDestroy( m_pFontSet
);
366 m_pFontSet
= nullptr;
368 m_pLanguageTag
.reset();
372 * PrintFontManager::initFontconfig
374 void PrintFontManager::initFontconfig()
376 FontCfgWrapper
& rWrapper
= FontCfgWrapper::get();
382 FontWeight
convertWeight(int weight
)
385 if( weight
<= FC_WEIGHT_THIN
)
387 else if( weight
<= FC_WEIGHT_ULTRALIGHT
)
388 return WEIGHT_ULTRALIGHT
;
389 else if( weight
<= FC_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
)
401 else if( weight
<= FC_WEIGHT_ULTRABOLD
)
402 return WEIGHT_ULTRABOLD
;
406 FontItalic
convertSlant(int slant
)
409 if( slant
== FC_SLANT_ITALIC
)
410 return ITALIC_NORMAL
;
411 else if( slant
== FC_SLANT_OBLIQUE
)
412 return ITALIC_OBLIQUE
;
416 FontPitch
convertSpacing(int spacing
)
419 if( spacing
== FC_MONO
|| spacing
== FC_CHARCELL
)
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
;
447 //FontConfig doesn't come with a way to remove an element from a FontSet as far
449 static void lcl_FcFontSetRemove(FcFontSet
* pFSet
, int i
)
451 FcPatternDestroy(pFSet
->fonts
[i
]);
453 int nTail
= pFSet
->nfont
- (i
+ 1);
457 memmove(pFSet
->fonts
+ i
, pFSet
->fonts
+ i
+ 1, nTail
*sizeof(FcPattern
*));
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
)
479 FontCfgWrapper
& rWrapper
= FontCfgWrapper::get();
481 FcFontSet
* pFSet
= rWrapper
.getFontSet();
482 const bool bMinimalFontset
= utl::ConfigManager::IsFuzzing();
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;
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")))
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
)
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
)
533 if (isPreviouslyDuplicateOrObsoleted(pFSet
, i
))
535 SAL_INFO("vcl.fonts.detail", "Ditching " << file
<< " as duplicate/obsolete");
539 // see if this font is already cached
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
)
552 std::vector
<std::unique_ptr
<PrintFont
>> aFonts
= analyzeFontFile( nDirID
, aBase
, reinterpret_cast<char*>(format
) );
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
--);
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
);
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
);
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
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
);
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
);
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" );
659 bool bCfgOk
= FcConfigParseAndLoad(FcConfigGetCurrent(),
660 reinterpret_cast<FcChar8
const *>(aConfFileName
.getStr()), FcTrue
);
662 fprintf( stderr
, "FcConfigParseAndLoad( \"%s\") => %d\n", aConfFileName
.getStr(), bCfgOk
);
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
;
677 nSlant
= FC_SLANT_ITALIC
;
680 nSlant
= FC_SLANT_OBLIQUE
;
685 FcPatternAddInteger(pPattern
, FC_SLANT
, nSlant
);
687 if( eWeight
!= WEIGHT_DONTKNOW
)
689 int nWeight
= FC_WEIGHT_NORMAL
;
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;
705 FcPatternAddInteger(pPattern
, FC_WEIGHT
, nWeight
);
707 if( eWidth
!= WIDTH_DONTKNOW
)
709 int nWidth
= FC_WIDTH_NORMAL
;
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;
724 FcPatternAddInteger(pPattern
, FC_WIDTH
, nWidth
);
726 if( ePitch
!= PITCH_DONTKNOW
)
728 int nSpacing
= FC_PROPORTIONAL
;
731 case PITCH_FIXED
: nSpacing
= FC_MONO
;break;
732 case PITCH_VARIABLE
: nSpacing
= FC_PROPORTIONAL
;break;
736 FcPatternAddInteger(pPattern
, FC_SPACING
, nSpacing
);
737 if (nSpacing
== FC_MONO
)
738 FcPatternAddString(pPattern
, FC_FAMILY
, reinterpret_cast<FcChar8
const *>("monospace"));
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
);
752 sLangAttrib
= OUStringToOString(rLangTag
.getBcp47(), RTL_TEXTENCODING_UTF8
).toAsciiLowerCase();
753 if (FcStrSetMember(xLangSet
.get(), reinterpret_cast<const FcChar8
*>(sLangAttrib
.getStr())))
758 sLangAttrib
= OUStringToOString(rLangTag
.getLanguageAndScript(), RTL_TEXTENCODING_UTF8
).toAsciiLowerCase();
759 if (FcStrSetMember(xLangSet
.get(), reinterpret_cast<const FcChar8
*>(sLangAttrib
.getStr())))
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())))
776 if (FcStrSetMember(xLangSet
.get(), reinterpret_cast<const FcChar8
*>(sLang
.getStr())))
783 OString sLangAttrib
= OUStringToOString(rLangTag
.getLanguageAndScript(), RTL_TEXTENCODING_UTF8
).toAsciiLowerCase();
784 if (sLangAttrib
.equalsIgnoreAsciiCase("pa-in"))
790 bool isEmoji(sal_uInt32 nCurrentChar
)
792 #if U_ICU_VERSION_MAJOR_NUM >= 57
793 return u_hasBinaryProperty(nCurrentChar
, UCHAR_EMOJI
);
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())
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();
812 //http://en.wiktionary.org/wiki/Category:Oriya_script_languages
818 //http://en.wiktionary.org/wiki/Category:Telugu_script_languages
825 //http://en.wiktionary.org/wiki/Category:Bengali_script_languages
826 case USCRIPT_BENGALI
:
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
))
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
);
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
)))
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;
939 pSet
= FcFontSetCreate();
940 // info: destroying the pSet destroys pResult implicitly
941 // since pResult was "added" to pSet
942 FcFontSetAdd( pSet
, pResult
);
947 if( pSet
->nfont
> 0 )
951 //extract the closest match
952 FcChar8
* file
= nullptr;
953 FcResult eFileRes
= FcPatternGetString(pSet
->fonts
[0], FC_FILE
, 0, &file
);
955 FcResult eIndexRes
= FcPatternGetInteger(pSet
->fonts
[0], FC_INDEX
, 0, &nEntryId
);
956 if (eIndexRes
!= FcResultMatch
)
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
));
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");
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
);
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
) );
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
)
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
1074 FontConfigFontOptions::~FontConfigFontOptions()
1076 FcPatternDestroy(mpPattern
);
1079 FcPattern
*FontConfigFontOptions::GetPattern() const
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
);
1125 (void) FcPatternGetInteger(pResult
,
1126 FC_HINT_STYLE
, 0, &hintstyle
);
1128 pOptions
.reset(new FontConfigFontOptions(pResult
));
1132 FcPatternDestroy( pPattern
);
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
);
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
);
1171 FcResult eIndexRes
= FcPatternGetInteger(pSet
->fonts
[0], FC_INDEX
, 0, &nEntryId
);
1172 if (eIndexRes
!= FcResultMatch
)
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
));
1183 getFontFastInfo( aFont
, rInfo
);
1186 // info: destroying the pSet destroys pResult implicitly
1187 // since pResult was "added" to pSet
1188 FcFontSetDestroy( pSet
);
1192 FcPatternDestroy( pPattern
);
1195 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */