10

I wish to find the exact height of text rendered in Windows. I have tried both GetTextExtentPoint32 and calling DrawText with the DT_CALCRECT flag and both give the same result.

It seems that the height returned is based on the full cell height, regardless of the actual text to be drawn.

The code below is the WM_PAINT handler for a standard Visual Studio 2013 Win32 project. It creates a (large) font and draws the sample text. The tallest part of the text is 98 pixels, but the value returned by GetTextExtentPoint32 is 131.

I realise that some applications might want the full cell height, but also some applications (like mine) just want the actual height used by the text.

Does anyone know how to find this information?

Yes, I can render to a memory DC and scan down looking for the first non-background coloured pixel – but that is going to be super slow.

Thanks

case WM_PAINT:
{
    hdc = BeginPaint (hWnd, &ps);

    HFONT hfont = CreateFont (-99, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH, L"Segoe UI Semibold");
    auto old_hfont = SelectObject (hdc, hfont);

    wchar_t sample_text[] = L"123 Testing 123";
    size_t sample_text_length = wcslen (sample_text);

    SIZE s;
    GetTextExtentPoint32 (hdc, sample_text, sample_text_length, &s);

    RECT r = {10, 10, 10 + s.cx, 10 + s.cy};

    SetBkColor   (hdc, RGB (80, 120, 160));
    SetTextColor (hdc, RGB (220, 220, 220));

    DrawText (hdc, sample_text, sample_text_length, &r, DT_SINGLELINE | DT_NOPREFIX | DT_LEFT | DT_TOP);

    SelectObject (hdc, old_hfont);
    DeleteObject (hfont);

    EndPaint (hWnd, &ps);
    break;
}
Anthony Wild
  • 137
  • 5
  • 2
    On an anti-aliased font, what would be your exact height? – RedX Mar 17 '15 at 13:56
  • The total number of raster lines written to by the DrawText operation? What I mean is the height between the first and last raster lines written to – Anthony Wild Mar 17 '15 at 13:57
  • Just out of curiosity, can you provide any further details on the use-case for this? [My guy instinct says that there may be a better way to solve your actual problem](http://meta.stackexchange.com/q/66377/what-is-the-xy-problem). – Anthony Mar 17 '15 at 15:10
  • Hi. Yes, I have a main window that contains horizontal tracks of information. Each track contains one or more data regions that are named. I would like to display the name in the top left of each region, but at the moment a lot of pixels are “wasted” by the returned height including room for pixels that are not used (in that particular name), leaving not-so-much room to show the actual data. Although the user can zoom (both horizontally and vertically), a data region is typically only about 20 pixels high – so three or four pixels “wasted” on blank space at the top of the text is not good. – Anthony Wild Mar 17 '15 at 15:25
  • 1
    One thing you'll find when trying to solve these problems is that you're writing code that would ostensibly do all the logic required to render the text with all kerning and offsets and other typography magic (including all the magic with overlapping characters found in Hebrew and other languages). You'll spend your entire life trying to get it right. I'd recommend just using DT_CALCRECT. Your results will be consistent, have clean padding around them and it's one line of code instead of thousands. – cppguy Aug 20 '15 at 21:23
  • Sorry but you're trying to do something that wasn't in the goal of text API.Draw and print font is one of the most complex thing. The api DT_CALCRECT is consistent and works. You certainly could do something clever for english lowercase on Arial to detect actual height but it will fail in so many other font/language it's not worth the hassle – ColdCat Aug 21 '15 at 10:07
  • 2
    With names like Anthony and Marc, I'd guess you're looking to get rid of the seemingly big gap of white space above English text. Reserved for accents and diacritics, (almost) absent in English text. It takes 32 pixels in the sample code, pretty noticeable. Simply subtract TEXTMETRICS.tmInternalLeading from the Y position. Careful to not overpaint anything above it. – Hans Passant Aug 21 '15 at 13:19
  • The memory DC method is the only method which will give you the actual extent of modified pixels, taking into account anti-aliasing and the vagaries of fancy fonts. Nor should it be super-slow if you do it once per unique string and cache the result. Other than that, consider HansPassant comment or PaulGroke answer. – Ben Aug 24 '15 at 15:03
  • @HansPassant I don't know about Anthony's original motivation for asking the question, but the use case that led me here was a requirement for tight packing of text that contains numbers only. When we switched from a font with short descenders to one with long descenders, the extra white space underneath became too much to bear. – Mark Ransom Aug 26 '15 at 15:36

4 Answers4

8

Have a look at GetGlyphOutline(GGO_METRICS) The returned GLYPHMETRICS struct should contain all the data you need to compute the extent.

Paul Groke
  • 6,259
  • 2
  • 31
  • 32
4

Your text isn't drawn directly, first he becomes a path which describes the outlines of your geometries/glyphs. A path consists of moves, lines and curves (and a close flag to the prior). Except curves, other path segments are extreme points to the final fill. Converting curves to lines and iterating through all path segments to find the minimum and maximum in horizontal & vertical dimension by the points results in the closest fitting rectangle to your text.

You can convert your text to a path by drawing him with calling BeginPath before and EndPath after. FlattenPath does the curve->line conversion. GetPath provides access to the path points on context. AbortPath finally removes the path from the context.

When you haven't set the background to transparent, the path may be the background around your text and already the first path segments are the background/extents rectangle lines - not what you want.

To simplify this method, you can exclude repeating characters and group characters by 1) below baseline 2) above midline 3) rest. This all together works very fast in comparison to your "look pixels" try.

Other useful sources about text sizes are font metrics (GetTextMetrics) and character widths (GetCharABCWidths)

Youka
  • 2,646
  • 21
  • 33
3

If your sample text contains only ASCII letters, you could manually evaluate the height of an ascending letter like b, a descending letter like g, and a median letter like x. You would do that once ahead of time, probably even offline, so any inefficient method is perfectly fine.

Then (depending on the font up to a small margin of error) computing the overall height is a simple check for ascenders (bdfhijklt and A-Z) and descenders (gjpqy) in your string.

klimpergeist
  • 343
  • 1
  • 12
  • But that's the question - how do you "manually evaluate the height" of a letter when the Windows API treats all characters as having the same height? – Mark Ransom Aug 18 '15 at 23:04
  • 1
    @MarkRansom By inefficiently scanning just as mentioned in the original post. The three heights will be precomputed once, potentially even offline, so being inefficient does not hurt at all. – klimpergeist Aug 18 '15 at 23:14
2

If this is a TrueType font, you may find the size of the glyph's positioning w.r.t. the 'previous' letter and line in the fmtx table as explained here. There is also similar information available for OpenType fonts, but it looks more cumbersome to derive something meaningful.

This does not give you the actual pixel-size of the glyph, but it does give you a more accurate idea where the true-type font path for a particular glyph is drawn in relation to the 'baseline', and the units scale with the point-size and are font-wide.

In your case I would take a look at FreeType, a 3rd party library that is fast and supports raster fonts, TrueType and OpenType fonts. It is widely used and very well documented and supported and can provide accurate metrics.

StarShine
  • 1,940
  • 1
  • 27
  • 45