freetype.c 75.9 KB
Newer Older
1 2 3
/*****************************************************************************
 * freetype.c : Put text on the video, using freetype2
 *****************************************************************************
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2002 - 2012 VLC authors and VideoLAN
5
 * $Id$
6
 *
7
 * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8
 *          Gildas Bazin <gbazin@videolan.org>
9
 *          Bernie Purcell <bitmap@videolan.org>
10
 *          Jean-Baptiste Kempf <jb@videolan.org>
11
 *          Felix Paul Kühne <fkuehne@videolan.org>
12
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
13 14 15
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
16 17 18 19
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
20 21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
22
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
23
 * You should have received a copy of the GNU Lesser General Public License
24
 * along with this program; if not, write to the Free Software Foundation, Inc.,
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
25
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 27 28 29 30 31
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

32 33 34
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
35
#include <math.h>
36

37
#include <vlc_common.h>
38
#include <vlc_plugin.h>
39 40 41 42
#include <vlc_stream.h>                        /* stream_MemoryNew */
#include <vlc_input.h>                         /* vlc_input_attachment_* */
#include <vlc_xml.h>                           /* xml_reader */
#include <vlc_dialog.h>                        /* FcCache dialog */
43 44
#include <vlc_filter.h>                                      /* filter_sys_t */
#include <vlc_text_style.h>                                   /* text_style_t*/
45 46

/* Freetype */
47
#include <ft2build.h>
48 49
#include FT_FREETYPE_H
#include FT_GLYPH_H
50
#include FT_STROKER_H
51
#include FT_SYNTHESIS_H
52

53 54
#define FT_FLOOR(X)     ((X & -64) >> 6)
#define FT_CEIL(X)      (((X + 63) & -64) >> 6)
55 56 57
#ifndef FT_MulFix
 #define FT_MulFix(v, s) (((v)*(s))>>16)
#endif
58

59
/* RTL */
60
#if defined(HAVE_FRIBIDI)
61
# include <fribidi/fribidi.h>
62 63
#endif

64 65 66 67
/* apple stuff */
#ifdef __APPLE__
# include <TargetConditionals.h>
# undef HAVE_FONTCONFIG
68
# define HAVE_GET_FONT_BY_FAMILY_NAME
69 70 71 72
#endif

/* Win32 */
#ifdef _WIN32
73
# undef HAVE_FONTCONFIG
74 75 76
# if !VLC_WINSTORE_APP
#  define HAVE_GET_FONT_BY_FAMILY_NAME
# endif
77 78
#endif

79
/* FontConfig */
80
#ifdef HAVE_FONTCONFIG
81
# define HAVE_GET_FONT_BY_FAMILY_NAME
82 83
#endif

84 85
#include <assert.h>

86
#include "text_renderer.h"
87
#include "platform_fonts.h"
88

89
/*****************************************************************************
90
 * Module descriptor
91
 *****************************************************************************/
92 93 94
static int  Create ( vlc_object_t * );
static void Destroy( vlc_object_t * );

95
#define FONT_TEXT N_("Font")
96
#define MONOSPACE_FONT_TEXT N_("Monospace Font")
97

98
#define FAMILY_LONGTEXT N_("Font family for the font you want to use")
Christophe Mutricy's avatar
Christophe Mutricy committed
99
#define FONT_LONGTEXT N_("Font file for the font you want to use")
100

101
#define FONTSIZE_TEXT N_("Font size in pixels")
102 103
#define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
    "that will be rendered on the video. " \
Sam Hocevar's avatar
Sam Hocevar committed
104
    "If set to something different than 0 this option will override the " \
105
    "relative font size." )
106
#define OPACITY_TEXT N_("Text opacity")
107 108 109 110 111 112 113 114 115 116 117
#define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
    "text that will be rendered on the video. 0 = transparent, " \
    "255 = totally opaque. " )
#define COLOR_TEXT N_("Text default color")
#define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
    "the video. This must be an hexadecimal (like HTML colors). The first two "\
    "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
    " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
#define FONTSIZER_TEXT N_("Relative font size")
#define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
    "fonts that will be rendered on the video. If absolute font size is set, "\
Christophe Mutricy's avatar
Christophe Mutricy committed
118
    "relative size will be overridden." )
119
#define BOLD_TEXT N_("Force bold")
120

121 122 123
#define BG_OPACITY_TEXT N_("Background opacity")
#define BG_COLOR_TEXT N_("Background color")

124 125 126 127
#define OUTLINE_OPACITY_TEXT N_("Outline opacity")
#define OUTLINE_COLOR_TEXT N_("Outline color")
#define OUTLINE_THICKNESS_TEXT N_("Outline thickness")

128 129 130 131 132 133
#define SHADOW_OPACITY_TEXT N_("Shadow opacity")
#define SHADOW_COLOR_TEXT N_("Shadow color")
#define SHADOW_ANGLE_TEXT N_("Shadow angle")
#define SHADOW_DISTANCE_TEXT N_("Shadow distance")


134 135 136
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") };
137 138 139
#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" )
140

141
static const int pi_color_values[] = {
142
  0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
143 144
  0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
  0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
145

146
static const char *const ppsz_color_descriptions[] = {
147 148 149
  N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
  N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
  N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
150

151 152 153 154 155 156 157
static const int pi_outline_thickness[] = {
    0, 2, 4, 6,
};
static const char *const ppsz_outline_thickness[] = {
    N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
};

158 159 160 161 162
vlc_module_begin ()
    set_shortname( N_("Text renderer"))
    set_description( N_("Freetype2 font renderer") )
    set_category( CAT_VIDEO )
    set_subcategory( SUBCAT_VIDEO_SUBPIC )
163

164
#ifdef HAVE_GET_FONT_BY_FAMILY_NAME
165
    add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
166
    add_font( "freetype-monofont", DEFAULT_MONOSPACE_FAMILY, MONOSPACE_FONT_TEXT, FAMILY_LONGTEXT, false )
167
#else
168
    add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
169
    add_loadfile( "freetype-monofont", DEFAULT_MONOSPACE_FONT_FILE, MONOSPACE_FONT_TEXT, FONT_LONGTEXT, false )
170
#endif
171

172
    add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
173
                 FONTSIZE_LONGTEXT, true )
Kaarlo Raiha's avatar
Kaarlo Raiha committed
174
        change_integer_range( 0, 4096)
175
        change_safe()
176

177 178 179 180 181
    add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
                 FONTSIZER_LONGTEXT, false )
        change_integer_list( pi_sizes, ppsz_sizes_text )
        change_safe()

182
    /* opacity valid on 0..255, with default 255 = fully opaque */
183
    add_integer_with_range( "freetype-opacity", 255, 0, 255,
184
        OPACITY_TEXT, OPACITY_LONGTEXT, false )
185
        change_safe()
186

187
    /* hook to the color values list, with default 0x00ffffff = white */
188
    add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
189
                 COLOR_LONGTEXT, false )
190
        change_integer_list( pi_color_values, ppsz_color_descriptions )
191
        change_safe()
192

193
    add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
194 195
        change_safe()

196
    add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
197
                            BG_OPACITY_TEXT, NULL, false )
198
        change_safe()
199
    add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
200
             NULL, false )
201 202 203
        change_integer_list( pi_color_values, ppsz_color_descriptions )
        change_safe()

204
    add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
205
                            OUTLINE_OPACITY_TEXT, NULL, false )
206
        change_safe()
207
    add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
208
             NULL, false )
209 210 211
        change_integer_list( pi_color_values, ppsz_color_descriptions )
        change_safe()
    add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
212
             NULL, false )
213 214 215
        change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
        change_safe()

216
    add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
217
                            SHADOW_OPACITY_TEXT, NULL, false )
218
        change_safe()
219
    add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
220
             NULL, false )
221 222 223
        change_integer_list( pi_color_values, ppsz_color_descriptions )
        change_safe()
    add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
224
                          SHADOW_ANGLE_TEXT, NULL, false )
225 226
        change_safe()
    add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
227
                          SHADOW_DISTANCE_TEXT, NULL, false )
228 229
        change_safe()

230
    add_obsolete_integer( "freetype-effect" );
231

232
    add_bool( "freetype-yuvp", false, YUVP_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
233
              YUVP_LONGTEXT, true )
234 235 236 237
    set_capability( "text renderer", 100 )
    add_shortcut( "text" )
    set_callbacks( Create, Destroy )
vlc_module_end ()
238

239 240 241 242 243

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/

244 245 246
typedef struct
{
    FT_BitmapGlyph p_glyph;
247
    FT_BitmapGlyph p_outline;
248
    FT_BitmapGlyph p_shadow;
249 250
    uint32_t       i_color;             /* ARGB color */
    int            i_line_offset;       /* underline/strikethrough offset */
251
    int            i_line_thickness;    /* underline/strikethrough thickness */
252 253
} line_character_t;

254
typedef struct line_desc_t line_desc_t;
255 256
struct line_desc_t
{
257
    line_desc_t      *p_next;
258 259

    int              i_width;
260
    int              i_height;
261
    int              i_base_line;
262 263
    int              i_character_count;
    line_character_t *p_character;
264 265 266
};

/*****************************************************************************
267
 * filter_sys_t: freetype local data
268 269
 *****************************************************************************
 * This structure is part of the video output thread descriptor.
270
 * It describes the freetype specific properties of an output thread.
271
 *****************************************************************************/
