freetype.c 85 KB
Newer Older
1
2
3
/*****************************************************************************
 * freetype.c : Put text on the video, using freetype2
 *****************************************************************************
4
 * Copyright (C) 2002 - 2007 the VideoLAN team
5
 * $Id$
6
 *
7
 * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8
 *          Gildas Bazin <gbazin@videolan.org>
9
 *          Bernie Purcell <bitmap@videolan.org>
10
11
12
13
14
15
16
17
18
19
20
21
22
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (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
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
dionoea's avatar
dionoea committed
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24
25
26
27
28
29
 *****************************************************************************/

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

30
31
32
33
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

34
#include <vlc_common.h>
35
#include <vlc_plugin.h>
zorglub's avatar
zorglub committed
36
37
#include <vlc_osd.h>
#include <vlc_filter.h>
38
39
#include <vlc_stream.h>
#include <vlc_xml.h>
40
#include <vlc_input.h>
41
#include <vlc_strings.h>
42
#include <vlc_dialog.h>
43

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
44
45
46
#include <math.h>
#include <errno.h>

47
48
49
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
50
51
#define FT_FLOOR(X)     ((X & -64) >> 6)
#define FT_CEIL(X)      (((X + 63) & -64) >> 6)
52
53
54
#ifndef FT_MulFix
 #define FT_MulFix(v, s) (((v)*(s))>>16)
#endif
55

56
#ifdef __APPLE__
57
#define DEFAULT_FONT "/System/Library/Fonts/LucidaGrande.dfont"
58
#define FC_DEFAULT_FONT "Lucida Grande"
59
60
#elif defined( SYS_BEOS )
#define DEFAULT_FONT "/boot/beos/etc/fonts/ttfonts/Swiss721.ttf"
61
#define FC_DEFAULT_FONT "Swiss"
62
63
#elif defined( WIN32 )
#define DEFAULT_FONT "" /* Default font found at run-time */
64
#define FC_DEFAULT_FONT "Arial"
65
#else
66
#define DEFAULT_FONT "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
67
#define FC_DEFAULT_FONT "Serif Bold"
68
69
#endif

70
71
72
73
#if defined(HAVE_FRIBIDI)
#include <fribidi/fribidi.h>
#endif

74
75
#ifdef HAVE_FONTCONFIG
#include <fontconfig/fontconfig.h>
76
77
#undef DEFAULT_FONT
#define DEFAULT_FONT FC_DEFAULT_FONT
78
79
#endif

80
81
#include <assert.h>

82
/*****************************************************************************
83
 * Module descriptor
84
 *****************************************************************************/
85
86
87
static int  Create ( vlc_object_t * );
static void Destroy( vlc_object_t * );

88
#define FONT_TEXT N_("Font")
89
90
91
92
93
94
95

#ifdef HAVE_FONTCONFIG
#define FONT_LONGTEXT N_("Font family for the font you want to use")
#else
#define FONT_LONGTEXT N_("Fontfile for the font you want to use")
#endif

96
#define FONTSIZE_TEXT N_("Font size in pixels")
97
98
#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
99
    "If set to something different than 0 this option will override the " \
100
    "relative font size." )
101
102
103
104
105
106
107
108
109
110
111
112
113
#define OPACITY_TEXT N_("Opacity")
#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, "\
    "relative size will be overriden." )
114

115
116
117
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") };
118
119
120
#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" )
121
#define EFFECT_TEXT N_("Font Effect")
122
#define EFFECT_LONGTEXT N_("It is possible to apply effects to the rendered " \
123
"text to improve its readability." )
124

125
#define EFFECT_BACKGROUND  1
126
127
128
#define EFFECT_OUTLINE     2
#define EFFECT_OUTLINE_FAT 3

129
130
131
132
static int const pi_effects[] = { 1, 2, 3 };
static const char *const ppsz_effects_text[] = {
    N_("Background"),N_("Outline"), N_("Fat Outline") };
