]> git.sesse.net Git - vlc/blob - modules/misc/quartztext.c
Merge branch 1.0-bugfix
[vlc] / modules / misc / quartztext.c
1 /*****************************************************************************
2  * quartztext.c : Put text on the video, using Mac OS X Quartz Engine
3  *****************************************************************************
4  * Copyright (C) 2007, 2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Bernie Purcell <bitmap@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 //////////////////////////////////////////////////////////////////////////////
25 // Preamble
26 //////////////////////////////////////////////////////////////////////////////
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_vout.h>
35 #include <vlc_osd.h>
36 #include <vlc_block.h>
37 #include <vlc_filter.h>
38 #include <vlc_stream.h>
39 #include <vlc_xml.h>
40 #include <vlc_input.h>
41 #include <vlc_strings.h>
42
43 #include <math.h>
44
45 #include <Carbon/Carbon.h>
46
47 #define DEFAULT_FONT           "Arial Black"
48 #define DEFAULT_FONT_COLOR     0xffffff
49 #define DEFAULT_REL_FONT_SIZE  16
50
51 #define VERTICAL_MARGIN 3
52 #define HORIZONTAL_MARGIN 10
53
54 //////////////////////////////////////////////////////////////////////////////
55 // Local prototypes
56 //////////////////////////////////////////////////////////////////////////////
57 static int  Create ( vlc_object_t * );
58 static void Destroy( vlc_object_t * );
59
60 static int LoadFontsFromAttachments( filter_t *p_filter );
61
62 static int RenderText( filter_t *, subpicture_region_t *,
63                        subpicture_region_t * );
64 static int RenderHtml( filter_t *, subpicture_region_t *,
65                        subpicture_region_t * );
66
67 static int GetFontSize( filter_t *p_filter );
68 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
69                        UniChar *psz_utfString, uint32_t i_text_len,
70                        uint32_t i_runs, uint32_t *pi_run_lengths,
71                        ATSUStyle *pp_styles );
72 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size,
73                               uint32_t i_font_color,
74                               bool b_bold, bool b_italic,
75                               bool b_uline );
76 //////////////////////////////////////////////////////////////////////////////
77 // Module descriptor
78 //////////////////////////////////////////////////////////////////////////////
79
80 // The preferred way to set font style information is for it to come from the
81 // subtitle file, and for it to be rendered with RenderHtml instead of
82 // RenderText. This module, unlike Freetype, doesn't provide any options to
83 // override the fallback font selection used when this style information is
84 // absent.
85 #define FONT_TEXT N_("Font")
86 #define FONT_LONGTEXT N_("Name for the font you want to use")
87 #define FONTSIZER_TEXT N_("Relative font size")
88 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
89     "fonts that will be rendered on the video. If absolute font size is set, "\
90     "relative size will be overriden." )
91 #define COLOR_TEXT N_("Text default color")
92 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
93     "the video. This must be an hexadecimal (like HTML colors). The first two "\
94     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
95     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
96
97 static const int pi_color_values[] = {
98   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
99   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
100   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
101
102 static const char *const ppsz_color_descriptions[] = {
103   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
104   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
105   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
106
107 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
108 static const char *const ppsz_sizes_text[] = {
109     N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
110
111 vlc_module_begin ()
112     set_shortname( N_("Mac Text renderer"))
113     set_description( N_("Quartz font renderer") )
114     set_category( CAT_VIDEO )
115     set_subcategory( SUBCAT_VIDEO_SUBPIC )
116
117     add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
118               false )
119     add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT,
120                  FONTSIZER_LONGTEXT, false )
121         change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
122     add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
123                  COLOR_LONGTEXT, false )
124         change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
125     set_capability( "text renderer", 150 )
126     add_shortcut( "text" )
127     set_callbacks( Create, Destroy )
128 vlc_module_end ()
129
130 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
131 struct offscreen_bitmap_t
132 {
133     uint8_t       *p_data;
134     int            i_bitsPerChannel;
135     int            i_bitsPerPixel;
136     int            i_bytesPerPixel;
137     int            i_bytesPerRow;
138 };
139
140 //////////////////////////////////////////////////////////////////////////////
141 // filter_sys_t: quartztext local data
142 //////////////////////////////////////////////////////////////////////////////
143 // This structure is part of the video output thread descriptor.
144 // It describes the freetype specific properties of an output thread.
145 //////////////////////////////////////////////////////////////////////////////
146 struct filter_sys_t
147 {
148     char          *psz_font_name;
149     uint8_t        i_font_opacity;
150     int            i_font_color;
151     int            i_font_size;
152
153     ATSFontContainerRef    *p_fonts;
154     int                     i_fonts;
155 };
156
157 #define UCHAR UniChar
158 #define TR_DEFAULT_FONT p_sys->psz_font_name
159 #define TR_FONT_STYLE_PTR ATSUStyle
160
161 #include "text_renderer.h"
162
163 //////////////////////////////////////////////////////////////////////////////
164 // Create: allocates osd-text video thread output method
165 //////////////////////////////////////////////////////////////////////////////
166 // This function allocates and initializes a Clone vout method.
167 //////////////////////////////////////////////////////////////////////////////
168 static int Create( vlc_object_t *p_this )
169 {
170     filter_t *p_filter = (filter_t *)p_this;
171     filter_sys_t *p_sys;
172
173     // Allocate structure
174     p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
175     if( !p_sys )
176         return VLC_ENOMEM;
177     p_sys->psz_font_name  = var_CreateGetString( p_this, "quartztext-font" );
178     p_sys->i_font_opacity = 255;
179     p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
180     p_sys->i_font_size    = GetFontSize( p_filter );
181
182     p_filter->pf_render_text = RenderText;
183     p_filter->pf_render_html = RenderHtml;
184
185     p_sys->p_fonts = NULL;
186     p_sys->i_fonts = 0;
187
188     LoadFontsFromAttachments( p_filter );
189
190     return VLC_SUCCESS;
191 }
192
193 //////////////////////////////////////////////////////////////////////////////
194 // Destroy: destroy Clone video thread output method
195 //////////////////////////////////////////////////////////////////////////////
196 // Clean up all data and library connections
197 //////////////////////////////////////////////////////////////////////////////
198 static void Destroy( vlc_object_t *p_this )
199 {
200     filter_t *p_filter = (filter_t *)p_this;
201     filter_sys_t *p_sys = p_filter->p_sys;
202
203     if( p_sys->p_fonts )
204     {
205         int   k;
206
207         for( k = 0; k < p_sys->i_fonts; k++ )
208         {
209             ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
210         }
211
212         free( p_sys->p_fonts );
213     }
214
215     free( p_sys->psz_font_name );
216     free( p_sys );
217 }
218
219 //////////////////////////////////////////////////////////////////////////////
220 // Make any TTF/OTF fonts present in the attachments of the media file
221 // available to the Quartz engine for text rendering
222 //////////////////////////////////////////////////////////////////////////////
223 static int LoadFontsFromAttachments( filter_t *p_filter )
224 {
225     filter_sys_t         *p_sys = p_filter->p_sys;
226     input_thread_t       *p_input;
227     input_attachment_t  **pp_attachments;
228     int                   i_attachments_cnt;
229     int                   k;
230     int                   rv = VLC_SUCCESS;
231
232     p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
233     if( ! p_input )
234         return VLC_EGENERIC;
235
236     if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
237     {
238         vlc_object_release(p_input);
239         return VLC_EGENERIC;
240     }
241
242     p_sys->i_fonts = 0;
243     p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
244     if(! p_sys->p_fonts )
245         rv = VLC_ENOMEM;
246
247     for( k = 0; k < i_attachments_cnt; k++ )
248     {
249         input_attachment_t *p_attach = pp_attachments[k];
250
251         if( p_sys->p_fonts )
252         {
253             if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
254                  !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) &&    // OTF
255                ( p_attach->i_data > 0 ) &&
256                ( p_attach->p_data != NULL ) )
257             {
258                 ATSFontContainerRef  container;
259
260                 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
261                                                         p_attach->i_data,
262                                                         kATSFontContextLocal,
263                                                         kATSFontFormatUnspecified,
264                                                         NULL,
265                                                         kATSOptionFlagsDefault,
266                                                         &container ))
267                 {
268                     p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
269                 }
270             }
271         }
272         vlc_input_attachment_Delete( p_attach );
273     }
274     free( pp_attachments );
275
276     vlc_object_release(p_input);
277
278     return rv;
279 }
280
281 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
282 // Original version of these functions available on:
283 // http://developer.apple.com/documentation/Carbon/Conceptual/QuickDrawToQuartz2D/tq_color/chapter_4_section_3.html
284
285 #define kGenericRGBProfilePathStr "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc"
286
287 static CMProfileRef OpenGenericProfile( void )
288 {
289     static CMProfileRef cached_rgb_prof = NULL;
290
291     // Create the profile reference only once
292     if( cached_rgb_prof == NULL )
293     {
294         OSStatus            err;
295         CMProfileLocation   loc;
296
297         loc.locType = cmPathBasedProfile;
298         strcpy( loc.u.pathLoc.path, kGenericRGBProfilePathStr );
299
300         err = CMOpenProfile( &cached_rgb_prof, &loc );
301
302         if( err != noErr )
303         {
304             cached_rgb_prof = NULL;
305         }
306     }
307
308     if( cached_rgb_prof )
309     {
310         // Clone the profile reference so that the caller has
311         // their own reference, not our cached one.
312         CMCloneProfileRef( cached_rgb_prof );
313     }
314
315     return cached_rgb_prof;
316 }
317
318 static CGColorSpaceRef CreateGenericRGBColorSpace( void )
319 {
320     static CGColorSpaceRef p_generic_rgb_cs = NULL;
321
322     if( p_generic_rgb_cs == NULL )
323     {
324         CMProfileRef generic_rgb_prof = OpenGenericProfile();
325
326         if( generic_rgb_prof )
327         {
328             p_generic_rgb_cs = CGColorSpaceCreateWithPlatformColorSpace( generic_rgb_prof );
329
330             CMCloseProfile( generic_rgb_prof );
331         }
332     }
333
334     return p_generic_rgb_cs;
335 }
336 #endif
337
338 static char *EliminateCRLF( char *psz_string )
339 {
340     char *p;
341     char *q;
342
343     for( p = psz_string; p && *p; p++ )
344     {
345         if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
346         {
347             for( q = p + 1; *q; q++ )
348                 *( q - 1 ) = *q;
349
350             *( q - 1 ) = '\0';
351         }
352     }
353     return psz_string;
354 }
355
356 // Convert UTF-8 string to UTF-16 character array -- internal Mac Endian-ness ;
357 // we don't need to worry about bidirectional text conversion as ATSUI should
358 // handle that for us automatically
359 static void ConvertToUTF16( const char *psz_utf8_str, uint32_t *pi_strlen, UniChar **ppsz_utf16_str )
360 {
361     CFStringRef   p_cfString;
362     int           i_string_length;
363
364     p_cfString = CFStringCreateWithCString( NULL, psz_utf8_str, kCFStringEncodingUTF8 );
365     if( !p_cfString )
366         return;
367
368     i_string_length = CFStringGetLength( p_cfString );
369
370     if( pi_strlen )
371         *pi_strlen = i_string_length;
372
373     if( !*ppsz_utf16_str )
374         *ppsz_utf16_str = (UniChar *) calloc( i_string_length, sizeof( UniChar ) );
375
376     CFStringGetCharacters( p_cfString, CFRangeMake( 0, i_string_length ), *ppsz_utf16_str );
377
378     CFRelease( p_cfString );
379 }
380
381 // Renders a text subpicture region into another one.
382 // It is used as pf_add_string callback in the vout method by this module
383 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
384                        subpicture_region_t *p_region_in )
385 {
386     filter_sys_t *p_sys = p_filter->p_sys;
387     UniChar      *psz_utf16_str = NULL;
388     uint32_t      i_string_length;
389     char         *psz_string;
390     int           i_font_color, i_font_alpha, i_font_size;
391     vlc_value_t val;
392     int i_scale = 1000;
393
394     p_sys->i_font_size    = GetFontSize( p_filter );
395
396     // Sanity check
397     if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
398     psz_string = p_region_in->psz_text;
399     if( !psz_string || !*psz_string ) return VLC_EGENERIC;
400
401     if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
402         i_scale = val.i_int;
403
404     if( p_region_in->p_style )
405     {
406         i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
407         i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
408         i_font_size  = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
409     }
410     else
411     {
412         i_font_color = p_sys->i_font_color;
413         i_font_alpha = 255 - p_sys->i_font_opacity;
414         i_font_size  = p_sys->i_font_size;
415     }
416
417     if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
418
419     ConvertToUTF16( EliminateCRLF( psz_string ), &i_string_length, &psz_utf16_str );
420
421     p_region_out->i_x = p_region_in->i_x;
422     p_region_out->i_y = p_region_in->i_y;
423
424     if( psz_utf16_str != NULL )
425     {
426         ATSUStyle p_style = CreateStyle( p_sys->psz_font_name, i_font_size,
427                                          (i_font_color & 0xffffff) |
428                                          ((i_font_alpha & 0xff) << 24),
429                                          false, false, false );
430         if( p_style )
431         {
432             RenderYUVA( p_filter, p_region_out, psz_utf16_str, i_string_length,
433                         1, &i_string_length, &p_style );
434         }
435
436         ATSUDisposeStyle( p_style );
437         free( psz_utf16_str );
438     }
439
440     return VLC_SUCCESS;
441 }
442
443
444 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size, uint32_t i_font_color,
445                               bool b_bold, bool b_italic, bool b_uline )
446 {
447     ATSUStyle   p_style;
448     OSStatus    status;
449     uint32_t    i_tag_cnt;
450
451     float f_red   = (float)(( i_font_color & 0x00FF0000 ) >> 16) / 255.0;
452     float f_green = (float)(( i_font_color & 0x0000FF00 ) >>  8) / 255.0;
453     float f_blue  = (float)(  i_font_color & 0x000000FF        ) / 255.0;
454     float f_alpha = ( 255.0 - (float)(( i_font_color & 0xFF000000 ) >> 24)) / 255.0;
455
456     ATSUFontID           font;
457     Fixed                font_size  = IntToFixed( i_font_size );
458     ATSURGBAlphaColor    font_color = { f_red, f_green, f_blue, f_alpha };
459     Boolean              bold       = b_bold;
460     Boolean              italic     = b_italic;
461     Boolean              uline      = b_uline;
462
463     ATSUAttributeTag tags[]        = { kATSUSizeTag, kATSURGBAlphaColorTag, kATSUQDItalicTag,
464                                        kATSUQDBoldfaceTag, kATSUQDUnderlineTag, kATSUFontTag };
465     ByteCount sizes[]              = { sizeof( Fixed ), sizeof( ATSURGBAlphaColor ), sizeof( Boolean ),
466                                        sizeof( Boolean ), sizeof( Boolean ), sizeof( ATSUFontID )};
467     ATSUAttributeValuePtr values[] = { &font_size, &font_color, &italic, &bold, &uline, &font };
468
469     i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
470
471     status = ATSUFindFontFromName( psz_fontname,
472                                    strlen( psz_fontname ),
473                                    kFontFullName,
474                                    kFontNoPlatform,
475                                    kFontNoScript,
476                                    kFontNoLanguageCode,
477                                    &font );
478
479     if( status != noErr )
480     {
481         // If we can't find a suitable font, just do everything else
482         i_tag_cnt--;
483     }
484
485     if( noErr == ATSUCreateStyle( &p_style ) )
486     {
487         if( noErr == ATSUSetAttributes( p_style, i_tag_cnt, tags, sizes, values ) )
488         {
489             return p_style;
490         }
491         ATSUDisposeStyle( p_style );
492     }
493     return NULL;
494 }
495
496 static ATSUStyle GetStyleFromFontStack( filter_sys_t *p_sys,
497         font_stack_t **p_fonts, bool b_bold, bool b_italic,
498         bool b_uline )
499 {
500     ATSUStyle   p_style = NULL;
501
502     char     *psz_fontname = NULL;
503     uint32_t  i_font_color = p_sys->i_font_color;
504     uint32_t  i_karaoke_bg_color = i_font_color; /* Use it */
505     int       i_font_size  = p_sys->i_font_size;
506
507     if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
508                                  &i_font_color, &i_karaoke_bg_color ))
509     {
510         p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
511                                b_bold, b_italic, b_uline );
512     }
513     return p_style;
514 }
515
516 static void SetupLine( filter_t *p_filter, const char *psz_text_in,
517                        UniChar **psz_text_out, uint32_t *pi_runs,
518                        uint32_t **ppi_run_lengths, ATSUStyle **ppp_styles,
519                        ATSUStyle p_style )
520 {
521     uint32_t i_string_length = 0;
522
523     ConvertToUTF16( psz_text_in, &i_string_length, psz_text_out );
524     *psz_text_out += i_string_length;
525
526     if( ppp_styles && ppi_run_lengths )
527     {
528         (*pi_runs)++;
529
530         if( *ppp_styles )
531             *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
532         else
533             *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
534
535         (*ppp_styles)[ *pi_runs - 1 ] = p_style;
536
537         if( *ppi_run_lengths )
538             *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
539         else
540             *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
541
542         (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
543     }
544 }
545
546 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
547                        subpicture_region_t *p_region_in )
548 {
549     int          rv = VLC_SUCCESS;
550     stream_t     *p_sub = NULL;
551     xml_t        *p_xml = NULL;
552     xml_reader_t *p_xml_reader = NULL;
553
554     if( !p_region_in || !p_region_in->psz_html )
555         return VLC_EGENERIC;
556
557     /* Reset the default fontsize in case screen metrics have changed */
558     p_filter->p_sys->i_font_size = GetFontSize( p_filter );
559
560     p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
561                               (uint8_t *) p_region_in->psz_html,
562                               strlen( p_region_in->psz_html ),
563                               true );
564     if( p_sub )
565     {
566         p_xml = xml_Create( p_filter );
567         if( p_xml )
568         {
569             bool b_karaoke = false;
570
571             p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
572             if( p_xml_reader )
573             {
574                 /* Look for Root Node */
575                 if( xml_ReaderRead( p_xml_reader ) == 1 )
576                 {
577                     char *psz_node = xml_ReaderName( p_xml_reader );
578
579                     if( !strcasecmp( "karaoke", psz_node ) )
580                     {
581                         /* We're going to have to render the text a number
582                          * of times to show the progress marker on the text.
583                          */
584                         var_SetBool( p_filter, "text-rerender", true );
585                         b_karaoke = true;
586                     }
587                     else if( !strcasecmp( "text", psz_node ) )
588                     {
589                         b_karaoke = false;
590                     }
591                     else
592                     {
593                         /* Only text and karaoke tags are supported */
594                         xml_ReaderDelete( p_xml, p_xml_reader );
595                         p_xml_reader = NULL;
596                         rv = VLC_EGENERIC;
597                     }
598
599                     free( psz_node );
600                 }
601             }
602
603             if( p_xml_reader )
604             {
605                 UniChar    *psz_text;
606                 int         i_len = 0;
607                 uint32_t    i_runs = 0;
608                 uint32_t    i_k_runs = 0;
609                 uint32_t   *pi_run_lengths = NULL;
610                 uint32_t   *pi_k_run_lengths = NULL;
611                 uint32_t   *pi_k_durations = NULL;
612                 ATSUStyle  *pp_styles = NULL;
613
614                 psz_text = (UniChar *) malloc( strlen( p_region_in->psz_html ) *
615                                                 sizeof( UniChar ) );
616                 if( psz_text )
617                 {
618                     uint32_t k;
619
620                     rv = ProcessNodes( p_filter, p_xml_reader,
621                                   p_region_in->p_style, psz_text, &i_len,
622                                   &i_runs, &pi_run_lengths, &pp_styles,
623                                   /* No karaoke support */
624                                   false, &i_k_runs, &pi_k_run_lengths, &pi_k_durations );
625
626                     assert( pi_k_run_lengths == NULL && pi_k_durations == NULL );
627
628                     p_region_out->i_x = p_region_in->i_x;
629                     p_region_out->i_y = p_region_in->i_y;
630
631                     if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
632                     {
633                         RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs,
634                              pi_run_lengths, pp_styles);
635                     }
636
637                     for( k=0; k<i_runs; k++)
638                         ATSUDisposeStyle( pp_styles[k] );
639                     free( pp_styles );
640                     free( pi_run_lengths );
641                     free( psz_text );
642                 }
643
644                 xml_ReaderDelete( p_xml, p_xml_reader );
645             }
646             xml_Delete( p_xml );
647         }
648         stream_Delete( p_sub );
649     }
650
651     return rv;
652 }
653
654 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
655                          offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
656 {
657     offscreen_bitmap_t *p_bitmap;
658     CGContextRef        p_context = NULL;
659
660     p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
661     if( p_bitmap )
662     {
663         p_bitmap->i_bitsPerChannel = 8;
664         p_bitmap->i_bitsPerPixel   = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
665         p_bitmap->i_bytesPerPixel  = p_bitmap->i_bitsPerPixel / 8;
666         p_bitmap->i_bytesPerRow    = i_width * p_bitmap->i_bytesPerPixel;
667
668         p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
669
670         *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
671
672         if( p_bitmap->p_data && *pp_colorSpace )
673         {
674             p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
675                                 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
676                                 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
677         }
678         if( p_context )
679         {
680             if( CGContextSetAllowsAntialiasing != NULL )
681             {
682                 CGContextSetAllowsAntialiasing( p_context, true );
683             }
684         }
685         *pp_memory = p_bitmap;
686     }
687
688     return p_context;
689 }
690
691 static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len,
692                                     uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles,
693                                     int i_width, int i_height, int *pi_textblock_height )
694 {
695     offscreen_bitmap_t  *p_offScreen  = NULL;
696     CGColorSpaceRef      p_colorSpace = NULL;
697     CGContextRef         p_context = NULL;
698
699     p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
700
701     if( p_context )
702     {
703         ATSUTextLayout p_textLayout;
704         OSStatus status = noErr;
705
706         status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
707                                                   i_runs,
708                                                   (const UniCharCount *) pi_run_lengths,
709                                                   pp_styles,
710                                                   &p_textLayout );
711         if( status == noErr )
712         {
713             // Attach our offscreen Image Graphics Context to the text style
714             // and setup the line alignment (have to specify the line width
715             // also in order for our chosen alignment to work)
716
717             Fract   alignment  = kATSUStartAlignment;
718             Fixed   line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
719
720             ATSUAttributeTag tags[]        = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
721             ByteCount sizes[]              = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
722             ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
723
724             int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
725
726             if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
727             {
728                 alignment = kATSUEndAlignment;
729             }
730             else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
731             {
732                 alignment = kATSUCenterAlignment;
733             }
734
735             ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
736
737             // let ATSUI deal with characters not-in-our-specified-font
738             ATSUSetTransientFontMatching( p_textLayout, true );
739
740             Fixed x = Long2Fix( HORIZONTAL_MARGIN );
741             Fixed y = Long2Fix( i_height );
742
743             // Set the line-breaks and draw individual lines
744             uint32_t i_start = 0;
745             uint32_t i_end = i_text_len;
746
747             // Set up black outlining of the text --
748             CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
749             CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
750             CGContextSetShadow( p_context, CGSizeMake( 0, 0 ), 5 );
751             float black_components[4] = {0, 0, 0, 1};
752             CGContextSetShadowWithColor (p_context, CGSizeMake( 0, 0 ), 5, CGColorCreate( CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB ), black_components ));
753             do
754             {
755                 // ATSUBreakLine will automatically pick up any manual '\n's also
756                 status = ATSUBreakLine( p_textLayout, i_start, line_width, true, (UniCharArrayOffset *) &i_end );
757                 if( ( status == noErr ) || ( status == kATSULineBreakInWord ) )
758                 {
759                     Fixed     ascent;
760                     Fixed     descent;
761                     uint32_t  i_actualSize;
762
763                     // Come down far enough to fit the height of this line --
764                     ATSUGetLineControl( p_textLayout, i_start, kATSULineAscentTag,
765                                     sizeof( Fixed ), &ascent, (ByteCount *) &i_actualSize );
766
767                     // Quartz uses an upside-down co-ordinate space -> y values decrease as
768                     // you move down the page
769                     y -= ascent;
770
771                     // Set the outlining for this line to be dependent on the size of the line -
772                     // make it about 5% of the ascent, with a minimum at 1.0
773                     float f_thickness = FixedToFloat( ascent ) * 0.05;
774                     CGContextSetLineWidth( p_context, (( f_thickness < 1.0 ) ? 1.0 : f_thickness ));
775                     ATSUDrawText( p_textLayout, i_start, i_end - i_start, x, y );
776
777                     // and now prepare for the next line by coming down far enough for our
778                     // descent
779                     ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag,
780                                     sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize );
781                     y -= descent;
782
783                     i_start = i_end;
784                 }
785                 else
786                     break;
787             }
788             while( i_end < i_text_len );
789
790             *pi_textblock_height = i_height - Fix2Long( y );
791             CGContextFlush( p_context );
792
793             ATSUDisposeTextLayout( p_textLayout );
794         }
795
796         CGContextRelease( p_context );
797     }
798     if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
799
800     return p_offScreen;
801 }
802
803 static int GetFontSize( filter_t *p_filter )
804 {
805     return p_filter->fmt_out.video.i_height / __MAX(1, var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" ));
806 }
807
808 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniChar *psz_utf16_str,
809                        uint32_t i_text_len, uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles )
810 {
811     offscreen_bitmap_t *p_offScreen = NULL;
812     int      i_textblock_height = 0;
813
814     int i_width = p_filter->fmt_out.video.i_visible_width;
815     int i_height = p_filter->fmt_out.video.i_visible_height;
816     int i_text_align = p_region->i_align & 0x3;
817
818     if( !psz_utf16_str )
819     {
820         msg_Err( p_filter, "Invalid argument to RenderYUVA" );
821         return VLC_EGENERIC;
822     }
823
824     p_offScreen = Compose( i_text_align, psz_utf16_str, i_text_len,
825                            i_runs, pi_run_lengths, pp_styles,
826                            i_width, i_height, &i_textblock_height );
827
828     if( !p_offScreen )
829     {
830         msg_Err( p_filter, "No offscreen buffer" );
831         return VLC_EGENERIC;
832     }
833
834     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
835     video_format_t fmt;
836     int x, y, i_offset, i_pitch;
837     uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
838
839     // Create a new subpicture region
840     memset( &fmt, 0, sizeof(video_format_t) );
841     fmt.i_chroma = VLC_CODEC_YUVA;
842     fmt.i_aspect = 0;
843     fmt.i_width = fmt.i_visible_width = i_width;
844     fmt.i_height = fmt.i_visible_height = i_textblock_height + VERTICAL_MARGIN * 2;
845     fmt.i_x_offset = fmt.i_y_offset = 0;
846
847     p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect );
848     if( !p_region->p_picture )
849         return VLC_EGENERIC;
850     p_region->fmt = fmt;
851
852     p_dst_y = p_region->p_picture->Y_PIXELS;
853     p_dst_u = p_region->p_picture->U_PIXELS;
854     p_dst_v = p_region->p_picture->V_PIXELS;
855     p_dst_a = p_region->p_picture->A_PIXELS;
856     i_pitch = p_region->p_picture->A_PITCH;
857
858     i_offset = VERTICAL_MARGIN *i_pitch;
859     for( y=0; y<i_textblock_height; y++)
860     {
861         for( x=0; x<i_width; x++)
862         {
863             int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel     ];
864             int i_red   = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
865             int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
866             int i_blue  = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
867
868             i_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
869                               802 * i_blue + 4096 + 131072 ) >> 13, 235);
870             i_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
871                              3598 * i_blue + 4096 + 1048576) >> 13, 240);
872             i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
873                               -585 * i_blue + 4096 + 1048576) >> 13, 240);
874
875             p_dst_y[ i_offset + x ] = i_y;
876             p_dst_u[ i_offset + x ] = i_u;
877             p_dst_v[ i_offset + x ] = i_v;
878             p_dst_a[ i_offset + x ] = i_alpha;
879         }
880         i_offset += i_pitch;
881     }
882
883     free( p_offScreen->p_data );
884     free( p_offScreen );
885
886     return VLC_SUCCESS;
887 }