[vlc-devel] [PATCH 1/4] freetype: font fallback structs and functions

Salah-Eddin Shaban salshaaban at gmail.com
Thu Oct 22 13:53:47 CEST 2015


---
 modules/text_renderer/freetype.c       | 517 +++++++++++++++++----------------
 modules/text_renderer/freetype.h       |  78 +++--
 modules/text_renderer/platform_fonts.c | 378 +++++++++++++++++++++++-
 modules/text_renderer/platform_fonts.h | 101 ++++++-
 modules/text_renderer/text_layout.c    | 157 ++++++++--
 5 files changed, 913 insertions(+), 318 deletions(-)

diff --git a/modules/text_renderer/freetype.c b/modules/text_renderer/freetype.c
index f55e23a..95b045e 100644
--- a/modules/text_renderer/freetype.c
+++ b/modules/text_renderer/freetype.c
@@ -44,12 +44,6 @@
 #include <vlc_text_style.h>                                   /* text_style_t*/
 #include <vlc_charset.h>
 
-/* Freetype */
-#include <ft2build.h>
-#include FT_FREETYPE_H
-#include FT_GLYPH_H
-#include FT_STROKER_H
-
 /* apple stuff */
 #ifdef __APPLE__
 # include <TargetConditionals.h>
@@ -280,6 +274,8 @@ static int LoadFontsFromAttachments( filter_t *p_filter )
     filter_sys_t         *p_sys = p_filter->p_sys;
     input_attachment_t  **pp_attachments;
     int                   i_attachments_cnt;
+    FT_Face               p_face = NULL;
+    char                 *psz_lc = NULL;
 
     if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
         return VLC_EGENERIC;
@@ -287,9 +283,15 @@ static int LoadFontsFromAttachments( filter_t *p_filter )
     p_sys->i_font_attachments = 0;
     p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
     if( !p_sys->pp_font_attachments )
+    {
+        for( int i = 0; i < i_attachments_cnt; ++i )
+            vlc_input_attachment_Delete( pp_attachments[ i ] );
+        free( pp_attachments );
         return VLC_ENOMEM;
+    }
 
-    for( int k = 0; k < i_attachments_cnt; k++ )
+    int k = 0;
+    for( ; k < i_attachments_cnt; k++ )
     {
         input_attachment_t *p_attach = pp_attachments[k];
 
@@ -298,15 +300,100 @@ static int LoadFontsFromAttachments( filter_t *p_filter )
             p_attach->i_data > 0 && p_attach->p_data )
         {
             p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
+
+            int                 i_font_idx = 0;
+
+            while( 0 == FT_New_Memory_Face( p_sys->p_library,
+                                            p_attach->p_data,
+                                            p_attach->i_data,
+                                            i_font_idx,
+                                            &p_face ))
+            {
+
+                bool b_bold = p_face->style_flags & FT_STYLE_FLAG_BOLD;
+                bool b_italic = p_face->style_flags & FT_STYLE_FLAG_ITALIC;
+
+                if( p_face->family_name )
+                    psz_lc = ToLower( p_face->family_name );
+                else
+                    if( asprintf( &psz_lc, FB_NAME"-%02d",
+                                  p_sys->i_fallback_counter++ ) < 0 )
+                        psz_lc = NULL;
+
+                if( unlikely( !psz_lc ) )
+                    goto error;
+
+                vlc_family_t *p_family =
+                    vlc_dictionary_value_for_key( &p_sys->family_map, psz_lc );
+
+                if( p_family == kVLCDictionaryNotFound )
+                {
+                    p_family = NewFamily( p_filter, psz_lc, &p_sys->p_families,
+                                          &p_sys->family_map, psz_lc );
+
+                    if( unlikely( !p_family ) )
+                        goto error;
+                }
+
+                free( psz_lc );
+                psz_lc = NULL;
+
+                char *psz_fontfile;
+                if( asprintf( &psz_fontfile, ":/%d",
+                              p_sys->i_font_attachments - 1 ) < 0
+                 || !NewFont( psz_fontfile, i_font_idx, b_bold, b_italic, p_family ) )
+                    goto error;
+
+                FT_Done_Face( p_face );
+                p_face = NULL;
+
+                i_font_idx++;
+            }
+
         }
         else
         {
             vlc_input_attachment_Delete( p_attach );
         }
     }
+
     free( pp_attachments );
 
+    /* Add font attachments to the "attachments" fallback list */
+    vlc_family_t *p_attachments = NULL;
+
+    for( vlc_family_t *p_family = p_sys->p_families; p_family;
+         p_family = p_family->p_next )
+    {
+        vlc_family_t *p_temp = NewFamily( p_filter, p_family->psz_name, &p_attachments,
+                                          NULL, NULL );
+        if( unlikely( !p_temp ) )
+        {
+            if( p_attachments )
+                FreeFamilies( p_attachments, NULL );
+            return VLC_ENOMEM;
+        }
+        else
+            p_temp->p_fonts = p_family->p_fonts;
+    }
+
+    if( p_attachments )
+        vlc_dictionary_insert( &p_sys->fallback_map, FB_LIST_ATTACHMENTS, p_attachments );
+
     return VLC_SUCCESS;
+
+error:
+    if( p_face )
+        FT_Done_Face( p_face );
+
+    if( psz_lc )
+        free( psz_lc );
+
+    for( int i = k + 1; i < i_attachments_cnt; ++i )
+        vlc_input_attachment_Delete( pp_attachments[ i ] );
+
+    free( pp_attachments );
+    return VLC_ENOMEM;
 }
 
 /*****************************************************************************
@@ -891,11 +978,10 @@ static void FreeStylesArray( text_style_t **pp_styles, size_t i_styles )
 }
 
 static uni_char_t* SegmentsToTextAndStyles( filter_t *p_filter, const text_segment_t *p_segment, size_t *pi_string_length,
-                                            text_style_t ***ppp_styles, size_t *pi_styles, bool b_grid )
+                                            text_style_t ***ppp_styles, size_t *pi_styles )
 {
     text_style_t **pp_styles = NULL;
     uni_char_t *psz_uni = NULL;
-    const int i_scale = ( b_grid ) ? 100 : var_InheritInteger( p_filter, "sub-text-scale");
     size_t i_size = 0;
     size_t i_nb_char = 0;
     *pi_styles = 0;
@@ -951,12 +1037,6 @@ static uni_char_t* SegmentsToTextAndStyles( filter_t *p_filter, const text_segme
         /* Overwrite any default or value with forced ones */
         text_style_Merge( p_style, p_filter->p_sys->p_forced_style, true );
 
-        if( i_scale != 100 )
-        {
-            p_style->i_font_size = p_style->i_font_size * i_scale / 100;
-            p_style->f_font_relsize = p_style->f_font_relsize * i_scale / 100;
-        }
-
         // i_string_bytes is a number of bytes, while here we're going to assign pointer by pointer
         for ( size_t i = 0; i < i_string_bytes / sizeof( *psz_uni ); ++i )
             pp_styles[i_nb_char + i] = p_style;
