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