0

I needed to write text left-to-right but each character rotated 90 degrees counter clock wise so that on a print out it would look like the text is written vertically top-to-bottom like this:

vertical text

To achieve this I've created a temporary Bitmap with the same resolution, PageUnit and PageScale properties as the main canvas this needs to be drawn on. I draw the string onto the Bitmap and position the characters as required and then DrawImage the Bitmap onto the main canvas.

This works correctly except that adjusting the main canvas' PageScale causes the Bitmap's text to pixelate even though its PageScale gets adjusted as well before the DrawString happens. Drawing the same string directly onto the main canvas (not rotated, of course) produces a perfect result. The text scales smoothly withe the PageScale.

Can anything think of a potential reason for this?

private void DrawStringVerticalStackingV4(string text, Graphics g, SizeF sizeMaxArea, PointF startPoint, Font font, Brush brush, StringFormat sf)
{
    if (text.Length == 0) return;


    // Duplicate the StringFormat but change the alignment and directionality.
    StringFormat sfVertical = (StringFormat)sf.Clone();

    sfVertical.Alignment = StringAlignment.Near;
    sfVertical.LineAlignment = StringAlignment.Near;
    sfVertical.FormatFlags = StringFormatFlags.DirectionVertical;

    SizeF sizeFullText = g.MeasureString(text, font);

    // Measure all the characters
    SizeF[] sizeChars = new SizeF[text.Length];

    for (int i = 0; i < text.Length; i++)
    {
        sizeChars[i] = g.MeasureString(text[i].ToString(), font, sizeMaxArea, StringFormat.GenericTypographic);
    }

    // Create bitmap to draw the string on
    float sumCharsHeight = (from item in sizeChars select item.Height).Sum();
    float maxCharsWidth = (from item in sizeChars select item.Width).Max() * 1.25f;

    Bitmap bmp = new Bitmap(MillimeterToPixel(g, sumCharsHeight), MillimeterToPixel(g, sizeMaxArea.Height), g);
    Graphics gBmp = Graphics.FromImage(bmp);

    bmp.MakeTransparent();

    gBmp.PageUnit = g.PageUnit;
    gBmp.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;

    // Draw each character at a time in reverse order
    float currentX = -1.5f;

    for (int i = text.Length - 1; i >= 0; i--)
    {
        string currentChar = text[i].ToString();

        gBmp.DrawString(currentChar, font, brush, currentX, (sizeMaxArea.Height / 2) - (sizeChars[i].Width / 2), sfVertical);

        currentX += sizeChars[i].Height;
    }

    // Flip the bitmap
    bmp.RotateFlip(RotateFlipType.Rotate180FlipNone);

    // Crop the temporary bitmap to the maximum size
    RectangleF srcRect = new RectangleF(0, 0, Math.Min(gBmp.VisibleClipBounds.Width, sizeMaxArea.Width), Math.Min(gBmp.VisibleClipBounds.Height, sizeMaxArea.Height));

    Bitmap bmpCropped = bmp.Clone(new Rectangle(0, 0, MillimeterToPixel(gBmp, srcRect.Width), MillimeterToPixel(gBmp, srcRect.Height)) , bmp.PixelFormat);
    Graphics gCropped = Graphics.FromImage(bmpCropped);

    gCropped.PageUnit = g.PageUnit;

    // Draw the bitmap on the original canvas
    srcRect.Width = gCropped.VisibleClipBounds.Width;
    srcRect.Height = gCropped.VisibleClipBounds.Height;

    RectangleF destRect = new RectangleF(startPoint.X, startPoint.Y, gCropped.VisibleClipBounds.Width, gCropped.VisibleClipBounds.Height);

    // This is important!!! Without it, the copied image is distorted.
    g.InterpolationMode = InterpolationMode.NearestNeighbor;

    g.DrawImage(bmpCropped, destRect, srcRect, g.PageUnit);

    // Housekeeping
    gCropped.Dispose();
    bmpCropped.Dispose();
    gBmp.Dispose();
    bmp.Dispose();
}

More Information

I have observed that it was the font size that wasn't changing correctly when drawing on the Bitmap so I did a few test that surprised me and I still don't quite understand why it's working the way it is.

Consider an empty form with just a PictureBox and a TrackBar going from 1 to 19 and set to 10 by default. The TrackBar controls the scale variable and it refreshes the PictureBox when its moved.

private void trackBar1_Scroll(object sender, EventArgs e)
{
    scale = trackBar1.Value / 10f;

    pictureBox1.Refresh();
}

Scenario 1 PageScale isn't changed, but the scale controls the font size. In this scenario, MeasureString returns the expected result whereby it returns a different size when the font size changes and the drawn test change in size with the font size.

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.PageUnit = GraphicsUnit.Millimeter;

    Console.WriteLine("Scale: " + scale);

    Font fontMM = new Font("Calibri", 8 * scale);

    Console.WriteLine("Font: " + fontMM.Size + " Millimeters: " + g.MeasureString("Test 123", fontMM));

    g.DrawString("Test 123", fontMM, new SolidBrush(Color.Black), 10, 10);
}

