Directwrite: getting font height

My goal: I want to get the font height of IDWriteTextFormat so that I can calculate how many lines of text can fit in an IDWriteTextLayout of a certain height.

My problem: Right now I am using this code to calculate the visible number of rows:

inline int kmTextCtrl::GetVisLines() const { /* pTextFormat is an IDWriteTextFormat pointer, dpi_y is the desktop vertical dpi, and GetHeight() returns the height (in pixels) of the render target. */ float size = (pTextFormat->GetFontSize()/72.0f)*dpi_y; return (int)(GetHeight()/size); } 

The calculation seems accurate for some fonts, but not for any of the TrueType fonts (for example: Courier New, Arial, Times New Roman). For these fonts, the displayed text is cropped well below the bottom vertical border of the render target.

Some context: I'm making a text scroll back control that uses IDWriteTextLayout to put text in a control rendering object. I use the result of GetVisLines () to determine how many lines of text are from the circular buffer (which stores the text in std :: lines by line) to pull out the layout and recreate it every time the window scrolls or changes.

This is done using the native Win32 C ++ API.

+7
source share
2 answers

The simplest and most reliable approach is simply to request text metrics from the layout itself, as one of the two things for which it was designed: drawing and measuring. You must create an IDWriteTextLayout using text format and call GetMetrics to get DWRITE_TEXT_METRICS::height . I assume that you are using ID2D1RenderTarget::DrawText and passing in a text format, so you may not have created the layout directly, but calling DrawText similar to calling CreateTextLayout yourself and then DrawTextLayout .

Remember that going through the lower levels to get this answer ( IDWriteFontFace , etc.) makes certain assumptions that a common text-ready text control should not assume, for example, assuming that the base font will be used and that all lines are the same. height. As long as all characters are present in this base font, this will work (chances are you mostly display English, so everything looks good), but add some CJK or RTL or emoji languages ​​(which is the base font) like Times New Roman, of course, does not support), and the line height will increase or decrease in accordance with the replacement of fonts. GDI resizes the replaced fonts so that they fit the base height of the font, but this leads to poorly compressed letters in languages ​​such as Thai and Tibetan, which need more space for lifting and lowering. IDWriteTextLayout and other layouts like those found in WPF / Word keep all glyphs of fonts of the same em size, which means that they are better placed when they are next to each other; but that means the row height is variable.

If you simply draw each line of text, as if they were all the same height, you can see overlapping between glyphs and heterogeneous baselines between the lines, or clipping at the top and bottom of the control. Thus, the ideal thing is to use the actual height of each row; but if you want them all to be the same height (or if that complicates the control too much), then at least set the explicit line spacing using SetLineSpacing with DWRITE_LINE_SPACING_UNIFORM equal to the base font - so the baselines are evenly distributed.

Although for the curious, IDWriteTextLayout calculates the line height as the maximum of all run heights on this line, and the height of one run (with the same font and em size) simply uses the project metrics: ascent + descent, plus any occurring lineGap to be present (most fonts set this value to zero, but Gabriola is a good example of a big line break). Note that all em sizes are indicated in DIP (which with typical 96DPI means 1: 1, DIP exactly == pixels), and not in dots (1/72 of an inch).

(ascent + descent + lineGap) * emSize / designUnitsPerEm

+9
source

I found an answer . To find the line spacing (font height plus space) in Directwrite, you should do something similar to the following:

 inline int kmTextCtrl::GetVisLines() const { IDWriteFontCollection* collection; TCHAR name[64]; UINT32 findex; BOOL exists; pTextFormat->GetFontFamilyName(name, 64); pTextFormat->GetFontCollection(&collection); collection->FindFamilyName(name, &findex, &exists); IDWriteFontFamily *ffamily; collection->GetFontFamily(findex, &ffamily); IDWriteFont* font; ffamily->GetFirstMatchingFont(pTextFormat->GetFontWeight(), pTextFormat->GetFontStretch(), pTextFormat->GetFontStyle(), &font); DWRITE_FONT_METRICS metrics; font->GetMetrics(&metrics); float ratio = pTextFormat->GetFontSize() / (float)metrics.designUnitsPerEm; float size = (metrics.ascent + metrics.descent + metrics.lineGap) * ratio; float height = GetHeight(); int retval = static_cast<int>(height/size); ffamily->Release(); collection->Release(); font->Release(); return retval; } 

Of course, you probably don't want to do all this with every call to a commonly used built-in function.

+7
source

All Articles