/*
 *  TEX Device Driver
 *  tpic.c:		tpic specials (version 2.2) support routines
 *  			Based on the specifications included in transfig package
 *  			by Oh-Yeah?		15 July 1993
 *  			Modified (dashed, dotted, spline) by A. Nakabayashi
 *  			Modified by I. Matsuda
 */

#ifndef NOTPIC	/* include the whole file */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define _DEF_STDIO_H_
#include "dd.h"
#include "err.h"

#define DEFAULT_PENSIZE 5	/* in milli-inches */

#define SIGNED_PIXEL int	/* signed pixel type for raster graphics */
#define TPIC_MI long	/* type of tpic milli-inches (x, y) */
 /* #define TWO_PAI_MORE 6.2833 *//* for drawing an arc: 2pai + eps */
#define TWO_PAI_LESS 6.2831	/* for drawing an arc: 2pai - eps */

static char token_sep[] = " ";

#ifndef NOTPIC_EXTENSION
/* bitblt.c */
void alter_bitblt_rt(BOOL flag);
void set_rot_matrix(PIXEL origin_x, PIXEL origin_y, double angle);

#endif

/* bitmap.c */
void write_rule(PIXEL h, PIXEL v, PIXEL a, PIXEL b);
void init_tile(double grayscale);	/* initialize overriding for tiles */
void fin_tile(void);	/* finish tiling. recover original data */
void write_tile(PIXEL h, PIXEL v, PIXEL a, PIXEL b);

/* buffer.c */
char *marea(unsigned int size);

/* pret.c */
PIXEL get_current_x(void);
PIXEL get_current_y(void);

/* sptopxl.asm or size.c (prtsize.c) */
PIXEL sptopixel(SCALED_PT sp);
PIXEL vtopixel(SCALED_PT sp);

#define FILL_EDGE 0x01	/* pixel point status flags */
#define VISIBLE   0x02

typedef struct {
	PIXEL x, y;
} pixelpoint;

typedef struct pixel_link_st {
	PIXEL p;
	unsigned char status;
	struct pixel_link_st *next;
} pixel_link;

/* called by initialize() in init.c, prtinit.c */
void tpic_init(PIXEL buf_height);
/* alloc y-bucket[vmax] at the beginning */

/* called by xxx1() in pret.c. interface is func ptr (tpic_special) */
BOOL tpic_pret(char *special);	/* interpret special. Ret TRUE if found */
BOOL tpic_ignore(char *dummy);	/* ignore special. Ret FALSE, always */

/* ptr to either function. activated when tpic_init() is called */
BOOL (*tpic_special) (char *special) = tpic_ignore;

void tpic_pn(char *s);
void tpic_pa(char *s);
void tpic_fp(char *dummy);
void tpic_ip(char *dummy);
void tpic_da(char *s);
void tpic_dt(char *s);
void tpic_sp(char *s);
PIXEL tpic_interval(double inch);
/* common part for calc an interval length */
void tpic_ar(char *s);
void tpic_ia(char *s);
void tpic_ar_and_ia(TPIC_MI x, TPIC_MI y, TPIC_MI rx, TPIC_MI ry,
					double start, double end);	/* common part */
void tpic_sh(char *s);
void tpic_wh(char *dummy);
void tpic_bk(char *dummy);
void tpic_tx(char *dummy);

#ifndef NOTPIC_EXTENSION
void tpic_rt(char *s);

#endif
void set_grayscale(double gray);
double get_grayscale(void);
void flush_line(void);
void flush_spline(void);
void flush_arc(pixelpoint origin, pixelpoint radius, double start, double end);
void flush_raster(void);
void rasterize_line(pixelpoint start, pixelpoint end, BOOL mask_head,
					BOOL mask_tail);
void draw_line(pixelpoint st, pixelpoint ed, PIXEL thick);
void rasterize_arc(pixelpoint origin, pixelpoint radius, double start,
				   double end);
void rasterize_quadarc(pixelpoint origin, pixelpoint radius, pixelpoint init,
					   pixelpoint fin, int plane);

#ifndef TPIC_OLD
void set_visible_discrete(PIXEL visible, PIXEL invisible);
void adjust_visible_discrete(pixelpoint start, pixelpoint end);

#endif

void set_visible(PIXEL visible, PIXEL invisible);
void init_visible(BOOL is_discrete);	/* initialize visible pattern */
void append_visible(PIXEL visible, PIXEL invisible, int repeat);
BOOL is_visible(void);
BOOL is_visible_discrete(void);
void raster_draw(void);	/* draw pixels defined in stack */
void set_pensize(TPIC_MI pensize);
PIXEL get_h_width(void);	/* for line thickness */
PIXEL get_v_width(void);	/* for line thickness */
void write_point(PIXEL x, PIXEL y);
void raster_fill(void);
void push_path(pixelpoint p);	/* push path p */
pixelpoint pop_path(void);	/* just take out */
void flush_path(void);	/* flush path */
int get_path_n(void);
void set_path_n(int);
BOOL is_path_closed(void);

#ifndef TPIC_NSPL
void spl_table(void);
void spl_clean_table(void);	/* clean table */
pixelpoint spl_get(double index);
void spl_each_table(PIXEL x[], PIXEL y[], int n, double sder[]);
void spl_each_periodic(PIXEL x[], PIXEL y[], int n, double sder[]);
double spl_each_get(PIXEL x[], PIXEL y[], int n, double sder[],
					double xint, int i);
void flush_spline_old(void);
void tpic_bz(char *s);
/* init, prtinit */
extern  f_tpic_turn_on;	/* default is 1 */
#endif

void push_point(PIXEL x, PIXEL y, BOOL fill_edge, BOOL visible);
void init_bucket(PIXEL buf_height);	/* make buckets */
void clean_bucket(void);
pixel_link **get_bucket(PIXEL y);
PIXEL get_bucket_min(void);
PIXEL get_bucket_max(void);
PIXEL get_vmax(void);

#ifndef NOTPIC_EXTENSION
pixel_link **get_extra_bucket(PIXEL y);
void clean_extra_bucket(void);

#endif

pixel_link *push_pixel(PIXEL p);
void clear_pixel_stack(void);
void get_current(void);	/* get current point (as pixelpoint) */
pixelpoint rel_to_abs(pixelpoint p);
pixelpoint mi_to_pixel(TPIC_MI x, TPIC_MI y);	/* milli-inches to pixel */

#ifdef VFTPIC
/* called by draw_character() in vfont.c */
void set_grayscale(double gray);
void rasterize_line(pixelpoint start, pixelpoint end, BOOL mask_head,
					BOOL mask_tail);
void set_visible(PIXEL visible, PIXEL invisible);
void raster_draw(void);	/* draw pixels defined in stack */
void set_pensize(TPIC_MI pensize);
void raster_fill(void);
void clean_bucket(void);
void freeze_tpic_status(void);
void melt_tpic_status(void);

#endif

void tpic_init(PIXEL buf_height)
{	   /* alloc y-bucket[vmax] at the beginning */
	ENTER("tpic_init");
	tpic_special = tpic_pret;	/* activate interpretation */
	init_bucket(buf_height);	/* make vmax y-buckets */
	set_pensize((TPIC_MI) DEFAULT_PENSIZE);
	END();
}

BOOL tpic_pret(char *special)
{	   /* interpret special. Ret TRUE if found */
	static struct {
		char str[2];
		void (*func) (char *);
	} *cmd_ptr, commands[] = {
	{"pn", tpic_pn},
	{"pa", tpic_pa},
	{"fp", tpic_fp},
	{"ip", tpic_ip},
	{"da", tpic_da},
	{"dt", tpic_dt},
	{"sp", tpic_sp},
	{"ar", tpic_ar},
	{"ia", tpic_ia},
	{"sh", tpic_sh},
	{"wh", tpic_wh},
	{"bk", tpic_bk},
	{"tx", tpic_tx},
#ifndef NOTPIC_EXTENSION
	{"rt", tpic_rt},	/* this is UNOFFICIAL, not portable */
#endif
#ifdef TPIC_NSPL
	{"Bz", tpic_bz},
#endif
	{"",   NULL}};

	BOOL found = FALSE;
	char *s;

	ENTER("tpic_pret");

	cmd_ptr = commands;			/* point to top */

	while (*(s = cmd_ptr->str) != NULL) {
		if (special[0] == s[0] && special[1] == s[1]) {
			(cmd_ptr->func) (special + 2);	/* forward str */
			found = TRUE;		/* found */
			break;
		}
		cmd_ptr++;
	}

	RETURN(found);
}

BOOL tpic_ignore(char *dummy)
{	   /* ignore special. Ret FALSE, always */
	ENTER("tpic_ignore");

	/*	error( WARNING, "tpic special encountered but skipped ... " ); */

	RETURN(FALSE);
}

