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]
