freetype.c 91.5 KB
Newer Older
1
2
3
/*****************************************************************************
 * freetype.c : Put text on the video, using freetype2
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL    
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
LGPL    
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
LGPL    
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
LGPL    
Jean-Baptiste Kempf committed
23
 * You should have received a copy of the GNU Lesser General Public License
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
24
 * along with this program; if not, write to the Free Software Foundation, Inc.,
Jean-Baptiste Kempf's avatar
LGPL    
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>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
39
40
41
42
43
#include <vlc_stream.h>                        /* stream_MemoryNew */
#include <vlc_input.h>                         /* vlc_input_attachment_* */
#include <vlc_xml.h>                           /* xml_reader */
#include <vlc_strings.h>                       /* resolve_xml_special_chars */
#include <vlc_dialog.h>                        /* FcCache dialog */
44
45
#include <vlc_filter.h>                                      /* filter_sys_t */
#include <vlc_text_style.h>                                   /* text_style_t*/
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
46
47
48

/* Default fonts */
#ifdef __APPLE__
49
50
51
52
# define SYSTEM_DEFAULT_FONT_FILE "/Library/Fonts/Arial Unicode.ttf"
# define SYSTEM_DEFAULT_FAMILY "Arial Unicode MS"
# define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/System/Library/Fonts/Monaco.dfont"
# define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monaco"
53
#elif defined( _WIN32 )
54
55
56
57
# define SYSTEM_DEFAULT_FONT_FILE "arial.ttf" /* Default path font found at run-time */
# define SYSTEM_DEFAULT_FAMILY "Arial"
# define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "cour.ttf"
# define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Courier New"
58
#elif defined( __OS2__ )
59
60
61
62
# define SYSTEM_DEFAULT_FONT_FILE "/psfonts/tnrwt_k.ttf"
# define SYSTEM_DEFAULT_FAMILY "Times New Roman WT K"
# define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/psfonts/mtsansdk.ttf"
# define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monotype Sans Duospace WT K"
63
#elif defined( __ANDROID__ )
64
65
66
67
# define SYSTEM_DEFAULT_FONT_FILE "/system/fonts/DroidSans-Bold.ttf"
# define SYSTEM_DEFAULT_FAMILY "Droid Sans Bold"
# define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/system/fonts/DroidSansMono.ttf"
# define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Droid Sans Mono"
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
68
#else
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# define SYSTEM_DEFAULT_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf"
# define SYSTEM_DEFAULT_FAMILY "Serif Bold"
# define SYSTEM_DEFAULT_MONOSPACE_FONT_FILE "/usr/share/fonts/truetype/freefont/FreeMono.ttf"
# define SYSTEM_DEFAULT_MONOSPACE_FAMILY "Monospace"
#endif

#ifndef DEFAULT_FONT_FILE
#define DEFAULT_FONT_FILE SYSTEM_DEFAULT_FONT_FILE
#endif

#ifndef DEFAULT_FAMILY
#define DEFAULT_FAMILY SYSTEM_DEFAULT_FAMILY
#endif

#ifndef DEFAULT_MONOSPACE_FONT_FILE
#define DEFAULT_MONOSPACE_FONT_FILE SYSTEM_DEFAULT_MONOSPACE_FONT_FILE
#endif

#ifndef DEFAULT_MONOSPACE_FAMILY
#define DEFAULT_MONOSPACE_FAMILY SYSTEM_DEFAULT_MONOSPACE_FAMILY
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
89
90
91
#endif

/* Freetype */
92
#include <freetype/ftsynth.h>
93
94
#include FT_FREETYPE_H
#include FT_GLYPH_H
95
96
#include FT_STROKER_H

97
98
#define FT_FLOOR(X)     ((X & -64) >> 6)
#define FT_CEIL(X)      (((X + 63) & -64) >> 6)
99
100
101
#ifndef FT_MulFix
 #define FT_MulFix(v, s) (((v)*(s))>>16)
#endif
102

103
104
/* apple stuff */
#ifdef __APPLE__
105
106
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE
107
#include <Carbon/Carbon.h>
108
#endif
109
#include <sys/param.h>                         /* for MAXPATHLEN */
110
111
112
113
#undef HAVE_FONTCONFIG
#define HAVE_STYLES
#endif

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
114
/* RTL */
115
#if defined(HAVE_FRIBIDI)
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
116
# include <fribidi/fribidi.h>
117
118
#endif

