Repository: microsoft/terminal (language: cpp)

Issue:
Combining Diacritic Marks are broken under AtlasEngine
### Windows Terminal version

1.13

### Windows build number

Any

### Other Software

_No response_

### Steps to reproduce

Type

```powershell
echo "[e`u{0301}`u{0301}]"
```

In PowerShell v6+

### Expected Behavior

Text like this should appear in the window:

![image](https://user-images.githubusercontent.com/50213618/145647680-bf033226-e39b-48a3-aa40-85f5281a24df.png)

The two acute accents should stack above letter `e`.

### Actual Behavior

![image](https://user-images.githubusercontent.com/50213618/145647627-1947af02-2204-479a-8e00-6c30d5f2d94e.png)

The acute accents are not properly placed.


Discussion / hints:
It looks like AtlasEngine doesn't do GPOS at all -- however GPOS is the key to support combining diacritics.
Is it related to #3546?
@Perlence It's somewhat related but the cause is different. It's not "expecting the quantity of glyphs [to] equal [...] the cells allocated for each line". It actually handles unicode drawing correctly, because it doesn't do its own unicode drawing in the first place (unlike our previous code). But it's missing a piece of text analysis to recognize combining diacritics in order to not split text in the wrong locations.

The Jewish word "ﭏלה‎" consists of the 3 clusters "ל", "ﭏ" and "ה" which can be cached independently without affecting text layout. The first one ("ﭏ") is a ligature consisting of "א" and "ל" and may _not_ be split up. The "AtlasEngine" has a system to break unicode text into exactly such "clusters" of related characters. Such clusters are cached together so that they only need to be drawn once and all future drawings can refer to the cached one.

What this issue implies is that the `canBreakShapingAfter` field of `DWRITE_SHAPING_TEXT_PROPERTIES` isn't enough to decide whether a run of glyphs forms a "cluster", because it doesn't handle combining diacritics, meaning we unfortunately will have to call `IDWriteTextAnalyzer::GetGlyphPlacements`. This is quite unfortunate, because we only need to draw text once and most of the time we actually only want to segment text without being interested in almost everything `GetGlyphPlacements` returns.

I'll tackle this issue in the near term though.

Fix patch:
diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp
index 4ef5567724d..dd9267698b3 100644
--- a/src/renderer/atlas/AtlasEngine.cpp
+++ b/src/renderer/atlas/AtlasEngine.cpp
@@ -904,6 +904,8 @@ void AtlasEngine::_recreateSizeDependentResources()
         _api.textProps = Buffer<DWRITE_SHAPING_TEXT_PROPERTIES>{ projectedTextSize };
         _api.glyphIndices = Buffer<u16>{ projectedGlyphSize };
         _api.glyphProps = Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES>{ projectedGlyphSize };
+        _api.glyphAdvances = Buffer<f32>{ projectedGlyphSize };
+        _api.glyphOffsets = Buffer<DWRITE_GLYPH_OFFSET>{ projectedGlyphSize };
 
         D3D11_BUFFER_DESC desc;
         desc.ByteWidth = gsl::narrow<u32>(totalCellCount * sizeof(Cell)); // totalCellCount can theoretically be UINT32_MAX!
@@ -1370,6 +1372,37 @@ void AtlasEngine::_flushBufferLine()
                         break;
                     }
 
+                    if (_api.glyphAdvances.size() < actualGlyphCount)
+                    {
+                        // Grow the buffer by at least 1.5x and at least of `actualGlyphCount` items.
+                        // The 1.5x growth ensures we don't reallocate every time we need 1 more slot.
+                        auto size = _api.glyphAdvances.size();
+                        size = size + (size >> 1);
+                        size = std::max<size_t>(size, actualGlyphCount);
+                        _api.glyphAdvances = Buffer<f32>{ size };
+                        _api.glyphOffsets = Buffer<DWRITE_GLYPH_OFFSET>{ size };
+                    }
+
+                    THROW_IF_FAILED(_sr.textAnalyzer->GetGlyphPlacements(
+                        /* textString          */ _api.bufferLine.data() + a.textPosition,
+                        /* clusterMap          */ _api.clusterMap.data(),
+                        /* textProps           */ _api.textProps.data(),
+                        /* textLength          */ a.textLength,
+                        /* glyphIndices        */ _api.glyphIndices.data(),
+                        /* glyphProps          */ _api.glyphProps.data(),
+                        /* glyphCount          */ actualGlyphCount,
+                        /* fontFace            */ mappedFontFace.get(),
+                        /* fontEmSize          */ _api.fontMetrics.fontSizeInDIP,
+                        /* isSideways          */ false,
+                        /* isRightToLeft       */ a.bidiLevel & 1,
+                        /* scriptAnalysis      */ &scriptAnalysis,
+                        /* localeName          */ nullptr,
+                        /* features            */ &features,
+                        /* featureRangeLengths */ &featureRangeLengths,
+                        /* featureRanges       */ featureRanges,
+                        /* glyphAdvances       */ _api.glyphAdvances.data(),
+                        /* glyphOffsets        */ _api.glyphOffsets.data()));
+
                     _api.textProps[a.textLength - 1].canBreakShapingAfter = 1;
 
                     size_t beg = 0;
diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h
index 84ca8d59296..df4f095e42c 100644
--- a/src/renderer/atlas/AtlasEngine.h
+++ b/src/renderer/atlas/AtlasEngine.h
@@ -724,6 +724,8 @@ namespace Microsoft::Console::Render
             Buffer<DWRITE_SHAPING_TEXT_PROPERTIES> textProps;
             Buffer<u16> glyphIndices;
             Buffer<DWRITE_SHAPING_GLYPH_PROPERTIES> glyphProps;
+            Buffer<f32> glyphAdvances;
+            Buffer<DWRITE_GLYPH_OFFSET> glyphOffsets;
             std::vector<DWRITE_FONT_FEATURE> fontFeatures; // changes are flagged as ApiInvalidations::Font|Size
             std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues; // changes are flagged as ApiInvalidations::Font|Size
             FontMetrics fontMetrics; // changes are flagged as ApiInvalidations::Font|Size


Test patch: