/*
 * Copyright 2009 Vincent Sanders <vince@simtec.co.uk>
 * Copyright 2009 Michael Drake <tlsa@netsurf-browser.org>
 *
 * This file is part of libnsfb, http://www.netsurf-browser.org/
 * Licenced under the MIT License,
 *                http://www.opensource.org/licenses/mit-license.php
 */

/** \file
 * generic plotter functions which are not depth dependant (implementation).
 */

#include <stdbool.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

#include "libnsfb.h"
#include "libnsfb_plot.h"
#include "libnsfb_plot_util.h"

#include "nsfb.h"
#include "plot.h"
#include "surface.h"

extern const nsfb_plotter_fns_t _nsfb_1bpp_plotters;
extern const nsfb_plotter_fns_t _nsfb_8bpp_plotters;
extern const nsfb_plotter_fns_t _nsfb_16bpp_plotters;
extern const nsfb_plotter_fns_t _nsfb_24bpp_plotters;
extern const nsfb_plotter_fns_t _nsfb_32bpp_xrgb8888_plotters;
extern const nsfb_plotter_fns_t _nsfb_32bpp_xbgr8888_plotters;

static bool set_clip(nsfb_t *nsfb, nsfb_bbox_t *clip)
{
	nsfb_bbox_t fbarea;

	/* screen area */
	fbarea.x0 = 0;
	fbarea.y0 = 0;
	fbarea.x1 = nsfb->width;
	fbarea.y1 = nsfb->height;

	if (clip == NULL) {
		nsfb->clip = fbarea;
	} else {
		if (!nsfb_plot_clip(&fbarea, clip))
			return false;

		nsfb->clip = *clip;
	}
	return true;
}

static bool get_clip(nsfb_t *nsfb, nsfb_bbox_t *clip)
{
	*clip = nsfb->clip;
	return true;
}

static bool clg(nsfb_t *nsfb, nsfb_colour_t c)
{
	return nsfb->plotter_fns->fill(nsfb, &nsfb->clip, c);
}

/**
 * Establish whether there is any value in a line's crossing.
 * (Helper function for find_span().)
 *
 * \param  x	 x coordinate of intersection
 * \param  y	 current y level
 * \param  x0	 line start coordinate
 * \param  y0	 line start coordinate
 * \param  x1	 line end coordinate
 * \param  y1	 line end coordinate
 * \return true	 if crossing has value
 *
 *                         +            |                             | /
 *                        /             |                             |/
 *   y level --      ----/----      ----+----      ----+----      ----+----
 *                      /              /              /|
 *                     +             /               / |
 *
 *                      (a)            (b)            (c)            (d)
 *
 *
 * Figure (a) values:  1     = 1  --  Odd  -- Valid crossing
 * Figure (b) values:  0 + 1 = 1  --  Odd  -- Valid crossing
 * Figure (c) values:  1 + 1 = 2  --  Even -- Not valid crossing
 * Figure (d) values:  0 + 0 = 0  --  Even -- Not valid crossing
 *
 * Vertices are shared between consecutive lines.  This function ensures that
 * the vertex point is only counted as a crossing for one of the lines by
 * only considering crossings of the top vertex.  This is what NetSurf's
 * plotter API expects.
 *
 * It's up to the client to call this function for both lines and check the
 * evenness of the total.
 */
static bool establish_crossing_value(int x, int y, int x0, int y0,
		int x1, int y1)
{
	bool v1 = (x == x0 && y == y0); /* whether we're crossing 1st vertex */
	bool v2 = (x == x1 && y == y1); /* whether we're crossing 2nd vertex */

	if ((v1 && (y0 < y1)) || (v2 && (y1 < y0))) {
		/* crossing top vertex */
		return true;
	} else if (!v1 && !v2) {
		/* Intersection with current y level is not at a vertex.
		 * Normal crossing. */
		return true;
	}
	return false;
}


