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


(cherry picked from commit 39e69a3d29)
This commit is contained in:
Marek Roszko 2023-02-21 20:26:30 -05:00 committed by Mark Roszko
parent 14381ac68a
commit fd4a0dbedf
6 changed files with 216 additions and 35 deletions

View File

@ -18,23 +18,25 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <fontconfig/fontconfig.h>
#include <font/fontconfig.h>
#include <pgm_base.h>
#include <wx/log.h>
#include <trace_helpers.h>
#include <string_utils.h>
#include <macros.h>
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<char*>( str ) );
return res;
}
void FONTCONFIG::getAllFamilyStrings( FONTCONFIG_PAT& aPat,
std::unordered_map<std::string, std::string>& 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<std::string, std::string> 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<std::string, std::string> famStrings;
FONTCONFIG_PAT patHolder{ font };
getAllFamilyStrings( patHolder, famStrings );
if( FcPatternGetString( font, FC_FAMILY, 0, &family ) == FcResultMatch )
{
fontName = wxString::FromUTF8( (char*) family );
@ -129,7 +227,12 @@ FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString &aFontName, wxString
has_ital = true;
}
if( fontName.Lower().StartsWith( aFontName.Lower() ) )
for( auto const& [key, val] : famStrings )
{
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;
@ -141,8 +244,13 @@ FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString &aFontName, wxString
retval = FF_RESULT::FF_SUBSTITUTE;
else
retval = FF_RESULT::FF_OK;
break;
}
}
}
}
FcPatternDestroy( font );
@ -158,13 +266,14 @@ FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString &aFontName, wxString
}
void FONTCONFIG::ListFonts( std::vector<std::string>& aFonts )
void FONTCONFIG::ListFonts( std::vector<std::string>& 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<std::string>& 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<std::string>& aFonts )
if( !outline )
continue;
std::string theFamily( reinterpret_cast<char *>( 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<std::string>& aFonts )
if( theFamily.length() > 0 && theFamily.front() == '.' )
continue;
std::map<std::string, FONTINFO>::iterator it = m_fonts.find( theFamily );
std::map<std::string, FONTINFO>::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<std::string>& aFonts )
if( fs )
FcFontSetDestroy( fs );
m_fontCacheLastLang = aDesiredLang;
}
for( const std::pair<const std::string, FONTINFO>& entry : m_fonts )
for( const std::pair<const std::string, FONTINFO>& entry : m_fontInfoCache )
aFonts.push_back( entry.second.Family() );
}

View File

@ -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() );

View File

@ -20,6 +20,7 @@
#include <widgets/font_choice.h>
#include <wx/fontenum.h>
#include <font/fontconfig.h>
#include <pgm_base.h>
// The "official" name of the building Kicad stroke font (always existing)
#include <font/kicad_font_name.h>
@ -32,7 +33,7 @@ FONT_CHOICE::FONT_CHOICE( wxWindow* aParent, int aId, wxPoint aPosition, wxSize
m_systemFontCount = wxChoice::GetCount();
std::vector<std::string> fontNames;
Fontconfig()->ListFonts( fontNames );
Fontconfig()->ListFonts( fontNames, std::string( Pgm().GetLanguageTag().utf8_str() ) );
wxArrayString menuList;

View File

@ -46,6 +46,7 @@
#include <wx/settings.h>
#include <string_utils.h>
#include <widgets/grid_combobox.h>
#include <pgm_base.h>
enum
@ -275,7 +276,7 @@ void FIELDS_GRID_TABLE<T>::initGrid( WX_GRID* aGrid )
wxArrayString fonts;
std::vector<std::string> fontNames;
Fontconfig()->ListFonts( fontNames );
Fontconfig()->ListFonts( fontNames, std::string( Pgm().GetLanguageTag().utf8_str() ) );
for( const std::string& name : fontNames )
fonts.Add( wxString( name ) );

View File

@ -21,6 +21,8 @@
#ifndef KICAD_FONTCONFIG_H
#define KICAD_FONTCONFIG_H
#include <fontconfig/fontconfig.h>
#include <wx/string.h>
#include <vector>
#include <map>
@ -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<std::string>& aFonts );
void ListFonts( std::vector<std::string>& aFonts, const std::string& aDesiredLang );
private:
std::map<std::string, FONTINFO> m_fonts;
std::map<std::string, FONTINFO> 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<std::string, std::string>& 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

View File

@ -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();
/**