static const int pi_color_values[] = {
133
  0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
134
135
  0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
  0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
136

137
static const char *const ppsz_color_descriptions[] = {
138
139
140
  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") };
141

142
143
144
145
146
vlc_module_begin ()
    set_shortname( N_("Text renderer"))
    set_description( N_("Freetype2 font renderer") )
    set_category( CAT_VIDEO )
    set_subcategory( SUBCAT_VIDEO_SUBPIC )
gbazin's avatar
   
gbazin committed
147

148
    add_font( "freetype-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
149
              false )
150

gbazin's avatar
   
gbazin committed
151
    add_integer( "freetype-fontsize", 0, NULL, FONTSIZE_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
152
                 FONTSIZE_LONGTEXT, true )
153

154
    /* opacity valid on 0..255, with default 255 = fully opaque */
155
    add_integer_with_range( "freetype-opacity", 255, 0, 255, NULL,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
156
        OPACITY_TEXT, OPACITY_LONGTEXT, true )
157

158
    /* hook to the color values list, with default 0x00ffffff = white */
159
    add_integer( "freetype-color", 0x00FFFFFF, NULL, COLOR_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
160
                 COLOR_LONGTEXT, false )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
161
        change_integer_list( pi_color_values, ppsz_color_descriptions, NULL )
162

gbazin's avatar
   
gbazin committed
163
    add_integer( "freetype-rel-fontsize", 16, NULL, FONTSIZER_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
164
                 FONTSIZER_LONGTEXT, false )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
165
        change_integer_list( pi_sizes, ppsz_sizes_text, NULL )
166
    add_integer( "freetype-effect", 2, NULL, EFFECT_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
167
                 EFFECT_LONGTEXT, false )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
168
        change_integer_list( pi_effects, ppsz_effects_text, NULL )
gbazin's avatar
   
gbazin committed
169

170
    add_bool( "freetype-yuvp", 0, NULL, YUVP_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
171
              YUVP_LONGTEXT, true )
172
173
174
175
    set_capability( "text renderer", 100 )
    add_shortcut( "text" )
    set_callbacks( Create, Destroy )
vlc_module_end ()
176

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201


/*****************************************************************************
 * 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_FONTCONFIG
static int RenderHtml( filter_t *, subpicture_region_t *,
                       subpicture_region_t * );
static char *FontConfig_Select( FcConfig *, const char *,
                                bool, bool, 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 line_desc_t line_desc_t;
sigmunau's avatar
sigmunau committed
202
203
struct line_desc_t
{
204
    /** NULL-terminated list of glyphs making the string */
sigmunau's avatar
sigmunau committed
205
    FT_BitmapGlyph *pp_glyphs;
206
    /** list of relative positions for the glyphs */
sigmunau's avatar
sigmunau committed
207
    FT_Vector      *p_glyph_pos;
208
209
210
211
    /** 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. */
212
213
214
    uint32_t       *p_fg_rgb;
    uint32_t       *p_bg_rgb;
    uint8_t        *p_fg_bg_ratio; /* 0x00=100% FG --> 0x7F=100% BG */
215
    bool      b_new_color_mode;
216
217
218
    /** underline information -- only supplied if text should be underlined */
    uint16_t       *pi_underline_offset;
    uint16_t       *pi_underline_thickness;
219

sigmunau's avatar
sigmunau committed
220
221
    int             i_height;
    int             i_width;
222
223
224
    int             i_red, i_green, i_blue;
    int             i_alpha;

sigmunau's avatar
sigmunau committed
225
    line_desc_t    *p_next;
226
};
227
static line_desc_t *NewLine( int );
228

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
229
230
231
typedef struct
{
    int         i_font_size;
232
233
    uint32_t    i_font_color;         /* ARGB */
    uint32_t    i_karaoke_bg_color;   /* ARGB */
234
235
236
    bool  b_italic;
    bool  b_bold;
    bool  b_underline;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
237
238
239
    char       *psz_fontname;
} ft_style_t;

240
241
static int Render( filter_t *, subpicture_region_t *, line_desc_t *, int, int);
static void FreeLines( line_desc_t * );
242
static void FreeLine( line_desc_t * );
243

244
/*****************************************************************************
245
 * filter_sys_t: freetype local data
246
247
 *****************************************************************************
 * This structure is part of the video output thread descriptor.
248
 * It describes the freetype specific properties of an output thread.
249
 *****************************************************************************/
250
struct filter_sys_t
251
252
253
{
    FT_Library     p_library;   /* handle to library     */
    FT_Face        p_face;      /* handle to face object */
254
    bool     i_use_kerning;
255
256
    uint8_t        i_font_opacity;
    int            i_font_color;
257
    int            i_font_size;
258
    int            i_effect;
259
260
261

    int            i_default_font_size;
    int            i_display_height;
262
#ifdef HAVE_FONTCONFIG
263
    char*          psz_fontfamily;
264
    xml_t         *p_xml;
265
#endif
266
267
268

    input_attachment_t **pp_font_attachments;
    int                  i_font_attachments;
269

270
271
};

272
#define UCHAR uint32_t
273
#define TR_DEFAULT_FONT p_sys->psz_fontfamily
274
275
276
277
#define TR_FONT_STYLE_PTR ft_style_t *

#include "text_renderer.h"

278
279
280
281
282
283
284
/*****************************************************************************
 * 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 )
{
285
286
    filter_t      *p_filter = (filter_t *)p_this;
    filter_sys_t  *p_sys;
287
288
289
290
291
    char          *psz_fontfile=NULL;
    char          *psz_fontfamily=NULL;
    int            i_error,fontindex;

#ifdef HAVE_FONTCONFIG
292
    FcPattern     *fontpattern = NULL, *fontmatch = NULL;
293
294
295
    /* Initialise result to Match, as fontconfig doesnt
     * really set this other than some error-cases */
    FcResult       fontresult = FcResultMatch;
296
297
#endif

298

299
    /* Allocate structure */
300
    p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
301
    if( !p_sys )
302
        return VLC_ENOMEM;
303
304
 #ifdef HAVE_FONTCONFIG
    p_sys->psz_fontfamily = NULL;
305
    p_sys->p_xml = NULL;
306
#endif
307
308
    p_sys->p_face = 0;
    p_sys->p_library = 0;
309
    p_sys->i_font_size = 0;
310
    p_sys->i_display_height = 0;
311

312
    var_Create( p_filter, "freetype-rel-fontsize",
313
                VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
314
315
316
317
318
319
320
321

    psz_fontfamily = var_CreateGetString( p_filter, "freetype-font" );
    p_sys->i_default_font_size = var_CreateGetInteger( p_filter, "freetype-fontsize" );
    p_sys->i_effect = var_CreateGetInteger( p_filter, "freetype-effect" );
    p_sys->i_font_opacity = var_CreateGetInteger( p_filter,"freetype-opacity" );
    p_sys->i_font_opacity = __MAX( __MIN( p_sys->i_font_opacity, 255 ), 0 );
    p_sys->i_font_color = var_CreateGetInteger( p_filter, "freetype-color" );
    p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
322

323
324
    fontindex=0;
    if( !psz_fontfamily || !*psz_fontfamily )
gbazin's avatar
   
gbazin committed
325
    {
326
327
328
#ifdef HAVE_FONTCONFIG
        free( psz_fontfamily);
        psz_fontfamily=strdup( DEFAULT_FONT );
gbazin's avatar
   
gbazin committed
329
#else
330
331
332
333
334
335
336
337
338
339
340
        free( psz_fontfamily );
        psz_fontfamily = (char *)malloc( PATH_MAX + 1 );
        if( !psz_fontfamily )
            goto error;
# ifdef WIN32
        GetWindowsDirectory( psz_fontfamily , PATH_MAX + 1 );
        strcat( psz_fontfamily, "\\fonts\\arial.ttf" );
# else
        strcpy( psz_fontfamily, DEFAULT_FONT );
# endif
        msg_Err( p_filter,"User didn't specify fontfile, using %s", psz_fontfamily);
gbazin's avatar
   
gbazin committed
341
#endif
342
    }
gbazin's avatar
   
gbazin committed
343

344
345
#ifdef HAVE_FONTCONFIG
    /* Lets find some fontfile from freetype-font variable family */
346
347
    char *psz_fontsize;
    if( asprintf( &psz_fontsize, "%d", p_sys->i_default_font_size ) == -1 )
348
        goto error;
349

350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
#ifdef WIN32
    dialog_progress_bar_t *p_dialog = dialog_ProgressCreate( p_filter,
            _("Building font cache"),
            _("Please wait while your font cache is rebuilt.\n"
                "This should take less than few minutes."), NULL );
    char *path;
    path = (char *)malloc( PATH_MAX + 1 );
    /* Fontconfig doesnt seem to know where windows fonts are with
     * current contribs. So just tell default windows font directory
     * is the place to search fonts
     */
    GetWindowsDirectory( path, PATH_MAX + 1 );
    strcat( path, "\\fonts" );
    if( p_dialog )
        dialog_ProgressSet( p_dialog, NULL, 0.4 );

    FcConfigAppFontAddDir( NULL , path );
    free(path);


    if( p_dialog )
        dialog_ProgressSet( p_dialog, NULL, 0.5 );
#endif
    mtime_t t1, t2;

    msg_Dbg( p_filter, "Building font database.");
    t1 = mdate();
    FcConfigBuildFonts( NULL );
    t2 = mdate();

    msg_Dbg( p_filter, "Finished building font database." );
    msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );

383
    fontpattern = FcPatternCreate();
384
385

    if( !fontpattern )
Ilkka Ollakka's avatar
Ilkka Ollakka committed
386
387
    {
        msg_Err( p_filter, "Creating fontpattern failed");
388
        goto error;
Ilkka Ollakka's avatar
Ilkka Ollakka committed
389
    }
390

391
392
393
394
#ifdef WIN32
    if( p_dialog )
        dialog_ProgressSet( p_dialog, NULL, 0.7 );
#endif
395
396
    FcPatternAddString( fontpattern, FC_FAMILY, psz_fontfamily);
    FcPatternAddString( fontpattern, FC_SIZE, psz_fontsize );
397
    free( psz_fontsize );
398
399

    if( FcConfigSubstitute( NULL, fontpattern, FcMatchPattern ) == FcFalse )
Ilkka Ollakka's avatar
Ilkka Ollakka committed
400
401
    {
        msg_Err( p_filter, "FontSubstitute failed");
402
        goto error;
Ilkka Ollakka's avatar
Ilkka Ollakka committed
403
    }
404
405
    FcDefaultSubstitute( fontpattern );

406
407
408
409
#ifdef WIN32
    if( p_dialog )
        dialog_ProgressSet( p_dialog, NULL, 0.8 );
#endif
410
411
412
    /* testing fontresult here doesn't do any good really, but maybe it will
     * in future as fontconfig code doesn't set it in all cases and just
     * returns NULL or doesn't set to to Match on all Match cases.*/
413
    fontmatch = FcFontMatch( NULL, fontpattern, &fontresult );
414
    if( !fontmatch || fontresult == FcResultNoMatch )
Ilkka Ollakka's avatar
Ilkka Ollakka committed
415
416
    {
        msg_Err( p_filter, "Fontmatching failed");
417
        goto error;
Ilkka Ollakka's avatar
Ilkka Ollakka committed
418
    }
419
420
421
422

    FcPatternGetString( fontmatch, FC_FILE, 0, (FcChar8 **)&psz_fontfile);
    FcPatternGetInteger( fontmatch, FC_INDEX, 0, &fontindex );
    if( !psz_fontfile )
Ilkka Ollakka's avatar
Ilkka Ollakka committed
423
424
    {
        msg_Err( p_filter, "Failed to get fontfile");
425
        goto error;
Ilkka Ollakka's avatar
Ilkka Ollakka committed
426
    }
427

428
    msg_Dbg( p_filter, "Using %s as font from file %s", psz_fontfamily, psz_fontfile );
429
    p_sys->psz_fontfamily = strdup( psz_fontfamily );
430
431
432
433
434
435
436
437
# ifdef WIN32
    if( p_dialog )
    {
        dialog_ProgressSet( p_dialog, NULL, 1.0 );
        dialog_ProgressDestroy( p_dialog );
    }
# endif

438
#else
439
    p_sys->psz_fontfamily = strdup( DEFAULT_FONT )
440
441
442
    psz_fontfile = psz_fontfamily;
#endif

443
444
445
446
447
448
    i_error = FT_Init_FreeType( &p_sys->p_library );
    if( i_error )
    {
        msg_Err( p_filter, "couldn't initialize freetype" );
        goto error;
    }
449

450
    i_error = FT_New_Face( p_sys->p_library, psz_fontfile ? psz_fontfile : "",
451
452
                           fontindex, &p_sys->p_face );

453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
    if( i_error == FT_Err_Unknown_File_Format )
    {
        msg_Err( p_filter, "file %s have unknown format", psz_fontfile );
        goto error;
    }
    else if( i_error )
    {
        msg_Err( p_filter, "failed to load font file %s", psz_fontfile );
        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;
    }

471
    p_sys->i_use_kerning = FT_HAS_KERNING( p_sys->p_face );
472

473
    if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
474

475
476
477
478

    p_sys->pp_font_attachments = NULL;
    p_sys->i_font_attachments = 0;

479
    p_filter->pf_render_text = RenderText;
480
#ifdef HAVE_FONTCONFIG
481
    p_filter->pf_render_html = RenderHtml;
482
483
    FcPatternDestroy( fontmatch );
    FcPatternDestroy( fontpattern );
484
485
#else
    p_filter->pf_render_html = NULL;
486
#endif
487

488
    free( psz_fontfamily );
489
490
    LoadFontsFromAttachments( p_filter );

491
    return VLC_SUCCESS;
492

493
494
495
496
497
error:
#ifdef HAVE_FONTCONFIG
    if( fontmatch ) FcPatternDestroy( fontmatch );
    if( fontpattern ) FcPatternDestroy( fontpattern );
#endif
498
499
    if( p_sys->p_face ) FT_Done_Face( p_sys->p_face );
    if( p_sys->p_library ) FT_Done_FreeType( p_sys->p_library );
ivoire's avatar
ivoire committed
500
    free( psz_fontfamily );
501
502
    free( p_sys );
    return VLC_EGENERIC;
503
504
505
506
507
508
509
510
}

