]> git.sesse.net Git - vlc/blobdiff - modules/misc/text_renderer/freetype.c
Added margin when using a background in freetype.
[vlc] / modules / misc / text_renderer / freetype.c
index 37ed9370524d470c93d8481259ecc3a6608934fe..1524ca3f9989e39978110f1f55d7844f5d1c3a75 100644 (file)
 #include <vlc_strings.h>                       /* resolve_xml_special_chars */
 #include <vlc_charset.h>                       /* ToCharset */
 #include <vlc_dialog.h>                        /* FcCache dialog */
+#include <vlc_filter.h>                                      /* filter_sys_t */
+#include <vlc_text_style.h>                                   /* text_style_t*/
 
 /* Default fonts */
 #ifdef __APPLE__
-# define DEFAULT_FONT "/Library/Fonts/Arial Black.ttf"
+# define DEFAULT_FONT_FILE "/Library/Fonts/Arial Black.ttf"
 # define DEFAULT_FAMILY "Arial Black"
 #elif defined( WIN32 )
-# define DEFAULT_FONT "arial.ttf" /* Default path font found at run-time */
+# define DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
 # define DEFAULT_FAMILY "Arial"
 #elif defined( HAVE_MAEMO )
-# define DEFAULT_FONT "/usr/share/fonts/nokia/nosnb.ttf"
+# define DEFAULT_FONT_FILE "/usr/share/fonts/nokia/nosnb.ttf"
 # define DEFAULT_FAMILY "Nokia Sans Bold"
 #else
-# define DEFAULT_FONT "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
+# define DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
 # define DEFAULT_FAMILY "Serif Bold"
 #endif
 
@@ -73,7 +75,6 @@
 
 /* Win32 GDI */
 #ifdef WIN32
-# define _WIN32_IE 0x0500
 # include <windows.h>
 # include <shlobj.h>
 # define HAVE_STYLES
@@ -118,23 +119,15 @@ 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")
+
 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") };
 #define YUVP_TEXT N_("Use YUVP renderer")
 #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 EFFECT_TEXT N_("Font Effect")
-#define EFFECT_LONGTEXT N_("It is possible to apply effects to the rendered " \
-"text to improve its readability." )
-
-enum { EFFECT_BACKGROUND  = 1,
-       EFFECT_OUTLINE     = 2,
-       EFFECT_OUTLINE_FAT = 3,
-};
-static int const pi_effects[] = { EFFECT_BACKGROUND, EFFECT_OUTLINE, EFFECT_OUTLINE_FAT };
-static const char *const ppsz_effects_text[] = {
-    N_("Background"),N_("Outline"), N_("Fat Outline") };
 
 static const int pi_color_values[] = {
   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
@@ -155,7 +148,7 @@ vlc_module_begin ()
 #ifdef HAVE_STYLES
     add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
 #else
-    add_font( "freetype-font", DEFAULT_FONT, FONT_TEXT, FONT_LONGTEXT, false )
+    add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
 #endif
 
     add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
@@ -163,7 +156,7 @@ vlc_module_begin ()
         change_safe()
 
     /* opacity valid on 0..255, with default 255 = fully opaque */
-    add_integer_with_range( "freetype-opacity", 255, 0, 255, NULL,
+    add_integer_with_range( "freetype-opacity", 255, 0, 255,
         OPACITY_TEXT, OPACITY_LONGTEXT, false )
         change_safe()
 
@@ -173,15 +166,20 @@ vlc_module_begin ()
         change_integer_list( pi_color_values, ppsz_color_descriptions )
         change_safe()
 
+    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( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
                  FONTSIZER_LONGTEXT, false )
         change_integer_list( pi_sizes, ppsz_sizes_text )
         change_safe()
 
-    add_integer( "freetype-effect", 2, EFFECT_TEXT,
-                 EFFECT_LONGTEXT, false )
-        change_integer_list( pi_effects, ppsz_effects_text )
-        change_safe()
+    add_obsolete_integer( "freetype-effect" );
 
     add_bool( "freetype-yuvp", false, YUVP_TEXT,
               YUVP_LONGTEXT, true )
@@ -195,74 +193,35 @@ vlc_module_end ()
  * Local prototypes
  *****************************************************************************/
 
-/* The RenderText call maps to pf_render_string, defined in vlc_filter.h */
-static int RenderText( filter_t *, subpicture_region_t *,
-                       subpicture_region_t * );
-
-#ifdef HAVE_STYLES
-static int RenderHtml( filter_t *, subpicture_region_t *,
-                       subpicture_region_t * );
-#endif
-#ifdef HAVE_FONTCONFIG
-static void FontConfig_BuildCache( filter_t * );
-static char* FontConfig_Select( FcConfig *, const char *,
-                                bool, bool, int, int * );
-#endif
-#ifdef WIN32
-static char* Win32_Select( filter_t *, const char *,
-                           bool, bool, int, int * );
-#endif
-
-static int LoadFontsFromAttachments( filter_t *p_filter );
-
-static int GetFontSize( filter_t *p_filter );
-static int SetFontSize( filter_t *, int );
-static void YUVFromRGB( uint32_t i_argb,
-                        uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v );
+typedef struct
+{
+    FT_BitmapGlyph p_glyph;
+    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 RGB information for styled text
-     * -- if the rendering mode supports it (RenderYUVA) and
-     *  b_new_color_mode is set, then it becomes possible to
-     *  have multicoloured text within the subtitles. */
-    uint32_t       *p_fg_rgb;
-    uint32_t       *p_bg_rgb;
-    uint8_t        *p_fg_bg_ratio; /* 0x00=100% FG --> 0x7F=100% BG */
-    bool            b_new_color_mode;
-    /** underline information -- only supplied if text should be underlined */
-    int            *pi_underline_offset;
-    uint16_t       *pi_underline_thickness;
-
-    int             i_height;
-    int             i_width;
-    int             i_red, i_green, i_blue;
-    int             i_alpha;
-
     line_desc_t    *p_next;
+
+    int              i_width;
+    int              i_base_line;
+    int              i_character_count;
+    line_character_t *p_character;
 };
-static line_desc_t *NewLine( int );
 
-typedef struct
+typedef struct font_stack_t font_stack_t;
+struct font_stack_t
 {
-    int         i_font_size;
-    uint32_t    i_font_color;         /* ARGB */
-    uint32_t    i_karaoke_bg_color;   /* ARGB */
-    bool        b_italic;
-    bool        b_bold;
-    bool        b_underline;
-    bool        b_through;
-    char       *psz_fontname;
-} ft_style_t;
-
-static int Render( filter_t *, subpicture_region_t *, line_desc_t *, int, int);
-static void FreeLines( line_desc_t * );
-static void FreeLine( line_desc_t * );
+    char          *psz_name;
+    int            i_size;
+    uint32_t       i_color;            /* ARGB */
+    uint32_t       i_karaoke_bg_color; /* ARGB */
+
+    font_stack_t  *p_next;
+};
 
 /*****************************************************************************
  * filter_sys_t: freetype local data
@@ -274,16 +233,17 @@ struct filter_sys_t
 {
     FT_Library     p_library;   /* handle to library     */
     FT_Face        p_face;      /* handle to face object */
-    bool           i_use_kerning;
     uint8_t        i_font_opacity;
     int            i_font_color;
     int            i_font_size;
-    int            i_effect;
+
+    uint8_t        i_background_opacity;
+    int            i_background_color;
 
     int            i_default_font_size;
     int            i_display_height;
-#ifdef HAVE_STYLES
     char*          psz_fontfamily;
+#ifdef HAVE_STYLES
     xml_reader_t  *p_xml;
 #ifdef WIN32
     char*          psz_win_fonts_path;
@@ -294,194 +254,20 @@ struct filter_sys_t
     int                  i_font_attachments;
 };
 
-#define UCHAR uint32_t
-#define TR_DEFAULT_FONT p_sys->psz_fontfamily
-#define TR_FONT_STYLE_PTR ft_style_t *
-
-#include "text_renderer.h"
-
-/*****************************************************************************
- * Create: allocates osd-text video thread output method
- *****************************************************************************
- * This function allocates and initializes a Clone vout method.
- *****************************************************************************/
-static int Create( vlc_object_t *p_this )
-{
-    filter_t      *p_filter = (filter_t *)p_this;
-    filter_sys_t  *p_sys;
-    char          *psz_fontfile   = NULL;
-    char          *psz_fontfamily = NULL;
-    int            i_error = 0, fontindex = 0;
-
-    /* Allocate structure */
-    p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
-    if( !p_sys )
-        return VLC_ENOMEM;
-
-#ifdef HAVE_STYLES
-    p_sys->psz_fontfamily   = NULL;
-    p_sys->p_xml            = NULL;
-#endif
-    p_sys->p_face           = 0;
-    p_sys->p_library        = 0;
-    p_sys->i_font_size      = 0;
-    p_sys->i_display_height = 0;
-
-    var_Create( p_filter, "freetype-rel-fontsize",
-                VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
-
-    psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
-    p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
-    p_sys->i_effect = var_InheritInteger( p_filter, "freetype-effect" );
-    p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
-    p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
-    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 );
-
-#ifdef WIN32
-    /* Get Windows Font folder */
-    wchar_t wdir[MAX_PATH];
-    if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
-    {
-        GetWindowsDirectoryW( wdir, MAX_PATH );
-        wcscat( wdir, L"\\fonts" );
-    }
-    p_sys->psz_win_fonts_path = FromWide( wdir );
-#endif
-
-    /* Set default psz_fontfamily */
-    if( !psz_fontfamily || !*psz_fontfamily )
-    {
-        free( psz_fontfamily );
-#ifdef HAVE_STYLES
-        psz_fontfamily = strdup( DEFAULT_FAMILY );
-#else
-        psz_fontfamily = (char *)malloc( PATH_MAX + 1 );
-        if( !psz_fontfamily )
-            goto error;
-# ifdef WIN32
-        strcat( psz_fontfamily, p_sys->psz_win_fonts_path );
-        strcat( psz_fontfamily, DEFAULT_FONT );
-# else
-        strcpy( psz_fontfamily, DEFAULT_FONT );
-# endif
-        msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
-#endif
-    }
-
-    /* Set the current font file */
-#ifdef HAVE_STYLES
-    p_sys->psz_fontfamily = psz_fontfamily;
-#ifdef HAVE_FONTCONFIG
-    FontConfig_BuildCache( p_filter );
-
-    /* */
-    psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
-                                      p_sys->i_default_font_size, &fontindex );
-#elif defined(WIN32)
-    psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
-                                 p_sys->i_default_font_size, &fontindex );
-
-#endif
-    msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
-
-    /* If nothing is found, use the default family */
-    if( !psz_fontfile )
-        psz_fontfile = psz_fontfamily;
-
-#else /* !HAVE_STYLES */
-    /* Use the default file */
-    psz_fontfile = psz_fontfamily;
-#endif
-
-    /* */
-    i_error = FT_Init_FreeType( &p_sys->p_library );
-    if( i_error )
-    {
-        msg_Err( p_filter, "couldn't initialize freetype" );
-        goto error;
-    }
-
-    i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
-                           fontindex, &p_sys->p_face );
-
-    if( i_error == FT_Err_Unknown_File_Format )
-    {
-        msg_Err( p_filter, "file %s have unknown format",
-                 psz_fontfile ? psz_fontfile : "(null)" );
-        goto error;
-    }
-    else if( i_error )
-    {
-        msg_Err( p_filter, "failed to load font file %s",
-                 psz_fontfile ? psz_fontfile : "(null)" );
-        goto error;
-    }
-
-    i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
-    if( i_error )
-    {
-        msg_Err( p_filter, "font has no unicode translation table" );
-        goto error;
-    }
-
-    p_sys->i_use_kerning = FT_HAS_KERNING( p_sys->p_face );
-
-    if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
-
-
-    p_sys->pp_font_attachments = NULL;
-    p_sys->i_font_attachments = 0;
-
-    p_filter->pf_render_text = RenderText;
-#ifdef HAVE_STYLES
-    p_filter->pf_render_html = RenderHtml;
-#else
-    p_filter->pf_render_html = NULL;
-#endif
-
-    LoadFontsFromAttachments( p_filter );
-
-    return VLC_SUCCESS;
-
-error:
-    if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
-    if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
-    free( psz_fontfamily );
-    free( p_sys );
-    return VLC_EGENERIC;
-}
-
-/*****************************************************************************
- * Destroy: destroy Clone video thread output method
- *****************************************************************************
- * Clean up all data and library connections
- *****************************************************************************/
-static void Destroy( vlc_object_t *p_this )
+/* */
+static void YUVFromRGB( uint32_t i_argb,
+                    uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
 {
-    filter_t *p_filter = (filter_t *)p_this;
-    filter_sys_t *p_sys = p_filter->p_sys;
-
-    if( p_sys->pp_font_attachments )
-    {
-        for( int k = 0; k < p_sys->i_font_attachments; k++ )
-            vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
-
-        free( p_sys->pp_font_attachments );
-    }
-
-#ifdef HAVE_STYLES
-    if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
-    free( p_sys->psz_fontfamily );
-#endif
-
-    /* FcFini asserts calling the subfunction FcCacheFini()
-     * even if no other library functions have been made since FcInit(),
-     * so don't call it. */
+    int i_red   = ( i_argb & 0x00ff0000 ) >> 16;
+    int i_green = ( i_argb & 0x0000ff00 ) >>  8;
+    int i_blue  = ( i_argb & 0x000000ff );
 
-    FT_Done_Face( p_sys->p_face );
-    FT_Done_FreeType( p_sys->p_library );
-    free( p_sys );
+    *pi_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
+                      802 * i_blue + 4096 + 131072 ) >> 13, 235);
+    *pi_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
+                     3598 * i_blue + 4096 + 1048576) >> 13, 240);
+    *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
+                      -585 * i_blue + 4096 + 1048576) >> 13, 240);
 }
 
 /*****************************************************************************
@@ -498,7 +284,7 @@ static int LoadFontsFromAttachments( filter_t *p_filter )
         return VLC_EGENERIC;
 
     p_sys->i_font_attachments = 0;
-    p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof( input_attachment_t * ));
+    p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
     if( !p_sys->pp_font_attachments )
         return VLC_ENOMEM;
 
@@ -522,74 +308,314 @@ static int LoadFontsFromAttachments( filter_t *p_filter )
     return VLC_SUCCESS;
 }
 
-/*****************************************************************************
- * Render: place string in picture
- *****************************************************************************
- * This function merges the previously rendered freetype glyphs into a picture
- *****************************************************************************/
-static int Render( filter_t *p_filter, subpicture_region_t *p_region,
-                   line_desc_t *p_line, int i_width, int i_height )
+static int GetFontSize( filter_t *p_filter )
 {
-    VLC_UNUSED(p_filter);
-    static const uint8_t pi_gamma[16] =
-        {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
-          0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
-
-    uint8_t *p_dst;
-    video_format_t fmt;
-    int i, x, y, i_pitch;
-    uint8_t i_y; /* YUV values, derived from incoming RGB */
-    int8_t i_u, i_v;
-
-    /* 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;
-
-    assert( !p_region->p_picture );
-    p_region->p_picture = picture_NewFromFormat( &fmt );
-    if( !p_region->p_picture )
-        return VLC_EGENERIC;
-    fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
-    p_region->fmt = fmt;
-
-    /* Calculate text color components */
-    i_y = (uint8_t)(( 66 * p_line->i_red  + 129 * p_line->i_green +
-                      25 * p_line->i_blue + 128) >> 8) +  16;
-    i_u = (int8_t)(( -38 * p_line->i_red  -  74 * p_line->i_green +
-                     112 * p_line->i_blue + 128) >> 8) + 128;
-    i_v = (int8_t)(( 112 * p_line->i_red  -  94 * p_line->i_green -
-                      18 * p_line->i_blue + 128) >> 8) + 128;
+    filter_sys_t *p_sys = p_filter->p_sys;
+    int           i_size = 0;
 
-    /* Build palette */
-    fmt.p_palette->i_entries = 16;
-    for( i = 0; i < 8; i++ )
+    if( p_sys->i_default_font_size )
     {
-        fmt.p_palette->palette[i][0] = 0;
-        fmt.p_palette->palette[i][1] = 0x80;
-        fmt.p_palette->palette[i][2] = 0x80;
-        fmt.p_palette->palette[i][3] = pi_gamma[i];
-        fmt.p_palette->palette[i][3] =
-            (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
+        i_size = p_sys->i_default_font_size;
     }
-    for( i = 8; i < fmt.p_palette->i_entries; i++ )
+    else
     {
-        fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
-        fmt.p_palette->palette[i][1] = i_u;
-        fmt.p_palette->palette[i][2] = i_v;
-        fmt.p_palette->palette[i][3] = pi_gamma[i];
-        fmt.p_palette->palette[i][3] =
-            (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
+        int i_ratio = var_GetInteger( p_filter, "freetype-rel-fontsize" );
+        if( i_ratio > 0 )
+        {
+            i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
+            p_filter->p_sys->i_display_height = p_filter->fmt_out.video.i_height;
+        }
     }
-
+    if( i_size <= 0 )
+    {
+        msg_Warn( p_filter, "invalid fontsize, using 12" );
+        i_size = 12;
+    }
+    return i_size;
+}
+
+static int SetFontSize( filter_t *p_filter, int i_size )
+{
+    filter_sys_t *p_sys = p_filter->p_sys;
+
+    if( !i_size )
+    {
+        i_size = GetFontSize( p_filter );
+
+        msg_Dbg( p_filter, "using fontsize: %i", i_size );
+    }
+
+    p_sys->i_font_size = i_size;
+
+    if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
+    {
+        msg_Err( p_filter, "couldn't set font size to %d", i_size );
+        return VLC_EGENERIC;
+    }
+
+    return VLC_SUCCESS;
+}
+
+#ifdef HAVE_STYLES
+#ifdef HAVE_FONTCONFIG
+static void FontConfig_BuildCache( filter_t *p_filter )
+{
+    /* */
+    msg_Dbg( p_filter, "Building font databases.");
+    mtime_t t1, t2;
+    t1 = mdate();
+
+#ifdef WIN32
+    dialog_progress_bar_t *p_dialog = NULL;
+    FcConfig *fcConfig = FcInitLoadConfig();
+
+    p_dialog = dialog_ProgressCreate( p_filter,
+            _("Building font cache"),
+            _("Please wait while your font cache is rebuilt.\n"
+                "This should take less than a few minutes."), NULL );
+
+/*    if( p_dialog )
+        dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
+
+    FcConfigBuildFonts( fcConfig );
+    if( p_dialog )
+    {
+//        dialog_ProgressSet( p_dialog, NULL, 1.0 );
+        dialog_ProgressDestroy( p_dialog );
+        p_dialog = NULL;
+    }
+#endif
+    t2 = mdate();
+    msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
+}
+
+/***
+ * \brief Selects a font matching family, bold, italic provided
+ ***/
+static char* FontConfig_Select( FcConfig* config, const char* family,
+                          bool b_bold, bool b_italic, int i_size, int *i_idx )
+{
+    FcResult result = FcResultMatch;
+    FcPattern *pat, *p_pat;
+    FcChar8* val_s;
+    FcBool val_b;
+
+    /* Create a pattern and fills it */
+    pat = FcPatternCreate();
+    if (!pat) return NULL;
+
+    /* */
+    FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
+    FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
+    FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
+    FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
+    if( i_size != -1 )
+    {
+        char *psz_fontsize;
+        if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
+        {
+            FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
+            free( psz_fontsize );
+        }
+    }
+
+    /* */
+    FcDefaultSubstitute( pat );
+    if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
+    {
+        FcPatternDestroy( pat );
+        return NULL;
+    }
+
+    /* Find the best font for the pattern, destroy the pattern */
+    p_pat = FcFontMatch( config, pat, &result );
+    FcPatternDestroy( pat );
+    if( !p_pat || result == FcResultNoMatch ) return NULL;
+
+    /* Check the new pattern */
+    if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
+        || ( val_b != FcTrue ) )
+    {
+        FcPatternDestroy( p_pat );
+        return NULL;
+    }
+    if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
+    {
+        *i_idx = 0;
+    }
+
+    if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
+    {
+        FcPatternDestroy( p_pat );
+        return NULL;
+    }
+
+    /* if( strcasecmp((const char*)val_s, family ) != 0 )
+        msg_Warn( p_filter, "fontconfig: selected font family is not"
+                            "the requested one: '%s' != '%s'\n",
+                            (const char*)val_s, family );   */
+
+    if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
+    {
+        FcPatternDestroy( p_pat );
+        return NULL;
+    }
+
+    FcPatternDestroy( p_pat );
+    return strdup( (const char*)val_s );
+}
+#endif
+
+#ifdef WIN32
+#define UNICODE
+#define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
+
+static int GetFileFontByName( const char *font_name, char **psz_filename )
+{
+    HKEY hKey;
+    wchar_t vbuffer[MAX_PATH];
+    wchar_t dbuffer[256];
+
+    if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey) != ERROR_SUCCESS )
+        return 1;
+
+    for( int index = 0;; index++ )
+    {
+        DWORD vbuflen = MAX_PATH - 1;
+        DWORD dbuflen = 255;
+
+        if( RegEnumValueW( hKey, index, vbuffer, &vbuflen,
+                           NULL, NULL, (LPBYTE)dbuffer, &dbuflen) != ERROR_SUCCESS )
+            return 2;
+
+        char *psz_value = FromWide( vbuffer );
+
+        char *s = strchr( psz_value,'(' );
+        if( s != NULL && s != psz_value ) s[-1] = '\0';
+
+        /* Manage concatenated font names */
+        if( strchr( psz_value, '&') ) {
+            if( strcasestr( psz_value, font_name ) != NULL )
+                break;
+        }
+        else {
+            if( strcasecmp( psz_value, font_name ) == 0 )
+                break;
+        }
+    }
+
+    *psz_filename = FromWide( dbuffer );
+    return 0;
+}
+
+
+static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
+                                     DWORD type, LPARAM lParam)
+{
+    VLC_UNUSED( metric );
+    if( (type & RASTER_FONTTYPE) ) return 1;
+    // if( lpelfe->elfScript ) FIXME
+
+    return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
+}
+
+static char* Win32_Select( filter_t *p_filter, const char* family,
+                           bool b_bold, bool b_italic, int i_size, int *i_idx )
+{
+    VLC_UNUSED( i_size );
+    // msg_Dbg( p_filter, "Here in Win32_Select, asking for %s", family );
+
+    /* */
+    LOGFONT lf;
+    lf.lfCharSet = DEFAULT_CHARSET;
+    if( b_italic )
+        lf.lfItalic = true;
+    if( b_bold )
+        lf.lfWeight = FW_BOLD;
+    strncpy( (LPSTR)&lf.lfFaceName, family, 32);
+
+    /* */
+    char *psz_filename = NULL;
+    HDC hDC = GetDC( NULL );
+    EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
+    ReleaseDC(NULL, hDC);
+
+    if( psz_filename == NULL )
+        return NULL;
+
+    /* FIXME: increase i_idx, when concatenated strings  */
+    i_idx = 0;
+
+    /* */
+    char *psz_tmp;
+    if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
+        return NULL;
+    return psz_tmp;
+}
+#endif
+
+#endif
+
+
+/*****************************************************************************
+ * RenderYUVP: place string in picture
+ *****************************************************************************
+ * 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,
+                       FT_BBox *p_bbox )
+{
+    VLC_UNUSED(p_filter);
+    static const uint8_t pi_gamma[16] =
+        {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
+          0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+    uint8_t *p_dst;
+    video_format_t fmt;
+    int i, x, y, i_pitch;
+    uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
+
+    /* Create a new subpicture region */
+    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 );
+    if( !p_region->p_picture )
+        return VLC_EGENERIC;
+    fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
+    p_region->fmt = fmt;
+
+    /* Calculate text color components
+     * Only use the first color */
+    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;
+    for( i = 0; i < 8; i++ )
+    {
+        fmt.p_palette->palette[i][0] = 0;
+        fmt.p_palette->palette[i][1] = 0x80;
+        fmt.p_palette->palette[i][2] = 0x80;
+        fmt.p_palette->palette[i][3] = pi_gamma[i];
+        fmt.p_palette->palette[i][3] =
+            (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
+    }
+    for( i = 8; i < fmt.p_palette->i_entries; i++ )
+    {
+        fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
+        fmt.p_palette->palette[i][1] = i_u;
+        fmt.p_palette->palette[i][2] = i_v;
+        fmt.p_palette->palette[i][3] = pi_gamma[i];
+        fmt.p_palette->palette[i][3] =
+            (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
+    }
+
     p_dst = p_region->p_picture->Y_PIXELS;
     i_pitch = p_region->p_picture->Y_PITCH;
 
@@ -598,44 +624,32 @@ static int Render( 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;
             }
         }
     }
@@ -667,960 +681,776 @@ static int Render( filter_t *p_filter, subpicture_region_t *p_region,
     return VLC_SUCCESS;
 }
 
-static void UnderlineGlyphYUVA( int i_line_thickness, int i_line_offset, bool b_ul_next_char,
-                                FT_BitmapGlyph  p_this_glyph, FT_Vector *p_this_glyph_pos,
-                                FT_BitmapGlyph  p_next_glyph, FT_Vector *p_next_glyph_pos,
-                                int i_glyph_tmax, int i_align_offset,
-                                uint8_t i_y, uint8_t i_u, uint8_t i_v,
-                                subpicture_region_t *p_region)
+/*****************************************************************************
+ * RenderYUVA: place string in picture
+ *****************************************************************************
+ * This function merges the previously rendered freetype glyphs into a picture
+ *****************************************************************************/
+static inline void BlendYUVAPixel( picture_t *p_picture,
+                                   int i_picture_x, int i_picture_y,
+                                   int i_a, int i_y, int i_u, int i_v,
+                                   int i_alpha )
 {
-    int i_pitch;
-    uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
+    int i_an = i_a * i_alpha / 255;
 
-    p_dst_y = p_region->p_picture->Y_PIXELS;
-    p_dst_u = p_region->p_picture->U_PIXELS;
-    p_dst_v = p_region->p_picture->V_PIXELS;
-    p_dst_a = p_region->p_picture->A_PIXELS;
-    i_pitch = p_region->p_picture->A_PITCH;
+    uint8_t *p_y = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + i_picture_x];
+    uint8_t *p_u = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch + i_picture_x];
+    uint8_t *p_v = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch + i_picture_x];
+    uint8_t *p_a = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch + i_picture_x];
 
-    int i_offset = ( p_this_glyph_pos->y + i_glyph_tmax + i_line_offset + 3 ) * i_pitch +
-                     p_this_glyph_pos->x + p_this_glyph->left + 3 + i_align_offset;
-
-    for( int y = 0; y < i_line_thickness; y++ )
+    int i_ao = *p_a;
+    if( i_ao == 0 )
     {
-        int i_extra = p_this_glyph->bitmap.width;
-
-        if( b_ul_next_char )
-        {
-            i_extra = (p_next_glyph_pos->x + p_next_glyph->left) -
-                      (p_this_glyph_pos->x + p_this_glyph->left);
-        }
-        for( int x = 0; x < i_extra; x++ )
+        *p_y = i_y;
+        *p_u = i_u;
+        *p_v = i_v;
+        *p_a = i_an;
+    }
+    else
+    {
+        *p_a = 255 - (255 - *p_a) * (255 - i_an) / 255;
+        if( *p_a != 0 )
         {
-            bool b_ok = true;
-
-            /* break the underline around the tails of any glyphs which cross it */
-            /* Strikethrough doesn't get broken */
-            for( int z = x - i_line_thickness;
-                 z < x + i_line_thickness && b_ok && (i_line_offset >= 0);
-                 z++ )
-            {
-                if( p_next_glyph && ( z >= i_extra ) )
-                {
-                    int i_row = i_line_offset + p_next_glyph->top + y;
-
-                    if( ( p_next_glyph->bitmap.rows > i_row ) &&
-                        p_next_glyph->bitmap.buffer[p_next_glyph->bitmap.width * i_row + z-i_extra] )
-                    {
-                        b_ok = false;
-                    }
-                }
-                else if ((z > 0 ) && (z < p_this_glyph->bitmap.width))
-                {
-                    int i_row = i_line_offset + p_this_glyph->top + y;
-
-                    if( ( p_this_glyph->bitmap.rows > i_row ) &&
-                        p_this_glyph->bitmap.buffer[p_this_glyph->bitmap.width * i_row + z] )
-                    {
-                        b_ok = false;
-                    }
-                }
-            }
-
-            if( b_ok )
-            {
-                p_dst_y[i_offset+x] = (i_y * 255) >> 8;
-                p_dst_u[i_offset+x] = i_u;
-                p_dst_v[i_offset+x] = i_v;
-                p_dst_a[i_offset+x] = 255;
-            }
+            *p_y = ( *p_y * i_ao * (255 - i_an) / 255 + i_y * i_an ) / *p_a;
+            *p_u = ( *p_u * i_ao * (255 - i_an) / 255 + i_u * i_an ) / *p_a;
+            *p_v = ( *p_v * i_ao * (255 - i_an) / 255 + i_v * i_an ) / *p_a;
         }
-        i_offset += i_pitch;
     }
 }
 
-static void DrawBlack( line_desc_t *p_line, int i_width, subpicture_region_t *p_region, int xoffset, int yoffset )
+static inline void BlendYUVAGlyph( 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 )
 {
-    uint8_t *p_dst = p_region->p_picture->A_PIXELS;
-    int i_pitch = p_region->p_picture->A_PITCH;
-    int y;
-
-    for( ; p_line != NULL; p_line = p_line->p_next )
+    for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
     {
-        int i_glyph_tmax=0, i = 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 )
-        {
-            if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
-            {
-                i_align_offset = i_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;
-            }
-        }
-
-        for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
-        {
-            FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
+        for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
+            BlendYUVAPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
+                            i_a, i_y, i_u, i_v,
+                            p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
+    }
+}
 
-            i_offset = ( p_line->p_glyph_pos[ i ].y +
-                i_glyph_tmax - p_glyph->top + 3 + yoffset ) *
-                i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
-                i_align_offset +xoffset;
+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,
+                                  const line_character_t *p_current,
+                                  const line_character_t *p_next )
+{
+    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( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
-            {
-                for( int x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
-                {
-                    if( p_glyph->bitmap.buffer[i_bitmap_offset] )
-                        if( p_dst[i_offset+x] <
-                            ((int)p_glyph->bitmap.buffer[i_bitmap_offset]) )
-                            p_dst[i_offset+x] =
-                                ((int)p_glyph->bitmap.buffer[i_bitmap_offset]);
-                }
-                i_offset += i_pitch;
-            }
-        }
+    for( int dx = 0; dx < i_line_width; dx++ )
+    {
+        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 );
     }
 }
 
-/*****************************************************************************
- * Render: place string in picture
- *****************************************************************************
- * This function merges the previously rendered freetype glyphs into a picture
- *****************************************************************************/
-static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
-                   line_desc_t *p_line, int i_width, int i_height )
+static int RenderYUVA( filter_t *p_filter,
+                       subpicture_region_t *p_region,
+                       line_desc_t *p_line_head,
+                       FT_BBox *p_bbox,
+                       int i_margin )
 {
-    uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
-    video_format_t fmt;
-    int i, y, i_pitch, i_alpha;
-    uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
-
-    if( i_width == 0 || i_height == 0 )
-        return VLC_SUCCESS;
+    filter_sys_t *p_sys = p_filter->p_sys;
 
     /* Create a new subpicture region */
-    memset( &fmt, 0, sizeof(video_format_t) );
-    fmt.i_chroma = VLC_CODEC_YUVA;
-    fmt.i_width = fmt.i_visible_width = i_width + 6;
-    fmt.i_height = fmt.i_visible_height = i_height + 6;
-    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;
+    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_text_width  + 2 * i_margin;
+    fmt.i_height         =
+    fmt.i_visible_height = i_text_height + 2 * i_margin;
 
-    p_region->p_picture = picture_NewFromFormat( &fmt );
+    picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
     if( !p_region->p_picture )
         return VLC_EGENERIC;
     p_region->fmt = fmt;
 
-    /* Calculate text color components */
-    YUVFromRGB( (p_line->i_red   << 16) |
-                (p_line->i_green <<  8) |
-                (p_line->i_blue       ),
-                &i_y, &i_u, &i_v);
-    i_alpha = p_line->i_alpha;
-
-    p_dst_y = p_region->p_picture->Y_PIXELS;
-    p_dst_u = p_region->p_picture->U_PIXELS;
-    p_dst_v = p_region->p_picture->V_PIXELS;
-    p_dst_a = p_region->p_picture->A_PIXELS;
-    i_pitch = p_region->p_picture->A_PITCH;
-
-    /* Initialize the region pixels */
-    if( p_filter->p_sys->i_effect != EFFECT_BACKGROUND )
+    /* Initialize the picture background */
+    uint8_t i_a = p_sys->i_background_opacity;
+    uint8_t 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 );
+    memset( p_picture->p[1].p_pixels, i_u,
+            p_picture->p[1].i_pitch * p_picture->p[1].i_lines );
+    memset( p_picture->p[2].p_pixels, i_v,
+            p_picture->p[2].i_pitch * p_picture->p[2].i_lines );
+    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 )
     {
-        memset( p_dst_y, 0x00, i_pitch * p_region->fmt.i_height );
-        memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
-        memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
-        memset( p_dst_a, 0, i_pitch * p_region->fmt.i_height );
-    }
-    else
-    {
-        memset( p_dst_y, 0x0, i_pitch * p_region->fmt.i_height );
-        memset( p_dst_u, 0x80, i_pitch * p_region->fmt.i_height );
-        memset( p_dst_v, 0x80, i_pitch * p_region->fmt.i_height );
-        memset( p_dst_a, 0x80, i_pitch * p_region->fmt.i_height );
-    }
-    if( p_filter->p_sys->i_effect == EFFECT_OUTLINE ||
-        p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
-    {
-        DrawBlack( p_line, i_width, p_region,  0,  0);
-        DrawBlack( p_line, i_width, p_region, -1,  0);
-        DrawBlack( p_line, i_width, p_region,  0, -1);
-        DrawBlack( p_line, i_width, p_region,  1,  0);
-        DrawBlack( p_line, i_width, p_region,  0,  1);
-    }
-
-    if( p_filter->p_sys->i_effect == EFFECT_OUTLINE_FAT )
-    {
-        DrawBlack( p_line, i_width, p_region, -1, -1);
-        DrawBlack( p_line, i_width, p_region, -1,  1);
-        DrawBlack( p_line, i_width, p_region,  1, -1);
-        DrawBlack( p_line, i_width, p_region,  1,  1);
-
-        DrawBlack( p_line, i_width, p_region, -2,  0);
-        DrawBlack( p_line, i_width, p_region,  0, -2);
-        DrawBlack( p_line, i_width, p_region,  2,  0);
-        DrawBlack( p_line, i_width, p_region,  0,  2);
-
-        DrawBlack( p_line, i_width, p_region, -2, -2);
-        DrawBlack( p_line, i_width, p_region, -2,  2);
-        DrawBlack( p_line, i_width, p_region,  2, -2);
-        DrawBlack( p_line, i_width, p_region,  2,  2);
-
-        DrawBlack( p_line, i_width, p_region, -3,  0);
-        DrawBlack( p_line, i_width, p_region,  0, -3);
-        DrawBlack( p_line, i_width, p_region,  3,  0);
-        DrawBlack( p_line, i_width, p_region,  0,  3);
-    }
-
-    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 = 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_offset = i_width - p_line->i_width;
-            }
+                i_align_left += ( i_text_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 += ( i_text_width - p_line->i_width ) / 2;
         }
