#include "winplus.h"
#pragma  hdrstop
#include <math.h>

/************************************************************************\
1. Написан - июль 1998. Описан один потомок класса CDCObject - CClientDC,
  инкапсулирующий контекст экранного устройства.
2. 27 июля 1998 г. - описан потомок CMetafileDC, оперирующий контекстом
  размещаемого метафайла
  30/08/98
3. Исправление бага - неверное поведение GetTextExtentPoint32 в метафай
  ловом контексте.  
\************************************************************************/

// =======================================================================
// Код защищенных методов, конструктора и деструктора
// =======================================================================

void CDCObject::ResetAll()
{
  hBrushOld=hBrush=NULL;
  hFontOld=hFont=NULL;
  hPenOld=hPen=NULL;
  nCurrentOrientation=0;
}

void CDCObject::DeleteBrush()
{
  if(hBrushOld)
  {
    ::SelectObject(HandleDC,hBrushOld);
    if(hBrush) ::DeleteObject(hBrush);
  }
  hBrushOld=hBrush=NULL;
}

void CDCObject::DeleteFont()
{
  if(hFontOld)
  {
    ::SelectObject(HandleDC,hFontOld);
    if(hFont) ::DeleteObject(hFont);
  }
  hFontOld=hFont=NULL;
}

void CDCObject::DeletePen()
{
  if(hPenOld)
  {
    ::SelectObject(HandleDC,hPenOld);
    if(hPen) ::DeleteObject(hPen);
  }
  hPenOld=hPen=NULL;
}

void CDCObject::ReleaseAll()
{
  DeleteBrush();
  DeleteFont();
  DeletePen();
  if(HandleWND)
  {
    ::ReleaseDC(HandleWND,HandleDC);
    HandleWND=NULL;
  }
  HandleDC=NULL;
}

CDCObject::CDCObject(HDC hDC)
{
  ResetAll();
  HandleDC=hDC;
}

CDCObject::~CDCObject()
{
  ReleaseAll();
}

// =======================================================================
// Код методов, создающих объекты GDI
// =======================================================================

void CDCObject::CreatePen(int fnPenStyle,int nWidth,COLORREF crColor)
{
  DeletePen();
  hPen=::CreatePen(fnPenStyle,nWidth,crColor);
  if(hPen) hPenOld=::SelectObject(HandleDC,hPen);
}

void CDCObject::CreatePenIndirect(CONST LOGPEN *lplgpn)
{
  DeletePen();
  hPen=::CreatePenIndirect(lplgpn);
  if(hPen) hPenOld=::SelectObject(HandleDC,hPen);
}

void CDCObject::CreateBrushIndirect(CONST LOGBRUSH *lplb)
{
  DeleteBrush();
  hBrush=::CreateBrushIndirect(lplb);
  if(hBrush) hBrushOld=::SelectObject(HandleDC,hBrush);
}

void CDCObject::CreateDIBPatternBrush(HGLOBAL hglbDIBPacked,UINT fuColorSpec)
{
  DeleteBrush();
  hBrush=::CreateDIBPatternBrush(hglbDIBPacked,fuColorSpec);
  if(hBrush) hBrushOld=::SelectObject(HandleDC,hBrush);
}

void CDCObject::CreateDIBPatternBrushPt(CONST VOID *lpPackedDIB,UINT iUsage)
{
  DeleteBrush();
  hBrush=::CreateDIBPatternBrushPt(lpPackedDIB,iUsage);
  if(hBrush) hBrushOld=::SelectObject(HandleDC,hBrush);
}

void CDCObject::CreatePatternBrush(HBITMAP hbmp)
{
  DeleteBrush();
  hBrush=::CreatePatternBrush(hbmp);
  if(hBrush) hBrushOld=::SelectObject(HandleDC,hBrush);
}

void CDCObject::CreateSolidBrush(COLORREF crColor)
{
  DeleteBrush();
  hBrush=::CreateSolidBrush(crColor);
  if(hBrush) hBrushOld=::SelectObject(HandleDC,hBrush);
}

void CDCObject::CreateFont(int nHeight,int nWidth,int nEscapement,
  int nOrientation,int fnWeight,DWORD fdwItalic,DWORD fdwUnderline,
  DWORD fdwStrikeOut,DWORD fdwCharSet,DWORD fdwOutputPrecision,
  DWORD fdwClipPrecision,DWORD fdwQuality,DWORD fdwPitchAndFamily,
  LPCTSTR lpszFace)
{
  DeleteFont();
  hFont=::CreateFont(nHeight,nWidth,nEscapement,nOrientation,fnWeight,
    fdwItalic,fdwUnderline,fdwStrikeOut,fdwCharSet,fdwOutputPrecision,
    fdwClipPrecision,fdwQuality,fdwPitchAndFamily,lpszFace);
  if(hFont)
  {
    hFontOld=::SelectObject(HandleDC,hFont);
    nCurrentOrientation=nOrientation;
  }
}

void CDCObject::CreateFontIndirect(CONST LOGFONT *lplf)
{
  DeleteFont();
  hFont=::CreateFontIndirect(lplf);
  if(hFont)
  {
    hFontOld=::SelectObject(HandleDC,hFont);
    nCurrentOrientation=lplf->lfOrientation;
  }
}

void CDCObject::CreateFont(int nSize,LPCTSTR lpszFace)
{
  DeleteFont();
  hFont=::CreateFont(nSize,0,0,0,FW_NORMAL, FALSE, FALSE, FALSE,
    DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,
    DEFAULT_QUALITY,DEFAULT_PITCH||FF_DONTCARE,lpszFace);
  if(hFont)
  {
    hFontOld=::SelectObject(HandleDC,hFont);
    nCurrentOrientation=0;
  }
}

void CDCObject::CreateFont(int nHeight,int nWidth,int nOrientation,
  LPCTSTR lpszFace)
{
  DeleteFont();
  hFont=::CreateFont(nHeight,nWidth,nOrientation,nOrientation,
    FW_NORMAL, FALSE, FALSE, FALSE,
    DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,
    DEFAULT_QUALITY,DEFAULT_PITCH||FF_DONTCARE,lpszFace);
  if(hFont)
  {
    hFontOld=::SelectObject(HandleDC,hFont);
    nCurrentOrientation=nOrientation;
  }
}

void CDCObject::CreateFont(int nHeight,int nWidth,int nOrientation,
    int fnWeight,DWORD fdwItalic,DWORD fdwUnderline,LPCTSTR lpszFace)
{
  DeleteFont();
  hFont=::CreateFont(nHeight,nWidth,nOrientation,nOrientation,
    fnWeight,fdwItalic,fdwUnderline,FALSE,
    DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,
    DEFAULT_QUALITY,DEFAULT_PITCH||FF_DONTCARE,lpszFace);
  if(hFont)
  {
    hFontOld=::SelectObject(HandleDC,hFont);
    nCurrentOrientation=nOrientation;
  }
}

void CDCObject::CreateFont(CFont *lpFont)
{
  CreateFontIndirect(lpFont->GetLogFont());
}

void CDCObject::CreatePen(CPen *lpPen)
{
  CreatePenIndirect(lpPen->GetLogPen());
}

void CDCObject::CreateBrush(CBrush *lpBrush)
{
  CreateBrushIndirect(lpBrush->GetLogBrush());
}

// =======================================================================
// Код методов, рисующих объектами GDI
// =======================================================================

void CDCObject::AngleArc(int X,int Y,DWORD dwRadius,FLOAT eStartAngle,
  FLOAT eSweepAngle)
{
  bLastError=(::AngleArc(HandleDC,X,Y,dwRadius,eStartAngle,
    eSweepAngle)==0);
}

void CDCObject::LineTo(int X,int Y)
{
  bLastError=(::LineTo(HandleDC,X,Y)==0);
}

void CDCObject::Polyline(CONST POINT *lppt,int cPoints)
{
  bLastError=(::Polyline(HandleDC,lppt,cPoints)==0);
}

void CDCObject::Arc(int nLeftRect,int nTopRect,int nRightRect,
  int nBottomRect,int nXStartArc,int nYStartArc,int nXEndArc,
  int nYEndArc)
{
  bLastError=(::Arc(HandleDC,nLeftRect,nTopRect,nRightRect,nBottomRect,
    nXStartArc,nYStartArc,nXEndArc,nYEndArc)==0);
}

void CDCObject::MoveToEx(int X,int Y,LPPOINT lpPoint)
{
  bLastError=(::MoveToEx(HandleDC,X,Y,lpPoint)==0);
}

void CDCObject::MoveTo(int X,int Y)
{
  bLastError=(::MoveToEx(HandleDC,X,Y,NULL)==0);
}

void CDCObject::PolylineTo(CONST POINT *lppt,DWORD cCount)
{
  bLastError=(::PolylineTo(HandleDC,lppt,cCount)==0);
}

void CDCObject::ArcTo(int nLeftRect,int nTopRect,int nRightRect,
  int nBottomRect,int nXRadial1,int nYRadial1,int nXRadial2,
  int nYRadial2)
{
  bLastError=(::ArcTo(HandleDC,nLeftRect,nTopRect,nRightRect,nBottomRect,
  nXRadial1,nYRadial1,nXRadial2,nYRadial2)==0);
}

void CDCObject::Pie(int nLeftRect,int nTopRect,int nRightRect,
  int nBottomRect,int nXRadial1,int nYRadial1,int nXRadial2,
  int nYRadial2)
{
  bLastError=(::Pie(HandleDC,nLeftRect,nTopRect,nRightRect,nBottomRect,
    nXRadial1,nYRadial1,nXRadial2,nYRadial2)==0);
}

void CDCObject::PolyPolygon(CONST POINT *lpPoints,
  CONST INT *lpPolyCounts,int nCount)
{
  bLastError=(::PolyPolygon(HandleDC,lpPoints,lpPolyCounts,nCount)==0);
}

void CDCObject::Chord(int nLeftRect,int nTopRect,int nRightRect,
  int nBottomRect,int nXRadial1,int nYRadial1,int nXRadial2,
  int nYRadial2)
{
  bLastError=(::Chord(HandleDC,nLeftRect,nTopRect,nRightRect,nBottomRect,
    nXRadial1,nYRadial1,nXRadial2,nYRadial2)==0);
}

void CDCObject::PolyBezier(CONST POINT *lppt,DWORD cPoints)
{
  bLastError=(::PolyBezier(HandleDC,lppt,cPoints)==0);
}

void CDCObject::PolyBezierTo(CONST POINT *lppt,DWORD cCount)
{
  bLastError=(::PolyBezierTo(HandleDC,lppt,cCount)==0);
}

void CDCObject::Rectangle(int left,int top,int right,int bottom)
{
  bLastError=(::Rectangle(HandleDC,left,top,right,bottom)==0);
}

void CDCObject::Ellipse(int nLeftRect,int nTopRect,int nRightRect,
  int nBottomRect)
{
  bLastError=(::Ellipse(HandleDC,nLeftRect,nTopRect,nRightRect,
    nBottomRect)==0);
}

void CDCObject::PolyDraw(CONST POINT *lppt,CONST BYTE *lpbTypes,
  int cCount)
{
  bLastError=(::PolyDraw(HandleDC,lppt,lpbTypes,cCount)==0);
}

void CDCObject::RoundRect(int nLeftRect,int nTopRect,int nRightRect,
  int nBottomRect,int nWidth,int nHeight)
{
  bLastError=(::RoundRect(HandleDC,nLeftRect,nTopRect,nRightRect,
    nBottomRect,nWidth,nHeight)==0);
}

void CDCObject::ExtTextOut(int X,int Y,UINT fuOptions,CONST RECT *lprc,
    LPCTSTR lpString,UINT cbCount,CONST INT *lpDx)
{
  bLastError=(::ExtTextOut(HandleDC,X,Y,fuOptions,lprc,lpString,
    cbCount,lpDx)==0);
}

void CDCObject::Polygon(CONST POINT *lpPoints,int nCount)
{
  bLastError=(::Polygon(HandleDC,lpPoints,nCount)==0);
}

void CDCObject::TextOut(int x,int y,char *lpStr,int cbString)
{
  bLastError=(::TextOut(HandleDC,x,y,lpStr,cbString)==0);
}

void CDCObject::TextOut(int x,int y,char *lpStr)
{
  bLastError=(::TextOut(HandleDC,x,y,lpStr,strlen(lpStr))==0);
}

void CDCObject::PolyPolyline(CONST POINT *lppt,
  CONST DWORD *lpdwPolyPoints,DWORD cCount)
{
  bLastError=(::PolyPolyline(HandleDC,lppt,lpdwPolyPoints,cCount)==0);
}

void CDCObject::Line(int x1,int y1,int x2,int y2)
{
  MoveTo(x1,y1);
  LineTo(x2,y2);
}

void CDCObject::PatBlt(int nXLeft,int nYLeft,int nWidth,int nHeight,
  DWORD dwRop)
{
  bLastError=(::PatBlt(HandleDC,nXLeft,nYLeft,nWidth,nHeight,dwRop)==0);
}

void CDCObject::PatBlt(RECT *lprcArea,DWORD dwRop)
{
  bLastError=(::PatBlt(HandleDC,
    lprcArea->left,lprcArea->top,
    lprcArea->right-lprcArea->left,lprcArea->bottom-lprcArea->top,
    dwRop)==0);
}

void CDCObject::HollowRectangle(RECT *lpRc)
{
  //  Convert RECT to 5 * POINT
  POINT aVertices[5];
  aVertices[0].x=aVertices[1].x=aVertices[4].x=lpRc->left;
  aVertices[0].y=aVertices[3].y=aVertices[4].y=lpRc->top;
  aVertices[1].y=aVertices[2].y=lpRc->bottom;
  aVertices[2].x=aVertices[3].x=lpRc->right;
  //  Draw rectangle
  bLastError=(::Polyline(HandleDC,aVertices,5)==0);
}

// =======================================================================
// Код методов, устанавливающих атрибуты контекста
// =======================================================================

void CDCObject::SetTextAlign(UINT fMode)
{
  bLastError=(::SetTextAlign(HandleDC,fMode)==GDI_ERROR);
}

void CDCObject::SetTextCharacterExtra(int nCharExtra)
{
  bLastError=(::SetTextCharacterExtra(HandleDC,nCharExtra)==0x80000000);
}

void CDCObject::SetTextColor(COLORREF crColor)
{
  ::SetTextColor(HandleDC, crColor);
  bLastError = FALSE;
}

void CDCObject::SetBkColor(COLORREF crColor)
{                    
  ::SetBkColor(HandleDC, crColor);
  bLastError = FALSE;
}

void CDCObject::SetMapMode(int fnMapMode)
{
  bLastError=(::SetMapMode(HandleDC,fnMapMode)==0);
}

void CDCObject::SetBkMode(int iBkMode)
{
  bLastError=(::SetBkMode(HandleDC,iBkMode)==0);
}

void CDCObject::SetTextJustification(int nBreakExtra,int nBreakCount)
{
  bLastError=(::SetTextJustification(HandleDC,nBreakExtra,
    nBreakCount)==0);
}

// =======================================================================
// Разное
// =======================================================================

void CDCObject::GetTextExtentPoint(LPCTSTR lpString,int cbString,
  LPSIZE lpSize)
{
  bLastError=(::GetTextExtentPoint32(HandleDC,lpString,cbString,
    lpSize)==0);
}

void CDCObject::GetTextExtentPoint(LPCTSTR lpString,LPSIZE lpSize)
{
  GetTextExtentPoint(lpString,strlen(lpString),lpSize);
}

//  Поворачивает прямоугольник вокруг верхнего левого угла.
//  Параметр Angle10 - десятые доли градуса угла поворота
static void
RotateRect(int Angle10, RECT *lpRc,POINT *lpPt,BOOL bLooped=FALSE)
{
  double angle = - (double)Angle10 * 0.00174532925199433;
  double s = sin(angle), c = cos(angle);
  lpPt[0].x=lpRc->left;
  lpPt[0].y=lpRc->top;
  lpPt[1].x=0;
  lpPt[1].y=lpRc->bottom-lpRc->top;
  lpPt[2].x=lpRc->right-lpRc->left;
  lpPt[2].y=lpRc->bottom-lpRc->top;
  lpPt[3].x=lpRc->right-lpRc->left;
  lpPt[3].y=0;
  double x, y;
  for(int i=1;i<4;i++)
  {
    x=lpPt[i].x*c-lpPt[i].y*s;
    y=lpPt[i].x*s+lpPt[i].y*c;
    lpPt[i].x=lpRc->left+x;
    lpPt[i].y=lpRc->top+y;
  }
  if(bLooped)
  {
    lpPt[4].x=lpPt[0].x;
    lpPt[4].y=lpPt[0].y;
  }
}

void CDCObject::GetTextRect(LPCTSTR lpString,POINT *lpOrigin,
  POINT *aVertices,BOOL bLooped)
{
  SIZE sz;
  GetTextExtentPoint(lpString,strlen(lpString),&sz);
  if(bLastError==FALSE)
  {
    RECT rc=
    {
      lpOrigin->x,
      lpOrigin->y,
      lpOrigin->x+sz.cx,
      lpOrigin->y+sz.cy
    };
    RotateRect(nCurrentOrientation,&rc,aVertices,bLooped);
    bLastError=FALSE;
  }
  else
    bLastError=TRUE;
}

// =======================================================================

CClientDC::CClientDC(CWindowBase *lpOutputFrame):CDCObject(NULL)
{
  GetWnd()=lpOutputFrame->GetWnd();
  GetDC()=::GetDC(GetWnd());
}

BOOL CClientDC::GetClientArea(RECT *lpRcClient)
{
  HWND hClientWindow = ::WindowFromDC(/*CDCObject::*/GetDC());
  return ::GetClientRect(hClientWindow,lpRcClient)!=0;
}

// =======================================================================
// Вспомогательные тип и процедура для поддержки размещаемых метафайлов
#define         PMPHKEY         0x9AC6CDD7

// Заголовок размещаемого метафайла
typedef         struct
{
  ULONG         key;
  USHORT        emptywmf;
  WORD          left, top, right, bottom;
  WORD          inch;
  DWORD         reserved;
  WORD          checksum;
}               PMFH;

// Процедура вычисляет контрольную сумму заголовка метафайла
static WORD pmfCheckSum(PMFH *pmfh)
{
  WORD *ptr=(WORD*)pmfh;
  WORD sum=0;
  for(int i=0;i<10;i++) sum^=ptr[i];
  return sum;
}

CMetafileDC::CMetafileDC(char *szFileName, RECT *lpRcInches1000)
  : CDCObject(NULL)
{
  /*CDCObject::*/GetDC()=CreateMetaFile(NULL);
  BoundsRect.left=BoundsRect.top=0;
  if(lpRcInches1000!=NULL)
  {
    BoundsRect.right = lpRcInches1000->right-lpRcInches1000->left;
    BoundsRect.bottom = lpRcInches1000->bottom-lpRcInches1000->top;
  }
  if((lpRcInches1000==NULL)||(BoundsRect.bottom<0)||(BoundsRect.right<0))
  {
    // Размер прямоугольника подбираем так, чтобы он умещался на лист A4:
    // ширина = 1000 точек/дюйм * 10 дюймов (25 см.) = 10000
    // высота = 1000 точек/дюйм * 6,7 дюймов (17 см.) = 6700
    BoundsRect.right = 10000;
    BoundsRect.bottom = 6700;
  }
  ::SetWindowOrgEx(/*CDCObject::*/GetDC(),BoundsRect.left,
    BoundsRect.top,NULL);
  ::SetWindowExtEx(/*CDCObject::*/GetDC(),BoundsRect.right,
    BoundsRect.bottom,NULL);
  // Сохраняем имя файла
  this->szFileName=szFileName;
}

CMetafileDC::~CMetafileDC()
{
  // Деструктор преобразует расширенный метафайл в размещаемый и
  // записывает данные на диск
  HMETAFILE hmf=::CloseMetaFile(/*CDCObject::*/GetDC());
  // Получаем размер блока памяти под данные метафайла в формате Win 3.1
  DWORD siz=::GetMetaFileBitsEx(hmf,0,NULL);
  if(!siz)
  {
    Report("Ошибка вызова 'GetMetaFileBitsEx'");
    return;
  }
  // Выделяем память под данные матефайла
  char *bits=(char*)/*MemOverride::*/malloc(siz);
  // Преобразуем данные формата EMF в данные формата WMF
  ::GetMetaFileBitsEx(hmf,siz,bits);
  // Подготавливаем заголовок размещаемого метафайла:
  PMFH aph;
  aph.key=PMPHKEY;
  aph.emptywmf=0;
  aph.left=BoundsRect.left;
  aph.top=BoundsRect.top;
  aph.right=BoundsRect.right;
  aph.bottom=BoundsRect.bottom;
  // Разрешение - 1000 точек на дюйм
  aph.inch=1000;
  aph.reserved=0;
  // Вычисляем и записываем в заголовок контрольную сумму
  aph.checksum=pmfCheckSum(&aph);
  // Создаем файл
  COutputBinaryFile *WMF= new COutputBinaryFile(szFileName);
  if(WMF->LastError())
  {
    if(WMF->IsExists()==FALSE)
      Report("Ошибка создания файла '%s'",szFileName);
    delete WMF;
    ::DeleteMetaFile(hmf);
    /*MemOverride::*/free(bits);
    return;
  }
  // Записываем заголовок
  WMF->write(&aph,22);
  if(WMF->LastError())
  {
    Report("Ошибка записи в метафайл '%s'",szFileName);
    delete WMF;
    ::DeleteFile(szFileName);
    ::DeleteMetaFile(hmf);
    /*MemOverride::*/free(bits);
    return;
  }
  // Записываем данные метафайла
  WMF->write(bits,siz);
  if(WMF->LastError())
  {
    Report("Ошибка записи в метафайл '%s'",szFileName);
    delete WMF;
    ::DeleteFile(szFileName);
    ::DeleteMetaFile(hmf);
    /*MemOverride::*/free(bits);
    return;
  }
  // Сбрасываем буфер на диск
  WMF->Commit();
  // Закрываем файл
  delete WMF;
  // Удаляем блок данных в формате EMF
  ::DeleteMetaFile(hmf);
  // Освобождаем память, занятую данными в формате WMF
  /*MemOverride::*/free(bits);
}

BOOL CMetafileDC::GetClientArea(RECT *lpRcClient)
{
  memcpy(lpRcClient,&BoundsRect,sizeof(RECT));
  return TRUE;
}

void CMetafileDC::GetTextExtentPoint(LPCTSTR lpString,
  int cbString,LPSIZE lpSize)
{
  HDC RefDC=::GetDC(NULL);
  HFONT hFntOld=(HFONT)::SelectObject(RefDC,hFont);
  ::GetTextExtentPoint32(RefDC,lpString,cbString,lpSize);
  ::SelectObject(RefDC,hFntOld);
  ::ReleaseDC(NULL,RefDC);
}
