aboutsummaryrefslogtreecommitdiffstats
path: root/libavfilter
diff options
context:
space:
mode:
authorMarc Jeffreys <maj160@live.co.uk>2014-06-21 05:41:45 +0100
committerMichael Niedermayer <michaelni@gmx.at>2014-07-16 00:40:39 +0200
commita0b71e9f3e95d22b098e4d81c26d3bfe1f53249f (patch)
tree2ce796b97faaee6fd9e44089a2c624a230ede051 /libavfilter
parent04980dbee805f372f0505a0afd9ae10c0da1e17e (diff)
downloadffmpeg-a0b71e9f3e95d22b098e4d81c26d3bfe1f53249f.tar.gz
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>
Diffstat (limited to 'libavfilter')
-rw-r--r--libavfilter/version.h2
-rw-r--r--libavfilter/vf_drawtext.c123
2 files changed, 118 insertions, 7 deletions
diff --git a/libavfilter/version.h b/libavfilter/version.h
index bf9191ecec..227d3203ff 100644
--- a/libavfilter/version.h
+++ b/libavfilter/version.h
@@ -30,7 +30,7 @@
#include "libavutil/version.h"
#define LIBAVFILTER_VERSION_MAJOR 4
-#define LIBAVFILTER_VERSION_MINOR 10
+#define LIBAVFILTER_VERSION_MINOR 11
#define LIBAVFILTER_VERSION_MICRO 100
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index 0d829a6d5e..0b93d14533 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,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 ?