/**
 * Find first filled span along horizontal line at given coordinate
 *
 * \param  p	 array of polygon vertices (x1, y1, x2, y2, ... , xN, yN)
 * \param  n	 number of polygon vertex values (N * 2)
 * \param  x	 current position along current scan line
 * \param  y	 position of current scan line
 * \param  x0	 updated to start of filled area
 * \param  x1	 updated to end of filled area
 * \return true	 if an intersection was found
 */
static bool find_span(const int *p, int n, int x, int y, int *x0, int *x1)
{
	enum {
		NO_MIN = INT_MIN + 1,
		NO_MAX = INT_MAX - 1,
	};
	int i;
	int p_x0, p_y0;
	int p_x1, p_y1;
	int x0_min, x1_min;
	int x_new;
	unsigned int x0c, x1c; /* counters for crossings at span end points */
	bool crossing_value;
	bool found_span_start = false;

	x0_min = x1_min = NO_MIN;
	x0c = x1c = 0;

	/* search row for next span, returning it if one exists */
	do {
		/* reset endpoint info, if valid span endpoints not found */
		if (!found_span_start) {
			*x0 = NO_MAX;
		}
		*x1 = NO_MAX;

		/* search all lines in polygon */
		for (i = 0; i < n; i = i + 2) {
			/* get line endpoints */
			if (i != n - 2) {
				/* not the last line */
				p_x0 = p[i];		p_y0 = p[i + 1];
				p_x1 = p[i + 2];	p_y1 = p[i + 3];
			} else {
				/* last line; 2nd endpoint is first vertex */
				p_x0 = p[i];		p_y0 = p[i + 1];
				p_x1 = p[0];		p_y1 = p[1];
			}
			/* ignore horizontal lines */
			if (p_y0 == p_y1)
				continue;

			/* ignore lines that don't cross this y level */
			if ((y < p_y0 && y < p_y1) || (y > p_y0 && y > p_y1))
				continue;

			if (p_x0 == p_x1) {
				/* vertical line, x is constant */
				x_new = p_x0;
			} else {
				/* find crossing (intersection of this line and
				 * current y level) */
				int num = (y - p_y0) * (p_x1 - p_x0);
				int den = (p_y1 - p_y0);

				/* To round to nearest (rather than down)
				 * half the denominator is either added to
				 * or subtracted from the numerator,
				 * depending on whether the numerator and
				 * denominator have the same sign. */
				num = ((num < 0) == (den < 0)) ?
						num + (den / 2) :
						num - (den / 2);
				x_new = p_x0 + num / den;
			}

			/* ignore crossings before current x */
			if (x_new < x ||
					(!found_span_start && x_new < x0_min) ||
					(found_span_start && x_new < x1_min))
				continue;

			crossing_value = establish_crossing_value(x_new, y,
					p_x0, p_y0, p_x1, p_y1);

			/* set nearest intersections as filled area endpoints */
			if (!found_span_start &&
				x_new < *x0 && crossing_value) {
				/* nearer than first endpoint */
				*x1 = *x0;
				x1c = x0c;
				*x0 = x_new;
				x0c = 1;
			} else if (!found_span_start &&
					x_new == *x0 && crossing_value) {
				/* same as first endpoint */
				x0c++;
			} else if (x_new < *x1 && crossing_value) {
				/* nearer than second endpoint */
				*x1 = x_new;
				x1c = 1;
			} else if (x_new == *x1 && crossing_value) {
				/* same as second endpoint */
				x1c++;
			}
		}
		/* check whether the span endpoints have been found */
		if (!found_span_start && x0c % 2 == 1) {
			/* valid fill start found */
			found_span_start = true;

		}
		if (x1c % 2 == 1) {
			/* valid fill endpoint found */
			if (!found_span_start) {
				/* not got a start yet; use this as start */
				found_span_start = true;
				x0c = x1c;
				*x0 = *x1;
			} else {
				/* got valid end of span */
				return true;
			}
		}
		/* if current positions aren't valid endpoints, set new
		 * minimums after current positions */
		if (!found_span_start)
			x0_min = *x0 + 1;
		x1_min = *x1 + 1;

	} while (*x1 != NO_MAX);

	/* no spans found */
	return false;
}


/**
 * Plot a polygon
 *
 * \param  nsfb	 framebuffer context
 * \param  p	 array of polygon vertices (x1, y1, x2, y2, ... , xN, yN)
 * \param  n	 number of polygon vertices (N)
 * \param  c	 fill colour
 * \return true	 if no errors
 */
static bool polygon(nsfb_t *nsfb, const int *p, unsigned int n, nsfb_colour_t c)
{
	int poly_x0, poly_y0; /* Bounding box top left corner */
	int poly_x1, poly_y1; /* Bounding box bottom right corner */
	int i, j; /* indexes */
	int x0, x1; /* filled span extents */
	int y; /* current y coordinate */
	int y_max; /* bottom of plot area */
	nsfb_bbox_t fline;
	nsfb_plot_pen_t pen;

	/* find no. of vertex values */
	int v = n * 2;

	/* Can't plot polygons with 2 or fewer vertices */
	if (n <= 2)
		return true;

	pen.stroke_colour = c;

	/* Find polygon bounding box */
	poly_x0 = poly_x1 = *p;
	poly_y0 = poly_y1 = p[1];
	for (i = 2; i < v; i = i + 2) {
		j = i + 1;
		if (p[i] < poly_x0)
			poly_x0 = p[i];
		else if (p[i] > poly_x1)
			poly_x1 = p[i];
		if (p[j] < poly_y0)
			poly_y0 = p[j];
		else if (p[j] > poly_y1)
			poly_y1 = p[j];
	}

	/* Don't try to plot it if it's outside the clip rectangle */
	if (nsfb->clip.y1 < poly_y0 ||
			nsfb->clip.y0 > poly_y1 ||
			nsfb->clip.x1 < poly_x0 ||
			nsfb->clip.x0 > poly_x1)
		return true;

	/* Find the top of the important area */
	if (poly_y0 > nsfb->clip.y0)
		y = poly_y0;
	else
		y = nsfb->clip.y0;

	/* Find the bottom of the important area */
	if (poly_y1 < nsfb->clip.y1)
		y_max = poly_y1;
	else
		y_max = nsfb->clip.y1;

	for (; y < y_max; y++) {
		x1 = poly_x0 - 1;
		/* For each row */
		while (find_span(p, v, x1 + 1, y, &x0, &x1)) {
			/* don't draw anything outside clip region */
			if (x1 < nsfb->clip.x0)
				continue;
			else if (x0 < nsfb->clip.x0)
				x0 = nsfb->clip.x0;
			if (x0 > nsfb->clip.x1)
				break;
			else if (x1 > nsfb->clip.x1)
				x1 = nsfb->clip.x1;

			fline.x0 = x0;
			fline.y0 = y;
			fline.x1 = x1;
			fline.y1 = y;

			/* draw this filled span on current row */
			nsfb->plotter_fns->line(nsfb, 1, &fline, &pen);

			/* don't look for more spans if already at end of clip
			 * region or polygon */
			if (x1 == nsfb->clip.x1 || x1 == poly_x1)
				break;
		}
	}
	return true;
}

static bool
rectangle(nsfb_t *nsfb, nsfb_bbox_t *rect,
	  int line_width, nsfb_colour_t c,
	  bool dotted, bool dashed)
{
	nsfb_bbox_t side[4];
	nsfb_plot_pen_t pen;

	pen.stroke_colour = c;
	pen.stroke_width = line_width;
	if (dotted || dashed) {
		pen.stroke_type = NFSB_PLOT_OPTYPE_PATTERN;
	} else {
		pen.stroke_type = NFSB_PLOT_OPTYPE_SOLID;
	}

	side[0] = *rect;
	side[1] = *rect;
	side[2] = *rect;
	side[3] = *rect;

	side[0].y1 = side[0].y0;
	side[1].y0 = side[1].y1;
	side[2].x1 = side[2].x0;
	side[3].x0 = side[3].x1;

	return nsfb->plotter_fns->line(nsfb, 4, side, &pen);
}

