libgdiplus
libgdiplus copied to clipboard
Graphics.GetHeight(Graphics g) returns incorrect result when DpiX/Y != 96
See this example program:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace DrawTest
{
class Program
{
const string fontFamily = "Source Code Pro";
const float fontSize = 10;
const FontStyle fontStyle = FontStyle.Regular;
[DllImport("libgdiplus", ExactSpelling = true)]
internal static extern string GetLibgdiplusVersion();
static void Main(string[] args)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Console.WriteLine($"Using the real GDI+");
else
Console.WriteLine($"Using libgdiplus version: {GetLibgdiplusVersion()}");
int dpi = 96; // Typical screen dpi
Console.WriteLine($"------ Test @ {dpi} dpi ------");
DoTest(dpi, GraphicsUnit.Pixel);
//DoTest(dpi, GraphicsUnit.Display);
//dpi = 100; // low resolution printer dpi
//Console.WriteLine($"------ Test @ {dpi} dpi ------");
//DoTest(dpi, GraphicsUnit.Pixel);
////DoTest(dpi, GraphicsUnit.Display);
dpi = 600; // high res printer dpi
Console.WriteLine($"------ Test @ {dpi} dpi ------");
DoTest(dpi, GraphicsUnit.Pixel);
//DoTest(dpi, GraphicsUnit.Display);
}
private static void DoTest(int res, GraphicsUnit unit)
{
using Bitmap bitmap = new Bitmap(1, 1);
bitmap.SetResolution(res, res);
var g = Graphics.FromImage(bitmap);
g.PageUnit = unit;
Console.WriteLine($"Graphics: PageUnit = {g.PageUnit}, PageScale = {g.PageScale}, DPI = {g.DpiX} x {g.DpiY}");
// Calculate the number of lines per page.
var font = new System.Drawing.Font(fontFamily, fontSize, fontStyle, GraphicsUnit.Point);
//var font = new System.Drawing.Font(fontFamily, fontSize / 72F * 96F, fontStyle, GraphicsUnit.Pixel);
Console.WriteLine($"Font: {font.Name}, {font.Size} in {font.Unit}s ({font.SizeInPoints}pts), {font.Style}.");
var fi = new FontInfo(g, font);
fi.Dump();
// Font.GetHeight() assumes 96dpi
Console.WriteLine($" Height: GetHeight() = {font.GetHeight()}");
// Font.GetHeight(res) should use res (and does on both Windows and libgdiplus 6.1)
float fExpectedHeight = font.GetHeight(res);
Console.WriteLine($" GetHeight({res}) = {fExpectedHeight}");
// Font.GetHeight(Graphics) honors Graphics.DpiY on Windows, but with libgdiplus 6.1 uses 96
Console.WriteLine($" GetHeight(g) = {font.GetHeight(g)}" + (fExpectedHeight == font.GetHeight(g) ? "" : " --- FAIL!"));
var lineSize = g.MeasureString(font.Name, font);
Console.WriteLine($"Size of \"{font.Name}\": {lineSize.Width} x {lineSize.Height}");
}
}
public class FontInfo
{
// Heights and positions in pixels.
public float EmHeightPixels;
public float AscentPixels;
public float DescentPixels;
public float CellHeightPixels;
public float InternalLeadingPixels;
public float LineSpacingPixels;
public float ExternalLeadingPixels;
// Distances from the top of the cell in pixels.
public float RelTop;
public float RelBaseline;
public float RelBottom;
public void Dump()
{
Console.WriteLine($" EmHeightPixels = {EmHeightPixels}");
Console.WriteLine($" AscentPixels = {AscentPixels}");
Console.WriteLine($" DescentPixels = {DescentPixels}");
Console.WriteLine($" CellHeightPixels = {CellHeightPixels}");
Console.WriteLine($"InternalLeadingPixels = {InternalLeadingPixels}");
Console.WriteLine($" LineSpacingPixels = {LineSpacingPixels}");
Console.WriteLine($"ExternalLeadingPixels = {ExternalLeadingPixels}");
Console.WriteLine($" RelTop = {RelTop}");
Console.WriteLine($" RelBaseline = {RelBaseline}");
Console.WriteLine($" RelBottom = {RelBottom}");
}
// Initialize the properties.
public FontInfo(Graphics gr, Font the_font)
{
float em_height = the_font.FontFamily.GetEmHeight(the_font.Style);
EmHeightPixels = ConvertUnits(gr, the_font.Size, the_font.Unit, GraphicsUnit.Pixel);
float design_to_pixels = EmHeightPixels / em_height;
AscentPixels = design_to_pixels * the_font.FontFamily.GetCellAscent(the_font.Style);
DescentPixels = design_to_pixels * the_font.FontFamily.GetCellDescent(the_font.Style);
CellHeightPixels = AscentPixels + DescentPixels;
InternalLeadingPixels = CellHeightPixels - EmHeightPixels;
LineSpacingPixels = design_to_pixels * the_font.FontFamily.GetLineSpacing(the_font.Style);
ExternalLeadingPixels = LineSpacingPixels - CellHeightPixels;
RelTop = InternalLeadingPixels;
RelBaseline = AscentPixels;
RelBottom = CellHeightPixels;
}
// Convert from one type of unit to another.
// I don't know how to do Display or World.
private float ConvertUnits(Graphics gr, float value, GraphicsUnit from_unit, GraphicsUnit to_unit)
{
if (from_unit == to_unit) return value;
// Convert to pixels.
switch (from_unit)
{
case GraphicsUnit.Document:
value *= gr.DpiX / 300;
break;
case GraphicsUnit.Inch:
value *= gr.DpiX;
break;
case GraphicsUnit.Millimeter:
value *= gr.DpiX / 25.4F;
break;
case GraphicsUnit.Pixel:
// Do nothing.
break;
case GraphicsUnit.Point:
value *= gr.DpiX / 72;
break;
default:
throw new Exception("Unknown input unit " + from_unit.ToString() + " in FontInfo.ConvertUnits");
}
// Convert from pixels to the new units.
switch (to_unit)
{
case GraphicsUnit.Document:
value /= gr.DpiX / 300;
break;
case GraphicsUnit.Inch:
value /= gr.DpiX;
break;
case GraphicsUnit.Millimeter:
value /= gr.DpiX / 25.4F;
break;
case GraphicsUnit.Pixel:
// Do nothing.
break;
case GraphicsUnit.Point:
value /= gr.DpiX / 72;
break;
default:
throw new Exception("Unknown output unit " + to_unit.ToString() + " in FontInfo.ConvertUnits");
}
return value;
}
}
}
Windows Result:
Using the real GDI+
------ Test @ 96 dpi ------
Graphics: PageUnit = Pixel, PageScale = 1, DPI = 96 x 96
Font: Source Code Pro, 10 in Points (10pts), Regular.
EmHeightPixels = 13.333334
AscentPixels = 13.12
DescentPixels = 3.64
CellHeightPixels = 16.76
InternalLeadingPixels = 3.4266663
LineSpacingPixels = 16.76
ExternalLeadingPixels = 0
RelTop = 3.4266663
RelBaseline = 13.12
RelBottom = 16.76
Height: GetHeight() = 16.759996
GetHeight(96) = 16.759996
GetHeight(g) = 16.759996
Size of "Source Code Pro": 128.04442 x 18.426662
------ Test @ 600 dpi ------
Graphics: PageUnit = Pixel, PageScale = 1, DPI = 600 x 600
Font: Source Code Pro, 10 in Points (10pts), Regular.
EmHeightPixels = 83.33333
AscentPixels = 81.99999
DescentPixels = 22.749998
CellHeightPixels = 104.74999
InternalLeadingPixels = 21.416664
LineSpacingPixels = 104.74999
ExternalLeadingPixels = 0
RelTop = 21.416664
RelBaseline = 81.99999
RelBottom = 104.74999
Height: GetHeight() = 16.759996
GetHeight(600) = 104.749985
GetHeight(g) = 104.749985
Size of "Source Code Pro": 800.2776 x 115.16665
Linux Result:
Using libgdiplus version: 6.1
------ Test @ 96 dpi ------
Graphics: PageUnit = Pixel, PageScale = 1, DPI = 96 x 96
Font: Source Code Pro, 10 in Points (10pts), Regular.
EmHeightPixels = 13.333334
AscentPixels = 13.12
DescentPixels = 3.64
CellHeightPixels = 16.76
InternalLeadingPixels = 3.4266663
LineSpacingPixels = 16.76
ExternalLeadingPixels = 0
RelTop = 3.4266663
RelBaseline = 13.12
RelBottom = 16.76
Height: GetHeight() = 16.76
GetHeight(96) = 16.76
GetHeight(g) = 16.76
Size of "Source Code Pro": 120 x 18
------ Test @ 600 dpi ------
Graphics: PageUnit = Pixel, PageScale = 1, DPI = 600 x 600
Font: Source Code Pro, 10 in Points (10pts), Regular.
EmHeightPixels = 83.33333
AscentPixels = 81.99999
DescentPixels = 22.749998
CellHeightPixels = 104.74999
InternalLeadingPixels = 21.416664
LineSpacingPixels = 104.74999
ExternalLeadingPixels = 0
RelTop = 21.416664
RelBaseline = 81.99999
RelBottom = 104.74999
Height: GetHeight() = 16.76
GetHeight(600) = 104.75
GetHeight(g) = 16.76 --- FAIL!
Size of "Source Code Pro": 120 x 18
Note this shows bugs in MeasureString
too; I'll log a separate issue for that.
Workaround is to use Graphics.GetHeight(int resolution)
, but this doesn't help when you're using 3rd party assemblies.
Related: https://github.com/mono/libgdiplus/issues/623