/*
    Copyright (C) 2001-2003 KSVG Team
    This file is part of the KDE project

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    aint with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <cfloat>

#include <tqimage.h>
#include <tqwmatrix.h>

#include "SVGPaint.h"
#include "SVGRectImpl.h"
#include "SVGAngleImpl.h"
#include "SVGPaintImpl.h"
#include "SVGUnitTypes.h"
#include "SVGHelperImpl.h"
#include "SVGDocumentImpl.h"
#include "SVGPointListImpl.h"
#include "SVGMarkerElement.h"
#include "SVGMarkerElementImpl.h"
#include "SVGSVGElementImpl.h"
#include "SVGPathSegListImpl.h"
#include "SVGAnimatedRectImpl.h"
#include "SVGAnimatedStringImpl.h"
#include "SVGImageElementImpl.h"
#include "SVGAnimatedAngleImpl.h"
#include "SVGAnimatedLengthImpl.h"
#include "SVGPolygonElementImpl.h"
#include "SVGClipPathElementImpl.h"
#include "SVGPolylineElementImpl.h"
#include "SVGAnimatedLengthListImpl.h"
#include "SVGAnimatedNumberImpl.h"
#include "SVGAnimatedEnumerationImpl.h"
#include "SVGPreserveAspectRatioImpl.h"
#include "SVGAnimatedPreserveAspectRatioImpl.h"
#include "SVGGradientElementImpl.h"
#include "SVGGradientElement.h"
#include "SVGLinearGradientElementImpl.h"
#include "SVGRadialGradientElementImpl.h"
#include "SVGPatternElementImpl.h"
#include "SVGPatternElement.h"
#include "SVGStopElementImpl.h"
#include "SVGStylableImpl.h"
#include "SVGAnimatedTransformListImpl.h"
#include "SVGTransformListImpl.h"
#include "SVGUnitConverter.h"
#include "SVGTextPathElementImpl.h"
#include "SVGMaskElementImpl.h"

#include "KSVGHelper.h"
#include "LibartCanvasItems.h"
#include "KSVGTextChunk.h"

#include "art_misc.h"
#include "art_render_misc.h"
#include "BezierPathLibart.h"
#include "Point.h"

#include <dom/dom_node.h>


#include <libart_lgpl/art_vpath.h>
#include <libart_lgpl/art_bpath.h>
#include <libart_lgpl/art_affine.h>
#include <libart_lgpl/art_svp_ops.h>
#include <libart_lgpl/art_svp_point.h>
#include <libart_lgpl/art_vpath_svp.h>
#include <libart_lgpl/art_svp_intersect.h>
#include <libart_lgpl/art_svp_vpath.h>
#include <libart_lgpl/art_svp_vpath_stroke.h>
#include <libart_lgpl/art_rect_svp.h>
#include <libart_lgpl/art_vpath_dash.h>
#include <libart_lgpl/art_render.h>
#include <libart_lgpl/art_rect_svp.h>
#include <libart_lgpl/art_render_gradient.h>
#include <libart_lgpl/art_render_svp.h>
#include <libart_lgpl/art_render_mask.h>

using namespace KSVG;

LibartShape::LibartShape(LibartCanvas *c, SVGStylableImpl *style) : m_canvas(c), m_style(style)
{
	m_fillSVP = 0;
	m_strokeSVP = 0;
	m_fillPainter = 0;
	m_strokePainter = 0;
}

LibartShape::~LibartShape()
{
	freeSVPs();
	delete m_fillPainter;
	delete m_strokePainter;
}

TQRect LibartShape::bbox() const
{
	TQRect rect;
	if(m_strokeSVP || m_fillSVP)
	{
		ArtIRect *irect = new ArtIRect();
		ArtVpath *vpath = art_vpath_from_svp(m_strokeSVP ? m_strokeSVP : m_fillSVP);
		art_vpath_bbox_irect(vpath, irect);
		art_free(vpath);

		rect.setX(irect->x0);
		rect.setY(irect->y0);
		rect.setWidth(irect->x1 - irect->x0);
		rect.setHeight(irect->y1 - irect->y0);

		delete irect;
	}

	return rect;
}

bool LibartShape::isVisible(SVGShapeImpl *tqshape)
{
	return m_referenced || (m_style->getVisible() && m_style->getDisplay() && tqshape->directRender());
}

bool LibartShape::fillContains(const TQPoint &p)
{
	if(m_fillSVP)
		return art_svp_point_wind(m_fillSVP, p.x(), p.y()) != 0;
	else
		return false;
}

bool LibartShape::strokeContains(const TQPoint &p)
{
	if(m_strokeSVP)
		return art_svp_point_wind(m_strokeSVP, p.x(), p.y()) != 0;
	else
		return false;
}

void LibartShape::update(CanvasItemUpdate reason, int param1, int param2)
{
	if(reason == UPDATE_STYLE)
	{
		if(!m_fillPainter || !m_strokePainter)
			LibartShape::init();
		if(m_fillPainter)
			m_fillPainter->update(m_style);
		if(m_strokePainter)
			m_strokePainter->update(m_style);
		m_canvas->tqinvalidate(this, false);
	}
	else if(reason == UPDATE_TRANSFORM)
	{
		reset();
		m_canvas->tqinvalidate(this, true);
	}
	else if(reason == UPDATE_ZOOM)
		reset();
	else if(reason == UPDATE_PAN)
	{
		if(m_fillSVP)
			ksvg_art_svp_move(m_fillSVP, param1, param2);
		if(m_strokeSVP)
			ksvg_art_svp_move(m_strokeSVP, param1, param2);
	}
	else if(reason == UPDATE_LINEWIDTH)
	{
		if(m_strokeSVP)
		{
			art_svp_free(m_strokeSVP);
			m_strokeSVP = 0;
		}
		init();
		m_canvas->tqinvalidate(this, true);
	}
}

void LibartShape::draw(SVGShapeImpl *tqshape)
{
	if(!m_referenced && (!m_style->getVisible() || !m_style->getDisplay() || !tqshape->directRender()))
		return;

	bool fillOk = m_fillSVP && m_style->isFilled();
	bool strokeOk = m_strokeSVP && m_style->isStroked() && m_style->getStrokeWidth()->baseVal()->value() > 0; // Spec: A zero value causes no stroke to be painted.

	if(fillOk || strokeOk)
	{
		if(m_fillPainter && m_fillSVP)
			m_fillPainter->draw(m_canvas, m_fillSVP, m_style, tqshape);

		if(m_strokePainter && m_strokeSVP)
			m_strokePainter->draw(m_canvas, m_strokeSVP, m_style, tqshape);
	}
}

void LibartShape::init(const SVGMatrixImpl *)
{
}

void LibartPainter::update(SVGStylableImpl *style)
{
	if(paintType(style) != SVG_PAINTTYPE_URI)
	{
		TQColor qcolor;
		if(paintType(style) == SVG_PAINTTYPE_CURRENTCOLOR)
			qcolor = style->getColor()->rgbColor().color();
		else
			qcolor = color(style);

		short _opacity = static_cast<short>(opacity(style) * 255 + 0.5);

		// Spec: clamping
		_opacity = _opacity < 0 ? 0 : _opacity;
		_opacity = _opacity > 255 ? 255 : _opacity;

		m_color = KSVGHelper::toArtColor(qcolor, _opacity);
	}
}

void LibartPainter::draw(LibartCanvas *canvas, _ArtSVP *svp, SVGStylableImpl *style, SVGShapeImpl *tqshape)
{
	ArtSVP *clippedSvp = canvas->clipSingleSVP(svp, tqshape);

	// Clipping
	ArtDRect bbox;
	art_drect_svp(&bbox, clippedSvp);

	// clamp to viewport
	int x0 = int(bbox.x0);
	int y0 = int(bbox.y0);

	// Use inclusive coords for x1/y1 for clipToBuffer
	int x1 = int(ceil(bbox.x1)) - 1;
	int y1 = int(ceil(bbox.y1)) - 1;

	if(x0 < int(canvas->width()) && y0 < int(canvas->height()) && x1 > -1 && y1 > -1)
	{
		canvas->clipToBuffer(x0, y0, x1, y1);

		TQRect screenBBox(x0, y0, x1 - x0 + 1, y1 - y0 + 1);

		TQByteArray tqmask = SVGMaskElementImpl::maskRectangle(tqshape, screenBBox);

		if(paintType(style) == SVG_PAINTTYPE_URI)
		{
			LibartPaintServer *pserver = static_cast<LibartPaintServer *>(SVGPaintServerImpl::paintServer(tqshape->ownerDoc(), paintUri(style)));

			if(pserver)
			{
				pserver->setBBoxTarget(tqshape);
				if(!pserver->finalized())
					pserver->finalizePaintServer();
				pserver->render(canvas, clippedSvp, opacity(style), tqmask, screenBBox);
			}
		}
		else
			canvas->drawSVP(clippedSvp, m_color, tqmask, screenBBox);
	}

	art_svp_free(clippedSvp);
}

LibartStrokePainter::LibartStrokePainter(SVGStylableImpl *style)
{
	update(style);
}

LibartFillPainter::LibartFillPainter(SVGStylableImpl *style)
{
	update(style);
}

void LibartShape::init()
{
	if(m_style->isFilled())
	{
		if(m_fillPainter == 0)
			m_fillPainter = new LibartFillPainter(m_style);
	}
	else
	{
		delete m_fillPainter;
		m_fillPainter = 0;
	}

	// Spec: A zero value causes no stroke to be painted.
	if(m_style->isStroked() && m_style->getStrokeWidth()->baseVal()->value() > 0)
	{
		if(m_strokePainter == 0)
			m_strokePainter = new LibartStrokePainter(m_style);
	}
	else
	{
		delete m_strokePainter;
		m_strokePainter = 0;
	}
}

void LibartShape::initClipItem()
{
	init();
}

ArtSVP *LibartShape::clipSVP()
{
	return m_fillSVP;
}

void LibartShape::freeSVPs()
{
	if(m_fillSVP)
		art_svp_free(m_fillSVP);
	if(m_strokeSVP)
		art_svp_free(m_strokeSVP);

	m_fillSVP = 0;
	m_strokeSVP = 0;
}

void LibartShape::calcClipSVP(ArtVpath *vec, SVGStylableImpl *style, const SVGMatrixImpl *matrix, _ArtSVP **clipSVP)
{
	double affine[6];
	KSVGHelper::matrixToAffine(matrix, affine);

	if(!style)
	{
		art_free(vec);
		return;
	}

	ArtVpath *vtemp = art_vpath_affine_transform(vec, affine);
	art_free(vec);
	vec = vtemp;

	ArtSVP *temp;
	ArtSvpWriter *swr;
	temp = art_svp_from_vpath(vec);

	if(style->getClipRule() == RULE_EVENODD)
		swr = art_svp_writer_rewind_new(ART_WIND_RULE_ODDEVEN);
	else
		swr = art_svp_writer_rewind_new(ART_WIND_RULE_NONZERO);

	art_svp_intersector(temp, swr);
	*clipSVP = art_svp_writer_rewind_reap(swr);

	art_svp_free(temp);
	art_free(vec);
}

void LibartShape::calcSVPs(ArtVpath *vec, SVGStylableImpl *style, const SVGMatrixImpl *matrix, ArtSVP **strokeSVP, ArtSVP **fillSVP)
{
	if(style)
	{
		double affine[6];
		KSVGHelper::matrixToAffine(matrix, affine);

		ArtVpath *temp = art_vpath_affine_transform(vec, affine);
		art_free(vec);
		vec = temp;
		calcSVPInternal(vec, style, affine, strokeSVP, fillSVP);
	}
	else
		art_free(vec);
}

void LibartShape::calcSVPs(ArtBpath *bpath, SVGStylableImpl *style, const SVGMatrixImpl *matrix, ArtSVP **strokeSVP, ArtSVP **fillSVP)
{
	if(style)
	{
		double affine[6];
		KSVGHelper::matrixToAffine(matrix, affine);

		ArtBpath *temp = art_bpath_affine_transform(bpath, affine);
		ArtVpath *vec = ksvg_art_bez_path_to_vec(temp, 0.25);
		art_free(temp);
		calcSVPInternal(vec, style, affine, strokeSVP, fillSVP);
	}
}

void LibartShape::calcSVPInternal(ArtVpath *vec, SVGStylableImpl *style, double *affine, ArtSVP **strokeSVP, ArtSVP **fillSVP)
{
	ArtSVP *svp;

	// Filling
	{
		ArtSvpWriter *swr;
		ArtSVP *temp;
		temp = art_svp_from_vpath(vec);

		if(style->getFillRule() == RULE_EVENODD)
			swr = art_svp_writer_rewind_new(ART_WIND_RULE_ODDEVEN);
		else
			swr = art_svp_writer_rewind_new(ART_WIND_RULE_NONZERO);

		art_svp_intersector(temp, swr);
		svp = art_svp_writer_rewind_reap(swr);

		*fillSVP = svp;
		art_svp_free(temp);
	}

	// Stroking
	if(style->isStroked() || style->getStrokeColor()->paintType() == SVG_PAINTTYPE_URI)
	{
		double ratio = art_affine_expansion(affine);

		unsigned int dashLength;
		if(style->getDashArray() && (dashLength = style->getDashArray()->baseVal()->numberOfItems()) > 0)
		{
			// HACK: libart will hang in art_vpath_dash() if passed an array with only zeroes.
			bool allZeroes = true;

			// there are dashes to be rendered
			ArtVpathDash dash;
			dash.offset = int(style->getDashOffset()->baseVal()->value()) * ratio;
			dash.n_dash = dashLength;
			double *dashes = new double[dashLength];
			for(unsigned int i = 0; i < dashLength; i++)
			{
				dashes[i] = style->getDashArray()->baseVal()->getItem(i)->value() * ratio;
				if(dashes[i] != 0.0)
					allZeroes = false;
			}
			dash.dash = dashes;

			if(!allZeroes)
			{
				// get the dashed VPath and use that for the stroke render operation
				ArtVpath *vec2 = art_vpath_dash(vec, &dash);
				art_free(vec);
				vec = vec2;
			}

			// reset the dashes
			delete [] dashes;
		}

		double penWidth = style->getStrokeWidth()->baseVal()->value() * ratio;
		svp = art_svp_vpath_stroke(vec, (ArtPathStrokeJoinType)style->getJoinStyle(), (ArtPathStrokeCapType)style->getCapStyle(), penWidth, style->getStrokeMiterlimit(), 0.25);

		*strokeSVP = svp;
	}
	art_free(vec);
}

// #####

LibartRectangle::LibartRectangle(LibartCanvas *c, SVGRectElementImpl *rect)
: LibartShape(c, rect), m_rect(rect)
{
	init();
}

void LibartRectangle::draw()
{
	if(isVisible())
		LibartShape::draw(m_rect);
}

bool LibartRectangle::isVisible()
{
	// Spec: a value of zero disables rendering
	return LibartShape::isVisible(m_rect) && m_rect->width()->baseVal()->value() > 0 && m_rect->height()->baseVal()->value() > 0;
}

void LibartRectangle::init()
{
	init(m_rect->screenCTM());
}

void LibartRectangle::init(const SVGMatrixImpl *screenCTM)
{
	LibartShape::init();
	double x = m_rect->x()->baseVal()->value();
	double y = m_rect->y()->baseVal()->value();
	double width = m_rect->width()->baseVal()->value();
	double height = m_rect->height()->baseVal()->value();
	double rx = m_rect->rx()->baseVal()->value();
	double ry = m_rect->ry()->baseVal()->value();

	// Spec: If there is no rx or ry specified, draw a normal rect
	if(rx == -1 && ry == -1)
	{
		ArtVpath *vec = allocVPath(6);

		vec[0].code = ART_MOVETO;
		vec[0].x = x;
		vec[0].y = y;

		vec[1].code = ART_LINETO;
		vec[1].x = x;
		vec[1].y = y + height;

		vec[2].code = ART_LINETO;
		vec[2].x = x + width;
		vec[2].y = y + height;

		vec[3].code = ART_LINETO;
		vec[3].x = x + width;
		vec[3].y = y;

		vec[4].code = ART_LINETO;
		vec[4].x = x;
		vec[4].y = y;

		vec[5].code = ART_END;

		if(m_context == NORMAL)
			calcSVPs(vec, m_rect, screenCTM, &m_strokeSVP, &m_fillSVP);
		else
			calcClipSVP(vec, m_rect, screenCTM, &m_fillSVP);
	}
	else
	{
		ArtVpath *res;
		ArtBpath *vec = allocBPath(10);

		int i = 0;

		// Spec: If rx isn't specified, but ry, set rx to ry
		if(rx == -1)
			rx = ry;

		// Spec: If ry isn't specified, but rx, set ry to rx
		if(ry == -1)
			ry = rx;

		// Spec: If rx is greater than half of the width of the rectangle
		//       then set rx to half of the width
		if(rx > width / 2)
			rx = width / 2;

		// Spec: If ry is greater than half of the height of the rectangle
		//       then set ry to half of the height
		if(ry > height / 2)
			ry = height / 2;

		vec[i].code = ART_MOVETO_OPEN;
		vec[i].x3 = x + rx;
		vec[i].y3 = y;

		i++;

		vec[i].code = ART_CURVETO;
		vec[i].x1 = x + rx * (1 - 0.552);
		vec[i].y1 = y;
		vec[i].x2 = x;
		vec[i].y2 = y + ry * (1 - 0.552);
		vec[i].x3 = x;
		vec[i].y3 = y + ry;

		i++;

		if(ry < height / 2)
		{
			vec[i].code = ART_LINETO;
			vec[i].x3 = x;
			vec[i].y3 = y + height - ry;

			i++;
		}
		vec[i].code = ART_CURVETO;
		vec[i].x1 = x;
		vec[i].y1 = y + height - ry * (1 - 0.552);
		vec[i].x2 = x + rx * (1 - 0.552);
		vec[i].y2 = y + height;
		vec[i].x3 = x + rx;
		vec[i].y3 = y + height;

		i++;

		if(rx < width / 2)
		{
			vec[i].code = ART_LINETO;
			vec[i].x3 = x + width - rx;
			vec[i].y3 = y + height;

			i++;
		}

		vec[i].code = ART_CURVETO;
		vec[i].x1 = x + width - rx * (1 - 0.552);
		vec[i].y1 = y + height;
		vec[i].x2 = x + width;
		vec[i].y2 = y + height - ry * (1 - 0.552);
		vec[i].x3 = x + width;

		vec[i].y3 = y + height - ry;

		i++;

		if(ry < height / 2)
		{
			vec[i].code = ART_LINETO;
			vec[i].x3 = x + width;
			vec[i].y3 = y + ry;

			i++;
		}
		vec[i].code = ART_CURVETO;
		vec[i].x1 = x + width;
		vec[i].y1 = y + ry * (1 - 0.552);
		vec[i].x2 = x + width - rx * (1 - 0.552);
		vec[i].y2 = y;
		vec[i].x3 = x + width - rx;
		vec[i].y3 = y;

		i++;

		if(rx < width / 2)
		{
			vec[i].code = ART_LINETO;
			vec[i].x3 = x + rx;
			vec[i].y3 = y;

			i++;
		}

		vec[i].code = ART_END;

		res = ksvg_art_bez_path_to_vec(vec, 0.25);
		if(m_context == NORMAL)
			calcSVPs(res, m_rect, screenCTM, &m_strokeSVP, &m_fillSVP);
		else
			calcClipSVP(res, m_rect, screenCTM, &m_fillSVP);
		art_free(vec);
	 }
}

// #####

LibartEllipse::LibartEllipse(LibartCanvas *c, SVGEllipseElementImpl *ellipse)
: LibartShape(c, ellipse), m_ellipse(ellipse)
{
	init();
}
	
void LibartEllipse::draw()
{
	if(isVisible())
		LibartShape::draw(m_ellipse);
}

bool LibartEllipse::isVisible()
{
	// Spec: dont render when rx and/or ry is zero
	return LibartShape::isVisible(m_ellipse) && m_ellipse->rx()->baseVal()->value() > 0 && m_ellipse->ry()->baseVal()->value() > 0;
}

void LibartEllipse::init()
{
	init(m_ellipse->screenCTM());
}

void LibartEllipse::init(const SVGMatrixImpl *screenCTM)
{
	LibartShape::init();
	ArtBpath *temp = allocBPath(6);

	double x1, y1, x2, y2, x3, y3;
	double len = 0.55228474983079356;
	double rx = m_ellipse->rx()->baseVal()->value();
	double ry = m_ellipse->ry()->baseVal()->value();
	double cx = m_ellipse->cx()->baseVal()->value();
	double cy = m_ellipse->cy()->baseVal()->value();
	double cos4[] = {1.0, 0.0, -1.0, 0.0, 1.0};
	double sin4[] = {0.0, 1.0, 0.0, -1.0, 0.0};
	int i = 0;

	temp[i].code = ART_MOVETO;
	temp[i].x3 = cx + rx;
	temp[i].y3 = cy;

	i++;

	while(i < 5)
	{
		x1 = cos4[i-1] + len * cos4[i];
		y1 = sin4[i-1] + len * sin4[i];
		x2 = cos4[i] + len * cos4[i-1];
		y2 = sin4[i] + len * sin4[i-1];
		x3 = cos4[i];
		y3 = sin4[i];

		temp[i].code = ART_CURVETO;
		temp[i].x1 = cx + x1 * rx;
		temp[i].y1 = cy + y1 * ry;
		temp[i].x2 = cx + x2 * rx;
		temp[i].y2 = cy + y2 * ry;
		temp[i].x3 = cx + x3 * rx;
		temp[i].y3 = cy + y3 * ry;

		i++;
	}

	temp[i].code = ART_END;

	if(m_context == NORMAL)
		calcSVPs(temp, m_ellipse, screenCTM, &m_strokeSVP, &m_fillSVP);
	else
		calcClipSVP(ksvg_art_bez_path_to_vec(temp, 0.25), m_ellipse, screenCTM, &m_fillSVP);
	art_free(temp);
}

// #####

LibartCircle::LibartCircle(LibartCanvas *c, SVGCircleElementImpl *circle)
: LibartShape(c, circle), m_circle(circle)
{
	init();
}

void LibartCircle::draw()
{
	// Spec: a value of zero disables rendering
	if(isVisible())
		LibartShape::draw(m_circle);
}

bool LibartCircle::isVisible()
{
	// Spec: dont render when rx and/or ry is zero
	return LibartShape::isVisible(m_circle) && m_circle->r()->baseVal()->value() > 0;
}

void LibartCircle::init()
{
	init(m_circle->screenCTM());
}

void LibartCircle::init(const SVGMatrixImpl *screenCTM)
{
	LibartShape::init();
	ArtBpath *temp = allocBPath(6);

	double x1, y1, x2, y2, x3, y3;
	double len = 0.55228474983079356;
	double r = m_circle->r()->baseVal()->value();
	double cx = m_circle->cx()->baseVal()->value();
	double cy = m_circle->cy()->baseVal()->value();
	double cos4[] = {1.0, 0.0, -1.0, 0.0, 1.0};
	double sin4[] = {0.0, 1.0, 0.0, -1.0, 0.0};
	int i = 0;

	temp[i].code = ART_MOVETO;
	temp[i].x3 = cx + r;
	temp[i].y3 = cy;

	i++;

	while(i < 5)
	{
		x1 = cos4[i-1] + len * cos4[i];
		y1 = sin4[i-1] + len * sin4[i];
		x2 = cos4[i] + len * cos4[i-1];
		y2 = sin4[i] + len * sin4[i-1];
		x3 = cos4[i];
		y3 = sin4[i];

		temp[i].code = ART_CURVETO;
		temp[i].x1 = cx + x1 * r;
		temp[i].y1 = cy + y1 * r;
		temp[i].x2 = cx + x2 * r;
		temp[i].y2 = cy + y2 * r;
		temp[i].x3 = cx + x3 * r;
		temp[i].y3 = cy + y3 * r;

		i++;
	}

	temp[i].code = ART_END;

	if(m_context == NORMAL)
		calcSVPs(temp, m_circle, screenCTM, &m_strokeSVP, &m_fillSVP);
	else
		calcClipSVP(ksvg_art_bez_path_to_vec(temp, 0.25), m_circle, screenCTM, &m_fillSVP);
	art_free(temp);
}

// #####

LibartLine::LibartLine(LibartCanvas *c, SVGLineElementImpl *line)
: LibartShape(c, line), MarkerHelper(), m_line(line)
{
	init();
}

LibartLine::~LibartLine()
{
}

void LibartLine::draw()
{
	LibartShape::draw(m_line);

	if(m_line->hasMarkers())
	{
		double x1 = m_line->x1()->baseVal()->value();
		double y1 = m_line->y1()->baseVal()->value();
		double x2 = m_line->x2()->baseVal()->value();
		double y2 = m_line->y2()->baseVal()->value();
		double slope = SVGAngleImpl::todeg(atan2(y2 - y1, x2 - x1));

		if(m_line->hasStartMarker())
			doStartMarker(m_line, m_line, x1, y1, slope);
		if(m_line->hasEndMarker())
			doEndMarker(m_line, m_line, x2, y2, slope);
	}
}

bool LibartLine::isVisible()
{
	return LibartShape::isVisible(m_line);
}

void LibartLine::init()
{
	init(m_line->screenCTM());
}

void LibartLine::init(const SVGMatrixImpl *screenCTM)
{
	LibartShape::init();
	ArtVpath *vec;

	vec = allocVPath(3);

	vec[0].code = ART_MOVETO_OPEN;
	vec[0].x = m_line->x1()->baseVal()->value();
	vec[0].y = m_line->y1()->baseVal()->value();

	vec[1].code = ART_LINETO;
	vec[1].x = m_line->x2()->baseVal()->value();
	vec[1].y = m_line->y2()->baseVal()->value();

	// A subpath consisting of a moveto and lineto to the same exact location or a subpath consisting of a moveto
	// and a closepath will be stroked only if the 'stroke-linecap' property is set to "round", producing a circle
	// centered at the given point.
	if(vec[1].x == vec[0].x && vec[1].y == vec[0].y && m_line->getCapStyle() == PATH_STROKE_CAP_ROUND)
		vec[1].x += .5;

	vec[2].code = ART_END;

	if(m_context == NORMAL)
	{
		calcSVPs(vec, m_line, screenCTM, &m_strokeSVP, &m_fillSVP);
		art_svp_free(m_fillSVP);
		m_fillSVP = 0;
	}
	else
		calcClipSVP(vec, m_line, screenCTM, &m_fillSVP);
}

// #####
LibartPoly::LibartPoly(LibartCanvas *c, SVGPolyElementImpl *poly)
: LibartShape(c, poly), MarkerHelper(), m_poly(poly)
{
}

LibartPoly::~LibartPoly()
{
}

void LibartPoly::init()
{
	init(m_poly->screenCTM());
}

void LibartPoly::draw()
{
	LibartShape::draw(m_poly);

	if(m_poly->hasMarkers())
		m_poly->drawMarkers();
}

bool LibartPoly::isVisible()
{
	return LibartShape::isVisible(m_poly);
}

// #####
LibartPolyline::LibartPolyline(LibartCanvas *c, SVGPolylineElementImpl *poly)
: LibartPoly(c, poly)
{
	LibartPoly::init();
}

LibartPolyline::~LibartPolyline()
{
}

void LibartPolyline::init(const SVGMatrixImpl *screenCTM)
{
	LibartShape::init();
	unsigned int numberOfPoints = m_poly->points()->numberOfItems();

	if(numberOfPoints < 1)
		return;

	ArtVpath *polyline = allocVPath(2 + numberOfPoints);

	polyline[0].code = ART_MOVETO_OPEN;
	polyline[0].x = m_poly->points()->getItem(0)->x();
	polyline[0].y = m_poly->points()->getItem(0)->y();

	unsigned int index;
	for(index = 1; index < numberOfPoints; index++)
	{
		polyline[index].code = ART_LINETO;
		polyline[index].x = m_poly->points()->getItem(index)->x();
		polyline[index].y = m_poly->points()->getItem(index)->y();
	}

	// A subpath consisting of a moveto and lineto to the same exact location or a subpath consisting of a moveto
	// and a closepath will be stroked only if the 'stroke-linecap' property is set to "round", producing a circle
	// centered at the given point.
	if(numberOfPoints == 2 && polyline[1].x == polyline[0].x && polyline[1].y == polyline[0].y && m_poly->getCapStyle() == PATH_STROKE_CAP_ROUND)
		polyline[1].x += .5;


	if(m_poly->isFilled()) // if the polyline must be filled, inform libart that it should not be closed.
	{
		polyline[index].code = (ArtPathcode) ART_END2;
		polyline[index].x = m_poly->points()->getItem(0)->x();
		polyline[index++].y = m_poly->points()->getItem(0)->y();
	}

	polyline[index].code = ART_END;
	if(m_context == NORMAL)
		calcSVPs(polyline, m_poly, screenCTM, &m_strokeSVP, &m_fillSVP);
	else
		calcClipSVP(polyline, m_poly, screenCTM, &m_fillSVP);
}

// #####

LibartPolygon::LibartPolygon(LibartCanvas *c, SVGPolygonElementImpl *poly)
: LibartPoly(c, poly)
{
	LibartPoly::init();
}

LibartPolygon::~LibartPolygon()
{
}

void LibartPolygon::init(const SVGMatrixImpl *screenCTM)
{
	LibartShape::init();
	unsigned int numberOfPoints = m_poly->points()->numberOfItems();

	if(numberOfPoints < 1)
		return;

	ArtVpath *polygon = allocVPath(2 + numberOfPoints);

	polygon[0].code = ART_MOVETO;
	polygon[0].x = m_poly->points()->getItem(0)->x();
	polygon[0].y = m_poly->points()->getItem(0)->y();

	unsigned int index;
	for(index = 1; index < numberOfPoints; index++)
	{
		polygon[index].code = ART_LINETO;
		polygon[index].x = m_poly->points()->getItem(index)->x();
		polygon[index].y = m_poly->points()->getItem(index)->y();
	}

	polygon[index].code = ART_LINETO;
	polygon[index].x = m_poly->points()->getItem(0)->x();
	polygon[index].y = m_poly->points()->getItem(0)->y();

	index++;
	polygon[index].code = ART_END;

	if(m_context == NORMAL)
		calcSVPs(polygon, m_poly, screenCTM, &m_strokeSVP, &m_fillSVP);
	else
		calcClipSVP(polygon, m_poly, screenCTM, &m_fillSVP);
}

// #####

LibartPath::LibartPath(LibartCanvas *c, SVGPathElementImpl *path)
: LibartShape(c, path), MarkerHelper(), T2P::BezierPathLibart(), ::SVGPathParser(), m_path(path)
{
	reset();
}

LibartPath::~LibartPath()
{
}

void LibartPath::reset()
{
	m_array.resize(0);
	LibartShape::reset();
}

void LibartPath::draw()
{
	LibartShape::draw(m_path);

	if(m_path->hasMarkers())
	{
		SVGPathElementImpl::MarkerData markers = m_path->markerData();
		int numMarkers = markers.numMarkers();

		if(m_path->hasStartMarker())
			doStartMarker(m_path, m_path, markers.marker(0).x, markers.marker(0).y, markers.marker(0).angle);

		for(int i = 1; i < numMarkers - 1; i++)
		{
			if(m_path->hasMidMarker())
				doMidMarker(m_path, m_path, markers.marker(i).x, markers.marker(i).y, markers.marker(i).angle);
		}

		if(m_path->hasEndMarker())
			doEndMarker(m_path, m_path, markers.marker(numMarkers - 1).x, markers.marker(numMarkers - 1).y, markers.marker(numMarkers - 1).angle);
	}
}

bool LibartPath::isVisible()
{
	return LibartShape::isVisible(m_path);
}

void LibartPath::init()
{
	init(m_path->screenCTM());
}

void LibartPath::init(const SVGMatrixImpl *screenCTM)
{
	LibartShape::init();
	if(m_array.count() > 0)
	{
		if(m_context == NORMAL)
			calcSVPs(m_array.data(), m_path, screenCTM, &m_strokeSVP, &m_fillSVP);
		else
			calcClipSVP(ksvg_art_bez_path_to_vec(m_array.data(), 0.25), m_path, screenCTM, &m_fillSVP);
	}
	else if(!m_path->getAttribute("d").string().isEmpty())
	{
		parseSVG(m_path->getAttribute("d").string(), true);

		int index = m_array.count();
		double curx = m_array[index - 1].x3;
		double cury = m_array[index - 1].y3;

		// Find last subpath
		int tqfind = -1;
		for(int i = index - 1; i >= 0; i--)
		{
			if(m_array[i].code == ART_MOVETO_OPEN || m_array[i].code == ART_MOVETO)
			{
				tqfind = i;
				break;
			}
		}

		// Fix a problem where the .svg file used floats as values... (sofico.svg)
		if(curx != m_array[tqfind].x3 && cury != m_array[tqfind].y3)
		{
			if((int) curx == (int) m_array[tqfind].x3 && (int) cury == (int) m_array[tqfind].y3)
			{
				ensureSpace(m_array, index)

				m_array[index].code = ART_LINETO;
				m_array[index].x3 = m_array[tqfind].x3;
				m_array[index].y3 = m_array[tqfind].y3;

				curx = m_array[tqfind].x3;
				cury = m_array[tqfind].y3;

				index++;
			}
		}

		// handle filled paths that are not closed explicitly
		if(m_path->getFillColor()->paintType() != SVG_PAINTTYPE_NONE)
		{
			if((int) curx != (int) m_array[tqfind].x3 || (int) cury != (int) m_array[tqfind].y3)
			{
				ensureSpace(m_array, index)

				m_array[index].code = (ArtPathcode)ART_END2;
				m_array[index].x3 = m_array[tqfind].x3;
				m_array[index].y3 = m_array[tqfind].y3;

				curx = m_array[tqfind].x3;
				cury = m_array[tqfind].y3;

				index++;
			}
		}

		// close
		ensureSpace(m_array, index)

		m_array[index].code = ART_END;

		// A subpath consisting of a moveto and lineto to the same exact location or a subpath consisting of a moveto
		// and a closepath will be stroked only if the 'stroke-linecap' property is set to "round", producing a circle
		// centered at the given point.
		if(index == 2 && m_array[1].code == ART_LINETO && m_array[1].x3 == m_array[0].x3 && m_array[1].y3 == m_array[0].y3 && m_path->getCapStyle() == PATH_STROKE_CAP_ROUND)
			m_array[1].x3 += .5;

		// There are pure-moveto paths which reference paint servers *bah*
		// Do NOT render them
		bool render = false;
		for(int i = index; i >= 0; i--)
		{
			if(m_array[i].code != ART_MOVETO_OPEN && m_array[i].code != ART_MOVETO && !(m_array[i].code >= ART_END))
			{
				render = true;
				break;
			}
		}

		if(render && m_context == NORMAL)
			calcSVPs(m_array.data(), m_path, screenCTM, &m_strokeSVP, &m_fillSVP);
		else
			calcClipSVP(ksvg_art_bez_path_to_vec(m_array.data(), 0.25), m_path, screenCTM, &m_fillSVP);
	}
}

void LibartPath::svgMoveTo(double x1, double y1, bool closed, bool)
{
	int index = m_array.count();

	if(index > 0 && !closed)
	{
		// Find last subpath
		int tqfind = -1;
		for(int i = index - 1; i >= 0; i--)
		{
			if(m_array[i].code == ART_MOVETO_OPEN || m_array[i].code == ART_MOVETO)
			{
				tqfind = i;
				break;
			}
		}
		
		ensureSpace(m_array, index)

		m_array[index].code = (ArtPathcode) ART_END2;
		m_array[index].x3 = m_array[tqfind].x3;
		m_array[index].y3 = m_array[tqfind].y3;

		index++;
	}

	ensureSpace(m_array, index)

	m_array[index].code = (index == 0) ? ART_MOVETO : ART_MOVETO_OPEN;
	m_array[index].x3 = x1;
	m_array[index].y3 = y1;
}

void LibartPath::svgLineTo(double x1, double y1, bool)
{
	int index = m_array.count();

	ensureSpace(m_array, index)

	m_array[index].code = ART_LINETO;
	m_array[index].x3 = x1;
	m_array[index].y3 = y1;
}

void LibartPath::svgCurveToCubic(double x1, double y1, double x2, double y2, double x3, double y3, bool)
{
	int index = m_array.count();

	ensureSpace(m_array, index)

	m_array[index].code = ART_CURVETO;
	m_array[index].x1 = x1;
	m_array[index].y1 = y1;
	m_array[index].x2 = x2;
	m_array[index].y2 = y2;
	m_array[index].x3 = x3;
	m_array[index].y3 = y3;
}

void LibartPath::svgClosePath()
{
	int index = m_array.count();
	double curx = m_array[index - 1].x3;
	double cury = m_array[index - 1].y3;

	int tqfind = -1;
	for(int i = index - 1; i >= 0; i--)
	{
		if(m_array[i].code == ART_MOVETO_OPEN || m_array[i].code == ART_MOVETO)
		{
			tqfind = i;
			break;
		}
	}

	if(tqfind != -1)
	{
		if(m_array[tqfind].x3 != curx || m_array[tqfind].y3 != cury)
		{
			ensureSpace(m_array, index)

			m_array[index].code = ART_LINETO;
			m_array[index].x3 = m_array[tqfind].x3;
			m_array[index].y3 = m_array[tqfind].y3;
		}
	}
}

// #####

LibartClipPath::LibartClipPath(LibartCanvas *c, SVGClipPathElementImpl *clipPath)
: CanvasClipPath(clipPath), m_canvas(c)
{
	m_clipSVP = 0;
	m_clipItems.setAutoDelete(true);
}

LibartClipPath::~LibartClipPath()
{
	if(m_clipSVP)
		art_svp_free(m_clipSVP);
}

void LibartClipPath::update(CanvasItemUpdate, int, int)
{
	if(m_clipSVP)
		art_svp_free(m_clipSVP);
	m_clipSVP = 0;
}

void LibartClipPath::init()
{
	SVGMatrixImpl *clipMatrix = 0;
	
	// Start with referencing element's coordinate system
	SVGLocatableImpl *locatableReferrer = dynamic_cast<SVGLocatableImpl *>(m_clipPath->getBBoxTarget());
	if(locatableReferrer)
		clipMatrix = locatableReferrer->getScreenCTM();
	else
		clipMatrix = SVGSVGElementImpl::createSVGMatrix();

	if(m_clipPath->clipPathUnits()->baseVal() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && m_clipPath->getBBoxTarget())
	{
		SVGRectImpl *rect = m_clipPath->getBBoxTarget()->getBBox();

		clipMatrix->translate(rect->qrect().x(), rect->qrect().y());
		clipMatrix->scaleNonUniform(rect->qrect().width(), rect->qrect().height());

		rect->deref();
	}

	// Add transformations on the clipPath element itself
	if(m_clipPath->localMatrix())
		clipMatrix->multiply(m_clipPath->localMatrix());

	if(m_clipSVP)
	{
		art_svp_free(m_clipSVP);
		m_clipSVP = 0;
	}

	DOM::Node node = m_clipPath->firstChild();
	for(; !node.isNull(); node = node.nextSibling())
	{
		SVGElementImpl *element = m_clipPath->ownerDoc()->getElementFromHandle(node.handle());
		SVGShapeImpl *tqshape = dynamic_cast<SVGShapeImpl *>(element);
		SVGTestsImpl *tests = dynamic_cast<SVGTestsImpl *>(element);

		bool ok = tests ? tests->ok() : true;

		if(element && tqshape && ok && !tqshape->isContainer())
		{
			LibartClipItem *clipElement = dynamic_cast<LibartClipItem *>(tqshape->item());
			
			if(dynamic_cast<LibartText *>(tqshape->item()))
			{
				// The cast to a clipElement above is failing when it is valid. But only
				// in the plugin - svgdisplay works fine. What's going on? (Adrian)
				clipElement = dynamic_cast<LibartText *>(tqshape->item());
			}

			if(clipElement)
			{
				clipElement->setRenderContext(CLIPPING);

				// Push coordinate system down to tqchildren.
				SVGLocatableImpl *locatable = dynamic_cast<SVGLocatableImpl *>(tqshape);
				if(locatable)
					locatable->updateCachedScreenCTM(clipMatrix);

				clipElement->initClipItem();

				ArtSVP *one = clipElement->clipSVP();
				if(!one)
					break;

				if(m_clipSVP == 0)
					m_clipSVP = LibartCanvas::copy_svp(one);
				else
				{
					ArtSVP *svp_union = art_svp_union(m_clipSVP, one);
					art_svp_free(m_clipSVP);
					m_clipSVP = svp_union;
				}
			}
		}
	}

	clipMatrix->deref();
}

void LibartClipPath::draw()
{
}

ArtSVP *LibartClipPath::clipSVP()
{
	return m_clipSVP;
}

// #####

LibartImage::LibartImage(LibartCanvas *c, SVGImageElementImpl *image)
: m_canvas(c), m_image(image)
{
}

LibartImage::~LibartImage()
{
}

void LibartImage::draw()
{
	if(isVisible())
	{
		SVGMatrixImpl *ctm = m_image->scaledImageMatrix();
		TQImage image = m_image->scaledImage();
		KSVGPolygon clippingPolygon = m_image->clippingShape();

		m_canvas->drawImage(image, m_image, ctm, clippingPolygon);

		ctm->deref();
	}
}

bool LibartImage::isVisible()
{
	return (m_referenced || (m_image->getVisible() && m_image->getDisplay() && m_image->directRender())) && m_image->image();
}

void LibartImage::init()
{
}

TQRect LibartImage::bbox() const
{
	TQRect bbox(static_cast<int>(m_image->x()->baseVal()->value()),
			   static_cast<int>(m_image->y()->baseVal()->value()),
			   static_cast<int>(m_image->width()->baseVal()->value()),
			   static_cast<int>(m_image->height()->baseVal()->value()));

	
	return SVGHelperImpl::fromUserspace(m_image, bbox);
}

// #####

LibartMarker::LibartMarker(LibartCanvas *c, SVGMarkerElementImpl *marker)
: CanvasMarker(marker), m_canvas(c)
{
}

LibartMarker::~LibartMarker()
{
}

void LibartMarker::init()
{
}

void LibartMarker::draw()
{
}

// #####

LibartText::LibartText(LibartCanvas *c, SVGTextElementImpl *text)
: CanvasText(text), m_canvas(c)
{
	m_drawFillItems.setAutoDelete(true);
	m_drawStrokeItems.setAutoDelete(true);
	m_fillPainters.setAutoDelete(true);
	m_strokePainters.setAutoDelete(true);

	init();
}

LibartText::~LibartText()
{
	clearSVPs();
}

LibartText::SVPElement::~SVPElement()
{
	if(svp)
		art_svp_free(svp);
}

TQRect LibartText::bbox() const
{
	TQRect result, rect;

	TQPtrListIterator<SVPElement> it1(m_drawFillItems);
	TQPtrListIterator<SVPElement> it2(m_drawStrokeItems);

	SVPElement *fill = it1.current(), *stroke = it2.current();
	while(fill != 0 || stroke != 0)
	{
		ArtIRect *irect = new ArtIRect();
		ArtVpath *vpath = art_vpath_from_svp((stroke && stroke->svp) ? stroke->svp : fill->svp);
		art_vpath_bbox_irect(vpath, irect);
		art_free(vpath);

		rect.setX(irect->x0);
		rect.setY(irect->y0);
		rect.setWidth(irect->x1 - irect->x0);
		rect.setHeight(irect->y1 - irect->y0);

		delete irect;

		result = result.unite(rect);

		fill = ++it1;
		stroke = ++it2;
	}
	return result;
}

bool LibartText::fillContains(const TQPoint &p)
{
	TQPtrListIterator<SVPElement> it(m_drawFillItems);

	SVPElement *fill = it.current();
	while(fill && fill->svp)
	{
		if(fill->svp && art_svp_point_wind(fill->svp, p.x(), p.y()) != 0)
			return true;

		fill = ++it;
	}

	return false;
}

bool LibartText::strokeContains(const TQPoint &p)
{
	TQPtrListIterator<SVPElement> it(m_drawStrokeItems);

	SVPElement *stroke = it.current();
	while(stroke && stroke->svp)
	{
		if(stroke->svp && art_svp_point_wind(stroke->svp, p.x(), p.y()) != 0)
			return true;

		stroke = ++it;
	}

	return false;
}

void LibartText::update(CanvasItemUpdate reason, int param1, int param2)
{
	if(reason == UPDATE_STYLE)
	{
		TQPtrListIterator<SVPElement> it1(m_drawFillItems);
		TQPtrListIterator<SVPElement> it2(m_drawStrokeItems);
		SVPElement *fill = it1.current(), *stroke = it2.current();
		while(fill != 0 || stroke != 0)
		{
			SVGTextContentElementImpl *text = fill ? fill->element : stroke->element;

			bool fillOk = fill && fill->svp && text->isFilled();
			bool strokeOk = stroke && stroke->svp && text->isStroked() && text->getStrokeWidth()->baseVal()->value() > 0; // Spec: A zero value causes no stroke to be painted.
			if(fillOk || strokeOk)
			{
				if(m_fillPainters.tqfind(text))
					m_fillPainters[text]->update(text);

				if(m_strokePainters.tqfind(text))
					m_strokePainters[text]->update(text);
			}
			fill = ++it1;
			stroke = ++it2;
		}
		m_canvas->tqinvalidate(this, false);
	}
	else if(reason == UPDATE_TRANSFORM)
	{
		clearSVPs();
		init();
		m_canvas->tqinvalidate(this, true);
	}
	else if(reason == UPDATE_ZOOM)
	{
		clearSVPs();
		init();
	}
	else if(reason == UPDATE_PAN)
	{
		TQPtrListIterator<SVPElement> it1(m_drawFillItems);
		TQPtrListIterator<SVPElement> it2(m_drawStrokeItems);

		double affine[6];
		KSVGHelper::matrixToAffine(m_text->screenCTM(), affine);

		SVPElement *fill = it1.current(), *stroke = it2.current();
		while(fill != 0 || stroke != 0)
		{
			SVGTextContentElementImpl *text = fill ? fill->element : stroke->element;

			bool fillOk = fill && fill->svp && text->isFilled();
			bool strokeOk = stroke && stroke->svp && text->isStroked() && text->getStrokeWidth()->baseVal()->value() > 0; // Spec: A zero value causes no stroke to be painted.

			if(fillOk)
				ksvg_art_svp_move(fill->svp, param1, param2);
			if(strokeOk)
				ksvg_art_svp_move(stroke->svp, param1, param2);
			fill = ++it1;
			stroke = ++it2;
		}
	}
	/*
	else if(reason == UPDATE_LINEWIDTH)
	{
	}*/
}

void LibartText::draw()
{
	TQPtrListIterator<SVPElement> it1(m_drawFillItems);
	TQPtrListIterator<SVPElement> it2(m_drawStrokeItems);

	SVPElement *fill = it1.current(), *stroke = it2.current();
	while(fill != 0 || stroke != 0)
	{
		SVGTextContentElementImpl *text = fill ? fill->element : stroke->element;
		if(!text || !text->getVisible() || !text->getDisplay() || !text->directRender())
			return;

		bool fillOk = fill && fill->svp && text->isFilled();
		bool strokeOk = stroke && stroke->svp && text->isStroked() && text->getStrokeWidth()->baseVal()->value() > 0; // Spec: A zero value causes no stroke to be painted.

		if(fillOk || strokeOk)
		{
			if(fillOk && m_fillPainters.tqfind(text))
				m_fillPainters[text]->draw(m_canvas, fill->svp, text, text);

			if(strokeOk && m_strokePainters.tqfind(text))
				m_strokePainters[text]->draw(m_canvas, stroke->svp, text, text);
		}
		fill = ++it1;
		stroke = ++it2;
	}
}

bool LibartText::isVisible()
{
	bool foundVisible = false;
	TQPtrListIterator<SVPElement> it1(m_drawFillItems);
	TQPtrListIterator<SVPElement> it2(m_drawStrokeItems);

	SVPElement *fill = it1.current(), *stroke = it2.current();
	while(fill != 0 || stroke != 0)
	{
		SVGTextContentElementImpl *text = fill ? fill->element : stroke->element;
		if(text && text->getVisible() && text->getDisplay() && text->directRender())
		{
			foundVisible = true;
			break;
		}

		fill = ++it1;
		stroke = ++it2;
	}

	return foundVisible;
}