119
/* Win32 GDI */
120
#ifdef _WIN32
121
122
123
124
# include <windows.h>
# include <shlobj.h>
# define HAVE_STYLES
# undef HAVE_FONTCONFIG
125
# include <vlc_charset.h>                                     /* FromT */
126
127
#endif

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
128
/* FontConfig */
129
#ifdef HAVE_FONTCONFIG
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
130
# include <fontconfig/fontconfig.h>
131
132
133
# define HAVE_STYLES
#endif

134
135
#include <assert.h>

136
#include "text_renderer.h"
137

138
/*****************************************************************************
139
 * Module descriptor
140
 *****************************************************************************/
141
142
143
static int  Create ( vlc_object_t * );
static void Destroy( vlc_object_t * );

144
#define FONT_TEXT N_("Font")
145
#define MONOSPACE_FONT_TEXT N_("Monospace Font")
146

147
#define FAMILY_LONGTEXT N_("Font family for the font you want to use")
Christophe Mutricy's avatar
Typos    
Christophe Mutricy committed
148
#define FONT_LONGTEXT N_("Font file for the font you want to use")
149

150
#define FONTSIZE_TEXT N_("Font size in pixels")
151
152
#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
153
    "If set to something different than 0 this option will override the " \
154
    "relative font size." )
155
#define OPACITY_TEXT N_("Text opacity")
156
157
158
159
160
161
162
163
164
165
166
#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
Typos    
Christophe Mutricy committed
167
    "relative size will be overridden." )
168
#define BOLD_TEXT N_("Force bold")
169

170
171
172
#define BG_OPACITY_TEXT N_("Background opacity")
#define BG_COLOR_TEXT N_("Background color")

173
174
175
176
#define OUTLINE_OPACITY_TEXT N_("Outline opacity")
#define OUTLINE_COLOR_TEXT N_("Outline color")
#define OUTLINE_THICKNESS_TEXT N_("Outline thickness")

177
178
179
180
181
182
#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")


183
184
185
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") };
186
187
188
#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" )
189

190
static const int pi_color_values[] = {
191
  0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
192
193
  0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
  0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
194

195
static const char *const ppsz_color_descriptions[] = {
196
197
198
  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") };
199

200
201
202
203
204
205
206
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"),
};

207
208
209
210
211
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
212

213
214
#ifdef HAVE_STYLES
    add_font( "freetype-font", DEFAULT_FAMILY, FONT_TEXT, FAMILY_LONGTEXT, false )
215
    add_font( "freetype-monofont", DEFAULT_MONOSPACE_FAMILY, MONOSPACE_FONT_TEXT, FAMILY_LONGTEXT, false )
216
#else
217
    add_loadfile( "freetype-font", DEFAULT_FONT_FILE, FONT_TEXT, FONT_LONGTEXT, false )
218
    add_loadfile( "freetype-monofont", DEFAULT_MONOSPACE_FONT_FILE, MONOSPACE_FONT_TEXT, FONT_LONGTEXT, false )
219
#endif
220

221
    add_integer( "freetype-fontsize", 0, FONTSIZE_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
222
                 FONTSIZE_LONGTEXT, true )
VLC_help's avatar
VLC_help committed
223
        change_integer_range( 0, 4096)
224
        change_safe()
225

226
227
228
229
230
    add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
                 FONTSIZER_LONGTEXT, false )
        change_integer_list( pi_sizes, ppsz_sizes_text )
        change_safe()

231
    /* opacity valid on 0..255, with default 255 = fully opaque */
232
    add_integer_with_range( "freetype-opacity", 255, 0, 255,
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
233
        OPACITY_TEXT, OPACITY_LONGTEXT, false )
234
        change_safe()
235

236
    /* hook to the color values list, with default 0x00ffffff = white */
237
    add_rgb( "freetype-color", 0x00FFFFFF, COLOR_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
238
                 COLOR_LONGTEXT, false )
239
        change_integer_list( pi_color_values, ppsz_color_descriptions )
240
        change_safe()
241

242
    add_bool( "freetype-bold", false, BOLD_TEXT, NULL, false )
243
244
        change_safe()

245
    add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
246
                            BG_OPACITY_TEXT, NULL, false )
247
        change_safe()
248
    add_rgb( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
249
             NULL, false )
250
251
252
        change_integer_list( pi_color_values, ppsz_color_descriptions )
        change_safe()

253
    add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
