The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * $Id: pdf.trm,v 1.13 2002/08/30 18:45:46 mikulik Exp $
 */

/*------------------------------
	GNUPLOT - pdf.trm

	This file is included by ../term.c.

	This driver uses PDFlib from www.pdflib.com

	Author:

		Hans-Bernhard Br"oker
		broeker@physik.rwth-aachen.de

	Licence: see the gnuplot copyright (to be merged into here...)

	Options: can #define PDF_DONT_COMPRESS to avoid PDF output
	generated being compressed (by the 'deflate' algorithm as used
	in 'zip' or 'gzip'). That helps in debugging.

------------------------------*/

/* CODEME: Add patterned lines (?). */

/* PM3D support by Johannes Zellner <johannes@zellner.org>, May-15-2002 */
/* set_color fixes by Petr Mikulik <mikulik@physics.muni.cz>, June-10-2002 */

/* 'filledboxes' with patterns are really lousy,
   but I don't know why. 04 Jun 2002 (joze) */
/* Text rotation 24-Jul-2002 Ethan A Merritt <merritt@u.washington.edu> */

#include "driver.h"

#ifdef TERM_REGISTER
register_term (pdf)
#endif

#ifdef TERM_PROTO
TERM_PUBLIC void PDF_options __PROTO ((void));
TERM_PUBLIC void PDF_init __PROTO ((void));
TERM_PUBLIC void PDF_graphics __PROTO ((void));
TERM_PUBLIC void PDF_text __PROTO ((void));
TERM_PUBLIC void PDF_linetype __PROTO ((int linetype));
TERM_PUBLIC void PDF_move __PROTO ((unsigned int x, unsigned int y));
TERM_PUBLIC void PDF_vector __PROTO ((unsigned int x, unsigned int y));
TERM_PUBLIC void PDF_put_text __PROTO ((unsigned int x, unsigned int y, const char *str));
TERM_PUBLIC void PDF_reset __PROTO ((void));
TERM_PUBLIC int PDF_justify_text __PROTO ((enum JUSTIFY mode));
TERM_PUBLIC int PDF_text_angle __PROTO ((int ang));
TERM_PUBLIC void PDF_point __PROTO ((unsigned int x, unsigned int y, int pointstyle));
TERM_PUBLIC int PDF_set_font __PROTO ((const char *font));
TERM_PUBLIC void PDF_boxfill __PROTO((int style, unsigned int x1, unsigned int y1, unsigned int width, unsigned int height));
TERM_PUBLIC void PDF_linewidth __PROTO ((double linewidth));

#ifdef PM3D
TERM_PUBLIC int PDF_make_palette __PROTO((t_sm_palette *));
TERM_PUBLIC void PDF_previous_palette __PROTO((void));
TERM_PUBLIC void PDF_set_color __PROTO((double));
TERM_PUBLIC void PDF_filled_polygon __PROTO((int, gpiPoint *));
#endif /* PM3D */

#define PDF_NUM_POINTTYPES 75	/* number of point symbol types not counting the dot */

#define PDF_RESOLUTION  (20)	/* number of terminal pixels per pt */
#define PDF_XMAX	(5*72*PDF_RESOLUTION) /* 5 inches, 72 pt/inch */
#define PDF_YMAX	(3*72*PDF_RESOLUTION) /* 3 inches, 72 pt/inch */

#endif /* TERM_PROTO */

#ifndef TERM_PROTO_ONLY
#ifdef TERM_BODY

#include <pdflib.h>

#ifdef PM3D
#   include "getcolor.h"
static t_sm_palette PDF_palette;
#endif

static PDF *myPDF = NULL;

static unsigned int PDF_xLast = UINT_MAX; /* current pen horizontal position*/
static unsigned int PDF_yLast = UINT_MAX; /* current pen vertical position*/

static int PDF_LineType = -3;	/* current line type*/
static double PDF_LineWidth = 1.0; /* current line width*/
static int PDF_TextAngle = 0;		/* current text orientation*/
static enum JUSTIFY PDF_TextJust = LEFT; /* current text justification*/

/* default text font family: */
static char PDF_fontNameDef[MAX_ID_LEN + 1] = "Helvetica"; 
static double PDF_fontSizeDef = 6;	/* default text size*/
/* current text font family: */
static char PDF_fontNameCur[MAX_ID_LEN + 1] = "Helvetica"; 
static double PDF_fontSizeCur = 6; /* current text size*/

static TBOOLEAN PDF_pageIsOpen = FALSE; /* already started a page ?? */
static TBOOLEAN PDF_pathIsOpen = FALSE; /* open path flag*/

static int PDF_fontAscent = 0;	/* estimated current font ascent*/
static int PDF_fontDescent = 0;	/* estimated current font descent*/
static int PDF_fontLeading = 0;	/* estimated current font leading*/
static int PDF_fontAvWidth = 0;	/* estimated current font char average width*/

static short PDF_Pen_RealID __PROTO ((int));
static void PDF_PathOpen __PROTO ((void));
static void PDF_PathClose __PROTO ((void));
static void PDF_SetFont __PROTO ((void));
#if USE_ULIG_FILLEDBOXES
static int PDF_DefinePattern1 __PROTO((int x1, int y1, int x2, int y2));
static int PDF_DefinePattern2 __PROTO((int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4));
static void PDF_DefinePatterns __PROTO((void));
enum { PDF_patterns = 6 };
static int PDF_patternHandles[PDF_patterns];
#endif

/*------------------------ helper functions -------------------*/

