]> git.sesse.net Git - vlc/blobdiff - modules/misc/text_renderer/freetype.c
Improved the order of options in freetype.
[vlc] / modules / misc / text_renderer / freetype.c
index 0822ffbb4eced1d99c9f243d27aa3fba876a0010..c431748c94fe491850da39c51a0d184c4afa440e 100644 (file)
@@ -62,6 +62,8 @@
 #include <freetype/ftsynth.h>
 #include FT_FREETYPE_H
 #include FT_GLYPH_H
+#include FT_STROKER_H
+
 #define FT_FLOOR(X)     ((X & -64) >> 6)
 #define FT_CEIL(X)      (((X + 63) & -64) >> 6)
 #ifndef FT_MulFix
@@ -119,6 +121,13 @@ static void Destroy( vlc_object_t * );
     "fonts that will be rendered on the video. If absolute font size is set, "\
     "relative size will be overridden." )
 
+#define BG_OPACITY_TEXT N_("Background opacity")
+#define BG_COLOR_TEXT N_("Background color")
+
+#define OUTLINE_OPACITY_TEXT N_("Outline opacity")
+#define OUTLINE_COLOR_TEXT N_("Outline color")
+#define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
+
 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
 static const char *const ppsz_sizes_text[] = {
     N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
@@ -136,6 +145,13 @@ static const char *const ppsz_color_descriptions[] = {
   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
 
+static const int pi_outline_thickness[] = {
+    0, 2, 4, 6,
+};
+static const char *const ppsz_outline_thickness[] = {
+    N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
+};
+
 vlc_module_begin ()
     set_shortname( N_("Text renderer"))
     set_description( N_("Freetype2 font renderer") )
@@ -152,6 +168,11 @@ vlc_module_begin ()
                  FONTSIZE_LONGTEXT, true )
         change_safe()
 
+    add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
+                 FONTSIZER_LONGTEXT, false )
+        change_integer_list( pi_sizes, ppsz_sizes_text )
+        change_safe()
+
     /* opacity valid on 0..255, with default 255 = fully opaque */
     add_integer_with_range( "freetype-opacity", 255, 0, 255,
         OPACITY_TEXT, OPACITY_LONGTEXT, false )
@@ -163,9 +184,24 @@ vlc_module_begin ()
         change_integer_list( pi_color_values, ppsz_color_descriptions )
         change_safe()
 
-    add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
-                 FONTSIZER_LONGTEXT, false )
-        change_integer_list( pi_sizes, ppsz_sizes_text )
+    add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
+                            BG_OPACITY_TEXT, "", false )
+        change_safe()
+    add_integer( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
+                 "", false )
+        change_integer_list( pi_color_values, ppsz_color_descriptions )
+        change_safe()
+
+    add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
+                            OUTLINE_OPACITY_TEXT, "", false )
+        change_safe()
+    add_integer( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
+                 "", false )
+        change_integer_list( pi_color_values, ppsz_color_descriptions )
+        change_safe()
+    add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
+                 "", false )
+        change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
         change_safe()
 
     add_obsolete_integer( "freetype-effect" );
@@ -182,22 +218,24 @@ vlc_module_end ()
  * Local prototypes
  *****************************************************************************/
 
+typedef struct
+{
+    FT_BitmapGlyph p_glyph;
+    FT_BitmapGlyph p_outline;
+    uint32_t       i_color;             /* ARGB color */
+    int            i_line_offset;       /* underline/strikethrough offset */
+    int            i_line_thickness;    /* underline/strikethrough thickness */
+} line_character_t;
+
 typedef struct line_desc_t line_desc_t;
 struct line_desc_t
 {
-    /** NULL-terminated list of glyphs making the string */
-    FT_BitmapGlyph *pp_glyphs;
-    /** list of relative positions for the glyphs */
-    FT_Vector      *p_glyph_pos;
-    /** list of ARGB information for styled text */
-    uint32_t       *pi_color;
-    /** underline/strikethrough information */
-    int            *pi_line_offset;
-    uint16_t       *pi_line_thickness;
-
-    int             i_width;
-
     line_desc_t    *p_next;
+
+    int              i_width;
+    int              i_base_line;
+    int              i_character_count;
+    line_character_t *p_character;
 };
 
 typedef struct font_stack_t font_stack_t;