+        int i_align_top = i_margin;
 
-        for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
+        /* Render all glyphs and underline/strikethrough */
+        for( int 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_a = 0xff - ((ch->i_color >> 24) & 0xff);
+            YUVFromRGB( ch->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 );
+
+            if( 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 );
+        }
+    }
 
-            i_offset = ( p_line->p_glyph_pos[ i ].y +
-                i_glyph_tmax - p_glyph->top + 3 ) *
-                i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 3 +
-                i_align_offset;
+    return VLC_SUCCESS;
+}
 
-            if( p_line->b_new_color_mode )
-            {
-                /* Every glyph can (and in fact must) have its own color */
-                YUVFromRGB( p_line->p_fg_rgb[ i ], &i_y, &i_u, &i_v );
-            }
+static text_style_t *CreateStyle( char *psz_fontname, int i_font_size,
+                                  uint32_t i_font_color, uint32_t i_karaoke_bg_color,
+                                  int i_style_flags )
+{
+    text_style_t *p_style = text_style_New();
+    if( !p_style )
+        return NULL;
 
-            for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
-            {
-                for( int x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
-                {
-                    uint8_t i_y_local = i_y;
-                    uint8_t i_u_local = i_u;
-                    uint8_t i_v_local = i_v;
+    p_style->psz_fontname = psz_fontname ? strdup( psz_fontname ) : NULL;
+    p_style->i_font_size  = i_font_size;
+    p_style->i_font_color = (i_font_color & 0x00ffffff) >>  0;
+    p_style->i_font_alpha = (i_font_color & 0xff000000) >> 24;
+    p_style->i_karaoke_background_color = (i_karaoke_bg_color & 0x00ffffff) >>  0;
+    p_style->i_karaoke_background_alpha = (i_karaoke_bg_color & 0xff000000) >> 24;
+    p_style->i_style_flags |= i_style_flags;
+    return p_style;
+}
 
-                    if( p_line->p_fg_bg_ratio != 0x00 )
-                    {
-                        int i_split = p_glyph->bitmap.width *
-                                      p_line->p_fg_bg_ratio[ i ] / 0x7f;
-
-                        if( x > i_split )
-                        {
-                            YUVFromRGB( p_line->p_bg_rgb[ i ],
-                                        &i_y_local, &i_u_local, &i_v_local );
-                        }
-                    }
+static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
+                     uint32_t i_color, uint32_t i_karaoke_bg_color )
+{
+    if( !p_font )
+        return VLC_EGENERIC;
 
-                    if( p_glyph->bitmap.buffer[i_bitmap_offset] )
-                    {
-                        p_dst_y[i_offset+x] = ((p_dst_y[i_offset+x] *(255-(int)p_glyph->bitmap.buffer[i_bitmap_offset])) +
-                                              i_y * ((int)p_glyph->bitmap.buffer[i_bitmap_offset])) >> 8;
+    font_stack_t *p_new = malloc( sizeof(*p_new) );
+    if( !p_new )
+        return VLC_ENOMEM;
 
-                        p_dst_u[i_offset+x] = i_u;
-                        p_dst_v[i_offset+x] = i_v;
+    p_new->p_next = NULL;
 
-                        if( p_filter->p_sys->i_effect == EFFECT_BACKGROUND )
-                            p_dst_a[i_offset+x] = 0xff;
-                    }
-                }
-                i_offset += i_pitch;
-            }
+    if( psz_name )
+        p_new->psz_name = strdup( psz_name );
+    else
+        p_new->psz_name = NULL;
 
-            if( p_line->pi_underline_thickness[ i ] )
-            {
-                UnderlineGlyphYUVA( p_line->pi_underline_thickness[ i ],
-                                    p_line->pi_underline_offset[ i ],
-                                   (p_line->pp_glyphs[i+1] && (p_line->pi_underline_thickness[ i + 1] > 0)),
-                                    p_line->pp_glyphs[i], &(p_line->p_glyph_pos[i]),
-                                    p_line->pp_glyphs[i+1], &(p_line->p_glyph_pos[i+1]),
-                                    i_glyph_tmax, i_align_offset,
-                                    i_y, i_u, i_v,
-                                    p_region);
-            }
-        }
+    p_new->i_size              = i_size;
+    p_new->i_color             = i_color;
+    p_new->i_karaoke_bg_color  = i_karaoke_bg_color;
+
+    if( !*p_font )
+    {
+        *p_font = p_new;
     }
+    else
+    {
+        font_stack_t *p_last;
 
-    /* Apply the alpha setting */
-    for( i = 0; i < (int)fmt.i_height * i_pitch; i++ )
-        p_dst_a[i] = p_dst_a[i] * (255 - i_alpha) / 255;
+        for( p_last = *p_font;
+             p_last->p_next;
+             p_last = p_last->p_next )
+        ;
 
+        p_last->p_next = p_new;
+    }
     return VLC_SUCCESS;
 }
 
-/**
- * This function renders a text subpicture region into another one.
- * It also calculates the size needed for this string, and renders the
- * needed glyphs into memory. It is used as pf_add_string callback in
- * the vout method by this module
- */
-static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
-                       subpicture_region_t *p_region_in )
+static int PopFont( font_stack_t **p_font )
 {
-    filter_sys_t *p_sys = p_filter->p_sys;
-    line_desc_t  *p_lines = NULL, *p_line = NULL, *p_next = NULL, *p_prev = NULL;
-    int i, i_pen_y, i_pen_x, i_error, i_glyph_index, i_previous;
-    uint32_t *psz_unicode, *psz_unicode_orig = NULL, i_char, *psz_line_start;
-    size_t i_string_length;
-    char *psz_string;
-    int i_font_color, i_font_alpha, i_font_size, i_red, i_green, i_blue;
-    vlc_value_t val;
-    int i_scale = 1000;
-
-    FT_BBox line;
-    FT_BBox glyph_size;
-    FT_Vector result;
-    FT_Glyph tmp_glyph;
-
-    /* Sanity check */
-    if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
-    psz_string = p_region_in->psz_text;
-    if( !psz_string || !*psz_string ) return VLC_EGENERIC;
+    font_stack_t *p_last, *p_next_to_last;
 
-    if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
-        i_scale = val.i_int;
+    if( !p_font || !*p_font )
+        return VLC_EGENERIC;
 
-    if( p_region_in->p_style )
+    p_next_to_last = NULL;
+    for( p_last = *p_font;
+         p_last->p_next;
+         p_last = p_last->p_next )
     {
-        i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
-        i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
-        i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
+        p_next_to_last = p_last;
     }
+
+    if( p_next_to_last )
+        p_next_to_last->p_next = NULL;
     else
-    {
-        i_font_color = p_sys->i_font_color;
-        i_font_alpha = 255 - p_sys->i_font_opacity;
-        i_font_size  = p_sys->i_default_font_size * i_scale / 1000;
-    }
+        *p_font = NULL;
 
-    if( i_font_color == 0xFFFFFF ) i_font_color = p_sys->i_font_color;
-    if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
-    SetFontSize( p_filter, i_font_size );
+    free( p_last->psz_name );
+    free( p_last );
 
-    i_red   = ( i_font_color & 0x00FF0000 ) >> 16;
-    i_green = ( i_font_color & 0x0000FF00 ) >>  8;
-    i_blue  =   i_font_color & 0x000000FF;
+    return VLC_SUCCESS;
+}
 
-    result.x =  result.y = 0;
-    line.xMin = line.xMax = line.yMin = line.yMax = 0;
+static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
+                     uint32_t *i_color, uint32_t *i_karaoke_bg_color )
+{
+    font_stack_t *p_last;
 
-#if defined(WORDS_BIGENDIAN)
-    psz_unicode = ToCharset( "UCS-4BE", psz_string, &i_string_length );
-#else
-    psz_unicode = ToCharset( "UCS-4LE", psz_string, &i_string_length );
-#endif
-    if( psz_unicode == NULL )
-        goto error;
-    psz_unicode_orig = psz_unicode;
-    i_string_length /= 4;
+    if( !p_font || !*p_font )
+        return VLC_EGENERIC;
 
-#if defined(HAVE_FRIBIDI)
-    {
-        uint32_t *p_fribidi_string;
-        size_t start_pos;
-        size_t pos = 0;
+    for( p_last=*p_font;
+         p_last->p_next;
+         p_last=p_last->p_next )
+    ;
 
-        p_fribidi_string = malloc( (i_string_length + 1) * sizeof(uint32_t) );
-        if( !p_fribidi_string )
-            goto error;
+    *psz_name            = p_last->psz_name;
+    *i_size              = p_last->i_size;
+    *i_color             = p_last->i_color;
+    *i_karaoke_bg_color  = p_last->i_karaoke_bg_color;
 
-        /* Do bidi conversion line-by-line */
-        while( pos < i_string_length )
-        {
-            while( pos < i_string_length )
-            {
-                i_char = psz_unicode[pos];
-                if (i_char != '\r' && i_char != '\n')
-                    break;
-                p_fribidi_string[pos] = i_char;
-                ++pos;
-            }
-            start_pos = pos;
-            while( pos < i_string_length )
-            {
-                i_char = psz_unicode[pos];
-                if (i_char == '\r' || i_char == '\n')
-                    break;
-                ++pos;
-            }
-            if (pos > start_pos)
-            {
-#if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
-                FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
-#else
-                FriBidiParType base_dir = FRIBIDI_PAR_LTR;
-#endif
-                fribidi_log2vis((FriBidiChar*)psz_unicode + start_pos,
-                                pos - start_pos,
-                                &base_dir,
-                                (FriBidiChar*)p_fribidi_string + start_pos,
-                                0, 0, 0);
-            }
-        }
-
-        free( psz_unicode_orig );
-        psz_unicode = psz_unicode_orig = p_fribidi_string;
-        p_fribidi_string[ i_string_length ] = 0;
-    }
-#endif
-
-    /* Calculate relative glyph positions and a bounding box for the
-     * entire string */
-    if( !(p_line = NewLine( strlen( psz_string ))) )
-        goto error;
-    p_lines = p_line;
-    i_pen_x = i_pen_y = 0;
-    i_previous = i = 0;
-    psz_line_start = psz_unicode;
+    return VLC_SUCCESS;
+}
 
-#define face p_sys->p_face
-#define glyph face->glyph
+static const struct {
+    const char *psz_name;
+    uint32_t   i_value;
+} p_html_colors[] = {
+    /* Official html colors */
+    { "Aqua",    0x00FFFF },
+    { "Black",   0x000000 },
+    { "Blue",    0x0000FF },
+    { "Fuchsia", 0xFF00FF },
+    { "Gray",    0x808080 },
+    { "Green",   0x008000 },
+    { "Lime",    0x00FF00 },
+    { "Maroon",  0x800000 },
+    { "Navy",    0x000080 },
+    { "Olive",   0x808000 },
+    { "Purple",  0x800080 },
+    { "Red",     0xFF0000 },
+    { "Silver",  0xC0C0C0 },
+    { "Teal",    0x008080 },
+    { "White",   0xFFFFFF },
+    { "Yellow",  0xFFFF00 },
+
+    /* Common ones */
+    { "AliceBlue", 0xF0F8FF },
+    { "AntiqueWhite", 0xFAEBD7 },
+    { "Aqua", 0x00FFFF },
+    { "Aquamarine", 0x7FFFD4 },
+    { "Azure", 0xF0FFFF },
+    { "Beige", 0xF5F5DC },
+    { "Bisque", 0xFFE4C4 },
+    { "Black", 0x000000 },
+    { "BlanchedAlmond", 0xFFEBCD },
+    { "Blue", 0x0000FF },
+    { "BlueViolet", 0x8A2BE2 },
+    { "Brown", 0xA52A2A },
+    { "BurlyWood", 0xDEB887 },
+    { "CadetBlue", 0x5F9EA0 },
+    { "Chartreuse", 0x7FFF00 },
+    { "Chocolate", 0xD2691E },
+    { "Coral", 0xFF7F50 },
+    { "CornflowerBlue", 0x6495ED },
+    { "Cornsilk", 0xFFF8DC },
+    { "Crimson", 0xDC143C },
+    { "Cyan", 0x00FFFF },
+    { "DarkBlue", 0x00008B },
+    { "DarkCyan", 0x008B8B },
+    { "DarkGoldenRod", 0xB8860B },
+    { "DarkGray", 0xA9A9A9 },
+    { "DarkGrey", 0xA9A9A9 },
+    { "DarkGreen", 0x006400 },
+    { "DarkKhaki", 0xBDB76B },
+    { "DarkMagenta", 0x8B008B },
+    { "DarkOliveGreen", 0x556B2F },
+    { "Darkorange", 0xFF8C00 },
+    { "DarkOrchid", 0x9932CC },
+    { "DarkRed", 0x8B0000 },
+    { "DarkSalmon", 0xE9967A },
+    { "DarkSeaGreen", 0x8FBC8F },
+    { "DarkSlateBlue", 0x483D8B },
+    { "DarkSlateGray", 0x2F4F4F },
+    { "DarkSlateGrey", 0x2F4F4F },
+    { "DarkTurquoise", 0x00CED1 },
+    { "DarkViolet", 0x9400D3 },
+    { "DeepPink", 0xFF1493 },
+    { "DeepSkyBlue", 0x00BFFF },
+    { "DimGray", 0x696969 },
+    { "DimGrey", 0x696969 },
+    { "DodgerBlue", 0x1E90FF },
+    { "FireBrick", 0xB22222 },
+    { "FloralWhite", 0xFFFAF0 },
+    { "ForestGreen", 0x228B22 },
+    { "Fuchsia", 0xFF00FF },
+    { "Gainsboro", 0xDCDCDC },
+    { "GhostWhite", 0xF8F8FF },
+    { "Gold", 0xFFD700 },
+    { "GoldenRod", 0xDAA520 },
+    { "Gray", 0x808080 },
+    { "Grey", 0x808080 },
+    { "Green", 0x008000 },
+    { "GreenYellow", 0xADFF2F },
+    { "HoneyDew", 0xF0FFF0 },
+    { "HotPink", 0xFF69B4 },
+    { "IndianRed", 0xCD5C5C },
+    { "Indigo", 0x4B0082 },
+    { "Ivory", 0xFFFFF0 },
+    { "Khaki", 0xF0E68C },
+    { "Lavender", 0xE6E6FA },
+    { "LavenderBlush", 0xFFF0F5 },
+    { "LawnGreen", 0x7CFC00 },
+    { "LemonChiffon", 0xFFFACD },
+    { "LightBlue", 0xADD8E6 },
+    { "LightCoral", 0xF08080 },
+    { "LightCyan", 0xE0FFFF },
+    { "LightGoldenRodYellow", 0xFAFAD2 },
+    { "LightGray", 0xD3D3D3 },
+    { "LightGrey", 0xD3D3D3 },
+    { "LightGreen", 0x90EE90 },
+    { "LightPink", 0xFFB6C1 },
+    { "LightSalmon", 0xFFA07A },
+    { "LightSeaGreen", 0x20B2AA },
+    { "LightSkyBlue", 0x87CEFA },
+    { "LightSlateGray", 0x778899 },
+    { "LightSlateGrey", 0x778899 },
+    { "LightSteelBlue", 0xB0C4DE },
+    { "LightYellow", 0xFFFFE0 },
+    { "Lime", 0x00FF00 },
+    { "LimeGreen", 0x32CD32 },
+    { "Linen", 0xFAF0E6 },
+    { "Magenta", 0xFF00FF },
+    { "Maroon", 0x800000 },
+    { "MediumAquaMarine", 0x66CDAA },
+    { "MediumBlue", 0x0000CD },
+    { "MediumOrchid", 0xBA55D3 },
+    { "MediumPurple", 0x9370D8 },
+    { "MediumSeaGreen", 0x3CB371 },
+    { "MediumSlateBlue", 0x7B68EE },
+    { "MediumSpringGreen", 0x00FA9A },
+    { "MediumTurquoise", 0x48D1CC },
+    { "MediumVioletRed", 0xC71585 },
+    { "MidnightBlue", 0x191970 },
+    { "MintCream", 0xF5FFFA },
+    { "MistyRose", 0xFFE4E1 },
+    { "Moccasin", 0xFFE4B5 },
+    { "NavajoWhite", 0xFFDEAD },
+    { "Navy", 0x000080 },
+    { "OldLace", 0xFDF5E6 },
+    { "Olive", 0x808000 },
+    { "OliveDrab", 0x6B8E23 },
+    { "Orange", 0xFFA500 },
+    { "OrangeRed", 0xFF4500 },
+    { "Orchid", 0xDA70D6 },
+    { "PaleGoldenRod", 0xEEE8AA },
+    { "PaleGreen", 0x98FB98 },
+    { "PaleTurquoise", 0xAFEEEE },
+    { "PaleVioletRed", 0xD87093 },
+    { "PapayaWhip", 0xFFEFD5 },
+    { "PeachPuff", 0xFFDAB9 },
+    { "Peru", 0xCD853F },
+    { "Pink", 0xFFC0CB },
+    { "Plum", 0xDDA0DD },
+    { "PowderBlue", 0xB0E0E6 },
+    { "Purple", 0x800080 },
+    { "Red", 0xFF0000 },
+    { "RosyBrown", 0xBC8F8F },
+    { "RoyalBlue", 0x4169E1 },
+    { "SaddleBrown", 0x8B4513 },
+    { "Salmon", 0xFA8072 },
+    { "SandyBrown", 0xF4A460 },
+    { "SeaGreen", 0x2E8B57 },
+    { "SeaShell", 0xFFF5EE },
+    { "Sienna", 0xA0522D },
+    { "Silver", 0xC0C0C0 },
+    { "SkyBlue", 0x87CEEB },
+    { "SlateBlue", 0x6A5ACD },
+    { "SlateGray", 0x708090 },
+    { "SlateGrey", 0x708090 },
+    { "Snow", 0xFFFAFA },
+    { "SpringGreen", 0x00FF7F },
+    { "SteelBlue", 0x4682B4 },
+    { "Tan", 0xD2B48C },
+    { "Teal", 0x008080 },
+    { "Thistle", 0xD8BFD8 },
+    { "Tomato", 0xFF6347 },
+    { "Turquoise", 0x40E0D0 },
+    { "Violet", 0xEE82EE },
+    { "Wheat", 0xF5DEB3 },
+    { "White", 0xFFFFFF },
+    { "WhiteSmoke", 0xF5F5F5 },
+    { "Yellow", 0xFFFF00 },
+    { "YellowGreen", 0x9ACD32 },
+
+    { NULL, 0 }
+};
 