void LibartText::init()
{
	init(m_text->screenCTM());
}

void LibartText::renderCallback(SVGTextContentElementImpl *element, const SVGMatrixImpl *screenCTM, T2P::GlyphSet *glyph, T2P::GlyphLayoutParams *params, double anchor) const
{
	unsigned int glyphCount = glyph->glyphCount(); // Don't call it n times in the for loop
	for(unsigned int i = 0; i < glyphCount; i++)
	{
		T2P::GlyphAffinePair *glyphAffine = glyph->set()[i];
		ArtBpath *bezier = static_cast<const T2P::BezierPathLibart *>(glyphAffine->transformatedPath())->m_array.data();
		ArtBpath *result = bezier;

		// text-anchor support
		if(anchor != 0)
		{
			double correct[6];

			if(!params->tb())
				art_affine_translate(correct, -anchor, 0);
			else
				art_affine_translate(correct, 0, -anchor);

			ArtBpath *temp = art_bpath_affine_transform(result, correct);
			//art_free(result);
			result = temp;
		}

		ArtSVP *fillSVP = 0, *strokeSVP = 0;

		if(m_context == NORMAL)
			LibartShape::calcSVPs(result, m_text, screenCTM, &strokeSVP, &fillSVP);
		else
			LibartShape::calcClipSVP(ksvg_art_bez_path_to_vec(result, 0.25), m_text, screenCTM, &fillSVP);

		SVPElement *fillElement = new SVPElement();
		fillElement->svp = fillSVP;
		fillElement->element = element;

		SVPElement *strokeElement = new SVPElement();
		strokeElement->svp = strokeSVP;
		strokeElement->element = element;

		m_drawFillItems.append(fillElement);
		m_drawStrokeItems.append(strokeElement);

		if(!m_fillPainters.tqfind(element) && element->isFilled())
			m_fillPainters.insert(element, new LibartFillPainter(element));

		// Spec: A zero value causes no stroke to be painted.
		if(!m_strokePainters.tqfind(element) && element->isStroked() && element->getStrokeWidth()->baseVal()->value() > 0)
			m_strokePainters.insert(element, new LibartStrokePainter(element));
	}
}

