Restrict DX run height adjustment to only relevant glyph AND Correct PTY rendering on trailing half of fullwidth glyphs (#4668)

## Summary of the Pull Request
- Height adjustment of a glyph is now restricted to itself in the DX
  renderer instead of applying to the entire run
- ConPTY compensates for drawing the right half of a fullwidth
  character. The entire render base has this behavior restored now as
  well.

## PR Checklist
* [x] Closes #2191
* [x] I work here
* [x] Tests added/passed
* [x] No doc
* [x] Am core contributor.

## Detailed Description of the Pull Request / Additional comments
Two issues:
1. On the DirectX renderer side, when confronted with shrinking a glyph,
   the correction code would apply the shrunken size to the entire run, not
   just the potentially individual glyph that needed to be reduced in size.
   Unfortunately while adjusting the horizontal X width can be done for
   each glyph in a run, the vertical Y height has to be adjusted for an
   entire run. So the solution here was to split the individual glyph
   needing shrinking out of the run into its own run so it can be shrunk.
2. On the ConPTY side, there was a long standing TODO that was never
   completed to deal with a request to draw only the right half of a
   two-column character. This meant that when encountering a request for
   the right half only, we would transmit the entire full character to be
   drawn, left and right halves, struck over the right half position. Now
   we correct the cursor back a position (if space) and draw it out so the
   right half is struck over where we believe the right half should be (and
   the left half is updated as well as a consequence, which should be OK.)

The reason this happens right now is because despite VIM only updating
two cells in the buffer, the differential drawing calculation in the
ConPTY is very simplistic and intersects only rectangles. This means
from the top left most character drawn down to the row/col cursor count
indicator in vim's modeline are redrawn with each character typed. This
catches the line below the edited line in the typing and refreshes it.
But incorrectly.

We need to address making ConPTY smarter about what it draws
incrementally as it's clearly way too chatty. But I plan to do that with
some of the structures I will be creating to solve #778.

## Validation Steps Performed
- Ran the scenario listed in #2191 in vim in the Terminal
- Added unit tests similar to examples given around glyph/text mapping
  in runs from Microsoft community page
This commit is contained in:
Michael Niksa 2020-02-20 16:24:12 -08:00 committed by GitHub
parent 7d6738cde7
commit 4420950337
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 514 additions and 95 deletions

View File

@ -294,6 +294,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{D3EF
build\scripts\Test-WindowsTerminalPackage.ps1 = build\scripts\Test-WindowsTerminalPackage.ps1
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dx.Unit.Tests", "src\renderer\dx\ut_dx\Dx.Unit.Tests.vcxproj", "{95B136F9-B238-490C-A7C5-5843C1FECAC4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty.Tests.Feature", "src\winconpty\ft_pty\winconpty.FeatureTests.vcxproj", "{024052DE-83FB-4653-AEA4-90790D29D5BD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAzBridge", "src\cascadia\TerminalAzBridge\TerminalAzBridge.vcxproj", "{067F0A06-FCB7-472C-96E9-B03B54E8E18D}"
@ -1423,6 +1425,32 @@ Global
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x64.Build.0 = Release|x64
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.ActiveCfg = Release|Win32
{A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.Build.0 = Release|Win32
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x64.ActiveCfg = Release|x64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x86.ActiveCfg = AuditMode|Win32
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x86.Build.0 = AuditMode|Win32
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|Any CPU.ActiveCfg = Debug|Win32
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM64.ActiveCfg = Debug|ARM64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM64.Build.0 = Debug|ARM64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x64.ActiveCfg = Debug|x64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x64.Build.0 = Debug|x64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x86.ActiveCfg = Debug|Win32
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x86.Build.0 = Debug|Win32
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|Any CPU.ActiveCfg = Release|Win32
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM64.ActiveCfg = Release|ARM64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM64.Build.0 = Release|ARM64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x64.ActiveCfg = Release|x64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x64.Build.0 = Release|x64
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x86.ActiveCfg = Release|Win32
{95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x86.Build.0 = Release|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|x64.ActiveCfg = Release|x64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|x86.ActiveCfg = AuditMode|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|x86.Build.0 = AuditMode|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|Any CPU.ActiveCfg = Debug|Win32
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM64.Build.0 = Debug|ARM64
@ -1525,6 +1553,7 @@ Global
{53DD5520-E64C-4C06-B472-7CE62CA539C9} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
{6B5A44ED-918D-4747-BFB1-2472A1FCA173} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
{D3EF7B96-CD5E-47C9-B9A9-136259563033} = {04170EEF-983A-4195-BFEF-2321E5E38A1E}
{95B136F9-B238-490C-A7C5-5843C1FECAC4} = {05500DEF-2294-41E3-AF9A-24E580B82836}
{024052DE-83FB-4653-AEA4-90790D29D5BD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
{067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {59840756-302F-44DF-AA47-441A9D673202}
EndGlobalSection

View File

@ -642,6 +642,11 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
// Ensure that our cluster vector is clear.
clusters.clear();
// Reset our flag to know when we're in the special circumstance
// of attempting to draw only the right-half of a two-column character
// as the first item in our run.
bool trimLeft = false;
// This inner loop will accumulate clusters until the color changes.
// When the color changes, it will save the new color off and break.
do
@ -653,7 +658,33 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
}
// Walk through the text data and turn it into rendering clusters.
clusters.emplace_back(it->Chars(), it->Columns());
// If we're on the first cluster to be added and it's marked as "trailing"
// (a.k.a. the right half of a two column character), then we need some special handling.
if (clusters.empty() && it->DbcsAttr().IsTrailing())
{
// If we have room to move to the left to start drawing...
if (screenPoint.X > 0)
{
// Move left to the one so the whole character can be struck correctly.
--screenPoint.X;
// And tell the next function to trim off the left half of it.
trimLeft = true;
// And add one to the number of columns we expect it to take as we insert it.
clusters.emplace_back(it->Chars(), it->Columns() + 1);
}
else
{
// If we didn't have room, move to the right one and just skip this one.
screenPoint.X++;
continue;
}
}
// Otherwise if it's not a special case, just insert it as is.
else
{
clusters.emplace_back(it->Chars(), it->Columns());
}
// Advance the cluster and column counts.
const auto columnCount = clusters.back().GetColumns();
@ -663,8 +694,7 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine,
} while (it);
// Do the painting.
// TODO: Calculate when trim left should be TRUE
THROW_IF_FAILED(pEngine->PaintBufferLine({ clusters.data(), clusters.size() }, screenPoint, false));
THROW_IF_FAILED(pEngine->PaintBufferLine({ clusters.data(), clusters.size() }, screenPoint, trimLeft));
// If we're allowed to do grid drawing, draw that now too (since it will be coupled with the color data)
if (_pData->IsGridLineDrawingAllowed())

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
@ -154,18 +154,7 @@ CustomTextLayout::CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory
}
// Resequence the resulting runs in order before returning to caller.
const size_t totalRuns = _runs.size();
std::vector<LinkedRun> runs;
runs.resize(totalRuns);
UINT32 nextRunIndex = 0;
for (size_t i = 0; i < totalRuns; ++i)
{
runs.at(i) = _runs.at(nextRunIndex);
nextRunIndex = _runs.at(nextRunIndex).nextRunIndex;
}
_runs.swap(runs);
_OrderRuns();
}
CATCH_RETURN();
return S_OK;
@ -368,6 +357,28 @@ CustomTextLayout::CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory
{
LOG_IF_FAILED(_CorrectGlyphRun(runIndex));
}
// If scale corrections were needed, we need to split the run.
for (auto [index, scale] : _glyphScaleCorrections)
{
// Split after the adjustment first so it
// takes a copy of all the run properties before we modify them.
// GH 4665: This is the other half of the potential future perf item.
// If glyphs needing the same scale are coalesced, we could
// break fewer times and have fewer runs.
_SetCurrentRun(index + 1);
_SplitCurrentRun(index + 1);
// Now split just this glyph off.
_SetCurrentRun(index);
_SplitCurrentRun(index);
// Get the run with the one glyph and adjust the scale.
auto& run = _GetCurrentRun();
run.fontScale = scale;
}
_OrderRuns();
}
CATCH_RETURN();
return S_OK;
@ -376,90 +387,94 @@ CustomTextLayout::CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory
// Routine Description:
// - Adjusts the advances for each glyph in the run so it fits within a fixed-column count of cells.
// Arguments:
// - runIndex - The ID number of the internal runs array to use while shaping
// - runIndex - The ID number of the internal runs array to use while shaping.
// Return Value:
// - S_OK or suitable DirectWrite or STL error code
[[nodiscard]] HRESULT CustomTextLayout::_CorrectGlyphRun(const UINT32 runIndex) noexcept
try
{
try
const Run& run = _runs.at(runIndex);
if (run.textLength == 0)
{
Run& run = _runs.at(runIndex);
if (run.textLength == 0)
{
return S_FALSE; // Nothing to do..
}
// We're going to walk through and check for advances that don't match the space that we expect to give out.
DWRITE_FONT_METRICS1 metrics;
run.fontFace->GetMetrics(&metrics);
// Walk through advances and space out characters that are too small to consume their box.
for (auto i = run.glyphStart; i < (run.glyphStart + run.glyphCount); i++)
{
// Advance is how wide in pixels the glyph is
auto& advance = _glyphAdvances.at(i);
// Offsets is how far to move the origin (in pixels) from where it is
auto& offset = _glyphOffsets.at(i);
// Get how many columns we expected the glyph to have and mutiply into pixels.
const auto columns = _textClusterColumns.at(i);
const auto advanceExpected = static_cast<float>(columns * _width);
// If what we expect is bigger than what we have... pad it out.
if (advanceExpected > advance)
{
// Get the amount of space we have leftover.
const auto diff = advanceExpected - advance;
// Move the X offset (pixels to the right from the left edge) by half the excess space
// so half of it will be left of the glyph and the other half on the right.
offset.advanceOffset += diff / 2;
// Set the advance to the perfect width we want.
advance = advanceExpected;
}
// If what we expect is smaller than what we have... rescale the font size to get a smaller glyph to fit.
else if (advanceExpected < advance)
{
// We need to retrieve the design information for this specific glyph so we can figure out the appropriate
// height proportional to the width that we desire.
INT32 advanceInDesignUnits;
RETURN_IF_FAILED(run.fontFace->GetDesignGlyphAdvances(1, &_glyphIndices.at(i), &advanceInDesignUnits));
// When things are drawn, we want the font size (as specified in the base font in the original format)
// to be scaled by some factor.
// i.e. if the original font size was 16, we might want to draw this glyph with a 15.2 size font so
// the width (and height) of the glyph will shrink to fit the monospace cell box.
// This pattern is copied from the DxRenderer's algorithm for figuring out the font height for a specific width
// and was advised by the DirectWrite team.
const float widthAdvance = static_cast<float>(advanceInDesignUnits) / metrics.designUnitsPerEm;
const auto fontSizeWant = advanceExpected / widthAdvance;
run.fontScale = fontSizeWant / _format->GetFontSize();
// Set the advance to the perfect width that we want.
advance = advanceExpected;
}
}
// Certain fonts, like Batang, contain glyphs for hidden control
// and formatting characters. So we'll want to explicitly force their
// advance to zero.
// I'm leaving this here for future reference, but I don't think we want invisible glyphs for this renderer.
//if (run.script.shapes & DWRITE_SCRIPT_SHAPES_NO_VISUAL)
//{
// std::fill(_glyphAdvances.begin() + glyphStart,
// _glyphAdvances.begin() + glyphStart + actualGlyphCount,
// 0.0f
// );
//}
return S_FALSE; // Nothing to do..
}
CATCH_RETURN();
// We're going to walk through and check for advances that don't match the space that we expect to give out.
DWRITE_FONT_METRICS1 metrics;
run.fontFace->GetMetrics(&metrics);
// Walk through advances and space out characters that are too small to consume their box.
for (auto i = run.glyphStart; i < (run.glyphStart + run.glyphCount); i++)
{
// Advance is how wide in pixels the glyph is
auto& advance = _glyphAdvances.at(i);
// Offsets is how far to move the origin (in pixels) from where it is
auto& offset = _glyphOffsets.at(i);
// Get how many columns we expected the glyph to have and mutiply into pixels.
const auto columns = _textClusterColumns.at(i);
const auto advanceExpected = static_cast<float>(columns * _width);
// If what we expect is bigger than what we have... pad it out.
if (advanceExpected > advance)
{
// Get the amount of space we have leftover.
const auto diff = advanceExpected - advance;
// Move the X offset (pixels to the right from the left edge) by half the excess space
// so half of it will be left of the glyph and the other half on the right.
offset.advanceOffset += diff / 2;
// Set the advance to the perfect width we want.
advance = advanceExpected;
}
// If what we expect is smaller than what we have... rescale the font size to get a smaller glyph to fit.
else if (advanceExpected < advance)
{
// We need to retrieve the design information for this specific glyph so we can figure out the appropriate
// height proportional to the width that we desire.
INT32 advanceInDesignUnits;
RETURN_IF_FAILED(run.fontFace->GetDesignGlyphAdvances(1, &_glyphIndices.at(i), &advanceInDesignUnits));
// When things are drawn, we want the font size (as specified in the base font in the original format)
// to be scaled by some factor.
// i.e. if the original font size was 16, we might want to draw this glyph with a 15.2 size font so
// the width (and height) of the glyph will shrink to fit the monospace cell box.
// This pattern is copied from the DxRenderer's algorithm for figuring out the font height for a specific width
// and was advised by the DirectWrite team.
const float widthAdvance = static_cast<float>(advanceInDesignUnits) / metrics.designUnitsPerEm;
const auto fontSizeWant = advanceExpected / widthAdvance;
const auto scaleProposed = fontSizeWant / _format->GetFontSize();
// Store the glyph scale correction for future run breaking
// GH 4665: In theory, we could also store the length of the new run and coalesce
// in case two adjacent glyphs need the same scale factor.
_glyphScaleCorrections.push_back(std::tuple{ i, scaleProposed });
// Set the advance to the perfect width that we want before figuring out if the scale factor doesn't match this run
advance = advanceExpected;
}
}
// Certain fonts, like Batang, contain glyphs for hidden control
// and formatting characters. So we'll want to explicitly force their
// advance to zero.
// I'm leaving this here for future reference, but I don't think we want invisible glyphs for this renderer.
//if (run.script.shapes & DWRITE_SCRIPT_SHAPES_NO_VISUAL)
//{
// std::fill(_glyphAdvances.begin() + glyphStart,
// _glyphAdvances.begin() + glyphStart + actualGlyphCount,
// 0.0f
// );
//}
return S_OK;
}
CATCH_RETURN();
// Routine Description:
// - Takes the analyzed and shaped textual information from the layout process and
@ -951,6 +966,18 @@ CustomTextLayout::CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory
return _runs.at(originalRunIndex);
}
// Routine Description:
// - Retrieves the current run according to the internal
// positioning set by Set/Split Current Run methods.
// Arguments:
// - <none>
// Return Value:
// - Mutable reference ot the current run.
[[nodiscard]] CustomTextLayout::LinkedRun& CustomTextLayout::_GetCurrentRun()
{
return _runs.at(_runIndex);
}
// Routine Description:
// - Move the current run to the given position.
// Since the analyzers generally return results in a forward manner,
@ -1007,5 +1034,126 @@ void CustomTextLayout::_SplitCurrentRun(const UINT32 splitPosition)
frontHalf.textLength = splitPoint;
frontHalf.nextRunIndex = gsl::narrow<UINT32>(totalRuns);
_runIndex = gsl::narrow<UINT32>(totalRuns);
// If there is already a glyph mapping in these runs,
// we need to correct it for the split as well.
// See also (for NxM):
// https://social.msdn.microsoft.com/Forums/en-US/993365bc-8689-45ff-a675-c5ed0c011788/dwriteglyphrundescriptionclustermap-explained
if (frontHalf.glyphCount > 0)
{
// Starting from this:
// TEXT (_text)
// f i ñ e
// CLUSTERMAP (_glyphClusters)
// 0 0 1 3
// GLYPH INDICIES (_glyphIndicies)
// 19 81 23 72
// With _runs length = 1
// _runs(0):
// - Text Index: 0
// - Text Length: 4
// - Glyph Index: 0
// - Glyph Length: 4
//
// If we split at text index = 2 (between i and ñ)...
// ... then this will be the state after the text splitting above:
//
// TEXT (_text)
// f i ñ e
// CLUSTERMAP (_glyphClusters)
// 0 0 1 3
// GLYPH INDICIES (_glyphIndicies)
// 19 81 23 72
// With _runs length = 2
// _runs(0):
// - Text Index: 0
// - Text Length: 2
// - Glyph Index: 0
// - Glyph Length: 4
// _runs(1):
// - Text Index: 2
// - Text Length: 2
// - Glyph Index: 0
// - Glyph Length: 4
//
// Notice that the text index/length values are correct,
// but we haven't fixed up the glyph index/lengths to match.
// We need it to say:
// With _runs length = 2
// _runs(0):
// - Text Index: 0
// - Text Length: 2
// - Glyph Index: 0
// - Glyph Length: 1
// _runs(1):
// - Text Index: 2
// - Text Length: 2
// - Glyph Index: 1
// - Glyph Length: 3
//
// Which means that the cluster map value under the beginning
// of the right-hand text range is our offset to fix all the values.
// In this case, that's 1 corresponding with the ñ.
const auto mapOffset = _glyphClusters.at(backHalf.textStart);
// The front half's glyph start index (position in _glyphIndicies)
// stays the same.
// The front half's glyph count (items in _glyphIndicies to consume)
// is the offset value as that's now one past the end of the front half.
// (and count is end index + 1)
frontHalf.glyphCount = mapOffset;
// The back half starts at the index that's one past the end of the front
backHalf.glyphStart = mapOffset;
// And the back half count (since it was copied from the front half above)
// now just needs to be subtracted by how many we gave the front half.
backHalf.glyphCount -= mapOffset;
// The CLUSTERMAP is also wrong given that it is relative
// to each run. And now there are two runs so the map
// value under the ñ and e need to updated to be relative
// to the text index "2" now instead of the original.
//
// For the entire range of the back half, we need to walk through and
// slide all the glyph mapping values to be relative to the new
// backHalf.glyphStart, or adjust it by the offset we just set it to.
const auto updateBegin = _glyphClusters.begin() + backHalf.textStart;
std::for_each_n(updateBegin, backHalf.textLength, [mapOffset](UINT16& n) {
n -= mapOffset;
});
}
}
// Routine Description:
// - Takes the linked runs stored in the state variable _runs
// and ensures that their vector/array indexes are in order in which they're drawn.
// - This is to be used after splitting and reordering them with the split/select functions
// as those manipulate the runs like a linked list (instead of an ordered array)
// while splitting to reduce copy overhead and just reorder them when complete with this func.
// Arguments:
// - <none> - Manipulates _runs variable.
// Return Value:
// - <none>
void CustomTextLayout::_OrderRuns()
{
const size_t totalRuns = _runs.size();
std::vector<LinkedRun> runs;
runs.resize(totalRuns);
UINT32 nextRunIndex = 0;
for (UINT32 i = 0; i < totalRuns; ++i)
{
runs.at(i) = _runs.at(nextRunIndex);
runs.at(i).nextRunIndex = i + 1;
nextRunIndex = _runs.at(nextRunIndex).nextRunIndex;
}
runs.back().nextRunIndex = 0;
_runs.swap(runs);
}
#pragma endregion

View File

@ -117,8 +117,10 @@ namespace Microsoft::Console::Render
};
[[nodiscard]] LinkedRun& _FetchNextRun(UINT32& textLength);
[[nodiscard]] LinkedRun& _GetCurrentRun();
void _SetCurrentRun(const UINT32 textPosition);
void _SplitCurrentRun(const UINT32 splitPosition);
void _OrderRuns();
[[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeFontFallback(IDWriteTextAnalysisSource* const source, UINT32 textPosition, UINT32 textLength);
[[nodiscard]] HRESULT STDMETHODCALLTYPE _SetMappedFont(UINT32 textPosition, UINT32 textLength, IDWriteFont* const font, FLOAT const scale);
@ -164,9 +166,28 @@ namespace Microsoft::Console::Render
UINT32 _runIndex;
// Glyph shaping results
std::vector<DWRITE_GLYPH_OFFSET> _glyphOffsets;
// Clusters are complicated. They're in respect to each individual run.
// The offsets listed here are in respect to the _text string, but from the beginning index of
// each run.
// That means if we have two runs, we will see 0 1 2 3 4 0 1 2 3 4 5 6 7... in this clusters count.
std::vector<UINT16> _glyphClusters;
// This appears to be the index of the glyph inside each font.
std::vector<UINT16> _glyphIndices;
std::vector<float> _glyphAdvances;
// These are used to further break the runs apart and adjust the font size so glyphs fit inside the cells.
std::vector<std::tuple<UINT32, float>> _glyphScaleCorrections;
#ifdef UNIT_TESTING
public:
CustomTextLayout() = default;
friend class CustomTextLayoutTests;
#endif
};
}

