[vlc-devel] [RFC PATCH] freetype: font fallback using fontconfig

Salah-Eddin Shaban salshaaban at gmail.com
Wed Jul 8 23:51:25 CEST 2015


Hello,

This is one way to implement font fallback using fontconfig. Platforms
that do not use fontconfig will require that we implement a custom
font database, which should not be very difficult.

Here I am doing the fallback on the codepoint level, which is the
method used by Qt and Libass. An alternative method is to try the
shaping process with the requested font first then for any missing
glyphs create new sub-runs, try shaping them again with different
fonts until shaping completes with no missing glyphs, but I think that
would be too slow.

This thread explains the difference a bit more:
http://lists.freedesktop.org/archives/harfbuzz/2015-May/004854.html

A few notes:

- We can no longer pass NULL for FcConfig to fontconfig functions.
This is because we are now adding font attachments to the FcConfig,
and passing NULL causes the default config to be used. This means that
later instances of the freetype module will use the same FcConfig,
which will contain stale records about font attachments in previous
media files.

- The way font attachments are processed here might need tweaking.
Judging from one comment in Libass's source, using the language as a
criteria in a matching pattern might cause some font attachments to be
ignored by fontconfig, because they lack certain characters to qualify
as covering a certain language. If this is indeed the case, we can
probably have another FcConfig in filter_sys_t devoted solely to font
attachments. We create this one using FcInitLoadConfig or
FcConfigCreate so that system fonts will not be included in it. And in
FontConfig_Select we search that first, without adding the language to
the matching pattern.
Application fonts in fontconfig are explained better here:
http://mces.blogspot.com/2015/05/how-to-use-custom-application-fonts.html

- I'm not sure if we really need to pass a language to LoadFace. I did
it at first to avoid calling LoadFace for each codepoint, but that was
happening anyway when HarfBuzz was disabled, and it was too slow, so I
had to think of something else. Now each run tries already loaded
faces first, and only calls LoadFace if none of them has the required
codepoint. This seems to work very well. So languageFromScript can
probably be removed altogether, along with the language parameter to
LoadFace. I left them for now to do some more testing. Sometimes
calling LoadFace without a language causes fontconfig to return a
different family even when no fallback is required. For example, if
LoadFace is called with the family "Droid Arabic Naskh", with language
and codepoint set to 0, it returns DroidSans.ttf. If language is set
to "ar" it correctly returns DroidNaskh-Regular.ttf. Apparently using
FcPatternAddWeak instead of FcPatternAddString to add the family has
something to do with it. This was done so that the language will take
precedence over the family. Anyway, it can be reverted, in which case
the font attachments issue mentioned above should be fixed as well.

- Also in one Libass comment, it is said that fonts in SSA subtitle
files are sometimes referenced using their full name (family + style).
I'm not sure whether it's something we too should handle.

- About creating a custom font database, this should probably be done
the Qt way, only simpler here since our requirements are simpler. When
building the database we use the OS2 table in font files to know what
Unicode scripts (or writing systems) are supported by each font (no
scanning of character maps, so this is much faster). Then when some
font is requested we try the family, any other fallback families
defined for it (which could possibly be configured by the user), and
all fonts that support the required Unicode script, and return the
first one containing the required codepoint. Some relevant code is in:

qt_registerFont()
QBasicFontDatabase::populateFontDatabase()
QBasicFontDatabase::addTTFile()
QPlatformFontDatabase::fontEngineMulti()

- An alternative to creating a font database for platforms that
currently use Dummy_Select is to create a "smart" Dummy_Select with a
hard-coded map of scripts to font files. Something also has to be done
about fonts that lack neutral characters like parentheses and
punctuation but that should be trivial.