@@ -981,11 +1061,25 @@ static int Render( filter_t *p_filter, subpicture_region_t *p_region_out,
     if( !p_region_in )
         return VLC_EGENERIC;
 
+    filter_sys_t *p_sys = p_filter->p_sys;
+    bool b_grid = p_region_in->b_gridmode;
+    p_sys->i_scale = ( b_grid ) ? 100 : var_InheritInteger( p_filter, "sub-text-scale");
+
+    /*
+     * Update the default face to reflect changes in video size or text scaling
+     */
+    p_sys->p_face = SelectAndLoadFace( p_filter, p_sys->p_default_style, 0 );
+    if( !p_sys->p_face )
+    {
+        msg_Err( p_filter, "Render(): Error loading default face" );
+        return VLC_EGENERIC;
+    }
+
     text_style_t **pp_styles = NULL;
     size_t i_text_length = 0;
     size_t i_styles = 0;
     uni_char_t *psz_text = SegmentsToTextAndStyles( p_filter, p_region_in->p_text, &i_text_length,
-                                                    &pp_styles, &i_styles, p_region_in->b_gridmode );
+                                                    &pp_styles, &i_styles );
     if( !psz_text || !pp_styles )
     {
         return VLC_EGENERIC;
@@ -1065,100 +1159,61 @@ static int Render( filter_t *p_filter, subpicture_region_t *p_region_out,
     return rv;
 }
 
+static void FreeFace( void *p_face, void *p_obj )
+{
+    VLC_UNUSED( p_obj );
+
+    FT_Done_Face( ( FT_Face ) p_face );
+}
+
 /*****************************************************************************
  * Create: allocates osd-text video thread output method
  *****************************************************************************
  * This function allocates and initializes a Clone vout method.
  *****************************************************************************/
-static int Init_FT( vlc_object_t *p_this,
-                    const char *psz_fontfile,
-                    const int fontindex )
+static int Create( vlc_object_t *p_this )
 {
-    filter_t      *p_filter = (filter_t *)p_this;
-    filter_sys_t  *p_sys = p_filter->p_sys;
-
-    /* */
-    int i_error = FT_Init_FreeType( &p_sys->p_library );
-    if( i_error )
-    {
-        msg_Err( p_filter, "couldn't initialize freetype" );
-        goto error;
-    }
-
-    i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
-                           fontindex, &p_sys->p_face );
+    filter_t      *p_filter         = ( filter_t * ) p_this;
+    filter_sys_t  *p_sys            = NULL;
 
-    if( i_error == FT_Err_Unknown_File_Format )
-    {
-        msg_Err( p_filter, "file %s have unknown format",
-                 psz_fontfile ? psz_fontfile : "(null)" );
-        goto error;
-    }
-    else if( i_error )
-    {
-        msg_Err( p_filter, "failed to load font file %s",
-                 psz_fontfile ? psz_fontfile : "(null)" );
-        goto error;
-    }
+    /* Allocate structure */
+    p_filter->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
+    if( !p_sys )
+        return VLC_ENOMEM;
 
-    i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
-    if( i_error )
+    if( FT_Init_FreeType( &p_sys->p_library ) )
     {
-        msg_Err( p_filter, "font has no unicode translation table" );
-        goto error;
+        msg_Err( p_filter, "Failed to initialize FreeType" );
+        free( p_sys );
+        return VLC_EGENERIC;
     }
 
-    if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, STYLE_DEFAULT_FONT_SIZE ) )
+    if( FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker ) )
     {
-        msg_Err( p_filter, "couldn't set font size to %d", STYLE_DEFAULT_FONT_SIZE );
-        goto error;
-    }
-
-    i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
-    if( i_error )
         msg_Err( p_filter, "Failed to create stroker for outlining" );
+        p_sys->p_stroker = NULL;
+    }
 
-    return VLC_SUCCESS;
-
-error:
-    if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
-    if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
-
-    return VLC_EGENERIC;
-}
-
-static int Create( vlc_object_t *p_this )
-{
-    filter_t      *p_filter = (filter_t *)p_this;
-    filter_sys_t  *p_sys;
-    char          *psz_fontfile   = NULL;
-    char          *psz_monofontfile   = NULL;
-    int            fontindex = 0, monofontindex = 0;
+    p_sys->pp_font_attachments = NULL;
+    p_sys->i_font_attachments = 0;
+    p_sys->p_families = NULL;
 
-    /* Allocate structure */
-    p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
-    if( !p_sys )
-        return VLC_ENOMEM;
+    vlc_dictionary_init( &p_sys->face_map, 50 );
+    vlc_dictionary_init( &p_sys->family_map, 50 );
+    vlc_dictionary_init( &p_sys->fallback_map, 20 );
 
-    p_sys->p_face           = 0;
-    p_sys->p_library        = 0;
+    p_sys->i_fallback_counter = 0;
+    p_sys->i_scale = 100;
 
     /* default style to apply to uncomplete segmeents styles */
     p_sys->p_default_style = text_style_Create( STYLE_FULLY_SET );
     if(unlikely(!p_sys->p_default_style))
-    {
-        free(p_sys);
-        return VLC_ENOMEM;
-    }
+        goto error;
 
     /* empty style for style overriding cases */
     p_sys->p_forced_style = text_style_Create( STYLE_NO_DEFAULTS );
     if(unlikely(!p_sys->p_forced_style))
-    {
-        text_style_Delete( p_sys->p_default_style );
-        free(p_sys);
-        return VLC_ENOMEM;
-    }
+        goto error;
 
     /* fills default and forced style */
     FillDefaultStyles( p_filter );
@@ -1200,6 +1255,9 @@ static int Create( vlc_object_t *p_this )
 #endif
     }
 
+    if( LoadFontsFromAttachments( p_filter ) == VLC_ENOMEM )
+        goto error;
+
 #ifdef HAVE_FONTCONFIG
     p_sys->pf_select = FontConfig_Select;
     FontConfig_BuildCache( p_filter );
@@ -1213,64 +1271,42 @@ static int Create( vlc_object_t *p_this )
     p_sys->pf_select = Dummy_Select;
 #endif
 
-    /* */
-    psz_fontfile = p_sys->pf_select( p_filter, p_sys->p_default_style->psz_fontname,
-                                     false, false, p_sys->p_default_style->i_font_size, &fontindex );
-    psz_monofontfile = p_sys->pf_select( p_filter, p_sys->p_default_style->psz_monofontname,
-                                         false, false, p_sys->p_default_style->i_font_size,
-                                          &monofontindex );
-    msg_Dbg( p_filter, "Using %s as font from file %s",
-             p_sys->p_default_style->psz_fontname, psz_fontfile );
-    msg_Dbg( p_filter, "Using %s as mono-font from file %s",
-             p_sys->p_default_style->psz_monofontname, psz_monofontfile );
-
-    /* If nothing is found, use the default family */
-    if( !psz_fontfile )
-        psz_fontfile = File_Select( p_sys->p_default_style->psz_fontname );
-    if( !psz_monofontfile )
-        psz_monofontfile = File_Select( p_sys->p_default_style->psz_monofontname );
-
-    if( Init_FT( p_this, psz_fontfile, fontindex ) != VLC_SUCCESS )
+    p_sys->p_face = SelectAndLoadFace( p_filter, p_sys->p_default_style, 0 );
+    if( !p_sys->p_face )
+    {
+        msg_Err( p_filter, "Error loading default face" );
         goto error;
-
-    int i_faces_size = 20;
-    p_sys->faces_cache.p_faces = malloc( i_faces_size * sizeof( *p_sys->faces_cache.p_faces ) );
-    p_sys->faces_cache.p_styles = malloc( i_faces_size * sizeof( *p_sys->faces_cache.p_styles ) );
-    p_sys->faces_cache.i_cache_size = i_faces_size;
-    p_sys->faces_cache.i_faces_count = 0;
-
-    p_sys->pp_font_attachments = NULL;
-    p_sys->i_font_attachments = 0;
+    }
 
     p_filter->pf_render = Render;
 
-    LoadFontsFromAttachments( p_filter );
-
-    free( psz_fontfile );
-    free( psz_monofontfile );
-
     return VLC_SUCCESS;
 
 error:
-    free( psz_fontfile );
-    free( psz_monofontfile );
     text_style_Delete( p_sys->p_default_style );
     text_style_Delete( p_sys->p_forced_style );
-    free( p_sys );
-    return VLC_EGENERIC;
-}
 
+    vlc_dictionary_clear( &p_sys->fallback_map, FreeFamilies, p_filter );
+    vlc_dictionary_clear( &p_sys->face_map, FreeFace, p_filter );
+    vlc_dictionary_clear( &p_sys->family_map, NULL, NULL );
+    if( p_sys->p_families )
+        FreeFamiliesAndFonts( p_sys->p_families );
 
