0

I am using ExtTextOut wingdi function to draw text. Also, I am using SetWorldTransform for scaling text but now I want to rotate text(at all angles) too. How can I change/pass parameters to it so it will scale as well as rotate too?

Below is the sample code block, this code works perfect but now I want to introduce rotating text as well without impacting scaling.

var inputString = "Sample text";
var fontSize = 31f;
var font = new Font("Avenir Black", fontSize, new FontStyle());

var startX1 = 50;
var startY1 = 50;

LOGFONT lf = new LOGFONT();
font.ToLogFont(lf, e.Graphics);

IntPtr hPrinterDC = e.Graphics.GetHdc();
StringBuilder sbText = new StringBuilder(inputString);
if (hPrinterDC != IntPtr.Zero)
{
    int nPrintHorzRes = GetDeviceCaps(hPrinterDC, HORZRES);
    int nPrintVertRes = GetDeviceCaps(hPrinterDC, VERTRES);
    int nPhysWidth = GetDeviceCaps(hPrinterDC, PHYSICALWIDTH);
    int nPhysHeight = GetDeviceCaps(hPrinterDC, PHYSICALHEIGHT);
    int nPhysOffsetX = GetDeviceCaps(hPrinterDC, PHYSICALOFFSETX);
    int nPhysOffsetY = GetDeviceCaps(hPrinterDC, PHYSICALOFFSETY);

    IntPtr hDCScreen = GetDC(IntPtr.Zero);
    float nLogPixelsXScreen = GetDeviceCaps(hDCScreen, LOGPIXELSX);
    float nLogPixelsYScreen = GetDeviceCaps(hDCScreen, LOGPIXELSY);
    ReleaseDC(hDCScreen, IntPtr.Zero);
    float nLogPixelsXPrinter = GetDeviceCaps(hPrinterDC, LOGPIXELSX);
    float nLogPixelsYPrinter = GetDeviceCaps(hPrinterDC, LOGPIXELSY);

    float nScaleX = Math.Max(nLogPixelsXScreen, nLogPixelsXPrinter) / Math.Min(nLogPixelsXScreen, nLogPixelsXPrinter);
    float nScaleY = Math.Max(nLogPixelsYScreen, nLogPixelsYPrinter) / Math.Min(nLogPixelsYScreen, nLogPixelsYPrinter);

    System.Drawing.Drawing2D.Matrix transform = new System.Drawing.Drawing2D.Matrix();
    transform.Scale(nScaleX, nScaleY);
    XFORM renderTransform = new XFORM();
    var elements = transform.Elements;
    var m11 = elements[0];
    var m12 = elements[1];
    var m21 = elements[2];
    var m22 = elements[3];
    var dx = elements[4];
    var dy = elements[5];

    renderTransform.eM11 = (float)m11;
    renderTransform.eM12 = (float)m12;
    renderTransform.eM21 = (float)m21;
    renderTransform.eM22 = (float)m22;
    
    int nOffsetX = 0;
    int nOffsetY = 0;
    renderTransform.eDx = (float)transform.OffsetX + nOffsetX;
    renderTransform.eDy = (float)transform.OffsetY + nOffsetY;
    SetGraphicsMode(hPrinterDC, GM_ADVANCED);
    
    SetMapMode(hPrinterDC, MM_TEXT);
    bool bRet = SetWorldTransform(hPrinterDC, ref renderTransform);

    var startX2 = startX1;
    var startY2 = startY1;
    RECT rc = new RECT(startX2, startY2, nPhysWidth, nPhysHeight);   

    SetViewportOrgEx(hPrinterDC, -nPhysOffsetX, -nPhysOffsetY, IntPtr.Zero);
    
    lf.lfHeight = (int)(lf.lfHeight / nScaleY);   

    IntPtr hFontNew = CreateFontIndirect(lf);
    IntPtr hFontOld = SelectObject(hPrinterDC, hFontNew);

    SetBkMode(hPrinterDC, TRANSPARENT);
    SetTextColor(hPrinterDC, ColorTranslator.ToWin32(Color.Blue));      
    ExtTextOut(hPrinterDC, rc.left, rc.top, ETO_OPAQUE, IntPtr.Zero, sbText, (uint)sbText.Length, IntPtr.Zero);

    rc.top += -lf.lfHeight;
    SetTextColor(hPrinterDC, ColorTranslator.ToWin32(Color.Green));
    ExtTextOut(hPrinterDC, rc.left, rc.top, ETO_OPAQUE, IntPtr.Zero, sbText, (uint)sbText.Length, IntPtr.Zero);

    var nExtra = -10.0f / nScaleX;    
    SetTextCharacterExtra(hPrinterDC, (int)nExtra);
    rc.top += -lf.lfHeight;
    SetTextColor(hPrinterDC, ColorTranslator.ToWin32(Color.Red));
    ExtTextOut(hPrinterDC, rc.left, rc.top, ETO_OPAQUE, IntPtr.Zero, sbText, (uint)sbText.Length, IntPtr.Zero);

    SelectObject(hPrinterDC, hFontOld);
    DeleteObject(hFontNew);
}

Here are pinvokes:

  [DllImport("Gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool SetWorldTransform(IntPtr hdc, ref XFORM lpxf);

        [StructLayout(LayoutKind.Sequential)]
        public struct XFORM
        {
            public float eM11;
            public float eM12;
            public float eM21;
            public float eM22;
            public float eDx;
            public float eDy;
        }

        [DllImport("Gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern int SetGraphicsMode(IntPtr hdc, int iMode);

        public const int GM_COMPATIBLE = 1;
        public const int GM_ADVANCED = 2;
        public const int GM_LAST = 2;
        
        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern IntPtr GetDC(IntPtr hWnd);

        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
        
        public const int TRANSPARENT = 1;
        public const int OPAQUE = 2;
        public const int BKMODE_LAST = 2;

        [DllImport("Gdi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern int SetBkMode(IntPtr hdc, int mode);
Abhishek Prajapati
  • 345
  • 1
  • 4
  • 15
  • 1
    See a description of combined Matrix functionality here: [Flip the GraphicsPath that draws the text/string](https://stackoverflow.com/a/53182901/7444103) (there's a **Caution** note about the order of composite transformations) and an *exercise* in Matrix rotation (using `Matrix.RotateAt()`) here: [Transparent Overlapping Circular Progress Bars](https://stackoverflow.com/a/53379442/7444103). Why are you using GDI to draw this when you have a Graphics object already? Can't you just use `TextRenderr.DrawText()` if you want the *alternative* rendering? – Jimi Nov 02 '20 at 04:51
  • 1
    Right, that was [TextRenderer.DrawText()](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.textrenderer.drawtext), not its pirate version :) – Jimi Nov 02 '20 at 05:29
  • Jimi, I have to achieve this scaling + rotation using GDI functions only as I am using GDI functions for character spacing which Graphics(GDI+) does not offer. – Abhishek Prajapati Nov 03 '20 at 00:29

0 Answers0