/* tpic commands */

/* pn s		Sets the pen size to s milli-inches. */
void tpic_pn(char *s)
{
	ENTER("tpic_pn");
	set_pensize((TPIC_MI) atol(s));
	END();
}

/* pa x y	Add point (x,y) to a path which is being stored.
			x and y are in milli-inches. */
void tpic_pa(char *s)
{
	TPIC_MI x, y;

	ENTER("tpic_pa");
	get_current();				/* get current point */
	x = (TPIC_MI) atol(strtok(s, token_sep));
	y = (TPIC_MI) atol(strtok(NULL, token_sep));
	push_path(rel_to_abs(mi_to_pixel(x, y)));
	END();
}

/* fp	Flush (using the current pen size) a previously defined path.
		The number of path elements is reset to zero.  If shading has been
		specified and path is closed, the area should be shaded. */
void tpic_fp(char *dummy)
{
	int tmp_path_n;

	ENTER("tpic_fp");
	if (get_grayscale() == 0) {
		tmp_path_n = get_path_n();
		set_visible(0, 1);		/* all invisible */
		flush_line();
		set_path_n(tmp_path_n);
	}
	set_visible(1, 0);			/* all visible */
	flush_line();
	flush_path();
	END();
}

/* ip	Same as fp except that the path is not drawn.
		Shading of the region is performed if it has been specified. */
void tpic_ip(char *dummy)
{
	ENTER("tpic_ip");
	set_visible(0, 1);			/* all invisible */
	flush_line();
	flush_path();
	END();
}

/* da f		Same as fp except that the line is drawn as dashed with f inches
			per dash (f is a real number). */
/* UNOFFICIAL extension: accepts two or more real args as black and white
lengths (like Postscript setdash), and draws the pattern continuously through
the points (i.e. dashes and dots are not necessarily on the points). */
void tpic_da(char *s)
{
	PIXEL interval, next;
	char *token;
	int tmp_path_n;

	ENTER("tpic_da");
	if (get_grayscale() == 0) {
		tmp_path_n = get_path_n();
		set_visible(0, 1);		/* all invisible */
		flush_line();
		set_path_n(tmp_path_n);
	}
	interval = tpic_interval(atof(strtok(s, token_sep)));
#ifndef NOTPIC_EXTENSION
	if ((token = strtok(NULL, token_sep)) != NULL) {
		/* UNOFFICIAL: two or more args, black white [black white ...] */
		init_visible(FALSE);	/* continuous */
		next = tpic_interval(atof(token));
		append_visible(interval, next, 1);
		while ((token = strtok(NULL, token_sep)) != NULL) {
			interval = tpic_interval(atof(token));
			next = tpic_interval(atof(strtok(NULL, token_sep)));
			append_visible(interval, next, 1);
		}
	}
	else						/* standard: one arg */
#endif
#ifndef TPIC_OLD
		set_visible_discrete(interval, interval);
#else
		set_visible(interval, interval);	/* continuous */
#endif
	flush_line();
	flush_path();
	END();
}

/* dt f		Same as da except that a dotted line is drawn, with f inches
			between each dot (f is a real number). */
void tpic_dt(char *s)
{
	PIXEL interval, dotsize;
	int tmp_path_n;

	ENTER("tpic_dt");
	if (get_grayscale() == 0) {
		tmp_path_n = get_path_n();
		set_visible(0, 1);		/* all invisible */
		flush_line();
		set_path_n(tmp_path_n);
	}
	interval = tpic_interval(atof(s));
	dotsize = (get_h_width() + get_v_width()) / 2;	/* use pen size */
	dotsize = max(dotsize, 1);
#ifndef TPIC_OLD
	set_visible_discrete(dotsize, max(interval - dotsize, 1));
#else
	set_visible(dotsize, max(interval - dotsize, 1));	/* continuous */
#endif
	flush_line();
	flush_path();
	END();
}

/* sp d		Same as fp except that a spline is drawn through the points.
			The spline is guaranteed to go through only the first and last
			points, depending on the relative positions of any intermediate
			points.  d is an optional real value; if d = 0 or the argument
			is omitted, the spline is drawn normally, as a solid line.  If
			d > 0, it is dashed with d as the dashwid, else if d < 0, the
			spline is dotted with -d as the dotwid. */
void tpic_sp(char *s)
{
	double d;
	PIXEL interval;
	int tmp_path_n;

	ENTER("tpic_sp");
	if (get_grayscale() == 0) {
		tmp_path_n = get_path_n();
		set_visible(0, 1);		/* all invisible */
		flush_spline();
		set_path_n(tmp_path_n);
	}
	s = strtok(s, token_sep);
	if (s == NULL || (d = atof(s)) == 0) {	/* solid */
		set_visible(1, 0);
	}
	else if (d > 0) {			/* dashed */
		interval = tpic_interval(d);
		set_visible(interval, interval);
	}
	else {						/* dotted */
		interval = tpic_interval(-d);
		set_visible(1, max(interval - 1, 1));
	}
	flush_spline();
	flush_path();
	END();
}

PIXEL tpic_interval(double inch)
{	   /* common part for calc an interval length */
	TPIC_MI mi;
	pixelpoint p;

	ENTER("tpic_interval");
	mi = (TPIC_MI) (inch * 1000);
	p = mi_to_pixel(mi, mi);	/* only horizontal value is used ... */
	RETURN(max(p.x, 1));		/* minimum value is unity */
}

/* ar x y rx ry s e		Draw an arc with center at (x,y), from starting
						angle s to ending angle e.  If it's a complete
						circle or ellipse, then rx and ry determine the
						x and y radii, respectively.  Otherwise, rx = ry
						will hold, and an arc from s to e should be drawn. */
void tpic_ar(char *s)
{
	TPIC_MI x, y, rx, ry;
	double start, end;
	ENTER("tpic_ar");

	x = (TPIC_MI) atol(strtok(s, token_sep));
	y = (TPIC_MI) atol(strtok(NULL, token_sep));
	rx = (TPIC_MI) atol(strtok(NULL, token_sep));
	ry = (TPIC_MI) atol(strtok(NULL, token_sep));
	start = atof(strtok(NULL, token_sep));
	end = atof(strtok(NULL, token_sep));
	if (get_grayscale() == 0) {
		set_visible(0, 1);		/* all invisible */
		tpic_ar_and_ia(x, y, rx, ry, start, end);
	}
	set_visible(1, 0);			/* all visible */
	tpic_ar_and_ia(x, y, rx, ry, start, end);
	END();
}

/* ia x y rx ry s e		The ia command is to ar what ip is to fp. */
void tpic_ia(char *s)
{
	TPIC_MI x, y, rx, ry;
	double start, end;
	ENTER("tpic_ia");

	x = (TPIC_MI) atol(strtok(s, token_sep));
	y = (TPIC_MI) atol(strtok(NULL, token_sep));
	rx = (TPIC_MI) atol(strtok(NULL, token_sep));
	ry = (TPIC_MI) atol(strtok(NULL, token_sep));
	start = atof(strtok(NULL, token_sep));
	end = atof(strtok(NULL, token_sep));
	set_visible(0, 1);			/* all invisible */
	tpic_ar_and_ia(x, y, rx, ry, start, end);
	END();
}

void tpic_ar_and_ia(TPIC_MI x, TPIC_MI y, TPIC_MI rx, TPIC_MI ry,
					double start, double end)
{	   /* common part */
	pixelpoint origin, radius;

	ENTER("tpic_ar_and_ia");
	get_current();				/* get current point */

	origin = rel_to_abs(mi_to_pixel(x, y));

	/*	if (fabs(end - start) < TWO_PAI_LESS) ry = rx; */

	radius = mi_to_pixel(abs(rx), abs(ry));	/* defence from neg rad (gpic) */
	radius.x = max(radius.x, 1);/* minimum value is unity */
	radius.y = max(radius.y, 1);
	flush_arc(origin, radius, start, end);
	END();
}

/* sh s		Shade the interior of the next closed figure defined with three
			or more pa commands followed by a fp or ip command, or by an ar
			or ia command.  s is a floating point number from 0 to 1.  Zero
			implies a white interior (including erasing anything underneath),
			while 1 implies black.  A value of 0.5 is a default gray shading.
			If s is not specified, a value of 0.5 should be used.  Shading
			applies to the interior only; the borders are still drawn using
			the current pen.  Pixels generated by the shading value should be
			ANDed with the pixels already in the area.  Subsequent text pixels
			should be ORed.  Thus, text followed by a sh 0 command covering
			the same area will disappear.  Any shading value greater than zero
			should add the shading to the existing text, with the text
			remaining visible.  Of course, a shading value of one will be
			completely black, so any text won't be distinguishable. */
