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

Use active font metrics for grid line rendering #6911

Closed
j4james opened this issue Jul 14, 2020 · 12 comments · Fixed by #7107
Closed

Use active font metrics for grid line rendering #6911

j4james opened this issue Jul 14, 2020 · 12 comments · Fixed by #7107
Labels
Area-Rendering Text rendering, emoji, complex glyph & font-fallback issues Help Wanted We encourage anyone to jump in on these. Issue-Task It's a feature request, but it doesn't really need a major design. Product-Conhost For issues in the Console codebase Product-Terminal The new Windows Terminal. 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 Jul 14, 2020

Description of the new feature/enhancement

I was looking at adding support for the crossed-out attribute, but found that it doesn't look very good without getting the font metrics to draw the line in the right position, and ideally the right size. And I know there have also been discussions before about the need to draw the underline in the right position and size.

So as a first step, I thought it might be good idea to put together a PR just gathering the font line metrics, and updating the grid line code to take those metrics into account. This is not about drawing the underline in the right position yet - just getting the existing grid lines to scale with the font size.

Proposed technical implementation details (optional)

My first thought was to use the underline thickness metric for the width of all grid lines, and you can see an example of what that might look like below:

image

But based on those results, I'm now more inclined to think a hard coded size might be preferable. Either option would be better than the current single pixel width, though, which is almost invisible on high dpi displays. If we do go with a hard coded value, I think something around the thickness of Cascadia or Fira Code would be reasonable (that's about 0.025em).

And while looking at this area of the code, I also wanted to propose some refactoring of the grid line renderer to be more efficient. In the current implementation, if you're drawing an underline across 20 cells, it actually renders it with 20 separate strokes, when it could easily be done in one. The left and right grid lines still need to be rendered one cell at a time, but that's a much less common occurrence.

So just to summarize:

  1. Is is OK if I do a PR to make the grid lines scale with the font size?
  2. If so, should I use the font's underline thickness or a fixed em size?
  3. Can I do a refactor/optimisation of the rendering code at the same time?
@j4james j4james added the Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. label Jul 14, 2020
@ghost ghost added Needs-Tag-Fix Doesn't match tag requirements Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting labels Jul 14, 2020
@j4james
Copy link
Collaborator Author

j4james commented Jul 14, 2020

I should mention that I tried to see what the grid lines looked like in the legacy console, but I couldn't get any of the COMMON_LVB_GRID attributes to work there. I suspect that's because they were originally only supported on FE versions of Windows (or something along those lines). But the upshot is, I'm not actually sure how these lines were originally intended to be rendered, so perhaps they should always be a single pixel wide regardless of font size. But even then, I would still expect some kind of DPI scaling to apply. And if that's the preferred approach, I'd be happy to do that too.

@DHowett
Copy link
Member

DHowett commented Jul 15, 2020

This one probably needs @miniksa's input. I think the gridlines were intended to be 1 px or 1 dip (pixel * scale).

Personally, I'm fine with making them scale based on font size at a fixed em proportion.

What's the optimization you're thinking about for 3? Niksa's knocking about in rendering recently, and I poked at it for #6193 (never landed, but might inform future direction).

Idly, I wonder if we're getting closer to the time when we need to decouple underline/overline from gridlines and render them differently (per the callout in #2916)...

@DHowett DHowett added Area-Rendering Text rendering, emoji, complex glyph & font-fallback issues Help Wanted We encourage anyone to jump in on these. Issue-Task It's a feature request, but it doesn't really need a major design. Product-Conhost For issues in the Console codebase Product-Terminal The new Windows Terminal. and removed Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. labels Jul 15, 2020
@ghost ghost removed the Needs-Tag-Fix Doesn't match tag requirements label Jul 15, 2020
@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 Jul 15, 2020
@DHowett DHowett added this to the 21H1 milestone Jul 15, 2020
@DHowett
Copy link
Member

DHowett commented Jul 15, 2020

I ought to have mentioned #2915 instead of #2916.

@j4james
Copy link
Collaborator Author

j4james commented Jul 16, 2020

This one probably needs @miniksa's input. I think the gridlines were intended to be 1 px or 1 dip (pixel * scale).

OK, I'll wait to see what he says. I'm not in any rush to submit anything yet - I'm still looking into the GDI side of things.

What's the optimization you're thinking about for 3?

