[vlc-devel] [PATCH] freetype: use HarfBuzz for text shaping
Jean-Baptiste Kempf
jb at videolan.org
Sat Apr 18 20:15:36 CEST 2015
Hello,
Thanks a LOT for your work. That's amazing!
I'm going to review it as soon as possible, probably tommorrow or
monday.
On 17 Apr, Salah-Eddin Shaban wrote :
> ---
> configure.ac | 17 +-
> modules/text_renderer/Makefile.am | 7 +-
> modules/text_renderer/freetype.c | 884 ++++--------------------
> modules/text_renderer/freetype.h | 72 ++
> modules/text_renderer/text_layout.c | 1273 +++++++++++++++++++++++++++++++++++
> modules/text_renderer/text_layout.h | 55 ++
> 6 files changed, 1559 insertions(+), 749 deletions(-)
> create mode 100644 modules/text_renderer/freetype.h
> create mode 100644 modules/text_renderer/text_layout.c
> create mode 100644 modules/text_renderer/text_layout.h
>
> diff --git a/configure.ac b/configure.ac
> index 0d7e48a..27709bc 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -3101,6 +3101,8 @@ AC_ARG_ENABLE(freetype,
> [ --enable-freetype freetype support (default auto)])
> AC_ARG_ENABLE(fribidi,
> [ --enable-fribidi fribidi support (default auto)])
> +AC_ARG_ENABLE(harfbuzz,
> + [ --enable-harfbuzz harfbuzz support (default auto)])
> AC_ARG_ENABLE(fontconfig,
> [ --enable-fontconfig fontconfig support (default auto)])
>
> @@ -3129,6 +3131,7 @@ AC_ARG_WITH([default-monospace-font-family],
> have_freetype="no"
> have_fontconfig="no"
> have_fribidi="no"
> +have_harfbuzz="no"
>
> if test "${enable_freetype}" != "no"; then
> PKG_CHECK_MODULES(FREETYPE, freetype2, [
> @@ -3151,7 +3154,18 @@ if test "${enable_freetype}" != "no"; then
> have_fribidi="yes"
> VLC_ADD_CPPFLAGS([skins2], [${FRIBIDI_CFLAGS} -DHAVE_FRIBIDI])
> VLC_ADD_LIBS([skins2], [${FRIBIDI_LIBS}])
> - ],[AC_MSG_WARN([${FRIBIDI_PKG_ERRORS}. Bidirectional support will be disabled in FreeType.])])
> + ],[AC_MSG_WARN([${FRIBIDI_PKG_ERRORS}. Bidirectional text and complex scripts (Arabic, Farsi, Thai...) will be disabled in FreeType.])])
> + fi
> +
> + dnl harfbuzz support
> + if test "${have_fribidi}" != "no"; then
> + if test "${enable_harfbuzz}" != "no"; then
> + PKG_CHECK_MODULES(HARFBUZZ, harfbuzz, [
> + have_harfbuzz="yes"
> + VLC_ADD_CPPFLAGS([skins2], [${HARFBUZZ_CFLAGS} -DHAVE_HARFBUZZ])
> + VLC_ADD_LIBS([skins2], [${HARFBUZZ_LIBS}])
> + ],[AC_MSG_WARN([${HARFBUZZ_PKG_ERRORS}. Support for complex scripts (Arabic, Farsi, Thai...) will be disabled in FreeType.])])
> + fi
> fi
> ],[
> AS_IF([test -n "${enable_freetype}"],[
> @@ -3163,6 +3177,7 @@ fi
> AM_CONDITIONAL([HAVE_FREETYPE], [test "${have_freetype}" = "yes"])
> AM_CONDITIONAL([HAVE_FONTCONFIG], [test "${have_fontconfig}" = "yes"])
> AM_CONDITIONAL([HAVE_FRIBIDI], [test "${have_fribidi}" = "yes"])
> +AM_CONDITIONAL([HAVE_HARFBUZZ], [test "${have_harfbuzz}" = "yes"])
>
>
> dnl
> diff --git a/modules/text_renderer/Makefile.am b/modules/text_renderer/Makefile.am
> index f75be77..92ee067 100644
> --- a/modules/text_renderer/Makefile.am
> +++ b/modules/text_renderer/Makefile.am
> @@ -6,7 +6,8 @@ text_LTLIBRARIES = libtdummy_plugin.la
> libfreetype_plugin_la_SOURCES = \
> text_renderer/text_renderer.c text_renderer/text_renderer.h \
> text_renderer/platform_fonts.c text_renderer/platform_fonts.h \
> - text_renderer/freetype.c
> + text_renderer/freetype.c text_renderer/freetype.h \
> + text_renderer/text_layout.c text_renderer/text_layout.h
> libfreetype_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(FREETYPE_CFLAGS)
> libfreetype_plugin_la_LIBADD = $(LIBM) $(FREETYPE_LIBS)
> if HAVE_FREETYPE
> @@ -20,6 +21,10 @@ if HAVE_FRIBIDI
> libfreetype_plugin_la_CPPFLAGS += $(FRIBIDI_CFLAGS) -DHAVE_FRIBIDI
> libfreetype_plugin_la_LIBADD += $(FRIBIDI_LIBS)
> endif
> +if HAVE_HARFBUZZ
> +libfreetype_plugin_la_CPPFLAGS += $(HARFBUZZ_CFLAGS) -DHAVE_HARFBUZZ
> +libfreetype_plugin_la_LIBADD += $(HARFBUZZ_LIBS)
> +endif
> libfreetype_plugin_la_LDFLAGS = $(AM_LDFLAGS) $(FREETYPE_LDFLAGS) -rpath '$(textdir)'
> if HAVE_DARWIN
> libfreetype_plugin_la_LDFLAGS += -Wl,-framework,Carbon
> diff --git a/modules/text_renderer/freetype.c b/modules/text_renderer/freetype.c
> index 8d314fe..fad5972 100644
> --- a/modules/text_renderer/freetype.c
> +++ b/modules/text_renderer/freetype.c
> @@ -48,18 +48,6 @@
> #include FT_FREETYPE_H
> #include FT_GLYPH_H
> #include FT_STROKER_H
> -#include FT_SYNTHESIS_H
> -
> -#define FT_FLOOR(X) ((X & -64) >> 6)
> -#define FT_CEIL(X) (((X + 63) & -64) >> 6)
> -#ifndef FT_MulFix
> - #define FT_MulFix(v, s) (((v)*(s))>>16)
> -#endif
> -
> -/* RTL */
> -#if defined(HAVE_FRIBIDI)
> -# include <fribidi/fribidi.h>
> -#endif
>
> /* apple stuff */
> #ifdef __APPLE__
> @@ -85,6 +73,8 @@
>
> #include "text_renderer.h"
> #include "platform_fonts.h"
> +#include "freetype.h"
> +#include "text_layout.h"
>
> /*****************************************************************************
> * Module descriptor
> @@ -130,6 +120,9 @@ static void Destroy( vlc_object_t * );
> #define SHADOW_ANGLE_TEXT N_("Shadow angle")
> #define SHADOW_DISTANCE_TEXT N_("Shadow distance")
>
> +#define TEXT_DIRECTION_TEXT N_("Text direction")
> +#define TEXT_DIRECTION_LONGTEXT N_("Paragraph base direction for the Unicode bi-directional algorithm.")
> +
>
> static const int pi_sizes[] = { 20, 18, 16, 12, 6 };
> static const char *const ppsz_sizes_text[] = {
> @@ -155,6 +148,15 @@ static const char *const ppsz_outline_thickness[] = {
> N_("None"), N_("Thin"), N_("Normal"), N_("Thick"),
> };
>
> +#ifdef HAVE_FRIBIDI
> +static const int pi_text_direction[] = {
> + 0, 1, 2,
> +};
> +static const char *const ppsz_text_direction[] = {
> + N_("Left to right"), N_("Right to left"), N_("Auto"),
> +};
> +#endif
> +
> vlc_module_begin ()
> set_shortname( N_("Text renderer"))
> set_description( N_("Freetype2 font renderer") )
> @@ -231,69 +233,19 @@ vlc_module_begin ()
>
> add_bool( "freetype-yuvp", false, YUVP_TEXT,
> YUVP_LONGTEXT, true )
> +
> +#ifdef HAVE_FRIBIDI
> + add_integer_with_range( "freetype-text-direction", 0, 0, 2, TEXT_DIRECTION_TEXT,
> + TEXT_DIRECTION_LONGTEXT, false )
> + change_integer_list( pi_text_direction, ppsz_text_direction )
> + change_safe()
> +#endif
> +
> set_capability( "text renderer", 100 )
> add_shortcut( "text" )
> set_callbacks( Create, Destroy )
> vlc_module_end ()
>
> -
> -/*****************************************************************************
> - * Local prototypes
> - *****************************************************************************/
> -
> -typedef struct
> -{
> - FT_BitmapGlyph p_glyph;
> - FT_BitmapGlyph p_outline;
> - FT_BitmapGlyph p_shadow;
> - 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
> -{
> - line_desc_t *p_next;
> -
> - int i_width;
> - int i_height;
> - int i_base_line;
> - int i_character_count;
> - line_character_t *p_character;
> -};
> -
> -/*****************************************************************************
> - * filter_sys_t: freetype local data
> - *****************************************************************************
> - * This structure is part of the video output thread descriptor.
> - * It describes the freetype specific properties of an output thread.
> - *****************************************************************************/
> -struct filter_sys_t
> -{
> - FT_Library p_library; /* handle to library */
> - FT_Face p_face; /* handle to face object */
> - FT_Stroker p_stroker; /* handle to path stroker object */
> -
> - xml_reader_t *p_xml; /* vlc xml parser */
> -
> - text_style_t style; /* Current Style */
> -
> - /* More styles... */
> - float f_shadow_vector_x;
> - float f_shadow_vector_y;
> - int i_default_font_size;
> -
> - /* Attachments */
> - input_attachment_t **pp_font_attachments;
> - int i_font_attachments;
> -
> - char * (*pf_select) (filter_t *, const char* family,
> - bool bold, bool italic, int size,
> - int *index);
> -
> -};
> -
> /* */
> static void YUVFromRGB( uint32_t i_argb,
> uint8_t *pi_y, uint8_t *pi_u, uint8_t *pi_v )
> @@ -908,52 +860,6 @@ static inline int RenderAXYZ( filter_t *p_filter,
>
>
>
> -static void FreeLine( line_desc_t *p_line )
> -{
> - 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 );
> - if( ch->p_shadow )
> - FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
> - }
> -
> - free( p_line->p_character );
> - free( p_line );
> -}
> -
> -static void FreeLines( line_desc_t *p_lines )
> -{
> - for( line_desc_t *p_line = p_lines; p_line != NULL; )
> - {
> - line_desc_t *p_next = p_line->p_next;
> - FreeLine( p_line );
> - p_line = p_next;
> - }
> -}
> -
> -static line_desc_t *NewLine( int i_count )
> -{
> - line_desc_t *p_line = malloc( sizeof(*p_line) );
> -
> - if( !p_line )
> - return NULL;
> -
> - p_line->p_next = NULL;
> - p_line->i_width = 0;
> - p_line->i_base_line = 0;
> - p_line->i_character_count = 0;
> -
> - p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
> - if( !p_line->p_character )
> - {
> - free( p_line );
> - return NULL;
> - }
> - return p_line;
> -}
>
> static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_style )
> {
> @@ -987,634 +893,6 @@ static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_styl
> return NULL;
> }
>
> -static FT_Face LoadFace( filter_t *p_filter,
> - const text_style_t *p_style )
> -{
> - filter_sys_t *p_sys = p_filter->p_sys;
> -
> - /* Look for a match amongst our attachments first */
> - FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
> -
> - /* Load system wide font otheriwse */
> - if( !p_face )
> - {
> - int i_idx = 0;
> - char *psz_fontfile = NULL;
> - if( p_sys->pf_select )
> - psz_fontfile = p_sys->pf_select( p_filter,
> - p_style->psz_fontname,
> - (p_style->i_style_flags & STYLE_BOLD) != 0,
> - (p_style->i_style_flags & STYLE_ITALIC) != 0,
> - -1,
> - &i_idx );
> - else
> - psz_fontfile = NULL;
> -
> - if( !psz_fontfile )
> - return NULL;
> -
> - if( *psz_fontfile == '\0' )
> - {
> - msg_Warn( p_filter,
> - "We were not able to find a matching font: \"%s\" (%s %s),"
> - " so using default font",
> - p_style->psz_fontname,
> - (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
> - (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
> - p_face = NULL;
> - }
> - else
> - {
> - if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
> - p_face = NULL;
> - }
> - free( psz_fontfile );
> - }
> - if( !p_face )
> - return NULL;
> -
> - if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
> - {
> - /* We've loaded a font face which is unhelpful for actually
> - * rendering text - fallback to the default one.
> - */
> - FT_Done_Face( p_face );
> - return NULL;
> - }
> - return p_face;
> -}
> -
> -static int GetGlyph( filter_t *p_filter,
> - FT_Glyph *pp_glyph, FT_BBox *p_glyph_bbox,
> - FT_Glyph *pp_outline, FT_BBox *p_outline_bbox,
> - FT_Glyph *pp_shadow, FT_BBox *p_shadow_bbox,
> -
> - FT_Face p_face,
> - int i_glyph_index,
> - int i_style_flags,
> - FT_Vector *p_pen,
> - FT_Vector *p_pen_shadow )
> -{
> - 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 ) )
> - {
> - msg_Err( p_filter, "unable to render text FT_Load_Glyph failed" );
> - return VLC_EGENERIC;
> - }
> -
> - /* Do synthetic styling now that Freetype supports it;
> - * ie. if the font we have loaded is NOT already in the
> - * style that the tags want, then switch it on; if they
> - * are then don't. */
> - if ((i_style_flags & STYLE_BOLD) && !(p_face->style_flags & FT_STYLE_FLAG_BOLD))
> - FT_GlyphSlot_Embolden( p_face->glyph );
> - if ((i_style_flags & STYLE_ITALIC) && !(p_face->style_flags & FT_STYLE_FLAG_ITALIC))
> - FT_GlyphSlot_Oblique( p_face->glyph );
> -
> - FT_Glyph glyph;
> - if( FT_Get_Glyph( p_face->glyph, &glyph ) )
> - {
> - msg_Err( p_filter, "unable to render text FT_Get_Glyph failed" );
> - return VLC_EGENERIC;
> - }
> -
> - FT_Glyph outline = NULL;
> - if( p_filter->p_sys->p_stroker )
> - {
> - outline = glyph;
> - if( FT_Glyph_StrokeBorder( &outline, p_filter->p_sys->p_stroker, 0, 0 ) )
> - outline = NULL;
> - }
> -
> - FT_Glyph shadow = NULL;
> - if( p_filter->p_sys->style.i_shadow_alpha > 0 )
> - {
> - shadow = outline ? outline : glyph;
> - if( FT_Glyph_To_Bitmap( &shadow, FT_RENDER_MODE_NORMAL, p_pen_shadow, 0 ) )
> - {
> - shadow = NULL;
> - }
> - else
> - {
> - FT_Glyph_Get_CBox( shadow, ft_glyph_bbox_pixels, p_shadow_bbox );
> - }
> - }
> - *pp_shadow = shadow;
> -
> - if( FT_Glyph_To_Bitmap( &glyph, FT_RENDER_MODE_NORMAL, p_pen, 1) )
> - {
> - FT_Done_Glyph( glyph );
> - if( outline )
> - FT_Done_Glyph( outline );
> - if( shadow )
> - FT_Done_Glyph( shadow );
> - return VLC_EGENERIC;
> - }
> - FT_Glyph_Get_CBox( glyph, ft_glyph_bbox_pixels, p_glyph_bbox );
> - *pp_glyph = glyph;
> -
> - if( outline )
> - {
> - 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;
> -
> - 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_BBox *p_bbox,
> - int *pi_max_face_height,
> -
> - uni_char_t *psz_text,
> - text_style_t **pp_styles,
> - uint32_t *pi_k_dates,
> - int i_len )
> -{
> - filter_sys_t *p_sys = p_filter->p_sys;
> - uni_char_t *p_fribidi_string = NULL;
> - text_style_t **pp_fribidi_styles = NULL;
> - int *p_new_positions = NULL;
> -
> -#if defined(HAVE_FRIBIDI)
> - {
> - int *p_old_positions;
> - int start_pos, pos = 0;
> -
> - pp_fribidi_styles = calloc( i_len, sizeof(*pp_fribidi_styles) );
> -
> - p_fribidi_string = malloc( (i_len + 1) * sizeof(*p_fribidi_string) );
> - p_old_positions = malloc( (i_len + 1) * sizeof(*p_old_positions) );
> - p_new_positions = malloc( (i_len + 1) * sizeof(*p_new_positions) );
> -
> - if( ! pp_fribidi_styles ||
> - ! p_fribidi_string ||
> - ! p_old_positions ||
> - ! p_new_positions )
> - {
> - free( p_old_positions );
> - free( p_new_positions );
> - free( p_fribidi_string );
> - free( pp_fribidi_styles );
> - return VLC_ENOMEM;
> - }
> -
> - /* Do bidi conversion line-by-line */
> - while(pos < i_len)
> - {
> - while(pos < i_len) {
> - if (psz_text[pos] != '\n')
> - break;
> - p_fribidi_string[pos] = psz_text[pos];
> - pp_fribidi_styles[pos] = pp_styles[pos];
> - p_new_positions[pos] = pos;
> - ++pos;
> - }
> - start_pos = pos;
> - while(pos < i_len) {
> - if (psz_text[pos] == '\n')
> - break;
> - ++pos;
> - }
> - if (pos > start_pos)
> - {
> -#if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
> - FriBidiCharType base_dir = FRIBIDI_TYPE_LTR;
> -#else
> - FriBidiParType base_dir = FRIBIDI_PAR_LTR;
> -#endif
> - fribidi_log2vis((FriBidiChar*)psz_text + start_pos,
> - pos - start_pos, &base_dir,
> - (FriBidiChar*)p_fribidi_string + start_pos,
> - p_new_positions + start_pos,
> - p_old_positions,
> - NULL );
> - for( int j = start_pos; j < pos; j++ )
> - {
> - pp_fribidi_styles[ j ] = pp_styles[ start_pos + p_old_positions[j - start_pos] ];
> - p_new_positions[ j ] += start_pos;
> - }
> - }
> - }
> - p_fribidi_string[ i_len ] = 0;
> - free( p_old_positions );
> -
> - pp_styles = pp_fribidi_styles;
> - psz_text = p_fribidi_string;
> - }
> -#endif
> - /* Work out the karaoke */
> - uint8_t *pi_karaoke_bar = NULL;
> - if( pi_k_dates )
> - {
> - pi_karaoke_bar = malloc( i_len * sizeof(*pi_karaoke_bar));
> - if( pi_karaoke_bar )
> - {
> - int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
> - for( int i = 0; i < i_len; i++ )
> - {
> - unsigned i_bar = p_new_positions ? p_new_positions[i] : i;
> - pi_karaoke_bar[i_bar] = pi_k_dates[i] >= i_elapsed;
> - }
> - }
> - }
> - free( p_new_positions );
> -
> - *pi_max_face_height = 0;
> - *pp_lines = NULL;
> - line_desc_t **pp_line_next = pp_lines;
> -
> - FT_BBox bbox = {
> - .xMin = INT_MAX,
> - .yMin = INT_MAX,
> - .xMax = INT_MIN,
> - .yMax = INT_MIN,
> - };
> - 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; )
> - {
> - /* Compute the length of the current text line */
> - int i_length = 0;
> - while( i_start + i_length < i_len && psz_text[i_start + i_length] != '\n' )
> - i_length++;
> -
> - /* 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;
> - FT_Vector pen = {
> - .x = 0,
> - .y = 0,
> - };
> - int i_face_height = 0;
> - FT_BBox line_bbox = {
> - .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;
> -
> -#define SAVE_BP(dst) do { \
> - dst.i_index = i_index; \
> - 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 );
> - SAVE_BP( break_point_fallback );
> -
> - while( i_index < i_start + i_length )
> - {
> - /* Split by common FT_Face + Size */
> - const text_style_t *p_current_style = pp_styles[i_index];
> - int i_part_length = 0;
> - while( i_index + i_part_length < i_start + i_length )
> - {
> - const text_style_t *p_style = pp_styles[i_index + i_part_length];
> - if( !FaceStyleEquals( p_style, p_current_style ) ||
> - p_style->i_font_size != p_current_style->i_font_size )
> - break;
> - i_part_length++;
> - }
> -
> - /* (Re)load/reconfigure the face if needed */
> - if( !FaceStyleEquals( p_current_style, p_previous_style ) )
> - {
> - if( p_face )
> - FT_Done_Face( p_face );
> - p_previous_style = NULL;
> -
> - p_face = LoadFace( p_filter, p_current_style );
> - }
> - FT_Face p_current_face = p_face ? p_face : p_sys->p_face;
> - if( !p_previous_style || p_previous_style->i_font_size != p_current_style->i_font_size ||
> - ((p_previous_style->i_style_flags ^ p_current_style->i_style_flags) & STYLE_HALFWIDTH) )
> -
> - {
> - int i_font_width = ( p_current_style->i_style_flags & STYLE_HALFWIDTH )
> - ? p_current_style->i_font_size / 2
> - : p_current_style->i_font_size;
> - if( FT_Set_Pixel_Sizes( p_current_face,
> - i_font_width,
> - 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 )
> - {
> - double f_outline_thickness = var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
> - f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
> - int i_radius = (p_current_style->i_font_size << 6) * 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(FT_MulFix(p_current_face->height,
> - p_current_face->size->metrics.y_scale)));
> -
> - /* Render the part */
> - bool b_break_line = false;
> - int i_glyph_last = 0;
> - FT_Vector advance = {
> - .x = 0,
> - .y = 0,
> - };
> - while( i_part_length > 0 )
> - {
> - const text_style_t *p_glyph_style = pp_styles[i_index];
> - uni_char_t character = psz_text[i_index];
> - int i_glyph_index = FT_Get_Char_Index( p_current_face, character );
> -
> - /* If the missing glyph is U+FEFF (ZERO WIDTH NO-BREAK SPACE) */
> - /* we can safely ignore it. Otherwise extra squares show up */
> - /* in Arabic text. */
> - if( i_glyph_index == 0 && character == 0xFEFF )
> - goto next;
> -
> -/* These are the most common Arabic diacritics */
> -#define DIACRITIC( a ) ( a >= 0x064B && a <= 0x0653 )
> -
> - /* Diacritics should be rendered over the preceding base glyph */
> - if( DIACRITIC( character ) )
> - {
> - pen.x -= advance.x;
> - pen.y -= advance.y;
> - }
> -
> - /* Get kerning vector */
> - FT_Vector kerning = { .x = 0, .y = 0 };
> - if( FT_HAS_KERNING( p_current_face ) && i_glyph_last != 0 && i_glyph_index != 0 )
> - {
> - FT_Get_Kerning( p_current_face, i_glyph_last, i_glyph_index, ft_kerning_default, &kerning );
> - }
> - if( p_glyph_style->i_spacing > 0 && i_glyph_last != 0 && i_glyph_index != 0 )
> - {
> - kerning.x = (p_glyph_style->i_spacing) << 6;
> - }
> -
> - /* 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,
> - };
> -
> - int i_font_width = ( p_current_style->i_style_flags & STYLE_HALFWIDTH )
> - ? p_current_style->i_font_size / 2
> - : p_current_style->i_font_size;
> - FT_Vector pen_shadow_new = {
> - .x = pen_new.x + p_sys->f_shadow_vector_x * (i_font_width << 6),
> - .y = pen_new.y + p_sys->f_shadow_vector_y * (p_current_style->i_font_size << 6),
> - };
> -
> - FT_Glyph glyph;
> - FT_BBox glyph_bbox;
> - FT_Glyph outline;
> - FT_BBox outline_bbox;
> - FT_Glyph shadow;
> - FT_BBox shadow_bbox;
> -
> - if( GetGlyph( p_filter,
> - &glyph, &glyph_bbox,
> - &outline, &outline_bbox,
> - &shadow, &shadow_bbox,
> - p_current_face, i_glyph_index, p_glyph_style->i_style_flags,
> - &pen_new, &pen_shadow_new ) )
> - goto next;
> -
> - FixGlyph( glyph, &glyph_bbox, p_current_face, &pen_new );
> - if( outline )
> - FixGlyph( outline, &outline_bbox, p_current_face, &pen_new );
> - if( shadow )
> - FixGlyph( shadow, &shadow_bbox, p_current_face, &pen_shadow_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_line_offset = 0;
> - int i_line_thickness = 0;
> - if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
> - {
> - 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_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_BBox line_bbox_new = line_bbox;
> - BBoxEnlarge( &line_bbox_new, &glyph_bbox );
> - if( outline )
> - BBoxEnlarge( &line_bbox_new, &outline_bbox );
> - if( shadow )
> - BBoxEnlarge( &line_bbox_new, &shadow_bbox );
> -
> - b_break_line = i_index > i_start &&
> - line_bbox_new.xMax - line_bbox_new.xMin >= (int)p_filter->fmt_out.video.i_visible_width;
> - if( b_break_line )
> - {
> - FT_Done_Glyph( glyph );
> - if( outline )
> - FT_Done_Glyph( outline );
> - if( shadow )
> - FT_Done_Glyph( shadow );
> -
> - break_point_t *p_bp = NULL;
> - if( break_point.i_index > i_start )
> - p_bp = &break_point;
> - else if( break_point_fallback.i_index > i_start )
> - p_bp = &break_point_fallback;
> -
> - if( p_bp )
> - {
> - msg_Dbg( p_filter, "Breaking line");
> - for( int i = p_bp->i_index; i < i_index; i++ )
> - {
> - 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 );
> - if( ch->p_shadow )
> - FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
> - }
> - 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
> - {
> - msg_Err( p_filter, "Breaking unbreakable line");
> - }
> - break;
> - }
> -
> - p_line->p_character[p_line->i_character_count++] = (line_character_t){
> - .p_glyph = (FT_BitmapGlyph)glyph,
> - .p_outline = (FT_BitmapGlyph)outline,
> - .p_shadow = (FT_BitmapGlyph)shadow,
> - .i_color = i_color,
> - .i_line_offset = i_line_offset,
> - .i_line_thickness = i_line_thickness,
> - };
> -
> - /* Diacritics do not determine advance values. We use */
> - /* the advance values from the last encountered base glyph, */
> - /* since multiple diacritics may follow a single base glyph. */
> - if( !DIACRITIC( character ) )
> - {
> - advance.x = p_current_face->glyph->advance.x;
> - advance.y = p_current_face->glyph->advance.y;
> - }
> -
> - pen.x = pen_new.x + advance.x;
> - pen.y = pen_new.y + advance.y;
> - line_bbox = line_bbox_new;
> - next:
> - i_glyph_last = i_glyph_index;
> - i_part_length--;
> - i_index++;
> -
> - if( character == ' ' || character == '\t' )
> - SAVE_BP( break_point );
> - else if( character == 160 )
> - SAVE_BP( break_point_fallback );
> - }
> - if( b_break_line )
> - break;
> - }
> -#undef SAVE_BP
> - /* Update our baseline */
> - if( i_face_height_previous > 0 )
> - i_base_line += __MAX(i_face_height, i_face_height_previous);
> - if( i_face_height > 0 )
> - 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 = __MAX(line_bbox.xMax - line_bbox.xMin, 0);
> - p_line->i_base_line = i_base_line;
> - p_line->i_height = __MAX(i_face_height, i_face_height_previous);
> - 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 - bbox.yMin >= (int)p_filter->fmt_out.video.i_visible_height )
> - {
> - msg_Err( p_filter, "Truncated too high subtitle" );
> - break;
> - }
> - }
> - if( p_face )
> - FT_Done_Face( p_face );
> -
> - free( pp_fribidi_styles );
> - free( p_fribidi_string );
> - free( pi_karaoke_bar );
> -
> - *p_bbox = bbox;
> - return VLC_SUCCESS;
> -}
> -
> static xml_reader_t *GetXMLReader( filter_t *p_filter, stream_t *p_sub )
> {
> xml_reader_t *p_xml_reader = p_filter->p_sys->p_xml;
> @@ -1766,9 +1044,9 @@ static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
>
> if( !rv && i_text_length > 0 )
> {
> - rv = ProcessLines( p_filter,
> - &p_lines, &bbox, &i_max_face_height,
> - psz_text, pp_styles, pi_k_durations, i_text_length );
> + rv = LayoutText( p_filter,
> + &p_lines, &bbox, &i_max_face_height,
> + psz_text, pp_styles, pi_k_durations, i_text_length );
> }
>
> p_region_out->i_x = p_region_in->i_x;
> @@ -2025,6 +1303,12 @@ static int Create( vlc_object_t *p_this )
> if( Init_FT( p_this, psz_fontfile, fontindex, f_outline_thickness ) != VLC_SUCCESS )
> goto error;
>
> + int i_faces_size = 20;
> + p_sys->faces_cache.p_faces = malloc( i_faces_size * sizeof( *p_sys->faces_cache.p_faces ) );
> + p_sys->faces_cache.p_styles = malloc( i_faces_size * sizeof( *p_sys->faces_cache.p_styles ) );
> + p_sys->faces_cache.i_cache_size = i_faces_size;
> + p_sys->faces_cache.i_faces_count = 0;
> +
> p_sys->pp_font_attachments = NULL;
> p_sys->i_font_attachments = 0;
>
> @@ -2055,6 +1339,7 @@ static void Destroy_FT( vlc_object_t *p_this )
>
> 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 );
> }
> @@ -2069,6 +1354,15 @@ static void Destroy( vlc_object_t *p_this )
> filter_t *p_filter = (filter_t *)p_this;
> filter_sys_t *p_sys = p_filter->p_sys;
>
> + faces_cache_t *p_cache = &p_sys->faces_cache;
> + for( int i = 0; i < p_cache->i_faces_count; ++i )
> + {
> + FT_Done_Face( p_cache->p_faces[ i ] );
> + free( p_cache->p_styles[ i ].psz_fontname );
> + }
> + free( p_sys->faces_cache.p_faces );
> + free( p_sys->faces_cache.p_styles );
> +
> if( p_sys->pp_font_attachments )
> {
> for( int k = 0; k < p_sys->i_font_attachments; k++ )
> @@ -2084,3 +1378,99 @@ static void Destroy( vlc_object_t *p_this )
> Destroy_FT( p_this );
> free( p_sys );
> }
> +
> +FT_Face LoadFace( filter_t *p_filter,
> + const text_style_t *p_style )
> +{
> + filter_sys_t *p_sys = p_filter->p_sys;
> +
> + faces_cache_t *p_cache = &p_sys->faces_cache;
> + for( int i = 0; i < p_cache->i_faces_count; ++i )
> + if( FaceStyleEquals( &p_cache->p_styles[ i ], p_style )
> + && p_cache->p_styles[ i ].i_font_size == p_style->i_font_size
> + && !( ( p_cache->p_styles[ i ].i_style_flags ^ p_style->i_style_flags ) & STYLE_HALFWIDTH ) )
> + return p_cache->p_faces[ i ];
> +
> + /* Look for a match amongst our attachments first */
> + FT_Face p_face = LoadEmbeddedFace( p_sys, p_style );
> +
> + /* Load system wide font otheriwse */
> + if( !p_face )
> + {
> + int i_idx = 0;
> + char *psz_fontfile = NULL;
> + if( p_sys->pf_select )
> + psz_fontfile = p_sys->pf_select( p_filter,
> + p_style->psz_fontname,
> + (p_style->i_style_flags & STYLE_BOLD) != 0,
> + (p_style->i_style_flags & STYLE_ITALIC) != 0,
> + -1,
> + &i_idx );
> + else
> + psz_fontfile = NULL;
> +
> + if( !psz_fontfile )
> + return NULL;
> +
> + if( *psz_fontfile == '\0' )
> + {
> + msg_Warn( p_filter,
> + "We were not able to find a matching font: \"%s\" (%s %s),"
> + " so using default font",
> + p_style->psz_fontname,
> + (p_style->i_style_flags & STYLE_BOLD) ? "Bold" : "",
> + (p_style->i_style_flags & STYLE_ITALIC) ? "Italic" : "" );
> + p_face = NULL;
> + }
> + else
> + {
> + if( FT_New_Face( p_sys->p_library, psz_fontfile, i_idx, &p_face ) )
> + p_face = NULL;
> + }
> + free( psz_fontfile );
> + }
> + if( !p_face )
> + return NULL;
> +
> + if( FT_Select_Charmap( p_face, ft_encoding_unicode ) )
> + {
> + /* We've loaded a font face which is unhelpful for actually
> + * rendering text - fallback to the default one.
> + */
> + FT_Done_Face( p_face );
> + return NULL;
> + }
> +
> + if( p_cache->i_faces_count == p_cache->i_cache_size )
> + {
> + FT_Face *p_new_faces =
> + realloc( p_cache->p_faces, p_cache->i_cache_size * 2 * sizeof( *p_cache->p_faces ) );
> + if( !p_new_faces )
> + {
> + FT_Done_Face( p_face );
> + return NULL;
> + }
> +
> + p_cache->p_faces = p_new_faces;
> +
> + text_style_t *p_new_styles =
> + realloc( p_cache->p_styles, p_cache->i_cache_size * 2 * sizeof( *p_cache->p_styles ) ) ;
> + if( !p_new_styles )
> + {
> + FT_Done_Face( p_face );
> + return NULL;
> + }
> +
> + p_cache->p_styles = p_new_styles;
> + p_cache->i_cache_size *= 2;
> + }
> +
> + text_style_t *p_face_style = p_cache->p_styles + p_cache->i_faces_count;
> + p_face_style->i_font_size = p_style->i_font_size;
> + p_face_style->i_style_flags = p_style->i_style_flags;
> + p_face_style->psz_fontname = strdup( p_style->psz_fontname );
> + p_cache->p_faces[ p_cache->i_faces_count ] = p_face;
> + ++p_cache->i_faces_count;
> +
> + return p_face;
> +}
> diff --git a/modules/text_renderer/freetype.h b/modules/text_renderer/freetype.h
> new file mode 100644
> index 0000000..7f5ce5e
> --- /dev/null
> +++ b/modules/text_renderer/freetype.h
> @@ -0,0 +1,72 @@
> +/*****************************************************************************
> + * freetype.h : Put text on the video, using freetype2
> + *****************************************************************************
> + * Copyright (C) 2015 VLC authors and VideoLAN
> + * $Id$
> + *
> + * Authors: Salah-Eddin Shaban <salshaaban at gmail.com>
> + *
> + * 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
> + * the Free Software Foundation; either version 2.1 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program; if not, write to the Free Software Foundation, Inc.,
> + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
> + *****************************************************************************/
> +
> +typedef struct faces_cache_t
> +{
> + FT_Face *p_faces;
> + text_style_t *p_styles;
> + int i_faces_count;
> + int i_cache_size;
> +} faces_cache_t;
> +
> +/*****************************************************************************
> + * filter_sys_t: freetype local data
> + *****************************************************************************
> + * This structure is part of the video output thread descriptor.
> + * It describes the freetype specific properties of an output thread.
> + *****************************************************************************/
> +struct filter_sys_t
> +{
> + FT_Library p_library; /* handle to library */
> + FT_Face p_face; /* handle to face object */
> + FT_Stroker p_stroker; /* handle to path stroker object */
> +
> + xml_reader_t *p_xml; /* vlc xml parser */
> +
> + text_style_t style; /* Current Style */
> +
> + /* More styles... */
> + float f_shadow_vector_x;
> + float f_shadow_vector_y;
> + int i_default_font_size;
> +
> + /* Attachments */
> + input_attachment_t **pp_font_attachments;
> + int i_font_attachments;
> +
> + /* Font faces cache */
> + faces_cache_t faces_cache;
> +
> + char * (*pf_select) (filter_t *, const char* family,
> + bool bold, bool italic, int size,
> + int *index);
> +
> +};
> +
> +#define FT_FLOOR(X) ((X & -64) >> 6)
> +#define FT_CEIL(X) (((X + 63) & -64) >> 6)
> +#ifndef FT_MulFix
> + #define FT_MulFix(v, s) (((v)*(s))>>16)
> +#endif
> +
> +FT_Face LoadFace( filter_t *p_filter, const text_style_t *p_style );
> diff --git a/modules/text_renderer/text_layout.c b/modules/text_renderer/text_layout.c
> new file mode 100644
> index 0000000..cb441d4
> --- /dev/null
> +++ b/modules/text_renderer/text_layout.c
> @@ -0,0 +1,1273 @@
> +/*****************************************************************************
> + * text_layout.c : Text shaping and layout
> + *****************************************************************************
> + * Copyright (C) 2015 VLC authors and VideoLAN
> + * $Id$
> + *
> + * Authors: Salah-Eddin Shaban <salshaaban at gmail.com>
> + *
> + * 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
> + * the Free Software Foundation; either version 2.1 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program; if not, write to the Free Software Foundation, Inc.,
> + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
> + *****************************************************************************/
> +
> +#include <vlc_common.h>
> +#include <vlc_filter.h>
> +#include <vlc_text_style.h>
> +
> +/* Freetype */
> +#include <ft2build.h>
> +#include FT_FREETYPE_H
> +#include FT_GLYPH_H
> +#include FT_STROKER_H
> +#include FT_SYNTHESIS_H
> +
> +/* RTL */
> +#if defined(HAVE_FRIBIDI)
> +# include <fribidi/fribidi.h>
> +#endif
> +
> +/* Complex Scripts */
> +#if defined(HAVE_HARFBUZZ)
> +# include <hb.h>
> +# include <hb-ft.h>
> +#endif
> +
> +#include "text_renderer.h"
> +#include "text_layout.h"
> +#include "freetype.h"
> +
> +/*
> + * Within a paragraph, run_desc_t represents a run of characters
> + * having the same font face, size, and style, Unicode script
> + * and text direction
> + */
> +typedef struct run_desc_t
> +{
> + int i_start_offset;
> + int i_end_offset;
> + FT_Face p_face;
> + text_style_t *p_style;
> +
> +#ifdef HAVE_HARFBUZZ
> + hb_script_t script;
> + hb_direction_t direction;
> + hb_font_t *p_hb_font;
> + hb_buffer_t *p_buffer;
> + hb_glyph_info_t *p_glyph_infos;
> + hb_glyph_position_t *p_glyph_positions;
> + unsigned int i_glyph_count;
> +#endif
> +
> +} run_desc_t;
> +
> +/*
> + * Glyph bitmaps. Advance and offset are 26.6 values
> + */
> +typedef struct glyph_bitmaps_t
> +{
> + FT_Glyph p_glyph;
> + FT_Glyph p_outline;
> + FT_Glyph p_shadow;
> + FT_BBox glyph_bbox;
> + FT_BBox outline_bbox;
> + FT_BBox shadow_bbox;
> + int i_x_offset;
> + int i_y_offset;
> + int i_x_advance;
> + int i_y_advance;
> +} glyph_bitmaps_t;
> +
> +typedef struct paragraph_t
> +{
> + uni_char_t *p_code_points; //Unicode code points
> + int *pi_glyph_indices; //Glyph index values within the run's font face
> + text_style_t **pp_styles;
> + int *pi_run_ids; //The run to which each glyph belongs
> + glyph_bitmaps_t *p_glyph_bitmaps;
> + uint8_t *pi_karaoke_bar;
> + int i_size;
> + run_desc_t *p_runs;
> + int i_runs_count;
> + int i_runs_size;
> +
> +#ifdef HAVE_HARFBUZZ
> + hb_script_t *p_scripts;
> +#endif
> +
> +#ifdef HAVE_FRIBIDI
> + FriBidiCharType *p_types;
> + FriBidiLevel *p_levels;
> + FriBidiStrIndex *pi_reordered_indices;
> + FriBidiParType paragraph_type;
> +#endif
> +
> +} paragraph_t;
> +
> +void FreeLine( line_desc_t *p_line )
> +{
> + 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 );
> + if( ch->p_shadow )
> + FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
> + }
> +
> + free( p_line->p_character );
> + free( p_line );
> +}
> +
> +void FreeLines( line_desc_t *p_lines )
> +{
> + for( line_desc_t *p_line = p_lines; p_line != NULL; )
> + {
> + line_desc_t *p_next = p_line->p_next;
> + FreeLine( p_line );
> + p_line = p_next;
> + }
> +}
> +
> +line_desc_t *NewLine( int i_count )
> +{
> + line_desc_t *p_line = malloc( sizeof(*p_line) );
> +
> + if( !p_line )
> + return NULL;
> +
> + p_line->p_next = NULL;
> + p_line->i_width = 0;
> + p_line->i_base_line = 0;
> + p_line->i_character_count = 0;
> +
> + p_line->bbox.xMin = INT_MAX;
> + p_line->bbox.yMin = INT_MAX;
> + p_line->bbox.xMax = INT_MIN;
> + p_line->bbox.yMax = INT_MIN;
> +
> + p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
> + if( !p_line->p_character )
> + {
> + free( p_line );
> + return NULL;
> + }
> + return p_line;
> +}
> +
> +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 paragraph_t *NewParagraph( filter_t *p_filter,
> + int i_size,
> + uni_char_t *p_code_points,
> + text_style_t **pp_styles,
> + uint32_t *pi_k_dates,
> + int i_runs_size )
> +{
> + paragraph_t *p_paragraph = calloc( 1, sizeof( paragraph_t ) );
> + if( !p_paragraph )
> + return 0;
> +
> + p_paragraph->i_size = i_size;
> + p_paragraph->p_code_points =
> + malloc( i_size * sizeof( *p_paragraph->p_code_points ) );
> + p_paragraph->pi_glyph_indices =
> + malloc( i_size * sizeof( *p_paragraph->pi_glyph_indices ) );
> + p_paragraph->pp_styles =
> + malloc( i_size * sizeof( *p_paragraph->pp_styles ) );
> + p_paragraph->pi_run_ids =
> + calloc( i_size, sizeof( *p_paragraph->pi_run_ids ) );
> + p_paragraph->p_glyph_bitmaps =
> + calloc( i_size, sizeof( *p_paragraph->p_glyph_bitmaps ) );
> + p_paragraph->pi_karaoke_bar =
> + calloc( i_size, sizeof( *p_paragraph->pi_karaoke_bar ) );
> +
> + p_paragraph->p_runs = calloc( i_runs_size, sizeof( run_desc_t ) );
> + p_paragraph->i_runs_size = i_runs_size;
> + p_paragraph->i_runs_count = 0;
> +
> + if( !p_paragraph->p_code_points || !p_paragraph->pi_glyph_indices
> + || !p_paragraph->pp_styles || !p_paragraph->pi_run_ids
> + || !p_paragraph->p_glyph_bitmaps || !p_paragraph->pi_karaoke_bar )
> + goto error;
> +
> + if( p_code_points )
> + memcpy( p_paragraph->p_code_points, p_code_points,
> + i_size * sizeof( *p_code_points ) );
> + if( pp_styles )
> + memcpy( p_paragraph->pp_styles, pp_styles,
> + i_size * sizeof( *pp_styles ) );
> + if( pi_k_dates )
> + {
> + int64_t i_elapsed = var_GetTime( p_filter, "spu-elapsed" ) / 1000;
> + for( int i = 0; i < i_size; ++i )
> + {
> + p_paragraph->pi_karaoke_bar[ i ] = pi_k_dates[ i ] >= i_elapsed;
> + }
> + }
> +
> +#ifdef HAVE_HARFBUZZ
> + p_paragraph->p_scripts = malloc( i_size * sizeof( *p_paragraph->p_scripts ) );
> + if( !p_paragraph->p_scripts )
> + goto error;
> +#endif
> +
> +#ifdef HAVE_FRIBIDI
> + p_paragraph->p_levels = malloc( i_size * sizeof( *p_paragraph->p_levels ) );
> + p_paragraph->p_types = malloc( i_size * sizeof( *p_paragraph->p_types ) );
> + p_paragraph->pi_reordered_indices =
> + malloc( i_size * sizeof( *p_paragraph->pi_reordered_indices ) );
> +
> + if( !p_paragraph->p_levels || !p_paragraph->p_types
> + || !p_paragraph->pi_reordered_indices )
> + goto error;
> +
> + int i_direction = var_InheritInteger( p_filter, "freetype-text-direction" );
> + if( i_direction == 0 )
> + p_paragraph->paragraph_type = FRIBIDI_PAR_LTR;
> + else if( i_direction == 1 )
> + p_paragraph->paragraph_type = FRIBIDI_PAR_RTL;
> + else
> + p_paragraph->paragraph_type = FRIBIDI_PAR_ON;
> +#endif
> +
> + return p_paragraph;
> +
> +error:
> + if( p_paragraph->p_code_points ) free( p_paragraph->p_code_points );
> + if( p_paragraph->pi_glyph_indices ) free( p_paragraph->pi_glyph_indices );
> + if( p_paragraph->pp_styles ) free( p_paragraph->pp_styles );
> + if( p_paragraph->pi_run_ids ) free( p_paragraph->pi_run_ids );
> + if( p_paragraph->p_glyph_bitmaps ) free( p_paragraph->p_glyph_bitmaps );
> + if (p_paragraph->pi_karaoke_bar ) free( p_paragraph->pi_karaoke_bar );
> + if( p_paragraph->p_runs ) free( p_paragraph->p_runs );
> +#ifdef HAVE_HARFBUZZ
> + if( p_paragraph->p_scripts ) free( p_paragraph->p_scripts );
> +#endif
> +#ifdef HAVE_FRIBIDI
> + if( p_paragraph->p_levels ) free( p_paragraph->p_levels );
> + if( p_paragraph->p_types ) free( p_paragraph->p_types );
> + if( p_paragraph->pi_reordered_indices )
> + free( p_paragraph->pi_reordered_indices );
> +#endif
> + free( p_paragraph );
> + return 0;
> +}
> +
> +static void FreeParagraph( paragraph_t *p_paragraph )
> +{
> + free( p_paragraph->p_runs );
> + free( p_paragraph->pi_glyph_indices );
> + free( p_paragraph->p_glyph_bitmaps );
> + free( p_paragraph->pi_karaoke_bar );
> + free( p_paragraph->pi_run_ids );
> + free( p_paragraph->pp_styles );
> + free( p_paragraph->p_code_points );
> +
> +#ifdef HAVE_HARFBUZZ
> + free( p_paragraph->p_scripts );
> +#endif
> +
> +#ifdef HAVE_FRIBIDI
> + free( p_paragraph->pi_reordered_indices );
> + free( p_paragraph->p_types );
> + free( p_paragraph->p_levels );
> +#endif
> +
> + free( p_paragraph );
> +}
> +
> +#ifdef HAVE_FRIBIDI
> +static int AnalyzeParagraph( paragraph_t *p_paragraph )
> +{
> + fribidi_get_bidi_types( p_paragraph->p_code_points,
> + p_paragraph->i_size,
> + p_paragraph->p_types );
> + fribidi_get_par_embedding_levels( p_paragraph->p_types,
> + p_paragraph->i_size,
> + &p_paragraph->paragraph_type,
> + p_paragraph->p_levels );
> +
> +#ifdef HAVE_HARFBUZZ
> + hb_unicode_funcs_t *p_funcs = hb_unicode_funcs_get_default();
> + for( int i = 0; i < p_paragraph->i_size; ++i )
> + p_paragraph->p_scripts[ i ] =
> + hb_unicode_script( p_funcs, p_paragraph->p_code_points[ i ] );
> +
> + hb_script_t i_last_script;
> + int i_last_script_index = -1;
> + int i_last_set_index = -1;
> +
> + /*
> + * For shaping to work, characters that are assigned HB_SCRIPT_COMMON or
> + * HB_SCRIPT_INHERITED should be resolved to the last encountered valid
> + * script value, if any, and to the first one following them otherwise
> + */
> + for( int i = 0; i < p_paragraph->i_size; ++i )
> + {
> + if( p_paragraph->p_scripts[ i ] == HB_SCRIPT_COMMON
> + || p_paragraph->p_scripts[ i ] == HB_SCRIPT_INHERITED)
> + {
> + if( i_last_script_index != -1)
> + {
> + p_paragraph->p_scripts[ i ] = i_last_script;
> + i_last_set_index = i;
> + }
> + }
> + else
> + {
> + for( int j = i_last_set_index + 1; j < i; ++j )
> + p_paragraph->p_scripts[ j ] = p_paragraph->p_scripts[ i ];
> +
> + i_last_script = p_paragraph->p_scripts[ i ];
> + i_last_script_index = i;
> + i_last_set_index = i;
> + }
> + }
> +#endif //HAVE_HARFBUZZ
> +
> + return VLC_SUCCESS;
> +}
> +#endif //HAVE_FRIBIDI
> +
> +static int AddRun( filter_t *p_filter,
> + paragraph_t *p_paragraph,
> + int i_start_offset,
> + int i_end_offset,
> + FT_Face p_face )
> +{
> + if( i_start_offset >= i_end_offset
> + || i_start_offset < 0 || i_start_offset >= p_paragraph->i_size
> + || i_end_offset <= 0 || i_end_offset > p_paragraph->i_size )
> + {
> + msg_Err( p_filter,
> + "AddRun() invalid parameters. Paragraph size: %d, "
> + "Start offset: %d, End offset: %d",
> + p_paragraph->i_size, i_start_offset, i_end_offset );
> + return VLC_EGENERIC;
> + }
> +
> + if( p_paragraph->i_runs_count == p_paragraph->i_runs_size )
> + {
> + run_desc_t *p_new_runs =
> + realloc( p_paragraph->p_runs,
> + p_paragraph->i_runs_size * 2 * sizeof( *p_new_runs ) );
> + if( !p_new_runs )
> + return VLC_ENOMEM;
> + p_paragraph->p_runs = p_new_runs;
> + p_paragraph->i_runs_size *= 2;
> + }
> +
> + int i_run_id = p_paragraph->i_runs_count;
> + run_desc_t *p_run = p_paragraph->p_runs + p_paragraph->i_runs_count++;
> + p_run->i_start_offset = i_start_offset;
> + p_run->i_end_offset = i_end_offset;
> + p_run->p_style = p_paragraph->pp_styles[ i_start_offset ];
> + p_run->p_face = p_face;
> +
> +#ifdef HAVE_HARFBUZZ
> + p_run->script = p_paragraph->p_scripts[ i_start_offset ];
> + p_run->direction = p_paragraph->p_levels[ i_start_offset ] & 1 ?
> + HB_DIRECTION_RTL : HB_DIRECTION_LTR;
> +#endif
> +
> + for( int i = i_start_offset; i < i_end_offset; ++i )
> + p_paragraph->pi_run_ids[ i ] = i_run_id;
> +
> + return VLC_SUCCESS;
> +}
> +
> +/*
> + * Segment a paragraph into runs
> + */
> +static int ItemizeParagraph( filter_t *p_filter, paragraph_t *p_paragraph )
> +{
> + if( p_paragraph->i_size <= 0 )
> + {
> + msg_Err( p_filter,
> + "ItemizeParagraph() invalid parameters. Paragraph size: %d",
> + p_paragraph->i_size );
> + return VLC_EGENERIC;
> + }
> +
> + int i_last_run_start = 0;
> + text_style_t *p_last_style = p_paragraph->pp_styles[ 0 ];
> +
> +#ifdef HAVE_HARFBUZZ
> + hb_script_t last_script = p_paragraph->p_scripts[ 0 ];
> + FriBidiLevel last_level = p_paragraph->p_levels[ 0 ];
> +#endif
> +
> + for( int i = 0; i <= p_paragraph->i_size; ++i )
> + {
> + if( i == p_paragraph->i_size
> +#ifdef HAVE_HARFBUZZ
> + || last_script != p_paragraph->p_scripts[ i ]
> + || last_level != p_paragraph->p_levels[ i ]
> +#endif
> + || p_last_style->i_font_size != p_paragraph->pp_styles[ i ]->i_font_size
> + || ( ( p_last_style->i_style_flags
> + ^ p_paragraph->pp_styles[ i ]->i_style_flags )
> + & STYLE_HALFWIDTH )
> + ||!FaceStyleEquals( p_last_style, p_paragraph->pp_styles[ i ] ) )
> + {
> + int i_ret = AddRun( p_filter, p_paragraph, i_last_run_start, i, 0 );
> + if( i_ret )
> + return i_ret;
> +
> + if( i < p_paragraph->i_size )
> + {
> + i_last_run_start = i;
> + p_last_style = p_paragraph->pp_styles[ i ];
> +#ifdef HAVE_HARFBUZZ
> + last_script = p_paragraph->p_scripts[ i ];
> + last_level = p_paragraph->p_levels[ i ];
> +#endif
> + }
> + }
> + }
> + return VLC_SUCCESS;
> +}
> +
> +/*
> + * Shape an itemized paragraph using HarfBuzz.
> + * This is where the glyphs of complex scripts get their positions
> + * (offsets and advance values) and final forms.
> + * Glyph substitutions of base glyphs and diacritics may take place,
> + * so the paragraph size may change.
> + */
> +#ifdef HAVE_HARFBUZZ
> +static int ShapeParagraphHarfBuzz( filter_t *p_filter,
> + paragraph_t **p_old_paragraph )
> +{
> + paragraph_t *p_paragraph = *p_old_paragraph;
> + paragraph_t *p_new_paragraph = 0;
> + filter_sys_t *p_sys = p_filter->p_sys;
> + int i_total_glyphs = 0;
> + int i_ret = VLC_EGENERIC;
> +
> + if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0 )
> + {
> + msg_Err( p_filter, "ShapeParagraphHarfBuzz() invalid parameters. "
> + "Paragraph size: %d. Runs count %d",
> + p_paragraph->i_size, p_paragraph->i_runs_count );
> + return VLC_EGENERIC;
> + }
> +
> + for( int i = 0; i < p_paragraph->i_runs_count; ++i )
> + {
> + run_desc_t *p_run = p_paragraph->p_runs + i;
> + text_style_t *p_style = p_run->p_style;
> +
> + /*
> + * When using HarfBuzz, this is where font faces are loaded.
> + * In the other two paths (shaping with FriBidi or no
> + * shaping at all), faces are loaded in LoadGlyphs()
> + */
> + FT_Face p_face = 0;
> + if( !p_run->p_face )
> + {
> + p_face = LoadFace( p_filter, p_style );
> + if( !p_face )
> + p_face = p_sys->p_face;
> + p_run->p_face = p_face;
> + }
> + else
> + p_face = p_run->p_face;
> +
> + int i_font_width = p_style->i_style_flags & STYLE_HALFWIDTH
> + ? p_style->i_font_size / 2 : p_style->i_font_size;
> +
> + if( FT_Set_Pixel_Sizes( p_face, i_font_width, p_style->i_font_size ) )
> + msg_Err( p_filter,
> + "Failed to set font size to %d", p_style->i_font_size );
> +
> + p_run->p_hb_font = hb_ft_font_create( p_face, 0 );
> + if( !p_run->p_hb_font )
> + {
> + msg_Err( p_filter,
> + "ShapeParagraphHarfBuzz(): hb_ft_font_create() error" );
> + i_ret = VLC_EGENERIC;
> + goto error;
> + }
> +
> + p_run->p_buffer = hb_buffer_create();
> + if( !p_run->p_buffer )
> + {
> + msg_Err( p_filter,
> + "ShapeParagraphHarfBuzz(): hb_buffer_create() error" );
> + i_ret = VLC_EGENERIC;
> + goto error;
> + }
> +
> + hb_buffer_set_direction( p_run->p_buffer, p_run->direction );
> + hb_buffer_set_script( p_run->p_buffer, p_run->script );
> +#ifdef __OS2__
> + hb_buffer_add_utf16( p_run->p_buffer,
> + p_paragraph->p_code_points + p_run->i_start_offset,
> + p_run->i_end_offset - p_run->i_start_offset, 0,
> + p_run->i_end_offset - p_run->i_start_offset );
> +#else
> + hb_buffer_add_utf32( p_run->p_buffer,
> + p_paragraph->p_code_points + p_run->i_start_offset,
> + p_run->i_end_offset - p_run->i_start_offset, 0,
> + p_run->i_end_offset - p_run->i_start_offset );
> +#endif
> + hb_shape( p_run->p_hb_font, p_run->p_buffer, 0, 0 );
> + p_run->p_glyph_infos =
> + hb_buffer_get_glyph_infos( p_run->p_buffer, &p_run->i_glyph_count );
> + p_run->p_glyph_positions =
> + hb_buffer_get_glyph_positions( p_run->p_buffer, &p_run->i_glyph_count );
> + i_total_glyphs += p_run->i_glyph_count;
> + }
> +
> + if( i_total_glyphs <= 0 )
> + {
> + msg_Err( p_filter,
> + "ShapeParagraphHarfBuzz() error. Shaped glyphs' count: %d",
> + i_total_glyphs );
> + i_ret = VLC_EGENERIC;
> + goto error;
> + }
> +
> + p_new_paragraph = NewParagraph( p_filter, i_total_glyphs, 0, 0, 0,
> + p_paragraph->i_runs_size );
> + if( !p_new_paragraph )
> + {
> + i_ret = VLC_ENOMEM;
> + goto error;
> + }
> + p_new_paragraph->paragraph_type = p_paragraph->paragraph_type;
> +
> + int i_index = 0;
> + for( int i = 0; i < p_paragraph->i_runs_count; ++i )
> + {
> + run_desc_t *p_run = p_paragraph->p_runs + i;
> + hb_glyph_info_t *p_infos = p_run->p_glyph_infos;
> + hb_glyph_position_t *p_positions = p_run->p_glyph_positions;
> + for( unsigned int j = 0; j < p_run->i_glyph_count; ++j )
> + {
> + /*
> + * HarfBuzz reverses the order of glyphs in RTL runs. We reverse
> + * it again here to keep the glyphs in theirs logical order.
> + * For line breaking of paragraphs to work correctly, visual
> + * reordering should be done after line breaking has taken
> + * place.
> + */
> + int i_run_index = p_run->direction == HB_DIRECTION_LTR ?
> + j : p_run->i_glyph_count - 1 - j;
> + int i_source_index =
> + p_infos[ i_run_index ].cluster + p_run->i_start_offset;
> +
> + p_new_paragraph->p_code_points[ i_index ] = 0;
> + p_new_paragraph->pi_glyph_indices[ i_index ] =
> + p_infos[ i_run_index ].codepoint;
> + p_new_paragraph->p_scripts[ i_index ] =
> + p_paragraph->p_scripts[ i_source_index ];
> + p_new_paragraph->p_types[ i_index ] =
> + p_paragraph->p_types[ i_source_index ];
> + p_new_paragraph->p_levels[ i_index ] =
> + p_paragraph->p_levels[ i_source_index ];
> + p_new_paragraph->pp_styles[ i_index ] =
> + p_paragraph->pp_styles[ i_source_index ];
> + p_new_paragraph->pi_karaoke_bar[ i_index ] =
> + p_paragraph->pi_karaoke_bar[ i_source_index ];
> + p_new_paragraph->p_glyph_bitmaps[ i_index ].i_x_offset =
> + p_positions[ i_run_index ].x_offset;
> + p_new_paragraph->p_glyph_bitmaps[ i_index ].i_y_offset =
> + p_positions[ i_run_index ].y_offset;
> + p_new_paragraph->p_glyph_bitmaps[ i_index ].i_x_advance =
> + p_positions[ i_run_index ].x_advance;
> + p_new_paragraph->p_glyph_bitmaps[ i_index ].i_y_advance =
> + p_positions[ i_run_index ].y_advance;
> +
> + ++i_index;
> + }
> + AddRun( p_filter, p_new_paragraph, i_index - p_run->i_glyph_count,
> + i_index, p_run->p_face );
> + }
> +
> + for( int i = 0; i < p_paragraph->i_runs_count; ++i )
> + {
> + hb_font_destroy( p_paragraph->p_runs[ i ].p_hb_font );
> + hb_buffer_destroy( p_paragraph->p_runs[ i ].p_buffer );
> + }
> + FreeParagraph( *p_old_paragraph );
> + *p_old_paragraph = p_new_paragraph;
> +
> + return VLC_SUCCESS;
> +
> +error:
> + for( int i = 0; i < p_paragraph->i_runs_count; ++i )
> + {
> + if( p_paragraph->p_runs[ i ].p_hb_font )
> + hb_font_destroy( p_paragraph->p_runs[ i ].p_hb_font );
> + if( p_paragraph->p_runs[ i ].p_buffer )
> + hb_buffer_destroy( p_paragraph->p_runs[ i ].p_buffer );
> + }
> +
> + if( p_new_paragraph )
> + FreeParagraph( p_new_paragraph );
> +
> + return i_ret;
> +}
> +#endif
> +
> +/*
> + * Shape a paragraph with FriBidi.
> + * Shaping with FriBidi is currently limited to mirroring and simple
> + * Arabic shaping.
> + */
> +#ifdef HAVE_FRIBIDI
> +#ifndef HAVE_HARFBUZZ
> +static int ShapeParagraphFriBidi( filter_t *p_filter, paragraph_t *p_paragraph )
> +{
> +
> + if( p_paragraph->i_size <= 0 )
> + {
> + msg_Err( p_filter,
> + "ShapeParagraphFriBidi() invalid parameters. Paragraph size: %d",
> + p_paragraph->i_size );
> + return VLC_EGENERIC;
> + }
> +
> + FriBidiJoiningType *p_joining_types =
> + malloc( p_paragraph->i_size * sizeof( *p_joining_types ) );
> + if( !p_joining_types )
> + return VLC_ENOMEM;
> +
> + fribidi_get_joining_types( p_paragraph->p_code_points,
> + p_paragraph->i_size, p_joining_types );
> + fribidi_join_arabic( p_paragraph->p_types, p_paragraph->i_size,
> + p_paragraph->p_levels, p_joining_types );
> + fribidi_shape( FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
> + p_paragraph->p_levels,
> + p_paragraph->i_size,
> + p_joining_types,
> + p_paragraph->p_code_points );
> +
> + free( p_joining_types );
> +
> + return VLC_SUCCESS;
> +}
> +
> +/*
> + * Zero-width invisible characters include Unicode control characters and
> + * zero-width spaces among other things. If not removed they can show up in the
> + * text as squares or other glyphs depending on the font. Zero-width spaces are
> + * inserted when shaping with FriBidi, when it performs glyph substitution for
> + * ligatures.
> + */
> +static int RemoveZeroWidthCharacters( paragraph_t *p_paragraph )
> +{
> + for( int i = 0; i < p_paragraph->i_size; ++i )
> + {
> + uni_char_t ch = p_paragraph->p_code_points[ i ];
> + if( ch == 0xfeff
> + || ch == 0x061c
> + || ( ch >= 0x202a && ch <= 0x202e )
> + || ( ch >= 0x2060 && ch <= 0x2069 )
> + || ( ch >= 0x200b && ch <= 0x200f ) )
> + {
> + glyph_bitmaps_t *p_bitmaps = p_paragraph->p_glyph_bitmaps + i;
> + if( p_bitmaps->p_glyph )
> + FT_Done_Glyph( p_bitmaps->p_glyph );
> + if( p_bitmaps->p_outline )
> + FT_Done_Glyph( p_bitmaps->p_outline );
> + p_bitmaps->p_glyph = 0;
> + p_bitmaps->p_outline = 0;
> + p_bitmaps->p_shadow = 0;
> + p_bitmaps->i_x_advance = 0;
> + p_bitmaps->i_y_advance = 0;
> + }
> + }
> +
> + return VLC_SUCCESS;
> +}
> +
> +/*
> + * Set advance values of non-spacing marks to zero. Diacritics are
> + * not positioned correctly but the text is more readable.
> + * For full shaping HarfBuzz is required.
> + */
> +static int ZeroNsmAdvance( paragraph_t *p_paragraph )
> +{
> + for( int i = 0; i < p_paragraph->i_size; ++i )
> + if( p_paragraph->p_types[ i ] == FRIBIDI_TYPE_NSM )
> + {
> + p_paragraph->p_glyph_bitmaps[ i ].i_x_advance = 0;
> + p_paragraph->p_glyph_bitmaps[ i ].i_y_advance = 0;
> + }
> + return VLC_SUCCESS;
> +}
> +#endif
> +#endif
> +
> +/*
> + * Load the glyphs of a paragraph. When shaping with HarfBuzz the glyph indices
> + * have already been determined at this point, as well as the advance values.
> + */
> +static int LoadGlyphs( filter_t *p_filter, paragraph_t *p_paragraph,
> + bool b_use_glyph_indices, bool b_overwrite_advance )
> +{
> + if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0 )
> + {
> + msg_Err( p_filter, "LoadGlyphs() invalid parameters. "
> + "Paragraph size: %d. Runs count %d", p_paragraph->i_size,
> + p_paragraph->i_runs_count );
> + return VLC_EGENERIC;
> + }
> +
> + filter_sys_t *p_sys = p_filter->p_sys;
> +
> + for( int i = 0; i < p_paragraph->i_runs_count; ++i )
> + {
> + run_desc_t *p_run = p_paragraph->p_runs + i;
> + text_style_t *p_style = p_run->p_style;
> +
> + FT_Face p_face;
> + if( !p_run->p_face )
> + {
> + p_face = LoadFace( p_filter, p_style );
> + if( !p_face )
> + p_face = p_sys->p_face;
> + p_run->p_face = p_face;
> + }
> + else
> + p_face = p_run->p_face;
> +
> + int i_font_width = p_style->i_style_flags & STYLE_HALFWIDTH ?
> + p_style->i_font_size / 2 : p_style->i_font_size;
> +
> + if( FT_Set_Pixel_Sizes( p_face, i_font_width, p_style->i_font_size ) )
> + msg_Err( p_filter,
> + "Failed to set font size to %d", p_style->i_font_size );
> +
> + if( p_sys->p_stroker )
> + {
> + double f_outline_thickness =
> + var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
> + f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
> + int i_radius = ( p_style->i_font_size << 6 ) * f_outline_thickness;
> + FT_Stroker_Set( p_sys->p_stroker,
> + i_radius,
> + FT_STROKER_LINECAP_ROUND,
> + FT_STROKER_LINEJOIN_ROUND, 0 );
> + }
> +
> + for( int j = p_run->i_start_offset; j < p_run->i_end_offset; ++j )
> + {
> + int i_glyph_index;
> + if( b_use_glyph_indices )
> + i_glyph_index = p_paragraph->pi_glyph_indices[ j ];
> + else
> + i_glyph_index =
> + FT_Get_Char_Index( p_face, p_paragraph->p_code_points[ j ] );
> +
> + glyph_bitmaps_t *p_bitmaps = p_paragraph->p_glyph_bitmaps + j;
> +
> + 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 ) )
> + {
> + p_bitmaps->p_glyph = 0;
> + p_bitmaps->p_outline = 0;
> + p_bitmaps->p_shadow = 0;
> + p_bitmaps->i_x_advance = 0;
> + p_bitmaps->i_y_advance = 0;
> + continue;
> + }
> +
> + if( ( p_style->i_style_flags & STYLE_BOLD )
> + && !( p_face->style_flags & FT_STYLE_FLAG_BOLD ) )
> + FT_GlyphSlot_Embolden( p_face->glyph );
> + if( ( p_style->i_style_flags & STYLE_ITALIC )
> + && !( p_face->style_flags & FT_STYLE_FLAG_ITALIC ) )
> + FT_GlyphSlot_Oblique( p_face->glyph );
> +
> + if( FT_Get_Glyph( p_face->glyph, &p_bitmaps->p_glyph ) )
> + {
> + p_bitmaps->p_glyph = 0;
> + p_bitmaps->p_outline = 0;
> + p_bitmaps->p_shadow = 0;
> + p_bitmaps->i_x_advance = 0;
> + p_bitmaps->i_y_advance = 0;
> + continue;
> + }
> +
> + if( p_filter->p_sys->p_stroker )
> + {
> + p_bitmaps->p_outline = p_bitmaps->p_glyph;
> + if( FT_Glyph_StrokeBorder( &p_bitmaps->p_outline,
> + p_filter->p_sys->p_stroker, 0, 0 ) )
> + p_bitmaps->p_outline = 0;
> + }
> +
> + if( p_filter->p_sys->style.i_shadow_alpha > 0 )
> + p_bitmaps->p_shadow = p_bitmaps->p_outline ?
> + p_bitmaps->p_outline : p_bitmaps->p_glyph;
> +
> + if( b_overwrite_advance )
> + {
> + p_bitmaps->i_x_advance = p_face->glyph->advance.x;
> + p_bitmaps->i_y_advance = p_face->glyph->advance.y;
> + }
> + }
> + }
> + return VLC_SUCCESS;
> +}
> +
> +static int NewLayoutLine( filter_t *p_filter,
> + paragraph_t *p_paragraph,
> + int i_start_offset, int i_end_offset,
> + line_desc_t **pp_line )
> +{
> + if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0
> + || i_start_offset >= i_end_offset
> + || i_start_offset < 0 || i_start_offset >= p_paragraph->i_size
> + || i_end_offset <= 0 || i_end_offset > p_paragraph->i_size )
> + {
> + msg_Err( p_filter,
> + "NewLayoutLine() invalid parameters. "
> + "Paragraph size: %d. Runs count: %d. "
> + "Start offset: %d. End offset: %d",
> + p_paragraph->i_size, p_paragraph->i_runs_count,
> + i_start_offset, i_end_offset );
> + return VLC_EGENERIC;
> + }
> +
> + line_desc_t *p_line = NewLine( i_end_offset - i_start_offset );
> + filter_sys_t *p_sys = p_filter->p_sys;
> + int i_last_run = -1;
> + run_desc_t *p_run = 0;
> + text_style_t *p_style = 0;
> + FT_Face p_face = 0;
> + FT_Vector pen = { .x = 0, .y = 0 };
> + int i_line_index = 0;
> +
> + int i_font_width = 0;
> + int i_ul_offset = 0;
> + int i_ul_thickness = 0;
> +
> +#ifdef HAVE_FRIBIDI
> + fribidi_reorder_line( 0, p_paragraph->p_types + i_start_offset,
> + i_end_offset - i_start_offset,
> + 0, p_paragraph->paragraph_type,
> + p_paragraph->p_levels + i_start_offset,
> + 0, p_paragraph->pi_reordered_indices + i_start_offset );
> +#endif
> +
> + for( int i = i_start_offset; i < i_end_offset; ++i, ++i_line_index )
> + {
> + int i_paragraph_index;
> +#ifdef HAVE_FRIBIDI
> + i_paragraph_index = p_paragraph->pi_reordered_indices[ i ];
> +#else
> + i_paragraph_index = i;
> +#endif
> +
> + line_character_t *p_ch = p_line->p_character + i_line_index;
> + glyph_bitmaps_t *p_bitmaps =
> + p_paragraph->p_glyph_bitmaps + i_paragraph_index;
> +
> + if( !p_bitmaps->p_glyph )
> + {
> + --i_line_index;
> + continue;
> + }
> +
> + if( i_last_run != p_paragraph->pi_run_ids[ i_paragraph_index ] )
> + {
> + i_last_run = p_paragraph->pi_run_ids[ i_paragraph_index ];
> + p_run = p_paragraph->p_runs + i_last_run;
> + p_style = p_run->p_style;
> + p_face = p_run->p_face;
> +
> + i_font_width = p_style->i_style_flags & STYLE_HALFWIDTH ?
> + p_style->i_font_size / 2 : p_style->i_font_size;
> + }
> +
> + FT_Vector pen_new = {
> + .x = pen.x + p_paragraph->p_glyph_bitmaps[ i_paragraph_index ].i_x_offset,
> + .y = pen.y + p_paragraph->p_glyph_bitmaps[ i_paragraph_index ].i_y_offset
> + };
> + FT_Vector pen_shadow = {
> + .x = pen_new.x + p_sys->f_shadow_vector_x * ( i_font_width << 6 ),
> + .y = pen_new.y + p_sys->f_shadow_vector_y * ( p_style->i_font_size << 6 )
> + };
> +
> + if( p_bitmaps->p_shadow )
> + {
> + if( FT_Glyph_To_Bitmap( &p_bitmaps->p_shadow, FT_RENDER_MODE_NORMAL,
> + &pen_shadow, 0 ) )
> + p_bitmaps->p_shadow = 0;
> + else
> + FT_Glyph_Get_CBox( p_bitmaps->p_shadow, ft_glyph_bbox_pixels,
> + &p_bitmaps->shadow_bbox );
> + }
> + if( p_bitmaps->p_glyph )
> + {
> + if( FT_Glyph_To_Bitmap( &p_bitmaps->p_glyph, FT_RENDER_MODE_NORMAL,
> + &pen_new, 1 ) )
> + {
> + FT_Done_Glyph( p_bitmaps->p_glyph );
> + if( p_bitmaps->p_outline )
> + FT_Done_Glyph( p_bitmaps->p_outline );
> + if( p_bitmaps->p_shadow )
> + FT_Done_Glyph( p_bitmaps->p_shadow );
> + --i_line_index;
> + continue;
> + }
> + else
> + FT_Glyph_Get_CBox( p_bitmaps->p_glyph, ft_glyph_bbox_pixels,
> + &p_bitmaps->glyph_bbox );
> + }
> + if( p_bitmaps->p_outline )
> + {
> + if( FT_Glyph_To_Bitmap( &p_bitmaps->p_outline, FT_RENDER_MODE_NORMAL,
> + &pen_new, 1 ) )
> + {
> + FT_Done_Glyph( p_bitmaps->p_outline );
> + p_bitmaps->p_outline = 0;
> + }
> + else
> + FT_Glyph_Get_CBox( p_bitmaps->p_outline, ft_glyph_bbox_pixels,
> + &p_bitmaps->outline_bbox );
> + }
> +
> + FixGlyph( p_bitmaps->p_glyph, &p_bitmaps->glyph_bbox,
> + p_face, &pen_new );
> + if( p_bitmaps->p_outline )
> + FixGlyph( p_bitmaps->p_outline, &p_bitmaps->outline_bbox,
> + p_face, &pen_new );
> + if( p_bitmaps->p_shadow )
> + FixGlyph( p_bitmaps->p_shadow, &p_bitmaps->shadow_bbox,
> + p_face, &pen_shadow );
> +
> + int i_line_offset = 0;
> + int i_line_thickness = 0;
> + text_style_t *p_glyph_style = p_paragraph->pp_styles[ i_paragraph_index ];
> + if( p_glyph_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
> + {
> + i_line_offset =
> + abs( FT_FLOOR( FT_MulFix( p_face->underline_position,
> + p_face->size->metrics.y_scale ) ) );
> +
> + i_line_thickness =
> + abs( FT_CEIL( FT_MulFix( p_face->underline_thickness,
> + p_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_line_offset -=
> + abs( FT_FLOOR( FT_MulFix( p_face->descender * 2,
> + p_face->size->metrics.y_scale ) ) );
> + }
> + else if( i_line_thickness > 0 )
> + {
> + p_bitmaps->glyph_bbox.yMin =
> + __MIN( p_bitmaps->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;
> + }
> + }
> +
> + p_ch->p_glyph = ( FT_BitmapGlyph ) p_bitmaps->p_glyph;
> + p_ch->p_outline = ( FT_BitmapGlyph ) p_bitmaps->p_outline;
> + p_ch->p_shadow = ( FT_BitmapGlyph ) p_bitmaps->p_shadow;
> +
> + bool b_karaoke = p_paragraph->pi_karaoke_bar[ i_paragraph_index ] != 0;
> + p_ch->i_color = b_karaoke ?
> + p_paragraph->pp_styles[ i_paragraph_index ]->i_karaoke_background_color
> + | p_paragraph->pp_styles[ i_paragraph_index ]->i_karaoke_background_alpha << 24
> + : p_paragraph->pp_styles[ i_paragraph_index ]->i_font_color
> + | p_paragraph->pp_styles[ i_paragraph_index ]->i_font_alpha << 24;
> +
> + p_ch->i_line_thickness = i_line_thickness;
> + p_ch->i_line_offset = i_line_offset;
> +
> + BBoxEnlarge( &p_line->bbox, &p_bitmaps->glyph_bbox );
> + if( p_bitmaps->p_outline )
> + BBoxEnlarge( &p_line->bbox, &p_bitmaps->outline_bbox );
> + if( p_bitmaps->p_shadow )
> + BBoxEnlarge( &p_line->bbox, &p_bitmaps->shadow_bbox );
> +
> + pen.x += p_bitmaps->i_x_advance;
> + pen.y += p_bitmaps->i_y_advance;
> + }
> +
> + p_line->i_width = __MAX( 0, p_line->bbox.xMax - p_line->bbox.xMin );
> + p_line->i_height = __MAX( 0, p_line->bbox.yMax - p_line->bbox.yMin );
> + p_line->i_character_count = i_line_index;
> +
> + 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 = p_line;
> + return VLC_SUCCESS;
> +}
> +
> +static int LayoutParagraph( filter_t *p_filter, paragraph_t *p_paragraph,
> + int i_max_width, line_desc_t **pp_lines )
> +{
> + if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0 )
> + {
> + msg_Err( p_filter, "LayoutParagraph() invalid parameters. "
> + "Paragraph size: %d. Runs count %d",
> + p_paragraph->i_size, p_paragraph->i_runs_count );
> + return VLC_EGENERIC;
> + }
> +
> + int i_line_start = 0;
> + FT_Pos i_width = 0;
> + int i_last_space = -1;
> + line_desc_t *p_first_line = 0;
> + line_desc_t **pp_line = &p_first_line;
> +
> +#ifdef HAVE_FRIBIDI
> + for( int i = 0; i < p_paragraph->i_size; ++i )
> + p_paragraph->pi_reordered_indices[ i ] = i;
> +#endif
> +
> + for( int i = 0; i <= p_paragraph->i_size; ++i )
> + {
> + if( i == p_paragraph->i_size )
> + {
> + if( i_line_start < i )
> + if( NewLayoutLine( p_filter, p_paragraph,
> + i_line_start, i, pp_line ) )
> + goto error;
> +
> + break;
> + }
> +
> + if( p_paragraph->p_code_points[ i ] == ' '
> +#ifdef HAVE_FRIBIDI
> + || p_paragraph->p_types[ i ] == FRIBIDI_TYPE_WS
> +#endif
> + )
> + {
> + if( i_line_start == i )
> + {
> + /*
> + * Free orphaned white space glyphs not belonging to any lines.
> + * At this point p_shadow points to either p_glyph or p_outline,
> + * so we should not free it explicitly.
> + */
> + if( p_paragraph->p_glyph_bitmaps[ i ].p_glyph )
> + FT_Done_Glyph( p_paragraph->p_glyph_bitmaps[ i ].p_glyph );
> + if( p_paragraph->p_glyph_bitmaps[ i ].p_outline )
> + FT_Done_Glyph( p_paragraph->p_glyph_bitmaps[ i ].p_outline );
> +
> + i_line_start = i + 1;
> + continue;
> + }
> +
> + if( i_last_space == i - 1 )
> + {
> + p_paragraph->p_glyph_bitmaps[ i - 1 ].i_x_advance = 0;
> + i_last_space = i;
> + continue;
> + }
> +
> + i_last_space = i;
> + }
> +
> + i_width += p_paragraph->p_glyph_bitmaps[ i ].i_x_advance;
> +
> + if( FT_CEIL( i_width ) >= i_max_width )
> + {
> + if( i_line_start == i )
> + {
> + msg_Err( p_filter,
> + "LayoutParagraph(): Width of single glyph exceeds maximum" );
> + goto error;
> + }
> +
> + int i_end_offset;
> + if( i_last_space > i_line_start )
> + i_end_offset = i_last_space;
> + else
> + i_end_offset = i;
> +
> + if( NewLayoutLine( p_filter, p_paragraph, i_line_start,
> + i_end_offset, pp_line ) )
> + goto error;
> +
> + pp_line = &( *pp_line )->p_next;
> + i_line_start = i_end_offset;
> + i = i_line_start - 1;
> + i_width = 0;
> + }
> + }
> +
> + *pp_lines = p_first_line;
> + return VLC_SUCCESS;
> +
> +error:
> + if( p_first_line )
> + FreeLines( p_first_line );
> + return VLC_EGENERIC;
> +}
> +
> +int LayoutText( filter_t *p_filter, line_desc_t **pp_lines,
> + FT_BBox *p_bbox, int *pi_max_face_height,
> +
> + uni_char_t *psz_text, text_style_t **pp_styles,
> + uint32_t *pi_k_dates, int i_len )
> +{
> + line_desc_t *p_first_line = 0;
> + line_desc_t **pp_line = &p_first_line;
> + paragraph_t *p_paragraph = 0;
> + int i_paragraph_start = 0;
> + int i_max_height = 0;
> +
> + for( int i = 0; i <= i_len; ++i )
> + {
> + if( i == i_len || psz_text[ i ] == '\n' )
> + {
> + if( i_paragraph_start == i )
> + {
> + i_paragraph_start = i + 1;
> + continue;
> + }
> +
> + p_paragraph = NewParagraph( p_filter, i - i_paragraph_start,
> + psz_text + i_paragraph_start,
> + pp_styles + i_paragraph_start,
> + pi_k_dates ?
> + pi_k_dates + i_paragraph_start : 0,
> + 20 );
> + if( !p_paragraph )
> + {
> + if( p_first_line ) FreeLines( p_first_line );
> + return VLC_ENOMEM;
> + }
> +
> +#ifdef HAVE_FRIBIDI
> + if( AnalyzeParagraph( p_paragraph ) )
> + goto error;
> +#endif
> +
> + if( ItemizeParagraph( p_filter, p_paragraph ) )
> + goto error;
> +
> +#if defined HAVE_HARFBUZZ
> + if( ShapeParagraphHarfBuzz( p_filter, &p_paragraph ) )
> + goto error;
> +
> + if( LoadGlyphs( p_filter, p_paragraph, true, false ) )
> + goto error;
> +
> +#elif defined HAVE_FRIBIDI
> + if( ShapeParagraphFriBidi( p_filter, p_paragraph ) )
> + goto error;
> + if( LoadGlyphs( p_filter, p_paragraph, false, true ) )
> + goto error;
> + if( RemoveZeroWidthCharacters( p_paragraph ) )
> + goto error;
> + if( ZeroNsmAdvance( p_paragraph ) )
> + goto error;
> +#else
> + if( LoadGlyphs( p_filter, p_paragraph, false, true ) )
> + goto error;
> +#endif
> +
> + /*
> + * Set max line width to allow for outline and shadow glyphs,
> + * and any extra width caused by visual reordering
> + */
> + int i_max_width = ( int ) p_filter->fmt_out.video.i_visible_width
> + - 2 * p_filter->p_sys->style.i_font_size;
> + if( LayoutParagraph( p_filter, p_paragraph,
> + i_max_width, pp_line ) )
> + goto error;
> +
> + for( ; *pp_line; pp_line = &( *pp_line )->p_next )
> + i_max_height = __MAX( i_max_height, ( *pp_line )->i_height );
> +
> + i_paragraph_start = i + 1;
> + }
> + }
> +
> + int i_base_line = 0;
> + FT_BBox bbox = {
> + .xMin = INT_MAX,
> + .yMin = INT_MAX,
> + .xMax = INT_MIN,
> + .yMax = INT_MIN
> + };
> +
> + for( line_desc_t *p_line = p_first_line; p_line; p_line = p_line->p_next )
> + {
> + p_line->i_base_line = i_base_line;
> + p_line->bbox.yMin -= i_base_line;
> + p_line->bbox.yMax -= i_base_line;
> + BBoxEnlarge( &bbox, &p_line->bbox );
> +
> + i_base_line += i_max_height;
> + }
> +
> + *pp_lines = p_first_line;
> + *p_bbox = bbox;
> + *pi_max_face_height = i_max_height;
> + return VLC_SUCCESS;
> +
> +error:
> + if( p_first_line ) FreeLines( p_first_line );
> + if( p_paragraph ) FreeParagraph( p_paragraph );
> + return VLC_EGENERIC;
> +}
> diff --git a/modules/text_renderer/text_layout.h b/modules/text_renderer/text_layout.h
> new file mode 100644
> index 0000000..1301a1b
> --- /dev/null
> +++ b/modules/text_renderer/text_layout.h
> @@ -0,0 +1,55 @@
> +/*****************************************************************************
> + * text_layout.h : Text shaping and layout
> + *****************************************************************************
> + * Copyright (C) 2015 VLC authors and VideoLAN
> + * $Id$
> + *
> + * Authors: Salah-Eddin Shaban <salshaaban at gmail.com>
> + *
> + * 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
> + * the Free Software Foundation; either version 2.1 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public License
> + * along with this program; if not, write to the Free Software Foundation, Inc.,
> + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
> + *****************************************************************************/
> +
> +typedef struct
> +{
> + FT_BitmapGlyph p_glyph;
> + FT_BitmapGlyph p_outline;
> + FT_BitmapGlyph p_shadow;
> + 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
> +{
> + line_desc_t *p_next;
> +
> + int i_width;
> + int i_height;
> + int i_base_line;
> + int i_character_count;
> + line_character_t *p_character;
> + FT_BBox bbox;
> +};
> +
> +void FreeLine( line_desc_t *p_line );
> +void FreeLines( line_desc_t *p_lines );
> +line_desc_t *NewLine( int i_count );
> +
> +int LayoutText( filter_t *p_filter, line_desc_t **pp_lines,
> + FT_BBox *p_bbox, int *pi_max_face_height,
> +
> + uni_char_t *psz_text, text_style_t **pp_styles,
> + uint32_t *pi_k_dates, int i_len );
> --
> 1.9.1
>
>
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
--
With my kindest regards,
--
Jean-Baptiste Kempf
http://www.jbkempf.com/ - +33 672 704 734
Sent from my Electronic Device
More information about the vlc-devel
mailing list