void tpic_sh(char *s)
{
	double gray;

	ENTER("tpic_sh");
	s = strtok(s, token_sep);
	gray = (s != NULL) ? atof(s) : 0.5;	/* if no spec, take default */
	set_grayscale(gray);
	END();
}

/* wh	This is the same as 'sh 0' */
void tpic_wh(char *dummy)
{
	ENTER("tpic_wh");
	tpic_sh(" 0");
	END();
}

/* bk	This is the same as 'sh 1' */
void tpic_bk(char *dummy)
{
	ENTER("tpic_bk");
	tpic_sh(" 1");
	END();
}

/* tx	This command was used to specify a texture in shading, and very
		device-specific.  Its implementation is discouraged. */
void tpic_tx(char *dummy)
{
	ENTER("tpic_tx");
	END();						/* ignore */
}

#ifndef NOTPIC_EXTENSION

static BOOL is_rotated = FALSE;

/* rt x y f		UNOFFICIAL: Rotate everything hereafter around point (x,y) by
angle f.  x and y (integers) are in milli-inches, and f is real in radians.
rotation is not accumulative.  rt x y 0 resets to default (no rotation). */
void tpic_rt(char *s)
{
	TPIC_MI x, y;
	pixelpoint origin;
	double angle;

	ENTER("tpic_rt");
	x = (TPIC_MI) atol(strtok(s, token_sep));
	y = (TPIC_MI) atol(strtok(NULL, token_sep));
	angle = atof(strtok(NULL, token_sep));
	if (angle == 0) {			/* reset */
		alter_bitblt_rt(is_rotated = FALSE);	/* rotation off */
	}
	else {
		get_current();			/* get current point */
		origin = rel_to_abs(mi_to_pixel(x, y));
		set_rot_matrix(origin.x, origin.y, angle);
#if 0
		if(is_rotated) {			/* debugged by 松田(3 lines) */
			alter_bitblt_rt(FALSE); /* 一旦通常の trimming に戻す */
		}
#endif
		alter_bitblt_rt(is_rotated = TRUE);	/* rotation on */
	}

	END();
}

#endif

/* general functions for raster graphics */
/* See D. F. Rogers, "Procedural Elements for Computer Graphics," McGraw-Hill,
New York (1985)(セイコー訳「実践コンピュータグラフィックス」日刊工業新聞社) */

static BOOL shade_on = FALSE;
static BOOL is_closed = FALSE;
static double grayscale = 0.5;

void set_grayscale(double gray)
{
	ENTER("set_grayscale");
	shade_on = TRUE;
	grayscale = gray;
	END();
}

double get_grayscale(void)
{
	ENTER("get_grayscale");
	if (shade_on == FALSE) RETURN(-1);
	RETURN(grayscale);
}

void flush_line(void)
{
	int i, n;
	pixelpoint p1, p2;
	BOOL mask_head, mask_tail;	/* flags for masking ends of a thick line */
	BOOL is_discrete;

	ENTER("flush_line");

	if ((n = get_path_n()) >= 2) {	/* enough points, Okay */
		is_closed = is_path_closed();
		is_discrete = is_visible_discrete();
		p1 = pop_path();
		mask_head = !is_closed;	/* if closed, do not mask */
		for (i = 1; i < n; i++) {
			p2 = pop_path();
#ifndef TPIC_OLD
			if (is_discrete)
				adjust_visible_discrete(p1, p2);	/* renew */
#endif
			mask_tail = (i == n - 1) ? (!is_closed) : FALSE;
			rasterize_line(p1, p2, mask_head, mask_tail);
			p1 = p2;
			mask_head = FALSE;
		}
		flush_raster();
	}
	END();
}

#define SPL_RESOLUTION 10

/* Here's contribution by A. Nakabayashi (Thanks!) */

void flush_spline(void)
	/* Bezier curve guided by a set of straight lines. */
	/* It begins at the same place, ends at the same place, and */
	/* in between is tangent to the mid-point of each guiding line */
{
	int i, j, n;
	pixelpoint p1, p2, p3, lastp, newp;
	double w, t1, t2, t3;

	ENTER("flush_spline");

#ifndef	TPIC_NSPL
	if (f_tpic_turn_on == 2){
		flush_spline_old();
		return;
	}
#endif
	if ((n = get_path_n()) >= 3) {	/* enough points, Okay */
		is_closed = is_path_closed();
		p3 = pop_path();
		p2 = pop_path();
		lastp.x = (PIXEL)((p2.x + p3.x) / 2 + 0.5);
		lastp.y = (PIXEL)((p2.y + p3.y) / 2 + 0.5);
		rasterize_line(p3, lastp, !is_closed, FALSE);	/* head end */
		/* if closed, do not mask */
		for (i = 1; i < n - 1; i++) {
			p1 = pop_path();
			for (j = 0; j < SPL_RESOLUTION; j++) {
				w = (double)(j + 1) / SPL_RESOLUTION;
				t1 = 0.5 * w * w;
				w -= 0.5;
				t2 = 0.75 - w * w;
				w -= 0.5;
				t3 = 0.5 * w * w;
				newp.x = (PIXEL)(t1 * p1.x + t2 * p2.x + t3 * p3.x + 0.5);
				newp.y = (PIXEL)(t1 * p1.y + t2 * p2.y + t3 * p3.y + 0.5);
				rasterize_line(lastp, newp, FALSE, FALSE);
				lastp = newp;
			}
			p3 = p2;
			p2 = p1;
		}
		rasterize_line(lastp, p1, FALSE, !is_closed);	/* tail end */
		flush_raster();
	}

	END();
}

#ifndef	TPIC_NSPL
 /* Here's the old version of spline */

void flush_spline_old(void)
	/* spline goes through path points */
{
	pixelpoint p1, p2;
	int i, j, n;
	static double tick = 1.0 / SPL_RESOLUTION;
	double t;
	BOOL mask_head, mask_tail;	/* flags for masking ends of a thick line */

	ENTER("flush_spline (old)");

	if ((n = get_path_n()) >= 3) {	/* enough points, Okay */
		is_closed = is_path_closed();
		spl_table();
		t = 0.0;
		p1 = spl_get(t);
		mask_head = !is_closed;	/* if closed, do not mask */
		for (i = 0; i < n - 1; i++) {
			for (j = 0; j < SPL_RESOLUTION; j++) {
				t += tick;
				p2 = spl_get(t);
				mask_tail = (i == n - 2 && j == SPL_RESOLUTION - 1) ?
							(!is_closed) : FALSE;
				rasterize_line(p1, p2, mask_head, mask_tail);
				p1 = p2;
				mask_head = FALSE;
			}
		}
		flush_raster();
		spl_clean_table();
	}
	flush_path();
	END();
}

void tpic_bz(char *s)
{
	f_tpic_turn_on = (atoi(strtok(s, token_sep)) == 0)?2:1;
}

#endif /* end of ifndef TPIC_NSPL */

void flush_arc(pixelpoint origin, pixelpoint radius, double start, double end)
{
	ENTER("flush_arc");

	is_closed = (fabs(end - start) >= TWO_PAI_LESS) ? TRUE : FALSE;
	rasterize_arc(origin, radius, start, end);
	flush_raster();

	END();
}

void flush_raster(void)
{
	ENTER("flush_raster");

	if (shade_on && is_closed) {
		raster_fill();
		shade_on = FALSE;
	}
	raster_draw();
	clean_bucket();
	END();
}

#define sign(A) ( ((A) > 0) ? 1 : (((A) < 0) ? -1 : 0) )
#define swap(x, y, tmp) tmp = x, x = y, y = tmp