254
                            OUTLINE_OPACITY_TEXT, NULL, false )
255
        change_safe()
256
    add_rgb( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
257
             NULL, false )
258
259
260
        change_integer_list( pi_color_values, ppsz_color_descriptions )
        change_safe()
    add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
261
             NULL, false )
262
263
264
        change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
        change_safe()

265
    add_integer_with_range( "freetype-shadow-opacity", 128, 0, 255,
266
                            SHADOW_OPACITY_TEXT, NULL, false )
267
        change_safe()
268
    add_rgb( "freetype-shadow-color", 0x00000000, SHADOW_COLOR_TEXT,
269
             NULL, false )
270
271
272
        change_integer_list( pi_color_values, ppsz_color_descriptions )
        change_safe()
    add_float_with_range( "freetype-shadow-angle", -45, -360, 360,
273
                          SHADOW_ANGLE_TEXT, NULL, false )
274
275
        change_safe()
    add_float_with_range( "freetype-shadow-distance", 0.06, 0.0, 1.0,
276
                          SHADOW_DISTANCE_TEXT, NULL, false )
277
278
        change_safe()

279
    add_obsolete_integer( "freetype-effect" );
gbazin's avatar
   
gbazin committed
280

281
    add_bool( "freetype-yuvp", false, YUVP_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
282
              YUVP_LONGTEXT, true )
283
284
285
286
    set_capability( "text renderer", 100 )
    add_shortcut( "text" )
    set_callbacks( Create, Destroy )
vlc_module_end ()
287

288
289
290
291
292

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

293
294
295
typedef struct
{
    FT_BitmapGlyph p_glyph;
296
    FT_BitmapGlyph p_outline;
297
    FT_BitmapGlyph p_shadow;
298
299
    uint32_t       i_color;             /* ARGB color */
    int            i_line_offset;       /* underline/strikethrough offset */
300
    int            i_line_thickness;    /* underline/strikethrough thickness */
301
302
} line_character_t;

303
typedef struct line_desc_t line_desc_t;
sigmunau's avatar
sigmunau committed
304
305
struct line_desc_t
{
306
    line_desc_t      *p_next;
307
308

    int              i_width;
309
    int              i_height;
310
    int              i_base_line;
311
312
    int              i_character_count;
    line_character_t *p_character;
313
314
315
};

/*****************************************************************************
316
 * filter_sys_t: freetype local data
317
318
 *****************************************************************************
 * This structure is part of the video output thread descriptor.
319
 * It describes the freetype specific properties of an output thread.
320
 *****************************************************************************/
321
struct filter_sys_t
322
323
324
{
    FT_Library     p_library;   /* handle to library     */
    FT_Face        p_face;      /* handle to face object */
325
    FT_Stroker     p_stroker;
326

327
    int            i_font_size;
328
    int            i_font_alpha;
329
    int            i_style_flags;
330

331
    int            i_outline_color;
332
    int            i_outline_alpha;
333

334
335
336
    float          f_shadow_vector_x;
    float          f_shadow_vector_y;
    int            i_shadow_color;
337
    int            i_shadow_alpha;
338

339
    int            i_default_font_size;
340

341
    char*          psz_fontname;
342
    char*          psz_monofontfamily;
343
#ifdef _WIN32
344
    char*          psz_win_fonts_path;
345
#endif
346

347
348
    xml_reader_t  *p_xml;

349
350
    input_attachment_t **pp_font_attachments;
    int                  i_font_attachments;
351
352
};

353
/* */
Laurent Aimar's avatar
Laurent Aimar committed
354
355
static void YUVFromRGB( uint32_t i_argb,
                    uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
356
{
Laurent Aimar's avatar
Laurent Aimar committed
357
358
359
    int i_red   = ( i_argb & 0x00ff0000 ) >> 16;
    int i_green = ( i_argb & 0x0000ff00 ) >>  8;
    int i_blue  = ( i_argb & 0x000000ff );
360

Laurent Aimar's avatar
Laurent Aimar committed
361
362
363
364
365
366
    *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);
367
}
368
369
370
371
372
373
374
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 );
}
375
376
377
378
379
380
381
382
383
384
/*****************************************************************************
 * 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;

385
    if( filter_GetInputAttachments( p_filter, &pp_attachments, &i_attachments_cnt ) )
386
        return VLC_EGENERIC;
bitmap's avatar
bitmap committed
387

388
    p_sys->i_font_attachments = 0;
389
    p_sys->pp_font_attachments = malloc( i_attachments_cnt * sizeof(*p_sys->pp_font_attachments));
390
391
    if( !p_sys->pp_font_attachments )
        return VLC_ENOMEM;
392

393
    for( int k = 0; k < i_attachments_cnt; k++ )
394
395
396
    {
        input_attachment_t *p_attach = pp_attachments[k];

397
398
399
        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 )
400
        {
401
            p_sys->pp_font_attachments[ p_sys->i_font_attachments++ ] = p_attach;
402
403
404
405
406
407
        }
        else
        {
            vlc_input_attachment_Delete( p_attach );
        }
    }
bitmap's avatar
bitmap committed
408
    free( pp_attachments );
409

410
    return VLC_SUCCESS;
411
412
}

Laurent Aimar's avatar
Laurent Aimar committed
413
static int GetFontSize( filter_t *p_filter )
414
{
Laurent Aimar's avatar
Laurent Aimar committed
415
416
    filter_sys_t *p_sys = p_filter->p_sys;
    int           i_size = 0;
417

Laurent Aimar's avatar
Laurent Aimar committed
418
419
    if( p_sys->i_default_font_size )
    {
420
        i_size = p_sys->i_default_font_size;
Laurent Aimar's avatar
Laurent Aimar committed
421
422
423
    }
    else
    {
424
        int i_ratio = var_InheritInteger( p_filter, "freetype-rel-fontsize" );
425
        if( i_ratio > 0 )
Laurent Aimar's avatar
Laurent Aimar committed
426
        {
427
            i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
Laurent Aimar's avatar
Laurent Aimar committed
428
429
430
431
432
        }
    }
    if( i_size <= 0 )
    {
        msg_Warn( p_filter, "invalid fontsize, using 12" );
433
        i_size = 12;
Laurent Aimar's avatar
Laurent Aimar committed
434
435
436
    }
    return i_size;
}
437

Laurent Aimar's avatar
Laurent Aimar committed
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
static int SetFontSize( filter_t *p_filter, int i_size )
{
    filter_sys_t *p_sys = p_filter->p_sys;

    if( !i_size )
    {
        i_size = GetFontSize( p_filter );

        msg_Dbg( p_filter, "using fontsize: %i", i_size );
    }

    p_sys->i_font_size = i_size;

    if( FT_Set_Pixel_Sizes( p_sys->p_face, 0, i_size ) )
    {
        msg_Err( p_filter, "couldn't set font size to %d", i_size );
        return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

#ifdef HAVE_STYLES
#ifdef HAVE_FONTCONFIG
static void FontConfig_BuildCache( filter_t *p_filter )
{
    /* */
    msg_Dbg( p_filter, "Building font databases.");
    mtime_t t1, t2;
    t1 = mdate();

469
470
471
472
#ifdef __OS2__
    FcInit();
#endif

473
#if defined( _WIN32 ) || defined( __APPLE__ )
Laurent Aimar's avatar
Laurent Aimar committed
474
475
476
477
478
479
480
481
482
483
484
485
    dialog_progress_bar_t *p_dialog = NULL;
    FcConfig *fcConfig = FcInitLoadConfig();

    p_dialog = dialog_ProgressCreate( p_filter,
            _("Building font cache"),
            _("Please wait while your font cache is rebuilt.\n"
                "This should take less than a few minutes."), NULL );

/*    if( p_dialog )
        dialog_ProgressSet( p_dialog, NULL, 0.5 ); */

    FcConfigBuildFonts( fcConfig );
486
487
488
489
490
491
492
493
494
#if defined( __APPLE__ )
    // By default, scan only the directory /System/Library/Fonts.
    // So build the set of available fonts under another directories,
    // and add the set to the current configuration.
    FcConfigAppFontAddDir( NULL, "~/Library/Fonts" );
    FcConfigAppFontAddDir( NULL, "/Library/Fonts" );
    FcConfigAppFontAddDir( NULL, "/Network/Library/Fonts" );
    //FcConfigAppFontAddDir( NULL, "/System/Library/Fonts" );
#endif
Laurent Aimar's avatar
Laurent Aimar committed
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
    if( p_dialog )
    {
//        dialog_ProgressSet( p_dialog, NULL, 1.0 );
        dialog_ProgressDestroy( p_dialog );
        p_dialog = NULL;
    }