272
struct filter_sys_t
273 274 275
{
    FT_Library     p_library;   /* handle to library     */
    FT_Face        p_face;      /* handle to face object */
276
    FT_Stroker     p_stroker;   /* handle to path stroker object */
277

278
    xml_reader_t  *p_xml;       /* vlc xml parser */
279

280
    text_style_t   style;       /* Current Style */
281

282
    /* More styles... */
283 284
    float          f_shadow_vector_x;
    float          f_shadow_vector_y;
285
    int            i_default_font_size;
286 287 288 289 290

    /* Attachments */
    input_attachment_t **pp_font_attachments;
    int                  i_font_attachments;

291 292 293 294
    char * (*pf_select) (filter_t *, const char* family,
                               bool bold, bool italic, int size,
                               int *index);

295 296
};

297
/* */
Laurent Aimar's avatar
Laurent Aimar committed
298 299
static void YUVFromRGB( uint32_t i_argb,
                    uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
300
{
Laurent Aimar's avatar
Laurent Aimar committed
301 302 303
    int i_red   = ( i_argb & 0x00ff0000 ) >> 16;
    int i_green = ( i_argb & 0x0000ff00 ) >>  8;
    int i_blue  = ( i_argb & 0x000000ff );
304

Laurent Aimar's avatar
Laurent Aimar committed
305 306 307 308 309 310
    *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);
311
}
312 313 314 315 316 317 318
static void RGBFromRGB( uint32_t i_argb,
                        uint8_t *pi_r, uint8_t *pi_g, uint8_t *pi_b )
{
    *pi_r = ( i_argb & 0x00ff0000 ) >> 16;
    *pi_g = ( i_argb & 0x0000ff00 ) >>  8;
    *pi_b = ( i_argb & 0x000000ff );
}
319

320 321 322 323 324 325 326 327 328 329
/*****************************************************************************
 * Make any TTF/OTF fonts present in the attachments of the media file
 * and store them for later use by the FreeType Engine
 *****************************************************************************/
static int LoadFontsFromAttachments( filter_t *p_filter )
{
    filter_sys_t         *p_sys = p_filter->p_sys;
    input_attachment_t  **pp_attachments;
    int                   i_attachments_cnt;

330
    if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
331
        return VLC_EGENERIC;
Bernie Purcell's avatar
Bernie Purcell committed
332

333
    p_sys->i_font_attachments = 0;
334
    p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
335 336
    if( !p_sys->pp_font_attachments )
        return VLC_ENOMEM;
337

338
    for( int k = 0; k < i_attachments_cnt; k++ )
339 340 341
    {
        input_attachment_t *p_attach = pp_attachments[k];

342 343 344
        if( ( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
              !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) &&    // OTF
            p_attach->i_data > 0 && p_attach->p_data )
345
        {
346
            p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
347 348 349 350 351 352
        }
        else
        {
            vlc_input_attachment_Delete( p_attach );
        }
    }
Bernie Purcell's avatar
Bernie Purcell committed
353
    free( pp_attachments );
354

355
    return VLC_SUCCESS;
356 357
}

Laurent Aimar's avatar
Laurent Aimar committed
358
static int GetFontSize( filter_t *p_filter )
359
{
Laurent Aimar's avatar
Laurent Aimar committed
360 361
    filter_sys_t *p_sys = p_filter->p_sys;
    int           i_size = 0;
362

Laurent Aimar's avatar
Laurent Aimar committed
363 364
    if( p_sys->i_default_font_size )
    {
365
        i_size = p_sys->i_default_font_size;
Laurent Aimar's avatar
Laurent Aimar committed
366 367 368
    }
    else
    {
369
        int i_ratio = var_InheritInteger( p_filter, "freetype-rel-fontsize" );
370
        if( i_ratio > 0 )
Laurent Aimar's avatar
Laurent Aimar committed
371
        {
372
            i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
Laurent Aimar's avatar
Laurent Aimar committed
373 374 375 376 377
        }
    }
    if( i_size <= 0 )
    {
        msg_Warn( p_filter, "invalid fontsize, using 12" );
378
        i_size = 12;
Laurent Aimar's avatar
Laurent Aimar committed
379 380 381
    }
    return i_size;
}
382

Laurent Aimar's avatar
Laurent Aimar committed
383 384 385 386 387 388 389 390 391 392 393
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 );
    }

394
    p_sys->style.i_font_size = i_size;
Laurent Aimar's avatar
Laurent Aimar committed
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410

    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;
}

/*****************************************************************************
 * 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,
411 412
                       line_desc_t *p_line,
                       FT_BBox *p_bbox )
Laurent Aimar's avatar
Laurent Aimar committed
413 414 415 416 417 418 419 420 421 422 423 424
{
    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 */
425 426 427 428 429
    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;
430

431
    assert( !p_region->p_picture );
432
    p_region->p_picture = picture_NewFromFormat( &fmt );
433 434
    if( !p_region->p_picture )
        return VLC_EGENERIC;
435
    fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
436
    p_region->fmt = fmt;
437

438 439
    /* Calculate text color components
     * Only use the first color */
440
    int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
441
    YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
442 443

    /* Build palette */
444
    fmt.p_palette->i_entries = 16;
445 446 447 448 449 450 451
    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] =
452
            (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
453 454
    }
    for( i = 8; i < fmt.p_palette->i_entries; i++ )
