Click here to Skip to main content
15,885,546 members
Articles / Multimedia / GDI+

Presenting EMFexplorer, a GDI+ experiment

Rate me:
Please Sign up or sign in to vote.
4.90/5 (28 votes)
29 Sep 20047 min read 128.8K   3.2K   49  
High quality EMF rendering, using GDI+
/*
*	This file is part of the EMFexplorer projet.
*	Copyright (C) 2004 Smith Charles.
*
*	This library is free software; you can redistribute it and/or
*	modify it under the terms of the GNU Lesser General Public
*	License as published by the Free Software Foundation; either
*	version 2.1 of the License, or (at your option) any later version.
*
*   This library is distributed in the hope that it will be useful,
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*   Lesser General Public License for more details.
*
*   You should have received a copy of the GNU Lesser General Public
*   License along with this library; if not, write to the Free Software
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
*
*	Extension: for commercial use, apply the Equity Public License, which
*	adds to the normal terms of the GLPL a condition of donation to the author.
*   If you are interested in support for this source code,
*   contact Smith Charles <smith.charles@free.fr> for more information.
*/


#include "stdafx.h"
#include "SCEMFdcRenderer.h"

// GDI+ won't allow DEBUG_NEW to work
//	#ifdef _DEBUG
//	#define new DEBUG_NEW
//	#undef THIS_FILE
//	static char THIS_FILE[] = __FILE__;
//	#endif


using namespace Gdiplus;

#define SC_CHECKGDIP_OBJ(pObj) \
	if (pObj) ASSERT(pObj->GetLastStatus()==Ok)

///
/// g_pFontCopies contains copies of TT fonts already installed for GDI and that
/// that GDI+ accepts to use only if they are placed in a private collection.
/// We don't want to create this private collection on a per-object basis.
/// To ensure a per-module caching, the collection is ref-counted.
/// Call SCCleanFontCopies() when a new document is loaded.
///
SCFontCollection*		g_pFontCopies = NULL;
SCFontCollectionHolder*	g_pFontCopiesHolder = NULL;

///////////////////////////////////////////////////////////////////////////////////
// Construction/Destruction
///////////////////////////////////////////////////////////////////////////////////
CSCEMFdcRenderer::CSCEMFdcRenderer():
	m_hDC(NULL),
	m_pGraphics(NULL),
	m_bMonochrome(FALSE),
	m_pPen(NULL),
	m_pBrush(NULL),
	m_pFont(NULL),
	m_pPath(NULL),
	m_dwLastError(SC_EMFERR_NOERROR),
	m_PtCurPos(0,0),
	m_pPenHolder(NULL),
	m_pBrushHolder(NULL),
	m_pFontHolder(NULL),
	m_pPathHolder(NULL),
	m_dwMapMode(MM_TEXT),
	m_dwBkMode(OPAQUE),
	m_dwFillMode(ALTERNATE),
	m_dwStretchBltMode(COLORONCOLOR),
	m_dwTextAlign(TA_LEFT|TA_BASELINE),
	m_dwROP2(R2_COPYPEN),
	m_BkColor(RGB(255, 255, 255)),
	m_TextColor(RGB(0, 0, 0)),
	m_dwArcDirection(AD_COUNTERCLOCKWISE),
	m_fMiterLimit(10.0),
	m_pImgAttribs(NULL),
	m_pFontCollection(NULL)
	// no constructor
		// m_WinSize(1,1),
		// m_ViewSize(1,1),
		// m_PtWinOrg(0,0),
		// m_PtViewOrg(0,0),
		// m_RcClipBox(0,0,0,0)
{
	m_WinSize.cx = m_WinSize.cy = 1;
	m_ViewSize.cx = m_ViewSize.cy = 1;
	m_PtWinOrg.x = m_PtWinOrg.y = 0;
	m_PtViewOrg.x = m_PtViewOrg.y = 0;
	SetRectEmpty(&m_RcClipBox);

	if (!g_pFontCopiesHolder)
	{
		ASSERT(NULL==g_pFontCopies);
		g_pFontCopies = new SCFontCollection;
		g_pFontCopiesHolder = new SCFontCollectionHolder(g_pFontCopies);
	}
	SC_REFCOUNTED_ADDREF(g_pFontCopiesHolder);
}

/*static*/
void CSCEMFdcRenderer::SCCleanFontCopies()
{
	SC_OBJHOLDER_SAFERELEASE_T(g_pFontCopiesHolder, g_pFontCopies, SCFontCollection);
}

CSCEMFdcRenderer::~CSCEMFdcRenderer()
{
	SMC_DELETE(m_pGraphics);
	SMC_DELETE(m_pImgAttribs);

	SC_OBJHOLDER_RELEASE_T(m_pPenHolder, m_pPen, GDPPen);
	SC_OBJCONTAINER_RELEASE_T(m_pBrushHolder, m_pBrush, GDPBrush, SCBrush);
	SC_OBJHOLDER_RELEASE_T(m_pFontHolder, m_pFont, SCFont);
	SC_OBJHOLDER_RELEASE_T(m_pPathHolder, m_pPath, SCPath);

	SC_OBJHOLDER_SAFERELEASE_T(g_pFontCopiesHolder, g_pFontCopies, SCFontCollection);
}

///////////////////////////////////////////////////////////////////////////////////
// Utilities
///////////////////////////////////////////////////////////////////////////////////