static short 
PDF_Pen_RealID (inPenCode)
    int inPenCode;
{
    if (inPenCode >= 13)
	inPenCode %= 13;	/* normalize pen code*/
    if (inPenCode <= L_TYPE_NODRAW)
	inPenCode = -2;

    return (inPenCode + 2);
}

/* Functions to ensure that as many move() and vector() calls as
 * possible get converted into a single long 'path', before closing it
 * with a stroke or similar command. */
static void
PDF_PathOpen ()
{
    PDF_pathIsOpen = TRUE;
}

static void
PDF_PathClose ()
{
    if (PDF_pathIsOpen) {
	PDF_stroke(myPDF);

	PDF_pathIsOpen = FALSE;
    }
}

/* Helper function to deal with switching over to a newly selected
 * font.  For now, this does not try to embed fonts into the PDF
 * file. This relies on the user restricting herself to the 14
 * 'classical' fonts built into every valid PDF reader application */
static void
PDF_SetFont ()
{
    int font_handle = PDF_findfont(myPDF, PDF_fontNameCur, "host", 0);

    PDF_setfont(myPDF, font_handle, PDF_fontSizeCur * PDF_RESOLUTION);

    /* Ask PDFlib for the actual numbers */
    PDF_fontAscent = (int) (PDF_RESOLUTION * PDF_fontSizeCur * PDF_get_value(myPDF, "ascender", 0));	
    PDF_fontDescent = (int) (- PDF_RESOLUTION * PDF_fontSizeCur * PDF_get_value(myPDF, "descender", 0));	
    PDF_fontLeading = (int) (PDF_RESOLUTION * PDF_fontSizeCur * 0.25); 
    
    /* Assume this particular string is a somewhat reasonable typical
     * output, for getting at the average character width */
    PDF_fontAvWidth = (int)
	(PDF_RESOLUTION * PDF_stringwidth(myPDF, "01234567890123456789",
					  font_handle, PDF_fontSizeCur)
	 / 20.0);
}

#if USE_ULIG_FILLEDBOXES
static int
PDF_DefinePattern1(int x1, int y1, int x2, int y2)
{
    int handle = PDF_begin_pattern(myPDF, 8, 8, 8, 8, 2 /* painttype */);
    PDF_setlinewidth(myPDF, 0.2);
    PDF_setlinecap(myPDF, 0); /* rounded ends */
    PDF_moveto(myPDF, x1, y1);
    PDF_lineto(myPDF, x2, y2);
    PDF_stroke(myPDF);
    PDF_end_pattern(myPDF);
    return handle;
}

static int
PDF_DefinePattern2(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4)
{
    int handle = PDF_begin_pattern(myPDF, 8, 8, 8, 8, 2 /* painttype */);
    PDF_setlinewidth(myPDF, 0.2);
    PDF_setlinecap(myPDF, 0); /* rounded ends */
    PDF_moveto(myPDF, x1, y1);
    PDF_lineto(myPDF, x2, y2);
    PDF_moveto(myPDF, x3, y3);
    PDF_lineto(myPDF, x4, y4);
    PDF_stroke(myPDF);
    PDF_end_pattern(myPDF);
    return handle;
}

static void
PDF_DefinePatterns()
{
    PDF_patternHandles[0] = PDF_DefinePattern1(0, 7, 7, 0);
    PDF_patternHandles[1] = PDF_DefinePattern1(0, 0, 7, 7);
    PDF_patternHandles[2] = PDF_DefinePattern2(0, 7, 3, 0, 4, 7, 7, 0);
    PDF_patternHandles[3] = PDF_DefinePattern2(0, 0, 3, 7, 4, 0, 7, 7);
    PDF_patternHandles[4] = PDF_DefinePattern2(0, 3, 7, 0, 0, 7, 7, 4);
    PDF_patternHandles[5] = PDF_DefinePattern2(0, 0, 7, 3, 0, 4, 7, 7);
}
#endif

/*------------------- the terminal entry functions --------------------*/


TERM_PUBLIC void
PDF_options ()
{
    struct value a;

    if (!END_OF_COMMAND) {	/* get default font family name*/
	if (almost_equals (c_token, "fn$ame"))  {
	    c_token++;

	    if (!END_OF_COMMAND && isstring (c_token)) {
		quote_str (PDF_fontNameDef, c_token, MAX_ID_LEN);
		c_token++;
	    } else
		int_error(c_token,"fname: expecting font name");
	}
    }

    if (!END_OF_COMMAND) {	/* get default font size*/
	if (almost_equals (c_token, "fs$ize")) {
	    c_token++;

	    if (END_OF_COMMAND)
		int_error(c_token,"fsize: expecting font size");
	    PDF_fontSizeDef = real (const_express (&a));
	}
    }

    if (!END_OF_COMMAND)
	 int_error(c_token, "unexpected text at end of command");

    /* Save options back into options string in normalized format */
    sprintf(term_options, "fname '%s'  fsize %g",
	    PDF_fontNameDef, PDF_fontSizeDef);
}