#endif
    t2 = mdate();
    msg_Dbg( p_filter, "Took %ld microseconds", (long)((t2 - t1)) );
}

/***
 * \brief Selects a font matching family, bold, italic provided
 ***/
static char* FontConfig_Select( FcConfig* config, const char* family,
                          bool b_bold, bool b_italic, int i_size, int *i_idx )
{
    FcResult result = FcResultMatch;
    FcPattern *pat, *p_pat;
    FcChar8* val_s;
    FcBool val_b;
516
    char *ret = NULL;
Laurent Aimar's avatar
Laurent Aimar committed
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572

    /* Create a pattern and fills it */
    pat = FcPatternCreate();
    if (!pat) return NULL;

    /* */
    FcPatternAddString( pat, FC_FAMILY, (const FcChar8*)family );
    FcPatternAddBool( pat, FC_OUTLINE, FcTrue );
    FcPatternAddInteger( pat, FC_SLANT, b_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN );
    FcPatternAddInteger( pat, FC_WEIGHT, b_bold ? FC_WEIGHT_EXTRABOLD : FC_WEIGHT_NORMAL );
    if( i_size != -1 )
    {
        char *psz_fontsize;
        if( asprintf( &psz_fontsize, "%d", i_size ) != -1 )
        {
            FcPatternAddString( pat, FC_SIZE, (const FcChar8 *)psz_fontsize );
            free( psz_fontsize );
        }
    }

    /* */
    FcDefaultSubstitute( pat );
    if( !FcConfigSubstitute( config, pat, FcMatchPattern ) )
    {
        FcPatternDestroy( pat );
        return NULL;
    }

    /* Find the best font for the pattern, destroy the pattern */
    p_pat = FcFontMatch( config, pat, &result );
    FcPatternDestroy( pat );
    if( !p_pat || result == FcResultNoMatch ) return NULL;

    /* Check the new pattern */
    if( ( FcResultMatch != FcPatternGetBool( p_pat, FC_OUTLINE, 0, &val_b ) )
        || ( val_b != FcTrue ) )
    {
        FcPatternDestroy( p_pat );
        return NULL;
    }
    if( FcResultMatch != FcPatternGetInteger( p_pat, FC_INDEX, 0, i_idx ) )
    {
        *i_idx = 0;
    }

    if( FcResultMatch != FcPatternGetString( p_pat, FC_FAMILY, 0, &val_s ) )
    {
        FcPatternDestroy( p_pat );
        return NULL;
    }

    /* if( strcasecmp((const char*)val_s, family ) != 0 )
        msg_Warn( p_filter, "fontconfig: selected font family is not"
                            "the requested one: '%s' != '%s'\n",
                            (const char*)val_s, family );   */

573
574
    if( FcResultMatch == FcPatternGetString( p_pat, FC_FILE, 0, &val_s ) )
        ret = strdup( (const char*)val_s );
Laurent Aimar's avatar
Laurent Aimar committed
575
576

    FcPatternDestroy( p_pat );
577
    return ret;
Laurent Aimar's avatar
Laurent Aimar committed
578
579
580
}
#endif

581
#ifdef _WIN32
582
#define FONT_DIR_NT _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts")
Laurent Aimar's avatar
Laurent Aimar committed
583

584
static int GetFileFontByName( LPCTSTR font_name, char **psz_filename )
Laurent Aimar's avatar
Laurent Aimar committed
585
586
{
    HKEY hKey;
587
588
    TCHAR vbuffer[MAX_PATH];
    TCHAR dbuffer[256];
Laurent Aimar's avatar
Laurent Aimar committed
589

590
    if( RegOpenKeyEx(HKEY_LOCAL_MACHINE, FONT_DIR_NT, 0, KEY_READ, &hKey)
591
            != ERROR_SUCCESS )
Laurent Aimar's avatar
Laurent Aimar committed
592
593
        return 1;

594
    char *font_name_temp = FromT( font_name );
595
596
    size_t fontname_len = strlen( font_name_temp );

Laurent Aimar's avatar
Laurent Aimar committed
597
598
599
600
601
    for( int index = 0;; index++ )
    {
        DWORD vbuflen = MAX_PATH - 1;
        DWORD dbuflen = 255;

602
603
        LONG i_result = RegEnumValue( hKey, index, vbuffer, &vbuflen,
                                      NULL, NULL, (LPBYTE)dbuffer, &dbuflen);
604
        if( i_result != ERROR_SUCCESS )
sebastien's avatar
sebastien committed
605
606
        {
            RegCloseKey( hKey );
607
            return i_result;
sebastien's avatar
sebastien committed
608
        }
Laurent Aimar's avatar
Laurent Aimar committed
609

610
        char *psz_value = FromT( vbuffer );
Laurent Aimar's avatar
Laurent Aimar committed
611
612
613
614
615
616

        char *s = strchr( psz_value,'(' );
        if( s != NULL && s != psz_value ) s[-1] = '\0';

        /* Manage concatenated font names */
        if( strchr( psz_value, '&') ) {
617
618
619
            if( strcasestr( psz_value, font_name_temp ) != NULL )
            {
                free( psz_value );
Laurent Aimar's avatar
Laurent Aimar committed
620
                break;
621
            }
Laurent Aimar's avatar
Laurent Aimar committed
622
623
        }
        else {
624
625
626
            if( strncasecmp( psz_value, font_name_temp, fontname_len ) == 0 )
            {
                free( psz_value );
Laurent Aimar's avatar
Laurent Aimar committed
627
                break;
628
            }
Laurent Aimar's avatar
Laurent Aimar committed
629
        }
630
631

        free( psz_value );
Laurent Aimar's avatar
Laurent Aimar committed
632
633
    }

634
    *psz_filename = FromT( dbuffer );
635
    free( font_name_temp );
sebastien's avatar
sebastien committed
636
    RegCloseKey( hKey );
Laurent Aimar's avatar
Laurent Aimar committed
637
638
639
640
641
642
643
644
645
646
647
    return 0;
}


static int CALLBACK EnumFontCallback(const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *metric,
                                     DWORD type, LPARAM lParam)
{
    VLC_UNUSED( metric );
    if( (type & RASTER_FONTTYPE) ) return 1;
    // if( lpelfe->elfScript ) FIXME

648
    return GetFileFontByName( (LPCTSTR)lpelfe->elfFullName, (char **)lParam );
Laurent Aimar's avatar
Laurent Aimar committed
649
650
651
652
653
654
655
}

static char* Win32_Select( filter_t *p_filter, const char* family,
                           bool b_bold, bool b_italic, int i_size, int *i_idx )
{
    VLC_UNUSED( i_size );

656
    if( !family || strlen( family ) < 1 )
657
658
        goto fail;

Laurent Aimar's avatar
Laurent Aimar committed
659
660
661
662
663
664
665
    /* */
    LOGFONT lf;
    lf.lfCharSet = DEFAULT_CHARSET;
    if( b_italic )
        lf.lfItalic = true;
    if( b_bold )
        lf.lfWeight = FW_BOLD;
666

667
668
    LPTSTR psz_fbuffer = ToT( family );
    _tcsncpy( (LPTSTR)&lf.lfFaceName, psz_fbuffer, LF_FACESIZE );
669
    free( psz_fbuffer );
Laurent Aimar's avatar
Laurent Aimar committed
670
671
672
673
674
675
676
677

    /* */
    char *psz_filename = NULL;
    HDC hDC = GetDC( NULL );
    EnumFontFamiliesEx(hDC, &lf, (FONTENUMPROC)&EnumFontCallback, (LPARAM)&psz_filename, 0);
    ReleaseDC(NULL, hDC);

    /* */
678
    if( psz_filename != NULL )
679
    {
680
681
682
683
684
685
686
        /* FIXME: increase i_idx, when concatenated strings  */
        i_idx = 0;

        /* Prepend the Windows Font path, when only a filename was provided */
        if( strchr( psz_filename, DIR_SEP_CHAR ) )
            return psz_filename;
        else
687
        {
688
689
690
691
692
693
            char *psz_tmp;
            if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, psz_filename ) == -1 )
            {
                free( psz_filename );
                return NULL;
            }
694
            free( psz_filename );
695
            return psz_tmp;
696
        }
697
698
    }
    else /* Let's take any font we can */
699
fail:
700
701
702
703
704
705
    {
        char *psz_tmp;
        if( asprintf( &psz_tmp, "%s\\%s", p_filter->p_sys->psz_win_fonts_path, "arial.ttf" ) == -1 )
            return NULL;
        else
            return psz_tmp;
706
    }
Laurent Aimar's avatar
Laurent Aimar committed
707
}
708
#endif /* _WIN32 */
Laurent Aimar's avatar
Laurent Aimar committed
709