Returns:

Scale: 1
Font: 8 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.1
Font: 8.8 Millimeters: {Width=12.09137, Height=4.17766}
Scale: 1.2
Font: 9.6 Millimeters: {Width=13.19058, Height=4.557447}
Scale: 1.3
Font: 10.4 Millimeters: {Width=14.28979, Height=4.937234}
Scale: 1.4
Font: 11.2 Millimeters: {Width=15.38901, Height=5.317022}
Scale: 1.5
Font: 12 Millimeters: {Width=16.48823, Height=5.696809}
Scale: 1.6
Font: 12.8 Millimeters: {Width=17.58744, Height=6.076596}
Scale: 1.7
Font: 13.6 Millimeters: {Width=18.68666, Height=6.456384}

Scenario 2 Only PageScale changes but font size stays the same. As expected, MeasureString returns a reducing size as the scale grows because the text would get smaller since the font stayed the same. BUT - DrawString draws the exact same size string every time regardless of PageScale if the font size is the same!!!

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.PageScale = scale;

    Console.WriteLine("Scale: " + scale);

    g.PageUnit = GraphicsUnit.Millimeter;

    Font fontMM = new Font("Calibri", 8);

    Console.WriteLine("Font: " + fontMM.Size + " Millimeters: " + g.MeasureString("Test 123", fontMM));

    g.DrawString("Test 123", fontMM, new SolidBrush(Color.Black), 10, 10);
}

This returns:

Scale: 1
Font: 8 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.1
Font: 8 Millimeters: {Width=9.992863, Height=3.452611}
Scale: 1.2
Font: 8 Millimeters: {Width=9.160125, Height=3.164894}
Scale: 1.3
Font: 8 Millimeters: {Width=8.455501, Height=2.921441}
Scale: 1.4
Font: 8 Millimeters: {Width=7.851536, Height=2.712766}
Scale: 1.5
Font: 8 Millimeters: {Width=7.3281, Height=2.531915}
Scale: 1.6
Font: 8 Millimeters: {Width=6.870094, Height=2.37367}
Scale: 1.7
Font: 8 Millimeters: {Width=6.465971, Height=2.234043}
Scale: 1.8
Font: 8 Millimeters: {Width=6.10675, Height=2.109929}
Scale: 1.9
Font: 8 Millimeters: {Width=5.785342, Height=1.99888}

Scenario 3 Since the font size on the screen did not actually change as PageScale increased, the only thing left to do is to increase the font size as well and this is where the problem lies. Increasing PageScale and the font size by the same factor effectively negates each other as far as MeasureString seems to be concerned! This means that MesaureString returns the exact same size no matter what the font size is. The positive thing though is that the drawn string does actually increase in size as one would expect when increasing PageScale.

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.PageScale = scale;

    Console.WriteLine("Scale: " + scale);

    g.PageUnit = GraphicsUnit.Millimeter;

    Font fontMM = new Font("Calibri", 8 * scale);

    Console.WriteLine("Font: " + fontMM.Size + " Millimeters: " + g.MeasureString("Test 123", fontMM));

    g.DrawString("Test 123", fontMM, new SolidBrush(Color.Black), 10, 10);
}

Returns:

Scale: 1
Font: 8 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.1
Font: 8.8 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.2
Font: 9.6 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.3
Font: 10.4 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.4
Font: 11.2 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.5
Font: 12 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.6
Font: 12.8 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.7
Font: 13.6 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.8
Font: 14.4 Millimeters: {Width=10.99215, Height=3.797873}
Scale: 1.9
Font: 15.2 Millimeters: {Width=10.99215, Height=3.797873}

Conclusion As far as I can tell, the fault here lies with DrawString that doesn't seem to be affected by PageScale. Am I wrong in thinking that this is not the expected behavior?!

8
  • What is the difference between bitmap and vector images? Commented May 27, 2019 at 20:19
  • There is GDI and GDI+ string drawing. You may want to try TextRenderer Commented May 27, 2019 at 21:13
  • @paulsm4 Thanks Paul, I completely understand that a bitmap is not a vector, but because its Graphics object supports PageUnit in millimeters, I would have expected it to render the same way as a normal control's Paint does. If I draw the same exact thing onto a PictureBox (for example) as I do onto a Bitmap with the exact same resolution, PageUnit and PageScale properties, and then draw that Bitmap onto the PictureBox, I would expect the results to be identical. Isn't that right? Commented Jun 1, 2019 at 17:57
  • 1
    @TaW Thank you for that. Unfortunately, I have to work in millimeters because the full drawing correlates to real-world dimensions and gets printed in the end. Commented Jun 1, 2019 at 20:30
  • 1
    I think I know where the problem stems though. For reasons best known to someone else, g.MeasureString(text, font) always returns the same SizeF regardless of font size which gets changed as PageScale changes. If I change the PageUnit to Display or Pixels as a test, then MeasureString works perfectly, but any real-world dimension returns the same result every time. Commented Jun 1, 2019 at 20:32

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.