/*
 *                            COPYRIGHT
 *
 *  PCB, interactive printed circuit board design
 *  Copyright (C) 1994,1995 Thomas Nau
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Contact addresses for paper mail and Email:
 *  Thomas Nau, Schlehenweg 15, 88471 Baustetten, Germany
 *  Thomas.Nau@rz.uni-ulm.de
 *
 */

/*
 *  This driver was modeled on the PCB 1.4.0 PS driver by Thomas Nau.  It was
 *  modified to produce Gerber/RS-274D photoplotter command files suitable
 *  for use in the fabrication of printed circuit boards by Albert John
 *  FitzPatrick III on April 6, 1996.
 *
 *  Contact address for Email:
 *  Albert John FitzPatrick III <ajf_nylorac@acm.org>
 *
 */

/*
 * Monte Bateman contributed the support for the recognition and the filling of
 * rectangular polygons.
 */

static	char	*rcsid = "$Id: dev_gerber.c,v 145.1 1997/07/26 12:56:19 nau Exp $";

/*
 * Gerber/RS-274D device driver
 */

/*
 * - RS-274D as used within this driver:
 *    + A RS-274D file consists of a sequence of blocks.
 *    + Each block consists of:
 *        = An optional setup sequence. (G)
 *        = An optional aperture selection. (D)
 *        = An optional arc parameter specification sequence. (I,J)
 *        = An optional motion parameter specification sequence. (X,Y)
 *        = An optional control command sequence (D,M)
 *        = A mandatory "*" termination character. ("*")
 *        = An optional ASCII CR-LF sequence. ("\015\012")
 *    + Setup Codes:
 *        = G01 - Enter vector mode.
 *        = G02 - Enter clockwise arc mode.
 *        = G03 - Enter counter clockwise arc mode.
 *        = G04 - Enter comment mode until the block ends.
 *        = G54 - Prepare to change aperture.
 *        = G75 - Select full 360 degree arc mode.
 *        = G90 - Enter absolute coordinate mode.
 *    + Aperture Selection Codes:
 *        = Dn  - Select aperture n, where n is:
 *                   # 10,11,...,19,70,71,20,21,...,29,72,73 for traditional
 *                     (usually vector) 24 aperture photoplotters.
 *                   # 10,11,... for modern (usually raster) photoplotters.
 *    + Arc Parameter Specification Codes:
 *        = In  - Specifies n as an arc center's X coordinate relative to
 *                the arc's origin.
 *        = Jn  - Specifies n as an arc center's Y coordinate relative to
 *                the arc's origin.
 *    + Motion Parameter Specfication Codes:
 *        = Xn  - Specifies the new X coordinate.
 *        = Yn  - Specifies the new Y coordinate.
 *    + Control Command Codes:
 *        = D01 - Move with aperture light on.
 *        = D02 - Move with aperture light off.
 *        = D03 - Move with aperture light off, flash it on, turn it off.
 *        = M02 - End photoplotting.
 *    + Note:
 *        = Optional codes are modal.  Previous values will be used in future
 *          blocks unless new values are provided within those blocks.
 *        = This driver makes little use of modality because it:
 *             # Only saves output file size, which was only critical in the
 *               age of paper tape files.
 *             # Leads to many additional failure modes based upon the
 *               unpredictability of layout content and the fact that I am
 *               too lazy to write a lower-level output monitor that could
 *               reliably elide redundant codes.
 *        = This driver attempts to make the output files editable by humans
 *          using standard POSIX text processing facilities.  Since the RS-274D
 *          standard allows for the optional termination of blocks with an
 *          ASCII CR-LF sequence, and the ASCII LF character has the same
 *          value as the POSIX newline character, and the POSIX text processing
 *          facilities coexist well with ASCII CR characters, and output file
 *          size is not a critical factor, this driver makes extensive use of
 *          the optional CR-LF sequence to punctuate the output codes.
 *
 *          Typically, each and every PCB drawing entity is placed within it's
 *          own block.  Large entities are placed in several consecutive
 *          blocks.  On occasion, comments are inserted to aid in the
 *          recognition of the entities (I.E. text).
 */

/* 
 * FIXME: Fill polygons (including rectangles).
 * FIXME: Handle production of physical layer (PCB group) files based upon the
 *	combination of logical layer (PCB layer) data.
 * FIXME: Beware of RS-274D character set limitations.  Filter non-RS-274D
 *	characters from output ('#' is not valid).
 * FIXME: Enhance media selection (allow offset 0, etc.) in printdialog.c.
 * FIXME: Handled arcs where height != width.
 * FIXME: Analyze computational accuracy especially with respect to sin()/cos().
 */

#include <math.h>
#include <time.h>
#include <pwd.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <varargs.h>

#include "global.h"

#include "data.h"
#include "dev_gerber.h"
#include "error.h"
#include "misc.h"
#include "rotate.h"

/* ---------------------------------------------------------------------------
 * some additional defines
 */

/* FIXME: Shouldn't this be in macro.h? */
#define TO_RADIANS(degrees) (M180 * (degrees))

/*----------------------------------------------------------------------------*/
/* Private constants                                                          */
/*----------------------------------------------------------------------------*/

/* FIXME: Port these constants to PCB; set based upon X resource and/or
	PCB constants. */

/* Maximum length of line with a PCB file. */
#define GB_MAXLINELEN 1024

/* Maximum length of token within a PCB file. */
#define GB_MAXTOKENLEN 64

/* Maximum number of aperture variations in use within a PCB file. */
#define GB_MAXAPERTURECOUNT 256

/*----------------------------------------------------------------------------*/
/* Private data structures                                                    */
/*----------------------------------------------------------------------------*/