View File

@ -21,7 +21,7 @@
using namespace DirectX;
std::atomic<size_t> Microsoft::Console::Render::DxEngine::_tracelogCount{ 0 };
#pragma warning(suppress : 26477)
#pragma warning(suppress : 26477) // We don't control tracelogging macros
TRACELOGGING_DEFINE_PROVIDER(g_hDxRenderProvider,
"Microsoft.Windows.Terminal.Renderer.DirectX",
// {c93e739e-ae50-5a14-78e7-f171e947535d}

View File

@ -1,2 +1,3 @@
DIRS= \
lib
lib \
ut_dx \

View File

@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "WexTestClass.h"
#include "..\..\inc\consoletaeftemplates.hpp"
#include "..\CustomTextLayout.h"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace Microsoft::Console::Render;
class Microsoft::Console::Render::CustomTextLayoutTests
{
TEST_CLASS(CustomTextLayoutTests);
TEST_METHOD(OrderRuns)
{
CustomTextLayout layout;
// Create linked list runs where a --> c --> b
CustomTextLayout::LinkedRun a;
a.nextRunIndex = 2;
a.textStart = 0;
CustomTextLayout::LinkedRun b;
b.nextRunIndex = 0;
b.textStart = 20;
CustomTextLayout::LinkedRun c;
c.nextRunIndex = 1;
c.textStart = 10;
// but insert them into the runs as a, b, c
layout._runs.push_back(a);
layout._runs.push_back(b);
layout._runs.push_back(c);
// Now order them.
layout._OrderRuns();
// Validate that they've been reordered to a, c, b by index so they can be iterated to go in order.
// The text starts should be in order 0, 10, 20.
// The next run indexes should point at each other.
VERIFY_ARE_EQUAL(a.textStart, layout._runs.at(0).textStart);
VERIFY_ARE_EQUAL(1u, layout._runs.at(0).nextRunIndex);
VERIFY_ARE_EQUAL(c.textStart, layout._runs.at(1).textStart);
VERIFY_ARE_EQUAL(2u, layout._runs.at(1).nextRunIndex);
VERIFY_ARE_EQUAL(b.textStart, layout._runs.at(2).textStart);
VERIFY_ARE_EQUAL(0u, layout._runs.at(2).nextRunIndex);
}
TEST_METHOD(SplitCurrentRunIncludingGlyphs)
{
CustomTextLayout layout;
// Put glyph data into the layout as if we've already gone through analysis.
// This data matches the verbose comment from the CustomTextLayout.cpp file
// and is derived from
// https://social.msdn.microsoft.com/Forums/en-US/993365bc-8689-45ff-a675-c5ed0c011788/dwriteglyphrundescriptionclustermap-explained
layout._text = L"fiñe";
layout._glyphIndices.push_back(19);
layout._glyphIndices.push_back(81);
layout._glyphIndices.push_back(23);
layout._glyphIndices.push_back(72);
layout._glyphClusters.push_back(0);
layout._glyphClusters.push_back(0);
layout._glyphClusters.push_back(1);
layout._glyphClusters.push_back(3);
// Set up the layout to have a run that already has glyph data inside of it.
CustomTextLayout::LinkedRun run;
run.textStart = 0;
run.textLength = 4;
run.glyphStart = 0;
run.glyphCount = 4;
layout._runs.push_back(run);
// Now split it in the middle per the comment example
layout._SetCurrentRun(2);
layout._SplitCurrentRun(2);
// And validate that the split state matches what we expected.
VERIFY_ARE_EQUAL(0u, layout._runs.at(0).textStart);
VERIFY_ARE_EQUAL(2u, layout._runs.at(0).textLength);
VERIFY_ARE_EQUAL(0u, layout._runs.at(0).glyphStart);
VERIFY_ARE_EQUAL(1u, layout._runs.at(0).glyphCount);
VERIFY_ARE_EQUAL(2u, layout._runs.at(1).textStart);
VERIFY_ARE_EQUAL(2u, layout._runs.at(1).textLength);
VERIFY_ARE_EQUAL(1u, layout._runs.at(1).glyphStart);
VERIFY_ARE_EQUAL(3u, layout._runs.at(1).glyphCount);
}
};

View File

@ -0,0 +1,12 @@
//Autogenerated file name + version resource file for Device Guard whitelisting effort
#include <windows.h>
#include <ntverp.h>
#define VER_FILETYPE VFT_UNKNOWN
#define VER_FILESUBTYPE VFT2_UNKNOWN
#define VER_FILEDESCRIPTION_STR ___TARGETNAME
#define VER_INTERNALNAME_STR ___TARGETNAME
#define VER_ORIGINALFILENAME_STR ___TARGETNAME
#include "common.ver"

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{95B136F9-B238-490C-A7C5-5843C1FECAC4}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>DxUnitTests</RootNamespace>
<ProjectName>Dx.Unit.Tests</ProjectName>
<TargetName>Dx.Unit.Tests</TargetName>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="CustomTextLayoutTests.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\types\lib\types.vcxproj">
<Project>{18d09a24-8240-42d6-8cb6-236eee820263}</Project>
</ProjectReference>
<ProjectReference Include="..\..\base\lib\base.vcxproj">
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
</ProjectReference>
<ProjectReference Include="..\lib\dx.vcxproj">
<Project>{48D21369-3D7B-4431-9967-24E81292CF62}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\precomp.h" />
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.build.tests.props" />
</Project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="ProductBuild" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(NTMAKEENV)\UniversalTest\Microsoft.TestInfrastructure.UniversalTest.props" />
</Project>

View File

@ -0,0 +1,34 @@
!include ..\..\project.unittest.inc
# -------------------------------------
# Program Information
# -------------------------------------
TARGETNAME = Microsoft.Console.Renderer.Dx.UnitTests
TARGETTYPE = DYNLINK
DLLDEF =
# -------------------------------------
# Sources, Headers, and Libraries
# -------------------------------------
SOURCES = \
$(SOURCES) \
CustomTextLayoutTests.cpp \
DefaultResource.rc \
INCLUDES = \
.. \
$(INCLUDES) \
TARGETLIBS = \
$(WINCORE_OBJ_PATH)\console\open\src\renderer\dx\lib\$(O)\ConRenderDx.lib \
$(TARGETLIBS) \
# -------------------------------------
# Localization
# -------------------------------------
# Autogenerated. Sets file name for Device Guard whitelisting effort, used in RC.exe.
C_DEFINES = $(C_DEFINES) -D___TARGETNAME="""$(TARGETNAME).$(TARGETTYPE)"""
MUI_VERIFY_NO_LOC_RESOURCE = 1

View File

@ -73,7 +73,7 @@ void RenderTracing::TraceString(const std::string_view& instr) const
const char* const seq = _seq.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceString",
TraceLoggingString(seq),
TraceLoggingUtf8String(seq),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else