1

This is for a header/footer on a printed page. The code below is painting the left & center parts of the header/footer (I've omitted the code that paints the right) and simplified logic to illustrate the problem better. The idea is the center part of header takes precedence and the left and right will be trimmed via StringTrimming.EllipsisPath if the center is too big for them to fit. This all works except for whenever trimming occurs, GDI+ is shifting the trimmed text vertically a bit.

EDIT: I've written a dedicated sample to illustrate the problem because my original post was not clear enough for folks. This is the entire code of the sample. It's a .NET Framework Winforms app created with VS19.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
        Font font = new Font("Century Gothic", 24F, FontStyle.Regular, GraphicsUnit.Point);
        string demo = "[Emergency 123]";

        StringFormat fmtTrimmed = new StringFormat(StringFormat.GenericTypographic)
        {
            Alignment = StringAlignment.Near,
            LineAlignment = StringAlignment.Near,
            Trimming = StringTrimming.EllipsisCharacter,
        };

        StringFormat fmtNotTrimmed = new StringFormat(StringFormat.GenericTypographic)
        {
            Alignment = StringAlignment.Center,
            LineAlignment = StringAlignment.Near,
        };

        try
        {
            Rectangle boundsHeader = new Rectangle(10, 10, this.ClientSize.Width - 20, (int)font.GetHeight() + 8);
            SizeF textSize = e.Graphics.MeasureString(demo, font, boundsHeader.Size, fmtNotTrimmed);

            boundsHeader.Inflate(1, 1);
            e.Graphics.DrawRectangle(Pens.Blue, boundsHeader);

            float textCenterBounds = (boundsHeader.Width - textSize.Width) / 2;
            e.Graphics.DrawRectangle(Pens.Green, boundsHeader.X + textCenterBounds, boundsHeader.Y, textSize.Width, textSize.Height);
            e.Graphics.DrawString(demo, font, Brushes.Black, boundsHeader, fmtNotTrimmed);

            RectangleF boundsLeft = new RectangleF(boundsHeader.X, boundsHeader.Y, textCenterBounds, boundsHeader.Height);
            SizeF sizeLeft = e.Graphics.MeasureString(demo, font, boundsLeft.Size, fmtTrimmed);

            e.Graphics.DrawRectangle(Pens.Red, boundsLeft.X, boundsLeft.Y, boundsLeft.Width, textSize.Height);
            e.Graphics.DrawString(demo, font, Brushes.Black, boundsLeft, fmtTrimmed);
        }
        finally
        {
            font.Dispose();
            fmtTrimmed.Dispose();
            fmtNotTrimmed.Dispose();
        }
    }

Here the app shows that if left is of size that trimming does not occur, it is aligned vertically correctly: enter image description here

If left is has a length where trimming occurs (note elipsis), vertical alignment shifts up slightly (note code uses Form1_Resize to resize rects). enter image description here

You can see this most clearly by looking at the y, g, and brackets.

I've tried every combination of StringTrimming, LineAlignment, and FormatFlags and cannot resolve this. Note that in the current implementation I've also tried making the Rectangle have a height of 0. Same issue occurs if the bounds.Height is the font height, sizeCenter.Height, or bigger.

I can't believe this is a bug in GDI+, so I've got to be doing something wrong. Many searches and re-reads of the docs have not helped. I'm at my wits end.

EDIT: based on @HansPassant's comments below, I've found different fonts behave differently. In the images above I was using "Microsoft Sans Serif".

  • "Arial" has same problem (bold or regular)
  • "Tahoma" does reproduce the problem.
  • "Cascadia Code" DOES reproduce (it's new and fixed pitch)
  • "Lucida Console" DOES NOT!?!?! reproduce
  • "Courier New" DOES.

I've determined that some fonts are worse than others. For example, if I change the code above to use 24F as the font size I can't reproduce it with Microsoft Sans Serif).

I've also determined that StringTrimming.Character and StringTrimming.ElipsisCharacter and StringTrimming.ElipsisPath all behave the same way (so the ellipsis char does not seem to be the cause).

EDIT: Showing another example here using Century Gothic regular: enter image description here

EDIT: This sample illustrates that e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias does NOT fix the problem when LineAlignment = StringAlignment.Far. enter image description here