/*****************************************************************************
 * Destroy: destroy Clone video thread output method
 *****************************************************************************
 * Clean up all data and library connections
 *****************************************************************************/
static void Destroy( vlc_object_t *p_this )
511
{
512
513
    filter_t *p_filter = (filter_t *)p_this;
    filter_sys_t *p_sys = p_filter->p_sys;
514

515
516
517
518
519
520
521
522
523
524
    if( p_sys->pp_font_attachments )
    {
        int   k;

        for( 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 );
    }

525
#ifdef HAVE_FONTCONFIG
526
    if( p_sys->p_xml ) xml_Delete( p_sys->p_xml );
527
    free( p_sys->psz_fontfamily );
528
#endif
529

530
531
532
    /* FcFini asserts calling the subfunction FcCacheFini()
     * even if no other library functions have been made since FcInit(),
     * so don't call it. */
533

534
535
536
    FT_Done_Face( p_sys->p_face );
    FT_Done_FreeType( p_sys->p_library );
    free( p_sys );
537
538
}

539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
/*****************************************************************************
 * 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_thread_t       *p_input;
    input_attachment_t  **pp_attachments;
    int                   i_attachments_cnt;
    int                   k;
    int                   rv = VLC_SUCCESS;

    p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
    if( ! p_input )
        return VLC_EGENERIC;
bitmap's avatar
bitmap committed
555

556
    if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
557
558
    {
        vlc_object_release(p_input);
559
        return VLC_EGENERIC;
560
    }
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589

    p_sys->i_font_attachments = 0;
    p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof( input_attachment_t * ));
    if(! p_sys->pp_font_attachments )
        rv = VLC_ENOMEM;

    for( k = 0; k < i_attachments_cnt; k++ )
    {
        input_attachment_t *p_attach = pp_attachments[k];

        if( p_sys->pp_font_attachments )
        {
            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 != NULL ) )
            {
                p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
            }
            else
            {
                vlc_input_attachment_Delete( p_attach );
            }
        }
        else
        {
            vlc_input_attachment_Delete( p_attach );
        }
    }
bitmap's avatar
bitmap committed
590
    free( pp_attachments );
591

592
593
    vlc_object_release(p_input);

594
595
596
    return rv;
}

597
598
599
600
601
/*****************************************************************************
 * Render: place string in picture
 *****************************************************************************
 * This function merges the previously rendered freetype glyphs into a picture
 *****************************************************************************/