enum ApertureShape {
	ROUND,		/* Shaped like a circle */
	DONUT,		/* Shaped like a donut */
	SQUARE,		/* Shaped like a square */
	RECTANGLE,	/* Shaped like a rectangle */
	POLYGON,	/* Shaped like a polygon */
	THERMAL,	/* Shaped like a thermal relief */
	THERMAL45	/* Shaped like a thermal45 relief */
};

typedef enum ApertureShape ApertureShape;

typedef struct Aperture {
	int dCode;				/* The RS-274X D code */
	int apertureXSize;			/* Size in mils */
	int apertureYSize;			/* Size in mils */
	ApertureShape apertureShape;		/* ROUND, DONUT, SQUARE, */
						/* RECTANGLE, POLYGON, */
						/* THERMAL, THERMAL45 */
} Aperture; 

typedef struct Apertures {
	int nextAvailable;			/* Number of apertures */
	Aperture aperture[GB_MAXAPERTURECOUNT];
} Apertures;

FILE	*appfile;
FILE	*mapfile;

/*----------------------------------------------------------------------------*/
/* Utility routines                                                           */
/*----------------------------------------------------------------------------*/

#define gerberX(pcb, x) ((long) (x))
#define gerberY(pcb, y) ((long) ((pcb)->MaxHeight - (y)))
#define gerberXOffset(pcb, x) ((long) (x))
#define gerberYOffset(pcb, y) ((long) -(y))

/* ---------------------------------------------------------------------------
 * some local prototypes
 */
static	char	*GB_Preamble(PrintInitTypePtr, char *);
static	void	GB_Exit(void);
static	void	GB_Init(PrintInitTypePtr);
static	void	GB_Postamble(void);
static	void	GB_SetColor(XColor, int);
static	void	GB_Drill(PinTypePtr, int);
static	void	GB_PrintLayer(LayerTypePtr, int, Boolean, int);
static	void	GB_PrintElementPackage(ElementTypePtr, int);
static	void	GB_PrintSomeText(TextTypePtr, int);
static	void	GB_PrintPad(PadTypePtr, int);
static	void	GB_PrintPinOrVia(PinTypePtr, int);
static	void	GB_PrintPadMask(PadTypePtr, int);
static	void	GB_PrintPinOrViaMask(PinTypePtr, int);
static	void	GB_PrintClearPinOrViaOnGroundplane(PinTypePtr, int);
static	void	GB_PrintMaskOrGroundplaneRectangle(Position, Position,
			Position, Position, int);
static  void    GB_PrintOutline(Position, Position,
			Position, Position, int);
static  void    GB_PrintAlignment(Position, Position,
			Position, Position, int);
static	void	GB_PrintDrillingHelper(PinTypePtr, int);

static	void	GB_PrintPolygon(FILE *, PolygonTypePtr, int);
static	void	GB_FPrintFilledRectangle(FILE *, Position, Position,
			Position, Position, int);
static	void	GB_FPrintPinOrVia(FILE *, PinTypePtr, int);
static	void	GB_PrintText(FILE *, TextTypePtr, int);
static  void    GB_PrintLine(FILE *, LineTypePtr, int);


/* ----------------------------------------------------------------------
 * some local identifiers
 *
 */
static	PrintDeviceType	GB_QueryConstants = {
	"Gerber/RS-274D",			/* name of driver */
	"gbr",					/* filename suffix */

	GB_Init,				/* initializes driver */
	GB_Exit,				/* exit code */
	GB_Preamble,
	GB_Postamble,

	GB_SetColor,				/* set color */
	GB_Drill,				/* drilling information */

	GB_PrintLayer,				/* print layer */
	GB_PrintElementPackage,			/* print element package */
	GB_PrintSomeText,			/* print some (silkscreen, etc.) text */
	GB_PrintPad,				/* print pad */
	GB_PrintPinOrVia,			/* print pin or via */
	GB_PrintPadMask,			/* print pad mask */
	GB_PrintPinOrViaMask,			/* print pin or via mask */
	GB_PrintClearPinOrViaOnGroundplane,	/* print clear pin or via on groundplane */
	GB_PrintMaskOrGroundplaneRectangle,	/* print filled rectangle
						   for ground planes and/or
						   for solder masks */
	GB_PrintOutline,			/* print board outline */
	GB_PrintAlignment,			/* print alignment marks */
	GB_PrintDrillingHelper,			/* print drilling helper marks */

	False,					/* handles colored output */
	False,					/* handles inverted output */
	True,					/* handles ground planes */
	True,					/* handles drill info */
	True,					/* handles masks */
	False,					/* handles media */
	False,					/* needs preprint calls */
	True,					/* needs an AppMap */
	};

static	PrintInitType	GB_Flags;

static	char	*GB_Functions[] = { "",
	};

static int GB_debug = 0;			/* Stimulates tracing */

static Apertures GB_Apertures = { 0 };		/* Holds aperture definitions */

static Boolean GB_ErrorOccurred;

/*----------------------------------------------------------------------------*/
/* Error Logging Routines                                                     */
/*----------------------------------------------------------------------------*/
static void logError(fp, format, va_alist)
	FILE *fp;
	char *format;
	va_dcl
{
	va_list args;
	char    s[1024];

	va_start(args);
	vsprintf(s, format, args);
	fputs(s, fp);
	va_end(args);
}

/*----------------------------------------------------------------------------*/
/* Aperture Routines                                                          */
/*----------------------------------------------------------------------------*/

char *apertureShape(ApertureShape shape)
{
	char *cp;

	if (shape == ROUND)
		cp = "round";
	else if (shape == DONUT)
		cp = "donut";
	else if (shape == SQUARE)
		cp = "square";
	else if (shape == RECTANGLE)
		cp = "rectangle";
	else if (shape == POLYGON)
		cp = "polygon";
	else if (shape == THERMAL)
		cp = "thermal";
	else if (shape == THERMAL45)
		cp = "thermal45";
	else
		cp = "*invalid-shape*";

	return (cp);
}

#define	why	"   TH      0    0.0000      D"
#define	aspace	" "

static int findApertureCode(
	Apertures *apertures,
	int width,
	int height,
	ApertureShape shape)
{
	Aperture *ap;
	int i;
	int dNum;
	char *ncp;
	char str1[5];
	char str2[5];
	double FL_apertureXSize;
	double FL_apertureYSize;
	char mapstr[70];
	char tmpstr[40];
	int strnum;

	/* Search for an appropriate aperture. */

	for (i = 0; i < apertures->nextAvailable; i++) {
		ap = &apertures->aperture[i];

		dNum = i;
		if (ap->apertureXSize == width
			&& ap->apertureYSize == height
			&& ap->apertureShape == shape) {
			return (ap->dCode);
		}
	}

	appfile = fopen("custom.app", "a+" );
	mapfile = fopen("default.map", "a+");

	dNum = apertures->nextAvailable++;
	ap = &apertures->aperture[dNum];

	ap->dCode = 10 + dNum;
	ap->apertureXSize = width;
	ap->apertureYSize = height;

	sprintf(str1,"0.%03d", ap->apertureXSize);
	FL_apertureXSize = atof(str1);
	sprintf(str2,"0.%03d", ap->apertureYSize);
	FL_apertureYSize = atof(str2);

	if (strcmp(apertureShape(shape), "round") == 0) {
		ap->apertureShape = ROUND;
		ncp = "Round";
		FL_apertureYSize = FL_apertureXSize;
	} else if (strcmp(apertureShape(shape), "donut") == 0) {
			ap->apertureShape = DONUT;
			ncp = "Donut";
	} else if (strcmp(apertureShape(shape), "square") == 0) {
			ap->apertureShape = SQUARE;
			ncp = "Square";
	} else if (strcmp(apertureShape(shape), "rectangle") == 0) {
			ap->apertureShape = RECTANGLE;
			ncp = "Rectangle";
	} else if (strcmp(apertureShape(shape), "polygon") == 0) {
			ap->apertureShape = POLYGON;
			ncp = "Polygon";
	} else if (strcmp(apertureShape(shape), "thermal") == 0) {
			ap->apertureShape = THERMAL;
			ncp = "Thermal";
	} else if (strcmp(apertureShape(shape), "thermal45") == 0) {
			ap->apertureShape = THERMAL45;
			ncp = "Thermal45";
	}

	fprintf(appfile, "D%d\t%d\t%d\t%s\015\012",
			ap->dCode,
			ap->apertureXSize,
			ap->apertureYSize,
			apertureShape(shape));

	sprintf(mapstr,"D%d ", ap->dCode);
	strnum = strlen(mapstr);
	if (strnum == 4)
		strncat(mapstr,aspace,1);
	sprintf(tmpstr,"%s ", ncp);
	strnum = strlen(tmpstr);
	strncat(mapstr,tmpstr,strnum);
	while(strnum < 10) {
		strncat(tmpstr,aspace,1);
		strnum = strlen(tmpstr);
		strncat(mapstr,aspace,1);
	}
	sprintf(tmpstr,"%04.3f00 ", FL_apertureXSize);
	strnum = strlen(tmpstr);
	strncat(mapstr,tmpstr,strnum);
	sprintf(tmpstr,"%04.3f00", FL_apertureYSize);
	strnum = strlen(tmpstr);
	strncat(mapstr,tmpstr,strnum);
	sprintf(tmpstr,"%s", why);
	strnum = strlen(tmpstr);
	strncat(mapstr,tmpstr,strnum);
	sprintf(tmpstr,"%d      ", ap->dCode);
	strnum = strlen(tmpstr);
	strncat(mapstr,tmpstr,strnum);
	if (strnum == 8)
		strncat(mapstr,aspace,1);
	sprintf(tmpstr,"0\015\012");
	strnum = strlen(tmpstr);
	strncat(mapstr,tmpstr,strnum);

	fprintf(mapfile, "%s",mapstr);

	fclose(mapfile);
	fclose(appfile);

	return (ap->dCode);
}