@@ -221,10 +259,18 @@ struct filter_sys_t
 {
     FT_Library     p_library;   /* handle to library     */
     FT_Face        p_face;      /* handle to face object */
+    FT_Stroker     p_stroker;
     uint8_t        i_font_opacity;
     int            i_font_color;
     int            i_font_size;
 
+    uint8_t        i_background_opacity;
+    int            i_background_color;
+
+    double         f_outline_thickness;
+    uint8_t        i_outline_opacity;
+    int            i_outline_color;
+
     int            i_default_font_size;
     int            i_display_height;
     char*          psz_fontfamily;
@@ -548,7 +594,8 @@ static char* Win32_Select( filter_t *p_filter, const char* family,
  * This function merges the previously rendered freetype glyphs into a picture
  *****************************************************************************/
 static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
-                       line_desc_t *p_line, int i_width, int i_height )
+                       line_desc_t *p_line,
+                       FT_BBox *p_bbox )
 {
     VLC_UNUSED(p_filter);
     static const uint8_t pi_gamma[16] =
@@ -561,17 +608,11 @@ static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
     uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
 
     /* Create a new subpicture region */
-    memset( &fmt, 0, sizeof(video_format_t) );
-    fmt.i_chroma = VLC_CODEC_YUVP;
-    fmt.i_width = fmt.i_visible_width = i_width + 4;
-    fmt.i_height = fmt.i_visible_height = i_height + 4;
-    if( p_region->fmt.i_visible_width > 0 )
-        fmt.i_visible_width = p_region->fmt.i_visible_width;
-    if( p_region->fmt.i_visible_height > 0 )
-        fmt.i_visible_height = p_region->fmt.i_visible_height;
-    fmt.i_x_offset = fmt.i_y_offset = 0;
-    fmt.i_sar_num = 1;
-    fmt.i_sar_den = 1;
+    video_format_Init( &fmt, VLC_CODEC_YUVP );
+    fmt.i_width          =
+    fmt.i_visible_width  = p_bbox->xMax - p_bbox->xMin + 4;
+    fmt.i_height         =
+    fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
 
     assert( !p_region->p_picture );
     p_region->p_picture = picture_NewFromFormat( &fmt );
@@ -582,8 +623,8 @@ static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
 
     /* Calculate text color components
      * Only use the first color */
-    int i_alpha = 0xff - ((p_line->pi_color[ 0 ] >> 24) & 0xff);
-    YUVFromRGB( p_line->pi_color[ 0 ], &i_y, &i_u, &i_v );
+    int i_alpha = 0xff - ((p_line->p_character[0].i_color >> 24) & 0xff);
+    YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
 
     /* Build palette */
     fmt.p_palette->i_entries = 16;
@@ -614,44 +655,32 @@ static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
 
     for( ; p_line != NULL; p_line = p_line->p_next )
     {
-        int i_glyph_tmax = 0;
-        int i_bitmap_offset, i_offset, i_align_offset = 0;
-        for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
-        {
-            FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
-            i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
-        }
-
-        if( p_line->i_width < i_width )
+        int i_align_left = 0;
+        if( p_line->i_width < fmt.i_visible_width )
         {
             if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
-            {
-                i_align_offset = i_width - p_line->i_width;
-            }
+                i_align_left = ( fmt.i_visible_width - p_line->i_width );
             else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
-            {
-                i_align_offset = ( i_width - p_line->i_width ) / 2;
-            }
+                i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
         }
+        int i_align_top = 0;
 
-        for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
+        for( i = 0; i < p_line->i_character_count; i++ )
         {
-            FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
+            const line_character_t *ch = &p_line->p_character[i];
+            FT_BitmapGlyph p_glyph = ch->p_glyph;
 
-            i_offset = ( p_line->p_glyph_pos[ i ].y +
-                i_glyph_tmax - p_glyph->top + 2 ) *
-                i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 2 +
-                i_align_offset;
+            int i_glyph_y = i_align_top  - p_glyph->top  + p_bbox->yMax + p_line->i_base_line;
+            int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
 
-            for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
+            for( y = 0; y < p_glyph->bitmap.rows; y++ )
             {
-                for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
+                for( x = 0; x < p_glyph->bitmap.width; x++ )
                 {
-                    if( p_glyph->bitmap.buffer[i_bitmap_offset] )
-                        p_dst[i_offset+x] =
-                         ((int)p_glyph->bitmap.buffer[i_bitmap_offset] + 8)/16;
+                    if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
+                        p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
+                            (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
                 }
-                i_offset += i_pitch;
             }
         }
     }
@@ -737,52 +766,19 @@ static inline void BlendYUVAGlyph( picture_t *p_picture,
 static inline void BlendYUVALine( picture_t *p_picture,
                                   int i_picture_x, int i_picture_y,
                                   int i_a, int i_y, int i_u, int i_v,
-                                  FT_BitmapGlyph p_glyph_current,
-                                  FT_BitmapGlyph p_glyph_next,
-                                  FT_Vector      *p_pos_current,
-                                  FT_Vector      *p_pos_next,
-                                  int i_line_thickness,
-                                  int i_line_offset,
-                                  bool is_next )
+                                  const line_character_t *p_current,
+                                  const line_character_t *p_next )
 {
-    int i_line_width = p_glyph_current->bitmap.width;
-    if( is_next )
-        i_line_width = (p_pos_next->x    + p_glyph_next->left) -
-                       (p_pos_current->x + p_glyph_current->left);
+    int i_line_width = p_current->p_glyph->bitmap.width;
+    if( p_next )
+        i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
 
     for( int dx = 0; dx < i_line_width; dx++ )
     {
-        /* break the underline around the tails of any glyphs which cross it
-           Strikethrough doesn't get broken */
-        bool b_ok = true;
-        for( int z = dx - i_line_thickness; z < dx + i_line_thickness && b_ok && i_line_offset >= 0; z++ )
-        {
-            FT_BitmapGlyph p_glyph_check = NULL;
-            int i_column;
-            if( p_glyph_next && z >= i_line_width )
-            {
-                i_column      = z - i_line_width;
-                p_glyph_check = p_glyph_next;
-            }
-            else if( z >= 0 && z < p_glyph_current->bitmap.width )
-            {
-                i_column      = z;
-                p_glyph_check = p_glyph_current;
-            }
-            if( p_glyph_check )
-            {
-                const FT_Bitmap *p_bitmap = &p_glyph_check->bitmap;
-                for( int dy = 0; dy < i_line_thickness && b_ok; dy++ )
-                {
-                    int i_row = i_line_offset + p_glyph_check->top + dy;
-                    b_ok = i_row >= p_bitmap->rows ||
-                           p_bitmap->buffer[p_bitmap->width * i_row + i_column] == 0;
-                }
-            }
-        }
-
-        for( int dy = 0; dy < i_line_thickness && b_ok; dy++ )
-            BlendYUVAPixel( p_picture, i_picture_x + dx, i_picture_y + i_line_offset + dy,
+        for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
+            BlendYUVAPixel( p_picture,
+                            i_picture_x + dx,
+                            i_picture_y + p_current->i_line_offset + dy,
                             i_a, i_y, i_u, i_v, 0xff );
     }
 }
@@ -790,17 +786,20 @@ static inline void BlendYUVALine( picture_t *p_picture,
 static int RenderYUVA( filter_t *p_filter,
                        subpicture_region_t *p_region,
                        line_desc_t *p_line_head,
-                       int i_width, int i_height )
+                       FT_BBox *p_bbox,
+                       int i_margin )
 {
     filter_sys_t *p_sys = p_filter->p_sys;
 
     /* Create a new subpicture region */
+    const int i_text_width  = p_bbox->xMax - p_bbox->xMin;
+    const int i_text_height = p_bbox->yMax - p_bbox->yMin;
     video_format_t fmt;
     video_format_Init( &fmt, VLC_CODEC_YUVA );
     fmt.i_width          =
-    fmt.i_visible_width  = i_width;
+    fmt.i_visible_width  = i_text_width  + 2 * i_margin;
     fmt.i_height         =
-    fmt.i_visible_height = i_height;
+    fmt.i_visible_height = i_text_height + 2 * i_margin;
 
     picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
     if( !p_region->p_picture )
@@ -808,10 +807,9 @@ static int RenderYUVA( filter_t *p_filter,
     p_region->fmt = fmt;
 
     /* Initialize the picture background */
-    uint32_t i_background = 0 ? 0x80000000 : 0xff000000;
-    uint8_t i_a = 0xff - ((i_background >> 24) & 0xff);
+    uint8_t i_a = p_sys->i_background_opacity;
     uint8_t i_y, i_u, i_v;
-    YUVFromRGB( i_background, &i_y, &i_u, &i_v );
+    YUVFromRGB( p_sys->i_background_color, &i_y, &i_u, &i_v );
 
     memset( p_picture->p[0].p_pixels, i_y,
             p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
@@ -822,50 +820,56 @@ static int RenderYUVA( filter_t *p_filter,
     memset( p_picture->p[3].p_pixels, i_a,
             p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
 
-    /* Render all lines */
-    for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
+    /* Render outline glyphs in the first pass, and then the normal glyphs */
+    for( int g = 0; g < 2; g++ )
     {
-        /* Left offset to take into account alignment */
-        int i_align_left = 0;
-        if( p_line->i_width < i_width )
+        /* Render all lines */
+        for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
         {
-            if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
-                i_align_left = i_width - p_line->i_width;
-            else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
-                i_align_left = ( i_width - p_line->i_width ) / 2;
-        }
-
-        /* Compute the top alignment
-         * FIXME seems bad (it seems that the glyphs are aligned too high) */
-        int i_align_top = 0;
-        for( int i = 0; p_line->pp_glyphs[i]; i++ )
-            i_align_top = __MAX( i_align_top, p_line->pp_glyphs[i]->top );
-
-        /* Render all glyphs and underline/strikethrough */
-        for( int i = 0; p_line->pp_glyphs[i]; i++ )
-        {
-            FT_BitmapGlyph p_glyph = p_line->pp_glyphs[i];
-
-            uint32_t i_color = p_line->pi_color[i];
-            i_a = 0xff - ((i_color >> 24) & 0xff);
-            YUVFromRGB( i_color, &i_y, &i_u, &i_v );
-
-            int i_picture_y = p_line->p_glyph_pos[i].y + i_align_top;
-            int i_picture_x = p_line->p_glyph_pos[i].x + i_align_left + p_glyph->left;
+            int i_align_left = i_margin;
+            if( p_line->i_width < i_text_width )
+            {
+                /* Left offset to take into account alignment */
+                if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
+                    i_align_left += ( i_text_width - p_line->i_width );
+                else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
+                    i_align_left += ( i_text_width - p_line->i_width ) / 2;
+            }
+            int i_align_top = i_margin;
 
-            BlendYUVAGlyph( p_picture, i_picture_x, i_picture_y - p_glyph->top,
-                            i_a, i_y, i_u, i_v,
-                            p_glyph );
-
-            const int i_line_thickness = p_line->pi_line_thickness[i];
-            const int i_line_offset    = p_line->pi_line_offset[i];
-            if( i_line_thickness > 0 )
-                BlendYUVALine( p_picture, i_picture_x, i_picture_y,
-                               i_a, i_y, i_u, i_v,
-                               p_glyph, p_line->pp_glyphs[i + 1],
-                               &p_line->p_glyph_pos[i], &p_line->p_glyph_pos[i + 1],
-                               i_line_thickness, i_line_offset,
-                               p_line->pp_glyphs[i + 1] && p_line->pi_line_thickness[i + 1] > 0 );
+            /* Render all glyphs and underline/strikethrough */
+            for( int i = 0; i < p_line->i_character_count; i++ )
+            {
+                const line_character_t *ch = &p_line->p_character[i];
+                FT_BitmapGlyph p_glyph = g == 0 ? ch->p_outline : ch->p_glyph;
+                if( !p_glyph )
+                    continue;
+
+                uint32_t i_color = ch->i_color;
+                i_a = 0xff - ((i_color >> 24) & 0xff);
+                if( g == 0 )
+                {
+                    i_a = i_a * p_sys->i_outline_opacity / 255;
+                    i_color = p_sys->i_outline_color;
+                }
+                YUVFromRGB( i_color, &i_y, &i_u, &i_v );
+
+                int i_glyph_y = i_align_top  - p_glyph->top  + p_bbox->yMax + p_line->i_base_line;
+                int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
+
+                BlendYUVAGlyph( p_picture,
+                                i_glyph_x, i_glyph_y,
+                                i_a, i_y, i_u, i_v,
+                                p_glyph );
+
+                /* underline/strikethrough are only rendered for the normal glyph */
+                if( g == 1 && ch->i_line_thickness > 0 )
+                    BlendYUVALine( p_picture,
+                                   i_glyph_x, i_glyph_y + p_glyph->top,
+                                   i_a, i_y, i_u, i_v,
+                                   &ch[0],
+                                   i + 1 < p_line->i_character_count ? &ch[1] : NULL );
+            }
         }
     }
 
@@ -1452,14 +1456,15 @@ static int ProcessNodes( filter_t *p_filter,
 
 static void FreeLine( line_desc_t *p_line )
 {
-    for( int i = 0; p_line->pp_glyphs && p_line->pp_glyphs[i] != NULL; i++ )
-        FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[i] );
-
-    free( p_line->pp_glyphs );
-    free( p_line->p_glyph_pos );
-    free( p_line->pi_color );
-    free( p_line->pi_line_offset );
-    free( p_line->pi_line_thickness );
+    for( int i = 0; i < p_line->i_character_count; i++ )
+    {
+        line_character_t *ch = &p_line->p_character[i];
+        FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
+        if( ch->p_outline )
+            FT_Done_Glyph( (FT_Glyph)ch->p_outline );
+    }
+
+    free( p_line->p_character );
     free( p_line );
 }
 
@@ -1480,24 +1485,17 @@ static line_desc_t *NewLine( int i_count )
     if( !p_line )
         return NULL;
 
-    p_line->i_width = 0;
-
     p_line->p_next = NULL;
+    p_line->i_width = 0;
+    p_line->i_base_line = 0;
+    p_line->i_character_count = 0;
 
-    p_line->pp_glyphs         = calloc( i_count + 1, sizeof(*p_line->pp_glyphs) );
-    p_line->p_glyph_pos       = calloc( i_count + 1, sizeof(*p_line->p_glyph_pos) );
-    p_line->pi_color          = calloc( i_count + 1, sizeof(*p_line->pi_color) );
-    p_line->pi_line_offset    = calloc( i_count + 1, sizeof(*p_line->pi_line_offset) );
-    p_line->pi_line_thickness = calloc( i_count + 1, sizeof(*p_line->pi_line_thickness) );
-
-    if( !p_line->pp_glyphs || !p_line->p_glyph_pos ||
-        !p_line->pi_color ||
-        !p_line->pi_line_offset || !p_line->pi_line_thickness )
+    p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
+    if( !p_line->p_character )
     {
-        FreeLine( p_line );
+        free( p_line );
         return NULL;
     }
-    p_line->pp_glyphs[0] = NULL;
     return p_line;
 }
 
@@ -1609,12 +1607,13 @@ static bool FaceStyleEquals( const text_style_t *p_style1,
 }
 
 static int GetGlyph( filter_t *p_filter,
-                     FT_Glyph *pp_glyph,
-                     FT_BBox  *p_bbox,
+                     FT_Glyph *pp_glyph,   FT_BBox *p_glyph_bbox,
+                     FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
 
                      FT_Face  p_face,
                      int i_glyph_index,
-                     int i_style_flags )
+                     int i_style_flags,
+                     FT_Vector *p_pen )
 {
     if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
         FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
@@ -1639,23 +1638,58 @@ static int GetGlyph( filter_t *p_filter,
         return VLC_EGENERIC;
     }
 
-    FT_BBox bbox;
-    FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, &bbox );
+    FT_Glyph outline = NULL;
+    if( p_filter->p_sys->p_stroker )
+    {
+        outline = glyph;
+        FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 );
+        FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
+
+        FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
+    }
+    *pp_outline = outline;
 
-    if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, 0, 1) )
+    if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
     {
         FT_Done_Glyph( glyph );
         return VLC_EGENERIC;
     }
 
+    FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
     *pp_glyph = glyph;
-    *p_bbox   = bbox;
+
     return VLC_SUCCESS;
 }
 
+static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
+{
+    FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
+    if( p_bbox->xMin >= p_bbox->xMax )
+    {
+        p_bbox->xMin = FT_CEIL(p_pen->x);
+        p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
+        glyph_bmp->left = p_bbox->xMin;
+    }
+    if( p_bbox->yMin >= p_bbox->yMax )
+    {
+        p_bbox->yMax = FT_CEIL(p_pen->y);
+        p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
+        glyph_bmp->top  = p_bbox->yMax;
+    }
+}
+
+static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
+{
+    p_max->xMin = __MIN(p_max->xMin, p->xMin);
+    p_max->yMin = __MIN(p_max->yMin, p->yMin);
+    p_max->xMax = __MAX(p_max->xMax, p->xMax);
+    p_max->yMax = __MAX(p_max->yMax, p->yMax);
+}
+
 static int ProcessLines( filter_t *p_filter,
                          line_desc_t **pp_lines,
-                         FT_Vector   *p_size,
+                         FT_BBox     *p_bbox,
+                         int         *pi_max_face_height,
 
                          uint32_t *psz_text,
                          text_style_t **pp_styles,
@@ -1751,16 +1785,18 @@ static int ProcessLines( filter_t *p_filter,
     }
     free( p_new_positions );
 
+    *pi_max_face_height = 0;
     *pp_lines = NULL;
     line_desc_t **pp_line_next = pp_lines;
 
     FT_BBox bbox = {
-        .xMin = 0,
-        .yMin = 0,
-        .xMax = 0,
-        .yMax = 0,
+        .xMin = INT_MAX,
+        .yMin = INT_MAX,
+        .xMax = INT_MIN,
+        .yMax = INT_MIN,
     };
-    FT_Vector pen = { .x = 0, .y = 0 };
+    int i_face_height_previous = 0;
+    int i_base_line = 0;
     const text_style_t *p_previous_style = NULL;
     FT_Face p_face = NULL;
     for( int i_start = 0; i_start < i_len; )
@@ -1773,19 +1809,26 @@ static int ProcessLines( filter_t *p_filter,
         /* Render the text line (or the begining if too long) into 0 or 1 glyph line */
         line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
         int i_index = i_start;
-        pen.x = 0;
+        FT_Vector pen = {
+            .x = 0,
+            .y = 0,
+        };
         int i_face_height = 0;
         FT_BBox line_bbox = {
-            .xMin = 0,
-            .yMin = 0,
-            .xMax = 0,
-            .yMax = 0,
+            .xMin = INT_MAX,
+            .yMin = INT_MAX,
+            .xMax = INT_MIN,
+            .yMax = INT_MIN,
         };
+        int i_ul_offset = 0;
+        int i_ul_thickness = 0;
         typedef struct {
             int       i_index;
             FT_Vector pen;
             FT_BBox   line_bbox;
             int i_face_height;
+            int i_ul_offset;
+            int i_ul_thickness;
         } break_point_t;
         break_point_t break_point;
         break_point_t break_point_fallback;
@@ -1795,6 +1838,8 @@ static int ProcessLines( filter_t *p_filter,
         dst.pen = pen; \
         dst.line_bbox = line_bbox; \
         dst.i_face_height = i_face_height; \
+        dst.i_ul_offset = i_ul_offset; \
+        dst.i_ul_thickness = i_ul_thickness; \
     } while(0)
 
         SAVE_BP( break_point );
@@ -1828,10 +1873,19 @@ static int ProcessLines( filter_t *p_filter,
             {
                 if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
                     msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
+                if( p_sys->p_stroker )
+                {
+                    int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
+                    FT_Stroker_Set( p_sys->p_stroker,
+                                    i_radius,
+                                    FT_STROKER_LINECAP_ROUND,
+                                    FT_STROKER_LINEJOIN_ROUND, 0 );
+                }
             }
             p_previous_style = p_current_style;
 
-            i_face_height = __MAX(i_face_height, FT_CEIL(p_current_face->size->metrics.height));
+            i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
+                                                                   p_current_face->size->metrics.y_scale)));
 
             /* Render the part */
             bool b_break_line = false;
@@ -1848,55 +1902,71 @@ static int ProcessLines( filter_t *p_filter,
                     FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
 
                 /* Get the glyph bitmap and its bounding box and all the associated properties */
+                FT_Vector pen_new = {
+                    .x = pen.x + kerning.x,
+                    .y = pen.y + kerning.y,
+                };
                 FT_Glyph glyph;
                 FT_BBox  glyph_bbox;
-                if( GetGlyph( p_filter, &glyph, &glyph_bbox,
-                              p_current_face, i_glyph_index, p_glyph_style->i_style_flags ) )
+                FT_Glyph outline;
+                FT_BBox  outline_bbox;
+                if( GetGlyph( p_filter,
+                              &glyph, &glyph_bbox,
+                              &outline, &outline_bbox,
+                              p_current_face, i_glyph_index, p_glyph_style->i_style_flags, &pen_new ) )
                     goto next;
 
-                FT_Vector glyph_pos = {
-                    .x = pen.x + FT_CEIL(kerning.x),
-                    .y = pen.y
-                };
+                FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
+                if( outline )
+                    FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
+                /* FIXME and what about outline */
+
                 bool     b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
                 uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
                                                 (p_glyph_style->i_karaoke_background_alpha << 24))
                                              : (p_glyph_style->i_font_color |
                                                 (p_glyph_style->i_font_alpha << 24));
-                int i_ul_offset    = 0;
-                int i_ul_thickness = 0;
+                int i_line_offset    = 0;
+                int i_line_thickness = 0;
                 if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
                 {
-                    i_ul_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
-                                                          p_current_face->size->metrics.y_scale)) );
-
-                    i_ul_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
+                    i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
                                                             p_current_face->size->metrics.y_scale)) );
 
