0

In windows application(C#), When draw string using DrawString function of System.Drawing.Graphics class. But values are not properly aligned with other values though X and Y coordinates are the same. It behaves differently for different fonts with different font sizes. I have considered an internal leading and deduct it from Y coordinate but still is not working for all font types.

In my example, I want all text part to be top-aligned with the horizontal line. But for different fonts and font size, It is not aligned properly. It is working for first font(Avenir Black) but not for others.

enter image description here

Below is the code I am using to generate this printed document:

     static void PrintDocument(string filePath)
        {
            // Create new document
            PrintDocument pd = new PrintDocument();
            pd.PrintPage += new PrintPageEventHandler(delegate (Object sender, PrintPageEventArgs e)
            {
                //e.Graphics.Clear(Color.White);

                var Canvas = e.Graphics;
                var rectF = new RectangleF(0f, 100, 1000, 78.74016f);

                Pen p = new Pen(Color.Black);
                p.Width = 1 / 2;
                p.DashStyle = DashStyle.Solid;
                Canvas.DrawLine(p, rectF.X, rectF.Y, rectF.X + rectF.Width, rectF.Y);

                rectF = new RectangleF(0f, 100, 250, 78.74016f);

                DrawPrice(Canvas, "Avenir Black", ref rectF);
                rectF.X += 20;
                rectF.Y = 100;
                DrawPrice(Canvas, "Arial", ref rectF);
                rectF.X += 20;
                rectF.Y = 100;
                DrawPrice(Canvas, "Calibri", ref rectF);
                rectF.X += 20;
                rectF.Y = 100;
                DrawPrice(Canvas, "Times New Roman", ref rectF);
            });
            pd.Print();

            Process.Start(filePath);
        }

        private static void DrawPrice(Graphics Canvas, string fontName, ref RectangleF rectF)
        {
            StringFormat format = new StringFormat();
            format.Alignment = StringAlignment.Center;
            format.LineAlignment = StringAlignment.Center;

            var superFont = new Font(fontName, 20, new FontStyle());
            var font = new Font(fontName, 61, new FontStyle());

            Brush brush = new SolidBrush(Color.FromName("black"));
            var currencySymbol = "$";
            var mainPrice = "29";
            var cents = "50";

            var superFontMetrics = FontInfo(Canvas, superFont, GraphicsUnit.Point);
            var fontMetrics = FontInfo(Canvas, font, GraphicsUnit.Point);
            var symbolSize = Canvas.MeasureString(currencySymbol, superFont, rectF.Size, format);
            var centsSize = Canvas.MeasureString(cents, superFont, rectF.Size, format);
            var dollarSize = Canvas.MeasureString(mainPrice, font, rectF.Size, format);

            Canvas.DrawString(currencySymbol, superFont, brush, new RectangleF(rectF.X, rectF.Y - superFontMetrics.InternalLeading, symbolSize.Width, symbolSize.Height), format);
            rectF.X += symbolSize.Width;
            Canvas.DrawString(mainPrice, font, brush, new RectangleF(rectF.X, rectF.Y, dollarSize.Width, rectF.Height), format);
            var mainPriceX = rectF.X + (dollarSize.Width / 2);
            rectF.X += dollarSize.Width;
            Canvas.DrawString(cents, superFont, brush, new RectangleF(rectF.X, rectF.Y - superFontMetrics.InternalLeading, centsSize.Width, centsSize.Height), format);
            rectF.Y += rectF.Height;
            rectF.X += centsSize.Width;

            var titleFont = new Font(fontName, 5, new FontStyle());
            var titleTextSize = Canvas.MeasureString(fontName, titleFont, rectF.Size, format);
            Canvas.DrawString(fontName, titleFont, brush, new RectangleF(mainPriceX, rectF.Y, titleTextSize.Width, titleTextSize.Height), format);
        }

        public static FontMetrics FontInfo(Graphics gr, Font font, GraphicsUnit toUnit)
        {
            FontMetrics metrics = new FontMetrics();
            float em_height = font.FontFamily.GetEmHeight(font.Style);
            metrics.EmHeight = ConvertUnits(gr, font.Size, font.Unit, toUnit);
            float design_to_points = metrics.EmHeight / em_height;

            metrics.Ascent = design_to_points * font.FontFamily.GetCellAscent(font.Style);
            metrics.Descent = design_to_points * font.FontFamily.GetCellDescent(font.Style);
            metrics.CellHeight = metrics.Ascent + metrics.Descent;
            metrics.InternalLeading = metrics.CellHeight - metrics.EmHeight;
            metrics.LineSpacing = design_to_points * font.FontFamily.GetLineSpacing(font.Style);
            metrics.ExternalLeading = metrics.LineSpacing - metrics.CellHeight;
            return metrics;
        }
Abhishek Prajapati
  • 345
  • 1
  • 4
  • 15
  • It is [possible](https://stackoverflow.com/questions/16083873/how-to-get-the-distance-between-the-baseline-of-text-and-the-horizontal-border-o) but hard because it is a dubious idea, imo, esp. if you want it on a per character basis: do you really want to top-align an 'm' with and 'M' ??? – TaW May 20 '20 at 07:29
  • Nope, I don't want to top-align `m` with `M`. I just want to align these three price components. – Abhishek Prajapati May 20 '20 at 23:34
  • OK, but that's how fonts work: They may or may not have varying space for varying glyphs at the top. The reason are artistic decisions of the font designers. You can confirm this by looking at the ascenders of a full glyph printout.. – TaW May 21 '20 at 03:11
  • I have gone through the [link](https://stackoverflow.com/questions/16083873/how-to-get-the-distance-between-the-baseline-of-text-and-the-horizontal-border-o) provided by you but It is to get baseline of text but I really need to remove the space from top. If you check the snapshot I attached. I have duducted internal leading from y coordinate but it didn't work for all type of fonts. In other words, can I identify coordinate of start line i.e. place from where text starts? – Abhishek Prajapati May 21 '20 at 04:05
  • You are right, my original comment was somewhat misleading; while you can get the ascenders' height from the fontfamily properties the actualy ascender's heights will not be the same for all glyphs. As your example shows some fonts make the numerals higher than any (or most) other glyphs. – TaW May 21 '20 at 06:03
  • So: No you can't get the effect you seek, unless you measure the actual height of the bounding box of all painted pixels. You can do that with a [graphicpath](https://stackoverflow.com/questions/33148543/how-to-draw-a-string-at-an-exact-pixel-position/33152250?r=SearchResults&s=56|24.2328#33152250) but will it be worth it?? When using that trick not all phrases of the same font will have the same baseline! – TaW May 21 '20 at 06:05
  • Nope, It will not be worth using that approach. I need to do something different. Anyways, thanks for your time mate!! – Abhishek Prajapati May 22 '20 at 00:39
  • Hello, [graphicspath](https://stackoverflow.com/questions/33148543/how-to-draw-a-string-at-an-exact-pixel-position/33152250?r=SearchResults&s=56|24.2328#33152250) is working. It did some minor changes in TranslateTransform inputs and it worked. Thank you so much – Abhishek Prajapati May 22 '20 at 01:49
  • But one minor problem with this approach is, it reduces the font size. – Abhishek Prajapati May 22 '20 at 02:35
  • _it reduces the font size_ - Um, it really shouldn't do that! (But the Baseline may move; that can't be helped except by tweaking the font size youself for fonts as needed to make it fill a certain height..) - But again: Fornt design is an art and imo one should respect the design decisions. – TaW May 22 '20 at 09:32
  • It is - because when we print using GraphicsPath.AddString, we don't have any option to set Font Size. Instead we need to pass emSize which is `The height of the em square box that bounds the character.` So I have to calculate the EMSize of font and pass. I did that and then it worked. I calculated it by this: `var emSize = (fontSizeInPoints * (ascent + descent)) / fontEmHeight;` Thanks, – Abhishek Prajapati May 25 '20 at 01:58

1 Answers1

1

After using GraphicsPath >> AddString method to print string, I got the result I want i.e. printed texts are top aligned for all type of fonts. Here is the output:

enter image description here

Below is the code I used:

private void DrawStringByGraphicsPath(Graphics g, Font font, Brush brush, ref RectangleF rectF, RectangleF elementRectF, StringFormat format, string text)
{
    if (!string.IsNullOrEmpty(text))
    {
        using (GraphicsPath graphicsPath = new GraphicsPath())
        {
            graphicsPath.AddString(text, font.FontFamily, (int)font.Style, GetEMSize(g, font), elementRectF.Location, format);

            // this is the net bounds without any whitespace:
            RectangleF br = graphicsPath.GetBounds();

            // Transform it for top alignment
            g.TranslateTransform(elementRectF.X - br.X, (elementRectF.Y - br.Y));
            g.FillPath(brush, graphicsPath);
            g.ResetTransform();
        }
    }
}

private float GetEMSize(Graphics canvas, Font font)
{
    return (font.SizeInPoints * (font.FontFamily.GetCellAscent(font.Style) + font.FontFamily.GetCellDescent(font.Style))) / font.FontFamily.GetEmHeight(font.Style);
}

Abhishek Prajapati
  • 345
  • 1
  • 4
  • 15