TERM_PUBLIC void
PDF_init ()
{
    static TBOOLEAN PDFlib_booted = FALSE;
    
    if (!PDFlib_booted) {
	PDF_boot();
	PDFlib_booted = TRUE;
    }

    if (!myPDF)
	myPDF = PDF_new();

    /*open new PDF file */ 
    if (PDF_open_fp(myPDF, gpoutfile) == -1)
	int_error(NO_CARET, "Error:cannot open PDF file .\n");

#ifdef PDF_DONT_COMPRESS
    /* for easier debugging of the output, turn off PDF stream
     * compression */
    PDF_set_value(myPDF, "compress", 0);
#endif

    PDF_set_info(myPDF,"Creator","gnuplot-3.8e");
    PDF_set_info(myPDF,"Author","Hans-Bernhard Broeker"); /* FIXME: put in username? */
    PDF_set_info(myPDF,"Title","gnuplot diagram"); /* FIXME: use 'set title', if any ? */
    
    PDF_LineType = -3;

    /* set current font to default */
    strcpy(PDF_fontNameCur, PDF_fontNameDef);
    PDF_fontSizeCur = PDF_fontSizeDef;

#if USE_ULIG_FILLEDBOXES
    PDF_DefinePatterns();
#endif

    /* Have to start the first page now, in order to know the actual
     * size of the selected font */
    PDF_graphics();

    /* set h_char, v_char*/
    term->h_char = PDF_fontAvWidth;
    term->v_char = (PDF_fontAscent + PDF_fontDescent + PDF_fontLeading);

    /* set h_tic, v_tic*/
    term->h_tic = term->v_tic = 3 * PDF_RESOLUTION;

    /* initialize terminal's pointsize from "set pointsize" value */
    term_pointsize = pointsize;	
}


TERM_PUBLIC void
PDF_graphics ()
{
    if (PDF_pageIsOpen) 
	return;			/* already open --> nothing to do */

    PDF_pathIsOpen = FALSE;
    PDF_xLast = PDF_yLast = UINT_MAX;

    /* set xmax, ymax*/
    term->xmax = PDF_XMAX * xsize;
    term->ymax = PDF_YMAX * ysize;
    
    PDF_begin_page(myPDF, (double)term->xmax / PDF_RESOLUTION,
		   (double)term->ymax / PDF_RESOLUTION);
    PDF_scale(myPDF, 1.0/PDF_RESOLUTION, 1.0/PDF_RESOLUTION);
    if (title.text && title.text[0])
	/* a title has been set --> use it as the bookmark name, too */
	PDF_add_bookmark(myPDF, title.text, 0, 1);
    PDF_pageIsOpen = TRUE;

    PDF_SetFont();
}


TERM_PUBLIC void
PDF_text ()
{
    PDF_PathClose();
    PDF_end_page(myPDF);
    PDF_pageIsOpen = FALSE;
}


TERM_PUBLIC void
PDF_reset ()
{
    assert(PDF_pageIsOpen == FALSE);
    PDF_close(myPDF);
    PDF_delete(myPDF);
    myPDF = NULL;
}


TERM_PUBLIC void
PDF_linetype (int linetype)
{
    if (linetype != PDF_LineType) {
	int real_linetype = PDF_Pen_RealID(linetype);
	struct rgb *this_color = web_color_rgbs + 1 + real_linetype;

	PDF_PathClose ();
	PDF_LineType = linetype;
	
	PDF_setrgbcolor(myPDF, this_color->r / 255.0,
			this_color->g / 255.0, this_color->b / 255.0);
    }
}


TERM_PUBLIC void
PDF_linewidth (double linewidth)
{
    PDF_PathClose();
    PDF_LineWidth = PDF_RESOLUTION * linewidth / 4.0;
    PDF_setlinewidth(myPDF, PDF_LineWidth);
}


TERM_PUBLIC void
PDF_move (unsigned int x, unsigned int y)
{
    if (PDF_pathIsOpen && x == PDF_xLast && y == PDF_yLast)  
	return;

    PDF_PathOpen ();
    PDF_moveto(myPDF, x, y);

    PDF_xLast = x;
    PDF_yLast = y;
}


TERM_PUBLIC void
PDF_vector (unsigned int x, unsigned int y)
{
    if (PDF_pathIsOpen && x == PDF_xLast && y == PDF_yLast) 
	return;

    PDF_PathOpen ();
    PDF_lineto(myPDF, x, y);

    PDF_xLast = x;
    PDF_yLast = y;
}

/* Helper function. Many symbols have an additional dot in their
 * center, so isolate its drawing into a separate function. */
static GP_INLINE void
PDF_dot (unsigned int x, unsigned int y)
{
    /* Imitate PS's way of creating a small dot by a zero-length line
     * segment with rounded endpoints */
    PDF_setlinecap(myPDF, 1); /* rounded ends */
    PDF_moveto(myPDF, x, y);
    PDF_lineto(myPDF, x, y);
    PDF_stroke(myPDF);
}
    