+                    i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
+                                                              p_current_face->size->metrics.y_scale)) );
+
                     if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
                     {
                         /* Move the baseline to make it strikethrough instead of
                          * underline. That means that strikethrough takes precedence
                          */
-                        i_ul_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
-                                                               p_current_face->size->metrics.y_scale)) );
+                        i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
+                                                                 p_current_face->size->metrics.y_scale)) );
+                    }
+                    else if( i_line_thickness > 0 )
+                    {
+                        glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
+
+                        /* The real underline thickness and position are
+                         * updated once the whole line has been parsed */
+                        i_ul_offset = __MAX( i_ul_offset, i_line_offset );
+                        i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
+                        i_line_thickness = -1;
                     }
                 }
-                FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
-                FT_BBox line_bbox_new = {
-                    .xMin = 0,
-                    .xMax = __MAX( line_bbox.xMax,
-                                   glyph_pos.x + glyph_bbox.xMax - glyph_bbox.xMin + glyph_bmp->left ),
-                    .yMin = 0,
-                    .yMax = __MAX( line_bbox.yMax,
-                                   glyph_pos.y + glyph_bbox.yMax - glyph_bbox.yMin + glyph_bmp->top ),
-                };
+                FT_BBox line_bbox_new = line_bbox;
+                BBoxEnlarge( &line_bbox_new, &glyph_bbox );
+                if( outline )
+                    BBoxEnlarge( &line_bbox_new, &outline_bbox );
 
                 b_break_line = i_index > i_start &&
