4

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.

NOhs
  • 2,780
  • 3
  • 25
  • 59
  • What about `m_spTextLayout.GetMetrics()` ? [DWRITE_TEXT_METRICS structure](https://msdn.microsoft.com/en-us/library/windows/desktop/dd368135(v=vs.85).aspx) – Peter Kostov Jan 21 '15 at 07:42

2 Answers2

10

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 over the text itself, not just the layout bounds.

HitTestPoint's isInside flag

Dwayne Robinson
  • 2,034
  • 1
  • 24
  • 39
  • Thank you VERY MUCH for this information! Do you think you could explain the isTrailingHit and isInside parameters a little more? I don't understand them. (E.g. https://msdn.microsoft.com/en-us/library/windows/desktop/dd371464(v=vs.85).aspx) – Andrew Apr 30 '17 at 04:46
  • 2
    @Andrew: isInside as an output from HitTestPoint means the x,y coordinates you passed were within the selection bounds of the text. If the x,y coordinate is above the first line, below the last line, to the left of the leftmost character on the line, or beyond the rightmost character, it's false. The isTrailingHit output parameter from HitTestPoint means you clicked on the trailing side of the bounding cell for a text cluster (that is the right half the box for left-to-right text, or left side for right-to-left text). – Dwayne Robinson May 02 '17 at 01:02
  • Awesome, thanks! What if it's inside the text's rectangle but outside of the text? (To the right of the last line of text but inside the first line horizontally.) Also, what exactly is a text cluster? I couldn't figure that out either. – Andrew May 02 '17 at 13:16
  • 2
    @Andrew: Added image above. A character cluster is an atomic group of characters. You don't normally encounter them in English or Chinese because every character is independent, but most complex scripts have them, and they may glue together or reorder. For example, U+0E01 THAI CHARACTER KO KAI 'ก' and U+0E33 THAI CHARACTER SARA AM 'ำ' form an indivisible unit that cannot be broken 'กำ' (just try clicking in the middle of them). – Dwayne Robinson May 06 '17 at 00:36
-2

You can get the layout's bounding rectangle via IDWriteTextLayout::GetMetrics.

    DWRITE_TEXT_METRICS textMetrics;
    textLayout->GetMetrics(&textMetrics);

Your rectangle is

    D2D1::RectF( textMetrics.left,
                 textMetrics.top, 
                 textMetrics.left + textMetrics.width,
                 textMetrics.top + textMetrics.height );

You can then draw the caret along the right boundary line.

vt.
  • 1,325
  • 12
  • 27
  • If I have a text "some text" and a second one indicating where I want to place the cursor "some te", will the text for non mono spaced fonts be laid out the same way so that I can use the second text to draw my cursor using ur suggested method? – NOhs Jan 22 '15 at 15:09
  • Yes, monospaced or not, this will give you the right box. – vt. Jan 22 '15 at 18:49
  • 1
    This answer is totally wrong in that it doesn't answer the question at all. The question was about the caret position, not the text bounds. – Andrew Apr 30 '17 at 04:41