void LibartText::init(const SVGMatrixImpl *screenCTM)
{
	int curx = 0, cury = 0, endx = 0, endy = 0;
	KSVGTextChunk *textChunk = CanvasText::createTextChunk(m_canvas, screenCTM, curx, cury, endx, endy);

	if(textChunk->count() > 0)
		CanvasText::createGlyphs(textChunk, m_canvas, screenCTM, curx, cury, endx, endy);

	delete textChunk;
}

void LibartText::clearSVPs()
{
	m_drawFillItems.clear();
	m_drawStrokeItems.clear();
	m_fillPainters.clear();
	m_strokePainters.clear();
}

void LibartText::addTextDecoration(SVGTextContentElementImpl *element, double x, double y, double width, double height) const
{
	if(m_text->isFilled() || m_text->isStroked())
	{
		// compute rect svp
		ArtVpath *vec = allocVPath(6);

		vec[0].code = ART_MOVETO;
		vec[0].x = x;
		vec[0].y = y;

		vec[1].code = ART_LINETO;
		vec[1].x = x;
		vec[1].y = y + height;

		vec[2].code = ART_LINETO;
		vec[2].x = x + width;
		vec[2].y = y + height;

		vec[3].code = ART_LINETO;
		vec[3].x = x + width;
		vec[3].y = y;

		vec[4].code = ART_LINETO;
		vec[4].x = x;
		vec[4].y = y;

		vec[5].code = ART_END;
		double affine[6];
		KSVGHelper::matrixToAffine(m_text->screenCTM(), affine);

		ArtVpath *temp = art_vpath_affine_transform(vec, affine);
		art_free(vec);
		vec = temp;

		if(m_text->isFilled())
		{
			ArtSvpWriter *swr;
			ArtSVP *temp = art_svp_from_vpath(vec);

			swr = art_svp_writer_rewind_new(ART_WIND_RULE_ODDEVEN);

			art_svp_intersector(temp, swr);
			ArtSVP *fillSVP = art_svp_writer_rewind_reap(swr);

			SVPElement *fillElement = new SVPElement();
			fillElement->svp = fillSVP;
			fillElement->element = element;

			m_drawFillItems.append(fillElement);

			if(!m_fillPainters.tqfind(element) && element->isFilled())
				m_fillPainters.insert(element, new LibartFillPainter(element));

			art_svp_free(temp);
		}
		// Stroking
		if(m_text->isStroked() || m_text->getStrokeColor()->paintType() == SVG_PAINTTYPE_URI)
		{
			double ratio = art_affine_expansion(affine);
			ArtSVP *strokeSVP = art_svp_vpath_stroke(vec, (ArtPathStrokeJoinType)m_text->getJoinStyle(), (ArtPathStrokeCapType)m_text->getCapStyle(), m_text->getStrokeWidth()->baseVal()->value() * ratio, m_text->getStrokeMiterlimit(), 0.25);

			SVPElement *strokeElement = new SVPElement();
			strokeElement->svp = strokeSVP;
			strokeElement->element = element;

			m_drawStrokeItems.append(strokeElement);

			// Spec: A zero value causes no stroke to be painted.
			if(!m_strokePainters.tqfind(element) && element->isStroked() && element->getStrokeWidth()->baseVal()->value() > 0)
				m_strokePainters.insert(element, new LibartStrokePainter(element));
		}
		art_free(vec);
	}
}

void LibartText::initClipItem()
{
	init();
}

ArtSVP *LibartText::clipSVP()
{
	ArtSVP *svp = 0;
	TQPtrListIterator<SVPElement> it(m_drawFillItems);

	SVPElement *fill = it.current();
	while(fill && fill->svp)
	{
		if(svp == 0)
			svp = LibartCanvas::copy_svp(fill->svp);
		else
		{
			ArtSVP *svp_union = art_svp_union(svp, fill->svp);
			art_svp_free(svp);
			svp = svp_union;
		}

		fill = ++it;
	}

	return svp;
}

ArtRender *LibartPaintServer::createRenderer(TQRect bbox, KSVGCanvas *c)
{
	int x0 = bbox.x();
	int y0 = bbox.y();
	int x1 = bbox.right();
	int y1 = bbox.bottom();

	c->clipToBuffer(x0, y0, x1, y1);

	// Note: We always pass 3 for the number of channels since the ART_ALPHA parameter
	// adds the alpha channel when present.
	ArtRender *render = 0;
	render = art_render_new(TQMIN(x0, x1),
							TQMIN(y0, y1),
							TQMAX(x0, x1) + 1,
							TQMAX(y0, y1) + 1,
							c->renderingBuffer() + x0 * c->nrChannels() + y0 * c->rowStride(),
							c->rowStride(), 3, 8,
							c->nrChannels() == 3 ? ART_ALPHA_NONE : ART_ALPHA_PREMUL, 0);

	return render;
}

void LibartGradient::parseGradientStops(SVGGradientElementImpl *gradient)
{
	const double epsilon = DBL_EPSILON;

	for(DOM::Node node = gradient->firstChild(); !node.isNull(); node = node.nextSibling())
	{
		SVGStopElementImpl *elem = dynamic_cast<SVGStopElementImpl *>(m_gradient->ownerDoc()->getElementFromHandle(node.handle()));
		if(elem)
		{
			m_stops.resize(m_stops.size() + 1);

			ArtGradientStop *stop = &(m_stops[m_stops.size() - 1]);

			stop->offset = elem->offset()->baseVal();

			// Spec: clamp range to 0 to 1
			if(stop->offset < epsilon)
				stop->offset = 0;
			else if(stop->offset > 1 - epsilon)
				stop->offset = 1;

			// Spec: if offset is less than previous offset, set it to the previous offset
			if(m_stops.size() > 1 && stop->offset < (stop - 1)->offset + epsilon)
				stop->offset = (stop - 1)->offset;

			// Get color
			TQColor qStopColor;

			if(elem->getStopColor()->colorType() == SVG_COLORTYPE_CURRENTCOLOR)
				qStopColor = elem->getColor()->rgbColor().color();
			else
				qStopColor = elem->getStopColor()->rgbColor().color();

			// Convert in a libart suitable form
			TQString tempName = qStopColor.name();
			const char *str = tempName.latin1();

			int stopColor = 0;

			for(int i = 1; str[i]; i++)
			{
				int hexval;
				if(str[i] >= '0' && str[i] <= '9')
					hexval = str[i] - '0';
				else if (str[i] >= 'A' && str[i] <= 'F')
					hexval = str[i] - 'A' + 10;
				else if (str[i] >= 'a' && str[i] <= 'f')
					hexval = str[i] - 'a' + 10;
				else
					break;

				stopColor = (stopColor << 4) + hexval;
			}

			// Apply stop-opacity
			float opacity = elem->stopOpacity();

			// Get rgba color including stop-opacity
			TQ_UINT32 rgba = (stopColor << 8) | int(opacity * 255.0 + 0.5);
			TQ_UINT32 r, g, b, a;

			a = rgba & 0xff;
			r = (rgba >> 24) & 0xff;
			g = (rgba >> 16) & 0xff;
			b = (rgba >> 8) & 0xff;

			stop->color[0] = ART_PIX_MAX_FROM_8(r);
			stop->color[1] = ART_PIX_MAX_FROM_8(g);
			stop->color[2] = ART_PIX_MAX_FROM_8(b);
			stop->color[3] = ART_PIX_MAX_FROM_8(a);
		}
	}
}