-static void Destroy_FT( vlc_object_t *p_this )
-{
-    filter_t *p_filter = (filter_t *)p_this;
-    filter_sys_t *p_sys = p_filter->p_sys;
+    if( p_sys->pp_font_attachments )
+    {
+        for( int k = 0; k < p_sys->i_font_attachments; k++ )
+            vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
+
+        free( p_sys->pp_font_attachments );
+    }
 
     if( p_sys->p_stroker )
         FT_Stroker_Done( p_sys->p_stroker );
 
-    FT_Done_Face( p_sys->p_face );
     FT_Done_FreeType( p_sys->p_library );
+
+    free( p_sys );
+    return VLC_EGENERIC;
 }
 
 /*****************************************************************************
@@ -1283,14 +1319,26 @@ static void Destroy( vlc_object_t *p_this )
     filter_t *p_filter = (filter_t *)p_this;
     filter_sys_t *p_sys = p_filter->p_sys;
 
-    faces_cache_t *p_cache = &p_sys->faces_cache;
-    for( int i = 0; i < p_cache->i_faces_count; ++i )
-    {
-        FT_Done_Face( p_cache->p_faces[ i ] );
-        free( p_cache->p_styles[ i ].psz_fontname );
-    }
-    free( p_sys->faces_cache.p_faces );
-    free( p_sys->faces_cache.p_styles );
+#if 0
+    msg_Dbg( p_filter, "------------------" );
+    msg_Dbg( p_filter, "p_sys->p_families:" );
+    msg_Dbg( p_filter, "------------------" );
+    DumpFamily( p_filter, p_sys->p_families, true, -1 );
+    msg_Dbg( p_filter, "-----------------" );
+    msg_Dbg( p_filter, "p_sys->family_map" );
+    msg_Dbg( p_filter, "-----------------" );
+    DumpDictionary( p_filter, &p_sys->family_map, false, 1 );
+    msg_Dbg( p_filter, "-------------------" );
+    msg_Dbg( p_filter, "p_sys->fallback_map" );
+    msg_Dbg( p_filter, "-------------------" );
+    DumpDictionary( p_filter, &p_sys->fallback_map, true, -1 );
+#endif
+
+    vlc_dictionary_clear( &p_sys->fallback_map, FreeFamilies, p_filter );
+    vlc_dictionary_clear( &p_sys->face_map, FreeFace, p_filter );
+    vlc_dictionary_clear( &p_sys->family_map, NULL, NULL );
+    if( p_sys->p_families )
+        FreeFamiliesAndFonts( p_sys->p_families );
 
     if( p_sys->pp_font_attachments )
     {
@@ -1303,59 +1351,19 @@ static void Destroy( vlc_object_t *p_this )
     text_style_Delete( p_sys->p_default_style );
     text_style_Delete( p_sys->p_forced_style );
 
-    Destroy_FT( p_this );
-    free( p_sys );
-}
-
-/* Face loading */
-bool FaceStyleEquals( const text_style_t *p_style1,
-                             const text_style_t *p_style2 )
-{
-    if( !p_style1 || !p_style2 )
-        return false;
-    if( p_style1 == p_style2 )
-        return true;
-
-    const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
-    return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
-           !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
-}
+    if( p_sys->p_stroker )
+        FT_Stroker_Done( p_sys->p_stroker );
 
-static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const char *psz_fontname,
-                                 const text_style_t *p_style )
-{
-    for( int k = 0; k < p_sys->i_font_attachments; k++ )
-    {
-        input_attachment_t *p_attach   = p_sys->pp_font_attachments[k];
-        int                 i_font_idx = 0;
-        FT_Face             p_face = NULL;
-
-        while( 0 == FT_New_Memory_Face( p_sys->p_library,
-                                        p_attach->p_data,
-                                        p_attach->i_data,
-                                        i_font_idx,
-                                        &p_face ))
-        {
-            if( p_face )
-            {
-                int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD)    ? STYLE_BOLD   : 0) |
-                                       ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
-                if( p_face->family_name != NULL
-                 && !strcasecmp( p_face->family_name, psz_fontname )
-                 && (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC))
-                                                          == i_style_received )
-                    return p_face;
+    FT_Done_FreeType( p_sys->p_library );
 
-                FT_Done_Face( p_face );
-            }
-            i_font_idx++;
-        }
-    }
-    return NULL;
+    free( p_sys );
 }
 