-                               line_bbox_new.xMax >= p_filter->fmt_out.video.i_visible_width;
+                               line_bbox_new.xMax - line_bbox_new.xMin >= p_filter->fmt_out.video.i_visible_width;
                 if( b_break_line )
                 {
                     FT_Done_Glyph( glyph );
+                    if( outline )
+                        FT_Done_Glyph( outline );
 
                     break_point_t *p_bp = NULL;
                     if( break_point.i_index > i_start )
@@ -1909,13 +1979,19 @@ static int ProcessLines( filter_t *p_filter,
                         msg_Dbg( p_filter, "Breaking line");
                         for( int i = p_bp->i_index; i < i_index; i++ )
                         {
-                            FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[i - i_start] );
-                            p_line->pp_glyphs[i - i_start] = NULL;
+                            line_character_t *ch = &p_line->p_character[i - i_start];
+                            FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
+                            if( ch->p_outline )
+                                FT_Done_Glyph( (FT_Glyph)ch->p_outline );
                         }
+                        p_line->i_character_count = p_bp->i_index - i_start;
+
                         i_index = p_bp->i_index;
                         pen = p_bp->pen;
                         line_bbox = p_bp->line_bbox;
                         i_face_height = p_bp->i_face_height;
+                        i_ul_offset = p_bp->i_ul_offset;
+                        i_ul_thickness = p_bp->i_ul_thickness;
                     }
                     else
                     {
@@ -1924,14 +2000,17 @@ static int ProcessLines( filter_t *p_filter,
                     break;
                 }
 
-                int i_line_index = i_index - i_start;
-                p_line->pp_glyphs[i_line_index] = (FT_BitmapGlyph)glyph;
-                p_line->p_glyph_pos[i_line_index] = glyph_pos;
-                p_line->pi_color[i_line_index] = i_color;
-                p_line->pi_line_offset[i_line_index] = i_ul_offset;
-                p_line->pi_line_thickness[i_line_index] = i_ul_thickness;
+                assert( p_line->i_character_count == i_index - i_start);
+                p_line->p_character[p_line->i_character_count++] = (line_character_t){
+                    .p_glyph = (FT_BitmapGlyph)glyph,
+                    .p_outline = (FT_BitmapGlyph)outline,
+                    .i_color = i_color,
+                    .i_line_offset = i_line_offset,
+                    .i_line_thickness = i_line_thickness,
+                };
 
-                pen.x += FT_CEIL(kerning.x) + FT_CEIL(p_current_face->glyph->advance.x);
+                pen.x = pen_new.x + p_current_face->glyph->advance.x;
+                pen.y = pen_new.y + p_current_face->glyph->advance.y;
                 line_bbox = line_bbox_new;
             next:
                 i_glyph_last = i_glyph_index;
@@ -1947,25 +2026,48 @@ static int ProcessLines( filter_t *p_filter,
                 break;
         }
 #undef SAVE_BP
-        bbox.xMax = __MAX(bbox.xMax, line_bbox.xMax);
-        bbox.yMax = __MAX(bbox.yMax, line_bbox.yMax);
-
-        pen.y += i_face_height;
+        /* Update our baseline */
+        if( i_face_height_previous > 0 )
+            i_base_line += __MAX(i_face_height, i_face_height_previous);
+        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 );
 
         /* Terminate and append the line */
         if( p_line )
         {
-            p_line->i_width  = line_bbox.xMax - line_bbox.xMin;
+            p_line->i_width  = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
+            p_line->i_base_line = i_base_line;
+            if( i_ul_thickness > 0 )
+            {
+                for( int i = 0; i < p_line->i_character_count; i++ )
+                {
+                    line_character_t *ch = &p_line->p_character[i];
+                    if( ch->i_line_thickness < 0 )
+                    {
+                        ch->i_line_offset    = i_ul_offset;
+                        ch->i_line_thickness = i_ul_thickness;
+                    }
+                }
+            }
+
             *pp_line_next = p_line;
             pp_line_next = &p_line->p_next;
         }
 
