我的目标:我想得到一个IDWriteTextFormat的字体的高度,这样我就可以计算出多少行文本可以适合一定高度的IDWriteTextLayout。
我的问题:现在我使用下面的代码来计算可见的行数:
inline int 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);
}
对于某些字体,计算似乎是准确的,但对于任何TrueType字体(例如:Courier New, Arial, Times New Roman)都不是。对于这些字体,显示的文本被剪辑到远低于渲染目标的下垂直边界。
一些背景:我正在制作一个文本滚动回缓冲区控件,它使用IDWriteTextLayout将文本放入控件的渲染目标。我使用GetVisLines()的结果来确定从循环缓冲区(按行将文本存储在std::strings中)拉入布局的文本行数,并在每次滚动窗口或调整窗口大小时重新创建它。
这是使用"native" Win32 API c++完成的。
最简单和最可靠的方法是询问布局本身的文本参数,因为这是设计它的两件事之一,绘图和测量。您将使用文本格式创建IDWriteTextLayout
,并调用GetMetrics
以获得DWRITE_TEXT_METRICS::height
。我猜你正在使用ID2D1RenderTarget::DrawText
并传递文本格式,所以你可能没有直接创建布局,但是调用DrawText
就像调用CreateTextLayout
一样,然后是DrawTextLayout
。
请注意,通过较低的层来获得这个答案(IDWriteFontFace
等)会做出某些假设,这些假设是通用的世界就绪文本控件不应该假设的,例如假设将使用基本字体并且所有行都是相同的高度。只要所有字符都以给定的基本字体出现,这恰好可以解决(可能您主要显示英语,这就是为什么所有字体看起来都很好),但如果加入一些CJK或RTL语言或表情符号(像Times New Roman这样的基本字体当然不支持),那么行高将相应地增加或缩小替换字体。GDI重新调整替代字体,使其适合基础字体的高度,但这会导致像泰语和藏语这样的语言中的字母排列不整齐,需要更多的上升和下降空间。IDWriteTextLayout
和WPF/Word中的其他布局将所有字体字形保持在相同的em大小,这意味着它们在彼此相邻时排列得更漂亮;但它确实意味着行高是可变的。
如果您只是将每一行文本绘制为相同的高度,则可以看到符号之间的重叠和行之间的非均匀基线,或者在控件的顶部和底部剪切。所以理想的做法是使用每条线的实际高度;但是如果你需要它们都是相同的高度(或者如果它使控件过于复杂),那么至少使用SetLineSpacing
和DWRITE_LINE_SPACING_UNIFORM
设置一个显式的行间距,以基本字体的行间距-这样基线是均匀间隔的。
虽然出于好奇,IDWriteTextLayout计算行高度作为该行上所有运行高度的最大值,而单次运行的高度(相同的字体和em大小)只是使用设计指标:上升+下降,加上任何碰巧存在的行间距(大多数字体将其设置为零,但Gabriola是一个很好的例子)。请注意,所有em尺寸都是DIP(典型的96DPI是1:1,DIP正好==像素),而不是点(1/72英寸)。
(ascent + descent + lineGap) * emSize / designUnitsPerEm
我找到了答案。要在Directwrite中找到一行的间距(字体高度加上间距),您必须执行以下操作:
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;
}
当然,您可能不希望每次调用一个常用的内联函数时都这样做。