My main objective was to change the grid line rendering from this pattern:

for (size_t i = 0; i < cchLine; i++)
{
    if (lines & GridLines::Top)
        drawline
    if (lines & GridLines::Left)
        drawline
    if (lines & GridLines::Bottom)
        drawline
    if (lines & GridLines::Right)
        drawline
}

to this sort of thing:

if (lines & GridLines::Top)
    drawline
if (lines & GridLines::Bottom)
    drawline
if (lines & GridLines::Left)
    for (size_t i = 0; i < cchLine; i++)
        drawline
if (lines & GridLines::Right)
    for (size_t i = 0; i < cchLine; i++)
        drawline

So in the primary use case of a top or bottom line, you only need a single line being drawn, instead of a loop with lots of little strokes joined together. And pulling the conditions out of the loops in the left and right cases make those a little more efficient too (you can also pull up several of the offset calculations).

These things probably aren't going to be a big deal in terms of performance, but I thought it was worth trying to clean it up a bit if I was going to be extending it anyway to support varying line widths (which is what we'll need for the underline and crossed-out attributes). Oh and I also noticed an off-by-one error in the top line rendering which needed fixing.

Niksa's knocking about in rendering recently, and I poked at it for #6193 (never landed, but might inform future direction).

If you want me to wait for you guys to finish anything there, just let me know. Most of what I'm looking at is in the PaintBufferGridLines methods in the Dx and Gdi render engines, in case that's relevant.

Idly, I wonder if we're getting closer to the time when we need to decouple underline/overline from gridlines and render them differently (per the callout in #2916)...

That was one my reasons for proposing this refactoring. Even if we keep the current grid lines at 1px, I'd still like to get the code to a state where it's easy to add lines of different widths and at different offsets, and also get the metadata in place for the other line types.

If you prefer, we could just include this work as part of the PR for the crossed-out attribute, or the underline decoupling, but I thought it might be cleaner to do it as a separate, preparatory step.

@mdtauk
Copy link

mdtauk commented Jul 16, 2020

Bold grid lines for instance, would benefit from changeable widths

@miniksa
Copy link
Member

miniksa commented Jul 16, 2020

My first thought was to use the underline thickness metric for the width of all grid lines, and you can see an example of what that might look like below:

I actually really like this as it harnesses the style imparted by each font author. That was one of the reasons why I tried scaling on the box/line characters instead of ignoring them and drawing our own like some Terminals do.

I am willing to be overridden, though, by the rest of you if you believe that I'm crazypants and we should do a specific pixel or em count (scaled by font and DPI) for all fonts.

I should mention that I tried to see what the grid lines looked like in the legacy console, but I couldn't get any of the COMMON_LVB_GRID attributes to work there. I suspect that's because they were originally only supported on FE versions of Windows (or something along those lines).

You have to use the ENABLE_LVB_GRID_WORLDWIDE flag on SetConsoleMode to make that happen. I had initially made it work worldwide when we took over this. But there was one Delphi app out there that used the "unused" flags in our buffer for its own scratch space in Western countries. So when we decided to pay attention to the flags, that app looked weird.

But the upshot is, I'm not actually sure how these lines were originally intended to be rendered, so perhaps they should always be a single pixel wide regardless of font size.

Per conhostv1, they were always one pixel.

But even then, I would still expect some kind of DPI scaling to apply. And if that's the preferred approach, I'd be happy to do that too.

conhostv1 didn't have High DPI support. It would have been system scaled. At which point the 1px would have been coarsely scaled into whatever made sense by DWM and it would have been bigger than 1px. So that would be my preference: to scale whatever we choose.

Personally, I'm fine with making them scale based on font size at a fixed em proportion.

I'm fine with this.

This one probably needs @miniksa's input. I think the gridlines were intended to be 1 px or 1 dip (pixel * scale).

OK, I'll wait to see what he says. I'm not in any rush to submit anything yet - I'm still looking into the GDI side of things.

Given that the decision came from conhostv1 era, I'm 99% sure that no one made a conscious decision here and just pulled "1px" out of their butt to make it happen and there is no documentation of why it was chosen.

We probably should preserve that 1px for the GDI side though (and the other oddball renderers, if they even handle grid lines) and only change the DX one to some other proportion.

What's the optimization you're thinking about for 3?

My main objective was to change the grid line rendering from this pattern:

<snip>