710
#ifdef __APPLE__
711
#if !TARGET_OS_IPHONE
712
713
714
715
716
717
718
static char* MacLegacy_Select( filter_t *p_filter, const char* psz_fontname,
                          bool b_bold, bool b_italic, int i_size, int *i_idx )
{
    VLC_UNUSED( b_bold );
    VLC_UNUSED( b_italic );
    VLC_UNUSED( i_size );
    FSRef ref;
719
    unsigned char path[MAXPATHLEN];
720
721
722
723
724
725
726
    char * psz_path;

    CFStringRef  cf_fontName;
    ATSFontRef   ats_font_id;

    *i_idx = 0;

727
728
729
    if( psz_fontname == NULL )
        return NULL;

730
    msg_Dbg( p_filter, "looking for %s", psz_fontname );
731
732
    cf_fontName = CFStringCreateWithCString( kCFAllocatorDefault, psz_fontname, kCFStringEncodingUTF8 );

733
734
    ats_font_id = ATSFontFindFromName( cf_fontName, kATSOptionFlagsIncludeDisabledMask );

735
    if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
736
737
    {
        msg_Dbg( p_filter, "ATS couldn't find %s by name, checking family", psz_fontname );
738
        ats_font_id = ATSFontFamilyFindFromName( cf_fontName, kATSOptionFlagsDefault );
739
740
741

        if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
        {
742
743
744
745
746
747
748
749
750
            msg_Dbg( p_filter, "ATS couldn't find either %s nor its family, checking PS name", psz_fontname );
            ats_font_id = ATSFontFindFromPostScriptName( cf_fontName, kATSOptionFlagsDefault );

            if ( ats_font_id == 0 || ats_font_id == 0xFFFFFFFFUL )
            {
                msg_Err( p_filter, "ATS couldn't find %s (no font name, family or PS name)", psz_fontname );
                CFRelease( cf_fontName );
                return NULL;
            }
751
752
        }
    }
753
    CFRelease( cf_fontName );
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

    if ( noErr != ATSFontGetFileReference( ats_font_id, &ref ) )
    {
        msg_Err( p_filter, "ATS couldn't get file ref for %s", psz_fontname );
        return NULL;
    }

    /* i_idx calculation by searching preceding fontIDs */
    /* with same FSRef                                       */
    {
        ATSFontRef  id2 = ats_font_id - 1;
        FSRef       ref2;

        while ( id2 > 0 )
        {
            if ( noErr != ATSFontGetFileReference( id2, &ref2 ) )
                break;
            if ( noErr != FSCompareFSRefs( &ref, &ref2 ) )
                break;

            id2 --;
        }
        *i_idx = ats_font_id - ( id2 + 1 );
    }

    if ( noErr != FSRefMakePath( &ref, path, sizeof(path) ) )
    {
        msg_Err( p_filter, "failure when getting path from FSRef" );
        return NULL;
    }
    msg_Dbg( p_filter, "found %s", path );

    psz_path = strdup( (char *)path );

    return psz_path;
}
#endif
791
#endif
792

793
#endif /* HAVE_STYLES */
Laurent Aimar's avatar
Laurent Aimar committed
794
795
796
797
798
799
800
801


/*****************************************************************************
 * 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,
802
803
                       line_desc_t *p_line,
                       FT_BBox *p_bbox )
Laurent Aimar's avatar
Laurent Aimar committed
804
805
806
807
808
809
810
811
812
813
814
815
{
    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 */
816
817
818
819
820
    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;
821

822
    assert( !p_region->p_picture );
823
    p_region->p_picture = picture_NewFromFormat( &fmt );
824
825
    if( !p_region->p_picture )
        return VLC_EGENERIC;
826
    fmt.p_palette = p_region->fmt.p_palette ? p_region->fmt.p_palette : malloc(sizeof(*fmt.p_palette));
827
    p_region->fmt = fmt;
828

829
830
    /* Calculate text color components
     * Only use the first color */
831
    int i_alpha = (p_line->p_character[0].i_color >> 24) & 0xff;
832
    YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
833
834

    /* Build palette */
835
    fmt.p_palette->i_entries = 16;
836
837
838
839
840
841
842
    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] =
843
            (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
844
845
    }
    for( i = 8; i < fmt.p_palette->i_entries; i++ )
