My objective: I want to get the height of an IDWriteTextFormat's font so I can calculate how many lines of text can fit in an IDWriteTextLayout of a certain height.
My problem: Right now I'm using this code to calculate the visible number of lines:
inline i开发者_StackOverflow社区nt kmTextCtrl::GetVisLines() const
{
/* pTextFormat is an IDWriteTextFormat pointer, dpi_y is the desktop's 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 to be accurate for some fonts, but not for any of the TrueType fonts (e.g.: Courier New, Arial, Times New Roman). For these fonts, the text shown is clipped well short of the lower vertical boundary of the render target.
Some context: I am making a text scroll back buffer control which uses an IDWriteTextLayout to put text to the control's render target. I use the result of GetVisLines() to determine how many lines of text from a circular buffer (which stores text in std::strings by the line) to pull into the layout, and recreate it every time the window is scrolled or resized.
This is being done using "native" Win32 API C++.
The simplest and most robust approach is to just ask the layout itself for text metrics, as that's one of the two things it was designed for, drawing and measurement. You would create an IDWriteTextLayout
using the text format and call GetMetrics
to get the DWRITE_TEXT_METRICS::height
. I'm guessing you're using ID2D1RenderTarget::DrawText
and passing a text format, so you may not have created a layout directly, but calling DrawText
is just like calling CreateTextLayout
yourself followed by DrawTextLayout
.
Beware that going through the lower layers to get this answer (IDWriteFontFace
and the like) makes certain assumptions that a generic world ready text control should not assume, such as assuming the base font will be used and that all the lines being the same height. So long as all characters are present in the given base font, this happens to work out (chances are you're mostly displaying English which is why all appears well), but throw in some CJK or RTL languages or emoji (which a base font like Times New Roman certainly doesn't support), and the line height will grow or shrink accordingly to the substituted fonts. GDI rescales substituted fonts such that they fit into the base font's height, but this leads to poorly scrunched letters in languages like Thai and Tibetan which need more breathing room for ascenders and descenders. IDWriteTextLayout
and other layouts like those in WPF/Word keep all the font glyphs at the same em size, which means they line up more nicely when adjacent to each other; but it does mean the line height is variable.
If you do just draw each line of text as if they were all the same height, you can see overlap between glyphs and non-uniform baselines between lines, or clipping at the top and bottom of the control. So the ideal thing to do is to use the actual height of each line; but if you need them to all be the same height (or if it complicates the control too much), then at least set an explicit line spacing using SetLineSpacing
with DWRITE_LINE_SPACING_UNIFORM
to that of the base font - that way the baselines are uniformly spaced.
Though for the curious, IDWriteTextLayout computes the line height as the maximum of all run heights on that line, and the height of a single run (same font and em size) just uses the design metrics: ascent + descent, plus any lineGap that happens to be present (most fonts set this to zero, but Gabriola is a good example of large line gap). Note all em sizes are in DIP's (which at typical 96DPI means 1:1, DIP's exactly == pixels), not points (1/72 inch).
(ascent + descent + lineGap) * emSize / designUnitsPerEm
I have found an answer. To find the spacing of a line (font height plus gap) in Directwrite, you must do something akin 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 that every time you have to call a frequently-used inline function.
精彩评论