static void initApertures(Apertures *apertures)
{
	int		i;
	Aperture	*ap;
	struct	passwd	*pwentry;
	char		line[GB_MAXLINELEN];

	apertures->nextAvailable = 0;

	i = apertures->nextAvailable;
	ap = &apertures->aperture[i];

	ap->dCode = 0;
	ap->apertureXSize = 0;
	ap->apertureYSize = 0;
	ap->apertureShape = 0;

	if ((mapfile = fopen("default.map", "r" )) == NULL) {
		mapfile = fopen("default.map", "w");

		/* ID the user. */
		pwentry = getpwuid(getuid());
		fprintf(mapfile, "# Format Gerber, Precision 2.3 \015\012");
		fprintf(mapfile, "# Default.Map for Gerber/RS-274D Format \015\012");
		fprintf(mapfile, "# View with GerberTool 6.2b1 by OrCAD, A HACK OUPPUT \015\012");
		fprintf(mapfile, "# Format Gerber, Precision 2.3 \015\012");
		fprintf(mapfile, "# Title: %s \015\012",UNKNOWN(PCB->Name));
		fprintf(mapfile, "# Creator: %s "RELEASE" \015\012", Progname);
		fprintf(mapfile, "# For: %s \015\012", pwentry->pw_name);
	        fprintf(mapfile, "# Format: Gerber/RS-274D 2,3 leading inch \015\012");
		fprintf(mapfile, "# PCB-Dimensions: %ld %ld \015\012",
			(long) PCB->MaxWidth,
			(long) PCB->MaxHeight);
		fprintf(mapfile, "# PCB-Coordinate-Origin: lower left \015\012");
		fprintf(mapfile, "#\015\012");
		fprintf(mapfile, "#    Shape     Width   Height    Type  Tool   Tool Size   Legend   R90\015\012");
		fprintf(mapfile, "#\015\012");
		fclose(mapfile);
	}

	if ((appfile = fopen("custom.app", "r" )) == NULL) {
		appfile = fopen("custom.app", "w");

		/* ID the user. */
		pwentry = getpwuid(getuid());
		fprintf(appfile, "# Format Gerber, Precision 2.3 \015\012");
		fprintf(appfile, "# Title: %s \015\012",UNKNOWN(PCB->Name));
		fprintf(appfile, "# Creator: %s "RELEASE" \015\012", Progname);
		fprintf(appfile, "# For: %s \015\012", pwentry->pw_name);
		fprintf(appfile, "# Format: Gerber/RS-274D 2,3 leading inch \015\012");
		fprintf(appfile, "# PCB-Dimensions: %ld %ld \015\012",
			(long) PCB->MaxWidth,
			(long) PCB->MaxHeight);
		fprintf(appfile, "# PCB-Coordinate-Origin: lower left \015\012");
		fprintf(appfile, "#\015\012");
		fprintf(appfile, "# DCode\tXSize\tYSize\tShape\015\012");
		fprintf(appfile, "#\tmils\tmils\015\012");
		fprintf(appfile, "#\015\012");
	} else {
		while (fgets(line, sizeof line, appfile) != (char *) NULL &&
			apertures->nextAvailable < GB_MAXAPERTURECOUNT) {
			if (*line == '#') {
				/* Skip comment lines */
				continue;
			} else if (strcmp(line, "\n") == 0) {
				/* Skip blank lines. */
				continue;
			} else {
				int dCode;
				int apertureXSize;
				int apertureYSize;
				char shape[GB_MAXTOKENLEN];

				if (sscanf(line, "D%d %d %d %s",
					&dCode,
					&apertureXSize,
					&apertureYSize,
					shape) == 4) {

					i = apertures->nextAvailable++;
					ap = &apertures->aperture[i];

					ap->dCode = dCode;
					ap->apertureXSize = apertureXSize;
					ap->apertureYSize = apertureYSize;

					if (strcmp(shape, "round") == 0) {
						ap->apertureShape = ROUND;
					} else if (strcmp(shape, "donut") == 0) {
						ap->apertureShape = DONUT;
					} else if (strcmp(shape, "square") == 0) {
						ap->apertureShape = SQUARE;
					} else if (strcmp(shape, "rectangle") == 0) {
						ap->apertureShape = RECTANGLE;
					} else if (strcmp(shape, "polygon") == 0) {
						ap->apertureShape = POLYGON;
					} else if (strcmp(shape, "thermal") == 0) {
						ap->apertureShape = THERMAL;
					} else if (strcmp(shape, "thermal45") == 0) {
						ap->apertureShape = THERMAL45;
					}

				}
			}
		}
	}
	fclose(appfile);
}

/* ---------------------------------------------------------------------------
 * returns information about this driver
 */
PrintDeviceTypePtr GB_Query(void)
{
	return(&GB_QueryConstants);
}

static void GB_Init(PrintInitTypePtr Flags)
{
}
static void GB_Exit(void)
{
}
/* ----------------------------------------------------------------------
 * prints custom Gerber/RS-274D compatible header with function definition
 * info struct is are passed in
 */
static char *GB_Preamble(PrintInitTypePtr Flags, char *Description)
{
	int		i;
	int		j;
	time_t		currenttime;
        char		utcTime[64];
        struct  passwd  *pwentry;

		/* save passed-in data */
	GB_Flags = *Flags;
	currenttime = time(NULL);

		/* Create a portable timestamp. */
        strftime(utcTime, sizeof utcTime, "%c UTC", gmtime(&currenttime));

		/* No errors have occurred so far. */
	GB_ErrorOccurred = False;

		/* ID the user. */
        pwentry = getpwuid(getuid());

	/* Print a cute file header at the beginning of each file. */
	fprintf(GB_Flags.FP, "G04 Title: %s, %s *\015\012", UNKNOWN(PCB->Name),
		UNKNOWN(Description));
	fprintf(GB_Flags.FP, "G04 Creator: %s "RELEASE" *\015\012", Progname);
	fprintf(GB_Flags.FP, "G04 CreationDate: %s *\015\012", utcTime);
	fprintf(GB_Flags.FP, "G04 For: %s *\015\012", pwentry->pw_name);
	fprintf(GB_Flags.FP, "G04 Format: Gerber/RS-274D 2,3 leading inch *\015\012");
	fprintf(GB_Flags.FP, "G04 PCB-Dimensions: %ld %ld *\015\012",
		(long) PCB->MaxWidth,
		(long) PCB->MaxHeight
		);
	fprintf(GB_Flags.FP, "G04 PCB-Coordinate-Origin: lower left *\015\012");

		/* black is default drawing color */
	fprintf(GB_Flags.FP,
		"G04 Color: R%ld G%ld B%ld *\015\012",
		0L,
		0L,
		0L);

		/* print own procedures */
	for (j = 0; j < ENTRIES(GB_Functions); j++)
		fprintf(GB_Flags.FP, "%s*\015\012", GB_Functions[j]);

        	/* We assume that the initial state has the light off. */

       		/* Signal Absolute Data. */
	fprintf(GB_Flags.FP,
        	"G90*\015\012");

       		/* Signal Straight Line Data. */
	fprintf(GB_Flags.FP,
       		"G01*\015\012");

		/* Load databases. */
        initApertures(&GB_Apertures);

	return(NULL);
}

/* ---------------------------------------------------------------------------
 * exit code for this driver is empty
 */
static void GB_Postamble(void)
{
	fprintf(GB_Flags.FP, "D02*\015\012");	/* Turn off the light */
	fprintf(GB_Flags.FP, "M02*\015\012");	/* Signal End-of-Plot. */

	if (GB_ErrorOccurred != False)
		logError(stderr,
		"An error occurred.  See the output file(s) for details.\n");
}

