On 30/01/2023 13:19, Paul B Mahol wrote:
On 1/30/23, Francesco Carusi <klimk...@tiscali.it> wrote:
On 28/01/2023 16:32, Paul B Mahol wrote:
On 1/28/23, Francesco Carusi <klimk...@tiscali.it> wrote:
On 27/01/2023 18:31, Paul B Mahol wrote:
On 1/27/23, Francesco Carusi <klimk...@tiscali.it> wrote:
On 26/01/2023 17:37, Paul B Mahol wrote:
On 1/26/23, Francesco Carusi <klimk...@tiscali.it> wrote:
On 26/01/2023 14:21, Paul B Mahol wrote:
On 1/26/23, Francesco Carusi <klimk...@tiscali.it> wrote:
The drawtext reinit command is also used in the docs as an example
for
the sendcmd filter, so I thought it was fine to use commands in
that
way. In my opinion it is also a convenient way to modify multiple
options at the same time.
Should the command match the name of a filter option instead?

Please do not top post.

It is much better to use already existing options for commands that
is
more intuitive to users. Also multiple options can be set at
runtime,
there is no such limitation.
ok, I'm going to remove the "change" command and add commands that
match
the options that it included.
Thanks, feel free to ask questions on #ffmpeg-devel irc channel.
I'm attaching the updated patch, I also updated the document at
https://github.com/yethie/FFmpeg/blob/master/drawtext/CHANGES.md
Thanks
Amazing, I like demos!