/* plotter routine for ellipse points */
static void
ellipsepoints(nsfb_t *nsfb, int cx, int cy, int x, int y, nsfb_colour_t c)
{
	nsfb->plotter_fns->point(nsfb, cx + x, cy + y, c);
	nsfb->plotter_fns->point(nsfb, cx - x, cy + y, c);
	nsfb->plotter_fns->point(nsfb, cx + x, cy - y, c);
	nsfb->plotter_fns->point(nsfb, cx - x, cy - y, c);
}

static void
ellipsefill(nsfb_t *nsfb, int cx, int cy, int x, int y, nsfb_colour_t c)
{
	nsfb_bbox_t fline[2];
	nsfb_plot_pen_t pen;

	pen.stroke_colour = c;

	fline[0].x0 = fline[1].x0 = cx - x;
	fline[0].x1 = fline[1].x1 = cx + x;
	fline[0].y0 = fline[0].y1 = cy + y;
	fline[1].y0 = fline[1].y1 = cy - y;

	nsfb->plotter_fns->line(nsfb, 2, fline, &pen);

}

#define ROUND(a) ((int)(a+0.5))

static bool
ellipse_midpoint(nsfb_t *nsfb,
		 int cx,
		 int cy,
		 int rx,
		 int ry,
		 nsfb_colour_t c,
		 void (ellipsefn)(nsfb_t *nsfb, int cx, int cy, int x, int y, nsfb_colour_t c))
{
	int rx2 = rx * rx;
	int ry2 = ry * ry;
	int tworx2 = 2 * rx2;
	int twory2 = 2 * ry2;
	int p;
	int x = 0;
	int y = ry;
	int px = 0;
	int py = tworx2 * y;

	ellipsefn(nsfb, cx, cy, x, y, c);

	/* region 1 */
	p = ROUND(ry2 - (rx2 * ry) + (0.25 * rx2));
	while (px < py) {
		x++;
		px += twory2;
		if (p <0) {
			p+=ry2 + px;
		} else {
			y--;
			py -= tworx2;
			p+=ry2 + px - py;
		}
		ellipsefn(nsfb, cx, cy, x, y, c);
	}

	/* region 2 */
	p = ROUND(ry2*(x+0.5)*(x+0.5) + rx2*(y-1)*(y-1) - rx2*ry2);
	while (y > 0) {
		y--;
		py -= tworx2;
		if (p > 0) {
			p+=rx2 - py;
		} else {
			x++;
			px += twory2;
			p+=rx2 - py + px;
		}
		ellipsefn(nsfb, cx, cy, x, y, c);
	}
	return true;
}


/* plotter routine for 8way circle symetry */
static void
circlepoints(nsfb_t *nsfb, int cx, int cy, int x, int y, nsfb_colour_t c)
{
	nsfb->plotter_fns->point(nsfb, cx + x, cy + y, c);
	nsfb->plotter_fns->point(nsfb, cx - x, cy + y, c);
	nsfb->plotter_fns->point(nsfb, cx + x, cy - y, c);
	nsfb->plotter_fns->point(nsfb, cx - x, cy - y, c);
	nsfb->plotter_fns->point(nsfb, cx + y, cy + x, c);
	nsfb->plotter_fns->point(nsfb, cx - y, cy + x, c);
	nsfb->plotter_fns->point(nsfb, cx + y, cy - x, c);
	nsfb->plotter_fns->point(nsfb, cx - y, cy - x, c);
}

