@ -59,6 +59,10 @@
# include "internal.h"
# include "video.h"
# if CONFIG_LIBFRIBIDI
# include <fribidi.h>
# endif
# include <ft2build.h>
# include FT_FREETYPE_H
# include FT_GLYPH_H
@ -182,6 +186,9 @@ typedef struct DrawTextContext {
int tc24hmax ; ///< 1 if timecode is wrapped to 24 hours, 0 otherwise
int reload ; ///< reload text file for each frame
int start_number ; ///< starting frame number for n/frame_num var
# if CONFIG_LIBFRIBIDI
int text_shaping ; ///< 1 to shape the text before drawing it
# endif
AVDictionary * metadata ;
} DrawTextContext ;
@ -226,6 +233,10 @@ static const AVOption drawtext_options[]= {
{ " fix_bounds " , " if true, check and fix text coords to avoid clipping " , OFFSET ( fix_bounds ) , AV_OPT_TYPE_INT , { . i64 = 1 } , 0 , 1 , FLAGS } ,
{ " start_number " , " start frame number for n/frame_num variable " , OFFSET ( start_number ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , FLAGS } ,
# if CONFIG_LIBFRIBIDI
{ " text_shaping " , " attempt to shape text before drawing " , OFFSET ( text_shaping ) , AV_OPT_TYPE_INT , { . i64 = 1 } , 0 , 1 , FLAGS } ,
# endif
/* FT_LOAD_* flags */
{ " ft_load_flags " , " set font loading flags for libfreetype " , OFFSET ( ft_load_flags ) , AV_OPT_TYPE_FLAGS , { . i64 = FT_LOAD_DEFAULT } , 0 , INT_MAX , FLAGS , " ft_load_flags " } ,
{ " default " , NULL , 0 , AV_OPT_TYPE_CONST , { . i64 = FT_LOAD_DEFAULT } , . flags = FLAGS , . unit = " ft_load_flags " } ,
@ -482,6 +493,99 @@ static int load_textfile(AVFilterContext *ctx)
return 0 ;
}
static inline int is_newline ( uint32_t c )
{
return c = = ' \n ' | | c = = ' \r ' | | c = = ' \f ' | | c = = ' \v ' ;
}
# if CONFIG_LIBFRIBIDI
static int shape_text ( AVFilterContext * ctx )
{
DrawTextContext * s = ctx - > priv ;
uint8_t * tmp ;
int ret = AVERROR ( ENOMEM ) ;
static const FriBidiFlags flags = FRIBIDI_FLAGS_DEFAULT |
FRIBIDI_FLAGS_ARABIC ;
FriBidiChar * unicodestr = NULL ;
FriBidiStrIndex len ;
FriBidiParType direction = FRIBIDI_PAR_LTR ;
FriBidiStrIndex line_start = 0 ;
FriBidiStrIndex line_end = 0 ;
FriBidiLevel * embedding_levels = NULL ;
FriBidiArabicProp * ar_props = NULL ;
FriBidiCharType * bidi_types = NULL ;
FriBidiStrIndex i , j ;
len = strlen ( s - > text ) ;
if ( ! ( unicodestr = av_malloc_array ( len , sizeof ( * unicodestr ) ) ) ) {
goto out ;
}
len = fribidi_charset_to_unicode ( FRIBIDI_CHAR_SET_UTF8 ,
s - > text , len , unicodestr ) ;
bidi_types = av_malloc_array ( len , sizeof ( * bidi_types ) ) ;
if ( ! bidi_types ) {
goto out ;
}
fribidi_get_bidi_types ( unicodestr , len , bidi_types ) ;
embedding_levels = av_malloc_array ( len , sizeof ( * embedding_levels ) ) ;
if ( ! embedding_levels ) {
goto out ;
}
if ( ! fribidi_get_par_embedding_levels ( bidi_types , len , & direction ,
embedding_levels ) ) {
goto out ;
}
ar_props = av_malloc_array ( len , sizeof ( * ar_props ) ) ;
if ( ! ar_props ) {
goto out ;
}
fribidi_get_joining_types ( unicodestr , len , ar_props ) ;
fribidi_join_arabic ( bidi_types , len , embedding_levels , ar_props ) ;
fribidi_shape ( flags , embedding_levels , len , ar_props , unicodestr ) ;
for ( line_end = 0 , line_start = 0 ; line_end < len ; line_end + + ) {
if ( is_newline ( unicodestr [ line_end ] ) | | line_end = = len - 1 ) {
if ( ! fribidi_reorder_line ( flags , bidi_types ,
line_end - line_start + 1 , line_start ,
direction , embedding_levels , unicodestr ,
NULL ) ) {
goto out ;
}
line_start = line_end + 1 ;
}
}
/* Remove zero-width fill chars put in by libfribidi */
for ( i = 0 , j = 0 ; i < len ; i + + )
if ( unicodestr [ i ] ! = FRIBIDI_CHAR_FILL )
unicodestr [ j + + ] = unicodestr [ i ] ;
len = j ;
if ( ! ( tmp = av_realloc ( s - > text , ( len * 4 + 1 ) * sizeof ( * s - > text ) ) ) ) {
/* Use len * 4, as a unicode character can be up to 4 bytes in UTF-8 */
goto out ;
}
s - > text = tmp ;
len = fribidi_unicode_to_charset ( FRIBIDI_CHAR_SET_UTF8 ,
unicodestr , len , s - > text ) ;
ret = 0 ;
out :
av_free ( unicodestr ) ;
av_free ( embedding_levels ) ;
av_free ( ar_props ) ;
av_free ( bidi_types ) ;
return ret ;
}
# endif
static av_cold int init ( AVFilterContext * ctx )
{
int err ;
@ -509,6 +613,12 @@ static av_cold int init(AVFilterContext *ctx)
return err ;
}
# if CONFIG_LIBFRIBIDI
if ( s - > text_shaping )
if ( ( err = shape_text ( ctx ) ) < 0 )
return err ;
# endif
if ( s - > reload & & ! s - > textfile )
av_log ( ctx , AV_LOG_WARNING , " No file to reload \n " ) ;
@ -617,11 +727,6 @@ static av_cold void uninit(AVFilterContext *ctx)
av_bprint_finalize ( & s - > expanded_text , NULL ) ;
}
static inline int is_newline ( uint32_t c )
{
return c = = ' \n ' | | c = = ' \r ' | | c = = ' \f ' | | c = = ' \v ' ;
}
static int config_input ( AVFilterLink * inlink )
{
AVFilterContext * ctx = inlink - > dst ;
@ -1132,9 +1237,15 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
DrawTextContext * s = ctx - > priv ;
int ret ;
if ( s - > reload )
if ( s - > reload ) {
if ( ( ret = load_textfile ( ctx ) ) < 0 )
return ret ;
# if CONFIG_LIBFRIBIDI
if ( s - > text_shaping )
if ( ( ret = shape_text ( ctx ) ) < 0 )
return ret ;
# endif
}
s - > var_values [ VAR_N ] = inlink - > frame_count + s - > start_number ;
s - > var_values [ VAR_T ] = frame - > pts = = AV_NOPTS_VALUE ?