如何对文本布局执行命中测试

提供有关如何将命中测试添加到使用 IDWriteTextLayout 接口显示文本的DirectWrite应用程序的简短教程。

本教程的结果是一个应用程序,该应用程序为鼠标左键单击的字符添加下划线,如以下屏幕截图所示。

“单击此文本”的屏幕截图

本操作方法包含以下部分:

步骤 1:创建文本布局。

首先,需要一个使用 IDWriteTextLayout 对象的应用程序。 如果已有一个使用文本布局显示文本的应用程序,请转到步骤 2。

若要添加文本布局,必须执行以下操作:

  1. 将指向 IDWriteTextLayout 接口的指针声明为 类的成员。

    IDWriteTextLayout* pTextLayout_;
    
  2. CreateDeviceIndependentResources 方法的末尾,通过调用 CreateTextLayout 方法创建 IDWriteTextLayout 接口对象。

    // Create a text layout using the text format.
    if (SUCCEEDED(hr))
    {
        RECT rect;
        GetClientRect(hwnd_, &rect); 
        float width  = rect.right  / dpiScaleX_;
        float height = rect.bottom / dpiScaleY_;
    
        hr = pDWriteFactory_->CreateTextLayout(
            wszText_,      // The string to be laid out and formatted.
            cTextLength_,  // The length of the string.
            pTextFormat_,  // The text format to apply to the string (contains font information, etc).
            width,         // The width of the layout box.
            height,        // The height of the layout box.
            &pTextLayout_  // The IDWriteTextLayout interface pointer.
            );
    }
    
  3. 然后,必须将对 ID2D1RenderTarget::D rawText 方法的调用更改为 ID2D1RenderTarget::D rawTextLayout ,如以下代码所示。

    pRT_->DrawTextLayout(
        origin,
        pTextLayout_,
        pBlackBrush_
        );
    

步骤 2:添加 OnClick 方法。

现在,向 类添加一个方法,该方法将使用文本布局的命中测试功能。

  1. 在类头文件中声明 OnClick 方法。

    void OnClick(
        UINT x,
        UINT y
        );
    
  2. 在类实现文件中定义 OnClick 方法。

     void DemoApp::OnClick(UINT x, UINT y)
     {    
     }
    

步骤 3:执行命中测试。

若要确定用户单击文本布局的位置,我们将使用 IDWriteTextLayout::HitTestPoint 方法。

将以下内容添加到步骤 2 中定义的 OnClick 方法。

  1. 声明我们将作为参数传递给 方法的变量。

    DWRITE_HIT_TEST_METRICS hitTestMetrics;
    BOOL isTrailingHit;
    BOOL isInside; 
    

    HitTestPoint 方法输出以下参数。

    变量 说明
    hitTestMetrics 完全封闭命中测试位置的几何图形。
    isInside 指示命中测试位置是否在文本字符串内。 如果为 FALSE,则返回最接近文本边缘的位置。
    isTrailingHit 指示命中测试位置是位于字符的前导侧还是尾部。
  2. 调用 IDWriteTextLayout 对象的 HitTestPoint 方法。

    pTextLayout_->HitTestPoint(
                    (FLOAT)x, 
                    (FLOAT)y,
                    &isTrailingHit,
                    &isInside,
                    &hitTestMetrics
                    );
    

    此示例中的代码传递位置的 xy 变量,而无需进行任何修改。 这可以在此示例中完成,因为文本布局的大小与窗口相同,并且源自窗口的左上角。 如果不是这种情况,则必须确定与文本布局的原点相关的坐标。

步骤 4:单击的文本下划线。

在调用 HitTestPoint 方法后,将以下内容添加到步骤 2 中定义的 OnClick

if (isInside == TRUE)
{
    BOOL underline;

    pTextLayout_->GetUnderline(hitTestMetrics.textPosition, &underline);

    DWRITE_TEXT_RANGE textRange = {hitTestMetrics.textPosition, 1};

    pTextLayout_->SetUnderline(!underline, textRange);
}

此代码执行以下操作。

  1. 使用 isInside 变量检查命中测试点是否位于文本内。

  2. hitTestMetrics 结构的 textPosition 成员包含单击的字符的从零开始的索引。

    通过将此值传递给 IDWriteTextLayout::GetUnderline 方法,获取此字符的下划线。

  3. 声明一个 DWRITE_TEXT_RANGE 变量,其起始位置设置为 hitTestMetrics.textPosition ,长度为 1。

  4. 使用 IDWriteTextLayout::SetUnderline 方法切换下划线。

设置下划线后,通过调用 类的 DrawD2DContent 方法重绘文本。

DrawD2DContent();

步骤 5:处理WM_LBUTTONDOWN消息。

最后,将 WM_LBUTTONDOWN 消息添加到应用程序的消息处理程序,并调用 类的 OnClick 方法。

case WM_LBUTTONDOWN:
    {
        int x = GET_X_LPARAM(lParam); 
        int y = GET_Y_LPARAM(lParam);

        pDemoApp->OnClick(x, y);
    }
    break;

GET_X_LPARAMGET_X_LPARAM 宏在 windowsx.h 头文件中声明。 它们可以轻松检索鼠标单击的 x 和 y 位置。