static void
circlefill(nsfb_t *nsfb, int cx, int cy, int x, int y, nsfb_colour_t c)
{
	nsfb_bbox_t fline[4];
	nsfb_plot_pen_t pen;

	pen.stroke_colour = c;

	fline[0].x0 = fline[1].x0 = cx - x;
	fline[0].x1 = fline[1].x1 = cx + x;
	fline[0].y0 = fline[0].y1 = cy + y;
	fline[1].y0 = fline[1].y1 = cy - y;

	fline[2].x0 = fline[3].x0 = cx - y;
	fline[2].x1 = fline[3].x1 = cx + y;
	fline[2].y0 = fline[2].y1 = cy + x;
	fline[3].y0 = fline[3].y1 = cy - x;

	nsfb->plotter_fns->line(nsfb, 4, fline, &pen);
}

static bool circle_midpoint(nsfb_t *nsfb,
		int cx,
		int cy,
		int r,
		nsfb_colour_t c,
		void (circfn)(nsfb_t *nsfb, int cx, int cy, int x, int y, nsfb_colour_t c))
{
	int x = 0;
	int y = r;
	int p = 1 - r;

	circfn(nsfb, cx, cy, x, y, c);
	while (x < y) {
		x++;
		if (p < 0) {
			p += 2 * x + 1;
		} else {
			y--;
			p += 2 * (x - y) + 1;
		}
		circfn(nsfb, cx, cy, x, y, c);
	}
	return true;
}

static bool ellipse(nsfb_t *nsfb, nsfb_bbox_t *ellipse, nsfb_colour_t c)
{
	int width = (ellipse->x1 - ellipse->x0)>>1;
	int height = (ellipse->y1 - ellipse->y0)>>1;

	if (width == height) {
		/* circle */
		return circle_midpoint(nsfb, ellipse->x0 + width,
				ellipse->y0 + height, width, c, circlepoints);
	} else {
		return ellipse_midpoint(nsfb, ellipse->x0 + width,
				ellipse->y0 + height, width, height, c,
				ellipsepoints);
	}
}

static bool ellipse_fill(nsfb_t *nsfb, nsfb_bbox_t *ellipse, nsfb_colour_t c)
{
	int width = (ellipse->x1 - ellipse->x0) >> 1;
	int height = (ellipse->y1 - ellipse->y0) >> 1;

	if (width == height) {
		/* circle */
		return circle_midpoint(nsfb, ellipse->x0 + width,
				ellipse->y0 + height, width, c, circlefill);
	} else {
		return ellipse_midpoint(nsfb, ellipse->x0 + width,
				ellipse->y0 + height, width, height, c,
				ellipsefill);
	}
}



/* copy an area of surface from one location to another.
 *
 * @warning This implementation is woefully incomplete!
 */
static bool
copy(nsfb_t *nsfb, nsfb_bbox_t *srcbox, nsfb_bbox_t *dstbox)
{
	int srcx = srcbox->x0;
	int srcy = srcbox->y0;
	int dstx = dstbox->x0;
	int dsty = dstbox->y0;
	int width = dstbox->x1 - dstbox->x0;
	int height = dstbox->y1 - dstbox->y0;
	uint8_t *srcptr;
	uint8_t *dstptr;
	int hloop;
	nsfb_bbox_t allbox;

	nsfb_plot_add_rect(srcbox, dstbox, &allbox);

	nsfb->surface_rtns->claim(nsfb, &allbox);

	srcptr = (nsfb->ptr +
			  (srcy * nsfb->linelen) +
			  ((srcx * nsfb->bpp) / 8));

	dstptr = (nsfb->ptr +
			  (dsty * nsfb->linelen) +
			  ((dstx * nsfb->bpp) / 8));


	if (width == nsfb->width) {
		/* take shortcut and use memmove */
		memmove(dstptr, srcptr, (width * height * nsfb->bpp) / 8);
	} else {
		if (srcy > dsty) {
			for (hloop = height; hloop > 0; hloop--) {
				memmove(dstptr, srcptr, (width * nsfb->bpp) / 8);
				srcptr += nsfb->linelen;
				dstptr += nsfb->linelen;
			}
		} else {
			srcptr += height * nsfb->linelen;
			dstptr += height * nsfb->linelen;
			for (hloop = height; hloop > 0; hloop--) {
				srcptr -= nsfb->linelen;
				dstptr -= nsfb->linelen;
				memmove(dstptr, srcptr, (width * nsfb->bpp) / 8);
			}
		}
	}

	nsfb->surface_rtns->update(nsfb, dstbox);

	return true;
}



static bool arc(nsfb_t *nsfb, int x, int y, int radius, int angle1,
		int angle2, nsfb_colour_t c)
{
	nsfb=nsfb;
	x = x;
	y = y;
	radius = radius;
	c = c;
	angle1=angle1;
	angle2=angle2;
	return true;
}

#define N_SEG 30

static int
cubic_points(unsigned int pointc,
			 nsfb_point_t *point,
			 nsfb_bbox_t *curve,
			 nsfb_point_t *ctrla,
			 nsfb_point_t *ctrlb)
{
	unsigned int seg_loop;
	double t;
	double one_minus_t;
	double a;
	double b;
	double c;
	double d;
	double x;
	double y;
	int cur_point;

	point[0].x = curve->x0;
	point[0].y = curve->y0;
	cur_point = 1;
	pointc--;

	for (seg_loop = 1; seg_loop < pointc; ++seg_loop) {
		t = (double)seg_loop / (double)pointc;

		one_minus_t = 1.0 - t;

		a = one_minus_t * one_minus_t * one_minus_t;
		b = 3.0 * t * one_minus_t * one_minus_t;
		c = 3.0 * t * t * one_minus_t;
		d = t * t * t;

		x = a * curve->x0 + b * ctrla->x + c * ctrlb->x + d * curve->x1;
		y = a * curve->y0 + b * ctrla->y + c * ctrlb->y + d * curve->y1;

		point[cur_point].x = x;
		point[cur_point].y = y;
		if ((point[cur_point].x != point[cur_point - 1].x) ||
				(point[cur_point].y != point[cur_point - 1].y))
			cur_point++;
	}

	point[cur_point].x = curve->x1;
	point[cur_point].y = curve->y1;
	if ((point[cur_point].x != point[cur_point - 1].x) ||
			(point[cur_point].y != point[cur_point - 1].y))
		cur_point++;

	return cur_point;
}

/* calculate a series of points which describe a quadratic bezier spline.
 *
 * fills an array of points with values describing a quadratic curve. Both the
 * start and end points are included as the first and last points
 * respectively. Only if the next point on the curve is different from its
 * predecessor is the point added which ensures points for the same position
 * are not repeated.
 */
static int
quadratic_points(unsigned int pointc,
				 nsfb_point_t *point,
				 nsfb_bbox_t *curve,
				 nsfb_point_t *ctrla)
{
	unsigned int seg_loop;
	double t;
	double one_minus_t;
	double a;
	double b;
	double c;
	double x;
	double y;
	int cur_point;

	point[0].x = curve->x0;
	point[0].y = curve->y0;
	cur_point = 1;
	pointc--; /* we have added the start point, one less point in the curve */

	for (seg_loop = 1; seg_loop < pointc; ++seg_loop) {
		t = (double)seg_loop / (double)pointc;

		one_minus_t = 1.0 - t;

		a = one_minus_t * one_minus_t;
		b = 2.0 * t * one_minus_t;
		c = t * t;

		x = a * curve->x0 + b * ctrla->x + c * curve->x1;
		y = a * curve->y0 + b * ctrla->y + c * curve->y1;

		point[cur_point].x = x;
		point[cur_point].y = y;
		if ((point[cur_point].x != point[cur_point - 1].x) ||
				(point[cur_point].y != point[cur_point - 1].y))
			cur_point++;
	}

	point[cur_point].x = curve->x1;
	point[cur_point].y = curve->y1;
	if ((point[cur_point].x != point[cur_point - 1].x) ||
			(point[cur_point].y != point[cur_point - 1].y))
		cur_point++;

	return cur_point;
}

