diff --git a/common/font/fontconfig.cpp b/common/font/fontconfig.cpp index 8f0a1fe69b..0b6efd60ac 100644 --- a/common/font/fontconfig.cpp +++ b/common/font/fontconfig.cpp @@ -18,23 +18,25 @@ * with this program. If not, see . */ -#include #include #include #include #include +#include +#include using namespace fontconfig; static FONTCONFIG* g_config = nullptr; - -inline static FcChar8* wxStringToFcChar8( const wxString& str ) +/** + * A simple wrapper to avoid exporing fontconfig in the header + */ +struct fontconfig::FONTCONFIG_PAT { - wxScopedCharBuffer const fcBuffer = str.ToUTF8(); - return (FcChar8*) fcBuffer.data(); -} + FcPattern* pat; +}; wxString FONTCONFIG::Version() @@ -61,19 +63,112 @@ FONTCONFIG* Fontconfig() } +bool FONTCONFIG::isLanguageMatch( const wxString& aSearchLang, const wxString& aSupportedLang ) +{ + if( aSearchLang.Lower() == aSupportedLang.Lower() ) + return true; + + if( aSupportedLang.empty() ) + return false; + + if( aSearchLang.empty() ) + return false; + + wxArrayString supportedLangBits; + wxStringSplit( aSupportedLang.Lower(), supportedLangBits, wxS( '-' ) ); + + wxArrayString searhcLangBits; + wxStringSplit( aSearchLang.Lower(), searhcLangBits, wxS( '-' ) ); + + // if either side of the comparison have only one section, then its a broad match but fine + // i.e. the haystack is declaring broad support or the search language is broad + if( searhcLangBits.size() == 1 || supportedLangBits.size() == 1 ) + { + return searhcLangBits[0] == supportedLangBits[0]; + } + + // the full two part comparison should have passed the initial shortcut + + return false; +} + + +std::string FONTCONFIG::getFcString( FONTCONFIG_PAT& aPat, const char* aObj, int aIdx ) +{ + FcChar8* str; + std::string res; + + if( FcPatternGetString( aPat.pat, aObj, aIdx, &str ) == FcResultMatch ) + res = std::string( reinterpret_cast( str ) ); + + return res; +} + + +void FONTCONFIG::getAllFamilyStrings( FONTCONFIG_PAT& aPat, + std::unordered_map& aFamStringMap ) +{ + std::string famLang; + std::string fam; + + int langIdx = 0; + do + { + famLang = getFcString( aPat, FC_FAMILYLANG, langIdx ); + + if( famLang.empty() && langIdx != 0 ) + break; + else + { + fam = getFcString( aPat, FC_FAMILY, langIdx ); + aFamStringMap.insert_or_assign( famLang, fam ); + } + } while( langIdx++ < std::numeric_limits< + int8_t>::max() ); //arbitrary to avoid getting stuck for any reason +} + + +std::string FONTCONFIG::getFamilyStringByLang( FONTCONFIG_PAT& aPat, const wxString& aDesiredLang ) +{ + std::unordered_map famStrings; + getAllFamilyStrings( aPat, famStrings ); + + if( famStrings.empty() ) + return ""; + + for( auto const& [key, val] : famStrings ) + { + if( isLanguageMatch( aDesiredLang, FROM_UTF8( key.c_str() ) ) ) + { + return val; + } + } + + // fall back to the first and maybe only available name + // most fonts by review don't even bother declaring more than one font family name + // and they don't even bother declare the language tag either, they just leave it blank + return famStrings.begin()->second; +} + + FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString &aFontName, wxString &aFontFile, bool aBold, bool aItalic ) { FF_RESULT retval = FF_RESULT::FF_ERROR; wxString qualifiedFontName = aFontName; + wxScopedCharBuffer const fcBuffer = qualifiedFontName.ToUTF8(); + + FcPattern* pat = FcPatternCreate(); + if( aBold ) - qualifiedFontName << wxS( ":Bold" ); + FcPatternAddString( pat, FC_STYLE, (const FcChar8*) "Bold" ); if( aItalic ) - qualifiedFontName << wxS( ":Italic" ); + FcPatternAddString( pat, FC_STYLE, (const FcChar8*) "Italic" ); + + FcPatternAddString( pat, FC_FAMILY, (FcChar8*) fcBuffer.data() ); - FcPattern* pat = FcNameParse( wxStringToFcChar8( qualifiedFontName ) ); FcConfigSubstitute( nullptr, pat, FcMatchPattern ); FcDefaultSubstitute( pat ); @@ -95,6 +190,9 @@ FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString &aFontName, wxString retval = FF_RESULT::FF_SUBSTITUTE; + std::unordered_map famStrings; + FONTCONFIG_PAT patHolder{ font }; + getAllFamilyStrings( patHolder, famStrings ); if( FcPatternGetString( font, FC_FAMILY, 0, &family ) == FcResultMatch ) { fontName = wxString::FromUTF8( (char*) family ); @@ -129,19 +227,29 @@ FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString &aFontName, wxString has_ital = true; } - if( fontName.Lower().StartsWith( aFontName.Lower() ) ) + for( auto const& [key, val] : famStrings ) { - if( ( aBold && !has_bold ) && ( aItalic && !has_ital ) ) - retval = FF_RESULT::FF_MISSING_BOLD_ITAL; - else if( aBold && !has_bold ) - retval = FF_RESULT::FF_MISSING_BOLD; - else if( aItalic && !has_ital ) - retval = FF_RESULT::FF_MISSING_ITAL; - else if( ( aBold != has_bold ) || ( aItalic != has_ital ) ) - retval = FF_RESULT::FF_SUBSTITUTE; - else - retval = FF_RESULT::FF_OK; + wxString searchFont; + searchFont = wxString::FromUTF8( (char*) val.data() ); + + if( searchFont.Lower().StartsWith( aFontName.Lower() ) ) + { + if( ( aBold && !has_bold ) && ( aItalic && !has_ital ) ) + retval = FF_RESULT::FF_MISSING_BOLD_ITAL; + else if( aBold && !has_bold ) + retval = FF_RESULT::FF_MISSING_BOLD; + else if( aItalic && !has_ital ) + retval = FF_RESULT::FF_MISSING_ITAL; + else if( ( aBold != has_bold ) || ( aItalic != has_ital ) ) + retval = FF_RESULT::FF_SUBSTITUTE; + else + retval = FF_RESULT::FF_OK; + + break; + } } + + } } @@ -158,13 +266,14 @@ FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString &aFontName, wxString } -void FONTCONFIG::ListFonts( std::vector& aFonts ) +void FONTCONFIG::ListFonts( std::vector& aFonts, const std::string& aDesiredLang ) { - if( m_fonts.empty() ) + // be sure to cache bust if the language changed + if( m_fontInfoCache.empty() || m_fontCacheLastLang != aDesiredLang ) { FcPattern* pat = FcPatternCreate(); - FcObjectSet* os = FcObjectSetBuild( FC_FAMILY, FC_STYLE, FC_LANG, FC_FILE, FC_OUTLINE, - nullptr ); + FcObjectSet* os = FcObjectSetBuild( FC_FAMILY, FC_FAMILYLANG, FC_STYLE, FC_LANG, FC_FILE, + FC_OUTLINE, nullptr ); FcFontSet* fs = FcFontList( nullptr, pat, os ); for( int i = 0; fs && i < fs->nfont; ++i ) @@ -172,12 +281,10 @@ void FONTCONFIG::ListFonts( std::vector& aFonts ) FcPattern* font = fs->fonts[i]; FcChar8* file; FcChar8* style; - FcChar8* family; FcLangSet* langSet; FcBool outline; if( FcPatternGetString( font, FC_FILE, 0, &file ) == FcResultMatch - && FcPatternGetString( font, FC_FAMILY, 0, &family ) == FcResultMatch && FcPatternGetString( font, FC_STYLE, 0, &style ) == FcResultMatch && FcPatternGetLangSet( font, FC_LANG, 0, &langSet ) == FcResultMatch && FcPatternGetBool( font, FC_OUTLINE, 0, &outline ) == FcResultMatch ) @@ -185,7 +292,9 @@ void FONTCONFIG::ListFonts( std::vector& aFonts ) if( !outline ) continue; - std::string theFamily( reinterpret_cast( family ) ); + FONTCONFIG_PAT patHolder{ font }; + std::string theFamily = + getFamilyStringByLang( patHolder, FROM_UTF8( aDesiredLang.c_str() ) ); #ifdef __WXMAC__ // On Mac (at least) some of the font names are in their own language. If @@ -238,10 +347,10 @@ void FONTCONFIG::ListFonts( std::vector& aFonts ) if( theFamily.length() > 0 && theFamily.front() == '.' ) continue; - std::map::iterator it = m_fonts.find( theFamily ); + std::map::iterator it = m_fontInfoCache.find( theFamily ); - if( it == m_fonts.end() ) - m_fonts.emplace( theFamily, fontInfo ); + if( it == m_fontInfoCache.end() ) + m_fontInfoCache.emplace( theFamily, fontInfo ); else it->second.Children().push_back( fontInfo ); } @@ -249,8 +358,10 @@ void FONTCONFIG::ListFonts( std::vector& aFonts ) if( fs ) FcFontSetDestroy( fs ); + + m_fontCacheLastLang = aDesiredLang; } - for( const std::pair& entry : m_fonts ) + for( const std::pair& entry : m_fontInfoCache ) aFonts.push_back( entry.second.Family() ); } diff --git a/common/pgm_base.cpp b/common/pgm_base.cpp index f44950af1a..0e74b023f7 100644 --- a/common/pgm_base.cpp +++ b/common/pgm_base.cpp @@ -775,6 +775,22 @@ void PGM_BASE::SetLanguageIdentifier( int menu_id ) } +wxString PGM_BASE::GetLanguageTag() +{ + const wxLanguageInfo* langInfo = wxLocale::GetLanguageInfo( m_language_id ); + + if( !langInfo ) + return ""; + else + { + wxString str = langInfo->GetCanonicalWithRegion(); + str.Replace( "_", "-" ); + + return str; + } +} + + void PGM_BASE::SetLanguagePath() { wxLocale::AddCatalogLookupPathPrefix( PATHS::GetLocaleDataPath() ); diff --git a/common/widgets/font_choice.cpp b/common/widgets/font_choice.cpp index d34146aed7..42db88c345 100644 --- a/common/widgets/font_choice.cpp +++ b/common/widgets/font_choice.cpp @@ -20,6 +20,7 @@ #include #include #include +#include // The "official" name of the building Kicad stroke font (always existing) #include @@ -32,7 +33,7 @@ FONT_CHOICE::FONT_CHOICE( wxWindow* aParent, int aId, wxPoint aPosition, wxSize m_systemFontCount = wxChoice::GetCount(); std::vector fontNames; - Fontconfig()->ListFonts( fontNames ); + Fontconfig()->ListFonts( fontNames, std::string( Pgm().GetLanguageTag().utf8_str() ) ); wxArrayString menuList; diff --git a/eeschema/fields_grid_table.cpp b/eeschema/fields_grid_table.cpp index c0e2d156db..d557403dc0 100644 --- a/eeschema/fields_grid_table.cpp +++ b/eeschema/fields_grid_table.cpp @@ -46,6 +46,7 @@ #include #include #include +#include enum @@ -275,7 +276,7 @@ void FIELDS_GRID_TABLE::initGrid( WX_GRID* aGrid ) wxArrayString fonts; std::vector fontNames; - Fontconfig()->ListFonts( fontNames ); + Fontconfig()->ListFonts( fontNames, std::string( Pgm().GetLanguageTag().utf8_str() ) ); for( const std::string& name : fontNames ) fonts.Add( wxString( name ) ); diff --git a/include/font/fontconfig.h b/include/font/fontconfig.h index 4018299c1d..0e690b2099 100644 --- a/include/font/fontconfig.h +++ b/include/font/fontconfig.h @@ -21,6 +21,8 @@ #ifndef KICAD_FONTCONFIG_H #define KICAD_FONTCONFIG_H +#include + #include #include #include @@ -29,6 +31,8 @@ namespace fontconfig { +struct FONTCONFIG_PAT; + class FONTCONFIG { public: @@ -56,11 +60,54 @@ public: /** * List the current available font families. + * + * @param aDesiredLang The desired language of font name to report back if available, otherwise it will fallback */ - void ListFonts( std::vector& aFonts ); + void ListFonts( std::vector& aFonts, const std::string& aDesiredLang ); private: - std::map m_fonts; + std::map m_fontInfoCache; + wxString m_fontCacheLastLang; + + /** + * Matches the two rfc 3306 language entries, used for when searching for matching family names + * + * The overall logic is simple, either both language tags matched exactly or one tag is "single" level + * that the other language tag contains. + * There's nuances to language tags beyond this but font tags will most likely never be more complex than + * say "zh-CN" or single tag "en". + * + * @param aSearchLang the language being searched for + * @param aSupportedLang the language being offered + */ + bool isLanguageMatch( const wxString& aSearchLang, const wxString& aSupportedLang ); + + /** + * Gets a list of all family name strings maped to lang + * + * @param aPat reference to FcPattern container + * @param aFamStringMap Map to be populated with key, value pairs representing lang to family name + */ + void getAllFamilyStrings( FONTCONFIG_PAT& aPat, + std::unordered_map& aFamStringMap ); + + /** + * Gets a family name based on desired language. + * This will fallback to english or first available string if no language matching string is found. + * + * @param aPat reference to FcPattern container + * @param aDesiredLang Language to research for (RFC3066 format) + */ + std::string getFamilyStringByLang( FONTCONFIG_PAT& APat, const wxString& aDesiredLang ); + + /** + * Wrapper of FcPatternGetString to return a std::string + * + * @param aPat reference to FcPattern container + * @param aObj The fontconfig property object like FC_FAMILY, FC_STYLE, etc + * @param aIdx The ith value associated with the property object + */ + std::string getFcString( FONTCONFIG_PAT& aPat, const char* aObj, int aIdx ); }; } // namespace fontconfig diff --git a/include/pgm_base.h b/include/pgm_base.h index 80d013cad9..7ef1d401af 100644 --- a/include/pgm_base.h +++ b/include/pgm_base.h @@ -220,6 +220,11 @@ public: */ virtual int GetSelectedLanguageIdentifier() const { return m_language_id; } + /** + * @return the current selected language in rfc3066 format + */ + virtual wxString GetLanguageTag(); + virtual void SetLanguagePath(); /**