Search code examples
c#fontsgdi+opentypecleartype

What is causing Calibri to lose ClearType between 9 and 14 pt?


What exactly is it that makes GDI+ switch to binary aliasing when using default Microsoft Office font Calibri between 9pt and 14pt with ClearTypeGridFit specified?

It's somewhat disconcerting. How many other fonts are also affected by whatever is behind this, and at what sizes? Is there a workaround? (Excluding GDI, which doesn't have the same text layout features?)

Here's the code I used to generate the image:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

    var height = 0;
    for (var i = 1; i <= 17; i++)
    {
        using (var font = new Font("Calibri", i))
        {
            var text = "ClearTypeGridFit " + i + "pt";
            e.Graphics.DrawString(text, font, SystemBrushes.ControlText, 0, height);
            height += (int)e.Graphics.MeasureString(text, font).Height;
        }
    }
}

Solution

  • Calibri comes with an EBLC table and EBDT table, which tells text engines that for certain point sizes, they should not try "their own scaling algorithms" but just use bitmaps that are stored directly in the font, instead.

    Each font size can come with its own list of "the following glyphs must be bitmapped at this size", called a "strike", so one glyph can have multiple bitmaps for multiple sizes (but there can be gaps, and when that happens bitmaps need to be scaled and things can go catastrophically wrong).

    For instance, Calibri has strikes for point sizes 12, 13, 15, 16, 17 and 19, with an example bitmap for A being:

    <ebdt_bitmap_format_1 name="A">
      <SmallGlyphMetrics>
        <height value="8"/>
        <width value="7"/>
        <BearingX value="0"/>
        <BearingY value="8"/>
        <Advance value="7"/>
      </SmallGlyphMetrics>
      <rawimagedata>
        10102828 447c8282  
      </rawimagedata>
    </ebdt_bitmap_format_1>
    

    This bitmap is referenced by the font size 12 strike, and is encoded as a 7x8 pixel bitmap. Since 12 is the lowest value, we run into problems when we use a font size lower than 12: suddenly we have to scale a bitmap. This can only go horribly wrong.

    If you look at something like WordPad, you can see that Microsoft's Uniscribe engine (used with GDI+; the modern equivalent is Direct2D with DirectWrite as text engine, instead)can scale these bitmaps down quite well (shown are sizes 5 through 20), but even Microsoft's own technology has clear limitations. We see that at font sizes 5, 6, and 7px the bitmaps are pretty horrible, and even 8, 10, and 11 look kind of wonky:

    A at sizes 5 through 20

    Scaled up:

    A at sizes 5 through 20, scaled up 3x

    Things get more interesting because not every glyph is represented in every strike, so while "A" has a bitmap at point size 12, there are glyphs for which the lowest point size with an explicit bitmap may be 13, or 15, or 16, or 17, or even 19.

    This means you have three problems:

    1. A font might "demand" the text engine uses its bitmaps, instead of trying to rasterise the vector outlines per the text engine's algorithms, and
    2. There is no magic font size above which all characters are rendered "nicely" and below which all characters are rendered "poorly". A font can have any number of "strikes", containing any subset of the font's encoded glyphs, effectively meaning that each character can have its own rules about when the text engine should switch from rasterised vector to embedded bitmap, and
    3. Text engines are entirely free to completely ignore the font's "demands" and do their own thing anyway, and finding out which engine does what is, despite having the internet at our disposal, virtually impossible. It's one of those things that no one seems to document.

    The easiest way to find out which fonts will do this is to simply check the font for an EBDT table at all - if there is one, this font will force engines to use bitmaps for very small (and sometimes very large) font sizes. If you want the specifics, you can run the font through TTX and then find the <EBDT> table start, to see what's really going on.

    Prepare to be overwhelmed, though. Calibri alone has bitmaps specified for well over a thousand glyphs, for example.