///
/// Prepare the graphic context for the rendering session.
///
void CSCEMFdcRenderer::SCSetupRendering()
{
	ASSERT(m_pGraphics);

	// 1. Get current DC state
	m_dwMapMode = GetMapMode(m_hDC);
	m_dwBkMode = GetBkMode(m_hDC);
	m_dwFillMode = GetPolyFillMode(m_hDC);
	m_dwStretchBltMode = GetStretchBltMode(m_hDC);
	m_dwTextAlign = GetTextAlign(m_hDC);
	m_dwROP2 = GetROP2(m_hDC);
	m_BkColor = SC_RGBCOLOR(GetBkColor(m_hDC));
	m_TextColor = SC_FINAL_COLOR(GetTextColor(m_hDC));
	m_dwArcDirection = GetArcDirection(m_hDC);
	m_bMonochrome = SCIsMonochromeDC(m_hDC);
	GetMiterLimit(m_hDC, &m_fMiterLimit);
	GetWindowExtEx(m_hDC, &m_WinSize);
	GetViewportExtEx(m_hDC, &m_ViewSize);
	GetWindowOrgEx(m_hDC, &m_PtWinOrg);
	GetViewportOrgEx(m_hDC, &m_PtViewOrg);
	// GetClipBox(m_hDC, &m_RcClipBox); // done elsewhere

	// clipping
	SCCropToPlayBox();

	// 2. Add user-requested attributes
	// texts
	m_pGraphics->SetTextRenderingHint(m_DrawingAttributes.eTextRenderingHint);
	m_pGraphics->SetTextContrast(m_DrawingAttributes.uiTextContrast);

	// curves
	m_pGraphics->SetSmoothingMode(m_DrawingAttributes.eSmoothingMode);

	// images
	m_pGraphics->SetInterpolationMode(m_DrawingAttributes.eInterpolationMode);
	m_pGraphics->SetPixelOffsetMode(m_DrawingAttributes.ePixelOffsetMode);

	// paper color
	COLORREF PaperColor = m_DrawingAttributes.crPaperColor;
	if (m_DrawingAttributes.bReverseVideo)
	{
		if ((m_DrawingAttributes.dwInkingMode & SC_REVERSE_VIDEO_MASK)!=SC_REVERSE_VIDEO_SKIPIMAGES)
		{
			if (PaperColor!=RGB(255, 255, 255))
			{
				ColorMap clrMap;
				clrMap.oldColor = Color(255, 255, 255, 255);
				clrMap.newColor = Color(255, GetRValue(PaperColor), GetGValue(PaperColor), GetBValue(PaperColor));
				
				m_pImgAttribs = new ImageAttributes;
				if (m_pImgAttribs)
					m_pImgAttribs->SetRemapTable(1, &clrMap);
			}
		}
		if (RGB(255, 255, 255)==m_BkColor)
			m_BkColor = SC_RGBCOLOR(~PaperColor);
	} else
	{
		if ((m_DrawingAttributes.dwInkingMode & SC_TRANSPARENCY_MASK)==SC_TRANSPARENCY_WHITECOLOR)
		{
			if (PaperColor!=RGB(255, 255, 255))
			{
				ColorMap clrMap;
				clrMap.oldColor = Color(255, 255, 255, 255);
				clrMap.newColor = Color(255, GetRValue(PaperColor), GetGValue(PaperColor), GetBValue(PaperColor));
				
				m_pImgAttribs = new ImageAttributes;
				if (m_pImgAttribs)
					m_pImgAttribs->SetRemapTable(1, &clrMap);
			}
		}
		if (RGB(255, 255, 255)==m_BkColor)
			m_BkColor = SC_RGBCOLOR(PaperColor);
	}

	// printing
	if (m_DrawingAttributes.bPrinting)
	{
		m_pGraphics->SetPageUnit(UnitPixel);
		m_pGraphics->SetPageScale(m_DrawingAttributes.fPageScale);
	}

	// default objects
	HGDIOBJ hGdiObj;
	if (hGdiObj = GetCurrentObject(m_hDC, OBJ_PEN))
	{
		SCOnChangePen((HPEN)hGdiObj);
	}
	if (hGdiObj = GetCurrentObject(m_hDC, OBJ_BRUSH))
	{
		SCOnChangeBrush((HBRUSH)hGdiObj);
	}
	if (hGdiObj = GetCurrentObject(m_hDC, OBJ_FONT))
	{
		SCOnChangeFont((HFONT)hGdiObj);
	}

	// background
	if (m_DrawingAttributes.bBkSolid)
	{
		Color BrushColor;
		BrushColor.SetFromCOLORREF(m_BkColor);
		SolidBrush brush(BrushColor);

		CRect rcBox(m_RcClipBox);
		SCDPtoLP((Point*)&rcBox, 2);

		Rect rectClip(rcBox.left, rcBox.top, RECT_WIDTH(rcBox), RECT_HEIGHT(rcBox));
		rectClip.Inflate(1, 1);
		m_pGraphics->FillRectangle(&brush, rectClip);
	}
}

void CSCEMFdcRenderer::SCDPtoLP(Point* pPoints, INT iNbPts)
{
	ASSERT(pPoints);
	ASSERT(m_pGraphics);

	Matrix matrix;
	m_pGraphics->GetTransform(&matrix);
	matrix.Invert();
	matrix.TransformPoints(pPoints, iNbPts);
}

void CSCEMFdcRenderer::SCLPtoDP(Point* pPoints, INT iNbPts)
{
	ASSERT(pPoints);
	ASSERT(m_pGraphics);

	Matrix matrix;
	m_pGraphics->GetTransform(&matrix);
	matrix.TransformPoints(pPoints, iNbPts);
}

#if 0
///
/// Update current position
///
/// Compiler bug?
/// Other compilation units won't see this inline if it's not exported
/// (definition is discarded when it's not called from this file).
/// Linker will complain. (Note: functions like SCCropToPlayBox doesn't show this problem).
inline void CSCEMFdcRenderer::SCUpdateCurPos(INT x, INT y)
{
	MoveToEx(m_hDC, x, y, NULL);
	m_PtCurPos.X = x;
	m_PtCurPos.Y = y;
}
/// To export it, use:
//	inline __declspec( dllexport ) void CSCEMFdcRenderer::SCUpdateCurPos(INT x, INT y)
/// But I'm not sure VC6 would really inline it.
#endif

///
///	Change color to conform to drawing attributes.
///
inline COLORREF CSCEMFdcRenderer::SCGetFinalColor(COLORREF crColor)
{
	if ((m_DrawingAttributes.dwInkingMode & SC_TRANSPARENCY_MASK)!=SC_TRANSPARENCY_NORMAL)
	{
		if (RGB(255, 255, 255)==SC_RGBCOLOR(crColor))
			return SC_FINAL_COLOR(m_DrawingAttributes.crPaperColor);
	}
	
	return SC_FINAL_COLOR(crColor);
}

///
///	Replace the current brush with a new one.
///
inline void CSCEMFdcRenderer::SCRefreshBrush(Brush* pBrush)
{
	ASSERT(m_pBrushHolder); // we are refreshing
	ASSERT(pBrush);			// can't refresh to NULL

	SCBrush* pSCBrush = m_pBrushHolder->SCDetachSubObj();
	ASSERT(pSCBrush);

	SC_OBJCONTAINER_RELEASE_T(m_pBrushHolder, m_pBrush, GDPBrush, SCBrush);
	m_pBrush = pBrush;
	SC_OBJCONTAINER_RENEW_T(m_pBrushHolder, m_pBrush, GDPBrush, pSCBrush, SCBrush);
}

///////////////////////////////////////////////////////////////////////////////////
// I_EMFRasterizer
///////////////////////////////////////////////////////////////////////////////////

///
///	Create the unique graphic context for the rendering session.
/// hDestDC is where the raster is written.
/// hSrcDC is a reference DC from which information is collected
///
BOOL CSCEMFdcRenderer::SCBeginRendering(HDC hDestDC, HDC hSrcDC)
{
	m_hDC = hSrcDC;
	
	// Copy the initial source transformation to the destination
	if (hDestDC != hSrcDC)
	{
		// TODO: revise this when DC cloning is ractivated

		// Combine with the initial source transformation
		XFORM xformSrc;
		GetWorldTransform(m_hDC, &xformSrc);
		int iGM = SetGraphicsMode(hDestDC, GM_ADVANCED);
		
		ModifyWorldTransform(hDestDC, &xformSrc, MWT_LEFTMULTIPLY);
		
		// should fail if current matrix is not identity
		SetGraphicsMode(hDestDC, iGM);

		// Save original transformation of destination
		XFORM xform;
		GetWorldTransform(hDestDC, &xform);
		m_DestMatrix.SetElements(xform.eM11, xform.eM12, xform.eM21, xform.eM22, xform.eDx, xform.eDy); 
	}
	
	// Compute clip box to use as metaregion
	{
#pragma message( __FILE__  "(335): TODO: Remove MFC dependency ")
		// For now it is needed to render into GDI metafiles
		CDC* pDC = CDC::FromHandle(hDestDC);
		HDC hAttribDC = pDC->m_hAttribDC;
		ASSERT(hAttribDC);
		
		HRGN hRgn = CreateRectRgn(0,0,0,0);
		int iRes = GetMetaRgn(hAttribDC, hRgn);
		ASSERT(iRes||(hAttribDC==hDestDC));
		if (!iRes)
		{// Sadly the meta region isn't meta enough. GetMetaRgn fails if you forget
			// to set a clipping region before calling it.
			iRes = GetClipBox(hAttribDC, &m_RcClipBox); // No! it's good for screen only
		} else
		{
			iRes = GetRgnBox(hRgn, &m_RcClipBox);
			DPtoLP(hAttribDC, (POINT*)&m_RcClipBox, 2); // warning: rounding errors here
			ASSERT(NULLREGION!=iRes && ERROR!=iRes); // there must be a rectangle
		}
		DeleteObject(hRgn);
	}
	
	SCNormalizeRect(&m_RcClipBox);
	if (IsRectEmpty(&m_RcClipBox))
	{// Set 'infinite' metaregion
		SetRect(&m_RcClipBox, -32767, -32767, 32767, 32767);
	}
	
	// Create GDI+ output surface
	SMC_DELETE(m_pGraphics);
	if (!SCCreateSurface(hDestDC))
	{
		m_dwLastError = SC_EMFERR_OUTOFMEMORY;
		return FALSE;
	}
	SCLPtoDP((Point*)&m_RcClipBox, 2); // we need it in target (GDI+) device coordinates

	// Setup graphic state (get other attributes from source DC)
	SCSetupRendering();
	return m_pGraphics->GetLastStatus()==Ok;
}

///
///	Push the current graphic state.
///
void CSCEMFdcRenderer::SCOnDCSaved()
{
	ASSERT(m_pGraphics);

	// Lock the objects matching GDI DC objects
	CSCGDIState* pStat = new CSCGDIState(
		m_pPenHolder,
		m_pBrushHolder,
		m_pFontHolder,
		m_pPathHolder,
		m_PtCurPos,
		m_pGraphics->Save(),
		m_dwMapMode,
		m_dwBkMode,
		m_dwFillMode,
		m_dwStretchBltMode,
		m_dwTextAlign,
		m_dwROP2,
		m_BkColor,
		m_TextColor,
		m_dwArcDirection,
		m_fMiterLimit,
		m_WinSize,
		m_ViewSize,
		m_PtWinOrg,
		m_PtViewOrg,
		m_RcClipBox
		);

	m_States.Add(pStat);
}

///
///	Pop the indicated graphic state, and discard all states from its index to top of stack.
///
void CSCEMFdcRenderer::SCOnDCRestored(INT iRelative)
{
	ASSERT(m_pGraphics);

	INT iPos;
	if (iRelative<0)
		iPos = m_States.GetSize() + iRelative;	// count from top of stack
	else
		iPos = iRelative;						// count from bottom of stack
	//ASSERT(iPos>=0 && iPos<m_States.GetSize());
	if (iPos<0 || iPos>=m_States.GetSize())
		return;

	// GDI+ state
	CSCGDIState* pStat = m_States[iPos];
	m_pGraphics->Restore(pStat->m_SvdGraphicsState);

	// Additional state information
		// Get flat data 
	m_PtCurPos = pStat->m_PtSvdCurPos;
	m_dwMapMode = pStat->m_dwSvdMapMode;
	m_dwBkMode = pStat->m_dwSvdBkMode;
	m_dwFillMode = pStat->m_dwSvdFillMode;
	m_dwStretchBltMode = pStat->m_dwSvdStretchBltMode;
	m_dwTextAlign = pStat->m_dwSvdTextAlign;
	m_dwROP2 = pStat->m_dwSvdROP2;
	m_BkColor = pStat->m_SvdBkColor;
	m_TextColor = pStat->m_SvdTextColor;
	m_dwArcDirection = pStat->m_dwSvdArcDirection;
	m_fMiterLimit = pStat->m_fSvdMiterLimit;
	m_WinSize = pStat->m_SvdWinSize;
	m_ViewSize = pStat->m_SvdViewSize;
	m_PtWinOrg = pStat->m_PtSvdWinOrg;
	m_PtViewOrg = pStat->m_PtSvdViewOrg;
	CopyRect(&m_RcClipBox, &pStat->m_RcSvdClipBox);

		// Get objects 
	SC_OBJHOLDER_RESTORE_T(m_pPenHolder, m_pPen, pStat->m_pSvdPenHolder, GDPPen);
	SC_OBJCONTAINER_RESTORE_T(m_pBrushHolder, m_pBrush, pStat->m_pSvdBrushHolder, GDPBrush, SCBrush);
	SC_OBJHOLDER_RESTORE_T(m_pFontHolder, m_pFont, pStat->m_pSvdFontHolder, SCFont);
	SC_OBJHOLDER_RESTORE_T(m_pPathHolder, m_pPath, pStat->m_pSvdPathHolder, SCPath);

	// Delete all states, starting from iPos
	INT iLen = m_States.GetSize();
	for (INT i=iPos; (i<iLen); i++)
	{
		delete m_States[i];
	}
	m_States.RemoveAt(iPos, iLen - iPos);

#ifdef _DEBUG
	// Check dangling pointers
	SC_CHECKGDIP_OBJ(m_pPen);
	SC_CHECKGDIP_OBJ(m_pBrush);
	SC_CHECKGDIP_OBJ(m_pFont);
	SC_CHECKGDIP_OBJ(m_pPath);
#endif
}

///
///	Move the current position.
///
void CSCEMFdcRenderer::SCMoveToEx(POINTL& PtDest)
{
	if (m_pPath)
	{
		if (m_pPath->SCGetEnd())
			SC_OBJHOLDER_RELEASE_T(m_pPathHolder, m_pPath, SCPath);
		else
			m_pPath->StartFigure();
	}
	m_PtCurPos.X = PtDest.x;
	m_PtCurPos.Y = PtDest.y;
}


///
///	Change the background mode and update hatched brush transparency.
///
void CSCEMFdcRenderer::SCSetBkMode(DWORD dwMode)
{
	ASSERT(m_pGraphics);
	m_dwBkMode = dwMode;
	if (m_pBrush && m_pBrush->GetType()==BrushTypeHatchFill)
	{// We must update transparency
		SCColor FgColor;
		((HatchBrush*)m_pBrush)->GetForegroundColor(&FgColor);

		SCColor BkColor;
		((HatchBrush*)m_pBrush)->GetBackgroundColor(&BkColor);
		BkColor.SCSetAlpha(((TRANSPARENT==m_dwBkMode) ? 0 : 255));

		HatchBrush* pBrush = new HatchBrush(((HatchBrush*)m_pBrush)->GetHatchStyle(), FgColor, BkColor);

		// Refresh the brush object
		SCRefreshBrush(pBrush);
	}
}

///
///	Change the text color and update monochrome brush color.
///
void CSCEMFdcRenderer::SCSetBkColor(COLORREF BkColor)
{
	m_BkColor = SCGetFinalColor(BkColor);

	if (!m_pBrushHolder)
		return;

	SCBrush* pSCBrush = m_pBrushHolder->SCGetSubObj();
	ASSERT(pSCBrush);

	if (!pSCBrush->SCIsMonoPattern())
		return;

	HBRUSH hBrush;
	if (hBrush = pSCBrush->SCGetHandle())
	{
		SCOnChangeBrush(hBrush);
	}
}

///
///	Change the background color and update monochrome brush color.
///
void CSCEMFdcRenderer::SCSetTextColor(COLORREF TxtColor)
{
	m_TextColor = SCGetFinalColor(TxtColor);

	if (!m_pBrushHolder)
		return;

	SCBrush* pSCBrush = m_pBrushHolder->SCGetSubObj();
	ASSERT(pSCBrush);

	if (!pSCBrush->SCIsMonoPattern())
		return;

	HBRUSH hBrush;
	if (hBrush = pSCBrush->SCGetHandle())
	{
		SCOnChangeBrush(hBrush);
	}
}

///////////////////////////////////////////////////////////////////////////////////
// Drawing objects
//

///
///	Convert the newly selected pen in m_hDC to its corresponding GDI+ object.
///
void CSCEMFdcRenderer::SCOnChangePen(HPEN hPen)
{
	ASSERT(m_pGraphics);
	SC_OBJHOLDER_RELEASE_T(m_pPenHolder, m_pPen, GDPPen);

	ASSERT(hPen);
	LOGPEN LogPen;
	if (::GetObject(hPen, sizeof(LogPen), &LogPen))
	{
		LogPen.lopnColor = SCGetFinalColor(LogPen.lopnColor);

		m_pPen = SCPenFromLogPen(LogPen);
	} else
	{// most likely the handle is not valid (maybe a deleted object)
		TRACE0("Invalid pen handle in DC");
	}

	SC_OBJHOLDER_RENEW_T(m_pPenHolder, m_pPen, GDPPen);
}

///
///	Convert the newly selected pen in m_hDC to its corresponding GDI+ object.
///
void CSCEMFdcRenderer::SCOnChangeExtPen(HPEN hPen)
{
	ASSERT(m_pGraphics);
	SC_OBJHOLDER_RELEASE_T(m_pPenHolder, m_pPen, GDPPen);

	ASSERT(hPen);
	DWORD dwSize = ::GetObject(hPen, sizeof(EXTLOGPEN), NULL);
	BYTE* pBytes = new BYTE[dwSize];
	if (::GetObject(hPen, dwSize, pBytes))
	{
		EXTLOGPEN* pExtLogPen = (EXTLOGPEN*)pBytes;
		if (BS_SOLID==pExtLogPen->elpBrushStyle || BS_HATCHED==pExtLogPen->elpBrushStyle)
		{
			pExtLogPen->elpColor = SCGetFinalColor(pExtLogPen->elpColor);
		}

		SCShortDCState TmpDCState(m_bMonochrome, m_dwROP2, m_TextColor, m_BkColor, m_dwBkMode);
		m_pPen = SCPenFromExtLogPen(pExtLogPen, TmpDCState);

		// Since a GDI+ pen has its own matrix, scaling problems may arise some day
		// (see comments in SCOnChangeBrush). In that event, think about something like:
		//		m_pPen->SetTransform(&BogusMatrix);
		// This looks as if, after giving the width of a pen, we should give a matrix
		// to scale it. Poor us, we don't have such information.
	}
	else
	{// most likely the handle is not valid (maybe a deleted object)
		TRACE0("Invalid extpen handle in DC");
	}
	delete [] pBytes;
	SC_OBJHOLDER_RENEW_T(m_pPenHolder, m_pPen, GDPPen);
}


///
///	Convert the newly selected brush in m_hDC to its corresponding GDI+ object.
///
void CSCEMFdcRenderer::SCOnChangeBrush(HBRUSH hBrush)
{
	ASSERT(m_pGraphics);
	SC_OBJCONTAINER_RELEASE_T(m_pBrushHolder, m_pBrush, GDPBrush, SCBrush);
	
	ASSERT(hBrush);
	LOGBRUSH LogBrush;
	LogBrush.lbHatch = 0;
	BOOL bMonoBrush = FALSE;
	if (::GetObject(hBrush, sizeof(LogBrush), &LogBrush))
	{
		if (BS_SOLID==LogBrush.lbStyle)
		{
			LogBrush.lbColor = SCGetFinalColor(LogBrush.lbColor);
		}
		SCShortDCState TmpDCState(m_bMonochrome, m_dwROP2, m_TextColor, m_BkColor, m_dwBkMode);

		m_pBrush = SCBrushFromLogBrush(LogBrush, TmpDCState);

		bMonoBrush = TmpDCState.bMonoBrush; // for brush update

#if 1
	// Strange!!
		if (m_pBrush && m_pBrush->GetType()==BrushTypeTextureFill)
		{
			// Note: When the brush is used, GDI+ won't use the transform in m_pGraphics
			// to properly display the brush.
			// I must exerce a strong moral on myself to just say that:
			// - GDI+ is a BS_... PATTERN!

			// Back to civilization.
			// Try to guess what these lines are doing.
			Matrix CurMatrix;
			m_pGraphics->GetTransform(&CurMatrix);
			if (CurMatrix.IsInvertible())
			{
				// Note: unexpected results in perspective, as we are setting up this
				// brush with the CURRENT state of m_pGraphics
				CurMatrix.Invert();
				((TextureBrush*)m_pBrush)->SetTransform(&CurMatrix);
			} else
			{ // else, let things fall down
				ASSERT(0);
			}
		}
#endif
	} else
	{// most likely the handle is not valid (maybe a deleted object)
		TRACE0("Invalid brush handle in DC");
	}
	SCBrush* pSCbrush = m_pBrush ? new SCBrush(hBrush, bMonoBrush) : NULL;
	SC_OBJCONTAINER_RENEW_T(m_pBrushHolder, m_pBrush, GDPBrush, pSCbrush, SCBrush);

	// Brush origin should be set. But it seems to have no effect on GDI+.
	// See comments in SCSetBrushOrg.
}

void CSCEMFdcRenderer::SCSetBrushOrg(POINTL& PtOrg)
{
	ASSERT(m_pGraphics);
#if 0
	// won't work? Maybe a device/world units problem (PtOrg is in device units)
	m_pGraphics->SetRenderingOrigin(PtOrg.x, PtOrg.y);
#else
	// won't work either. TODO: find the bug.
	Point Pts[2];
	Pts[1].X = PtOrg.x;
	Pts[1].Y = PtOrg.y;
	Pts[0].X = 0;
	Pts[0].Y = 0;
	
	Matrix matrix;
	m_pGraphics->GetTransform(&matrix);
	matrix.Invert();
	matrix.TransformPoints((Point*)&Pts, 2);
	Pts[1].X -= Pts[0].X;
	Pts[1].Y -= Pts[0].Y;
	
	m_pGraphics->SetRenderingOrigin(Pts[1].X, Pts[1].Y);
#endif
	// Origin should be stored for brush update. But since SetRenderingOrigin is called
	// on GDPGraphics, not on the brush, the setting is likely to stay until the next
	// SCSetBrushOrg call.
}


void CSCEMFdcRenderer::SCSelectPrivateFont(LOGFONT& rLogFont, SCFontCollection* pFontCollection, DWORD dwSimul)
{
	ASSERT(pFontCollection);

	// VC6 compiler won't accept the assignment OR with an enum (and that's correct).
	// FontStyle Style = FontStyleRegular;
	INT Style = FontStyleRegular;
	if (rLogFont.lfWeight>=FW_BOLD)
		Style = FontStyleBold;
	if (rLogFont.lfItalic)
		Style |= FontStyleItalic;
#if 0
	// You know what? The text would disappear as well (see attributes simulation).
	if (rLogFont.lfUnderline)
		Style |= FontStyleUnderline;
	if (rLogFont.lfStrikeOut)
		Style |= FontStyleStrikeout;
#endif

#ifdef _UNICODE
		WCHAR*	wFaceName = (WCHAR*)rLogFont.lfFaceName;
#else
		WCHAR	wFaceName[LF_FACESIZE];
		MultiByteToWideChar(CP_ACP, 0, (LPSTR)rLogFont.lfFaceName, LF_FACESIZE, wFaceName, LF_FACESIZE);
#endif

	TEXTMETRIC tm;
	GetTextMetrics(m_hDC, &tm);
	m_pFont = new SCFont(wFaceName, (REAL)(tm.tmHeight - tm.tmInternalLeading), (FontStyle)Style, pFontCollection);
	if (m_pFont)
	{
		if (m_pFont->IsAvailable())
		{
			m_pFont->SCFinalInit(m_hDC, rLogFont.lfEscapement, dwSimul, NULL);
			SC_OBJHOLDER_RENEW_T(m_pFontHolder, m_pFont, SCFont);
		} else
		{
			SMC_SAFEDELETE(m_pFont);
		}
	}
}

///
///	Convert the newly selected font in m_hDC to its corresponding GDI+ object.
///
void CSCEMFdcRenderer::SCOnChangeFont(HFONT hFont)
{
	ASSERT(m_pGraphics);
	SC_OBJHOLDER_RELEASE_T(m_pFontHolder, m_pFont, SCFont);

	// get the logfont
	ASSERT(hFont);
	LOGFONT LogFont;
	INT iRes = ::GetObject(hFont, sizeof(LogFont), &LogFont);
	if (!iRes)
	{// most likely the handle is not valid (maybe a deleted object)
		TRACE0("Invalid font handle in DC");
		SC_OBJHOLDER_RENEW_T(m_pFontHolder, m_pFont, SCFont);
		return;
	}

	ASSERT(abs(LogFont.lfEscapement)<=3600);

	// TODO: solve the case of GM_ADVANCED. For now, assume GM_COMPATIBLE.
	ASSERT((LogFont.lfEscapement==LogFont.lfOrientation)||
		   (GetGraphicsMode(m_hDC)==GM_ADVANCED));

	DWORD dwSimul = 0;
	// 0) Check attributes
	// We can't do:
	// m_pFont = new SCFont(m_hDC);
	// GDI+ does not support underlined/overstriked/symbol fonts (this is not a joke).
	// We must remove unsupported attributes. Otherwise, the text would simply disappear,
	// though m_pFont->IsAvailable() would return TRUE.
	if (LogFont.lfUnderline)
	{
		LogFont.lfUnderline = 0;
		dwSimul |= SC_FONT_SIMUL_UNDERLINE;
		// I know, I can use FontStyleUnderline in a FontStyle.
		// But, what am I doing here? Am I creating a font? What is a font now?
	}
	if (LogFont.lfStrikeOut)
	{
		LogFont.lfStrikeOut = 0;
		dwSimul |= SC_FONT_SIMUL_OVERSTRIKE;
		// I know, I can use FontStyleStrikeout in a FontStyle.
	}

	// 1) Check application installed fonts
	// GDI+ is unable to recognize a temporary/private GDI TT font installed by the
	// application. It bogusly replies: 'NotTrueTypeFont!'
	// So the caller must install temporary fonts in a SCFontCollection and pass it
	// to the renderer. And we check this collection first.
	if (m_pFontCollection &&
		m_pFontCollection->SCIsInstalledFacename((LPTSTR)LogFont.lfFaceName))
	{
		SCSelectPrivateFont(LogFont, m_pFontCollection, dwSimul);
		ASSERT(m_pFont);
		return;
	}

	// 2) Check DC font. Don't forget, We can't do: m_pFont = new SCFont(m_hDC);
	m_pFont = new SCFont(m_hDC, &LogFont);
	if (m_pFont)
	{
		if (m_pFont->IsAvailable())
		{
			m_pFont->SCFinalInit(m_hDC, LogFont.lfEscapement, dwSimul, NULL);
			SC_OBJHOLDER_RENEW_T(m_pFontHolder, m_pFont, SCFont);
			return;
		}
		SMC_SAFEDELETE(m_pFont);
	}

#if 1
	// 3) Attempt copying TT font in local private collection
	ASSERT(!m_pFont);
	ASSERT(g_pFontCopies);
	
	BOOL bFound = g_pFontCopies->SCIsInstalledFacename((LPTSTR)LogFont.lfFaceName);
	if (!bFound)
	{
		long lDataSize = ::GetFontData(m_hDC, 0, 0, NULL, 0);
		if (GDI_ERROR==lDataSize)
		{
			DWORD dwError = ::GetLastError();
			//ASSERT(0);
		} else
		{// read font data
			LPBYTE pData = new BYTE[lDataSize];
			ASSERT(pData);
			if (pData)
			{
				if (bFound=(::GetFontData(m_hDC, 0, 0, pData, lDataSize)>0))
				{
					g_pFontCopies->AddMemoryFont(pData, lDataSize);
				}
				delete [] pData;
				ASSERT(bFound);
			}
		}
	}
	if (bFound)
		SCSelectPrivateFont(LogFont, g_pFontCopies, dwSimul);
#endif

	// 4) Last chance: approximate font.
	//    We are lived to the good will of GDI to give us a TT font
	if (!m_pFont)
	{
		HFONT hFontSubs = SCTTFontFromLOGFONT(LogFont, m_hDC);
		// hFontSubs is selected
		m_pFont = new SCFont(m_hDC, &LogFont);

		// along with other attributes, store hFontSubs for deletion
		m_pFont->SCFinalInit(m_hDC, LogFont.lfEscapement, dwSimul, hFontSubs);
		SC_OBJHOLDER_RENEW_T(m_pFontHolder, m_pFont, SCFont);
	}
	// Yet GDI+ may refuse it
	ASSERT(m_pFont && m_pFont->IsAvailable());
}

///
///	Convert the newly selected palette in m_hDC to its corresponding GDI+ object.
///
void CSCEMFdcRenderer::SCOnChangePalette(HPALETTE hPal)
{
#pragma message( __FILE__  "(880): TODO: Palette management.")
}


///////////////////////////////////////////////////////////////////////////////////
/// Transforms

///
/// Force the transform to be exactly what is given
///
void CSCEMFdcRenderer::SCApplyTransform(XFORM& xform)
{
	ASSERT(m_pGraphics);
	Matrix matrix(xform.eM11, xform.eM12, xform.eM21, xform.eM22, xform.eDx, xform.eDy);
	m_pGraphics->SetTransform(&matrix);
}

///
///	Combine World to Page and Page to Device into a single World to Device transform
/// (since we don't have the possibility to make the distinction)
///
void CSCEMFdcRenderer::SCSetWorldTransform(XFORM& xform)
{
	ASSERT(m_pGraphics);

	// World to page
	Matrix matrix(xform.eM11, xform.eM12, xform.eM21, xform.eM22, xform.eDx, xform.eDy);
#if 1
	// Page to device
	POINT PtOrg;
	PtOrg.x = MulDiv(m_PtViewOrg.x, m_WinSize.cx, m_ViewSize.cx) - m_PtWinOrg.x;
	PtOrg.y = MulDiv(m_PtViewOrg.y, m_WinSize.cy, m_ViewSize.cy) - m_PtWinOrg.y;
	Matrix MatOrg(1, 0, 0, 1, (REAL)PtOrg.x, (REAL)PtOrg.y);

	matrix.Multiply(&MatOrg);
#endif
	m_pGraphics->SetTransform(&matrix);
}

///
///	.
///
void CSCEMFdcRenderer::SCModifyWorldTransform(XFORM& xform, DWORD dwMode)
{
	ASSERT(m_pGraphics);

#if 0
	// Won't work. We have seen dwMode=4 (?), exceeding MWT_MAX;
	if (MWT_IDENTITY==dwMode)
	{
		m_pGraphics->ResetTransform();
	} else
	{
		Matrix matrix(xform.eM11, xform.eM12, xform.eM21, xform.eM22, xform.eDx, xform.eDy);
		m_pGraphics->MultiplyTransform(&matrix, (MWT_LEFTMULTIPLY==dwMode)? MatrixOrderPrepend : MatrixOrderAppend);
	}
#else
	// So we just ignore bad values.
	// ASSERT(dwMode>=MWT_MIN && dwMode<=MWT_MAX); // Would fail too often
	switch (dwMode)
	{
	case MWT_IDENTITY:
		m_pGraphics->ResetTransform();
		//SCOnExtentChanged();
		break;

	case MWT_LEFTMULTIPLY:
		{
			Matrix matrix(xform.eM11, xform.eM12, xform.eM21, xform.eM22, xform.eDx, xform.eDy);
			m_pGraphics->MultiplyTransform(&matrix, MatrixOrderPrepend);
		}
		break;

	case MWT_RIGHTMULTIPLY:
		{
			Matrix matrix(xform.eM11, xform.eM12, xform.eM21, xform.eM22, xform.eDx, xform.eDy);
			m_pGraphics->MultiplyTransform(&matrix, MatrixOrderAppend);
		}
		break;

	case 4:
		{// Maybe a printer driver calling a Microsoft private, hidden, function into GDI
			
			// Tests (but how on Earth could it work?)
			// 1) do nothing => no image
			// 2) reset  => big image
			// 3) append  => no image
			// 4) prepend  => no image

//				// 5) panic, panic
//				XFORM xformp;
//				if (GetWorldTransform(m_hDC, &xformp))
//				{
//					//Matrix matrix(xformp.eM11, xformp.eM12, xformp.eM21, xformp.eM22, xformp.eDx, xformp.eDy);
//					//m_pGraphics->SetTransform(&matrix);
//					break;
//				} // else sing "Desperado..."
		}
		// the man is falling through

	default:
		{
			//ASSERT(0);
		}
	}
#endif
}

///////////////////////////////////////////////////////////////////////////////////
/// Mapping modes and scaling

void CSCEMFdcRenderer::SCSetMapMode(DWORD dwMode)
{
	m_dwMapMode = dwMode;
	switch (m_dwMapMode)
	{
	case MM_ANISOTROPIC:
		return;

	case MM_TEXT:
        m_WinSize.cx	= 1;
        m_WinSize.cy	= 1;
		m_ViewSize.cx	= 1;
		m_ViewSize.cy	= 1;
		SCOnExtentChanged();
		return;
	}
#pragma message( __FILE__  "(997): TODO: use destination DC, not m_hDC ")
	INT horzSize = GetDeviceCaps( m_hDC, HORZSIZE ); // in mm
	INT vertSize = GetDeviceCaps( m_hDC, VERTSIZE ); // in mm
	INT horzRes  = GetDeviceCaps( m_hDC, HORZRES );  // in pixels
	INT vertRes  = GetDeviceCaps( m_hDC, VERTRES );  // in pixels
	
	switch (m_dwMapMode)
	{
	case MM_LOMETRIC:
        m_WinSize.cx	= horzSize;
        m_WinSize.cy	= vertSize;
        m_ViewSize.cx	= horzRes / 10;
        m_ViewSize.cy	= -vertRes / 10;
        break;

    case MM_ISOTROPIC:
		{
			// MSDN: "Coordinate Mapping"
			// yVE = (yWE * ((xVE * xA) / yA)) / xWE
			//	where:
			//	
			//	yVE : viewport y-extent 
			//	yWE : window y-extent 
			//	xVE : viewport x-extent 
			//	xWE : window x-extent 
			//	xA : device x-aspect (XASPECT index to GetDeviceCaps) 
			//	yA : device y-aspect (YASPECT index to GetDeviceCaps) 
			INT xA = GetDeviceCaps( m_hDC, ASPECTX );
			INT yA = GetDeviceCaps( m_hDC, ASPECTY );
			m_WinSize.cx	= horzSize;
			m_WinSize.cy	= vertSize;
			m_ViewSize.cx	= horzRes / 10;
			m_ViewSize.cy = -(INT)((m_WinSize.cy * ((m_ViewSize.cx * xA) / yA)) / m_WinSize.cx);
			// TODO: revise to keep the smallest between horzRes and vertRes, and reduce the other
		}
        break;

    case MM_HIMETRIC:
        m_WinSize.cx	= horzSize * 10;
        m_WinSize.cy	= vertSize * 10;
        m_ViewSize.cx	= horzRes / 10;
        m_ViewSize.cy	= -vertRes / 10;
        break;

    case MM_LOENGLISH:
        m_WinSize.cx	= horzSize;
        m_WinSize.cy	= vertSize;
        m_ViewSize.cx	= MulDiv(254, horzRes, 1000);
        m_ViewSize.cy	= -MulDiv(254, vertRes, 1000);
        break;

    case MM_HIENGLISH:
        m_WinSize.cx	= horzSize * 10;
        m_WinSize.cy	= vertSize * 10;
        m_ViewSize.cx	= MulDiv(254, horzRes, 1000);
        m_ViewSize.cy	= -MulDiv(254, vertRes, 1000);
        break;

    case MM_TWIPS:
        m_WinSize.cx	= MulDiv(144, horzSize, 10);
        m_WinSize.cy	= MulDiv(144, vertSize, 10);
        m_ViewSize.cx	= MulDiv(254, horzRes, 1000);
        m_ViewSize.cy	= -MulDiv(254, vertRes, 1000);
        break;

	default:
		ASSERT(0);
	}
	SCOnExtentChanged();
}

void CSCEMFdcRenderer::SCSetWindowOrg(INT iX, INT iY)
{
	ASSERT(m_pGraphics);
	m_PtWinOrg.x = iX;
	m_PtWinOrg.y = iY;
	SCOnExtentChanged();
}

void CSCEMFdcRenderer::SCSetViewportOrg(INT iX, INT iY)
{
	ASSERT(m_pGraphics);
	m_PtViewOrg.x = iX;
	m_PtViewOrg.y = iY;
	SCOnExtentChanged();
}

//	"When the following mapping modes are set, calls to the SetWindowExtEx and
//	SetViewportExtEx functions are ignored. 
//	
//	MM_HIENGLISH, MM_HIMETRIC, MM_LOENGLISH, MM_LOMETRIC, MM_TEXT, MM_TWIPS"
//

void CSCEMFdcRenderer::SCSetWindowExtent(INT iCx, INT iCy)
{
	ASSERT(m_pGraphics);

	switch (m_dwMapMode)
	{
	case MM_ANISOTROPIC:
		m_WinSize.cx = iCx;
		m_WinSize.cy = iCy;
		break;

	case MM_ISOTROPIC:
		m_WinSize.cx = iCx;
		m_WinSize.cy = iCy;
		// For MM_ISOTROPIC, adjust viewport extent
		// "An application must call the SetWindowExtEx function before it calls
		// SetViewportExtEx"
		{
			ASSERT(m_WinSize.cx);
			INT xA = GetDeviceCaps( m_hDC, ASPECTX );
			INT yA = GetDeviceCaps( m_hDC, ASPECTY );
			m_ViewSize.cx	= GetDeviceCaps( m_hDC, HORZRES ) / 10;
			m_ViewSize.cy = -(INT)((m_WinSize.cy * ((m_ViewSize.cx * xA) / yA)) / m_WinSize.cx);
		}
		break;

	// just to see
	case MM_TEXT:
		// and I saw MM_TEXT
		m_WinSize.cx = 1;
		m_WinSize.cy = 1;
		break;

	default:
		ASSERT(0); // just to see
	}

	SCOnExtentChanged();
}

//	"When the following mapping modes are set, calls to the SetWindowExtEx and
//	SetViewportExtEx functions are ignored. 
//	
//	MM_HIENGLISH, MM_HIMETRIC, MM_LOENGLISH, MM_LOMETRIC, MM_TEXT, MM_TWIPS"
//

void CSCEMFdcRenderer::SCSetViewportExtent(INT iCx, INT iCy)
{
	ASSERT(m_pGraphics);

	switch (m_dwMapMode)
	{
	case MM_ANISOTROPIC:
		m_ViewSize.cx = iCx;
		m_ViewSize.cy = iCy;
		break;

	case MM_ISOTROPIC:
		m_ViewSize.cx = iCx;
		m_ViewSize.cy = iCy;
		// For MM_ISOTROPIC, adjust viewport extent
		// "An application must call the SetWindowExtEx function before it calls
		// SetViewportExtEx"
#pragma message( __FILE__  "(1153): TODO: use destination DC ")
		{
			INT xA = GetDeviceCaps( m_hDC, ASPECTX );
			INT yA = GetDeviceCaps( m_hDC, ASPECTY );
			m_ViewSize.cy = -(INT)((m_WinSize.cy * ((m_ViewSize.cx * xA) / yA)) / m_WinSize.cx);
			// TODO: revise to keep the smallest between iCx and iCy, and reduce the other
		}
		break;

	// just to see
	case MM_TEXT:
		// and I saw MM_TEXT
		m_ViewSize.cx = 1;
		m_ViewSize.cy = 1;
		break;

	default:
		ASSERT(0); // just to see
	}

	SCOnExtentChanged();
}

void CSCEMFdcRenderer::SCScaleWindow(LONG lxNum, LONG lxDenom, LONG lyNum, LONG lyDenom)
{
	ASSERT(m_pGraphics);
	if (MM_ANISOTROPIC==m_dwMapMode || MM_ISOTROPIC==m_dwMapMode)
	{// Note: We have no specification about these conditions (seem natural)
		m_WinSize.cx = MulDiv(m_WinSize.cx, lxNum, lxDenom);
		m_WinSize.cy = MulDiv(m_WinSize.cy, lyNum, lyDenom);
	}

#if 1
	// Normally, origin is not concerned (this seems to reproduce a GDI bug)
	m_PtWinOrg.x = MulDiv(m_PtWinOrg.x, lxNum, lxDenom);
	m_PtWinOrg.y = MulDiv(m_PtWinOrg.y, lyNum, lyDenom);
#endif

	SCOnExtentChanged();
}

void CSCEMFdcRenderer::SCScaleViewport(LONG lxNum, LONG lxDenom, LONG lyNum, LONG lyDenom)
{
	ASSERT(m_pGraphics);
	if (MM_ANISOTROPIC==m_dwMapMode || MM_ISOTROPIC==m_dwMapMode)
	{// Note: We have no specification about these conditions (seem natural)
		m_ViewSize.cx = MulDiv(m_ViewSize.cx, lxNum, lxDenom);
		m_ViewSize.cy = MulDiv(m_ViewSize.cy, lyNum, lyDenom);
	}

#if 1
	// Normally, origin is not concerned (this seems to reproduce a GDI bug)
	m_PtViewOrg.x = MulDiv(m_PtViewOrg.x, lxNum, lxDenom);
	m_PtViewOrg.y = MulDiv(m_PtViewOrg.y, lyNum, lyDenom);
#endif

	SCOnExtentChanged();
}

void CSCEMFdcRenderer::SCOnExtentChanged()
{
	ASSERT(m_pGraphics);
	ASSERT(m_WinSize.cx && m_WinSize.cy);

	Matrix matrix;
	matrix.Scale(float(m_ViewSize.cx)/float(m_WinSize.cx), float(m_ViewSize.cy)/float(m_WinSize.cy));

	POINT PtOrg;
	PtOrg.x = MulDiv(m_PtViewOrg.x, m_WinSize.cx, m_ViewSize.cx) - m_PtWinOrg.x;
	PtOrg.y = MulDiv(m_PtViewOrg.y, m_WinSize.cy, m_ViewSize.cy) - m_PtWinOrg.y;

	matrix.Translate((REAL)PtOrg.x, (REAL)PtOrg.y);
	// Combine the new transform with the original one that
	// was in the destitation DC before rendering started.
#pragma message( __FILE__  "(1227): TODO: Investigation: revise the use of m_DestMatrix")
	matrix.Multiply(&m_DestMatrix);
	m_pGraphics->SetTransform(&matrix);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions