[FFmpeg-devel] [PATCH v2] drawtext: Add basic text shaping using libfribidi - Fixes ticket #3758

Marc Jeffreys maj160 at live.co.uk
Thu Jul 10 12:47:16 CEST 2014


Changes since last time:
I've made the changes to configure, and squashed the patches together.
Option changed from fribidi=1 (default 0) to text_shaping=1 (default 1).
(Ideas for better names are definitely welcome.)
Hopefully I've made the documentation more understandable.
No longer testing for NULL before av_free.

---
 configure                 |   3 ++
 doc/filters.texi          |  11 ++++
 libavfilter/vf_drawtext.c | 130 +++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 138 insertions(+), 6 deletions(-)

diff --git a/configure b/configure
index 658efb2..6777d91 100755
--- a/configure
+++ b/configure
@@ -209,6 +209,7 @@ External library support:
   --enable-libfdk-aac      enable AAC de/encoding via libfdk-aac [no]
   --enable-libflite        enable flite (voice synthesis) support via libflite [no]
   --enable-libfreetype     enable libfreetype [no]
+  --enable-libfribidi      enable libfribidi [no]
   --enable-libgme          enable Game Music Emu via libgme [no]
   --enable-libgsm          enable GSM de/encoding via libgsm [no]
   --enable-libiec61883     enable iec61883 via libiec61883 [no]
@@ -1332,6 +1333,7 @@ EXTERNAL_LIBRARY_LIST="
     libflite
     libfontconfig
     libfreetype
+    libfribidi
     libgme
     libgsm
     libiec61883
@@ -4724,6 +4726,7 @@ enabled libflite          && require2 libflite "flite/flite.h" flite_init $flite
 enabled fontconfig        && enable libfontconfig
 enabled libfontconfig     && require_pkg_config fontconfig "fontconfig/fontconfig.h" FcInit
 enabled libfreetype       && require_libfreetype
+enabled libfribidi        && require_pkg_config fribidi fribidi.h fribidi_version_info
 enabled libgme            && require  libgme gme/gme.h gme_new_emu -lgme -lstdc++
 enabled libgsm            && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do
                                    check_lib "${gsm_hdr}" gsm_create -lgsm && break;
diff --git a/doc/filters.texi b/doc/filters.texi
index ada33a7..1b6f85e 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -3653,6 +3653,8 @@ To enable compilation of this filter, you need to configure FFmpeg with
 @code{--enable-libfreetype}.
 To enable default font fallback and the @var{font} option you need to
 configure FFmpeg with @code{--enable-libfontconfig}.
+To enable the @var{text_shaping} option, you need to configure FFmpeg with
+ at code{--enable-libfribidi}.
 
 @subsection Syntax
 
@@ -3707,6 +3709,12 @@ This parameter is mandatory if the fontconfig support is disabled.
 The font size to be used for drawing text.
 The default value of @var{fontsize} is 16.
 
+ at item text_shaping
+If set to 1, attempt to shape the text (for example, reverse the order of
+right-to-left text and join Arabic characters) before drawing it.
+Otherwise, just draw the text exactly as given.
+By default 1 (if supported).
+
 @item ft_load_flags
 The flags to be used for loading the fonts.
 
@@ -4010,6 +4018,9 @@ For more information about libfreetype, check:
 For more information about fontconfig, check:
 @url{http://freedesktop.org/software/fontconfig/fontconfig-user.html}.
 
+For more information about libfribidi, check:
+ at url{http://fribidi.org/}.
+
 @section edgedetect
 
 Detect and draw edges. The filter uses the Canny Edge Detection algorithm.
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index 0d829a6..b29411e 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -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,106 @@ 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 = 0;
+    static 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(len * sizeof(*unicodestr)))) {
+        ret = AVERROR(ENOMEM);
+        goto out;
+    }
+    len = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8,
+                                     s->text, len, unicodestr);
+
+    bidi_types = av_malloc(len * sizeof(*bidi_types));
+    if (!bidi_types) {
+        ret = AVERROR(ENOMEM);
+        goto out;
+    }
+
+    fribidi_get_bidi_types(unicodestr, len, bidi_types);
+
+    embedding_levels = av_malloc(len * sizeof(*embedding_levels));
+    if (!embedding_levels) {
+        ret = AVERROR(ENOMEM);
+        goto out;
+    }
+
+    if (!fribidi_get_par_embedding_levels(bidi_types, len, &direction,
+                                         embedding_levels)) {
+        ret = AVERROR(ENOMEM);
+        goto out;
+    }
+
+    ar_props = av_malloc(len * sizeof(*ar_props));
+    if (!ar_props) {
+        ret = AVERROR(ENOMEM);
+        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)) {
+                ret = AVERROR(ENOMEM);
+                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 one unicode character can be up to 4 bytes in UTF-8 */
+        ret = AVERROR(ENOMEM);
+        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 +620,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 +734,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 +1244,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 ?
-- 
1.8.3.1



More information about the ffmpeg-devel mailing list