/* ----------------------------------------------------------------------
 * handles drilling information
 */
static void GB_Drill(PinTypePtr PinOrVia, int unsued)
{
	/* FIXME: Accumulate and sort by drill size. */
	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD03*\015\012",
		findApertureCode(&GB_Apertures,
			PinOrVia->DrillingHole,
			TEST_FLAG(SQUAREFLAG, PinOrVia) ? PinOrVia->DrillingHole : 0,
			TEST_FLAG(SQUAREFLAG, PinOrVia) ? SQUARE : ROUND),
		gerberX(PCB, PinOrVia->X),
		gerberY(PCB, PinOrVia->Y));
}

/* ----------------------------------------------------------------------
 * prints layer data
 */
static void GB_PrintLayer(LayerTypePtr Layer, int GroupNumber,
	Boolean SilkscreenTextFlag, int unsued)
{
	FILE *FP;

	FP = GB_Flags.FP;
	fprintf(FP,
		"G04 LayerGroup: %i *\015\012",
		GroupNumber + 1);
	fprintf(FP,
		"G04 Layer: \"%s\" (%i) *\015\012",
		UNKNOWN(Layer->Name),
		GetLayerNumber(PCB->Data, Layer) + 1);
	LINE_LOOP(Layer, GB_PrintLine(FP, line, unsued));
	if (! SilkscreenTextFlag)
		TEXT_LOOP(Layer, GB_PrintText(FP, text, unsued));
	POLYGON_LOOP(Layer, GB_PrintPolygon(FP, polygon, unsued));
}

/* ----------------------------------------------------------------------
 * prints a line
 */
static void GB_PrintLine(FILE *FP, LineTypePtr Line, int unsued)
{
	fprintf(FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, Line->Thickness, 0, ROUND),
		gerberX(PCB, Line->Point1.X),
		gerberY(PCB, Line->Point1.Y),
		gerberX(PCB, Line->Point2.X),
		gerberY(PCB, Line->Point2.Y));
}

/* ----------------------------------------------------------------------
 * Checks a four-point polygon to see if it's a rectangle.
 * Tick off pairs of X & Y coords; if we get four matches,
 * we have a rectangle.
 */
static int isRectangle(PolygonTypePtr Ptr)
{
	Cardinal i, j;
	int matches = 0;

        if (Ptr->PointN != 4)
	   return(0);

        for (i=0; i<4; i++)
           for (j = i+1; j<4; j++) {
	      if (Ptr->Points[i].X == Ptr->Points[j].X)
	         matches++;

	      if (Ptr->Points[i].Y == Ptr->Points[j].Y)
	         matches++;
	   }

        if (matches == 4)
	   return(1);
	else
	   return(0);
 }

/* ---------------------------------------------------------------------------
 * prints a filled polygon
 */
static void GB_PrintPolygon(FILE *FP, PolygonTypePtr Ptr, int unsued)
{
	static int isRectangle(PolygonTypePtr Ptr);
	int firstTime = 1;
	Position startX, startY;

	/* FIXME: Steal code from somewhere to complete this function. */
	/* FIXME: Beware of overexposure; handle it, if possible
		(probably too hard and only needed for a true photoplotter). */
	/* FIXME: Don't use 5 mil line width: Fill the polygon. */
	/* FIXME: The polygon wire-frames use 5 mil lines on center, therefore
		2.5 mil overhangs each ideal polygon edge. */
	/* FIXME: Consider the following notes by Monte Bateman:
		Here is how I would implement the more general polygon fill:

		1.  Use the built-in variables for minx, maxx, miny, and maxy for the
    		specified polygon.  Allocate a block of memory (2-D array) that is
    		(maxx - minx) X (maxy - miny) large.  Init the array to all zeroes.

		2.  Run through the list of points and "draw" lines between them into this
    		array.  Set to one each array element where a line crossess.
    		Use Bresenham's algorithm to draw the lines.

		3.  When finished, scan each row of the array.  When you find a '1',
    		start a horizontal line there.  When you find the next '1', end the
    		horizontal line there.  This will take a bit of experimenting to
    		find the proper spacing; I suggest using a 2mil round aperture and
    		scan every other horizontal row in the array.

		4.  This should give (close to) the desired results.  There is probably
    		a much better way to allocate the memory and "draw" the polygon
    		lines --- we really only need one bit per pixel, but that makes the
    		memory mapping tricky.  In reality, the large areas will be filled
    		with rectangles, and that is already hadled with my earlier code.
    		Non-rectangular polygons will be used to fill in weird-shaped little
    		corners, etc.  So it won't need too huge a block of memory, and you 
    		can get rid of it when finished.

    		I also wouldn't worry too much about the bizarre, folded polygons
    		(like the one in your test file).  If a user gets something that
    		doesn't fill correctly, they can go back a re-draw with "regular"
    		polygons and rectangles.  I would bet that 90% of the filling will be
    		simple rectangles, and the next largest percentage will be triangles.
		*/

	if (isRectangle(Ptr)) {
		GB_FPrintFilledRectangle(
			FP,
			Ptr->BoundingBox.X1,
			Ptr->BoundingBox.Y1,
			Ptr->BoundingBox.X2,
			Ptr->BoundingBox.Y2,
			unsued);
	} else {
		fprintf(FP,
			"G54D%d*",
			findApertureCode(&GB_Apertures, 5, 0, ROUND));
		POLYGONPOINT_LOOP(Ptr,
			{
				fprintf(FP,
					"X%ldY%ld%s*\015\012",
					gerberX(PCB, point->X),
					gerberY(PCB, point->Y),
					(firstTime ? "D02" : "D01"));
				if (firstTime) {
					firstTime = 0;
					startX = point->X;
					startY = point->Y;
				}
			}
 		);
		fprintf(FP,
			"X%ldY%ldD01*\015\012",
			gerberX(PCB, startX),
			gerberY(PCB, startY));
	}
}

