10

我的目标: 我想获得 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 中的文本)中的多少行文本拉入布局,并在每次滚动或调整窗口大小时重新创建它。

这是使用“本机”Win32 API C++ 完成的。

4

2 回答 2

11

最简单和最可靠的方法是只向布局本身询问文本指标,因为这是它设计的两件事之一,绘图和测量。IDWriteTextLayout您将使用文本格式创建一个并调用GetMetrics以获取DWRITE_TEXT_METRICS::height. 我猜您正在使用ID2D1RenderTarget::DrawText并传递文本格式,因此您可能没有直接创建布局,但调用DrawText就像调用CreateTextLayout自己后跟DrawTextLayout.

请注意,通过较低层来获得此答案(IDWriteFontFace等等)会做出某些通用世界就绪文本控件不应假设的假设,例如假设将使用基本字体并且所有行的高度相同。只要所有字符都出现在给定的基本字体中,这恰好可以解决(您可能主要显示英语,这就是为什么一切看起来都很好),但是抛出一些 CJK 或 RTL 语言或表情符号(基本字体像 Times New Roman 当然不支持),并且行高会根据替换的字体而增加或缩小。GDI 重新调整替换字体以使其适合基本字体的高度,但这会导致泰语和藏语等语言中的字母皱缩不佳,需要为上升和下降提供更多喘息空间。IDWriteTextLayout和 WPF/Word 中的其他布局将所有字体字形保持在相同的 em 大小,这意味着它们在彼此相邻时排列得更好;但这确实意味着行高是可变的。

如果您只绘制每一行文本,就好像它们都具有相同的高度,您可以看到字形之间的重叠和行之间的不均匀基线,或者在控件的顶部和底部进行剪切。所以理想的做法是使用每条线的实际高度;但是如果您需要它们都具有相同的高度(或者如果它使控件过于复杂),那么至少使用SetLineSpacingwith设置一个明确的行距DWRITE_LINE_SPACING_UNIFORM到基本字体的行距 - 这样基线是均匀间隔的。

尽管出于好奇,IDWriteTextLayout 将行高计算为该行上所有运行高度的最大值,并且单个运行的高度(相同的字体和 em 大小)仅使用设计指标:上升 + 下降,加上发生的任何 lineGap存在(大多数字体将此设置为零,但 Gabriola 是大行间距的一个很好的例子)。请注意,所有 em 大小都以 DIP 为单位(在典型的 96DPI 下,这意味着 1:1,DIP 正好 == 像素),而不是点(1/72 英寸)。

(ascent + descent + lineGap) * emSize / designUnitsPerEm

于 2011-09-10T07:40:14.943 回答
7

我找到了答案。要在 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;
}

当然,您可能不想在每次必须调用常用的内联函数时都这样做。

于 2011-04-10T06:23:04.647 回答