/*****************************************************************************
* quartztext.c : Put text on the video, using Mac OS X Quartz Engine
*****************************************************************************
- * Copyright (C) 2007, 2009 VLC authors and VideoLAN
+ * Copyright (C) 2007, 2009, 2012 VLC authors and VideoLAN
* $Id$
*
* Authors: Bernie Purcell <bitmap@videolan.org>
+ * Pierre d'Herbemont <pdherbemont # videolan dot>
+ * Felix Paul Kühne <fkuehne # videolan # org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
#include <vlc_common.h>
#include <vlc_plugin.h>
-#include <vlc_osd.h>
#include <vlc_stream.h>
#include <vlc_xml.h>
#include <vlc_input.h>
+#include <vlc_filter.h>
#include <TargetConditionals.h>
#include <ApplicationServices/ApplicationServices.h>
#endif
-#define DEFAULT_FONT "Arial Black"
+#define DEFAULT_FONT "Helvetica-Neue"
#define DEFAULT_FONT_COLOR 0xffffff
#define DEFAULT_REL_FONT_SIZE 16
CFMutableAttributedStringRef p_attrString);
static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
- bool b_bold, bool b_italic, bool b_underline,
+ bool b_bold, bool b_italic, bool b_underline, bool b_halfwidth,
+ int i_spacing,
CFRange p_range, CFMutableAttributedStringRef p_attrString);
/*****************************************************************************
"the video. This must be an hexadecimal (like HTML colors). The first two "\
"chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
" #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white")
+#define OUTLINE_TEXT N_("Add outline")
+#define SHADOW_TEXT N_("Add shadow")
static const int pi_color_values[] = {
0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
0x00FF0000, 0x00FF00FF, 0x00FFFF00, 0x00808000, 0x00008000, 0x00008080,
0x0000FF00, 0x00800080, 0x00000080, 0x000000FF, 0x0000FFFF };
+static const char *const ppsz_color_names[] = {
+ "black", "gray", "silver", "white", "maroon",
+ "red", "fuchsia", "yellow", "olive", "green",
+ "teal", "lime", "purple", "navy", "blue", "aqua" };
+
static const char *const ppsz_color_descriptions[] = {
N_("Black"), N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"),
N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
add_integer("quartztext-color", 0x00FFFFFF, COLOR_TEXT,
COLOR_LONGTEXT, false)
change_integer_list(pi_color_values, ppsz_color_descriptions)
+ add_bool("quartztext-outline", false, OUTLINE_TEXT, NULL, false)
+ add_bool("quartztext-shadow", true, SHADOW_TEXT, NULL, false)
set_capability("text renderer", 50)
add_shortcut("text")
set_callbacks(Create, Destroy)
uint8_t i_font_opacity;
int i_font_color;
int i_font_size;
+ bool b_outline;
+ bool b_shadow;
#ifndef TARGET_OS_IPHONE
ATSFontContainerRef *p_fonts;
p_sys->psz_font_name = var_CreateGetString(p_this, "quartztext-font");
p_sys->i_font_opacity = 255;
p_sys->i_font_color = VLC_CLIP(var_CreateGetInteger(p_this, "quartztext-color") , 0, 0xFFFFFF);
- p_sys->i_font_size = GetFontSize(p_filter);
+ p_sys->b_outline = var_InheritBool(p_this, "quartztext-outline");
+ p_sys->b_shadow = var_InheritBool(p_this, "quartztext-shadow");
+ p_sys->i_font_size = GetFontSize(p_filter);
p_filter->pf_render_text = RenderText;
p_filter->pf_render_html = RenderHtml;
{
filter_sys_t *p_sys = p_filter->p_sys;
char *psz_string;
- int i_font_alpha, i_font_size;
+ char *psz_fontname;
+ int i_font_size;
+ int i_spacing = 0;
+ int i_font_alpha;
uint32_t i_font_color;
- bool b_bold, b_uline, b_italic;
+ bool b_bold, b_uline, b_italic, b_halfwidth;
vlc_value_t val;
- b_bold = b_uline = b_italic = FALSE;
+ b_bold = b_uline = b_italic = b_halfwidth = FALSE;
+ VLC_UNUSED(p_chroma_list);
- p_sys->i_font_size = GetFontSize(p_filter);
+ p_sys->i_font_size = GetFontSize(p_filter);
// Sanity check
if (!p_region_in || !p_region_out)
return VLC_EGENERIC;
if (p_region_in->p_style) {
+ psz_fontname = p_region_in->p_style->psz_fontname ?
+ p_region_in->p_style->psz_fontname : p_sys->psz_font_name;
i_font_color = VLC_CLIP(p_region_in->p_style->i_font_color, 0, 0xFFFFFF);
- i_font_alpha = VLC_CLIP(p_region_in->p_style->i_font_alpha, 0, 255);
i_font_size = VLC_CLIP(p_region_in->p_style->i_font_size, 0, 255);
if (p_region_in->p_style->i_style_flags) {
if (p_region_in->p_style->i_style_flags & STYLE_BOLD)
b_italic = TRUE;
if (p_region_in->p_style->i_style_flags & STYLE_UNDERLINE)
b_uline = TRUE;
+ if (p_region_in->p_style->i_style_flags & STYLE_HALFWIDTH)
+ b_halfwidth = TRUE;
}
+ i_spacing = VLC_CLIP(p_region_in->p_style->i_spacing, 0, 255);
} else {
+ psz_fontname = p_sys->psz_font_name;
i_font_color = p_sys->i_font_color;
- i_font_alpha = 255 - p_sys->i_font_opacity;
i_font_size = p_sys->i_font_size;
}
- if (!i_font_alpha)
- i_font_alpha = 255 - p_sys->i_font_opacity;
-
if (i_font_size <= 0) {
msg_Warn(p_filter, "invalid fontsize, using 12");
if (VLC_SUCCESS == var_Get(p_filter, "scale", &val))
CFRelease(p_cfString);
len = CFAttributedStringGetLength(p_attrString);
- setFontAttibutes(p_sys->psz_font_name, i_font_size, i_font_color, b_bold, b_italic, b_uline,
+ setFontAttibutes(psz_fontname, i_font_size, i_font_color, b_bold, b_italic, b_uline, b_halfwidth,
+ i_spacing,
CFRangeMake(0, len), p_attrString);
RenderYUVA(p_filter, p_region_out, p_attrString);
}
else
i_font_size = atoi(value);
- } else if (!strcasecmp("color", attr) && (value[0] == '#')) {
- i_font_color = strtol(value + 1, NULL, 16);
- i_font_color &= 0x00ffffff;
+ } else if (!strcasecmp("color", attr)) {
+ if (value[0] == '#') {
+ i_font_color = strtol(value + 1, NULL, 16);
+ i_font_color &= 0x00ffffff;
+ } else {
+ /* color detection fallback */
+ unsigned int count = sizeof(ppsz_color_names);
+ for (unsigned x = 0; x < count; x++) {
+ if (!strcmp(value, ppsz_color_names[x])) {
+ i_font_color = pi_color_values[x];
+ break;
+ }
+ }
+ }
} else if (!strcasecmp("alpha", attr) && (value[0] == '#')) {
i_font_alpha = strtol(value + 1, NULL, 16);
i_font_alpha &= 0xff;
}
static void setFontAttibutes(char *psz_fontname, int i_font_size, uint32_t i_font_color,
- bool b_bold, bool b_italic, bool b_underline,
+ bool b_bold, bool b_italic, bool b_underline, bool b_halfwidth,
+ int i_spacing,
CFRange p_range, CFMutableAttributedStringRef p_attrString)
{
CFStringRef p_cfString;
CTFontRef p_font;
- // Handle font name and size
- p_cfString = CFStringCreateWithCString(NULL,
+ int i_font_width = b_halfwidth ? i_font_size / 2 : i_font_size;
+ CGAffineTransform trans = CGAffineTransformMakeScale((float)i_font_width
+ / i_font_size, 1.0);
+
+ // fallback on default
+ if (!psz_fontname)
+ psz_fontname = (char *)DEFAULT_FONT;
+
+ p_cfString = CFStringCreateWithCString(kCFAllocatorDefault,
psz_fontname,
kCFStringEncodingUTF8);
p_font = CTFontCreateWithName(p_cfString,
(float)i_font_size,
- NULL);
+ &trans);
CFRelease(p_cfString);
CFAttributedStringSetAttribute(p_attrString,
p_range,
slant);
CFRelease(slant);
- // Handle foreground colour
+ // fetch invalid colors
+ if (i_font_color == 0xFFFFFFFF)
+ i_font_color = 0x00FFFFFF;
+
+ // Handle foreground color
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = { (float)((i_font_color & 0x00ff0000) >> 16) / 255.0,
(float)((i_font_color & 0x0000ff00) >> 8) / 255.0,
fg_text);
CFRelease(fg_text);
+ // spacing
+ if (i_spacing > 0)
+ {
+ CGFloat spacing = i_spacing;
+ CFNumberRef spacingCFNum = CFNumberCreate(NULL,
+ kCFNumberCGFloatType, &spacing);
+ CFAttributedStringSetAttribute(p_attrString,
+ p_range,
+ kCTKernAttributeName,
+ spacingCFNum);
+ CFRelease(spacingCFNum);
+ }
}
static void GetAttrStrFromFontStack(font_stack_t **p_fonts,
setFontAttibutes(psz_fontname,
i_font_size,
i_font_color,
- b_bold, b_italic, b_uline,
+ b_bold, b_italic, b_uline, FALSE,
+ 0,
p_range,
p_attrString);
}
stream_t *p_sub = NULL;
xml_t *p_xml = NULL;
xml_reader_t *p_xml_reader = NULL;
+ VLC_UNUSED(p_chroma_list);
if (!p_region_in || !p_region_in->psz_html)
return VLC_EGENERIC;
if (p_sub) {
p_xml = xml_Create(p_filter);
if (p_xml) {
- bool b_karaoke = false;
-
p_xml_reader = xml_ReaderCreate(p_xml, p_sub);
if (p_xml_reader) {
/* Look for Root Node */
* of times to show the progress marker on the text.
*/
var_SetBool(p_filter, "text-rerender", true);
- b_karaoke = true;
- } else if (!strcasecmp("text", name))
- b_karaoke = false;
- else {
+ } else if (strcasecmp("text", name)) {
/* Only text and karaoke tags are supported */
msg_Dbg(p_filter, "Unsupported top-level tag "
"<%s> ignored.", name);
return p_context;
}
-static offscreen_bitmap_t *Compose(int i_text_align,
+static offscreen_bitmap_t *Compose(filter_t *p_filter,
+ subpicture_region_t *p_region,
CFMutableAttributedStringRef p_attrString,
unsigned i_width,
unsigned i_height,
unsigned *pi_textblock_height)
{
+ filter_sys_t *p_sys = p_filter->p_sys;
offscreen_bitmap_t *p_offScreen = NULL;
CGColorSpaceRef p_colorSpace = NULL;
CGContextRef p_context = NULL;
CGContextSetTextMatrix(p_context, CGAffineTransformIdentity);
- if (i_text_align == SUBPICTURE_ALIGN_RIGHT)
+ if (p_region->i_align & SUBPICTURE_ALIGN_RIGHT)
horiz_flush = 1.0;
- else if (i_text_align != SUBPICTURE_ALIGN_LEFT)
+ else if ((p_region->i_align & SUBPICTURE_ALIGN_LEFT) == 0)
horiz_flush = 0.5;
else
horiz_flush = 0.0;
CGPathRelease(p_path);
// Set up black outlining of the text --
- CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
- CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
+ if (p_sys->b_outline)
+ {
+ CGContextSetRGBStrokeColor(p_context, 0, 0, 0, 0.5);
+ CGContextSetTextDrawingMode(p_context, kCGTextFillStroke);
+ }
+
+ // Shadow
+ if (p_sys->b_shadow)
+ {
+ // TODO: Use CGContextSetShadowWithColor.
+ // TODO: Use user defined parrameters (color, distance, etc.)
+ CGContextSetShadow(p_context, CGSizeMake(3.0f, -3.0f), 2.0f);
+ }
if (frame != NULL) {
CFArrayRef lines;
double penOffset = CTLineGetPenOffsetForFlush(line, horiz_flush, (i_width - HORIZONTAL_MARGIN*2));
penPosition.x = HORIZONTAL_MARGIN + penOffset;
+ if (horiz_flush == 0.0)
+ penPosition.x = p_region->i_x;
penPosition.y -= ascent;
CGContextSetTextPosition(p_context, penPosition.x, penPosition.y);
CTLineDraw(line, p_context);
static int GetFontSize(filter_t *p_filter)
{
- return p_filter->fmt_out.video.i_height / DEFAULT_REL_FONT_SIZE;
+ int i_size = 0;
+
+ int i_ratio = var_CreateGetInteger( p_filter, "quartztext-rel-fontsize" );
+ if( i_ratio > 0 )
+ i_size = (int)p_filter->fmt_out.video.i_height / i_ratio;
+
+ if( i_size <= 0 )
+ {
+ msg_Warn( p_filter, "invalid fontsize, using 12" );
+ i_size = 12;
+ }
+ return i_size;
}
static int RenderYUVA(filter_t *p_filter, subpicture_region_t *p_region,
unsigned i_width = p_filter->fmt_out.video.i_visible_width;
unsigned i_height = p_filter->fmt_out.video.i_visible_height;
- unsigned i_text_align = p_region->i_align & 0x3;
if (!p_attrString) {
msg_Err(p_filter, "Invalid argument to RenderYUVA");
return VLC_EGENERIC;
}
- p_offScreen = Compose(i_text_align, p_attrString,
+ p_offScreen = Compose(p_filter, p_region, p_attrString,
i_width, i_height, &i_textblock_height);
if (!p_offScreen) {
fmt.i_sar_den = 1;
p_region->p_picture = picture_NewFromFormat(&fmt);
- if (!p_region->p_picture)
+ if (!p_region->p_picture) {
+ free(p_offScreen->p_data);
+ free(p_offScreen);
return VLC_EGENERIC;
+ }
p_region->fmt = fmt;
p_dst_y = p_region->p_picture->Y_PIXELS;