+        *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
+
         /* Skip what we have rendered and the line delimitor if present */
         i_start = i_index;
         if( i_start < i_len && psz_text[i_start] == '\n' )
             i_start++;
 
-        if( bbox.yMax >= p_filter->fmt_out.video.i_visible_height )
+        if( bbox.yMax - bbox.yMin >= p_filter->fmt_out.video.i_visible_height )
         {
             msg_Err( p_filter, "Truncated too high subtitle" );
             break;
@@ -1978,8 +2080,7 @@ static int ProcessLines( filter_t *p_filter,
     free( p_fribidi_string );
     free( pi_karaoke_bar );
 
-    p_size->x = bbox.xMax - bbox.xMin;
-    p_size->y = bbox.yMax - bbox.yMin;
+    *p_bbox = bbox;
     return VLC_SUCCESS;
 }
 
@@ -2019,7 +2120,8 @@ static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
     /* */
     int rv = VLC_SUCCESS;
     int i_text_length = 0;
-    FT_Vector result = {0, 0};
+    FT_BBox bbox;
+    int i_max_face_height;
     line_desc_t *p_lines = NULL;
 
     uint32_t *pi_k_durations   = NULL;
@@ -2112,7 +2214,7 @@ static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
     if( !rv && i_text_length > 0 )
     {
         rv = ProcessLines( p_filter,
-                           &p_lines, &result,
+                           &p_lines, &bbox, &i_max_face_height,
                            psz_text, pp_styles, pi_k_durations, i_text_length );
     }
 
@@ -2121,15 +2223,15 @@ static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
 
     /* Don't attempt to render text that couldn't be layed out
      * properly. */
-    if( !rv && i_text_length > 0 && result.x > 0 && result.y > 0)
+    if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
     {
         if( var_InheritBool( p_filter, "freetype-yuvp" ) )
-            RenderYUVP( p_filter, p_region_out, p_lines,
-                        result.x, result.y );
+            RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
         else
-            RenderYUVA( p_filter, p_region_out, p_lines,
-                        result.x, result.y );
-
+            RenderYUVA( p_filter, p_region_out,
+                        p_lines,
+                        &bbox,
+                        p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0 );
 
         /* With karaoke, we're going to have to render the text a number
          * of times to show the progress marker on the text.
@@ -2205,6 +2307,18 @@ static int Create( vlc_object_t *p_this )
     p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
     p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
 
+    p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
+    p_sys->i_background_opacity = __MAX( __MIN( p_sys->i_background_opacity, 255 ), 0 );
+    p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
+    p_sys->i_background_color = __MAX( __MIN( p_sys->i_background_color, 0xFFFFFF ), 0 );
+
+    p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
+    p_sys->f_outline_thickness = __MAX( __MIN( p_sys->f_outline_thickness, 0.5 ), 0.0 );
+    p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
+    p_sys->i_outline_opacity = __MAX( __MIN( p_sys->i_outline_opacity, 255 ), 0 );
+    p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
+    p_sys->i_outline_color = __MAX( __MIN( p_sys->i_outline_color, 0xFFFFFF ), 0 );
+
 #ifdef WIN32
     /* Get Windows Font folder */
     wchar_t wdir[MAX_PATH];
@@ -2294,6 +2408,13 @@ static int Create( vlc_object_t *p_this )
 
     if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
 
+    p_sys->p_stroker = NULL;
+    if( p_sys->f_outline_thickness > 0.001 )
+    {
+        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->pp_font_attachments = NULL;
     p_sys->i_font_attachments = 0;
@@ -2347,6 +2468,8 @@ static void Destroy( vlc_object_t *p_this )
      * even if no other library functions have been made since FcInit(),
      * so don't call it. */
 
+    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 );