1 /*****************************************************************************
2 * quartztext.c : Put text on the video, using Mac OS X Quartz Engine
3 *****************************************************************************
4 * Copyright (C) 2007 the VideoLAN team
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 //////////////////////////////////////////////////////////////////////////////
26 //////////////////////////////////////////////////////////////////////////////
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
36 #include <vlc_block.h>
37 #include <vlc_filter.h>
38 #include <vlc_stream.h>
40 #include <vlc_input.h>
44 #include <Carbon/Carbon.h>
46 #define DEFAULT_FONT "Arial Black"
47 #define DEFAULT_FONT_COLOR 0xffffff
48 #define DEFAULT_REL_FONT_SIZE 16
50 #define VERTICAL_MARGIN 3
51 #define HORIZONTAL_MARGIN 10
53 //////////////////////////////////////////////////////////////////////////////
55 //////////////////////////////////////////////////////////////////////////////
56 static int Create ( vlc_object_t * );
57 static void Destroy( vlc_object_t * );
59 static int LoadFontsFromAttachments( filter_t *p_filter );
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 * );
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,
75 //////////////////////////////////////////////////////////////////////////////
77 //////////////////////////////////////////////////////////////////////////////
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
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" )
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 };
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") };
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") };
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 );
116 add_string( "quartztext-font", DEFAULT_FONT, NULL, FONT_TEXT, FONT_LONGTEXT,
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 );
129 typedef struct font_stack_t font_stack_t;
134 uint32_t i_color; // ARGB
136 font_stack_t *p_next;
139 typedef struct offscreen_bitmap_t offscreen_bitmap_t;
140 struct offscreen_bitmap_t
143 int i_bitsPerChannel;
149 //////////////////////////////////////////////////////////////////////////////
150 // filter_sys_t: quartztext local data
151 //////////////////////////////////////////////////////////////////////////////
152 // This structure is part of the video output thread descriptor.
153 // It describes the freetype specific properties of an output thread.
154 //////////////////////////////////////////////////////////////////////////////
158 uint8_t i_font_opacity;
162 ATSFontContainerRef *p_fonts;
166 //////////////////////////////////////////////////////////////////////////////
167 // Create: allocates osd-text video thread output method
168 //////////////////////////////////////////////////////////////////////////////
169 // This function allocates and initializes a Clone vout method.
170 //////////////////////////////////////////////////////////////////////////////
171 static int Create( vlc_object_t *p_this )
173 filter_t *p_filter = (filter_t *)p_this;
176 // Allocate structure
177 p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
180 p_sys->psz_font_name = var_CreateGetString( p_this, "quartztext-font" );
181 p_sys->i_font_opacity = 255;
182 p_sys->i_font_color = __MAX( __MIN( var_CreateGetInteger( p_this, "quartztext-color" ) , 0xFFFFFF ), 0 );
183 p_sys->i_font_size = GetFontSize( p_filter );
185 p_filter->pf_render_text = RenderText;
186 p_filter->pf_render_html = RenderHtml;
188 p_sys->p_fonts = NULL;
191 LoadFontsFromAttachments( p_filter );
196 //////////////////////////////////////////////////////////////////////////////
197 // Destroy: destroy Clone video thread output method
198 //////////////////////////////////////////////////////////////////////////////
199 // Clean up all data and library connections
200 //////////////////////////////////////////////////////////////////////////////
201 static void Destroy( vlc_object_t *p_this )
203 filter_t *p_filter = (filter_t *)p_this;
204 filter_sys_t *p_sys = p_filter->p_sys;
210 for( k = 0; k < p_sys->i_fonts; k++ )
212 ATSFontDeactivate( p_sys->p_fonts[k], NULL, kATSOptionFlagsDefault );
215 free( p_sys->p_fonts );
218 free( p_sys->psz_font_name );
222 //////////////////////////////////////////////////////////////////////////////
223 // Make any TTF/OTF fonts present in the attachments of the media file
224 // available to the Quartz engine for text rendering
225 //////////////////////////////////////////////////////////////////////////////
226 static int LoadFontsFromAttachments( filter_t *p_filter )
228 filter_sys_t *p_sys = p_filter->p_sys;
229 input_thread_t *p_input;
230 input_attachment_t **pp_attachments;
231 int i_attachments_cnt;
233 int rv = VLC_SUCCESS;
235 p_input = (input_thread_t *)vlc_object_find( p_filter, VLC_OBJECT_INPUT, FIND_PARENT );
239 if( VLC_SUCCESS != input_Control( p_input, INPUT_GET_ATTACHMENTS, &pp_attachments, &i_attachments_cnt ))
241 vlc_object_release(p_input);
246 p_sys->p_fonts = malloc( i_attachments_cnt * sizeof( ATSFontContainerRef ) );
247 if(! p_sys->p_fonts )
250 for( k = 0; k < i_attachments_cnt; k++ )
252 input_attachment_t *p_attach = pp_attachments[k];
256 if(( !strcmp( p_attach->psz_mime, "application/x-truetype-font" ) || // TTF
257 !strcmp( p_attach->psz_mime, "application/x-font-otf" ) ) && // OTF
258 ( p_attach->i_data > 0 ) &&
259 ( p_attach->p_data != NULL ) )
261 ATSFontContainerRef container;
263 if( noErr == ATSFontActivateFromMemory( p_attach->p_data,
265 kATSFontContextLocal,
266 kATSFontFormatUnspecified,
268 kATSOptionFlagsDefault,
271 p_sys->p_fonts[ p_sys->i_fonts++ ] = container;
275 vlc_input_attachment_Delete( p_attach );
277 free( pp_attachments );
279 vlc_object_release(p_input);
284 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_4
285 // Original version of these functions available on:
286 // http://developer.apple.com/documentation/Carbon/Conceptual/QuickDrawToQuartz2D/tq_color/chapter_4_section_3.html
288 #define kGenericRGBProfilePathStr "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc"
290 static CMProfileRef OpenGenericProfile( void )
292 static CMProfileRef cached_rgb_prof = NULL;
294 // Create the profile reference only once
295 if( cached_rgb_prof == NULL )
298 CMProfileLocation loc;
300 loc.locType = cmPathBasedProfile;
301 strcpy( loc.u.pathLoc.path, kGenericRGBProfilePathStr );
303 err = CMOpenProfile( &cached_rgb_prof, &loc );
307 cached_rgb_prof = NULL;
311 if( cached_rgb_prof )
313 // Clone the profile reference so that the caller has
314 // their own reference, not our cached one.
315 CMCloneProfileRef( cached_rgb_prof );
318 return cached_rgb_prof;
321 static CGColorSpaceRef CreateGenericRGBColorSpace( void )
323 static CGColorSpaceRef p_generic_rgb_cs = NULL;
325 if( p_generic_rgb_cs == NULL )
327 CMProfileRef generic_rgb_prof = OpenGenericProfile();
329 if( generic_rgb_prof )
331 p_generic_rgb_cs = CGColorSpaceCreateWithPlatformColorSpace( generic_rgb_prof );
333 CMCloseProfile( generic_rgb_prof );
337 return p_generic_rgb_cs;
341 static char *EliminateCRLF( char *psz_string )
346 for( p = psz_string; p && *p; p++ )
348 if( ( *p == '\r' ) && ( *(p+1) == '\n' ) )
350 for( q = p + 1; *q; q++ )
359 // Convert UTF-8 string to UTF-16 character array -- internal Mac Endian-ness ;
360 // we don't need to worry about bidirectional text conversion as ATSUI should
361 // handle that for us automatically
362 static void ConvertToUTF16( const char *psz_utf8_str, uint32_t *pi_strlen, UniChar **ppsz_utf16_str )
364 CFStringRef p_cfString;
367 p_cfString = CFStringCreateWithCString( NULL, psz_utf8_str, kCFStringEncodingUTF8 );
371 i_string_length = CFStringGetLength( p_cfString );
374 *pi_strlen = i_string_length;
376 if( !*ppsz_utf16_str )
377 *ppsz_utf16_str = (UniChar *) calloc( i_string_length, sizeof( UniChar ) );
379 CFStringGetCharacters( p_cfString, CFRangeMake( 0, i_string_length ), *ppsz_utf16_str );
381 CFRelease( p_cfString );
384 // Renders a text subpicture region into another one.
385 // It is used as pf_add_string callback in the vout method by this module
386 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
387 subpicture_region_t *p_region_in )
389 filter_sys_t *p_sys = p_filter->p_sys;
390 UniChar *psz_utf16_str = NULL;
391 uint32_t i_string_length;
393 int i_font_color, i_font_alpha, i_font_size;
397 p_sys->i_font_size = GetFontSize( p_filter );
400 if( !p_region_in || !p_region_out ) return VLC_EGENERIC;
401 psz_string = p_region_in->psz_text;
402 if( !psz_string || !*psz_string ) return VLC_EGENERIC;
404 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
407 if( p_region_in->p_style )
409 i_font_color = __MAX( __MIN( p_region_in->p_style->i_font_color, 0xFFFFFF ), 0 );
410 i_font_alpha = __MAX( __MIN( p_region_in->p_style->i_font_alpha, 255 ), 0 );
411 i_font_size = __MAX( __MIN( p_region_in->p_style->i_font_size, 255 ), 0 ) * i_scale / 1000;
415 i_font_color = p_sys->i_font_color;
416 i_font_alpha = 255 - p_sys->i_font_opacity;
417 i_font_size = p_sys->i_font_size;
420 if( !i_font_alpha ) i_font_alpha = 255 - p_sys->i_font_opacity;
422 ConvertToUTF16( EliminateCRLF( psz_string ), &i_string_length, &psz_utf16_str );
424 p_region_out->i_x = p_region_in->i_x;
425 p_region_out->i_y = p_region_in->i_y;
427 if( psz_utf16_str != NULL )
429 ATSUStyle p_style = CreateStyle( p_sys->psz_font_name, i_font_size,
430 (i_font_color & 0xffffff) |
431 ((i_font_alpha & 0xff) << 24),
432 false, false, false );
435 RenderYUVA( p_filter, p_region_out, psz_utf16_str, i_string_length,
436 1, &i_string_length, &p_style );
439 ATSUDisposeStyle( p_style );
440 free( psz_utf16_str );
447 static ATSUStyle CreateStyle( char *psz_fontname, int i_font_size, uint32_t i_font_color,
448 bool b_bold, bool b_italic, bool b_uline )
454 float f_red = (float)(( i_font_color & 0x00FF0000 ) >> 16) / 255.0;
455 float f_green = (float)(( i_font_color & 0x0000FF00 ) >> 8) / 255.0;
456 float f_blue = (float)( i_font_color & 0x000000FF ) / 255.0;
457 float f_alpha = ( 255.0 - (float)(( i_font_color & 0xFF000000 ) >> 24)) / 255.0;
460 Fixed font_size = IntToFixed( i_font_size );
461 ATSURGBAlphaColor font_color = { f_red, f_green, f_blue, f_alpha };
462 Boolean bold = b_bold;
463 Boolean italic = b_italic;
464 Boolean uline = b_uline;
466 ATSUAttributeTag tags[] = { kATSUSizeTag, kATSURGBAlphaColorTag, kATSUQDItalicTag,
467 kATSUQDBoldfaceTag, kATSUQDUnderlineTag, kATSUFontTag };
468 ByteCount sizes[] = { sizeof( Fixed ), sizeof( ATSURGBAlphaColor ), sizeof( Boolean ),
469 sizeof( Boolean ), sizeof( Boolean ), sizeof( ATSUFontID )};
470 ATSUAttributeValuePtr values[] = { &font_size, &font_color, &italic, &bold, &uline, &font };
472 i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
474 status = ATSUFindFontFromName( psz_fontname,
475 strlen( psz_fontname ),
482 if( status != noErr )
484 // If we can't find a suitable font, just do everything else
488 if( noErr == ATSUCreateStyle( &p_style ) )
490 if( noErr == ATSUSetAttributes( p_style, i_tag_cnt, tags, sizes, values ) )
494 ATSUDisposeStyle( p_style );
499 static int PushFont( font_stack_t **p_font, const char *psz_name, int i_size,
507 p_new = malloc( sizeof( font_stack_t ) );
511 p_new->p_next = NULL;
514 p_new->psz_name = strdup( psz_name );
516 p_new->psz_name = NULL;
518 p_new->i_size = i_size;
519 p_new->i_color = i_color;
527 font_stack_t *p_last;
529 for( p_last = *p_font;
531 p_last = p_last->p_next )
534 p_last->p_next = p_new;
539 static int PopFont( font_stack_t **p_font )
541 font_stack_t *p_last, *p_next_to_last;
543 if( !p_font || !*p_font )
546 p_next_to_last = NULL;
547 for( p_last = *p_font;
549 p_last = p_last->p_next )
551 p_next_to_last = p_last;
555 p_next_to_last->p_next = NULL;
559 free( p_last->psz_name );
565 static int PeekFont( font_stack_t **p_font, char **psz_name, int *i_size,
568 font_stack_t *p_last;
570 if( !p_font || !*p_font )
575 p_last=p_last->p_next )
578 *psz_name = p_last->psz_name;
579 *i_size = p_last->i_size;
580 *i_color = p_last->i_color;
585 static ATSUStyle GetStyleFromFontStack( filter_sys_t *p_sys,
586 font_stack_t **p_fonts, bool b_bold, bool b_italic,
589 ATSUStyle p_style = NULL;
591 char *psz_fontname = NULL;
592 uint32_t i_font_color = p_sys->i_font_color;
593 int i_font_size = p_sys->i_font_size;
595 if( VLC_SUCCESS == PeekFont( p_fonts, &psz_fontname, &i_font_size,
598 p_style = CreateStyle( psz_fontname, i_font_size, i_font_color,
599 b_bold, b_italic, b_uline );
604 static const struct {
605 const char *psz_name;
607 } p_html_colors[] = {
608 /* Official html colors */
609 { "Aqua", 0x00FFFF },
610 { "Black", 0x000000 },
611 { "Blue", 0x0000FF },
612 { "Fuchsia", 0xFF00FF },
613 { "Gray", 0x808080 },
614 { "Green", 0x008000 },
615 { "Lime", 0x00FF00 },
616 { "Maroon", 0x800000 },
617 { "Navy", 0x000080 },
618 { "Olive", 0x808000 },
619 { "Purple", 0x800080 },
621 { "Silver", 0xC0C0C0 },
622 { "Teal", 0x008080 },
623 { "White", 0xFFFFFF },
624 { "Yellow", 0xFFFF00 },
627 { "AliceBlue", 0xF0F8FF },
628 { "AntiqueWhite", 0xFAEBD7 },
629 { "Aqua", 0x00FFFF },
630 { "Aquamarine", 0x7FFFD4 },
631 { "Azure", 0xF0FFFF },
632 { "Beige", 0xF5F5DC },
633 { "Bisque", 0xFFE4C4 },
634 { "Black", 0x000000 },
635 { "BlanchedAlmond", 0xFFEBCD },
636 { "Blue", 0x0000FF },
637 { "BlueViolet", 0x8A2BE2 },
638 { "Brown", 0xA52A2A },
639 { "BurlyWood", 0xDEB887 },
640 { "CadetBlue", 0x5F9EA0 },
641 { "Chartreuse", 0x7FFF00 },
642 { "Chocolate", 0xD2691E },
643 { "Coral", 0xFF7F50 },
644 { "CornflowerBlue", 0x6495ED },
645 { "Cornsilk", 0xFFF8DC },
646 { "Crimson", 0xDC143C },
647 { "Cyan", 0x00FFFF },
648 { "DarkBlue", 0x00008B },
649 { "DarkCyan", 0x008B8B },
650 { "DarkGoldenRod", 0xB8860B },
651 { "DarkGray", 0xA9A9A9 },
652 { "DarkGrey", 0xA9A9A9 },
653 { "DarkGreen", 0x006400 },
654 { "DarkKhaki", 0xBDB76B },
655 { "DarkMagenta", 0x8B008B },
656 { "DarkOliveGreen", 0x556B2F },
657 { "Darkorange", 0xFF8C00 },
658 { "DarkOrchid", 0x9932CC },
659 { "DarkRed", 0x8B0000 },
660 { "DarkSalmon", 0xE9967A },
661 { "DarkSeaGreen", 0x8FBC8F },
662 { "DarkSlateBlue", 0x483D8B },
663 { "DarkSlateGray", 0x2F4F4F },
664 { "DarkSlateGrey", 0x2F4F4F },
665 { "DarkTurquoise", 0x00CED1 },
666 { "DarkViolet", 0x9400D3 },
667 { "DeepPink", 0xFF1493 },
668 { "DeepSkyBlue", 0x00BFFF },
669 { "DimGray", 0x696969 },
670 { "DimGrey", 0x696969 },
671 { "DodgerBlue", 0x1E90FF },
672 { "FireBrick", 0xB22222 },
673 { "FloralWhite", 0xFFFAF0 },
674 { "ForestGreen", 0x228B22 },
675 { "Fuchsia", 0xFF00FF },
676 { "Gainsboro", 0xDCDCDC },
677 { "GhostWhite", 0xF8F8FF },
678 { "Gold", 0xFFD700 },
679 { "GoldenRod", 0xDAA520 },
680 { "Gray", 0x808080 },
681 { "Grey", 0x808080 },
682 { "Green", 0x008000 },
683 { "GreenYellow", 0xADFF2F },
684 { "HoneyDew", 0xF0FFF0 },
685 { "HotPink", 0xFF69B4 },
686 { "IndianRed", 0xCD5C5C },
687 { "Indigo", 0x4B0082 },
688 { "Ivory", 0xFFFFF0 },
689 { "Khaki", 0xF0E68C },
690 { "Lavender", 0xE6E6FA },
691 { "LavenderBlush", 0xFFF0F5 },
692 { "LawnGreen", 0x7CFC00 },
693 { "LemonChiffon", 0xFFFACD },
694 { "LightBlue", 0xADD8E6 },
695 { "LightCoral", 0xF08080 },
696 { "LightCyan", 0xE0FFFF },
697 { "LightGoldenRodYellow", 0xFAFAD2 },
698 { "LightGray", 0xD3D3D3 },
699 { "LightGrey", 0xD3D3D3 },
700 { "LightGreen", 0x90EE90 },
701 { "LightPink", 0xFFB6C1 },
702 { "LightSalmon", 0xFFA07A },
703 { "LightSeaGreen", 0x20B2AA },
704 { "LightSkyBlue", 0x87CEFA },
705 { "LightSlateGray", 0x778899 },
706 { "LightSlateGrey", 0x778899 },
707 { "LightSteelBlue", 0xB0C4DE },
708 { "LightYellow", 0xFFFFE0 },
709 { "Lime", 0x00FF00 },
710 { "LimeGreen", 0x32CD32 },
711 { "Linen", 0xFAF0E6 },
712 { "Magenta", 0xFF00FF },
713 { "Maroon", 0x800000 },
714 { "MediumAquaMarine", 0x66CDAA },
715 { "MediumBlue", 0x0000CD },
716 { "MediumOrchid", 0xBA55D3 },
717 { "MediumPurple", 0x9370D8 },
718 { "MediumSeaGreen", 0x3CB371 },
719 { "MediumSlateBlue", 0x7B68EE },
720 { "MediumSpringGreen", 0x00FA9A },
721 { "MediumTurquoise", 0x48D1CC },
722 { "MediumVioletRed", 0xC71585 },
723 { "MidnightBlue", 0x191970 },
724 { "MintCream", 0xF5FFFA },
725 { "MistyRose", 0xFFE4E1 },
726 { "Moccasin", 0xFFE4B5 },
727 { "NavajoWhite", 0xFFDEAD },
728 { "Navy", 0x000080 },
729 { "OldLace", 0xFDF5E6 },
730 { "Olive", 0x808000 },
731 { "OliveDrab", 0x6B8E23 },
732 { "Orange", 0xFFA500 },
733 { "OrangeRed", 0xFF4500 },
734 { "Orchid", 0xDA70D6 },
735 { "PaleGoldenRod", 0xEEE8AA },
736 { "PaleGreen", 0x98FB98 },
737 { "PaleTurquoise", 0xAFEEEE },
738 { "PaleVioletRed", 0xD87093 },
739 { "PapayaWhip", 0xFFEFD5 },
740 { "PeachPuff", 0xFFDAB9 },
741 { "Peru", 0xCD853F },
742 { "Pink", 0xFFC0CB },
743 { "Plum", 0xDDA0DD },
744 { "PowderBlue", 0xB0E0E6 },
745 { "Purple", 0x800080 },
747 { "RosyBrown", 0xBC8F8F },
748 { "RoyalBlue", 0x4169E1 },
749 { "SaddleBrown", 0x8B4513 },
750 { "Salmon", 0xFA8072 },
751 { "SandyBrown", 0xF4A460 },
752 { "SeaGreen", 0x2E8B57 },
753 { "SeaShell", 0xFFF5EE },
754 { "Sienna", 0xA0522D },
755 { "Silver", 0xC0C0C0 },
756 { "SkyBlue", 0x87CEEB },
757 { "SlateBlue", 0x6A5ACD },
758 { "SlateGray", 0x708090 },
759 { "SlateGrey", 0x708090 },
760 { "Snow", 0xFFFAFA },
761 { "SpringGreen", 0x00FF7F },
762 { "SteelBlue", 0x4682B4 },
764 { "Teal", 0x008080 },
765 { "Thistle", 0xD8BFD8 },
766 { "Tomato", 0xFF6347 },
767 { "Turquoise", 0x40E0D0 },
768 { "Violet", 0xEE82EE },
769 { "Wheat", 0xF5DEB3 },
770 { "White", 0xFFFFFF },
771 { "WhiteSmoke", 0xF5F5F5 },
772 { "Yellow", 0xFFFF00 },
773 { "YellowGreen", 0x9ACD32 },
778 static int HandleFontAttributes( xml_reader_t *p_xml_reader,
779 font_stack_t **p_fonts, int i_scale )
782 char *psz_fontname = NULL;
783 uint32_t i_font_color = 0xffffff;
784 int i_font_alpha = 0;
785 int i_font_size = 24;
787 // Default all attributes to the top font in the stack -- in case not
788 // all attributes are specified in the sub-font
789 if( VLC_SUCCESS == PeekFont( p_fonts,
794 psz_fontname = strdup( psz_fontname );
795 i_font_size = i_font_size * 1000 / i_scale;
797 i_font_alpha = (i_font_color >> 24) & 0xff;
798 i_font_color &= 0x00ffffff;
800 while ( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
802 char *psz_name = xml_ReaderName( p_xml_reader );
803 char *psz_value = xml_ReaderValue( p_xml_reader );
805 if( psz_name && psz_value )
807 if( !strcasecmp( "face", psz_name ) )
809 free( psz_fontname );
810 psz_fontname = strdup( psz_value );
812 else if( !strcasecmp( "size", psz_name ) )
814 if( ( *psz_value == '+' ) || ( *psz_value == '-' ) )
816 int i_value = atoi( psz_value );
818 if( ( i_value >= -5 ) && ( i_value <= 5 ) )
819 i_font_size += ( i_value * i_font_size ) / 10;
820 else if( i_value < -5 )
821 i_font_size = - i_value;
822 else if( i_value > 5 )
823 i_font_size = i_value;
826 i_font_size = atoi( psz_value );
828 else if( !strcasecmp( "color", psz_name ) )
830 if( psz_value[0] == '#' )
832 i_font_color = strtol( psz_value + 1, NULL, 16 );
833 i_font_color &= 0x00ffffff;
837 for( int i = 0; p_html_colors[i].psz_name != NULL; i++ )
839 if( !strncasecmp( psz_value, p_html_colors[i].psz_name, strlen(p_html_colors[i].psz_name) ) )
841 i_font_color = p_html_colors[i].i_value;
847 else if( !strcasecmp( "alpha", psz_name ) &&
848 ( psz_value[0] == '#' ) )
850 i_font_alpha = strtol( psz_value + 1, NULL, 16 );
851 i_font_alpha &= 0xff;
857 rv = PushFont( p_fonts,
859 i_font_size * i_scale / 1000,
860 (i_font_color & 0xffffff) | ((i_font_alpha & 0xff) << 24) );
862 free( psz_fontname );
867 static int ProcessNodes( filter_t *p_filter,
868 xml_reader_t *p_xml_reader,
869 text_style_t *p_font_style,
874 uint32_t **ppi_run_lengths,
875 ATSUStyle **ppp_styles )
877 int rv = VLC_SUCCESS;
878 filter_sys_t *p_sys = p_filter->p_sys;
879 UniChar *psz_text_orig = psz_text;
880 font_stack_t *p_fonts = NULL;
884 char *psz_node = NULL;
886 bool b_italic = false;
888 bool b_uline = false;
890 if( VLC_SUCCESS == var_Get( p_filter, "scale", &val ))
895 rv = PushFont( &p_fonts,
896 p_font_style->psz_fontname,
897 p_font_style->i_font_size * i_scale / 1000,
898 (p_font_style->i_font_color & 0xffffff) |
899 ((p_font_style->i_font_alpha & 0xff) << 24) );
901 if( p_font_style->i_style_flags & STYLE_BOLD )
903 if( p_font_style->i_style_flags & STYLE_ITALIC )
905 if( p_font_style->i_style_flags & STYLE_UNDERLINE )
910 rv = PushFont( &p_fonts,
911 p_sys->psz_font_name,
913 p_sys->i_font_color );
915 if( rv != VLC_SUCCESS )
918 while ( ( xml_ReaderRead( p_xml_reader ) == 1 ) )
920 switch ( xml_ReaderNodeType( p_xml_reader ) )
922 case XML_READER_NONE:
924 case XML_READER_ENDELEM:
925 psz_node = xml_ReaderName( p_xml_reader );
928 if( !strcasecmp( "font", psz_node ) )
930 else if( !strcasecmp( "b", psz_node ) )
932 else if( !strcasecmp( "i", psz_node ) )
934 else if( !strcasecmp( "u", psz_node ) )
940 case XML_READER_STARTELEM:
941 psz_node = xml_ReaderName( p_xml_reader );
944 if( !strcasecmp( "font", psz_node ) )
945 rv = HandleFontAttributes( p_xml_reader, &p_fonts, i_scale );
946 else if( !strcasecmp( "b", psz_node ) )
948 else if( !strcasecmp( "i", psz_node ) )
950 else if( !strcasecmp( "u", psz_node ) )
952 else if( !strcasecmp( "br", psz_node ) )
954 uint32_t i_string_length;
956 ConvertToUTF16( "\n", &i_string_length, &psz_text );
957 psz_text += i_string_length;
962 *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
964 *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
966 (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
968 if( *ppi_run_lengths )
969 *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
971 *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
973 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
978 case XML_READER_TEXT:
979 psz_node = xml_ReaderValue( p_xml_reader );
982 uint32_t i_string_length;
984 // Turn any multiple-whitespaces into single spaces
985 char *s = strpbrk( psz_node, "\t\r\n " );
988 int i_whitespace = strspn( s, "\t\r\n " );
990 if( i_whitespace > 1 )
993 strlen( s ) - i_whitespace + 1 );
996 s = strpbrk( s, "\t\r\n " );
999 ConvertToUTF16( psz_node, &i_string_length, &psz_text );
1000 psz_text += i_string_length;
1005 *ppp_styles = (ATSUStyle *) realloc( *ppp_styles, *pi_runs * sizeof( ATSUStyle ) );
1007 *ppp_styles = (ATSUStyle *) malloc( *pi_runs * sizeof( ATSUStyle ) );
1009 (*ppp_styles)[ *pi_runs - 1 ] = GetStyleFromFontStack( p_sys, &p_fonts, b_bold, b_italic, b_uline );
1011 if( *ppi_run_lengths )
1012 *ppi_run_lengths = (uint32_t *) realloc( *ppi_run_lengths, *pi_runs * sizeof( uint32_t ) );
1014 *ppi_run_lengths = (uint32_t *) malloc( *pi_runs * sizeof( uint32_t ) );
1016 (*ppi_run_lengths)[ *pi_runs - 1 ] = i_string_length;
1024 *pi_len = psz_text - psz_text_orig;
1026 while( VLC_SUCCESS == PopFont( &p_fonts ) );
1031 static int RenderHtml( filter_t *p_filter, subpicture_region_t *p_region_out,
1032 subpicture_region_t *p_region_in )
1034 int rv = VLC_SUCCESS;
1035 stream_t *p_sub = NULL;
1036 xml_t *p_xml = NULL;
1037 xml_reader_t *p_xml_reader = NULL;
1039 if( !p_region_in || !p_region_in->psz_html )
1040 return VLC_EGENERIC;
1042 /* Reset the default fontsize in case screen metrics have changed */
1043 p_filter->p_sys->i_font_size = GetFontSize( p_filter );
1045 p_sub = stream_MemoryNew( VLC_OBJECT(p_filter),
1046 (uint8_t *) p_region_in->psz_html,
1047 strlen( p_region_in->psz_html ),
1051 p_xml = xml_Create( p_filter );
1054 bool b_karaoke = false;
1056 p_xml_reader = xml_ReaderCreate( p_xml, p_sub );
1059 /* Look for Root Node */
1060 if( xml_ReaderRead( p_xml_reader ) == 1 )
1062 char *psz_node = xml_ReaderName( p_xml_reader );
1064 if( !strcasecmp( "karaoke", psz_node ) )
1066 /* We're going to have to render the text a number
1067 * of times to show the progress marker on the text.
1069 var_SetBool( p_filter, "text-rerender", true );
1072 else if( !strcasecmp( "text", psz_node ) )
1078 /* Only text and karaoke tags are supported */
1079 xml_ReaderDelete( p_xml, p_xml_reader );
1080 p_xml_reader = NULL;
1092 uint32_t i_runs = 0;
1093 uint32_t *pi_run_lengths = NULL;
1094 ATSUStyle *pp_styles = NULL;
1096 psz_text = (UniChar *) malloc( strlen( p_region_in->psz_html ) *
1097 sizeof( UniChar ) );
1102 rv = ProcessNodes( p_filter, p_xml_reader,
1103 p_region_in->p_style, psz_text, &i_len,
1104 &i_runs, &pi_run_lengths, &pp_styles );
1106 p_region_out->i_x = p_region_in->i_x;
1107 p_region_out->i_y = p_region_in->i_y;
1109 if(( rv == VLC_SUCCESS ) && ( i_len > 0 ))
1111 RenderYUVA( p_filter, p_region_out, psz_text, i_len, i_runs,
1112 pi_run_lengths, pp_styles);
1115 for( k=0; k<i_runs; k++)
1116 ATSUDisposeStyle( pp_styles[k] );
1118 free( pi_run_lengths );
1122 xml_ReaderDelete( p_xml, p_xml_reader );
1124 xml_Delete( p_xml );
1126 stream_Delete( p_sub );
1132 static CGContextRef CreateOffScreenContext( int i_width, int i_height,
1133 offscreen_bitmap_t **pp_memory, CGColorSpaceRef *pp_colorSpace )
1135 offscreen_bitmap_t *p_bitmap;
1136 CGContextRef p_context = NULL;
1138 p_bitmap = (offscreen_bitmap_t *) malloc( sizeof( offscreen_bitmap_t ));
1141 p_bitmap->i_bitsPerChannel = 8;
1142 p_bitmap->i_bitsPerPixel = 4 * p_bitmap->i_bitsPerChannel; // A,R,G,B
1143 p_bitmap->i_bytesPerPixel = p_bitmap->i_bitsPerPixel / 8;
1144 p_bitmap->i_bytesPerRow = i_width * p_bitmap->i_bytesPerPixel;
1146 p_bitmap->p_data = calloc( i_height, p_bitmap->i_bytesPerRow );
1148 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
1149 *pp_colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGB );
1151 *pp_colorSpace = CreateGenericRGBColorSpace();
1154 if( p_bitmap->p_data && *pp_colorSpace )
1156 p_context = CGBitmapContextCreate( p_bitmap->p_data, i_width, i_height,
1157 p_bitmap->i_bitsPerChannel, p_bitmap->i_bytesPerRow,
1158 *pp_colorSpace, kCGImageAlphaPremultipliedFirst);
1162 #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_1
1163 // OS X 10.1 doesn't support weak linking of this call which is only available
1164 // int 10.4 and later
1165 if( CGContextSetAllowsAntialiasing != NULL )
1167 CGContextSetAllowsAntialiasing( p_context, true );
1171 *pp_memory = p_bitmap;
1177 static offscreen_bitmap_t *Compose( int i_text_align, UniChar *psz_utf16_str, uint32_t i_text_len,
1178 uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles,
1179 int i_width, int i_height, int *pi_textblock_height )
1181 offscreen_bitmap_t *p_offScreen = NULL;
1182 CGColorSpaceRef p_colorSpace = NULL;
1183 CGContextRef p_context = NULL;
1185 p_context = CreateOffScreenContext( i_width, i_height, &p_offScreen, &p_colorSpace );
1189 ATSUTextLayout p_textLayout;
1190 OSStatus status = noErr;
1192 status = ATSUCreateTextLayoutWithTextPtr( psz_utf16_str, 0, i_text_len, i_text_len,
1194 (const UniCharCount *) pi_run_lengths,
1197 if( status == noErr )
1199 // Attach our offscreen Image Graphics Context to the text style
1200 // and setup the line alignment (have to specify the line width
1201 // also in order for our chosen alignment to work)
1203 Fract alignment = kATSUStartAlignment;
1204 Fixed line_width = Long2Fix( i_width - HORIZONTAL_MARGIN * 2 );
1206 ATSUAttributeTag tags[] = { kATSUCGContextTag, kATSULineFlushFactorTag, kATSULineWidthTag };
1207 ByteCount sizes[] = { sizeof( CGContextRef ), sizeof( Fract ), sizeof( Fixed ) };
1208 ATSUAttributeValuePtr values[] = { &p_context, &alignment, &line_width };
1210 int i_tag_cnt = sizeof( tags ) / sizeof( ATSUAttributeTag );
1212 if( i_text_align == SUBPICTURE_ALIGN_RIGHT )
1214 alignment = kATSUEndAlignment;
1216 else if( i_text_align != SUBPICTURE_ALIGN_LEFT )
1218 alignment = kATSUCenterAlignment;
1221 ATSUSetLayoutControls( p_textLayout, i_tag_cnt, tags, sizes, values );
1223 // let ATSUI deal with characters not-in-our-specified-font
1224 ATSUSetTransientFontMatching( p_textLayout, true );
1226 Fixed x = Long2Fix( HORIZONTAL_MARGIN );
1227 Fixed y = Long2Fix( i_height );
1229 // Set the line-breaks and draw individual lines
1230 uint32_t i_start = 0;
1231 uint32_t i_end = i_text_len;
1233 // Set up black outlining of the text --
1234 CGContextSetRGBStrokeColor( p_context, 0, 0, 0, 0.5 );
1235 CGContextSetTextDrawingMode( p_context, kCGTextFillStroke );
1236 CGContextSetShadow( p_context, CGSizeMake( 0, 0 ), 5 );
1237 float black_components[4] = {1, 1, 1, 1};
1238 CGContextSetShadowWithColor (p_context, CGSizeMake( 0, 0 ), 5, CGColorCreate( kCGColorSpaceGenericRGB, black_components ));
1241 // ATSUBreakLine will automatically pick up any manual '\n's also
1242 status = ATSUBreakLine( p_textLayout, i_start, line_width, true, (UniCharArrayOffset *) &i_end );
1243 if( ( status == noErr ) || ( status == kATSULineBreakInWord ) )
1247 uint32_t i_actualSize;
1249 // Come down far enough to fit the height of this line --
1250 ATSUGetLineControl( p_textLayout, i_start, kATSULineAscentTag,
1251 sizeof( Fixed ), &ascent, (ByteCount *) &i_actualSize );
1253 // Quartz uses an upside-down co-ordinate space -> y values decrease as
1254 // you move down the page
1257 // Set the outlining for this line to be dependent on the size of the line -
1258 // make it about 5% of the ascent, with a minimum at 1.0
1259 float f_thickness = FixedToFloat( ascent ) * 0.05;
1260 CGContextSetLineWidth( p_context, (( f_thickness < 1.0 ) ? 1.0 : f_thickness ));
1261 ATSUDrawText( p_textLayout, i_start, i_end - i_start, x, y );
1263 // and now prepare for the next line by coming down far enough for our
1265 ATSUGetLineControl( p_textLayout, i_start, kATSULineDescentTag,
1266 sizeof( Fixed ), &descent, (ByteCount *) &i_actualSize );
1274 while( i_end < i_text_len );
1276 *pi_textblock_height = i_height - Fix2Long( y );
1277 CGContextFlush( p_context );
1279 ATSUDisposeTextLayout( p_textLayout );
1282 CGContextRelease( p_context );
1284 if( p_colorSpace ) CGColorSpaceRelease( p_colorSpace );
1289 static int GetFontSize( filter_t *p_filter )
1291 return p_filter->fmt_out.video.i_height / __MAX(1, var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" ));
1294 static int RenderYUVA( filter_t *p_filter, subpicture_region_t *p_region, UniChar *psz_utf16_str,
1295 uint32_t i_text_len, uint32_t i_runs, uint32_t *pi_run_lengths, ATSUStyle *pp_styles )
1297 offscreen_bitmap_t *p_offScreen = NULL;
1298 int i_textblock_height = 0;
1300 int i_width = p_filter->fmt_out.video.i_visible_width;
1301 int i_height = p_filter->fmt_out.video.i_visible_height;
1302 int i_text_align = p_region->i_align & 0x3;
1304 if( !psz_utf16_str )
1306 msg_Err( p_filter, "Invalid argument to RenderYUVA" );
1307 return VLC_EGENERIC;
1310 p_offScreen = Compose( i_text_align, psz_utf16_str, i_text_len,
1311 i_runs, pi_run_lengths, pp_styles,
1312 i_width, i_height, &i_textblock_height );
1316 msg_Err( p_filter, "No offscreen buffer" );
1317 return VLC_EGENERIC;
1320 uint8_t *p_dst_y,*p_dst_u,*p_dst_v,*p_dst_a;
1322 int x, y, i_offset, i_pitch;
1323 uint8_t i_y, i_u, i_v; // YUV values, derived from incoming RGB
1324 subpicture_region_t *p_region_tmp;
1326 // Create a new subpicture region
1327 memset( &fmt, 0, sizeof(video_format_t) );
1328 fmt.i_chroma = VLC_FOURCC('Y','U','V','A');
1330 fmt.i_width = fmt.i_visible_width = i_width;
1331 fmt.i_height = fmt.i_visible_height = i_textblock_height + VERTICAL_MARGIN * 2;
1332 fmt.i_x_offset = fmt.i_y_offset = 0;
1333 p_region_tmp = spu_CreateRegion( p_filter, &fmt );
1336 msg_Err( p_filter, "cannot allocate SPU region" );
1337 return VLC_EGENERIC;
1339 p_region->fmt = p_region_tmp->fmt;
1340 p_region->picture = p_region_tmp->picture;
1341 free( p_region_tmp );
1343 p_dst_y = p_region->picture.Y_PIXELS;
1344 p_dst_u = p_region->picture.U_PIXELS;
1345 p_dst_v = p_region->picture.V_PIXELS;
1346 p_dst_a = p_region->picture.A_PIXELS;
1347 i_pitch = p_region->picture.A_PITCH;
1349 i_offset = VERTICAL_MARGIN *i_pitch;
1350 for( y=0; y<i_textblock_height; y++)
1352 for( x=0; x<i_width; x++)
1354 int i_alpha = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel ];
1355 int i_red = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 1 ];
1356 int i_green = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 2 ];
1357 int i_blue = p_offScreen->p_data[ y * p_offScreen->i_bytesPerRow + x * p_offScreen->i_bytesPerPixel + 3 ];
1359 i_y = (uint8_t)__MIN(abs( 2104 * i_red + 4130 * i_green +
1360 802 * i_blue + 4096 + 131072 ) >> 13, 235);
1361 i_u = (uint8_t)__MIN(abs( -1214 * i_red + -2384 * i_green +
1362 3598 * i_blue + 4096 + 1048576) >> 13, 240);
1363 i_v = (uint8_t)__MIN(abs( 3598 * i_red + -3013 * i_green +
1364 -585 * i_blue + 4096 + 1048576) >> 13, 240);
1366 p_dst_y[ i_offset + x ] = i_y;
1367 p_dst_u[ i_offset + x ] = i_u;
1368 p_dst_v[ i_offset + x ] = i_v;
1369 p_dst_a[ i_offset + x ] = i_alpha;
1371 i_offset += i_pitch;
1374 free( p_offScreen->p_data );
1375 free( p_offScreen );