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