TERM_PUBLIC void
PDF_point (unsigned int x, unsigned int y, int number)
{
    PDF_PathClose ();
    PDF_save(myPDF);

    if (number < 0) {
	/* Like the PostScript driver, treat all negative numbers as
         * 'with dots'. */
	PDF_dot(x, y);
    } else {
	/* Change coordinate system so the point symbols themselves
	 * can be drawn without depending on position or size (-->
	 * better compression and less coding for gnuplot) */
	/* NB: I use the do_pointsize() default implementation, which
	 * just stores the last set pointsize into `term_pointsize',
	 * to avoid introducing another static driver-local variable
	 * */
	PDF_translate(myPDF, x, y);
	PDF_scale(myPDF, term->h_tic / 2.0 * term_pointsize,
		  term->v_tic / 2.0 * term_pointsize);
	/* Correct linewidth to counter the scaling effect --- assume
	 * h_tic is usable, to avoid having to average h_ and v_tic */
	PDF_setlinewidth(myPDF,
			 PDF_LineWidth / (term->h_tic / 2.0 * term_pointsize));
	switch (number %= PDF_NUM_POINTTYPES) {
	case 0:			/* Plus */
	    PDF_moveto(myPDF, -1, 0);
	    PDF_lineto(myPDF, 1, 0);
	    PDF_moveto(myPDF, 0, -1);
	    PDF_lineto(myPDF, 0, 1);
	    PDF_stroke(myPDF);
	    break;
	case 2:			/* Star */
	    PDF_moveto(myPDF, -1, 0);
	    PDF_lineto(myPDF, 1, 0);
	    PDF_moveto(myPDF, 0, -1);
	    PDF_lineto(myPDF, 0, 1);
	    /* FALLTHROUGH */
	case 1:			/* Cross */
	    PDF_moveto(myPDF, -1, -1);
	    PDF_lineto(myPDF, 1, 1);
	    PDF_moveto(myPDF, 1, -1);
	    PDF_lineto(myPDF, -1, 1);
	    PDF_stroke(myPDF);
	    break;

/* For each x = 0..5, 4 shapes are defined: 
 * 3 + 2*x --> hollow symbol with a dot at its center
 * 4 + 2*x --> solid symbol filled in linetype's color
 * 63 + x  --> hollow symbol without the center dot 
 * 69 + x  --> symbol filled with white --> opaque symbol */
 
	case 63+0:		/* BoxEmpty */
	case 3+2*0:		/* Box */
	    PDF_moveto(myPDF, -1, -1);
	    PDF_lineto(myPDF, 1, -1);
	    PDF_lineto(myPDF, 1, 1);
	    PDF_lineto(myPDF, -1, 1);
	    PDF_closepath_stroke(myPDF);
	    if (number == 3) PDF_dot(0,0);
	    break;
	case 69+0:		/* BoxWhitefilled */
	    PDF_setgray_fill(myPDF, 1);
	    /* FALLTHROUGH */
	case 4+2*0:		/* BoxFilled */
	    PDF_moveto(myPDF, -1, -1);
	    PDF_lineto(myPDF, 1, -1);
	    PDF_lineto(myPDF, 1, 1);
	    PDF_lineto(myPDF, -1, 1);
	    PDF_closepath_fill_stroke(myPDF);
	    break;

	case 63+1:		/* CircleEmpty */
	case 3+2*1:		/* Circle */
	    PDF_circle(myPDF, 0, 0, 1);
	    PDF_stroke(myPDF);
	    if (number == 5) PDF_dot(0,0);
	    break;
	case 69+1:		/* CircleWhitefilled */
	    PDF_setgray_fill(myPDF, 1);
	    /* FALLTHROUGH */
	case 4+2*1:		/* CircleFilled */
	    PDF_circle(myPDF, 0, 0, 1);
	    PDF_fill_stroke(myPDF);
	    break;

	case 63+2:		/* TriangleUpEmpty */
	case 3+2*2:		/* TriangleUp */
	    PDF_moveto(myPDF, 0, 1.12);
	    PDF_lineto(myPDF, -1, -0.5);
	    PDF_lineto(myPDF, 1, -0.5);
	    PDF_closepath_stroke(myPDF);
	    if (number == 7) PDF_dot(0,0);
	    break;
	case 69+2:		/* TriangleUpWhitefilled */
	    PDF_setgray_fill(myPDF, 1);
	    /* FALLTHROUGH */
	case 4+2*2:			/* TriangleUpFilled */
	    PDF_moveto(myPDF, 0, 1.12);
	    PDF_lineto(myPDF, -1, -0.5);
	    PDF_lineto(myPDF, 1, -0.5);
	    PDF_closepath_fill_stroke(myPDF);
	    break;

	case 63+3:		/* TriangleDownEmpty */
	case 3+2*3:		/* TriangleDown */
	    PDF_moveto(myPDF, 0, -1.12);
	    PDF_lineto(myPDF, -1, 0.5);
	    PDF_lineto(myPDF, 1, 0.5);
	    PDF_closepath_stroke(myPDF);
	    if (number == 9) PDF_dot(0,0);
	    break;
	case 69+3:		/* TriangleDownWhitefilled */
	    PDF_setgray_fill(myPDF, 1);
	    /* FALLTHROUGH */
	case 4+2*3:		/* TriangleDownFilled */
	    PDF_moveto(myPDF, 0, -1.12);
	    PDF_lineto(myPDF, -1, 0.5);
	    PDF_lineto(myPDF, 1, 0.5);
	    PDF_closepath_fill_stroke(myPDF);
	    break;

	case 63+4:		/* DiamondEmpty */
	case 3+2*4:		/* Diamond */
	    PDF_moveto(myPDF, 0, -1);
	    PDF_lineto(myPDF, 1, 0);
	    PDF_lineto(myPDF, 0, 1);
	    PDF_lineto(myPDF, -1, 0);
	    PDF_closepath_stroke(myPDF);
	    if (number == 11) PDF_dot(0,0);
	    break;
	case 69+4:		/* DiamondWhitefilled */
	    PDF_setgray_fill(myPDF, 1);
	    /* FALLTHROUGH */
	case 4+2*4:		/* DiamondFilled */
	    PDF_moveto(myPDF, 0, -1);
	    PDF_lineto(myPDF, 1, 0);
	    PDF_lineto(myPDF, 0, 1);
	    PDF_lineto(myPDF, -1, 0);
	    PDF_closepath_fill_stroke(myPDF);
	    break;

	case 63+5:		/* PentagonEmpty */
	case 3+2*5:		/* Pentagon */
	    PDF_moveto(myPDF, 0, 1);
	    PDF_lineto(myPDF, -0.95, 0.31);
	    PDF_lineto(myPDF, -0.58, -0.81);
	    PDF_lineto(myPDF, +0.58, -0.81);
	    PDF_lineto(myPDF, +0.95, 0.31);
	    PDF_closepath_stroke(myPDF);
	    if (number == 13) PDF_dot(0,0);
	    break;
	case 69+5:		/* PentagonWhitefilled */
	    PDF_setgray_fill(myPDF, 1);
	    /* FALLTHROUGH */
	case 4+2*5:		/* PentagonFilled */
	    PDF_moveto(myPDF, 0, 1);
	    PDF_lineto(myPDF, -0.95, 0.31);
	    PDF_lineto(myPDF, -0.58, -0.81);
	    PDF_lineto(myPDF, +0.58, -0.81);
	    PDF_lineto(myPDF, +0.95, 0.31);
	    PDF_closepath_fill_stroke(myPDF);
	    break;
	    
/* 15 + (0..15): circles with varying parts of'em filled. The added
 * number is a bit-pattern of the 4 quadrants: 1 signals a quadrant
 * filled */
	case 15+0:
	    PDF_moveto(myPDF, 0, 0);
	    PDF_lineto(myPDF, 0, 1);
	    PDF_arc(myPDF, 0, 0, 1, 90, 360+90);
	    PDF_closepath_stroke(myPDF);
	    break;

/* Generalize common code into a macro... */
#define CIRCLE_SINGLE_PIESLICE(x, y, angle1, angle2)		\
	    PDF_moveto(myPDF, 0, 0);				\
	    PDF_lineto(myPDF, (x), (y));			\
	    PDF_arc(myPDF, 0, 0, 1, (angle1), (angle2));	\
	    PDF_lineto(myPDF, 0, 0);				\
	    PDF_closepath(myPDF);				\
	    PDF_fill_stroke(myPDF);				\
	    PDF_arc(myPDF, 0, 0, 1, (angle2), (angle1) + 360);	\
	    PDF_stroke(myPDF);					\
	    break;

#define CIRCLE_SINGLE_QUADRANT(x, y, angle)			\
	    CIRCLE_SINGLE_PIESLICE(x, y, angle, angle+90);
	case 15+1:
	    CIRCLE_SINGLE_QUADRANT(1, 0, 0);
	case 15+2:
	    CIRCLE_SINGLE_QUADRANT(0, 1, 90);
	case 15+4:
	    CIRCLE_SINGLE_QUADRANT(-1, 0, 180);
	case 15+8:
	    CIRCLE_SINGLE_QUADRANT(0, -1, 270);
#undef CIRCLE_SINGLE_QUADRANT

#define CIRCLE_TWO_NEIGHBOR_QUADRANTS(x, y, angle)		\
	    CIRCLE_SINGLE_PIESLICE(x, y, angle, angle+180)
	case 15+3:	
	    CIRCLE_TWO_NEIGHBOR_QUADRANTS(1, 0, 0);
	case 15+6:
	    CIRCLE_TWO_NEIGHBOR_QUADRANTS(0, 1, 90);
	case 15+12:
	    CIRCLE_TWO_NEIGHBOR_QUADRANTS(-1, 0, 180);
	case 15+9:
	    CIRCLE_TWO_NEIGHBOR_QUADRANTS(0, -1, 270);
#undef CIRCLE_TWO_NEIGHBOR_QUADRANTS
	  
#define CIRCLE_TWO_OPPOSING_QUADRANTS(x, y, angle)		\
	    PDF_moveto(myPDF, 0, 0);				\
	    PDF_lineto(myPDF, x, y);				\
	    PDF_arc(myPDF, 0, 0, 1, angle, angle + 90);		\
	    PDF_lineto(myPDF, 0, 0);				\
	    PDF_fill_stroke(myPDF);				\
	    PDF_moveto(myPDF, 0, 0);				\
	    PDF_lineto(myPDF, -x, -y);				\
	    PDF_arc(myPDF, 0, 0, 1, angle + 180, angle + 270);	\
	    PDF_lineto(myPDF, 0, 0);				\
	    PDF_fill_stroke(myPDF);				\
	    PDF_arc(myPDF, 0, 0, 1, angle + 90, angle + 360);	\
	    PDF_stroke(myPDF);					\
	    break;
	case 15+5:
	    CIRCLE_TWO_OPPOSING_QUADRANTS(1, 0, 0);
	case 15+10:
	    CIRCLE_TWO_OPPOSING_QUADRANTS(0, 1, 90);
#undef CIRCLE_TWO_OPPOSING_QUADRANTS
	    
#define CIRCLE_THREE_QUADRANTS(x, y, angle)			\
	    CIRCLE_SINGLE_PIESLICE(x, y, angle, angle+270)
	case 15+7:
	    CIRCLE_THREE_QUADRANTS(1, 0, 0);	    
	case 15+14:
	    CIRCLE_THREE_QUADRANTS(0, 1, 90);	    
	case 15+13:
	    CIRCLE_THREE_QUADRANTS(-1, 0, 180);	    
	case 15+11:
	    CIRCLE_THREE_QUADRANTS(0, -1, 270);	    
#undef CIRCLE_THREE_QUADRANTS
#undef CIRCLE_SINGLE_PIESLICE

	case 15+15:		
	    PDF_circle(myPDF, 0, 0, 1);
	    PDF_closepath_fill_stroke(myPDF);
	    break;


/*************************************************************************/
/* 31 + (0..15): squares with different quadrants of them filled in. */
/*************************************************************************/
/*************************************************************************/
/* 47 + (0..15): diamonds with filled quadrants as given by bit pattern  */
/*   Diamonds are drawn as squares rotated by 45 degrees, so can use
 * fall-through from diamond to squares, and re-use some macros. */
/*************************************************************************/
	case 47+0:
	    PDF_rotate(myPDF, 45);
	    /* FALLTHROUGH */
	case 31+0:
	    PDF_moveto(myPDF, 0, 0);
	    PDF_lineto(myPDF, 0, 1);
	    PDF_lineto(myPDF, -1, 1);
	    PDF_lineto(myPDF, -1, -1);
	    PDF_lineto(myPDF, 1, -1);
	    PDF_lineto(myPDF, 1, 1);
	    PDF_lineto(myPDF, 0, 1);
	    PDF_stroke(myPDF);
	    break;

	case 47+15:
	    PDF_rotate(myPDF, 45);
	    /* FALLTHROUGH */
	case 31+15:		
	    PDF_moveto(myPDF, -1, 1);
	    PDF_lineto(myPDF, -1, -1);
	    PDF_lineto(myPDF, 1, -1);
	    PDF_lineto(myPDF, 1, 1);
	    PDF_closepath_fill_stroke(myPDF);
	    break;

/* macros defining shapes of the partly filled symbols. Done by
 * rotating the starting point (x0, y0) by 90 degrees or 45 degrees
 * (with length adjustment).  The rotations can be done without
 * trigonometric function calls, since their values are known:
 * cos(90)=0, sin(90)=1, cos(45)=sin(45)=1/sqrt(2).  A good compiler
 * should be able to optimize away all the local variables and
 * loops...  */

#define SQUARE_SINGLE_PIESLICE(x0, y0, quadrants)			\
	    {								\
		int quadrant = 0;					\
		int x= x0, y=y0;					\
		PDF_moveto(myPDF, 0, 0);				\
		PDF_lineto(myPDF, x, y);				\
		/* poor man's rotation by 45 and 90 degrees around the	\
		 * square's outline. */					\
		while (quadrant++ < quadrants) {			\
		    int dummy;						\
		    PDF_lineto(myPDF, x-y, x+y);			\
		    dummy = x; x = -y; y = dummy;			\
		}							\
		PDF_lineto(myPDF, x, y);				\
		PDF_closepath_fill_stroke(myPDF);			\
		PDF_moveto(myPDF, x, y);				\
		while (quadrant++ <= 4) {				\
		    int dummy;						\
		    PDF_lineto(myPDF, x-y, x+y);			\
		    dummy = x; x = -y; y = dummy;			\
		}							\
		PDF_lineto(myPDF, x, y);				\
		PDF_stroke(myPDF);					\
	    }								\
	    break;

#define SQUARE_TWO_OPPOSING_QUADRANTS(x0, y0, angle)	\
	    {						\
		int x = x0, y = y0, dummy;		\
		int counter = 0;			\
							\
		while (counter++ < 2) {			\
		    PDF_moveto(myPDF, 0, 0);		\
		    PDF_lineto(myPDF, x, y);		\
		    PDF_lineto(myPDF, x-y, x+y);	\
		    dummy = x; x = -y; y = dummy;	\
		    PDF_lineto(myPDF, x, y);		\
		    PDF_closepath_fill_stroke(myPDF);	\
							\
		    PDF_moveto(myPDF, x, y);		\
		    PDF_lineto(myPDF, x-y, x+y);	\
		    dummy = x; x = -y; y = dummy;	\
		    PDF_lineto(myPDF, x, y);		\
		    PDF_stroke(myPDF);			\
		}					\
		break;					\
	    }

/* Macros for diamonds just prepend the rotation and then call those
 * for squares: */
#define DIAMOND_SINGLE_PIESLICE(x, y, quadrants)	\
	    PDF_rotate(myPDF, 45);			\
	    SQUARE_SINGLE_PIESLICE(x, y, quadrants);
#define DIAMOND_TWO_OPPOSING_QUADRANTS(x, y, angle)	\
	    PDF_rotate(myPDF, 45);			\
	    SQUARE_TWO_OPPOSING_QUADRANTS(x, y, angle);

/* ... and now all the individual cases. The 'angle' arguments' are
 * purely for the sake of easing cut'n'paste with the circle case */
#define SQUARE_SINGLE_QUADRANT(x, y, angle)			\
	    SQUARE_SINGLE_PIESLICE(x, y, 1);
	case 31+1:
	    SQUARE_SINGLE_QUADRANT(1, 0, 0);
	case 31+2:
	    SQUARE_SINGLE_QUADRANT(0, 1, 90);
	case 31+4:
	    SQUARE_SINGLE_QUADRANT(-1, 0, 180);
	case 31+8:
	    SQUARE_SINGLE_QUADRANT(0, -1, 270);
#undef SQUARE_SINGLE_QUADRANT

#define SQUARE_TWO_NEIGHBOR_QUADRANTS(x, y, angle)		\
	    SQUARE_SINGLE_PIESLICE(x, y, 2)
	case 31+3:	
	    SQUARE_TWO_NEIGHBOR_QUADRANTS(1, 0, 0);
	case 31+6:
	    SQUARE_TWO_NEIGHBOR_QUADRANTS(0, 1, 90);
	case 31+12:
	    SQUARE_TWO_NEIGHBOR_QUADRANTS(-1, 0, 180);
	case 31+9:
	    SQUARE_TWO_NEIGHBOR_QUADRANTS(0, -1, 270);
#undef SQUARE_TWO_NEIGHBOR_QUADRANTS
	  
	case 31+5:
	    SQUARE_TWO_OPPOSING_QUADRANTS(1, 0, 0);
	case 31+10:
	    SQUARE_TWO_OPPOSING_QUADRANTS(0, 1, 90);
	    
#define SQUARE_THREE_QUADRANTS(x, y, angle)			\
	    SQUARE_SINGLE_PIESLICE(x, y, 3)
	case 31+7:
	    SQUARE_THREE_QUADRANTS(1, 0, 0);	    
	case 31+14:
	    SQUARE_THREE_QUADRANTS(0, 1, 90);	    
	case 31+13:
	    SQUARE_THREE_QUADRANTS(-1, 0, 180);	    
	case 31+11:
	    SQUARE_THREE_QUADRANTS(0, -1, 270);	    
#undef SQUARE_THREE_QUADRANTS

#define DIAMOND_SINGLE_QUADRANT(x, y, angle)			\
	    DIAMOND_SINGLE_PIESLICE(x, y, 1)
	case 47+1:
	    DIAMOND_SINGLE_QUADRANT(1, 0, 0);
	case 47+2:
	    DIAMOND_SINGLE_QUADRANT(0, 1, 90);
	case 47+4:
	    DIAMOND_SINGLE_QUADRANT(-1, 0, 180);
	case 47+8:
	    DIAMOND_SINGLE_QUADRANT(0, -1, 270);
#undef DIAMOND_SINGLE_QUADRANT

#define DIAMOND_TWO_NEIGHBOR_QUADRANTS(x, y, angle)		\
	    DIAMOND_SINGLE_PIESLICE(x, y, 2)
	case 47+3:	
	    DIAMOND_TWO_NEIGHBOR_QUADRANTS(1, 0, 0);
	case 47+6:
	    DIAMOND_TWO_NEIGHBOR_QUADRANTS(0, 1, 90);
	case 47+12:
	    DIAMOND_TWO_NEIGHBOR_QUADRANTS(-1, 0, 180);
	case 47+9:
	    DIAMOND_TWO_NEIGHBOR_QUADRANTS(0, -1, 270);
#undef DIAMOND_TWO_NEIGHBOR_QUADRANTS
	  

	case 47+5:
	    DIAMOND_TWO_OPPOSING_QUADRANTS(1, 0, 0);
	case 47+10:
	    DIAMOND_TWO_OPPOSING_QUADRANTS(0, 1, 90);
#undef DIAMOND_TWO_OPPOSING_QUADRANTS
#undef SQUARE_TWO_OPPOSING_QUADRANTS
	    
#define DIAMOND_THREE_QUADRANTS(x, y, angle)			\
	    DIAMOND_SINGLE_PIESLICE(x, y, 3)
	case 47+7:
	    DIAMOND_THREE_QUADRANTS(1, 0, 0);	    
	case 47+14:
	    DIAMOND_THREE_QUADRANTS(0, 1, 90);	    
	case 47+13:
	    DIAMOND_THREE_QUADRANTS(-1, 0, 180);	    
	case 47+11:
	    DIAMOND_THREE_QUADRANTS(0, -1, 270);	    
#undef DIAMOND_THREE_QUADRANTS
#undef DIAMOND_SINGLE_PIESLICE
#undef SQUARE_SINGLE_PIESLICE

	default:
	    int_warn(NO_CARET, "PDF: unknown point type number %d", number);
	}	
    }

    PDF_restore(myPDF);
    PDF_xLast = x;
    PDF_yLast = y;
}