602
603
static int Render( filter_t *p_filter, subpicture_region_t *p_region,
                   line_desc_t *p_line, int i_width, int i_height )
604
{
Pierre's avatar
Pierre committed
605
    VLC_UNUSED(p_filter);
606
    static const uint8_t pi_gamma[16] =
607
608
        {0x00, 0x52, 0x84, 0x96, 0xb8, 0xca, 0xdc, 0xee, 0xff,
          0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
609

610
    uint8_t *p_dst;
611
612
    video_format_t fmt;
    int i, x, y, i_pitch;
613
614
    uint8_t i_y; /* YUV values, derived from incoming RGB */
    int8_t i_u, i_v;
615
616
617

    /* Create a new subpicture region */
    memset( &fmt, 0, sizeof(video_format_t) );
618
    fmt.i_chroma = VLC_CODEC_YUVP;
619
    fmt.i_aspect = 0;
620
621
    fmt.i_width = fmt.i_visible_width = i_width + 4;
    fmt.i_height = fmt.i_visible_height = i_height + 4;
622
623
624
625
    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;
626
    fmt.i_x_offset = fmt.i_y_offset = 0;
627

628
629
630
631
632
    assert( !p_region->p_picture );
    p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect );
    if( !p_region->p_picture )
        return VLC_EGENERIC;
    p_region->fmt = fmt;
633
634
635
636
637
638
639
640
641
642

    /* 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;

    /* Build palette */
643
    fmt.p_palette->i_entries = 16;
644
645
646
647
648
649
650
651
652
653
    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] * (255 - p_line->i_alpha) / 255;
    }
    for( i = 8; i < fmt.p_palette->i_entries; i++ )