455
    {
456
        fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
457 458
        fmt.p_palette->palette[i][1] = i_u;
        fmt.p_palette->palette[i][2] = i_v;
459
        fmt.p_palette->palette[i][3] = pi_gamma[i];
460
        fmt.p_palette->palette[i][3] =
461
            (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
462 463
    }

464 465
    p_dst = p_region->p_picture->Y_PIXELS;
    i_pitch = p_region->p_picture->Y_PITCH;
466

467
    /* Initialize the region pixels */
468
    memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
469

470
    for( ; p_line != NULL; p_line = p_line->p_next )
471
    {
472
        int i_align_left = 0;
473
        if( p_line->i_width < (int)fmt.i_visible_width )
474
        {
475
            if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
476
                i_align_left = ( fmt.i_visible_width - p_line->i_width );
477
            else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
478
                i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
479
        }
480
        int i_align_top = 0;
481

482
        for( i = 0; i < p_line->i_character_count; i++ )
483
        {
484 485
            const line_character_t *ch = &p_line->p_character[i];
            FT_BitmapGlyph p_glyph = ch->p_glyph;
486

487 488
            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;
489

490
            for( y = 0; y < p_glyph->bitmap.rows; y++ )
491
            {
492
                for( x = 0; x < p_glyph->bitmap.width; x++ )
493
                {
494 495 496
                    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;
497 498
                }
            }
499 500
        }
    }
501

502 503 504
    /* Outlining (find something better than nearest neighbour filtering ?) */
    if( 1 )
    {
505
        uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
506 507
        uint8_t *p_top = p_dst; /* Use 1st line as a cache */
        uint8_t left, current;
508

509 510
        for( y = 1; y < (int)fmt.i_height - 1; y++ )
        {
511
            if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
512
            p_dst += p_region->p_picture->Y_PITCH;
513 514 515
            left = 0;

            for( x = 1; x < (int)fmt.i_width - 1; x++ )
516
            {
517
                current = p_dst[x];
518
                p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
519
                             p_dst[x -1 + p_region->p_picture->Y_PITCH ] + p_dst[x + p_region->p_picture->Y_PITCH] + p_dst[x + 1 + p_region->p_picture->Y_PITCH]) / 16;
520
                left = current;
521 522
            }
        }
523
        memset( p_top, 0, fmt.i_width );
524
    }
525 526

    return VLC_SUCCESS;
527
}
528

529 530 531 532 533
/*****************************************************************************
 * RenderYUVA: place string in picture
 *****************************************************************************
 * This function merges the previously rendered freetype glyphs into a picture
 *****************************************************************************/
534 535 536 537 538 539 540 541 542 543 544 545 546
static void FillYUVAPicture( picture_t *p_picture,
                             int i_a, int i_y, int i_u, int 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 );
}

547 548 549 550
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 )
551
{
552
    int i_an = i_a * i_alpha / 255;
553

554 555 556 557
    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];
558

559 560
    int i_ao = *p_a;
    if( i_ao == 0 )
561
    {
562 563 564 565 566 567 568 569 570
        *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 )
571
        {
572 573 574
            *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;
575
        }
576 577
    }
}
578

579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
static void FillRGBAPicture( picture_t *p_picture,
                             int i_a, int i_r, int i_g, int i_b )
{
    for( int dy = 0; dy < p_picture->p[0].i_visible_lines; dy++ )
    {
        for( int dx = 0; dx < p_picture->p[0].i_visible_pitch; dx += 4 )
        {
            uint8_t *p_rgba = &p_picture->p->p_pixels[dy * p_picture->p->i_pitch + dx];
            p_rgba[0] = i_r;
            p_rgba[1] = i_g;
            p_rgba[2] = i_b;
            p_rgba[3] = i_a;
        }
    }
}

static inline void BlendRGBAPixel( picture_t *p_picture,
596
                                   int i_picture_x, int i_picture_y,
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
                                   int i_a, int i_r, int i_g, int i_b,
                                   int i_alpha )
{
    int i_an = i_a * i_alpha / 255;

    uint8_t *p_rgba = &p_picture->p->p_pixels[i_picture_y * p_picture->p->i_pitch + 4 * i_picture_x];

    int i_ao = p_rgba[3];
    if( i_ao == 0 )
    {
        p_rgba[0] = i_r;
        p_rgba[1] = i_g;
        p_rgba[2] = i_b;
        p_rgba[3] = i_an;
    }
    else
    {
        p_rgba[3] = 255 - (255 - p_rgba[3]) * (255 - i_an) / 255;
        if( p_rgba[3] != 0 )
        {
            p_rgba[0] = ( p_rgba[0] * i_ao * (255 - i_an) / 255 + i_r * i_an ) / p_rgba[3];
            p_rgba[1] = ( p_rgba[1] * i_ao * (255 - i_an) / 255 + i_g * i_an ) / p_rgba[3];
            p_rgba[2] = ( p_rgba[2] * i_ao * (255 - i_an) / 255 + i_b * i_an ) / p_rgba[3];
        }
    }
}

