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) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22#include <font/fontconfig.h>
23#include <wx/log.h>
24#include <trace_helpers.h>
25#include <string_utils.h>
26#include <macros.h>
27#include <cstdint>
28
29using namespace fontconfig;
30
31static FONTCONFIG* g_config = nullptr;
32
37{
38 FcPattern* pat;
39};
40
41
43{
44 return wxString::Format( "%d.%d.%d", FC_MAJOR, FC_MINOR, FC_REVISION );
45}
46
47
49{
50};
51
52
54{
55 if( !g_config )
56 {
57 FcInit();
58 g_config = new FONTCONFIG();
59 }
60
61 return g_config;
62}
63
64
65bool FONTCONFIG::isLanguageMatch( const wxString& aSearchLang, const wxString& aSupportedLang )
66{
67 if( aSearchLang.Lower() == aSupportedLang.Lower() )
68 return true;
69
70 if( aSupportedLang.empty() )
71 return false;
72
73 if( aSearchLang.empty() )
74 return false;
75
76 wxArrayString supportedLangBits;
77 wxStringSplit( aSupportedLang.Lower(), supportedLangBits, wxS( '-' ) );
78
79 wxArrayString searhcLangBits;
80 wxStringSplit( aSearchLang.Lower(), searhcLangBits, wxS( '-' ) );
81
82 // if either side of the comparison have only one section, then its a broad match but fine
83 // i.e. the haystack is declaring broad support or the search language is broad
84 if( searhcLangBits.size() == 1 || supportedLangBits.size() == 1 )
85 {
86 return searhcLangBits[0] == supportedLangBits[0];
87 }
88
89 // the full two part comparison should have passed the initial shortcut
90
91 return false;
92}
93
94
95std::string FONTCONFIG::getFcString( FONTCONFIG_PAT& aPat, const char* aObj, int aIdx )
96{
97 FcChar8* str;
98 std::string res;
99
100 if( FcPatternGetString( aPat.pat, aObj, aIdx, &str ) == FcResultMatch )
101 res = std::string( reinterpret_cast<char*>( str ) );
102
103 return res;
104}
105
106
108 std::unordered_map<std::string, std::string>& aFamStringMap )
109{
110 std::string famLang;
111 std::string fam;
112
113 int langIdx = 0;
114 do
115 {
116 famLang = getFcString( aPat, FC_FAMILYLANG, langIdx );
117
118 if( famLang.empty() && langIdx != 0 )
119 break;
120 else
121 {
122 fam = getFcString( aPat, FC_FAMILY, langIdx );
123 aFamStringMap.insert_or_assign( famLang, fam );
124 }
125 } while( langIdx++ < std::numeric_limits<
126 int8_t>::max() ); //arbitrary to avoid getting stuck for any reason
127}
128
129
130std::string FONTCONFIG::getFamilyStringByLang( FONTCONFIG_PAT& aPat, const wxString& aDesiredLang )
131{
132 std::unordered_map<std::string, std::string> famStrings;
133 getAllFamilyStrings( aPat, famStrings );
134
135 if( famStrings.empty() )
136 return "";
137
138 for( auto const& [key, val] : famStrings )
139 {
140 if( isLanguageMatch( aDesiredLang, From_UTF8( key.c_str() ) ) )
141 {
142 return val;
143 }
144 }
145
146 // fall back to the first and maybe only available name
147 // most fonts by review don't even bother declaring more than one font family name
148 // and they don't even bother declare the language tag either, they just leave it blank
149 return famStrings.begin()->second;
150}
151
152
153FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString &aFontName, wxString &aFontFile,
154 int& aFaceIndex, bool aBold, bool aItalic )
155{
157 wxString qualifiedFontName = aFontName;
158
159 wxScopedCharBuffer const fcBuffer = qualifiedFontName.ToUTF8();
160
161 FcPattern* pat = FcPatternCreate();
162
163 if( aBold )
164 FcPatternAddString( pat, FC_STYLE, (const FcChar8*) "Bold" );
165
166 if( aItalic )
167 FcPatternAddString( pat, FC_STYLE, (const FcChar8*) "Italic" );
168
169 FcPatternAddString( pat, FC_FAMILY, (FcChar8*) fcBuffer.data() );
170
171 FcConfigSubstitute( nullptr, pat, FcMatchPattern );
172 FcDefaultSubstitute( pat );
173
174 FcResult r = FcResultNoMatch;
175 FcPattern* font = FcFontMatch( nullptr, pat, &r );
176
177 wxString fontName;
178
179 if( font )
180 {
181 FcChar8* file = nullptr;
182
183 if( FcPatternGetString( font, FC_FILE, 0, &file ) == FcResultMatch )
184 {
185 aFontFile = wxString::FromUTF8( (char*) file );
186 aFaceIndex = 0;
187
188 wxString styleStr;
189 FcChar8* family = nullptr;
190 FcChar8* style = nullptr;
191
193
194 std::unordered_map<std::string, std::string> famStrings;
195 FONTCONFIG_PAT patHolder{ font };
196
197 getAllFamilyStrings( patHolder, famStrings );
198
199 if( FcPatternGetString( font, FC_FAMILY, 0, &family ) == FcResultMatch )
200 {
201 FcPatternGetInteger( font, FC_INDEX, 0, &aFaceIndex );
202
203 fontName = wxString::FromUTF8( (char*) family );
204
205 if( FcPatternGetString( font, FC_STYLE, 0, &style ) == FcResultMatch )
206 {
207 styleStr = wxString::FromUTF8( (char*) style );
208
209 if( !styleStr.IsEmpty() )
210 {
211 styleStr.Replace( wxS( " " ), wxS( ":" ) );
212 fontName += ":" + styleStr;
213 }
214 }
215
216 bool has_bold = false;
217 bool has_ital = false;
218 wxString lower_style = styleStr.Lower();
219
220 if( lower_style.Contains( wxS( "bold" ) )
221 || lower_style.Contains( wxS( "black" ) )
222 || lower_style.Contains( wxS( "thick" ) )
223 || lower_style.Contains( wxS( "dark" ) ) )
224 {
225 has_bold = true;
226 }
227
228 if( lower_style.Contains( wxS( "italic" ) )
229 || lower_style.Contains( wxS( "oblique" ) )
230 || lower_style.Contains( wxS( "slant" ) ) )
231 {
232 has_ital = true;
233 }
234
235 for( auto const& [key, val] : famStrings )
236 {
237 wxString searchFont;
238 searchFont = wxString::FromUTF8( (char*) val.data() );
239
240 if( searchFont.Lower().StartsWith( aFontName.Lower() ) )
241 {
242 if( ( aBold && !has_bold ) && ( aItalic && !has_ital ) )
244 else if( aBold && !has_bold )
246 else if( aItalic && !has_ital )
248 else if( ( aBold != has_bold ) || ( aItalic != has_ital ) )
250 else
251 retval = FF_RESULT::FF_OK;
252
253 break;
254 }
255 }
256 }
257 }
258
259 FcPatternDestroy( font );
260 }
261
262 if( retval == FF_RESULT::FF_ERROR )
263 wxLogWarning( _( "Error loading font '%s'." ), qualifiedFontName );
264 else if( retval == FF_RESULT::FF_SUBSTITUTE )
265 wxLogWarning( _( "Font '%s' not found; substituting '%s'." ), qualifiedFontName, fontName );
266
267 FcPatternDestroy( pat );
268 return retval;
269}
270
271
272void FONTCONFIG::ListFonts( std::vector<std::string>& aFonts, const std::string& aDesiredLang )
273{
274 // be sure to cache bust if the language changed
275 if( m_fontInfoCache.empty() || m_fontCacheLastLang != aDesiredLang )
276 {
277 FcPattern* pat = FcPatternCreate();
278 FcObjectSet* os = FcObjectSetBuild( FC_FAMILY, FC_FAMILYLANG, FC_STYLE, FC_LANG, FC_FILE,
279 FC_OUTLINE, nullptr );
280 FcFontSet* fs = FcFontList( nullptr, pat, os );
281
282 for( int i = 0; fs && i < fs->nfont; ++i )
283 {
284 FcPattern* font = fs->fonts[i];
285 FcChar8* file;
286 FcChar8* style;
287 FcLangSet* langSet;
288 FcBool outline;
289
290 if( FcPatternGetString( font, FC_FILE, 0, &file ) == FcResultMatch
291 && FcPatternGetString( font, FC_STYLE, 0, &style ) == FcResultMatch
292 && FcPatternGetLangSet( font, FC_LANG, 0, &langSet ) == FcResultMatch
293 && FcPatternGetBool( font, FC_OUTLINE, 0, &outline ) == FcResultMatch )
294 {
295 if( !outline )
296 continue;
297
298 FONTCONFIG_PAT patHolder{ font };
299 std::string theFamily =
300 getFamilyStringByLang( patHolder, From_UTF8( aDesiredLang.c_str() ) );
301
302#ifdef __WXMAC__
303 // On Mac (at least) some of the font names are in their own language. If
304 // the OS doesn't support this language then we get a bunch of garbage names
305 // in the font menu.
306 //
307 // GTK, on the other hand, doesn't appear to support wxLocale::IsAvailable(),
308 // so we can't run these checks.
309
310 FcStrSet* langStrSet = FcLangSetGetLangs( langSet );
311 FcStrList* langStrList = FcStrListCreate( langStrSet );
312 FcChar8* langStr = FcStrListNext( langStrList );
313 bool langSupported = false;
314
315 if( !langStr )
316 {
317 // Symbol fonts (Wingdings, etc.) have no language
318 langSupported = true;
319 }
320 else while( langStr )
321 {
322 wxString langWxStr( reinterpret_cast<char *>( langStr ) );
323 const wxLanguageInfo* langInfo = wxLocale::FindLanguageInfo( langWxStr );
324
325 if( langInfo && wxLocale::IsAvailable( langInfo->Language ) )
326 {
327 langSupported = true;
328 break;
329 }
330 else
331 {
332 wxLogTrace( traceFonts, wxS( "Font '%s' language '%s' not supported by OS." ),
333 theFamily, langWxStr );
334 }
335
336 langStr = FcStrListNext( langStrList );
337 }
338
339 FcStrListDone( langStrList );
340 FcStrSetDestroy( langStrSet );
341
342 if( !langSupported )
343 continue;
344#endif
345
346 std::string theFile( reinterpret_cast<char *>( file ) );
347 std::string theStyle( reinterpret_cast<char *>( style ) );
348 FONTINFO fontInfo( theFile, theStyle, theFamily );
349
350 if( theFamily.length() > 0 && theFamily.front() == '.' )
351 continue;
352
353 std::map<std::string, FONTINFO>::iterator it = m_fontInfoCache.find( theFamily );
354
355 if( it == m_fontInfoCache.end() )
356 m_fontInfoCache.emplace( theFamily, fontInfo );
357 else
358 it->second.Children().push_back( fontInfo );
359 }
360 }
361
362 if( fs )
363 FcFontSetDestroy( fs );
364
365 m_fontCacheLastLang = aDesiredLang;
366 }
367
368 for( const std::pair<const std::string, FONTINFO>& entry : m_fontInfoCache )
369 aFonts.push_back( entry.second.Family() );
370}
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:107
static wxString Version()
Definition: fontconfig.cpp:42
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:153
void ListFonts(std::vector< std::string > &aFonts, const std::string &aDesiredLang)
List the current available font families.
Definition: fontconfig.cpp:272
std::string getFcString(FONTCONFIG_PAT &aPat, const char *aObj, int aIdx)
Wrapper of FcPatternGetString to return a std::string.
Definition: fontconfig.cpp:95
std::string getFamilyStringByLang(FONTCONFIG_PAT &APat, const wxString &aDesiredLang)
Gets a family name based on desired language.
Definition: fontconfig.cpp:130
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:65
wxString m_fontCacheLastLang
Definition: fontconfig.h:72
#define _(s)
static FONTCONFIG * g_config
Definition: fontconfig.cpp:31
FONTCONFIG * Fontconfig()
Definition: fontconfig.cpp:53
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:37
VECTOR3I res
wxLogTrace helper definitions.