654
    {
655
        fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
656
657
        fmt.p_palette->palette[i][1] = i_u;
        fmt.p_palette->palette[i][2] = i_v;
658
        fmt.p_palette->palette[i][3] = pi_gamma[i];
659
        fmt.p_palette->palette[i][3] =
660
            (int)fmt.p_palette->palette[i][3] * (255 - p_line->i_alpha) / 255;
661
662
    }

663
664
    p_dst = p_region->p_picture->Y_PIXELS;
    i_pitch = p_region->p_picture->Y_PITCH;
665

666
    /* Initialize the region pixels */
667
    memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
668

669
    for( ; p_line != NULL; p_line = p_line->p_next )
670
671
    {
        int i_glyph_tmax = 0;
672
        int i_bitmap_offset, i_offset, i_align_offset = 0;
sigmunau's avatar
sigmunau committed
673
674
675
        for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
        {
            FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
676
            i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
sigmunau's avatar
sigmunau committed
677
        }
678

679
        if( p_line->i_width < i_width )
680
        {
681
            if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
682
683
684
            {
                i_align_offset = i_width - p_line->i_width;
            }
685
            else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
686
687
688
689
            {
                i_align_offset = ( i_width - p_line->i_width ) / 2;
            }
        }
690

sigmunau's avatar
sigmunau committed
691
692
693
        for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
        {
            FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
694
695

            i_offset = ( p_line->p_glyph_pos[ i ].y +
696
697
                i_glyph_tmax - p_glyph->top + 2 ) *
                i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 2 +
698
                i_align_offset;
699
700

            for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
sigmunau's avatar
sigmunau committed
701
            {
702
                for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
sigmunau's avatar
sigmunau committed
703
                {
704
705
706
                    if( p_glyph->bitmap.buffer[i_bitmap_offset] )
                        p_dst[i_offset+x] =
                         ((int)p_glyph->bitmap.buffer[i_bitmap_offset] + 8)/16;
sigmunau's avatar
sigmunau committed
707
                }
708
                i_offset += i_pitch;
sigmunau's avatar
sigmunau committed
709
            }
710
711
        }
    }