On 7/9/15, Salah-Eddin Shaban <salshaaban at gmail.com> wrote:
> ---
>  modules/text_renderer/freetype.c       | 237
> ++++++++++++++++++++++++++-------
>  modules/text_renderer/freetype.h       |  29 ++--
>  modules/text_renderer/platform_fonts.c |  41 +++++-
>  modules/text_renderer/platform_fonts.h |  16 ++-
>  modules/text_renderer/text_layout.c    | 210 ++++++++++++++++++++++++++++-
>  5 files changed, 454 insertions(+), 79 deletions(-)
>
> diff --git a/modules/text_renderer/freetype.c
> b/modules/text_renderer/freetype.c
> index 2773779..3958c89 100644
> --- a/modules/text_renderer/freetype.c
> +++ b/modules/text_renderer/freetype.c
> @@ -67,12 +67,13 @@
>
>  /* FontConfig */
>  #ifdef HAVE_FONTCONFIG
> +# include <fontconfig/fontconfig.h>
> +# include <fontconfig/fcfreetype.h>
>  # define HAVE_GET_FONT_BY_FAMILY_NAME
>  #endif
>
>  #include <assert.h>
>
> -#include "text_renderer.h"
>  #include "platform_fonts.h"
>  #include "freetype.h"
>  #include "text_layout.h"
> @@ -297,6 +298,35 @@ 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;
> +
> +#ifdef HAVE_FONTCONFIG
> +            char buffer[ 10 ];
> +            FT_Face p_face;
> +            int i_num_faces = 0;
> +            int i_index = 0;
> +            snprintf( buffer, sizeof( buffer ), ":/%d",
> p_sys->i_font_attachments - 1 );
> +
> +            do {
> +                if( FT_New_Memory_Face( p_sys->p_library,
> p_attach->p_data,
> +                                        p_attach->i_data, i_index, &p_face
> ) )
> +                    continue;
> +
> +                i_num_faces = p_face->num_faces;
> +
> +                FcPattern *p_pattern =
> +                    FcFreeTypeQueryFace( p_face,
> +                                         ( const FcChar8* )buffer,
> i_index,
> +                                         FcConfigGetBlanks( p_sys->p_config
> ) );
> +
> +                FcFontSet *p_set = FcConfigGetFonts( p_sys->p_config,
> FcSetSystem );
> +
> +                if( !p_pattern || !p_set || !FcFontSetAdd( p_set, p_pattern
> ) )
> +                    msg_Err( p_filter,
> +                             "Error adding font attachment to font
> database" );
> +
> +                FT_Done_Face( p_face );
> +            } while( ++i_index < i_num_faces );
> +#endif
>          }
>          else
>          {
> @@ -863,6 +893,7 @@ static inline int RenderAXYZ( filter_t *p_filter,
>
>
>
> +#ifndef HAVE_FONTCONFIG
>  static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t
> *p_style )
>  {
>      for( int k = 0; k < p_sys->i_font_attachments; k++ )
> @@ -894,6 +925,7 @@ static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys,
> const text_style_t *p_styl
>      }
>      return NULL;
>  }
> +#endif
>
>  static xml_reader_t *GetXMLReader( filter_t *p_filter, stream_t *p_sub )
>  {
> @@ -1275,6 +1307,7 @@ static int Create( vlc_object_t *p_this )
>      p_sys->style.psz_monofontname = psz_monofontfamily;
>
>  #ifdef HAVE_FONTCONFIG
> +    p_sys->p_config = FcInitLoadConfigAndFonts();
>      p_sys->pf_select = FontConfig_Select;
>      FontConfig_BuildCache( p_filter );
>  #elif defined( __APPLE__ )
> @@ -1289,10 +1322,11 @@ static int Create( vlc_object_t *p_this )
>
>      /* */
>      psz_fontfile = p_sys->pf_select( p_filter, psz_fontname, false, false,
> -                                      p_sys->i_default_font_size,
> &fontindex );
> +                                     p_sys->i_default_font_size,
> &fontindex,
> +                                     NULL, 0 );
>      psz_monofontfile = p_sys->pf_select( p_filter, psz_monofontfamily,
> false,
> -                                          false,
> p_sys->i_default_font_size,
> -                                          &monofontindex );
> +                                         false,
> p_sys->i_default_font_size,
> +                                         &monofontindex, NULL, 0 );
>      msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontname,
> psz_fontfile );
>      msg_Dbg( p_filter, "Using %s as mono-font from file %s",
> psz_monofontfamily, psz_monofontfile );
>
> @@ -1305,11 +1339,7 @@ static int Create( vlc_object_t *p_this )
>      if( Init_FT( p_this, psz_fontfile, fontindex, f_outline_thickness ) !=
> VLC_SUCCESS )
>          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;
> +    vlc_dictionary_init( &p_sys->faces_cache, 20 );
>
>      p_sys->pp_font_attachments = NULL;
>      p_sys->i_font_attachments = 0;
> @@ -1346,6 +1376,17 @@ static void Destroy_FT( vlc_object_t *p_this )
>      FT_Done_FreeType( p_sys->p_library );
>  }
>
> +static void FreeFace( void *p_face, void *p_obj )
> +{
> +
> +    VLC_UNUSED( p_obj );
> +
> +    //filter_t *p_filter = ( filter_t * ) p_obj;
> +    //msg_Dbg( p_filter, "FT_Done_Face: 0x%lx", ( long ) p_face );
> +
> +    FT_Done_Face( ( FT_Face ) p_face );
> +}
> +
> /*****************************************************************************
>   * Destroy: destroy Clone video thread output method
>
> *****************************************************************************
> @@ -1356,14 +1397,7 @@ 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 );
> +    vlc_dictionary_clear( &p_sys->faces_cache, FreeFace, p_filter );
>
>      if( p_sys->pp_font_attachments )
>      {
> @@ -1378,23 +1412,43 @@ static void Destroy( vlc_object_t *p_this )
>      free( p_sys->style.psz_monofontname );
>
>      Destroy_FT( p_this );
> +
> +#ifdef HAVE_FONTCONFIG
> +    FcConfigDestroy( p_sys->p_config );
> +#endif
> +
>      free( p_sys );
>  }
>
> +#ifndef HAVE_FONTCONFIG
>  FT_Face LoadFace( filter_t *p_filter,
> -                  const text_style_t *p_style )
> +                  const text_style_t *p_style,
> +                  const char *language,
> +                  uni_char_t codepoint )
>  {
> +    VLC_UNUSED(language);
> +    VLC_UNUSED(codepoint);
> +
>      filter_sys_t *p_sys = p_filter->p_sys;
> +    char buffer[ 128 ];
> +
> +    const char *p_bold = (p_style->i_style_flags & STYLE_BOLD) ? " Bold" :
> "";
> +    const char *p_italic = (p_style->i_style_flags & STYLE_ITALIC) ? "
> Italic" : "";
> +    int i_font_width = (p_style->i_style_flags & STYLE_HALFWIDTH) ?
> +                        p_style->i_font_size / 2 : p_style->i_font_size;
> +    snprintf( buffer, sizeof( buffer ), "%s%s%s - %d - %d",
> +              p_style->psz_fontname,
> +              p_bold, p_italic,
> +              p_style->i_font_size, i_font_width );
> +
> +    FT_Face p_face = vlc_dictionary_value_for_key( &p_sys->faces_cache,
> +                                                   buffer );
> +    if( p_face != kVLCDictionaryNotFound )
> +        return p_face;
>
> -    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 == p_style->i_font_size
> -         && !( ( p_cache->p_styles[ i ].i_style_flags ^
> p_style->i_style_flags ) & STYLE_HALFWIDTH ) )
> -            return p_cache->p_faces[ i ];
>
>      /* Look for a match amongst our attachments first */
> -    FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
> +    p_face = LoadEmbeddedFace( p_sys, p_style );
>
>      /* Load system wide font otheriwse */
>      if( !p_face )
> @@ -1407,7 +1461,8 @@ FT_Face LoadFace( filter_t *p_filter,
>                                               (p_style->i_style_flags &
> STYLE_BOLD) != 0,
>                                               (p_style->i_style_flags &
> STYLE_ITALIC) != 0,
>                                               -1,
> -                                             &i_idx );
> +                                             &i_idx,
> +                                             NULL, 0 );
>          else
>              psz_fontfile = NULL;
>
> @@ -1443,9 +1498,6 @@ FT_Face LoadFace( filter_t *p_filter,
>          return NULL;
>      }
>
> -    int i_font_width = p_style->i_style_flags & STYLE_HALFWIDTH
> -                     ? p_style->i_font_size / 2 : p_style->i_font_size;
> -
>      if( FT_Set_Pixel_Sizes( p_face, i_font_width, p_style->i_font_size ) )
>      {
>          msg_Err( p_filter,
> @@ -1454,36 +1506,119 @@ FT_Face LoadFace( filter_t *p_filter,
>          return NULL;
>      }
>
> -    if( p_cache->i_faces_count == p_cache->i_cache_size )
> +    //msg_Dbg( p_filter, "LoadFace: adding face to cache: 0x%lx %s", ( long
> ) p_face, buffer );
> +
> +    vlc_dictionary_insert( &p_sys->faces_cache, buffer, p_face );
> +
> +    return p_face;
> +}
> +
> +#else
> +
> +FT_Face LoadFace( filter_t *p_filter,
> +                  const text_style_t *p_style,
> +                  const char *language,
> +                  uni_char_t codepoint )
> +{
> +    //msg_Dbg( p_filter, "LoadFace: font requested: %s, language: %s,
> codepoint: %x",
> +    //         p_style->psz_fontname, language, codepoint );
> +
> +    filter_sys_t *p_sys = p_filter->p_sys;
> +    char buffer[ 128 ];
> +
> +    int  i_idx = 0;
> +    char *psz_fontfile = NULL;
> +    if( p_sys->pf_select )
> +        psz_fontfile = p_sys->pf_select( p_filter,
> +                                         p_style->psz_fontname,
> +                                         (p_style->i_style_flags &
> STYLE_BOLD) != 0,
> +                                         (p_style->i_style_flags &
> STYLE_ITALIC) != 0,
> +                                         -1,
> +                                         &i_idx,
> +                                         language, codepoint );
> +    else
> +        psz_fontfile = NULL;
> +
> +    if( !psz_fontfile )
> +        return NULL;
> +
> +    if( *psz_fontfile == '\0' )
>      {
> -        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;
> -        }
> +        msg_Warn( p_filter,
> +                  "We were not able to find a matching font: \"%s\" (%s
> %s),"
> +                  " so using default font",
> +                  p_style->psz_fontname,
> +                  (p_style->i_style_flags & STYLE_BOLD)   ? "Bold" : "",
> +                  (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : ""
> );
> +        free( psz_fontfile );
> +        return NULL;
> +    }
> +
> +    //msg_Dbg( p_filter, "LoadFace: font returned: %s, face index: %d",
> +    //         psz_fontfile, i_idx );
>
> -        p_cache->p_faces = p_new_faces;
> +    int i_font_width = (p_style->i_style_flags & STYLE_HALFWIDTH) ?
> +            p_style->i_font_size / 2 : p_style->i_font_size;
> +    snprintf( buffer, sizeof( buffer ), "%s - %d - %d - %d",
> +              psz_fontfile,
> +              i_idx,
> +              p_style->i_font_size, i_font_width );
>
> -        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_Face p_face = vlc_dictionary_value_for_key( &p_sys->faces_cache,
> +                                                   buffer );
> +    if( p_face != kVLCDictionaryNotFound )
> +    {
> +        free( psz_fontfile );
> +        return p_face;
> +    }
> +
> +    if( psz_fontfile[0] == ':' && psz_fontfile[1] == '/' )
> +    {
> +        int i_attach = atoi( psz_fontfile + 2 );
> +        if( i_attach < 0 || i_attach >= p_sys->i_font_attachments )
>          {
> -            FT_Done_Face( p_face );
> -            return NULL;
> +            msg_Err( p_filter, "Invalid font attachment index" );
> +            p_face = NULL;
> +        }
> +        else
> +        {
> +            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;
>          }
> +    }
> +    else
> +        if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face )
> )
> +            p_face = NULL;
> +
> +    free( psz_fontfile );
>
> -        p_cache->p_styles = p_new_styles;
> -        p_cache->i_cache_size *= 2;
> +    if( !p_face )
> +        return NULL;
> +
> +    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.
> +         */
> +        FT_Done_Face( p_face );
> +        return NULL;
>      }
>
> -    text_style_t *p_face_style = p_cache->p_styles +
> p_cache->i_faces_count;
> -    p_face_style->i_font_size = p_style->i_font_size;
> -    p_face_style->i_style_flags = p_style->i_style_flags;
> -    p_face_style->psz_fontname = strdup( p_style->psz_fontname );
> -    p_cache->p_faces[ p_cache->i_faces_count ] = p_face;
> -    ++p_cache->i_faces_count;
> +    if( FT_Set_Pixel_Sizes( p_face, i_font_width, p_style->i_font_size ) )
> +    {
> +        msg_Err( p_filter,
> +                 "Failed to set font size to %d", p_style->i_font_size );
> +        FT_Done_Face( p_face );
> +        return NULL;
> +    }
> +
> +    //msg_Dbg( p_filter, "LoadFace: adding face to cache: 0x%lx %s", ( long
> ) p_face, buffer );
> +
> +    vlc_dictionary_insert( &p_sys->faces_cache, buffer, p_face );
>
>      return p_face;
>  }
> +
> +#endif
> diff --git a/modules/text_renderer/freetype.h
> b/modules/text_renderer/freetype.h
> index d486773..beb1489 100644
> --- a/modules/text_renderer/freetype.h
> +++ b/modules/text_renderer/freetype.h
> @@ -25,13 +25,15 @@
>   * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
>
> *****************************************************************************/
>
> -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 <vlc_arrays.h>
> +
> +#include <ft2build.h>
> +#include FT_FREETYPE_H
> +#include FT_STROKER_H
> +
> +#ifdef HAVE_FONTCONFIG
> +#include <fontconfig/fontconfig.h>
> +#endif
>
> /*****************************************************************************
>   * filter_sys_t: freetype local data
> @@ -59,12 +61,16 @@ struct filter_sys_t
>      int                  i_font_attachments;
>
>      /* Font faces cache */
> -    faces_cache_t  faces_cache;
> +    vlc_dictionary_t     faces_cache;
>
>      char * (*pf_select) (filter_t *, const char* family,
> -                               bool bold, bool italic, int size,
> -                               int *index);
> +                         bool bold, bool italic, int size,
> +                         int *index, const char *language,
> +                         uni_char_t codepoint);
>
> +#ifdef HAVE_FONTCONFIG
> +    FcConfig *p_config;
> +#endif
>  };
>
>  #define FT_FLOOR(X)     ((X & -64) >> 6)
> @@ -73,4 +79,5 @@ struct filter_sys_t
>   #define FT_MulFix(v, s) (((v)*(s))>>16)
>  #endif
>
> -FT_Face LoadFace( filter_t *p_filter, const text_style_t *p_style );
> +FT_Face LoadFace( filter_t *p_filter, const text_style_t *p_style,
> +                  const char *language, uni_char_t codepoint );
> diff --git a/modules/text_renderer/platform_fonts.c
> b/modules/text_renderer/platform_fonts.c
> index 7869dba..543466b 100644
> --- a/modules/text_renderer/platform_fonts.c
> +++ b/modules/text_renderer/platform_fonts.c
> @@ -49,6 +49,7 @@
>
>  /* Win32 GDI */
>  #ifdef _WIN32
> +# undef HAVE_FONTCONFIG
>  # include <windows.h>
>  # include <shlobj.h>
>  # include <vlc_charset.h>                                     /* FromT */
> @@ -60,6 +61,7 @@
>  #endif
>
>  #include "platform_fonts.h"
> +#include "freetype.h"
>
>  #ifdef HAVE_FONTCONFIG
>  void FontConfig_BuildCache( filter_t *p_filter )
> @@ -110,22 +112,37 @@ 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_size, int
> *i_idx,
> +                         const char *language, uni_char_t codepoint )
>  {
>      FcResult result = FcResultMatch;
>      FcPattern *pat, *p_pat;
>      FcChar8* val_s;
>      FcBool val_b;
>      char *ret = NULL;
> -    FcConfig* config = NULL;
> -    VLC_UNUSED(p_filter);
> +
> +    filter_sys_t *p_sys = p_filter->p_sys;
> +    FcConfig* config = p_sys->p_config;
>
>      /* Create a pattern and fills it */
>      pat = FcPatternCreate();
>      if (!pat) return NULL;
>
>      /* */
> -    FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
> +    if ( language )
> +        FcPatternAddString( pat, FC_LANG, ( const FcChar8* ) language );
> +    if ( codepoint )
> +    {
> +        FcCharSet *cs = FcCharSetCreate();
> +        FcCharSetAddChar( cs, codepoint );
> +        FcPatternAddCharSet( pat, FC_CHARSET, cs );
> +        FcCharSetDestroy( cs );
> +    }
> +    FcValue value;
> +    value.type = FcTypeString;
> +    value.u.s = (const FcChar8*) family;
> +    FcPatternAddWeak( pat, FC_FAMILY, value, true );
> +
>      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 );
> @@ -259,11 +276,14 @@ 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_size, int *i_idx,
> +                    const char *language, uni_char_t codepoint )
>  {
>      VLC_UNUSED( i_size );
>      VLC_UNUSED( i_idx );
>      VLC_UNUSED( p_filter );
> +    VLC_UNUSED( language );
> +    VLC_UNUSED( codepoint );
>
>      if( !family || strlen( family ) < 1 )
>          goto fail;
> @@ -328,11 +348,15 @@ 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_size, int
> *i_idx,
> +                        const char *language, uni_char_t codepoint )
>  {
>      VLC_UNUSED( b_bold );
>      VLC_UNUSED( b_italic );
>      VLC_UNUSED( i_size );
> +    VLC_UNUSED( language );
> +    VLC_UNUSED( codepoint );
> +
>      FSRef ref;
>      unsigned char path[MAXPATHLEN];
>      char * psz_path;
> @@ -409,13 +433,16 @@ 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_size, int *i_idx,
> +                    const char *language, uni_char_t codepoint )
>  {
>      VLC_UNUSED(p_filter);
>      VLC_UNUSED(b_bold);
>      VLC_UNUSED(b_italic);
>      VLC_UNUSED(i_size);
>      VLC_UNUSED(i_idx);
> +    VLC_UNUSED(language);
> +    VLC_UNUSED(codepoint);
>
>      char *psz_fontname;
>  # if defined( _WIN32 ) && !VLC_WINSTORE_APP
> diff --git a/modules/text_renderer/platform_fonts.h
> b/modules/text_renderer/platform_fonts.h
> index cff52b1..de58f71 100644
> --- a/modules/text_renderer/platform_fonts.h
> +++ b/modules/text_renderer/platform_fonts.h
> @@ -33,6 +33,8 @@
>  # include "config.h"
>  #endif
>
> +#include "text_renderer.h"
> +
>  /* Default fonts */
>  #ifdef __APPLE__
>  # define SYSTEM_DEFAULT_FONT_FILE "/Library/Fonts/Arial Unicode.ttf"
> @@ -80,25 +82,29 @@
>
>  #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_size, int
> *i_idx,
> +                         const char *language, 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_size, int *i_idx,
> +                    const char *language, 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_size, int
> *i_idx,
> +                        const char *language, 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_size, int *i_idx,
> +                    const char *language, uni_char_t codepoint );
>
> -#define File_Select(a) Dummy_Select(NULL, a, 0, 0, 0, NULL)
> +#define File_Select(a) Dummy_Select(NULL, a, 0, 0, 0, NULL, NULL, 0)
> diff --git a/modules/text_renderer/text_layout.c
> b/modules/text_renderer/text_layout.c
> index bb6019b..da0b981 100644
> --- a/modules/text_renderer/text_layout.c
> +++ b/modules/text_renderer/text_layout.c
> @@ -55,6 +55,10 @@
>  # include <hb-ft.h>
>  #endif
>
> +#if defined(__APPLE__) || defined(_WIN32)
> +# undef HAVE_FONTCONFIG
> +#endif
> +
>  #include "text_renderer.h"
>  #include "text_layout.h"
>  #include "freetype.h"
> @@ -105,6 +109,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;
> @@ -223,6 +228,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 =
> @@ -235,9 +242,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 )
> @@ -286,6 +293,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 );
> @@ -310,6 +318,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 );
>
> @@ -431,6 +440,193 @@ static int AddRun( filter_t *p_filter,
>  }
>
>  /*
> + * This is based on the list in Qt's qfontconfigdatabase.cpp
> + */
> +#if defined( HAVE_HARFBUZZ ) && defined( HAVE_FONTCONFIG )
> +static const char * languageFromScript( hb_script_t script )
> +{
> +    switch( script )
> +    {
> +    case HB_SCRIPT_LATIN: return "en";
> +    case HB_SCRIPT_GREEK: return "el";
> +    case HB_SCRIPT_CYRILLIC: return "ru";
> +    case HB_SCRIPT_ARMENIAN: return "hy";
> +    case HB_SCRIPT_HEBREW: return "he";
> +    case HB_SCRIPT_ARABIC: return "ar";
> +    case HB_SCRIPT_SYRIAC: return "syr";
> +    case HB_SCRIPT_THAANA: return "dv";
> +    case HB_SCRIPT_DEVANAGARI: return "hi";
> +    case HB_SCRIPT_BENGALI: return "bn";
> +    case HB_SCRIPT_GURMUKHI: return "pa";
> +    case HB_SCRIPT_GUJARATI: return "gu";
> +    case HB_SCRIPT_ORIYA: return "or";
> +    case HB_SCRIPT_TAMIL: return "ta";
> +    case HB_SCRIPT_TELUGU: return "te";
> +    case HB_SCRIPT_KANNADA: return "kn";
> +    case HB_SCRIPT_MALAYALAM: return "ml";
> +    case HB_SCRIPT_SINHALA: return "si";
> +    case HB_SCRIPT_THAI: return "th";
> +    case HB_SCRIPT_LAO: return "lo";
> +    case HB_SCRIPT_TIBETAN: return "bo";
> +    case HB_SCRIPT_MYANMAR: return "my";
> +    case HB_SCRIPT_GEORGIAN: return "ka";
> +    case HB_SCRIPT_HANGUL: return "ko";
> +    case HB_SCRIPT_ETHIOPIC: return "am";
> +    case HB_SCRIPT_CHEROKEE: return "chr";
> +    case HB_SCRIPT_CANADIAN_ABORIGINAL: return "cr";
> +    case HB_SCRIPT_OGHAM: return "sga";
> +    case HB_SCRIPT_RUNIC: return "non";
> +    case HB_SCRIPT_KHMER: return "km";
> +    case HB_SCRIPT_MONGOLIAN: return "mn";
> +    case HB_SCRIPT_HIRAGANA: return "ja";
> +    case HB_SCRIPT_KATAKANA: return "ja";
> +    case HB_SCRIPT_BOPOMOFO: return "zh-TW";
> +    //case HB_SCRIPT_HAN:
> +    case HB_SCRIPT_YI: return "ii";
> +    case HB_SCRIPT_OLD_ITALIC: return "ett";
> +    case HB_SCRIPT_GOTHIC: return "got";
> +    case HB_SCRIPT_DESERET: return "en";
> +    case HB_SCRIPT_TAGALOG: return "fil";
> +    case HB_SCRIPT_HANUNOO: return "hnn";
> +    case HB_SCRIPT_BUHID: return "bku";
> +    case HB_SCRIPT_TAGBANWA: return "tbw";
> +    case HB_SCRIPT_COPTIC: return "cop";
> +    case HB_SCRIPT_LIMBU: return "lif";
> +    case HB_SCRIPT_TAI_LE: return "tdd";
> +    case HB_SCRIPT_LINEAR_B: return "grc";
> +    case HB_SCRIPT_UGARITIC: return "uga";
> +    case HB_SCRIPT_SHAVIAN: return "en";
> +    case HB_SCRIPT_OSMANYA: return "so";
> +    case HB_SCRIPT_CYPRIOT: return "grc";
> +    //case HB_SCRIPT_BRAILLE
> +    case HB_SCRIPT_BUGINESE: return "bug";
> +    case HB_SCRIPT_NEW_TAI_LUE: return "khb";
> +    case HB_SCRIPT_GLAGOLITIC: return "cu";
> +    case HB_SCRIPT_TIFINAGH: return "shi";
> +    case HB_SCRIPT_SYLOTI_NAGRI: return "syl";
> +    case HB_SCRIPT_OLD_PERSIAN: return "peo";
> +    case HB_SCRIPT_KHAROSHTHI: return "pra";
> +    case HB_SCRIPT_BALINESE: return "ban";
> +    case HB_SCRIPT_CUNEIFORM: return "akk";
> +    case HB_SCRIPT_PHOENICIAN: return "phn";
> +    case HB_SCRIPT_PHAGS_PA: return "lzh";
> +    case HB_SCRIPT_NKO: return "man";
> +    case HB_SCRIPT_SUNDANESE: return "su";
> +    case HB_SCRIPT_LEPCHA: return "lep";
> +    case HB_SCRIPT_OL_CHIKI: return "sat";
> +    case HB_SCRIPT_VAI: return "vai";
> +    case HB_SCRIPT_SAURASHTRA: return "saz";
> +    case HB_SCRIPT_KAYAH_LI: return "eky";
> +    case HB_SCRIPT_REJANG: return "rej";
> +    case HB_SCRIPT_LYCIAN: return "xlc";
> +    case HB_SCRIPT_CARIAN: return "xcr";
> +    case HB_SCRIPT_LYDIAN: return "xld";
> +    case HB_SCRIPT_CHAM: return "cjm";
> +    case HB_SCRIPT_TAI_THAM: return "nod";
> +    case HB_SCRIPT_TAI_VIET: return "blt";
> +    case HB_SCRIPT_AVESTAN: return "ae";
> +    case HB_SCRIPT_EGYPTIAN_HIEROGLYPHS: return "egy";
> +    case HB_SCRIPT_SAMARITAN: return "smp";
> +    case HB_SCRIPT_LISU: return "lis";
> +    case HB_SCRIPT_BAMUM: return "bax";
> +    case HB_SCRIPT_JAVANESE: return "jv";
> +    case HB_SCRIPT_MEETEI_MAYEK: return "mni";
> +    case HB_SCRIPT_IMPERIAL_ARAMAIC: return "arc";
> +    case HB_SCRIPT_OLD_SOUTH_ARABIAN: return "xsa";
> +    case HB_SCRIPT_INSCRIPTIONAL_PARTHIAN: return "xpr";
> +    case HB_SCRIPT_INSCRIPTIONAL_PAHLAVI: return "pal";
> +    case HB_SCRIPT_OLD_TURKIC: return "otk";
> +    case HB_SCRIPT_KAITHI: return "bh";
> +    case HB_SCRIPT_BATAK: return "bbc";
> +    case HB_SCRIPT_BRAHMI: return "pra";
> +    case HB_SCRIPT_MANDAIC: return "myz";
> +    case HB_SCRIPT_CHAKMA: return "ccp";
> +    case HB_SCRIPT_MEROITIC_CURSIVE: return "xmr";
> +    case HB_SCRIPT_MEROITIC_HIEROGLYPHS: return "xmr";
> +    case HB_SCRIPT_MIAO: return "hmd";
> +    case HB_SCRIPT_SHARADA: return "sa";
> +    case HB_SCRIPT_SORA_SOMPENG: return "srb";
> +    case HB_SCRIPT_TAKRI: return "doi";
> +
> +    default: return NULL;
> +    }
> +}
> +#endif
> +
> +/*
> + * 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_FONTCONFIG
> +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;
> +    }
> +
> +    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};
> +
> +#ifdef HAVE_HARFBUZZ
> +    pp_faces[ 0 ] =
> +        LoadFace( p_filter, p_style,
> +                  languageFromScript( p_paragraph->p_scripts[
> i_start_offset ] ),
> +                  0 );
> +#else
> +    pp_faces[ 0 ] = LoadFace( p_filter, p_style, NULL, 0 );
> +#endif
> +
> +    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 ] =
> +                     LoadFace( p_filter, p_style, NULL,
> +                               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;
> +
> +        } 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 ] ) )
> +                return VLC_EGENERIC;
> +
> +            i_run_start = i;
> +        }
> +    }
> +
> +    return VLC_SUCCESS;
> +}
> +#endif
> +
> +/*
>   * Segment a paragraph into runs
>   */
>  static int ItemizeParagraph( filter_t *p_filter, paragraph_t *p_paragraph
> )
> @@ -464,7 +660,11 @@ static int ItemizeParagraph( filter_t *p_filter,
> paragraph_t *p_paragraph )
>                   & STYLE_HALFWIDTH )
>              ||!FaceStyleEquals( p_last_style, p_paragraph->pp_styles[ i ] )
> )
>          {
> +#ifdef HAVE_FONTCONFIG
> +            int i_ret = AddRunWithFallback( p_filter, p_paragraph,
> i_last_run_start, i );
> +#else
>              int i_ret = AddRun( p_filter, p_paragraph, i_last_run_start, i,
> 0 );
> +#endif
>              if( i_ret )
>                  return i_ret;
>
> @@ -520,7 +720,7 @@ static int ShapeParagraphHarfBuzz( filter_t *p_filter,
>          FT_Face p_face = 0;
>          if( !p_run->p_face )
>          {
> -            p_face = LoadFace( p_filter, p_style );
> +            p_face = LoadFace( p_filter, p_style, NULL, 0 );
>              if( !p_face )
>              {
>                  p_face = p_sys->p_face;
> @@ -776,7 +976,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 );
> +            p_face = LoadFace( p_filter, p_style, NULL, 0 );
>              if( !p_face )
>              {
>                  p_face = p_sys->p_face;
> --
> 1.9.1
>
>




More information about the vlc-devel mailing list