1

I am running into inconsistencies with how glyphs are spaced in some situations.

I am using DirectWrite with a IDWriteFontFace and using the GetGlyphIndices with GetDesignGlyphMetrics to get the metrics of the glyphs so I can take each glyph, store it as an image, then reconstruct the whole string using the glyph information (advance, lsb, rsb, etc). I am rendering each glyph using ID2D1RenderTarget.DrawTextLayout with WIC bitmaps. While 99% of tests I have done seem to work, I am running into inconsistent spacing and I have no idea what is causing it. Maybe someone can shed some light, but I am not sure where to start.

From my basic interpretation the font layout is composed of rendering the glyphs as lsb + width, and then the place to put the next character is called the advance and the advance width is composed of lsb, glyph width, rsb.


I am using this Zapfino font, because it seems fairly complex and a good metric to test if things are rendering correctly.

So here are some rendering comparisons line by line:

  1. correct render: Here are two different strings left and right, correctly rendered through paint.net

  2. my render: I include the advance placements (red) and the starting position (yellow). On the left, we have World! rendered, and the orld! is correctly placed, but the distance between W and o is too large now compared to the correct rendering.

    On the right side we have Hold! rendered and for the most part is consistent with the paint.net render.

  3. I overlap both to give a better idea of comparison. I align the left of the orld! so you can see that atleast that part is consistent, it's the W is off. Right side is almost identical.

  4. Paint.net shows the cursor position in what I assume is the advance. You can see that the advance placement, the o is right on top of it. But with o on the right side, you can see it has distance from advance to the o. When rendering the o at the front (5), next to a different character, or by itself, it still has that distance. I believe (appearance wise) it's the LSB that seems to shrink?

  5. orld! rendered with paint.net with the cursor. You can see the o is intersecting the advance. Some other examples, We has the same problem as Wo, shrunk LSB, but for instance in Wi, the glyph has proper distance from the advance, like it would be on its own.

enter image description here

What is this effect called where it shrinks the LSB or moves the placement of the glyph depending on characters? How can I detect such behavior through glyph data?

Charlie
  • 680
  • 4
  • 11
  • Are you processing one character at a time, i.e. getting the glyph and metrics for each character and then drawing each to the render target? If so, that's not what you should be doing. – Peter Constable Jun 06 '20 at 23:16
  • Yes, currently I need to render each glyph separately. To do that, I am taking the rendered glyphs and arranging them based on the glyph and layout metrics. – Charlie Jun 17 '20 at 19:09
  • Rendering glyph-by-glyph significantly reduces functionality. For instance, your example image shows a script / calligraphic typeface with connections between some pairs of letters. The font probably has alternate glyphs that provide appropriate connecting strokes based on the adjacent glyphs. But you won't get contextual substitutions if you are processing one glyph at a time. OpenType and DirectWrite are really geared toward rendering _runs_ of text. – Peter Constable Jun 17 '20 at 19:53
  • I realize that I will have to sacrifice some functionality. Unfortunately for the use case I am using, we have to separate each glyph into it's own BGRA data to be used as a texture in OpenGL. Otherwise each text draw would be considered a separate texture/draw call which would make it unusable. With this method we can have text rendering without sacrificing performance. I was looking for ways to expand it for some common features, such as kerning. `TryGetFontTable` should get me closer atleast. – Charlie Jun 17 '20 at 20:26

2 Answers2

1

Shaping takes care of GPOS kerning automatically, you don't need to access values directly. Layout goes through shaping obviously, applying kerning this way. IDWriteTextLayout1 interface allows enabling pair kerning per range, but that probably only used if no kerning GPOS feature is available.

Basically, rendering with plain design advances and nominal glyphs is only useful for demonstration purposes, everything else should go through shaping process, either with layout API or manually through GetGlyphs()/GetGlyphPlacements().

bunglehead
  • 1,104
  • 1
  • 14
  • 22
0

I figured it out, it is kerning. I was thrown off because when I checked the font, DirectWrite returns 0 for HasKerningPairs. However, I opened up the font in a font editor and could see it does have kerning pairs. Further looking at the docs it says:

Newer fonts may have only GPOS kerning instead of the legacy pair-table kerning. Such fonts, like Gabriola, will only return 0's for adjustments. GetKerningPairAdjustments doesn't virtualize and flatten these GPOS entries into kerning pairs.

They don't offer any other way to grab them, so this is a dead end. There is no way to get these variables from the font in DirectWrite, which I find odd because the built in TextLayout makes these adjustments internally. However, as such there is no solution to this issue with the API provided by DirectWrite, this can be marked solved and unresolved.

Charlie
  • 680
  • 4
  • 11
  • 1
    Processing GSUB and GPOS tables yourself is _very non-trivial_. If you really want to do it, [start here to learn details](https://learn.microsoft.com/en-us/typography/opentype/spec/ttochap1). You can use [IDWriteFontFace::TryGetFontTable()](https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontface-trygetfonttable) to get the raw tables to process. – Peter Constable Jun 06 '20 at 23:19
  • "the built in TextLayout makes these adjustments internally" - `IDWriteTextLayout` just defers all GPOS work to the `GetGlyphs()` call, as it doesn't know anything about GPOS itself. – Dwayne Robinson Jun 12 '20 at 02:30
  • What part of the `GetGlyphs` does GPOS get processed in the `TextAnalyzer`? From looking at it, there are a lot of things client implemented. Just want to know if I'd have to implement the whole parsing of the opentype tables somewhere in the client to be able to do this or if I can rely on `TextAnalyzer` to take care of it? – Charlie Jun 24 '20 at 18:55