static bool
polylines(nsfb_t *nsfb,
		  int pointc,
		  const nsfb_point_t *points,
		  nsfb_plot_pen_t *pen)
{
	int point_loop;
	nsfb_bbox_t line;

	if (pen->stroke_type != NFSB_PLOT_OPTYPE_NONE) {
		for (point_loop = 0; point_loop < (pointc - 1); point_loop++) {
			line = *(nsfb_bbox_t *)&points[point_loop];
			nsfb->plotter_fns->line(nsfb, 1, &line, pen);
		}
	}
	return true;
}



static bool
quadratic(nsfb_t *nsfb,
		  nsfb_bbox_t *curve,
		  nsfb_point_t *ctrla,
		  nsfb_plot_pen_t *pen)
{
	nsfb_point_t points[N_SEG];

	if (pen->stroke_type == NFSB_PLOT_OPTYPE_NONE)
		return false;

	return polylines(nsfb, quadratic_points(N_SEG, points, curve, ctrla),
			points, pen);
}

static bool
cubic(nsfb_t *nsfb,
	  nsfb_bbox_t *curve,
	  nsfb_point_t *ctrla,
	  nsfb_point_t *ctrlb,
	  nsfb_plot_pen_t *pen)
{
	nsfb_point_t points[N_SEG];

	if (pen->stroke_type == NFSB_PLOT_OPTYPE_NONE)
		return false;

	return polylines(nsfb, cubic_points(N_SEG, points, curve, ctrla,ctrlb),
			points, pen);
}


static bool
path(nsfb_t *nsfb, int pathc, nsfb_plot_pathop_t *pathop, nsfb_plot_pen_t *pen)
{
	int path_loop;
	nsfb_point_t *pts;
	nsfb_point_t *curpt;
	int ptc = 0;
	nsfb_bbox_t curve;
	nsfb_point_t ctrla;
	nsfb_point_t ctrlb;
	int added_count = 0;
	int bpts;

	/* count the verticies in the path and add N_SEG extra for curves */
	for (path_loop = 0; path_loop < pathc; path_loop++) {
		ptc++;
		if ((pathop[path_loop].operation == NFSB_PLOT_PATHOP_QUAD) ||
				(pathop[path_loop].operation ==
				 NFSB_PLOT_PATHOP_CUBIC))
			ptc += N_SEG;
	}

	/* allocate storage for the vertexes */
	curpt = pts = malloc(ptc * sizeof(nsfb_point_t));
	if (curpt == NULL) {
		return false;
	}

	for (path_loop = 0; path_loop < pathc; path_loop++) {
		switch (pathop[path_loop].operation) {
		case NFSB_PLOT_PATHOP_QUAD:
			curpt-=2;
			added_count -= 2;
			curve.x0 = pathop[path_loop - 2].point.x;
			curve.y0 = pathop[path_loop - 2].point.y;
			ctrla.x = pathop[path_loop - 1].point.x;
			ctrla.y = pathop[path_loop - 1].point.y;
			curve.x1 = pathop[path_loop].point.x;
			curve.y1 = pathop[path_loop].point.y;
			bpts = quadratic_points(N_SEG, curpt, &curve, &ctrla);
			curpt += bpts;
			added_count += bpts;
			break;

		case NFSB_PLOT_PATHOP_CUBIC:
			curpt-=3;
			added_count -=3;
			curve.x0 = pathop[path_loop - 3].point.x;
			curve.y0 = pathop[path_loop - 3].point.y;
			ctrla.x = pathop[path_loop - 2].point.x;
			ctrla.y = pathop[path_loop - 2].point.y;
			ctrlb.x = pathop[path_loop - 1].point.x;
			ctrlb.y = pathop[path_loop - 1].point.y;
			curve.x1 = pathop[path_loop].point.x;
			curve.y1 = pathop[path_loop].point.y;
			bpts = cubic_points(N_SEG, curpt, &curve, &ctrla, &ctrlb);
			curpt += bpts;
			added_count += bpts;
			break;

		default:
			*curpt = pathop[path_loop].point;
			curpt++;
			added_count ++;
			break;
		}
	}

	if (pen->fill_type != NFSB_PLOT_OPTYPE_NONE) {
		polygon(nsfb, (int *)pts, added_count, pen->fill_colour);
	}

	if (pen->stroke_type != NFSB_PLOT_OPTYPE_NONE) {
		polylines(nsfb, added_count, pts, pen);
	}

	free(pts);

	return true;
}

