A
AliR \(VC++ MVP\)
I'm trying to write a RTF render code in C#. I have the code in C++/MFC and
it works fine, but I have run into a problem with the C# code. I think the
C# code is from Microsoft, and I added scaling to it. The C# code is
working fine on my desktop PC but it cuts off some of the text when I run it
on my laptop. The MFC code works fine both on the laptop and the PC. I
have looked at all the numbers used in the MFC and the C# code and they are
identical. Both PC and Laptop are at 1440x900 96dpi font size.
I have been looking at the code for 3 or 4 days now without any improvment.
Was wondering if anyone here wants to take a stab at it.
You can download both sample projects:
http://www.learnstar.com/AliR/RTFScaler.zip
http://www.learnstar.com/AliR/RTFScalerMFC.zip
Thanks
AliR.
Here is the MFC code
// *** copying/scaling/rotating and shearing the text is done here
void CRTFScalerDlg::RenderText(int nScalePercent,int nRotationDegrees,int
nHorzShearPercent,int nVertShearPercent)
{
// validate the arguments
if (nScalePercent < 1)
nScalePercent = 1;
if (nScalePercent > 10000)
nScalePercent = 10000;
if (nRotationDegrees < 0)
nRotationDegrees = 0;
if (nRotationDegrees > 360)
nRotationDegrees = 360;
if (nHorzShearPercent < 0)
nHorzShearPercent = 0;
if (nHorzShearPercent > 100)
nHorzShearPercent = 100;
if (nVertShearPercent < 0)
nVertShearPercent = 0;
if (nVertShearPercent > 100)
nVertShearPercent = 100;
// setup target window, rect and DC
CWnd* pTargetWin = GetDlgItem(IDC_EDIT1);
CRect crTarget;
pTargetWin->GetClientRect(&crTarget);
crTarget.OffsetRect(0,20);
CDC* pTargetDC = pTargetWin->GetDC();
pTargetDC->IntersectClipRect(&crTarget); // so we don't clobber the parent
dialog
pTargetDC->SetBkMode(TRANSPARENT); // so the legend doesn't erase too
much
// also need advanced graphics mode for the world transform stuff
if (!SetGraphicsMode(pTargetDC->m_hDC,GM_ADVANCED))
{
pTargetDC->DrawText("Nice try but I need Windows NT, 2000, XP or 2003 for
this.",crTarget,DT_SINGLELINE | DT_VCENTER | DT_CENTER);
return;
}
// erase previous contents
FillRect(pTargetDC->m_hDC,crTarget,(HBRUSH)(COLOR_WINDOW + 1));
CString Temp;
Temp.Format("crTarget %d,%d
%d,%d\n",crTarget.left,crTarget.top,crTarget.Width(),crTarget.Height());
TRACE(Temp);
// setup rectangles for metafile fiddling
CSize cTargetSize = crTarget.Size();
pTargetDC->DPtoHIMETRIC(&cTargetSize); // from MM_Text to MM_HIMETRIC
CSize TopLeft;
TopLeft.cx = crTarget.left;
TopLeft.cy = crTarget.top;
pTargetDC->DPtoHIMETRIC(&TopLeft); // from MM_Text to MM_HIMETRIC
CRect cHiMetricRect(0,0,cTargetSize.cx,cTargetSize.cy);
CRect cTwipsRect(0,0,(TWIPS_INCH * cTargetSize.cx + HIMETRIC_INCH / 2) /
HIMETRIC_INCH,
(TWIPS_INCH * cTargetSize.cy + HIMETRIC_INCH / 2) / HIMETRIC_INCH);
Temp.Format("MetaSize
%d,%d\n",cHiMetricRect.Width(),cHiMetricRect.Height());
TRACE(Temp);
Temp.Format("cTwipsRect %d,%d\n",cTwipsRect.Width(),cTwipsRect.Height());
TRACE(Temp);
// create the enhanced metafile
HDC hDCEMF = CreateEnhMetaFile(pTargetDC->m_hDC,NULL,cHiMetricRect,NULL);
// setup the format struct and do the RTF range formatting call
FORMATRANGE stFR;
stFR.hdcTarget = stFR.hdc = hDCEMF; // render to the memory helper EMF
stFR.rcPage = stFR.rc = cTwipsRect; // using this rectangle (in twips)
stFR.chrg.cpMin = 0; // and render all of the text
stFR.chrg.cpMax = -1;
GetDlgItem(IDC_RICHEDIT1)->SendMessage(EM_FORMATRANGE,TRUE,(LPARAM) &stFR);
GetDlgItem(IDC_RICHEDIT1)->SendMessage(EM_FORMATRANGE,TRUE,NULL); // this
call clears the cache
// drawing into the metafile is done, get ourselves an handle to it (used
for the replay)
HENHMETAFILE hEMF = CloseEnhMetaFile(hDCEMF);
// calculate the automagic fudge factors by getting the device metrics
int nHorzSize = pTargetDC->GetDeviceCaps(HORZSIZE ); // width in
millimeters
int nVertSize = pTargetDC->GetDeviceCaps(VERTSIZE ); // height in
millimeters
int nHorzRes = pTargetDC->GetDeviceCaps(HORZRES ); // width in pixels
int nVertRes = pTargetDC->GetDeviceCaps(VERTRES ); // height in
pixels
int nLogPixelsX = pTargetDC->GetDeviceCaps(LOGPIXELSX); // # of pixels per
inch horizontally
int nLogPixelsY = pTargetDC->GetDeviceCaps(LOGPIXELSY); // # of pixels per
inch vertically
float fHorzSizeInches = nHorzSize / 25.4f;
float fVertSizeInches = nVertSize / 25.4f;
float fHorzFudgeFactor = (nHorzRes / fHorzSizeInches) / nLogPixelsX; //
divide expected DPI with actual DPI
float fVertFudgeFactor = (nVertRes / fVertSizeInches) / nLogPixelsY; //
Temp.Format("Fudgfactor %.2f %.2f\n",fHorzFudgeFactor,fVertFudgeFactor);
TRACE(Temp);
// change from degrees to radians
// (also need to change sign since page space is upside down)
float fRotationInRadians = (PI * (0 - nRotationDegrees)) / 180.0F;
// do the world transforms, first apply the scale and fudge factors
XFORM stX;
ZeroMemory(&stX,sizeof(stX));
stX.eM11 = fHorzFudgeFactor * (nScalePercent / 100.0f);
stX.eM22 = fVertFudgeFactor * (nScalePercent / 100.0f);
SetWorldTransform(pTargetDC->m_hDC,&stX);
// then apply the rotation
ZeroMemory(&stX,sizeof(stX));
stX.eM11 = (float) cos(fRotationInRadians);
stX.eM12 = (float) sin(fRotationInRadians);
stX.eM21 = 0 - (float) sin(fRotationInRadians);
stX.eM22 = (float) cos(fRotationInRadians);
ModifyWorldTransform(pTargetDC->m_hDC,&stX,MWT_LEFTMULTIPLY);
// finally apply the horizontal and vertical shearing
ZeroMemory(&stX,sizeof(stX));
stX.eM11 = 1.0f;
stX.eM12 = nHorzShearPercent / 100.0f;
stX.eM21 = nVertShearPercent / 100.0f;
stX.eM22 = 1.0f;
ModifyWorldTransform(pTargetDC->m_hDC,&stX,MWT_LEFTMULTIPLY);
crTarget.OffsetRect(0,-20);
crTarget.OffsetRect(0,20 / (nScalePercent / 100.0f) * fVertFudgeFactor);
// play the metafile
pTargetDC->PlayMetaFile(hEMF,&crTarget);
// that's it, kiss the metafile goodbye
DeleteEnhMetaFile(hEMF);
// also show a legend
ModifyWorldTransform(pTargetDC->m_hDC,NULL,MWT_IDENTITY);
CString sLegend;
sLegend.Format("Scale: %d%% rotation: %dº horizontal shear: %d%%
vertical shear: %d%%.",
nScalePercent,nRotationDegrees,nHorzShearPercent,nVertShearPercent);
pTargetDC->SetTextColor(RGB(55,111,111));
pTargetDC->DrawText(sLegend,crTarget,DT_SINGLELINE | DT_VCENTER |
DT_CENTER);
}
Here is the C# code:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Text;
public static class Graphics_DrawRtfText
{
private static RichTextBoxDrawer rtfDrawer;
public static void DrawRtfText(this Graphics graphics, string rtf,
RectangleF layoutArea,float xFactor)
{
if (Graphics_DrawRtfText.rtfDrawer == null)
{
Graphics_DrawRtfText.rtfDrawer = new RichTextBoxDrawer();
}
Graphics_DrawRtfText.rtfDrawer.Rtf = rtf;
Graphics_DrawRtfText.rtfDrawer.Draw(graphics, layoutArea,xFactor);
}
private class RichTextBoxDrawer : RichTextBox
{
//Code converted from code found here:
http://support.microsoft.com/kb/812425/en-us
//Convert the unit used by the .NET framework (1/100 inch)
//and the unit used by Win32 API calls (twips 1/1440 inch)
private const double anInch = 14.4;
protected override CreateParams CreateParams
{
get
{
CreateParams createParams = base.CreateParams;
if (SafeNativeMethods.LoadLibrary("msftedit.dll") !=
IntPtr.Zero)
{
createParams.ExStyle |=
SafeNativeMethods.WS_EX_TRANSPARENT; // transparent
createParams.ClassName = "RICHEDIT50W";
}
return createParams;
}
}
private void DPToHIMETRIC(Graphics graphics,ref SizeF size)
{
size.Width = (size.Width * 2540.0f) / graphics.DpiX;
size.Height = (size.Height * 2540.0f) / graphics.DpiY;
}
public void Draw(Graphics graphics, RectangleF layoutArea, float
xFactor)
{
System.Diagnostics.Debug.WriteLine("LayoutArea " + layoutArea);
SizeF metaSize = layoutArea.Size;
DPToHIMETRIC(graphics, ref metaSize);
System.Diagnostics.Debug.WriteLine("MetaSize " + metaSize);
IntPtr hdc = graphics.GetHdc();
//create a metafile, convert the size to himetrics
Metafile metafile = new Metafile(hdc, new
RectangleF(0,0,metaSize.Width,metaSize.Height));
graphics.ReleaseHdc(hdc);
Graphics g = Graphics.FromImage(metafile);
IntPtr hDCEMF = g.GetHdc();
//Calculate the area to render.
SafeNativeMethods.RECT rectLayoutArea;
rectLayoutArea.Left = 0;
rectLayoutArea.Top = 0;
rectLayoutArea.Right = (int)((1440 * metaSize.Width + 2540 / 2)
/ 2540);
rectLayoutArea.Bottom = (int)((1440 * metaSize.Height + 2540 /
2) / 2540);
System.Diagnostics.Debug.WriteLine(String.Format("RectLayoutArea
({0},{1})",rectLayoutArea.Right,rectLayoutArea.Bottom));
SafeNativeMethods.FORMATRANGE fmtRange;
fmtRange.chrg.cpMax = -1; //Indicate character from
to character to
fmtRange.chrg.cpMin = 0;
fmtRange.hdc = hDCEMF; //Use the same DC for
measuring and rendering
fmtRange.hdcTarget = hDCEMF; //Point at printer hDC
fmtRange.rc = rectLayoutArea; //Indicate the area on page
to print
fmtRange.rcPage = rectLayoutArea; //Indicate size of page
IntPtr wParam = IntPtr.Zero;
wParam = new IntPtr(1);
//Get the pointer to the FORMATRANGE structure in memory
IntPtr lParam = IntPtr.Zero;
lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
Marshal.StructureToPtr(fmtRange, lParam, false);
SafeNativeMethods.SendMessage(this.Handle,
SafeNativeMethods.EM_FORMATRANGE, wParam, lParam);
SafeNativeMethods.SendMessage(this.Handle,
SafeNativeMethods.EM_FORMATRANGE, wParam, IntPtr.Zero);
//Free the block of memory allocated
Marshal.FreeCoTaskMem(lParam);
//Release the device context handle obtained by a previous call
g.ReleaseHdc(hDCEMF);
g.Dispose();
hdc = graphics.GetHdc();
int nHorzSize = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.HORZSIZE);
int nVertSize = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.VERTSIZE);
int nHorzRes = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.HORZRES);
int nVertRes = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.VERTRES);
int nLogPixelsX = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.LOGPIXELSX);
int nLogPixelsY = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.LOGPIXELSY);
graphics.ReleaseHdc(hdc);
float fHorzSizeInches = nHorzSize / 25.4f;
float fVertSizeInches = nVertSize / 25.4f;
float fHorzFudgeFactor = (nHorzRes / fHorzSizeInches) /
nLogPixelsX;
float fVertFudgeFactor = (nVertRes / fVertSizeInches) /
nLogPixelsY;
System.Diagnostics.Debug.WriteLine("Fudge Factor " +
fHorzFudgeFactor.ToString() + " " + fVertFudgeFactor.ToString() + " XFactor
" + xFactor.ToString());
Pen RedPen = new Pen(Color.Red);
graphics.DrawRectangle(RedPen, layoutArea.X * xFactor,
layoutArea.Y * xFactor, layoutArea.Width * xFactor, layoutArea.Height *
xFactor);
float Left = layoutArea.Left;
float Top = layoutArea.Top;
//layoutArea.X = layoutArea.Y = 0;
layoutArea.Offset(-Left, -Top);
layoutArea.Offset(Left / fHorzFudgeFactor, Top /
fVertFudgeFactor);
System.Drawing.Drawing2D.GraphicsState state = graphics.Save();
graphics.ScaleTransform(fHorzFudgeFactor * xFactor,
fVertFudgeFactor * xFactor);
graphics.DrawImage(metafile, layoutArea);
graphics.Restore(state);
System.Diagnostics.Debug.WriteLine("Layout Aread :
"+layoutArea);
}
#region SafeNativeMethods
private static class SafeNativeMethods
{
[DllImport("USER32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg,
IntPtr wp, IntPtr lp);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hdc, DeviceCap
nIndex);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct CHARRANGE
{
public int cpMin; //First character of range (0 for
start of doc)
public int cpMax; //Last character of range (-1 for
end of doc)
}
[StructLayout(LayoutKind.Sequential)]
public struct FORMATRANGE
{
public IntPtr hdc; //Actual DC to draw on
public IntPtr hdcTarget; //Target DC for determining text
formatting
public RECT rc; //Region of the DC to
draw to (in twips)
public RECT rcPage; //Region of the whole DC
(page size) (in twips)
public CHARRANGE chrg; //Range of text to draw (see
earlier declaration)
}
public enum DeviceCap : int
{
/// <summary>
/// Device driver version
/// </summary>
DRIVERVERSION = 0,
/// <summary>
/// Device classification
/// </summary>
TECHNOLOGY = 2,
/// <summary>
/// Horizontal size in millimeters
/// </summary>
HORZSIZE = 4,
/// <summary>
/// Vertical size in millimeters
/// </summary>
VERTSIZE = 6,
/// <summary>
/// Horizontal width in pixels
/// </summary>
HORZRES = 8,
/// <summary>
/// Vertical height in pixels
/// </summary>
VERTRES = 10,
/// <summary>
/// Number of bits per pixel
/// </summary>
BITSPIXEL = 12,
/// <summary>
/// Number of planes
/// </summary>
PLANES = 14,
/// <summary>
/// Number of brushes the device has
/// </summary>
NUMBRUSHES = 16,
/// <summary>
/// Number of pens the device has
/// </summary>
NUMPENS = 18,
/// <summary>
/// Number of markers the device has
/// </summary>
NUMMARKERS = 20,
/// <summary>
/// Number of fonts the device has
/// </summary>
NUMFONTS = 22,
/// <summary>
/// Number of colors the device supports
/// </summary>
NUMCOLORS = 24,
/// <summary>
/// Size required for device descriptor
/// </summary>
PDEVICESIZE = 26,
/// <summary>
/// Curve capabilities
/// </summary>
CURVECAPS = 28,
/// <summary>
/// Line capabilities
/// </summary>
LINECAPS = 30,
/// <summary>
/// Polygonal capabilities
/// </summary>
POLYGONALCAPS = 32,
/// <summary>
/// Text capabilities
/// </summary>
TEXTCAPS = 34,
/// <summary>
/// Clipping capabilities
/// </summary>
CLIPCAPS = 36,
/// <summary>
/// Bitblt capabilities
/// </summary>
RASTERCAPS = 38,
/// <summary>
/// Length of the X leg
/// </summary>
ASPECTX = 40,
/// <summary>
/// Length of the Y leg
/// </summary>
ASPECTY = 42,
/// <summary>
/// Length of the hypotenuse
/// </summary>
ASPECTXY = 44,
/// <summary>
/// Shading and Blending caps
/// </summary>
SHADEBLENDCAPS = 45,
/// <summary>
/// Logical pixels inch in X
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Logical pixels inch in Y
/// </summary>
LOGPIXELSY = 90,
/// <summary>
/// Number of entries in physical palette
/// </summary>
SIZEPALETTE = 104,
/// <summary>
/// Number of reserved entries in palette
/// </summary>
NUMRESERVED = 106,
/// <summary>
/// Actual color resolution
/// </summary>
COLORRES = 108,
// Printing related DeviceCaps. These replace the
appropriate Escapes
/// <summary>
/// Physical Width in device units
/// </summary>
PHYSICALWIDTH = 110,
/// <summary>
/// Physical Height in device units
/// </summary>
PHYSICALHEIGHT = 111,
/// <summary>
/// Physical Printable Area x margin
/// </summary>
PHYSICALOFFSETX = 112,
/// <summary>
/// Physical Printable Area y margin
/// </summary>
PHYSICALOFFSETY = 113,
/// <summary>
/// Scaling factor x
/// </summary>
SCALINGFACTORX = 114,
/// <summary>
/// Scaling factor y
/// </summary>
SCALINGFACTORY = 115,
/// <summary>
/// Current vertical refresh rate of the display device (for
displays only) in Hz
/// </summary>
VREFRESH = 116,
/// <summary>
/// Horizontal width of entire desktop in pixels
/// </summary>
DESKTOPVERTRES = 117,
/// <summary>
/// Vertical height of entire desktop in pixels
/// </summary>
DESKTOPHORZRES = 118,
/// <summary>
/// Preferred blt alignment
/// </summary>
BLTALIGNMENT = 119
}
public const int WM_USER = 0x0400;
public const int EM_FORMATRANGE = WM_USER + 57;
public const int WS_EX_TRANSPARENT = 0x20;
}
#endregion
}
}
it works fine, but I have run into a problem with the C# code. I think the
C# code is from Microsoft, and I added scaling to it. The C# code is
working fine on my desktop PC but it cuts off some of the text when I run it
on my laptop. The MFC code works fine both on the laptop and the PC. I
have looked at all the numbers used in the MFC and the C# code and they are
identical. Both PC and Laptop are at 1440x900 96dpi font size.
I have been looking at the code for 3 or 4 days now without any improvment.
Was wondering if anyone here wants to take a stab at it.
You can download both sample projects:
http://www.learnstar.com/AliR/RTFScaler.zip
http://www.learnstar.com/AliR/RTFScalerMFC.zip
Thanks
AliR.
Here is the MFC code
// *** copying/scaling/rotating and shearing the text is done here
void CRTFScalerDlg::RenderText(int nScalePercent,int nRotationDegrees,int
nHorzShearPercent,int nVertShearPercent)
{
// validate the arguments
if (nScalePercent < 1)
nScalePercent = 1;
if (nScalePercent > 10000)
nScalePercent = 10000;
if (nRotationDegrees < 0)
nRotationDegrees = 0;
if (nRotationDegrees > 360)
nRotationDegrees = 360;
if (nHorzShearPercent < 0)
nHorzShearPercent = 0;
if (nHorzShearPercent > 100)
nHorzShearPercent = 100;
if (nVertShearPercent < 0)
nVertShearPercent = 0;
if (nVertShearPercent > 100)
nVertShearPercent = 100;
// setup target window, rect and DC
CWnd* pTargetWin = GetDlgItem(IDC_EDIT1);
CRect crTarget;
pTargetWin->GetClientRect(&crTarget);
crTarget.OffsetRect(0,20);
CDC* pTargetDC = pTargetWin->GetDC();
pTargetDC->IntersectClipRect(&crTarget); // so we don't clobber the parent
dialog
pTargetDC->SetBkMode(TRANSPARENT); // so the legend doesn't erase too
much
// also need advanced graphics mode for the world transform stuff
if (!SetGraphicsMode(pTargetDC->m_hDC,GM_ADVANCED))
{
pTargetDC->DrawText("Nice try but I need Windows NT, 2000, XP or 2003 for
this.",crTarget,DT_SINGLELINE | DT_VCENTER | DT_CENTER);
return;
}
// erase previous contents
FillRect(pTargetDC->m_hDC,crTarget,(HBRUSH)(COLOR_WINDOW + 1));
CString Temp;
Temp.Format("crTarget %d,%d
%d,%d\n",crTarget.left,crTarget.top,crTarget.Width(),crTarget.Height());
TRACE(Temp);
// setup rectangles for metafile fiddling
CSize cTargetSize = crTarget.Size();
pTargetDC->DPtoHIMETRIC(&cTargetSize); // from MM_Text to MM_HIMETRIC
CSize TopLeft;
TopLeft.cx = crTarget.left;
TopLeft.cy = crTarget.top;
pTargetDC->DPtoHIMETRIC(&TopLeft); // from MM_Text to MM_HIMETRIC
CRect cHiMetricRect(0,0,cTargetSize.cx,cTargetSize.cy);
CRect cTwipsRect(0,0,(TWIPS_INCH * cTargetSize.cx + HIMETRIC_INCH / 2) /
HIMETRIC_INCH,
(TWIPS_INCH * cTargetSize.cy + HIMETRIC_INCH / 2) / HIMETRIC_INCH);
Temp.Format("MetaSize
%d,%d\n",cHiMetricRect.Width(),cHiMetricRect.Height());
TRACE(Temp);
Temp.Format("cTwipsRect %d,%d\n",cTwipsRect.Width(),cTwipsRect.Height());
TRACE(Temp);
// create the enhanced metafile
HDC hDCEMF = CreateEnhMetaFile(pTargetDC->m_hDC,NULL,cHiMetricRect,NULL);
// setup the format struct and do the RTF range formatting call
FORMATRANGE stFR;
stFR.hdcTarget = stFR.hdc = hDCEMF; // render to the memory helper EMF
stFR.rcPage = stFR.rc = cTwipsRect; // using this rectangle (in twips)
stFR.chrg.cpMin = 0; // and render all of the text
stFR.chrg.cpMax = -1;
GetDlgItem(IDC_RICHEDIT1)->SendMessage(EM_FORMATRANGE,TRUE,(LPARAM) &stFR);
GetDlgItem(IDC_RICHEDIT1)->SendMessage(EM_FORMATRANGE,TRUE,NULL); // this
call clears the cache
// drawing into the metafile is done, get ourselves an handle to it (used
for the replay)
HENHMETAFILE hEMF = CloseEnhMetaFile(hDCEMF);
// calculate the automagic fudge factors by getting the device metrics
int nHorzSize = pTargetDC->GetDeviceCaps(HORZSIZE ); // width in
millimeters
int nVertSize = pTargetDC->GetDeviceCaps(VERTSIZE ); // height in
millimeters
int nHorzRes = pTargetDC->GetDeviceCaps(HORZRES ); // width in pixels
int nVertRes = pTargetDC->GetDeviceCaps(VERTRES ); // height in
pixels
int nLogPixelsX = pTargetDC->GetDeviceCaps(LOGPIXELSX); // # of pixels per
inch horizontally
int nLogPixelsY = pTargetDC->GetDeviceCaps(LOGPIXELSY); // # of pixels per
inch vertically
float fHorzSizeInches = nHorzSize / 25.4f;
float fVertSizeInches = nVertSize / 25.4f;
float fHorzFudgeFactor = (nHorzRes / fHorzSizeInches) / nLogPixelsX; //
divide expected DPI with actual DPI
float fVertFudgeFactor = (nVertRes / fVertSizeInches) / nLogPixelsY; //
Temp.Format("Fudgfactor %.2f %.2f\n",fHorzFudgeFactor,fVertFudgeFactor);
TRACE(Temp);
// change from degrees to radians
// (also need to change sign since page space is upside down)
float fRotationInRadians = (PI * (0 - nRotationDegrees)) / 180.0F;
// do the world transforms, first apply the scale and fudge factors
XFORM stX;
ZeroMemory(&stX,sizeof(stX));
stX.eM11 = fHorzFudgeFactor * (nScalePercent / 100.0f);
stX.eM22 = fVertFudgeFactor * (nScalePercent / 100.0f);
SetWorldTransform(pTargetDC->m_hDC,&stX);
// then apply the rotation
ZeroMemory(&stX,sizeof(stX));
stX.eM11 = (float) cos(fRotationInRadians);
stX.eM12 = (float) sin(fRotationInRadians);
stX.eM21 = 0 - (float) sin(fRotationInRadians);
stX.eM22 = (float) cos(fRotationInRadians);
ModifyWorldTransform(pTargetDC->m_hDC,&stX,MWT_LEFTMULTIPLY);
// finally apply the horizontal and vertical shearing
ZeroMemory(&stX,sizeof(stX));
stX.eM11 = 1.0f;
stX.eM12 = nHorzShearPercent / 100.0f;
stX.eM21 = nVertShearPercent / 100.0f;
stX.eM22 = 1.0f;
ModifyWorldTransform(pTargetDC->m_hDC,&stX,MWT_LEFTMULTIPLY);
crTarget.OffsetRect(0,-20);
crTarget.OffsetRect(0,20 / (nScalePercent / 100.0f) * fVertFudgeFactor);
// play the metafile
pTargetDC->PlayMetaFile(hEMF,&crTarget);
// that's it, kiss the metafile goodbye
DeleteEnhMetaFile(hEMF);
// also show a legend
ModifyWorldTransform(pTargetDC->m_hDC,NULL,MWT_IDENTITY);
CString sLegend;
sLegend.Format("Scale: %d%% rotation: %dº horizontal shear: %d%%
vertical shear: %d%%.",
nScalePercent,nRotationDegrees,nHorzShearPercent,nVertShearPercent);
pTargetDC->SetTextColor(RGB(55,111,111));
pTargetDC->DrawText(sLegend,crTarget,DT_SINGLELINE | DT_VCENTER |
DT_CENTER);
}
Here is the C# code:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Text;
public static class Graphics_DrawRtfText
{
private static RichTextBoxDrawer rtfDrawer;
public static void DrawRtfText(this Graphics graphics, string rtf,
RectangleF layoutArea,float xFactor)
{
if (Graphics_DrawRtfText.rtfDrawer == null)
{
Graphics_DrawRtfText.rtfDrawer = new RichTextBoxDrawer();
}
Graphics_DrawRtfText.rtfDrawer.Rtf = rtf;
Graphics_DrawRtfText.rtfDrawer.Draw(graphics, layoutArea,xFactor);
}
private class RichTextBoxDrawer : RichTextBox
{
//Code converted from code found here:
http://support.microsoft.com/kb/812425/en-us
//Convert the unit used by the .NET framework (1/100 inch)
//and the unit used by Win32 API calls (twips 1/1440 inch)
private const double anInch = 14.4;
protected override CreateParams CreateParams
{
get
{
CreateParams createParams = base.CreateParams;
if (SafeNativeMethods.LoadLibrary("msftedit.dll") !=
IntPtr.Zero)
{
createParams.ExStyle |=
SafeNativeMethods.WS_EX_TRANSPARENT; // transparent
createParams.ClassName = "RICHEDIT50W";
}
return createParams;
}
}
private void DPToHIMETRIC(Graphics graphics,ref SizeF size)
{
size.Width = (size.Width * 2540.0f) / graphics.DpiX;
size.Height = (size.Height * 2540.0f) / graphics.DpiY;
}
public void Draw(Graphics graphics, RectangleF layoutArea, float
xFactor)
{
System.Diagnostics.Debug.WriteLine("LayoutArea " + layoutArea);
SizeF metaSize = layoutArea.Size;
DPToHIMETRIC(graphics, ref metaSize);
System.Diagnostics.Debug.WriteLine("MetaSize " + metaSize);
IntPtr hdc = graphics.GetHdc();
//create a metafile, convert the size to himetrics
Metafile metafile = new Metafile(hdc, new
RectangleF(0,0,metaSize.Width,metaSize.Height));
graphics.ReleaseHdc(hdc);
Graphics g = Graphics.FromImage(metafile);
IntPtr hDCEMF = g.GetHdc();
//Calculate the area to render.
SafeNativeMethods.RECT rectLayoutArea;
rectLayoutArea.Left = 0;
rectLayoutArea.Top = 0;
rectLayoutArea.Right = (int)((1440 * metaSize.Width + 2540 / 2)
/ 2540);
rectLayoutArea.Bottom = (int)((1440 * metaSize.Height + 2540 /
2) / 2540);
System.Diagnostics.Debug.WriteLine(String.Format("RectLayoutArea
({0},{1})",rectLayoutArea.Right,rectLayoutArea.Bottom));
SafeNativeMethods.FORMATRANGE fmtRange;
fmtRange.chrg.cpMax = -1; //Indicate character from
to character to
fmtRange.chrg.cpMin = 0;
fmtRange.hdc = hDCEMF; //Use the same DC for
measuring and rendering
fmtRange.hdcTarget = hDCEMF; //Point at printer hDC
fmtRange.rc = rectLayoutArea; //Indicate the area on page
to print
fmtRange.rcPage = rectLayoutArea; //Indicate size of page
IntPtr wParam = IntPtr.Zero;
wParam = new IntPtr(1);
//Get the pointer to the FORMATRANGE structure in memory
IntPtr lParam = IntPtr.Zero;
lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
Marshal.StructureToPtr(fmtRange, lParam, false);
SafeNativeMethods.SendMessage(this.Handle,
SafeNativeMethods.EM_FORMATRANGE, wParam, lParam);
SafeNativeMethods.SendMessage(this.Handle,
SafeNativeMethods.EM_FORMATRANGE, wParam, IntPtr.Zero);
//Free the block of memory allocated
Marshal.FreeCoTaskMem(lParam);
//Release the device context handle obtained by a previous call
g.ReleaseHdc(hDCEMF);
g.Dispose();
hdc = graphics.GetHdc();
int nHorzSize = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.HORZSIZE);
int nVertSize = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.VERTSIZE);
int nHorzRes = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.HORZRES);
int nVertRes = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.VERTRES);
int nLogPixelsX = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.LOGPIXELSX);
int nLogPixelsY = SafeNativeMethods.GetDeviceCaps(hdc,
SafeNativeMethods.DeviceCap.LOGPIXELSY);
graphics.ReleaseHdc(hdc);
float fHorzSizeInches = nHorzSize / 25.4f;
float fVertSizeInches = nVertSize / 25.4f;
float fHorzFudgeFactor = (nHorzRes / fHorzSizeInches) /
nLogPixelsX;
float fVertFudgeFactor = (nVertRes / fVertSizeInches) /
nLogPixelsY;
System.Diagnostics.Debug.WriteLine("Fudge Factor " +
fHorzFudgeFactor.ToString() + " " + fVertFudgeFactor.ToString() + " XFactor
" + xFactor.ToString());
Pen RedPen = new Pen(Color.Red);
graphics.DrawRectangle(RedPen, layoutArea.X * xFactor,
layoutArea.Y * xFactor, layoutArea.Width * xFactor, layoutArea.Height *
xFactor);
float Left = layoutArea.Left;
float Top = layoutArea.Top;
//layoutArea.X = layoutArea.Y = 0;
layoutArea.Offset(-Left, -Top);
layoutArea.Offset(Left / fHorzFudgeFactor, Top /
fVertFudgeFactor);
System.Drawing.Drawing2D.GraphicsState state = graphics.Save();
graphics.ScaleTransform(fHorzFudgeFactor * xFactor,
fVertFudgeFactor * xFactor);
graphics.DrawImage(metafile, layoutArea);
graphics.Restore(state);
System.Diagnostics.Debug.WriteLine("Layout Aread :
"+layoutArea);
}
#region SafeNativeMethods
private static class SafeNativeMethods
{
[DllImport("USER32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg,
IntPtr wp, IntPtr lp);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hdc, DeviceCap
nIndex);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct CHARRANGE
{
public int cpMin; //First character of range (0 for
start of doc)
public int cpMax; //Last character of range (-1 for
end of doc)
}
[StructLayout(LayoutKind.Sequential)]
public struct FORMATRANGE
{
public IntPtr hdc; //Actual DC to draw on
public IntPtr hdcTarget; //Target DC for determining text
formatting
public RECT rc; //Region of the DC to
draw to (in twips)
public RECT rcPage; //Region of the whole DC
(page size) (in twips)
public CHARRANGE chrg; //Range of text to draw (see
earlier declaration)
}
public enum DeviceCap : int
{
/// <summary>
/// Device driver version
/// </summary>
DRIVERVERSION = 0,
/// <summary>
/// Device classification
/// </summary>
TECHNOLOGY = 2,
/// <summary>
/// Horizontal size in millimeters
/// </summary>
HORZSIZE = 4,
/// <summary>
/// Vertical size in millimeters
/// </summary>
VERTSIZE = 6,
/// <summary>
/// Horizontal width in pixels
/// </summary>
HORZRES = 8,
/// <summary>
/// Vertical height in pixels
/// </summary>
VERTRES = 10,
/// <summary>
/// Number of bits per pixel
/// </summary>
BITSPIXEL = 12,
/// <summary>
/// Number of planes
/// </summary>
PLANES = 14,
/// <summary>
/// Number of brushes the device has
/// </summary>
NUMBRUSHES = 16,
/// <summary>
/// Number of pens the device has
/// </summary>
NUMPENS = 18,
/// <summary>
/// Number of markers the device has
/// </summary>
NUMMARKERS = 20,
/// <summary>
/// Number of fonts the device has
/// </summary>
NUMFONTS = 22,
/// <summary>
/// Number of colors the device supports
/// </summary>
NUMCOLORS = 24,
/// <summary>
/// Size required for device descriptor
/// </summary>
PDEVICESIZE = 26,
/// <summary>
/// Curve capabilities
/// </summary>
CURVECAPS = 28,
/// <summary>
/// Line capabilities
/// </summary>
LINECAPS = 30,
/// <summary>
/// Polygonal capabilities
/// </summary>
POLYGONALCAPS = 32,
/// <summary>
/// Text capabilities
/// </summary>
TEXTCAPS = 34,
/// <summary>
/// Clipping capabilities
/// </summary>
CLIPCAPS = 36,
/// <summary>
/// Bitblt capabilities
/// </summary>
RASTERCAPS = 38,
/// <summary>
/// Length of the X leg
/// </summary>
ASPECTX = 40,
/// <summary>
/// Length of the Y leg
/// </summary>
ASPECTY = 42,
/// <summary>
/// Length of the hypotenuse
/// </summary>
ASPECTXY = 44,
/// <summary>
/// Shading and Blending caps
/// </summary>
SHADEBLENDCAPS = 45,
/// <summary>
/// Logical pixels inch in X
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Logical pixels inch in Y
/// </summary>
LOGPIXELSY = 90,
/// <summary>
/// Number of entries in physical palette
/// </summary>
SIZEPALETTE = 104,
/// <summary>
/// Number of reserved entries in palette
/// </summary>
NUMRESERVED = 106,
/// <summary>
/// Actual color resolution
/// </summary>
COLORRES = 108,
// Printing related DeviceCaps. These replace the
appropriate Escapes
/// <summary>
/// Physical Width in device units
/// </summary>
PHYSICALWIDTH = 110,
/// <summary>
/// Physical Height in device units
/// </summary>
PHYSICALHEIGHT = 111,
/// <summary>
/// Physical Printable Area x margin
/// </summary>
PHYSICALOFFSETX = 112,
/// <summary>
/// Physical Printable Area y margin
/// </summary>
PHYSICALOFFSETY = 113,
/// <summary>
/// Scaling factor x
/// </summary>
SCALINGFACTORX = 114,
/// <summary>
/// Scaling factor y
/// </summary>
SCALINGFACTORY = 115,
/// <summary>
/// Current vertical refresh rate of the display device (for
displays only) in Hz
/// </summary>
VREFRESH = 116,
/// <summary>
/// Horizontal width of entire desktop in pixels
/// </summary>
DESKTOPVERTRES = 117,
/// <summary>
/// Vertical height of entire desktop in pixels
/// </summary>
DESKTOPHORZRES = 118,
/// <summary>
/// Preferred blt alignment
/// </summary>
BLTALIGNMENT = 119
}
public const int WM_USER = 0x0400;
public const int EM_FORMATRANGE = WM_USER + 57;
public const int WS_EX_TRANSPARENT = 0x20;
}
#endregion
}
}