/* 
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program 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 General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "stdafx.h"
#include "Glyph.h"
#include "Split.h"

#define deg2rad(d) (float)(M_PI/180*(d))

namespace ssf
{
	Glyph::Glyph()
	{
		c = 0;
		font = NULL;
		ascent = descent = width = spacing = fill = 0;
		tl.x = tl.y = tls.x = tls.y = 0;
	}

	float Glyph::GetBackgroundSize() const
	{
		return style.background.size * (scale.cx + scale.cy) / 2;
	}

	float Glyph::GetShadowDepth() const
	{
		return style.shadow.depth * (scale.cx + scale.cy) / 2;
	}

	CRect Glyph::GetClipRect() const
	{
		CRect r = bbox + tl;

		int size = (int)(GetBackgroundSize() + 0.5);
		int depth = (int)(GetShadowDepth() + 0.5);

		r.InflateRect(size, size);
		r.InflateRect(depth, depth);

		r.left >>= 6;
		r.top >>= 6;
		r.right = (r.right + 32) >> 6;
		r.bottom = (r.bottom + 32) >> 6;

		return r;
	}

	void Glyph::CreateBkg()
	{
		path_bkg.RemoveAll();

		if(style.background.type == L"enlarge" && style.background.size > 0)
		{
			path_bkg.Enlarge(path, GetBackgroundSize());
		}
		else if(style.background.type == L"box" && style.background.size >= 0)
		{
			if(c != ssf::Text::LSEP)
			{
				int s = (int)(GetBackgroundSize() + 0.5);
				int x0 = (!vertical ? -spacing/2 : ascent - row_ascent);
				int y0 = (!vertical ? ascent - row_ascent : -spacing/2);
				int x1 = x0 + (!vertical ? width + spacing : row_ascent + row_descent);
				int y1 = y0 + (!vertical ? row_ascent + row_descent : width + spacing);
				path_bkg.types.SetCount(4);
				path_bkg.types[0] = PT_MOVETO;
				path_bkg.types[1] = PT_LINETO;
				path_bkg.types[2] = PT_LINETO;
				path_bkg.types[3] = PT_LINETO;
				path_bkg.points.SetCount(4);
				path_bkg.points[0] = CPoint(x0-s, y0-s);
				path_bkg.points[1] = CPoint(x1+s, y0-s);
				path_bkg.points[2] = CPoint(x1+s, y1+s);
				path_bkg.points[3] = CPoint(x0-s, y1+s);
			}
		}
	}

	void Glyph::CreateSplineCoeffs(const CRect& spdrc)
	{
		spline.RemoveAll();

		if(style.placement.path.IsEmpty())
			return;

		size_t i = 0, j = style.placement.path.GetCount();

		CAtlArray<Point> pts;
		pts.SetCount(j + 2);

		Point p;

		while(i < j)
		{
			p.x = style.placement.path[i].x * scale.cx + spdrc.left * 64;
			p.y = style.placement.path[i].y * scale.cy + spdrc.top * 64;
			pts[++i] = p;
		}

		if(pts.GetCount() >= 4)
		{
			if(pts[1].x == pts[j].x && pts[1].y == pts[j].y)
			{
				pts.SetAt(0, pts[j-1]);
				pts.SetAt(j+1, pts[2]);
			}
			else
			{
				p.x = pts[1].x*2 - pts[2].x;
				p.y = pts[1].y*2 - pts[2].y;
				pts.SetAt(0, p);

				p.x = pts[j].x*2 - pts[j-1].x;
				p.y = pts[j].y*2 - pts[j-1].y;
				pts.SetAt(j+1, p);
			}

			spline.SetCount(pts.GetCount()-3);

			for(size_t i = 0, j = pts.GetCount()-4; i <= j; i++)
			{
				static const float _1div6 = 1.0f / 6;

				SplineCoeffs sc;

				sc.cx[3] = _1div6*(-  pts[i+0].x + 3*pts[i+1].x - 3*pts[i+2].x + pts[i+3].x);
				sc.cx[2] = _1div6*( 3*pts[i+0].x - 6*pts[i+1].x + 3*pts[i+2].x);
				sc.cx[1] = _1div6*(-3*pts[i+0].x	            + 3*pts[i+2].x);
				sc.cx[0] = _1div6*(   pts[i+0].x + 4*pts[i+1].x + 1*pts[i+2].x);

				sc.cy[3] = _1div6*(-  pts[i+0].y + 3*pts[i+1].y - 3*pts[i+2].y + pts[i+3].y);
				sc.cy[2] = _1div6*( 3*pts[i+0].y - 6*pts[i+1].y + 3*pts[i+2].y);
				sc.cy[1] = _1div6*(-3*pts[i+0].y                + 3*pts[i+2].y);
				sc.cy[0] = _1div6*(   pts[i+0].y + 4*pts[i+1].y + 1*pts[i+2].y);

				spline.SetAt(i, sc);
			}
		}
	}

	void Glyph::Transform(GlyphPath& path, CPoint org, const CRect& subrect)
	{
		// TODO: add sse code path

		float sx = style.font.scale.cx;
		float sy = style.font.scale.cy;

		bool brotate = style.placement.angle.x || style.placement.angle.y || style.placement.angle.z;
		bool bspline = !spline.IsEmpty();
		bool bscale = brotate || bspline || sx != 1 || sy != 1;

		float caz = cos(deg2rad(style.placement.angle.z));
		float saz = sin(deg2rad(style.placement.angle.z));
		float cax = cos(deg2rad(style.placement.angle.x));
		float sax = sin(deg2rad(style.placement.angle.x));
		float cay = cos(deg2rad(style.placement.angle.y));
		float say = sin(deg2rad(style.placement.angle.y));

		for(size_t i = 0, j = path.types.GetCount(); i < j; i++)
		{
			CPoint p = path.points[i];

			if(bscale)
			{
				float x, y, z, xx, yy, zz;

				x = sx * (p.x - org.x);
				y = sy * (p.y - org.y);
				z = 0;

				if(bspline)
				{
					float pos = vertical ? y + org.y + tl.y - subrect.top : x + org.x + tl.x - subrect.left;
					float size = vertical ? subrect.Size().cy : subrect.Size().cx;
					float dist = vertical ? x : y;

					const SplineCoeffs* sc;
					float t;

					if(pos >= size)
					{
						sc = &spline[spline.GetCount() - 1];
						t = 1;
					}
					else
					{
						float u = size / spline.GetCount();
						sc = &spline[max((int)(pos / u), 0)];
						t = fmod(pos, u) / u;
					}

					float nx = sc->cx[1] + 2*t*sc->cx[2] + 3*t*t*sc->cx[3];
					float ny = sc->cy[1] + 2*t*sc->cy[2] + 3*t*t*sc->cy[3];
					float nl = 1.0f / sqrt(nx*nx + ny*ny);

					nx *= nl;
					ny *= nl;

					x = sc->cx[0] + t*(sc->cx[1] + t*(sc->cx[2] + t*sc->cx[3])) - ny * dist - org.x - tl.x;
					y = sc->cy[0] + t*(sc->cy[1] + t*(sc->cy[2] + t*sc->cy[3])) + nx * dist - org.y - tl.y;
				}

				if(brotate)
				{
					xx = x*caz + y*saz;
					yy = -(x*saz - y*caz);
					zz = z;

					x = xx;
					y = yy*cax + zz*sax;
					z = yy*sax - zz*cax;

					xx = x*cay + z*say;
					yy = y;
					zz = x*say - z*cay;

					zz = 1.0f / (max(zz, -19000) + 20000);

					x = (xx * 20000) * zz;
					y = (yy * 20000) * zz;
				}

				p.x = (int)(x + org.x + 0.5);
				p.y = (int)(y + org.y + 0.5);

				path.points[i] = p;
			}

			if(p.x < bbox.left) bbox.left = p.x;
			if(p.x > bbox.right) bbox.right = p.x;
			if(p.y < bbox.top) bbox.top = p.y;
			if(p.y > bbox.bottom) bbox.bottom = p.y;
		}
	}

	void Glyph::Transform(CPoint org, const CRect& subrect)
	{
		if(!style.placement.org.auto_x) org.x = style.placement.org.x * scale.cx;
		if(!style.placement.org.auto_y) org.y = style.placement.org.y * scale.cy;

		org -= tl;

		bbox.SetRect(INT_MAX, INT_MAX, INT_MIN, INT_MIN);

		Transform(path_bkg, org, subrect);
		Transform(path, org, subrect);

		bbox |= CRect(0, 0, 0, 0);
	}

	void Glyph::Rasterize()
	{
		if(!path_bkg.IsEmpty())
		{
			ras_bkg.ScanConvert(path_bkg, bbox);
			ras_bkg.Rasterize(tl.x, tl.y);
		}

		ras.ScanConvert(path, bbox);

		if(style.background.type == L"outline" && style.background.size > 0)
		{
			ras.CreateWidenedRegion((int)(GetBackgroundSize() + 0.5));
		}

		//

		Rasterizer* r = path_bkg.IsEmpty() ? &ras : &ras_bkg;
		int plane = path_bkg.IsEmpty() ? (style.font.color.a < 255 ? 2 : 1) : 0;
		
		ras.Rasterize(tl.x, tl.y);
		r->Blur(style.background.blur, plane);

		if(style.shadow.depth > 0)
		{
			ras_shadow.Reuse(*r);

			float depth = GetShadowDepth();

			tls.x = tl.x + (int)(depth * cos(deg2rad(style.shadow.angle)) + 0.5);
			tls.y = tl.y + (int)(depth * -sin(deg2rad(style.shadow.angle)) + 0.5);

			ras_shadow.Rasterize(tls.x, tls.y);
			ras_shadow.Blur(style.shadow.blur, plane ? 1 : 0);
		}
	}

}