846
    {
847
        fmt.p_palette->palette[i][0] = i * 16 * i_y / 256;
848
849
        fmt.p_palette->palette[i][1] = i_u;
        fmt.p_palette->palette[i][2] = i_v;
850
        fmt.p_palette->palette[i][3] = pi_gamma[i];
851
        fmt.p_palette->palette[i][3] =
852
            (int)fmt.p_palette->palette[i][3] * i_alpha / 255;
853
854
    }

855
856
    p_dst = p_region->p_picture->Y_PIXELS;
    i_pitch = p_region->p_picture->Y_PITCH;
857

858
    /* Initialize the region pixels */
859
    memset( p_dst, 0, i_pitch * p_region->fmt.i_height );
860

861
    for( ; p_line != NULL; p_line = p_line->p_next )
862
    {
863
        int i_align_left = 0;
864
        if( p_line->i_width < (int)fmt.i_visible_width )
865
        {
866
            if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
867
                i_align_left = ( fmt.i_visible_width - p_line->i_width );
868
            else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
869
                i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
870
        }
871
        int i_align_top = 0;
872

873
        for( i = 0; i < p_line->i_character_count; i++ )
sigmunau's avatar
sigmunau committed
874
        {
875
876
            const line_character_t *ch = &p_line->p_character[i];
            FT_BitmapGlyph p_glyph = ch->p_glyph;
877

878
879
            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;
880

881
            for( y = 0; y < p_glyph->bitmap.rows; y++ )
sigmunau's avatar
sigmunau committed
882
            {
883
                for( x = 0; x < p_glyph->bitmap.width; x++ )
sigmunau's avatar
sigmunau committed
884
                {
885
886
887
                    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;
sigmunau's avatar
sigmunau committed
888
889
                }
            }
890
891
        }
    }
892

893
894
895
    /* Outlining (find something better than nearest neighbour filtering ?) */
    if( 1 )
    {
896
        uint8_t *p_dst = p_region->p_picture->Y_PIXELS;
897
898
        uint8_t *p_top = p_dst; /* Use 1st line as a cache */
        uint8_t left, current;
899

900
901
        for( y = 1; y < (int)fmt.i_height - 1; y++ )
        {
902
            if( y > 1 ) memcpy( p_top, p_dst, fmt.i_width );
903
            p_dst += p_region->p_picture->Y_PITCH;
904
905
906
            left = 0;

            for( x = 1; x < (int)fmt.i_width - 1; x++ )
sigmunau's avatar
sigmunau committed
907
            {
908
                current = p_dst[x];
909
                p_dst[x] = ( 8 * (int)p_dst[x] + left + p_dst[x+1] + p_top[x -1]+ p_top[x] + p_top[x+1] +
910
                             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;
911
                left = current;
sigmunau's avatar
sigmunau committed
912
913
            }
        }
914
        memset( p_top, 0, fmt.i_width );
sigmunau's avatar
sigmunau committed
915
    }
916
917

    return VLC_SUCCESS;
sigmunau's avatar
sigmunau committed
918
}
919

920
921
922
923
924
/*****************************************************************************
 * RenderYUVA: place string in picture
 *****************************************************************************
 * This function merges the previously rendered freetype glyphs into a picture
 *****************************************************************************/
925
926
927
928
929
930
931
932
933
934
935
936
937
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 );
}

938
939
940
941
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 )
942
{
943
    int i_an = i_a * i_alpha / 255;
944

945
946
947
948
    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];
949

950
951
    int i_ao = *p_a;
    if( i_ao == 0 )
952
    {
953
954
955
956
957
958
959
960
961
        *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 )
962
        {
963
964
965
            *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;
966
        }
967
968
    }
}
969

970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
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,
987
                                   int i_picture_x, int i_picture_y,
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
                                   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];
        }
    }
}

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

1021
1022
1023
1024
{
    for( int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
    {
        for( int dx = 0; dx < p_glyph->bitmap.width; dx++ )
1025
1026
1027
            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] );
1028
1029
1030
    }
}

1031
static inline void BlendAXYZLine( picture_t *p_picture,
1032
                                  int i_picture_x, int i_picture_y,
1033
                                  int i_a, int i_x, int i_y, int i_z,
1034
                                  const line_character_t *p_current,
1035
1036
                                  const line_character_t *p_next,
                                  void (*BlendPixel)(picture_t *, int, int, int, int, int, int, int) )
1037
{
1038
    int i_line_width = p_current->p_glyph->bitmap.width;