From 39e69a3d2918b6b2392d797f646adf9616a0eb6f Mon Sep 17 00:00:00 2001 From: Marek Roszko Date: Tue, 21 Feb 2023 20:26:30 -0500 Subject: [PATCH] Fix up handling of font names available in multiple names ttf fonts can declare multiple language names. Many by default opt to just declare one and not tag it any particular language. However, there are CJK languages that typically leverage this function the most. They'll tag both a "en" and a cjk lang family name in CJK characters. To be as user friendly as possible, we need to display said fonts in the CJK languages if KiCad to set to such a locale. Fixes https://gitlab.com/kicad/code/kicad/-/issues/14011 --- common/font/fontconfig.cpp | 173 +++++++++++++++++++++++++++------ common/pgm_base.cpp | 16 +++ common/widgets/font_choice.cpp | 3 +- eeschema/fields_grid_table.cpp | 3 +- include/font/fontconfig.h | 51 +++++++++- include/pgm_base.h | 5 + 6 files changed, 216 insertions(+), 35 deletions(-) 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(); /**