NEWS updates
[vlc/solaris.git] / modules / misc / quartztext.c
blob732f276f2c1d90a2af50fafd6e513ef089f332e6
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$
7 * Authors: Bernie Purcell <bitmap@videolan.org>
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.
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.
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 *****************************************************************************/
24 //////////////////////////////////////////////////////////////////////////////
25 // Preamble
26 //////////////////////////////////////////////////////////////////////////////
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_osd.h>
35 #include <vlc_stream.h>
36 #include <vlc_xml.h>
37 #include <vlc_input.h>
39 // Fix ourselves ColorSync headers that gets included in ApplicationServices.
40 #define DisposeCMProfileIterateUPP(a) DisposeCMProfileIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
41 #define DisposeCMMIterateUPP(a) DisposeCMMIterateUPP(CMProfileIterateUPP userUPP __attribute__((unused)))
42 #define __MACHINEEXCEPTIONS__
43 #include <ApplicationServices/ApplicationServices.h>
45 #define DEFAULT_FONT "Arial Black"
46 #define DEFAULT_FONT_COLOR 0xffffff
47 #define DEFAULT_REL_FONT_SIZE 16
49 #define VERTICAL_MARGIN 3
50 #define HORIZONTAL_MARGIN 10
52 //////////////////////////////////////////////////////////////////////////////
53 // Local prototypes
54 //////////////////////////////////////////////////////////////////////////////
55 static int Create ( vlc_object_t * );
56 static void Destroy( vlc_object_t * );
58 static int LoadFontsFromAttachments( filter_t *p_filter );
60 static int RenderText( filter_t *, subpicture_region_t *,
61 subpicture_region_t * );
62 static int RenderHtml( filter_t *, subpicture_region_t *,
63 subpicture_region_t * );
65 static int GetFontSize( filter_t *p_filter );
66 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
67 CFMutableAttributedStringRef p_attrString );
69 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
70 bool b_bold, bool b_italic, bool b_underline,
71 CFRange p_range, CFMutableAttributedStringRef p_attrString );
73 //////////////////////////////////////////////////////////////////////////////
74 // Module descriptor
75 //////////////////////////////////////////////////////////////////////////////
77 // The preferred way to set font style information is for it to come from the
78 // subtitle file, and for it to be rendered with RenderHtml instead of
79 // RenderText.
80 #define FONT_TEXT N_("Font")
81 #define FONT_LONGTEXT N_("Name for the font you want to use")
82 #define FONTSIZER_TEXT N_("Relative font size")
83 #define FONTSIZER_LONGTEXT N_("This is the relative default size of the " \
84 "fonts that will be rendered on the video. If absolute font size is set, "\
85 "relative size will be overriden." )
86 #define COLOR_TEXT N_("Text default color")
87 #define COLOR_LONGTEXT N_("The color of the text that will be rendered on "\
88 "the video. This must be an hexadecimal (like HTML colors). The first two "\
89 "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
90 " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
92 static const int pi_color_values[] = {
93 0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
94 0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
95 0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
97 static const char *const ppsz_color_descriptions[] = {
98 N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
99 N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
100 N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
102 static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
103 static const char *const ppsz_sizes_text[] = {
104 N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
106 vlc_module_begin ()
107 set_shortname( N_("Text renderer for Mac"))
108 set_description( N_("CoreText font renderer") )
109 set_category( CAT_VIDEO )
110 set_subcategory( SUBCAT_VIDEO_SUBPIC )
112 add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
113 false )
114 add_integer( "quartztext-rel-fontsize", DEFAULT_REL_FONT_SIZE, NULL, FONTSIZER_TEXT,
115 FONTSIZER_LONGTEXT, false )
116 change_integer_list( pi_sizes, ppsz_sizes_text, NULL );
117 add_integer( "quartztext-color", 0x00FFFFFF, NULL, COLOR_TEXT,
118 COLOR_LONGTEXT, false )
119 change_integer_list( pi_color_values, ppsz_color_descriptions, NULL );
120 set_capability( "text renderer", 150 )
121 add_shortcut( "text" )
122 set_callbacks( Create, Destroy )
123 vlc_module_end ()
125 typedef struct font_stack_t font_stack_t;
126 struct font_stack_t
128 char *psz_name;
129 int i_size;
130 uint32_t i_color; // ARGB
132 font_stack_t *p_next;
135 typedef struct
137 int i_font_size;
138 uint32_t i_font_color; /* ARGB */
139 bool b_italic;
140 bool b_bold;
141 bool b_underline;
142 char *psz_fontname;
143 } ft_style_t;
145 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
146 struct offscreen_bitmap_t
148 uint8_t *p_data;
149 int i_bitsPerChannel;
150 int i_bitsPerPixel;
151 int i_bytesPerPixel;
152 int i_bytesPerRow;
155 //////////////////////////////////////////////////////////////////////////////
156 // filter_sys_t: quartztext local data
157 //////////////////////////////////////////////////////////////////////////////
158 // This structure is part of the video output thread descriptor.
159 // It describes the freetype specific properties of an output thread.
160 //////////////////////////////////////////////////////////////////////////////
161 struct filter_sys_t
163 char *psz_font_name;
164 uint8_t i_font_opacity;
165 int i_font_color;
166 int i_font_size;
168 ATSFontContainerRef *p_fonts;
169 int i_fonts;
172 //////////////////////////////////////////////////////////////////////////////
173 // Create: allocates osd-text video thread output method
174 //////////////////////////////////////////////////////////////////////////////
175 // This function allocates and initializes a Clone vout method.
176 //////////////////////////////////////////////////////////////////////////////
177 static int Create( vlc_object_t *p_this )
179 filter_t *p_filter = (filter_t *)p_this;
180 filter_sys_t *p_sys;
182 // Allocate structure
183 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
184 if( !p_sys )
185 return VLC_ENOMEM;
186 p_sys->psz_font_name = var_CreateGetString( p_this, "quartztext-font" );
187 p_sys->i_font_opacity = 255;
188 p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
189 p_sys->i_font_size = GetFontSize( p_filter );
191 p_filter->pf_render_text = RenderText;
192 p_filter->pf_render_html = RenderHtml;
194 p_sys->p_fonts = NULL;
195 p_sys->i_fonts = 0;
197 LoadFontsFromAttachments( p_filter );
199 return VLC_SUCCESS;
202 //////////////////////////////////////////////////////////////////////////////
203 // Destroy: destroy Clone video thread output method
204 //////////////////////////////////////////////////////////////////////////////
205 // Clean up all data and library connections
206 //////////////////////////////////////////////////////////////////////////////
207 static void Destroy( vlc_object_t *p_this )
209 filter_t *p_filter = (filter_t *)p_this;
210 filter_sys_t *p_sys = p_filter->p_sys;
212 if( p_sys->p_fonts )
214 int k;
216 for( k = 0; k < p_sys->i_fonts; k++ )
218 ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
221 free( p_sys->p_fonts );
224 free( p_sys->psz_font_name );
225 free( p_sys );
228 //////////////////////////////////////////////////////////////////////////////
229 // Make any TTF/OTF fonts present in the attachments of the media file
230 // available to the Quartz engine for text rendering
231 //////////////////////////////////////////////////////////////////////////////
232 static int LoadFontsFromAttachments( filter_t *p_filter )
234 filter_sys_t *p_sys = p_filter->p_sys;
235 input_thread_t *p_input;
236 input_attachment_t **pp_attachments;
237 int i_attachments_cnt;
238 int k;
239 int rv = VLC_SUCCESS;
241 p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
242 if( ! p_input )
243 return VLC_EGENERIC;
245 if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
247 vlc_object_release(p_input);
248 return VLC_EGENERIC;
251 p_sys->i_fonts = 0;
252 p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
253 if(! p_sys->p_fonts )
254 rv = VLC_ENOMEM;
256 for( k = 0; k < i_attachments_cnt; k++ )
258 input_attachment_t *p_attach = pp_attachments[k];
260 if( p_sys->p_fonts )
262 if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
263 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
264 ( p_attach->i_data > 0 ) &&
265 ( p_attach->p_data != NULL ) )
267 ATSFontContainerRef container;
269 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
270 p_attach->i_data,
271 kATSFontContextLocal,
272 kATSFontFormatUnspecified,
273 NULL,
274 kATSOptionFlagsDefault,
275 &container ))
277 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
281 vlc_input_attachment_Delete( p_attach );
283 free( pp_attachments );
285 vlc_object_release(p_input);
287 return rv;
290 static char *EliminateCRLF( char *psz_string )
292 char *p;
293 char *q;
295 for( p = psz_string; p && *p; p++ )
297 if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
299 for( q = p + 1; *q; q++ )
300 *( q - 1 ) = *q;
302 *( q - 1 ) = '\0';
305 return psz_string;
308 // Renders a text subpicture region into another one.
309 // It is used as pf_add_string callback in the vout method by this module
310 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
311 subpicture_region_t *p_region_in )
313 filter_sys_t *p_sys = p_filter->p_sys;
314 char *psz_string;
315 int i_font_alpha, i_font_size;
316 uint32_t i_font_color;
317 bool b_bold, b_uline, b_italic;
318 vlc_value_t val;
319 int i_scale = 1000;
320 b_bold = b_uline = b_italic = FALSE;
322 p_sys->i_font_size = GetFontSize( p_filter );
324 // Sanity check
325 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
326 psz_string = p_region_in->psz_text;
327 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
329 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
330 i_scale = val.i_int;
332 if( p_region_in->p_style )
334 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
335 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
336 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
337 if( p_region_in->p_style->i_style_flags )
339 if( p_region_in->p_style->i_style_flags & STYLE_BOLD )
340 b_bold = TRUE;
341 if( p_region_in->p_style->i_style_flags & STYLE_ITALIC )
342 b_italic = TRUE;
343 if( p_region_in->p_style->i_style_flags & STYLE_UNDERLINE )
344 b_uline = TRUE;
347 else
349 i_font_color = p_sys->i_font_color;
350 i_font_alpha = 255 - p_sys->i_font_opacity;
351 i_font_size = p_sys->i_font_size;
354 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
356 if( i_font_size <= 0 )
358 msg_Warn( p_filter, "invalid fontsize, using 12" );
359 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
360 i_font_size = 12 * val.i_int / 1000;
361 else
362 i_font_size = 12;
365 p_region_out->i_x = p_region_in->i_x;
366 p_region_out->i_y = p_region_in->i_y;
368 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
370 if( p_attrString )
372 CFStringRef p_cfString;
373 int len;
375 EliminateCRLF( psz_string);
376 p_cfString = CFStringCreateWithCString( NULL, psz_string, kCFStringEncodingUTF8 );
377 CFAttributedStringReplaceString( p_attrString, CFRangeMake(0, 0), p_cfString );
378 CFRelease( p_cfString );
379 len = CFAttributedStringGetLength( p_attrString );
381 setFontAttibutes( p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline,
382 CFRangeMake( 0, len ), p_attrString);
384 RenderYUVA( p_filter, p_region_out, p_attrString );
386 CFRelease(p_attrString);
388 return VLC_SUCCESS;
392 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
393 uint32_t i_color )
395 font_stack_t *p_new;
397 if( !p_font )
398 return VLC_EGENERIC;
400 p_new = malloc( sizeof( font_stack_t ) );
401 if( ! p_new )
402 return VLC_ENOMEM;
404 p_new->p_next = NULL;
406 if( psz_name )
407 p_new->psz_name = strdup( psz_name );
408 else
409 p_new->psz_name = NULL;
411 p_new->i_size = i_size;
412 p_new->i_color = i_color;
414 if( !*p_font )
416 *p_font = p_new;
418 else
420 font_stack_t *p_last;
422 for( p_last = *p_font;
423 p_last->p_next;
424 p_last = p_last->p_next )
427 p_last->p_next = p_new;
429 return VLC_SUCCESS;
432 static int PopFont( font_stack_t **p_font )
434 font_stack_t *p_last, *p_next_to_last;
436 if( !p_font || !*p_font )
437 return VLC_EGENERIC;
439 p_next_to_last = NULL;
440 for( p_last = *p_font;
441 p_last->p_next;
442 p_last = p_last->p_next )
444 p_next_to_last = p_last;
447 if( p_next_to_last )
448 p_next_to_last->p_next = NULL;
449 else
450 *p_font = NULL;
452 free( p_last->psz_name );
453 free( p_last );
455 return VLC_SUCCESS;
458 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
459 uint32_t *i_color )
461 font_stack_t *p_last;
463 if( !p_font || !*p_font )
464 return VLC_EGENERIC;
466 for( p_last=*p_font;
467 p_last->p_next;
468 p_last=p_last->p_next )
471 *psz_name = p_last->psz_name;
472 *i_size = p_last->i_size;
473 *i_color = p_last->i_color;
475 return VLC_SUCCESS;
478 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
479 font_stack_t **p_fonts, int i_scale )
481 int rv;
482 char *psz_fontname = NULL;
483 uint32_t i_font_color = 0xffffff;
484 int i_font_alpha = 0;
485 int i_font_size = 24;
487 // Default all attributes to the top font in the stack -- in case not
488 // all attributes are specified in the sub-font
489 if( VLC_SUCCESS == PeekFont( p_fonts,
490 &psz_fontname,
491 &i_font_size,
492 &i_font_color ))
494 psz_fontname = strdup( psz_fontname );
495 i_font_size = i_font_size * 1000 / i_scale;
497 i_font_alpha = (i_font_color >> 24) & 0xff;
498 i_font_color &= 0x00ffffff;
500 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
502 char *psz_name = xml_ReaderName( p_xml_reader );
503 char *psz_value = xml_ReaderValue( p_xml_reader );
505 if( psz_name && psz_value )
507 if( !strcasecmp( "face", psz_name ) )
509 free( psz_fontname );
510 psz_fontname = strdup( psz_value );
512 else if( !strcasecmp( "size", psz_name ) )
514 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
516 int i_value = atoi( psz_value );
518 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
519 i_font_size += ( i_value * i_font_size ) / 10;
520 else if( i_value < -5 )
521 i_font_size = - i_value;
522 else if( i_value > 5 )
523 i_font_size = i_value;
525 else
526 i_font_size = atoi( psz_value );
528 else if( !strcasecmp( "color", psz_name ) &&
529 ( psz_value[0] == '#' ) )
531 i_font_color = strtol( psz_value + 1, NULL, 16 );
532 i_font_color &= 0x00ffffff;
534 else if( !strcasecmp( "alpha", psz_name ) &&
535 ( psz_value[0] == '#' ) )
537 i_font_alpha = strtol( psz_value + 1, NULL, 16 );
538 i_font_alpha &= 0xff;
540 free( psz_name );
541 free( psz_value );
544 rv = PushFont( p_fonts,
545 psz_fontname,
546 i_font_size * i_scale / 1000,
547 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
549 free( psz_fontname );
551 return rv;
554 static void setFontAttibutes( char *psz_fontname, int i_font_size, uint32_t i_font_color,
555 bool b_bold, bool b_italic, bool b_underline,
556 CFRange p_range, CFMutableAttributedStringRef p_attrString )
558 CFStringRef p_cfString;
559 CTFontRef p_font;
561 // Handle font name and size
562 p_cfString = CFStringCreateWithCString( NULL,
563 psz_fontname,
564 kCFStringEncodingUTF8 );
565 p_font = CTFontCreateWithName( p_cfString,
566 (float)i_font_size,
567 NULL );
568 CFRelease( p_cfString );
569 CFAttributedStringSetAttribute( p_attrString,
570 p_range,
571 kCTFontAttributeName,
572 p_font );
573 CFRelease( p_font );
575 // Handle Underline
576 SInt32 _uline;
577 if( b_underline )
578 _uline = kCTUnderlineStyleSingle;
579 else
580 _uline = kCTUnderlineStyleNone;
582 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &_uline);
583 CFAttributedStringSetAttribute( p_attrString,
584 p_range,
585 kCTUnderlineStyleAttributeName,
586 underline );
587 CFRelease( underline );
589 // Handle Bold
590 float _weight;
591 if( b_bold )
592 _weight = 0.5;
593 else
594 _weight = 0.0;
596 CFNumberRef weight = CFNumberCreate(NULL, kCFNumberFloatType, &_weight);
597 CFAttributedStringSetAttribute( p_attrString,
598 p_range,
599 kCTFontWeightTrait,
600 weight );
601 CFRelease( weight );
603 // Handle Italic
604 float _slant;
605 if( b_italic )
606 _slant = 1.0;
607 else
608 _slant = 0.0;
610 CFNumberRef slant = CFNumberCreate(NULL, kCFNumberFloatType, &_slant);
611 CFAttributedStringSetAttribute( p_attrString,
612 p_range,
613 kCTFontSlantTrait,
614 slant );
615 CFRelease( slant );
617 // Handle foreground colour
618 CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
619 CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
620 (float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
621 (float)((i_font_color & 0x000000ff) ) / 255.0,
622 (float)(255-((i_font_color & 0xff000000) >> 24)) / 255.0 };
623 CGColorRef fg_text = CGColorCreate(rgbColorSpace, components);
624 CGColorSpaceRelease(rgbColorSpace);
626 CFAttributedStringSetAttribute( p_attrString,
627 p_range,
628 kCTForegroundColorAttributeName,
629 fg_text );
630 CFRelease( fg_text );
634 static void GetAttrStrFromFontStack( font_stack_t **p_fonts,
635 bool b_bold, bool b_italic, bool b_uline,
636 CFRange p_range, CFMutableAttributedStringRef p_attrString )
638 char *psz_fontname = NULL;
639 int i_font_size = 0;
640 uint32_t i_font_color = 0;
642 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
643 &i_font_color ))
645 setFontAttibutes( psz_fontname,
646 i_font_size,
647 i_font_color,
648 b_bold, b_italic, b_uline,
649 p_range,
650 p_attrString );
654 static int ProcessNodes( filter_t *p_filter,
655 xml_reader_t *p_xml_reader,
656 text_style_t *p_font_style,
657 CFMutableAttributedStringRef p_attrString )
659 int rv = VLC_SUCCESS;
660 filter_sys_t *p_sys = p_filter->p_sys;
661 font_stack_t *p_fonts = NULL;
662 vlc_value_t val;
663 int i_scale = 1000;
665 char *psz_node = NULL;
667 bool b_italic = false;
668 bool b_bold = false;
669 bool b_uline = false;
671 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
672 i_scale = val.i_int;
674 if( p_font_style )
676 rv = PushFont( &p_fonts,
677 p_font_style->psz_fontname,
678 p_font_style->i_font_size * i_scale / 1000,
679 (p_font_style->i_font_color & 0xffffff) |
680 ((p_font_style->i_font_alpha & 0xff) << 24) );
682 if( p_font_style->i_style_flags & STYLE_BOLD )
683 b_bold = true;
684 if( p_font_style->i_style_flags & STYLE_ITALIC )
685 b_italic = true;
686 if( p_font_style->i_style_flags & STYLE_UNDERLINE )
687 b_uline = true;
689 else
691 rv = PushFont( &p_fonts,
692 p_sys->psz_font_name,
693 p_sys->i_font_size,
694 p_sys->i_font_color );
696 if( rv != VLC_SUCCESS )
697 return rv;
699 while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
701 switch ( xml_ReaderNodeType( p_xml_reader ) )
703 case XML_READER_NONE:
704 break;
705 case XML_READER_ENDELEM:
706 psz_node = xml_ReaderName( p_xml_reader );
708 if( psz_node )
710 if( !strcasecmp( "font", psz_node ) )
711 PopFont( &p_fonts );
712 else if( !strcasecmp( "b", psz_node ) )
713 b_bold = false;
714 else if( !strcasecmp( "i", psz_node ) )
715 b_italic = false;
716 else if( !strcasecmp( "u", psz_node ) )
717 b_uline = false;
719 free( psz_node );
721 break;
722 case XML_READER_STARTELEM:
723 psz_node = xml_ReaderName( p_xml_reader );
724 if( psz_node )
726 if( !strcasecmp( "font", psz_node ) )
727 rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
728 else if( !strcasecmp( "b", psz_node ) )
729 b_bold = true;
730 else if( !strcasecmp( "i", psz_node ) )
731 b_italic = true;
732 else if( !strcasecmp( "u", psz_node ) )
733 b_uline = true;
734 else if( !strcasecmp( "br", psz_node ) )
736 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
737 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), CFSTR("\n") );
739 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
740 CFRangeMake( 0, 1 ),
741 p_attrnode );
742 CFAttributedStringReplaceAttributedString( p_attrString,
743 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
744 p_attrnode);
745 CFRelease( p_attrnode );
747 free( psz_node );
749 break;
750 case XML_READER_TEXT:
751 psz_node = xml_ReaderValue( p_xml_reader );
752 if( psz_node )
754 CFStringRef p_cfString;
755 int len;
757 // Turn any multiple-whitespaces into single spaces
758 char *s = strpbrk( psz_node, "\t\r\n " );
759 while( s )
761 int i_whitespace = strspn( s, "\t\r\n " );
763 if( i_whitespace > 1 )
764 memmove( &s[1],
765 &s[i_whitespace],
766 strlen( s ) - i_whitespace + 1 );
767 *s++ = ' ';
769 s = strpbrk( s, "\t\r\n " );
773 CFMutableAttributedStringRef p_attrnode = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
774 p_cfString = CFStringCreateWithCString( NULL, psz_node, kCFStringEncodingUTF8 );
775 CFAttributedStringReplaceString( p_attrnode, CFRangeMake(0, 0), p_cfString );
776 CFRelease( p_cfString );
777 len = CFAttributedStringGetLength( p_attrnode );
779 GetAttrStrFromFontStack( &p_fonts, b_bold, b_italic, b_uline,
780 CFRangeMake( 0, len ),
781 p_attrnode );
783 CFAttributedStringReplaceAttributedString( p_attrString,
784 CFRangeMake(CFAttributedStringGetLength(p_attrString), 0),
785 p_attrnode);
786 CFRelease( p_attrnode );
787 free( psz_node );
789 break;
793 while( VLC_SUCCESS == PopFont( &p_fonts ) );
795 return rv;
798 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
799 subpicture_region_t *p_region_in )
801 int rv = VLC_SUCCESS;
802 stream_t *p_sub = NULL;
803 xml_t *p_xml = NULL;
804 xml_reader_t *p_xml_reader = NULL;
806 if( !p_region_in || !p_region_in->psz_html )
807 return VLC_EGENERIC;
809 /* Reset the default fontsize in case screen metrics have changed */
810 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
812 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
813 (uint8_t *) p_region_in->psz_html,
814 strlen( p_region_in->psz_html ),
815 true );
816 if( p_sub )
818 p_xml = xml_Create( p_filter );
819 if( p_xml )
821 bool b_karaoke = false;
823 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
824 if( p_xml_reader )
826 /* Look for Root Node */
827 if( xml_ReaderRead( p_xml_reader ) == 1 )
829 char *psz_node = xml_ReaderName( p_xml_reader );
831 if( !strcasecmp( "karaoke", psz_node ) )
833 /* We're going to have to render the text a number
834 * of times to show the progress marker on the text.
836 var_SetBool( p_filter, "text-rerender", true );
837 b_karaoke = true;
839 else if( !strcasecmp( "text", psz_node ) )
841 b_karaoke = false;
843 else
845 /* Only text and karaoke tags are supported */
846 xml_ReaderDelete( p_xml, p_xml_reader );
847 p_xml_reader = NULL;
848 rv = VLC_EGENERIC;
851 free( psz_node );
855 if( p_xml_reader )
857 int i_len;
859 CFMutableAttributedStringRef p_attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
860 rv = ProcessNodes( p_filter, p_xml_reader,
861 p_region_in->p_style, p_attrString );
863 i_len = CFAttributedStringGetLength( p_attrString );
865 p_region_out->i_x = p_region_in->i_x;
866 p_region_out->i_y = p_region_in->i_y;
868 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
870 RenderYUVA( p_filter, p_region_out, p_attrString );
872 CFRelease(p_attrString);
874 xml_ReaderDelete( p_xml, p_xml_reader );
876 xml_Delete( p_xml );
878 stream_Delete( p_sub );
881 return rv;
884 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
885 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
887 offscreen_bitmap_t *p_bitmap;
888 CGContextRef p_context = NULL;
890 p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
891 if( p_bitmap )
893 p_bitmap->i_bitsPerChannel = 8;
894 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
895 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
896 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
898 p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
900 *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
902 if( p_bitmap->p_data && *pp_colorSpace )
904 p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
905 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
906 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
908 if( p_context )
910 if( CGContextSetAllowsAntialiasing != NULL )
912 CGContextSetAllowsAntialiasing( p_context, true );
915 *pp_memory = p_bitmap;
918 return p_context;
921 static offscreen_bitmap_t *Compose( int i_text_align,
922 CFMutableAttributedStringRef p_attrString,
923 unsigned i_width,
924 unsigned i_height,
925 unsigned *pi_textblock_height )
927 offscreen_bitmap_t *p_offScreen = NULL;
928 CGColorSpaceRef p_colorSpace = NULL;
929 CGContextRef p_context = NULL;
931 p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
933 *pi_textblock_height = 0;
934 if( p_context )
936 float horiz_flush;
938 CGContextSetTextMatrix( p_context, CGAffineTransformIdentity );
940 if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
941 horiz_flush = 1.0;
942 else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
943 horiz_flush = 0.5;
944 else
945 horiz_flush = 0.0;
947 // Create the framesetter with the attributed string.
948 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(p_attrString);
949 if( framesetter )
951 CTFrameRef frame;
952 CGMutablePathRef p_path = CGPathCreateMutable();
953 CGRect p_bounds = CGRectMake( (float)HORIZONTAL_MARGIN,
954 (float)VERTICAL_MARGIN,
955 (float)(i_width - HORIZONTAL_MARGIN*2),
956 (float)(i_height - VERTICAL_MARGIN *2));
957 CGPathAddRect( p_path, NULL, p_bounds );
959 // Create the frame and draw it into the graphics context
960 frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), p_path, NULL);
962 CGPathRelease(p_path);
964 // Set up black outlining of the text --
965 CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
966 CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
968 if( frame != NULL )
970 CFArrayRef lines;
971 CGPoint penPosition;
973 lines = CTFrameGetLines( frame );
974 penPosition.y = i_height;
975 for (int i=0; i<CFArrayGetCount( lines ); i++)
977 CGFloat ascent, descent, leading;
979 CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
980 CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
982 // Set the outlining for this line to be dependant on the size of the line -
983 // make it about 5% of the ascent, with a minimum at 1.0
984 float f_thickness = ascent * 0.05;
985 CGContextSetLineWidth( p_context, (( f_thickness > 1.0 ) ? 1.0 : f_thickness ));
987 double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
988 penPosition.x = HORIZONTAL_MARGIN + penOffset;
989 penPosition.y -= ascent;
990 CGContextSetTextPosition( p_context, penPosition.x, penPosition.y );
991 CTLineDraw( line, p_context );
992 penPosition.y -= descent + leading;
995 *pi_textblock_height = i_height - penPosition.y;
997 CFRelease(frame);
999 CFRelease(framesetter);
1001 CGContextFlush( p_context );
1002 CGContextRelease( p_context );
1004 if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
1006 return p_offScreen;
1009 static int GetFontSize( filter_t *p_filter )
1011 return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
1014 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region,
1015 CFMutableAttributedStringRef p_attrString )
1017 offscreen_bitmap_t *p_offScreen = NULL;
1018 unsigned i_textblock_height = 0;
1020 unsigned i_width = p_filter->fmt_out.video.i_visible_width;
1021 unsigned i_height = p_filter->fmt_out.video.i_visible_height;
1022 unsigned i_text_align = p_region->i_align & 0x3;
1024 if( !p_attrString )
1026 msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1027 return VLC_EGENERIC;
1030 p_offScreen = Compose( i_text_align, p_attrString,
1031 i_width, i_height, &i_textblock_height );
1033 if( !p_offScreen )
1035 msg_Err( p_filter, "No offscreen buffer" );
1036 return VLC_EGENERIC;
1039 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1040 video_format_t fmt;
1041 int i_offset;
1042 unsigned x, y, i_pitch;
1043 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1045 // Create a new subpicture region
1046 memset( &fmt, 0, sizeof(video_format_t) );
1047 fmt.i_chroma = VLC_CODEC_YUVA;
1048 fmt.i_width = fmt.i_visible_width = i_width;
1049 fmt.i_height = fmt.i_visible_height = __MIN( i_height, i_textblock_height + VERTICAL_MARGIN * 2);
1050 fmt.i_x_offset = fmt.i_y_offset = 0;
1052 p_region->p_picture = picture_NewFromFormat( &fmt );
1053 if( !p_region->p_picture )
1054 return VLC_EGENERIC;
1055 p_region->fmt = fmt;
1057 p_dst_y = p_region->p_picture->Y_PIXELS;
1058 p_dst_u = p_region->p_picture->U_PIXELS;
1059 p_dst_v = p_region->p_picture->V_PIXELS;
1060 p_dst_a = p_region->p_picture->A_PIXELS;
1061 i_pitch = p_region->p_picture->A_PITCH;
1063 i_offset = (i_height + VERTICAL_MARGIN < fmt.i_height) ? VERTICAL_MARGIN *i_pitch : 0 ;
1064 for( y = 0; y < fmt.i_height; y++)
1066 for( x = 0; x < fmt.i_width; x++)
1068 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1069 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1070 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1071 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1073 i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
1074 802 * i_blue + 4096 + 131072 ) >> 13, 235);
1075 i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
1076 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1077 i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1078 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1080 p_dst_y[ i_offset + x ] = i_y;
1081 p_dst_u[ i_offset + x ] = i_u;
1082 p_dst_v[ i_offset + x ] = i_v;
1083 p_dst_a[ i_offset + x ] = i_alpha;
1085 i_offset += i_pitch;
1088 free( p_offScreen->p_data );
1089 free( p_offScreen );
1091 return VLC_SUCCESS;