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,i nt nRotationDegree s,int
nHorzShearPerce nt,int nVertShearPerce nt)
{
// validate the arguments
if (nScalePercent < 1)
nScalePercent = 1;
if (nScalePercent 10000)
nScalePercent = 10000;
if (nRotationDegre es < 0)
nRotationDegree s = 0;
if (nRotationDegre es 360)
nRotationDegree s = 360;
if (nHorzShearPerc ent < 0)
nHorzShearPerce nt = 0;
if (nHorzShearPerc ent 100)
nHorzShearPerce nt = 100;
if (nVertShearPerc ent < 0)
nVertShearPerce nt = 0;
if (nVertShearPerc ent 100)
nVertShearPerce nt = 100;
// setup target window, rect and DC
CWnd* pTargetWin = GetDlgItem(IDC_ EDIT1);
CRect crTarget;
pTargetWin->GetClientRect( &crTarget);
crTarget.Offset Rect(0,20);
CDC* pTargetDC = pTargetWin->GetDC();
pTargetDC->IntersectClipR ect(&crTarget); // so we don't clobber the parent
dialog
pTargetDC->SetBkMode(TRAN SPARENT); // so the legend doesn't erase too
much
// also need advanced graphics mode for the world transform stuff
if (!SetGraphicsMo de(pTargetDC->m_hDC,GM_ADVAN CED))
{
pTargetDC->DrawText("Ni ce try but I need Windows NT, 2000, XP or 2003 for
this.",crTarget ,DT_SINGLELINE | DT_VCENTER | DT_CENTER);
return;
}
// erase previous contents
FillRect(pTarge tDC->m_hDC,crTarget ,(HBRUSH)(COLOR _WINDOW + 1));
CString Temp;
Temp.Format("cr Target %d,%d
%d,%d\n",crTarg et.left,crTarge t.top,crTarget. Width(),crTarge t.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("Me taSize
%d,%d\n",cHiMet ricRect.Width() ,cHiMetricRect. Height());
TRACE(Temp);
Temp.Format("cT wipsRect %d,%d\n",cTwips Rect.Width(),cT wipsRect.Height ());
TRACE(Temp);
// create the enhanced metafile
HDC hDCEMF = CreateEnhMetaFi le(pTargetDC->m_hDC,NULL,cHi MetricRect,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,TR UE,(LPARAM) &stFR);
GetDlgItem(IDC_ RICHEDIT1)->SendMessage(EM _FORMATRANGE,TR UE,NULL); // this
call clears the cache
// drawing into the metafile is done, get ourselves an handle to it (used
for the replay)
HENHMETAFILE hEMF = CloseEnhMetaFil e(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 fHorzFudgeFacto r = (nHorzRes / fHorzSizeInches ) / nLogPixelsX; //
divide expected DPI with actual DPI
float fVertFudgeFacto r = (nVertRes / fVertSizeInches ) / nLogPixelsY; //
Temp.Format("Fu dgfactor %.2f %.2f\n",fHorzFu dgeFactor,fVert FudgeFactor);
TRACE(Temp);
// change from degrees to radians
// (also need to change sign since page space is upside down)
float fRotationInRadi ans = (PI * (0 - nRotationDegree s)) / 180.0F;
// do the world transforms, first apply the scale and fudge factors
XFORM stX;
ZeroMemory(&stX ,sizeof(stX));
stX.eM11 = fHorzFudgeFacto r * (nScalePercent / 100.0f);
stX.eM22 = fVertFudgeFacto r * (nScalePercent / 100.0f);
SetWorldTransfo rm(pTargetDC->m_hDC,&stX);
// then apply the rotation
ZeroMemory(&stX ,sizeof(stX));
stX.eM11 = (float) cos(fRotationIn Radians);
stX.eM12 = (float) sin(fRotationIn Radians);
stX.eM21 = 0 - (float) sin(fRotationIn Radians);
stX.eM22 = (float) cos(fRotationIn Radians);
ModifyWorldTran sform(pTargetDC->m_hDC,&stX,MWT _LEFTMULTIPLY);
// finally apply the horizontal and vertical shearing
ZeroMemory(&stX ,sizeof(stX));
stX.eM11 = 1.0f;
stX.eM12 = nHorzShearPerce nt / 100.0f;
stX.eM21 = nVertShearPerce nt / 100.0f;
stX.eM22 = 1.0f;
ModifyWorldTran sform(pTargetDC->m_hDC,&stX,MWT _LEFTMULTIPLY);
crTarget.Offset Rect(0,-20);
crTarget.Offset Rect(0,20 / (nScalePercent / 100.0f) * fVertFudgeFacto r);
// play the metafile
pTargetDC->PlayMetaFile(h EMF,&crTarget);
// that's it, kiss the metafile goodbye
DeleteEnhMetaFi le(hEMF);
// also show a legend
ModifyWorldTran sform(pTargetDC->m_hDC,NULL,MWT _IDENTITY);
CString sLegend;
sLegend.Format( "Scale: %d%% rotation: %dº horizontal shear: %d%%
vertical shear: %d%%.",
nScalePercent,n RotationDegrees ,nHorzShearPerc ent,nVertShearP ercent);
pTargetDC->SetTextColor(R GB(55,111,111)) ;
pTargetDC->DrawText(sLege nd,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_DrawRt fText
{
private static RichTextBoxDraw er rtfDrawer;
public static void DrawRtfText(thi s Graphics graphics, string rtf,
RectangleF layoutArea,floa t xFactor)
{
if (Graphics_DrawR tfText.rtfDrawe r == null)
{
Graphics_DrawRt fText.rtfDrawer = new RichTextBoxDraw er();
}
Graphics_DrawRt fText.rtfDrawer .Rtf = rtf;
Graphics_DrawRt fText.rtfDrawer .Draw(graphics, layoutArea,xFac tor);
}
private class RichTextBoxDraw er : 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.CreatePara ms;
if (SafeNativeMeth ods.LoadLibrary ("msftedit.dll" ) !=
IntPtr.Zero)
{
createParams.Ex Style |=
SafeNativeMetho ds.WS_EX_TRANSP ARENT; // transparent
createParams.Cl assName = "RICHEDIT50 W";
}
return createParams;
}
}
private void DPToHIMETRIC(Gr aphics 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.Diagnost ics.Debug.Write Line("LayoutAre a " + layoutArea);
SizeF metaSize = layoutArea.Size ;
DPToHIMETRIC(gr aphics, ref metaSize);
System.Diagnost ics.Debug.Write Line("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.Releas eHdc(hdc);
Graphics g = Graphics.FromIm age(metafile);
IntPtr hDCEMF = g.GetHdc();
//Calculate the area to render.
SafeNativeMetho ds.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.Diagnost ics.Debug.Write Line(String.For mat("RectLayout Area
({0},{1})",rect LayoutArea.Righ t,rectLayoutAre a.Bottom));
SafeNativeMetho ds.FORMATRANGE fmtRange;
fmtRange.chrg.c pMax = -1; //Indicate character from
to character to
fmtRange.chrg.c pMin = 0;
fmtRange.hdc = hDCEMF; //Use the same DC for
measuring and rendering
fmtRange.hdcTar get = 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.AllocCo TaskMem(Marshal .SizeOf(fmtRang e));
Marshal.Structu reToPtr(fmtRang e, lParam, false);
SafeNativeMetho ds.SendMessage( this.Handle,
SafeNativeMetho ds.EM_FORMATRAN GE, wParam, lParam);
SafeNativeMetho ds.SendMessage( this.Handle,
SafeNativeMetho ds.EM_FORMATRAN GE, wParam, IntPtr.Zero);
//Free the block of memory allocated
Marshal.FreeCoT askMem(lParam);
//Release the device context handle obtained by a previous call
g.ReleaseHdc(hD CEMF);
g.Dispose();
hdc = graphics.GetHdc ();
int nHorzSize = SafeNativeMetho ds.GetDeviceCap s(hdc,
SafeNativeMetho ds.DeviceCap.HO RZSIZE);
int nVertSize = SafeNativeMetho ds.GetDeviceCap s(hdc,
SafeNativeMetho ds.DeviceCap.VE RTSIZE);
int nHorzRes = SafeNativeMetho ds.GetDeviceCap s(hdc,
SafeNativeMetho ds.DeviceCap.HO RZRES);
int nVertRes = SafeNativeMetho ds.GetDeviceCap s(hdc,
SafeNativeMetho ds.DeviceCap.VE RTRES);
int nLogPixelsX = SafeNativeMetho ds.GetDeviceCap s(hdc,
SafeNativeMetho ds.DeviceCap.LO GPIXELSX);
int nLogPixelsY = SafeNativeMetho ds.GetDeviceCap s(hdc,
SafeNativeMetho ds.DeviceCap.LO GPIXELSY);
graphics.Releas eHdc(hdc);
float fHorzSizeInches = nHorzSize / 25.4f;
float fVertSizeInches = nVertSize / 25.4f;
float fHorzFudgeFacto r = (nHorzRes / fHorzSizeInches ) /
nLogPixelsX;
float fVertFudgeFacto r = (nVertRes / fVertSizeInches ) /
nLogPixelsY;
System.Diagnost ics.Debug.Write Line("Fudge Factor " +
fHorzFudgeFacto r.ToString() + " " + fVertFudgeFacto r.ToString() + " XFactor
" + xFactor.ToStrin g());
Pen RedPen = new Pen(Color.Red);
graphics.DrawRe ctangle(RedPen, layoutArea.X * xFactor,
layoutArea.Y * xFactor, layoutArea.Widt h * xFactor, layoutArea.Heig ht *
xFactor);
float Left = layoutArea.Left ;
float Top = layoutArea.Top;
//layoutArea.X = layoutArea.Y = 0;
layoutArea.Offs et(-Left, -Top);
layoutArea.Offs et(Left / fHorzFudgeFacto r, Top /
fVertFudgeFacto r);
System.Drawing. Drawing2D.Graph icsState state = graphics.Save() ;
graphics.ScaleT ransform(fHorzF udgeFactor * xFactor,
fVertFudgeFacto r * xFactor);
graphics.DrawIm age(metafile, layoutArea);
graphics.Restor e(state);
System.Diagnost ics.Debug.Write Line("Layout Aread :
"+layoutAre a);
}
#region SafeNativeMetho ds
private static class SafeNativeMetho ds
{
[DllImport("USER 32.dll")]
public static extern IntPtr SendMessage(Int Ptr hWnd, int msg,
IntPtr wp, IntPtr lp);
[DllImport("kern el32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr LoadLibrary(str ing lpFileName);
[DllImport("gdi3 2.dll")]
public static extern int GetDeviceCaps(I ntPtr hdc, DeviceCap
nIndex);
[StructLayout(La youtKind.Sequen tial)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(La youtKind.Sequen tial)]
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(La youtKind.Sequen tial)]
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_TRANSPARE NT = 0x20;
}
#endregion
}
}