tig
  • 3,424
  • 3
  • 32
  • 65
  • Are you using monospaced fonts? – Cleptus Dec 03 '19 at 15:00
  • Not in this case. The image above is "Microsoft Sans Serif". Based on your query, I just tried "Lucida Console" which is monospace and the issue does NOT reproduce. However, I need to support both types. – tig Dec 03 '19 at 15:20
  • Issue is not that it shifts, the point-size of the font in the 2nd screenshot is smaller. The font you use was discontinued a long time ago so you always get a fallback substitute. That this could be a different substitute if the text needs to be trimmed is unexpected, but I suppose possible if the original substitute font does not have a glyph for the ellipsis. – Hans Passant Dec 03 '19 at 15:33
  • This code is not clear. What is `bounds`? You use it for everything. Why do you set it's height to `0`? Why do you measure the string, assign it to `sizeCenter` and never use it? You should measure the string passing a size. – Jimi Dec 03 '19 at 15:35
  • @HansPassant Weird. I just tried it with "Arial". Same bad result. Then I tried with "Tahoma" and it does not reproduce. I also tried changing to `StringTrimming.Character` (no elipsis) and it still fails to do the right thing with "Arial" and "Microsoft Sans Serif" – tig Dec 03 '19 at 15:39
  • @jimi bounds is the `RectangleF` denoting the header/footer. As I noted in the example shown I was trying `height = 0` because the docs say that DrawString behaves differently when you do that and I was trying that. It makes no difference. I use `sizeCenter` to size the left and right bounds; that code is missing in the sample presented. – tig Dec 03 '19 at 15:42
  • The missing code counts. When you draw a string with precision, you need to specify a RectangleF, especially with `LineLimit`: setting `bounds.Height = 0` you're passing a *line* to `DrawString()`. Add `e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;`. -- In the edit, `sizeCenter` is used to resize the `bounds.Width` but the `bounds.Location` is unchanged so it's not clear what you're print and where. `sizeLeft` is never used. Btw, `StringFormat.GenericTypographic` already sets `NoClip`, `FitBlackBox` and `LineLimit`. – Jimi Dec 03 '19 at 15:55
  • @Jimi - I should have just posted the whole thing vs. trying to clean it up and post partial. I've updated the post the code as it now sits. Still reproduces the problem even with the use of `TextRenderingHint` as you suggested. – tig Dec 03 '19 at 16:09
  • This is not the code *as it now sits*: `using StringFormat fmt = new StringFormat(...)`? What is this? This wouldn't even compile. `Font.Family`? `bounds` and `CalcBounds();` are undefined. This is all too imprecise... – Jimi Dec 03 '19 at 16:36
  • @jimi added more details above. – tig Dec 03 '19 at 16:46
  • Isn't that [using block a c# 8.0](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement) feature? – LarsTech Dec 03 '19 at 17:11
  • @Jimi I just discovered that `g.TextRenderingHint = TextRenderingHint.AntiAlias` addresses the issue. All other settings of `TextRenderingHint` cause the problem to reproduce. I'd can take this as a solution, but I'd like to understand why. – tig Dec 03 '19 at 17:43
  • If I could test your code, I'ld tell you. Anyway, read the notes here: [Drawing a Long String on to a Bitmap results in Drawing Issues](https://stackoverflow.com/a/49953353/7444103). – Jimi Dec 03 '19 at 17:46
  • @Jimi I've updated the code in my question to be a sample that just illustrates the problem. Note that in doing this `g.TextRenderingHint = TextRenderingHint.AntiAlias` still appears fix the problem in all cases I've tested. I want to know why. – tig Dec 05 '19 at 19:59
  • There are some errors in the calculations. Try to always use a Size/SizeF or Rectangle/RectangleF (when available as an option) to measure dynamic elements with both GDI+ and GDI (TextRenderer.MeasureString/DrawString). If you provide a `Point` or a `Width`, the calculation will almost always be wrong or not aligned (a reference is missing, so the glyph internal alignment/overhanging/padding is used instead). A [PasteBin of the modified code](https://pastebin.com/xmuvbMgz). Resize the Form to shrink the text on the left and compare to what's on the right of it. – Jimi Dec 05 '19 at 20:54
  • To draw the Text with more *precision*, considering the Font's Cell Ascent and Descent measures (maybe optionally using a GraphicsPath object), read the notes here: [Properly draw text using GraphicsPath](https://stackoverflow.com/a/53074638/7444103). – Jimi Dec 05 '19 at 20:59
  • @jimi I used your code and with `Microsoft Sans Serif` as my font (instead of `Century Gothic`) the problem still reproduces. In fact, i was able to reproduce by testing with `Font("Century Gothic", 24F, FontStyle.Regular, GraphicsUnit.Point`. I do appreciate the implicit code-review in your version ;-). But my original point is valid: when trimming, DrawText changes vertical alignment. – tig Dec 05 '19 at 21:33
  • [Graphics sample](https://imgur.com/a/4HgpxQE) of the results. – Jimi Dec 05 '19 at 22:05
  • @Jimi - I pasted your code into my form directly. By simply changing the fontsize with `Century Gothic` I was able to reproduce the problem. In addition, I just reproduced it with `LineAlignment = StringAlignment.Far` EVEN when `TextRenderingHint = TextRenderingHint.AntiAlias`. I see your video and see you don't see the problem. But I still see it. – tig Dec 05 '19 at 22:08
  • If you used exactly the code I posted, I don't see how you could have a result as shown in the last image you posted (well, you've seen the animation, what happens - or, doesn't happen - even changing the Font on-the-fly). If your display (the screen) is scaled (you don't have 96 dpi), set the Form's `AutoScaleMode = Dpi` and make your app DpiAware. It *shouldn't* change the way a Font is drawn, but, sometimes, the screen dpi does *influence* the drawing (not like that, usually, but it might). – Jimi Dec 05 '19 at 22:32
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/203707/discussion-between-tig-and-jimi). – tig Dec 05 '19 at 23:16

0 Answers0