From 54cfac6d5c12e39c0483876f2d2ccdfe79eeee77 Mon Sep 17 00:00:00 2001 From: Kieran Kunhya Date: Wed, 26 Aug 2026 19:43:42 +0100 Subject: [PATCH 1/3] upipe_freetype: index a glyph bitmap by its pitch The renderer read the coverage of a glyph at buffer[j * width + i], which is only where it is when the bitmap has no padding between its lines. Both the small bitmap cache and a rendered glyph say what the distance between two lines is, so use it: with a padded bitmap the glyph was sheared by a pixel per line and read past the end of the buffer on the last ones. Co-Authored-By: Claude Opus 5 (1M context) --- lib/upipe-freetype/upipe_freetype.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/upipe-freetype/upipe_freetype.c b/lib/upipe-freetype/upipe_freetype.c index 2d865304f..0d7084ec9 100644 --- a/lib/upipe-freetype/upipe_freetype.c +++ b/lib/upipe-freetype/upipe_freetype.c @@ -660,7 +660,7 @@ static bool upipe_freetype_handle(struct upipe *upipe, struct uref *uref, &type, index, &sbit, NULL)) continue; - int left, top, width, height, xadvance, yadvance; + int left, top, width, height, pitch, xadvance, yadvance; unsigned char *buffer; if (!sbit->buffer) { if (FTC_ImageCache_Lookup(upipe_freetype->img_cache, @@ -675,6 +675,7 @@ static bool upipe_freetype_handle(struct upipe *upipe, struct uref *uref, top = slot->top; width = slot->bitmap.width; height = slot->bitmap.rows; + pitch = slot->bitmap.pitch; xadvance = glyph->advance.x; yadvance = glyph->advance.y; buffer = slot->bitmap.buffer; @@ -684,6 +685,7 @@ static bool upipe_freetype_handle(struct upipe *upipe, struct uref *uref, top = sbit->top; width = sbit->width; height = sbit->height; + pitch = sbit->pitch; /* scale to 16.16 */ xadvance = sbit->xadvance << 16; yadvance = sbit->yadvance << 16; @@ -700,7 +702,7 @@ static bool upipe_freetype_handle(struct upipe *upipe, struct uref *uref, if (ypos + j < 0 || ypos + j >= vsize) continue; - uint8_t px = buffer[j * width + i] * upipe_freetype->foreground[3] / 0xff; + uint8_t px = buffer[j * pitch + i] * upipe_freetype->foreground[3] / 0xff; #define DO_PLANE(Plane, Val) \ if (Plane.p) { \ From 67f2c65e3adaf45b1425316620457d9b443c1f09 Mon Sep 17 00:00:00 2001 From: Kieran Kunhya Date: Wed, 26 Aug 2026 19:45:01 +0100 Subject: [PATCH 2/3] upipe_freetype: fix the horizontal position of a bounding box The left of the box was taken from the first glyph under "if (!i)", but the string index is advanced before the test, so it was never zero and the box always started at zero however far into the line the first glyph sat. Take it from the first glyph that is actually looked up. The height was assigned twice, the second time without the guard the first one had, which made the guard do nothing; drop the second one. Co-Authored-By: Claude Opus 5 (1M context) --- lib/upipe-freetype/upipe_freetype.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/upipe-freetype/upipe_freetype.c b/lib/upipe-freetype/upipe_freetype.c index 0d7084ec9..c1589a8ea 100644 --- a/lib/upipe-freetype/upipe_freetype.c +++ b/lib/upipe-freetype/upipe_freetype.c @@ -945,6 +945,7 @@ static int _upipe_freetype_get_bbox(struct upipe *upipe, FT_Bool use_kerning = FT_HAS_KERNING(upipe_freetype->face); FT_UInt previous = 0; FT_Pos yMax = 0; + bool first = true; int64_t width = 0; for (size_t i = 0; str[i] != '\0';) { @@ -978,8 +979,10 @@ static int _upipe_freetype_get_bbox(struct upipe *upipe, FT_BBox ft_bbox; FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &ft_bbox); - if (!i) + if (first) { bbox.x = ft_bbox.xMin; + first = false; + } if (ft_bbox.yMin < bbox.y) bbox.y = ft_bbox.yMin; if (ft_bbox.yMax > yMax) @@ -991,7 +994,6 @@ static int _upipe_freetype_get_bbox(struct upipe *upipe, if (yMax > bbox.y) bbox.height = yMax - bbox.y; - bbox.height = yMax - bbox.y; if (width > 0) /* get width ceil and downscale 16.16 integer */ From 2aeb36fa580c74f384fc8270a35252245c97f0ee Mon Sep 17 00:00:00 2001 From: Kieran Kunhya Date: Wed, 26 Aug 2026 19:45:54 +0100 Subject: [PATCH 3/3] upipe_freetype: do not read uninitialised values The scaler handed to the cache manager is a stack structure with two fields left unset; the manager keeps a copy of it. They are the resolutions, which it only reads when the sizes are not in pixels, so nothing came of it, but zero them rather than hash and store whatever was on the stack. FT_Get_Advance leaves its output untouched when it fails, and the result was added to the total either way. Co-Authored-By: Claude Opus 5 (1M context) --- lib/upipe-freetype/upipe_freetype.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/upipe-freetype/upipe_freetype.c b/lib/upipe-freetype/upipe_freetype.c index c1589a8ea..dc806ba90 100644 --- a/lib/upipe-freetype/upipe_freetype.c +++ b/lib/upipe-freetype/upipe_freetype.c @@ -186,6 +186,7 @@ static int upipe_freetype_load_face(struct upipe *upipe) return UBASE_ERR_INVALID; FTC_ScalerRec scaler; + memset(&scaler, 0, sizeof (scaler)); scaler.face_id = upipe_freetype->font; scaler.width = upipe_freetype->pixel_size; scaler.height = upipe_freetype->pixel_size; @@ -1046,10 +1047,9 @@ static int _upipe_freetype_get_advance(struct upipe *upipe, } FT_Fixed advance; - FT_Get_Advance(upipe_freetype->face, index, FT_LOAD_NO_SCALE, - &advance); - - total_advance += advance; + if (!FT_Get_Advance(upipe_freetype->face, index, FT_LOAD_NO_SCALE, + &advance)) + total_advance += advance; previous = index; }