712

713
714
715
    /* Outlining (find something better than nearest neighbour filtering ?) */
    if( 1 )
    {
716
        uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
717
718
        uint8_t *p_top = p_dst; /* Use 1st line as a cache */
        uint8_t left, current;
719

720
721
        for( y = 1; y < (int)fmt.i_height - 1; y++ )
        {
722
            if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
723
            p_dst += p_region->p_picture->Y_PITCH;
724
725
726
            left = 0;

            for( x = 1; x < (int)fmt.i_width - 1; x++ )
sigmunau's avatar
sigmunau committed
727
            {
728
                current = p_dst[x];
729
                p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
730
                             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;
731
                left = current;
sigmunau's avatar
sigmunau committed
732
733
            }
        }
734
        memset( p_top, 0, fmt.i_width );
sigmunau's avatar
sigmunau committed
735
    }
736
737

    return VLC_SUCCESS;
sigmunau's avatar
sigmunau committed
738
}
739

740
static void UnderlineGlyphYUVA( int i_line_thickness, int i_line_offset, bool b_ul_next_char,
741
742
743
                                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,
Rafaël Carré's avatar
Rafaël Carré committed
744
                                uint8_t i_y, uint8_t i_u, uint8_t i_v,
745
746
747
748
749
750
                                subpicture_region_t *p_region)
{
    int y, x, z;
    int i_pitch;
    uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;

751
752
753
754
755
    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;
756
757
758
759
760
761
762

    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( y = 0; y < i_line_thickness; y++ )
    {
        int i_extra = p_this_glyph->bitmap.width;
763

764
765
766
767
768
769
770
        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( x = 0; x < i_extra; x++ )
        {
771
            bool b_ok = true;
772
773
774
775
776
777
778
779
780
781
782
783
784

            /* break the underline around the tails of any glyphs which cross it */
            for( z = x - i_line_thickness;
                 z < x + i_line_thickness && b_ok;
                 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] )
                    {
785
                        b_ok = false;
786
787
788
789
790
791
792
793
794
                    }
                }
                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] )
                    {
795
                        b_ok = false;
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
                    }
                }
            }

            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;
            }
        }
        i_offset += i_pitch;
    }
}