void rasterize_line(pixelpoint start, pixelpoint end, BOOL mask_head,
					BOOL mask_tail)
	/* get pixels representing a straight line by Bresenham method */
	/* Two BOOLeans are for masking ends of thick lines; otherwise */
	/* they would be a little bit too long */
	/* Note. edge selection is only necessary for filling; if speed is a
	critical problem, rewrite codes to skip it in drawing-only cases.  */
{
	PIXEL x, y, temp, *x_ptr, *y_ptr, y_prev, thick;
	SIGNED_PIXEL dx, dy, sx, sy, tems, e, i;
	BOOL edge, visible, down, f_visi;
	pixelpoint st, ed;

	ENTER("rasterize_line");

	x = start.x;
	y = start.y;
	dx = end.x - start.x;
	dy = end.y - start.y;
	sx = sign(dx);
	sy = sign(dy);
	dx = abs(dx);
	dy = abs(dy);

	down = (sy < 0) ? TRUE : FALSE;
	if (dy > dx) {				/* swap x and y */
		swap(dx, dy, tems);
		swap(sx, sy, tems);
		swap(x, y, temp);
		x_ptr = &y;				/* ptrs to actual x and y */
		y_ptr = &x;
		thick = get_h_width();
	}
	else {
		x_ptr = &x;
		y_ptr = &y;
		thick = get_v_width();
	}

	if (dx == 0) {
		/* start point == end point. just put one point and exit */
		push_point(*x_ptr, *y_ptr, FALSE, is_visible());
		END();
	}

	edge = down;
	/* if line goes up, do not count start point as edge; if goes down,
	count it.  this assures correct counting of vertices as fill edges:
	bottom vertex, 0; top vertex, 2; in-between vertex 1. */

	f_visi = FALSE;
	e = 2 * dy - dx;
	for (i = 0; i < dx; i++) {
		visible = is_visible();	/* ask 'em anyway */
		if (i == 0 && !mask_head && (dx == 0 || dy == 0)) {
			push_point(*x_ptr, *y_ptr, edge, visible);
		} else {
			push_point(*x_ptr, *y_ptr, edge, FALSE);
		}
		if (visible && !f_visi) {
			st.x = *x_ptr;
			st.y = *y_ptr;
		}
		if (!visible && f_visi) {
			ed.x = *x_ptr;
			ed.y = *y_ptr;
			draw_line(st, ed, thick);
		}
		f_visi = visible;
		y_prev = *y_ptr;
		while (e >= 0) {
			y += sy;
			e -= 2 * dx;
		}
		x += sx;
		e += 2 * dy;
		edge = (*y_ptr != y_prev) ? TRUE : FALSE;
		/* half scanline. if point goes over y + 1/2, count it as edge */
	}
	if (f_visi) {
		ed.x = *x_ptr;
		ed.y = *y_ptr;
		draw_line(st, ed, thick);
	}
	visible = is_visible();		/* ask 'em anyway */
	if (mask_tail || (dx != 0 && dy != 0)) {
		visible = FALSE;
	}
	push_point(*x_ptr, *y_ptr, edge, visible);	/* last point */

	if (down)
		push_point(*x_ptr, *y_ptr, TRUE, FALSE);
	/* tricky: if line goes down, add an extra vertex on the last point to
	assure correct fill-edge counting; ignore bottom, count top */

	END();
}

/* 太さを持った直線の rasterize */
void draw_line(pixelpoint st, pixelpoint ed, PIXEL thick)
{
	SIGNED_PIXEL xd, yd, xw, yh, i, tmp;
	SIGNED_PIXEL y0, y1, y2, y3, x0, x1, w;
	BOOL turn = FALSE;
	long d;
	ENTER("draw_line");

	xd = ed.x - st.x;
	yd = ed.y - st.y;
	if(max(st.y, ed.y) < -thick || min(st.y, ed.y) > get_vmax() + thick) {
#ifndef NOTPIC_EXTENSION
		if (!is_rotated)
#endif
			END();
	}	/* trimming */
	if (abs(xd) <= 1 && abs(yd) <= 1) {
		write_point(st.x, st.y);
		END();
	}
	if (st.x == ed.x) {
		write_rule(st.x - thick/2, max(st.y, ed.y), abs(yd) + 1, thick);
		END();
	}
	if (st.y == ed.y) {
		write_rule(min(st.x, ed.x), st.y + thick/2, thick, abs(xd) + 1);
		END();
	}
	if (abs(xd) > abs(yd)) {	/* x 軸と y 軸をひっくり返す */
		swap(xd, yd, tmp);
		swap(st.x, st.y, tmp);
		swap(ed.x, ed.y, tmp);
		turn = TRUE;
	}
	if (st.y > ed.y) {	/* 上から下の直線とみなす */
		swap(st.x, ed.x, tmp);
		swap(st.y, ed.y, tmp);
		xd = -xd;
		yd = -yd;
	}
	d = hypot(xd, yd) * 1000;
	/* x 軸を切り取る幅 */
	xw = ((long)thick * d + labs(yd) * 500) / (labs(yd) * 1000);
	if (xw <= 0) xw = 1;
	yh = ((long)thick * labs(xd) * 1000) / d;	/* 切り口の高さ */
	y0 = st.y - yh / 2;
	y1 = y0 + yh;
	y2 = ed.y - yh / 2;
	y3 = y2 + yh;
	for(i = y0; i <= y3; i++) {
		d = (long)(i - st.y) * xd;
		if (d >= 0) {
			x0 = st.x - xw/2 + (d + (yd - 1) / 2) / yd;
		} else {
			x0 = st.x - xw/2 + (d - yd / 2) / yd;
		}
		x1 = x0 + xw;
		if(i < y1) {
			w = xw * (i - y0 + 1) / (yh + 1);
			if(xd > 0) {
				x0 += xw - w;
			} else {
				x1 -= xw - w;
			}
		}
		if(i > y2) {
			w = xw * (y3 - i + 1) / (yh + 1);
			if(xd < 0) {
				x0 += xw - w;
			} else {
				x1 -= xw - w;
			}
		}
		if (x0 < x1) {
			if (turn) {
				write_rule(i, x1 - 1, x1 - x0, 1);
			} else {
				write_rule(x0, i, 1, x1 - x0);
			}
		}
	}
	END();
}

void rasterize_arc(pixelpoint origin, pixelpoint radius, double start,
				   double end)
	/* get pixels representing an arc by Bresenham method */
{
	pixelpoint top, bottom, init[4], fin[4], temp_init, temp_fin;
	PIXEL rx, ry;
	double sx, sy, ex, ey;
	BOOL is_full, is_top_to_tail = FALSE;
	int i, j, plane_init, plane_fin;

	ENTER("rasterize_arc");

	rx = radius.x;
	ry = radius.y;
	/* start on +y axis, down clockwise to +x axis (1st plane) */
	top.x = 0;
	top.y = ry;
	bottom.x = rx;
	bottom.y = 0;

	is_full = (fabs(end - start) >= TWO_PAI_LESS) ? TRUE : FALSE;
	if (is_full) {				/* full circle or ellipse */
		rasterize_quadarc(origin, radius, top, bottom, 4);	/* all plane */
	}
	else {						/* arc */

		/* fill by default (full), clockwise */
		init[0] = fin[1] = init[2] = fin[3] = bottom;
		fin[0] = init[1] = fin[2] = init[3] = top;

		sx = rx * cos(start);
		sy = ry * sin(start);
		/*		plane_init = (int)(floor(start * 4 /
			((start > 0) ? TWO_PAI_LESS : TWO_PAI_MORE)) * 1.01);
		plane_init = (unsigned int)plane_init % 4; */
		plane_init = (sy > 0) ? ((sx > 0) ? 0 : 1) :
			((sy < 0) ? ((sx < 0) ? 2 : 3) : ((sx > 0) ? 0 : 2));
		/* bound belongs to the latter region, anti-clockwise */
		ex = rx * cos(end);
		ey = ry * sin(end);
		/*		plane_fin = (int)(ceil(end * 4 /
			((end > 0) ? TWO_PAI_MORE : TWO_PAI_LESS)) * 1.01) - 1;
		plane_fin = (unsigned int)plane_fin % 4; */
		plane_fin = (ey > 0) ? ((ex >= 0) ? 0 : 1) :
			((ey < 0) ? ((ex <= 0) ? 2 : 3) : ((ex > 0) ? 3 : 1));
		/* bound belongs to the former region, anti-clockwise */

		if (plane_init == plane_fin &&
			((plane_init <= 1) ? (sx < ex) : (sx > ex))) {
			/* 0-1-2-3-0 etc */
			is_top_to_tail = TRUE;
			temp_init = init[plane_init];	/* keep */
			temp_fin = fin[plane_fin];	/* keep */
		}
		init[plane_init].x = (PIXEL)(fabs(sx) + 0.5);	/* round 'em */
		init[plane_init].y = (PIXEL)(fabs(sy) + 0.5);	/* by OkI (Thanks!) */
		fin[plane_fin].x = (PIXEL)(fabs(ex) + 0.5);
		fin[plane_fin].y = (PIXEL)(fabs(ey) + 0.5);

		i = plane_init;

		if (is_top_to_tail) {	/* 0-1-2-3-0 etc */
			rasterize_quadarc(origin, radius, init[i], temp_fin, i);
			init[i] = temp_init;
			if (++i > 3)
				i = 0;
		}
		for (j = 0; j < 4; j++) {
			rasterize_quadarc(origin, radius, init[i], fin[i], i);
			if (i == plane_fin)
				break;
			if (++i > 3)
				i = 0;
		}

	}

	END();
}

void rasterize_quadarc(pixelpoint origin, pixelpoint radius, pixelpoint init,
					   pixelpoint fin, int plane)
	/* get pixels representing 1/4 arc by clockwise Bresenham method */
	/* init and fin points should be in 1st plane (x > 0, y > 0) */
	/* if plane > 3, all planes are selected */
	/* Note. edge selection is only necessary for filling; if speed is a
	critical problem, rewrite codes to skip it in drawing-only cases.  */
{
	/*	SIGNED_PIXEL x, y, del, sdel, lim_x, lim_y, rx, ry, rx2, ry2; */
	SIGNED_PIXEL xx, yy;
	long x, y, lim_x, lim_y, rx, ry;
	double del, sdel, rx2, ry2;

	/* (x, y), real pixel; (f(x) = x * ry, g(y) = y * rx), transformed point */
	/* transformed circle: f(x)^2 + g(y)^2 = (rx * ry)^2 */
	BOOL active_plane[4], edge, visible;
	static SIGNED_PIXEL tr_mat[4][2] =
	{
		{ 1,  1},
		{-1,  1},
		{-1, -1},
		{ 1, -1}
	};

	/* sign of x and y for each plane */
	int i;

	ENTER("rasterize_quadarc");

	rx = radius.x;
	ry = radius.y;
	rx2 = rx * rx;
	ry2 = ry * ry;

	if (plane < 0 || plane > 3) {
		for (i = 0; i < 4; i++)
			active_plane[i] = TRUE;	/* all planes */
	}
	else {						/* only one plane */
		for (i = 0; i < 4; i++)
			active_plane[i] = FALSE;
		active_plane[plane] = TRUE;
	}

	if (init.y > fin.y) {		/* 1st plane, clockwise: y decreasing ... */
		x = init.x;
		y = init.y;
		lim_x = fin.x;
		lim_y = fin.y;
	}
	else {
		x = fin.x;
		y = fin.y;
		lim_x = init.x;
		lim_y = init.y;
	}
	del = ry2 * (x + 1) * (x + 1) + rx2 * ((y - 1) * (y - 1) - ry2);
	/* del = f(x + 1)^2 + g(y - 1)^2 - rxy^2 */

	edge = FALSE;				/* do not count start point as edge */
	while (TRUE) {
		visible = is_visible();
		for (i = 0; i < 4; i++) {
			if (active_plane[i]) {
				xx = (PIXEL)(origin.x + tr_mat[i][0] * x);
				yy = (PIXEL)(origin.y + tr_mat[i][1] * y);
				push_point(xx, yy, edge, FALSE);
				if (visible) write_point(xx, yy);
			}
		}
		if (y <= lim_y && x >= lim_x)
			break;
		if (del < 0) {			/* right-down(RD) point < r */
			sdel = 2 * del - rx2 * (1 - 2 * y);
			/* sdel = f(x + 1)^2 + g(y)^2 - rxy^2
				+ f(x + 1)^2 + g(y - 1)^2 - rxy^2 */
			if (sdel <= 0) {	/* select right(R) point */
				x++;
				del += ry2 * (1 + 2 * x);
				edge = FALSE;	/* not edge */
				continue;
			}
		}
		else if (del > 0) {		/* RD point > r */
			sdel = 2 * del - ry2 * (1 + 2 * x);
			/* sdel = f(x + 1)^2 + g(y - 1)^2 - rxy^2
				+ f(x)^2 + g(y - 1)^2 - rxy^2 */
			if (sdel > 0) {		/* select down(D) point */
				y--;
				del += rx2 * (1 - 2 * y);
				edge = TRUE;	/* edge */
				continue;
			}
		}
		x++;					/* select RD point */
		y--;
		del += ry2 * (1 + 2 * x) + rx2 * (1 - 2 * y);
		edge = TRUE;
		/* half scanline. if point goes over y + 1/2, count it as edge */
	}

	for (i = 0; i < 2; i++) {
		/* tricky: for (x, y) and (-x, y) planes, add extra vertices to assure
		correct fill-edge counting; ignore bottom, count top */
		if (active_plane[i]) {
			push_point((PIXEL)(origin.x + tr_mat[i][0] * init.x),
					(PIXEL)(origin.y + tr_mat[i][1] * init.y), TRUE, FALSE);
			push_point((PIXEL)(origin.x + tr_mat[i][0] * fin.x),
					 (PIXEL)(origin.y + tr_mat[i][1] * fin.y), TRUE, FALSE);
		}
	}

	END();
}

/* visible/invisible functions */

#ifndef TPIC_OLD	/* Here's the new version of dashed and dotted lines */

static PIXEL discrete_visible, discrete_invisible;

void set_visible_discrete(PIXEL visible, PIXEL invisible)
{
	ENTER("set_visible_discrete");
	init_visible(TRUE);
	discrete_visible = visible;
	discrete_invisible = invisible;
	END();
}

void adjust_visible_discrete(pixelpoint start, pixelpoint end)
	/* set discrete visible pattern for a dashed or dotted line */
	/* the dots will be that far apart. the dashes will be that long, and */
	/* the intervening spaces will be as close as possible to that size */
{
	PIXEL pixels, period, black, white, residue, adjust, exdot;
	int repeat, extra, exhalf;
	double dx, dy, dist;

	ENTER("adjust_visible_discrete");
	dx = start.x - end.x;
	dy = start.y - end.y;
	dx = abs(dx);
	dy = abs(dy);
	pixels = (int)max(dx, dy) + 1;	/* total count */
	dist = sqrt(dx * dx + dy * dy) + 1;
	period = (PIXEL)(discrete_visible + discrete_invisible) * (pixels / dist);
	period = max(period, 1);	/* black + white count */

	init_visible(TRUE);
	black = (PIXEL)(discrete_visible * (pixels / dist));	/* black count */
	if (discrete_visible > 0)
		black = max(black, 1);	/* keep non-zero */
	white = period - black;
	repeat = (int)((pixels - black) / period);
	residue = pixels - black - repeat * period;
	if (residue > black) {	/* enough, put one more period and shrink white */
		repeat++;
		residue -= period;
	}
	if (repeat > 0) {	/* long enough */
		adjust = (PIXEL)(residue / repeat);	/* more white to adjust */
		extra = (int)(residue - repeat * adjust);	/* further more white */
		exdot = (PIXEL)((extra >= 0) ? 1 : -1);
		extra = abs(extra);
		exhalf = extra / 2;	/* sandwich by two exdot regions */
		append_visible(black, white + adjust + exdot, exhalf);
		append_visible(black, white + adjust, repeat - extra);
		append_visible(black, white + adjust + exdot, extra - exhalf);
	}
	append_visible(pixels, 0, 1);	/* the last one (rest) */

	END();
}

#endif /* end of ifndef TPIC_OLD */

void set_visible(PIXEL visible, PIXEL invisible)
	/* set a simple pattern (one sequence, continuous) for visibility */
{
	ENTER("set_visible");
	if (invisible == 0)			/* just reducing overheads in is_visible() */
		visible = 1000;
	else if (visible == 0)
		invisible = 1000;

	init_visible(FALSE);		/* continuous */
	append_visible(visible, invisible, 1000);
	END();
}

/* visible/invisible primitives */

#define VIS_PATTERN_SIZE 20	/* maximum */

static BOOL is_vis_discrete = FALSE;	/* for dashed and dotted lines */
typedef struct {
	PIXEL pixels[2];
	int repeat;
} vis_sequence;

static vis_sequence vis_pattern[VIS_PATTERN_SIZE];
static int vis_repeat, vis_index, vis_index_max;
static PIXEL vis_count, vis_turn;
static BOOL vis_flag;

void init_visible(BOOL is_discrete)
{	   /* initialize visible pattern */
	ENTER("init_visible");
	is_vis_discrete = is_discrete;
	vis_repeat = vis_index = vis_index_max = 0;
	vis_count = vis_turn = 0;
	vis_flag = TRUE;
	END();
}

void append_visible(PIXEL visible, PIXEL invisible, int repeat)
	/* append a period for visibility.  repeat this pattern <repeat> times */
{
	ENTER("append_visible");
	if (vis_index_max >= VIS_PATTERN_SIZE) {
		error(WARNING, "tpic: visible pattern exceeds max ... v(^^;)v");
		END();
	}

	if (repeat <= 0)
		END();					/* ignore */
	vis_pattern[vis_index_max].pixels[TRUE] = visible;
	vis_pattern[vis_index_max].pixels[FALSE] = invisible;
	vis_pattern[vis_index_max].repeat = repeat;
	vis_index_max++;
	if (vis_index_max == 1)
		vis_turn = visible;		/* first turn */

	END();
}

BOOL is_visible(void)
{	   /* ret visible status */
	ENTER("is_visible");

	if (++vis_count > vis_turn) {
		vis_count = 1;			/* reset and count one */
		do {
			vis_flag = !vis_flag;	/* flip */
			if (vis_flag && ++vis_repeat >= vis_pattern[vis_index].repeat) {
				vis_repeat = 0;	/* reset */
				if (++vis_index >= vis_index_max)
					vis_index = 0;
			}
			vis_turn = vis_pattern[vis_index].pixels[vis_flag];
		} while (vis_turn <= 0);/* go on for non-zero turn */
	}

	RETURN(vis_flag);
}

BOOL is_visible_discrete(void)
{
	return (is_vis_discrete);
}

/* raster draw/fill functions */

void raster_draw(void)
{	   /* draw pixels defined in stack */
	PIXEL y, ymin, ymax;
	pixel_link *xlink;

	ENTER("raster_draw");
	ymin = get_bucket_min();
	ymax = get_bucket_max();

	for (y = ymin; y <= ymax; y++) {
		xlink = *get_bucket(y);

		while (xlink != NULL) {
			if (xlink->status & VISIBLE)
				write_point(xlink->p, y);
			xlink = xlink->next;
		}
	}
	END();
}

static PIXEL h_width, h_half, v_width, v_half;	/* pensize */

void set_pensize(TPIC_MI pensize)
{
	pixelpoint p;

	ENTER("set_pensize");

	if (pensize == 0) {			/* zero-width */
		h_half = h_width = v_half = v_width = 0;
	}
	else {						/* minimum width is unity */
		p = mi_to_pixel(pensize, pensize);
		h_half = (h_width = max(p.x, 1)) / 2;
		v_half = (v_width = max(p.y, 1)) / 2;
	}

	END();
}

PIXEL get_h_width(void)
{	   /* for line thickness */
	RETURN(h_width);
}

PIXEL get_v_width(void)
{	   /* for line thickness */
	RETURN(v_width);
}

void write_point(PIXEL x, PIXEL y)
{	   /* write on a bitmap */
	ENTER("write_point");

	write_rule(x - h_half, y + v_half, v_width, h_width);
	/* left-bottom point should be specified */
	/* x is not trimmed. y is trimmed if not rotated, not trimmed if rotated.
	i.e., write_rule() should trim x always, and trim (rotated) y correctly */
	/* caution: zero-width should be handled safely in write_rule() */
	/* just a rule is written as a point. this causes some overheads.
	also, a circle instead of a rule would be better, but ..... (^^;) */

	END();
}

void raster_fill(void)
	/* fill the area defined in stack by ordered edge-list algorithm */
	/* edges should be properly selected in advance */
{
	PIXEL y, x[2], ymin, ymax;
	SIGNED_PIXEL dx;
	pixel_link *xlink;
	int i;

	ENTER("raster_fill");

	init_tile(grayscale);		/* select tiling pattern */
	ymin = get_bucket_min();
	ymax = get_bucket_max();

	for (y = ymin; y <= ymax; y++) {
		xlink = *get_bucket(y);

		while (xlink != NULL && !(xlink->status & FILL_EDGE))
			xlink = xlink->next;	/* skip non-edge points */

		i = 0;
		while (xlink != NULL) {	/* search pairs (xmin, xmax) */

			x[i++] = xlink->p;	/* edge found */
			xlink = xlink->next;

			if (i < 2)
				continue;		/* go on to get a pair */

			/* pair found. identical and adjacent points are also counted
			as a pair */
			/* for (x = xmin + 1; x < xmax; x++) put_pixel(x, y); */
			/* if ((dx = x[1] - x[0] - 1) > 0)
				write_tile(x[0] + 1, y, 1, dx); */ /* inside only */

			if ((dx = x[1] - x[0] + 1) > 0)
				write_tile(x[0], y, 1, dx);	/* inside + edges */
				/* Here, x is not trimmed; y is trimmed if not rotated, 
				not trimmed if rotated. thus, write_tile() should trim x 
				always, and trim (rotated) y correctly */

			i = 0;				/* reset and search a new pair */
		}

		/* if not a pair, just ignore it */

	}

	fin_tile();					/* recover black tile */
	END();
}

/* functions to push paths and calc spline interpolation */

#define PATH_ARRAY_SIZE 100	/* cluster size (not maximum size) */

static PIXEL *path_x = NULL, *path_y = NULL;
static int path_n = 0;

void push_path(pixelpoint p)
{	   /* push path p */
	static int array_size = 0;

	ENTER("push_path");

	if (path_n > 0 &&
		p.x == path_x[path_n - 1] && p.y == path_y[path_n - 1])
		END();
	/* ignore the same point. this is necessary for spline interpolation */

	if (path_n >= array_size) {	/* not enough, re-allocate */
		array_size += PATH_ARRAY_SIZE;
		if ((path_x = (PIXEL *)realloc(path_x, sizeof(PIXEL) * array_size))
			== NULL ||
		     (path_y = (PIXEL *)realloc(path_y, sizeof(PIXEL) * array_size))
			== NULL) {
			error(NO_MEMORY, "tpic: path stack full ... v(^^;)v");
			END();
		}
	}
	path_x[path_n] = p.x;
	path_y[path_n++] = p.y;

	END();
}

pixelpoint pop_path(void)
{	   /* just take out */
	pixelpoint p;

	ENTER("pop_path");

	if (path_n == 0) {			/* empty */
		error(WARNING, "tpic: path stack empty ... v(^^;)v");
		p.x = p.y = 0;
	}
	else {
		p.x = path_x[--path_n];
		p.y = path_y[path_n];
	}

	RETURN(p);
}

void flush_path(void)
{	   /* flush path */
	ENTER("flush_path");

	path_n = 0;

/*	array_size = 0;
	free(path_x);
	free(path_y);
	path_x = path_y = NULL *//* not necessary ... */

	END();
}

int get_path_n(void)
{
	ENTER("get_path_n");
	RETURN(path_n);
}

void set_path_n(int n)
{
	ENTER("set_path_n");
	path_n = n;
	END();
}

BOOL is_path_closed(void)
{
	BOOL flag;

	ENTER("is_path_closed");

	if (path_n < 4)
		RETURN(FALSE);

	flag = (path_x[0] == path_x[path_n - 1] &&
			path_y[0] == path_y[path_n - 1]) ? TRUE : FALSE;
	/* top point == last point */

	RETURN(flag);
}

#ifndef TPIC_NSPL	/* These routines are for the old version of spline */

static PIXEL *spl_t;	/* intermediate variable: sum of distances */
static double *spl_x;	/* 1/6 of Spline 2nd derivatives */
static double *spl_y;

void spl_table(void)
	/* make tables for a pair of natural or periodic cubic splines:
	x = f(t), y = g(t); t = sum[sqrt(dx * dx + dy * dy)] */
{
	int i;
	double dx, dy;
	PIXEL distance, sum;

	ENTER("spl_table");

	if ((spl_t = (PIXEL *)marea(sizeof(PIXEL) * path_n)) == NULL ||
		    (spl_x = (double *)marea(sizeof(double) * path_n)) == NULL ||
		    (spl_y = (double *)marea(sizeof(double) * path_n)) == NULL)
		    END();

	spl_t[0] = sum = (PIXEL)0;
	for (i = 1; i < path_n; i++) {
		dx = path_x[i] - path_x[i - 1];
		dy = path_y[i] - path_y[i - 1];
		distance = (PIXEL)sqrt(dx * dx + dy * dy);
		sum += max(distance, 1);
		/* assure that spl_t[i] > spl_t[i - 1], but interpolation may
		look weird, anyway */
		spl_t[i] = sum;			/* intermediate variable */
	}

	/* check if closed. presently identical as is_path_closed() */
	/*	if (path_x[0] == path_x[path_n - 1] &&
		path_y[0] == path_y[path_n - 1]) { */

	if (is_path_closed()) {		/* closed. periodic */
		spl_each_periodic(spl_t, path_x, path_n, spl_x);
		spl_each_periodic(spl_t, path_y, path_n, spl_y);
	}
	else {						/* open. natural */
		spl_each_table(spl_t, path_x, path_n, spl_x);
		spl_each_table(spl_t, path_y, path_n, spl_y);
	}

	END();
}

void spl_clean_table(void)
{	   /* clean table */
	ENTER("spl_clean_table");
	free(spl_t);
	free(spl_x);
	free(spl_y);
	END();
}

