dotnet / winforms

Windows Forms is a .NET UI framework for building Windows desktop applications.
MIT License
4.44k stars 988 forks source link

Text size is a bit off using Graphics and very wrong using GraphicsPath #7485

Open janseris opened 2 years ago

janseris commented 2 years ago

.NET version

.NET Framework 4.7.2

Did it work in .NET Framework?

No

Did it work in any of the earlier releases of .NET Core or .NET 5+?

probably in none

Issue description

What was my initial goal:

This seems like an impossible task with current WinForms API because there are two string size calculations which produce different results and both are wrong. Graphics is approx. to 90 % right while GraphicsPath is like 60 % right or less,

Drawing a string with font size 60 points on coordinates [200, 200] with Graphics and GraphicsPath produces two very different results (shown by text). String size calculation using Graphics.MeasureString and GraphicsPath.GetBounds methods produces two very different results (shown by rectangles).

image

Steps to reproduce

WindowsFormsApp2.zip

Note: because of the GraphicsPath behavior, the best size calculation (fit into rectangle) can be done by bruteforcing font size from largest to lowest using GraphicsPath with GetBounds. However the result works fine only for drawing by GraphicsPath (if drawn by Graphics with font size calculated using GraphicsPath, the text will be much bigger because font sizes returned by GraphicsPath are very small (unrealistic))

Edit: this project shows how to get the drawn position of GraphicsPath right, however it is kinda complicated because if GraphicsPath contains multiple objects, this must probably be done for every object (create an additional GraphicsPath object, measure size and offset, drop the GraphicsPath object and then use the correction to draw the actual GraphicsPath object): GraphicsPath corrected drawing location.zip

image

janseris commented 2 years ago

@JeremyKuhne Having played with this a lot, I noticed that the offset of the GraphicsPath containing string is probably caused by the fact that for different string content, a different offset is generated because different symbols are sized differently and might reach the max value which is that offset of the GraphicsPath bounding rectangle from the original point would be 0. I have not tested this though. And if this is true, the offset is not an issue.

I have created a sample project which might help illustrating this (among other different things): https://github.com/janseris/GraphicsPathRotatedStringDrawingIssue

weltkante commented 2 years ago

you can use Graphics.MeasureCharacterRanges to get tight bounds for Graphics.DrawString.

Never used GraphicsPath so no idea why it interprets font size differently than the rest of the APIs.

JeremyKuhne commented 1 year ago

@janseris Sorry that I couldn't look at this any sooner. One fundamental difference is that when you draw directly to the Graphics object a scale factor is applied to the em size based on the font unit. This isn't done when writing to a path.

The logic looks something like this:

public static float EmScale(Font font, Graphics g)
{
    switch (font.Unit)
    {
        case GraphicsUnit.Document:
            return g.DpiX / 300.0f / g.PageScale;
        case GraphicsUnit.Point:
            return g.DpiX / 72.0f / g.PageScale;
        case GraphicsUnit.Inch:
            return g.DpiX / g.PageScale;
        case GraphicsUnit.Millimeter:
            return g.DpiX / 25.4f / g.PageScale;
        case GraphicsUnit.Display:
        case GraphicsUnit.Pixel:
        case GraphicsUnit.World:
        default:
            return 1.0f;
    }
}

If you use this logic to multiply the em parameter for AddString you'll get the right scale.

image

I don't know much about the details of how outputting to path differs from rendering to pixels outside of that. Unfortunately I probably won't get a chance to dig into that part in the near future.

ghost commented 1 year ago

This issue is now marked as "help wanted", and we’re looking for a community volunteer to work on this issue. If we receive no interest in 180 days, we will close the issue. To learn more about how we handle feature requests, please see our documentation.

Happy Coding!