]> git.sesse.net Git - vlc/blob - modules/misc/quartztext.c
9521efa06815053b3373254ecc51eb25120bb5fb
[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 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
42 #include <math.h>
43
44 #include <Carbon/Carbon.h>
45
46 #define DEFAULT_FONT           "Arial Black"
47 #define DEFAULT_FONT_COLOR     0xffffff
48 #define DEFAULT_REL_FONT_SIZE  16
49
50 #define VERTICAL_MARGIN 3
51 #define HORIZONTAL_MARGIN 10
52
53 //////////////////////////////////////////////////////////////////////////////
54 // Local prototypes
55 //////////////////////////////////////////////////////////////////////////////
56 static int  Create ( vlc_object_t * );
57 static void Destroy( vlc_object_t * );
58
59 static int LoadFontsFromAttachments( filter_t *p_filter );
60
61 static int RenderText( filter_t *, subpicture_region_t *,
62                        subpicture_region_t * );
63 static int RenderHtml( filter_t *, subpicture_region_t *,
64                        subpicture_region_t * );
65
66 static int GetFontSize( filter_t *p_filter );
67 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
68                        UniChar *psz_utfString, uint32_t i_text_len,
69                        uint32_t i_runs, uint32_t *pi_run_lengths,
70                        ATSUStyle *pp_styles );
71 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size,
72                               uint32_t i_font_color,
73                               bool b_bold, bool b_italic,
74                               bool b_uline );
75 //////////////////////////////////////////////////////////////////////////////
76 // Module descriptor
77 //////////////////////////////////////////////////////////////////////////////
78
79 // The preferred way to set font style information is for it to come from the
80 // subtitle file, and for it to be rendered with RenderHtml instead of
81 // RenderText. This module, unlike Freetype, doesn't provide any options to
82 // override the fallback font selection used when this style information is
83 // absent.
84 #define FONT_TEXT N_("Font")
85 #define FONT_LONGTEXT N_("Name for the font you want to use")
86 #define FONTSIZER_TEXT N_("Relative font size")
87 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
88     "fonts that will be rendered on the video. If absolute font size is set, "\
89     "relative size will be overriden." )
90 #define COLOR_TEXT N_("Text default color")
91 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
92     "the video. This must be an hexadecimal (like HTML colors). The first two "\
93     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
94     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
95
96 static const int pi_color_values[] = {
97   0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
98   0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
99   0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
100
101 static const char *const ppsz_color_descriptions[] = {
102   N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
103   N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
104   N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
105
106 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
107 static const char *const ppsz_sizes_text[] = {
108     N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
109
110 vlc_module_begin();
111     set_shortname( N_("Mac Text renderer"));
112     set_description( N_("Quartz font renderer") );
113     set_category( CAT_VIDEO );
114     set_subcategory( SUBCAT_VIDEO_SUBPIC );
115
116     add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
117               false );
118     add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT,
119                  FONTSIZER_LONGTEXT, false );
120         change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
121     add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
122                  COLOR_LONGTEXT, false );
123         change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
124     set_capability( "text renderer", 150 );
125     add_shortcut( "text" );
126     set_callbacks( Create, Destroy );
127 vlc_module_end();
128
129 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
130 struct offscreen_bitmap_t
131 {
132     uint8_t       *p_data;
133     int            i_bitsPerChannel;
134     int            i_bitsPerPixel;
135     int            i_bytesPerPixel;
136     int            i_bytesPerRow;
137 };
138
139 //////////////////////////////////////////////////////////////////////////////
140 // filter_sys_t: quartztext local data
141 //////////////////////////////////////////////////////////////////////////////
142 // This structure is part of the video output thread descriptor.
143 // It describes the freetype specific properties of an output thread.
144 //////////////////////////////////////////////////////////////////////////////
145 struct filter_sys_t
146 {
147     char          *psz_font_name;
148     uint8_t        i_font_opacity;
149     int            i_font_color;
150     int            i_font_size;
151
152     ATSFontContainerRef    *p_fonts;
153     int                     i_fonts;
154 };
155
156 #define UCHAR UniChar
157 #define TR_DEFAULT_FONT p_sys->psz_font_name
158 #define TR_DEFAULT_COLOR p_sys->i_font_color
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 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
671         *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
672 #else
673         *pp_colorSpace = CreateGenericRGBColorSpace();
674 #endif
675
676         if( p_bitmap->p_data && *pp_colorSpace )
677         {
678             p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
679                                 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
680                                 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
681         }
682         if( p_context )
683         {
684 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_1
685             // OS X 10.1 doesn't support weak linking of this call which is only available
686             // int 10.4 and later
687             if( CGContextSetAllowsAntialiasing != NULL )
688             {
689                 CGContextSetAllowsAntialiasing( p_context, true );
690             }
691 #endif
692         }
693         *pp_memory = p_bitmap;
694     }
695
696     return p_context;
697 }
698
699 static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len,
700                                     uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles,
701                                     int i_width, int i_height, int *pi_textblock_height )
702 {
703     offscreen_bitmap_t  *p_offScreen  = NULL;
704     CGColorSpaceRef      p_colorSpace = NULL;
705     CGContextRef         p_context = NULL;
706
707     p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
708
709     if( p_context )
710     {
711         ATSUTextLayout p_textLayout;
712         OSStatus status = noErr;
713
714         status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
715                                                   i_runs,
716                                                   (const UniCharCount *) pi_run_lengths,
717                                                   pp_styles,
718                                                   &p_textLayout );
719         if( status == noErr )
720         {
721             // Attach our offscreen Image Graphics Context to the text style
722             // and setup the line alignment (have to specify the line width
723             // also in order for our chosen alignment to work)
724
725             Fract   alignment  = kATSUStartAlignment;
726             Fixed   line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
727
728             ATSUAttributeTag tags[]        = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
729             ByteCount sizes[]              = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
730             ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
731
732             int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
733
734             if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
735             {
736                 alignment = kATSUEndAlignment;
737             }
738             else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
739             {
740                 alignment = kATSUCenterAlignment;
741             }
742
743             ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
744
745             // let ATSUI deal with characters not-in-our-specified-font
746             ATSUSetTransientFontMatching( p_textLayout, true );
747
748             Fixed x = Long2Fix( HORIZONTAL_MARGIN );
749             Fixed y = Long2Fix( i_height );
750
751             // Set the line-breaks and draw individual lines
752             uint32_t i_start = 0;
753             uint32_t i_end = i_text_len;
754
755             // Set up black outlining of the text --
756             CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
757             CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
758             CGContextSetShadow( p_context, CGSizeMake( 0, 0 ), 5 );
759             float black_components[4] = {0, 0, 0, 1};
760             CGContextSetShadowWithColor (p_context, CGSizeMake( 0, 0 ), 5, CGColorCreate( CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB ), black_components ));
761             do
762             {
763                 // ATSUBreakLine will automatically pick up any manual '\n's also
764                 status = ATSUBreakLine( p_textLayout, i_start, line_width, true, (UniCharArrayOffset *) &i_end );
765                 if( ( status == noErr ) || ( status == kATSULineBreakInWord ) )
766                 {
767                     Fixed     ascent;
768                     Fixed     descent;
769                     uint32_t  i_actualSize;
770
771                     // Come down far enough to fit the height of this line --
772                     ATSUGetLineControl( p_textLayout, i_start, kATSULineAscentTag,
773                                     sizeof( Fixed ), &ascent, (ByteCount *) &i_actualSize );
774
775                     // Quartz uses an upside-down co-ordinate space -> y values decrease as
776                     // you move down the page
777                     y -= ascent;
778
779                     // Set the outlining for this line to be dependent on the size of the line -
780                     // make it about 5% of the ascent, with a minimum at 1.0
781                     float f_thickness = FixedToFloat( ascent ) * 0.05;
782                     CGContextSetLineWidth( p_context, (( f_thickness < 1.0 ) ? 1.0 : f_thickness ));
783                     ATSUDrawText( p_textLayout, i_start, i_end - i_start, x, y );
784
785                     // and now prepare for the next line by coming down far enough for our
786                     // descent
787                     ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag,
788                                     sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize );
789                     y -= descent;
790
791                     i_start = i_end;
792                 }
793                 else
794                     break;
795             }
796             while( i_end < i_text_len );
797
798             *pi_textblock_height = i_height - Fix2Long( y );
799             CGContextFlush( p_context );
800
801             ATSUDisposeTextLayout( p_textLayout );
802         }
803
804         CGContextRelease( p_context );
805     }
806     if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
807
808     return p_offScreen;
809 }
810
811 static int GetFontSize( filter_t *p_filter )
812 {
813     return p_filter->fmt_out.video.i_height / __MAX(1, var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" ));
814 }
815
816 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniChar *psz_utf16_str,
817                        uint32_t i_text_len, uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles )
818 {
819     offscreen_bitmap_t *p_offScreen = NULL;
820     int      i_textblock_height = 0;
821
822     int i_width = p_filter->fmt_out.video.i_visible_width;
823     int i_height = p_filter->fmt_out.video.i_visible_height;
824     int i_text_align = p_region->i_align & 0x3;
825
826     if( !psz_utf16_str )
827     {
828         msg_Err( p_filter, "Invalid argument to RenderYUVA" );
829         return VLC_EGENERIC;
830     }
831
832     p_offScreen = Compose( i_text_align, psz_utf16_str, i_text_len,
833                            i_runs, pi_run_lengths, pp_styles,
834                            i_width, i_height, &i_textblock_height );
835
836     if( !p_offScreen )
837     {
838         msg_Err( p_filter, "No offscreen buffer" );
839         return VLC_EGENERIC;
840     }
841
842     uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
843     video_format_t fmt;
844     int x, y, i_offset, i_pitch;
845     uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
846
847     // Create a new subpicture region
848     memset( &fmt, 0, sizeof(video_format_t) );
849     fmt.i_chroma = VLC_FOURCC('Y','U','V','A');
850     fmt.i_aspect = 0;
851     fmt.i_width = fmt.i_visible_width = i_width;
852     fmt.i_height = fmt.i_visible_height = i_textblock_height + VERTICAL_MARGIN * 2;
853     fmt.i_x_offset = fmt.i_y_offset = 0;
854
855     p_region->p_picture = picture_New( fmt.i_chroma, fmt.i_width, fmt.i_height, fmt.i_aspect );
856     if( !p_region->p_picture )
857         return VLC_EGENERIC;
858     p_region->fmt = fmt;
859
860     p_dst_y = p_region->p_picture->Y_PIXELS;
861     p_dst_u = p_region->p_picture->U_PIXELS;
862     p_dst_v = p_region->p_picture->V_PIXELS;
863     p_dst_a = p_region->p_picture->A_PIXELS;
864     i_pitch = p_region->p_picture->A_PITCH;
865
866     i_offset = VERTICAL_MARGIN *i_pitch;
867     for( y=0; y<i_textblock_height; y++)
868     {
869         for( x=0; x<i_width; x++)
870         {
871             int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel     ];
872             int i_red   = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
873             int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
874             int i_blue  = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
875
876             i_y = (uint8_t)__MIN(abs( 2104 * i_red  + 4130 * i_green +
877                               802 * i_blue + 4096 + 131072 ) >> 13, 235);
878             i_u = (uint8_t)__MIN(abs( -1214 * i_red  + -2384 * i_green +
879                              3598 * i_blue + 4096 + 1048576) >> 13, 240);
880             i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
881                               -585 * i_blue + 4096 + 1048576) >> 13, 240);
882
883             p_dst_y[ i_offset + x ] = i_y;
884             p_dst_u[ i_offset + x ] = i_u;
885             p_dst_v[ i_offset + x ] = i_v;
886             p_dst_a[ i_offset + x ] = i_alpha;
887         }
888         i_offset += i_pitch;
889     }
890
891     free( p_offScreen->p_data );
892     free( p_offScreen );
893
894     return VLC_SUCCESS;
895 }