microsoft / cascadia-code

This is a fun, new monospaced font that includes programming ligatures and is designed to enhance the modern look and feel of the Windows Terminal.
Other
25.39k stars 803 forks source link

GDI rendering is too large, cutting of glyphs #544

Closed weltkante closed 3 years ago

weltkante commented 3 years ago

Cascadia family version

2102.025

Cascadia family variant(s)

Cascadia Code (the version with ligatures), Cascadia Mono (the version without ligatures)

Font file format(s)

I don't know (preinstalled font, accessed through Windows API)

Platform

Windows 10 / 21H1

Other Software

WinForms in .NET Framework (all versions), MFC, native edit controls

What happened?

At 20pt Size the Font reports a height of 33 pixel (actually slightly larger than 32 which gets rounded up). This agrees with other rendering engines like WPF.

However when rendering the font renders at 35 pixel size and is offset vertically, leading to cutoff of descenders. This makes the font not usable in these contexts.

overdraw

Does Cascadia support rendering in classic GDI? This was originally reported in the WinForms repo (dotnet/winforms#5255) when using Cascadia Code on a TextBox control. It also affects MFC and win32 applications using the native edit control.

aaronbell commented 3 years ago

In my testing of Cascadia under GDI environments I have not had any issues with Cascadia's rendering (though maybe I was testing in environments that were OK?), and I'm a bit surprised that this issue is raised now when the font has been out for almost 2 years. I'd have expected it to come up much sooner. :)

My understanding is that GDI uses the font's winAscent and winDescent values for metrics. These are considered the "glyph safe" range wherein any glyph data outside of that range will get clipped (cut off) and not rendered in apps like PowerPoint or Excel.

As a result, there needs be a careful balance between setting the metrics large enough to account for languages such as Vietnamese which require greater vertical space to contain the necessary diacritics, and not setting the metrics so large as to cause over-large linespace rendering for English, or other languages, which don't have such an extensive diacritic set. Often times this results in some data extending outside of the "glyph safe" range, as it does in Cascadia Code. The approach used, then, was intended to allow more modern rendering environments (like DWrite) to see the full glyph data and GDI would have to accept slightly clipped (but still readable) rendering.

So it surprises me that it seems that WinForms renders the baseline position based on the font data itself, rather than just using the reported winAscent and winDescent metrics and clipping from there. Unfortunately, I'm not sure if there is anything I can do to change things in the font at this point as it would necessitate changing font metrics or design. And I'm not familiar enough with the technical implementation of TextRenderer to suggest a way to solve it there.

weltkante commented 3 years ago

In my testing of Cascadia under GDI environments I have not had any issues with Cascadia's rendering (though maybe I was testing in environments that were OK?)

If you intend to support GDI rendering it'd be interesting to reproduce and compare some of these tests to determine if something regressed (either in Windows or the font) or if its something in the user environment that causes this.

If you only intend to support vector based rendering like Direct2D / WPF / etc. then thats a statement that can be made, but since you say its been tested it'd be interesting to figure out what goes wrong, if possible with reasonable effort.

So it surprises me that it seems that WinForms renders the baseline position based on the font data itself, rather than just using the reported winAscent and winDescent metrics and clipping from there.

This is GDI rendering not WinForms rendering, it was only the initial report that involved WinForms, I can repro the same behavior over other GDI based APIs

I'm not familiar enough with the technical implementation of TextRenderer to suggest a way to solve it there.

It just forwards the rendering to GDI, I can reproduce the same issue doing a plain DrawText API call against a HDC. Probably other GDI-based APIs as well.

From what I can tell its not this specific size that causes problems either, I tried various sizes over a reasonable range and they all cut off descenders.

weltkante commented 3 years ago

Update: digging deeper it seems the Font height is the issue - depending on the API you use you get different heights reported. So while most APIs (and across frameworks) agree that a 20pt Cascadia Code is 33 pixel height, some parts of Windows apparently have the opinion it should be 35 pixel height. I'll investigate more, not sure why it reports different heights to different APIs, or how much you can/want to do in the font here.

aaronbell commented 3 years ago

@weltkante, thank you so much for your investigations! I'm (sadly) not terribly surprised that there are different ways to get the font metrics values. And that would help explain why in my testing I didn't run into this issue.

The only thing I can think to do would be to increase the font height metric to align between the different APIs, but that would have the impact of causing all other GDI users to suffer an increased line-height and reduced number of lines of code in a given window. Soooo, if this can get sorted by using a different API then that'd be fabulous!

weltkante commented 3 years ago

I've done some more research and experiments, including loading the font with freetype2, which agrees with GDI+ and WPF about what the font metrics are, and that a 20pt font size should be 31 pixel. It seems GDI reads/calculates entirely different font metrics:

em height 20pt / 27px GDI others formula
font height (line spacing) 35px 31px reported by API
ascent 29px 26px reported by API
descent 6px 7px reported by API
internal leading 8px (reported) 6px (calculated) (ascent + descent) - em height
external leading 0px (reported) -2px (calculated) line spacing - (ascent + descent)

I understand that these numbers are not directly comparable and that you probably have to make trade-offs for whatever tables GDI is reading from, what I want to highlight is that the relative scales between numbers seems to be off-balance and configured contradictory. In particular in one metric ascenders and internal leading are huge, in the other metric descenders are so large they technically overlap with the next line (unless I made a mistake in the calculations)

At this point its your decision on whether there is something you can do or that having two divergent metrics is the best you can provide to accommodate the specific needs of GDI rendering vs. vector rendering. In this case, if there's nothing that can be done, feel free to close this issue.

As for the original problem, I don't think WinForms can ever be entirely fixed since its been designed to expose GDI+ APIs to the developer yet many native controls its wrapping are rendering with classic GDI. Its possible for users to work around the problem manually but the Cascadia font doesn't work out of the box in WinForms. The team is considering adding workarounds to individual builtin controls in the future to perform layout with metrics from GDI, but in general the problem of mixed APIs will remain.

aaronbell commented 3 years ago

Thank you so much for your extensive investigations and detailed feedback!

It sounds like when I was testing, I was actually looking at an application that uses GDI+ rather than classic GDI.

Resolving this issue will require modifying the winAscent and winDescent values to make exactly the extreme height and depth of the font. While this is a relatively simple change to make (and export a classic GDI-specific version), I am hesitant to do so as there are already so many variants of Cascadia Code available and I worry that adding more will result in further user confusion, for a relatively(?) small audience. As such, I lean towards not supporting classic GDI at this time (not that the font is well hinted for rendering in that environment either).

In the future, when we eventually create a 'Cascadia Customizer" tool, it would make sense to include a switch to set classic GDI metrics, but for the time being, I'm classifying this as 'won't fix'.

Thanks!