So in the primary use case of a top or bottom line, you only need a single line being drawn, instead of a loop with lots of little strokes joined together. And pulling the conditions out of the loops in the left and right cases make those a little more efficient too (you can also pull up several of the offset calculations).

These things probably aren't going to be a big deal in terms of performance, but I thought it was worth trying to clean it up a bit if I was going to be extending it anyway to support varying line widths (which is what we'll need for the underline and crossed-out attributes). Oh and I also noticed an off-by-one error in the top line rendering which needed fixing.

Yes, I like this. Fewer draw commands is always better.

Niksa's knocking about in rendering recently, and I poked at it for #6193 (never landed, but might inform future direction).

If you want me to wait for you guys to finish anything there, just let me know. Most of what I'm looking at is in the PaintBufferGridLines methods in the Dx and Gdi render engines, in case that's relevant.

No, don't wait. Go ahead.

Idly, I wonder if we're getting closer to the time when we need to decouple underline/overline from gridlines and render them differently (per the callout in #2916)...

That was one my reasons for proposing this refactoring. Even if we keep the current grid lines at 1px, I'd still like to get the code to a state where it's easy to add lines of different widths and at different offsets, and also get the metadata in place for the other line types.

If you prefer, we could just include this work as part of the PR for the crossed-out attribute, or the underline decoupling, but I thought it might be cleaner to do it as a separate, preparatory step.

I also agree with having them go in their own pass if necessary.

I am OK with refactoring going in its own preparatory step to be cleaner.

@j4james
Copy link
Collaborator Author

j4james commented Jul 17, 2020

We probably should preserve that 1px for the GDI side though (and the other oddball renderers, if they even handle grid lines) and only change the DX one to some other proportion.

I'm not thrilled by this idea. At the moment DX is only used in Windows Terminal, and that doesn't actually use grid lines other than as a way to emulate underline and overline. Underline we're going to replace with a real underline with font-specific position and width. But overline isn't covered by font metrics, so it would be convenient if we could just leave it an alias for the top grid line.

But if we go with that plan, then I'd expect the grid line width to be consistent, so that overline looked the same in both renderers. The other option would be for overline to get its own attribute bit, but then the DX renderer has no need to show grid lines at all, so there's still no point in making it render things differently.

Bottom line is I don't really care what width we use, but I do think it would be preferable if it were consistent across renderers.

@miniksa
Copy link
Member

miniksa commented Jul 20, 2020

We probably should preserve that 1px for the GDI side though (and the other oddball renderers, if they even handle grid lines) and only change the DX one to some other proportion.

I'm not thrilled by this idea. At the moment DX is only used in Windows Terminal, and that doesn't actually use grid lines other than as a way to emulate underline and overline. Underline we're going to replace with a real underline with font-specific position and width. But overline isn't covered by font metrics, so it would be convenient if we could just leave it an alias for the top grid line.

But if we go with that plan, then I'd expect the grid line width to be consistent, so that overline looked the same in both renderers. The other option would be for overline to get its own attribute bit, but then the DX renderer has no need to show grid lines at all, so there's still no point in making it render things differently.

Bottom line is I don't really care what width we use, but I do think it would be preferable if it were consistent across renderers.

Well, okay. We can make it consistent across renderers. I just try to avoid changing any of the other ones when possible because leaving them alone is a 0% chance of bugs/regression where as changing them in any way is now non-zero. I feel it's easier to avoid the problem. But if you are passionate about it being consistent, that's fine with me.

@j4james
Copy link
Collaborator Author

j4james commented Jul 21, 2020

First off, I just want to be clear that I am happy to let you guys make the final decision here - don't let me push you into doing something you're not comfortable with. That said, I've been playing around with a couple of different renderings to see how they feel, and think this might help with the decision making.

In the image below you can see some test cases with three different fonts (Cascadia Mono, Consolas, and MS Gothic) in a range of "normal" font sizes (12pt to 18pt). It's rendered on a high DPI display (at 200%) so you'll want to scale the image to 50% in the browser to get an accurate feel for the size.

In each case I'm rendering both top and bottom grid lines as well as a proper underline and strikethrough line. The grid lines are rendered in 3 different ways: 1 physical pixel (on the left); 1 device-independent pixel, i.e. 2 physical pixels (in the centre); and using the underline width (on the right).

