Understanding the GDI+ Vertical Text Rotation Bug

In WinForms (using System.Drawing / GDI+), rendering vertical CJK (Chinese, Japanese, Korean) text using StringFormatFlags.DirectionVertical combined with Graphics.RotateTransform often leads to broken layouts. Instead of rotating the entire text block as a single cohesive unit, individual characters end up rotated sideways, misplaced, or rendered in the wrong reading order.

This happens because GDI+ handles vertical text layout by applying internal rotations to glyphs (keeping CJK characters upright while rotating Latin characters). When you apply a global RotateTransform to the Graphics object, these two transformation matrices clash, resulting in incorrectly oriented characters.

How to Solve the Issue

There are two highly reliable workarounds to solve this issue in .NET and .NET Framework.

Solution 1: Render to an Offscreen Bitmap First (Recommended)

The most robust way to rotate vertical text without messing up glyph orientation is to render the text onto a temporary, transparent Bitmap using your vertical format, and then draw that bitmap onto your main canvas using the rotation transform. This isolates the text rendering engine from the world transformation matrix.

private void DrawRotatedVerticalText(Graphics g, string text, Font font, Brush brush, Rectangle r, float angle)
{
    // 1. Create a temporary bitmap matching the size of the target area
    using (Bitmap bmp = new Bitmap(r.Width, r.Height))
    {
        using (Graphics bmpGraphics = Graphics.FromImage(bmp))
        {
            bmpGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            bmpGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

            var sf = new StringFormat(StringFormat.GenericTypographic)
            {
                Alignment = StringAlignment.Center,
                LineAlignment = StringAlignment.Center,
                FormatFlags = StringFormatFlags.DirectionVertical | StringFormatFlags.NoWrap,
                Trimming = StringTrimming.EllipsisCharacter,
            };

            // Draw the vertical text normally (unrotated) onto the bitmap
            bmpGraphics.DrawString(text, font, brush, new RectangleF(0, 0, r.Width, r.Height), sf);
        }

        // 2. Rotate and draw the bitmap onto the main graphics context
        var oldTransform = g.Transform;
        try
        { 
            float cx = r.X + r.Width / 2f;
            float cy = r.Y + r.Height / 2f;

            g.TranslateTransform(cx, cy);
            g.RotateTransform(angle);
            
            // Draw the bitmap centered on the rotation point
            g.DrawImage(bmp, -r.Width / 2f, -r.Height / 2f);
        }
        finally
        {
            g.Transform = oldTransform;
        }
    }
}

Solution 2: Use Win32 Vertical Fonts (The "@" Font Prefix)

Windows includes native vertical versions of CJK fonts prefixed with an "at" symbol (e.g., @Microsoft YaHei or @MS Gothic). These fonts automatically rotate characters 90 degrees counter-clockwise, meaning you write them horizontally, and then rotate the entire graphics context by 270 degrees (or -90 degrees) to display them vertically.

using (var font = new Font("@Microsoft YaHei", 12, FontStyle.Bold))
{
    // Note the '@' prefix. This tells Windows to use the vertical glyph layout.
    // When using vertical fonts, you handle the rotation manually via Graphics.RotateTransform.
}

Summary

  • Why it fails: GDI+'s DirectionVertical combines poorly with RotateTransform on CJK characters due to overlapping transformation matrices.
  • The Best Fix: Render the vertical text to an intermediate Bitmap first, then rotate and draw the bitmap. This completely bypasses the GDI+ matrix conflict.