TERM_PUBLIC int
PDF_justify_text (enum JUSTIFY mode)
{
    PDF_TextJust = mode;
    return (TRUE);
}


TERM_PUBLIC int
PDF_text_angle (int ang)
{
    PDF_TextAngle = ang;
    return (TRUE);
}


TERM_PUBLIC void
PDF_put_text (unsigned int x, unsigned int y, const char *str)
{
    char *alignment = NULL;
    double h = x, v = y;
    
    PDF_PathClose ();

    /* horizontal justification*/
    switch (PDF_TextJust) {
    case LEFT:
	alignment = "left";
	break;
    case CENTRE:
	alignment = "center";
	break;
    case RIGHT:
	alignment = "right";
	break;
    }

    if (PDF_TextAngle) {
	PDF_save(myPDF);
	PDF_translate(myPDF, h, v);
	PDF_rotate(myPDF, PDF_TextAngle);
 	/* vertical justification*/
	PDF_translate(myPDF, 0, -(PDF_fontAscent-PDF_fontDescent)/2);
	PDF_show_boxed(myPDF, str, 0,0, 0, 0, alignment, NULL);
	PDF_restore(myPDF);
    } else {
 	/* vertical justification*/
	v -= (PDF_fontAscent - PDF_fontDescent) / 2;
	PDF_show_boxed(myPDF, str, h , v, 0, 0, alignment, NULL);
    }

}