-    while( *psz_unicode )
+static int HandleFontAttributes( xml_reader_t *p_xml_reader,
+                                 font_stack_t **p_fonts )
+{
+    int        rv;
+    char      *psz_fontname = NULL;
+    uint32_t   i_font_color = 0xffffff;
+    int        i_font_alpha = 0;
+    uint32_t   i_karaoke_bg_color = 0x00ffffff;
+    int        i_font_size  = 24;
+
+    /* Default all attributes to the top font in the stack -- in case not
+     * all attributes are specified in the sub-font
+     */
+    if( VLC_SUCCESS == PeekFont( p_fonts,
+                                 &psz_fontname,
+                                 &i_font_size,
+                                 &i_font_color,
+                                 &i_karaoke_bg_color ))
     {
-        i_char = *psz_unicode++;
-        if( i_char == '\r' ) /* ignore CR chars wherever they may be */
-        {
-            continue;
-        }
-
-        if( i_char == '\n' )
-        {
-            psz_line_start = psz_unicode;
-            if( !(p_next = NewLine( strlen( psz_string ))) )
-                goto error;
-            p_line->p_next = p_next;
-            p_line->i_width = line.xMax;
-            p_line->i_height = face->size->metrics.height >> 6;
-            p_line->pp_glyphs[ i ] = NULL;
-            p_line->i_alpha = i_font_alpha;
-            p_line->i_red = i_red;
-            p_line->i_green = i_green;
-            p_line->i_blue = i_blue;
-            p_prev = p_line;
-            p_line = p_next;
-            result.x = __MAX( result.x, line.xMax );
-            result.y += face->size->metrics.height >> 6;
-            i_pen_x = 0;
-            i_previous = i = 0;
-            line.xMin = line.xMax = line.yMin = line.yMax = 0;
-            i_pen_y += face->size->metrics.height >> 6;
-#if 0
-            msg_Dbg( p_filter, "Creating new line, i is %d", i );
-#endif
-            continue;
-        }
+        psz_fontname = strdup( psz_fontname );
+        i_font_size = i_font_size;
+    }
+    i_font_alpha = (i_font_color >> 24) & 0xff;
+    i_font_color &= 0x00ffffff;
 
-        i_glyph_index = FT_Get_Char_Index( face, i_char );
-        if( p_sys->i_use_kerning && i_glyph_index
-            && i_previous )
+    const char *name, *value;
+    while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
+    {
+        if( !strcasecmp( "face", name ) )
         {
-            FT_Vector delta;
-            FT_Get_Kerning( face, i_previous, i_glyph_index,
-                            ft_kerning_default, &delta );
-            i_pen_x += delta.x >> 6;
-
+            free( psz_fontname );
+            psz_fontname = strdup( value );
         }
-        p_line->p_glyph_pos[ i ].x = i_pen_x;
-        p_line->p_glyph_pos[ i ].y = i_pen_y;
-        i_error = FT_Load_Glyph( face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT );
-        if( i_error )
+        else if( !strcasecmp( "size", name ) )
         {
-            i_error = FT_Load_Glyph( face, i_glyph_index, FT_LOAD_DEFAULT );
-            if( i_error )
+            if( ( *value == '+' ) || ( *value == '-' ) )
             {
-                msg_Err( p_filter, "unable to render text FT_Load_Glyph returned"
-                                   " %d", i_error );
-                goto error;
+                int i_value = atoi( value );
+
+                if( ( i_value >= -5 ) && ( i_value <= 5 ) )
+                    i_font_size += ( i_value * i_font_size ) / 10;
+                else if( i_value < -5 )
+                    i_font_size = - i_value;
+                else if( i_value > 5 )
+                    i_font_size = i_value;
             }
+            else
+                i_font_size = atoi( value );
         }
-        i_error = FT_Get_Glyph( glyph, &tmp_glyph );
-        if( i_error )
-        {
-            msg_Err( p_filter, "unable to render text FT_Get_Glyph returned "
-                               "%d", i_error );
-            goto error;
-        }
-        FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
-        i_error = FT_Glyph_To_Bitmap( &tmp_glyph, ft_render_mode_normal, 0, 1);
-        if( i_error )
-        {
-            FT_Done_Glyph( tmp_glyph );
-            continue;
-        }
-        p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
-
-        /* Do rest */
-        line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
-            glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
-        if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
+        else if( !strcasecmp( "color", name ) )
         {
-            FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
-            p_line->pp_glyphs[ i ] = NULL;
-            FreeLine( p_line );
-            p_line = NewLine( strlen( psz_string ));
-            if( p_prev ) p_prev->p_next = p_line;
-            else p_lines = p_line;
-
-            uint32_t *psz_unicode_saved = psz_unicode;
-            while( psz_unicode > psz_line_start && *psz_unicode != ' ' )
+            if( value[0] == '#' )
             {
-                psz_unicode--;
-            }
-            if( psz_unicode == psz_line_start )
-            {   /* try harder to break that line */
-                psz_unicode = psz_unicode_saved;
-                while( psz_unicode > psz_line_start &&
-                    *psz_unicode != '_'  && *psz_unicode != '/' &&
-                    *psz_unicode != '\\' && *psz_unicode != '.' )
-                {
-                    psz_unicode--;
-                }
-            }
-            if( psz_unicode == psz_line_start )
-            {
-                msg_Warn( p_filter, "unbreakable string" );
-                goto error;
+                i_font_color = strtol( value + 1, NULL, 16 );
+                i_font_color &= 0x00ffffff;
             }
             else
             {
-                *psz_unicode = '\n';
+                for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
+                {
+                    if( !strncasecmp( value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
+                    {
+                        i_font_color = p_html_colors[i].i_value;
+                        break;
+                    }
+                }
             }
-            psz_unicode = psz_line_start;
-            i_pen_x = 0;
-            i_previous = i = 0;
-            line.xMin = line.xMax = line.yMin = line.yMax = 0;
-            continue;
         }
-        line.yMax = __MAX( line.yMax, glyph_size.yMax );
-        line.yMin = __MIN( line.yMin, glyph_size.yMin );
-
-        i_previous = i_glyph_index;
-        i_pen_x += glyph->advance.x >> 6;
-        i++;
+        else if( !strcasecmp( "alpha", name ) && ( value[0] == '#' ) )
+        {
+            i_font_alpha = strtol( value + 1, NULL, 16 );
+            i_font_alpha &= 0xff;
+        }
     }
+    rv = PushFont( p_fonts,
+                   psz_fontname,
+                   i_font_size,
+                   (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24),
+                   i_karaoke_bg_color );
 
-    p_line->i_width = line.xMax;
-    p_line->i_height = face->size->metrics.height >> 6;
-    p_line->pp_glyphs[ i ] = NULL;
-    p_line->i_alpha = i_font_alpha;
-    p_line->i_red = i_red;
-    p_line->i_green = i_green;
-    p_line->i_blue = i_blue;
-    result.x = __MAX( result.x, line.xMax );
-    result.y += line.yMax - line.yMin;
-
-#undef face
-#undef glyph
-
-    p_region_out->i_x = p_region_in->i_x;
-    p_region_out->i_y = p_region_in->i_y;
-
-    if( var_InheritBool( p_filter, "freetype-yuvp" ) )
-        Render( p_filter, p_region_out, p_lines, result.x, result.y );
-    else
-        RenderYUVA( p_filter, p_region_out, p_lines, result.x, result.y );
-
-    free( psz_unicode_orig );
-    FreeLines( p_lines );
-    return VLC_SUCCESS;
+    free( psz_fontname );
 
- error:
-    free( psz_unicode_orig );
-    FreeLines( p_lines );
-    return VLC_EGENERIC;
+    return rv;
 }
 
-#ifdef HAVE_STYLES
-static ft_style_t *CreateStyle( char *psz_fontname, int i_font_size,
-        uint32_t i_font_color, uint32_t i_karaoke_bg_color, bool b_bold,
-        bool b_italic, bool b_uline, bool b_through )
+/* Turn any multiple-whitespaces into single spaces */
+static void HandleWhiteSpace( char *psz_node )
 {
-    ft_style_t  *p_style = malloc( sizeof( ft_style_t ));
-
-    if( p_style )
+    char *s = strpbrk( psz_node, "\t\r\n " );
+    while( s )
     {
-        p_style->i_font_size        = i_font_size;
-        p_style->i_font_color       = i_font_color;
-        p_style->i_karaoke_bg_color = i_karaoke_bg_color;
-        p_style->b_italic           = b_italic;
-        p_style->b_bold             = b_bold;
-        p_style->b_underline        = b_uline;
-        p_style->b_through          = b_through;
-
-        p_style->psz_fontname = strdup( psz_fontname );
-    }
-    return p_style;
-}
+        int i_whitespace = strspn( s, "\t\r\n " );
 
-static void DeleteStyle( ft_style_t *p_style )
-{
-    if( p_style )
-    {
-        free( p_style->psz_fontname );
-        free( p_style );
+        if( i_whitespace > 1 )
+            memmove( &s[1],
+                     &s[i_whitespace],
+                     strlen( s ) - i_whitespace + 1 );
+        *s++ = ' ';
+
+        s = strpbrk( s, "\t\r\n " );
     }
 }
 
-static bool StyleEquals( ft_style_t *s1, ft_style_t *s2 )
+
+static text_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
+                                            font_stack_t **p_fonts,
+                                            int i_style_flags )
 {
-    if( !s1 || !s2 )
-        return false;
-    if( s1 == s2 )
-        return true;
+    char       *psz_fontname = NULL;
+    uint32_t    i_font_color = p_sys->i_font_color & 0x00ffffff;
+    uint32_t    i_karaoke_bg_color = i_font_color;
+    int         i_font_size  = p_sys->i_font_size;
 
-    if(( s1->i_font_size  == s2->i_font_size ) &&
-       ( s1->i_font_color == s2->i_font_color ) &&
-       ( s1->b_italic     == s2->b_italic ) &&
-       ( s1->b_through    == s2->b_through ) &&
-       ( s1->b_bold       == s2->b_bold ) &&
-       ( s1->b_underline  == s2->b_underline ) &&
-       ( !strcmp( s1->psz_fontname, s2->psz_fontname )))
-    {
-        return true;
-    }
-    return false;
+    if( PeekFont( p_fonts, &psz_fontname, &i_font_size,
+                  &i_font_color, &i_karaoke_bg_color ) )
+        return NULL;
+
+    return CreateStyle( psz_fontname, i_font_size, i_font_color,
+                        i_karaoke_bg_color,
+                        i_style_flags );
 }
 
-static void IconvText( filter_t *p_filter, const char *psz_string,
-                       size_t *i_string_length, uint32_t **ppsz_unicode )
+static unsigned SetupText( filter_t *p_filter,
+                           uint32_t *psz_text_out,
+                           text_style_t **pp_styles,
+                           uint32_t *pi_k_dates,
+
+                           const char *psz_text_in,
+                           text_style_t *p_style,
+                           uint32_t i_k_date )
 {
-    *i_string_length = 0;
-    if( *ppsz_unicode == NULL )
-        return;
+    size_t i_string_length;
 
-    size_t i_length;
-    uint32_t *psz_tmp =
+    size_t i_string_bytes;
 #if defined(WORDS_BIGENDIAN)
-            ToCharset( "UCS-4BE", psz_string, &i_length );
+    uint32_t *psz_tmp = ToCharset( "UCS-4BE", psz_text_in, &i_string_bytes );
 #else
-            ToCharset( "UCS-4LE", psz_string, &i_length );
+    uint32_t *psz_tmp = ToCharset( "UCS-4LE", psz_text_in, &i_string_bytes );
 #endif
-    if( !psz_tmp )
+    if( psz_tmp )
+    {
+        memcpy( psz_text_out, psz_tmp, i_string_bytes );
+        i_string_length = i_string_bytes / 4;
+        free( psz_tmp );
+    }
+    else
     {
         msg_Warn( p_filter, "failed to convert string to unicode (%m)" );
-        return;
+        i_string_length = 0;
     }
-    memcpy( *ppsz_unicode, psz_tmp, i_length );
-    *i_string_length = i_length / 4;
-
-    free( psz_tmp );
-}
-
-static ft_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
-        font_stack_t **p_fonts, bool b_bold, bool b_italic,
-        bool b_uline, bool b_through )
-{
-    ft_style_t   *p_style = NULL;
-
-    char       *psz_fontname = NULL;
-    uint32_t    i_font_color = p_sys->i_font_color & 0x00ffffff;
-    uint32_t    i_karaoke_bg_color = i_font_color;
-    int         i_font_size  = p_sys->i_font_size;
 
-    if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
-                                 &i_font_color, &i_karaoke_bg_color ))
+    if( i_string_length > 0 )
     {
-        p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
-                i_karaoke_bg_color, b_bold, b_italic, b_uline, b_through );
+        for( unsigned i = 0; i < i_string_length; i++ )
+            pp_styles[i] = p_style;
     }
-    return p_style;
+    else
+    {
+        text_style_Delete( p_style );
+    }
+    if( i_string_length > 0 && pi_k_dates )
+    {
+        for( unsigned i = 0; i < i_string_length; i++ )
+            pi_k_dates[i] = i_k_date;
+    }
+    return i_string_length;
 }
 
-static int RenderTag( filter_t *p_filter, FT_Face p_face, int i_font_color,
-                      bool b_uline, bool b_through, bool b_bold,
-                      bool b_italic, int i_karaoke_bgcolor,
-                      line_desc_t *p_line, uint32_t *psz_unicode,
-                      int *pi_pen_x, int i_pen_y, int *pi_start,
-                      FT_Vector *p_result )
+static int ProcessNodes( filter_t *p_filter,
+                         uint32_t *psz_text,
+                         text_style_t **pp_styles,
+                         uint32_t *pi_k_dates,
+                         int *pi_len,
+                         xml_reader_t *p_xml_reader,
+                         text_style_t *p_font_style )
 {
-    FT_BBox      line;
-    int          i_yMin, i_yMax;
-    int          i;
-    bool   b_first_on_line = true;
-
-    int          i_previous = 0;
-    int          i_pen_x_start = *pi_pen_x;
+    int           rv      = VLC_SUCCESS;
+    filter_sys_t *p_sys   = p_filter->p_sys;
+    int i_text_length     = 0;
+    font_stack_t *p_fonts = NULL;
+    uint32_t i_k_date     = 0;
 
-    uint32_t *psz_unicode_start = psz_unicode;
+    int i_style_flags = 0;
 
-    line.xMin = line.xMax = line.yMin = line.yMax = 0;
-
-    /* Account for part of line already in position */
-    for( i = 0; i<*pi_start; i++ )
+    if( p_font_style )
     {
-        FT_BBox glyph_size;
-
-        FT_Glyph_Get_CBox( (FT_Glyph) p_line->pp_glyphs[ i ],
-                            ft_glyph_bbox_pixels, &glyph_size );
-
-        line.xMax = p_line->p_glyph_pos[ i ].x + glyph_size.xMax -
-            glyph_size.xMin + p_line->pp_glyphs[ i ]->left;
-        line.yMax = __MAX( line.yMax, glyph_size.yMax );
-        line.yMin = __MIN( line.yMin, glyph_size.yMin );
+        rv = PushFont( &p_fonts,
+               p_font_style->psz_fontname,
+               p_font_style->i_font_size,
+               (p_font_style->i_font_color & 0xffffff) |
+                   ((p_font_style->i_font_alpha & 0xff) << 24),
+               (p_font_style->i_karaoke_background_color & 0xffffff) |
+                   ((p_font_style->i_karaoke_background_alpha & 0xff) << 24));
+
+        i_style_flags = p_font_style->i_style_flags & (STYLE_BOLD |
+                                                       STYLE_ITALIC |
+                                                       STYLE_UNDERLINE |
+                                                       STYLE_STRIKEOUT);
     }
-    i_yMin = line.yMin;
-    i_yMax = line.yMax;
-
-    if( line.xMax > 0 )
-        b_first_on_line = false;
-
-    while( *psz_unicode && ( *psz_unicode != '\n' ) )
+#ifdef HAVE_STYLES
+    else
     {
-        FT_BBox glyph_size;
-        FT_Glyph tmp_glyph;
-        int i_error;
-
-        int i_glyph_index = FT_Get_Char_Index( p_face, *psz_unicode++ );
-        if( FT_HAS_KERNING( p_face ) && i_glyph_index
-            && i_previous )
-        {
-            FT_Vector delta;
-            FT_Get_Kerning( p_face, i_previous, i_glyph_index,
-                            ft_kerning_default, &delta );
-            *pi_pen_x += delta.x >> 6;
-        }
-        p_line->p_glyph_pos[ i ].x = *pi_pen_x;
-        p_line->p_glyph_pos[ i ].y = i_pen_y;
-
-        i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT );
-        if( i_error )
-        {
-            i_error = FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT );
-            if( i_error )
-            {
-                msg_Err( p_filter,
-                       "unable to render text FT_Load_Glyph returned %d", i_error );
-                p_line->pp_glyphs[ i ] = NULL;
-                return VLC_EGENERIC;
-            }
-        }
+        rv = PushFont( &p_fonts,
+                       p_sys->psz_fontfamily,
+                       p_sys->i_font_size,
+                       (p_sys->i_font_color & 0xffffff) |
+                          (((255-p_sys->i_font_opacity) & 0xff) << 24),
+                       0x00ffffff );
+    }
+#endif
 
-        /* Do synthetic styling now that Freetype supports it;
-         * ie. if the font we have loaded is NOT already in the
-         * style that the tags want, then switch it on; if they
-         * are then don't. */
-        if (b_bold && !( p_face->style_flags & FT_STYLE_FLAG_BOLD ))
-            FT_GlyphSlot_Embolden( p_face->glyph );
-        if (b_italic && !( p_face->style_flags & FT_STYLE_FLAG_ITALIC ))
-            FT_GlyphSlot_Oblique( p_face->glyph );
-
-        i_error = FT_Get_Glyph( p_face->glyph, &tmp_glyph );
-        if( i_error )
-        {
-            msg_Err( p_filter,
-                    "unable to render text FT_Get_Glyph returned %d", i_error );
-            p_line->pp_glyphs[ i ] = NULL;
-            return VLC_EGENERIC;
-        }
-        FT_Glyph_Get_CBox( tmp_glyph, ft_glyph_bbox_pixels, &glyph_size );
-        i_error = FT_Glyph_To_Bitmap( &tmp_glyph, FT_RENDER_MODE_NORMAL, 0, 1);
-        if( i_error )
-        {
-            FT_Done_Glyph( tmp_glyph );
-            continue;
-        }
-        if( b_uline || b_through )
-        {
-            float aOffset = FT_FLOOR(FT_MulFix(p_face->underline_position,
-                                               p_face->size->metrics.y_scale));
-            float aSize = FT_CEIL(FT_MulFix(p_face->underline_thickness,
-                                            p_face->size->metrics.y_scale));
-
-            p_line->pi_underline_offset[ i ]  =
-                                       ( aOffset < 0 ) ? -aOffset : aOffset;
-            p_line->pi_underline_thickness[ i ] =
-                                       ( aSize < 0 ) ? -aSize   : aSize;
-            if (b_through)
-            {
-                /* Move the baseline to make it strikethrough instead of
-                 * underline. That means that strikethrough takes precedence
-                 */
-                float aDescent = FT_FLOOR(FT_MulFix(p_face->descender*2,
-                                                    p_face->size->metrics.y_scale));
-
-                p_line->pi_underline_offset[ i ]  -=
-                                       ( aDescent < 0 ) ? -aDescent : aDescent;
-            }
-        }
+    if( rv != VLC_SUCCESS )
+        return rv;
 
-        p_line->pp_glyphs[ i ] = (FT_BitmapGlyph)tmp_glyph;
-        p_line->p_fg_rgb[ i ] = i_font_color & 0x00ffffff;
-        p_line->p_bg_rgb[ i ] = i_karaoke_bgcolor & 0x00ffffff;
-        p_line->p_fg_bg_ratio[ i ] = 0x00;
+    const char *node;
+    int type;
 
-        line.xMax = p_line->p_glyph_pos[i].x + glyph_size.xMax -
-                    glyph_size.xMin + ((FT_BitmapGlyph)tmp_glyph)->left;
-        if( line.xMax > (int)p_filter->fmt_out.video.i_visible_width - 20 )
+    while ( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
+    {
+        switch ( type )
         {
-            for( ; i >= *pi_start; i-- )
-                FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[ i ] );
-            i = *pi_start;
+            case XML_READER_ENDELEM:
+                if( !strcasecmp( "font", node ) )
+                    PopFont( &p_fonts );
+                else if( !strcasecmp( "b", node ) )
+                    i_style_flags &= ~STYLE_BOLD;
+               else if( !strcasecmp( "i", node ) )
+                    i_style_flags &= ~STYLE_ITALIC;
+                else if( !strcasecmp( "u", node ) )
+                    i_style_flags &= ~STYLE_UNDERLINE;
+                else if( !strcasecmp( "s", node ) )
+                    i_style_flags &= ~STYLE_STRIKEOUT;
+                break;
 
-            while( psz_unicode > psz_unicode_start && *psz_unicode != ' ' )
-            {
-                psz_unicode--;
-            }
-            if( psz_unicode == psz_unicode_start )
-            {
-                if( b_first_on_line )
+            case XML_READER_STARTELEM:
+                if( !strcasecmp( "font", node ) )
+                    HandleFontAttributes( p_xml_reader, &p_fonts );
+                else if( !strcasecmp( "b", node ) )
+                    i_style_flags |= STYLE_BOLD;
+                else if( !strcasecmp( "i", node ) )
+                    i_style_flags |= STYLE_ITALIC;
+                else if( !strcasecmp( "u", node ) )
+                    i_style_flags |= STYLE_UNDERLINE;
+                else if( !strcasecmp( "s", node ) )
+                    i_style_flags |= STYLE_STRIKEOUT;
+                else if( !strcasecmp( "br", node ) )
                 {
-                    msg_Warn( p_filter, "unbreakable string" );
-                    p_line->pp_glyphs[ i ] = NULL;
-                    return VLC_EGENERIC;
+                    i_text_length += SetupText( p_filter,
+                                                &psz_text[i_text_length],
+                                                &pp_styles[i_text_length],
+                                                pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
+                                                "\n",
+                                                GetStyleFromFontStack( p_sys,
+                                                                       &p_fonts,
+                                                                       i_style_flags ),
+                                                i_k_date );
                 }
-                *pi_pen_x = i_pen_x_start;
-
-                p_line->i_width = line.xMax;
-                p_line->i_height = __MAX( p_line->i_height,
-                                          p_face->size->metrics.height >> 6 );
-                p_line->pp_glyphs[ i ] = NULL;
+                else if( !strcasecmp( "k", node ) )
+                {
+                    /* Karaoke tags */
+                    const char *name, *value;
+                    while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) != NULL )
+                    {
+                        if( !strcasecmp( "t", name ) && value )
+                            i_k_date += atoi( value );
+                    }
+                }
+                break;
 
-                p_result->x = __MAX( p_result->x, line.xMax );
-                p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
-                                                         i_yMax - i_yMin ) );
-                return VLC_SUCCESS;
-            }
-            else
+            case XML_READER_TEXT:
             {
-                *psz_unicode = '\n';
-            }
-            psz_unicode = psz_unicode_start;
-            *pi_pen_x = i_pen_x_start;
-            i_previous = 0;
-
-            line.yMax = i_yMax;
-            line.yMin = i_yMin;
+                char *psz_node = strdup( node );
+                if( unlikely(!psz_node) )
+                    break;
 
-            continue;
+                HandleWhiteSpace( psz_node );
+                resolve_xml_special_chars( psz_node );
+
+                i_text_length += SetupText( p_filter,
+                                            &psz_text[i_text_length],
+                                            &pp_styles[i_text_length],
+                                            pi_k_dates ? &pi_k_dates[i_text_length] : NULL,
+                                            psz_node,
+                                            GetStyleFromFontStack( p_sys,
+                                                                   &p_fonts,
+                                                                   i_style_flags ),
+                                            i_k_date );
+                free( psz_node );
+                break;
+            }
         }
-        line.yMax = __MAX( line.yMax, glyph_size.yMax );
-        line.yMin = __MIN( line.yMin, glyph_size.yMin );
-
-        i_previous = i_glyph_index;
-        *pi_pen_x += p_face->glyph->advance.x >> 6;
-        i++;
     }
-    p_line->i_width = line.xMax;
-    p_line->i_height = __MAX( p_line->i_height,
-                              p_face->size->metrics.height >> 6 );
-    p_line->pp_glyphs[ i ] = NULL;
 
-    p_result->x = __MAX( p_result->x, line.xMax );
-    p_result->y = __MAX( p_result->y, __MAX( p_line->i_height,
-                         line.yMax - line.yMin ) );
+    *pi_len = i_text_length;
 
-    *pi_start = i;
-
-    /* Get rid of any text processed - if necessary repositioning
-     * at the start of a new line of text
-     */
-    if( !*psz_unicode )
-    {
-        *psz_unicode_start = '\0';
-    }
-    else if( psz_unicode > psz_unicode_start )
-    {
-        for( i=0; psz_unicode[ i ]; i++ )
-            psz_unicode_start[ i ] = psz_unicode[ i ];
-        psz_unicode_start[ i ] = '\0';
-    }
+    while( VLC_SUCCESS == PopFont( &p_fonts ) );
 
     return VLC_SUCCESS;
 }
 
-static void SetupLine( filter_t *p_filter, const char *psz_text_in,
-                       uint32_t **psz_text_out, uint32_t *pi_runs,
-                       uint32_t **ppi_run_lengths, ft_style_t ***ppp_styles,
-                       ft_style_t *p_style )
+static void FreeLine( line_desc_t *p_line )
 {
-    size_t i_string_length;
+    for( int i = 0; i < p_line->i_character_count; i++ )
+        FT_Done_Glyph( (FT_Glyph)p_line->p_character[i].p_glyph );
 
-    IconvText( p_filter, psz_text_in, &i_string_length, psz_text_out );
-    *psz_text_out += i_string_length;
+    free( p_line->p_character );
+    free( p_line );
+}
 
-    if( ppp_styles && ppi_run_lengths )
+static void FreeLines( line_desc_t *p_lines )
+{
+    for( line_desc_t *p_line = p_lines; p_line != NULL; )
     {
-        (*pi_runs)++;
-
-        /* XXX this logic looks somewhat broken */
-
-        if( *ppp_styles )
-        {
-            *ppp_styles = realloc_or_free( *ppp_styles,
-                                          *pi_runs * sizeof( ft_style_t * ) );
-        }
-        else if( *pi_runs == 1 )
-        {
-            *ppp_styles = malloc( *pi_runs * sizeof( ft_style_t * ) );
-        }
+        line_desc_t *p_next = p_line->p_next;
+        FreeLine( p_line );
+        p_line = p_next;
+    }
+}
 
-        /* We have just malloc'ed this memory successfully -
-         * *pi_runs HAS to be within the memory area of *ppp_styles */
-        if( *ppp_styles )
-        {
-            (*ppp_styles)[ *pi_runs - 1 ] = p_style;
-            p_style = NULL;
-        }
+static line_desc_t *NewLine( int i_count )
+{
+    line_desc_t *p_line = malloc( sizeof(*p_line) );
 
-        /* XXX more iffy logic */
+    if( !p_line )
+        return NULL;
 
-        if( *ppi_run_lengths )
-        {
-            *ppi_run_lengths = realloc_or_free( *ppi_run_lengths,
-                                              *pi_runs * sizeof( uint32_t ) );
-        }
-        else if( *pi_runs == 1 )
-        {
-            *ppi_run_lengths = (uint32_t *)
-                malloc( *pi_runs * sizeof( uint32_t ) );
-        }
+    p_line->p_next = NULL;
+    p_line->i_width = 0;
+    p_line->i_base_line = 0;
+    p_line->i_character_count = 0;
 
-        /* same remarks here */
-        if( *ppi_run_lengths )
-        {
-            (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
-        }
+    p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
+    if( !p_line->p_character )
+    {
+        free( p_line );
+        return NULL;
     }
-    /* If we couldn't use the p_style argument due to memory allocation
-     * problems above, release it here.
-     */
-    if( p_style ) DeleteStyle( p_style );
+    return p_line;
 }
 
-static int CheckForEmbeddedFont( filter_sys_t *p_sys, FT_Face *pp_face, ft_style_t *p_style )
+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++ )
     {
@@ -1636,112 +1466,186 @@ static int CheckForEmbeddedFont( filter_sys_t *p_sys, FT_Face *pp_face, ft_style
         {
             if( p_face )
             {
-                bool match = !strcasecmp( p_face->family_name,
-                                                p_style->psz_fontname );
-
-                if( p_face->style_flags & FT_STYLE_FLAG_BOLD )
-                    match = match && p_style->b_bold;
-                else
-                    match = match && !p_style->b_bold;
-
-                if( p_face->style_flags & FT_STYLE_FLAG_ITALIC )
-                    match = match && p_style->b_italic;
-                else
-                    match = match && !p_style->b_italic;
-
-                if(  match )
-                {
-                    *pp_face = p_face;
-                    return VLC_SUCCESS;
-                }
+                int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD)    ? STYLE_BOLD   : 0) |
+                                       ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
+                if( !strcasecmp( p_face->family_name, p_style->psz_fontname ) &&
+                    (p_style->i_style_flags & (STYLE_BOLD | STYLE_BOLD)) == i_style_received )
+                    return p_face;
 
                 FT_Done_Face( p_face );
             }
             i_font_idx++;
         }
     }
