[vlc-devel] [RFC PATCH] freetype: using Pango for text layout

Salah-Eddin Shaban salshaaban at gmail.com
Wed Jan 21 02:26:41 CET 2015


---
 configure.ac                     |  11 ++
 modules/text_renderer/freetype.c | 297 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 308 insertions(+)

diff --git a/configure.ac b/configure.ac
index 516e9c7..214633f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3117,6 +3117,8 @@ AC_ARG_ENABLE(fribidi,
   [  --enable-fribidi        fribidi support    (default auto)])
 AC_ARG_ENABLE(fontconfig,
   [  --enable-fontconfig     fontconfig support (default auto)])
+AC_ARG_ENABLE(pangoft,
+  [  --enable-pangoft        pangoft support    (default auto)])
 
 AC_ARG_WITH([default-font],
     AS_HELP_STRING([--with-default-font=PATH],
@@ -3175,6 +3177,15 @@ if test "${enable_freetype}" != "no"; then
         ],[AC_MSG_WARN([${FRIBIDI_PKG_ERRORS}. Bidirectional support will be disabled in FreeType.])])
       fi
 
+      dnl pangoft support
+      if test "${enable_pangoft}" != "no"; then
+        PKG_CHECK_MODULES(PANGOFT, pangoft2, [
+          VLC_ADD_CPPFLAGS([freetype], [${PANGOFT_CFLAGS} -DHAVE_PANGOFT])
+          VLC_ADD_LIBS([freetype], [${PANGOFT_LIBS}])
+        ],[AC_MSG_WARN([${PANGOFT_PKG_ERRORS}. Support for complex scripts (Arabic, Farsi, Thai, etc.) will be limited in FreeType.])])
+      fi
+
+
   ],[
   have_freetype=no
   AS_IF([test -n "${enable_freetype}"],[
diff --git a/modules/text_renderer/freetype.c b/modules/text_renderer/freetype.c
index 050ace6..cde3a37 100644
--- a/modules/text_renderer/freetype.c
+++ b/modules/text_renderer/freetype.c
@@ -61,6 +61,13 @@
 # include <fribidi/fribidi.h>
 #endif
 
+/* Complex Scripts */
+#if defined(HAVE_PANGOFT)
+# include <pango/pango.h>
+# include <pango/pangoft2.h>
+# include <glib.h>
+#endif
+
 /* apple stuff */
 #ifdef __APPLE__
 # include <TargetConditionals.h>
@@ -138,6 +145,10 @@ static const char *const ppsz_sizes_text[] = {
 #define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
   "This option is only needed if you want to encode into DVB subtitles" )
 
+#define PANGO_TEXT N_("Use Pango for text layout")
+#define PANGO_LONGTEXT N_("Use Pango to layout the text. Required for " \
+  "complex scripts (Arabic, Thai, etc.)")
+
 static const int pi_color_values[] = {
   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
@@ -231,6 +242,10 @@ vlc_module_begin ()
 
     add_bool( "freetype-yuvp", false, YUVP_TEXT,
               YUVP_LONGTEXT, true )
+#ifdef HAVE_PANGOFT
+    add_bool( "freetype-pango", false, PANGO_TEXT,
+              PANGO_LONGTEXT, true )
+#endif
     set_capability( "text renderer", 100 )
     add_shortcut( "text" )
     set_callbacks( Create, Destroy )
@@ -271,6 +286,9 @@ struct line_desc_t
  *****************************************************************************/
 struct filter_sys_t
 {
+#ifdef HAVE_PANGOFT
+    PangoFontMap  *p_fontmap;
+#endif
     FT_Library     p_library;   /* handle to library     */
     FT_Face        p_face;      /* handle to face object */
     FT_Stroker     p_stroker;   /* handle to path stroker object */
@@ -1148,6 +1166,268 @@ static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
     p_max->yMax = __MAX(p_max->yMax, p->yMax);
 }
 
+#ifdef HAVE_PANGOFT
+static int ProcessLinesPangoFT2( filter_t *p_filter,
+                                 line_desc_t **pp_lines,
+                                 FT_BBox *p_bbox,
+                                 int *pi_max_face_height,
+                                 uni_char_t *psz_text,
+                                 text_style_t **pp_styles,
+                                 uint32_t *pi_k_dates,
+                                 int i_len )
+{
+    filter_sys_t   *p_sys = p_filter->p_sys;
+    *pi_max_face_height = 0;
+    *pp_lines = NULL;
+    line_desc_t **pp_line_next = pp_lines;
+
+    FT_BBox bbox = { .xMin = INT_MAX, .yMin = INT_MAX,
+                     .xMax = INT_MIN, .yMax = INT_MIN };
+
+    int i_face_height_previous = 0, i_base_line = 0;
+    //const text_style_t *p_previous_style = NULL;
+    //FT_Face p_face = NULL;
+
+    /*
+     * Convert to UTF-8 for Pango. psz_text is not recognized as
+     * UCS-4 unless the byte order is reversed.
+     */
+    for( int i = 0; i < i_len; ++i )
+    {
+        guchar *p = ( guchar * ) psz_text + i * sizeof( *psz_text );
+        guchar ch0 = *( p ); guchar ch1 = *( p + 1 );
+        guchar ch2 = *( p + 2 ); guchar ch3 = *( p + 3 );
+
+        *( p + 0 ) = ch3; *( p + 1 ) = ch2;
+        *( p + 2 ) = ch1; *( p + 3 ) = ch0;
+    }
+
+    gsize l_bytes_read = 0, l_bytes_written = 0;
+    gchar *psz_utf8 = g_convert( ( gchar * ) psz_text, i_len * sizeof( *psz_text ),
+                                 "UTF-8", "UCS-4", &l_bytes_read, &l_bytes_written, 0 );
+    if( !psz_utf8 ) {
+        msg_Err( p_filter, "Failed to convert text to UTF-8" );
+        return VLC_EGENERIC;
+    }
+
+    PangoFontMap *p_fm;
+    PangoContext *p_context;
+    PangoFontDescription *p_font_desc;
+    PangoLayout *p_layout;
+
+    p_fm = p_sys->p_fontmap;
+    p_context = pango_font_map_create_context(p_fm);
+    if( !p_context )
+    {
+        msg_Err( p_filter, "Failed to create a Pango context" );
+        g_free( psz_utf8 );
+        return VLC_EGENERIC;
+    }
+    pango_context_set_base_dir( p_context, PANGO_DIRECTION_LTR );
+    p_font_desc = pango_font_description_new();
+    pango_font_description_set_family( p_font_desc, p_sys->style.psz_fontname );
+    pango_font_description_set_absolute_size( p_font_desc, p_sys->style.i_font_size * PANGO_SCALE );
+    pango_context_set_font_description( p_context, p_font_desc );
+    p_layout = pango_layout_new( p_context );
+    pango_layout_set_width( p_layout, ( int ) p_filter->fmt_out.video.i_visible_width
+                            * PANGO_SCALE );
+    pango_layout_set_height( p_layout, ( int ) p_filter->fmt_out.video.i_visible_height
+                             * PANGO_SCALE );
+    pango_layout_set_auto_dir( p_layout, false );
+
+    /* Set attributes that affect text shaping */
+    PangoAttrList *p_list = pango_attr_list_new();
+    text_style_t *p_style = pp_styles[0];
+    gchar *p0 = psz_utf8, *p1 = psz_utf8;
+    for( int i = 0; i < i_len; ++i ) {
+        if( !FaceStyleEquals( p_style, pp_styles[i] ) ||
+            p_style->i_font_size != pp_styles[i]->i_font_size ||
+            i == i_len - 1 )
+        {
+            p_font_desc = pango_font_description_new();
+            pango_font_description_set_family( p_font_desc, p_style->psz_fontname );
+            pango_font_description_set_absolute_size( p_font_desc,
+                                                     p_style->i_font_size * PANGO_SCALE );
+
+            PangoAttribute *p_attr = pango_attr_font_desc_new( p_font_desc );
+            p_attr->start_index = p0 - psz_utf8;
+            p_attr->end_index = p1 - psz_utf8;
+            pango_attr_list_insert( p_list, p_attr );
+            p_style = pp_styles[i];
+            p0 = p1;
+        }
+        p1 = g_utf8_next_char(p1);
+    }
+
+    /*    Perform text shaping    */
+    pango_layout_set_attributes( p_layout, p_list );
+    pango_layout_set_text( p_layout, psz_utf8, l_bytes_written);
+
+    /*  Now the text has been laid out, fill our pp_lines  */
+    GSList *p_layout_lines = pango_layout_get_lines( p_layout );
+    while( p_layout_lines ) {
+        PangoLayoutLine *p_layout_line = ( PangoLayoutLine * ) p_layout_lines->data;
+        int i_glyph_count = 0;
+        GSList *p_runs = p_layout_line->runs;
+        while( p_runs ) {
+            PangoLayoutRun *p_run = ( PangoLayoutRun * ) p_runs->data;
+            PangoGlyphString *p_glyphs = p_run->glyphs;
+            i_glyph_count += p_glyphs->num_glyphs;
+            p_runs = g_slist_next( p_runs );
+        }
+
+        line_desc_t *p_line = i_glyph_count > 0 ? NewLine( i_glyph_count ) : NULL;
+        if( p_line ) p_line->i_character_count = i_glyph_count;
+        FT_Vector pen = { .x = 0, .y = 0 };
+        int i_font_width = p_sys->style.i_font_size;
+        int i_face_height = 0;
+        FT_BBox line_bbox = { .xMin = INT_MAX, .yMin = INT_MAX,
+                              .xMax = INT_MIN, .yMax = INT_MIN };
+
+        /*
+         * A cluster denotes a base glyph + its diacritics (accent glyphs). To apply
+         * remaining styles to glyphs we need to iterate through the clusters. That
+         * seems to be the only way for us to know which source text characters correspond
+         * to which glyphs. There's no 1:1 mapping here since Pango may have performed
+         * glyph substitutions according to OpenType tables.
+         */
+        p_runs = p_layout_line->runs;
+        int i_line_index = 0;
+        while( p_runs ) {
+            PangoLayoutRun *p_run = ( PangoLayoutRun * ) p_runs->data;
+            PangoGlyphString *p_glyphs = p_run->glyphs;
+            PangoGlyphItemIter cluster_iter;
+            gboolean b_rtl = p_run->item->analysis.level % 2;
+            gboolean b_have_cluster = b_rtl ?
+                pango_glyph_item_iter_init_end( &cluster_iter, p_run, psz_utf8 ) :
+                pango_glyph_item_iter_init_start( &cluster_iter, p_run, psz_utf8 );
+
+            for (             ;
+                  b_have_cluster;
+                  b_have_cluster = b_rtl ?
+                    pango_glyph_item_iter_prev_cluster ( &cluster_iter ) :
+                    pango_glyph_item_iter_next_cluster ( &cluster_iter ) )
+            {
+                int i_glyph_index = b_rtl ? cluster_iter.end_glyph + 1 :
+                                              cluster_iter.start_glyph;
+                while( b_rtl ? ( i_glyph_index != cluster_iter.start_glyph + 1) :
+                               ( i_glyph_index != cluster_iter.end_glyph ) )
+                {
+                    FT_Glyph glyph;
+                    FT_BBox  glyph_bbox;
+                    FT_Glyph outline;
+                    FT_BBox  outline_bbox;
+                    FT_Glyph shadow;
+                    FT_BBox  shadow_bbox;
+
+                    FT_Face p_face = pango_fc_font_lock_face(
+                        PANGO_FC_FONT( p_run->item->analysis.font ) );
+                    i_face_height = __MAX( i_face_height,
+                        FT_CEIL(FT_MulFix(p_face->height, p_face->size->metrics.y_scale)));
+
+                    FT_Vector pen_new;
+                    /* Divide by 16 to convert from PangoUnits to FreeType's 26.6 format */
+                    pen_new.x = pen.x + p_glyphs->glyphs[i_glyph_index].geometry.x_offset / 16;
+                    pen_new.y = pen.y - p_glyphs->glyphs[i_glyph_index].geometry.y_offset / 16;
+
+                    FT_Vector pen_shadow = {
+                        .x = pen_new.x + p_sys->f_shadow_vector_x * (i_font_width << 6),
+                        .y = pen_new.y + p_sys->f_shadow_vector_y * (i_font_width << 6),
+                    };
+
+                    if( GetGlyph( p_filter,
+                            &glyph, &glyph_bbox,
+                            &outline, &outline_bbox,
+                            &shadow, &shadow_bbox,
+                            p_face, p_glyphs->glyphs[i_glyph_index].glyph, 0,
+                            &pen_new, &pen_shadow) )
+                    {
+                        p_line->p_character[i_line_index++] = (line_character_t) {0};
+                        pango_fc_font_unlock_face(
+                            PANGO_FC_FONT( p_run->item->analysis.font ) );
+                        ++i_glyph_index;
+                        continue;
+                    }
+
+                    FixGlyph( glyph, &glyph_bbox, p_face, &pen_new );
+                    if( outline )
+                        FixGlyph( outline, &outline_bbox, p_face, &pen_new );
+                    if( shadow )
+                        FixGlyph( shadow, &shadow_bbox, p_face, &pen_shadow );
+
+                    pango_fc_font_unlock_face(
+                        PANGO_FC_FONT( p_run->item->analysis.font ) );
+
+                    FT_BBox line_bbox_new = line_bbox;
+                    BBoxEnlarge( &line_bbox_new, &glyph_bbox );
+                    if( outline )
+                        BBoxEnlarge( &line_bbox_new, &outline_bbox );
+                    if( shadow )
+                        BBoxEnlarge( &line_bbox_new, &shadow_bbox );
+
+                    gchar *p = &psz_utf8[ cluster_iter.start_index ];
+                    int i_index_in_chars = g_utf8_strlen( psz_utf8, p - psz_utf8 );
+                    p_style = pp_styles[ i_index_in_chars ];
+                    p_line->p_character[ i_line_index++ ] = (line_character_t) {
+                        .p_glyph = (FT_BitmapGlyph) glyph,
+                        .p_outline = (FT_BitmapGlyph) outline,
+                        .p_shadow = (FT_BitmapGlyph) shadow,
+                        .i_color = p_style->i_font_color | p_style->i_font_alpha << 24,
+                        .i_line_offset = 0,
+                        .i_line_thickness = 0,
+                    };
+
+                    /*
+                     * We're now using Pango for glyph positioning. If 2 glyphs should
+                     * be rendered on top of one another, the width of the first will
+                     * be 0. So we no longer have to check for diacritics or zero-width
+                     * spaces, etc.
+                     */
+                    pen.x = pen.x + p_glyphs->glyphs[i_glyph_index].geometry.width / 16;
+                    line_bbox = line_bbox_new;
+                    ++i_glyph_index;
+                }
+            }
+            p_runs = g_slist_next(p_runs);
+        }
+
+        if( i_face_height_previous > 0 )
+            i_base_line += __MAX(i_face_height, i_face_height_previous);
+        if( i_face_height > 0 )
+            i_face_height_previous = i_face_height;
+
+
+        /* Update the line bbox with the actual base line */
+        if (line_bbox.yMax > line_bbox.yMin) {
+            line_bbox.yMin -= i_base_line;
+            line_bbox.yMax -= i_base_line;
+        }
+        BBoxEnlarge( &bbox, &line_bbox );
+
+        if( p_line )
+        {
+            p_line->i_width  = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
+            p_line->i_base_line = i_base_line;
+            p_line->i_height = __MAX(i_face_height, i_face_height_previous);
+            *pp_line_next = p_line;
+            pp_line_next = &p_line->p_next;
+
+        }
+        *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
+
+        p_layout_lines = g_slist_next(p_layout_lines);
+    }
+
+    g_object_unref( p_layout );
+    pango_font_description_free( p_font_desc );
+    g_object_unref( p_context );
+    if( psz_utf8 ) g_free( psz_utf8 );
+
+    *p_bbox = bbox;
+    return VLC_SUCCESS;
+}
+#endif
+
 static int ProcessLines( filter_t *p_filter,
                          line_desc_t **pp_lines,
                          FT_BBox     *p_bbox,
@@ -1767,9 +2047,20 @@ static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
 
     if( !rv && i_text_length > 0 )
     {
+#ifdef HAVE_PANGOFT
+        if( var_InheritBool( p_filter, "freetype-pango" ) )
+            rv = ProcessLinesPangoFT2( p_filter,
+                                       &p_lines, &bbox, &i_max_face_height,
+                                       psz_text, pp_styles, pi_k_durations, i_text_length );
+        else
+            rv = ProcessLines( p_filter,
+                               &p_lines, &bbox, &i_max_face_height,
+                               psz_text, pp_styles, pi_k_durations, i_text_length );
+#else
         rv = ProcessLines( p_filter,
                            &p_lines, &bbox, &i_max_face_height,
                            psz_text, pp_styles, pi_k_durations, i_text_length );
+#endif
     }
 
     p_region_out->i_x = p_region_in->i_x;
@@ -1865,6 +2156,9 @@ static int Init_FT( vlc_object_t *p_this,
     filter_sys_t  *p_sys = p_filter->p_sys;
 
     /* */
+#ifdef HAVE_PANGOFT
+    p_sys->p_fontmap = pango_ft2_font_map_new();
+#endif
     int i_error = FT_Init_FreeType( &p_sys->p_library );
     if( i_error )
     {
@@ -2058,6 +2352,9 @@ static void Destroy_FT( vlc_object_t *p_this )
         FT_Stroker_Done( p_sys->p_stroker );
     FT_Done_Face( p_sys->p_face );
     FT_Done_FreeType( p_sys->p_library );
+#ifdef HAVE_PANGOFT
+    g_object_unref( p_sys->p_fontmap );
+#endif
 }
 
 /*****************************************************************************
-- 
1.9.1




More information about the vlc-devel mailing list