pixelpoint spl_get(double index)
	/* get an interpolated point between path_?[i] and path_?[i + 1].
	i = (int)index */
{
	int i;
	pixelpoint p;
	double distance;

	ENTER("spl_get");

	if ((i = (int)index) >= path_n - 1) {
		/* out of range, return end point */
		p.x = path_x[path_n - 1];
		p.y = path_y[path_n - 1];
	}
	else {
		distance = (double)spl_t[i]
			+ (index - (double)i) * (double)(spl_t[i + 1] - spl_t[i]);
		p.x = spl_each_get(spl_t, path_x, path_n, spl_x, distance, i);
		p.y = spl_each_get(spl_t, path_y, path_n, spl_y, distance, i);
	}

	RETURN(p);
}

void spl_each_table(PIXEL x[], PIXEL y[], int n, double sder[])
	/* make a table for natural cubic spline. n >= 3, x[i] < x[i + 1] */
	/* see: W. H. Press et al., "Numerical Recipes in C," Cambridge Univ.
	Press(1988); 奥村晴彦「C 言語による最新アルゴリズム事典」技術評論社 
	(1991) */
{
	double *h;	/* h[i] = x[i + 1] - x[i] */
	double temp, *b;
	int i;

	ENTER("spl_each_table");

	if ((h = (double *)marea(sizeof(double) * n)) == NULL ||
		    (b = (double *)marea(sizeof(double) * n)) == NULL)
		    END();

	for (i = 0; i < n - 1; i++) {
		h[i] = (double)(x[i + 1] - x[i]);
		sder[i] = (double)(y[i + 1] - y[i]) / h[i];
	}
	for (i = n - 2; i > 0; i--) {
		b[i] = 2 * (double)(x[i + 1] - x[i - 1]);
		sder[i] -= sder[i - 1];
	}

	sder[0] = sder[n - 1] = 0;
	/* solve tridiagonal equations for sder[1], .., sder[n - 2] */
	for (i = 1; i < n - 2; i++) {
		temp = h[i] / b[i];
		b[i + 1] -= temp * h[i];
		sder[i + 1] -= temp * sder[i];
	}
	sder[n - 2] /= b[n - 2];
	for (i = n - 3; i >= 1; i--)
		sder[i] = (sder[i] - h[i] * sder[i + 1]) / b[i];

	free(h);
	free(b);
	END();
}

void spl_each_periodic(PIXEL x[], PIXEL y[], int n, double sder[])
	/* make a table for periodic cubic spline. n >= 3, x[i] < x[i + 1],
	y[0] = y[n - 1] */
{
	double *h;	/* h[i] = x[i + 1] - x[i] */
	double temp, *b, *raux;
	int i;

	ENTER("spl_each_periodic");

	if ((h = (double *)marea(sizeof(double) * n)) == NULL ||
		    (b = (double *)marea(sizeof(double) * n)) == NULL ||
		    (raux = (double *)marea(sizeof(double) * n)) == NULL)
		    END();

	for (i = 0; i < n - 1; i++) {
		h[i] = (double)(x[i + 1] - x[i]);
		sder[i] = (double)(y[i + 1] - y[i]) / h[i];
	}
	b[n - 1] = 2 * (h[n - 2] + h[0]);	/* periodic boundary condition */
	sder[n - 1] = sder[0] - sder[n - 2];
	for (i = n - 2; i > 0; i--) {
		b[i] = 2 * (double)(x[i + 1] - x[i - 1]);
		sder[i] -= sder[i - 1];
	}

	for (i = 2; i < n - 2; i++)
		raux[i] = 0;			/* auxiliary for periodic cond */
	raux[1] = h[0];
	raux[n - 2] = h[n - 2];
	raux[n - 1] = b[n - 1];

	/* solve tridiagonal equations for both sder and raux (1, .., n - 1) */
	for (i = 1; i < n - 1; i++) {
		temp = h[i] / b[i];
		b[i + 1] -= temp * h[i];
		sder[i + 1] -= temp * sder[i];
		raux[i + 1] -= temp * raux[i];
	}
	sder[0] = sder[n - 1];
	raux[0] = raux[n - 1];
	for (i = n - 3; i >= 0; i--) {
		temp = h[i] / b[i + 1];
		sder[i] -= temp * sder[i + 1];
		raux[i] -= temp * raux[i + 1];
	}
	temp = sder[0] / raux[0];
	sder[0] = sder[n - 1] = temp;
	for (i = 1; i < n - 1; i++)
		sder[i] = (sder[i] - raux[i] * temp) / b[i];

	free(h);
	free(b);
	free(raux);
	END();
}

double spl_each_get(PIXEL x[], PIXEL y[], int n, double sder[],
					double xint, int i)
	/* get an interpolated value yint. x[i] < xint < x[i + 1] */
{
	double h, d, yint;

	ENTER("spl_each_get");

	if (i < 0 || i >= n - 1) {
		error(WARNING, "tpic: spline interpolation out of range ...v(:_;)v");
		RETURN(0.0);
	}
	h = (double)(x[i + 1] - x[i]);
	d = xint - (double)x[i];
	/*	yint = (double)y[i] + (double)(y[i + 1] - y[i]) * d / h
		- (1.0 / 6.0) * (h - d)
		* ((h + d) * sder[i + 1] + (2 * h - d) * sder[i]) * d / h; */
	yint = ((double)(y[i + 1] - y[i])
		- (h - d) * ((h + d) * sder[i + 1] + (2 * h - d) * sder[i])) * d / h
		+ (double)y[i];

	RETURN(yint);
}

#endif /* TPIC_NSPL */

/* function to push pixel points */

void push_point(PIXEL x, PIXEL y, BOOL fill_edge, BOOL visible)
	/* push x, put and sort it in y-bucket */
	/* Note. y-bucket sort is only necessary for filling; if speed is a 
	critical problem, rewrite codes to skip it in drawing-only cases.  */
{
	pixel_link *x_ptr, **y_bucket_ptr, *ptr;

	ENTER("push_point");

	if ((y_bucket_ptr = get_bucket(y)) == NULL)
		END();					/* out of range */
	if ((x_ptr = push_pixel(x)) == NULL)
		END();					/* memory error */

	x_ptr->status = (fill_edge) ? FILL_EDGE : 0;
	/* used for filling as edges */
	if (visible)
		x_ptr->status |= VISIBLE;

	if ((ptr = *y_bucket_ptr) == NULL || !fill_edge)
		goto link_it;
	/* if bucket is empty or it is not edge, just put it on the top.
	otherwise, search for proper position */

	while (!(ptr->status & FILL_EDGE)) {	/* skip non-edge points */
		y_bucket_ptr = &(ptr->next);	/* ptr to addr of next ptr */
		ptr = *y_bucket_ptr;	/* next ptr itself */
		if (ptr == NULL)
			goto link_it;		/* tail */
	}
	while (x > ptr->p) {
		/* x sort. simple but enough, mostly 2 points */
		y_bucket_ptr = &(ptr->next);	/* ptr to addr of next ptr */
		ptr = *y_bucket_ptr;
		if (ptr == NULL)
			break;				/* tail */
	}

	/* resulting sequence:
	y-bucket top -> non-edges -> edges (x sorted) -> NULL */

  link_it:
	x_ptr->next = ptr;
	*y_bucket_ptr = x_ptr;

	END();
}

/* y-bucket functions */

static pixel_link **bucket = NULL;	/* y-bucket */
static PIXEL vmax;	/* bitmap height = number of buckets */
static PIXEL bucket_min, bucket_max;	/* span of buckets being used */

void init_bucket(PIXEL buf_height)
{	   /* make buckets */
	ENTER("init_bucket");

	/*	if (bucket != NULL) free(bucket); *//* this should not occur */

	vmax = buf_height;	/* bitmap height. does not change throughout */
	if ((bucket = (pixel_link **) marea(sizeof(pixel_link *) * vmax))
		== NULL)
		END();			/* memory error */

	/* maximum span for clean_bucket(): clean all buckets */
	bucket_min = 0;
	bucket_max = vmax - 1;
	clean_bucket();

	END();
}

#define MAX_PIXEL 32766	/* max value of type int */

void clean_bucket(void)
{
	PIXEL i, imin, imax;

	ENTER("clean_bucket");

	imin = max(bucket_min, 0);
	imax = min(bucket_max, vmax - 1);
	for (i = imin; i <= imax; i++)
		bucket[i] = NULL;		/* make 'em empty */

	bucket_min = MAX_PIXEL;		/* span re-initialize */
	bucket_max = -MAX_PIXEL;

#ifndef NOTPIC_EXTENSION
	if (is_rotated)
		clean_extra_bucket();
#endif
	clear_pixel_stack();		/* clear pixel stack */

	END();
}

