KiCad PCB EDA Suite
Loading...
Searching...
No Matches
fontconfig.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2021 Ola Rinta-Koski
5 * Copyright (C) 2023 CERN (www.cern.ch)
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include <mutex>
23#include <font/fontconfig.h>
24#include <wx/log.h>
25#include <trace_helpers.h>
26#include <string_utils.h>
27#include <macros.h>
28#include <cstdint>
29#include <reporter.h>
30#include <embedded_files.h>
31
32#ifdef __WIN32__
33#include <windows.h>
34#endif
35
36using namespace fontconfig;
37
38static FONTCONFIG* g_config = nullptr;
39static bool g_fcInitSuccess = false;
40
42static std::mutex g_fontConfigMutex;
43
48{
49 FcPattern* pat;
50};
51
52
54{
55 return wxString::Format( "%d.%d.%d", FC_MAJOR, FC_MINOR, FC_REVISION );
56}
57
58
62
63
65{
66 std::lock_guard lock( g_fontConfigMutex );
67 s_reporter = aReporter;
68}
69
70
72{
73 std::lock_guard lock( g_fontConfigMutex );
74 return s_reporter;
75}
76
77
86static void bootstrapFc()
87{
88#if defined( _MSC_VER )
89 __try
90 {
91#endif
92 FcInit();
93 g_fcInitSuccess = true;
94#if defined( _MSC_VER )
95 }
96 __except( GetExceptionCode() == STATUS_IN_PAGE_ERROR ? EXCEPTION_EXECUTE_HANDLER
97 : EXCEPTION_CONTINUE_SEARCH )
98 {
99 g_fcInitSuccess = false;
100
101 // We have documented cases that fontconfig while trying to cache fonts
102 // ends up using freetype to try and get font info
103 // freetype itself reads fonts through memory mapping instead of normal file APIs
104 // there are crashes reading fonts sometimes as a result that return STATUS_IN_PAGE_ERROR
105 }
106#endif
107}
108
109
111{
112 if( !g_config )
113 {
114 bootstrapFc();
115 g_config = new FONTCONFIG();
116 }
117
118 return g_config;
119}
120
121
122bool FONTCONFIG::isLanguageMatch( const wxString& aSearchLang, const wxString& aSupportedLang )
123{
124 if( aSearchLang.Lower() == aSupportedLang.Lower() )
125 return true;
126
127 if( aSupportedLang.empty() )
128 return false;
129
130 if( aSearchLang.empty() )
131 return false;
132
133 wxArrayString supportedLangBits;
134 wxStringSplit( aSupportedLang.Lower(), supportedLangBits, wxS( '-' ) );
135
136 wxArrayString searhcLangBits;
137 wxStringSplit( aSearchLang.Lower(), searhcLangBits, wxS( '-' ) );
138
139 // if either side of the comparison have only one section, then its a broad match but fine
140 // i.e. the haystack is declaring broad support or the search language is broad
141 if( searhcLangBits.size() == 1 || supportedLangBits.size() == 1 )
142 {
143 return searhcLangBits[0] == supportedLangBits[0];
144 }
145
146 // the full two part comparison should have passed the initial shortcut
147
148 return false;
149}
150
151
152std::string FONTCONFIG::getFcString( FONTCONFIG_PAT& aPat, const char* aObj, int aIdx )
153{
154 FcChar8* str;
155 std::string res;
156
157 if( FcPatternGetString( aPat.pat, aObj, aIdx, &str ) == FcResultMatch )
158 res = std::string( reinterpret_cast<char*>( str ) );
159
160 return res;
161}
162
163
165 std::unordered_map<std::string, std::string>& aFamStringMap )
166{
167 std::string famLang;
168 std::string fam;
169
170 int langIdx = 0;
171
172 do
173 {
174 famLang = getFcString( aPat, FC_FAMILYLANG, langIdx );
175
176 if( famLang.empty() && langIdx != 0 )
177 {
178 break;
179 }
180 else
181 {
182 fam = getFcString( aPat, FC_FAMILY, langIdx );
183 aFamStringMap.insert_or_assign( famLang, fam );
184 }
185 } while( langIdx++ < std::numeric_limits<
186 int8_t>::max() ); //arbitrary to avoid getting stuck for any reason
187}
188
189
190std::string FONTCONFIG::getFamilyStringByLang( FONTCONFIG_PAT& aPat, const wxString& aDesiredLang )
191{
192 std::unordered_map<std::string, std::string> famStrings;
193 getAllFamilyStrings( aPat, famStrings );
194
195 if( famStrings.empty() )
196 return "";
197
198 for( auto const& [key, val] : famStrings )
199 {
200 if( isLanguageMatch( aDesiredLang, From_UTF8( key.c_str() ) ) )
201 {
202 return val;
203 }
204 }
205
206 // fall back to the first and maybe only available name
207 // most fonts by review don't even bother declaring more than one font family name
208 // and they don't even bother declare the language tag either, they just leave it blank
209 return famStrings.begin()->second;
210}
211
212
213FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString& aFontName, wxString& aFontFile,
214 int& aFaceIndex, bool aBold, bool aItalic,
215 const std::vector<wxString>* aEmbeddedFiles )
216{
218
219 if( !g_fcInitSuccess )
220 return retval;
221
222 // If the original font name contains any of these, then it is bold, regardless
223 // of whether we are looking for bold or not
224 if( aFontName.Lower().Contains( wxS( "bold" ) ) // also catches ultrabold
225 || aFontName.Lower().Contains( wxS( "heavy" ) )
226 || aFontName.Lower().Contains( wxS( "black" ) ) // also catches extrablack
227 || aFontName.Lower().Contains( wxS( "thick" ) )
228 || aFontName.Lower().Contains( wxS( "dark" ) ) )
229 {
230 aBold = true;
231 }
232
233 FcConfig* config = FcConfigGetCurrent();
234
235 if( aEmbeddedFiles )
236 {
237 for( const auto& file : *aEmbeddedFiles )
238 {
239 FcConfigAppFontAddFile( config, (const FcChar8*) file.c_str().AsChar() );
240 }
241 }
242
243 wxString qualifiedFontName = aFontName;
244
245 wxScopedCharBuffer const fcBuffer = qualifiedFontName.ToUTF8();
246
247 FcPattern* pat = FcPatternCreate();
248
249 if( aBold )
250 FcPatternAddString( pat, FC_STYLE, (const FcChar8*) "Bold" );
251
252 if( aItalic )
253 FcPatternAddString( pat, FC_STYLE, (const FcChar8*) "Italic" );
254
255 FcPatternAddString( pat, FC_FAMILY, (FcChar8*) fcBuffer.data() );
256
257 FcConfigSubstitute( config, pat, FcMatchPattern );
258 FcDefaultSubstitute( pat );
259
260 FcResult r = FcResultNoMatch;
261 FcPattern* font = FcFontMatch( config, pat, &r );
262
263 wxString fontName;
264
265 if( font )
266 {
267 FcChar8* file = nullptr;
268
269 if( FcPatternGetString( font, FC_FILE, 0, &file ) == FcResultMatch )
270 {
271 aFontFile = wxString::FromUTF8( (char*) file );
272 aFaceIndex = 0;
273
274 wxString styleStr;
275 FcChar8* family = nullptr;
276 FcChar8* style = nullptr;
277
279
280 std::unordered_map<std::string, std::string> famStrings;
281 FONTCONFIG_PAT patHolder{ font };
282
283 getAllFamilyStrings( patHolder, famStrings );
284
285 if( FcPatternGetString( font, FC_FAMILY, 0, &family ) == FcResultMatch )
286 {
287 FcPatternGetInteger( font, FC_INDEX, 0, &aFaceIndex );
288
289 fontName = wxString::FromUTF8( (char*) family );
290
291 if( FcPatternGetString( font, FC_STYLE, 0, &style ) == FcResultMatch )
292 {
293 styleStr = wxString::FromUTF8( (char*) style );
294
295 if( !styleStr.IsEmpty() )
296 {
297 styleStr.Replace( ' ', ':' );
298 fontName += ':' + styleStr;
299 }
300 }
301
302 bool has_bold = false;
303 bool has_ital = false;
304 wxString lower_style = styleStr.Lower();
305
306 if( lower_style.Contains( wxS( "thin" ) )
307 || lower_style.Contains( wxS( "light" ) ) // catches ultra & extra light
308 || lower_style.Contains( wxS( "regular" ) )
309 || lower_style.Contains( wxS( "roman" ) )
310 || lower_style.Contains( wxS( "book" ) ) )
311 {
312 has_bold = false;
313 }
314 else if( lower_style.Contains( wxS( "medium" ) )
315 || lower_style.Contains( wxS( "semibold" ) )
316 || lower_style.Contains( wxS( "demibold" ) ) )
317 {
318 has_bold = aBold;
319 }
320 else if( lower_style.Contains( wxS( "bold" ) ) // also catches ultrabold
321 || lower_style.Contains( wxS( "heavy" ) )
322 || lower_style.Contains( wxS( "black" ) ) // also catches extrablack
323 || lower_style.Contains( wxS( "thick" ) )
324 || lower_style.Contains( wxS( "dark" ) ) )
325 {
326 has_bold = true;
327 }
328
329 if( lower_style.Contains( wxS( "italic" ) )
330 || lower_style.Contains( wxS( "oblique" ) )
331 || lower_style.Contains( wxS( "slant" ) ) )
332 {
333 has_ital = true;
334 }
335
336 for( auto const& [key, val] : famStrings )
337 {
338 wxString searchFont;
339 searchFont = wxString::FromUTF8( (char*) val.data() );
340
341 if( searchFont.Lower().StartsWith( aFontName.Lower() ) )
342 {
343 if( ( aBold && !has_bold ) && ( aItalic && !has_ital ) )
345 else if( aBold && !has_bold )
347 else if( aItalic && !has_ital )
349 else if( ( aBold != has_bold ) || ( aItalic != has_ital ) )
351 else
352 retval = FF_RESULT::FF_OK;
353
354 break;
355 }
356 }
357 }
358 }
359
360 FcPatternDestroy( font );
361 }
362
363 if( retval == FF_RESULT::FF_ERROR )
364 {
365 if( s_reporter )
366 s_reporter->Report( wxString::Format( _( "Error loading font '%s'." ),
367 qualifiedFontName ) );
368 }
369 else if( retval == FF_RESULT::FF_SUBSTITUTE )
370 {
371 fontName.Replace( ':', ' ' );
372
373 // If we missed a case but the matching found the original font name, then we are
374 // not substituting
375 if( fontName.CmpNoCase( qualifiedFontName ) == 0 )
376 retval = FF_RESULT::FF_OK;
377 else if( s_reporter )
378 s_reporter->Report( wxString::Format( _( "Font '%s' not found; substituting '%s'." ),
379 qualifiedFontName, fontName ) );
380 }
381
382 FcPatternDestroy( pat );
383 return retval;
384}
385
386
387void FONTCONFIG::ListFonts( std::vector<std::string>& aFonts, const std::string& aDesiredLang,
388 const std::vector<wxString>* aEmbeddedFiles, bool aForce )
389{
390 if( !g_fcInitSuccess )
391 return;
392
393 // be sure to cache bust if the language changed
394 if( m_fontInfoCache.empty() || m_fontCacheLastLang != aDesiredLang || aForce )
395 {
396 FcConfig* config = FcConfigGetCurrent();
397
398 if( aEmbeddedFiles )
399 {
400 for( const auto& file : *aEmbeddedFiles )
401 {
402 FcConfigAppFontAddFile( config, (const FcChar8*) file.c_str().AsChar() );
403 }
404 }
405
406 FcPattern* pat = FcPatternCreate();
407 FcObjectSet* os = FcObjectSetBuild( FC_FAMILY, FC_FAMILYLANG, FC_STYLE, FC_LANG, FC_FILE,
408 FC_OUTLINE, nullptr );
409 FcFontSet* fs = FcFontList( config, pat, os );
410
411 for( int i = 0; fs && i < fs->nfont; ++i )
412 {
413 FcPattern* font = fs->fonts[i];
414 FcChar8* file;
415 FcChar8* style;
416 FcLangSet* langSet;
417 FcBool outline;
418
419 if( FcPatternGetString( font, FC_FILE, 0, &file ) == FcResultMatch
420 && FcPatternGetString( font, FC_STYLE, 0, &style ) == FcResultMatch
421 && FcPatternGetLangSet( font, FC_LANG, 0, &langSet ) == FcResultMatch
422 && FcPatternGetBool( font, FC_OUTLINE, 0, &outline ) == FcResultMatch )
423 {
424 if( !outline )
425 continue;
426
427 FONTCONFIG_PAT patHolder{ font };
428 std::string theFamily =
429 getFamilyStringByLang( patHolder, From_UTF8( aDesiredLang.c_str() ) );
430
431#ifdef __WXMAC__
432 // On Mac (at least) some of the font names are in their own language. If
433 // the OS doesn't support this language then we get a bunch of garbage names
434 // in the font menu.
435 //
436 // GTK, on the other hand, doesn't appear to support wxLocale::IsAvailable(),
437 // so we can't run these checks.
438
439 static std::map<wxString, bool> availableLanguages;
440
441 FcStrSet* langStrSet = FcLangSetGetLangs( langSet );
442 FcStrList* langStrList = FcStrListCreate( langStrSet );
443 FcChar8* langStr = FcStrListNext( langStrList );
444 bool langSupported = false;
445
446 if( !langStr )
447 {
448 // Symbol fonts (Wingdings, etc.) have no language
449 langSupported = true;
450 }
451 else while( langStr )
452 {
453 wxString langWxStr( reinterpret_cast<char *>( langStr ) );
454
455 if( availableLanguages.find( langWxStr ) == availableLanguages.end() )
456 {
457 const wxLanguageInfo* langInfo = wxLocale::FindLanguageInfo( langWxStr );
458 bool available = langInfo && wxLocale::IsAvailable( langInfo->Language );
459
460 availableLanguages[ langWxStr ] = available;
461 }
462
463 if( availableLanguages[ langWxStr ] )
464 {
465 langSupported = true;
466 break;
467 }
468 else
469 {
470 wxLogTrace( traceFonts,
471 wxS( "Font '%s' language '%s' not supported by OS." ),
472 theFamily, langWxStr );
473 }
474
475 langStr = FcStrListNext( langStrList );
476 }
477
478 FcStrListDone( langStrList );
479 FcStrSetDestroy( langStrSet );
480
481 if( !langSupported )
482 continue;
483#endif
484
485 std::string theFile( reinterpret_cast<char *>( file ) );
486 std::string theStyle( reinterpret_cast<char *>( style ) );
487 FONTINFO fontInfo( std::move( theFile ), std::move( theStyle ), theFamily );
488
489 if( theFamily.length() > 0 && theFamily.front() == '.' )
490 continue;
491
492 std::map<std::string, FONTINFO>::iterator it = m_fontInfoCache.find( theFamily );
493
494 if( it == m_fontInfoCache.end() )
495 m_fontInfoCache.emplace( theFamily, fontInfo );
496 else
497 it->second.Children().push_back( fontInfo );
498 }
499 }
500
501 if( fs )
502 FcFontSetDestroy( fs );
503
504 m_fontCacheLastLang = aDesiredLang;
505 }
506
507 for( const std::pair<const std::string, FONTINFO>& entry : m_fontInfoCache )
508 aFonts.push_back( entry.second.Family() );
509}
510
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
static REPORTER * s_reporter
Definition fontconfig.h:94
void getAllFamilyStrings(FONTCONFIG_PAT &aPat, std::unordered_map< std::string, std::string > &aFamStringMap)
Get a list of all family name strings mapped to lang.
static wxString Version()
static void SetReporter(REPORTER *aReporter)
Set the reporter to use for reporting font substitution warnings.
std::map< std::string, FONTINFO > m_fontInfoCache
Definition fontconfig.h:92
FF_RESULT FindFont(const wxString &aFontName, wxString &aFontFile, int &aFaceIndex, bool aBold, bool aItalic, const std::vector< wxString > *aEmbeddedFiles=nullptr)
Given a fully-qualified font name ("Times:Bold:Italic") find the closest matching font and return its...
std::string getFcString(FONTCONFIG_PAT &aPat, const char *aObj, int aIdx)
Wrapper of FcPatternGetString to return a std::string.
std::string getFamilyStringByLang(FONTCONFIG_PAT &APat, const wxString &aDesiredLang)
Get a family name based on desired language.
static REPORTER * GetReporter()
Get the current reporter used for font substitution warnings.
bool isLanguageMatch(const wxString &aSearchLang, const wxString &aSupportedLang)
Match two rfc 3306 language entries, used for when searching for matching family names.
void ListFonts(std::vector< std::string > &aFonts, const std::string &aDesiredLang, const std::vector< wxString > *aEmbeddedFiles=nullptr, bool aForce=false)
List the current available font families.
wxString m_fontCacheLastLang
Definition fontconfig.h:93
#define _(s)
static FONTCONFIG * g_config
FONTCONFIG * Fontconfig()
static void bootstrapFc()
This is simply a wrapper to call FcInit() with SEH for Windows.
static bool g_fcInitSuccess
static std::mutex g_fontConfigMutex
const wxChar *const traceFonts
Flag to enable locale debug output.
This file contains miscellaneous commonly used macros and functions.
wxString From_UTF8(const char *cstring)
void wxStringSplit(const wxString &aText, wxArrayString &aStrings, wxChar aSplitter)
Split aString to a string list separated at aSplitter.
A simple wrapper to avoid exporting fontconfig in the header.
VECTOR3I res
wxLogTrace helper definitions.