I found the middle one a bit odd, because with Cascadia you end up with grid lines wider than the standard underline, which just seems wrong. And then if you scale the font size up far enough the opposite happens - the underline becomes wider than the grid lines - so it's not even consistent.

The one on the right (using the underline width) is nice in some ways, but with MS Gothic if feels like the grid lines are smothering the text, and this is one of the fonts I suspect is most likely to be used with grid lines. And in the case of Cascadia, the underline width is so narrow that's it's no different from a 1 pixel grid line anyway, at least for most "normal" font sizes.

So after all that I'm starting to think our current rendering (i.e. 1 physical pixel) may well be the best option. However, I'd be a little happier with something like 0.025 em. That's still going to be 1 pixel for most normal font sizes (i.e. it'll look exactly like the left column), but if you did happen to zoom in a large amount, it would feel more natural to see the grid lines scaling.

In short: first preference 0.025 em, second preference 1 physical pixel, but I'll leave it to you guys to make the final choice (and DX and GDI being different is not the end of the world for me). The way the code is now written it's essentially a one line change in each renderer to switch between widths, so it's not a big deal to play around with the different options if you change your mind.

image

@miniksa
Copy link
Member

miniksa commented Jul 22, 2020

You're right, MS Gothic is most likely to be used with gridlines so that's the one we should probably make look the best with them.

I'm alright with doing the 0.025em option across all the renderers. If it's that close to how it already is in a majority of cases, I can't see it causing me much if any trouble with the other renderers also changing.

Also, I think I like the way the left most column of these looks as the best option. I'd rather it be based on a text metric though like a fractional em than on a pixel though, so I agree with your priority ranking.

@DHowett
Copy link
Member

DHowett commented Jul 22, 2020

Yeah, I agree with that order as well. 👍

max(0.025em, 1px), presumably? actually, you covered this

@ghost ghost added the In-PR This issue has a related PR label Jul 28, 2020
@ghost ghost closed this as completed in #7107 Jul 30, 2020
@ghost ghost removed the In-PR This issue has a related PR label Jul 30, 2020
ghost pushed a commit that referenced this issue Jul 30, 2020
This is a refactoring of the grid line renderers, adjusting the line
widths to scale with the font size, and optimising the implementation to
cut down on the number of draw calls. It also extends the supported grid
line types to include true underlines and strike-through lines in the
style of the active font.

The main gist of the optimisation was to render the horizontal lines
with a single draw call, instead of a loop with lots of little strokes
joined together. In the case of the vertical lines, which still needed
to be handled in a loop, I've tried to move the majority of static
calculations outside the loop, so there is bit of optimisation there
too.

At the same time this code was updated to support a variable stroke
width for the lines, instead of having them hardcoded to 1 pixel. The
width is now calculated as a fraction of the font size (0.025 "em"),
which is still going to be 1 pixel wide in most typical usage, but will
scale up appropriately if you zoom in far enough.

And in preparation for supporting the SGR strike-through attribute, and
true underlines, I've extended the grid line renders with options for
handling those line types as well. The offset and thickness of the lines
is obtained from the font metrics (rounded to a pixel width, with a
minimum of one pixel), so they match the style of the font.

VALIDATION

For now we're still only rendering grid lines, and only the top and
bottom lines in the case of the DirectX renderer in Windows Terminal. So
to test, I hacked in some code to force the renderer to use all the
different options, confirming that they were working in both the GDI and
DirectX renderers.

I've tested the output with a number of different fonts, comparing it
with the same text rendered in WordPad. For the most part they match
exactly, but there can be slight differences when we adjust the font
size for grid alignment. And in the case of the GDI renderer, where
we're working with pixel heights rather than points, it's difficult to
match the sizes exactly.

This is a first step towards supporting the strike-through attribute
(#6205) and true underlines (#2915).

Closes #6911
@ghost ghost added the Resolution-Fix-Committed Fix is checked in, but it might be 3-4 weeks until a release. label Jul 30, 2020
@ghost
Copy link

ghost commented Aug 26, 2020

🎉This issue was addressed in #7107, which has now been successfully released as Windows Terminal Preview v1.3.2382.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 Help Wanted We encourage anyone to jump in on these. Issue-Task It's a feature request, but it doesn't really need a major design. Product-Conhost For issues in the Console codebase Product-Terminal The new Windows Terminal. 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.

4 participants