624 625
static void FillARGBPicture(picture_t *pic, int a, int r, int g, int b)
{
626 627 628 629 630 631 632 633 634 635 636 637
    if (a == 0)
        r = g = b = 0;
    if (a == r && a == b && a == g)
    {   /* fast path */
        memset(pic->p->p_pixels, a, pic->p->i_visible_lines * pic->p->i_pitch);
        return;
    }

    uint_fast32_t pixel = VLC_FOURCC(a, r, g, b);
    uint8_t *line = pic->p->p_pixels;

    for (unsigned lines = pic->p->i_visible_lines; lines > 0; lines--)
638
    {
639 640 641 642
        uint32_t *pixels = (uint32_t *)line;
        for (unsigned cols = pic->p->i_visible_pitch; cols > 0; cols -= 4)
            *(pixels++) = pixel;
        line += pic->p->i_pitch;
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
    }
}

static inline void BlendARGBPixel(picture_t *pic, int pic_x, int pic_y,
                                  int a, int r, int g, int b, int alpha)
{
    uint8_t *rgba = &pic->p->p_pixels[pic_y * pic->p->i_pitch + 4 * pic_x];
    int an = a * alpha / 255;
    int ao = rgba[3];

    if (ao == 0)
    {
        rgba[0] = an;
        rgba[1] = r;
        rgba[2] = g;
        rgba[3] = b;
    }
    else
    {
        rgba[0] = 255 - (255 - rgba[0]) * (255 - an) / 255;
        if (rgba[0] != 0)
        {
            rgba[1] = (rgba[1] * ao * (255 - an) / 255 + r * an ) / rgba[0];
            rgba[2] = (rgba[2] * ao * (255 - an) / 255 + g * an ) / rgba[0];
            rgba[3] = (rgba[3] * ao * (255 - an) / 255 + b * an ) / rgba[0];
        }
    }
}

672 673 674 675 676 677
static inline void BlendAXYZGlyph( picture_t *p_picture,
                                   int i_picture_x, int i_picture_y,
                                   int i_a, int i_x, int i_y, int i_z,
                                   FT_BitmapGlyph p_glyph,
                                   void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )

678 679 680 681
{
    for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
    {
        for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
682 683 684
            BlendPixel( p_picture, i_picture_x + dx, i_picture_y + dy,
                        i_a, i_x, i_y, i_z,
                        p_glyph->bitmap.buffer[dy * p_glyph->bitmap.width + dx] );
685 686 687
    }
}