/* ----------------------------------------------------------------------
 * prints a text
 * the routine is identical to DrawText() in module draw.c except
 * that DrawLine() and DrawRectangle() are replaced by their corresponding
 * printing routines
 */
static void GB_PrintText(FILE *FP, TextTypePtr Text, int unsued)
{
	Position	x = 0,
			width;
	unsigned char	*string = (unsigned char *) Text->TextString;
	Cardinal	n;
	FontTypePtr	font = &PCB->Font;

		/* Add the text to the gerber file as a comment. */
	if (string && *string)
		fprintf(FP,
			"G04 Text: %s *\015\012",
			string);

		/* get the center of the text for mirroring */
	width = Text->Direction & 0x01 ? 
		Text->BoundingBox.Y2 -Text->BoundingBox.Y1 :
		Text->BoundingBox.X2 -Text->BoundingBox.X1;
	while (string && *string)
	{
			/* draw lines if symbol is valid and data is present */
		if (*string <= MAX_FONTPOSITION && font->Symbol[*string].Valid)
		{
			LineTypePtr	line = font->Symbol[*string].Line;
			LineType	newline;

			for (n = font->Symbol[*string].LineN; n; n--, line++)
			{
					/* create one line, scale, move, rotate and swap it */
				newline = *line;
				newline.Point1.X = (newline.Point1.X +x) *Text->Scale /100;
				newline.Point1.Y = newline.Point1.Y      *Text->Scale /100;
				newline.Point2.X = (newline.Point2.X +x) *Text->Scale /100;
				newline.Point2.Y = newline.Point2.Y      *Text->Scale /100;
				newline.Thickness = newline.Thickness *Text->Scale /100;

				RotateLineLowLevel(&newline, 0, 0, Text->Direction);

					/* the labels of SMD objects on the bottom
					 * side haven't been swapped yet, only their offset
					 */
				if (TEST_FLAG(ONSOLDERFLAG, Text))
				{
					newline.Point1.X = SWAP_SIGN_X(newline.Point1.X);
					newline.Point1.Y = SWAP_SIGN_Y(newline.Point1.Y);
					newline.Point2.X = SWAP_SIGN_X(newline.Point2.X);
					newline.Point2.Y = SWAP_SIGN_Y(newline.Point2.Y);
				}
					/* add offset and draw line */
				newline.Point1.X += Text->X;
				newline.Point1.Y += Text->Y;
				newline.Point2.X += Text->X;
				newline.Point2.Y += Text->Y;
				GB_PrintLine(FP, &newline, unsued);
			}

				/* move on to next cursor position */
			x += (font->Symbol[*string].Width +font->Symbol[*string].Delta);
		}
		else
		{
				/* the default symbol is a filled box */
			BoxType		defaultsymbol = PCB->Font.DefaultSymbol;
			Position	size = (defaultsymbol.X2 -defaultsymbol.X1) *6/5;

			defaultsymbol.X1 = (defaultsymbol.X1 +x) *Text->Scale /100;
			defaultsymbol.Y1 = defaultsymbol.Y1      *Text->Scale /100;
			defaultsymbol.X2 = (defaultsymbol.X2 +x) *Text->Scale /100;
			defaultsymbol.Y2 = defaultsymbol.Y2      *Text->Scale /100;

			RotateBoxLowLevel(&defaultsymbol, 0, 0, Text->Direction);

			if (TEST_FLAG(ONSOLDERFLAG, Text))
			{
				defaultsymbol.X1 = SWAP_SIGN_X(defaultsymbol.X1);
				defaultsymbol.Y1 = SWAP_SIGN_Y(defaultsymbol.Y1);
				defaultsymbol.X2 = SWAP_SIGN_X(defaultsymbol.X2);
				defaultsymbol.Y2 = SWAP_SIGN_Y(defaultsymbol.Y2);
			}
				/* add offset and draw filled box */
			defaultsymbol.X1 += Text->X;
			defaultsymbol.Y1 += Text->Y;
			defaultsymbol.X2 += Text->X;
			defaultsymbol.Y2 += Text->Y;
			GB_FPrintFilledRectangle(FP,
				defaultsymbol.X1,
				defaultsymbol.Y1,
				defaultsymbol.X2,
				defaultsymbol.Y2,
				unsued);

				/* move on to next cursor position */
			x += size;
		}
		string++;
	}
}

/* ----------------------------------------------------------------------
 * prints text
 */

static void GB_PrintSomeText(TextTypePtr Text, int unsued)
{
	GB_PrintText(GB_Flags.FP, Text, unsued);
}

/* ----------------------------------------------------------------------
 * prints package outline and selected text (Canonical, Instance or Value)
 */