-    return VLC_EGENERIC;
+    return NULL;
 }
 
-static int ProcessLines( filter_t *p_filter,
-                         uint32_t *psz_text,
-                         int i_len,
+static FT_Face LoadFace( filter_t *p_filter,
+                         const text_style_t *p_style )
+{
+    filter_sys_t *p_sys = p_filter->p_sys;
 
-                         uint32_t i_runs,
-                         uint32_t *pi_run_lengths,
-                         ft_style_t **pp_styles,
-                         line_desc_t **pp_lines,
+    /* Look for a match amongst our attachments first */
+    FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
 
-                         FT_Vector *p_result,
+    /* Load system wide font otheriwse */
+    if( !p_face )
+    {
+        int  i_idx = 0;
+        char *psz_fontfile;
+#ifdef HAVE_FONTCONFIG
+        psz_fontfile = FontConfig_Select( NULL,
+                                          p_style->psz_fontname,
+                                          (p_style->i_style_flags & STYLE_BOLD) != 0,
+                                          (p_style->i_style_flags & STYLE_ITALIC) != 0,
+                                          -1,
+                                          &i_idx );
+#elif defined( WIN32 )
+        psz_fontfile = Win32_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 );
+#else
+        psz_fontfile = NULL;
+#endif
+        if( !psz_fontfile )
+            return NULL;
 
-                         bool b_karaoke,
-                         uint32_t i_k_runs,
-                         uint32_t *pi_k_run_lengths,
-                         uint32_t *pi_k_durations )
-{
-    filter_sys_t   *p_sys = p_filter->p_sys;
-    ft_style_t    **pp_char_styles;
-    int            *p_new_positions = NULL;
-    int8_t         *p_levels = NULL;
-    uint8_t        *pi_karaoke_bar = NULL;
-    uint32_t        i, j, k;
-    int             i_prev;
-
-    /* Assign each character in the text string its style explicitly, so that
-     * after the characters have been shuffled around by Fribidi, we can re-apply
-     * the styles, and to simplify the calculation of runs within a line.
-     */
-    pp_char_styles = (ft_style_t **) malloc( i_len * sizeof( ft_style_t * ));
-    if( !pp_char_styles )
-        return VLC_ENOMEM;
+        if( *psz_fontfile == '\0' )
+        {
+            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" : "" );
+            p_face = NULL;
+        }
+        else
+        {
+            if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
+                p_face = NULL;
+        }
+        free( psz_fontfile );
+    }
+    if( !p_face )
+        return NULL;
 
-    if( b_karaoke )
+    if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
     {
-        pi_karaoke_bar = (uint8_t *) malloc( i_len * sizeof( uint8_t ));
-        /* If we can't allocate sufficient memory for karaoke, continue anyway -
-         * we just won't be able to display the progress bar; at least we'll
-         * get the text.
+        /* 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;
+    }
+    return p_face;
+}
+
+static bool FaceStyleEquals( const text_style_t *p_style1,
+                             const text_style_t *p_style2 )
+{
+    if( !p_style1 || !p_style2 )
+        return false;
+    if( p_style1 == p_style2 )
+        return true;
+
+    const int i_style_mask = STYLE_BOLD | STYLE_ITALIC;
+    return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask) &&
+           !strcmp( p_style1->psz_fontname, p_style2->psz_fontname );
+}
+
+static int GetGlyph( filter_t *p_filter,
+                     FT_Glyph *pp_glyph,
+                     FT_BBox  *p_bbox,
+
+                     FT_Face  p_face,
+                     int i_glyph_index,
+                     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 ) )
+    {
+        msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
+        return VLC_EGENERIC;
+    }
+
+    /* Do synthetic styling now that Freetype supports it;
+     * ie. if the font we have loaded is NOT already in the
+     * style that the tags want, then switch it on; if they
+     * are then don't. */
+    if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
+        FT_GlyphSlot_Embolden( p_face->glyph );
+    if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
+        FT_GlyphSlot_Oblique( p_face->glyph );
+
+    FT_Glyph glyph;
+    if( FT_Get_Glyph( p_face->glyph, &glyph ) )
+    {
+        msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
+        return VLC_EGENERIC;
+    }
+
+    if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
+    {
+        FT_Done_Glyph( glyph );
+        return VLC_EGENERIC;
     }
 
-    i = 0;
-    for( j = 0; j < i_runs; j++ )
-        for( k = 0; k < pi_run_lengths[ j ]; k++ )
-            pp_char_styles[ i++ ] = pp_styles[ j ];
+    FT_BBox bbox;
+    FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, &bbox );
+
+    *pp_glyph = glyph;
+    *p_bbox   = bbox;
+    return VLC_SUCCESS;
+}
+
+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_BBox     *p_bbox,
+                         int         *pi_max_face_height,
+
+                         uint32_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;
+    uint32_t       *p_fribidi_string = NULL;
+    text_style_t   **pp_fribidi_styles = NULL;
+    int            *p_new_positions = NULL;
 
 #if defined(HAVE_FRIBIDI)
     {
-        ft_style_t  **pp_char_styles_new;
-        int         *p_old_positions;
-        uint32_t    *p_fribidi_string;
+        int    *p_old_positions;
         int start_pos, pos = 0;
 
-        pp_char_styles_new  = (ft_style_t **)
-            malloc( i_len * sizeof( ft_style_t * ));
+        pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
 
-        p_fribidi_string = (uint32_t *)
-            malloc( (i_len + 1) * sizeof(uint32_t) );
-        p_old_positions = (int *)
-            malloc( (i_len + 1) * sizeof( int ) );
-        p_new_positions = (int *)
-            malloc( (i_len + 1) * sizeof( int ) );
-        p_levels = (int8_t *)
-            malloc( (i_len + 1) * sizeof( int8_t ) );
+        p_fribidi_string  = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
+        p_old_positions   = malloc( (i_len + 1) * sizeof(*p_old_positions) );
+        p_new_positions   = malloc( (i_len + 1) * sizeof(*p_new_positions) );
 
-        if( ! pp_char_styles_new ||
+        if( ! pp_fribidi_styles ||
             ! p_fribidi_string ||
             ! p_old_positions ||
-            ! p_new_positions ||
-            ! p_levels )
+            ! p_new_positions )
         {
-            free( p_levels );
             free( p_old_positions );
             free( p_new_positions );
             free( p_fribidi_string );
-            free( pp_char_styles_new );
-            free( pi_karaoke_bar );
-
-            free( pp_char_styles );
+            free( pp_fribidi_styles );
             return VLC_ENOMEM;
         }
 
@@ -1752,9 +1656,8 @@ static int ProcessLines( filter_t *p_filter,
                 if (psz_text[pos] != '\n')
                     break;
                 p_fribidi_string[pos] = psz_text[pos];
-                pp_char_styles_new[pos] = pp_char_styles[pos];
+                pp_fribidi_styles[pos] = pp_styles[pos];
                 p_new_positions[pos] = pos;
-                p_levels[pos] = 0;
                 ++pos;
             }
             start_pos = pos;
@@ -1775,821 +1678,697 @@ static int ProcessLines( filter_t *p_filter,
                         (FriBidiChar*)p_fribidi_string + start_pos,
                         p_new_positions + start_pos,
                         p_old_positions,
-                        p_levels + start_pos );
-                for( j = (uint32_t) start_pos; j < (uint32_t) pos; j++ )
+                        NULL );
+                for( int j = start_pos; j < pos; j++ )
                 {
-                    pp_char_styles_new[ j ] = pp_char_styles[ start_pos +
-                                                p_old_positions[ j - start_pos ] ];
+                    pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
                     p_new_positions[ j ] += start_pos;
                 }
             }
         }
-        free( p_old_positions );
-        free( pp_char_styles );
-        pp_char_styles = pp_char_styles_new;
-        psz_text = p_fribidi_string;
         p_fribidi_string[ i_len ] = 0;
-    }
-#endif
-    /* Work out the karaoke */
-    if( pi_karaoke_bar )
-    {
-        int64_t i_last_duration = 0;
-        int64_t i_duration = 0;
-        int64_t i_start_pos = 0;
-        int64_t i_elapsed  = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
-
-        for( k = 0; k< i_k_runs; k++ )
-        {
-             double fraction = 0.0;
-
-             i_duration += pi_k_durations[ k ];
-
-             if( i_duration < i_elapsed )
-             {
-                 /* Completely finished this run-length -
-                  * let it render normally */
-
-                 fraction = 1.0;
-             }
-             else if( i_elapsed < i_last_duration )
-             {
-                 /* Haven't got up to this segment yet -
-                  * render it completely in karaoke BG mode */
-
-                 fraction = 0.0;
-             }
-             else
-             {
-                 /* Partway through this run */
-
-                 fraction = (double)(i_elapsed - i_last_duration) /
-                            (double)pi_k_durations[ k ];
-             }
-             for( i = 0; i < pi_k_run_lengths[ k ]; i++ )
-             {
-                 double shade = pi_k_run_lengths[ k ] * fraction;
-
-                 if( p_new_positions )
-                     j = p_new_positions[ i_start_pos + i ];
-                 else
-                     j = i_start_pos + i;
-
-                 if( i < (uint32_t)shade )
-                     pi_karaoke_bar[ j ] = 0xff;
-                 else if( (double)i > shade )
-                     pi_karaoke_bar[ j ] = 0x00;
-                 else
-                 {
-                     shade -= (int)shade;
-                     pi_karaoke_bar[ j ] = ((int)(shade * 128.0) & 0x7f) |
-                                   ((p_levels ? (p_levels[ j ] % 2) : 0 ) << 7);
-                 }
-             }
-
-             i_last_duration = i_duration;
-             i_start_pos += pi_k_run_lengths[ k ];
-        }
-    }
-    free( p_levels );
-    free( p_new_positions );
-
-    FT_Vector tmp_result;
-
-    line_desc_t *p_line = NULL;
-    line_desc_t *p_prev = NULL;
-
-    int i_pen_x = 0;
-    int i_pen_y = 0;
-    int i_posn  = 0;
-
-    p_result->x = p_result->y = 0;
-    tmp_result.x = tmp_result.y = 0;
-
-    i_prev = 0;
-    for( k = 0; k <= (uint32_t) i_len; k++ )
-    {
-        if( ( k == (uint32_t) i_len ) ||
-          ( ( k > 0 ) &&
-            !StyleEquals( pp_char_styles[ k ], pp_char_styles[ k - 1] ) ) )
-        {
-            ft_style_t *p_style = pp_char_styles[ k - 1 ];
-
-            /* End of the current style run */
-            FT_Face p_face = NULL;
-            int      i_idx = 0;
-
-            /* Look for a match amongst our attachments first */
-            CheckForEmbeddedFont( p_sys, &p_face, p_style );
-
-            if( ! p_face )
-            {
-                char *psz_fontfile = NULL;
-
-#ifdef HAVE_FONTCONFIG
-                psz_fontfile = FontConfig_Select( NULL,
-                                                  p_style->psz_fontname,
-                                                  p_style->b_bold,
-                                                  p_style->b_italic,
-                                                  -1,
-                                                  &i_idx );
-#elif defined( WIN32 )
-                psz_fontfile = Win32_Select( p_filter,
-                                            p_style->psz_fontname,
-                                            p_style->b_bold,
-                                            p_style->b_italic,
-                                            -1,
-                                            &i_idx );
-#else
-# error FIXME
-#endif
-                if( psz_fontfile && ! *psz_fontfile )
-                {
-                    msg_Warn( p_filter, "We were not able to find a matching font: \"%s\" %s,"
-                        " so using default font", p_style->psz_fontname,
-                        ((p_style->b_bold && p_style->b_italic) ? "(Bold,Italic)" :
-                                               (p_style->b_bold ? "(Bold)" :
-                                             (p_style->b_italic ? "(Italic)" : ""))) );
-                    free( psz_fontfile );
-                    psz_fontfile = NULL;
-                }
-
-                if( psz_fontfile )
-                {
-                    if( FT_New_Face( p_sys->p_library,
-                                psz_fontfile, i_idx, &p_face ) )
-                    {
-                        free( psz_fontfile );
-                        free( pp_char_styles );
-#if defined(HAVE_FRIBIDI)
-                        free( psz_text );
+        free( p_old_positions );
+
+        pp_styles = pp_fribidi_styles;
+        psz_text = p_fribidi_string;
+    }
 #endif
-                        free( pi_karaoke_bar );
-                        return VLC_EGENERIC;
-                    }
-                    free( psz_fontfile );
-                }
-            }
-            if( p_face &&
-                FT_Select_Charmap( p_face, ft_encoding_unicode ) )
+    /* Work out the karaoke */
+    uint8_t *pi_karaoke_bar = NULL;
+    if( pi_k_dates )
+    {
+        pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
+        if( pi_karaoke_bar )
+        {
+            int64_t i_elapsed  = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
+            for( int i = 0; i < i_len; i++ )
             {
-                /* We've loaded a font face which is unhelpful for actually
-                 * rendering text - fallback to the default one.
-                 */
-                 FT_Done_Face( p_face );
-                 p_face = NULL;
+                unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
+                pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
             }
+        }
+    }
+    free( p_new_positions );
 
-            if( FT_Select_Charmap( p_face ? p_face : p_sys->p_face,
-                        ft_encoding_unicode ) ||
-                FT_Set_Pixel_Sizes( p_face ? p_face : p_sys->p_face, 0,
-                    p_style->i_font_size ) )
+    *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;
+    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; )
+    {
+        /* Compute the length of the current text line */
+        int i_length = 0;
+        while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
+            i_length++;
+
+        /* 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;
+        FT_Vector pen = {
+            .x = 0,
+            .y = 0,
+        };
+        int i_face_height = 0;
+        FT_BBox line_bbox = {
+            .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;
+
+#define SAVE_BP(dst) do { \
+        dst.i_index = i_index; \
+        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 );
+        SAVE_BP( break_point_fallback );
+
+        while( i_index < i_start + i_length )
+        {
+            /* Split by common FT_Face + Size */
+            const text_style_t *p_current_style = pp_styles[i_index];
+            int i_part_length = 0;
+            while( i_index + i_part_length < i_start + i_length )
             {
-                if( p_face ) FT_Done_Face( p_face );
-                free( pp_char_styles );
-#if defined(HAVE_FRIBIDI)
-                free( psz_text );
-#endif
-                free( pi_karaoke_bar );
-                return VLC_EGENERIC;
+                const text_style_t *p_style = pp_styles[i_index + i_part_length];
+                if( !FaceStyleEquals( p_style, p_current_style ) ||
+                    p_style->i_font_size != p_current_style->i_font_size )
+                    break;
+                i_part_length++;
             }
-            p_sys->i_use_kerning =
-                        FT_HAS_KERNING( ( p_face  ? p_face : p_sys->p_face ) );
 
+            /* (Re)load/reconfigure the face if needed */
+            if( !FaceStyleEquals( p_current_style, p_previous_style ) )
+            {
+                if( p_face )
+                    FT_Done_Face( p_face );
+                p_previous_style = NULL;
 
-            uint32_t *psz_unicode = (uint32_t *)
-                              malloc( (k - i_prev + 1) * sizeof( uint32_t ));
-            if( !psz_unicode )
+                p_face = LoadFace( p_filter, p_current_style );
+            }
+            FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
+            if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size )
             {
-                if( p_face ) FT_Done_Face( p_face );
-                free( pp_char_styles );
-                free( psz_unicode );
-#if defined(HAVE_FRIBIDI)
-                free( psz_text );
-#endif
-                free( pi_karaoke_bar );
-                return VLC_ENOMEM;
+                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 );
             }
-            memcpy( psz_unicode, psz_text + i_prev,
-                                        sizeof( uint32_t ) * ( k - i_prev ) );
-            psz_unicode[ k - i_prev ] = 0;
-            while( *psz_unicode )
+            p_previous_style = p_current_style;
+
+            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;
+            int i_glyph_last = 0;
+            while( i_part_length > 0 )
             {
-                if( !p_line )
+                const text_style_t *p_glyph_style = pp_styles[i_index];
+                uint32_t character = psz_text[i_index];
+                int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
+
+                /* Get kerning vector */
+                FT_Vector kerning = { .x = 0, .y = 0 };
+                if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
+                    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, &pen_new ) )
+                    goto next;
+
+                FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
+                if( glyph_bbox.xMin >= glyph_bbox.xMax )
                 {
-                    if( !(p_line = NewLine( i_len - i_prev)) )
-                    {
-                        if( p_face ) FT_Done_Face( p_face );
-                        free( pp_char_styles );
-                        free( psz_unicode );
-#if defined(HAVE_FRIBIDI)
-                        free( psz_text );
-#endif
-                        free( pi_karaoke_bar );
-                        return VLC_ENOMEM;
-                    }
-                    /* New Color mode only works in YUVA rendering mode --
-                     * (RGB mode has palette constraints on it). We therefore
-                     * need to populate the legacy colour fields also.
-                     */
-                    p_line->b_new_color_mode = true;
-                    p_line->i_alpha = ( p_style->i_font_color & 0xff000000 ) >> 24;
-                    p_line->i_red   = ( p_style->i_font_color & 0x00ff0000 ) >> 16;
-                    p_line->i_green = ( p_style->i_font_color & 0x0000ff00 ) >>  8;
-                    p_line->i_blue  = ( p_style->i_font_color & 0x000000ff );
-                    p_line->p_next = NULL;
-                    i_pen_x = 0;
-                    i_pen_y += tmp_result.y;
-                    tmp_result.x = 0;
-                    tmp_result.y = 0;
-                    i_posn = 0;
-                    if( p_prev ) p_prev->p_next = p_line;
-                    else *pp_lines = p_line;
+                    glyph_bbox.xMin = FT_CEIL(pen_new.x);
+                    glyph_bbox.xMax = FT_CEIL(pen_new.x + p_current_face->glyph->advance.x);
+                    glyph_bmp->left = glyph_bbox.xMin;
                 }