TERM_PUBLIC int
PDF_set_font (const char *font)
{
    if (strlen (font) > 0) {	/* if available, parse the font specification ("fontname,fontsize")*/
	short index;
	char *token,
	    seps[] = ",", *buffer = (char *) malloc (strlen (font) + 1);

	if (buffer == NULL)
	    return (FALSE);
	strcpy (buffer, font);

	for (token = strtok (buffer, seps), index = 1;
	     token != NULL; token = strtok (NULL, seps), index++
	    ) {
	    switch (index) {
	    case 1:
		strcpy (PDF_fontNameCur, token);
		break;	/* font name*/
	    case 2:
		PDF_fontSizeCur = atoi (token);
		break;	/* font size*/
	    default:
		break;
	    }
	}

	free (buffer);
    } else {			/* otherwise simply reset the default font*/
	strcpy (PDF_fontNameCur, PDF_fontNameDef);
	PDF_fontSizeCur = PDF_fontSizeDef;
    }


    PDF_PathClose();
    PDF_SetFont();
    return (TRUE);
}

TERM_PUBLIC void
PDF_boxfill(int style, unsigned int x1, unsigned int y1,
	    unsigned int width, unsigned int height)
{
#if USE_ULIG_FILLEDBOXES
    /* fillpar:
     * - solid   : 0 - 100
     * - pattern : 0 - 100
     */
    int fillpar = style >> 4;

    /* style:
     *   0 == empty
     *   1 == solid
     *   2 == pattern
     */
    style &= 0xf;
#else
    (void) style;		/* unused */
#endif

    PDF_PathClose();
    PDF_save(myPDF);

#if USE_ULIG_FILLEDBOXES
    switch (style) {
	case 0: /* fill with white */
#endif
	    PDF_setgray(myPDF, 1);
#if USE_ULIG_FILLEDBOXES
	    break;
	case 1: /* solid fill */
	    {
		int real_linetype = PDF_Pen_RealID(PDF_LineType);
		struct rgb *this_color = web_color_rgbs + 1 + real_linetype;

		double fact = (100 - fillpar) * 0.01;
		double fact_ = (1.0 - fact) / 255.0;

		double red = (double)this_color->r   * fact_ + fact;
		double green = (double)this_color->g * fact_ + fact;
		double blue = (double)this_color->b  * fact_ + fact;

		PDF_setcolor(myPDF, "fill", "rgb", red, green, blue, 0 /* unused */);
	    }
	    break;
	case 2: /* pattern fill */
	    fillpar = fillpar % (PDF_patterns + 1) /* 0 == white */;
	    switch (fillpar) {
		case 0:
		    /* fill with white */
		    PDF_setcolor(myPDF, "fill", "rgb", 1, 1, 1, 0 /* unused */);
		    break;
		default:
		    /* fill with white */
		    PDF_setcolor(myPDF, "fill", "pattern", PDF_patternHandles[fillpar - 1], 0, 0, 0);
	    }
	    break;
	default:
	    break;
    }
#endif

    PDF_rect(myPDF, x1, y1, width, height);
    PDF_fill(myPDF);
    PDF_restore(myPDF);
}

