From cf52bfcc5515ecb5506a4e31ba6b92e6d75f48bc Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Wed, 25 Jan 2023 13:10:31 -0800 Subject: [PATCH] Handle missing Bold/Ital outline fonts If the font face doesn't include a Bold or Italic version, we still want to display the font as bold/italic, so we fake it with freetype. This also prevents recurring error messages where the outline font warns about "substitutes" within the same font family. Also allows variants on the weight descriptor to be used without throwing a substitution warning Fixes https://gitlab.com/kicad/code/kicad/issues/13654 --- common/font/fontconfig.cpp | 70 ++++++++++++++++++++++++++---------- common/font/outline_font.cpp | 35 ++++++++++++++---- include/font/fontconfig.h | 12 ++++++- include/font/outline_font.h | 16 +++++++-- 4 files changed, 105 insertions(+), 28 deletions(-) diff --git a/common/font/fontconfig.cpp b/common/font/fontconfig.cpp index ecd84bcd75..0f01352d53 100644 --- a/common/font/fontconfig.cpp +++ b/common/font/fontconfig.cpp @@ -61,9 +61,19 @@ FONTCONFIG* Fontconfig() } -bool FONTCONFIG::FindFont( const wxString& aFontName, wxString& aFontFile ) +FONTCONFIG::FF_RESULT FONTCONFIG::FindFont( const wxString &aFontName, wxString &aFontFile, + bool aBold, bool aItalic ) { - FcPattern* pat = FcNameParse( wxStringToFcChar8( aFontName ) ); + FF_RESULT retval = FF_RESULT::ERROR; + wxString qualifiedFontName = aFontName; + + if( aBold ) + qualifiedFontName << wxS( ":Bold" ); + + if( aItalic ) + qualifiedFontName << wxS( ":Italic" ); + + FcPattern* pat = FcNameParse( wxStringToFcChar8( qualifiedFontName ) ); FcConfigSubstitute( nullptr, pat, FcMatchPattern ); FcDefaultSubstitute( pat ); @@ -71,8 +81,6 @@ bool FONTCONFIG::FindFont( const wxString& aFontName, wxString& aFontFile ) FcPattern* font = FcFontMatch( nullptr, pat, &r ); wxString fontName; - bool ok = false; - bool substituted = false; if( font ) { @@ -81,17 +89,19 @@ bool FONTCONFIG::FindFont( const wxString& aFontName, wxString& aFontFile ) if( FcPatternGetString( font, FC_FILE, 0, &file ) == FcResultMatch ) { aFontFile = wxString::FromUTF8( (char*) file ); - + wxString styleStr; FcChar8* family = nullptr; FcChar8* style = nullptr; + retval = FF_RESULT::SUBSTITUTE; + if( FcPatternGetString( font, FC_FAMILY, 0, &family ) == FcResultMatch ) { fontName = wxString::FromUTF8( (char*) family ); if( FcPatternGetString( font, FC_STYLE, 0, &style ) == FcResultMatch ) { - wxString styleStr = wxString::FromUTF8( (char*) style ); + styleStr = wxString::FromUTF8( (char*) style ); if( !styleStr.IsEmpty() ) { @@ -100,27 +110,51 @@ bool FONTCONFIG::FindFont( const wxString& aFontName, wxString& aFontFile ) } } - // TODO: report Regular vs Book, Italic vs Oblique, etc. or filter them out? + bool has_bold = false; + bool has_ital = false; + wxString lower_style = styleStr.Lower(); - if( aFontName.Contains( ":" ) ) - substituted = aFontName.CmpNoCase( fontName ) != 0; - else - substituted = !fontName.StartsWith( aFontName ); + if( lower_style.Contains( wxS( "bold" ) ) + || lower_style.Contains( wxS( "black" ) ) + || lower_style.Contains( wxS( "thick" ) ) + || lower_style.Contains( wxS( "dark" ) ) ) + { + has_bold = true; + } + + if( lower_style.Contains( wxS( "italic" ) ) + || lower_style.Contains( wxS( "oblique" ) ) + || lower_style.Contains( wxS( "slant" ) ) ) + { + has_ital = true; + } + + if( fontName.Lower().StartsWith( aFontName.Lower() ) ) + { + if( ( aBold && !has_bold ) && ( aItalic && !has_ital ) ) + retval = FF_RESULT::MISSING_BOLD_ITAL; + else if( aBold && !has_bold ) + retval = FF_RESULT::MISSING_BOLD; + else if( aItalic && !has_ital ) + retval = FF_RESULT::MISSING_ITAL; + else if( ( aBold != has_bold ) || ( aItalic != has_ital ) ) + retval = FF_RESULT::SUBSTITUTE; + else + retval = FF_RESULT::OK; + } } - - ok = true; } FcPatternDestroy( font ); } - if( !ok ) - wxLogWarning( _( "Error loading font '%s'." ), aFontName ); - else if( substituted ) - wxLogWarning( _( "Font '%s' not found; substituting '%s'." ), aFontName, fontName ); + if( retval == FF_RESULT::ERROR ) + wxLogWarning( _( "Error loading font '%s'." ), qualifiedFontName ); + else if( retval == FF_RESULT::SUBSTITUTE ) + wxLogWarning( _( "Font '%s' not found; substituting '%s'." ), qualifiedFontName, fontName ); FcPatternDestroy( pat ); - return ok; + return retval; } diff --git a/common/font/outline_font.cpp b/common/font/outline_font.cpp index b8db7faa1d..45fdc3e4d1 100644 --- a/common/font/outline_font.cpp +++ b/common/font/outline_font.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include FT_GLYPH_H #include FT_BBOX_H @@ -46,7 +47,9 @@ FT_Library OUTLINE_FONT::m_freeType = nullptr; OUTLINE_FONT::OUTLINE_FONT() : m_face(NULL), - m_faceSize( 16 ) + m_faceSize( 16 ), + m_fakeBold( false ), + m_fakeItal( false ) { if( !m_freeType ) FT_Init_FreeType( &m_freeType ); @@ -90,15 +93,17 @@ OUTLINE_FONT* OUTLINE_FONT::LoadFont( const wxString& aFontName, bool aBold, boo OUTLINE_FONT* font = new OUTLINE_FONT(); wxString fontFile; - wxString qualifiedFontName = aFontName; + using fc = fontconfig::FONTCONFIG; - if( aBold ) - qualifiedFontName << wxS( ":Bold" ); + fc::FF_RESULT retval = Fontconfig()->FindFont( aFontName, fontFile, aBold, aItalic ); - if( aItalic ) - qualifiedFontName << wxS( ":Italic" ); + if( retval == fc::FF_RESULT::MISSING_BOLD || retval == fc::FF_RESULT::MISSING_BOLD_ITAL ) + font->SetFakeBold(); - if( Fontconfig()->FindFont( qualifiedFontName, fontFile ) ) + if( retval == fc::FF_RESULT::MISSING_ITAL || retval == fc::FF_RESULT::MISSING_BOLD_ITAL ) + font->SetFakeItal(); + + if( retval != fc::FF_RESULT::ERROR ) (void) font->loadFace( fontFile ); font->m_fontName = aFontName; // Keep asked-for name, even if we substituted. @@ -357,8 +362,24 @@ VECTOR2I OUTLINE_FONT::getTextAsGlyphs( BOX2I* aBBox, std::vectorglyph->outline, 1 << 6 ); + // contours is a collection of all outlines in the glyph; for example the 'o' glyph // generally contains 2 contours, one for the glyph outline and one for the hole CONTOURS contours; diff --git a/include/font/fontconfig.h b/include/font/fontconfig.h index 26954155be..19c60eb8ba 100644 --- a/include/font/fontconfig.h +++ b/include/font/fontconfig.h @@ -36,13 +36,23 @@ public: static wxString Version(); + enum class FF_RESULT + { + OK, + ERROR, + SUBSTITUTE, + MISSING_BOLD, + MISSING_ITAL, + MISSING_BOLD_ITAL + }; + /** * Given a fully-qualified font name ("Times:Bold:Italic") find the closest matching font * and return its filepath in \a aFontFile. * * A return value of false indicates a serious error in the font system. */ - bool FindFont( const wxString& aFontName, wxString& aFontFile ); + FF_RESULT FindFont( const wxString& aFontName, wxString& aFontFile, bool aBold, bool aItalic ); /** * List the current available font families. diff --git a/include/font/outline_font.h b/include/font/outline_font.h index 5096d700e8..5c1e19902f 100644 --- a/include/font/outline_font.h +++ b/include/font/outline_font.h @@ -63,12 +63,22 @@ public: bool IsBold() const override { - return m_face && ( m_face->style_flags & FT_STYLE_FLAG_BOLD ); + return m_face && ( m_fakeBold || ( m_face->style_flags & FT_STYLE_FLAG_BOLD ) ); } bool IsItalic() const override { - return m_face && ( m_face->style_flags & FT_STYLE_FLAG_ITALIC ); + return m_face && ( m_fakeItal || ( m_face->style_flags & FT_STYLE_FLAG_ITALIC ) ); + } + + void SetFakeBold() + { + m_fakeBold = true; + } + + void SetFakeItal() + { + m_fakeItal = true; } /** @@ -128,6 +138,8 @@ private: static FT_Library m_freeType; FT_Face m_face; const int m_faceSize; + bool m_fakeBold; + bool m_fakeItal; // cache for glyphs converted to straight segments // key is glyph index (FT_GlyphSlot field glyph_index)