Browse code

avfilter/drawtext: Add basic text shaping using libfribidi

Fixes ticket #3758

Reviewed-by: Andrey Utkin <andrey.krieger.utkin@gmail.com>
Signed-off-by: Michael Niedermayer <michaelni@gmx.at>

Marc Jeffreys authored on 2014/06/21 13:41:45
Showing 6 changed files
... ...
@@ -35,6 +35,7 @@ version <next>:
35 35
 - LRC demuxer and muxer
36 36
 - Samba protocol (via libsmbclient)
37 37
 - WebM DASH Manifest muxer
38
+- libfribidi support in drawtext
38 39
 
39 40
 
40 41
 version 2.2:
... ...
@@ -164,6 +164,7 @@
164 164
     • signalstats filter
165 165
     • hqx filter (hq2x, hq3x, hq4x)
166 166
     • flanger filter
167
+    • libfribidi support in drawtext
167 168
 
168 169
  ┌────────────────────────────┐
169 170
  │ ⚠  Behaviour changes       │
... ...
@@ -209,6 +209,7 @@ External library support:
209 209
   --enable-libfdk-aac      enable AAC de/encoding via libfdk-aac [no]
210 210
   --enable-libflite        enable flite (voice synthesis) support via libflite [no]
211 211
   --enable-libfreetype     enable libfreetype [no]
212
+  --enable-libfribidi      enable libfribidi [no]
212 213
   --enable-libgme          enable Game Music Emu via libgme [no]
213 214
   --enable-libgsm          enable GSM de/encoding via libgsm [no]
214 215
   --enable-libiec61883     enable iec61883 via libiec61883 [no]
... ...
@@ -1333,6 +1334,7 @@ EXTERNAL_LIBRARY_LIST="
1333 1333
     libflite
1334 1334
     libfontconfig
1335 1335
     libfreetype
1336
+    libfribidi
1336 1337
     libgme
1337 1338
     libgsm
1338 1339
     libiec61883
... ...
@@ -4729,6 +4731,7 @@ enabled libflite          && require2 libflite "flite/flite.h" flite_init $flite
4729 4729
 enabled fontconfig        && enable libfontconfig
4730 4730
 enabled libfontconfig     && require_pkg_config fontconfig "fontconfig/fontconfig.h" FcInit
4731 4731
 enabled libfreetype       && require_libfreetype
4732
+enabled libfribidi        && require_pkg_config fribidi fribidi.h fribidi_version_info
4732 4733
 enabled libgme            && require  libgme gme/gme.h gme_new_emu -lgme -lstdc++
4733 4734
 enabled libgsm            && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do
4734 4735
                                    check_lib "${gsm_hdr}" gsm_create -lgsm && break;
... ...
@@ -3653,6 +3653,8 @@ To enable compilation of this filter, you need to configure FFmpeg with
3653 3653
 @code{--enable-libfreetype}.
3654 3654
 To enable default font fallback and the @var{font} option you need to
3655 3655
 configure FFmpeg with @code{--enable-libfontconfig}.
3656
+To enable the @var{text_shaping} option, you need to configure FFmpeg with
3657
+@code{--enable-libfribidi}.
3656 3658
 
3657 3659
 @subsection Syntax
3658 3660
 
... ...
@@ -3707,6 +3709,12 @@ This parameter is mandatory if the fontconfig support is disabled.
3707 3707
 The font size to be used for drawing text.
3708 3708
 The default value of @var{fontsize} is 16.
3709 3709
 
3710
+@item text_shaping
3711
+If set to 1, attempt to shape the text (for example, reverse the order of
3712
+right-to-left text and join Arabic characters) before drawing it.
3713
+Otherwise, just draw the text exactly as given.
3714
+By default 1 (if supported).
3715
+
3710 3716
 @item ft_load_flags
3711 3717
 The flags to be used for loading the fonts.
3712 3718
 
... ...
@@ -4010,6 +4018,9 @@ For more information about libfreetype, check:
4010 4010
 For more information about fontconfig, check:
4011 4011
 @url{http://freedesktop.org/software/fontconfig/fontconfig-user.html}.
4012 4012
 
4013
+For more information about libfribidi, check:
4014
+@url{http://fribidi.org/}.
4015
+
4013 4016
 @section edgedetect
4014 4017
 
4015 4018
 Detect and draw edges. The filter uses the Canny Edge Detection algorithm.
... ...
@@ -30,7 +30,7 @@
30 30
 #include "libavutil/version.h"
31 31
 
32 32
 #define LIBAVFILTER_VERSION_MAJOR   4
33
-#define LIBAVFILTER_VERSION_MINOR  10
33
+#define LIBAVFILTER_VERSION_MINOR  11
34 34
 #define LIBAVFILTER_VERSION_MICRO 100
35 35
 
36 36
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
... ...
@@ -59,6 +59,10 @@
59 59
 #include "internal.h"
60 60
 #include "video.h"
61 61
 
62
+#if CONFIG_LIBFRIBIDI
63
+#include <fribidi.h>
64
+#endif
65
+
62 66
 #include <ft2build.h>
63 67
 #include FT_FREETYPE_H
64 68
 #include FT_GLYPH_H
... ...
@@ -182,6 +186,9 @@ typedef struct DrawTextContext {
182 182
     int tc24hmax;                   ///< 1 if timecode is wrapped to 24 hours, 0 otherwise
183 183
     int reload;                     ///< reload text file for each frame
184 184
     int start_number;               ///< starting frame number for n/frame_num var
185
+#if CONFIG_LIBFRIBIDI
186
+    int text_shaping;               ///< 1 to shape the text before drawing it
187
+#endif
185 188
     AVDictionary *metadata;
186 189
 } DrawTextContext;
187 190
 
... ...
@@ -226,6 +233,10 @@ static const AVOption drawtext_options[]= {
226 226
     {"fix_bounds", "if true, check and fix text coords to avoid clipping",  OFFSET(fix_bounds), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS},
227 227
     {"start_number", "start frame number for n/frame_num variable", OFFSET(start_number), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS},
228 228
 
229
+#if CONFIG_LIBFRIBIDI
230
+    {"text_shaping", "attempt to shape text before drawing", OFFSET(text_shaping), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS},
231
+#endif
232
+
229 233
     /* FT_LOAD_* flags */
230 234
     { "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" },
231 235
         { "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)
482 482
     return 0;
483 483
 }
484 484
 
485
+static inline int is_newline(uint32_t c)
486
+{
487
+    return c == '\n' || c == '\r' || c == '\f' || c == '\v';
488
+}
489
+
490
+#if CONFIG_LIBFRIBIDI
491
+static int shape_text(AVFilterContext *ctx)
492
+{
493
+    DrawTextContext *s = ctx->priv;
494
+    uint8_t *tmp;
495
+    int ret = AVERROR(ENOMEM);
496
+    static const FriBidiFlags flags = FRIBIDI_FLAGS_DEFAULT |
497
+                                      FRIBIDI_FLAGS_ARABIC;
498
+    FriBidiChar *unicodestr = NULL;
499
+    FriBidiStrIndex len;
500
+    FriBidiParType direction = FRIBIDI_PAR_LTR;
501
+    FriBidiStrIndex line_start = 0;
502
+    FriBidiStrIndex line_end = 0;
503
+    FriBidiLevel *embedding_levels = NULL;
504
+    FriBidiArabicProp *ar_props = NULL;
505
+    FriBidiCharType *bidi_types = NULL;
506
+    FriBidiStrIndex i,j;
507
+
508
+    len = strlen(s->text);
509
+    if (!(unicodestr = av_malloc_array(len, sizeof(*unicodestr)))) {
510
+        goto out;
511
+    }
512
+    len = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8,
513
+                                     s->text, len, unicodestr);
514
+
515
+    bidi_types = av_malloc_array(len, sizeof(*bidi_types));
516
+    if (!bidi_types) {
517
+        goto out;
518
+    }
519
+
520
+    fribidi_get_bidi_types(unicodestr, len, bidi_types);
521
+
522
+    embedding_levels = av_malloc_array(len, sizeof(*embedding_levels));
523
+    if (!embedding_levels) {
524
+        goto out;
525
+    }
526
+
527
+    if (!fribidi_get_par_embedding_levels(bidi_types, len, &direction,
528
+                                          embedding_levels)) {
529
+        goto out;
530
+    }
531
+
532
+    ar_props = av_malloc_array(len, sizeof(*ar_props));
533
+    if (!ar_props) {
534
+        goto out;
535
+    }
536
+
537
+    fribidi_get_joining_types(unicodestr, len, ar_props);
538
+    fribidi_join_arabic(bidi_types, len, embedding_levels, ar_props);
539
+    fribidi_shape(flags, embedding_levels, len, ar_props, unicodestr);
540
+
541
+    for (line_end = 0, line_start = 0; line_end < len; line_end++) {
542
+        if (is_newline(unicodestr[line_end]) || line_end == len - 1) {
543
+            if (!fribidi_reorder_line(flags, bidi_types,
544
+                                      line_end - line_start + 1, line_start,
545
+                                      direction, embedding_levels, unicodestr,
546
+                                      NULL)) {
547
+                goto out;
548
+            }
549
+            line_start = line_end + 1;
550
+        }
551
+    }
552
+
553
+    /* Remove zero-width fill chars put in by libfribidi */
554
+    for (i = 0, j = 0; i < len; i++)
555
+        if (unicodestr[i] != FRIBIDI_CHAR_FILL)
556
+            unicodestr[j++] = unicodestr[i];
557
+    len = j;
558
+
559
+    if (!(tmp = av_realloc(s->text, (len * 4 + 1) * sizeof(*s->text)))) {
560
+        /* Use len * 4, as a unicode character can be up to 4 bytes in UTF-8 */
561
+        goto out;
562
+    }
563
+
564
+    s->text = tmp;
565
+    len = fribidi_unicode_to_charset(FRIBIDI_CHAR_SET_UTF8,
566
+                                     unicodestr, len, s->text);
567
+    ret = 0;
568
+
569
+out:
570
+    av_free(unicodestr);
571
+    av_free(embedding_levels);
572
+    av_free(ar_props);
573
+    av_free(bidi_types);
574
+    return ret;
575
+}
576
+#endif
577
+
485 578
 static av_cold int init(AVFilterContext *ctx)
486 579
 {
487 580
     int err;
... ...
@@ -509,6 +613,12 @@ static av_cold int init(AVFilterContext *ctx)
509 509
             return err;
510 510
     }
511 511
 
512
+#if CONFIG_LIBFRIBIDI
513
+    if (s->text_shaping)
514
+        if ((err = shape_text(ctx)) < 0)
515
+            return err;
516
+#endif
517
+
512 518
     if (s->reload && !s->textfile)
513 519
         av_log(ctx, AV_LOG_WARNING, "No file to reload\n");
514 520
 
... ...
@@ -617,11 +727,6 @@ static av_cold void uninit(AVFilterContext *ctx)
617 617
     av_bprint_finalize(&s->expanded_text, NULL);
618 618
 }
619 619
 
620
-static inline int is_newline(uint32_t c)
621
-{
622
-    return c == '\n' || c == '\r' || c == '\f' || c == '\v';
623
-}
624
-
625 620
 static int config_input(AVFilterLink *inlink)
626 621
 {
627 622
     AVFilterContext *ctx = inlink->dst;
... ...
@@ -1132,9 +1237,15 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
1132 1132
     DrawTextContext *s = ctx->priv;
1133 1133
     int ret;
1134 1134
 
1135
-    if (s->reload)
1135
+    if (s->reload) {
1136 1136
         if ((ret = load_textfile(ctx)) < 0)
1137 1137
             return ret;
1138
+#if CONFIG_LIBFRIBIDI
1139
+        if (s->text_shaping)
1140
+            if ((ret = shape_text(ctx)) < 0)
1141
+                return ret;
1142
+#endif
1143
+    }
1138 1144
 
1139 1145
     s->var_values[VAR_N] = inlink->frame_count+s->start_number;
1140 1146
     s->var_values[VAR_T] = frame->pts == AV_NOPTS_VALUE ?