688
static inline void BlendAXYZLine( picture_t *p_picture,
689
                                  int i_picture_x, int i_picture_y,
690
                                  int i_a, int i_x, int i_y, int i_z,
691
                                  const line_character_t *p_current,
692 693
                                  const line_character_t *p_next,
                                  void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
694
{
695
    int i_line_width = p_current->p_glyph->bitmap.width;
696 697
    if( p_next )
        i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
698

699
    for( int dx = 0; dx < i_line_width; dx++ )
700
    {
701
        for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
702 703 704 705
            BlendPixel( p_picture,
                        i_picture_x + dx,
                        i_picture_y + p_current->i_line_offset + dy,
                        i_a, i_x, i_y, i_z, 0xff );
706 707 708
    }
}

709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
static inline void RenderBackground( subpicture_region_t *p_region,
                                     line_desc_t *p_line_head,
                                     FT_BBox *p_bbox,
                                     int i_margin,
                                     picture_t *p_picture,
                                     int i_text_width,
                                     void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
                                     void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
{
    for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
    {
        int i_align_left = i_margin;
        int i_align_top = i_margin;
        int line_start = 0;
        int line_end = 0;
        unsigned line_top = 0;
        int line_bottom = 0;
        int max_height = 0;

        if( p_line->i_width < i_text_width )
        {
            /* Left offset to take into account alignment */
            if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
                i_align_left += ( i_text_width - p_line->i_width );
            else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
                i_align_left = i_margin; /* Keep it the way it is */
            else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
                i_align_left += ( i_text_width - p_line->i_width ) / 2;
        }

        /* Find the tallest character in the line */
        for( int i = 0; i < p_line->i_character_count; i++ ) {
            const line_character_t *ch = &p_line->p_character[i];
            FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
            if (p_glyph->top > max_height)
                max_height = p_glyph->top;
        }

        /* Compute the background for the line (identify leading/trailing space) */
        for( int i = 0; i < p_line->i_character_count; i++ ) {
            const line_character_t *ch = &p_line->p_character[i];
            FT_BitmapGlyph p_glyph = ch->p_outline ? ch->p_outline : ch->p_glyph;
            if (p_glyph && p_glyph->bitmap.rows > 0) {
                // Found a non-whitespace character
                line_start = i_align_left + p_glyph->left - p_bbox->xMin;
                break;
            }
        }

        /* Fudge factor to make sure caption background edges are left aligned
           despite variable font width */
        if (line_start < 12)
            line_start = 0;

        /* Find right boundary for bounding box for background */
        for( int i = p_line->i_character_count; i > 0; i-- ) {
            const line_character_t *ch = &p_line->p_character[i - 1];
            FT_BitmapGlyph p_glyph = ch->p_shadow ? ch->p_shadow : ch->p_glyph;
            if (p_glyph && p_glyph->bitmap.rows > 0) {
                // Found a non-whitespace character
                line_end = i_align_left + p_glyph->left - p_bbox->xMin + p_glyph->bitmap.width;
                break;
            }
        }

        /* Setup color for the background */
        uint8_t i_x, i_y, i_z;
        ExtractComponents( 0x000000, &i_x, &i_y, &i_z );

        /* Compute the upper boundary for the background */
        if ((i_align_top + p_line->i_base_line - max_height) < 0)
            line_top = i_align_top + p_line->i_base_line;
        else
            line_top = i_align_top + p_line->i_base_line - max_height;

        /* Compute lower boundary for the background */
        line_bottom =  __MIN(line_top + p_line->i_height, p_region->fmt.i_visible_height);

        /* Render the actual background */
        for( int dy = line_top; dy < line_bottom; dy++ )
        {
            for( int dx = line_start; dx < line_end; dx++ )
                BlendPixel( p_picture, dx, dy, 0xff, i_x, i_y, i_z, 0xff );
        }
    }
}

796 797 798 799 800 801 802 803 804
static inline int RenderAXYZ( filter_t *p_filter,
                              subpicture_region_t *p_region,
                              line_desc_t *p_line_head,
                              FT_BBox *p_bbox,
                              int i_margin,
                              vlc_fourcc_t i_chroma,
                              void (*ExtractComponents)( uint32_t, uint8_t *, uint8_t *, uint8_t * ),
                              void (*FillPicture)( picture_t *p_picture, int, int, int, int ),
                              void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
805
{
806
    filter_sys_t *p_sys = p_filter->p_sys;
807 808

    /* Create a new subpicture region */
809 810
    const int i_text_width  = p_bbox->xMax - p_bbox->xMin;
    const int i_text_height = p_bbox->yMax - p_bbox->yMin;
811
    video_format_t fmt;
812
    video_format_Init( &fmt, i_chroma );
813
    fmt.i_width          =
814
    fmt.i_visible_width  = i_text_width  + 2 * i_margin;
815
    fmt.i_height         =
816
    fmt.i_visible_height = i_text_height + 2 * i_margin;
817

818
    picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
819 820 821
    if( !p_region->p_picture )
        return VLC_EGENERIC;
    p_region->fmt = fmt;
822

823
    /* Initialize the picture background */
824 825
    uint8_t i_a = var_InheritInteger( p_filter, "freetype-background-opacity" );
    i_a = VLC_CLIP( i_a, 0, 255 );
826
    uint8_t i_x, i_y, i_z;
827

828
    if (p_region->b_renderbg) {
829
        /* Render the background just under the text */
830
        FillPicture( p_picture, 0x00, 0x00, 0x00, 0x00 );
831 832
        RenderBackground(p_region, p_line_head, p_bbox, i_margin, p_picture, i_text_width,
                         ExtractComponents, BlendPixel);
833 834
    } else {
        /* Render background under entire subpicture block */
835 836 837
        int i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
        i_background_color = VLC_CLIP( i_background_color, 0, 0xFFFFFF );
        ExtractComponents( i_background_color, &i_x, &i_y, &i_z );
838
        FillPicture( p_picture, i_a, i_x, i_y, i_z );
839 840
    }

841 842
    /* Render shadow then outline and then normal glyphs */
    for( int g = 0; g < 3; g++ )
843
    {
844 845
        /* Render all lines */
        for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
846
        {
847 848 849 850 851 852
            int i_align_left = i_margin;
            if( p_line->i_width < i_text_width )
            {
                /* Left offset to take into account alignment */
                if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
                    i_align_left += ( i_text_width - p_line->i_width );
853
                else if( (p_region->i_align & 0x10) == SUBPICTURE_ALIGN_LEAVETEXT)
854
                    i_align_left = i_margin; /* Keep it the way it is */
855 856 857 858
                else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
                    i_align_left += ( i_text_width - p_line->i_width ) / 2;
            }
            int i_align_top = i_margin;
859

860 861 862 863
            /* Render all glyphs and underline/strikethrough */
            for( int i = 0; i < p_line->i_character_count; i++ )
            {
                const line_character_t *ch = &p_line->p_character[i];
864
                FT_BitmapGlyph p_glyph = g == 0 ? ch->p_shadow : g == 1 ? ch->p_outline : ch->p_glyph;
865 866 867
                if( !p_glyph )
                    continue;

868
                i_a = (ch->i_color >> 24) & 0xff;
869 870 871
                uint32_t i_color;
                switch (g) {
                case 0:
872 873
                    i_a     = i_a * p_sys->style.i_shadow_alpha / 255;
                    i_color = p_sys->style.i_shadow_color;
874 875
                    break;
                case 1:
876 877
                    i_a     = i_a * p_sys->style.i_outline_alpha / 255;
                    i_color = p_sys->style.i_outline_color;
878 879 880 881
                    break;
                default:
                    i_color = ch->i_color;
                    break;
882
                }
883
                ExtractComponents( i_color, &i_x, &i_y, &i_z );
884 885 886 887

                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;

888
                BlendAXYZGlyph( p_picture,
889
                                i_glyph_x, i_glyph_y,
890
                                i_a, i_x, i_y, i_z,
891 892
                                p_glyph,
                                BlendPixel );
893 894

                /* underline/strikethrough are only rendered for the normal glyph */
895
                if( g == 2 && ch->i_line_thickness > 0 )
896
                    BlendAXYZLine( p_picture,
897
                                   i_glyph_x, i_glyph_y + p_glyph->top,
898
                                   i_a, i_x, i_y, i_z,
899
                                   &ch[0],
900 901
                                   i + 1 < p_line->i_character_count ? &ch[1] : NULL,
                                   BlendPixel );
902
            }
903 904
        }
    }
905

906 907 908
    return VLC_SUCCESS;
}

909

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
910

911 912
static void FreeLine( line_desc_t *p_line )
{
913
    for( int i = 0; i < p_line->i_character_count; i++ )
914 915 916 917 918
    {
        line_character_t *ch = &p_line->p_character[i];
        FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
        if( ch->p_outline )
            FT_Done_Glyph( (FT_Glyph)ch->p_outline );
919 920
        if( ch->p_shadow )
            FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
921
    }
922 923

    free( p_line->p_character );
924 925 926 927 928
    free( p_line );
}

static void FreeLines( line_desc_t *p_lines )
{
929
    for( line_desc_t *p_line = p_lines; p_line != NULL; )
930
    {
931
        line_desc_t *p_next = p_line->p_next;
932
        FreeLine( p_line );
933
        p_line = p_next;
934 935 936 937 938
    }
}

static line_desc_t *NewLine( int i_count )
{
939 940 941 942
    line_desc_t *p_line = malloc( sizeof(*p_line) );

    if( !p_line )
        return NULL;
943 944

    p_line->p_next = NULL;
945
    p_line->i_width = 0;
946
    p_line->i_base_line = 0;
947
    p_line->i_character_count = 0;
948

949 950
    p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
    if( !p_line->p_character )
951
    {
952
        free( p_line );
953 954 955 956 957
        return NULL;
    }
    return p_line;
}

958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975
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++ )
    {
        input_attachment_t *p_attach   = p_sys->pp_font_attachments[k];
        int                 i_font_idx = 0;
        FT_Face             p_face = NULL;

        while( 0 == FT_New_Memory_Face( p_sys->p_library,
                                        p_attach->p_data,
                                        p_attach->i_data,
                                        i_font_idx,
                                        &p_face ))
        {
            if( p_face )
            {
                int i_style_received = ((p_face->style_flags & FT_STYLE_FLAG_BOLD)    ? STYLE_BOLD   : 0) |
                                       ((p_face->style_flags & FT_STYLE_FLAG_ITALIC ) ? STYLE_ITALIC : 0);
976 977 978 979
                if( p_face->family_name != NULL
                 && !strcasecmp( p_face->family_name, p_style->psz_fontname )
                 && (p_style->i_style_flags & (STYLE_BOLD | STYLE_ITALIC))
                                                          == i_style_received )
980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
                    return p_face;

                FT_Done_Face( p_face );
            }
            i_font_idx++;
        }
    }
    return NULL;
}

static FT_Face LoadFace( filter_t *p_filter,
                         const text_style_t *p_style )
{
    filter_sys_t *p_sys = p_filter->p_sys;

    /* Look for a match amongst our attachments first */
    FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );

    /* Load system wide font otheriwse */
    if( !p_face )
    {
        int  i_idx = 0;
1002
        char *psz_fontfile = NULL;
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
        if( p_sys->pf_select )
            psz_fontfile = p_sys->pf_select( p_filter,
                                             p_style->psz_fontname,
                                             (p_style->i_style_flags & STYLE_BOLD) != 0,
                                             (p_style->i_style_flags & STYLE_ITALIC) != 0,
                                             -1,
                                             &i_idx );
        else
            psz_fontfile = NULL;

1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
        if( !psz_fontfile )
            return NULL;

        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( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
    {
        /* We've loaded a font face which is unhelpful for actually
         * rendering text - fallback to the default one.
         */
        FT_Done_Face( p_face );
        return NULL;
    }
    return p_face;
}
1046

1047
static int GetGlyph( filter_t *p_filter,
1048 1049
                     FT_Glyph *pp_glyph,   FT_BBox *p_glyph_bbox,
                     FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
1050
                     FT_Glyph *pp_shadow,  FT_BBox *p_shadow_bbox,
1051 1052 1053

                     FT_Face  p_face,
                     int i_glyph_index,
1054
                     int i_style_flags,
1055 1056
                     FT_Vector *p_pen,
                     FT_Vector *p_pen_shadow )
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
{
    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;
    }

1081 1082 1083 1084
    FT_Glyph outline = NULL;
    if( p_filter->p_sys->p_stroker )
    {
        outline = glyph;
Erwan Tulou's avatar
Erwan Tulou committed
1085 1086
        if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
            outline = NULL;
1087
    }
1088

1089
    FT_Glyph shadow = NULL;
1090
    if( p_filter->p_sys->style.i_shadow_alpha > 0 )
1091 1092
    {
        shadow = outline ? outline : glyph;
Erwan Tulou's avatar
Erwan Tulou committed
1093 1094 1095 1096 1097 1098 1099 1100
        if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0  ) )
        {
            shadow = NULL;
        }
        else
        {
            FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
        }
1101
    }
