【问题标题】:Directwrite: Getting a font's heightDirectwrite:获取字体的高度
【发布时间】:2011-08-01 04:29:24
【问题描述】:

我的目标: 我想获取 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++ 完成的。

【问题讨论】:

    标签: c++ winapi native direct2d directwrite


    【解决方案1】:

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

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

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

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

    (ascent + descent + lineGap) * emSize / designUnitsPerEm

    【讨论】:

    • 虽然 CJK 或 RTL 可能会被忽略(不应该),但表情符号很难反驳。表情符号将触发许多字体的字体替换。 :-) 或者更确切地说,???。
    • GetMetrics 为 DWRITE_TEXT_METRICS.width 返回 +infinity 是什么意思?对于常规文本,不换行,为 w & h 传递浮点最大值?
    • @SimonMourier:? 可能是错误的字体指标。我隐约记得看到巨大字形前进的宽度返回无穷大(DWRITE_TEXT_METRICS::width 本质上是该行字形前进的std::accumulate())。我会通过尝试不同的字体和不同的文本来缩小范围,虽然这不应该是原因,但传递了一个较小的最大值 (FLT_MAX/2)。
    • 字体是 Segoe UI 大小 16。实际上,我看不出它是无限的,但是谢谢,我会试试看 :-)
    • @SimonMourier:啊,是的,理由。调用CreateTextLayout 时,将maxWidth 参数设置为您的边界的实际限制(例如按钮控件)。否则,当使用DWRITE_WORD_WRAPPING_NO_WRAP 时,允许布局对齐传递的范围(并且无穷大非常宽?),但对齐实际上是要与DWRITE_WORD_WRAPPING_WRAP 一起使用。
    【解决方案2】:

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

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

    【讨论】:

    • 这绝对是错误的做法。请参阅 Dwayne 的回复以获得适当的解决方案。
    • @DmitriNesteruk,因为它做了太多假设。例如,您开始测量线条时甚至没有考虑稍后可能会根据实际文本数据使用不同的字体。接下来它忽略行间距模式,并假设布局计算间距的方式为(上升 + 下降 + lineGap)。请参阅上面评价最高的答案,它更好地解释了为什么这是不可靠的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2010-09-18
    • 2013-06-14
    • 1970-01-01
    • 2011-06-26
    • 2016-02-20
    • 1970-01-01
    • 1970-01-01
    • 2012-05-02
    相关资源
    最近更新 更多