#ifdef PM3D

TERM_PUBLIC int
PDF_make_palette(t_sm_palette *palette)
{
    if (palette == NULL) {
	/* pdf can do continuous colors */
	return 0;
    }

    return 0;
}


TERM_PUBLIC void
PDF_set_color(double gray)
{
    rgb_color color;

    if( sm_palette.use_maxcolors != 0 ) {
        /* finite nb of colors explicitly requested */
        if( gray >= ( (double)(sm_palette.use_maxcolors-1) )
	              / sm_palette.use_maxcolors )
	    gray = 1.0;
	else
	    gray = floor( gray * sm_palette.use_maxcolors ) 
	           / sm_palette.use_maxcolors;
    }


    color_from_gray( gray, &color );

    /* make sure that the path is stroked with the current color
     * before changing the color */
    PDF_PathClose();
    PDF_setcolor(myPDF, "both", "rgb", color.r, color.g, color.b, 0 /* unused */);
    /* mark linetype invalid so that the color will be
     * set, when PDF_linetype() is called */
    PDF_LineType = -3;
}


TERM_PUBLIC void
PDF_filled_polygon(int points, gpiPoint* corners)
{
    PDF_PathClose();
    if (4 != points) {
	fprintf(stderr,
	       	"** ERROR PDF_filled_polygon supports only 4 points (file %s, line %d)",
	       	__FILE__, __LINE__);
	return;
    }
    PDF_moveto(myPDF, corners[0].x, corners[0].y);
    PDF_lineto(myPDF, corners[1].x, corners[1].y);
    PDF_lineto(myPDF, corners[2].x, corners[2].y);
    PDF_lineto(myPDF, corners[3].x, corners[3].y);
    PDF_lineto(myPDF, corners[0].x, corners[0].y);
    PDF_fill(myPDF);
}

