History

avfilter/vf_drawtext: improve glyph shaping and positioning

- text is now shaped using libharfbuz - glyphs position is now accurate to 1/4 pixel in both directions - the default line height is now the one defined in the font Adds libharfbuzz dependency.

yethie committed 3 years ago

Commit 1eeb59a1 parent ba00ed33 files · +592 −280Browse files

Unified · Split

3 files
  1. configure +4 −1
  2. doc/filters.texi +5 −3
  3. libavfilter/vf_drawtext.c +583 −276
configure+4−1
@@ -236,6 +236,7 @@ External library support: · 235 unmodified lines
236236 --enable-libfontconfig enable libfontconfig, useful for drawtext filter [no]
237237 --enable-libfreetype enable libfreetype, needed for drawtext filter [no]
238238 --enable-libfribidi enable libfribidi, improves drawtext filter [no]
239+ --enable-libharfbuzz enable libharfbuzz, needed for drawtext filter [no]
239240 --enable-libglslang enable GLSL->SPIRV compilation via libglslang [no]
240241 --enable-libgme enable Game Music Emu via libgme [no]
241242 --enable-libgsm enable GSM de/encoding via libgsm [no]
@@ -1859,6 +1860,7 @@ EXTERNAL_LIBRARY_LIST=" · 1617 unmodified lines
18591860 libfontconfig
18601861 libfreetype
18611862 libfribidi
1863+ libharfbuzz
18621864 libglslang
18631865 libgme
18641866 libgsm
@@ -3726,7 +3728,7 @@ dilation_opencl_filter_deps="opencl" · 1861 unmodified lines
37263728 dnn_classify_filter_select="dnn"
37273729 dnn_detect_filter_select="dnn"
37283730 dnn_processing_filter_select="dnn"
3729−drawtext_filter_deps="libfreetype"
3731+drawtext_filter_deps="libfreetype libharfbuzz"
37303732 drawtext_filter_suggest="libfontconfig libfribidi"
37313733 elbg_filter_deps="avcodec"
37323734 eq_filter_deps="gpl"
@@ -6693,6 +6695,7 @@ enabled fontconfig && enable libfontconfig · 2960 unmodified lines
66936695 enabled libfontconfig && require_pkg_config libfontconfig fontconfig "fontconfig/fontconfig.h" FcInit
66946696 enabled libfreetype && require_pkg_config libfreetype freetype2 "ft2build.h FT_FREETYPE_H" FT_Init_FreeType
66956697 enabled libfribidi && require_pkg_config libfribidi fribidi fribidi.h fribidi_version_info
6698+enabled libharfbuzz && require_pkg_config libharfbuzz harfbuzz hb.h hb_buffer_create
66966699 enabled libglslang && { check_lib spirv_compiler glslang/Include/glslang_c_interface.h glslang_initialize_process \
66976700 -lglslang -lMachineIndependent -lOSDependent -lHLSL -lOGLCompiler -lGenericCodeGen \
66986701 -lSPVRemapper -lSPIRV -lSPIRV-Tools-opt -lSPIRV-Tools -lpthread -lstdc++ -lm ||
doc/filters.texi+5−3
@@ -12344,7 +12344,7 @@ Draw a text string or text from a specified file on top of a video, using the · 12343 unmodified lines
1234412344 libfreetype library.
1234512345
1234612346 To enable compilation of this filter, you need to configure FFmpeg with
12347−@code{--enable-libfreetype}.
12347+@code{--enable-libfreetype} and @code{--enable-libharfbuzz}.
1234812348 To enable default font fallback and the @var{font} option you need to
1234912349 configure FFmpeg with @code{--enable-libfontconfig}.
1235012350 To enable the @var{text_shaping} option, you need to configure FFmpeg with
@@ -12372,8 +12372,7 @@ option, check the @ref{color syntax,,"Color" section in the ffmpeg-utils manual, · 21 unmodified lines
1237212372 The default value of @var{boxcolor} is "white".
1237312373
1237412374 @item line_spacing
12375−Set the line spacing in pixels of the border to be drawn around the box using @var{box}.
12376−The default value of @var{line_spacing} is 0.
12375+Set the line spacing in pixels. The default value of @var{line_spacing} is 0.
1237712376
1237812377 @item borderw
1237912378 Set the width of the border to be drawn around the text using @var{bordercolor}.
@@ -12873,6 +12872,9 @@ For more information about fontconfig, check: · 493 unmodified lines
1287312872 For more information about libfribidi, check:
1287412873 @url{http://fribidi.org/}.
1287512874
12875+For more information about libharfbuzz, check:
12876+@url{https://github.com/harfbuzz/harfbuzz}.
12877+
1287612878 @section edgedetect
1287712879
1287812880 Detect and draw edges. The filter uses the Canny Edge Detection algorithm.
libavfilter/vf_drawtext.c+583−276
@@ -1,4 +1,5 @@
11 /*
2+ * Copyright (c) 2023 Francesco Carusi
23 * Copyright (c) 2011 Stefano Sabatini
34 * Copyright (c) 2010 S.N. Hemanth Meenakshisundaram
45 * Copyright (c) 2003 Gustavo Sverzut Barbieri <gsbarbieri@yahoo.com.br>
@@ -72,10 +73,16 @@ · 67 unmodified lines
7273 #include FT_GLYPH_H
7374 #include FT_STROKER_H
7475
76+#include <hb.h>
77+#include <hb-ft.h>
78+
79+// Ceiling operation for positive integers division
80+#define POS_CEIL(x, y) ((x)/(y) + ((x)%(y) != 0))
81+
7582 static const char *const var_names[] = {
7683 "dar",
7784 "hsub", "vsub",
78− "line_h", "lh", ///< line height, same as max_glyph_h
85+ "line_h", "lh", ///< line height
7986 "main_h", "h", "H", ///< height of the input video
8087 "main_w", "w", "W", ///< width of the input video
8188 "max_glyph_a", "ascent", ///< max glyph ascender
@@ -156,6 +163,72 @@ enum expansion_mode { · 74 unmodified lines
156163 EXP_STRFTIME,
157164 };
158165
166+typedef struct HarfbuzzData {
167+ hb_buffer_t* buf;
168+ hb_font_t* font;
169+ unsigned int glyph_count;
170+ hb_glyph_info_t* glyph_info;
171+ hb_glyph_position_t* glyph_pos;
172+} HarfbuzzData;
173+
174+/** Information about a single glyph in a text line */
175+typedef struct GlyphInfo {
176+ uint32_t code; ///< the glyph code point
177+ int x; ///< the x position of the glyph
178+ int y; ///< the y position of the glyph
179+ int shift_x64; ///< the horizontal shift of the glyph in 26.6 units
180+ int shift_y64; ///< the vertical shift of the glyph in 26.6 units
181+} GlyphInfo;
182+
183+/** Information about a single line of text */
184+typedef struct TextLine {
185+ int offset_left64; ///< offset between the origin and
186+ /// the leftmost pixel of the first glyph
187+ int offset_right64; ///< maximum offset between the origin and
188+ /// the rightmost pixel of the last glyph
189+ int width64; ///< width of the line
190+ HarfbuzzData hb_data; ///< libharfbuzz data of this text line
191+ GlyphInfo* glyphs; ///< array of glyphs in this text line
192+ int cluster_offset; ///< the offset at which this line begins
193+} TextLine;
194+
195+/** A glyph as loaded and rendered using libfreetype */
196+typedef struct Glyph {
197+ FT_Glyph glyph;
198+ FT_Glyph border_glyph;
199+ uint32_t code;
200+ unsigned int fontsize;
201+ /** Glyph bitmaps with 1/4 pixel precision in both directions */
202+ FT_BitmapGlyph bglyph[16];
203+ /** Outlined glyph bitmaps with 1/4 pixel precision in both directions */
204+ FT_BitmapGlyph border_bglyph[16];
205+ FT_BBox bbox;
206+} Glyph;
207+
208+/** Global text metrics */
209+typedef struct TextMetrics {
210+ int offset_top64; ///< ascender amount of the first line (in 26.6 units)
211+ int offset_bottom64; ///< descender amount of the last line (in 26.6 units)
212+ int offset_left64; ///< maximum offset between the origin and
213+ /// the leftmost pixel of the first glyph
214+ /// of each line (in 26.6 units)
215+ int offset_right64; ///< maximum offset between the origin and
216+ /// the rightmost pixel of the last glyph
217+ /// of each line (in 26.6 units)
218+ int line_height64; ///< the font-defined line height
219+ int width; ///< width of the longest line - ceil(width64/64)
220+ int height; ///< total height of the text - ceil(height64/64)
221+
222+ int min_y64; ///< minimum value of bbox.yMin among glyphs (in 26.6 units)
223+ int max_y64; ///< maximum value of bbox.yMax among glyphs (in 26.6 units)
224+ int min_x64; ///< minimum value of bbox.xMin among glyphs (in 26.6 units)
225+ int max_x64; ///< maximum value of bbox.xMax among glyphs (in 26.6 units)
226+
227+ // Position of the background box (without borders)
228+ int rect_x; ///< x position of the box
229+ int rect_y; ///< y position of the box
230+} TextMetrics;
231+
159232 typedef struct DrawTextContext {
160233 const AVClass *class;
161234 int exp_mode; ///< expansion mode to use for the text
@@ -169,11 +242,9 @@ typedef struct DrawTextContext { · 7 unmodified lines
169242 uint8_t *fontcolor_expr; ///< fontcolor expression to evaluate
170243 AVBPrint expanded_fontcolor; ///< used to contain the expanded fontcolor spec
171244 int ft_load_flags; ///< flags used for loading fonts, see FT_LOAD_*
172− FT_Vector *positions; ///< positions for each element in the text
173− size_t nb_positions; ///< number of elements of positions array
174245 char *textfile; ///< file with text to be drawn
175− int x; ///< x position to start drawing text
176− int y; ///< y position to start drawing text
246+ double x; ///< x position to start drawing text
247+ double y; ///< y position to start drawing text
177248 int max_glyph_w; ///< max glyph width
178249 int max_glyph_h; ///< max glyph height
179250 int shadowx, shadowy;
@@ -186,7 +257,10 @@ typedef struct DrawTextContext { · 6 unmodified lines
186257 int line_spacing; ///< lines spacing in pixels
187258 short int draw_box; ///< draw box around text - true or false
188259 int boxborderw; ///< box border width
189− int use_kerning; ///< font kerning is used - true/false
260+ int bb_top; ///< the size of the top box border
261+ int bb_right; ///< the size of the right box border
262+ int bb_bottom; ///< the size of the bottom box border
263+ int bb_left; ///< the size of the left box border
190264 int tabsize; ///< tab size
191265 int fix_bounds; ///< do we let it go out of frame bounds - t/f
192266
@@ -221,6 +295,12 @@ typedef struct DrawTextContext { · 28 unmodified lines
221295 int text_shaping; ///< 1 to shape the text before drawing it
222296 #endif
223297 AVDictionary *metadata;
298+
299+ TextLine *lines; ///< computed information about text lines
300+ int line_count; ///< the number of text lines
301+ uint32_t *tab_clusters; ///< the position of tab characters in the text
302+ int tab_count; ///< the number of tab characters
303+ int blank_advance64; ///< the size of the space character
224304 } DrawTextContext;
225305
226306 #define OFFSET(x) offsetof(DrawTextContext, x)
@@ -236,7 +316,7 @@ static const AVOption drawtext_options[]= { · 9 unmodified lines
236316 {"bordercolor", "set border color", OFFSET(bordercolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, FLAGS},
237317 {"shadowcolor", "set shadow color", OFFSET(shadowcolor.rgba), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, FLAGS},
238318 {"box", "set box", OFFSET(draw_box), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
239− {"boxborderw", "set box borders width", OFFSET(boxborderw), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS},
319+ {"boxborderw", "set box borders width", OFFSET(boxborderw), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS},
240320 {"line_spacing", "set line spacing in pixels", OFFSET(line_spacing), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS},
241321 {"fontsize", "set font size", OFFSET(fontsize_expr), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS},
242322 {"x", "set x expression", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, FLAGS},
@@ -305,19 +385,6 @@ static const struct ft_error { · 62 unmodified lines
305385
306386 #define FT_ERRMSG(e) ft_errors[e].err_msg
307387
308−typedef struct Glyph {
309− FT_Glyph glyph;
310− FT_Glyph border_glyph;
311− uint32_t code;
312− unsigned int fontsize;
313− FT_Bitmap bitmap; ///< array holding bitmaps of font
314− FT_Bitmap border_bitmap; ///< array holding bitmaps of font border
315− FT_BBox bbox;
316− int advance;
317− int bitmap_left;
318− int bitmap_top;
319−} Glyph;
320−
321388 static int glyph_cmp(const void *key, const void *b)
322389 {
323390 const Glyph *a = key, *bb = b;
@@ -329,77 +396,6 @@ static int glyph_cmp(const void *key, const void *b) · 5 unmodified lines
329396 return FFDIFFSIGN((int64_t)a->fontsize, (int64_t)bb->fontsize);
330397 }
331398
332−/**
333− * Load glyphs corresponding to the UTF-32 codepoint code.
334− */
335−static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code)
336−{
337− DrawTextContext *s = ctx->priv;
338− FT_BitmapGlyph bitmapglyph;
339− Glyph *glyph;
340− struct AVTreeNode *node = NULL;
341− int ret;
342−
343− /* load glyph into s->face->glyph */
344− if (FT_Load_Char(s->face, code, s->ft_load_flags))
345− return AVERROR(EINVAL);
346−
347− glyph = av_mallocz(sizeof(*glyph));
348− if (!glyph) {
349− ret = AVERROR(ENOMEM);
350− goto error;
351− }
352− glyph->code = code;
353− glyph->fontsize = s->fontsize;
354−
355− if (FT_Get_Glyph(s->face->glyph, &glyph->glyph)) {
356− ret = AVERROR(EINVAL);
357− goto error;
358− }
359− if (s->borderw) {
360− glyph->border_glyph = glyph->glyph;
361− if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0) ||
362− FT_Glyph_To_Bitmap(&glyph->border_glyph, FT_RENDER_MODE_NORMAL, 0, 1)) {
363− ret = AVERROR_EXTERNAL;
364− goto error;
365− }
366− bitmapglyph = (FT_BitmapGlyph) glyph->border_glyph;
367− glyph->border_bitmap = bitmapglyph->bitmap;
368− }
369− if (FT_Glyph_To_Bitmap(&glyph->glyph, FT_RENDER_MODE_NORMAL, 0, 1)) {
370− ret = AVERROR_EXTERNAL;
371− goto error;
372− }
373− bitmapglyph = (FT_BitmapGlyph) glyph->glyph;
374−
375− glyph->bitmap = bitmapglyph->bitmap;
376− glyph->bitmap_left = bitmapglyph->left;
377− glyph->bitmap_top = bitmapglyph->top;
378− glyph->advance = s->face->glyph->advance.x >> 6;
379−
380− /* measure text height to calculate text_height (or the maximum text height) */
381− FT_Glyph_Get_CBox(glyph->glyph, ft_glyph_bbox_pixels, &glyph->bbox);
382−
383− /* cache the newly created glyph */
384− if (!(node = av_tree_node_alloc())) {
385− ret = AVERROR(ENOMEM);
386− goto error;
387− }
388− av_tree_insert(&s->glyphs, glyph, glyph_cmp, &node);
389−
390− if (glyph_ptr)
391− *glyph_ptr = glyph;
392− return 0;
393−
394−error:
395− if (glyph)
396− av_freep(&glyph->glyph);
397−
398− av_freep(&glyph);
399− av_freep(&node);
400− return ret;
401−}
402−
403399 static av_cold int set_fontsize(AVFilterContext *ctx, unsigned int fontsize)
404400 {
405401 int err;
@@ -718,11 +714,114 @@ static enum AVFrameSideDataType text_source_string_parse(const char *text_source · 312 unmodified lines
718714 }
719715 }
720716
717+static inline int get_subpixel_idx(int shift_x64, int shift_y64)
718+{
719+ int idx = (shift_x64 >> 2) + (shift_y64 >> 4);
720+ return idx;
721+}
722+
723+// Loads and (optionally) renders a glyph
724+static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code, int8_t shift_x64, int8_t shift_y64)
725+{
726+ DrawTextContext *s = ctx->priv;
727+ Glyph dummy = { 0 };
728+ Glyph *glyph;
729+ FT_Vector shift;
730+ struct AVTreeNode *node = NULL;
731+ int ret = 0;
732+
733+ /* get glyph */
734+ dummy.code = code;
735+ dummy.fontsize = s->fontsize;
736+ glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
737+ if (!glyph) {
738+ if (FT_Load_Glyph(s->face, code, s->ft_load_flags)) {
739+ return AVERROR(EINVAL);
740+ }
741+ glyph = av_mallocz(sizeof(*glyph));
742+ if (!glyph) {
743+ ret = AVERROR(ENOMEM);
744+ goto error;
745+ }
746+ glyph->code = code;
747+ glyph->fontsize = s->fontsize;
748+ if (FT_Get_Glyph(s->face->glyph, &glyph->glyph)) {
749+ ret = AVERROR(EINVAL);
750+ goto error;
751+ }
752+ if (s->borderw) {
753+ glyph->border_glyph = glyph->glyph;
754+ if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0)) {
755+ ret = AVERROR_EXTERNAL;
756+ goto error;
757+ }
758+ }
759+ /* measure text height to calculate text_height (or the maximum text height) */
760+ FT_Glyph_Get_CBox(glyph->glyph, FT_GLYPH_BBOX_SUBPIXELS, &glyph->bbox);
761+
762+ /* cache the newly created glyph */
763+ if (!(node = av_tree_node_alloc())) {
764+ ret = AVERROR(ENOMEM);
765+ goto error;
766+ }
767+ av_tree_insert(&s->glyphs, glyph, glyph_cmp, &node);
768+ } else {
769+ if (s->borderw && !glyph->border_glyph) {
770+ glyph->border_glyph = glyph->glyph;
771+ if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0)) {
772+ ret = AVERROR_EXTERNAL;
773+ goto error;
774+ }
775+ }
776+ }
777+
778+ // Check if a bitmap is needed
779+ if (shift_x64 >= 0 && shift_y64 >= 0) {
780+ // Get the bitmap subpixel index (0 -> 15)
781+ int idx = get_subpixel_idx(shift_x64, shift_y64);
782+ shift.x = shift_x64;
783+ shift.y = shift_y64;
784+
785+ if (!glyph->bglyph[idx]) {
786+ FT_Glyph tmp_glyph = glyph->glyph;
787+ if (FT_Glyph_To_Bitmap(&tmp_glyph, FT_RENDER_MODE_NORMAL, &shift, 0)) {
788+ ret = AVERROR_EXTERNAL;
789+ goto error;
790+ }
791+ glyph->bglyph[idx] = (FT_BitmapGlyph)tmp_glyph;
792+ if (glyph->bglyph[idx]->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
793+ av_log(ctx, AV_LOG_ERROR, "Monocromatic (1bpp) fonts are not supported.\n");
794+ ret = AVERROR(EINVAL);
795+ goto error;
796+ }
797+ }
798+ if (s->borderw && !glyph->border_bglyph[idx]) {
799+ FT_Glyph tmp_glyph = glyph->border_glyph;
800+ if (FT_Glyph_To_Bitmap(&tmp_glyph, FT_RENDER_MODE_NORMAL, &shift, 0)) {
801+ ret = AVERROR_EXTERNAL;
802+ goto error;
803+ }
804+ glyph->border_bglyph[idx] = (FT_BitmapGlyph)tmp_glyph;
805+ }
806+ }
807+ if (glyph_ptr) {
808+ *glyph_ptr = glyph;
809+ }
810+ return 0;
811+
812+error:
813+ if (glyph && glyph->glyph)
814+ FT_Done_Glyph(glyph->glyph);
815+
816+ av_freep(&glyph);
817+ av_freep(&node);
818+ return ret;
819+}
820+
721821 static av_cold int init(AVFilterContext *ctx)
722822 {
723823 int err;
724824 DrawTextContext *s = ctx->priv;
725− Glyph *glyph;
726825
727826 av_expr_free(s->fontsize_pexpr);
728827 s->fontsize_pexpr = NULL;
@@ -811,17 +910,8 @@ static av_cold int init(AVFilterContext *ctx) · 82 unmodified lines
811910 FT_STROKER_LINEJOIN_ROUND, 0);
812911 }
813912
814− s->use_kerning = FT_HAS_KERNING(s->face);
815−
816913 /* load the fallback glyph with code 0 */
817− load_glyph(ctx, NULL, 0);
818−
819− /* set the tabsize in pixels */
820− if ((err = load_glyph(ctx, &glyph, ' ')) < 0) {
821− av_log(ctx, AV_LOG_ERROR, "Could not set tabsize.\n");
822− return err;
823− }
824− s->tabsize *= glyph->advance;
914+ load_glyph(ctx, NULL, 0, 0, 0);
825915
826916 if (s->exp_mode == EXP_STRFTIME &&
827917 (strchr(s->text, '%') || strchr(s->text, '\\')))
@@ -844,6 +934,14 @@ static int glyph_enu_free(void *opaque, void *elem) · 16 unmodified lines
844934
845935 FT_Done_Glyph(glyph->glyph);
846936 FT_Done_Glyph(glyph->border_glyph);
937+ for (int t = 0; t < 16; ++t) {
938+ if (glyph->bglyph[t] != NULL) {
939+ FT_Done_Glyph((FT_Glyph)glyph->bglyph[t]);
940+ }
941+ if (glyph->border_bglyph[t] != NULL) {
942+ FT_Done_Glyph((FT_Glyph)glyph->border_bglyph[t]);
943+ }
944+ }
847945 av_free(elem);
848946 return 0;
849947 }
@@ -859,9 +957,6 @@ static av_cold void uninit(AVFilterContext *ctx) · 9 unmodified lines
859957
860958 s->x_pexpr = s->y_pexpr = s->a_pexpr = s->fontsize_pexpr = NULL;
861959
862− av_freep(&s->positions);
863− s->nb_positions = 0;
864−
865960 av_tree_enumerate(s->glyphs, NULL, NULL, glyph_enu_free);
866961 av_tree_destroy(s->glyphs);
867962 s->glyphs = NULL;
@@ -1325,91 +1420,283 @@ static int expand_text(AVFilterContext *ctx, char *text, AVBPrint *bp) · 457 unmodified lines
13251420 return 0;
13261421 }
13271422
1423+static void update_color_with_alpha(DrawTextContext *s, FFDrawColor *color, const FFDrawColor incolor)
1424+{
1425+ *color = incolor;
1426+ color->rgba[3] = (color->rgba[3] * s->alpha) / 255;
1427+ ff_draw_color(&s->dc, color, color->rgba);
1428+}
1429+
1430+static void update_alpha(DrawTextContext *s)
1431+{
1432+ double alpha = av_expr_eval(s->a_pexpr, s->var_values, &s->prng);
1433+
1434+ if (isnan(alpha))
1435+ return;
1436+
1437+ if (alpha >= 1.0)
1438+ s->alpha = 255;
1439+ else if (alpha <= 0)
1440+ s->alpha = 0;
1441+ else
1442+ s->alpha = 256 * alpha;
1443+}
1444+
13281445 static int draw_glyphs(DrawTextContext *s, AVFrame *frame,
1329− int width, int height,
13301446 FFDrawColor *color,
1447+ TextMetrics *metrics,
13311448 int x, int y, int borderw)
13321449 {
1333− char *text = s->expanded_text.str;
1334− uint32_t code = 0;
1335− int i, x1, y1;
1336− uint8_t *p;
1337− Glyph *glyph = NULL;
1338−
1339− for (i = 0, p = text; *p; i++) {
1340− FT_Bitmap bitmap;
1341− Glyph dummy = { 0 };
1342− GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_invalid;);
1343−continue_on_invalid:
1450+ int g, l, x1, y1, w1, h1, idx;
1451+ int dx = 0, dy = 0, pdx = 0;
1452+ GlyphInfo *info;
1453+ Glyph dummy = { 0 }, *glyph;
1454+ FT_Bitmap bitmap;
1455+ FT_BitmapGlyph b_glyph;
1456+ int clip_x = 0, clip_y = 0;
1457+
1458+ clip_x = FFMIN(metrics->rect_x + metrics->width + s->bb_right, frame->width);
1459+ clip_y = FFMIN(metrics->rect_y + metrics->height + s->bb_bottom, frame->height);
1460+
1461+ for (l = 0; l < s->line_count; ++l) {
1462+ TextLine *line = &s->lines[l];
1463+ for (g = 0; g < line->hb_data.glyph_count; ++g) {
1464+ info = &line->glyphs[g];
1465+ dummy.fontsize = s->fontsize;
1466+ dummy.code = info->code;
1467+ glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
1468+ if (!glyph) {
1469+ return AVERROR(EINVAL);
1470+ }
13441471
1345− /* skip new line chars, just go to new line */
1346− if (code == '\n' || code == '\r' || code == '\t')
1347− continue;
1472+ idx = get_subpixel_idx(info->shift_x64, info->shift_y64);
1473+ b_glyph = borderw ? glyph->border_bglyph[idx] : glyph->bglyph[idx];
1474+ bitmap = b_glyph->bitmap;
1475+ x1 = x + info->x + b_glyph->left;
1476+ y1 = y + info->y - b_glyph->top;
1477+ w1 = bitmap.width;
1478+ h1 = bitmap.rows;
1479+
1480+ // Offset of the glyph's bitmap in the visible region
1481+ dx = dy = 0;
1482+ if (x1 < metrics->rect_x - s->bb_left) {
1483+ dx = metrics->rect_x - s->bb_left - x1;
1484+ x1 = metrics->rect_x - s->bb_left;
1485+ }
1486+ if (y1 < metrics->rect_y - s->bb_top) {
1487+ dy = metrics->rect_y - s->bb_top - y1;
1488+ y1 = metrics->rect_y - s->bb_top;
1489+ }
13481490
1349− dummy.code = code;
1350− dummy.fontsize = s->fontsize;
1351− glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
1491+ // check if the glyph is empty or out of the clipping region
1492+ if (dx >= w1 || dy >= h1 || x1 >= clip_x || y1 >= clip_y) {
1493+ continue;
1494+ }
13521495
1353− bitmap = borderw ? glyph->border_bitmap : glyph->bitmap;
1496+ pdx = dx + dy * bitmap.pitch;
1497+ w1 = FFMIN(clip_x - x1, w1 - dx);
1498+ h1 = FFMIN(clip_y - y1, h1 - dy);
13541499
1355− if (glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO &&
1356− glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
1357− return AVERROR(EINVAL);
1500+ ff_blend_mask(&s->dc, color, frame->data, frame->linesize, clip_x, clip_y,
1501+ bitmap.buffer + pdx, bitmap.pitch, w1, h1, 3, 0, x1, y1);
1502+ }
1503+ }
13581504
1359− x1 = s->positions[i].x+s->x+x - borderw;
1360− y1 = s->positions[i].y+s->y+y - borderw;
1505+ return 0;
1506+}
13611507
1362− ff_blend_mask(&s->dc, color,
1363− frame->data, frame->linesize, width, height,
1364− bitmap.buffer, bitmap.pitch,
1365− bitmap.width, bitmap.rows,
1366− bitmap.pixel_mode == FT_PIXEL_MODE_MONO ? 0 : 3,
1367− 0, x1, y1);
1508+// Shapes a line of text using libharfbuzz
1509+static int shape_text_hb(DrawTextContext *s, HarfbuzzData* hb, const char* text, int textLen)
1510+{
1511+ hb->buf = hb_buffer_create();
1512+ if(!hb_buffer_allocation_successful(hb->buf)) {
1513+ return AVERROR(ENOMEM);
13681514 }
1515+ hb_buffer_set_direction(hb->buf, HB_DIRECTION_LTR);
1516+ hb_buffer_set_script(hb->buf, HB_SCRIPT_LATIN);
1517+ hb_buffer_set_language(hb->buf, hb_language_from_string("en", -1));
1518+ hb_buffer_guess_segment_properties(hb->buf);
1519+ hb->font = hb_ft_font_create(s->face, NULL);
1520+ if(hb->font == NULL) {
1521+ return AVERROR(ENOMEM);
1522+ }
1523+ hb_ft_font_set_funcs(hb->font);
1524+ hb_buffer_add_utf8(hb->buf, text, textLen, 0, -1);
1525+ hb_shape(hb->font, hb->buf, NULL, 0);
1526+ hb->glyph_info = hb_buffer_get_glyph_infos(hb->buf, &hb->glyph_count);
1527+ hb->glyph_pos = hb_buffer_get_glyph_positions(hb->buf, &hb->glyph_count);
13691528
13701529 return 0;
13711530 }
13721531
1373−
1374−static void update_color_with_alpha(DrawTextContext *s, FFDrawColor *color, const FFDrawColor incolor)
1532+static void hb_destroy(HarfbuzzData *hb)
13751533 {
1376− *color = incolor;
1377− color->rgba[3] = (color->rgba[3] * s->alpha) / 255;
1378− ff_draw_color(&s->dc, color, color->rgba);
1534+ hb_buffer_destroy(hb->buf);
1535+ hb_font_destroy(hb->font);
1536+ hb->buf = NULL;
1537+ hb->font = NULL;
1538+ hb->glyph_info = NULL;
1539+ hb->glyph_pos = NULL;
13791540 }
13801541
1381−static void update_alpha(DrawTextContext *s)
1542+static int measure_text(AVFilterContext *ctx, TextMetrics *metrics)
13821543 {
1383− double alpha = av_expr_eval(s->a_pexpr, s->var_values, &s->prng);
1544+ DrawTextContext *s = ctx->priv;
1545+ char *text = s->expanded_text.str;
1546+ char *textdup = NULL, *start = NULL;
1547+ int num_chars = 0;
1548+ int width64 = 0, w64 = 0;
1549+ int cur_min_y64 = 0, first_max_y64 = -32000;
1550+ int first_min_x64 = 32000, last_max_x64 = -32000;
1551+ int min_y64 = 32000, max_y64 = -32000, min_x64 = 32000, max_x64 = -32000;
1552+ int line_count = 0;
1553+ uint32_t code = 0;
1554+ Glyph *glyph = NULL;
1555+ int height64;
1556+
1557+ int i, tab_idx = 0, last_tab_idx = 0, line_offset = 0;
1558+ char* p;
1559+ int ret = 0;
1560+
1561+ // Count the lines and the tab characters
1562+ s->tab_count = 0;
1563+ for (i = 0, p = text; 1; i++) {
1564+ GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed;);
1565+continue_on_failed:
1566+ if (is_newline(code) || code == 0) {
1567+ ++line_count;
1568+ if (code == 0) {
1569+ break;
1570+ }
1571+ } else if (code == '\t') {
1572+ ++s->tab_count;
1573+ }
1574+ }
13841575
1385− if (isnan(alpha))
1386− return;
1576+ // Evaluate the width of the space character if needed to replace tabs
1577+ if (s->tab_count > 0 && !s->blank_advance64) {
1578+ HarfbuzzData hb_data;
1579+ ret = shape_text_hb(s, &hb_data, " ", 1);
1580+ if(ret != 0) {
1581+ goto done;
1582+ }
1583+ s->blank_advance64 = hb_data.glyph_pos[0].x_advance;
1584+ hb_destroy(&hb_data);
1585+ }
13871586
1388− if (alpha >= 1.0)
1389− s->alpha = 255;
1390− else if (alpha <= 0)
1391− s->alpha = 0;
1392− else
1393− s->alpha = 256 * alpha;
1587+ s->line_count = line_count;
1588+ s->lines = av_mallocz(line_count * sizeof(TextLine));
1589+ s->tab_clusters = av_mallocz(s->tab_count * sizeof(uint32_t));
1590+ for (i = 0; i < s->tab_count; ++i) {
1591+ s->tab_clusters[i] = -1;
1592+ }
1593+
1594+ start = textdup = av_strdup(text);
1595+ if (textdup == NULL) {
1596+ ret = AVERROR(ENOMEM);
1597+ goto done;
1598+ }
1599+ line_count = 0;
1600+ for (i = 0, p = textdup; 1; i++) {
1601+ if (*p == '\t') {
1602+ s->tab_clusters[tab_idx++] = i;
1603+ *p = ' ';
1604+ }
1605+ GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed2;);
1606+continue_on_failed2:
1607+ if (is_newline(code) || code == 0) {
1608+ TextLine *cur_line = &s->lines[line_count];
1609+ HarfbuzzData *hb = &cur_line->hb_data;
1610+ cur_line->cluster_offset = line_offset;
1611+ ret = shape_text_hb(s, hb, start, num_chars);
1612+ if (ret != 0) {
1613+ goto done;
1614+ }
1615+ w64 = 0;
1616+ cur_min_y64 = 32000;
1617+ for (int t = 0; t < hb->glyph_count; ++t) {
1618+ uint8_t is_tab = last_tab_idx < s->tab_count &&
1619+ hb->glyph_info[t].cluster == s->tab_clusters[last_tab_idx] - line_offset;
1620+ if (is_tab) {
1621+ ++last_tab_idx;
1622+ }
1623+ ret = load_glyph(ctx, &glyph, hb->glyph_info[t].codepoint, -1, -1);
1624+ if (ret != 0) {
1625+ goto done;
1626+ }
1627+ if (line_count == 0) {
1628+ first_max_y64 = FFMAX(glyph->bbox.yMax, first_max_y64);
1629+ }
1630+ if (t == 0) {
1631+ cur_line->offset_left64 = glyph->bbox.xMin;
1632+ first_min_x64 = FFMIN(glyph->bbox.xMin, first_min_x64);
1633+ }
1634+ if (t == hb->glyph_count - 1) {
1635+ w64 += glyph->bbox.xMax;
1636+ last_max_x64 = FFMAX(glyph->bbox.xMax, last_max_x64);
1637+ cur_line->offset_right64 = glyph->bbox.xMax;
1638+ } else {
1639+ if (is_tab) {
1640+ int size = s->blank_advance64 * s->tabsize;
1641+ w64 = (w64 / size + 1) * size;
1642+ } else {
1643+ w64 += hb->glyph_pos[t].x_advance;
1644+ }
1645+ }
1646+ cur_min_y64 = FFMIN(glyph->bbox.yMin, cur_min_y64);
1647+ min_y64 = FFMIN(glyph->bbox.yMin, min_y64);
1648+ max_y64 = FFMAX(glyph->bbox.yMax, max_y64);
1649+ min_x64 = FFMIN(glyph->bbox.xMin, min_x64);
1650+ max_x64 = FFMAX(glyph->bbox.xMax, max_x64);
1651+ }
1652+
1653+ cur_line->width64 = w64;
1654+
1655+ av_log(s, AV_LOG_DEBUG, " Line: %d -- glyphs count: %d - width64: %d - offset_left64: %d - offset_right64: %d)\n",
1656+ line_count, hb->glyph_count, cur_line->width64, cur_line->offset_left64, cur_line->offset_right64);
1657+
1658+ if (w64 > width64) {
1659+ width64 = w64;
1660+ }
1661+ num_chars = -1;
1662+ start = p;
1663+ ++line_count;
1664+ line_offset = i + 1;
1665+ }
1666+
1667+ if (code == 0) break;
1668+ ++num_chars;
1669+ }
1670+
1671+ metrics->line_height64 = s->face->size->metrics.height;
1672+
1673+ metrics->width = POS_CEIL(width64, 64);
1674+ height64 = (metrics->line_height64 + s->line_spacing * 64) *
1675+ (FFMAX(0, line_count - 1)) + first_max_y64 - cur_min_y64;
1676+ metrics->height = POS_CEIL(height64, 64);
1677+
1678+ metrics->offset_top64 = first_max_y64;
1679+ metrics->offset_right64 = last_max_x64;
1680+ metrics->offset_bottom64 = cur_min_y64;
1681+ metrics->offset_left64 = first_min_x64;
1682+ metrics->min_x64 = min_x64;
1683+ metrics->min_y64 = min_y64;
1684+ metrics->max_x64 = max_x64;
1685+ metrics->max_y64 = max_y64;
1686+
1687+done:
1688+ av_free(textdup);
1689+ return ret;
13941690 }
13951691
1396−static int draw_text(AVFilterContext *ctx, AVFrame *frame,
1397− int width, int height)
1692+static int draw_text(AVFilterContext *ctx, AVFrame *frame)
13981693 {
13991694 DrawTextContext *s = ctx->priv;
14001695 AVFilterLink *inlink = ctx->inputs[0];
1401−
1402− uint32_t code = 0, prev_code = 0;
1403− int x = 0, y = 0, i = 0, ret;
1404− int max_text_line_w = 0, len;
1405− int box_w, box_h;
1406− char *text;
1407− uint8_t *p;
1408− int y_min = 32000, y_max = -32000;
1409− int x_min = 32000, x_max = -32000;
1410− FT_Vector delta;
1411− Glyph *glyph = NULL, *prev_glyph = NULL;
1412− Glyph dummy = { 0 };
1696+ int x = 0, y = 0, ret;
1697+ int shift_x64, shift_y64;
1698+ int x64, y64;
1699+ Glyph *glyph = NULL;
14131700
14141701 time_t now = time(0);
14151702 struct tm ltime;
@@ -1420,6 +1707,14 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame, · 4 unmodified lines
14201707 FFDrawColor bordercolor;
14211708 FFDrawColor boxcolor;
14221709
1710+ int width = frame->width;
1711+ int height = frame->height;
1712+ int rec_x = 0, rec_y = 0, rec_width = 0, rec_height = 0;
1713+ int is_outside = 0;
1714+ int last_tab_idx = 0;
1715+
1716+ TextMetrics metrics;
1717+
14231718 av_bprint_clear(bp);
14241719
14251720 if (s->basetime != AV_NOPTS_VALUE)
@@ -1448,13 +1743,6 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame, · 22 unmodified lines
14481743
14491744 if (!av_bprint_is_complete(bp))
14501745 return AVERROR(ENOMEM);
1451− text = s->expanded_text.str;
1452− if ((len = s->expanded_text.len) > s->nb_positions) {
1453− if (!(s->positions =
1454− av_realloc(s->positions, len*sizeof(*s->positions))))
1455− return AVERROR(ENOMEM);
1456− s->nb_positions = len;
1457− }
14581746
14591747 if (s->fontcolor_expr[0]) {
14601748 /* If expression is set, evaluate and replace the static value */
@@ -1470,85 +1758,26 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame, · 9 unmodified lines
14701758 ff_draw_color(&s->dc, &s->fontcolor, s->fontcolor.rgba);
14711759 }
14721760
1473− x = 0;
1474− y = 0;
1475−
1476− if ((ret = update_fontsize(ctx)) < 0)
1761+ if ((ret = update_fontsize(ctx)) < 0) {
14771762 return ret;
1478−
1479− /* load and cache glyphs */
1480− for (i = 0, p = text; *p; i++) {
1481− GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_invalid;);
1482−continue_on_invalid:
1483−
1484− /* get glyph */
1485− dummy.code = code;
1486− dummy.fontsize = s->fontsize;
1487− glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
1488− if (!glyph) {
1489− ret = load_glyph(ctx, &glyph, code);
1490− if (ret < 0)
1491− return ret;
1492− }
1493−
1494− y_min = FFMIN(glyph->bbox.yMin, y_min);
1495− y_max = FFMAX(glyph->bbox.yMax, y_max);
1496− x_min = FFMIN(glyph->bbox.xMin, x_min);
1497− x_max = FFMAX(glyph->bbox.xMax, x_max);
14981763 }
1499− s->max_glyph_h = y_max - y_min;
1500− s->max_glyph_w = x_max - x_min;
1501−
1502− /* compute and save position for each glyph */
1503− glyph = NULL;
1504− for (i = 0, p = text; *p; i++) {
1505− GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_invalid2;);
1506−continue_on_invalid2:
1507−
1508− /* skip the \n in the sequence \r\n */
1509− if (prev_code == '\r' && code == '\n')
1510− continue;
1511−
1512− prev_code = code;
1513− if (is_newline(code)) {
1514−
1515− max_text_line_w = FFMAX(max_text_line_w, x);
1516− y += s->max_glyph_h + s->line_spacing;
1517− x = 0;
1518− continue;
1519− }
1520−
1521− /* get glyph */
1522− prev_glyph = glyph;
1523− dummy.code = code;
1524− dummy.fontsize = s->fontsize;
1525− glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
1526−
1527− /* kerning */
1528− if (s->use_kerning && prev_glyph && glyph->code) {
1529− FT_Get_Kerning(s->face, prev_glyph->code, glyph->code,
1530− ft_kerning_default, &delta);
1531− x += delta.x >> 6;
1532− }
15331764
1534− /* save position */
1535− s->positions[i].x = x + glyph->bitmap_left;
1536− s->positions[i].y = y - glyph->bitmap_top + y_max;
1537− if (code == '\t') x = (x / s->tabsize + 1)*s->tabsize;
1538− else x += glyph->advance;
1765+ if ((ret = measure_text(ctx, &metrics)) < 0) {
1766+ return ret;
15391767 }
15401768
1541− max_text_line_w = FFMAX(x, max_text_line_w);
1769+ s->max_glyph_h = POS_CEIL(metrics.max_y64 - metrics.min_y64, 64);
1770+ s->max_glyph_w = POS_CEIL(metrics.max_x64 - metrics.min_x64, 64);
15421771
1543− s->var_values[VAR_TW] = s->var_values[VAR_TEXT_W] = max_text_line_w;
1544− s->var_values[VAR_TH] = s->var_values[VAR_TEXT_H] = y + s->max_glyph_h;
1772+ s->var_values[VAR_TW] = s->var_values[VAR_TEXT_W] = metrics.width;
1773+ s->var_values[VAR_TH] = s->var_values[VAR_TEXT_H] = metrics.height;
15451774
15461775 s->var_values[VAR_MAX_GLYPH_W] = s->max_glyph_w;
15471776 s->var_values[VAR_MAX_GLYPH_H] = s->max_glyph_h;
1548− s->var_values[VAR_MAX_GLYPH_A] = s->var_values[VAR_ASCENT ] = y_max;
1549− s->var_values[VAR_MAX_GLYPH_D] = s->var_values[VAR_DESCENT] = y_min;
1777+ s->var_values[VAR_MAX_GLYPH_A] = s->var_values[VAR_ASCENT] = POS_CEIL(metrics.max_y64, 64);
1778+ s->var_values[VAR_MAX_GLYPH_D] = s->var_values[VAR_DESCENT] = POS_CEIL(metrics.min_y64, 64);
15501779
1551− s->var_values[VAR_LINE_H] = s->var_values[VAR_LH] = s->max_glyph_h;
1780+ s->var_values[VAR_LINE_H] = s->var_values[VAR_LH] = metrics.line_height64 / 64.;
15521781
15531782 if (s->text_source == AV_FRAME_DATA_DETECTION_BBOXES) {
15541783 s->var_values[VAR_X] = s->x;
@@ -1566,56 +1795,139 @@ continue_on_invalid2: · 11 unmodified lines
15661795 update_color_with_alpha(s, &bordercolor, s->bordercolor);
15671796 update_color_with_alpha(s, &boxcolor , s->boxcolor );
15681797
1569− box_w = max_text_line_w;
1570− box_h = y + s->max_glyph_h;
1798+ if (s->draw_box && s->boxborderw) {
1799+ s->bb_top = s->bb_right = s->bb_bottom = s->bb_left = s->boxborderw;
1800+ } else {
1801+ s->bb_top = s->bb_right = s->bb_bottom = s->bb_left = 0;
1802+ }
15711803
15721804 if (s->fix_bounds) {
1573−
15741805 /* calculate footprint of text effects */
1575− int boxoffset = s->draw_box ? FFMAX(s->boxborderw, 0) : 0;
15761806 int borderoffset = s->borderw ? FFMAX(s->borderw, 0) : 0;
15771807
1578− int offsetleft = FFMAX3(boxoffset, borderoffset,
1808+ int offsetleft = FFMAX3(FFMAX(s->bb_left, 0), borderoffset,
15791809 (s->shadowx < 0 ? FFABS(s->shadowx) : 0));
1580− int offsettop = FFMAX3(boxoffset, borderoffset,
1810+ int offsettop = FFMAX3(FFMAX(s->bb_top, 0), borderoffset,
15811811 (s->shadowy < 0 ? FFABS(s->shadowy) : 0));
1582−
1583− int offsetright = FFMAX3(boxoffset, borderoffset,
1812+ int offsetright = FFMAX3(FFMAX(s->bb_right, 0), borderoffset,
15841813 (s->shadowx > 0 ? s->shadowx : 0));
1585− int offsetbottom = FFMAX3(boxoffset, borderoffset,
1814+ int offsetbottom = FFMAX3(FFMAX(s->bb_bottom, 0), borderoffset,
15861815 (s->shadowy > 0 ? s->shadowy : 0));
15871816
1588−
15891817 if (s->x - offsetleft < 0) s->x = offsetleft;
15901818 if (s->y - offsettop < 0) s->y = offsettop;
15911819
1592− if (s->x + box_w + offsetright > width)
1593− s->x = FFMAX(width - box_w - offsetright, 0);
1594− if (s->y + box_h + offsetbottom > height)
1595− s->y = FFMAX(height - box_h - offsetbottom, 0);
1820+ if (s->x + metrics.width + offsetright > width)
1821+ s->x = FFMAX(width - metrics.width - offsetright, 0);
1822+ if (s->y + metrics.height + offsetbottom > height)
1823+ s->y = FFMAX(height - metrics.height - offsetbottom, 0);
15961824 }
15971825
1598− /* draw box */
1599− if (s->draw_box)
1600− ff_blend_rectangle(&s->dc, &boxcolor,
1601− frame->data, frame->linesize, width, height,
1602− s->x - s->boxborderw, s->y - s->boxborderw,
1603− box_w + s->boxborderw * 2, box_h + s->boxborderw * 2);
1826+ x = 0;
1827+ y = 0;
1828+ x64 = (int)(s->x * 64.);
1829+ y64 = (int)(s->y * 64. + metrics.offset_top64);
1830+
1831+ for (int l = 0; l < s->line_count; ++l) {
1832+ TextLine *line = &s->lines[l];
1833+ HarfbuzzData *hb = &line->hb_data;
1834+ line->glyphs = av_mallocz(hb->glyph_count * sizeof(GlyphInfo));
1835+
1836+ for (int t = 0; t < hb->glyph_count; ++t) {
1837+ GlyphInfo *g_info = &line->glyphs[t];
1838+ uint8_t is_tab = last_tab_idx < s->tab_count &&
1839+ hb->glyph_info[t].cluster == s->tab_clusters[last_tab_idx] - line->cluster_offset;
1840+ int true_x, true_y;
1841+ if (is_tab) {
1842+ ++last_tab_idx;
1843+ }
1844+ true_x = x + hb->glyph_pos[t].x_offset;
1845+ true_y = y + hb->glyph_pos[t].y_offset;
1846+ shift_x64 = (((x64 + true_x) >> 4) & 0b0011) << 4;
1847+ shift_y64 = ((4 - (((y64 + true_y) >> 4) & 0b0011)) & 0b0011) << 4;
1848+
1849+ ret = load_glyph(ctx, &glyph, hb->glyph_info[t].codepoint, shift_x64, shift_y64);
1850+ if (ret != 0) {
1851+ return ret;
1852+ }
1853+ g_info->code = hb->glyph_info[t].codepoint;
1854+ g_info->x = (x64 + true_x) >> 6;
1855+ g_info->y = ((y64 + true_y) >> 6) + (shift_y64 > 0 ? 1 : 0);
1856+ g_info->shift_x64 = shift_x64;
1857+ g_info->shift_y64 = shift_y64;
1858+
1859+ if (!is_tab) {
1860+ x += hb->glyph_pos[t].x_advance;
1861+ } else {
1862+ int size = s->blank_advance64 * s->tabsize;
1863+ x = (x / size + 1) * size;
1864+ }
1865+ y += hb->glyph_pos[t].y_advance;
1866+ }
16041867
1605− if (s->shadowx || s->shadowy) {
1606− if ((ret = draw_glyphs(s, frame, width, height,
1607− &shadowcolor, s->shadowx, s->shadowy, 0)) < 0)
1608− return ret;
1868+ y += metrics.line_height64 + s->line_spacing * 64;
1869+ x = 0;
16091870 }
16101871
1611− if (s->borderw) {
1612− if ((ret = draw_glyphs(s, frame, width, height,
1613− &bordercolor, 0, 0, s->borderw)) < 0)
1872+ metrics.rect_x = s->x;
1873+ metrics.rect_y = s->y;
1874+
1875+ if (!s->draw_box) {
1876+ // Create a border for the clipping region to take into account subpixel
1877+ // errors in text measurement and effects.
1878+ int borderoffset = s->borderw ? FFMAX(s->borderw, 0) : 0;
1879+ s->bb_left = borderoffset + (s->shadowx < 0 ? FFABS(s->shadowx) : 0) + 1;
1880+ s->bb_top = borderoffset + (s->shadowy < 0 ? FFABS(s->shadowy) : 0) + 1;
1881+ s->bb_right = borderoffset + (s->shadowx > 0 ? s->shadowx : 0) + 1;
1882+ s->bb_bottom = borderoffset + (s->shadowy > 0 ? s->shadowy : 0) + 1;
1883+ }
1884+
1885+ /* Check if the whole box is out of the frame */
1886+ is_outside = metrics.rect_x - s->bb_left >= width ||
1887+ metrics.rect_y - s->bb_top >= height ||
1888+ metrics.rect_x + metrics.width + s->bb_right <= 0 ||
1889+ metrics.rect_y + metrics.height + s->bb_bottom <= 0;
1890+
1891+ if (!is_outside) {
1892+ /* draw box */
1893+ if (s->draw_box) {
1894+ rec_x = metrics.rect_x - s->bb_left;
1895+ rec_y = metrics.rect_y - s->bb_top;
1896+ rec_width = metrics.width + s->bb_right + s->bb_left;
1897+ rec_height = metrics.height + s->bb_bottom + s->bb_top;
1898+ ff_blend_rectangle(&s->dc, &boxcolor,
1899+ frame->data, frame->linesize, width, height,
1900+ rec_x, rec_y, rec_width, rec_height);
1901+ }
1902+
1903+ if (s->shadowx || s->shadowy) {
1904+ if ((ret = draw_glyphs(s, frame, &shadowcolor, &metrics,
1905+ s->shadowx, s->shadowy, s->borderw)) < 0) {
1906+ return ret;
1907+ }
1908+ }
1909+
1910+ if (s->borderw) {
1911+ if ((ret = draw_glyphs(s, frame, &bordercolor, &metrics,
1912+ 0, 0, s->borderw)) < 0) {
1913+ return ret;
1914+ }
1915+ }
1916+
1917+ if ((ret = draw_glyphs(s, frame, &fontcolor, &metrics, 0,
1918+ 0, 0)) < 0) {
16141919 return ret;
1920+ }
16151921 }
1616− if ((ret = draw_glyphs(s, frame, width, height,
1617− &fontcolor, 0, 0, 0)) < 0)
1618− return ret;
1922+
1923+ // FREE data structures
1924+ for (int l = 0; l < s->line_count; ++l) {
1925+ TextLine *line = &s->lines[l];
1926+ av_freep(&line->glyphs);
1927+ hb_destroy(&line->hb_data);
1928+ }
1929+ av_freep(&s->lines);
1930+ av_freep(&s->tab_clusters);
16191931
16201932 return 0;
16211933 }
@@ -1691,14 +2003,9 @@ FF_ENABLE_DEPRECATION_WARNINGS · 69 unmodified lines
16912003 s->x = bbox->x;
16922004 s->y = bbox->y - s->fontsize;
16932005 }
1694− draw_text(ctx, frame, frame->width, frame->height);
2006+ draw_text(ctx, frame);
16952007 }
16962008
1697− av_log(ctx, AV_LOG_DEBUG, "n:%d t:%f text_w:%d text_h:%d x:%d y:%d\n",
1698− (int)s->var_values[VAR_N], s->var_values[VAR_T],
1699− (int)s->var_values[VAR_TEXT_W], (int)s->var_values[VAR_TEXT_H],
1700− s->x, s->y);
1701−
17022009 return ff_filter_frame(outlink, frame);
17032010 }
17042011

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t