1102
    *pp_shadow = shadow;
1103

1104
    if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
1105 1106
    {
        FT_Done_Glyph( glyph );
1107 1108
        if( outline )
            FT_Done_Glyph( outline );
1109 1110
        if( shadow )
            FT_Done_Glyph( shadow );
1111 1112
        return VLC_EGENERIC;
    }
1113
    FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
1114
    *pp_glyph = glyph;
1115

1116 1117 1118 1119 1120 1121 1122
    if( outline )
    {
        FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
        FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
    }
    *pp_outline = outline;

1123 1124 1125
    return VLC_SUCCESS;
}

1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142
static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
{
    FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
    if( p_bbox->xMin >= p_bbox->xMax )
    {
        p_bbox->xMin = FT_CEIL(p_pen->x);
        p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
        glyph_bmp->left = p_bbox->xMin;
    }
    if( p_bbox->yMin >= p_bbox->yMax )
    {
        p_bbox->yMax = FT_CEIL(p_pen->y);
        p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
        glyph_bmp->top  = p_bbox->yMax;
    }
}

1143 1144 1145 1146 1147 1148 1149 1150
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);
}

1151 1152
static int ProcessLines( filter_t *p_filter,
                         line_desc_t **pp_lines,
1153 1154
                         FT_BBox     *p_bbox,
                         int         *pi_max_face_height,
Bernie Purcell's avatar
Bernie Purcell committed
1155

1156
                         uni_char_t *psz_text,
1157 1158 1159
                         text_style_t **pp_styles,
                         uint32_t *pi_k_dates,
                         int i_len )
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
1160 1161
{
    filter_sys_t   *p_sys = p_filter->p_sys;
1162
    uni_char_t     *p_fribidi_string = NULL;
1163
    text_style_t   **pp_fribidi_styles = NULL;
1164
    int            *p_new_positions = NULL;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
1165 1166 1167

#if defined(HAVE_FRIBIDI)
    {
1168
        int    *p_old_positions;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
1169
        int start_pos, pos = 0;
1170

1171
        pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
1172

1173 1174 1175
        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) );