pixel_link **get_bucket(PIXEL y)
	/* ret ptr to y-bucket (ptr to first x_link is in it). bucket_min, max
	are adjusted */
{
	pixel_link **answer;

	ENTER("get_bucket");

	if (y < 0 || y >= vmax) {	/* out of range */
#ifndef NOTPIC_EXTENSION
		if (!is_rotated)
			RETURN(NULL);		/* trim y direction */
		answer = get_extra_bucket(y);	/* no trimming, get bucket anyhow */
#else
		RETURN(NULL);
#endif
	}
	else {
		answer = bucket + y;
	}

	bucket_min = min(bucket_min, y);	/* record span */
	bucket_max = max(bucket_max, y);
	RETURN(answer);
}

PIXEL get_bucket_min(void)
{
	ENTER("get_bucket_min");
	RETURN(bucket_min);
}

PIXEL get_bucket_max(void)
{
	ENTER("get_bucket_max");
	RETURN(bucket_max);
}

PIXEL get_vmax(void)
{
	ENTER("get_vmax");
	RETURN(vmax);
}

#ifndef NOTPIC_EXTENSION
/* rotation-supporting buckets */

#define EXTRA_BUCKET_SIZE 300	/* cluster size (not maximum size) */
typedef struct extra_bucket_st {
	PIXEL y_offset;	/* y of top bucket */
	pixel_link *bucket[EXTRA_BUCKET_SIZE];	/* bucket array */
	struct extra_bucket_st *next;
} extra_bucket_link;

static extra_bucket_link *extra_bucket = NULL;	/* ptr to extra buckets */

pixel_link **get_extra_bucket(PIXEL y)
	/* ret ptr to out-of-range y-bucket (ptr to first x_link is in it) */
{
	extra_bucket_link **before, *ptr;
	PIXEL y_offset, i;

	ENTER("get_extra_bucket");
	before = &extra_bucket;		/* ptr to var for appending a new cluster */
	ptr = extra_bucket;
	while (ptr != NULL) {
		y_offset = ptr->y_offset;
		if (y >= y_offset && y < y_offset + EXTRA_BUCKET_SIZE)	/* found */
			RETURN(ptr->bucket + (y - y_offset));	/* ret ptr to bucket */
		before = &(ptr->next);
		ptr = ptr->next;
	}

	if ((ptr = (extra_bucket_link *) marea(sizeof(extra_bucket_link)))
		== NULL)
		RETURN(NULL);			/* memory error */
	if (y >= 0) {
		/* vmax + 0 (== offset), 1, 2, .., EXTRA_BUCKET_SIZE - 1 */
		y_offset = y - vmax;
		ptr->y_offset = y_offset - y_offset % EXTRA_BUCKET_SIZE + vmax;
	}
	else {
		/* -EXTRA_BUCKET_SIZE (== offset), .., -2, -1 */
		y_offset = (y + 1) % EXTRA_BUCKET_SIZE + EXTRA_BUCKET_SIZE - 1;
		/* e.g. EBS = 4: y = -1, -2, -3, -4, -5 -> 3, 2, 1, 0, 3 */
		ptr->y_offset = y - y_offset;
	}
	for (i = 0; i < EXTRA_BUCKET_SIZE; i++)
		ptr->bucket[i] = NULL;	/* create empty buckets */
	ptr->next = NULL;			/* tail */
	*before = ptr;				/* link it */
	RETURN(ptr->bucket + (y - ptr->y_offset));	/* ret ptr to bucket */
}

void clean_extra_bucket(void)
{
	extra_bucket_link *work;

	ENTER("clean_extra_bucket");
	while (extra_bucket != NULL) {
		work = extra_bucket->next;
		free(extra_bucket);
		extra_bucket = work;
	}
	END();
}

#endif

/* pixel points stack functions */

#define PIXEL_ARRAY_SIZE 300	/* cluster size (not maximum size) */

typedef struct pixel_array_st {
	int number;
	pixel_link array[PIXEL_ARRAY_SIZE];
	struct pixel_array_st *next;
} pixel_array;

static pixel_array *pixel_stack = NULL;

pixel_link *push_pixel(PIXEL p)
	/* ret link ptr; flag and next ptr are not defined yet */
{
	pixel_array *new;
	pixel_link *link;

	ENTER("push_pixel");
	if (pixel_stack == NULL ||
		pixel_stack->number == PIXEL_ARRAY_SIZE) {	/* make a new array */
		if ((new = (pixel_array *) marea(sizeof(pixel_array))) == NULL)
			RETURN(NULL);		/* memory error */
		new->number = 0;
		new->next = pixel_stack;
		pixel_stack = new;
	}
	link = &(pixel_stack->array[pixel_stack->number++]);
	link->p = p;
	RETURN(link);
}

void clear_pixel_stack(void)
{
	pixel_array *work;

	ENTER("clear_pixel_stack");

	while (pixel_stack != NULL) {
		work = pixel_stack->next;
		free(pixel_stack);
		pixel_stack = work;
	}

	END();
}

/* scale conversion functions */

static pixelpoint current;	/* current point */

void get_current(void)
{	   /* get current point (as pixelpoint) */
	ENTER("get_current");
	current.x = get_current_x();/* get as PIXEL */
	current.y = get_current_y();
	END();
}

pixelpoint rel_to_abs(pixelpoint p)
	/* relative coordinates to absolute coordinates */
{
	pixelpoint answer;

	ENTER("rel_to_abs");
	answer.x = current.x + p.x;
	answer.y = current.y + p.y;
	RETURN(answer);
}

/* 1 milli-inch = 72.27 * 65536 / 1000 scaled-points */
#ifdef NOFLOAT
#define MI_SP 4736
#else
#define MI_SP 4736.28672
#endif

pixelpoint mi_to_pixel(TPIC_MI x, TPIC_MI y)
{	   /* milli-inches to pixel */
	pixelpoint p;

	ENTER("mi_to_pixel");
	p.x = sptopixel((SCALED_PT)(x * MI_SP));
	p.y = vtopixel((SCALED_PT)(y * MI_SP));
	RETURN(p);
}

#ifdef VFTPIC
/* functions to freeze and melt tpic status, for overriding in vfont.c */

/* these are just temp_staffs. (ha-ha) */
static BOOL temp_shade_on;
static BOOL temp_is_closed;
static double temp_grayscale;
static PIXEL temp_h_width, temp_h_half, temp_v_width, temp_v_half;

/* the followings (discrete visible pattern, path, spline, y-bucket,
pixel stack and current position) are currently either unused for overriding
or used immediately in tpic, so that they are unnecessary to be frozen.
Future use is possible; stay tuned */
/*
#ifndef NOTPIC_EXTENSION
static BOOL temp_is_rotated;
#endif
static PIXEL temp_discrete_visible, temp_discrete_invisible;
static BOOL temp_is_vis_discrete;
static vis_sequence temp_vis_pattern[VIS_PATTERN_SIZE];
static int temp_vis_repeat, temp_vis_index, temp_vis_index_max;
static PIXEL temp_vis_count, temp_vis_turn;
static BOOL temp_vis_flag;

static PIXEL *temp_path_x, *temp_path_y;
static int   temp_path_n;

#ifdef TPIC_OLD
static PIXEL  *temp_spl_t;
static double *temp_spl_x;
static double *temp_spl_y;
#endif

static pixel_link **temp_bucket;
static PIXEL temp_vmax;
static PIXEL temp_bucket_min, temp_bucket_max;
#ifndef NOTPIC_EXTENSION
static extra_bucket_link *temp_extra_bucket;
#endif
static pixel_array *temp_pixel_stack;
static pixelpoint temp_current;
*/

void freeze_tpic_status(void)
{
	int i;

	ENTER("freeze_tpic_status");

	temp_shade_on = shade_on;
	temp_is_closed = is_closed;
	temp_grayscale = grayscale;
	temp_h_width = h_width;
	temp_h_half = h_half;
	temp_v_width = v_width;
	temp_v_half = v_half;
#ifndef NOTPIC_EXTENSION
	if (is_rotated)
		alter_bitblt_rt(FALSE);	/* no rotation in vfont proc */
#endif
	END();
}

void melt_tpic_status(void)
{
	int i;

	ENTER("melt_tpic_status");

	shade_on = temp_shade_on;
	is_closed = temp_is_closed;
	grayscale = temp_grayscale;
	h_width = temp_h_width;
	h_half = temp_h_half;
	v_width = temp_v_width;
	v_half = temp_v_half;
#ifndef NOTPIC_EXTENSION
	if (is_rotated)
		alter_bitblt_rt(TRUE);	/* recover rotation */
	/* it is assumed that rot matrix is intact in vfont proc */
#endif
	END();
}

#endif /* end of ifdef VFTPIC */

#endif /* end of ifndef NOTPIC */

/* end of all */