bool select_plotters(nsfb_t *nsfb)
{
	const nsfb_plotter_fns_t *table = NULL;

	switch (nsfb->format) {

	case NSFB_FMT_XBGR8888: /* 32bpp Unused Blue Green Red */
	case NSFB_FMT_ABGR8888: /* 32bpp Alpha Blue Green Red */
		table = &_nsfb_32bpp_xbgr8888_plotters;
		nsfb->bpp = 32;
		break;

	case NSFB_FMT_XRGB8888: /* 32bpp Unused Red Green Blue */
	case NSFB_FMT_ARGB8888: /* 32bpp Alpha Red Green Blue */
		table = &_nsfb_32bpp_xrgb8888_plotters;
		nsfb->bpp = 32;
		break;


	case NSFB_FMT_RGB888: /* 24 bpp Alpha Red Green Blue */
#ifdef ENABLE_24_BPP
		table = &_nsfb_24bpp_plotters;
		nsfb->bpp = 24;
		break;
#else
		return false;
#endif

	case NSFB_FMT_ARGB1555: /* 16 bpp 555 */ 
	case NSFB_FMT_RGB565: /* 16 bpp 565 */ 
		table = &_nsfb_16bpp_plotters;
		nsfb->bpp = 16;
		break;

	case NSFB_FMT_I8: /* 8bpp indexed */
		table = &_nsfb_8bpp_plotters;
		nsfb->bpp = 8;
		break;

	case NSFB_FMT_I1: /* black and white */
#ifdef ENABLE_1_BPP
		table = &_nsfb_1bpp_plotters; 
		nsfb->bpp = 1
		break;
#else
		return false;
#endif

	case NSFB_FMT_ANY: /* No specific format - use surface default */
	default:
		return false;
	}

	if (nsfb->plotter_fns != NULL) {
		free(nsfb->plotter_fns);
	}

	nsfb->plotter_fns = calloc(1, sizeof(nsfb_plotter_fns_t));
	if (nsfb->plotter_fns == NULL) {
		return false;
	}

	memcpy(nsfb->plotter_fns, table, sizeof(nsfb_plotter_fns_t));

	/* set the generics */
	nsfb->plotter_fns->clg = clg;
	nsfb->plotter_fns->set_clip = set_clip;
	nsfb->plotter_fns->get_clip = get_clip;
	nsfb->plotter_fns->polygon = polygon;
	nsfb->plotter_fns->rectangle = rectangle;
	nsfb->plotter_fns->ellipse = ellipse;
	nsfb->plotter_fns->ellipse_fill = ellipse_fill;
	nsfb->plotter_fns->copy = copy;
	nsfb->plotter_fns->arc = arc;
	nsfb->plotter_fns->quadratic = quadratic;
	nsfb->plotter_fns->cubic = cubic;
	nsfb->plotter_fns->path = path;
	nsfb->plotter_fns->polylines = polylines;

	/* set default clip rectangle to size of framebuffer */
	nsfb->clip.x0 = 0;
	nsfb->clip.y0 = 0;
	nsfb->clip.x1 = nsfb->width;
	nsfb->clip.y1 = nsfb->height;

	return true;
}

/*
 * Local variables:
 *  c-basic-offset: 4
 *  tab-width: 8
 * End:
 */