-
-                if( RenderTag( p_filter, p_face ? p_face : p_sys->p_face,
-                               p_style->i_font_color, p_style->b_underline,
-                               p_style->b_through,
-                               p_style->b_bold,
-                               p_style->b_italic,
-                               p_style->i_karaoke_bg_color,
-                               p_line, psz_unicode, &i_pen_x, i_pen_y, &i_posn,
-                               &tmp_result ) != VLC_SUCCESS )
+                if( glyph_bbox.yMin >= glyph_bbox.yMax )
                 {
-                    if( p_face ) FT_Done_Face( p_face );
-                    free( pp_char_styles );
-                    free( psz_unicode );
-#if defined(HAVE_FRIBIDI)
-                    free( psz_text );
-#endif
-                    free( pi_karaoke_bar );
-                    return VLC_EGENERIC;
+                    glyph_bbox.yMax = FT_CEIL(pen_new.y);
+                    glyph_bbox.yMin = FT_CEIL(pen_new.y + p_current_face->glyph->advance.y);
+                    glyph_bmp->top  = glyph_bbox.yMax;
                 }
 
-                if( *psz_unicode )
+                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_line_offset    = 0;
+                int i_line_thickness = 0;
+                if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
                 {
-                    p_result->x = __MAX( p_result->x, tmp_result.x );
-                    p_result->y += tmp_result.y;
+                    i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
+                                                            p_current_face->size->metrics.y_scale)) );
 
-                    p_prev = p_line;
-                    p_line = NULL;
+                    i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
+                                                              p_current_face->size->metrics.y_scale)) );
 
-                    if( *psz_unicode == '\n')
+                    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_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 )
                     {
-                        uint32_t *c_ptr;
+                        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_BBox line_bbox_new = line_bbox;
+                BBoxEnlarge( &line_bbox_new, &glyph_bbox );
 
-                        for( c_ptr = psz_unicode; *c_ptr; c_ptr++ )
-                        {
-                            *c_ptr = *(c_ptr+1);
-                        }
+                b_break_line = i_index > i_start &&
+                               line_bbox_new.xMax - line_bbox_new.xMin >= p_filter->fmt_out.video.i_visible_width;
+                if( b_break_line )
+                {
+                    FT_Done_Glyph( glyph );
+
+                    break_point_t *p_bp = NULL;
+                    if( break_point.i_index > i_start )
+                        p_bp = &break_point;
+                    else if( break_point_fallback.i_index > i_start )
+                        p_bp = &break_point_fallback;
+
+                    if( p_bp )
+                    {
+                        msg_Dbg( p_filter, "Breaking line");
+                        for( int i = p_bp->i_index; i < i_index; i++ )
+                            FT_Done_Glyph( (FT_Glyph)p_line->p_character[i - i_start].p_glyph );
+                        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
+                    {
+                        msg_Err( p_filter, "Breaking unbreakable line");
+                    }
+                    break;
                 }
+
+                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,
+                    .i_color = i_color,
+                    .i_line_offset = i_line_offset,
+                    .i_line_thickness = i_line_thickness,
+                };
+
+                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;
+                i_part_length--;
+                i_index++;
+
+                if( character == ' ' || character == '\t' )
+                    SAVE_BP( break_point );
+                else if( character == 160 )
+                    SAVE_BP( break_point_fallback );
             }
-            free( psz_unicode );
-            if( p_face ) FT_Done_Face( p_face );
-            i_prev = k;
+            if( b_break_line )
+                break;
         }
-    }
-    free( pp_char_styles );
-#if defined(HAVE_FRIBIDI)
-    free( psz_text );
-#endif
-    if( p_line )
-    {
-        p_result->x = __MAX( p_result->x, tmp_result.x );
-        p_result->y += tmp_result.y;
-    }
+#undef SAVE_BP
+        /* 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 );
 
-    if( pi_karaoke_bar )
-    {
-        int i = 0;
-        for( p_line = *pp_lines; p_line; p_line=p_line->p_next )
+        /* Terminate and append the line */
+        if( p_line )
         {
-            for( k = 0; p_line->pp_glyphs[ k ]; k++, i++ )
+            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 )
             {
-                if( (pi_karaoke_bar[ i ] & 0x7f) == 0x7f)
-                {
-                    /* do nothing */
-                }
-                else if( (pi_karaoke_bar[ i ] & 0x7f) == 0x00)
-                {
-                    /* 100% BG colour will render faster if we
-                     * instead make it 100% FG colour, so leave
-                     * the ratio alone and copy the value across
-                     */
-                    p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
-                }
-                else
+                for( int i = 0; i < p_line->i_character_count; i++ )
                 {
-                    if( pi_karaoke_bar[ i ] & 0x80 )
+                    line_character_t *ch = &p_line->p_character[i];
+                    if( ch->i_line_thickness < 0 )
                     {
-                        /* Swap Left and Right sides over for Right aligned
-                         * language text (eg. Arabic, Hebrew)
-                         */
-                        uint32_t i_tmp = p_line->p_fg_rgb[ k ];
-
-                        p_line->p_fg_rgb[ k ] = p_line->p_bg_rgb[ k ];
-                        p_line->p_bg_rgb[ k ] = i_tmp;
+                        ch->i_line_offset    = i_ul_offset;
+                        ch->i_line_thickness = i_ul_thickness;
                     }
-                    p_line->p_fg_bg_ratio[ k ] = (pi_karaoke_bar[ i ] & 0x7f);
                 }
             }
-            /* Jump over the '\n' at the line-end */
-            i++;
+
+            *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 - bbox.yMin >= p_filter->fmt_out.video.i_visible_height )
+        {
+            msg_Err( p_filter, "Truncated too high subtitle" );
+            break;
         }
-        free( pi_karaoke_bar );
     }
+    if( p_face )
+        FT_Done_Face( p_face );
+
+    free( pp_fribidi_styles );
+    free( p_fribidi_string );
+    free( pi_karaoke_bar );
 
+    *p_bbox = bbox;
     return VLC_SUCCESS;
 }
 
-static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
-                       subpicture_region_t *p_region_in )
+/**
+ * This function renders a text subpicture region into another one.
+ * It also calculates the size needed for this string, and renders the
+ * needed glyphs into memory. It is used as pf_add_string callback in
+ * the vout method by this module
+ */
+static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
+                         subpicture_region_t *p_region_in, bool b_html )
 {
-    int          rv = VLC_SUCCESS;
-    stream_t     *p_sub = NULL;
+    filter_sys_t *p_sys = p_filter->p_sys;
+
+    if( !p_region_in )
+        return VLC_EGENERIC;
+    if( b_html && !p_region_in->psz_html )
+        return VLC_EGENERIC;
+    if( !b_html && !p_region_in->psz_text )
+        return VLC_EGENERIC;
+
+    const size_t i_text_max = strlen( b_html ? p_region_in->psz_html
+                                             : p_region_in->psz_text );
 
-    if( !p_region_in || !p_region_in->psz_html )
+    uint32_t *psz_text = calloc( i_text_max, sizeof( *psz_text ) );
+    text_style_t **pp_styles = calloc( i_text_max, sizeof( *pp_styles ) );
+    if( !psz_text || !pp_styles )
+    {
+        free( psz_text );
+        free( pp_styles );
         return VLC_EGENERIC;
+    }
 
     /* Reset the default fontsize in case screen metrics have changed */
     p_filter->p_sys->i_font_size = GetFontSize( p_filter );
 
-    p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
-                              (uint8_t *) p_region_in->psz_html,
-                              strlen( p_region_in->psz_html ),
-                              true );
-    if( unlikely(p_sub == NULL) )
-        return VLC_SUCCESS;
-
-    xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
-    bool b_karaoke = false;
+    /* */
+    int rv = VLC_SUCCESS;
+    int i_text_length = 0;
+    FT_BBox bbox;
+    int i_max_face_height;
+    line_desc_t *p_lines = NULL;
 
-    if( !p_xml_reader )
-        p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
-    else
-        p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
+    uint32_t *pi_k_durations   = NULL;
 
-    p_filter->p_sys->p_xml = p_xml_reader;
-    if( p_xml_reader )
+#ifdef HAVE_STYLES
+    if( b_html )
     {
-        /* Look for Root Node */
-        const char *node;
+        stream_t *p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
+                                            (uint8_t *) p_region_in->psz_html,
+                                            strlen( p_region_in->psz_html ),
+                                            true );
+        if( unlikely(p_sub == NULL) )
+            return VLC_SUCCESS;
+
+        xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
+        if( !p_xml_reader )
+            p_xml_reader = xml_ReaderCreate( p_filter, p_sub );
+        else
+            p_xml_reader = xml_ReaderReset( p_xml_reader, p_sub );
+        p_filter->p_sys->p_xml = p_xml_reader;
+
+        if( !p_xml_reader )
+            rv = VLC_EGENERIC;
 
-        if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
+        if( !rv )
         {
-            if( !strcasecmp( "karaoke", node ) )
-            {
-                /* We're going to have to render the text a number
-                 * of times to show the progress marker on the text.
-                 */
-                var_SetBool( p_filter, "text-rerender", true );
-                b_karaoke = true;
-            }
-            else if( !strcasecmp( "text", node ) )
+            /* Look for Root Node */
+            const char *node;
+
+            if( xml_ReaderNextNode( p_xml_reader, &node ) == XML_READER_STARTELEM )
             {
-                b_karaoke = false;
+                if( strcasecmp( "karaoke", node ) == 0 )
+                {
+                    pi_k_durations = calloc( i_text_max, sizeof( *pi_k_durations ) );
+                }
+                else if( strcasecmp( "text", node ) != 0 )
+                {
+                    /* Only text and karaoke tags are supported */
+                    msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
+                             node );
+                    rv = VLC_EGENERIC;
+                }
             }
             else
             {
-                /* Only text and karaoke tags are supported */
-                msg_Dbg( p_filter, "Unsupported top-level tag <%s> ignored.",
-                         node );
+                msg_Err( p_filter, "Malformed HTML subtitle" );
                 rv = VLC_EGENERIC;
             }
         }
-        else
+        if( !rv )
         {
-            msg_Err( p_filter, "Malformed HTML subtitle" );
-            rv = VLC_EGENERIC;
+            rv = ProcessNodes( p_filter,
+                               psz_text, pp_styles, pi_k_durations, &i_text_length,
+                               p_xml_reader, p_region_in->p_style );
         }
 
-        if( rv != VLC_SUCCESS )
-        {
+        if( p_xml_reader )
             p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
-            p_xml_reader = NULL;
-        }
-    }
-
-    if( p_xml_reader )
-    {
-        uint32_t   *psz_text;
-        int         i_len            = 0;
-        uint32_t    i_runs           = 0;
-        uint32_t    i_k_runs         = 0;
-        uint32_t   *pi_run_lengths   = NULL;
-        uint32_t   *pi_k_run_lengths = NULL;
-        uint32_t   *pi_k_durations   = NULL;
-        ft_style_t  **pp_styles      = NULL;
-        FT_Vector    result          = {0, 0};
-        line_desc_t  *p_lines        = NULL;
-
-        psz_text = (uint32_t *)malloc( strlen( p_region_in->psz_html ) *
-                                       sizeof( uint32_t ) );
-        if( psz_text )
-        {
-            rv = ProcessNodes( p_filter, p_xml_reader,
-                               p_region_in->p_style, psz_text, &i_len,
-                               &i_runs, &pi_run_lengths, &pp_styles,
-                               b_karaoke, &i_k_runs, &pi_k_run_lengths,
-                               &pi_k_durations );
-
-            p_region_out->i_x = p_region_in->i_x;
-            p_region_out->i_y = p_region_in->i_y;
-
-            if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
-            {
-                rv = ProcessLines( p_filter, psz_text, i_len, i_runs,
-                                   pi_run_lengths, pp_styles, &p_lines,
-                                   &result, b_karaoke, i_k_runs,
-                                   pi_k_run_lengths, pi_k_durations );
-            }
-
-            for( uint_fast32_t k=0; k<i_runs; k++)
-                 DeleteStyle( pp_styles[k] );
-            free( pp_styles );
-            free( pi_run_lengths );
-            free( psz_text );
-
-            /* Don't attempt to render text that couldn't be layed out
-             * properly. */
-            if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
-            {
-                if( var_InheritBool( p_filter, "freetype-yuvp" ) )
-                    Render( p_filter, p_region_out, p_lines,
-                            result.x, result.y );
-                else
-                    RenderYUVA( p_filter, p_region_out, p_lines,
-                                result.x, result.y );
-            }
-        }
-        p_filter->p_sys->p_xml = xml_ReaderReset( p_xml_reader, NULL );
-        FreeLines( p_lines );
-    }
-    stream_Delete( p_sub );
-    return rv;
-}
-
-#ifdef WIN32
-#define UNICODE
-#define FONT_DIR_NT "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
-
-static int GetFileFontByName( const char *font_name, char **psz_filename )
-{
-    HKEY hKey;
-    wchar_t vbuffer[MAX_PATH];
-    wchar_t dbuffer[256];
-
-    if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey) != ERROR_SUCCESS )
-        return 1;
-
-    for( int index = 0;; index++ )
-    {
-        DWORD vbuflen = MAX_PATH - 1;
-        DWORD dbuflen = 255;
-
-        if( RegEnumValueW( hKey, index, vbuffer, &vbuflen,
-                           NULL, NULL, (LPBYTE)dbuffer, &dbuflen) != ERROR_SUCCESS )
-            return 2;
-
-        char *psz_value = FromWide( vbuffer );
-
-        char *s = strchr( psz_value,'(' );
-        if( s != NULL && s != psz_value ) s[-1] = '\0';
-
-        /* Manage concatenated font names */
-        if( strchr( psz_value, '&') ) {
-            if( strcasestr( psz_value, font_name ) != NULL )
-                break;
-        }
-        else {
-            if( strcasecmp( psz_value, font_name ) == 0 )
-                break;
-        }
-    }
-
-    *psz_filename = FromWide( dbuffer );
-    return 0;
-}
-
-
-static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
-                                     DWORD type, LPARAM lParam)
-{
-    VLC_UNUSED( metric );
-    if( (type & RASTER_FONTTYPE) ) return 1;
-    // if( lpelfe->elfScript ) FIXME
-
-    return GetFileFontByName( (const char *)lpelfe->elfFullName, (char **)lParam );
-}
-
-static char* Win32_Select( filter_t *p_filter, const char* family,
-                           bool b_bold, bool b_italic, int i_size, int *i_idx )
-{
-    VLC_UNUSED( i_size );
-    // msg_Dbg( p_filter, "Here in Win32_Select, asking for %s", family );
-
-    /* */
-    LOGFONT lf;
-    lf.lfCharSet = DEFAULT_CHARSET;
-    if( b_italic )
-        lf.lfItalic = true;
-    if( b_bold )
-        lf.lfWeight = FW_BOLD;
-    strncpy( (LPSTR)&lf.lfFaceName, family, 32);
-
-    /* */
-    char *psz_filename = NULL;
-    HDC hDC = GetDC( NULL );
-    EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
-    ReleaseDC(NULL, hDC);
-
-    if( psz_filename == NULL )
-        return NULL;
-
-    /* FIXME: increase i_idx, when concatenated strings  */
-    i_idx = 0;
-
-    /* */
-    char *psz_tmp;
-    if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
-        return NULL;
-    return psz_tmp;
-}
-#endif
-
-#ifdef HAVE_FONTCONFIG
-static void FontConfig_BuildCache( filter_t *p_filter )
-{
-    /* */
-    msg_Dbg( p_filter, "Building font databases.");
-    mtime_t t1, t2;
-    t1 = mdate();
-
-#ifdef WIN32
-    dialog_progress_bar_t *p_dialog = NULL;
-    FcConfig *fcConfig = FcInitLoadConfig();
-
-    p_dialog = dialog_ProgressCreate( p_filter,
-            _("Building font cache"),
-            _("Please wait while your font cache is rebuilt.\n"
-                "This should take less than a few minutes."), NULL );
-
-/*    if( p_dialog )
-        dialog_ProgressSet( p_dialog, NULL, 0.5 ); */
 
-    FcConfigBuildFonts( fcConfig );
-    if( p_dialog )
-    {
-//        dialog_ProgressSet( p_dialog, NULL, 1.0 );
-        dialog_ProgressDestroy( p_dialog );
-        p_dialog = NULL;
+        stream_Delete( p_sub );
     }
-#endif
-    t2 = mdate();
-    msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
-}
-
-/***
- * \brief Selects a font matching family, bold, italic provided
- ***/
-static char* FontConfig_Select( FcConfig* config, const char* family,
-                          bool b_bold, bool b_italic, int i_size, int *i_idx )
-{
-    FcResult result = FcResultMatch;
-    FcPattern *pat, *p_pat;
-    FcChar8* val_s;
-    FcBool val_b;
-
-    /* Create a pattern and fills it */
-    pat = FcPatternCreate();
-    if (!pat) return NULL;
-
-    /* */
-    FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
-    FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
-    FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
-    FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
-    if( i_size != -1 )
+    else
+#endif
     {
-        char *psz_fontsize;
-        if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
-            FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
+        text_style_t *p_style;
+        if( p_region_in->p_style )
+            p_style = CreateStyle( p_region_in->p_style->psz_fontname,
+                                   p_region_in->p_style->i_font_size,
+                                   (p_region_in->p_style->i_font_color & 0xffffff) |
+                                   ((p_region_in->p_style->i_font_alpha & 0xff) << 24),
+                                   0x00ffffff,
+                                   p_region_in->p_style->i_style_flags & (STYLE_BOLD |
+                                                                          STYLE_ITALIC |
+                                                                          STYLE_UNDERLINE |
+                                                                          STYLE_STRIKEOUT) );
+        else
+            p_style = CreateStyle( p_sys->psz_fontfamily,
+                                   p_sys->i_font_size,
+                                   (p_sys->i_font_color & 0xffffff) |
+                                   (((255-p_sys->i_font_opacity) & 0xff) << 24),
+                                   0x00ffffff, 0);
+
+        i_text_length = SetupText( p_filter,
+                                   psz_text,
+                                   pp_styles,
+                                   NULL,
+                                   p_region_in->psz_text, p_style, 0 );
     }
 
-    /* */
-    FcDefaultSubstitute( pat );
-    if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
+    if( !rv && i_text_length > 0 )
     {
-        FcPatternDestroy( pat );
-        return NULL;
+        rv = ProcessLines( p_filter,
+                           &p_lines, &bbox, &i_max_face_height,
+                           psz_text, pp_styles, pi_k_durations, i_text_length );
     }
 
-    /* Find the best font for the pattern, destroy the pattern */
-    p_pat = FcFontMatch( config, pat, &result );
-    FcPatternDestroy( pat );
-    if( !p_pat || result == FcResultNoMatch ) return NULL;
+    p_region_out->i_x = p_region_in->i_x;
+    p_region_out->i_y = p_region_in->i_y;
 
-    /* Check the new pattern */
-    if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
-        || ( val_b != FcTrue ) )
-    {
-        FcPatternDestroy( p_pat );
-        return NULL;
-    }
-    if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
+    /* Don't attempt to render text that couldn't be layed out
+     * properly. */
+    if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
     {
-        *i_idx = 0;
-    }
+        if( var_InheritBool( p_filter, "freetype-yuvp" ) )
+            RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
+        else
+            RenderYUVA( p_filter, p_region_out,
+                        p_lines,
+                        &bbox,
+                        p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0 );
 
