PR #23130 opened by michaelni
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23130
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23130.patch


>From 50e6792b3b759e1687ff2c541a891ccdb36873cc Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 17 May 2026 15:14:35 +0200
Subject: [PATCH 1/4] avfilter/vf_drawtext: plug error-path leaks in
 measure_text/draw_text

Signed-off-by: Michael Niedermayer <[email protected]>
---
 libavfilter/vf_drawtext.c | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index 005e150de7..abfe96088e 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -1551,6 +1551,15 @@ continue_on_failed2:
 
 done:
     av_free(textdup);
+    if (ret < 0) {
+        if (s->lines) {
+            for (int l = 0; l < s->line_count; ++l)
+                hb_destroy(&s->lines[l].hb_data);
+        }
+        av_freep(&s->lines);
+        av_freep(&s->tab_clusters);
+        s->line_count = 0;
+    }
     return ret;
 }
 
@@ -1741,7 +1750,7 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame)
 
             ret = load_glyph(ctx, &glyph, hb->glyph_info[t].codepoint, 
shift_x64, shift_y64);
             if (ret != 0) {
-                return ret;
+                goto fail;
             }
             g_info->code = hb->glyph_info[t].codepoint;
             g_info->x = (x64 + true_x) >> 6;
@@ -1803,23 +1812,25 @@ static int draw_text(AVFilterContext *ctx, AVFrame 
*frame)
         if (s->shadowx || s->shadowy) {
             if ((ret = draw_glyphs(ctx, frame, &shadowcolor, &metrics,
                     s->shadowx, s->shadowy, s->borderw)) < 0) {
-                return ret;
+                goto fail;
             }
         }
 
         if (s->borderw) {
             if ((ret = draw_glyphs(ctx, frame, &bordercolor, &metrics,
                     0, 0, s->borderw)) < 0) {
-                return ret;
+                goto fail;
             }
         }
 
         if ((ret = draw_glyphs(ctx, frame, &fontcolor, &metrics, 0,
                 0, 0)) < 0) {
-            return ret;
+            goto fail;
         }
     }
 
+    ret = 0;
+fail:
     // FREE data structures
     for (int l = 0; l < s->line_count; ++l) {
         TextLine *line = &s->lines[l];
@@ -1828,8 +1839,9 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame)
     }
     av_freep(&s->lines);
     av_freep(&s->tab_clusters);
+    s->line_count = 0;
 
-    return 0;
+    return ret;
 }
 
 static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
-- 
2.52.0


>From e2674efeb2b757f8fadf52ef5c4ba841ec8e4639 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 17 May 2026 15:54:36 +0200
Subject: [PATCH 2/4] avfilter/drawutils: add ff_blend_bgra() for premultiplied
 BGRA bitmaps

vf_drawtext will use this to draw color emoji glyphs (FT_PIXEL_MODE_BGRA)

Co-Authored-By: AI
---
 libavfilter/drawutils.c | 38 ++++++++++++++++++++++++++++++++++++++
 libavfilter/drawutils.h | 20 ++++++++++++++++++++
 2 files changed, 58 insertions(+)

diff --git a/libavfilter/drawutils.c b/libavfilter/drawutils.c
index 75bf8f4755..b42f7606fd 100644
--- a/libavfilter/drawutils.c
+++ b/libavfilter/drawutils.c
@@ -655,6 +655,44 @@ void ff_blend_mask(FFDrawContext *draw, FFDrawColor *color,
     }
 }
 
+void ff_blend_bgra(FFDrawContext *draw,
+                   uint8_t *dst[], int dst_linesize[], int dst_w, int dst_h,
+                   const uint8_t *src, int src_linesize,
+                   int src_w, int src_h, int x0, int y0)
+{
+    int xs0, ys0;
+
+    clip_interval(dst_w, &x0, &src_w, &xs0);
+    clip_interval(dst_h, &y0, &src_h, &ys0);
+    if (src_w <= 0 || src_h <= 0)
+        return;
+    src += ys0 * src_linesize + xs0 * 4;
+
+    for (int y = 0; y < src_h; y++) {
+        const uint8_t *srow = src + y * src_linesize;
+        for (int x = 0; x < src_w; x++) {
+            unsigned b = srow[4 * x + 0];
+            unsigned g = srow[4 * x + 1];
+            unsigned r = srow[4 * x + 2];
+            unsigned a = srow[4 * x + 3];
+            uint8_t rgba[4], mask;
+            FFDrawColor color;
+
+            if (!a)
+                continue;
+
+            rgba[0] = FFMIN(255, (r * 255 + a / 2) / a);
+            rgba[1] = FFMIN(255, (g * 255 + a / 2) / a);
+            rgba[2] = FFMIN(255, (b * 255 + a / 2) / a);
+            rgba[3] = 255;
+            ff_draw_color(draw, &color, rgba);
+            mask = a;
+            ff_blend_mask(draw, &color, dst, dst_linesize, dst_w, dst_h,
+                          &mask, 0, 1, 1, 3, 0, x + x0, y + y0);
+        }
+    }
+}
+
 int ff_draw_round_to_sub(FFDrawContext *draw, int sub_dir, int round_dir,
                          int value)
 {
diff --git a/libavfilter/drawutils.h b/libavfilter/drawutils.h
index a7fc967a85..9f01800591 100644
--- a/libavfilter/drawutils.h
+++ b/libavfilter/drawutils.h
@@ -153,6 +153,26 @@ void ff_blend_mask(FFDrawContext *draw, FFDrawColor *color,
                    const uint8_t *mask, int mask_linesize, int mask_w, int 
mask_h,
                    int l2depth, unsigned endianness, int x0, int y0);
 
+/**
+ * Blend a premultiplied BGRA bitmap onto the destination image.
+ *
+ * @param draw           draw context
+ * @param dst            destination image
+ * @param dst_linesize   line stride of the destination
+ * @param dst_w          width of the destination image
+ * @param dst_h          height of the destination image
+ * @param src            premultiplied BGRA source bitmap
+ * @param src_linesize   line stride of the source bitmap
+ * @param src_w          width of the source bitmap, in pixels
+ * @param src_h          height of the source bitmap, in pixels
+ * @param x0             horizontal position of the overlay in the destination
+ * @param y0             vertical position of the overlay in the destination
+ */
+void ff_blend_bgra(FFDrawContext *draw,
+                   uint8_t *dst[], int dst_linesize[], int dst_w, int dst_h,
+                   const uint8_t *src, int src_linesize,
+                   int src_w, int src_h, int x0, int y0);
+
 /**
  * Round a dimension according to subsampling.
  *
-- 
2.52.0


>From f2594a27a860729fa4c43168d2f5fb152fcef6e6 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 17 May 2026 16:29:32 +0200
Subject: [PATCH 3/4] avfilter/vf_drawtext: render color emoji and other
 FT_PIXEL_MODE_BGRA glyphs

Verified:
  ffmpeg -f lavfi -i color=s=1280x120:d=1 \
      -vf "drawtext=fontfile=NotoColorEmoji.ttf:\
text='FFmpeg \xF0\x9F\x98\x80 \xE2\x9D\xA4':fontsize=109:x=20:y=35" \
      -frames:v 1 out.png

Co-Authored-By: AI
---
 libavfilter/vf_drawtext.c | 60 +++++++++++++++++++++++++++++++--------
 1 file changed, 48 insertions(+), 12 deletions(-)

diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index abfe96088e..f58b4dd5ff 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -315,6 +315,7 @@ typedef struct DrawTextContext {
     int tab_count;                  ///< the number of tab characters
     int blank_advance64;            ///< the size of the space character
     int tab_warning_printed;        ///< ensure the tab warning to be printed 
only once
+    int mono_warning_printed;       ///< ensure the mono-glyph warning is 
printed only once
 } DrawTextContext;
 
 #define OFFSET(x) offsetof(DrawTextContext, x)
@@ -435,7 +436,21 @@ static av_cold int set_fontsize(AVFilterContext *ctx, 
unsigned int fontsize)
     int err;
     DrawTextContext *s = ctx->priv;
 
-    if ((err = FT_Set_Pixel_Sizes(s->face, 0, fontsize))) {
+    err = FT_Set_Pixel_Sizes(s->face, 0, fontsize);
+    if (err && s->face->num_fixed_sizes > 0) {
+        int best = 0, best_diff = INT_MAX;
+        for (int i = 0; i < s->face->num_fixed_sizes; i++) {
+            int strike = s->face->available_sizes[i].y_ppem >> 6;
+            int diff = abs(strike - (int)fontsize);
+            if (diff < best_diff) {
+                best_diff = diff;
+                best = i;
+            }
+        }
+        err = FT_Select_Size(s->face, best);
+    }
+
+    if (err) {
         av_log(ctx, AV_LOG_ERROR, "Could not set font size to %d pixels: %s\n",
                fontsize, FT_ERRMSG(err));
         return AVERROR(EINVAL);
@@ -741,7 +756,7 @@ static int load_glyph(AVFilterContext *ctx, Glyph 
**glyph_ptr, uint32_t code, in
     dummy.fontsize = s->fontsize;
     glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
     if (!glyph) {
-        if (FT_Load_Glyph(s->face, code, s->ft_load_flags)) {
+        if (FT_Load_Glyph(s->face, code, s->ft_load_flags | FT_LOAD_COLOR)) {
             return AVERROR(EINVAL);
         }
         glyph = av_mallocz(sizeof(*glyph));
@@ -755,7 +770,7 @@ static int load_glyph(AVFilterContext *ctx, Glyph 
**glyph_ptr, uint32_t code, in
             ret = AVERROR(EINVAL);
             goto error;
         }
-        if (s->borderw) {
+        if (s->borderw && glyph->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
             glyph->border_glyph = glyph->glyph;
             if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0)) 
{
                 ret = AVERROR_EXTERNAL;
@@ -772,7 +787,8 @@ static int load_glyph(AVFilterContext *ctx, Glyph 
**glyph_ptr, uint32_t code, in
         }
         av_tree_insert(&s->glyphs, glyph, glyph_cmp, &node);
     } else {
-        if (s->borderw && !glyph->border_glyph) {
+        if (s->borderw && !glyph->border_glyph &&
+            glyph->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
             glyph->border_glyph = glyph->glyph;
             if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0)) 
{
                 ret = AVERROR_EXTERNAL;
@@ -795,13 +811,21 @@ static int load_glyph(AVFilterContext *ctx, Glyph 
**glyph_ptr, uint32_t code, in
                 goto error;
             }
             glyph->bglyph[idx] = (FT_BitmapGlyph)tmp_glyph;
-            if (glyph->bglyph[idx]->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
-                av_log(ctx, AV_LOG_ERROR, "Monocromatic (1bpp) fonts are not 
supported.\n");
-                ret = AVERROR(EINVAL);
-                goto error;
+            if (glyph->bglyph[idx]->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY &&
+                glyph->bglyph[idx]->bitmap.pixel_mode != FT_PIXEL_MODE_BGRA) {
+                FT_Glyph bg = (FT_Glyph)glyph->bglyph[idx];
+                if (bg != glyph->glyph && bg != glyph->border_glyph)
+                    FT_Done_Glyph(bg);
+                glyph->bglyph[idx] = NULL;
+                if (!s->mono_warning_printed) {
+                    s->mono_warning_printed = 1;
+                    av_log(ctx, AV_LOG_WARNING,
+                           "Font has no scalable / colour glyph for at "
+                           "least one character; those will be skipped.\n");
+                }
             }
         }
-        if (s->borderw && !glyph->border_bglyph[idx]) {
+        if (s->borderw && glyph->border_glyph && !glyph->border_bglyph[idx]) {
             FT_Glyph tmp_glyph = glyph->border_glyph;
             if (FT_Glyph_To_Bitmap(&tmp_glyph, FT_RENDER_MODE_NORMAL, &shift, 
0)) {
                 ret = AVERROR_EXTERNAL;
@@ -1317,6 +1341,10 @@ static int draw_glyphs(AVFilterContext *ctx, AVFrame 
*frame,
             }
 
             idx = get_subpixel_idx(info->shift_x64, info->shift_y64);
+            if (!glyph->bglyph[idx])
+                continue;
+            if (borderw && !glyph->border_bglyph[idx])
+                continue;
             b_glyph = borderw ? glyph->border_bglyph[idx] : glyph->bglyph[idx];
             bitmap = b_glyph->bitmap;
             x1 = x + info->x + b_glyph->left;
@@ -1346,12 +1374,20 @@ static int draw_glyphs(AVFilterContext *ctx, AVFrame 
*frame,
                 continue;
             }
 
-            pdx = dx + dy * bitmap.pitch;
             w1 = FFMIN(clip_x - x1, w1 - dx);
             h1 = FFMIN(clip_y - y1, h1 - dy);
 
-            ff_blend_mask(&s->dc, color, frame->data, frame->linesize, clip_x, 
clip_y,
-                bitmap.buffer + pdx, bitmap.pitch, w1, h1, 3, 0, x1, y1);
+            if (bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
+                if (borderw || x || y)
+                    continue;
+                pdx = dx * 4 + dy * bitmap.pitch;
+                ff_blend_bgra(&s->dc, frame->data, frame->linesize, clip_x, 
clip_y,
+                              bitmap.buffer + pdx, bitmap.pitch, w1, h1, x1, 
y1);
+            } else {
+                pdx = dx + dy * bitmap.pitch;
+                ff_blend_mask(&s->dc, color, frame->data, frame->linesize, 
clip_x, clip_y,
+                              bitmap.buffer + pdx, bitmap.pitch, w1, h1, 3, 0, 
x1, y1);
+            }
         }
     }
 
-- 
2.52.0


>From bc99b9691003444e9f1254b18136b45a17a3c7ba Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Sun, 17 May 2026 17:00:43 +0200
Subject: [PATCH 4/4] avfilter/vf_drawtext: per-glyph fontconfig fallback for
 missing characters

After this:
  ffmpeg -f lavfi -i color=s=1280x200:d=1 \
      -vf "drawtext=fontfile=NotoColorEmoji.ttf:\
text='FFmpeg \xF0\x9F\x98\x80 
\xE2\x9D\xA4':fontsize=109:fontcolor=white:x=20:y=35" \
      -frames:v 1 out.png
renders "FFmpeg" (white outlines, picked up from NotoSans-Regular via
fontconfig) alongside the emoji from the primary face.

Co-Authored-By: AI
---
 libavfilter/vf_drawtext.c | 212 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 202 insertions(+), 10 deletions(-)

diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index f58b4dd5ff..f2531666e7 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -193,6 +193,7 @@ typedef struct TextLine {
     int width64;                    ///< width of the line
     HarfbuzzData hb_data;           ///< libharfbuzz data of this text line
     GlyphInfo* glyphs;              ///< array of glyphs in this text line
+    int *face_ids;                  ///< per-hb_data-glyph face id (0 = 
primary; 1+ = s->fallbacks[i-1]); NULL means all-primary
     int cluster_offset;             ///< the offset at which this line begins
 } TextLine;
 
@@ -202,6 +203,8 @@ typedef struct Glyph {
     FT_Glyph border_glyph;
     uint32_t code;
     unsigned int fontsize;
+    /** 0 for the primary face, 1+N for s->fallbacks[N-1].face */
+    int face_id;
     /** Glyph bitmaps with 1/4 pixel precision in both directions */
     FT_BitmapGlyph bglyph[16];
     /** Outlined glyph bitmaps with 1/4 pixel precision in both directions */
@@ -209,6 +212,10 @@ typedef struct Glyph {
     FT_BBox bbox;
 } Glyph;
 
+typedef struct DrawTextFallback {
+    FT_Face face;
+} DrawTextFallback;
+
 /** Global text metrics */
 typedef struct TextMetrics {
     int offset_top64;               ///< ascender amount of the first line (in 
26.6 units)
@@ -281,6 +288,8 @@ typedef struct DrawTextContext {
     FT_Library library;             ///< freetype font library handle
     FT_Face face;                   ///< freetype font face handle
     FT_Stroker stroker;             ///< freetype stroker handle
+    DrawTextFallback *fallbacks;    ///< fontconfig-resolved fallback faces 
(lazily added)
+    int nb_fallbacks;
     struct AVTreeNode *glyphs;      ///< rendered glyphs, stored using the 
UTF-32 char code
     char *x_expr;                   ///< expression for x position
     char *y_expr;                   ///< expression for y position
@@ -423,12 +432,13 @@ static const struct ft_error {
 static int glyph_cmp(const void *key, const void *b)
 {
     const Glyph *a = key, *bb = b;
-    int64_t diff = (int64_t)a->code - (int64_t)bb->code;
 
+    if (a->face_id != bb->face_id)
+        return a->face_id - bb->face_id;
+    int64_t diff = (int64_t)a->code - (int64_t)bb->code;
     if (diff != 0)
         return diff > 0 ? 1 : -1;
-    else
-        return FFDIFFSIGN((int64_t)a->fontsize, (int64_t)bb->fontsize);
+    return FFDIFFSIGN((int64_t)a->fontsize, (int64_t)bb->fontsize);
 }
 
 static av_cold int set_fontsize(AVFilterContext *ctx, unsigned int fontsize)
@@ -456,6 +466,23 @@ static av_cold int set_fontsize(AVFilterContext *ctx, 
unsigned int fontsize)
         return AVERROR(EINVAL);
     }
 
+    /* Also resize any fallback faces so their FT metrics stay in sync. */
+    for (int i = 0; i < s->nb_fallbacks; i++) {
+        FT_Face fb = s->fallbacks[i].face;
+        if (FT_Set_Pixel_Sizes(fb, 0, fontsize) && fb->num_fixed_sizes > 0) {
+            int best = 0, best_diff = INT_MAX;
+            for (int j = 0; j < fb->num_fixed_sizes; j++) {
+                int strike = fb->available_sizes[j].y_ppem >> 6;
+                int diff = abs(strike - (int)fontsize);
+                if (diff < best_diff) {
+                    best_diff = diff;
+                    best = j;
+                }
+            }
+            FT_Select_Size(fb, best);
+        }
+    }
+
     // Whenever the underlying FT_Face changes, harfbuzz has to be notified of 
the change.
     for (int line = 0; line < s->line_count; line++) {
         TextLine *cur_line = &s->lines[line];
@@ -617,6 +644,105 @@ fail:
     FcPatternDestroy(best);
     return err;
 }
+
+static int find_fallback_face(AVFilterContext *ctx, uint32_t codepoint)
+{
+    DrawTextContext *s = ctx->priv;
+    FcConfig *fc = NULL;
+    FcCharSet *cs = NULL;
+    FcPattern *pat = NULL, *best = NULL;
+    FcResult result;
+    FcChar8 *filename = NULL;
+    int fc_index = 0;
+    FT_Face face = NULL;
+    DrawTextFallback *fb;
+    void *tmp;
+    int ret = 0;
+
+    /* Skip codepoint 0 (NUL) and look for a face we already loaded. */
+    if (!codepoint)
+        return 0;
+    for (int i = 0; i < s->nb_fallbacks; i++)
+        if (FT_Get_Char_Index(s->fallbacks[i].face, codepoint))
+            return i + 1;
+
+    fc = FcInitLoadConfigAndFonts();
+    if (!fc)
+        return 0;
+    pat = FcPatternCreate();
+    cs = FcCharSetCreate();
+    if (!pat || !cs)
+        goto done;
+    FcCharSetAddChar(cs, codepoint);
+    FcPatternAddCharSet(pat, FC_CHARSET, cs);
+    FcPatternAddBool(pat, FC_SCALABLE, FcTrue);
+    FcDefaultSubstitute(pat);
+    if (!FcConfigSubstitute(fc, pat, FcMatchPattern))
+        goto done;
+    best = FcFontMatch(fc, pat, &result);
+    if (!best || result != FcResultMatch)
+        goto done;
+    FcPatternGetInteger(best, FC_INDEX, 0, &fc_index);
+    if (FcPatternGetString(best, FC_FILE, 0, &filename) != FcResultMatch)
+        goto done;
+
+    if (FT_New_Face(s->library, (const char *)filename, fc_index, &face))
+        goto done;
+    /* Confirm the matched face actually has the codepoint; fontconfig will
+     * happily return a "best effort" font even when no fontfile has it. */
+    if (!FT_Get_Char_Index(face, codepoint)) {
+        FT_Done_Face(face);
+        face = NULL;
+        goto done;
+    }
+    if (FT_Set_Pixel_Sizes(face, 0, s->fontsize) &&
+        face->num_fixed_sizes > 0) {
+        int best_strike = 0, best_diff = INT_MAX;
+        for (int i = 0; i < face->num_fixed_sizes; i++) {
+            int strike = face->available_sizes[i].y_ppem >> 6;
+            int diff = abs(strike - (int)s->fontsize);
+            if (diff < best_diff) {
+                best_diff = diff;
+                best_strike = i;
+            }
+        }
+        FT_Select_Size(face, best_strike);
+    }
+
+    tmp = av_realloc_array(s->fallbacks, s->nb_fallbacks + 1,
+                           sizeof(*s->fallbacks));
+    if (!tmp) {
+        FT_Done_Face(face);
+        face = NULL;
+        goto done;
+    }
+    s->fallbacks = tmp;
+    fb = &s->fallbacks[s->nb_fallbacks++];
+    fb->face = face;
+    av_log(ctx, AV_LOG_VERBOSE,
+           "drawtext: using fallback font \"%s\" for U+%04X\n",
+           filename, codepoint);
+    ret = s->nb_fallbacks;
+    face = NULL;
+
+done:
+    if (face)
+        FT_Done_Face(face);
+    if (best)
+        FcPatternDestroy(best);
+    if (pat)
+        FcPatternDestroy(pat);
+    if (cs)
+        FcCharSetDestroy(cs);
+    if (fc)
+        FcConfigDestroy(fc);
+    return ret;
+}
+#else
+static int find_fallback_face(AVFilterContext *ctx, uint32_t codepoint)
+{
+    return 0;
+}
 #endif
 
 static int load_font(AVFilterContext *ctx)
@@ -742,9 +868,11 @@ static inline int get_subpixel_idx(int shift_x64, int 
shift_y64)
 }
 
 // Loads and (optionally) renders a glyph
-static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code, 
int8_t shift_x64, int8_t shift_y64)
+static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code,
+                      int8_t shift_x64, int8_t shift_y64, int face_id)
 {
     DrawTextContext *s = ctx->priv;
+    FT_Face face = face_id == 0 ? s->face : s->fallbacks[face_id - 1].face;
     Glyph dummy = { 0 };
     Glyph *glyph;
     FT_Vector shift;
@@ -754,9 +882,10 @@ static int load_glyph(AVFilterContext *ctx, Glyph 
**glyph_ptr, uint32_t code, in
     /* get glyph */
     dummy.code = code;
     dummy.fontsize = s->fontsize;
+    dummy.face_id = face_id;
     glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
     if (!glyph) {
-        if (FT_Load_Glyph(s->face, code, s->ft_load_flags | FT_LOAD_COLOR)) {
+        if (FT_Load_Glyph(face, code, s->ft_load_flags | FT_LOAD_COLOR)) {
             return AVERROR(EINVAL);
         }
         glyph = av_mallocz(sizeof(*glyph));
@@ -766,7 +895,8 @@ static int load_glyph(AVFilterContext *ctx, Glyph 
**glyph_ptr, uint32_t code, in
         }
         glyph->code  = code;
         glyph->fontsize = s->fontsize;
-        if (FT_Get_Glyph(s->face->glyph, &glyph->glyph)) {
+        glyph->face_id = face_id;
+        if (FT_Get_Glyph(face->glyph, &glyph->glyph)) {
             ret = AVERROR(EINVAL);
             goto error;
         }
@@ -1083,7 +1213,7 @@ static av_cold int init(AVFilterContext *ctx)
     }
 
     /* load the fallback glyph with code 0 */
-    load_glyph(ctx, NULL, 0, 0, 0);
+    load_glyph(ctx, NULL, 0, 0, 0, 0);
 
     if (s->exp_mode == EXP_STRFTIME &&
         (strchr(s->text, '%') || strchr(s->text, '\\')))
@@ -1153,6 +1283,12 @@ static av_cold void uninit(AVFilterContext *ctx)
     av_tree_destroy(s->glyphs);
     s->glyphs = NULL;
 
+    for (int i = 0; i < s->nb_fallbacks; i++) {
+        FT_Done_Face(s->fallbacks[i].face);
+    }
+    av_freep(&s->fallbacks);
+    s->nb_fallbacks = 0;
+
     FT_Done_Face(s->face);
     FT_Stroker_Done(s->stroker);
     FT_Done_FreeType(s->library);
@@ -1335,6 +1471,7 @@ static int draw_glyphs(AVFilterContext *ctx, AVFrame 
*frame,
             info = &line->glyphs[g];
             dummy.fontsize = s->fontsize;
             dummy.code = info->code;
+            dummy.face_id = line->face_ids ? line->face_ids[g] : 0;
             glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
             if (!glyph) {
                 return AVERROR(EINVAL);
@@ -1427,6 +1564,53 @@ static void hb_destroy(HarfbuzzData *hb)
     hb->glyph_pos = NULL;
 }
 
+static int apply_glyph_fallback(AVFilterContext *ctx, HarfbuzzData *hb,
+                                int **face_ids_out,
+                                const char *line_text, int line_len)
+{
+    int *face_ids = NULL;
+
+    for (int t = 0; t < hb->glyph_count; t++) {
+        const uint8_t *p, *end;
+        unsigned cluster;
+        uint32_t cp;
+        int face_id;
+        FT_Face fb;
+        FT_UInt gid;
+        DrawTextContext *s = ctx->priv;
+
+        if (hb->glyph_info[t].codepoint != 0)
+            continue;
+        cluster = hb->glyph_info[t].cluster;
+        if (cluster >= (unsigned)line_len)
+            continue;
+        p = (const uint8_t *)line_text + cluster;
+        end = (const uint8_t *)line_text + line_len;
+        GET_UTF8(cp, p < end ? *p++ : 0, cp = 0xFFFD; continue;);
+
+        face_id = find_fallback_face(ctx, cp);
+        if (face_id == 0)
+            continue;
+        fb = s->fallbacks[face_id - 1].face;
+        gid = FT_Get_Char_Index(fb, cp);
+        if (!gid)
+            continue;
+        if (!face_ids) {
+            face_ids = av_calloc(hb->glyph_count, sizeof(*face_ids));
+            if (!face_ids)
+                return AVERROR(ENOMEM);
+        }
+        hb->glyph_info[t].codepoint = gid;
+        face_ids[t] = face_id;
+        if (!FT_Load_Glyph(fb, gid, FT_LOAD_DEFAULT | FT_LOAD_COLOR)) {
+            hb->glyph_pos[t].x_advance = fb->glyph->advance.x;
+            hb->glyph_pos[t].y_advance = fb->glyph->advance.y;
+        }
+    }
+    *face_ids_out = face_ids;
+    return 0;
+}
+
 static int measure_text(AVFilterContext *ctx, TextMetrics *metrics)
 {
     DrawTextContext *s = ctx->priv;
@@ -1504,15 +1688,19 @@ continue_on_failed2:
             if (ret != 0) {
                 goto done;
             }
+            ret = apply_glyph_fallback(ctx, hb, &cur_line->face_ids, start, 
len);
+            if (ret != 0)
+                goto done;
             w64 = 0;
             cur_min_y64 = 32000;
             for (int t = 0; t < hb->glyph_count; ++t) {
+                int face_id = cur_line->face_ids ? cur_line->face_ids[t] : 0;
                 uint8_t is_tab = last_tab_idx < s->tab_count &&
                     hb->glyph_info[t].cluster == s->tab_clusters[last_tab_idx] 
- line_offset;
                 if (is_tab) {
                     ++last_tab_idx;
                 }
-                ret = load_glyph(ctx, &glyph, hb->glyph_info[t].codepoint, -1, 
-1);
+                ret = load_glyph(ctx, &glyph, hb->glyph_info[t].codepoint, -1, 
-1, face_id);
                 if (ret != 0) {
                     goto done;
                 }
@@ -1589,8 +1777,10 @@ done:
     av_free(textdup);
     if (ret < 0) {
         if (s->lines) {
-            for (int l = 0; l < s->line_count; ++l)
+            for (int l = 0; l < s->line_count; ++l) {
                 hb_destroy(&s->lines[l].hb_data);
+                av_freep(&s->lines[l].face_ids);
+            }
         }
         av_freep(&s->lines);
         av_freep(&s->tab_clusters);
@@ -1784,7 +1974,8 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame)
             shift_x64 = (((x64 + true_x) >> 4) & 0b0011) << 4;
             shift_y64 = ((4 - (((y64 + true_y) >> 4) & 0b0011)) & 0b0011) << 4;
 
-            ret = load_glyph(ctx, &glyph, hb->glyph_info[t].codepoint, 
shift_x64, shift_y64);
+            ret = load_glyph(ctx, &glyph, hb->glyph_info[t].codepoint, 
shift_x64, shift_y64,
+                             line->face_ids ? line->face_ids[t] : 0);
             if (ret != 0) {
                 goto fail;
             }
@@ -1871,6 +2062,7 @@ fail:
     for (int l = 0; l < s->line_count; ++l) {
         TextLine *line = &s->lines[l];
         av_freep(&line->glyphs);
+        av_freep(&line->face_ids);
         hb_destroy(&line->hb_data);
     }
     av_freep(&s->lines);
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to