Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Direct2D CreateTextLayout() - How to get caret coordinates

I am rendering Text using Direct2D starting with a text Layout

HRESULT hr = m_spWriteFactory->CreateTextLayout(
        m_wsText.c_str( ),
        m_wsText.length( ),
        m_spWriteTextFormat.Get( ),
        m_rect.right - m_rect.left - m_spacing.right - m_spacing.left,
        m_rect.bottom - m_rect.top - m_spacing.top - m_spacing.bottom,
        &m_spTextLayout
        );

and then rendering it to a bitmap which I later use with Direct3D

m_sp2DDeviceContext->DrawTextLayout(
                D2D1::Point2F( m_spacing.left, m_spacing.top ),
                m_spTextLayout.Get( ),
                m_spTextBrush.Get( )
                );

I would like to draw a simple thin flashing line as a caret. I know how to draw a line and how to make it appear / disappear.

Question: How do I get the starting point and the end point coordinates for my caret line?

Simplification: If it is much easier to assume that the text consists of one line only, then that's ok. But of course a more general solution is appreciated.

like image 751
NOhs Avatar asked Oct 24 '25 18:10

NOhs


1 Answers

Use IDWriteTextLayout's hit-testing functions to determine these:

  • HitTestTextPosition for mapping a text position index (relative to the first character) to a rectangle.
  • HitTestTextRange for getting a whole range of rectangles such as for selection.
  • HitTestPoint for mapping a mouse coordinate to a text position index.

For carets, this below works for all horizontal reading directions and proportional/monospace fonts:

...
DWRITE_HIT_TEST_METRICS hitTestMetrics;
float caretX, caretY;
bool isTrailingHit = false; // Use the leading character edge for simplicity here.

// Map text position index to caret coordinate and hit-test rectangle.
textLayout->HitTestTextPosition(
    textPosition,
    isTrailingHit,
    OUT &caretX,
    OUT &caretY,
    OUT &hitTestMetrics
    );

// Respect user settings.
DWORD caretWidth = 1;
SystemParametersInfo(SPI_GETCARETWIDTH, 0, OUT &caretWidth, 0);
DWORD halfCaretWidth = caretWidth / 2u;

// Draw a thin rectangle.
D2D1::RectF caretRect = {
    layoutOriginX + caretX - halfCaretWidth,
    layoutOriginY + hitTestMetrics.top,
    layoutOriginX + caretX + (caretWidth - halfCaretWidth),
    layoutOriginY + hitTestMetrics.top + hitTestMetrics.height
};
solidColorBrush->SetColor(D2D1::ColorF::AliceBlue);
d2dRenderTarget->FillRectangle(&caretRect, solidColorBrush);

Notes:

  • The above code as-is doesn't account for vertical reading directions such as in Japanese newspapers. You would need to draw a wide flat caret instead of the tall thin one here when the DWRITE_READING_DIRECTION was either top-to-bottom or bottom-to-top.
  • IDWriteTextLayout::GetMetrics only gives the overall bounding box to you, not the caret position.
  • IDWriteTextLayout::HitTestPoint's isInside flag is true if it is inside any of the bounding boxes (including text and inline objects), not just the layout bounds.

HitTestPoint's isInside flag

like image 195
Dwayne Robinson Avatar answered Oct 27 '25 08:10

Dwayne Robinson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!