-    if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
-    {
-        FcPatternDestroy( p_pat );
-        return NULL;
+        /* With karaoke, we're going to have to render the text a number
+         * of times to show the progress marker on the text.
+         */
+        if( pi_k_durations )
+            var_SetBool( p_filter, "text-rerender", true );
     }
 
-    /* if( strcasecmp((const char*)val_s, family ) != 0 )
-        msg_Warn( p_filter, "fontconfig: selected font family is not"
-                            "the requested one: '%s' != '%s'\n",
-                            (const char*)val_s, family );   */
+    FreeLines( p_lines );
 
-    if( FcResultMatch != FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
+    free( psz_text );
+    for( int i = 0; i < i_text_length; i++ )
     {
-        FcPatternDestroy( p_pat );
-        return NULL;
+        if( pp_styles[i] && ( i + 1 == i_text_length || pp_styles[i] != pp_styles[i + 1] ) )
+            text_style_Delete( pp_styles[i] );
     }
+    free( pp_styles );
+    free( pi_k_durations );
 
-    FcPatternDestroy( p_pat );
-    return strdup( (const char*)val_s );
+    return rv;
 }
-#endif
-#else /* from now no styles */
 
-static void SetupLine( filter_t *p_filter, const char *psz_text_in,
-                       uint32_t **psz_text_out, uint32_t *pi_runs,
-                       uint32_t **ppi_run_lengths, ft_style_t ***ppp_styles,
-                       ft_style_t *p_style )
+static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
+                       subpicture_region_t *p_region_in )
 {
-        VLC_UNUSED(p_filter);
-        VLC_UNUSED(psz_text_in);
-        VLC_UNUSED(psz_text_out);
-        VLC_UNUSED(pi_runs);
-        VLC_UNUSED(ppi_run_lengths);
-        VLC_UNUSED(ppp_styles);
-        VLC_UNUSED(p_style);
+    return RenderCommon( p_filter, p_region_out, p_region_in, false );
 }
 
-static ft_style_t *GetStyleFromFontStack( filter_sys_t *p_sys,
-        font_stack_t **p_fonts, bool b_bold, bool b_italic,
-        bool b_uline, bool b_through )
+#ifdef HAVE_STYLES
+
+static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
+                       subpicture_region_t *p_region_in )
 {
-        VLC_UNUSED(p_sys);
-        VLC_UNUSED(p_fonts);
-        VLC_UNUSED(b_bold);
-        VLC_UNUSED(b_italic);
-        VLC_UNUSED(b_uline);
-        VLC_UNUSED(b_through);
-        return NULL;
+    return RenderCommon( p_filter, p_region_out, p_region_in, true );
 }
+
 #endif
 
-static void FreeLine( line_desc_t *p_line )
+/*****************************************************************************
+ * Create: allocates osd-text video thread output method
+ *****************************************************************************
+ * This function allocates and initializes a Clone vout method.
+ *****************************************************************************/
+static int Create( vlc_object_t *p_this )
 {
-    unsigned int i;
-    for( i = 0; 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->p_fg_rgb );
-    free( p_line->p_bg_rgb );
-    free( p_line->p_fg_bg_ratio );
-    free( p_line->pi_underline_offset );
-    free( p_line->pi_underline_thickness );
-    free( p_line );
-}
+    filter_t      *p_filter = (filter_t *)p_this;
+    filter_sys_t  *p_sys;
+    char          *psz_fontfile   = NULL;
+    char          *psz_fontfamily = NULL;
+    int            i_error = 0, fontindex = 0;
 
-static void FreeLines( line_desc_t *p_lines )
-{
-    line_desc_t *p_line, *p_next;
+    /* Allocate structure */
+    p_filter->p_sys = p_sys = malloc( sizeof(*p_sys) );
+    if( !p_sys )
+        return VLC_ENOMEM;
+
+    p_sys->psz_fontfamily   = NULL;
+#ifdef HAVE_STYLES
+    p_sys->p_xml            = NULL;
+#endif
+    p_sys->p_face           = 0;
+    p_sys->p_library        = 0;
+    p_sys->i_font_size      = 0;
+    p_sys->i_display_height = 0;
+
+    var_Create( p_filter, "freetype-rel-fontsize",
+                VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
+
+    psz_fontfamily = var_InheritString( p_filter, "freetype-font" );
+    p_sys->i_default_font_size = var_InheritInteger( p_filter, "freetype-fontsize" );
+    p_sys->i_font_opacity = var_InheritInteger( p_filter,"freetype-opacity" );
+    p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
+    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 );
 
-    if( !p_lines ) return;
+    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 );
+
+#ifdef WIN32
+    /* Get Windows Font folder */
+    wchar_t wdir[MAX_PATH];
+    if( S_OK != SHGetFolderPathW( NULL, CSIDL_FONTS, NULL, SHGFP_TYPE_CURRENT, wdir ) )
+    {
+        GetWindowsDirectoryW( wdir, MAX_PATH );
+        wcscat( wdir, L"\\fonts" );
+    }
+    p_sys->psz_win_fonts_path = FromWide( wdir );
+#endif
 
-    for( p_line = p_lines; p_line != NULL; p_line = p_next )
+    /* Set default psz_fontfamily */
+    if( !psz_fontfamily || !*psz_fontfamily )
     {
-        p_next = p_line->p_next;
-        FreeLine( p_line );
+        free( psz_fontfamily );
+#ifdef HAVE_STYLES
+        psz_fontfamily = strdup( DEFAULT_FAMILY );
+#else
+# ifdef WIN32
+        if( asprintf( &psz_fontfamily, "%s"DEFAULT_FONT_FILE, p_sys->psz_win_fonts_path ) == -1 )
+            goto error;
+# else
+        psz_fontfamily = strdup( DEFAULT_FONT_FILE );
+# endif
+        msg_Err( p_filter,"User specified an empty fontfile, using %s", psz_fontfamily );
+#endif
     }
-}
 
-static line_desc_t *NewLine( int i_count )
-{
-    line_desc_t *p_line = malloc( sizeof(line_desc_t) );
+    /* Set the current font file */
+    p_sys->psz_fontfamily = psz_fontfamily;
+#ifdef HAVE_STYLES
+#ifdef HAVE_FONTCONFIG
+    FontConfig_BuildCache( p_filter );
 
-    if( !p_line ) return NULL;
-    p_line->i_height = 0;
-    p_line->i_width = 0;
-    p_line->p_next = NULL;
+    /* */
+    psz_fontfile = FontConfig_Select( NULL, psz_fontfamily, false, false,
+                                      p_sys->i_default_font_size, &fontindex );
+#elif defined(WIN32)
+    psz_fontfile = Win32_Select( p_filter, psz_fontfamily, false, false,
+                                 p_sys->i_default_font_size, &fontindex );
+
+#endif
+    msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
+
+    /* If nothing is found, use the default family */
+    if( !psz_fontfile )
+        psz_fontfile = strdup( psz_fontfamily );
+
+#else /* !HAVE_STYLES */
+    /* Use the default file */
+    psz_fontfile = psz_fontfamily;
+#endif
 
-    p_line->pp_glyphs = malloc( sizeof(FT_BitmapGlyph) * ( i_count + 1 ) );
-    p_line->p_glyph_pos = malloc( sizeof( FT_Vector ) * ( i_count + 1 ) );
-    p_line->p_fg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) );
-    p_line->p_bg_rgb = malloc( sizeof( uint32_t ) * ( i_count + 1 ) );
-    p_line->p_fg_bg_ratio = calloc( i_count + 1, sizeof( uint8_t ) );
-    p_line->pi_underline_offset = calloc( i_count + 1, sizeof( int ) );
-    p_line->pi_underline_thickness = calloc( i_count + 1, sizeof( uint16_t ) );
-    if( ( p_line->pp_glyphs == NULL ) ||
-        ( p_line->p_glyph_pos == NULL ) ||
-        ( p_line->p_fg_rgb == NULL ) ||
-        ( p_line->p_bg_rgb == NULL ) ||
-        ( p_line->p_fg_bg_ratio == NULL ) ||
-        ( p_line->pi_underline_offset == NULL ) ||
-        ( p_line->pi_underline_thickness == NULL ) )
+    /* */
+    i_error = FT_Init_FreeType( &p_sys->p_library );
+    if( i_error )
     {
-        free( p_line->pi_underline_thickness );
-        free( p_line->pi_underline_offset );
-        free( p_line->p_fg_rgb );
-        free( p_line->p_bg_rgb );
-        free( p_line->p_fg_bg_ratio );
-        free( p_line->p_glyph_pos );
-        free( p_line->pp_glyphs );
-        free( p_line );
-        return NULL;
+        msg_Err( p_filter, "couldn't initialize freetype" );
+        goto error;
     }
-    p_line->pp_glyphs[0] = NULL;
-    p_line->b_new_color_mode = false;
 
-    return p_line;
-}
-
-static int GetFontSize( filter_t *p_filter )
-{
-    filter_sys_t *p_sys = p_filter->p_sys;
-    vlc_value_t   val;
-    int           i_size = 0;
+    i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
+                           fontindex, &p_sys->p_face );
 
-    if( p_sys->i_default_font_size )
+    if( i_error == FT_Err_Unknown_File_Format )
     {
-        if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
-            i_size = p_sys->i_default_font_size * val.i_int / 1000;
-        else
-            i_size = p_sys->i_default_font_size;
+        msg_Err( p_filter, "file %s have unknown format",
+                 psz_fontfile ? psz_fontfile : "(null)" );
+        goto error;
     }
-    else
+    else if( i_error )
     {
-        var_Get( p_filter, "freetype-rel-fontsize", &val );
-        if( val.i_int  > 0 )
-        {
-            i_size = (int)p_filter->fmt_out.video.i_height / val.i_int;
-            p_filter->p_sys->i_display_height =
-                p_filter->fmt_out.video.i_height;
-        }
+        msg_Err( p_filter, "failed to load font file %s",
+                 psz_fontfile ? psz_fontfile : "(null)" );
+        goto error;
     }
-    if( i_size <= 0 )
+#ifdef HAVE_STYLES
+    free( psz_fontfile );
+#endif
+
+    i_error = FT_Select_Charmap( p_sys->p_face, ft_encoding_unicode );
+    if( i_error )
     {
-        msg_Warn( p_filter, "invalid fontsize, using 12" );
-        if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
-            i_size = 12 * val.i_int / 1000;
-        else
-            i_size = 12;
+        msg_Err( p_filter, "font has no unicode translation table" );
+        goto error;
     }
-    return i_size;
+
+    if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
+
+
+    p_sys->pp_font_attachments = NULL;
+    p_sys->i_font_attachments = 0;
+
+    p_filter->pf_render_text = RenderText;
+#ifdef HAVE_STYLES
+    p_filter->pf_render_html = RenderHtml;
+#else
+    p_filter->pf_render_html = NULL;
+#endif
+
+    LoadFontsFromAttachments( p_filter );
+
+    return VLC_SUCCESS;
+
+error:
+    if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
+    if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
+#ifdef HAVE_STYLES
+    free( psz_fontfile );
+#endif
+    free( psz_fontfamily );
+    free( p_sys );
+    return VLC_EGENERIC;
 }
 
-static int SetFontSize( filter_t *p_filter, int i_size )
+/*****************************************************************************
+ * Destroy: destroy Clone video thread output method
+ *****************************************************************************
+ * Clean up all data and library connections
+ *****************************************************************************/
+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;
 
-    if( !i_size )
+    if( p_sys->pp_font_attachments )
     {
-        i_size = GetFontSize( p_filter );
+        for( int k = 0; k < p_sys->i_font_attachments; k++ )
+            vlc_input_attachment_Delete( p_sys->pp_font_attachments[k] );
 
-        msg_Dbg( p_filter, "using fontsize: %i", i_size );
+        free( p_sys->pp_font_attachments );
     }
 
-    p_sys->i_font_size = i_size;
+#ifdef HAVE_STYLES
+    if( p_sys->p_xml ) xml_ReaderDelete( p_sys->p_xml );
+#endif
+    free( p_sys->psz_fontfamily );
 
-    if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
-    {
-        msg_Err( p_filter, "couldn't set font size to %d", i_size );
-        return VLC_EGENERIC;
-    }
+    /* FcFini asserts calling the subfunction FcCacheFini()
+     * even if no other library functions have been made since FcInit(),
+     * so don't call it. */
 
-    return VLC_SUCCESS;
+    FT_Done_Face( p_sys->p_face );
+    FT_Done_FreeType( p_sys->p_library );
+    free( p_sys );
 }
 
-static void YUVFromRGB( uint32_t i_argb,
-                    uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
-{
-    int i_red   = ( i_argb & 0x00ff0000 ) >> 16;
-    int i_green = ( i_argb & 0x0000ff00 ) >>  8;
-    int i_blue  = ( i_argb & 0x000000ff );
 
-    *pi_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
-                      802 * i_blue + 4096 + 131072 ) >> 13, 235);
-    *pi_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
-                     3598 * i_blue + 4096 + 1048576) >> 13, 240);
-    *pi_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
-                      -585 * i_blue + 4096 + 1048576) >> 13, 240);
-}