TERM_PUBLIC void
PDF_previous_palette(void)
{
}

#endif /* PM3D */
    
#endif /* TERM_BODY */

#ifdef TERM_TABLE
TERM_TABLE_START (pdf_driver)
    "pdf", "PDF (Portable Document File) file driver",
    0 /* xmax */ , 0 /* ymax */ , 0 /* vchar */ , 0 /* hchar */ ,
    0 /* vtic */ , 0 /* htic */ ,
    PDF_options, PDF_init, PDF_reset, PDF_text, null_scale, PDF_graphics,
    PDF_move, PDF_vector, PDF_linetype, PDF_put_text, PDF_text_angle,
    PDF_justify_text, PDF_point, do_arrow, PDF_set_font, do_pointsize,
    TERM_BINARY,
    0 /* suspend */, 0 /* resume */ , PDF_boxfill, PDF_linewidth
#ifdef PM3D
#   ifdef USE_MOUSE
   , 0, 0, 0, 0, 0 /* no mouse support for pdf */
#   endif
   , PDF_make_palette,
   PDF_previous_palette,
   PDF_set_color,
   PDF_filled_polygon
#endif /* PM3D */
TERM_TABLE_END (pdf_driver)
#undef LAST_TERM
#define LAST_TERM pdf_driver
#endif /* TERM_TABLE */

#endif /* TERM_PROTO_ONLY */

#ifdef TERM_HELP
    START_HELP (pdf)
    "1 pdf",
    "?commands set terminal pdf",
    "?set terminal pdf",
    "?set term pdf",
    "?terminal pdf",
    "?term pdf",
    "?pdf",
    " This terminal produces files in the Adobe Portable Document Format",
    " (PDF), useable for printing or display with tools like Acrobat Reader",
    "",
    " Syntax:",
    "       set terminal pdf {fname \"<font>\"} {fsize <fontsize>}",
    "",
    " where <font> is the name of the default font to use (default Helvetica)",
    " and <fontsize> is the font size (in points, default 12)"
    END_HELP (pdf)
#endif