[vlc-devel] [PATCH] dwrite: fix family name lookup

Salah-Eddin Shaban salah at videolan.org
Sat Mar 24 10:11:53 CET 2018


Close #19662
---
 modules/text_renderer/freetype/fonts/dwrite.cpp | 309 +++++++++++++++++++++---
 1 file changed, 278 insertions(+), 31 deletions(-)

diff --git a/modules/text_renderer/freetype/fonts/dwrite.cpp b/modules/text_renderer/freetype/fonts/dwrite.cpp
index 018a0b38dd..4caff06d3c 100644
--- a/modules/text_renderer/freetype/fonts/dwrite.cpp
+++ b/modules/text_renderer/freetype/fonts/dwrite.cpp
@@ -33,6 +33,7 @@
 #include <wrl/client.h>
 #include <vector>
 #include <stdexcept>
+#include <regex>
 
 #include "../platform_fonts.h"
 
@@ -310,38 +311,235 @@ public:
     }
 };
 
-static void DWrite_ParseFamily( IDWriteFontFamily *p_dw_family, vlc_family_t *p_family, vector< FT_Stream > &streams )
+/*
+ * The following four functions are from
+ * https://msdn.microsoft.com/zh-cn/library/windows/desktop/dd941714.aspx
+ */
+template <class T> void SafeRelease( T **ppT )
 {
-    for( int i = 0; i < 4; ++i )
+    if( *ppT )
+    {
+        ( *ppT )->Release();
+        *ppT = NULL;
+    }
+}
+
+/*
+ * Select a name from a list of localized names and use a simple
+ * fallback scheme if the desired locale isn't supported.
+ */
+HRESULT GetLocalizedName( IDWriteLocalizedStrings* names, const wchar_t* locale, wstring& familyName )
+{
+    HRESULT hr = S_OK;
+
+    UINT32 nameIndex;
+    BOOL   nameExists;
+    UINT32 nameLength = 0;
+
+    try
+    {
+        hr = names->FindLocaleName( locale, &nameIndex, &nameExists );
+
+        // If there is no name with the desired locale fallback to US English.
+        if( SUCCEEDED( hr ) )
+        {
+            if( !nameExists )
+                hr = names->FindLocaleName( L"en-us", &nameIndex, &nameExists );
+
+            // If the name still doesn't exist just take the first one.
+            if( !nameExists )
+                nameIndex = 0;
+        }
+
+        if( SUCCEEDED( hr ) )
+        {
+            hr = names->GetStringLength( nameIndex, &nameLength );
+        }
+
+        if( SUCCEEDED( hr ) )
+        {
+            familyName.resize( nameLength + 1 );
+            hr = names->GetString( nameIndex, &familyName[ 0 ], ( UINT32 ) familyName.size() );
+        }
+    }
+    catch( ... )
+    {
+        hr = E_FAIL;
+    }
+
+    return hr;
+}
+
+
+/*
+ * Get the family name (e.g. "Arial" in "Arial Bold") for a font family
+ */
+HRESULT GetFontFamilyName( IDWriteFontFamily* fontFamily, const wchar_t* locale, wstring& familyName )
+{
+    HRESULT hr = S_OK;
+
+    IDWriteLocalizedStrings* familyNames = NULL;
+
+    hr = fontFamily->GetFamilyNames( &familyNames );
+
+    if( SUCCEEDED( hr ) )
+    {
+        hr = GetLocalizedName( familyNames, locale, familyName );
+    }
+
+    SafeRelease( &familyNames );
+
+    return hr;
+}
+
+/*
+ * Get the face name (e.g. "Bold" in "Arial Bold") for a font
+ */
+HRESULT GetFontFaceName( IDWriteFont* font, const wchar_t* locale, wstring& faceName )
+{
+    HRESULT hr = S_OK;
+
+    IDWriteLocalizedStrings* faceNames = NULL;
+
+    hr = font->GetFaceNames( &faceNames );
+
+    if( SUCCEEDED( hr ) )
+    {
+        hr = GetLocalizedName( faceNames, locale, faceName );
+    }
+
+    SafeRelease( &faceNames );
+
+    return hr;
+}
+
+/*
+ * Remove any extra null characters and escape any regex metacharacters
+ */
+static wstring SanitizeName( const wstring &name )
+{
+    const auto pattern = wregex{ L"[.^$|()\\[\\]{}*+?\\\\]" };
+    const auto replace = wstring{ L"\\\\&" };
+    auto result = regex_replace( wstring{ name.c_str() }, pattern, replace,
+                                 regex_constants::match_default | regex_constants::format_sed );
+    return result;
+}
+
+/*
+ * Check for a partial match e.g. between Roboto and Roboto Thin.
+ * In this case p_matched will be set to Roboto and p_unmatched to Thin.
+ */
+static bool DWrite_PartialMatch( const wstring &full_name, const wstring &partial_name,
+                                 wstring *p_matched = nullptr, wstring *p_unmatched = nullptr )
+{
+    auto pattern = wstring{ L"^(" } + SanitizeName( partial_name ) + wstring{ L")\\s*(.*)$" };
+    auto rx = wregex{ pattern, wregex::icase };
+
+    auto match = wsmatch{};
+
+    if( !regex_match( full_name, match, rx ) )
+        return false;
+
+    if( p_matched )
+        *p_matched = match[ 1 ].str();
+    if( p_unmatched )
+        *p_unmatched = match[ 2 ].str();
+    return true;
+}
+
+static vector< ComPtr< IDWriteFont > > DWrite_GetFonts( IDWriteFontFamily *p_dw_family, const wstring &face_name )
+{
+    vector< ComPtr< IDWriteFont > > result;
+    wchar_t buff_sys[ LOCALE_NAME_MAX_LENGTH ] = {};
+    wchar_t buff_usr[ LOCALE_NAME_MAX_LENGTH ] = {};
+
+    GetSystemDefaultLocaleName( buff_sys, LOCALE_NAME_MAX_LENGTH );
+    GetUserDefaultLocaleName( buff_usr, LOCALE_NAME_MAX_LENGTH );
+
+    if( !face_name.empty() )
+    {
+        UINT32 i_count = p_dw_family->GetFontCount();
+        for( UINT32 i = 0; i < i_count; ++i )
+        {
+            ComPtr< IDWriteFont > p_dw_font;
+            wstring name_en;
+            wstring name_sys;
+            wstring name_usr;
+
+            if( p_dw_family->GetFont( i, p_dw_font.GetAddressOf() )
+                || !p_dw_font )
+                throw runtime_error( "GetFont() failed" );
+
+            if( GetFontFaceName( p_dw_font.Get(), L"en-US", name_en )
+             || GetFontFaceName( p_dw_font.Get(), buff_sys, name_sys )
+             || GetFontFaceName( p_dw_font.Get(), buff_usr, name_usr ) )
+                throw runtime_error( "GetFontFaceName() failed" );
+
+            if( DWrite_PartialMatch( name_en, face_name ) ||
+                DWrite_PartialMatch( name_sys, face_name ) ||
+                DWrite_PartialMatch( name_usr, face_name ) )
+                result.push_back( p_dw_font );
+        }
+    }
+
+    if( face_name.empty() || result.empty() )
+    {
+        for( int i = 0; i < 4; ++i )
+        {
+            ComPtr< IDWriteFont > p_dw_font;
+            DWRITE_FONT_STYLE style;
+            DWRITE_FONT_WEIGHT weight;
+
+            switch( i )
+            {
+            case 0:
+                weight = DWRITE_FONT_WEIGHT_NORMAL; style = DWRITE_FONT_STYLE_NORMAL;
+                break;
+            case 1:
+                weight = DWRITE_FONT_WEIGHT_BOLD; style = DWRITE_FONT_STYLE_NORMAL;
+                break;
+            case 2:
+                weight = DWRITE_FONT_WEIGHT_NORMAL; style = DWRITE_FONT_STYLE_ITALIC;
+                break;
+            default:
+                weight = DWRITE_FONT_WEIGHT_BOLD; style = DWRITE_FONT_STYLE_ITALIC;
+                break;
+            }
+
+            if( p_dw_family->GetFirstMatchingFont( weight, DWRITE_FONT_STRETCH_NORMAL, style, p_dw_font.GetAddressOf() )
+                || !p_dw_font )
+                throw runtime_error( "GetFirstMatchingFont() failed" );
+
+            result.push_back( p_dw_font );
+        }
+    }
+
+    return result;
+}
+
+static void DWrite_ParseFamily( IDWriteFontFamily *p_dw_family, const wstring &face_name,
+                                vlc_family_t *p_family, vector< FT_Stream > &streams )
+{
+    vector< ComPtr< IDWriteFont > > fonts = DWrite_GetFonts( p_dw_family, face_name );
+    bool b_styles[ 2 ][ 2 ] = { false };
+
+    for( size_t i = 0; i < fonts.size(); ++i )
     {
         ComPtr< IDWriteFont >             p_dw_font;
         ComPtr< IDWriteFontFace >         p_dw_face;
         ComPtr< IDWriteFontFileLoader >   p_dw_loader;
         ComPtr< IDWriteFontFile >         p_dw_file;
 
-        DWRITE_FONT_STYLE style;
-        DWRITE_FONT_WEIGHT weight;
-        bool b_bold, b_italic;
+        p_dw_font = fonts[ i ];
 
-        switch( i )
-        {
-        case 0:
-            weight = DWRITE_FONT_WEIGHT_NORMAL; style = DWRITE_FONT_STYLE_NORMAL;
-            b_bold = false; b_italic = false; break;
-        case 1:
-            weight = DWRITE_FONT_WEIGHT_BOLD; style = DWRITE_FONT_STYLE_NORMAL;
-            b_bold = true; b_italic = false; break;
-        case 2:
-            weight = DWRITE_FONT_WEIGHT_NORMAL; style = DWRITE_FONT_STYLE_ITALIC;
-            b_bold = false; b_italic = true; break;
-        case 3:
-            weight = DWRITE_FONT_WEIGHT_BOLD; style = DWRITE_FONT_STYLE_ITALIC;
-            b_bold = true; b_italic = true; break;
-        }
+        bool b_bold = p_dw_font->GetWeight() >= 700,
+            b_italic = ( p_dw_font->GetStyle() == DWRITE_FONT_STYLE_ITALIC ||
+                p_dw_font->GetStyle() == DWRITE_FONT_STYLE_OBLIQUE );
 
-        if( p_dw_family->GetFirstMatchingFont( weight, DWRITE_FONT_STRETCH_NORMAL, style, p_dw_font.GetAddressOf() )
-         || !p_dw_font )
-            throw runtime_error( "GetFirstMatchingFont() failed" );
+        //avoid duplicates
+        bool &b_added = b_styles[ b_bold ? 1 : 0 ][ b_italic ? 1 : 0 ];
+        if( b_added )
+            continue;
 
         if( p_dw_font->CreateFontFace( p_dw_face.GetAddressOf() ) )
             throw runtime_error( "CreateFontFace() failed" );
@@ -388,6 +586,7 @@ static void DWrite_ParseFamily( IDWriteFontFamily *p_dw_family, vlc_family_t *p_
         if( asprintf( &psz_font_path, ":dw/%d", ( int ) streams.size() - 1 ) < 0 )
             throw bad_alloc();
 
+        b_added = true;
         NewFont( psz_font_path, i_font_index, b_bold, b_italic, p_family );
     }
 }
@@ -398,6 +597,10 @@ extern "C" const vlc_family_t *DWrite_GetFamily( filter_t *p_filter, const char
     dw_sys_t                     *p_dw_sys     = ( dw_sys_t * ) p_sys->p_dw_sys;
     ComPtr< IDWriteFontFamily >   p_dw_family;
 
+    UINT32 i_index;
+    BOOL b_exists = false;
+    wstring family_name, face_name;
+
     char *psz_lc = ToLower( psz_family );
     if( unlikely( !psz_lc ) )
         return NULL;
@@ -422,24 +625,68 @@ extern "C" const vlc_family_t *DWrite_GetFamily( filter_t *p_filter, const char
     if( unlikely( !pwsz_family ) )
         goto done;
 
-    UINT32 i_index;
-    BOOL b_exists;
+    /* Try to find an exact match first */
+    if( !p_dw_sys->p_dw_system_fonts->FindFamilyName( pwsz_family, &i_index, &b_exists ) && b_exists )
+    {
+        if( p_dw_sys->p_dw_system_fonts->GetFontFamily( i_index, p_dw_family.GetAddressOf() ) )
+        {
+            msg_Warn( p_filter, "DWrite_GetFamily: GetFontFamily() failed" );
+        }
+    }
 
-    if( p_dw_sys->p_dw_system_fonts->FindFamilyName( pwsz_family, &i_index, &b_exists ) || !b_exists )
+    /*
+     * Try to find a partial match e.g between Roboto and Roboto Thin.
+     * If found we search the Roboto family for faces starting with Thin (Thin, Thin Italic etc)
+     */
+    if( !p_dw_family )
     {
-        msg_Warn( p_filter, "DWrite_GetFamily: FindFamilyName() failed" );
-        goto done;
+        UINT32 i_count = p_dw_sys->p_dw_system_fonts->GetFontFamilyCount();
+        wchar_t buff_sys[ LOCALE_NAME_MAX_LENGTH ] = {};
+        wchar_t buff_usr[ LOCALE_NAME_MAX_LENGTH ] = {};
+
+        GetSystemDefaultLocaleName( buff_sys, LOCALE_NAME_MAX_LENGTH );
+        GetUserDefaultLocaleName( buff_usr, LOCALE_NAME_MAX_LENGTH );
+
+        for( i_index = 0; i_index < i_count; ++i_index )
+        {
+            ComPtr< IDWriteFontFamily > p_cur_family;
+            if( p_dw_sys->p_dw_system_fonts->GetFontFamily( i_index, p_cur_family.GetAddressOf() ) )
+            {
+                msg_Warn( p_filter, "DWrite_GetFamily: GetFontFamily() failed" );
+                continue;
+            }
+
+            wstring name_en, name_sys, name_usr;
+
+            if( GetFontFamilyName( p_cur_family.Get(), L"en-US", name_en )
+             || GetFontFamilyName( p_cur_family.Get(), buff_sys, name_sys )
+             || GetFontFamilyName( p_cur_family.Get(), buff_usr, name_usr ) )
+            {
+                msg_Warn( p_filter, "DWrite_GetFamily: GetFontFamilyName() failed" );
+                continue;
+            }
+
+            wstring requested_name{ pwsz_family };
+
+            if( !DWrite_PartialMatch( requested_name, name_en, &family_name, &face_name ) &&
+                !DWrite_PartialMatch( requested_name, name_sys, &family_name, &face_name ) &&
+                !DWrite_PartialMatch( requested_name, name_usr, &family_name, &face_name ) )
+                continue;
+
+            p_dw_family = p_cur_family;
+            break;
+        }
     }
 
-    if( p_dw_sys->p_dw_system_fonts->GetFontFamily( i_index, p_dw_family.GetAddressOf() ) )
+    if( !p_dw_family )
     {
-        msg_Err( p_filter, "DWrite_GetFamily: GetFontFamily() failed" );
+        msg_Warn( p_filter, "DWrite_GetFamily: family not found" );
         goto done;
     }
 
     try
     {
-        DWrite_ParseFamily( p_dw_family.Get(), p_family, p_dw_sys->streams );
+        DWrite_ParseFamily( p_dw_family.Get(), face_name, p_family, p_dw_sys->streams );
     }
     catch( const exception &e )
     {
-- 
2.13.6



More information about the vlc-devel mailing list