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