1176

1177
        if( ! pp_fribidi_styles ||
1178 1179
            ! p_fribidi_string ||
            ! p_old_positions ||
1180
            ! p_new_positions )
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
1181
        {
1182 1183 1184
            free( p_old_positions );
            free( p_new_positions );
            free( p_fribidi_string );
1185
            free( pp_fribidi_styles );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
1186 1187 1188 1189 1190 1191 1192 1193 1194 1195
            return VLC_ENOMEM;
        }

        /* Do bidi conversion line-by-line */
        while(pos < i_len)
        {
            while(pos < i_len) {
                if (psz_text[pos] != '\n')
                    break;
                p_fribidi_string[pos] = psz_text[pos];
1196
                pp_fribidi_styles[pos] = pp_styles[pos];
1197
                p_new_positions[pos] = pos;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
1198 1199 1200 1201 1202 1203 1204 1205 1206 1207
                ++pos;
            }
            start_pos = pos;
            while(pos < i_len) {
                if (psz_text[pos] == '\n')
                    break;
                ++pos;
            }
            if (pos > start_pos)
            {
1208 1209 1210
#if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
                FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
#else
1211
                FriBidiParType base_dir = FRIBIDI_PAR_LTR;
1212
#endif
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
1213 1214 1215
                fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
                        pos - start_pos, &base_dir,
                        (FriBidiChar*)p_fribidi_string + start_pos,
1216 1217
                        p_new_positions + start_pos,
                        p_old_positions,
1218
                        NULL );
1219
                for( int j = start_pos; j < pos; j++ )
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
1220
                {
1221
                    pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
1222
                    p_new_positions[ j ] += start_pos;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
1223 1224 1225
                }
            }
        }
1226
        p_fribidi_string[ i_len ] = 0;