+/* Face loading */
 int ConvertToLiveSize( filter_t *p_filter, const text_style_t *p_style )
 {
+    filter_sys_t *p_sys = p_filter->p_sys;
+
     int i_font_size = STYLE_DEFAULT_FONT_SIZE;
     if( p_style->i_font_size )
     {
@@ -1365,115 +1373,118 @@ int ConvertToLiveSize( filter_t *p_filter, const text_style_t *p_style )
     {
         i_font_size = (int) p_filter->fmt_out.video.i_height * p_style->f_font_relsize;
     }
+
+    if( p_sys->i_scale != 100 )
+        i_font_size = i_font_size * p_sys->i_scale / 100;
+
     return i_font_size;
 }
 
-FT_Face LoadFace( filter_t *p_filter,
-                  const text_style_t *p_style, int i_font_size )
+FT_Face LoadFace( filter_t *p_filter, const char *psz_fontfile, int i_idx,
+                  const text_style_t *p_style )
 {
     filter_sys_t *p_sys = p_filter->p_sys;
+    char *psz_key = NULL;
 
-    faces_cache_t *p_cache = &p_sys->faces_cache;
-    for( int i = 0; i < p_cache->i_faces_count; ++i )
-        if( FaceStyleEquals( &p_cache->p_styles[ i ], p_style )
-         && p_cache->p_styles[ i ].i_font_size == i_font_size
-         && !( ( p_cache->p_styles[ i ].i_style_flags ^ p_style->i_style_flags ) & STYLE_HALFWIDTH ) )
-            return p_cache->p_faces[ i ];
+    int i_font_size  = ConvertToLiveSize( p_filter, p_style );
+    int i_font_width = p_style->i_style_flags & STYLE_HALFWIDTH ?
+                       i_font_size / 2 : i_font_size;
 
-    const char *psz_fontname = (p_style->i_style_flags & STYLE_MONOSPACED)
-                               ? p_style->psz_monofontname : p_style->psz_fontname;
+    if( asprintf( &psz_key, "%s - %d - %d - %d",
+                  psz_fontfile, i_idx,
+                  i_font_size, i_font_width ) < 0 )
+        return NULL;
 
-    /* Look for a match amongst our attachments first */
-    FT_Face p_face = LoadEmbeddedFace( p_sys, psz_fontname, p_style );
+    FT_Face p_face = vlc_dictionary_value_for_key( &p_sys->face_map, psz_key );
+    if( p_face != kVLCDictionaryNotFound )
+        goto done;
 
-    /* Load system wide font otheriwse */
-    if( !p_face )
+    if( psz_fontfile[0] == ':' && psz_fontfile[1] == '/' )
     {
-        int  i_idx = 0;
-        char *psz_fontfile = NULL;
-        if( p_sys->pf_select )
-            psz_fontfile = p_sys->pf_select( p_filter,
-                                             psz_fontname,
-                                             (p_style->i_style_flags & STYLE_BOLD) != 0,
-                                             (p_style->i_style_flags & STYLE_ITALIC) != 0,
-                                             -1,
-                                             &i_idx );
-        else
-            psz_fontfile = NULL;
-
-        if( !psz_fontfile )
-            return NULL;
-
-        if( *psz_fontfile == '\0' )
+        int i_attach = atoi( psz_fontfile + 2 );
+        if( i_attach < 0 || i_attach >= p_sys->i_font_attachments )
         {
-            msg_Warn( p_filter,
-                      "We were not able to find a matching font: \"%s\" (%s %s),"
-                      " so using default font",
-                      psz_fontname,
-                      (p_style->i_style_flags & STYLE_BOLD)   ? "Bold" : "",
-                      (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
+            msg_Err( p_filter, "LoadFace: Invalid font attachment index" );
             p_face = NULL;
         }
         else
         {
-            if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
+            input_attachment_t *p_attach = p_sys->pp_font_attachments[ i_attach ];
+            if( FT_New_Memory_Face( p_sys->p_library, p_attach->p_data,
+                                    p_attach->i_data, i_idx, &p_face ) )
                 p_face = NULL;
         }
-        free( psz_fontfile );
     }
+    else
+        if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
+        {
+            msg_Err( p_filter, "LoadFace: Error creating face for %s", psz_key );
+            p_face = NULL;
+        }
+
     if( !p_face )
-        return NULL;
+        goto done;
 
     if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
     {
         /* We've loaded a font face which is unhelpful for actually
          * rendering text - fallback to the default one.
          */
+        msg_Err( p_filter, "LoadFace: Error selecting charmap for %s", psz_key );
         FT_Done_Face( p_face );
-        return NULL;
+        p_face = NULL;
+        goto done;
     }
 
-    int i_font_width = p_style->i_style_flags & STYLE_HALFWIDTH
-                     ? i_font_size / 2 : i_font_size;
-
     if( FT_Set_Pixel_Sizes( p_face, i_font_width, i_font_size ) )
     {
         msg_Err( p_filter,
-                 "Failed to set font size to %d", i_font_size );
+                 "LoadFace: Failed to set font size for %s", psz_key );
         FT_Done_Face( p_face );
-        return NULL;
+        p_face = NULL;
+        goto done;
     }
 
-    if( p_cache->i_faces_count == p_cache->i_cache_size )
-    {
-        FT_Face *p_new_faces =
-                realloc( p_cache->p_faces, p_cache->i_cache_size * 2 * sizeof( *p_cache->p_faces ) );
-        if( !p_new_faces )
-        {
-            FT_Done_Face( p_face );
-            return NULL;
-        }
+    vlc_dictionary_insert( &p_sys->face_map, psz_key, p_face );
 
-        p_cache->p_faces = p_new_faces;
+done:
+    free( psz_key );
+    return p_face;
+}
 
-        text_style_t *p_new_styles =
-                realloc( p_cache->p_styles, p_cache->i_cache_size * 2 * sizeof( *p_cache->p_styles ) ) ;
-        if( !p_new_styles )
-        {
-            FT_Done_Face( p_face );
-            return NULL;
-        }
+FT_Face SelectAndLoadFace( filter_t *p_filter, const text_style_t *p_style,
+                           uni_char_t codepoint )
+{
+    filter_sys_t *p_sys = p_filter->p_sys;
 
-        p_cache->p_styles = p_new_styles;
-        p_cache->i_cache_size *= 2;
+    const char *psz_fontname = (p_style->i_style_flags & STYLE_MONOSPACED)
+                               ? p_style->psz_monofontname : p_style->psz_fontname;
+
+    bool b_bold = p_style->i_style_flags & STYLE_BOLD;
+    bool b_italic = p_style->i_style_flags & STYLE_ITALIC;
+
+    FT_Face p_face = NULL;
+
+
+    int  i_idx = 0;
+    char *psz_fontfile = NULL;
+    if( p_sys->pf_select )
+        psz_fontfile = p_sys->pf_select( p_filter, psz_fontname, b_bold, b_italic,
+                                         &i_idx, codepoint );
+    else
+        psz_fontfile = NULL;
+
+    if( !psz_fontfile || *psz_fontfile == '\0' )
+    {
+        msg_Warn( p_filter,
+                  "SelectAndLoadFace: no font found for family: %s, codepoint: 0x%x",
+                  psz_fontname, codepoint );
+        free( psz_fontfile );
+        return NULL;
     }
 
-    text_style_t *p_face_style = p_cache->p_styles + p_cache->i_faces_count;
-    p_face_style->i_font_size = i_font_size;
-    p_face_style->i_style_flags = p_style->i_style_flags;
-    p_face_style->psz_fontname = strdup( psz_fontname );
-    p_cache->p_faces[ p_cache->i_faces_count ] = p_face;
-    ++p_cache->i_faces_count;
+    p_face = LoadFace( p_filter, psz_fontfile, i_idx, p_style );
 
+    free( psz_fontfile );
     return p_face;
 }
diff --git a/modules/text_renderer/freetype.h b/modules/text_renderer/freetype.h
index ae7c36f..7ec2e61 100644
--- a/modules/text_renderer/freetype.h
+++ b/modules/text_renderer/freetype.h
@@ -29,14 +29,14 @@
 #define VLC_FREETYPE_H
 
 #include <vlc_text_style.h>                                   /* text_style_t*/
+#include <vlc_arrays.h>
 
-typedef struct faces_cache_t
-{
-    FT_Face        *p_faces;
-    text_style_t   *p_styles;
-    int            i_faces_count;
-    int            i_cache_size;
-} faces_cache_t;
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include FT_STROKER_H
+
+#include "platform_fonts.h"
 
 /*****************************************************************************
  * filter_sys_t: freetype local data
@@ -61,13 +61,48 @@ struct filter_sys_t
     input_attachment_t **pp_font_attachments;
     int                  i_font_attachments;
 
-    /* Font faces cache */
-    faces_cache_t  faces_cache;
+    /*
+     * This is the master family list. It owns the lists of vlc_font_t's
+     * and should be freed using FreeFamiliesAndFonts()
+     */
+    vlc_family_t      *p_families;
 
-    char * (*pf_select) (filter_t *, const char* family,
-                               bool bold, bool italic, int size,
-                               int *index);
+    /*
+     * This maps a family name to a vlc_family_t within the master list
+     */
+    vlc_dictionary_t  family_map;
+
+    /*
+     * This maps a family name to a fallback list of vlc_family_t's.
+     * Fallback lists only reference the lists of vlc_font_t's within the
+     * master list, so they should be freed using FreeFamilies()
+     */
+    vlc_dictionary_t  fallback_map;
+
+    /* Font face cache */
+    vlc_dictionary_t  face_map;
 
+    int               i_fallback_counter;
+    int               i_scale;
+
+    char * (*pf_select) (filter_t *, const char* family,
+                         bool bold, bool italic,
+                         int *index, uni_char_t codepoint);
+
+    /*
+     * Get a pointer to the vlc_family_t in the master list that matches psz_family.
+     * Add this family to the list if it hasn't been added yet.
+     */
+    const vlc_family_t * (*pf_get_family) ( filter_t *p_filter, const char *psz_family );
+
+    /*
+     * Get the fallback list for psz_family from the system and cache
+     * it in fallback_map.
+     * On Windows fallback lists are populated progressively as required
+     * using Uniscribe, so we need the codepoint here.
+     */
+    vlc_family_t * (*pf_get_fallbacks) ( filter_t *p_filter, const char *psz_family,
+                                         uni_char_t codepoint );
 };
 
 #define FT_FLOOR(X)     ((X & -64) >> 6)
@@ -76,23 +111,12 @@ struct filter_sys_t
  #define FT_MulFix(v, s) (((v)*(s))>>16)
 #endif
 
-#ifdef __OS2__
-typedef uint16_t uni_char_t;
-# define FREETYPE_TO_UCS    "UCS-2LE"
-#else
-typedef uint32_t uni_char_t;
-# if defined(WORDS_BIGENDIAN)
-#  define FREETYPE_TO_UCS   "UCS-4BE"
-# else
-#  define FREETYPE_TO_UCS   "UCS-4LE"
-# endif
-#endif
+FT_Face LoadFace( filter_t *p_filter, const char *psz_fontfile, int i_idx,
+                  const text_style_t *p_style );
 
+FT_Face SelectAndLoadFace( filter_t *p_filter, const text_style_t *p_style,
+                           uni_char_t codepoint );
 
-FT_Face LoadFace( filter_t *p_filter, const text_style_t *p_style, int );
 int ConvertToLiveSize( filter_t *p_filter, const text_style_t *p_style );
 
-bool FaceStyleEquals( const text_style_t *p_style1,
-                      const text_style_t *p_style2 );
-
 #endif
diff --git a/modules/text_renderer/platform_fonts.c b/modules/text_renderer/platform_fonts.c
index 7869dba..1195272 100644
--- a/modules/text_renderer/platform_fonts.c
+++ b/modules/text_renderer/platform_fonts.c
@@ -9,6 +9,7 @@
  *          Bernie Purcell <bitmap at videolan.org>
  *          Jean-Baptiste Kempf <jb at videolan.org>
  *          Felix Paul Kühne <fkuehne at videolan.org>
+ *          Salah-Eddin Shaban <salshaaban at gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published by
@@ -36,6 +37,7 @@
 #include <vlc_common.h>
 #include <vlc_filter.h>                                      /* filter_sys_t */
 #include <vlc_text_style.h>                                   /* text_style_t*/
+#include <ctype.h>
 
 /* apple stuff */
 #ifdef __APPLE__
@@ -60,6 +62,359 @@
 #endif
 
 #include "platform_fonts.h"
+#include "freetype.h"
+
+static FT_Face GetFace( filter_t *p_filter, vlc_font_t *p_font )
+{
+    filter_sys_t *p_sys = p_filter->p_sys;
+
+    if( p_font->p_face )
+        return p_font->p_face;
+
+    p_font->p_face = LoadFace( p_filter, p_font->psz_fontfile, p_font->i_index,
+                               p_sys->p_default_style );
+
+    return p_font->p_face;
+}
+
+static vlc_font_t *GetBestFont( filter_t *p_filter, const vlc_family_t *p_family,
+                                bool b_bold, bool b_italic, uni_char_t codepoint )
+{
+    int i_best_score = 0;
+    vlc_font_t *p_best_font = p_family->p_fonts;
+
+    for( vlc_font_t *p_font = p_family->p_fonts; p_font; p_font = p_font->p_next )
+    {
+        int i_score = 0;
+
+        if( codepoint )
+        {
+            FT_Face p_face = GetFace( p_filter, p_font );
+            if( p_face && FT_Get_Char_Index( p_face, codepoint ) )
+                i_score += 1000;
+        }
+
+        if( !!p_font->b_bold == !!b_bold )
+            i_score += 100;
+        if( !!p_font->b_italic == !!b_italic )
+            i_score += 10;
+
+        if( i_score > i_best_score )
+        {
+            p_best_font = p_font;
+            i_best_score = i_score;
+        }
+    }
+
+    return p_best_font;
+}
+
+static vlc_family_t *SearchFallbacks( filter_t *p_filter, vlc_family_t *p_fallbacks,
+                                      uni_char_t codepoint )
+{
+    filter_sys_t *p_sys = p_filter->p_sys;
+    vlc_family_t *p_family = NULL;
+
+    for( vlc_family_t *p_fallback = p_fallbacks; p_fallback;
+         p_fallback = p_fallback->p_next )
+    {
+        if( !p_fallback->p_fonts )
+        {
+            const vlc_family_t *p_temp =
+                    p_sys->pf_get_family( p_filter, p_fallback->psz_name );
+            if( !p_temp || !p_temp->p_fonts )
+                continue;
+            p_fallback->p_fonts = p_temp->p_fonts;
+        }
+
+        FT_Face p_face = GetFace( p_filter, p_fallback->p_fonts );
+        if( !p_face || !FT_Get_Char_Index( p_face, codepoint ) )
+            continue;
+        p_family = p_fallback;
+        break;
+    }
+
+    return p_family;
+}
+
+vlc_family_t *NewFamily( filter_t *p_filter, const char *psz_family,
+                         vlc_family_t **pp_list, vlc_dictionary_t *p_dict,
+                         const char *psz_key )
+{
+    filter_sys_t *p_sys = p_filter->p_sys;
+    vlc_family_t *p_family = NULL;
+
+    p_family = calloc( 1, sizeof( *p_family ) );
+
+    char *psz_name;
+    if( psz_family && *psz_family )
+        psz_name = ToLower( psz_family );
+    else
+        if( asprintf( &psz_name, FB_NAME"-%02d",
+                      p_sys->i_fallback_counter++ ) < 0 )
+            psz_name = NULL;
+
+    char *psz_lc = NULL;
+    if( likely( psz_name ) )
+    {
+        if( !psz_key )
+            psz_lc = strdup( psz_name );
+        else
+            psz_lc = ToLower( psz_key );
+    }
+
+    if( unlikely( !p_family || !psz_name || !psz_lc ) )
+    {
+        free( p_family );
+        free( psz_name );
+        free( psz_lc );
+        return NULL;
+    }
+
+    p_family->psz_name = psz_name;
+
+    if( pp_list )
+        AppendFamily( pp_list, p_family );
+
+    if( p_dict )
+    {
+        vlc_family_t *p_root = vlc_dictionary_value_for_key( p_dict, psz_lc );
+        if( p_root )
+            AppendFamily( &p_root, p_family );
+        else
+            vlc_dictionary_insert( p_dict, psz_lc, p_family );
+    }
+
+    free( psz_lc );
+    return p_family;
+}
+
+vlc_font_t *NewFont( char *psz_fontfile, int i_index,
+                     bool b_bold, bool b_italic,
+                     vlc_family_t *p_parent )
+{
+    vlc_font_t *p_font = calloc( 1, sizeof( *p_font ) );
+
+    if( unlikely( !p_font ) )
+    {
+        free( psz_fontfile );
+        return NULL;
+    }
+
+    p_font->psz_fontfile = psz_fontfile;
+    p_font->i_index = i_index;
+    p_font->b_bold = b_bold;
+    p_font->b_italic = b_italic;
+
+    if( p_parent )
+    {
+        /* Keep regular faces first */
+        if( p_parent->p_fonts
+         && ( p_parent->p_fonts->b_bold || p_parent->p_fonts->b_italic )
+         && !b_bold && !b_italic )
+        {
+            p_font->p_next = p_parent->p_fonts;
+            p_parent->p_fonts = p_font;
+        }
+        else
+            AppendFont( &p_parent->p_fonts, p_font );
+    }
+
+    return p_font;
+}
+
+void FreeFamiliesAndFonts( vlc_family_t *p_family )
+{
+    if( p_family->p_next )
+        FreeFamiliesAndFonts( p_family->p_next );
+
+    for( vlc_font_t *p_font = p_family->p_fonts; p_font; )
+    {
+        vlc_font_t *p_temp = p_font->p_next;
+        free( p_font->psz_fontfile );
+        free( p_font );
+        p_font = p_temp;
+    }
+
+    free( p_family->psz_name );
+    free( p_family );
+}
+
+void FreeFamilies( void *p_families, void *p_obj )
+{
+    vlc_family_t *p_family = ( vlc_family_t * ) p_families;
+
+    if( p_family->p_next )
+        FreeFamilies( p_family->p_next, p_obj );
+
+    free( p_family->psz_name );
+    free( p_family );
+}
+
+vlc_family_t *InitDefaultList( filter_t *p_filter, const char *const *ppsz_default,
+                               int i_size )
+{
+
+    vlc_family_t  *p_default  = NULL;
+    filter_sys_t  *p_sys = p_filter->p_sys;
+
+    for( int i = 0; i < i_size; ++i )
+    {
+        const vlc_family_t *p_family =
+                p_sys->pf_get_family( p_filter, ppsz_default[ i ] );
+
+        if( p_family )
+        {
+            vlc_family_t *p_temp =
+                NewFamily( p_filter, ppsz_default[ i ], &p_default, NULL, NULL );
+
+            if( unlikely( !p_temp ) )
+                goto error;
+
+            p_temp->p_fonts = p_family->p_fonts;
+        }
+    }
+
+    if( p_default )
+        vlc_dictionary_insert( &p_sys->fallback_map, FB_LIST_DEFAULT, p_default );
+
+    return p_default;
+
+error:
+    if( p_default ) FreeFamilies( p_default, NULL );
+    return NULL;
+}
+
+void DumpFamily( filter_t *p_filter, const vlc_family_t *p_family,
+                 bool b_dump_fonts, int i_max_families )
+{
+
+    if( i_max_families < 0 )
+        i_max_families = INT_MAX;
+
+    for( int i = 0; p_family && i < i_max_families ; p_family = p_family->p_next, ++i )
+    {
+        msg_Dbg( p_filter, "\t[0x%"PRIxPTR"] %s",
+                 ( uintptr_t ) p_family, p_family->psz_name );
+
+        if( b_dump_fonts )
+        {
+            for( vlc_font_t *p_font = p_family->p_fonts; p_font; p_font = p_font->p_next )
+            {
+                const char *psz_style = NULL;
+                if( !p_font->b_bold && !p_font->b_italic )
+                    psz_style = "Regular";
+                else if( p_font->b_bold && !p_font->b_italic )
+                    psz_style = "Bold";
+                else if( !p_font->b_bold && p_font->b_italic )
+                    psz_style = "Italic";
+                else if( p_font->b_bold && p_font->b_italic )
+                    psz_style = "Bold Italic";
+
+                msg_Dbg( p_filter, "\t\t[0x%"PRIxPTR"] (%s): %s - %d",
+                         ( uintptr_t ) p_font, psz_style,
+                         p_font->psz_fontfile, p_font->i_index );
+
+            }
+
+        }
+    }
+}
+
+void DumpDictionary( filter_t *p_filter, const vlc_dictionary_t *p_dict,
+                     bool b_dump_fonts, int i_max_families )
+{
+    char **ppsz_keys = vlc_dictionary_all_keys( p_dict );
+    for( int i = 0; ppsz_keys[ i ]; ++i )
+    {
+        vlc_family_t *p_family = vlc_dictionary_value_for_key( p_dict, ppsz_keys[ i ] );
+        msg_Dbg( p_filter, "Key: %s", ppsz_keys[ i ] );
+        if( p_family )
+            DumpFamily( p_filter, p_family, b_dump_fonts, i_max_families );
+        free( ppsz_keys[ i ] );
+    }
+    free( ppsz_keys );
+}
+
+char* ToLower( const char *psz_src )
+{
+    int i_size = strlen( psz_src ) + 1;
+    char *psz_buffer = malloc( i_size );
+    if( unlikely( !psz_buffer ) )
+        return NULL;
+
+    for( int i = 0; i < i_size; ++i )
+        psz_buffer[ i ] = tolower( psz_src[ i ] );
+
+    return psz_buffer;
+}
+
+char* Generic_Select( filter_t *p_filter, const char* psz_family,
+                      bool b_bold, bool b_italic,
+                      int *i_idx, uni_char_t codepoint )
+{
+
+    filter_sys_t *p_sys = p_filter->p_sys;
+    const vlc_family_t *p_family = NULL;
+    vlc_family_t *p_fallbacks = NULL;
+
+    if( codepoint )
+    {
+        /*
+         * Try regular face of the same family first.
+         * It usually has the best coverage.
+         */
+        const vlc_family_t *p_temp = p_sys->pf_get_family( p_filter, psz_family );
+        if( p_temp && p_temp->p_fonts )
+        {
+            FT_Face p_face = GetFace( p_filter, p_temp->p_fonts );
+            if( p_face && FT_Get_Char_Index( p_face, codepoint ) )
+                p_family = p_temp;
+        }
+
+        /* Try font attachments */
+        if( !p_family )
+        {
+            p_fallbacks = vlc_dictionary_value_for_key( &p_sys->fallback_map,
+                                                        FB_LIST_ATTACHMENTS );
+            if( p_fallbacks )
+                p_family = SearchFallbacks( p_filter, p_fallbacks, codepoint );
+        }
+
+        /* Try system fallbacks */
+        if( !p_family )
+        {
+            p_fallbacks = p_sys->pf_get_fallbacks( p_filter, psz_family, codepoint );
+            if( p_fallbacks )
+                p_family = SearchFallbacks( p_filter, p_fallbacks, codepoint );
+        }
+
+        /* Try the default fallback list, if any */
+        if( !p_family )
+        {
+            p_fallbacks = vlc_dictionary_value_for_key( &p_sys->fallback_map,
+                                                        FB_LIST_DEFAULT );
+            if( p_fallbacks )
+                p_family = SearchFallbacks( p_filter, p_fallbacks, codepoint );
+        }
+
+        if( !p_family )
+            return NULL;
+    }
+
+    if( !p_family )
+        p_family = p_sys->pf_get_family( p_filter, psz_family );
+
+    vlc_font_t *p_font;
+    if( p_family && ( p_font = GetBestFont( p_filter, p_family, b_bold,
+                                            b_italic, codepoint ) ) )
+    {
+        *i_idx = p_font->i_index;
+        return strdup( p_font->psz_fontfile );
+    }
+
+    return File_Select( SYSTEM_DEFAULT_FONT_FILE );
+}
 
 #ifdef HAVE_FONTCONFIG
 void FontConfig_BuildCache( filter_t *p_filter )
@@ -110,7 +465,8 @@ void FontConfig_BuildCache( filter_t *p_filter )
  * \brief Selects a font matching family, bold, italic provided
  ***/
 char* FontConfig_Select( filter_t *p_filter, const char* family,
-                          bool b_bold, bool b_italic, int i_size, int *i_idx )
+                         bool b_bold, bool b_italic,
+                         int *i_idx, uni_char_t codepoint )
 {
     FcResult result = FcResultMatch;
     FcPattern *pat, *p_pat;
@@ -119,6 +475,7 @@ char* FontConfig_Select( filter_t *p_filter, const char* family,
     char *ret = NULL;
     FcConfig* config = NULL;
     VLC_UNUSED(p_filter);
+    VLC_UNUSED(codepoint);
 
     /* Create a pattern and fills it */
     pat = FcPatternCreate();
@@ -129,10 +486,6 @@ char* FontConfig_Select( filter_t *p_filter, const char* family,
     FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
     FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
     FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
-    if( i_size > 0 )
-    {
-        FcPatternAddDouble( pat, FC_SIZE, (double)i_size );
-    }
 
     /* */
     FcDefaultSubstitute( pat );
@@ -259,9 +612,10 @@ static char* GetWindowsFontPath()
 }
 
 char* Win32_Select( filter_t *p_filter, const char* family,
-                           bool b_bold, bool b_italic, int i_size, int *i_idx )
+                    bool b_bold, bool b_italic,
+                    int *i_idx, uni_char_t codepoint )
 {
-    VLC_UNUSED( i_size );
+    VLC_UNUSED( codepoint );
     VLC_UNUSED( i_idx );
     VLC_UNUSED( p_filter );
 
@@ -328,11 +682,12 @@ fail:
 #ifdef __APPLE__
 #if !TARGET_OS_IPHONE
 char* MacLegacy_Select( filter_t *p_filter, const char* psz_fontname,
-                          bool b_bold, bool b_italic, int i_size, int *i_idx )
+                        bool b_bold, bool b_italic,
+                        int *i_idx, uni_char_t codepoint )
 {
     VLC_UNUSED( b_bold );
     VLC_UNUSED( b_italic );
-    VLC_UNUSED( i_size );
+    VLC_UNUSED( codepoint );
     FSRef ref;
     unsigned char path[MAXPATHLEN];
     char * psz_path;
@@ -409,12 +764,13 @@ char* MacLegacy_Select( filter_t *p_filter, const char* psz_fontname,
 #endif
 
 char* Dummy_Select( filter_t *p_filter, const char* psz_font,
-                    bool b_bold, bool b_italic, int i_size, int *i_idx )
+                    bool b_bold, bool b_italic,
+                    int *i_idx, uni_char_t codepoint )
 {
     VLC_UNUSED(p_filter);
     VLC_UNUSED(b_bold);
     VLC_UNUSED(b_italic);
-    VLC_UNUSED(i_size);
+    VLC_UNUSED(codepoint);
     VLC_UNUSED(i_idx);
 
     char *psz_fontname;
diff --git a/modules/text_renderer/platform_fonts.h b/modules/text_renderer/platform_fonts.h
index cff52b1..ce788ec 100644
--- a/modules/text_renderer/platform_fonts.h
+++ b/modules/text_renderer/platform_fonts.h
@@ -9,6 +9,7 @@
  *          Bernie Purcell <bitmap at videolan.org>
  *          Jean-Baptiste Kempf <jb at videolan.org>
  *          Felix Paul Kühne <fkuehne at videolan.org>
+ *          Salah-Eddin Shaban <salshaaban at gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published by
@@ -29,10 +30,28 @@
  * Preamble
  *****************************************************************************/
 
+#ifndef PLATFORM_FONTS_H
+#define PLATFORM_FONTS_H
+
 #ifdef HAVE_CONFIG_H
 # include "config.h"
 #endif
 
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#ifdef __OS2__
+typedef uint16_t uni_char_t;
+# define FREETYPE_TO_UCS    "UCS-2LE"
+#else
+typedef uint32_t uni_char_t;
+# if defined(WORDS_BIGENDIAN)
+#  define FREETYPE_TO_UCS   "UCS-4BE"
+# else
+#  define FREETYPE_TO_UCS   "UCS-4LE"
+# endif
+#endif
+
 /* Default fonts */
 #ifdef __APPLE__
 # define SYSTEM_DEFAULT_FONT_FILE "/Library/Fonts/Arial Unicode.ttf"
@@ -77,28 +96,100 @@
 #define DEFAULT_MONOSPACE_FAMILY SYSTEM_DEFAULT_MONOSPACE_FAMILY
 #endif
 
+typedef struct vlc_font_t vlc_font_t;
+struct vlc_font_t
+{
+    vlc_font_t *p_next;
+    char       *psz_fontfile;
+    int         i_index;
+    bool        b_bold;
+    bool        b_italic;
+    FT_Face     p_face;
+};
+
+typedef struct vlc_family_t vlc_family_t;
+struct vlc_family_t
+{
+    vlc_family_t *p_next;
+    char         *psz_name;
+    vlc_font_t   *p_fonts;
+};
+
+#define FB_LIST_ATTACHMENTS "attachments"
+#define FB_LIST_DEFAULT     "default"
+#define FB_NAME             "fallback"
 
 #ifdef HAVE_FONTCONFIG
 char* FontConfig_Select( filter_t *p_filter, const char* family,
-                          bool b_bold, bool b_italic, int i_size, int *i_idx );
+                         bool b_bold, bool b_italic,
+                         int *i_idx, uni_char_t codepoint );
 void FontConfig_BuildCache( filter_t *p_filter );
 #endif
 
 
 #if defined( _WIN32 ) && !VLC_WINSTORE_APP
 char* Win32_Select( filter_t *p_filter, const char* family,
-                           bool b_bold, bool b_italic, int i_size, int *i_idx );
+                    bool b_bold, bool b_italic,
+                    int *i_idx, uni_char_t codepoint );
 
 #endif /* _WIN32 */
 
 #ifdef __APPLE__
 #if !TARGET_OS_IPHONE
 char* MacLegacy_Select( filter_t *p_filter, const char* psz_fontname,
-                          bool b_bold, bool b_italic, int i_size, int *i_idx );
+                        bool b_bold, bool b_italic,
+                        int *i_idx, uni_char_t codepoint );
 #endif
 #endif
 
 char* Dummy_Select( filter_t *p_filter, const char* family,
-                    bool b_bold, bool b_italic, int i_size, int *i_idx );
+                    bool b_bold, bool b_italic,
+                    int *i_idx, uni_char_t codepoint );
+
+#define File_Select(a) Dummy_Select(NULL, a, 0, 0, NULL, 0)
+
+char* Generic_Select( filter_t *p_filter, const char* family,
+                      bool b_bold, bool b_italic,
+                      int *i_idx, uni_char_t codepoint );
+
+static inline void AppendFont( vlc_font_t **pp_list, vlc_font_t *p_font )
+{
+    while( *pp_list )
+        pp_list = &( *pp_list )->p_next;
+
+    *pp_list = p_font;
+}
+
+static inline void AppendFamily( vlc_family_t **pp_list, vlc_family_t *p_family )
+{
+    while( *pp_list )
+        pp_list = &( *pp_list )->p_next;
+
+    *pp_list = p_family;
+}
+
+vlc_family_t *NewFamily( filter_t *p_filter, const char *psz_family,
+                         vlc_family_t **pp_list, vlc_dictionary_t *p_dict,
+                         const char *psz_key );
+
+/* This function takes ownership of psz_fontfile */
+vlc_font_t *NewFont( char *psz_fontfile, int i_index,
+                     bool b_bold, bool b_italic,
+                     vlc_family_t *p_parent );
+
+void FreeFamiliesAndFonts( vlc_family_t *p_family );
+void FreeFamilies( void *p_families, void *p_obj );
+
+
+vlc_family_t *InitDefaultList( filter_t *p_filter, const char *const *ppsz_default,
+                               int i_size );
+
+void DumpFamily( filter_t *p_filter, const vlc_family_t *p_family,
+                 bool b_dump_fonts, int i_max_families );
+
+void DumpDictionary( filter_t *p_filter, const vlc_dictionary_t *p_dict,
+                     bool b_dump_fonts, int i_max_families );
+
+char* ToLower( const char *psz_src );
 
-#define File_Select(a) Dummy_Select(NULL, a, 0, 0, 0, NULL)
+#endif //PLATFORM_FONTS_H
diff --git a/modules/text_renderer/text_layout.c b/modules/text_renderer/text_layout.c
index f028502..a3386f9 100644
--- a/modules/text_renderer/text_layout.c
+++ b/modules/text_renderer/text_layout.c
@@ -34,7 +34,6 @@
 #endif
 
 #include <vlc_common.h>
-#include <vlc_charset.h>
 #include <vlc_filter.h>
 #include <vlc_text_style.h>
 
@@ -105,6 +104,7 @@ typedef struct paragraph_t
     uni_char_t *p_code_points;            //Unicode code points
     int *pi_glyph_indices;                //Glyph index values within the run's font face
     text_style_t **pp_styles;
+    FT_Face *pp_faces;
     int *pi_run_ids;                      //The run to which each glyph belongs
     glyph_bitmaps_t *p_glyph_bitmaps;
     uint8_t *pi_karaoke_bar;
@@ -225,6 +225,8 @@ static paragraph_t *NewParagraph( filter_t *p_filter,
             malloc( i_size * sizeof( *p_paragraph->pi_glyph_indices ) );
     p_paragraph->pp_styles =
             malloc( i_size * sizeof( *p_paragraph->pp_styles ) );
+    p_paragraph->pp_faces =
+            calloc( i_size, sizeof( *p_paragraph->pp_faces ) );
     p_paragraph->pi_run_ids =
             calloc( i_size, sizeof( *p_paragraph->pi_run_ids ) );
     p_paragraph->p_glyph_bitmaps =
@@ -237,9 +239,9 @@ static paragraph_t *NewParagraph( filter_t *p_filter,
     p_paragraph->i_runs_count = 0;
 
     if( !p_paragraph->p_code_points || !p_paragraph->pi_glyph_indices
-     || !p_paragraph->pp_styles || !p_paragraph->pi_run_ids
-     || !p_paragraph->p_glyph_bitmaps || !p_paragraph->pi_karaoke_bar
-     || !p_paragraph->p_runs )
+     || !p_paragraph->pp_styles || !p_paragraph->pp_faces
+     || !p_paragraph->pi_run_ids|| !p_paragraph->p_glyph_bitmaps
+     || !p_paragraph->pi_karaoke_bar || !p_paragraph->p_runs )
         goto error;
 
     if( p_code_points )
@@ -288,6 +290,7 @@ error:
     if( p_paragraph->p_code_points ) free( p_paragraph->p_code_points );
     if( p_paragraph->pi_glyph_indices ) free( p_paragraph->pi_glyph_indices );
     if( p_paragraph->pp_styles ) free( p_paragraph->pp_styles );
+    if( p_paragraph->pp_faces ) free( p_paragraph->pp_faces );
     if( p_paragraph->pi_run_ids ) free( p_paragraph->pi_run_ids );
     if( p_paragraph->p_glyph_bitmaps ) free( p_paragraph->p_glyph_bitmaps );
     if (p_paragraph->pi_karaoke_bar ) free( p_paragraph->pi_karaoke_bar );
@@ -312,6 +315,7 @@ static void FreeParagraph( paragraph_t *p_paragraph )
     free( p_paragraph->p_glyph_bitmaps );
     free( p_paragraph->pi_karaoke_bar );
     free( p_paragraph->pi_run_ids );
+    free( p_paragraph->pp_faces );
     free( p_paragraph->pp_styles );
     free( p_paragraph->p_code_points );
 
@@ -385,7 +389,8 @@ static int AddRun( filter_t *p_filter,
                    paragraph_t *p_paragraph,
                    int i_start_offset,
                    int i_end_offset,
-                   FT_Face p_face )
+                   FT_Face p_face,
+                   const text_style_t *p_style )
 {
     if( i_start_offset >= i_end_offset
      || i_start_offset < 0 || i_start_offset >= p_paragraph->i_size
@@ -417,9 +422,13 @@ static int AddRun( filter_t *p_filter,
     run_desc_t *p_run = p_paragraph->p_runs + p_paragraph->i_runs_count++;
     p_run->i_start_offset = i_start_offset;
     p_run->i_end_offset = i_end_offset;
-    p_run->p_style = p_paragraph->pp_styles[ i_start_offset ];
     p_run->p_face = p_face;
 
+    if( p_style )
+        p_run->p_style = p_style;
+    else
+        p_run->p_style = p_paragraph->pp_styles[ i_start_offset ];
+
 #ifdef HAVE_HARFBUZZ
     p_run->script = p_paragraph->p_scripts[ i_start_offset ];
     p_run->direction = p_paragraph->p_levels[ i_start_offset ] & 1 ?
@@ -433,6 +442,109 @@ static int AddRun( filter_t *p_filter,
 }
 
 /*
+ * Add a run with font fallback, possibly breaking the run further
+ * into runs of glyphs that end up having the same font face.
+ */
+#ifdef HAVE_FONT_FALLBACK
+static int AddRunWithFallback( filter_t *p_filter, paragraph_t *p_paragraph,
+                               int i_start_offset, int i_end_offset )
+{
+    if( i_start_offset >= i_end_offset
+     || i_start_offset < 0 || i_start_offset >= p_paragraph->i_size
+     || i_end_offset <= 0  || i_end_offset > p_paragraph->i_size )
+    {
+        msg_Err( p_filter,
+                 "AddRunWithFallback() invalid parameters. Paragraph size: %d, "
+                 "Start offset: %d, End offset: %d",
+                 p_paragraph->i_size, i_start_offset, i_end_offset );
+        return VLC_EGENERIC;
+    }
+
+    const text_style_t *p_style = p_paragraph->pp_styles[ i_start_offset ];
+
+    /* Maximum number of faces to try for each run */
+    #define MAX_FACES 5
+    FT_Face pp_faces[ MAX_FACES ] = {0};
+
+    pp_faces[ 0 ] = SelectAndLoadFace( p_filter, p_style, 0 );
+
+    for( int i = i_start_offset; i < i_end_offset; ++i )
+    {
+        int i_index = 0;
+        int i_glyph_index = 0;
+        FT_Face p_face = NULL;
+        do {
+            p_face = pp_faces[ i_index ];
+            if( !p_face )
+                p_face = pp_faces[ i_index ] =
+                     SelectAndLoadFace( p_filter, p_style,
+                                        p_paragraph->p_code_points[ i ] );
+            if( !p_face )
+                continue;
+            i_glyph_index = FT_Get_Char_Index( p_face,
+                                               p_paragraph->p_code_points[ i ] );
+            if( i_glyph_index )
+            {
+                p_paragraph->pp_faces[ i ] = p_face;
+
+                /*
+                 * Move p_face to the beginning of the array. Otherwise strikethrough
+                 * lines can appear segmented, being rendered at a certain height
+                 * through spaces and at a different height through words
+                 */
+                if( i_index > 0 )
+                {
+                    pp_faces[ i_index ] = pp_faces[ 0 ];
+                    pp_faces[ 0 ] = p_face;
+                }
+            }
+
+        } while( i_glyph_index == 0 && ++i_index < MAX_FACES );
+    }
+
+    int i_run_start = i_start_offset;
+    for( int i = i_start_offset; i <= i_end_offset; ++i )
+    {
+        if( i == i_end_offset
+         || p_paragraph->pp_faces[ i_run_start ] != p_paragraph->pp_faces[ i ] )
+        {
+            if( AddRun( p_filter, p_paragraph, i_run_start, i,
+                        p_paragraph->pp_faces[ i_run_start ], NULL ) )
+                return VLC_EGENERIC;
+
+            i_run_start = i;
+        }
+    }
+
+    return VLC_SUCCESS;
+}
+#endif
+
+static bool FaceStyleEquals( filter_t *p_filter, const text_style_t *p_style1,
+                             const text_style_t *p_style2 )
+{
+    if( !p_style1 || !p_style2 )
+        return false;
+    if( p_style1 == p_style2 )
+        return true;
+
+    const int i_style_mask = STYLE_BOLD | STYLE_ITALIC | STYLE_HALFWIDTH;
+
+    const char *psz_fontname1 = p_style1->i_style_flags & STYLE_MONOSPACED
+                              ? p_style1->psz_monofontname : p_style1->psz_fontname;
+
+    const char *psz_fontname2 = p_style2->i_style_flags & STYLE_MONOSPACED
+                              ? p_style2->psz_monofontname : p_style2->psz_fontname;
+
+    const int i_size1 = ConvertToLiveSize( p_filter, p_style1 );
+    const int i_size2 = ConvertToLiveSize( p_filter, p_style2 );
+
+    return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask)
+         && i_size1 == i_size2
+         && !strcasecmp( psz_fontname1, psz_fontname2 );
+}
+
+/*
  * Segment a paragraph into runs
  */
 static int ItemizeParagraph( filter_t *p_filter, paragraph_t *p_paragraph )
@@ -460,16 +572,14 @@ static int ItemizeParagraph( filter_t *p_filter, paragraph_t *p_paragraph )
             || last_script != p_paragraph->p_scripts[ i ]
             || last_level != p_paragraph->p_levels[ i ]
 #endif
-            || ( p_paragraph->pp_styles[ i ] != NULL && (
-                p_last_style->i_font_size != p_paragraph->pp_styles[ i ]->i_font_size
-                || ( ( p_last_style->i_style_flags
-                     ^ p_paragraph->pp_styles[ i ]->i_style_flags )
-                     & STYLE_HALFWIDTH )
-                ||!FaceStyleEquals( p_last_style, p_paragraph->pp_styles[ i ] ) )
-            )
-        )
+            || !FaceStyleEquals( p_filter, p_last_style, p_paragraph->pp_styles[ i ] ) )
         {
-            int i_ret = AddRun( p_filter, p_paragraph, i_last_run_start, i, 0 );
+            int i_ret;
+#ifdef HAVE_FONT_FALLBACK
+            i_ret = AddRunWithFallback( p_filter, p_paragraph, i_last_run_start, i );
+#else
+            i_ret = AddRun( p_filter, p_paragraph, i_last_run_start, i, NULL, NULL );
+#endif
             if( i_ret )
                 return i_ret;
 
@@ -516,17 +626,20 @@ static int ShapeParagraphHarfBuzz( filter_t *p_filter,
     {
         run_desc_t *p_run = p_paragraph->p_runs + i;
         const text_style_t *p_style = p_run->p_style;
-        const int i_live_size = ConvertToLiveSize( p_filter, p_style );
 
         /*
-         * When using HarfBuzz, this is where font faces are loaded.
-         * In the other two paths (shaping with FriBidi or no
-         * shaping at all), faces are loaded in LoadGlyphs()
+         * With HarfBuzz and no font fallback, this is where font faces
+         * are loaded. In the other two paths (shaping with FriBidi or no
+         * shaping at all), faces are loaded in LoadGlyphs().
+         *
+         * If we have font fallback, font faces in all paths will be
+         * loaded in AddRunWithFallback(), except for runs of codepoints
+         * for which no font could be found.
          */
         FT_Face p_face = 0;
         if( !p_run->p_face )
         {
-            p_face = LoadFace( p_filter, p_style, i_live_size );
+            p_face = SelectAndLoadFace( p_filter, p_style, 0 );
             if( !p_face )
             {
                 p_face = p_sys->p_face;
@@ -637,7 +750,7 @@ static int ShapeParagraphHarfBuzz( filter_t *p_filter,
             ++i_index;
         }
         if( AddRun( p_filter, p_new_paragraph, i_index - p_run->i_glyph_count,
-                    i_index, p_run->p_face ) )
+                    i_index, p_run->p_face, p_run->p_style ) )
             goto error;
     }
 
@@ -785,7 +898,7 @@ static int LoadGlyphs( filter_t *p_filter, paragraph_t *p_paragraph,
         FT_Face p_face = 0;
         if( !p_run->p_face )
         {
-            p_face = LoadFace( p_filter, p_style, i_live_size );
+            p_face = SelectAndLoadFace( p_filter, p_style, 0 );
             if( !p_face )
             {
                 /* Uses the default font and style */
-- 
1.9.1



More information about the vlc-devel mailing list