#include <freetype/ftsynth.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
+#include FT_STROKER_H
+
#define FT_FLOOR(X) ((X & -64) >> 6)
#define FT_CEIL(X) (((X + 63) & -64) >> 6)
#ifndef FT_MulFix
"fonts that will be rendered on the video. If absolute font size is set, "\
"relative size will be overridden." )
+#define BG_OPACITY_TEXT N_("Background opacity")
+#define BG_COLOR_TEXT N_("Background color")
+
+#define OUTLINE_OPACITY_TEXT N_("Outline opacity")
+#define OUTLINE_COLOR_TEXT N_("Outline color")
+#define OUTLINE_THICKNESS_TEXT N_("Outline thickness")
+
static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
static const char *const ppsz_sizes_text[] = {
N_("Smaller"), N_("Small"), N_("Normal"), N_("Large"), N_("Larger") };
N_("Red"), N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"), N_("Teal"),
N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"), N_("Aqua") };
+static const int pi_outline_thickness[] = {
+ 0, 2, 4, 6,
+};
+static const char *const ppsz_outline_thickness[] = {
+ N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
+};
+
vlc_module_begin ()
set_shortname( N_("Text renderer"))
set_description( N_("Freetype2 font renderer") )
FONTSIZE_LONGTEXT, true )
change_safe()
+ add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
+ FONTSIZER_LONGTEXT, false )
+ change_integer_list( pi_sizes, ppsz_sizes_text )
+ change_safe()
+
/* opacity valid on 0..255, with default 255 = fully opaque */
add_integer_with_range( "freetype-opacity", 255, 0, 255,
OPACITY_TEXT, OPACITY_LONGTEXT, false )
change_integer_list( pi_color_values, ppsz_color_descriptions )
change_safe()
- add_integer( "freetype-rel-fontsize", 16, FONTSIZER_TEXT,
- FONTSIZER_LONGTEXT, false )
- change_integer_list( pi_sizes, ppsz_sizes_text )
+ add_integer_with_range( "freetype-background-opacity", 0, 0, 255,
+ BG_OPACITY_TEXT, "", false )
+ change_safe()
+ add_integer( "freetype-background-color", 0x00000000, BG_COLOR_TEXT,
+ "", false )
+ change_integer_list( pi_color_values, ppsz_color_descriptions )
+ change_safe()
+
+ add_integer_with_range( "freetype-outline-opacity", 255, 0, 255,
+ OUTLINE_OPACITY_TEXT, "", false )
+ change_safe()
+ add_integer( "freetype-outline-color", 0x00000000, OUTLINE_COLOR_TEXT,
+ "", false )
+ change_integer_list( pi_color_values, ppsz_color_descriptions )
+ change_safe()
+ add_integer_with_range( "freetype-outline-thickness", 4, 0, 50, OUTLINE_THICKNESS_TEXT,
+ "", false )
+ change_integer_list( pi_outline_thickness, ppsz_outline_thickness )
change_safe()
add_obsolete_integer( "freetype-effect" );
* Local prototypes
*****************************************************************************/
+typedef struct
+{
+ FT_BitmapGlyph p_glyph;
+ FT_BitmapGlyph p_outline;
+ uint32_t i_color; /* ARGB color */
+ int i_line_offset; /* underline/strikethrough offset */
+ int i_line_thickness; /* underline/strikethrough thickness */
+} line_character_t;
+
typedef struct line_desc_t line_desc_t;
struct line_desc_t
{
- /** NULL-terminated list of glyphs making the string */
- FT_BitmapGlyph *pp_glyphs;
- /** list of relative positions for the glyphs */
- FT_Vector *p_glyph_pos;
- /** list of ARGB information for styled text */
- uint32_t *pi_color;
- /** underline/strikethrough information */
- int *pi_line_offset;
- uint16_t *pi_line_thickness;
-
- int i_width;
-
line_desc_t *p_next;
+
+ int i_width;
+ int i_base_line;
+ int i_character_count;
+ line_character_t *p_character;
};
typedef struct font_stack_t font_stack_t;
{
FT_Library p_library; /* handle to library */
FT_Face p_face; /* handle to face object */
+ FT_Stroker p_stroker;
uint8_t i_font_opacity;
int i_font_color;
int i_font_size;
+ uint8_t i_background_opacity;
+ int i_background_color;
+
+ double f_outline_thickness;
+ uint8_t i_outline_opacity;
+ int i_outline_color;
+
int i_default_font_size;
int i_display_height;
char* psz_fontfamily;
* This function merges the previously rendered freetype glyphs into a picture
*****************************************************************************/
static int RenderYUVP( filter_t *p_filter, subpicture_region_t *p_region,
- line_desc_t *p_line, int i_width, int i_height )
+ line_desc_t *p_line,
+ FT_BBox *p_bbox )
{
VLC_UNUSED(p_filter);
static const uint8_t pi_gamma[16] =
uint8_t i_y, i_u, i_v; /* YUV values, derived from incoming RGB */
/* Create a new subpicture region */
- memset( &fmt, 0, sizeof(video_format_t) );
- fmt.i_chroma = VLC_CODEC_YUVP;
- fmt.i_width = fmt.i_visible_width = i_width + 4;
- fmt.i_height = fmt.i_visible_height = i_height + 4;
- if( p_region->fmt.i_visible_width > 0 )
- fmt.i_visible_width = p_region->fmt.i_visible_width;
- if( p_region->fmt.i_visible_height > 0 )
- fmt.i_visible_height = p_region->fmt.i_visible_height;
- fmt.i_x_offset = fmt.i_y_offset = 0;
- fmt.i_sar_num = 1;
- fmt.i_sar_den = 1;
+ video_format_Init( &fmt, VLC_CODEC_YUVP );
+ fmt.i_width =
+ fmt.i_visible_width = p_bbox->xMax - p_bbox->xMin + 4;
+ fmt.i_height =
+ fmt.i_visible_height = p_bbox->yMax - p_bbox->yMin + 4;
assert( !p_region->p_picture );
p_region->p_picture = picture_NewFromFormat( &fmt );
/* Calculate text color components
* Only use the first color */
- int i_alpha = 0xff - ((p_line->pi_color[ 0 ] >> 24) & 0xff);
- YUVFromRGB( p_line->pi_color[ 0 ], &i_y, &i_u, &i_v );
+ int i_alpha = 0xff - ((p_line->p_character[0].i_color >> 24) & 0xff);
+ YUVFromRGB( p_line->p_character[0].i_color, &i_y, &i_u, &i_v );
/* Build palette */
fmt.p_palette->i_entries = 16;
for( ; p_line != NULL; p_line = p_line->p_next )
{
- int i_glyph_tmax = 0;
- int i_bitmap_offset, i_offset, i_align_offset = 0;
- for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
- {
- FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
- i_glyph_tmax = __MAX( i_glyph_tmax, p_glyph->top );
- }
-
- if( p_line->i_width < i_width )
+ int i_align_left = 0;
+ if( p_line->i_width < fmt.i_visible_width )
{
if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
- {
- i_align_offset = i_width - p_line->i_width;
- }
+ i_align_left = ( fmt.i_visible_width - p_line->i_width );
else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
- {
- i_align_offset = ( i_width - p_line->i_width ) / 2;
- }
+ i_align_left = ( fmt.i_visible_width - p_line->i_width ) / 2;
}
+ int i_align_top = 0;
- for( i = 0; p_line->pp_glyphs[i] != NULL; i++ )
+ for( i = 0; i < p_line->i_character_count; i++ )
{
- FT_BitmapGlyph p_glyph = p_line->pp_glyphs[ i ];
+ const line_character_t *ch = &p_line->p_character[i];
+ FT_BitmapGlyph p_glyph = ch->p_glyph;
- i_offset = ( p_line->p_glyph_pos[ i ].y +
- i_glyph_tmax - p_glyph->top + 2 ) *
- i_pitch + p_line->p_glyph_pos[ i ].x + p_glyph->left + 2 +
- i_align_offset;
+ int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
+ int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
- for( y = 0, i_bitmap_offset = 0; y < p_glyph->bitmap.rows; y++ )
+ for( y = 0; y < p_glyph->bitmap.rows; y++ )
{
- for( x = 0; x < p_glyph->bitmap.width; x++, i_bitmap_offset++ )
+ for( x = 0; x < p_glyph->bitmap.width; x++ )
{
- if( p_glyph->bitmap.buffer[i_bitmap_offset] )
- p_dst[i_offset+x] =
- ((int)p_glyph->bitmap.buffer[i_bitmap_offset] + 8)/16;
+ if( p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] )
+ p_dst[(i_glyph_y + y) * i_pitch + (i_glyph_x + x)] =
+ (p_glyph->bitmap.buffer[y * p_glyph->bitmap.width + x] + 8)/16;
}
- i_offset += i_pitch;
}
}
}
static inline void BlendYUVALine( picture_t *p_picture,
int i_picture_x, int i_picture_y,
int i_a, int i_y, int i_u, int i_v,
- FT_BitmapGlyph p_glyph_current,
- FT_BitmapGlyph p_glyph_next,
- FT_Vector *p_pos_current,
- FT_Vector *p_pos_next,
- int i_line_thickness,
- int i_line_offset,
- bool is_next )
+ const line_character_t *p_current,
+ const line_character_t *p_next )
{
- int i_line_width = p_glyph_current->bitmap.width;
- if( is_next )
- i_line_width = (p_pos_next->x + p_glyph_next->left) -
- (p_pos_current->x + p_glyph_current->left);
+ int i_line_width = p_current->p_glyph->bitmap.width;
+ if( p_next )
+ i_line_width = p_next->p_glyph->left - p_current->p_glyph->left;
for( int dx = 0; dx < i_line_width; dx++ )
{
- /* break the underline around the tails of any glyphs which cross it
- Strikethrough doesn't get broken */
- bool b_ok = true;
- for( int z = dx - i_line_thickness; z < dx + i_line_thickness && b_ok && i_line_offset >= 0; z++ )
- {
- FT_BitmapGlyph p_glyph_check = NULL;
- int i_column;
- if( p_glyph_next && z >= i_line_width )
- {
- i_column = z - i_line_width;
- p_glyph_check = p_glyph_next;
- }
- else if( z >= 0 && z < p_glyph_current->bitmap.width )
- {
- i_column = z;
- p_glyph_check = p_glyph_current;
- }
- if( p_glyph_check )
- {
- const FT_Bitmap *p_bitmap = &p_glyph_check->bitmap;
- for( int dy = 0; dy < i_line_thickness && b_ok; dy++ )
- {
- int i_row = i_line_offset + p_glyph_check->top + dy;
- b_ok = i_row >= p_bitmap->rows ||
- p_bitmap->buffer[p_bitmap->width * i_row + i_column] == 0;
- }
- }
- }
-
- for( int dy = 0; dy < i_line_thickness && b_ok; dy++ )
- BlendYUVAPixel( p_picture, i_picture_x + dx, i_picture_y + i_line_offset + dy,
+ for( int dy = 0; dy < p_current->i_line_thickness; dy++ )
+ BlendYUVAPixel( p_picture,
+ i_picture_x + dx,
+ i_picture_y + p_current->i_line_offset + dy,
i_a, i_y, i_u, i_v, 0xff );
}
}
static int RenderYUVA( filter_t *p_filter,
subpicture_region_t *p_region,
line_desc_t *p_line_head,
- int i_width, int i_height )
+ FT_BBox *p_bbox,
+ int i_margin )
{
filter_sys_t *p_sys = p_filter->p_sys;
/* Create a new subpicture region */
+ const int i_text_width = p_bbox->xMax - p_bbox->xMin;
+ const int i_text_height = p_bbox->yMax - p_bbox->yMin;
video_format_t fmt;
video_format_Init( &fmt, VLC_CODEC_YUVA );
fmt.i_width =
- fmt.i_visible_width = i_width;
+ fmt.i_visible_width = i_text_width + 2 * i_margin;
fmt.i_height =
- fmt.i_visible_height = i_height;
+ fmt.i_visible_height = i_text_height + 2 * i_margin;
picture_t *p_picture = p_region->p_picture = picture_NewFromFormat( &fmt );
if( !p_region->p_picture )
p_region->fmt = fmt;
/* Initialize the picture background */
- uint32_t i_background = 0 ? 0x80000000 : 0xff000000;
- uint8_t i_a = 0xff - ((i_background >> 24) & 0xff);
+ uint8_t i_a = p_sys->i_background_opacity;
uint8_t i_y, i_u, i_v;
- YUVFromRGB( i_background, &i_y, &i_u, &i_v );
+ YUVFromRGB( p_sys->i_background_color, &i_y, &i_u, &i_v );
memset( p_picture->p[0].p_pixels, i_y,
p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
memset( p_picture->p[3].p_pixels, i_a,
p_picture->p[3].i_pitch * p_picture->p[3].i_lines );
- /* Render all lines */
- for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
+ /* Render outline glyphs in the first pass, and then the normal glyphs */
+ for( int g = 0; g < 2; g++ )
{
- /* Left offset to take into account alignment */
- int i_align_left = 0;
- if( p_line->i_width < i_width )
+ /* Render all lines */
+ for( line_desc_t *p_line = p_line_head; p_line != NULL; p_line = p_line->p_next )
{
- if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
- i_align_left = i_width - p_line->i_width;
- else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
- i_align_left = ( i_width - p_line->i_width ) / 2;
- }
-
- /* Compute the top alignment
- * FIXME seems bad (it seems that the glyphs are aligned too high) */
- int i_align_top = 0;
- for( int i = 0; p_line->pp_glyphs[i]; i++ )
- i_align_top = __MAX( i_align_top, p_line->pp_glyphs[i]->top );
-
- /* Render all glyphs and underline/strikethrough */
- for( int i = 0; p_line->pp_glyphs[i]; i++ )
- {
- FT_BitmapGlyph p_glyph = p_line->pp_glyphs[i];
-
- uint32_t i_color = p_line->pi_color[i];
- i_a = 0xff - ((i_color >> 24) & 0xff);
- YUVFromRGB( i_color, &i_y, &i_u, &i_v );
-
- int i_picture_y = p_line->p_glyph_pos[i].y + i_align_top;
- int i_picture_x = p_line->p_glyph_pos[i].x + i_align_left + p_glyph->left;
+ int i_align_left = i_margin;
+ if( p_line->i_width < i_text_width )
+ {
+ /* Left offset to take into account alignment */
+ if( (p_region->i_align & 0x3) == SUBPICTURE_ALIGN_RIGHT )
+ i_align_left += ( i_text_width - p_line->i_width );
+ else if( (p_region->i_align & 0x3) != SUBPICTURE_ALIGN_LEFT )
+ i_align_left += ( i_text_width - p_line->i_width ) / 2;
+ }
+ int i_align_top = i_margin;
- BlendYUVAGlyph( p_picture, i_picture_x, i_picture_y - p_glyph->top,
- i_a, i_y, i_u, i_v,
- p_glyph );
-
- const int i_line_thickness = p_line->pi_line_thickness[i];
- const int i_line_offset = p_line->pi_line_offset[i];
- if( i_line_thickness > 0 )
- BlendYUVALine( p_picture, i_picture_x, i_picture_y,
- i_a, i_y, i_u, i_v,
- p_glyph, p_line->pp_glyphs[i + 1],
- &p_line->p_glyph_pos[i], &p_line->p_glyph_pos[i + 1],
- i_line_thickness, i_line_offset,
- p_line->pp_glyphs[i + 1] && p_line->pi_line_thickness[i + 1] > 0 );
+ /* Render all glyphs and underline/strikethrough */
+ for( int i = 0; i < p_line->i_character_count; i++ )
+ {
+ const line_character_t *ch = &p_line->p_character[i];
+ FT_BitmapGlyph p_glyph = g == 0 ? ch->p_outline : ch->p_glyph;
+ if( !p_glyph )
+ continue;
+
+ uint32_t i_color = ch->i_color;
+ i_a = 0xff - ((i_color >> 24) & 0xff);
+ if( g == 0 )
+ {
+ i_a = i_a * p_sys->i_outline_opacity / 255;
+ i_color = p_sys->i_outline_color;
+ }
+ YUVFromRGB( i_color, &i_y, &i_u, &i_v );
+
+ int i_glyph_y = i_align_top - p_glyph->top + p_bbox->yMax + p_line->i_base_line;
+ int i_glyph_x = i_align_left + p_glyph->left - p_bbox->xMin;
+
+ BlendYUVAGlyph( p_picture,
+ i_glyph_x, i_glyph_y,
+ i_a, i_y, i_u, i_v,
+ p_glyph );
+
+ /* underline/strikethrough are only rendered for the normal glyph */
+ if( g == 1 && ch->i_line_thickness > 0 )
+ BlendYUVALine( p_picture,
+ i_glyph_x, i_glyph_y + p_glyph->top,
+ i_a, i_y, i_u, i_v,
+ &ch[0],
+ i + 1 < p_line->i_character_count ? &ch[1] : NULL );
+ }
}
}
static void FreeLine( line_desc_t *p_line )
{
- for( int i = 0; p_line->pp_glyphs && p_line->pp_glyphs[i] != NULL; i++ )
- FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[i] );
-
- free( p_line->pp_glyphs );
- free( p_line->p_glyph_pos );
- free( p_line->pi_color );
- free( p_line->pi_line_offset );
- free( p_line->pi_line_thickness );
+ for( int i = 0; i < p_line->i_character_count; i++ )
+ {
+ line_character_t *ch = &p_line->p_character[i];
+ FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
+ if( ch->p_outline )
+ FT_Done_Glyph( (FT_Glyph)ch->p_outline );
+ }
+
+ free( p_line->p_character );
free( p_line );
}
if( !p_line )
return NULL;
- p_line->i_width = 0;
-
p_line->p_next = NULL;
+ p_line->i_width = 0;
+ p_line->i_base_line = 0;
+ p_line->i_character_count = 0;
- p_line->pp_glyphs = calloc( i_count + 1, sizeof(*p_line->pp_glyphs) );
- p_line->p_glyph_pos = calloc( i_count + 1, sizeof(*p_line->p_glyph_pos) );
- p_line->pi_color = calloc( i_count + 1, sizeof(*p_line->pi_color) );
- p_line->pi_line_offset = calloc( i_count + 1, sizeof(*p_line->pi_line_offset) );
- p_line->pi_line_thickness = calloc( i_count + 1, sizeof(*p_line->pi_line_thickness) );
-
- if( !p_line->pp_glyphs || !p_line->p_glyph_pos ||
- !p_line->pi_color ||
- !p_line->pi_line_offset || !p_line->pi_line_thickness )
+ p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
+ if( !p_line->p_character )
{
- FreeLine( p_line );
+ free( p_line );
return NULL;
}
- p_line->pp_glyphs[0] = NULL;
return p_line;
}
}
static int GetGlyph( filter_t *p_filter,
- FT_Glyph *pp_glyph,
- FT_BBox *p_bbox,
+ FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
+ FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
FT_Face p_face,
int i_glyph_index,
- int i_style_flags )
+ int i_style_flags,
+ FT_Vector *p_pen )
{
if( FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT ) &&
FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
return VLC_EGENERIC;
}
- FT_BBox bbox;
- FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, &bbox );
+ FT_Glyph outline = NULL;
+ if( p_filter->p_sys->p_stroker )
+ {
+ outline = glyph;
+ FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 );
+ FT_Glyph_To_Bitmap( &outline, FT_RENDER_MODE_NORMAL, p_pen, 1 );
+
+ FT_Glyph_Get_CBox( outline, ft_glyph_bbox_pixels, p_outline_bbox );
+ }
+ *pp_outline = outline;
- if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, 0, 1) )
+ if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
{
FT_Done_Glyph( glyph );
return VLC_EGENERIC;
}
+ FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
*pp_glyph = glyph;
- *p_bbox = bbox;
+
return VLC_SUCCESS;
}
+static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox, FT_Face face, const FT_Vector *p_pen )
+{
+ FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
+ if( p_bbox->xMin >= p_bbox->xMax )
+ {
+ p_bbox->xMin = FT_CEIL(p_pen->x);
+ p_bbox->xMax = FT_CEIL(p_pen->x + face->glyph->advance.x);
+ glyph_bmp->left = p_bbox->xMin;
+ }
+ if( p_bbox->yMin >= p_bbox->yMax )
+ {
+ p_bbox->yMax = FT_CEIL(p_pen->y);
+ p_bbox->yMin = FT_CEIL(p_pen->y + face->glyph->advance.y);
+ glyph_bmp->top = p_bbox->yMax;
+ }
+}
+
+static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
+{
+ p_max->xMin = __MIN(p_max->xMin, p->xMin);
+ p_max->yMin = __MIN(p_max->yMin, p->yMin);
+ p_max->xMax = __MAX(p_max->xMax, p->xMax);
+ p_max->yMax = __MAX(p_max->yMax, p->yMax);
+}
+
static int ProcessLines( filter_t *p_filter,
line_desc_t **pp_lines,
- FT_Vector *p_size,
+ FT_BBox *p_bbox,
+ int *pi_max_face_height,
uint32_t *psz_text,
text_style_t **pp_styles,
}
free( p_new_positions );
+ *pi_max_face_height = 0;
*pp_lines = NULL;
line_desc_t **pp_line_next = pp_lines;
FT_BBox bbox = {
- .xMin = 0,
- .yMin = 0,
- .xMax = 0,
- .yMax = 0,
+ .xMin = INT_MAX,
+ .yMin = INT_MAX,
+ .xMax = INT_MIN,
+ .yMax = INT_MIN,
};
- FT_Vector pen = { .x = 0, .y = 0 };
+ int i_face_height_previous = 0;
+ int i_base_line = 0;
const text_style_t *p_previous_style = NULL;
FT_Face p_face = NULL;
for( int i_start = 0; i_start < i_len; )
/* Render the text line (or the begining if too long) into 0 or 1 glyph line */
line_desc_t *p_line = i_length > 0 ? NewLine( i_length ) : NULL;
int i_index = i_start;
- pen.x = 0;
+ FT_Vector pen = {
+ .x = 0,
+ .y = 0,
+ };
int i_face_height = 0;
FT_BBox line_bbox = {
- .xMin = 0,
- .yMin = 0,
- .xMax = 0,
- .yMax = 0,
+ .xMin = INT_MAX,
+ .yMin = INT_MAX,
+ .xMax = INT_MIN,
+ .yMax = INT_MIN,
};
+ int i_ul_offset = 0;
+ int i_ul_thickness = 0;
typedef struct {
int i_index;
FT_Vector pen;
FT_BBox line_bbox;
int i_face_height;
+ int i_ul_offset;
+ int i_ul_thickness;
} break_point_t;
break_point_t break_point;
break_point_t break_point_fallback;
dst.pen = pen; \
dst.line_bbox = line_bbox; \
dst.i_face_height = i_face_height; \
+ dst.i_ul_offset = i_ul_offset; \
+ dst.i_ul_thickness = i_ul_thickness; \
} while(0)
SAVE_BP( break_point );
{
if( FT_Set_Pixel_Sizes( p_current_face, 0, p_current_style->i_font_size ) )
msg_Err( p_filter, "Failed to set font size to %d", p_current_style->i_font_size );
+ if( p_sys->p_stroker )
+ {
+ int i_radius = (p_current_style->i_font_size << 6) * p_sys->f_outline_thickness;
+ FT_Stroker_Set( p_sys->p_stroker,
+ i_radius,
+ FT_STROKER_LINECAP_ROUND,
+ FT_STROKER_LINEJOIN_ROUND, 0 );
+ }
}
p_previous_style = p_current_style;
- i_face_height = __MAX(i_face_height, FT_CEIL(p_current_face->size->metrics.height));
+ i_face_height = __MAX(i_face_height, FT_CEIL(FT_MulFix(p_current_face->height,
+ p_current_face->size->metrics.y_scale)));
/* Render the part */
bool b_break_line = false;
FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
/* Get the glyph bitmap and its bounding box and all the associated properties */
+ FT_Vector pen_new = {
+ .x = pen.x + kerning.x,
+ .y = pen.y + kerning.y,
+ };
FT_Glyph glyph;
FT_BBox glyph_bbox;
- if( GetGlyph( p_filter, &glyph, &glyph_bbox,
- p_current_face, i_glyph_index, p_glyph_style->i_style_flags ) )
+ FT_Glyph outline;
+ FT_BBox outline_bbox;
+ if( GetGlyph( p_filter,
+ &glyph, &glyph_bbox,
+ &outline, &outline_bbox,
+ p_current_face, i_glyph_index, p_glyph_style->i_style_flags, &pen_new ) )
goto next;
- FT_Vector glyph_pos = {
- .x = pen.x + FT_CEIL(kerning.x),
- .y = pen.y
- };
+ FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
+ if( outline )
+ FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
+ /* FIXME and what about outline */
+
bool b_karaoke = pi_karaoke_bar && pi_karaoke_bar[i_index] != 0;
uint32_t i_color = b_karaoke ? (p_glyph_style->i_karaoke_background_color |
(p_glyph_style->i_karaoke_background_alpha << 24))
: (p_glyph_style->i_font_color |
(p_glyph_style->i_font_alpha << 24));
- int i_ul_offset = 0;
- int i_ul_thickness = 0;
+ int i_line_offset = 0;
+ int i_line_thickness = 0;
if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
{
- i_ul_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
- p_current_face->size->metrics.y_scale)) );
-
- i_ul_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
+ i_line_offset = abs( FT_FLOOR(FT_MulFix(p_current_face->underline_position,
p_current_face->size->metrics.y_scale)) );
+ i_line_thickness = abs( FT_CEIL(FT_MulFix(p_current_face->underline_thickness,
+ p_current_face->size->metrics.y_scale)) );
+
if( p_glyph_style->i_style_flags & STYLE_STRIKEOUT )
{
/* Move the baseline to make it strikethrough instead of
* underline. That means that strikethrough takes precedence
*/
- i_ul_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
- p_current_face->size->metrics.y_scale)) );
+ i_line_offset -= abs( FT_FLOOR(FT_MulFix(p_current_face->descender*2,
+ p_current_face->size->metrics.y_scale)) );
+ }
+ else if( i_line_thickness > 0 )
+ {
+ glyph_bbox.yMin = __MIN( glyph_bbox.yMin, - i_line_offset - i_line_thickness );
+
+ /* The real underline thickness and position are
+ * updated once the whole line has been parsed */
+ i_ul_offset = __MAX( i_ul_offset, i_line_offset );
+ i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
+ i_line_thickness = -1;
}
}
- FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
- FT_BBox line_bbox_new = {
- .xMin = 0,
- .xMax = __MAX( line_bbox.xMax,
- glyph_pos.x + glyph_bbox.xMax - glyph_bbox.xMin + glyph_bmp->left ),
- .yMin = 0,
- .yMax = __MAX( line_bbox.yMax,
- glyph_pos.y + glyph_bbox.yMax - glyph_bbox.yMin + glyph_bmp->top ),
- };
+ FT_BBox line_bbox_new = line_bbox;
+ BBoxEnlarge( &line_bbox_new, &glyph_bbox );
+ if( outline )
+ BBoxEnlarge( &line_bbox_new, &outline_bbox );
b_break_line = i_index > i_start &&
- line_bbox_new.xMax >= p_filter->fmt_out.video.i_visible_width;
+ line_bbox_new.xMax - line_bbox_new.xMin >= p_filter->fmt_out.video.i_visible_width;
if( b_break_line )
{
FT_Done_Glyph( glyph );
+ if( outline )
+ FT_Done_Glyph( outline );
break_point_t *p_bp = NULL;
if( break_point.i_index > i_start )
msg_Dbg( p_filter, "Breaking line");
for( int i = p_bp->i_index; i < i_index; i++ )
{
- FT_Done_Glyph( (FT_Glyph)p_line->pp_glyphs[i - i_start] );
- p_line->pp_glyphs[i - i_start] = NULL;
+ line_character_t *ch = &p_line->p_character[i - i_start];
+ FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
+ if( ch->p_outline )
+ FT_Done_Glyph( (FT_Glyph)ch->p_outline );
}
+ p_line->i_character_count = p_bp->i_index - i_start;
+
i_index = p_bp->i_index;
pen = p_bp->pen;
line_bbox = p_bp->line_bbox;
i_face_height = p_bp->i_face_height;
+ i_ul_offset = p_bp->i_ul_offset;
+ i_ul_thickness = p_bp->i_ul_thickness;
}
else
{
break;
}
- int i_line_index = i_index - i_start;
- p_line->pp_glyphs[i_line_index] = (FT_BitmapGlyph)glyph;
- p_line->p_glyph_pos[i_line_index] = glyph_pos;
- p_line->pi_color[i_line_index] = i_color;
- p_line->pi_line_offset[i_line_index] = i_ul_offset;
- p_line->pi_line_thickness[i_line_index] = i_ul_thickness;
+ assert( p_line->i_character_count == i_index - i_start);
+ p_line->p_character[p_line->i_character_count++] = (line_character_t){
+ .p_glyph = (FT_BitmapGlyph)glyph,
+ .p_outline = (FT_BitmapGlyph)outline,
+ .i_color = i_color,
+ .i_line_offset = i_line_offset,
+ .i_line_thickness = i_line_thickness,
+ };
- pen.x += FT_CEIL(kerning.x) + FT_CEIL(p_current_face->glyph->advance.x);
+ pen.x = pen_new.x + p_current_face->glyph->advance.x;
+ pen.y = pen_new.y + p_current_face->glyph->advance.y;
line_bbox = line_bbox_new;
next:
i_glyph_last = i_glyph_index;
break;
}
#undef SAVE_BP
- bbox.xMax = __MAX(bbox.xMax, line_bbox.xMax);
- bbox.yMax = __MAX(bbox.yMax, line_bbox.yMax);
-
- pen.y += i_face_height;
+ /* Update our baseline */
+ if( i_face_height_previous > 0 )
+ i_base_line += __MAX(i_face_height, i_face_height_previous);
+ i_face_height_previous = i_face_height;
+
+ /* Update the line bbox with the actual base line */
+ if (line_bbox.yMax > line_bbox.yMin) {
+ line_bbox.yMin -= i_base_line;
+ line_bbox.yMax -= i_base_line;
+ }
+ BBoxEnlarge( &bbox, &line_bbox );
/* Terminate and append the line */
if( p_line )
{
- p_line->i_width = line_bbox.xMax - line_bbox.xMin;
+ p_line->i_width = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
+ p_line->i_base_line = i_base_line;
+ if( i_ul_thickness > 0 )
+ {
+ for( int i = 0; i < p_line->i_character_count; i++ )
+ {
+ line_character_t *ch = &p_line->p_character[i];
+ if( ch->i_line_thickness < 0 )
+ {
+ ch->i_line_offset = i_ul_offset;
+ ch->i_line_thickness = i_ul_thickness;
+ }
+ }
+ }
+
*pp_line_next = p_line;
pp_line_next = &p_line->p_next;
}
+ *pi_max_face_height = __MAX( *pi_max_face_height, i_face_height );
+
/* Skip what we have rendered and the line delimitor if present */
i_start = i_index;
if( i_start < i_len && psz_text[i_start] == '\n' )
i_start++;
- if( bbox.yMax >= p_filter->fmt_out.video.i_visible_height )
+ if( bbox.yMax - bbox.yMin >= p_filter->fmt_out.video.i_visible_height )
{
msg_Err( p_filter, "Truncated too high subtitle" );
break;
free( p_fribidi_string );
free( pi_karaoke_bar );
- p_size->x = bbox.xMax - bbox.xMin;
- p_size->y = bbox.yMax - bbox.yMin;
+ *p_bbox = bbox;
return VLC_SUCCESS;
}
/* */
int rv = VLC_SUCCESS;
int i_text_length = 0;
- FT_Vector result = {0, 0};
+ FT_BBox bbox;
+ int i_max_face_height;
line_desc_t *p_lines = NULL;
uint32_t *pi_k_durations = NULL;
if( !rv && i_text_length > 0 )
{
rv = ProcessLines( p_filter,
- &p_lines, &result,
+ &p_lines, &bbox, &i_max_face_height,
psz_text, pp_styles, pi_k_durations, i_text_length );
}
/* Don't attempt to render text that couldn't be layed out
* properly. */
- if( !rv && i_text_length > 0 && result.x > 0 && result.y > 0)
+ if( !rv && i_text_length > 0 && bbox.xMin < bbox.xMax && bbox.yMin < bbox.yMax )
{
if( var_InheritBool( p_filter, "freetype-yuvp" ) )
- RenderYUVP( p_filter, p_region_out, p_lines,
- result.x, result.y );
+ RenderYUVP( p_filter, p_region_out, p_lines, &bbox );
else
- RenderYUVA( p_filter, p_region_out, p_lines,
- result.x, result.y );
-
+ RenderYUVA( p_filter, p_region_out,
+ p_lines,
+ &bbox,
+ p_sys->i_background_opacity > 0 ? i_max_face_height / 4 : 0 );
/* With karaoke, we're going to have to render the text a number
* of times to show the progress marker on the text.
p_sys->i_font_color = var_InheritInteger( p_filter, "freetype-color" );
p_sys->i_font_color = __MAX( __MIN( p_sys->i_font_color , 0xFFFFFF ), 0 );
+ p_sys->i_background_opacity = var_InheritInteger( p_filter,"freetype-background-opacity" );;
+ p_sys->i_background_opacity = __MAX( __MIN( p_sys->i_background_opacity, 255 ), 0 );
+ p_sys->i_background_color = var_InheritInteger( p_filter, "freetype-background-color" );
+ p_sys->i_background_color = __MAX( __MIN( p_sys->i_background_color, 0xFFFFFF ), 0 );
+
+ p_sys->f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
+ p_sys->f_outline_thickness = __MAX( __MIN( p_sys->f_outline_thickness, 0.5 ), 0.0 );
+ p_sys->i_outline_opacity = var_InheritInteger( p_filter, "freetype-outline-opacity" );
+ p_sys->i_outline_opacity = __MAX( __MIN( p_sys->i_outline_opacity, 255 ), 0 );
+ p_sys->i_outline_color = var_InheritInteger( p_filter, "freetype-outline-color" );
+ p_sys->i_outline_color = __MAX( __MIN( p_sys->i_outline_color, 0xFFFFFF ), 0 );
+
#ifdef WIN32
/* Get Windows Font folder */
wchar_t wdir[MAX_PATH];
if( SetFontSize( p_filter, 0 ) != VLC_SUCCESS ) goto error;
+ p_sys->p_stroker = NULL;
+ if( p_sys->f_outline_thickness > 0.001 )
+ {
+ i_error = FT_Stroker_New( p_sys->p_library, &p_sys->p_stroker );
+ if( i_error )
+ msg_Err( p_filter, "Failed to create stroker for outlining" );
+ }
p_sys->pp_font_attachments = NULL;
p_sys->i_font_attachments = 0;
* even if no other library functions have been made since FcInit(),
* so don't call it. */
+ if( p_sys->p_stroker )
+ FT_Stroker_Done( p_sys->p_stroker );
FT_Done_Face( p_sys->p_face );
FT_Done_FreeType( p_sys->p_library );
free( p_sys );