Search code examples
c#fontsgdi+system.drawing

Wrong value of FontFamily GetCellDescent


The value of descent I get from System.Drawing.FontFamily.GetCellDescent for some fonts is different from values of I get when the font is read by other APIs/sources (e.g. SixLabors.Fonts library or FontForge program).

As a concrete example, Calibri font:

var fontName = "Calibri";
var systemDrawingFontFamily = System.Drawing.FontFamily.Families.SingleOrDefault(ff => ff.Name == fontName);
        
Console.WriteLine($"System.Drawing EmHeight {systemDrawingFontFamily.GetEmHeight(System.Drawing.FontStyle.Regular)}");
Console.WriteLine($"System.Drawing CellAscent {systemDrawingFontFamily.GetCellAscent(System.Drawing.FontStyle.Regular)}");
Console.WriteLine($"System.Drawing CellDescent {systemDrawingFontFamily.GetCellDescent(System.Drawing.FontStyle.Regular)}");
Console.WriteLine($"System.Drawing LineSpacing {systemDrawingFontFamily.GetLineSpacing(System.Drawing.FontStyle.Regular)}");
        
var sixLaborsFont = SixLabors.Fonts.SystemFonts.Families.SingleOrDefault(x => x.Name == fontName);
var sixLaborsFontMetrics = sixLaborsFont.CreateFont(0, SixLabors.Fonts.FontStyle.Regular).FontMetrics;
Console.WriteLine($"6L EmHeight {sixLaborsFontMetrics.UnitsPerEm}");
Console.WriteLine($"6L Ascender {sixLaborsFontMetrics.Ascender}");
Console.WriteLine($"6L Descender {-sixLaborsFontMetrics.Descender}");
Console.WriteLine($"6L LineGap {sixLaborsFontMetrics.LineGap}");
Console.WriteLine($"6L LineHeight {sixLaborsFontMetrics.LineHeight}");
System.Drawing EmHeight 2048  
System.Drawing CellAscent 1950  
System.Drawing CellDescent 550  
System.Drawing LineSpacing 2500  
6L EmHeight 2048  
6L Ascender 1536  
6L Descender 512  
6L LineGap 452  
6L LineHeight 2500

Typography has kind of confused vocabulary, the ascender for MS includes internal leading, but descender should be same.

Why is the descender value obtained from System.Drawing different from other sources? How is it calculated?

I went through all system fonts and in about 20% of cases, the descent has different value.


Solution

  • FontFamily.GetCellDescent actually calls Win32 API GDI+ function GetCellDescent. I assume that GDI+ uses same logic as GDI.

    GDI has a documentation page with details about font sizes (emphasis mine):

    Font Ascenders and Descenders

    Some applications determine the line spacing between text lines of different sizes by using a font's maximum ascender and descender. An application can retrieve these values by calling the GetTextMetrics function and then checking the tmAscent and tmDescent members of the TEXTMETRIC.
    The maximum ascent and descent are different from the typographic ascent and descent. In TrueType and OpenType fonts, the typographic ascent and descent are typically the top of the f glyph and bottom of the g glyph. An application can retrieve the typographic ascender and descender for a TrueType or OpenType font by calling the GetOutlineTextMetrics function and checking the values in the otmMacAscent and otmMacDescent members of the OUTLINETEXTMETRIC structure.
    enter image description here

    So basically the FontFamily.GetCellDescent finds a character with maximum bottom size and returns that value (in logical units).

    When I called GDI API functions the

    • GetOutlineTextMetricsand - returned a expected ascent and descent values identical to SixLabors.Fonts and FontForge
    • GetTextMetrics - returned values identical to GetCellAscent, GetCellDescent and GetLineSpacing.

    Better answer: GetCellDescent returns a win descent metric from OS/2, as seen on the different page of FontForge. I modified it and loaded the modified font and got this number. FontForge dialogue