static void GB_PrintElementPackage(ElementTypePtr Element, int unsued)
{
	FILE *FP;
	long arcStartX, arcStartY;
	long arcStopX, arcStopY;
	int useCWArc;

	FP = GB_Flags.FP;

	ELEMENTLINE_LOOP(Element, GB_PrintLine(FP, line, unsued););
	ARC_LOOP(Element, 
		{	arcStartX = arc->X
				+ arc->Width * cos(TO_RADIANS(arc->StartAngle));
			arcStartY = arc->Y
				+ arc->Height * sin(TO_RADIANS(arc->StartAngle));
			arcStopX= arc->X
				+ arc->Width * cos(TO_RADIANS(arc->StartAngle + arc->Delta));
			arcStopY = arc->Y
				+ arc->Height * sin(TO_RADIANS(arc->StartAngle + arc->Delta));

			fprintf(FP,
				"G04 %d %d %d %d %d %d %d Arc *\015\012",
				(int) arc->X,
				(int) arc->Y,
				(int) arc->Width,
				(int) arc->Height,
				(int) arc->Thickness,
				arc->StartAngle,
				arc->Delta);

			fprintf(FP,
				"G54D%d*X%ldY%ldD02*G03I%ldJ%ld" \
				"X%ldY%ldD01*G01*\015\012",
				findApertureCode(&GB_Apertures,
					arc->Thickness, 0, ROUND),
				gerberX(PCB, arcStartX),
				gerberY(PCB, arcStartY),
				gerberXOffset(PCB, arc->X - arcStartX),
				gerberYOffset(PCB, arc->Y - arcStartY),
				gerberX(PCB, arcStopX),
				gerberY(PCB, arcStopY));
		}
	);
	GB_PrintText(FP, &ELEMENT_TEXT(PCB, Element), unsued);
}
/*

                if (arc->StartAngle == 0 || arc->StartAngle == 180)
                        useCWArc = 1;
                else if (arc->StartAngle == 90 || arc->StartAngle == 270)
                        useCWArc = 0;
                else {
                        useCWArc = -1;
                }

                if (useCWArc == -1) {
                        GB_ErrorOccurred = True;
                        logError(GB_Flags.FP,
                                "G04 %d %d %d %d %d %d %d Arc *\015\012",
                                (int) arc->X,
                                (int) arc->Y,
                                (int) arc->Width,
                                (int) arc->Height,
                                (int) arc->Thickness,
                                arc->StartAngle,
                                arc->Delta);
                } else {
 --stuff--
                        fprintf(FP,
                                "G54D%d*",
                                findApertureCode(&GB_Apertures,
                                        arc->Thickness, 0, ROUND));
                        fprintf(FP,
                                "X%ldY%ldD02*G75%sI%ldJ%ldX%ldY%ldD01*G01*\015\0                                gerberX(PCB, arcStartX),
                                gerberY(PCB, arcStartY),
                                (useCWArc ? "G02" : "G03"),
                                gerberXOffset(PCB, arc->X - arcStartX),
                                gerberYOffset(PCB, arc->Y - arcStartY),
                                gerberX(PCB, arcStopX),
                                gerberY(PCB, arcStopY));
                }
*/

/* ----------------------------------------------------------------------
 * prints a pad
 */
static void GB_PrintPad(PadTypePtr Pad, int unsued)
{
	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures,
			Pad->Thickness,
			TEST_FLAG(SQUAREFLAG, Pad) ? Pad->Thickness : 0,
			TEST_FLAG(SQUAREFLAG, Pad) ? SQUARE : ROUND),
		gerberX(PCB, Pad->Point1.X),
		gerberY(PCB, Pad->Point1.Y),
		gerberX(PCB, Pad->Point2.X),
		gerberY(PCB, Pad->Point2.Y));
}

/* ----------------------------------------------------------------------
 * prints a via or pin to a specified file
 */
static void GB_FPrintPinOrVia(FILE *FP, PinTypePtr Ptr, int unsued)
{
	fprintf(FP,
		"G54D%d*X%ldY%ldD03*\015\012",
                findApertureCode(&GB_Apertures,
				Ptr->Thickness,
			TEST_FLAG(SQUAREFLAG, Ptr) ?
					Ptr->Thickness : 0,
			TEST_FLAG(SQUAREFLAG, Ptr) ? SQUARE : ROUND),
		gerberX(PCB, Ptr->X),
		gerberY(PCB, Ptr->Y));
}

/* ----------------------------------------------------------------------
 * prints a via or pin
 */
static void GB_PrintPinOrVia(PinTypePtr Ptr, int unsued)
{
	GB_FPrintPinOrVia(GB_Flags.FP, Ptr, unsued);
}

/* ----------------------------------------------------------------------
 * clears the area around a via or pin on the groundplane
 */
static void GB_PrintClearPinOrViaOnGroundplane(PinTypePtr Pin, int unsued)
{
	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD03*\015\012",
		findApertureCode(&GB_Apertures,
				(int) Pin->Thickness,
			TEST_FLAG(SQUAREFLAG, Pin) ?
					(int) Pin->Thickness : 0,
			TEST_FLAG(SQUAREFLAG, Pin) ? SQUARE : ROUND),
		gerberX(PCB, Pin->X),
		gerberY(PCB, Pin->Y));

/* add thermals; by Harry Eaton
   copied from dev_ps.c
   a "donut" with two X's phase shifted by 45 degress
   real slick for a thermal shape

   ADD ME!!!!!!!!!!!!!!!!

        if (TEST_FLAG(ONGROUNDPLANEFLAG, Ptr))
        {
                fprintf(PS_Flags.FP, "%d %d %d %d %d L\n",
                        (int) (Ptr->X -width -GROUNDPLANEFRAME),
                        (int) Ptr->Y,
                        (int) (Ptr->X +width +GROUNDPLANEFRAME),
                        (int) Ptr->Y,
                        (int) Ptr->DrillingHole);
                fprintf(PS_Flags.FP, "%d %d %d %d %d L\n",
                        (int) Ptr->X,
                        (int) (Ptr->Y -width -GROUNDPLANEFRAME),
                        (int) Ptr->X,
                        (int) (Ptr->Y +width +GROUNDPLANEFRAME),
                        (int) Ptr->DrillingHole);
                fprintf(PS_Flags.FP, "%d %d %d %d %d L\n",
                        (int) (Ptr->X -width -GROUNDPLANEFRAME),
                        (int) (Ptr->Y -width -GROUNDPLANEFRAME),
                        (int) (Ptr->X +width +GROUNDPLANEFRAME),
                        (int) (Ptr->Y +width +GROUNDPLANEFRAME),
                        (int) Ptr->DrillingHole);
                fprintf(PS_Flags.FP, "%d %d %d %d %d L\n",
                        (int) (Ptr->X +width +GROUNDPLANEFRAME),
                        (int) (Ptr->Y -width -GROUNDPLANEFRAME),
                        (int) (Ptr->X -width -GROUNDPLANEFRAME),
                        (int) (Ptr->Y +width +GROUNDPLANEFRAME),
                        (int) Ptr->DrillingHole);
                fprintf(PS_Flags.FP, "%d %d %d CLRPV\n",
                        (int) Ptr->X, (int) Ptr->Y,
                        (int) Ptr->DrillingHole);
        }
*/
}

