Issue with DirectWrite text layouts on some fonts

Started by
2 comments, last by realSuperku 1 year, 2 months ago

Hello!

I'm working in my own DirectX11.1 engine. Text rendering used to be done via Direct2D and DirectWrite. As rendering text that way is pretty slow I buffered results to small canvases and only updated them when the strings changed.

To improve performance I now render glyphs to an atlas (using the same old D2D and DirectWrite approach), then use a IDWriteTextLayout layout, call its Draw function with a custom renderer (IDWriteTextRenderer), which iterates over the DWRITE_GLYPH_RUN and adds instances to an instanced glyph quad renderer.

This works fine and is much faster than the previous attempt. However, the rendering breaks weirdly on some strings. I first noticed this in my player debug panel:

It seems to break after the small f but only sometimes. If I replace the f with some other character it works fine:

I checked the glyphAdvances in the DrawGlyphRun callback. They are all over the place (they differ from what they should be on a regular string) after those troublesome fs. Then I realized the last character of a messed up string is missing. Turns out the glyphRun->glyphCount is indeed 1 lower than it should be.

I used GetClusterMetrics to analyze the IDWriteTextLayout:

hr = pDWriteFactory_->CreateTextLayout(wszText_, cTextLength_, text->font->textFormat, text->width, text->height, &text->pDWriteTextLayout);
if (FAILED(hr)) ...
DWRITE_CLUSTER_METRICS clusterMetrics[64];
UINT32 actualClusterCount = 0;
text->pDWriteTextLayout->GetClusterMetrics(clusterMetrics, 64, &actualClusterCount);
kuOutPrintf("\n pDWriteTextLayout(%S): cTextLength_(%d) actualClusterCount(%d)", wszText_, (int)cTextLength_, (int)actualClusterCount);
if (ID == 117 || ID == 118) breakpoint

This showed me that

pDWriteTextLayout(tlick: state(0) progress(0.0)): cTextLength_(29) actualClusterCount(29)
pDWriteTextLayout(flick: state(0) progress(0.0)): cTextLength_(29) actualClusterCount(28)

the actualClusterCount is lower here already and that the issue is not in the rendering itself. Looking at the clusterMetrics showed me that the length member of the first clusterMetrics entry on the faulty “flick” string has a length of 2, instead of 1 like all the others. The string I then provided as a fixed literal (forgot what it's called in case that's wrong) L"flick: state(0) progress(0.0)" to prevent string formatting issues and checked the string's codepoints, just in case (102 (f), 108 (l) and so on as it should be).

I made sure that the settings for my IDWriteTextFormat text->font->textFormat are identical on both calls by checking all members (get functions from the interface). I replaced my font Josefin Sans with Arial and guess what, the issue was gone. I thought the font might be the issue in the end then. So I downloaded Noto Sans but the same problem appeared, identical misplacement (and length 2 in the clusterMetrics).

Using the IDWriteTextAnalyzer seems a bit difficult, I attempted that for a bit earlier but I thought I could see something there.

Does anyone have an idea what I could do? The issue is in CreateTextLayout (see code above) in my opinion. Is it a DirectWrite bug?

Thank you.

EDIT: Also, I adjusted text->width and text→height to make sure it's not a size based formatting issue.

None

Advertisement

A guess: Are you hit by kerning and/or ligatures?

Kerning adjusts spacing between characters based on the codepoint pair (space between “f” and “b” is different than between “f” and “i”), ligatures replace some pairs by better code points, eg (“f", “i”) → “fi” (as a single codepoint).

https://en.wikipedia.org/wiki/Kerning

https://en.wikipedia.org/wiki/Ligature_(writing)

Thank you very much for the response!

I was aware of Kerning and used DirectWrite's glyphAdvances to position the glyphs but I did not have Ligature on my mind. This was indeed the issue.

I then realized my approach of using Unicode codepoints was incorrect and that I had to use glyph indices instead and buffer those. This made it a bit more cumbersome and costly but it's working for the time being and I'm happy.

See “ff” in offset and “fl” in flick.

None

This topic is closed to new replies.

Advertisement