SDL_ttf: Characters clipped within a string

When using SDL_ttf, I am finding characters are clipped.

This happens between the characters of a string, rendered using
TTF_RenderText_Shaded().

ie. where SDL_ttf itself renders the characters within the given string.

The problem clearly visible when using TTF_HINTING_NONE, but I’m not sure
whether it is also happening in other hinting modes.

Attached is a screenshot, from DejaVuSans.ttf at 15pt. It shows clipping
clearly on the ‘0’ and ‘a’ characters. The corresponding code is below.

Visually, it looks to be 1 pixel width that is clipped, regardless of font
size (eg. try 30). I tried alternative kerning settings, with no affect.

Am I right to think that this is a bug?

I can’t see a sensible workaround in my app, as I can’t get SDL_ttf to
render the final column of pixels – even character-by-character.

Thanks–
Mark

/*

  • Illustrate clipping of characters
    */

#include <stdio.h>
#include <SDL.h>
#include <SDL_ttf.h>

SDL_Surface *surface;
TTF_Font *font;

void draw(void)
{
const SDL_Color fg = { 255, 255, 255, 255 }, bg = { 0, 0, 0, 255 };
SDL_Surface *rendered;
SDL_Rect dest, source;

rendered = TTF_RenderText_Shaded(font, "Track at 101.0 BPM a0a0", fg, bg);

source.x = 0;
source.y = 0;
source.w = rendered->w;
source.h = rendered->h;

dest.x = 0;
dest.y = 0;

SDL_BlitSurface(rendered, &source, surface, &dest);
SDL_FreeSurface(rendered);

SDL_UpdateRect(surface, 0, 0, source.w, source.h);

}

int main(int argc, char *argv[])
{
if (argc != 2) {
fputs(“usage: test-ttf \n”, stderr);
return 1;
}

if (SDL_Init(SDL_INIT_VIDEO) == -1)
    abort();

if (TTF_Init() == -1)
    abort();

font = TTF_OpenFont(argv[1], 15);
if (font == NULL)
    abort();

TTF_SetFontHinting(font, TTF_HINTING_NONE);
TTF_SetFontKerning(font, 1);

surface = SDL_SetVideoMode(400, 200, 32, 0);
if (surface == NULL)
    abort();

for (;;) {
    SDL_Event event;

    if (SDL_WaitEvent(&event) < 0)
        abort();

    switch (event.type) {
    case SDL_QUIT:
        goto done;
    }

    draw();
}

done:

TTF_CloseFont(font);
TTF_Quit();
SDL_Quit();

return 0;

}
-------------- next part --------------
A non-text attachment was scrubbed…
Name: ttf-clip.png
Type: application/octet-stream
Size: 1726 bytes
Desc:
URL: http://lists.libsdl.org/pipermail/sdl-libsdl.org/attachments/20130102/181d5687/attachment.obj

Is it me or are “t”, “P” and “M” not affected? points that
antialiasing at the right of those letters works just fine
Possibly
others too, but those three seem like a sure giveaway.

2013/1/2, Mark Hills :> When using SDL_ttf, I am finding characters are clipped.

This happens between the characters of a string, rendered using
TTF_RenderText_Shaded().

ie. where SDL_ttf itself renders the characters within the given string.

The problem clearly visible when using TTF_HINTING_NONE, but I’m not sure
whether it is also happening in other hinting modes.

Attached is a screenshot, from DejaVuSans.ttf at 15pt. It shows clipping
clearly on the ‘0’ and ‘a’ characters. The corresponding code is below.

Visually, it looks to be 1 pixel width that is clipped, regardless of font
size (eg. try 30). I tried alternative kerning settings, with no affect.

Am I right to think that this is a bug?

I can’t see a sensible workaround in my app, as I can’t get SDL_ttf to
render the final column of pixels – even character-by-character.

Thanks


Mark

/*

  • Illustrate clipping of characters
    */

#include <stdio.h>
#include <SDL.h>
#include <SDL_ttf.h>

SDL_Surface *surface;
TTF_Font *font;

void draw(void)
{
const SDL_Color fg = { 255, 255, 255, 255 }, bg = { 0, 0, 0, 255 };
SDL_Surface *rendered;
SDL_Rect dest, source;

rendered = TTF_RenderText_Shaded(font, "Track at 101.0 BPM a0a0", fg,

bg);

source.x = 0;
source.y = 0;
source.w = rendered->w;
source.h = rendered->h;

dest.x = 0;
dest.y = 0;

SDL_BlitSurface(rendered, &source, surface, &dest);
SDL_FreeSurface(rendered);

SDL_UpdateRect(surface, 0, 0, source.w, source.h);

}

int main(int argc, char *argv[])
{
if (argc != 2) {
fputs(“usage: test-ttf \n”, stderr);
return 1;
}

if (SDL_Init(SDL_INIT_VIDEO) == -1)
    abort();

if (TTF_Init() == -1)
    abort();

font = TTF_OpenFont(argv[1], 15);
if (font == NULL)
    abort();

TTF_SetFontHinting(font, TTF_HINTING_NONE);
TTF_SetFontKerning(font, 1);

surface = SDL_SetVideoMode(400, 200, 32, 0);
if (surface == NULL)
    abort();

for (;;) {
    SDL_Event event;

    if (SDL_WaitEvent(&event) < 0)
        abort();

    switch (event.type) {
    case SDL_QUIT:
        goto done;
    }

    draw();
}

done:

TTF_CloseFont(font);
TTF_Quit();
SDL_Quit();

return 0;

}

Is it me or are “t”, “P” and “M” not affected? points that
antialiasing at the right of those letters works just fine
Possibly
others too, but those three seem like a sure giveaway.

Yes, I think you are right, these letters don’t appear to be affected in
this example.

If it’s happening only in specific cases, presumably the bug is more
awkward than a simple out-by-one.

But as I have not seen this in other Freetype-basd software (tho I did not
build a specific test), I presume the bug still resides in SDL somewhere?

If I were able to find time to look at the code, can anyone hint what
might be the first thing to look for?

ThanksOn Wed, 2 Jan 2013, Sik the hedgehog wrote:


Mark

Is it me or are “t”, “P” and “M” not affected? points that
antialiasing at the right of those letters works just fine
Possibly
others too, but those three seem like a sure giveaway.

Yes, I think you are right, these letters don’t appear to be affected in
this example.

If it’s happening only in specific cases, presumably the bug is more
awkward than a simple out-by-one.
[…]

To follow my own email, I think I found the cause of a (not insignificant)
bug.

I verified that the following changes fixes the example. Where the code
uses:

cached->minx = FT_FLOOR(metrics->horiBearingX);
cached->maxx = cached->minx + FT_CEIL(metrics->width);

it is calculating the size of bitmap required for a specific character,
and rounding the intermediate calculation.

Where it really needs to calculate and then round; eg.

cached->minx = FT_FLOOR(metrics->horiBearingX);
cached->maxx = FT_CEIL(metrics->horiBearingX + metrics->width);

If I understand correctly, this is why the problem affects few characters
– those where minx strays either to left of zero, or far to the right
from zero.

This pattern is repeated elsewhere in the code. Looks like there are
Y-coordinate cases and ‘advance’ that may need some closer inspection.

If the above sounds about right, I’ll work on a patch.On Fri, 4 Jan 2013, Mark Hills wrote:

On Wed, 2 Jan 2013, Sik the hedgehog wrote:


Mark

Somewhat offtopic, but after I looked at SDL_ttf code, I did this:

Tweak the spanners for your needs, beats wrestling with BlendMode and
the blits’ overhead any day.

Trivial patches to the HB’s build system to rid it from Cairo, glib
and ICU dependencies are on its bugtracker.–

./lxnt

Fixes a bug where each occurance of a specific characters in a string
appears clipped horizontally.

The vertical equivalent may need more attention; a fix is not attempted in
this patch.—
changeset: 223:1eb38e785668
branch: SDL-1.2
tag: tip
parent: 194:1ae8bba8f1d5
user: Mark Hills <@Mark_Hills>
date: Sun Jan 06 20:51:08 2013 +0000
summary: Use the correct rounding of character width calculation

diff -r 1ae8bba8f1d5 -r 1eb38e785668 SDL_ttf.c
— a/SDL_ttf.c Sat Jan 28 11:15:31 2012 -0500
+++ b/SDL_ttf.c Sun Jan 06 20:51:08 2013 +0000
@@ -605,7 +605,7 @@
if ( FT_IS_SCALABLE( face ) ) {
/* Get the bounding box */
cached->minx = FT_FLOOR(metrics->horiBearingX);

  •   	cached->maxx = cached->minx + FT_CEIL(metrics->width);
    
  •   	cached->maxx = FT_CEIL(metrics->horiBearingX + metrics->width);
      	cached->maxy = FT_FLOOR(metrics->horiBearingY);
      	cached->miny = cached->maxy - FT_CEIL(metrics->height);
      	cached->yoffset = font->ascent - cached->maxy;
    

@@ -618,7 +618,7 @@
* assumptions about non-scalable formats.
* */
cached->minx = FT_FLOOR(metrics->horiBearingX);

  •   	cached->maxx = cached->minx + FT_CEIL(metrics->horiAdvance);
    
  •   	cached->maxx = FT_CEIL(metrics->horiBearingX + metrics->horiAdvance);
      	cached->maxy = FT_FLOOR(metrics->horiBearingY);
      	cached->miny = cached->maxy - FT_CEIL(face->available_sizes[font->font_size_family].height);
      	cached->yoffset = 0;

When investigating the clipping of characters horizontally, I came across
the following oddity in the vertical calculation:

cached->maxy = FT_FLOOR(metrics->horiBearingY);
cached->miny = cached->maxy - FT_CEIL(metrics->height);

Is there some reason the ‘max’ value be subject to FT_FLOOR, and not
FT_CEIL? (is this a bug?)

I verified that ‘max’ is indeed the higher of the two values (ie. min and
max are not simply mis-labelled), and some values are:

max min
10 -1
8 -1
8 -1
8 -1
11 -1
0 0
[…]

If the possibilty of a character being cropped is to be avoided, it seems
that it should be:

cached->maxy = FT_CEIL(metrics->horiBearingY);

but by making this fix, the rendered text moves up by one pixel.

So either I made a mistake here, or to fix this leads to a significant
change in rendering.

FWIW, the ‘fixed’ miny calculation does not cause obvious change, and is:

cached->miny = FT_FLOOR(metrics->horiBearingY - metrics->height);–
Mark

Nice! Thanks for sharing! :)On Fri, Jan 4, 2013 at 3:48 PM, Alexander Sabourenkov wrote:

Somewhat offtopic, but after I looked at SDL_ttf code, I did this:

https://github.com/lxnt/ex-sdl-freetype-harfbuzz/

Tweak the spanners for your needs, beats wrestling with BlendMode and
the blits’ overhead any day.

Trivial patches to the HB’s build system to rid it from Cairo, glib
and ICU dependencies are on its bugtracker.

./lxnt


SDL mailing list
SDL at lists.libsdl.org
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org