/* ----------------------------------------------------------------------
 * prints a pad mask
 */
static void GB_PrintPadMask(PadTypePtr Pad, int unsued)
{
		/* Add pad to the solder mask. */
	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures,
				Pad->Thickness,
			TEST_FLAG(SQUAREFLAG, Pad) ?
					Pad->Thickness : 0,
			TEST_FLAG(SQUAREFLAG, Pad) ? SQUARE : ROUND),
		gerberX(PCB, Pad->Point1.X),
		gerberY(PCB, Pad->Point1.Y),
		gerberX(PCB, Pad->Point2.X),
		gerberY(PCB, Pad->Point2.Y));
}

/* ----------------------------------------------------------------------
 * prints a via or pin mask
 */
static void GB_PrintPinOrViaMask(PinTypePtr Pin, int unsued)
{
		/* Add pin/via to the solder mask. */
	GB_FPrintPinOrVia(GB_Flags.FP, Pin, unsued);
}

/* ---------------------------------------------------------------------------
 * draws a filled rectangle to the specified file
 */
static void GB_FPrintFilledRectangle(FILE *FP, Position X1, Position Y1,
	Position X2, Position Y2, int unsued)
{
	int i, j;

	fprintf(FP, "G04 FilledRectangle X%d Y%d X%d Y%d *\015\012",
		(int) X1, (int) Y1, (int) X2, (int) Y2);

	fprintf(FP,
		"G54D%d*",
		findApertureCode(&GB_Apertures, 5, 5, SQUARE));

	for (j = Y1; j < Y2; j += 4) {
 		fprintf(FP,
			"X%ldY%ldD02*X%ldY%ldD01*\015\012",
			gerberX(PCB, X1 + 1),
			gerberY(PCB, j),
			gerberX(PCB, X2 - 1),
			gerberY(PCB, j));
 	}
}

/* ---------------------------------------------------------------------------
 * draws a filled rectangle for the ground plane and/or solder mask.
 */
static void GB_PrintMaskOrGroundplaneRectangle(Position X1, Position Y1,
	Position X2, Position Y2, int unsued)
{
	fprintf(GB_Flags.FP, "G04 Image-Polarity: This is a negative Gerber file *\015\012");
	fprintf(GB_Flags.FP, "G04 Note: All apertures give are undersized; adjust for clearances *\015\012");
}

/* ---------------------------------------------------------------------------
 * draw the outlines of a layout;
 * the upper/left and lower/right corner are passed
 */
static void GB_PrintOutline(Position X1, Position Y1,
	Position X2, Position Y2, int unsued)
{
	fprintf(GB_Flags.FP, "G04 Outline *\015\012");

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		(int) X1, (int) Y1, (int) X2, (int) Y1);

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		(int) X2, (int) Y1, (int) X2, (int) Y2);

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		(int) X2, (int) Y2, (int) X1, (int) Y2);

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		(int) X1, (int) Y2, (int) X1, (int) Y1);
}

/* ---------------------------------------------------------------------------
 * draw the alignment targets;
 * the upper/left and lower/right corner are passed
 */
static void GB_PrintAlignment(Position X1, Position Y1,
	Position X2, Position Y2, int unsued)
{
	int XZ1 = (int) X1 + Settings.AlignmentDistance;
	int XZ2 = (int) X2 - Settings.AlignmentDistance;
	int YZ1 = (int) Y1 + Settings.AlignmentDistance;
	int YZ2 = (int) Y2 - Settings.AlignmentDistance;

	fprintf(GB_Flags.FP, "G04 Alignment Targets *\015\012");

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		(int) X1, (int) Y1, XZ1, (int) Y1);

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		XZ2, (int) Y1, (int) X2, (int) Y1);

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		(int) X2, (int) Y1, (int) X2, YZ1);

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		(int) X2, YZ2, (int) X2, (int) Y2);

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		(int) X2, (int) Y2, XZ2, (int) Y2);

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		XZ1, (int) Y2, (int) X1, (int) Y2);

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		(int) X1, (int) Y2, (int) X1, YZ2);

	fprintf(GB_Flags.FP,
		"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
		findApertureCode(&GB_Apertures, 5, 0, ROUND),
		(int) X1, YZ1, (int) X1, (int) Y1);
}

/* ---------------------------------------------------------------------------
 * prints a via or pin
 */
static void GB_PrintDrillingHelper(PinTypePtr Pin, int unsued)
{
	if (Pin->DrillingHole >= 4*MIN_PINORVIAHOLE)
		fprintf(GB_Flags.FP,
			"G54D%d*X%ldY%ldD03*\015\012",
			findApertureCode(&GB_Apertures,
				Pin->DrillingHole, 0, ROUND),
			(int) Pin->X, (int) Pin->Y);
}

/* ----------------------------------------------------------------------
 * queries color from X11 database and generates Gerber/RS-274D compatible
 * comment.
 */
static void GB_SetColor(XColor RGB, int unsued)
{
	fprintf(GB_Flags.FP,
		"G04 Color: R%ld G%ld B%ld *\015\012",
		(long) ((float) RGB.red / 65535.0 * 1000),
		(long) ((float) RGB.green / 65535.0 * 1000),
		(long) ((float) RGB.blue / 65535.0) * 1000);
}
