freetype: fix extra squares in Arabic subtitles (fixes #6152 #8117)
[vlc.git] / modules / text_renderer / win32text.c
1 /*****************************************************************************
2  * win32text.c : Text drawing routines using the TextOut win32 API
3  *****************************************************************************
4  * Copyright (C) 2002 - 2009 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Gildas Bazin <gbazin@videolan.org>
8  *          Pierre Ynard
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34 #include <vlc_charset.h>
35 #include <vlc_plugin.h>
36 #include <vlc_vout.h>
37 #include <vlc_block.h>
38 #include <vlc_filter.h>
39
40 #include <math.h>
41
42 /*****************************************************************************
43  * Local prototypes
44  *****************************************************************************/
45 static int  Create ( vlc_object_t * );
46 static void Destroy( vlc_object_t * );
47
48 /* The RenderText call maps to pf_render_string, defined in vlc_filter.h */
49 static int RenderText( filter_t *, subpicture_region_t *,
50                        subpicture_region_t *,
51                        const vlc_fourcc_t * );
52
53 static int Render( filter_t *, subpicture_region_t *, uint8_t *, int, int);
54 static int SetFont( filter_t *, int );
55
56 /*****************************************************************************
57  * Module descriptor
58  *****************************************************************************/
59 #define FONT_TEXT N_("Font")
60 #define FONT_LONGTEXT N_("Filename for the font you want to use")
61 #define FONTSIZE_TEXT N_("Font size in pixels")
62 #define FONTSIZE_LONGTEXT N_("This is the default size of the fonts " \
63      "that will be rendered on the video. " \
64      "If set to something different than 0 this option will override the " \
65      "relative font size." )
66 #define OPACITY_TEXT N_("Opacity")
67 #define OPACITY_LONGTEXT N_("The opacity (inverse of transparency) of the " \
68      "text that will be rendered on the video. 0 = transparent, " \
69      "255 = totally opaque. " )
70 #define COLOR_TEXT N_("Text default color")
71 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
72   "the video. This must be an hexadecimal (like HTML colors). The first two "\
73   "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
74   " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
75 #define FONTSIZER_TEXT N_("Relative font size")
76 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
77   "fonts that will be rendered on the video. If absolute font size is set, "\
78    "relative size will be overridden." )
79
80 static int const pi_sizes[] = { 20, 18, 16, 12, 6 };
81 static char *const ppsz_sizes_text[] = {
82     N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
83 static const int pi_color_values[] = {
84   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
85   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
86   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
87
88 static const char *const ppsz_color_descriptions[] = {
89   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
90   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
91   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
92
93 vlc_module_begin ()
94     set_shortname( N_("Text renderer"))
95     set_description( N_("Win32 font renderer") )
96     set_category( CAT_VIDEO )
97     set_subcategory( SUBCAT_VIDEO_SUBPIC )
98
99     add_integer( "win32text-fontsize", 0, FONTSIZE_TEXT,
100                  FONTSIZE_LONGTEXT, true )
101
102     /* opacity valid on 0..255, with default 255 = fully opaque */
103     add_integer_with_range( "win32-opacity", 255, 0, 255,
104         OPACITY_TEXT, OPACITY_LONGTEXT, false )
105
106     /* hook to the color values list, with default 0x00ffffff = white */
107     add_integer( "win32text-color", 0x00FFFFFF, COLOR_TEXT,
108                  COLOR_LONGTEXT, true )
109         change_integer_list( pi_color_values, ppsz_color_descriptions )
110
111     add_integer( "win32text-rel-fontsize", 16, FONTSIZER_TEXT,
112                  FONTSIZER_LONGTEXT, false )
113         change_integer_list( pi_sizes, ppsz_sizes_text )
114
115     set_capability( "text renderer", 50 )
116     add_shortcut( "text" )
117     set_callbacks( Create, Destroy )
118 vlc_module_end ()
119
120 /*****************************************************************************
121  * filter_sys_t: win32text local data
122  *****************************************************************************/
123 struct filter_sys_t
124 {
125     uint8_t        i_font_opacity;
126     int            i_font_color;
127     int            i_font_size;
128
129     int            i_default_font_size;
130     int            i_display_height;
131
132     HDC hcdc;
133     HFONT hfont;
134     HFONT hfont_bak;
135     int i_logpy;
136 };
137
138 static const uint8_t pi_gamma[16] =
139   {0x00, 0x41, 0x52, 0x63, 0x84, 0x85, 0x96, 0xa7, 0xb8, 0xc9,
140    0xca, 0xdb, 0xdc, 0xed, 0xee, 0xff};
141
142 /*****************************************************************************
143  * Create: creates the module
144  *****************************************************************************/
145 static int Create( vlc_object_t *p_this )
146 {
147     filter_t *p_filter = (filter_t *)p_this;
148     filter_sys_t *p_sys;
149     char *psz_fontfile = NULL;
150     vlc_value_t val;
151     HDC hdc;
152
153     /* Allocate structure */
154     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
155     if( !p_sys )
156         return VLC_ENOMEM;
157     p_sys->i_font_size = 0;
158     p_sys->i_display_height = 0;
159
160     var_Create( p_filter, "win32text-font",
161                 VLC_VAR_STRING | VLC_VAR_DOINHERIT );
162     var_Create( p_filter, "win32text-fontsize",
163                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
164     var_Create( p_filter, "win32text-rel-fontsize",
165                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
166     var_Create( p_filter, "win32text-opacity",
167                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
168     var_Get( p_filter, "win32text-opacity", &val );
169     p_sys->i_font_opacity = VLC_CLIP( val.i_int, 0, 255 );
170     var_Create( p_filter, "win32text-color",
171                 VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
172     var_Get( p_filter, "win32text-color", &val );
173     p_sys->i_font_color = VLC_CLIP( val.i_int, 0, 0xFFFFFF );
174
175     p_sys->hfont = p_sys->hfont_bak = 0;
176     hdc = GetDC( NULL );
177     p_sys->hcdc = CreateCompatibleDC( hdc );
178     p_sys->i_logpy = GetDeviceCaps( hdc, LOGPIXELSY );
179     ReleaseDC( NULL, hdc );
180     SetBkMode( p_sys->hcdc, TRANSPARENT );
181
182     var_Get( p_filter, "win32text-fontsize", &val );
183     p_sys->i_default_font_size = val.i_int;
184     if( SetFont( p_filter, 0 ) != VLC_SUCCESS ) goto error;
185
186     free( psz_fontfile );
187     p_filter->pf_render_text = RenderText;
188     p_filter->pf_render_html = NULL;
189     return VLC_SUCCESS;
190
191  error:
192     free( psz_fontfile );
193     free( p_sys );
194     return VLC_EGENERIC;
195 }
196
197 /*****************************************************************************
198  * Destroy: destroy the module
199  *****************************************************************************/
200 static void Destroy( vlc_object_t *p_this )
201 {
202     filter_t *p_filter = (filter_t *)p_this;
203     filter_sys_t *p_sys = p_filter->p_sys;
204
205     if( p_sys->hfont_bak ) SelectObject( p_sys->hcdc, p_sys->hfont_bak );
206     if( p_sys->hfont ) DeleteObject( p_sys->hfont );
207     DeleteDC( p_sys->hcdc );
208     free( p_sys );
209 }
210
211 /*****************************************************************************
212  * Render: place string in picture
213  *****************************************************************************
214  * This function merges the previously rendered win32text glyphs into a picture
215  *****************************************************************************/
216 static int Render( filter_t *p_filter, subpicture_region_t *p_region,
217                    uint8_t *p_bitmap, int i_width, int i_height )
218 {
219     uint8_t *p_dst;
220     video_format_t fmt;
221     int i, i_pitch;
222     bool b_outline = true;
223
224     /* Create a new subpicture region */
225     memset( &fmt, 0, sizeof(video_format_t) );
226     fmt.i_chroma = VLC_CODEC_YUVP;
227     fmt.i_width = fmt.i_visible_width = i_width + (b_outline ? 4 : 0);
228     fmt.i_height = fmt.i_visible_height = i_height + (b_outline ? 4 : 0);
229     fmt.i_x_offset = fmt.i_y_offset = 0;
230     fmt.i_sar_num = 1;
231     fmt.i_sar_den = 1;
232
233     /* Build palette */
234     fmt.p_palette = calloc( 1, sizeof(*fmt.p_palette) );
235     if( !fmt.p_palette )
236         return VLC_EGENERIC;
237     fmt.p_palette->i_entries = 16;
238     for( i = 0; i < fmt.p_palette->i_entries; i++ )
239     {
240         fmt.p_palette->palette[i][0] = pi_gamma[i];
241         fmt.p_palette->palette[i][1] = 128;
242         fmt.p_palette->palette[i][2] = 128;
243         fmt.p_palette->palette[i][3] = pi_gamma[i];
244     }
245
246     p_region->p_picture = picture_NewFromFormat( &fmt );
247     if( !p_region->p_picture )
248     {
249         free( fmt.p_palette );
250         return VLC_EGENERIC;
251     }
252     p_region->fmt = fmt;
253
254     p_dst = p_region->p_picture->Y_PIXELS;
255     i_pitch = p_region->p_picture->Y_PITCH;
256
257     if( b_outline )
258     {
259         memset( p_dst, 0, i_pitch * fmt.i_height );
260         p_dst += p_region->p_picture->Y_PITCH * 2 + 2;
261     }
262
263     for( i = 0; i < i_height; i++ )
264     {
265         memcpy( p_dst, p_bitmap, i_width );
266         p_bitmap += (i_width+3) & ~3;
267         p_dst += i_pitch;
268     }
269
270     /* Outlining (find something better than nearest neighbour filtering ?) */
271     if( b_outline )
272     {
273         uint8_t *p_top = p_dst; /* Use 1st line as a cache */
274         uint8_t left, current;
275         int x, y;
276
277         p_dst = p_region->p_picture->Y_PIXELS;
278
279         for( y = 1; y < (int)fmt.i_height - 1; y++ )
280         {
281             memcpy( p_top, p_dst, fmt.i_width );
282             p_dst += i_pitch;
283             left = 0;
284
285             for( x = 1; x < (int)fmt.i_width - 1; x++ )
286             {
287                 current = p_dst[x];
288                 p_dst[x] = ( 4 * (int)p_dst[x] + left + p_top[x] + p_dst[x+1] +
289                              p_dst[x + i_pitch]) / 8;
290                 left = current;
291             }
292         }
293         memset( p_top, 0, fmt.i_width );
294     }
295
296     return VLC_SUCCESS;
297 }
298
299 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
300                        subpicture_region_t *p_region_in,
301                        const vlc_fourcc_t *p_chroma_list )
302 {
303     filter_sys_t *p_sys = p_filter->p_sys;
304     int i_font_color, i_font_alpha, i_font_size;
305     uint8_t *p_bitmap;
306     TCHAR *psz_string;
307     int i, i_width, i_height;
308     HBITMAP bitmap, bitmap_bak;
309     BITMAPINFO *p_bmi;
310     RECT rect = { 0, 0, 0, 0 };
311
312     /* Sanity check */
313     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
314     if( !p_region_in->psz_text || !*p_region_in->psz_text )
315         return VLC_EGENERIC;
316
317     psz_string = ToT(p_region_in->psz_text);
318     if( psz_string == NULL )
319         return VLC_EGENERIC;
320     if( !*psz_string )
321     {
322         free( psz_string );
323         return VLC_EGENERIC;
324     }
325
326     if( p_region_in->p_style )
327     {
328         i_font_color = VLC_CLIP( p_region_in->p_style->i_font_color, 0, 0xFFFFFF );
329         i_font_alpha = VLC_CLIP( p_region_in->p_style->i_font_alpha, 0, 255 );
330         i_font_size  = VLC_CLIP( p_region_in->p_style->i_font_size, 0, 255 );
331     }
332     else
333     {
334         i_font_color = p_sys->i_font_color;
335         i_font_alpha = 255 - p_sys->i_font_opacity;
336         i_font_size = p_sys->i_default_font_size;
337     }
338
339     SetFont( p_filter, i_font_size );
340
341     SetTextColor( p_sys->hcdc, RGB( (i_font_color >> 16) & 0xff,
342                   (i_font_color >> 8) & 0xff, i_font_color & 0xff) );
343
344     DrawText( p_sys->hcdc, psz_string, -1, &rect,
345               DT_CALCRECT | DT_CENTER | DT_NOPREFIX );
346     i_width = rect.right; i_height = rect.bottom;
347
348     p_bmi = malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*16);
349     memset( p_bmi, 0, sizeof(BITMAPINFOHEADER) );
350     p_bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
351     p_bmi->bmiHeader.biWidth = (i_width+3) & ~3;
352     p_bmi->bmiHeader.biHeight = - i_height;
353     p_bmi->bmiHeader.biPlanes = 1;
354     p_bmi->bmiHeader.biBitCount = 8;
355     p_bmi->bmiHeader.biCompression = BI_RGB;
356     p_bmi->bmiHeader.biClrUsed = 16;
357
358     for( i = 0; i < 16; i++ )
359     {
360         p_bmi->bmiColors[i].rgbBlue =
361             p_bmi->bmiColors[i].rgbGreen =
362                 p_bmi->bmiColors[i].rgbRed = pi_gamma[i];
363     }
364
365     bitmap = CreateDIBSection( p_sys->hcdc, p_bmi, DIB_RGB_COLORS,
366                                (void **)&p_bitmap, NULL, 0 );
367     if( !bitmap )
368     {
369         msg_Err( p_filter, "could not create bitmap" );
370         free( psz_string );
371         return VLC_EGENERIC;
372     }
373
374     bitmap_bak = SelectObject( p_sys->hcdc, bitmap );
375     FillRect( p_sys->hcdc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH) );
376
377     if( !DrawText( p_sys->hcdc, psz_string, -1, &rect,
378                    DT_CENTER | DT_NOPREFIX ) )
379     {
380         msg_Err( p_filter, "could not draw text" );
381     }
382
383     p_region_out->i_x = p_region_in->i_x;
384     p_region_out->i_y = p_region_in->i_y;
385     Render( p_filter, p_region_out, p_bitmap, i_width, i_height );
386
387     SelectObject( p_sys->hcdc, bitmap_bak );
388     DeleteObject( bitmap );
389     free( psz_string );
390     return VLC_SUCCESS;
391 }
392
393 static int SetFont( filter_t *p_filter, int i_size )
394 {
395     filter_sys_t *p_sys = p_filter->p_sys;
396     LOGFONT logfont;
397
398     if( i_size && i_size == p_sys->i_font_size ) return VLC_SUCCESS;
399
400     if( !i_size )
401     {
402         vlc_value_t val;
403
404         if( !p_sys->i_default_font_size &&
405             p_sys->i_display_height == (int)p_filter->fmt_out.video.i_height )
406             return VLC_SUCCESS;
407
408         if( p_sys->i_default_font_size )
409         {
410             i_size = p_sys->i_default_font_size;
411         }
412         else
413         {
414             var_Get( p_filter, "win32text-rel-fontsize", &val );
415             i_size = (int)p_filter->fmt_out.video.i_height / val.i_int;
416             p_filter->p_sys->i_display_height =
417                 p_filter->fmt_out.video.i_height;
418         }
419         if( i_size <= 0 )
420         {
421             msg_Warn( p_filter, "invalid fontsize, using 12" );
422             i_size = 12;
423         }
424
425         msg_Dbg( p_filter, "using fontsize: %i", i_size );
426     }
427
428     p_sys->i_font_size = i_size;
429
430     if( p_sys->hfont_bak ) SelectObject( p_sys->hcdc, p_sys->hfont_bak );
431     if( p_sys->hfont ) DeleteObject( p_sys->hfont );
432
433     i_size = i_size * (int64_t)p_sys->i_logpy / 72;
434
435     logfont.lfHeight = i_size;
436     logfont.lfWidth = 0;
437     logfont.lfEscapement = 0;
438     logfont.lfOrientation = 0;
439     logfont.lfWeight = 0;
440     logfont.lfItalic = FALSE;
441     logfont.lfUnderline = FALSE;
442     logfont.lfStrikeOut = FALSE;
443     logfont.lfCharSet = ANSI_CHARSET;
444     logfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
445     logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
446     logfont.lfQuality = ANTIALIASED_QUALITY;
447     logfont.lfPitchAndFamily = DEFAULT_PITCH;
448     memcpy( logfont.lfFaceName, _T("Arial"), sizeof(_T("Arial")) );
449
450     p_sys->hfont = CreateFontIndirect( &logfont );
451
452     p_sys->hfont_bak = SelectObject( p_sys->hcdc, p_sys->hfont );
453
454     return VLC_SUCCESS;
455 }