void LibartGradient::finalizePaintServer()
{
	parseGradientStops(m_gradient->stopsSource());

	TQString _href = SVGURIReferenceImpl::getTarget(m_gradient->href()->baseVal().string());
	if(!_href.isEmpty())
		reference(_href);

	setFinalized();
}

void LibartGradient::reference(const TQString &)
{
}

void LibartLinearGradient::render(KSVGCanvas *c, ArtSVP *svp, float opacity, TQByteArray tqmask, TQRect screenBBox)
{
	if(!m_stops.isEmpty())
	{
		m_linear->converter()->finalize(getBBoxTarget(), m_linear->ownerSVGElement(), m_linear->gradientUnits()->baseVal());

		ArtKSVGGradientLinear *linear = art_new(ArtKSVGGradientLinear, 1);

		if(m_linear->spreadMethod()->baseVal() == SVG_SPREADMETHOD_REPEAT)
			linear->spread = ART_GRADIENT_REPEAT;
		else if(m_linear->spreadMethod()->baseVal() == SVG_SPREADMETHOD_REFLECT)
			linear->spread = ART_GRADIENT_REFLECT;
		else
			linear->spread = ART_GRADIENT_PAD;

		linear->interpolation = m_linear->getColorInterpolation() == CI_SRGB ? ART_KSVG_SRGB_INTERPOLATION : ART_KSVG_LINEARRGB_INTERPOLATION;

		ArtRender *render = createRenderer(screenBBox, c);

		double _x1 = m_linear->x1()->baseVal()->value();
		double _y1 = m_linear->y1()->baseVal()->value();
		double _x2 = m_linear->x2()->baseVal()->value();
		double _y2 = m_linear->y2()->baseVal()->value();

		// Respect current transformation matrix (so gradients zoom with...)
		SVGTransformableImpl *transformable = dynamic_cast<SVGTransformableImpl *>(getBBoxTarget());
		SVGMatrixImpl *matrix = 0;
		if(transformable)
			matrix = transformable->getScreenCTM();
		else
			matrix = SVGSVGElementImpl::createSVGMatrix();

		const double epsilon = DBL_EPSILON;

		if(m_linear->gradientUnits()->baseVal() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
		{
			// Here we're undoing the unit-converter's work because putting the
			// bounding box transform into the matrix here lets the gradient transform
			// sit at the right point in the chain to work with bounding box coordinates. It
			// also removes the need for code to generate the 'not perpendicular to gradient vector' effect.
			SVGRectImpl *userBbox = getBBoxTarget()->getBBox();

			double width = userBbox->width();
			double height = userBbox->height();

			// Catch case of width or height of zero, which can be the case for lines.
			if(width < epsilon)
				width = 1;
			if(height < epsilon)
				height = 1;

			_x1 /= width;
			_y1 /= height;
			_x2 /= width;
			_y2 /= height;

			matrix->translate(userBbox->x(), userBbox->y());
			matrix->scaleNonUniform(width, height);

			userBbox->deref();
		}

		// Adjust to gradient transform
		SVGMatrixImpl *gradTrans = m_linear->gradientTransform()->baseVal()->concatenate();
		if(gradTrans)
		{
			matrix->multiply(gradTrans);
			gradTrans->deref();
		}

		double dx = _x2 - _x1;
		double dy = _y2 - _y1;

		if(fabs(dx) < epsilon && fabs(dy) < epsilon)
		{
			// Lines can generate (0, 0) with bbox coords.
			dx = 1;
			dy = 0;
		}

		double angle = atan2(dy, dx);
		double length = sqrt(dx * dx + dy * dy);

		const double pi = 3.14159265358979323846;

		matrix->translate(_x1, _y1);
		matrix->scale(length);
		matrix->rotate(angle * 180.0 / pi);

		double affine[6];

		KSVGHelper::matrixToAffine(matrix, affine);
		art_affine_invert(linear->affine, affine);

		matrix->deref();

		TQMemArray<ArtGradientStop> stops = m_stops;
		stops.detach();

		for(unsigned int i = 0; i < stops.size(); i++)
			stops[i].color[3] = ArtPixMaxDepth(stops[i].color[3] * opacity + 0.5);

		if(m_linear->x1()->baseVal()->valueInSpecifiedUnits() == m_linear->x2()->baseVal()->valueInSpecifiedUnits()
			 && m_linear->y1()->baseVal()->valueInSpecifiedUnits() == m_linear->y2()->baseVal()->valueInSpecifiedUnits())
		{
			// Spec: If x1 == x2 and y1 == y2, paint the area in a single colour, using the colour
			// of the last stop.
			//
			// Using valueInSpecifiedUnits() so that we are comparing the values before possible
			// conversion to bounding box units by the converter.
			if(stops.size() > 1)
			{
				stops[0] = stops[stops.size() - 1];
				stops.resize(1);
			}
		}

		linear->stops = &(stops[0]);
		linear->n_stops = stops.size();

		art_render_svp(render, svp);
		art_ksvg_render_gradient_linear(render, linear, ART_FILTER_HYPER);

		if(tqmask.data())
			art_render_mask(render, screenBBox.left(), screenBBox.top(), screenBBox.right() + 1, screenBBox.bottom() + 1,
				(const art_u8 *)tqmask.data(), screenBBox.width());

		art_render_invoke(render);

		art_free(linear);
	}
}

void LibartRadialGradient::render(KSVGCanvas *c, ArtSVP *svp, float opacity, TQByteArray tqmask, TQRect screenBBox)
{
	if(!m_stops.isEmpty())
	{
		m_radial->converter()->finalize(getBBoxTarget(), m_radial->ownerSVGElement(), m_radial->gradientUnits()->baseVal());

		ArtKSVGGradientRadial *radial = art_new(ArtKSVGGradientRadial, 1);

		if(m_radial->spreadMethod()->baseVal() == SVG_SPREADMETHOD_REPEAT)
			radial->spread = ART_GRADIENT_REPEAT;
		else if(m_radial->spreadMethod()->baseVal() == SVG_SPREADMETHOD_REFLECT)
			radial->spread = ART_GRADIENT_REFLECT;
		else
			radial->spread = ART_GRADIENT_PAD;

		radial->interpolation = m_radial->getColorInterpolation() == CI_SRGB ? ART_KSVG_SRGB_INTERPOLATION : ART_KSVG_LINEARRGB_INTERPOLATION;

		ArtRender *render = createRenderer(screenBBox, c);

		// Respect current transformation matrix (so gradients zoom with...)
		SVGTransformableImpl *transformable = dynamic_cast<SVGTransformableImpl *>(getBBoxTarget());
		SVGMatrixImpl *matrix = 0;
		if(transformable)
			matrix = transformable->getScreenCTM();
		else
			matrix = SVGSVGElementImpl::createSVGMatrix();

		double _cx = m_radial->cx()->baseVal()->value();
		double _cy = m_radial->cy()->baseVal()->value();
		double _r = m_radial->r()->baseVal()->value();
		
		double _fx;
		double _fy;

		// Spec: If attribute fx is not specified, fx will coincide with cx.
		if(m_radial->getAttribute("fx").isEmpty())
			_fx = _cx;
		else
			_fx = m_radial->fx()->baseVal()->value();

		// Spec: If attribute fy is not specified, fy will coincide with cy.
		if(m_radial->getAttribute("fy").isEmpty())
			_fy = _cy;
		else
			_fy = m_radial->fy()->baseVal()->value();

		const double epsilon = DBL_EPSILON;

		if(m_radial->gradientUnits()->baseVal() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
		{
			// Here we're undoing the unit-converter's work because putting the
			// bounding box transform into the matrix here lets the gradient transform
			// sit at the right point in the chain to work with bounding box coordinates.
			// It also produces the elliptical tqshape due to the non-uniform scaling.
			SVGRectImpl *userBBox = getBBoxTarget()->getBBox();

			double width = userBBox->width();
			double height = userBBox->height();

			// Catch case of width or height of zero, which can be the case for lines.
			if(width < epsilon)
				width = 1;
			if(height < epsilon)
				height = 1;

			_cx /= width;
			_cy /= height;
			_fx /= width;
			_fy /= height;
			_r /= (sqrt(width * width + height * height) / 1.4142135623731);

			matrix->translate(userBBox->x(), userBBox->y());
			matrix->scaleNonUniform(width, height);

			userBBox->deref();
		}

		// Adjust to gradient transforms
		SVGMatrixImpl *transform = m_radial->gradientTransform()->baseVal()->concatenate();

		if(transform)
		{
			matrix->multiply(transform);
			transform->deref();
		}

		double fx = (_fx - _cx) / _r;
		double fy = (_fy - _cy) / _r;

		if(fx * fx + fy * fy > 0.99)
		{
			// Spec: If (fx, fy) lies outside the circle defined by (cx, cy) and r, set (fx, fy)
			// to the point of intersection of the line through (fx, fy) and the circle.
			//
			// Note: We need to keep (fx, fy) inside the unit circle in order for
			// libart to render the gradient correctly.
			double angle = atan2(fy, fx);
			fx = cos(angle) * 0.99;
			fy = sin(angle) * 0.99;
		}

		radial->fx = fx;
		radial->fy = fy;

		matrix->translate(_cx, _cy);
		matrix->scale(_r);

		double affine[6];

		KSVGHelper::matrixToAffine(matrix, affine);
		art_affine_invert(radial->affine, affine);

		matrix->deref();

		TQMemArray<ArtGradientStop> stops = m_stops;
		stops.detach();

		for(unsigned int i = 0; i < stops.size(); i++)
			stops[i].color[3] = ArtPixMaxDepth(stops[i].color[3] * opacity + 0.5);

		radial->stops = &(stops[0]);
		radial->n_stops = stops.size();

		art_render_svp(render, svp);
		art_ksvg_render_gradient_radial(render, radial, ART_FILTER_HYPER);

		if(tqmask.data())
			art_render_mask(render, screenBBox.left(), screenBBox.top(), screenBBox.right() + 1, screenBBox.bottom() + 1,
				(const art_u8 *)tqmask.data(), screenBBox.width());

		art_render_invoke(render);

		art_free(radial);
	}
}

LibartPattern::LibartPattern(SVGPatternElementImpl *pattern)
	: m_pattern(pattern)
{
}

void LibartPattern::finalizePaintServer()
{
	m_pattern->finalizePaintServer();
	setFinalized();
}

void LibartPattern::reference(const TQString &href)
{
	m_pattern->reference(href);
}

void LibartPattern::render(KSVGCanvas *c, ArtSVP *svp, float opacity, TQByteArray tqmask, TQRect screenBBox)
{
	SVGPatternElementImpl::Tile tile = m_pattern->createTile(getBBoxTarget());

	if(!tile.image().isNull())
	{
		TQWMatrix m = tile.screenToTile();
		double affine[6];
		 
		affine[0] = m.m11();
		affine[1] = m.m12();
		affine[2] = m.m21();
		affine[3] = m.m22();
		affine[4] = m.dx();
		affine[5] = m.dy();

		int alpha = int(opacity * 255 + 0.5);

		ksvg_art_rgb_texture(svp, c->renderingBuffer() + screenBBox.x() * c->nrChannels() + screenBBox.y() * c->rowStride(), screenBBox.left(), screenBBox.top(), screenBBox.right() + 1, screenBBox.bottom() + 1, c->rowStride(), c->nrChannels(), tile.image().bits(), tile.image().width(), tile.image().height(), tile.image().width() * 4, affine, ART_FILTER_NEAREST, 0L, alpha, (art_u8 *)tqmask.data());
	}
}

// vim:ts=4:noet