Could improve code style of newly added/changed lines?
For example opening { put on separate line. So code style is in sync
with rest of codebase.
Sure, I'll put opening { on a new line for functions, not for control
statements, like in the rest of the code. Is it fine?
Yes. Thanks.

The commands stuff does not need to use strcmp to detect if option
value have been changed, you could avoid strcmp by just caching old
value prior to calling function the picks new values, and after that
just compare old with new and then if it differs call needed code.
I'll cache the numeric values. I think that caching string values is not
the preferred solution because in addition to the strcmp needed to check
the value, it would also need a strdup to cache the previous value, even
when the command does not involve those options. Does it sound
reasonable?
Yes.
I'm attaching the patch that includes the changes we discussed.
space between 'for' and '('

Do not keep old code in comments if its no longer relevant or working.

Ok I added spaces between control statements (if, for, while) and '(', and also cleaned up comments. Following Anton Khirnov suggestion I tried to split the changes into multiple commits. However the first one is quite bit since it contains a major change in how the filter works and cannot be split further.
Patches attached.

On 26/01/2023 11:50, Paul B Mahol wrote:
On 1/26/23, Francesco Carusi <klimk...@tiscali.it> wrote:
Hi, I'm new to contributing to ffmpeg!

I modified the drawtext filter to improve text rendering and add
some
features. You can find a high level description of the changes
at
this
link:

https://github.com/yethie/FFmpeg/blob/master/drawtext/CHANGES.md

I'm also attaching the patch file.
I looked for the filter maintainer to discuss about the changes
I
made
but it looks like there isn't any, am I correct?

Please let me know if this is the right way to submit my
contribution.
Why filter can not support normal commands for options? Like
most/all
other filters that have support for changing options values at
runtime.

The reinit and yours added change option(s) are very
strange/inconvenient things to do.
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".
From 50a25f1502a215000d0b4bd337204804b59f407d Mon Sep 17 00:00:00 2001
From: yethie <klimk...@tiscali.it>
Date: Fri, 3 Feb 2023 14:13:59 +0100
Subject: [PATCH 1/7] use libharfbuzz to shape text . the filter now depends on
 libharfbuzz . the default line height is set equal to the one defined in the
 font metrics . subpixel precision to 1/4 pixel is now used when rendering
 glyphs

---
 configure                 |   5 +-
 doc/filters.texi          |   8 +-
 libavfilter/vf_drawtext.c | 961 +++++++++++++++++++++++++-------------
 3 files changed, 648 insertions(+), 326 deletions(-)

diff --git a/configure b/configure
index c726076da1..54504c0dbc 100755
--- a/configure
+++ b/configure
@@ -235,6 +235,7 @@ External library support:
   --enable-libfontconfig   enable libfontconfig, useful for drawtext filter 
[no]
   --enable-libfreetype     enable libfreetype, needed for drawtext filter [no]
   --enable-libfribidi      enable libfribidi, improves drawtext filter [no]
+  --enable-libharfbuzz     enable libharfbuzz, needed for drawtext filter [no]
   --enable-libglslang      enable GLSL->SPIRV compilation via libglslang [no]
   --enable-libgme          enable Game Music Emu via libgme [no]
   --enable-libgsm          enable GSM de/encoding via libgsm [no]
@@ -1818,6 +1819,7 @@ EXTERNAL_LIBRARY_LIST="
     libfontconfig
     libfreetype
     libfribidi
+    libharfbuzz
     libglslang
     libgme
     libgsm
@@ -3660,7 +3662,7 @@ dilation_opencl_filter_deps="opencl"
 dnn_classify_filter_select="dnn"
 dnn_detect_filter_select="dnn"
 dnn_processing_filter_select="dnn"
-drawtext_filter_deps="libfreetype"
+drawtext_filter_deps="libfreetype libharfbuzz"
 drawtext_filter_suggest="libfontconfig libfribidi"
 elbg_filter_deps="avcodec"
 eq_filter_deps="gpl"
@@ -6584,6 +6586,7 @@ enabled fontconfig        && enable libfontconfig
 enabled libfontconfig     && require_pkg_config libfontconfig fontconfig 
"fontconfig/fontconfig.h" FcInit
 enabled libfreetype       && require_pkg_config libfreetype freetype2 
"ft2build.h FT_FREETYPE_H" FT_Init_FreeType
 enabled libfribidi        && require_pkg_config libfribidi fribidi fribidi.h 
fribidi_version_info
+enabled libharfbuzz       && require_pkg_config libharfbuzz harfbuzz hb.h 
hb_buffer_create
 enabled libglslang && { check_lib spirv_compiler 
glslang/Include/glslang_c_interface.h glslang_initialize_process \
                             -lglslang -lMachineIndependent -lOSDependent 
-lHLSL -lOGLCompiler -lGenericCodeGen \
                             -lSPVRemapper -lSPIRV -lSPIRV-Tools-opt 
-lSPIRV-Tools -lpthread -lstdc++ -lm ||
diff --git a/doc/filters.texi b/doc/filters.texi
index 3a54c68f3e..49d7218e84 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -12032,7 +12032,7 @@ Draw a text string or text from a specified file on top 
of a video, using the
 libfreetype library.
 
 To enable compilation of this filter, you need to configure FFmpeg with
-@code{--enable-libfreetype}.
+@code{--enable-libfreetype} and @code{--enable-libharfbuzz}.
 To enable default font fallback and the @var{font} option you need to
 configure FFmpeg with @code{--enable-libfontconfig}.
 To enable the @var{text_shaping} option, you need to configure FFmpeg with
@@ -12060,8 +12060,7 @@ option, check the @ref{color syntax,,"Color" section in 
the ffmpeg-utils manual,
 The default value of @var{boxcolor} is "white".
 
 @item line_spacing
-Set the line spacing in pixels of the border to be drawn around the box using 
@var{box}.
-The default value of @var{line_spacing} is 0.
+Set the line spacing in pixels. The default value of @var{line_spacing} is 0.
 
 @item borderw
 Set the width of the border to be drawn around the text using 
@var{bordercolor}.
@@ -12559,6 +12558,9 @@ For more information about fontconfig, check:
 For more information about libfribidi, check:
 @url{http://fribidi.org/}.
 
+For more information about libharfbuzz, check:
+@url{https://github.com/harfbuzz/harfbuzz}.
+
 @section edgedetect
 
 Detect and draw edges. The filter uses the Canny Edge Detection algorithm.
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index 50012bb258..7a0a255c5e 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -1,4 +1,5 @@
 /*
+ * Copyright (c) 2023 Francesco Carusi
  * Copyright (c) 2011 Stefano Sabatini
  * Copyright (c) 2010 S.N. Hemanth Meenakshisundaram
  * Copyright (c) 2003 Gustavo Sverzut Barbieri <gsbarbi...@yahoo.com.br>
@@ -20,6 +21,14 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+/*
+ * Changelog - 2023
+ *
+ * - This filter now depends on libharfbuzz for text shaping.
+ * - Glyphs position is now accurate to 1/4 pixel in both directions
+ * - The default line height is now the one defined in the font
+ */
+
 /**
  * @file
  * drawtext filter, based on the original vhook/drawtext.c
@@ -72,14 +81,20 @@
 #include FT_GLYPH_H
 #include FT_STROKER_H
 
+#include <hb.h>
+#include <hb-ft.h>
+
+// Ceiling operation for positive integers division
+#define POS_CEIL(x, y) ((x)/(y) + ((x)%(y) != 0))
+
 static const char *const var_names[] = {
     "dar",
     "hsub", "vsub",
-    "line_h", "lh",           ///< line height, same as max_glyph_h
+    "line_h", "lh",           ///< line height
     "main_h", "h", "H",       ///< height of the input video
     "main_w", "w", "W",       ///< width  of the input video
-    "max_glyph_a", "ascent",  ///< max glyph ascent
-    "max_glyph_d", "descent", ///< min glyph descent
+    "max_glyph_a", "ascent",  ///< max glyph ascender
+    "max_glyph_d", "descent", ///< min glyph descender
     "max_glyph_h",            ///< max glyph height
     "max_glyph_w",            ///< max glyph width
     "n",                      ///< number of frame
@@ -148,12 +163,78 @@ enum expansion_mode {
     EXP_STRFTIME,
 };
 
+typedef struct HarfbuzzData {
+    hb_buffer_t* buf;
+    hb_font_t* font;
+    unsigned int glyph_count;
+    hb_glyph_info_t* glyph_info;
+    hb_glyph_position_t* glyph_pos;
+} HarfbuzzData;
+
+/** Information about a single glyph in a text line */
+typedef struct GlyphInfo {
+    uint32_t code;                  ///< the glyph code point
+    int x;                          ///< the x position of the glyph
+    int y;                          ///< the y position of the glyph
+    int shift_x64;                  ///< the horizontal shift of the glyph in 
26.6 units
+    int shift_y64;                  ///< the vertical shift of the glyph in 
26.6 units
+} GlyphInfo;
+
+/** Information about a single line of text */
+typedef struct TextLine {
+    int offset_left64;              ///< offset between the origin and
+                                    ///  the leftmost pixel of the first glyph
+    int offset_right64;             ///< maximum offset between the origin and
+                                    ///  the rightmost pixel of the last glyph
+    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 cluster_offset;             ///< the offset at which this line begins
+} TextLine;
+
+/** A glyph as loaded and rendered using libfreetype */
+typedef struct Glyph {
+    FT_Glyph glyph;
+    FT_Glyph border_glyph;
+    uint32_t code;
+    unsigned int fontsize;
+    /** 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 */
+    FT_BitmapGlyph border_bglyph[16];
+    FT_BBox bbox;
+} Glyph;
+
+/** Global text metrics */
+typedef struct TextMetrics {
+    int offset_top64;               ///< ascender amount of the first line (in 
26.6 units)
+    int offset_bottom64;            ///< descender amount of the last line (in 
26.6 units)
+    int offset_left64;              ///< maximum offset between the origin and
+                                    ///  the leftmost pixel of the first glyph
+                                    ///  of each line (in 26.6 units)
+    int offset_right64;             ///< maximum offset between the origin and
+                                    ///  the rightmost pixel of the last glyph
+                                    ///  of each line (in 26.6 units)
+    int line_height64;              ///< the font-defined line height
+    int width;                      ///< width of the longest line - 
ceil(width64/64)
+    int height;                     ///< total height of the text - 
ceil(height64/64)
+
+    int min_y64;                    ///< minimum value of bbox.yMin among 
glyphs (in 26.6 units)
+    int max_y64;                    ///< maximum value of bbox.yMax among 
glyphs (in 26.6 units)
+    int min_x64;                    ///< minimum value of bbox.xMin among 
glyphs (in 26.6 units)
+    int max_x64;                    ///< maximum value of bbox.xMax among 
glyphs (in 26.6 units)
+
+    // Position of the background box (without borders)
+    int rect_x;                     ///< x position of the box
+    int rect_y;                     ///< y position of the box
+} TextMetrics;
+
 typedef struct DrawTextContext {
     const AVClass *class;
     int exp_mode;                   ///< expansion mode to use for the text
     int reinit;                     ///< tells if the filter is being reinited
 #if CONFIG_LIBFONTCONFIG
-    uint8_t *font;              ///< font to be used
+    uint8_t *font;                  ///< font to be used
 #endif
     uint8_t *fontfile;              ///< font to be used
     uint8_t *text;                  ///< text to be drawn
@@ -161,11 +242,9 @@ typedef struct DrawTextContext {
     uint8_t *fontcolor_expr;        ///< fontcolor expression to evaluate
     AVBPrint expanded_fontcolor;    ///< used to contain the expanded 
fontcolor spec
     int ft_load_flags;              ///< flags used for loading fonts, see 
FT_LOAD_*
-    FT_Vector *positions;           ///< positions for each element in the text
-    size_t nb_positions;            ///< number of elements of positions array
     char *textfile;                 ///< file with text to be drawn
-    int x;                          ///< x position to start drawing text
-    int y;                          ///< y position to start drawing text
+    double x;                       ///< x position to start drawing text
+    double y;                       ///< y position to start drawing text
     int max_glyph_w;                ///< max glyph width
     int max_glyph_h;                ///< max glyph height
     int shadowx, shadowy;
@@ -178,7 +257,12 @@ typedef struct DrawTextContext {
     int line_spacing;               ///< lines spacing in pixels
     short int draw_box;             ///< draw box around text - true or false
     int boxborderw;                 ///< box border width
-    int use_kerning;                ///< font kerning is used - true/false
+    int bb_top;                     ///< the size of the top box border
+    int bb_right;                   ///< the size of the right box border
+    int bb_bottom;                  ///< the size of the bottom box border
+    int bb_left;                    ///< the size of the left box border
+    int box_width;                  ///< the width of box
+    int box_height;                 ///< the height of box
     int tabsize;                    ///< tab size
     int fix_bounds;                 ///< do we let it go out of frame bounds - 
t/f
 
@@ -213,31 +297,37 @@ typedef struct DrawTextContext {
     int text_shaping;               ///< 1 to shape the text before drawing it
 #endif
     AVDictionary *metadata;
+
+    TextLine *lines;                ///< computed information about text lines
+    int line_count;                 ///< the number of text lines
+    uint32_t *tab_clusters;         ///< the position of tab characters in the 
text
+    int tab_count;                  ///< the number of tab characters
+    int blank_advance64;            ///< the size of the space character
 } DrawTextContext;
 
 #define OFFSET(x) offsetof(DrawTextContext, x)
 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
 
 static const AVOption drawtext_options[]= {
-    {"fontfile",    "set font file",        OFFSET(fontfile),           
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
-    {"text",        "set text",             OFFSET(text),               
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
-    {"textfile",    "set text file",        OFFSET(textfile),           
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
-    {"fontcolor",   "set foreground color", OFFSET(fontcolor.rgba),     
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
+    {"fontfile",       "set font file",         OFFSET(fontfile),           
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
+    {"text",           "set text",              OFFSET(text),               
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
+    {"textfile",       "set text file",         OFFSET(textfile),           
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
+    {"fontcolor",      "set foreground color",  OFFSET(fontcolor.rgba),     
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
     {"fontcolor_expr", "set foreground color expression", 
OFFSET(fontcolor_expr), AV_OPT_TYPE_STRING, {.str=""}, 0, 0, FLAGS},
-    {"boxcolor",    "set box color",        OFFSET(boxcolor.rgba),      
AV_OPT_TYPE_COLOR,  {.str="white"}, 0, 0, FLAGS},
-    {"bordercolor", "set border color",     OFFSET(bordercolor.rgba),   
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
-    {"shadowcolor", "set shadow color",     OFFSET(shadowcolor.rgba),   
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
-    {"box",         "set box",              OFFSET(draw_box),           
AV_OPT_TYPE_BOOL,   {.i64=0},     0,        1       , FLAGS},
-    {"boxborderw",  "set box border width", OFFSET(boxborderw),         
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN,  INT_MAX , FLAGS},
-    {"line_spacing",  "set line spacing in pixels", OFFSET(line_spacing),   
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN,  INT_MAX,FLAGS},
-    {"fontsize",    "set font size",        OFFSET(fontsize_expr),      
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0 , FLAGS},
-    {"x",           "set x expression",     OFFSET(x_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
-    {"y",           "set y expression",     OFFSET(y_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
-    {"shadowx",     "set shadow x offset",  OFFSET(shadowx),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN,  INT_MAX , FLAGS},
-    {"shadowy",     "set shadow y offset",  OFFSET(shadowy),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN,  INT_MAX , FLAGS},
-    {"borderw",     "set border width",     OFFSET(borderw),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN,  INT_MAX , FLAGS},
-    {"tabsize",     "set tab size",         OFFSET(tabsize),            
AV_OPT_TYPE_INT,    {.i64=4},     0,        INT_MAX , FLAGS},
-    {"basetime",    "set base time",        OFFSET(basetime),           
AV_OPT_TYPE_INT64,  {.i64=AV_NOPTS_VALUE}, INT64_MIN, INT64_MAX , FLAGS},
+    {"boxcolor",       "set box color",         OFFSET(boxcolor.rgba),      
AV_OPT_TYPE_COLOR,  {.str="white"}, 0, 0, FLAGS},
+    {"bordercolor",    "set border color",      OFFSET(bordercolor.rgba),   
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
+    {"shadowcolor",    "set shadow color",      OFFSET(shadowcolor.rgba),   
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
+    {"box",            "set box",               OFFSET(draw_box),           
AV_OPT_TYPE_BOOL,   {.i64=0},     0, 1, FLAGS},
+    {"boxborderw",     "set box borders width", OFFSET(boxborderw),         
AV_OPT_TYPE_INT,    {.i64=0},     0, INT_MAX, FLAGS},
+    {"line_spacing",   "set line spacing in pixels", OFFSET(line_spacing),  
AV_OPT_TYPE_INT,    {.i64=-1},    INT_MIN, INT_MAX, FLAGS},
+    {"fontsize",       "set font size",         OFFSET(fontsize_expr),      
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
+    {"x",              "set x expression",      OFFSET(x_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
+    {"y",              "set y expression",      OFFSET(y_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
+    {"shadowx",        "set shadow x offset",   OFFSET(shadowx),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, FLAGS},
+    {"shadowy",        "set shadow y offset",   OFFSET(shadowy),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, FLAGS},
+    {"borderw",        "set border width",      OFFSET(borderw),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, FLAGS},
+    {"tabsize",        "set tab size",          OFFSET(tabsize),            
AV_OPT_TYPE_INT,    {.i64=4},     0, INT_MAX, FLAGS},
+    {"basetime",       "set base time",         OFFSET(basetime),           
AV_OPT_TYPE_INT64,  {.i64=AV_NOPTS_VALUE}, INT64_MIN, INT64_MAX, FLAGS},
 #if CONFIG_LIBFONTCONFIG
     { "font",        "Font name",            OFFSET(font),               
AV_OPT_TYPE_STRING, { .str = "Sans" },           .flags = FLAGS },
 #endif
@@ -248,15 +338,15 @@ static const AVOption drawtext_options[]= {
         {"strftime", "set strftime expansion (deprecated)", OFFSET(exp_mode), 
AV_OPT_TYPE_CONST, {.i64=EXP_STRFTIME}, 0, 0, FLAGS, "expansion"},
 
     {"timecode",        "set initial timecode",             
OFFSET(tc_opt_string), AV_OPT_TYPE_STRING,   {.str=NULL}, 0, 0, FLAGS},
-    {"tc24hmax",        "set 24 hours max (timecode only)", OFFSET(tc24hmax),  
    AV_OPT_TYPE_BOOL,     {.i64=0},           0,        1, FLAGS},
-    {"timecode_rate",   "set rate (timecode only)",         OFFSET(tc_rate),   
    AV_OPT_TYPE_RATIONAL, {.dbl=0},           0,  INT_MAX, FLAGS},
-    {"r",               "set rate (timecode only)",         OFFSET(tc_rate),   
    AV_OPT_TYPE_RATIONAL, {.dbl=0},           0,  INT_MAX, FLAGS},
-    {"rate",            "set rate (timecode only)",         OFFSET(tc_rate),   
    AV_OPT_TYPE_RATIONAL, {.dbl=0},           0,  INT_MAX, FLAGS},
-    {"reload",     "reload text file at specified frame interval", 
OFFSET(reload),     AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS},
-    { "alpha",       "apply alpha while rendering", OFFSET(a_expr),      
AV_OPT_TYPE_STRING, { .str = "1"     },          .flags = FLAGS },
-    {"fix_bounds", "check and fix text coords to avoid clipping", 
OFFSET(fix_bounds), AV_OPT_TYPE_BOOL, {.i64=0}, 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},
-    {"text_source", "the source of text", OFFSET(text_source_string), 
AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS },
+    {"tc24hmax",        "set 24 hours max (timecode only)", OFFSET(tc24hmax),  
    AV_OPT_TYPE_BOOL,     {.i64=0},    0, 1, FLAGS},
+    {"timecode_rate",   "set rate (timecode only)",         OFFSET(tc_rate),   
    AV_OPT_TYPE_RATIONAL, {.dbl=0},    0, INT_MAX, FLAGS},
+    {"r",               "set rate (timecode only)",         OFFSET(tc_rate),   
    AV_OPT_TYPE_RATIONAL, {.dbl=0},    0, INT_MAX, FLAGS},
+    {"rate",            "set rate (timecode only)",         OFFSET(tc_rate),   
    AV_OPT_TYPE_RATIONAL, {.dbl=0},    0, INT_MAX, FLAGS},
+    {"reload",          "reload text file at specified frame interval", 
OFFSET(reload), AV_OPT_TYPE_INT, {.i64=0},    0, INT_MAX, FLAGS},
+    {"alpha",           "apply alpha while rendering",      OFFSET(a_expr),    
    AV_OPT_TYPE_STRING,   {.str = "1"}, .flags = FLAGS},
+    {"fix_bounds",      "check and fix text coords to avoid clipping", 
OFFSET(fix_bounds), AV_OPT_TYPE_BOOL, {.i64=0}, 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},
+    {"text_source",     "the source of text", OFFSET(text_source_string), 
AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS },
 
 #if CONFIG_LIBFRIBIDI
     {"text_shaping", "attempt to shape text before drawing", 
OFFSET(text_shaping), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1, FLAGS},
@@ -297,18 +387,24 @@ static const struct ft_error {
 
 #define FT_ERRMSG(e) ft_errors[e].err_msg
 
-typedef struct Glyph {
-    FT_Glyph glyph;
-    FT_Glyph border_glyph;
-    uint32_t code;
-    unsigned int fontsize;
-    FT_Bitmap bitmap; ///< array holding bitmaps of font
-    FT_Bitmap border_bitmap; ///< array holding bitmaps of font border
-    FT_BBox bbox;
-    int advance;
-    int bitmap_left;
-    int bitmap_top;
-} Glyph;
+
+// 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);
+
+// Shapes a line of text using libharfbuzz
+static void shape_text_hb(DrawTextContext *s, HarfbuzzData* hb, const char* 
text, int textLen);
+
+// Performs text measurements
+static int measure_text(AVFilterContext *ctx, TextMetrics *metrics);
+
+// Draws glyphs on the frame
+static int draw_glyphs(DrawTextContext *s, AVFrame *frame,
+                       FFDrawColor *color, TextMetrics *metrics,
+                       int x, int y, int borderw);
+
+// Draws text on the frame
+static int draw_text(AVFilterContext *ctx, AVFrame *frame);
 
 static int glyph_cmp(const void *key, const void *b)
 {
@@ -316,80 +412,9 @@ static int glyph_cmp(const void *key, const void *b)
     int64_t diff = (int64_t)a->code - (int64_t)bb->code;
 
     if (diff != 0)
-         return diff > 0 ? 1 : -1;
+        return diff > 0 ? 1 : -1;
     else
-         return FFDIFFSIGN((int64_t)a->fontsize, (int64_t)bb->fontsize);
-}
-
-/**
- * Load glyphs corresponding to the UTF-32 codepoint code.
- */
-static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code)
-{
-    DrawTextContext *s = ctx->priv;
-    FT_BitmapGlyph bitmapglyph;
-    Glyph *glyph;
-    struct AVTreeNode *node = NULL;
-    int ret;
-
-    /* load glyph into s->face->glyph */
-    if (FT_Load_Char(s->face, code, s->ft_load_flags))
-        return AVERROR(EINVAL);
-
-    glyph = av_mallocz(sizeof(*glyph));
-    if (!glyph) {
-        ret = AVERROR(ENOMEM);
-        goto error;
-    }
-    glyph->code  = code;
-    glyph->fontsize = s->fontsize;
-
-    if (FT_Get_Glyph(s->face->glyph, &glyph->glyph)) {
-        ret = AVERROR(EINVAL);
-        goto error;
-    }
-    if (s->borderw) {
-        glyph->border_glyph = glyph->glyph;
-        if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0) ||
-            FT_Glyph_To_Bitmap(&glyph->border_glyph, FT_RENDER_MODE_NORMAL, 0, 
1)) {
-            ret = AVERROR_EXTERNAL;
-            goto error;
-        }
-        bitmapglyph = (FT_BitmapGlyph) glyph->border_glyph;
-        glyph->border_bitmap = bitmapglyph->bitmap;
-    }
-    if (FT_Glyph_To_Bitmap(&glyph->glyph, FT_RENDER_MODE_NORMAL, 0, 1)) {
-        ret = AVERROR_EXTERNAL;
-        goto error;
-    }
-    bitmapglyph = (FT_BitmapGlyph) glyph->glyph;
-
-    glyph->bitmap      = bitmapglyph->bitmap;
-    glyph->bitmap_left = bitmapglyph->left;
-    glyph->bitmap_top  = bitmapglyph->top;
-    glyph->advance     = s->face->glyph->advance.x >> 6;
-
-    /* measure text height to calculate text_height (or the maximum text 
height) */
-    FT_Glyph_Get_CBox(glyph->glyph, ft_glyph_bbox_pixels, &glyph->bbox);
-
-    /* cache the newly created glyph */
-    if (!(node = av_tree_node_alloc())) {
-        ret = AVERROR(ENOMEM);
-        goto error;
-    }
-    av_tree_insert(&s->glyphs, glyph, glyph_cmp, &node);
-
-    if (glyph_ptr)
-        *glyph_ptr = glyph;
-    return 0;
-
-error:
-    if (glyph)
-        av_freep(&glyph->glyph);
-
-    av_freep(&glyph);
-    av_freep(&node);
-    return ret;
+        return FFDIFFSIGN((int64_t)a->fontsize, (int64_t)bb->fontsize);
 }
 
 static av_cold int set_fontsize(AVFilterContext *ctx, unsigned int fontsize)
@@ -439,7 +464,6 @@ static av_cold int update_fontsize(AVFilterContext *ctx)
            return err;
 
         size = av_expr_eval(s->fontsize_pexpr, s->var_values, &s->prng);
-
         if (!isnan(size)) {
             roundedsize = round(size);
             // test for overflow before cast
@@ -447,7 +471,6 @@ static av_cold int update_fontsize(AVFilterContext *ctx)
                 av_log(ctx, AV_LOG_ERROR, "fontsize overflow\n");
                 return AVERROR(EINVAL);
             }
-
             fontsize = roundedsize;
         }
     }
@@ -548,7 +571,7 @@ static int load_font_fontconfig(AVFilterContext *ctx)
         goto fail;
     }
 
-    av_log(ctx, AV_LOG_INFO, "Using \"%s\"\n", filename);
+    av_log(ctx, AV_LOG_VERBOSE, "Using \"%s\"\n", filename);
     if (parse_err)
         s->default_fontsize = size + 0.5;
 
@@ -690,6 +713,7 @@ static int shape_text(AVFilterContext *ctx)
     s->text = tmp;
     len = fribidi_unicode_to_charset(FRIBIDI_CHAR_SET_UTF8,
                                      unicodestr, len, s->text);
+
     ret = 0;
 
 out:
@@ -715,7 +739,6 @@ static av_cold int init(AVFilterContext *ctx)
 {
     int err;
     DrawTextContext *s = ctx->priv;
-    Glyph *glyph;
 
     av_expr_free(s->fontsize_pexpr);
     s->fontsize_pexpr = NULL;
@@ -804,17 +827,8 @@ static av_cold int init(AVFilterContext *ctx)
                        FT_STROKER_LINEJOIN_ROUND, 0);
     }
 
-    s->use_kerning = FT_HAS_KERNING(s->face);
-
     /* load the fallback glyph with code 0 */
-    load_glyph(ctx, NULL, 0);
-
-    /* set the tabsize in pixels */
-    if ((err = load_glyph(ctx, &glyph, ' ')) < 0) {
-        av_log(ctx, AV_LOG_ERROR, "Could not set tabsize.\n");
-        return err;
-    }
-    s->tabsize *= glyph->advance;
+    load_glyph(ctx, NULL, 0, 0, 0);
 
     if (s->exp_mode == EXP_STRFTIME &&
         (strchr(s->text, '%') || strchr(s->text, '\\')))
@@ -837,6 +851,14 @@ static int glyph_enu_free(void *opaque, void *elem)
 
     FT_Done_Glyph(glyph->glyph);
     FT_Done_Glyph(glyph->border_glyph);
+    for (int t = 0; t < 16; ++t) {
+        if (glyph->bglyph[t] != NULL) {
+            FT_Done_Glyph((FT_Glyph)glyph->bglyph[t]);
+        }
+        if (glyph->border_bglyph[t] != NULL) {
+            FT_Done_Glyph((FT_Glyph)glyph->border_bglyph[t]);
+        }
+    }
     av_free(elem);
     return 0;
 }
@@ -852,9 +874,6 @@ static av_cold void uninit(AVFilterContext *ctx)
 
     s->x_pexpr = s->y_pexpr = s->a_pexpr = s->fontsize_pexpr = NULL;
 
-    av_freep(&s->positions);
-    s->nb_positions = 0;
-
     av_tree_enumerate(s->glyphs, NULL, NULL, glyph_enu_free);
     av_tree_destroy(s->glyphs);
     s->glyphs = NULL;
@@ -880,15 +899,15 @@ static int config_input(AVFilterLink *inlink)
     ff_draw_color(&s->dc, &s->bordercolor, s->bordercolor.rgba);
     ff_draw_color(&s->dc, &s->boxcolor,    s->boxcolor.rgba);
 
-    s->var_values[VAR_w]     = s->var_values[VAR_W]     = 
s->var_values[VAR_MAIN_W] = inlink->w;
-    s->var_values[VAR_h]     = s->var_values[VAR_H]     = 
s->var_values[VAR_MAIN_H] = inlink->h;
-    s->var_values[VAR_SAR]   = inlink->sample_aspect_ratio.num ? 
av_q2d(inlink->sample_aspect_ratio) : 1;
-    s->var_values[VAR_DAR]   = (double)inlink->w / inlink->h * 
s->var_values[VAR_SAR];
-    s->var_values[VAR_HSUB]  = 1 << s->dc.hsub_max;
-    s->var_values[VAR_VSUB]  = 1 << s->dc.vsub_max;
-    s->var_values[VAR_X]     = NAN;
-    s->var_values[VAR_Y]     = NAN;
-    s->var_values[VAR_T]     = NAN;
+    s->var_values[VAR_w]    = s->var_values[VAR_W] = s->var_values[VAR_MAIN_W] 
= inlink->w;
+    s->var_values[VAR_h]    = s->var_values[VAR_H] = s->var_values[VAR_MAIN_H] 
= inlink->h;
+    s->var_values[VAR_SAR]  = inlink->sample_aspect_ratio.num ? 
av_q2d(inlink->sample_aspect_ratio) : 1;
+    s->var_values[VAR_DAR]  = (double)inlink->w / inlink->h * 
s->var_values[VAR_SAR];
+    s->var_values[VAR_HSUB] = 1 << s->dc.hsub_max;
+    s->var_values[VAR_VSUB] = 1 << s->dc.vsub_max;
+    s->var_values[VAR_X]    = NAN;
+    s->var_values[VAR_Y]    = NAN;
+    s->var_values[VAR_T]    = NAN;
 
     av_lfg_init(&s->prng, av_get_random_seed());
 
@@ -948,8 +967,7 @@ static int command(AVFilterContext *ctx, const char *cmd, 
const char *arg, char
 
         ctx->priv = new;
         return config_input(ctx->inputs[0]);
-    } else
-        return AVERROR(ENOSYS);
+    }
 
 fail:
     av_log(ctx, AV_LOG_ERROR, "Failed to process command. Continuing with 
existing parameters.\n");
@@ -1318,91 +1336,369 @@ static int expand_text(AVFilterContext *ctx, char 
*text, AVBPrint *bp)
     return 0;
 }
 
-static int draw_glyphs(DrawTextContext *s, AVFrame *frame,
-                       int width, int height,
-                       FFDrawColor *color,
-                       int x, int y, int borderw)
+static void update_color_with_alpha(DrawTextContext *s, FFDrawColor *color, 
const FFDrawColor incolor)
 {
-    char *text = s->expanded_text.str;
-    uint32_t code = 0;
-    int i, x1, y1;
-    uint8_t *p;
-    Glyph *glyph = NULL;
+    *color = incolor;
+    color->rgba[3] = (color->rgba[3] * s->alpha) / 255;
+    ff_draw_color(&s->dc, color, color->rgba);
+}
 
-    for (i = 0, p = text; *p; i++) {
-        FT_Bitmap bitmap;
-        Glyph dummy = { 0 };
-        GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto 
continue_on_invalid;);
-continue_on_invalid:
+static void update_alpha(DrawTextContext *s)
+{
+    double alpha = av_expr_eval(s->a_pexpr, s->var_values, &s->prng);
 
-        /* skip new line chars, just go to new line */
-        if (code == '\n' || code == '\r' || code == '\t')
-            continue;
+    if (isnan(alpha))
+        return;
 
-        dummy.code = code;
-        dummy.fontsize = s->fontsize;
-        glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
+    if (alpha >= 1.0)
+        s->alpha = 255;
+    else if (alpha <= 0)
+        s->alpha = 0;
+    else
+        s->alpha = 256 * alpha;
+}
+
+static inline int get_subpixel_idx(int shift_x64, int shift_y64)
+{
+    int idx = (shift_x64 >> 2) + (shift_y64 >> 4);
+    return idx;
+}
 
-        bitmap = borderw ? glyph->border_bitmap : glyph->bitmap;
+static int load_glyph(AVFilterContext *ctx, Glyph **glyph_ptr, uint32_t code, 
int8_t shift_x64, int8_t shift_y64)
+{
+    DrawTextContext *s = ctx->priv;
+    Glyph dummy = { 0 };
+    Glyph *glyph;
+    FT_Vector shift;
+    struct AVTreeNode *node = NULL;
+    int ret = 0;
 
-        if (glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO &&
-            glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
+    /* get glyph */
+    dummy.code = code;
+    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)) {
             return AVERROR(EINVAL);
+        }
+        glyph = av_mallocz(sizeof(*glyph));
+        if (!glyph) {
+            ret = AVERROR(ENOMEM);
+            goto error;
+        }
+        glyph->code  = code;
+        glyph->fontsize = s->fontsize;
+        if (FT_Get_Glyph(s->face->glyph, &glyph->glyph)) {
+            ret = AVERROR(EINVAL);
+            goto error;
+        }
+        if (s->borderw) {
+            glyph->border_glyph = glyph->glyph;
+            if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0)) 
{
+                ret = AVERROR_EXTERNAL;
+                goto error;
+            }
+        }
+        /* measure text height to calculate text_height (or the maximum text 
height) */
+        FT_Glyph_Get_CBox(glyph->glyph, FT_GLYPH_BBOX_SUBPIXELS, &glyph->bbox);
+
+        /* cache the newly created glyph */
+        if (!(node = av_tree_node_alloc())) {
+            ret = AVERROR(ENOMEM);
+            goto error;
+        }
+        av_tree_insert(&s->glyphs, glyph, glyph_cmp, &node);
+    } else {
+        if (s->borderw && !glyph->border_glyph) {
+            glyph->border_glyph = glyph->glyph;
+            if (FT_Glyph_StrokeBorder(&glyph->border_glyph, s->stroker, 0, 0)) 
{
+                ret = AVERROR_EXTERNAL;
+                goto error;
+            }
+        }
+    }
+
+    // Check if a bitmap is needed
+    if (shift_x64 >= 0 && shift_y64 >= 0) {
+        // Get the bitmap subpixel index (0 -> 15)
+        int idx = get_subpixel_idx(shift_x64, shift_y64);
+        shift.x = shift_x64;
+        shift.y = shift_y64;
+
+        if (!glyph->bglyph[idx]) {
+            FT_Glyph tmp_glyph = glyph->glyph;
+            if (FT_Glyph_To_Bitmap(&tmp_glyph, FT_RENDER_MODE_NORMAL, &shift, 
0)) {
+                ret = AVERROR_EXTERNAL;
+                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 (s->borderw && !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;
+                goto error;
+            }
+            glyph->border_bglyph[idx] = (FT_BitmapGlyph)tmp_glyph;
+        }
+    }
+    if (glyph_ptr) {
+        *glyph_ptr = glyph;
+    }
+    return 0;
 
-        x1 = s->positions[i].x+s->x+x - borderw;
-        y1 = s->positions[i].y+s->y+y - borderw;
+error:
+    if (glyph && glyph->glyph)
+        FT_Done_Glyph(glyph->glyph);
 
-        ff_blend_mask(&s->dc, color,
-                      frame->data, frame->linesize, width, height,
-                      bitmap.buffer, bitmap.pitch,
-                      bitmap.width, bitmap.rows,
-                      bitmap.pixel_mode == FT_PIXEL_MODE_MONO ? 0 : 3,
-                      0, x1, y1);
+    av_freep(&glyph);
+    av_freep(&node);
+    return ret;
+}
+
+static int draw_glyphs(DrawTextContext *s, AVFrame *frame,
+                       FFDrawColor *color,
+                       TextMetrics *metrics,
+                       int x, int y, int borderw)
+{
+    int g, l, x1, y1, w1, h1, idx;
+    int dx = 0, dy = 0, pdx = 0;
+    GlyphInfo *info;
+    Glyph dummy = { 0 }, *glyph;
+    FT_Bitmap bitmap;
+    FT_BitmapGlyph b_glyph;
+    int clip_x = 0, clip_y = 0;
+
+    clip_x = FFMIN(metrics->rect_x + s->box_width + s->bb_right, frame->width);
+    clip_y = FFMIN(metrics->rect_y + s->box_height + s->bb_bottom, 
frame->height);
+
+    for (l = 0; l < s->line_count; ++l) {
+        TextLine *line = &s->lines[l];
+        for (g = 0; g < line->hb_data.glyph_count; ++g) {
+            info = &line->glyphs[g];
+            dummy.fontsize = s->fontsize;
+            dummy.code = info->code;
+            glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
+            if (!glyph) {
+                return AVERROR(EINVAL);
+            }
+
+            idx = get_subpixel_idx(info->shift_x64, info->shift_y64);
+            b_glyph = borderw ? glyph->border_bglyph[idx] : glyph->bglyph[idx];
+            bitmap = b_glyph->bitmap;
+            x1 = x + info->x + b_glyph->left;
+            y1 = y + info->y - b_glyph->top;
+            w1 = bitmap.width;
+            h1 = bitmap.rows;
+
+            // Offset of the glyph's bitmap in the visible region
+            dx = dy = 0;
+            if (x1 < metrics->rect_x - s->bb_left) {
+                dx = metrics->rect_x - s->bb_left - x1;
+                x1 = metrics->rect_x - s->bb_left;
+            }
+            if (y1 < metrics->rect_y - s->bb_top) {
+                dy = metrics->rect_y - s->bb_top - y1;
+                y1 = metrics->rect_y - s->bb_top;
+            }
+
+            // check if the glyph is empty or out of the clipping region
+            if (dx >= w1 || dy >= h1 || x1 >= clip_x || y1 >= clip_y) {
+                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);
+        }
     }
 
     return 0;
 }
 
+static void shape_text_hb(DrawTextContext *s, HarfbuzzData* hb, const char* 
text, int textLen) 
+{
+    hb->buf = hb_buffer_create();
+    hb_buffer_set_direction(hb->buf, HB_DIRECTION_LTR);
+    hb_buffer_set_script(hb->buf, HB_SCRIPT_LATIN);
+    hb_buffer_set_language(hb->buf, hb_language_from_string("en", -1));
+    hb_buffer_guess_segment_properties(hb->buf);
+    hb->font = hb_ft_font_create(s->face, NULL);
+    hb_ft_font_set_funcs(hb->font);
+    hb_buffer_add_utf8(hb->buf, text, textLen, 0, -1);
+    hb_shape(hb->font, hb->buf, NULL, 0);
+    hb->glyph_info = hb_buffer_get_glyph_infos(hb->buf, &hb->glyph_count);
+    hb->glyph_pos = hb_buffer_get_glyph_positions(hb->buf, &hb->glyph_count);
+}
 
-static void update_color_with_alpha(DrawTextContext *s, FFDrawColor *color, 
const FFDrawColor incolor)
+static void hb_destroy(HarfbuzzData *hb) 
 {
-    *color = incolor;
-    color->rgba[3] = (color->rgba[3] * s->alpha) / 255;
-    ff_draw_color(&s->dc, color, color->rgba);
+    hb_buffer_destroy(hb->buf);
+    hb_font_destroy(hb->font);
+    hb->buf = NULL;
+    hb->font = NULL;
+    hb->glyph_info = NULL;
+    hb->glyph_pos = NULL;
 }
 
-static void update_alpha(DrawTextContext *s)
+static int measure_text(AVFilterContext *ctx, TextMetrics *metrics)
 {
-    double alpha = av_expr_eval(s->a_pexpr, s->var_values, &s->prng);
+    DrawTextContext *s = ctx->priv;
+    char* text = s->expanded_text.str;
+    char *textdup = av_strdup(text), *start = textdup;
+    int num_chars = 0;
+    int width64 = 0, w64 = 0;
+    int cur_min_y64 = 0, first_max_y64 = -32000;
+    int first_min_x64 = 32000, last_max_x64 = -32000;
+    int min_y64 = 32000, max_y64 = -32000, min_x64 = 32000, max_x64 = -32000;
+    int line_count = 0;
+    uint32_t code = 0;
+    Glyph *glyph = NULL;
 
-    if (isnan(alpha))
-        return;
+    int i, tab_idx = 0, last_tab_idx = 0, line_offset = 0;
+    char* p;
+    int ret = 0;
+
+    // Count the lines and the tab characters
+    s->tab_count = 0;
+    for (i = 0, p = text; 1; i++) {
+        GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed;);
+continue_on_failed:
+        if (is_newline(code) || code == 0) {
+            ++line_count;
+            if (code == 0) {
+                break;
+            }
+        } else if (code == '\t') {
+            ++s->tab_count;
+        }
+    }
 
-    if (alpha >= 1.0)
-        s->alpha = 255;
-    else if (alpha <= 0)
-        s->alpha = 0;
-    else
-        s->alpha = 256 * alpha;
+    // Evaluate the width of the space character if needed to replace tabs
+    if (s->tab_count > 0 && !s->blank_advance64) {
+        HarfbuzzData hb_data;
+        shape_text_hb(s, &hb_data, " ", 1);
+        s->blank_advance64 = hb_data.glyph_pos[0].x_advance;
+        hb_destroy(&hb_data);
+    }
+
+    s->line_count = line_count;
+    s->lines = av_mallocz(line_count * sizeof(TextLine));
+    s->tab_clusters = av_mallocz(s->tab_count * sizeof(uint32_t));
+    for (i = 0; i < s->tab_count; ++i) {
+        s->tab_clusters[i] = -1;
+    }
+
+    line_count = 0;
+    for (i = 0, p = textdup; 1; i++) {
+        if (*p == '\t') {
+            s->tab_clusters[tab_idx++] = i;
+            *p = ' ';
+        }
+        GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto 
continue_on_failed2;);
+continue_on_failed2:
+        if (is_newline(code) || code == 0) {
+            TextLine *cur_line = &s->lines[line_count];
+            HarfbuzzData *hb = &cur_line->hb_data;
+            cur_line->cluster_offset = line_offset;
+            shape_text_hb(s, hb, start, num_chars);
+            w64 = 0;
+            cur_min_y64 = 32000;
+            for (int t = 0; t < hb->glyph_count; ++t) {
+                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);
+                if (ret != 0) {
+                    break;
+                }
+                if (line_count == 0) {
+                    first_max_y64 = FFMAX(glyph->bbox.yMax, first_max_y64);
+                }
+                if (t == 0) {
+                    cur_line->offset_left64 = glyph->bbox.xMin;
+                    first_min_x64 = FFMIN(glyph->bbox.xMin, first_min_x64);
+                }
+                if (t == hb->glyph_count - 1) {
+                    w64 += glyph->bbox.xMax;
+                    last_max_x64 = FFMAX(glyph->bbox.xMax, last_max_x64);
+                    cur_line->offset_right64 = glyph->bbox.xMax;
+                } else {
+                    if (is_tab) {
+                        int size = s->blank_advance64 * s->tabsize;
+                        w64 = (w64 / size + 1) * size;
+                    } else {
+                        w64 += hb->glyph_pos[t].x_advance;
+                    }
+                }
+                cur_min_y64 = FFMIN(glyph->bbox.yMin, cur_min_y64);
+                min_y64 = FFMIN(glyph->bbox.yMin, min_y64);
+                max_y64 = FFMAX(glyph->bbox.yMax, max_y64);
+                min_x64 = FFMIN(glyph->bbox.xMin, min_x64);
+                max_x64 = FFMAX(glyph->bbox.xMax, max_x64);
+            }
+
+            if (ret == 0) {
+                cur_line->width64 = w64;
+
+                av_log(s, AV_LOG_DEBUG, "  Line: %d -- glyphs count: %d - 
width64: %d - offset_left64: %d - offset_right64: %d)\n",
+                    line_count, hb->glyph_count, cur_line->width64, 
cur_line->offset_left64, cur_line->offset_right64);
+
+                if (w64 > width64) {
+                    width64 = w64;
+                }
+                num_chars = -1;
+                start = p;
+                ++line_count;
+                line_offset = i + 1;
+            }
+        }
+
+        if (code == 0 || ret != 0) break;
+        ++num_chars;
+    }
+
+    if (ret == 0) {
+        int height64;
+        metrics->line_height64 = s->face->size->metrics.height;
+        
+        metrics->width = POS_CEIL(width64, 64);
+        height64 = (metrics->line_height64 + s->line_spacing * 64) *
+            (FFMAX(0, line_count - 1)) + first_max_y64 - cur_min_y64;
+        metrics->height = POS_CEIL(height64, 64);
+
+        metrics->offset_top64 = first_max_y64;
+        metrics->offset_right64 = last_max_x64;
+        metrics->offset_bottom64 = cur_min_y64;
+        metrics->offset_left64 = first_min_x64;
+        metrics->min_x64 = min_x64;
+        metrics->min_y64 = min_y64;
+        metrics->max_x64 = max_x64;
+        metrics->max_y64 = max_y64;
+    }
+
+    av_free(textdup);
+    return ret;
 }
 
-static int draw_text(AVFilterContext *ctx, AVFrame *frame,
-                     int width, int height)
+static int draw_text(AVFilterContext *ctx, AVFrame *frame)
 {
     DrawTextContext *s = ctx->priv;
     AVFilterLink *inlink = ctx->inputs[0];
-
-    uint32_t code = 0, prev_code = 0;
-    int x = 0, y = 0, i = 0, ret;
-    int max_text_line_w = 0, len;
-    int box_w, box_h;
-    char *text;
-    uint8_t *p;
-    int y_min = 32000, y_max = -32000;
-    int x_min = 32000, x_max = -32000;
-    FT_Vector delta;
-    Glyph *glyph = NULL, *prev_glyph = NULL;
-    Glyph dummy = { 0 };
+    int x = 0, y = 0, ret;
+    int shift_x64, shift_y64;
+    int x64, y64;
+    Glyph *glyph = NULL;
 
     time_t now = time(0);
     struct tm ltime;
@@ -1413,9 +1709,17 @@ static int draw_text(AVFilterContext *ctx, AVFrame 
*frame,
     FFDrawColor bordercolor;
     FFDrawColor boxcolor;
 
+    int width = frame->width;
+    int height = frame->height;
+    int rec_x = 0, rec_y = 0, rec_width = 0, rec_height = 0;
+    int is_outside = 0;
+    int last_tab_idx = 0;
+
+    TextMetrics metrics;
+
     av_bprint_clear(bp);
 
-    if(s->basetime != AV_NOPTS_VALUE)
+    if (s->basetime != AV_NOPTS_VALUE)
         now= frame->pts*av_q2d(ctx->inputs[0]->time_base) + 
s->basetime/1000000;
 
     switch (s->exp_mode) {
@@ -1441,13 +1745,6 @@ static int draw_text(AVFilterContext *ctx, AVFrame 
*frame,
 
     if (!av_bprint_is_complete(bp))
         return AVERROR(ENOMEM);
-    text = s->expanded_text.str;
-    if ((len = s->expanded_text.len) > s->nb_positions) {
-        if (!(s->positions =
-              av_realloc(s->positions, len*sizeof(*s->positions))))
-            return AVERROR(ENOMEM);
-        s->nb_positions = len;
-    }
 
     if (s->fontcolor_expr[0]) {
         /* If expression is set, evaluate and replace the static value */
@@ -1463,85 +1760,24 @@ static int draw_text(AVFilterContext *ctx, AVFrame 
*frame,
         ff_draw_color(&s->dc, &s->fontcolor, s->fontcolor.rgba);
     }
 
-    x = 0;
-    y = 0;
-
-    if ((ret = update_fontsize(ctx)) < 0)
+    if ((ret = update_fontsize(ctx)) < 0) {
         return ret;
-
-    /* load and cache glyphs */
-    for (i = 0, p = text; *p; i++) {
-        GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto 
continue_on_invalid;);
-continue_on_invalid:
-
-        /* get glyph */
-        dummy.code = code;
-        dummy.fontsize = s->fontsize;
-        glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
-        if (!glyph) {
-            ret = load_glyph(ctx, &glyph, code);
-            if (ret < 0)
-                return ret;
-        }
-
-        y_min = FFMIN(glyph->bbox.yMin, y_min);
-        y_max = FFMAX(glyph->bbox.yMax, y_max);
-        x_min = FFMIN(glyph->bbox.xMin, x_min);
-        x_max = FFMAX(glyph->bbox.xMax, x_max);
     }
-    s->max_glyph_h = y_max - y_min;
-    s->max_glyph_w = x_max - x_min;
-
-    /* compute and save position for each glyph */
-    glyph = NULL;
-    for (i = 0, p = text; *p; i++) {
-        GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto 
continue_on_invalid2;);
-continue_on_invalid2:
-
-        /* skip the \n in the sequence \r\n */
-        if (prev_code == '\r' && code == '\n')
-            continue;
-
-        prev_code = code;
-        if (is_newline(code)) {
-
-            max_text_line_w = FFMAX(max_text_line_w, x);
-            y += s->max_glyph_h + s->line_spacing;
-            x = 0;
-            continue;
-        }
-
-        /* get glyph */
-        prev_glyph = glyph;
-        dummy.code = code;
-        dummy.fontsize = s->fontsize;
-        glyph = av_tree_find(s->glyphs, &dummy, glyph_cmp, NULL);
-
-        /* kerning */
-        if (s->use_kerning && prev_glyph && glyph->code) {
-            FT_Get_Kerning(s->face, prev_glyph->code, glyph->code,
-                           ft_kerning_default, &delta);
-            x += delta.x >> 6;
-        }
 
-        /* save position */
-        s->positions[i].x = x + glyph->bitmap_left;
-        s->positions[i].y = y - glyph->bitmap_top + y_max;
-        if (code == '\t') x  = (x / s->tabsize + 1)*s->tabsize;
-        else              x += glyph->advance;
-    }
+    measure_text(ctx, &metrics);
 
-    max_text_line_w = FFMAX(x, max_text_line_w);
+    s->max_glyph_h = POS_CEIL(metrics.max_y64 - metrics.min_y64, 64);
+    s->max_glyph_w = POS_CEIL(metrics.max_x64 - metrics.min_x64, 64);
 
-    s->var_values[VAR_TW] = s->var_values[VAR_TEXT_W] = max_text_line_w;
-    s->var_values[VAR_TH] = s->var_values[VAR_TEXT_H] = y + s->max_glyph_h;
+    s->var_values[VAR_TW] = s->var_values[VAR_TEXT_W] = metrics.width;
+    s->var_values[VAR_TH] = s->var_values[VAR_TEXT_H] = metrics.height;
 
     s->var_values[VAR_MAX_GLYPH_W] = s->max_glyph_w;
     s->var_values[VAR_MAX_GLYPH_H] = s->max_glyph_h;
-    s->var_values[VAR_MAX_GLYPH_A] = s->var_values[VAR_ASCENT ] = y_max;
-    s->var_values[VAR_MAX_GLYPH_D] = s->var_values[VAR_DESCENT] = y_min;
+    s->var_values[VAR_MAX_GLYPH_A] = s->var_values[VAR_ASCENT] = 
POS_CEIL(metrics.max_y64, 64);
+    s->var_values[VAR_MAX_GLYPH_D] = s->var_values[VAR_DESCENT] = 
POS_CEIL(metrics.min_y64, 64);
 
-    s->var_values[VAR_LINE_H] = s->var_values[VAR_LH] = s->max_glyph_h;
+    s->var_values[VAR_LINE_H] = s->var_values[VAR_LH] = metrics.line_height64 
/ 64.;
 
     if (s->text_source == AV_FRAME_DATA_DETECTION_BBOXES) {
         s->var_values[VAR_X] = s->x;
@@ -1559,56 +1795,142 @@ continue_on_invalid2:
     update_color_with_alpha(s, &bordercolor, s->bordercolor);
     update_color_with_alpha(s, &boxcolor   , s->boxcolor   );
 
-    box_w = max_text_line_w;
-    box_h = y + s->max_glyph_h;
+    if (s->draw_box && s->boxborderw) {
+        s->bb_top = s->bb_right = s->bb_bottom = s->bb_left = s->boxborderw;
+    } else {
+        s->bb_top = s->bb_right = s->bb_bottom = s->bb_left = 0;
+    }
 
     if (s->fix_bounds) {
-
         /* calculate footprint of text effects */
-        int boxoffset     = s->draw_box ? FFMAX(s->boxborderw, 0) : 0;
         int borderoffset  = s->borderw  ? FFMAX(s->borderw, 0) : 0;
 
-        int offsetleft = FFMAX3(boxoffset, borderoffset,
+        int offsetleft = FFMAX3(FFMAX(s->bb_left, 0), borderoffset,
                                 (s->shadowx < 0 ? FFABS(s->shadowx) : 0));
-        int offsettop = FFMAX3(boxoffset, borderoffset,
+        int offsettop = FFMAX3(FFMAX(s->bb_top, 0), borderoffset,
                                 (s->shadowy < 0 ? FFABS(s->shadowy) : 0));
-
-        int offsetright = FFMAX3(boxoffset, borderoffset,
+        int offsetright = FFMAX3(FFMAX(s->bb_right, 0), borderoffset,
                                  (s->shadowx > 0 ? s->shadowx : 0));
-        int offsetbottom = FFMAX3(boxoffset, borderoffset,
+        int offsetbottom = FFMAX3(FFMAX(s->bb_bottom, 0), borderoffset,
                                   (s->shadowy > 0 ? s->shadowy : 0));
 
-
         if (s->x - offsetleft < 0) s->x = offsetleft;
         if (s->y - offsettop < 0)  s->y = offsettop;
 
-        if (s->x + box_w + offsetright > width)
-            s->x = FFMAX(width - box_w - offsetright, 0);
-        if (s->y + box_h + offsetbottom > height)
-            s->y = FFMAX(height - box_h - offsetbottom, 0);
+        if (s->x + metrics.width + offsetright > width)
+            s->x = FFMAX(width - metrics.width - offsetright, 0);
+        if (s->y + metrics.height + offsetbottom > height)
+            s->y = FFMAX(height - metrics.height - offsetbottom, 0);
     }
 
-    /* draw box */
-    if (s->draw_box)
-        ff_blend_rectangle(&s->dc, &boxcolor,
-                           frame->data, frame->linesize, width, height,
-                           s->x - s->boxborderw, s->y - s->boxborderw,
-                           box_w + s->boxborderw * 2, box_h + s->boxborderw * 
2);
+    x = 0;
+    y = 0;
+    x64 = (int)(s->x * 64.);
+    y64 = (int)(s->y * 64. + metrics.offset_top64);
+
+    for (int l = 0; l < s->line_count; ++l) {
+        TextLine *line = &s->lines[l];
+        HarfbuzzData *hb = &line->hb_data;
+        line->glyphs = av_mallocz(hb->glyph_count * sizeof(GlyphInfo));
+
+        for (int t = 0; t < hb->glyph_count; ++t) {
+            GlyphInfo *g_info = &line->glyphs[t];
+            uint8_t is_tab = last_tab_idx < s->tab_count &&
+                hb->glyph_info[t].cluster == s->tab_clusters[last_tab_idx] - 
line->cluster_offset;
+            int true_x, true_y;
+            if (is_tab) {
+                ++last_tab_idx;
+            }
+            true_x = x + hb->glyph_pos[t].x_offset;
+            true_y = y + hb->glyph_pos[t].y_offset;
+            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);
+            if (ret != 0) {
+                return ret;
+            }
+            g_info->code = hb->glyph_info[t].codepoint;
+            g_info->x = (x64 + true_x) >> 6;
+            g_info->y = ((y64 + true_y) >> 6) + (shift_y64 > 0 ? 1 : 0);
+            g_info->shift_x64 = shift_x64;
+            g_info->shift_y64 = shift_y64;
+
+            if (!is_tab) {
+                x += hb->glyph_pos[t].x_advance;
+            } else {
+                int size = s->blank_advance64 * s->tabsize;
+                x = (x / size + 1) * size;
+            }
+            y += hb->glyph_pos[t].y_advance;
+        }
 
-    if (s->shadowx || s->shadowy) {
-        if ((ret = draw_glyphs(s, frame, width, height,
-                               &shadowcolor, s->shadowx, s->shadowy, 0)) < 0)
-            return ret;
+        y += metrics.line_height64 + s->line_spacing * 64;
+        x = 0;
     }
 
-    if (s->borderw) {
-        if ((ret = draw_glyphs(s, frame, width, height,
-                               &bordercolor, 0, 0, s->borderw)) < 0)
+    metrics.rect_x = s->x;
+    metrics.rect_y = s->y;
+    
+    s->box_width = metrics.width;
+    s->box_height = metrics.height;
+
+    if (!s->draw_box) {
+        // Create a border for the clipping region to take into account 
subpixel
+        // errors in text measurement and effects.
+        int borderoffset = s->borderw ? FFMAX(s->borderw, 0) : 0;
+        s->bb_left = borderoffset + (s->shadowx < 0 ? FFABS(s->shadowx) : 0) + 
1;
+        s->bb_top = borderoffset + (s->shadowy < 0 ? FFABS(s->shadowy) : 0) + 
1;
+        s->bb_right = borderoffset + (s->shadowx > 0 ? s->shadowx : 0) + 1;
+        s->bb_bottom = borderoffset + (s->shadowy > 0 ? s->shadowy : 0) + 1;
+    }
+
+    /* Check if the whole box is out of the frame */        
+    is_outside = metrics.rect_x - s->bb_left >= width ||
+                    metrics.rect_y - s->bb_top >= height ||
+                    metrics.rect_x + s->box_width + s->bb_right <= 0 ||
+                    metrics.rect_y + s->box_height + s->bb_bottom <= 0;
+
+    if (!is_outside) {
+        /* draw box */
+        if (s->draw_box) {
+            rec_x = metrics.rect_x - s->bb_left;
+            rec_y = metrics.rect_y - s->bb_top;
+            rec_width = s->box_width + s->bb_right + s->bb_left;
+            rec_height = s->box_height + s->bb_bottom + s->bb_top;
+            ff_blend_rectangle(&s->dc, &boxcolor,
+                frame->data, frame->linesize, width, height,
+                rec_x, rec_y, rec_width, rec_height);
+        }
+
+        if (s->shadowx || s->shadowy) {
+            if ((ret = draw_glyphs(s, frame, &shadowcolor, &metrics,
+                    s->shadowx, s->shadowy, s->borderw)) < 0) {
+                return ret;
+            }
+        }
+
+        if (s->borderw) {
+            if ((ret = draw_glyphs(s, frame, &bordercolor, &metrics,
+                    0, 0, s->borderw)) < 0) {
+                return ret;
+            }
+        }
+
+        if ((ret = draw_glyphs(s, frame, &fontcolor, &metrics, 0,
+                0, 0)) < 0) {
             return ret;
+        }
     }
-    if ((ret = draw_glyphs(s, frame, width, height,
-                           &fontcolor, 0, 0, 0)) < 0)
-        return ret;
+
+    // FREE data structures
+    for (int l = 0; l < s->line_count; ++l) {
+        TextLine *line = &s->lines[l];
+        av_freep(&line->glyphs);
+        hb_destroy(&line->hb_data);
+    }
+    av_freep(&s->lines);
+    av_freep(&s->tab_clusters);
 
     return 0;
 }
@@ -1680,14 +2002,9 @@ FF_ENABLE_DEPRECATION_WARNINGS
             s->x = bbox->x;
             s->y = bbox->y - s->fontsize;
         }
-        draw_text(ctx, frame, frame->width, frame->height);
+        draw_text(ctx, frame);
     }
 
-    av_log(ctx, AV_LOG_DEBUG, "n:%d t:%f text_w:%d text_h:%d x:%d y:%d\n",
-           (int)s->var_values[VAR_N], s->var_values[VAR_T],
-           (int)s->var_values[VAR_TEXT_W], (int)s->var_values[VAR_TEXT_H],
-           s->x, s->y);
-
     return ff_filter_frame(outlink, frame);
 }
 
-- 
2.30.2

From c0c082c7ab95a722eba2d74adfbdc403f51eddc6 Mon Sep 17 00:00:00 2001
From: yethie <klimk...@tiscali.it>
Date: Fri, 3 Feb 2023 14:23:23 +0100
Subject: [PATCH 2/7] box borders can now be set independently

---
 doc/filters.texi          | 12 +++++++++++-
 libavfilter/vf_drawtext.c | 41 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 49 insertions(+), 4 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index 49d7218e84..0775b366d8 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -12051,7 +12051,17 @@ The default value of @var{box} is 0.
 
 @item boxborderw
 Set the width of the border to be drawn around the box using @var{boxcolor}.
-The default value of @var{boxborderw} is 0.
+The value must be specified using one of the following formats:
+@itemize @bullet
+@item @code{boxborderw=10} set the width of all the borders to 10
+@item @code{boxborderw=10|20} set the width of the top and bottom borders to 10
+    and the width of the left and right borders to 20
+@item @code{boxborderw=10|20|30} set the width of the top border to 10, the 
width
+    of the bottom border to 30 and the width of the left and right borders to 
20
+@item @code{boxborderw=10|20|30|40} set the borders width to 10 (top), 20 
(right),
+    30 (bottom), 40 (left)
+@end itemize
+The default value of @var{boxborderw} is "0".
 
 @item boxcolor
 The color to be used for drawing box around text. For the syntax of this
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index 7a0a255c5e..ce2814b0e0 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -27,6 +27,8 @@
  * - This filter now depends on libharfbuzz for text shaping.
  * - Glyphs position is now accurate to 1/4 pixel in both directions
  * - The default line height is now the one defined in the font
+ * - The boxborderw parameter can now contain a different value for each border
+ *   (e.g. boxborderw=top|right|bottom|left)
  */
 
 /**
@@ -256,7 +258,8 @@ typedef struct DrawTextContext {
 
     int line_spacing;               ///< lines spacing in pixels
     short int draw_box;             ///< draw box around text - true or false
-    int boxborderw;                 ///< box border width
+    char* boxborderw;               ///< box border width (padding)
+                                    ///  allowed formats: "all", "vert|oriz", 
"top|right|bottom|left"
     int bb_top;                     ///< the size of the top box border
     int bb_right;                   ///< the size of the right box border
     int bb_bottom;                  ///< the size of the bottom box border
@@ -318,7 +321,7 @@ static const AVOption drawtext_options[]= {
     {"bordercolor",    "set border color",      OFFSET(bordercolor.rgba),   
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
     {"shadowcolor",    "set shadow color",      OFFSET(shadowcolor.rgba),   
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
     {"box",            "set box",               OFFSET(draw_box),           
AV_OPT_TYPE_BOOL,   {.i64=0},     0, 1, FLAGS},
-    {"boxborderw",     "set box borders width", OFFSET(boxborderw),         
AV_OPT_TYPE_INT,    {.i64=0},     0, INT_MAX, FLAGS},
+    {"boxborderw",     "set box borders width", OFFSET(boxborderw),         
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
     {"line_spacing",   "set line spacing in pixels", OFFSET(line_spacing),  
AV_OPT_TYPE_INT,    {.i64=-1},    INT_MIN, INT_MAX, FLAGS},
     {"fontsize",       "set font size",         OFFSET(fontsize_expr),      
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
     {"x",              "set x expression",      OFFSET(x_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
@@ -735,6 +738,21 @@ static enum AVFrameSideDataType 
text_source_string_parse(const char *text_source
     }
 }
 
+// Convert a string formatted as "n1|n2|...|nN" into an integer array
+static int string_to_array(const char* source, int* result, int result_size)
+{
+    int counter = 0, size = strlen(source) + 1;
+    char *saveptr, *curval, *dup = av_malloc(size);
+    av_strlcpy(dup, source, size);
+    if (result_size > 0 && (curval = av_strtok(dup, "|", &saveptr))) {
+        do {
+            result[counter++] = atoi(curval);
+        } while ((curval = av_strtok(NULL, "|", &saveptr)) && counter < 
result_size);
+    }
+    av_free(dup);
+    return counter;
+}
+
 static av_cold int init(AVFilterContext *ctx)
 {
     int err;
@@ -1796,7 +1814,24 @@ static int draw_text(AVFilterContext *ctx, AVFrame 
*frame)
     update_color_with_alpha(s, &boxcolor   , s->boxcolor   );
 
     if (s->draw_box && s->boxborderw) {
-        s->bb_top = s->bb_right = s->bb_bottom = s->bb_left = s->boxborderw;
+        int bbsize[4];
+        int count;
+        count = string_to_array(s->boxborderw, bbsize, 4);
+        if (count == 1) {
+            s->bb_top = s->bb_right = s->bb_bottom = s->bb_left = bbsize[0];
+        } else if (count == 2) {
+            s->bb_top = s->bb_bottom = bbsize[0];
+            s->bb_right = s->bb_left = bbsize[1];
+        } else if (count == 3) {
+            s->bb_top = bbsize[0];
+            s->bb_right = s->bb_left = bbsize[1];
+            s->bb_bottom = bbsize[2];
+        } else if (count == 4) {
+            s->bb_top = bbsize[0];
+            s->bb_right = bbsize[1];
+            s->bb_bottom = bbsize[2];
+            s->bb_left = bbsize[3];
+        }
     } else {
         s->bb_top = s->bb_right = s->bb_bottom = s->bb_left = 0;
     }
-- 
2.30.2

From 78a18f854c2ab410ad87bf6aa568222358cb58b8 Mon Sep 17 00:00:00 2001
From: yethie <klimk...@tiscali.it>
Date: Fri, 3 Feb 2023 14:28:39 +0100
Subject: [PATCH 3/7] boxw and boxh options

---
 doc/filters.texi          |  8 ++++++++
 libavfilter/vf_drawtext.c | 11 +++++++++--
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index 0775b366d8..2a84fa9cc8 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -12049,6 +12049,14 @@ Used to draw a box around text using the background 
color.
 The value must be either 1 (enable) or 0 (disable).
 The default value of @var{box} is 0.
 
+@item boxw
+Set the width of the box to be drawn around text.
+The default value of @var{boxw} is computed automatically to match the text 
width
+
+@item boxh
+Set the height of the box to be drawn around text.
+The default value of @var{boxh} is computed automatically to match the text 
height
+
 @item boxborderw
 Set the width of the border to be drawn around the box using @var{boxcolor}.
 The value must be specified using one of the following formats:
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index ce2814b0e0..c67824c006 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -26,6 +26,8 @@
  *
  * - This filter now depends on libharfbuzz for text shaping.
  * - Glyphs position is now accurate to 1/4 pixel in both directions
+ * - The size of the background box can now be forced with the boxw
+ *   and boxh parameters
  * - The default line height is now the one defined in the font
  * - The boxborderw parameter can now contain a different value for each border
  *   (e.g. boxborderw=top|right|bottom|left)
@@ -301,6 +303,9 @@ typedef struct DrawTextContext {
 #endif
     AVDictionary *metadata;
 
+    int boxw;                       ///< the value of the boxw parameter
+    int boxh;                       ///< the value of the boxh parameter
+
     TextLine *lines;                ///< computed information about text lines
     int line_count;                 ///< the number of text lines
     uint32_t *tab_clusters;         ///< the position of tab characters in the 
text
@@ -326,6 +331,8 @@ static const AVOption drawtext_options[]= {
     {"fontsize",       "set font size",         OFFSET(fontsize_expr),      
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
     {"x",              "set x expression",      OFFSET(x_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
     {"y",              "set y expression",      OFFSET(y_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
+    {"boxw",           "set box width",         OFFSET(boxw),               
AV_OPT_TYPE_INT,    {.i64=0},     0, INT_MAX, FLAGS},
+    {"boxh",           "set box height",        OFFSET(boxh),               
AV_OPT_TYPE_INT,    {.i64=0},     0, INT_MAX, FLAGS},
     {"shadowx",        "set shadow x offset",   OFFSET(shadowx),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, FLAGS},
     {"shadowy",        "set shadow y offset",   OFFSET(shadowy),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, FLAGS},
     {"borderw",        "set border width",      OFFSET(borderw),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, FLAGS},
@@ -1907,8 +1914,8 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame)
     metrics.rect_x = s->x;
     metrics.rect_y = s->y;
     
-    s->box_width = metrics.width;
-    s->box_height = metrics.height;
+    s->box_width = s->boxw == 0 ? metrics.width : s->boxw;
+    s->box_height = s->boxh == 0 ? metrics.height : s->boxh;
 
     if (!s->draw_box) {
         // Create a border for the clipping region to take into account 
subpixel
-- 
2.30.2

From 025c1705634887b29817ce648cc6f496acdb5135 Mon Sep 17 00:00:00 2001
From: yethie <klimk...@tiscali.it>
Date: Fri, 3 Feb 2023 14:34:07 +0100
Subject: [PATCH 4/7] new variables added

---
 doc/filters.texi          | 12 ++++++++++++
 libavfilter/vf_drawtext.c | 14 ++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/doc/filters.texi b/doc/filters.texi
index 2a84fa9cc8..cb1a3ff983 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -12286,6 +12286,18 @@ contained in the rendered text, it is equivalent to 
@var{ascent} -
 maximum glyph width, that is the maximum width for all the glyphs
 contained in the rendered text
 
+@item font_a
+the ascent size defined in the font metrics
+
+@item font_d
+the descent size defined in the font metrics
+
+@item top_a
+the maximum ascender of the glyphs of the first text line
+
+@item bottom_d
+the maximum descender of the glyphs of the last text line
+
 @item n
 the number of input frame, starting from 0
 
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index c67824c006..25348ba7de 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -31,6 +31,8 @@
  * - The default line height is now the one defined in the font
  * - The boxborderw parameter can now contain a different value for each border
  *   (e.g. boxborderw=top|right|bottom|left)
+ * - The following new variables can be used in the x and y expressions:
+ *   font_a, font_d, top_a, bottom_d
  */
 
 /**
@@ -101,6 +103,10 @@ static const char *const var_names[] = {
     "max_glyph_d", "descent", ///< min glyph descender
     "max_glyph_h",            ///< max glyph height
     "max_glyph_w",            ///< max glyph width
+    "font_a",                 ///< font-defined ascent
+    "font_d",                 ///< font-defined descent
+    "top_a",                  ///< max glyph ascender of the top line
+    "bottom_d",               ///< max glyph descender of the bottom line
     "n",                      ///< number of frame
     "sar",
     "t",                      ///< timestamp expressed in seconds
@@ -144,6 +150,10 @@ enum var_name {
     VAR_MAX_GLYPH_D, VAR_DESCENT,
     VAR_MAX_GLYPH_H,
     VAR_MAX_GLYPH_W,
+    VAR_FONT_A,
+    VAR_FONT_D,
+    VAR_TOP_A,
+    VAR_BOTTOM_D,
     VAR_N,
     VAR_SAR,
     VAR_T,
@@ -1800,8 +1810,12 @@ static int draw_text(AVFilterContext *ctx, AVFrame 
*frame)
     s->var_values[VAR_MAX_GLYPH_W] = s->max_glyph_w;
     s->var_values[VAR_MAX_GLYPH_H] = s->max_glyph_h;
     s->var_values[VAR_MAX_GLYPH_A] = s->var_values[VAR_ASCENT] = 
POS_CEIL(metrics.max_y64, 64);
+    s->var_values[VAR_FONT_A] = s->face->size->metrics.ascender / 64;
     s->var_values[VAR_MAX_GLYPH_D] = s->var_values[VAR_DESCENT] = 
POS_CEIL(metrics.min_y64, 64);
+    s->var_values[VAR_FONT_D] = -s->face->size->metrics.descender / 64;
 
+    s->var_values[VAR_TOP_A] = POS_CEIL(metrics.offset_top64, 64);
+    s->var_values[VAR_BOTTOM_D] = POS_CEIL(metrics.offset_bottom64, 64);
     s->var_values[VAR_LINE_H] = s->var_values[VAR_LH] = metrics.line_height64 
/ 64.;
 
     if (s->text_source == AV_FRAME_DATA_DETECTION_BBOXES) {
-- 
2.30.2

From 41c008975860f7b7a9d607d1bf3784985d8974dd Mon Sep 17 00:00:00 2001
From: yethie <klimk...@tiscali.it>
Date: Fri, 3 Feb 2023 14:36:39 +0100
Subject: [PATCH 5/7] vertical and horizontal text alignment

---
 doc/filters.texi          |  5 +++++
 libavfilter/vf_drawtext.c | 47 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index cb1a3ff983..47e04a3ac3 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -12080,6 +12080,11 @@ The default value of @var{boxcolor} is "white".
 @item line_spacing
 Set the line spacing in pixels. The default value of @var{line_spacing} is 0.
 
+@item text_align
+Set the vertical and horizontal alignment of the text with respect to the box 
boundaries.
+The value must contain exactly two letters, one for the vertical alignment 
(T=top,
+M=middle, B=bottom) and one for the horizontal alignment (L=left, C=center, 
R=right).
+
 @item borderw
 Set the width of the border to be drawn around the text using 
@var{bordercolor}.
 The default value of @var{borderw} is 0.
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index 25348ba7de..0b01a78509 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -28,6 +28,8 @@
  * - Glyphs position is now accurate to 1/4 pixel in both directions
  * - The size of the background box can now be forced with the boxw
  *   and boxh parameters
+ * - Text can be aligned horizontally (top, middle, bottom) and vertically
+ *   (left, center, right) relative to the background box
  * - The default line height is now the one defined in the font
  * - The boxborderw parameter can now contain a different value for each border
  *   (e.g. boxborderw=top|right|bottom|left)
@@ -315,6 +317,7 @@ typedef struct DrawTextContext {
 
     int boxw;                       ///< the value of the boxw parameter
     int boxh;                       ///< the value of the boxh parameter
+    uint8_t *text_align;            ///< the horizontal and vertical text 
alignment
 
     TextLine *lines;                ///< computed information about text lines
     int line_count;                 ///< the number of text lines
@@ -339,6 +342,7 @@ static const AVOption drawtext_options[]= {
     {"boxborderw",     "set box borders width", OFFSET(boxborderw),         
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
     {"line_spacing",   "set line spacing in pixels", OFFSET(line_spacing),  
AV_OPT_TYPE_INT,    {.i64=-1},    INT_MIN, INT_MAX, FLAGS},
     {"fontsize",       "set font size",         OFFSET(fontsize_expr),      
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
+    {"text_align",     "set text alignment",    OFFSET(text_align),         
AV_OPT_TYPE_STRING, {.str="TL"},  0, 0, FLAGS},
     {"x",              "set x expression",      OFFSET(x_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
     {"y",              "set y expression",      OFFSET(y_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
     {"boxw",           "set box width",         OFFSET(boxw),               
AV_OPT_TYPE_INT,    {.i64=0},     0, INT_MAX, FLAGS},
@@ -770,6 +774,19 @@ static int string_to_array(const char* source, int* 
result, int result_size)
     return counter;
 }
 
+static int validate_text_align(char* text_align)
+{
+    int err = 0;
+    if (strlen(text_align) != 2
+        || strchr("LCRTMB", text_align[0]) == NULL || strchr("LCRTMB", 
text_align[1]) == NULL
+        || (strchr("TMB", text_align[0]) != NULL && strchr("LCR", 
text_align[1]) == NULL)
+        || (strchr("LCR", text_align[0]) != NULL && strchr("TMB", 
text_align[1]) == NULL)) {
+        err = AVERROR(EINVAL);
+    }
+
+    return err;
+}
+
 static av_cold int init(AVFilterContext *ctx)
 {
     int err;
@@ -835,6 +852,14 @@ static av_cold int init(AVFilterContext *ctx)
         return AVERROR(EINVAL);
     }
 
+    if ((err = validate_text_align(s->text_align))) {
+        av_log(ctx, AV_LOG_ERROR,
+               "The value provided for parameter 'text_align' is not 
valid,\n");
+        av_log(ctx, AV_LOG_ERROR,
+               "please specify a two characters string containing only one 
letter for horizontal alignment ('LCR') and one for vertical alignment 
('TMB')\n");
+        return err;
+    }
+
 #if CONFIG_LIBFRIBIDI
     if (s->text_shaping)
         if ((err = shape_text(ctx)) < 0)
@@ -1507,13 +1532,27 @@ static int draw_glyphs(DrawTextContext *s, AVFrame 
*frame,
     Glyph dummy = { 0 }, *glyph;
     FT_Bitmap bitmap;
     FT_BitmapGlyph b_glyph;
+    uint8_t j_center = 0, j_right = 0, j_middle = 0, j_bottom = 0;
+    int line_w, offset_y = 0;
     int clip_x = 0, clip_y = 0;
 
+    j_center = strstr(s->text_align, "C") > 0;
+    j_right = strstr(s->text_align, "R") > 0;
+    j_middle = strstr(s->text_align, "M") > 0;
+    j_bottom = strstr(s->text_align, "B") > 0;
+
+    if (j_middle) {
+        offset_y = (s->box_height - metrics->height) / 2;
+    } else if (j_bottom) {
+        offset_y = s->box_height - metrics->height;
+    }
+
     clip_x = FFMIN(metrics->rect_x + s->box_width + s->bb_right, frame->width);
     clip_y = FFMIN(metrics->rect_y + s->box_height + s->bb_bottom, 
frame->height);
 
     for (l = 0; l < s->line_count; ++l) {
         TextLine *line = &s->lines[l];
+        line_w = POS_CEIL(line->width64, 64);
         for (g = 0; g < line->hb_data.glyph_count; ++g) {
             info = &line->glyphs[g];
             dummy.fontsize = s->fontsize;
@@ -1527,10 +1566,16 @@ static int draw_glyphs(DrawTextContext *s, AVFrame 
*frame,
             b_glyph = borderw ? glyph->border_bglyph[idx] : glyph->bglyph[idx];
             bitmap = b_glyph->bitmap;
             x1 = x + info->x + b_glyph->left;
-            y1 = y + info->y - b_glyph->top;
+            y1 = y + info->y - b_glyph->top + offset_y;
             w1 = bitmap.width;
             h1 = bitmap.rows;
 
+            if (j_center) {
+                x1 += (s->box_width - line_w) / 2;
+            } else if (j_right) {
+                x1 += s->box_width - line_w;
+            }
+
             // Offset of the glyph's bitmap in the visible region
             dx = dy = 0;
             if (x1 < metrics->rect_x - s->bb_left) {
-- 
2.30.2

From caf63e997f5d2275d3d1bfe70151a9c29609939a Mon Sep 17 00:00:00 2001
From: yethie <klimk...@tiscali.it>
Date: Fri, 3 Feb 2023 14:40:25 +0100
Subject: [PATCH 6/7] new y_align option

---
 doc/filters.texi          | 10 ++++++++++
 libavfilter/vf_drawtext.c | 40 ++++++++++++++++++++++++++++++++-------
 2 files changed, 43 insertions(+), 7 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index 47e04a3ac3..1fa6ccdea8 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -12085,6 +12085,16 @@ Set the vertical and horizontal alignment of the text 
with respect to the box bo
 The value must contain exactly two letters, one for the vertical alignment 
(T=top,
 M=middle, B=bottom) and one for the horizontal alignment (L=left, C=center, 
R=right).
 
+@item y_align
+Specify what the @var{y} value is referred to. Possible values are:
+@itemize @bullet
+@item @code{text} the top of the highest glyph of the first text line is 
placed at @var{y}
+@item @code{baseline} the baseline of the first text line is placed at @var{y}
+@item @code{font} the baseline of the first text line is placed at @var{y} 
plus the
+    ascent (in pixels) defined in the font metrics
+@end itemize
+The default value of @var{y_align} is "text" for backward compatibility.
+
 @item borderw
 Set the width of the border to be drawn around the text using 
@var{bordercolor}.
 The default value of @var{borderw} is 0.
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index 0b01a78509..4784acb29f 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -31,6 +31,9 @@
  * - Text can be aligned horizontally (top, middle, bottom) and vertically
  *   (left, center, right) relative to the background box
  * - The default line height is now the one defined in the font
+ * - The new y_align parameter specifies if the user provided y value is
+ *   referred to the top of the text, to the font baseline or to the
+ *   top of the font.
  * - The boxborderw parameter can now contain a different value for each border
  *   (e.g. boxborderw=top|right|bottom|left)
  * - The following new variables can be used in the x and y expressions:
@@ -179,6 +182,12 @@ enum expansion_mode {
     EXP_STRFTIME,
 };
 
+enum y_alignment {
+    YA_TEXT,
+    YA_BASELINE,
+    YA_FONT,
+};
+
 typedef struct HarfbuzzData {
     hb_buffer_t* buf;
     hb_font_t* font;
@@ -318,6 +327,7 @@ typedef struct DrawTextContext {
     int boxw;                       ///< the value of the boxw parameter
     int boxh;                       ///< the value of the boxh parameter
     uint8_t *text_align;            ///< the horizontal and vertical text 
alignment
+    int y_align;                    ///< the value of the y_align parameter
 
     TextLine *lines;                ///< computed information about text lines
     int line_count;                 ///< the number of text lines
@@ -360,6 +370,10 @@ static const AVOption drawtext_options[]= {
         {"none",     "set no expansion",                    OFFSET(exp_mode), 
AV_OPT_TYPE_CONST, {.i64=EXP_NONE},     0, 0, FLAGS, "expansion"},
         {"normal",   "set normal expansion",                OFFSET(exp_mode), 
AV_OPT_TYPE_CONST, {.i64=EXP_NORMAL},   0, 0, FLAGS, "expansion"},
         {"strftime", "set strftime expansion (deprecated)", OFFSET(exp_mode), 
AV_OPT_TYPE_CONST, {.i64=EXP_STRFTIME}, 0, 0, FLAGS, "expansion"},
+    {"y_align",   "set the y alignment",    OFFSET(y_align), AV_OPT_TYPE_INT,  
{.i64=YA_TEXT}, 0, 2, FLAGS, "y_align"},
+        {"text",     "y is referred to the top of the first text line", 
OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_TEXT},     0, 0, FLAGS, "y_align"},
+        {"baseline", "y is referred to the baseline of the first line", 
OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_BASELINE}, 0, 0, FLAGS, "y_align"},
+        {"font",     "y is referred to the font defined line metrics",  
OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_FONT},     0, 0, FLAGS, "y_align"},
 
     {"timecode",        "set initial timecode",             
OFFSET(tc_opt_string), AV_OPT_TYPE_STRING,   {.str=NULL}, 0, 0, FLAGS},
     {"tc24hmax",        "set 24 hours max (timecode only)", OFFSET(tc24hmax),  
    AV_OPT_TYPE_BOOL,     {.i64=0},    0, 1, FLAGS},
@@ -1749,14 +1763,16 @@ continue_on_failed2:
     }
 
     if (ret == 0) {
-        int height64;
         metrics->line_height64 = s->face->size->metrics.height;
         
         metrics->width = POS_CEIL(width64, 64);
-        height64 = (metrics->line_height64 + s->line_spacing * 64) *
-            (FFMAX(0, line_count - 1)) + first_max_y64 - cur_min_y64;
-        metrics->height = POS_CEIL(height64, 64);
-
+        if (s->y_align == YA_FONT) {
+            metrics->height = POS_CEIL(metrics->line_height64 * line_count, 
64);
+        } else {
+            int height64 = (metrics->line_height64 + s->line_spacing * 64) *
+                (FFMAX(0, line_count - 1)) + first_max_y64 - cur_min_y64;
+            metrics->height = POS_CEIL(height64, 64);
+        }
         metrics->offset_top64 = first_max_y64;
         metrics->offset_right64 = last_max_x64;
         metrics->offset_bottom64 = cur_min_y64;
@@ -1927,7 +1943,13 @@ static int draw_text(AVFilterContext *ctx, AVFrame 
*frame)
     x = 0;
     y = 0;
     x64 = (int)(s->x * 64.);
-    y64 = (int)(s->y * 64. + metrics.offset_top64);
+    if (s->y_align == YA_FONT) {
+        y64 = (int)(s->y * 64. + s->face->size->metrics.ascender);
+    } else if (s->y_align == YA_BASELINE) {
+        y64 = (int)(s->y * 64.);
+    } else {
+        y64 = (int)(s->y * 64. + metrics.offset_top64);
+    }
 
     for (int l = 0; l < s->line_count; ++l) {
         TextLine *line = &s->lines[l];
@@ -1971,7 +1993,11 @@ static int draw_text(AVFilterContext *ctx, AVFrame 
*frame)
     }
 
     metrics.rect_x = s->x;
-    metrics.rect_y = s->y;
+    if (s->y_align == YA_BASELINE) {
+        metrics.rect_y = s->y - metrics.offset_top64 / 64;
+    } else {
+        metrics.rect_y = s->y;
+    }
     
     s->box_width = s->boxw == 0 ? metrics.width : s->boxw;
     s->box_height = s->boxh == 0 ? metrics.height : s->boxh;
-- 
2.30.2

From 1b0ed4a2f928f5b6e70caaf85de6ac68b6e623c7 Mon Sep 17 00:00:00 2001
From: yethie <klimk...@tiscali.it>
Date: Fri, 3 Feb 2023 14:43:38 +0100
Subject: [PATCH 7/7] many options are now supported as commands

---
 doc/filters.texi          | 25 ++++++++++-
 libavfilter/vf_drawtext.c | 89 ++++++++++++++++++++++++++++-----------
 2 files changed, 89 insertions(+), 25 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index 1fa6ccdea8..3995e62102 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -12477,11 +12477,34 @@ Full filter invocation with sendcmd would look like 
this:
 @example
 sendcmd=c='56.0 drawtext reinit fontsize=56\:fontcolor=green\:text=Hello\\ 
World'
 @end example
-@end table
 
 If the entire argument can't be parsed or applied as valid values then the 
filter will
 continue with its existing parameters.
 
+@end table
+
+The following options are also supported as @ref{commands}:
+
+@itemize @bullet
+@item x
+@item y
+@item alpha
+@item fontsize
+@item fontcolor
+@item boxcolor
+@item bordercolor
+@item shadowcolor
+@item box
+@item boxw
+@item boxh
+@item boxborderw
+@item line_spacing
+@item text_align
+@item shadowx
+@item shadowy
+@item borderw
+@end itemize
+
 @subsection Examples
 
 @itemize
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index 4784acb29f..0ac4046d76 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -36,6 +36,7 @@
  *   top of the font.
  * - The boxborderw parameter can now contain a different value for each border
  *   (e.g. boxborderw=top|right|bottom|left)
+ * - Many filter parameters are now supported as commands.
  * - The following new variables can be used in the x and y expressions:
  *   font_a, font_d, top_a, bottom_d
  */
@@ -338,29 +339,30 @@ typedef struct DrawTextContext {
 
 #define OFFSET(x) offsetof(DrawTextContext, x)
 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+#define TFLAGS 
AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
 
 static const AVOption drawtext_options[]= {
     {"fontfile",       "set font file",         OFFSET(fontfile),           
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
-    {"text",           "set text",              OFFSET(text),               
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
+    {"text",           "set text",              OFFSET(text),               
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, TFLAGS},
     {"textfile",       "set text file",         OFFSET(textfile),           
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
-    {"fontcolor",      "set foreground color",  OFFSET(fontcolor.rgba),     
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
+    {"fontcolor",      "set foreground color",  OFFSET(fontcolor.rgba),     
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, TFLAGS},
     {"fontcolor_expr", "set foreground color expression", 
OFFSET(fontcolor_expr), AV_OPT_TYPE_STRING, {.str=""}, 0, 0, FLAGS},
-    {"boxcolor",       "set box color",         OFFSET(boxcolor.rgba),      
AV_OPT_TYPE_COLOR,  {.str="white"}, 0, 0, FLAGS},
-    {"bordercolor",    "set border color",      OFFSET(bordercolor.rgba),   
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
-    {"shadowcolor",    "set shadow color",      OFFSET(shadowcolor.rgba),   
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, FLAGS},
-    {"box",            "set box",               OFFSET(draw_box),           
AV_OPT_TYPE_BOOL,   {.i64=0},     0, 1, FLAGS},
-    {"boxborderw",     "set box borders width", OFFSET(boxborderw),         
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
-    {"line_spacing",   "set line spacing in pixels", OFFSET(line_spacing),  
AV_OPT_TYPE_INT,    {.i64=-1},    INT_MIN, INT_MAX, FLAGS},
-    {"fontsize",       "set font size",         OFFSET(fontsize_expr),      
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, FLAGS},
-    {"text_align",     "set text alignment",    OFFSET(text_align),         
AV_OPT_TYPE_STRING, {.str="TL"},  0, 0, FLAGS},
-    {"x",              "set x expression",      OFFSET(x_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
-    {"y",              "set y expression",      OFFSET(y_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, FLAGS},
-    {"boxw",           "set box width",         OFFSET(boxw),               
AV_OPT_TYPE_INT,    {.i64=0},     0, INT_MAX, FLAGS},
-    {"boxh",           "set box height",        OFFSET(boxh),               
AV_OPT_TYPE_INT,    {.i64=0},     0, INT_MAX, FLAGS},
-    {"shadowx",        "set shadow x offset",   OFFSET(shadowx),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, FLAGS},
-    {"shadowy",        "set shadow y offset",   OFFSET(shadowy),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, FLAGS},
-    {"borderw",        "set border width",      OFFSET(borderw),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, FLAGS},
-    {"tabsize",        "set tab size",          OFFSET(tabsize),            
AV_OPT_TYPE_INT,    {.i64=4},     0, INT_MAX, FLAGS},
+    {"boxcolor",       "set box color",         OFFSET(boxcolor.rgba),      
AV_OPT_TYPE_COLOR,  {.str="white"}, 0, 0, TFLAGS},
+    {"bordercolor",    "set border color",      OFFSET(bordercolor.rgba),   
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, TFLAGS},
+    {"shadowcolor",    "set shadow color",      OFFSET(shadowcolor.rgba),   
AV_OPT_TYPE_COLOR,  {.str="black"}, 0, 0, TFLAGS},
+    {"box",            "set box",               OFFSET(draw_box),           
AV_OPT_TYPE_BOOL,   {.i64=0},     0, 1, TFLAGS},
+    {"boxborderw",     "set box borders width", OFFSET(boxborderw),         
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, TFLAGS},
+    {"line_spacing",   "set line spacing in pixels", OFFSET(line_spacing),  
AV_OPT_TYPE_INT,    {.i64=-1},    INT_MIN, INT_MAX, TFLAGS},
+    {"fontsize",       "set font size",         OFFSET(fontsize_expr),      
AV_OPT_TYPE_STRING, {.str=NULL},  0, 0, TFLAGS},
+    {"text_align",     "set text alignment",    OFFSET(text_align),         
AV_OPT_TYPE_STRING, {.str="TL"},  0, 0, TFLAGS},
+    {"x",              "set x expression",      OFFSET(x_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, TFLAGS},
+    {"y",              "set y expression",      OFFSET(y_expr),             
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, TFLAGS},
+    {"boxw",           "set box width",         OFFSET(boxw),               
AV_OPT_TYPE_INT,    {.i64=0},     0, INT_MAX, TFLAGS},
+    {"boxh",           "set box height",        OFFSET(boxh),               
AV_OPT_TYPE_INT,    {.i64=0},     0, INT_MAX, TFLAGS},
+    {"shadowx",        "set shadow x offset",   OFFSET(shadowx),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, TFLAGS},
+    {"shadowy",        "set shadow y offset",   OFFSET(shadowy),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, TFLAGS},
+    {"borderw",        "set border width",      OFFSET(borderw),            
AV_OPT_TYPE_INT,    {.i64=0},     INT_MIN, INT_MAX, TFLAGS},
+    {"tabsize",        "set tab size",          OFFSET(tabsize),            
AV_OPT_TYPE_INT,    {.i64=4},     0, INT_MAX, TFLAGS},
     {"basetime",       "set base time",         OFFSET(basetime),           
AV_OPT_TYPE_INT64,  {.i64=AV_NOPTS_VALUE}, INT64_MIN, INT64_MAX, FLAGS},
 #if CONFIG_LIBFONTCONFIG
     { "font",        "Font name",            OFFSET(font),               
AV_OPT_TYPE_STRING, { .str = "Sans" },           .flags = FLAGS },
@@ -370,7 +372,7 @@ static const AVOption drawtext_options[]= {
         {"none",     "set no expansion",                    OFFSET(exp_mode), 
AV_OPT_TYPE_CONST, {.i64=EXP_NONE},     0, 0, FLAGS, "expansion"},
         {"normal",   "set normal expansion",                OFFSET(exp_mode), 
AV_OPT_TYPE_CONST, {.i64=EXP_NORMAL},   0, 0, FLAGS, "expansion"},
         {"strftime", "set strftime expansion (deprecated)", OFFSET(exp_mode), 
AV_OPT_TYPE_CONST, {.i64=EXP_STRFTIME}, 0, 0, FLAGS, "expansion"},
-    {"y_align",   "set the y alignment",    OFFSET(y_align), AV_OPT_TYPE_INT,  
{.i64=YA_TEXT}, 0, 2, FLAGS, "y_align"},
+    {"y_align",   "set the y alignment",    OFFSET(y_align), AV_OPT_TYPE_INT,  
{.i64=YA_TEXT}, 0, 2, TFLAGS, "y_align"},
         {"text",     "y is referred to the top of the first text line", 
OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_TEXT},     0, 0, FLAGS, "y_align"},
         {"baseline", "y is referred to the baseline of the first line", 
OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_BASELINE}, 0, 0, FLAGS, "y_align"},
         {"font",     "y is referred to the font defined line metrics",  
OFFSET(y_align), AV_OPT_TYPE_CONST, {.i64=YA_FONT},     0, 0, FLAGS, "y_align"},
@@ -381,7 +383,7 @@ static const AVOption drawtext_options[]= {
     {"r",               "set rate (timecode only)",         OFFSET(tc_rate),   
    AV_OPT_TYPE_RATIONAL, {.dbl=0},    0, INT_MAX, FLAGS},
     {"rate",            "set rate (timecode only)",         OFFSET(tc_rate),   
    AV_OPT_TYPE_RATIONAL, {.dbl=0},    0, INT_MAX, FLAGS},
     {"reload",          "reload text file at specified frame interval", 
OFFSET(reload), AV_OPT_TYPE_INT, {.i64=0},    0, INT_MAX, FLAGS},
-    {"alpha",           "apply alpha while rendering",      OFFSET(a_expr),    
    AV_OPT_TYPE_STRING,   {.str = "1"}, .flags = FLAGS},
+    {"alpha",           "apply alpha while rendering",      OFFSET(a_expr),    
    AV_OPT_TYPE_STRING,   {.str = "1"}, .flags = TFLAGS},
     {"fix_bounds",      "check and fix text coords to avoid clipping", 
OFFSET(fix_bounds), AV_OPT_TYPE_BOOL, {.i64=0}, 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},
     {"text_source",     "the source of text", OFFSET(text_source_string), 
AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS },
@@ -892,11 +894,13 @@ static av_cold int init(AVFilterContext *ctx)
     if ((err = update_fontsize(ctx)) < 0)
         return err;
 
+    // Always init the stroker, may be needed if borderw is set via command
+    if (FT_Stroker_New(s->library, &s->stroker)) {
+        av_log(ctx, AV_LOG_ERROR, "Coult not init FT stroker\n");
+        return AVERROR_EXTERNAL;
+    }
+
     if (s->borderw) {
-        if (FT_Stroker_New(s->library, &s->stroker)) {
-            av_log(ctx, AV_LOG_ERROR, "Coult not init FT stroker\n");
-            return AVERROR_EXTERNAL;
-        }
         FT_Stroker_Set(s->stroker, s->borderw << 6, FT_STROKER_LINECAP_ROUND,
                        FT_STROKER_LINEJOIN_ROUND, 0);
     }
@@ -919,6 +923,23 @@ static int query_formats(AVFilterContext *ctx)
     return ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0));
 }
 
+static int glyph_enu_border_free(void *opaque, void *elem)
+{
+    Glyph *glyph = elem;
+
+    if (glyph->border_glyph != NULL) {
+        for (int t = 0; t < 16; ++t) {
+            if (glyph->border_bglyph[t] != NULL) {
+                FT_Done_Glyph((FT_Glyph)glyph->border_bglyph[t]);
+                glyph->border_bglyph[t] = NULL;
+            }
+        }
+        FT_Done_Glyph(glyph->border_glyph);
+        glyph->border_glyph = NULL;
+    }
+    return 0;
+}
+
 static int glyph_enu_free(void *opaque, void *elem)
 {
     Glyph *glyph = elem;
@@ -1041,6 +1062,26 @@ static int command(AVFilterContext *ctx, const char 
*cmd, const char *arg, char
 
         ctx->priv = new;
         return config_input(ctx->inputs[0]);
+    } else {
+        int old_borderw = old->borderw;
+        if ((ret = ff_filter_process_command(ctx, cmd, arg, res, res_len, 
flags)) < 0) {
+            return ret;
+        }
+        if (old->borderw != old_borderw) {
+            FT_Stroker_Set(old->stroker, old->borderw << 6, 
FT_STROKER_LINECAP_ROUND,
+                        FT_STROKER_LINEJOIN_ROUND, 0);
+            // Dispose the old border glyphs
+            av_tree_enumerate(old->glyphs, NULL, NULL, glyph_enu_border_free); 
                           
+        } else if (strcmp(cmd, "text_align") == 0) {
+            if (validate_text_align(old->text_align) != 0) {
+                av_log(ctx, AV_LOG_ERROR,
+                    "Invalid command value '%s' for 'text_align'\n", 
old->text_align);
+            }
+        } else if (strcmp(cmd, "fontsize") == 0) {
+            av_expr_free(old->fontsize_pexpr);
+            old->fontsize_pexpr = NULL;
+        }
+        return config_input(ctx->inputs[0]);
     }
 
 fail:
-- 
2.30.2

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".

Reply via email to