812
813
static void DrawBlack( line_desc_t *p_line, int i_width, subpicture_region_t *p_region, int xoffset, int yoffset )
{
814
815
    uint8_t *p_dst = p_region->p_picture->A_PIXELS;
    int i_pitch = p_region->p_picture->A_PITCH;
816
817
818
819
820
821
822
823
824
825
826
827
    int x,y;

    for( ; p_line != NULL; p_line = p_line->p_next )
    {
        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 );
        }

828
        if( p_line->i_width < i_width )
829
        {
830
            if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
831
832
833
            {
                i_align_offset = i_width - p_line->i_width;
            }
834
            else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
            {
                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 ];

            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;

            for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
            {
                for( 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;
            }
        }
    }
863

864
865
866
867
868
869
870
871
872
873
874
875
}

/*****************************************************************************
 * 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 )
{
    uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
    video_format_t fmt;
876
877
    int i, x, y, i_pitch, i_alpha;
    uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
878

879
880
881
    if( i_width == 0 || i_height == 0 )
        return VLC_SUCCESS;

882
883
    /* Create a new subpicture region */
    memset( &fmt, 0, sizeof(video_format_t) );
884
    fmt.i_chroma = VLC_CODEC_YUVA;
885
886
887
    fmt.i_aspect = 0;
    fmt.i_width = fmt.i_visible_width = i_width + 6;
    fmt.i_height = fmt.i_visible_height = i_height + 6;
888
889
890
891
    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;
892
893
    fmt.i_x_offset = fmt.i_y_offset = 0;

894
895
896
897
    p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect );
    if( !p_region->p_picture )
        return VLC_EGENERIC;
    p_region->fmt = fmt;
898
899

    /* Calculate text color components */
900
901
902
903
    YUVFromRGB( (p_line->i_red   << 16) |
                (p_line->i_green <<  8) |
                (p_line->i_blue       ),
                &i_y, &i_u, &i_v);
904
    i_alpha = p_line->i_alpha;
905

906
907
908
909
910
    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;
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935

    /* Initialize the region pixels */
    if( p_filter->p_sys->i_effect != EFFECT_BACKGROUND )
    {
        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);
    }
936

937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
    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 );
        }

970
        if( p_line->i_width < i_width )
971
        {
972
            if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
973
974
975
            {
                i_align_offset = i_width - p_line->i_width;
            }
976
            else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
977
978
979
980
981
982
983
984
985
986
987
988
989
990
            {
                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 ];

            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;

991
992
993
            if( p_line->b_new_color_mode )
            {
                /* Every glyph can (and in fact must) have its own color */
994
                YUVFromRGB( p_line->p_fg_rgb[ i ], &i_y, &i_u, &i_v );
995
996
            }

997
998
999
1000
            for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
            {
                for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
                {
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
                    uint8_t i_y_local = i_y;
                    uint8_t i_u_local = i_u;
                    uint8_t i_v_local = i_v;

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

1017
1018
                    if( p_glyph->bitmap.buffer[i_bitmap_offset] )
                    {
1019
1020
                        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;
1021

1022
1023
                        p_dst_u[i_offset+x] = i_u;
                        p_dst_v[i_offset+x] = i_v;
1024

1025
1026
1027
1028
1029
1030
                        if( p_filter->p_sys->i_effect == EFFECT_BACKGROUND )
                            p_dst_a[i_offset+x] = 0xff;
                    }
                }
                i_offset += i_pitch;
            }
1031
1032
1033
1034
1035
1036
1037
1038
1039

            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,
Rafaël Carré's avatar
Rafaël Carré committed
1040
                                    i_y, i_u, i_v,
1041
1042
                                    p_region);
            }