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