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 (C) 2021-2022 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
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#define WIN32_LEAN_AND_MEAN
34#include <windows.h>
35#endif
36
37using namespace fontconfig;
38
39static FONTCONFIG* g_config = nullptr;
40static bool g_fcInitSuccess = false;
41
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
60{
61};
62
63
65{
66 s_reporter = aReporter;
67}
68
69
77static void bootstrapFc()
78{
79#if defined( _MSC_VER )
80 __try
81 {
82#endif
83 FcInit();
84 g_fcInitSuccess = true;
85#if defined( _MSC_VER )
86 }
87 __except( GetExceptionCode() == STATUS_IN_PAGE_ERROR ? EXCEPTION_EXECUTE_HANDLER
88 : EXCEPTION_CONTINUE_SEARCH )
89 {
90 g_fcInitSuccess = false;
91 // We have documented cases that fontconfig while trying to cache fonts
92 // ends up using freetype to try and get font info
93 // freetype itself reads fonts through memory mapping instead of normal file APIs
94 // there are crashes reading fonts sometimes as a result that return STATUS_IN_PAGE_ERROR
95 }
96#endif
97}
98
99
101{
102 if( !g_config )
103 {
104 bootstrapFc();
105 g_config = new FONTCONFIG();
106 }
107
108 return g_config;
109}
110
111
112bool FONTCONFIG::isLanguageMatch( const wxString& aSearchLang, const wxString& aSupportedLang )
113{
114 if( aSearchLang.Lower() == aSupportedLang.Lower() )
115 return true;
116
117 if( aSupportedLang.empty() )
118 return false;
119
120 if( aSearchLang.empty() )
121 return false;
122
123 wxArrayString supportedLangBits;
124 wxStringSplit( aSupportedLang.Lower(), supportedLangBits, wxS( '-' ) );
125
126 wxArrayString searhcLangBits;
127 wxStringSplit( aSearchLang.Lower(), searhcLangBits, wxS( '-' ) );
128
129 // if either side of the comparison have only one section, then its a broad match but fine
130 // i.e. the haystack is declaring broad support or the search language is broad
131 if( searhcLangBits.size() == 1 || supportedLangBits.size() == 1 )
132 {
133 return searhcLangBits[0] == supportedLangBits[0];
134 }
135
136 // the full two part comparison should have passed the initial shortcut
137
138 return false;
139}
140
141
142std::string FONTCONFIG::getFcString( FONTCONFIG_PAT& aPat, const char* aObj, int aIdx )
143{
144 FcChar8* str;
145 std::string res;
146
147 if( FcPatternGetString( aPat.pat, aObj, aIdx, &str ) == FcResultMatch )
148 res = std::string( reinterpret_cast<char*>( str ) );
149
150 return res;
151}
152
153
155 std::unordered_map<std::string, std::string>& aFamStringMap )
156{
157 std::string famLang;
158 std::string fam;
159
160 int langIdx = 0;
161 do
162 {
163 famLang = getFcString( aPat, FC_FAMILYLANG, langIdx );
164
165 if( famLang.empty() && langIdx != 0 )
166 break;
167 else
168 {
169 fam = getFcString( aPat, FC_FAMILY, langIdx );
170 aFamStringMap.insert_or_assign( famLang, fam );
171 }
172 } while( langIdx++ < std::numeric_limits<
173 int8_t>::max() ); //arbitrary to avoid getting stuck for any reason
174}
175
176
177std::string FONTCONFIG::getFamilyStringByLang( FONTCONFIG_PAT& aPat, const wxString& aDesiredLang )
178{
179 std::unordered_map<std::string, std::string> famStrings;
180 getAllFamilyStrings( aPat, famStrings );
181
182 if( famStrings.empty() )
183 return "";
184
185 for( auto const& [key, val] : famStrings )
186 {
187 if( isLanguageMatch( aDesiredLang, From_UTF8( key.c_str() ) ) )
188 {
189 return val;
190 }
191 }
192
193 // fall back to the first and maybe only available name
194 // most fonts by review don't even bother declaring more than one font family name
195 // and they don't even bother declare the language tag either, they just leave it blank
196 return famStrings.begin()->second;
197}
198
199
200FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString& aFontName, wxString& aFontFile,
201 int& aFaceIndex, bool aBold, bool aItalic,
202 const std::vector<wxString>* aEmbeddedFiles )
203{
205
206 if( !g_fcInitSuccess )
207 return retval;
208
209 // If the original font name contains any of these, then it is bold, regardless
210 // of whether we are looking for bold or not
211 if( aFontName.Lower().Contains( wxS( "bold" ) ) // also catches ultrabold
212 || aFontName.Lower().Contains( wxS( "heavy" ) )
213 || aFontName.Lower().Contains( wxS( "black" ) ) // also catches extrablack
214 || aFontName.Lower().Contains( wxS( "thick" ) )
215 || aFontName.Lower().Contains( wxS( "dark" ) ) )
216 {
217 aBold = true;
218 }
219
220 FcConfig* config = FcConfigGetCurrent();
221
222 if( aEmbeddedFiles )
223 {
224 for( const auto& file : *aEmbeddedFiles )
225 {
226 FcConfigAppFontAddFile( config, (const FcChar8*) file.c_str().AsChar() );
227 }
228 }
229
230 wxString qualifiedFontName = aFontName;
231
232 wxScopedCharBuffer const fcBuffer = qualifiedFontName.ToUTF8();
233
234 FcPattern* pat = FcPatternCreate();
235
236 if( aBold )
237 FcPatternAddString( pat, FC_STYLE, (const FcChar8*) "Bold" );
238
239 if( aItalic )
240 FcPatternAddString( pat, FC_STYLE, (const FcChar8*) "Italic" );
241
242 FcPatternAddString( pat, FC_FAMILY, (FcChar8*) fcBuffer.data() );
243
244 FcConfigSubstitute( config, pat, FcMatchPattern );
245 FcDefaultSubstitute( pat );
246
247 FcResult r = FcResultNoMatch;
248 FcPattern* font = FcFontMatch( config, pat, &r );
249
250 wxString fontName;
251
252 if( font )
253 {
254 FcChar8* file = nullptr;
255
256 if( FcPatternGetString( font, FC_FILE, 0, &file ) == FcResultMatch )
257 {
258 aFontFile = wxString::FromUTF8( (char*) file );
259 aFaceIndex = 0;
260
261 wxString styleStr;
262 FcChar8* family = nullptr;
263 FcChar8* style = nullptr;
264
266
267 std::unordered_map<std::string, std::string> famStrings;
268 FONTCONFIG_PAT patHolder{ font };
269
270 getAllFamilyStrings( patHolder, famStrings );
271
272 if( FcPatternGetString( font, FC_FAMILY, 0, &family ) == FcResultMatch )
273 {
274 FcPatternGetInteger( font, FC_INDEX, 0, &aFaceIndex );
275
276 fontName = wxString::FromUTF8( (char*) family );
277
278 if( FcPatternGetString( font, FC_STYLE, 0, &style ) == FcResultMatch )
279 {
280 styleStr = wxString::FromUTF8( (char*) style );
281
282 if( !styleStr.IsEmpty() )
283 {
284 styleStr.Replace( ' ', ':' );
285 fontName += ':' + styleStr;
286 }
287 }
288
289 bool has_bold = false;
290 bool has_ital = false;
291 wxString lower_style = styleStr.Lower();
292
293 if( lower_style.Contains( wxS( "thin" ) )
294 || lower_style.Contains( wxS( "light" ) ) // also cataches ultralight and extralight
295 || lower_style.Contains( wxS( "regular" ) )
296 || lower_style.Contains( wxS( "roman" ) )
297 || lower_style.Contains( wxS( "book" ) ) )
298 {
299 has_bold = false;
300 }
301 else if( lower_style.Contains( wxS( "medium" ) )
302 || lower_style.Contains( wxS( "semibold" ) )
303 || lower_style.Contains( wxS( "demibold" ) ) )
304 {
305 has_bold = aBold;
306 }
307 else if( lower_style.Contains( wxS( "bold" ) ) // also catches ultrabold
308 || lower_style.Contains( wxS( "heavy" ) )
309 || lower_style.Contains( wxS( "black" ) ) // also catches extrablack
310 || lower_style.Contains( wxS( "thick" ) )
311 || lower_style.Contains( wxS( "dark" ) ) )
312 {
313 has_bold = true;
314 }
315
316 if( lower_style.Contains( wxS( "italic" ) )
317 || lower_style.Contains( wxS( "oblique" ) )
318 || lower_style.Contains( wxS( "slant" ) ) )
319 {
320 has_ital = true;
321 }
322
323 for( auto const& [key, val] : famStrings )
324 {
325 wxString searchFont;
326 searchFont = wxString::FromUTF8( (char*) val.data() );
327
328 if( searchFont.Lower().StartsWith( aFontName.Lower() ) )
329 {
330 if( ( aBold && !has_bold ) && ( aItalic && !has_ital ) )
332 else if( aBold && !has_bold )
334 else if( aItalic && !has_ital )
336 else if( ( aBold != has_bold ) || ( aItalic != has_ital ) )
338 else
339 retval = FF_RESULT::FF_OK;
340
341 break;
342 }
343 }
344 }
345 }
346
347 FcPatternDestroy( font );
348 }
349
350 if( retval == FF_RESULT::FF_ERROR )
351 {
352 if( s_reporter )
353 s_reporter->Report( wxString::Format( _( "Error loading font '%s'." ), qualifiedFontName ) );
354 }
355 else if( retval == FF_RESULT::FF_SUBSTITUTE )
356 {
357 fontName.Replace( ':', ' ' );
358
359 // If we missed a case but the matching found the original font name, then we are not substituting
360 if( fontName.CmpNoCase( qualifiedFontName ) == 0 )
361 retval = FF_RESULT::FF_OK;
362 else if( s_reporter )
363 s_reporter->Report( wxString::Format( _( "Font '%s' not found; substituting '%s'." ), qualifiedFontName, fontName ) );
364 }
365
366 FcPatternDestroy( pat );
367 return retval;
368}
369
370
371void FONTCONFIG::ListFonts( std::vector<std::string>& aFonts, const std::string& aDesiredLang,
372 const std::vector<wxString>* aEmbeddedFiles, bool aForce )
373{
374 if( !g_fcInitSuccess )
375 return;
376
377 // be sure to cache bust if the language changed
378 if( m_fontInfoCache.empty() || m_fontCacheLastLang != aDesiredLang || aForce )
379 {
380 FcConfig* config = FcConfigGetCurrent();
381
382 if( aEmbeddedFiles )
383 {
384 for( const auto& file : *aEmbeddedFiles )
385 {
386 FcConfigAppFontAddFile( config, (const FcChar8*) file.c_str().AsChar() );
387 }
388 }
389
390 FcPattern* pat = FcPatternCreate();
391 FcObjectSet* os = FcObjectSetBuild( FC_FAMILY, FC_FAMILYLANG, FC_STYLE, FC_LANG, FC_FILE,
392 FC_OUTLINE, nullptr );
393 FcFontSet* fs = FcFontList( config, pat, os );
394
395 for( int i = 0; fs && i < fs->nfont; ++i )
396 {
397 FcPattern* font = fs->fonts[i];
398 FcChar8* file;
399 FcChar8* style;
400 FcLangSet* langSet;
401 FcBool outline;
402
403 if( FcPatternGetString( font, FC_FILE, 0, &file ) == FcResultMatch
404 && FcPatternGetString( font, FC_STYLE, 0, &style ) == FcResultMatch
405 && FcPatternGetLangSet( font, FC_LANG, 0, &langSet ) == FcResultMatch
406 && FcPatternGetBool( font, FC_OUTLINE, 0, &outline ) == FcResultMatch )
407 {
408 if( !outline )
409 continue;
410
411 FONTCONFIG_PAT patHolder{ font };
412 std::string theFamily =
413 getFamilyStringByLang( patHolder, From_UTF8( aDesiredLang.c_str() ) );
414
415#ifdef __WXMAC__
416 // On Mac (at least) some of the font names are in their own language. If
417 // the OS doesn't support this language then we get a bunch of garbage names
418 // in the font menu.
419 //
420 // GTK, on the other hand, doesn't appear to support wxLocale::IsAvailable(),
421 // so we can't run these checks.
422
423 static std::map<wxString, bool> availableLanguages;
424
425 FcStrSet* langStrSet = FcLangSetGetLangs( langSet );
426 FcStrList* langStrList = FcStrListCreate( langStrSet );
427 FcChar8* langStr = FcStrListNext( langStrList );
428 bool langSupported = false;
429
430 if( !langStr )
431 {
432 // Symbol fonts (Wingdings, etc.) have no language
433 langSupported = true;
434 }
435 else while( langStr )
436 {
437 wxString langWxStr( reinterpret_cast<char *>( langStr ) );
438
439 if( availableLanguages.find( langWxStr ) == availableLanguages.end() )
440 {
441 const wxLanguageInfo* langInfo = wxLocale::FindLanguageInfo( langWxStr );
442 bool available = langInfo && wxLocale::IsAvailable( langInfo->Language );
443
444 availableLanguages[ langWxStr ] = available;
445 }
446
447 if( availableLanguages[ langWxStr ] )
448 {
449 langSupported = true;
450 break;
451 }
452 else
453 {
454 wxLogTrace( traceFonts, wxS( "Font '%s' language '%s' not supported by OS." ),
455 theFamily, langWxStr );
456 }
457
458 langStr = FcStrListNext( langStrList );
459 }
460
461 FcStrListDone( langStrList );
462 FcStrSetDestroy( langStrSet );
463
464 if( !langSupported )
465 continue;
466#endif
467
468 std::string theFile( reinterpret_cast<char *>( file ) );
469 std::string theStyle( reinterpret_cast<char *>( style ) );
470 FONTINFO fontInfo( std::move( theFile ), std::move( theStyle ), theFamily );
471
472 if( theFamily.length() > 0 && theFamily.front() == '.' )
473 continue;
474
475 std::map<std::string, FONTINFO>::iterator it = m_fontInfoCache.find( theFamily );
476
477 if( it == m_fontInfoCache.end() )
478 m_fontInfoCache.emplace( theFamily, fontInfo );
479 else
480 it->second.Children().push_back( fontInfo );
481 }
482 }
483
484 if( fs )
485 FcFontSetDestroy( fs );
486
487 m_fontCacheLastLang = aDesiredLang;
488 }
489
490 for( const std::pair<const std::string, FONTINFO>& entry : m_fontInfoCache )
491 aFonts.push_back( entry.second.Family() );
492}
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:72
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
static REPORTER * s_reporter
Definition: fontconfig.h:85
void getAllFamilyStrings(FONTCONFIG_PAT &aPat, std::unordered_map< std::string, std::string > &aFamStringMap)
Gets a list of all family name strings maped to lang.
Definition: fontconfig.cpp:154
static wxString Version()
Definition: fontconfig.cpp:53
static void SetReporter(REPORTER *aReporter)
Set the reporter to use for reporting font substitution warnings.
Definition: fontconfig.cpp:64
std::map< std::string, FONTINFO > m_fontInfoCache
Definition: fontconfig.h:83
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...
Definition: fontconfig.cpp:200
std::string getFcString(FONTCONFIG_PAT &aPat, const char *aObj, int aIdx)
Wrapper of FcPatternGetString to return a std::string.
Definition: fontconfig.cpp:142
std::string getFamilyStringByLang(FONTCONFIG_PAT &APat, const wxString &aDesiredLang)
Gets a family name based on desired language.
Definition: fontconfig.cpp:177
bool isLanguageMatch(const wxString &aSearchLang, const wxString &aSupportedLang)
Matches the two rfc 3306 language entries, used for when searching for matching family names.
Definition: fontconfig.cpp:112
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.
Definition: fontconfig.cpp:371
wxString m_fontCacheLastLang
Definition: fontconfig.h:84
#define _(s)
static FONTCONFIG * g_config
Definition: fontconfig.cpp:39
FONTCONFIG * Fontconfig()
Definition: fontconfig.cpp:100
static void bootstrapFc()
This is simply a wrapper to call FcInit() with SEH for Windows SEH on Windows can only be used in fun...
Definition: fontconfig.cpp:77
static bool g_fcInitSuccess
Definition: fontconfig.cpp:40
const wxChar *const traceFonts
Flag to enable locale debug output.
This file contains miscellaneous commonly used macros and functions.
static WX_STRING_REPORTER s_reporter
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 exporing fontconfig in the header.
Definition: fontconfig.cpp:48
VECTOR3I res
wxLogTrace helper definitions.