Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DBCS characters rendered incorrectly when scrolling horizontally in conhost #8390

Closed
j4james opened this issue Nov 24, 2020 · 4 comments · Fixed by #8438
Closed

DBCS characters rendered incorrectly when scrolling horizontally in conhost #8390

j4james opened this issue Nov 24, 2020 · 4 comments · Fixed by #8438
Labels
Area-Rendering Text rendering, emoji, complex glyph & font-fallback issues Impact-Compatibility Sure don't work like it used to. Impact-Correctness It be wrong. Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conhost For issues in the Console codebase Resolution-Fix-Committed Fix is checked in, but it might be 3-4 weeks until a release.
Milestone

Comments

@j4james
Copy link
Collaborator

j4james commented Nov 24, 2020

Environment

Windows build number: Version 10.0.18363.1198
Windows Terminal version (if applicable): Commit 1fbcf34

Steps to reproduce

  1. Build OpenConsole.
  2. Run Host.EXE with a bash shell.
  3. Open the console properties and turn off Wrap text output on resize.
  4. Also make sure the Screen Buffer Size is wider than the Window Size so there's a horizontal scrollbar.
  5. Enter some DBCS characters at the prompt (the example below uses 子小尒尢).
  6. Wiggle the horizontal scroll bar backwards and forwards a few times, so those characters are being scrolled in and out of view.

I should add that I just picked these characters at random from charmap, so my apologies if they mean anything offensive.

Expected behavior

The characters should always be rendered correctly.

image

Actual behavior

Sometimes the characters get chopped in half, or rendered in the wrong place so they're overlapping other characters.

image

I can't reproduce this in the conhost that comes with my current version of Windows, so it looks like it might be a regression. But I also checked out the initial commit of OpenConsole and it seems to have already been broken then. Is it possible my version of Windows is so old that it's prior to the first open source release?

Anyway, I think I know what the problem is - a couple of problems actually. I'll try and write up an explanation later with my proposed fix.

@ghost ghost added Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting Needs-Tag-Fix Doesn't match tag requirements labels Nov 24, 2020
@j4james
Copy link
Collaborator Author

j4james commented Nov 24, 2020

As far as I can tell, the problem is in the Renderer::_PaintBufferOutputHelper method. When the content starts with the trailing half of a DBCS character, it tries to move the screenPoint back a position, so it can safely render the full character. See below:

// 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.
columnCount = it->Columns() + 1;
_clusterBuffer.emplace_back(it->Chars(), columnCount);
}
else
{
// If we didn't have room, move to the right one and just skip this one.
screenPoint.X++;
continue;
}

But as you can see, if the X position is already in column 0, it instead moves forward one position and "skips" that character. At least that's the intention, but the code doesn't actually increment the iterator, so it just ends up rendering the character in the wrong place. And even if it worked as intended, at best you're going to end up with half a character not rendered.

My suggested fix for this is just to drop that check. If the X position is already in column 0, let it go negative. It should then paint the character exactly as expected, with half of it off screen. At least that seems to work for the GDI engine.

But that brings us to the next problem. When we're dealing with the second half of a DBCS char, the it->Columns() value is 1, so we set the columnCount to it->Columns() + 1 to allow for the fact that it's actually going to occupy two cells. However, that columnCount variable is also used to increment the iterator, so it ends up incrementing too much, and any subsequent characters will be rendered in the wrong place, or possibly skipped altogether. See below:

// Advance the cluster and column counts.
it += columnCount > 0 ? columnCount : 1; // prevent infinite loop for no visible columns
cols += columnCount;

My suggested fix is to use the it->Columns() value itself for the increment, since that should always be correct. So something like this:

it += std::max<size_t>(it->Columns(), 1);

I'm not sure if we still need to make sure the increment is non-zero, but it probably wouldn't harm.

@DHowett DHowett added Impact-Compatibility Sure don't work like it used to. Impact-Correctness It be wrong. Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conhost For issues in the Console codebase Area-Rendering Text rendering, emoji, complex glyph & font-fallback issues labels Nov 25, 2020
@ghost ghost removed the Needs-Tag-Fix Doesn't match tag requirements label Nov 25, 2020
@DHowett
Copy link
Member

DHowett commented Nov 25, 2020

I believe @miniksa changed something in here recently (after 1836x, as you've observed) that fixed an issue with ConPTY; blame might be enlightening here. 😄

Otherwise, this looks like the right fix. I hope we have test coverage for the ConPTY thing!

@DHowett DHowett removed the Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting label Nov 25, 2020
@DHowett DHowett added this to the Windows vNext milestone Nov 25, 2020
@ghost ghost added the In-PR This issue has a related PR label Nov 29, 2020
@j4james
Copy link
Collaborator Author

j4james commented Nov 29, 2020

@DHowett I think the PR you're thinking of is #4668, but that didn't introduce this bug. It was an attempt to fix a related issue with fullwidth glyphs (#2191), but didn't correctly handle this particular case. I've now created a PR with my proposed changes, and confirmed that the test cases described in #2191 are still working correctly with those changes.

However, I should note that the #2191 issue is no longer reproducible in Terminal even with this whole section commented out (i.e. the fix introduced in #4668). I suspect that might be because the DX engine now always invalidates a full line at a time. I could reproduce the problem with the GDI engine in conhost, though, so could confirm that the fix does still work there.

@ghost ghost closed this as completed in #8438 Dec 4, 2020
ghost pushed a commit that referenced this issue Dec 4, 2020
When the renderer is called on to render part of a line starting halfway
through a DBCS character (as can occur in conhost when the viewport is
offset horizontally), it could result in the character not being
displayed, and/or with following the characters drawn in the wrong
place. This PR is an attempt to fix those problems. 

The original code for handling the trailing half of fullwidth glyphs was
introduced in PR #4668 to fix issue #2191.

When the content being rendered starts with the trailing half of a DBCS
character, the renderer tries to move the `screenPoint` back a position,
so it can instead render the full character, but instructing the render
engine to trim off the left half of it.

If the X position was already in column 0, though, it would instead move
forward one position, intending to skip that character. At best this
would mean the half character wouldn't be rendered, but since the
iterator wasn't incremented correctly, it actually just ended up
rendering the character in the wrong place.

The fix for this was simply to drop the check for the X position being
in column 0, and allow it go negative. The rendering engine would then
just start rendering the character partially off screen, and only the
second half of it would be displayed, which is exactly what is needed.

The second problem was that the code incrementing the iterator was using
the `columnCount` variable rather than the `it->Columns()` value for the
current position. When dealing with the trailing half of a DBCS
character, the `columnCount` is 2, but the `Columns()` value is 1. If
you use the former rather than the later, then the renderer may skip the
following character.

## Validation Steps Performed
I've developed a more easily reproducible version of the test case
described in #8390, and confirmed that the problem no longer occurs when
this PR is applied.

I've also manually confirmed that the problem described in #2191 that
was fixed by PR #4668 is still working correctly now.

Closes #8390
@ghost ghost added Resolution-Fix-Committed Fix is checked in, but it might be 3-4 weeks until a release. and removed In-PR This issue has a related PR labels Dec 4, 2020
@ghost
Copy link

ghost commented Jan 28, 2021

🎉This issue was addressed in #8438, which has now been successfully released as Windows Terminal Preview v1.6.10272.0.:tada:

Handy links:

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Rendering Text rendering, emoji, complex glyph & font-fallback issues Impact-Compatibility Sure don't work like it used to. Impact-Correctness It be wrong. Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conhost For issues in the Console codebase Resolution-Fix-Committed Fix is checked in, but it might be 3-4 weeks until a release.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants