//-----------------------------------------------------------------------------
// AScript canvas module
//-----------------------------------------------------------------------------
#include <ascript.h>

AScript_BeginModule(canvas)

AScript_DeclarePrivSymbol(red);
AScript_DeclarePrivSymbol(blue);
AScript_DeclarePrivSymbol(green);
AScript_DeclarePrivSymbol(cyan);
AScript_DeclarePrivSymbol(light_blue);
AScript_DeclarePrivSymbol(yellow);
AScript_DeclarePrivSymbol(black);
AScript_DeclarePrivSymbol(white);
AScript_DeclarePrivSymbol(grey);

AScript_DeclarePrivSymbol(solid);
AScript_DeclarePrivSymbol(dot);
AScript_DeclarePrivSymbol(dash);
AScript_DeclarePrivSymbol(dot_dash);

AScript_DeclarePrivSymbol(bdiagonal);
AScript_DeclarePrivSymbol(cross);
AScript_DeclarePrivSymbol(diagcross);
AScript_DeclarePrivSymbol(fdiagonal);
AScript_DeclarePrivSymbol(horizontal);
AScript_DeclarePrivSymbol(vertical);

AScript_DeclarePrivSymbol(normal);
AScript_DeclarePrivSymbol(default);
AScript_DeclarePrivSymbol(decorative);
AScript_DeclarePrivSymbol(roman);
AScript_DeclarePrivSymbol(script);
AScript_DeclarePrivSymbol(swiss);
AScript_DeclarePrivSymbol(modern);
AScript_DeclarePrivSymbol(teletype);
AScript_DeclarePrivSymbol(slant);
AScript_DeclarePrivSymbol(italic);
AScript_DeclarePrivSymbol(light);
AScript_DeclarePrivSymbol(bold);

AScript_DeclarePrivSymbol(n);
AScript_DeclarePrivSymbol(ne);
AScript_DeclarePrivSymbol(e);
AScript_DeclarePrivSymbol(se);
AScript_DeclarePrivSymbol(s);
AScript_DeclarePrivSymbol(sw);
AScript_DeclarePrivSymbol(w);
AScript_DeclarePrivSymbol(nw);
AScript_DeclarePrivSymbol(c);

//-----------------------------------------------------------------------------
// Anchor
//-----------------------------------------------------------------------------
enum Anchor {
	ANCHOR_N,
	ANCHOR_NE,
	ANCHOR_E,
	ANCHOR_SE,
	ANCHOR_S,
	ANCHOR_SW,
	ANCHOR_W,
	ANCHOR_NW,
	ANCHOR_C,
};

void DeclareAnchorAttrs(Function *pFunc)
{
	pFunc->DeclareAttr(AScript_PrivSymbol(n));
	pFunc->DeclareAttr(AScript_PrivSymbol(ne));
	pFunc->DeclareAttr(AScript_PrivSymbol(e));
	pFunc->DeclareAttr(AScript_PrivSymbol(se));
	pFunc->DeclareAttr(AScript_PrivSymbol(s));
	pFunc->DeclareAttr(AScript_PrivSymbol(sw));
	pFunc->DeclareAttr(AScript_PrivSymbol(w));
	pFunc->DeclareAttr(AScript_PrivSymbol(nw));
	pFunc->DeclareAttr(AScript_PrivSymbol(c));
}

Anchor GetAnchor(const SymbolSet &attrs)
{
	Anchor anchor = ANCHOR_SW;
	if (attrs.IsSet(AScript_PrivSymbol(n))) {
		anchor = ANCHOR_N;
	} else if (attrs.IsSet(AScript_PrivSymbol(ne))) {
		anchor = ANCHOR_NE;
	} else if (attrs.IsSet(AScript_PrivSymbol(e))) {
		anchor = ANCHOR_E;
	} else if (attrs.IsSet(AScript_PrivSymbol(se))) {
		anchor = ANCHOR_SE;
	} else if (attrs.IsSet(AScript_PrivSymbol(s))) {
		anchor = ANCHOR_S;
	} else if (attrs.IsSet(AScript_PrivSymbol(sw))) {
		anchor = ANCHOR_SW;
	} else if (attrs.IsSet(AScript_PrivSymbol(w))) {
		anchor = ANCHOR_W;
	} else if (attrs.IsSet(AScript_PrivSymbol(nw))) {
		anchor = ANCHOR_NW;
	} else if (attrs.IsSet(AScript_PrivSymbol(c))) {
		anchor = ANCHOR_C;
	}
	return anchor;
}

//-----------------------------------------------------------------------------
// Color
//-----------------------------------------------------------------------------
struct Color {
	unsigned char _red, _blue, _green;
	inline Color() : _red(0), _blue(0), _green(0) {}
	inline Color(unsigned char red, unsigned char blue, unsigned char green) :
									_red(red), _blue(blue), _green(green) {}
	Color(Signal sig, const Value &value);
#if defined(HAVE_WINDOWS_H)
	inline COLORREF ToWin32() const { return RGB(_red, _blue, _green); }
#endif
};

//-----------------------------------------------------------------------------
// Device
//-----------------------------------------------------------------------------
class Device {
protected:
	int _cntRef;
	Number _width, _height;
public:
	inline Device(Number width, Number height) : _cntRef(1),
											_width(width), _height(height) {}
	inline Device *IncRef() { _cntRef++; return this; }
	inline int DecRef() { _cntRef--; return _cntRef; }
	inline int GetRefCnt() const { return _cntRef; }
	inline static void Delete(Device *pDevice) {
		if (pDevice != NULL && pDevice->DecRef() <= 0) delete pDevice;
	}
	virtual ~Device();
	Value Initialize(Environment &env, Signal sig, const Function *pFuncBlock);
	virtual const char *GetName() const = 0;
	virtual void Close() = 0;
	virtual void Fill(Signal sig, const Value &color) = 0;
	virtual void SetPen(Signal sig, const Value &color, Number width, const Symbol *pSymbol) = 0;
	virtual void SetBrush(Signal sig, const Value &color, const Symbol *pSymbol) = 0;
	virtual void SetFont(Signal sig, Number height,
			const Symbol *family, const Symbol *style, const Symbol *weight,
			const char *faceName) = 0;
	virtual void SetTextColor(Signal sig, const Value &color) = 0;
	virtual void Text(Signal sig, Number x, Number y, const char *text, Number wdBound, Number htBound, Anchor anchor) = 0;
	virtual void TextRot(Signal sig, Number x, Number y, const char *text, Number angle) = 0;
	virtual void Line(Signal sig, Number x1, Number y1, Number x2, Number y2) = 0;
	virtual void Rectangle(Signal sig, Number x, Number y, Number width, Number height, Anchor anchor) = 0;
	virtual void Ellipse(Signal sig, Number x, Number y, Number width, Number height, Anchor anchor) = 0;
	virtual void Pie(Signal sig, Number x, Number y, Number width, Number height,
								Number degStart, Number degEnd, Anchor anchor) = 0;
	virtual void Polygon(Signal sig, const ValueList &xs, const ValueList &ys, bool closeFlag) = 0;
	virtual void Polygon(Signal sig, const ValueList &pts, bool closeFlag) = 0;
	inline Number GetWidth() const { return _width; }
	inline Number GetHeight() const { return _height; }
};

//-----------------------------------------------------------------------------
// Object_Canvas
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Canvas);

class Object_Canvas : public Object {
private:
	Device *_pDevice;
public:
	AScript_DeclareObjectAccessor(Canvas)
public:
	inline Object_Canvas(Environment &env, Device *pDevice) :
						Object(AScript_PrivClass(Canvas)), _pDevice(pDevice) {}
	inline Object_Canvas(const Object_Canvas &obj) :
						Object(obj), _pDevice(obj._pDevice->IncRef()) {}
	virtual ~Object_Canvas();
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	Device &Device() { return *_pDevice; }
};

//-----------------------------------------------------------------------------
// Color
//-----------------------------------------------------------------------------
Color::Color(Signal sig, const Value &value)
{
	if (value.IsList()) {
		const ValueList &valList = value.GetList();
		size_t len = valList.size();
		if (len < 3) {
			sig.SetError(ERR_ValueError, "insufficient list value for color");
			return;
		}
		_red = valList[0].GetUChar();
		_green = valList[1].GetUChar();
		_blue = valList[2].GetUChar();
	} else if (value.IsString()) {
		const char *str = value.GetString();
		if (*str == '#') str++;
		char *strp;
		unsigned long num = ::strtoul(str, &strp, 16);
		if (strp - str != 6) {
			sig.SetError(ERR_ValueError, "invalid format of color string");
			return;
		}
		*this = Color(
			static_cast<unsigned char>(num >> 16),
			static_cast<unsigned char>(num >> 8),
			static_cast<unsigned char>(num >> 0));
	} else if (value.IsSymbol()) {
		const Symbol *pSymbol = value.GetSymbol();
		if (pSymbol->IsIdentical(AScript_PrivSymbol(black))) {
			*this = Color(0, 0, 0);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(blue))) {
			*this = Color(0, 0, 255);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(green))) {
			*this = Color(0, 255, 0);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(light_blue))) {
			*this = Color(0, 255, 255);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(red))) {
			*this = Color(255, 0, 0);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(cyan))) {
			*this = Color(255, 0, 255);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(yellow))) {
			*this = Color(255, 255, 0);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(white))) {
			*this = Color(255, 255, 255);
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(grey))) {
			*this = Color(128, 128, 128);
		} else {
			sig.SetError(ERR_ValueError, "invalid color symbol %s", pSymbol->GetName());
			return;
		}
	} else {
		*this = Color(0, 0, 0);
	}
}

//-----------------------------------------------------------------------------
// Device
//-----------------------------------------------------------------------------
Device::~Device()
{
}

Value Device::Initialize(Environment &env, Signal sig, const Function *pFuncBlock)
{
	SetPen(sig, Value(AScript_PrivSymbol(black)), 1., AScript_PrivSymbol(solid));
	SetFont(sig, 8.0, AScript_PrivSymbol(default), AScript_PrivSymbol(normal),
									AScript_PrivSymbol(normal), "");
	Fill(sig, Value(AScript_PrivSymbol(white)));
	Object_Canvas *pObj = new Object_Canvas(env, this);
	Value result(pObj);
	if (pFuncBlock != NULL) {
		Environment envBlock(&env, ENVTYPE_Block);
		ValueList valList(result);
		Args args(valList);
		pFuncBlock->Eval(envBlock, sig, args);
		
		// temporary handling before GC is correctly implemented
		pObj->Device().Close();
		
		result = Value::Null;
	}
	return result;
}

//-----------------------------------------------------------------------------
// Device_EnhMetaFile
//-----------------------------------------------------------------------------
class Device_EnhMetaFile : public Device {
private:
	HDC _hdc;
	RECT _rc;
	LOGFONT _lf;
public:
	Device_EnhMetaFile(Signal sig, const char *fileName,
		Number width, Number height, const char *appName, const char *imageName);
	inline POINT ToPoint(Number x, Number y) {
		POINT pt;
		pt.x = static_cast<long>(x * 100);
		pt.y = static_cast<long>(y * 100) + _rc.bottom;
		return pt;
	}
	virtual ~Device_EnhMetaFile();
	virtual const char *GetName() const;
	virtual void Close();
	virtual void Fill(Signal sig, const Value &color);
	virtual void SetPen(Signal sig, const Value &color, Number width, const Symbol *pSymbol);
	virtual void SetBrush(Signal sig, const Value &color, const Symbol *pSymbol);
	virtual void SetFont(Signal sig, Number height,
			const Symbol *family, const Symbol *style, const Symbol *weight,
			const char *faceName);
	virtual void SetTextColor(Signal sig, const Value &color);
	virtual void Text(Signal sig, Number x, Number y, const char *text, Number wdBound, Number htBound, Anchor anchor);
	virtual void TextRot(Signal sig, Number x, Number y, const char *text, Number angle);
	virtual void Line(Signal sig, Number x1, Number y1, Number x2, Number y2);
	virtual void Rectangle(Signal sig, Number x, Number y, Number width, Number height, Anchor anchor);
	virtual void Ellipse(Signal sig, Number x, Number y, Number width, Number height, Anchor anchor);
	virtual void Pie(Signal sig, Number x, Number y, Number width, Number height,
								Number degStart, Number degEnd, Anchor anchor);
	virtual void Polygon(Signal sig, const ValueList &xs, const ValueList &ys, bool closeFlag);
	virtual void Polygon(Signal sig, const ValueList &pts, bool closeFlag);
private:
	static void PrepareSimpleLogfont(LOGFONT *plf, LONG lHeight, LPCTSTR pszFaceName);
	static void MakeRect(RECT *prc, const POINT &pt, int width, int height, Anchor anchor);
};

Device_EnhMetaFile::Device_EnhMetaFile(Signal sig, const char *fileName,
		Number width, Number height, const char *appName, const char *imageName) :
														Device(width, height)
{
	_rc.left = 0, _rc.top = 0;
	_rc.right = static_cast<long>(width * 100);		// unit: 0.01mm
	_rc.bottom = static_cast<long>(height * 100);	// unit: 0.01mm
	PrepareSimpleLogfont(&_lf, 10, "");
	char *description = NULL;
	if (appName != NULL) {
		size_t lenAppName = ::strlen(appName);
		if (imageName == NULL) {
			description = new char[lenAppName + 2];
			::strcpy(description, appName);
			description[lenAppName + 1] = '\0';
		} else {
			size_t lenImageName = ::strlen(imageName);
			description = new char[lenAppName + lenImageName + 3];
			::strcpy(description, appName);
			::strcpy(description + lenAppName + 1, imageName);
			description[lenAppName + lenImageName + 2] = '\0';
		}
	}
	_hdc = ::CreateEnhMetaFile(NULL, fileName, &_rc, description);
	_rc.bottom = -_rc.bottom;
	delete[] description;
	// SetViewPortOrg doesn't work correctly with some devices
	if (_hdc == NULL) {
		sig.SetError(ERR_IOError, "failed to create an enhanced meta file");
		return;
	}
	::SetMapMode(_hdc, MM_HIMETRIC);				// 1 unit = 0.01 mm
}

Device_EnhMetaFile::~Device_EnhMetaFile()
{
	Close();
}

const char *Device_EnhMetaFile::GetName() const
{
	return "EnhancedMetaFile";
}

void Device_EnhMetaFile::Close()
{
	if (_hdc == NULL) return;
	//::printf("CloseEnhMetaFile\n");
	HENHMETAFILE hEmf = ::CloseEnhMetaFile(_hdc);
	::DeleteEnhMetaFile(hEmf);
	_hdc = NULL;
}

void Device_EnhMetaFile::Fill(Signal sig, const Value &color)
{
	Color colorWk(sig, color);
	if (sig.IsSignalled()) return;
	::FillRect(_hdc, &_rc, ::CreateSolidBrush(colorWk.ToWin32()));
}

void Device_EnhMetaFile::SetPen(Signal sig,
					const Value &color, Number width, const Symbol *pSymbol)
{
	if (_hdc == NULL) return;
	if (color.IsInvalid()) {
		::SelectObject(_hdc, ::CreatePen(PS_NULL, 0, RGB(0, 0, 0)));
		return;
	}
	int fnPenStyle = PS_SOLID;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(solid))) {
		fnPenStyle = PS_SOLID;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(dash))) {
		fnPenStyle = PS_DASH;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(dot))) {
		fnPenStyle = PS_DOT;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(dot_dash))) {
		fnPenStyle = PS_DASHDOT;
	} else {
		sig.SetError(ERR_ValueError, "invalid pen style %s", pSymbol->GetName());
		return;
	}
	int nWidth = static_cast<int>(width * 100 * 25.4 * 0.25 / 72);
	Color colorWk(sig, color);
	if (sig.IsSignalled()) return;
	::SelectObject(_hdc, ::CreatePen(fnPenStyle, nWidth, colorWk.ToWin32()));
}

void Device_EnhMetaFile::SetBrush(Signal sig, const Value &color, const Symbol *pSymbol)
{
	if (_hdc == NULL) return;
	if (color.IsInvalid()) {
		::SelectObject(_hdc, reinterpret_cast<HBRUSH>(::GetStockObject(NULL_BRUSH)));
		return;
	}
	Color colorWk(sig, color);
	if (sig.IsSignalled()) return;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(solid))) {
		::SelectObject(_hdc, ::CreateSolidBrush(colorWk.ToWin32()));
	} else {
		int fnStyle = 0;
		if (pSymbol->IsIdentical(AScript_PrivSymbol(bdiagonal))) {
			fnStyle = HS_BDIAGONAL;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(cross))) {
			fnStyle = HS_CROSS;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(diagcross))) {
			fnStyle = HS_DIAGCROSS;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(fdiagonal))) {
			fnStyle = HS_FDIAGONAL;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(horizontal))) {
			fnStyle = HS_HORIZONTAL;
		} else if (pSymbol->IsIdentical(AScript_PrivSymbol(vertical))) {
			fnStyle = HS_VERTICAL;
		} else {
			sig.SetError(ERR_ValueError, "invalid brush style %s", pSymbol->GetName());
			return;
		}
		::SelectObject(_hdc, ::CreateHatchBrush(fnStyle, colorWk.ToWin32()));
	}
}

void Device_EnhMetaFile::SetFont(Signal sig, Number height,
			const Symbol *family, const Symbol *style, const Symbol *weight,
			const char *faceName)
{
	// 1pt = 25.4/72mm = 0.353mm
	PrepareSimpleLogfont(&_lf, static_cast<int>(height * 100), "");
	if (family->IsIdentical(AScript_PrivSymbol(default))) {
		_lf.lfPitchAndFamily = FF_DONTCARE;
	} else if (family->IsIdentical(AScript_PrivSymbol(decorative))) {
		_lf.lfPitchAndFamily = FF_DECORATIVE;
	} else if (family->IsIdentical(AScript_PrivSymbol(roman))) {
		_lf.lfPitchAndFamily = FF_ROMAN;
	} else if (family->IsIdentical(AScript_PrivSymbol(script))) {
		_lf.lfPitchAndFamily = FF_SCRIPT;
	} else if (family->IsIdentical(AScript_PrivSymbol(swiss))) {
		_lf.lfPitchAndFamily = FF_SWISS;
	} else if (family->IsIdentical(AScript_PrivSymbol(modern))) {
		_lf.lfPitchAndFamily = FF_MODERN;
	} else if (family->IsIdentical(AScript_PrivSymbol(teletype))) {
		_lf.lfPitchAndFamily = FF_SWISS | FIXED_PITCH;
	} else {
		sig.SetError(ERR_ValueError, "invalid family symbol %s", family->GetName());
		return;
	}
	if (style->IsIdentical(AScript_PrivSymbol(italic))) {
		_lf.lfItalic = TRUE;
	} else if (style->IsIdentical(AScript_PrivSymbol(slant))) {
		_lf.lfStrikeOut = TRUE;
	}
	if (weight->IsIdentical(AScript_PrivSymbol(normal))) {
		_lf.lfWeight = FW_NORMAL;
	} else if (weight->IsIdentical(AScript_PrivSymbol(light))) {
		_lf.lfWeight = FW_LIGHT;
	} else if (weight->IsIdentical(AScript_PrivSymbol(bold))) {
		_lf.lfWeight = FW_BOLD;
	} else {
		sig.SetError(ERR_ValueError, "invalid weight symbol %s", weight->GetName());
		return;
	}
	HFONT hFont = ::CreateFontIndirect(&_lf);
	::SelectObject(_hdc, hFont);
}

void Device_EnhMetaFile::SetTextColor(Signal sig, const Value &color)
{
	Color colorWk(sig, color);
	if (sig.IsSignalled()) return;
	::SetTextColor(_hdc, colorWk.ToWin32());
}

void Device_EnhMetaFile::Text(Signal sig, Number x, Number y,
				const char *text, Number wdBound, Number htBound, Anchor anchor)
{
	if (_hdc == NULL) return;
	RECT rc;
	rc.left = rc.right = rc.top = rc.bottom = 0;
	::DrawText(_hdc, text, -1, &rc, DT_CALCRECT);
	wdBound *= 100, htBound *= 100;
	if (wdBound <= 0) wdBound = rc.right;
	if (htBound <= 0) htBound = -rc.bottom;
	MakeRect(&rc, ToPoint(x, y),
				static_cast<int>(wdBound), static_cast<int>(htBound), anchor);
	//MakeRect(&rc, ToPoint(_xCur, _yCur), rc.right - rc.left, rc.top - rc.bottom, anchor);
	::SetTextAlign(_hdc, TA_TOP);
	::DrawText(_hdc, text, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	//::DrawText(_hdc, text, -1, &rc, DT_SINGLELINE);
}

void Device_EnhMetaFile::TextRot(Signal sig, Number x, Number y, const char *text, Number angle)
{
	if (_hdc == NULL) return;
	POINT pt = ToPoint(x, y);
	LOGFONT lf = _lf;
	lf.lfEscapement = -static_cast<int>(angle * 10);
	lf.lfOrientation = lf.lfEscapement;
	HFONT hFont = ::CreateFontIndirect(&lf);
	HFONT hFontOld = reinterpret_cast<HFONT>(::SelectObject(_hdc, hFont));
	::SetTextAlign(_hdc, TA_BOTTOM);
	::TextOut(_hdc, pt.x, pt.y, text, static_cast<int>(::strlen(text)));
	::SelectObject(_hdc, hFontOld);
}

void Device_EnhMetaFile::Line(Signal sig, Number x1, Number y1, Number x2, Number y2)
{
	if (_hdc == NULL) return;
	POINT pt1 = ToPoint(x1, y1);
	POINT pt2 = ToPoint(x2, y2);
	::MoveToEx(_hdc, pt1.x, pt1.y, NULL);
	::LineTo(_hdc, pt2.x, pt2.y);
}

void Device_EnhMetaFile::Rectangle(Signal sig, Number x, Number y,
									Number width, Number height, Anchor anchor)
{
	if (_hdc == NULL) return;
	RECT rc;
	MakeRect(&rc, ToPoint(x, y),
		static_cast<int>(width * 100), static_cast<int>(height * 100), anchor);
	::Rectangle(_hdc, rc.left, rc.top, rc.right, rc.bottom);
}

void Device_EnhMetaFile::Ellipse(Signal sig, Number x, Number y,
									Number width, Number height, Anchor anchor)
{
	if (_hdc == NULL) return;
	RECT rc;
	MakeRect(&rc, ToPoint(x, y),
		static_cast<int>(width * 100), static_cast<int>(height * 100), anchor);
	::Ellipse(_hdc, rc.left, rc.top, rc.right, rc.bottom);
}

void Device_EnhMetaFile::Pie(Signal sig, Number x, Number y, Number width, Number height,
								Number degStart, Number degEnd, Anchor anchor)
{
	if (_hdc == NULL) return;
	RECT rc;
	MakeRect(&rc, ToPoint(x, y),
		static_cast<int>(width * 100), static_cast<int>(height * 100), anchor);
	double radStart = degStart * 3.1415926 / 180;
	double radEnd = degEnd * 3.1415926 / 180;
	int xCenter = (rc.left + rc.right) / 2;
	int yCenter = (rc.top + rc.bottom) / 2;
	double lenBeam = 100000;
	int xStart = static_cast<int>(lenBeam * ::cos(radStart) + xCenter);
	int yStart = static_cast<int>(lenBeam * ::sin(radStart) + yCenter);
	int xEnd = static_cast<int>(xCenter + ::cos(radEnd) * lenBeam);
	int yEnd = static_cast<int>(yCenter + ::sin(radEnd) * lenBeam);
	::Pie(_hdc, rc.left, rc.top, rc.right, rc.bottom, xStart, yStart, xEnd, yEnd);
}

void Device_EnhMetaFile::Polygon(Signal sig,
					const ValueList &xs, const ValueList &ys, bool closeFlag)
{
	if (_hdc == NULL) return;
	int cnt = static_cast<int>((xs.size() < ys.size())? xs.size() : ys.size());
	if (cnt == 0) return;
	POINT *ptList = new POINT[cnt];
	ValueList::const_iterator xp = xs.begin();
	ValueList::const_iterator yp = ys.begin();
	int i = 0;
	for (POINT *ptpDst = ptList; i < cnt; xp++, yp++, ptpDst++, i++) {
		*ptpDst = ToPoint(xp->GetNumber(), yp->GetNumber());
	}
	if (closeFlag) {
		::Polygon(_hdc, ptList, cnt);
	} else {
		::Polyline(_hdc, ptList, cnt);
	}
	delete[] ptList;
}

void Device_EnhMetaFile::Polygon(Signal sig, const ValueList &pts, bool closeFlag)
{
	if (_hdc == NULL) return;
	int cnt = static_cast<int>(pts.size());
	if (cnt == 0) return;
	POINT *ptList = new POINT[cnt];
	ValueList::const_iterator ptp = pts.begin();
	POINT *ptpDst = ptList;
	foreach_const (ValueList, pValue, pts) {
		if (!pValue->IsList()) {
			sig.SetError(ERR_ValueError, "element of point list must be a list");
			return;
		}
		const ValueList &valList = pValue->GetList();
		if (valList.size() != 2) {
			sig.SetError(ERR_ValueError, "wrong format of point list");
			return;
		}
		*ptpDst = ToPoint(valList[0].GetNumber(), valList[1].GetNumber());
		ptpDst++;
	}
	if (closeFlag) {
		::Polygon(_hdc, ptList, cnt);
	} else {
		::Polyline(_hdc, ptList, cnt);
	}
	delete[] ptList;
}

void Device_EnhMetaFile::PrepareSimpleLogfont(LOGFONT *plf,
											LONG lHeight, LPCTSTR pszFaceName)
{
	plf->lfHeight = lHeight; 
	plf->lfWidth = 0;
	plf->lfEscapement = 0;		// 1/10 degree
	plf->lfOrientation = 0;		// 1/10 degree
	plf->lfWeight = FW_NORMAL;
	plf->lfItalic = FALSE;
	plf->lfUnderline = FALSE;
	plf->lfStrikeOut = FALSE;
	plf->lfCharSet = DEFAULT_CHARSET;
	plf->lfOutPrecision = OUT_DEFAULT_PRECIS;
	plf->lfClipPrecision = CLIP_DEFAULT_PRECIS;
	plf->lfQuality = DEFAULT_QUALITY;
	plf->lfPitchAndFamily = DEFAULT_PITCH;
	::strcpy(plf->lfFaceName, pszFaceName);
}

void Device_EnhMetaFile::MakeRect(RECT *prc,
						const POINT &pt, int width, int height, Anchor anchor)
{
	prc->left =
		(anchor == ANCHOR_NW || anchor == ANCHOR_W || anchor == ANCHOR_SW)? pt.x :
		(anchor == ANCHOR_NE || anchor == ANCHOR_E || anchor == ANCHOR_SE)? pt.x - width + 1 :
		pt.x - width / 2;
	prc->right = prc->left + width;
	prc->bottom =
		(anchor == ANCHOR_NW || anchor == ANCHOR_N || anchor == ANCHOR_NE)? pt.y - height + 1 :
		(anchor == ANCHOR_SW || anchor == ANCHOR_S || anchor == ANCHOR_SE)? pt.y :
		pt.y - height / 2;
	prc->top = prc->bottom + height;
}

//-----------------------------------------------------------------------------
// Object_Canvas
//-----------------------------------------------------------------------------
Object_Canvas::~Object_Canvas()
{
	Device::Delete(_pDevice);
}

Object *Object_Canvas::Clone() const
{
	return new Object_Canvas(*this);
}

String Object_Canvas::ToString(Signal sig, bool exprFlag)
{
	String rtn;
	rtn += "<canvas:";
	rtn += _pDevice->GetName();
	rtn += ">";
	return rtn;
}

//-----------------------------------------------------------------------------
// AScipr interfaces for Object_Canvas
//-----------------------------------------------------------------------------
// canvas.canvas#width()
AScript_DeclareMethod(Canvas, width)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

AScript_ImplementMethod(Canvas, width)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	return Value(pObj->Device().GetWidth());
}

// canvas.canvas#height()
AScript_DeclareMethod(Canvas, height)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

AScript_ImplementMethod(Canvas, height)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	return Value(pObj->Device().GetHeight());
}

// canvas.canvas#setfont(height:number, family:symbol => `default,
//			style:symbol => `normal, weight:symbol => `normal, facename?:string):reduce
AScript_DeclareMethod(Canvas, setfont)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "height", VTYPE_Number);
	DeclareArg(env, "family", VTYPE_Symbol, OCCUR_Once, false, false,
								new Expr_Symbol(AScript_PrivSymbol(default)));
	DeclareArg(env, "style", VTYPE_Symbol, OCCUR_Once, false, false,
								new Expr_Symbol(AScript_PrivSymbol(normal)));
	DeclareArg(env, "weight", VTYPE_Symbol, OCCUR_Once, false, false,
								new Expr_Symbol(AScript_PrivSymbol(normal)));
	DeclareArg(env, "facename", VTYPE_String, OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Canvas, setfont)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().SetFont(sig, args.GetNumber(0),
			args.GetSymbol(1), args.GetSymbol(2), args.GetSymbol(3),
			args.IsString(4)? args.GetString(4) : "");
	return args.GetSelf();
}

// canvas.canvas#settextcolor(color):reduce
AScript_DeclareMethod(Canvas, settextcolor)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "color", VTYPE_Any);
}

AScript_ImplementMethod(Canvas, settextcolor)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().SetTextColor(sig, args.GetValue(0));
	return args.GetSelf();
}

// canvas.canvas#setpen(color, width:number => 1, style:symbol => `solid):reduce
AScript_DeclareMethod(Canvas, setpen)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "color", VTYPE_Any);
	DeclareArg(env, "width", VTYPE_Number, OCCUR_Once, false, false,
								new Expr_Value(1));
	DeclareArg(env, "style", VTYPE_Symbol, OCCUR_Once, false, false,
								new Expr_Symbol(AScript_PrivSymbol(solid)));
}

AScript_ImplementMethod(Canvas, setpen)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().SetPen(sig, args.GetValue(0),
							args.GetNumber(1), args.GetSymbol(2));
	return args.GetSelf();
}

// canvas.canvas#setbrush(color, style:symbol => `solid):reduce
AScript_DeclareMethod(Canvas, setbrush)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "color", VTYPE_Any);
	DeclareArg(env, "style", VTYPE_Symbol, OCCUR_Once, false, false,
								new Expr_Symbol(AScript_PrivSymbol(solid)));
}

AScript_ImplementMethod(Canvas, setbrush)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().SetBrush(sig, args.GetValue(0), args.GetSymbol(1));
	return args.GetSelf();
}

// canvas.canvas#text(x:number, y:number, text:string, width_bound?:number, height_bound?:number):reduce:[n,ne,e,se,s,sw,w,nw,c]
AScript_DeclareMethod(Canvas, text)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "x", VTYPE_Number);
	DeclareArg(env, "y", VTYPE_Number);
	DeclareArg(env, "text", VTYPE_String);
	DeclareArg(env, "width_bound", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareArg(env, "height_bound", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareAnchorAttrs(this);
}

AScript_ImplementMethod(Canvas, text)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().Text(sig, args.GetNumber(0), args.GetNumber(1),
		args.GetString(2),
		args.IsNumber(3)? args.GetNumber(3) : -1,
		args.IsNumber(4)? args.GetNumber(4) : -1, GetAnchor(args.GetAttrs()));
	return args.GetSelf();
}

// canvas.canvas#textrot(x:number, y:number, text:string, angle:number):reduce
AScript_DeclareMethod(Canvas, textrot)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "x", VTYPE_Number);
	DeclareArg(env, "y", VTYPE_Number);
	DeclareArg(env, "text", VTYPE_String);
	DeclareArg(env, "angle", VTYPE_Number);
}

AScript_ImplementMethod(Canvas, textrot)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().TextRot(sig, args.GetNumber(0), args.GetNumber(1),
							args.GetString(2), args.GetNumber(3));
	return args.GetSelf();
}

// canvas.canvas#line(x1:number, y1:number, x2:number, y2:number):map:reduce
AScript_DeclareMethod(Canvas, line)
{
	SetMode(RSLTMODE_Reduce, FLAG_Map);
	DeclareArg(env, "x1", VTYPE_Number);
	DeclareArg(env, "y1", VTYPE_Number);
	DeclareArg(env, "x2", VTYPE_Number);
	DeclareArg(env, "y2", VTYPE_Number);
}

AScript_ImplementMethod(Canvas, line)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().Line(sig, args.GetNumber(0), args.GetNumber(1),
					args.GetNumber(2), args.GetNumber(3));
	return args.GetSelf();
}

// canvas.canvas#rectangle(x:number, y:number, width:number, height:number):map:reduce
AScript_DeclareMethod(Canvas, rectangle)
{
	SetMode(RSLTMODE_Reduce, FLAG_Map);
	DeclareArg(env, "x", VTYPE_Number);
	DeclareArg(env, "y", VTYPE_Number);
	DeclareArg(env, "width", VTYPE_Number);
	DeclareArg(env, "height", VTYPE_Number);
	DeclareAnchorAttrs(this);
}

AScript_ImplementMethod(Canvas, rectangle)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().Rectangle(sig, args.GetNumber(0), args.GetNumber(1),
		args.GetNumber(2), args.GetNumber(3), GetAnchor(args.GetAttrs()));
	return args.GetSelf();
}

// canvas.canvas#ellipse(x:number, y:number, width:number, height:number):map:reduce
AScript_DeclareMethod(Canvas, ellipse)
{
	SetMode(RSLTMODE_Reduce, FLAG_Map);
	DeclareArg(env, "x", VTYPE_Number);
	DeclareArg(env, "y", VTYPE_Number);
	DeclareArg(env, "width", VTYPE_Number);
	DeclareArg(env, "height", VTYPE_Number);
	DeclareAnchorAttrs(this);
}

AScript_ImplementMethod(Canvas, ellipse)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().Ellipse(sig, args.GetNumber(0), args.GetNumber(1),
		args.GetNumber(2), args.GetNumber(3), GetAnchor(args.GetAttrs()));
	return args.GetSelf();
}

// canvas.canvas#pie(x:number, y:number, width:number, height:number, start_degree:number, end_degree:number):map:reduce
AScript_DeclareMethod(Canvas, pie)
{
	SetMode(RSLTMODE_Reduce, FLAG_Map);
	DeclareArg(env, "x", VTYPE_Number);
	DeclareArg(env, "y", VTYPE_Number);
	DeclareArg(env, "width", VTYPE_Number);
	DeclareArg(env, "height", VTYPE_Number);
	DeclareArg(env, "start_degree", VTYPE_Number);
	DeclareArg(env, "end_degree", VTYPE_Number);
	DeclareAnchorAttrs(this);
}

AScript_ImplementMethod(Canvas, pie)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().Pie(sig, args.GetNumber(0), args.GetNumber(1),
		args.GetNumber(2), args.GetNumber(3),
		args.GetNumber(4), args.GetNumber(5), GetAnchor(args.GetAttrs()));
	return args.GetSelf();
}

// canvas.canvas#polyline(xs[]:number, ys[]:number):reduce
AScript_DeclareMethod(Canvas, polyline)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "xs", VTYPE_Number, OCCUR_Once, true);
	DeclareArg(env, "ys", VTYPE_Number, OCCUR_Once, true);
}

AScript_ImplementMethod(Canvas, polyline)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().Polygon(sig, args.GetList(0), args.GetList(1), false);
	return args.GetSelf();
}

// canvas.canvas#polylinep(pts[]:List):reduce
AScript_DeclareMethod(Canvas, polylinep)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "pts", VTYPE_List, OCCUR_Once, true);
}

AScript_ImplementMethod(Canvas, polylinep)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().Polygon(sig, args.GetList(0), false);
	return args.GetSelf();
}

// canvas.canvas#polygon(xs[]:number, ys[]:number):reduce
AScript_DeclareMethod(Canvas, polygon)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "xs", VTYPE_Number, OCCUR_Once, true);
	DeclareArg(env, "ys", VTYPE_Number, OCCUR_Once, true);
}

AScript_ImplementMethod(Canvas, polygon)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().Polygon(sig, args.GetList(0), args.GetList(1), true);
	return args.GetSelf();
}

// canvas.canvas#polygonp(pts[]:List):reduce
AScript_DeclareMethod(Canvas, polygonp)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "pts", VTYPE_List, OCCUR_Once, true);
}

AScript_ImplementMethod(Canvas, polygonp)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().Polygon(sig, args.GetList(0), true);
	return args.GetSelf();
}

// str = canvas.canvas#close()
AScript_DeclareMethod(Canvas, close)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
}

AScript_ImplementMethod(Canvas, close)
{
	Object_Canvas *pObj = Object_Canvas::GetSelfObj(args);
	pObj->Device().Close();
	return Value::Null;
}

AScript_ImplementPrivClass(Canvas)
{
	AScript_AssignMethod(Canvas, width);
	AScript_AssignMethod(Canvas, height);
	AScript_AssignMethod(Canvas, setfont);
	AScript_AssignMethod(Canvas, settextcolor);
	AScript_AssignMethod(Canvas, setpen);
	AScript_AssignMethod(Canvas, setbrush);
	AScript_AssignMethod(Canvas, text);
	AScript_AssignMethod(Canvas, textrot);
	AScript_AssignMethod(Canvas, line);
	AScript_AssignMethod(Canvas, rectangle);
	AScript_AssignMethod(Canvas, ellipse);
	AScript_AssignMethod(Canvas, pie);
	AScript_AssignMethod(Canvas, polyline);
	AScript_AssignMethod(Canvas, polylinep);
	AScript_AssignMethod(Canvas, polygon);
	AScript_AssignMethod(Canvas, polygonp);
	AScript_AssignMethod(Canvas, close);
}

//-----------------------------------------------------------------------------
// AScipr module functions
//-----------------------------------------------------------------------------
// create_emf(filename:string, width:number, height:number, appname?:string, imagename?:string) {block?}
// unit of width and height is mm
AScript_DeclareFunction(create_emf)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "filename", VTYPE_String);
	DeclareArg(env, "width", VTYPE_Number);
	DeclareArg(env, "height", VTYPE_Number);
	DeclareArg(env, "appname", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "imagename", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(create_emf)
{
	Device *pDevice = new Device_EnhMetaFile(sig,
		args.GetString(0), args.GetNumber(1), args.GetNumber(2),
		args.IsString(3)? args.GetString(3) : NULL,
		args.IsString(4)? args.GetString(4) : NULL);
	if (sig.IsSignalled()) {
		delete pDevice;
		return Value::Null;
	}
	const Function *pFuncBlock =
						args.GetBlockFunc(env, sig, GetSymbolForBlock());
	return pDevice->Initialize(env, sig, pFuncBlock);
}

AScript_ModuleEntry()
{
	// symbol realization
	AScript_RealizePrivSymbol(red);
	AScript_RealizePrivSymbol(blue);
	AScript_RealizePrivSymbol(green);
	AScript_RealizePrivSymbol(cyan);
	AScript_RealizePrivSymbol(light_blue);
	AScript_RealizePrivSymbol(yellow);
	AScript_RealizePrivSymbol(black);
	AScript_RealizePrivSymbol(white);
	AScript_RealizePrivSymbol(grey);
	AScript_RealizePrivSymbol(solid);
	AScript_RealizePrivSymbol(dot);
	AScript_RealizePrivSymbol(dash);
	AScript_RealizePrivSymbol(dot_dash);
	AScript_RealizePrivSymbol(bdiagonal);
	AScript_RealizePrivSymbol(cross);
	AScript_RealizePrivSymbol(diagcross);
	AScript_RealizePrivSymbol(fdiagonal);
	AScript_RealizePrivSymbol(horizontal);
	AScript_RealizePrivSymbol(vertical);
	AScript_RealizePrivSymbol(normal);
	AScript_RealizePrivSymbol(default);
	AScript_RealizePrivSymbol(decorative);
	AScript_RealizePrivSymbol(roman);
	AScript_RealizePrivSymbol(script);
	AScript_RealizePrivSymbol(swiss);
	AScript_RealizePrivSymbol(modern);
	AScript_RealizePrivSymbol(teletype);
	AScript_RealizePrivSymbol(slant);
	AScript_RealizePrivSymbol(italic);
	AScript_RealizePrivSymbol(light);
	AScript_RealizePrivSymbol(bold);
	AScript_RealizePrivSymbol(n);
	AScript_RealizePrivSymbol(ne);
	AScript_RealizePrivSymbol(e);
	AScript_RealizePrivSymbol(se);
	AScript_RealizePrivSymbol(s);
	AScript_RealizePrivSymbol(sw);
	AScript_RealizePrivSymbol(w);
	AScript_RealizePrivSymbol(nw);
	AScript_RealizePrivSymbol(c);
	// class realization
	AScript_RealizePrivClass(Canvas, "canvas", env.LookupClass(VTYPE_Object));
	// function assignment
	AScript_AssignFunction(create_emf);
}

AScript_ModuleTerminate()
{
}

AScript_EndModule(canvas, canvas)

AScript_RegisterModule(canvas)
