Search code examples
windowswinapifontsgdi

Why is font size different in vertical direction


I created two rulers - one vertical and one horizontal:

Screenshot

Now in the vertical ruler, is 'size' of the text visually larger(aprox. 5-6 pixels longer).

Why?

Relevant code:

WM_CREATE:

LOGFONT Lf = {0};

Lf.lfHeight = 12;

lstrcpyW(Lf.lfFaceName, L"Arial");

if (!g_pGRI->bHorizontal)
{
    Lf.lfEscapement = 900; // <----For vertical ruler!
}

g_pGRI->hfRuler = CreateFontIndirectW(&Lf);

SelectFont(g_pGRI->hdRuler, g_pGRI->hfRuler);

WM_PAINT:

    SetTextColor(g_pGRI->hdRuler, g_pGRI->cBorder);

    SetBkColor(g_pGRI->hdRuler, g_pGRI->cBackground);

    SetTextAlign(g_pGRI->hdRuler, TA_CENTER);

#define INCREMENT 10

    WCHAR wText[16] = {0};

    if (g_pGRI->bHorizontal)
    {
        INT ixTicks = RECTWIDTH(g_pGRI->rRuler) / INCREMENT;

        for (INT ix = 0; ix < ixTicks + 1; ix++)
        {
            MoveToEx(g_pGRI->hdRuler, INCREMENT * ix, 0, NULL);

            if (ix % INCREMENT == 0)
            {
                //This is major tick.

                LineTo(g_pGRI->hdRuler, INCREMENT * ix, g_pGRI->lMajor);

                wsprintfW(wText, L"%d", INCREMENT * ix);

                TextOutW(g_pGRI->hdRuler, INCREMENT * ix + 1, g_pGRI->lMajor + 1, wText, CHARACTERCOUNT(wText));
            }
            else
            {
                //This is minor tick.

                LineTo(g_pGRI->hdRuler, INCREMENT * ix, g_pGRI->lMinor);
            }
        }
    } 
    else
    {
        INT iyTicks = RECTHEIGHT(g_pGRI->rRuler) / INCREMENT;

        for (INT iy = 0; iy < iyTicks + 1; iy++)
        {
            MoveToEx(g_pGRI->hdRuler, 0, INCREMENT * iy, NULL);

            if (iy % INCREMENT == 0)
            {
                //This is major tick.

                LineTo(g_pGRI->hdRuler, g_pGRI->lMajor, INCREMENT * iy);

                wsprintfW(wText, L"%d", INCREMENT * iy);

                TextOutW(g_pGRI->hdRuler, g_pGRI->lMajor + 1, INCREMENT * iy + 1, wText, CHARACTERCOUNT(wText));
            }
            else
            {
                //This is minor tick.

                LineTo(g_pGRI->hdRuler, g_pGRI->lMinor, INCREMENT * iy);
            }
        }
    }
}

Solution

  • Background

    There are several different schemes for rasterizing text in a legible way when the text is small relative to the size of a pixel. For example, if the stroke width is supposed to be 1.25 pixels wide, you either have to round it off to a whole number of pixels, use antialiasing, or use subpixel rendering (like ClearType). Rounding is usually controlled by "hints" built into the font by the font designer.

    Hinting is the main reason why text width doesn't always scale exactly with the text height. For example, if, because of rounding, the left hump of a lowercase m is a pixel wider than the right one, a hint might tell the renderer to round the width up to make the letter symmetric. The result is that the character is a tad wider relative to its height than the ideal character.

    This issue

    What's likely happening here is that when GDI renders the string horizontally, each subsequent character may start at a fractional position, which is simulated by antialiasing or subpixel (ClearType) rendering. But, when rendering vertically, it appears that each subsequent character's starting position is rounded up to the next whole pixel, which tends to make the vertical text a couple pixels "longer" than its horizontal counterpart. Effectively, the kerning is always rounded up to the next whole pixel.

    It's likely that more effort was put into the common case of horizontal text rendering, making it easier to read (and possibly faster to render). The general case of rendering at any other angle may have been implemented in a simpler manner, working glyph-by-glyph instead of with the entire string.

    Things to Try

    If you want them to look that same, you'll probably have to make a small compromise in the visual quality of the horizontal labels. Here are a few things I can think of to try:

    1. Render the labels with regular antialiasing instead of ClearType subpixel rendering. (You can do this by setting the lfQuality field in the LOGFONT.) You would then draw the horizontal labels in the normal manner. For the vertical labels, draw them to an offscreen buffer horizontally, rotate it, and then blit the buffer to the screen. This gives you labels that look identical. The reason I suggest regular antialiasing is that it's invariant to the rotation. ClearType rendering had an inherent orientation and thus cannot be rotated without creating fringing. I've used this approach for graph labels with good results.

    2. Render the horizontal labels character by character, rounding the starting point up to the next whole pixel. This should make the horizontal labels look like the vertical ones. Typographically, they won't look as good, but for small labels like this, it's probably less distracting than having the horizontal and vertical labels visually mismatched.

    3. Another answer suggested rendering the horizontal labels with a very small, but non-zero, escapement and orientation, forcing those to go through the same rendering pipeline as the vertical labels. This may be the easiest solution for short labels like yours. If you had to handle longer strings of text, I'd suggest one of the first two methods.