/*
 *                            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.4 Gerber RS-274D driver by
 *  Albert John FitzPatrick.  It was modified to produce Gerber/RS-274X
 *  photoplotter command files suitable for use in the fabrication of
 *  printed circuit boards by Andre Mark Hedrick on July 10, 1997.
 *
 *  This driver will generate RS-274X Gerber Extended files that are
 *  readable by OrCAD 7.01.  All information is based on the RS-274X
 *  format/structure standards down loaded from ArtWork Inc.
 *
 *  Contact address for Email:
 *  Andre Mark Hedrick <hedrick@astro.dyer.vanderbilt.edu>
 *
 */

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

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

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

/* 
 * 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: Enhance media selection (allow offset 0, etc.) in printdialog.c.
 * 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_gerberX.h"
#include "error.h"
#include "misc.h"
#include "rotate.h"
#include "search.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 GBX_MAXLINELEN 1024

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

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

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

enum XApertureShape {
	ROUND,			/* Circle */
	DONUT,			/* Donut */
	SQUARE,			/* Square */
	RECTANGLE,		/* Rectangle */
	POLYGON,		/* Polygon */
	THERMAL,		/* Thermal Relief */
	SQUARETHERMAL,		/* Special Square Thermal Cross */
	RECTANGLETHERMAL,	/* Special Square Thermal Cross */
	CLEAR,			/* Special Circle Clearance */
	SQUARECLEAR,		/* Special Square Clearance */
	RECTANGLECLEAR		/* Special Rectangle Clearance */
};

typedef enum XApertureShape XApertureShape;

typedef struct Aperture {
	int dCode;				/* The RS-274X D code */
	int apertureXSize;			/* Size in mils */
	int apertureYSize;			/* Size in mils */
	XApertureShape XapertureShape;		/* ROUND, DONUT, ETC... */
} Aperture; 

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

FILE	*appfile;

/*---------------------------------------------------------------------------*/
/* 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	*GBX_Init(PrintInitTypePtr, char *);
static	void	GBX_Exit(void);
static	void	GBX_SetColor(XColor, int);
static	void	GBX_Drill(PinTypePtr, int);
static	void	GBX_ClearPin(PinTypePtr, Boolean, int);
static	void	GBX_LayerPreamble(FILE *, LayerTypePtr, int);
static	void	GBX_PrintLayer(LayerTypePtr, int, Boolean, int);
static	void	GBX_PrintElementPackage(ElementTypePtr, int);
static	void	GBX_PrintSomeText(TextTypePtr, int);
static	void	GBX_PrintPad(PadTypePtr, int);
static	void	GBX_PrintPinOrVia(PinTypePtr, int);
static	void	GBX_PrintPadMask(PadTypePtr, int);
static	void	GBX_PrintPinOrViaMask(PinTypePtr, int);
static	void	GBX_PrintClearPinOrViaOnGroundplane(PinTypePtr, int);
static	void	GBX_PrintMaskOrGroundplaneRectangle(Position,
			Position, Position, Position, int);
static  void	GBX_PrintOutline(Position, Position,
			Position, Position, int);
static  void	GBX_PrintAlignment(Position, Position,
			Position, Position, int);
static	void	GBX_PrintDrillingHelper(PinTypePtr, int);

static	void	GBX_PrintPolygon(FILE *, PolygonTypePtr, int);
static	void	GBX_FPrintFilledRectangle(FILE *, Position,
			Position, Position, Position, int);
static	void	GBX_FPrintPinOrVia(FILE *, PinTypePtr, int);
static	void	GBX_PrintText(FILE *, TextTypePtr, int);
static	void	GBX_PrintLine(FILE *, LineTypePtr, int);

static	void	initApertures(Apertures *apertures);
static	void	DoApertures(int, int, int, XApertureShape);


/* ----------------------------------------------------------------------
 * some local identifiers
 *
 */
static	PrintDeviceType	GBX_QueryConstants =
{	"Gerber/RS-274X",			/* name of driver */
	"gbx",					/* filename suffix */

	GBX_Init,				/* initializes driver */
	GBX_Exit,				/* exit code */

	GBX_SetColor,				/* set color */
	GBX_Drill,				/* drilling information */

	GBX_PrintLayer,				/* print layer */
	GBX_PrintElementPackage,		/* print element package */
	GBX_PrintSomeText,			/* print some
						   (silkscreen, etc.) text */
	GBX_PrintPad,				/* print pad */
	GBX_PrintPinOrVia,			/* print pin or via */
	GBX_PrintPadMask,			/* print pad mask */
	GBX_PrintPinOrViaMask,			/* print pin or via mask */
	GBX_PrintClearPinOrViaOnGroundplane,	/* print clear pin or via
						   on groundplane */
	GBX_PrintMaskOrGroundplaneRectangle,	/* print filled rectangle
						   for ground planes and/or
						   for solder masks */
	GBX_PrintOutline,			/* print board outline */
	GBX_PrintAlignment,			/* print alignment marks */
	GBX_PrintDrillingHelper,		/* print drill helper marks */

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

static	PrintInitType	GBX_Flags;

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

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

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

static Boolean GBX_ErrorOccurred;

static int lastTherm = 1;
static int ClearLayerStarted = 0;

/*---------------------------------------------------------------------------*/
/* 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 * XapertureShape(XApertureShape shape)
{
	switch(shape) {
		case ROUND:		return "round";
		case DONUT:		return "donut";
		case SQUARE:		return "square";
		case RECTANGLE:		return "rectangle";
		case POLYGON:		return "polygon";
		case THERMAL:		return "thermal";
		case SQUARETHERMAL:	return "squarethermal";
		case RECTANGLETHERMAL:	return "rectanglethermal";
		case CLEAR:		return "clearround";
		case SQUARECLEAR:	return "clearsquare";
		case RECTANGLECLEAR:	return "clearrectangle";
		default:		return "*invalid-shape*";
	}
}

static void DoApertures( 
	int dCode,
	int width,
	int height,
	XApertureShape shape)
{
        float Xclearance;
        float Yclearance;

	Xclearance = (width + 2 * GROUNDPLANEFRAME)/1000.0;
	Yclearance = (height + 2 * GROUNDPLANEFRAME)/1000.0;

	switch(shape) {
		case ROUND:
			fprintf(GBX_Flags.FP,
				"%%ADD%dC,%5.3f*%%\015\012",
				dCode,
				width/1000.0);
			break;
		case DONUT:
			fprintf(GBX_Flags.FP,
				"%%ADD%dC,%5.3fX%5.3f*%%\015\012",
				dCode,
				width/1000.0,
				height/1000.0);
			break;
		case SQUARE:
		case RECTANGLE:
			fprintf(GBX_Flags.FP,
				"%%ADD%dR,%5.3fX%5.3f*%%\015\012",
				dCode,
				width/1000.0,
				height/1000.0);
			break;
		case POLYGON:
                        fprintf(GBX_Flags.FP,
                                "%%ADD%d%P,%5.3fX%5.3f*%%\015\012",
                                dCode,
				width/1000.0,
				height/1000.0);
			break;
		case THERMAL:
			fprintf(GBX_Flags.FP,
				"%%AMTHERM%d*7,0,0,%5.3f,%5.3f,%5.3f,45*%%\015\012",
				lastTherm,
				Xclearance,
				width/1000.0,
				height/2000.0);
			fprintf(GBX_Flags.FP,
				"%%ADD%dTHERM%d*%%\015\012",
				dCode,
				lastTherm++);
			break;
                case SQUARETHERMAL:
		case RECTANGLETHERMAL:
			fprintf(GBX_Flags.FP,
				"%%AMTHERM%d*7,0,0,%5.3f,%5.3f,%5.3f,45*%%\015\012",
				lastTherm,
				Xclearance,
				width/1000.0,
				height/2000.0);
			fprintf(GBX_Flags.FP,
				"%%ADD%dTHERM%d*%%\015\012",
				dCode,
				lastTherm++);
			break;
		case CLEAR:
			fprintf(GBX_Flags.FP,
				"%%ADD%dC,%5.3fX%5.3f*%%\015\012",
				dCode,
				Xclearance,
				width/1000.0);
			break;
		case SQUARECLEAR:
		case RECTANGLECLEAR:
			fprintf(GBX_Flags.FP,
				"%%ADD%dR,%5.3fX%5.3fX%5.3fX%5.3f*%%\015\012",
				dCode,
				Xclearance,
				Yclearance,
				width/1000.0,
				height/1000.0);
			break;
	}
}

static int findApertureCode(
	Apertures *apertures,
	int width,
	int height,
	XApertureShape shape)
{
	Aperture *ap;
	int i;
	int dNum = 0;

	/* 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->XapertureShape == shape) {
			return (ap->dCode);
		}
	}

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

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

	appfile = fopen("gerberX.app", "a+" );

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

	fclose(appfile);

	DoApertures(ap->dCode, ap->apertureXSize,
		ap->apertureYSize, ap->XapertureShape);

	return (ap->dCode);
}

static void initApertures(Apertures *apertures)
{
	int		i;
	Aperture	*ap;
	char		line[GBX_MAXLINELEN];

	apertures->nextAvailable = 0;

	if ((appfile = fopen("gerberX.app", "r" )) == NULL) {
		i = apertures->nextAvailable;
		ap = &apertures->aperture[i];
		ap->dCode = 0;
		ap->apertureXSize = 0;
		ap->apertureYSize = 0;
		ap->XapertureShape = 0;

		return;
	} else {
		while (fgets(line, sizeof line, appfile) != (char *) NULL &&
			apertures->nextAvailable < GBX_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[GBX_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->XapertureShape = ROUND;
					} else if (strcmp(shape, "donut") == 0) {
						ap->XapertureShape = DONUT;
					} else if (strcmp(shape, "square") == 0) {
						ap->XapertureShape = SQUARE;
					} else if (strcmp(shape, "rectangle") == 0) {
						ap->XapertureShape = RECTANGLE;
					} else if (strcmp(shape, "polygon") == 0) {
						ap->XapertureShape = POLYGON;
					} else if (strcmp(shape, "thermal") == 0) {
						ap->XapertureShape = THERMAL;
					} else if (strcmp(shape, "squarethermal") == 0) {
						ap->XapertureShape = SQUARETHERMAL;
					} else if (strcmp(shape, "rectanglethermal") == 0) {
						ap->XapertureShape = RECTANGLETHERMAL;
					} else if (strcmp(shape, "clearround") == 0) {
						ap->XapertureShape = CLEAR;
					} else if (strcmp(shape, "clearsquare") == 0) {
						ap->XapertureShape = SQUARECLEAR;
					} else if (strcmp(shape, "clearrectangle") == 0) {
						ap->XapertureShape = RECTANGLECLEAR;
					}

					DoApertures(ap->dCode,
						ap->apertureXSize,
						ap->apertureYSize,
						ap->XapertureShape);
				}
			}
		}
	}
	fclose(appfile);
}

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

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

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

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

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

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

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

	fprintf(GBX_Flags.FP,
			"*G04 PCB-Coordinate-Origin:" \
			" lower left ***\015\012");

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

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

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

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

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

	fprintf(GBX_Flags.FP, "%%ICAS*%%\015\012");
	fprintf(GBX_Flags.FP, "%%MOIN*%%\015\012");

	if(GBX_Flags.MirrorFlag)
			fprintf(GBX_Flags.FP, "%%IPNEG*%%\015\012");
		else
			fprintf(GBX_Flags.FP, "%%IPPOS*%%\015\012");

/*
	if(GBX_Flags.InvertFlag)
			fprintf(GBX_Flags.FP,"%%LPD*%%\015\012");
		else
			fprintf(GBX_Flags.FP,"%%LPC*%%\015\012");
*/

	fprintf(GBX_Flags.FP,
		"%%ASAXBY*%%\015\012");
	fprintf(GBX_Flags.FP,
		"G74*%%FSAN2X23Y23*%%\015\012");
	fprintf(GBX_Flags.FP,
		"*G04 Aperture Definitions ***\015\012");
	initApertures(&GBX_Apertures);

	return(NULL);
}

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

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

/* ----------------------------------------------------------------------
 * handles drilling information
 */
static void GBX_Drill(PinTypePtr PinOrVia, int GXAFlag)
{
	if (GXAFlag) {
		fprintf(GBX_Flags.FP,
			"G54D%d*X%ldY%ldD03*\015\012",
			findApertureCode(&GBX_Apertures,
				PinOrVia->DrillingHole,
				TEST_FLAG(SQUAREFLAG, PinOrVia) ? PinOrVia->DrillingHole : 0,
				TEST_FLAG(SQUAREFLAG, PinOrVia) ? SQUARE : ROUND),
			gerberX(PCB, PinOrVia->X),
			gerberY(PCB, PinOrVia->Y));
	} else {
		findApertureCode(&GBX_Apertures,
			PinOrVia->DrillingHole,
			TEST_FLAG(SQUAREFLAG, PinOrVia) ? PinOrVia->DrillingHole : 0,
			TEST_FLAG(SQUAREFLAG, PinOrVia) ? SQUARE : ROUND);
	}
}

/* ----------------------------------------------------------------------
 * Clears the pads and vias away from polygon regions 
 */
static void GBX_ClearPin(PinTypePtr Pin, Boolean thermal, int GXAFlag)
{
	FILE *FP;
	FP = GBX_Flags.FP;

	if (GXAFlag) {
		if (thermal)
			fprintf(FP, "G54D%d*X%ldY%ldD03*\015\012",
				findApertureCode(&GBX_Apertures,
					Pin->Thickness,
					TEST_FLAG(SQUAREFLAG, Pin) ? Pin->Thickness : 0,
					TEST_FLAG(SQUAREFLAG, Pin) ? SQUARETHERMAL : THERMAL),
				gerberX(PCB, Pin->X),
				gerberY(PCB, Pin->Y));
		else
			fprintf(FP, "G54D%d*X%ldY%ldD03*\015\012",
				findApertureCode(&GBX_Apertures,
					Pin->Thickness,
					TEST_FLAG(SQUAREFLAG, Pin) ? Pin->Thickness : 0,
					TEST_FLAG(SQUAREFLAG, Pin) ? SQUARECLEAR : CLEAR),
				gerberX(PCB, Pin->X),
				gerberY(PCB, Pin->Y));
	} else {
		if (thermal)
			findApertureCode(&GBX_Apertures,
				Pin->Thickness,
				TEST_FLAG(SQUAREFLAG, Pin) ? Pin->Thickness : 0,
				TEST_FLAG(SQUAREFLAG, Pin) ? SQUARETHERMAL : THERMAL);
		else
			findApertureCode(&GBX_Apertures,
				Pin->Thickness,
				TEST_FLAG(SQUAREFLAG, Pin) ? Pin->Thickness : 0,
				TEST_FLAG(SQUAREFLAG, Pin) ? SQUARECLEAR : CLEAR);
	}
}

/* ----------------------------------------------------------------------
 * prints preamble to layer's data
 */
static void GBX_LayerPreamble(FILE *FP, LayerTypePtr Layer, int GroupNumber)
{
	fprintf(FP,
		"%%LN%s*%%\015\012%%LPD*%%\015\012",
		UNKNOWN(Layer->Name));
	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);
}

/* ----------------------------------------------------------------------
 * prints layer data
 */
static void GBX_PrintLayer(LayerTypePtr Layer, int GroupNumber,
	Boolean SilkscreenTextFlag, int GXAFlag)
{
	FILE *FP;
	FP = GBX_Flags.FP;

	if (GXAFlag)
		GBX_LayerPreamble(FP, Layer, GroupNumber);

	LINE_LOOP(Layer, GBX_PrintLine(FP, line, GXAFlag));
	if (! SilkscreenTextFlag)
		TEXT_LOOP(Layer, GBX_PrintText(FP, text, GXAFlag));
	POLYGON_LOOP(Layer, GBX_PrintPolygon(FP, polygon, GXAFlag));
}

/* ----------------------------------------------------------------------
 * prints a line
 */
static void GBX_PrintLine(FILE *FP, LineTypePtr Line, int GXAFlag)
{
	if (GXAFlag) {
		fprintf(FP,
			"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
			findApertureCode(&GBX_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));
        } else {
		findApertureCode(&GBX_Apertures,
			Line->Thickness, 0, ROUND);
	}
}

/* ----------------------------------------------------------------------
 * 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 GBX_PrintPolygon(FILE *FP, PolygonTypePtr Ptr, int GXAFlag)
{
	static int isRectangle(PolygonTypePtr Ptr);
	int firstTime = 1;
	Position startX, startY;

	if (isRectangle(Ptr)) {
		GBX_FPrintFilledRectangle(
			FP,
			Ptr->BoundingBox.X1,
			Ptr->BoundingBox.Y1,
			Ptr->BoundingBox.X2,
			Ptr->BoundingBox.Y2,
			GXAFlag);
	} else if (GXAFlag) {
		fprintf(FP,"G36*\015\012");
		fprintf(FP,
			"G54D%d*",
			findApertureCode(&GBX_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));
		fprintf(FP,"G37*\015\012");
	} else {
		findApertureCode(&GBX_Apertures, 5, 0, ROUND);
	}
}

/* ----------------------------------------------------------------------
 * 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 GBX_PrintText(FILE *FP, TextTypePtr Text, int GXAFlag)
{
	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) && GXAFlag)
		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;
				GBX_PrintLine(FP, &newline, GXAFlag);
			}

				/* 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.X2 = SWAP_SIGN_X(defaultsymbol.X2);
				defaultsymbol.Y1 = SWAP_SIGN_Y(defaultsymbol.Y1);
				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;
			GBX_FPrintFilledRectangle(FP,
				defaultsymbol.X1,
				defaultsymbol.Y1,
				defaultsymbol.X2,
				defaultsymbol.Y2,
				GXAFlag);

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

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

static void GBX_PrintSomeText(TextTypePtr Text, int GXAFlag)
{
	GBX_PrintText(GBX_Flags.FP, Text, GXAFlag);
}

/* ----------------------------------------------------------------------
 * prints an arc
 */
static void GBX_PrintArcLowLevel(FILE *FP, ArcTypePtr Arc, int GXAFlag)
{
	long arcStartX, arcStartY;
	long arcStopX, arcStopY;

	if (GXAFlag) {

		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(&GBX_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));

	} else {
		findApertureCode(&GBX_Apertures,
			Arc->Thickness, 0, ROUND);
	}
}

/* ----------------------------------------------------------------------
 * prints package outline and selected text (Canonical, Instance or Value)
 */
static void GBX_PrintElementPackage(ElementTypePtr Element, int GXAFlag)
{
	FILE *FP;
	FP = GBX_Flags.FP;

	ELEMENTLINE_LOOP(Element, GBX_PrintLine(FP, line, GXAFlag););
	ARC_LOOP(Element, GBX_PrintArcLowLevel(FP, arc, GXAFlag););
	GBX_PrintText(FP, &ELEMENT_TEXT(PCB, Element), GXAFlag);
}

/* ----------------------------------------------------------------------
 * prints a pad
 */
static void GBX_PrintPad(PadTypePtr Pad, int GXAFlag)
{
	if (GXAFlag) {
		fprintf(GBX_Flags.FP,
			"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
			findApertureCode(&GBX_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));
	} else {
		findApertureCode(&GBX_Apertures,
			Pad->Thickness,
			TEST_FLAG(SQUAREFLAG, Pad) ? Pad->Thickness : 0,
			TEST_FLAG(SQUAREFLAG, Pad) ? SQUARE : ROUND);
	}
}

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

/* ----------------------------------------------------------------------
 * prints a via or pin
 */
static void GBX_PrintPinOrVia(PinTypePtr Ptr, int GXAFlag)
{
	GBX_FPrintPinOrVia(GBX_Flags.FP, Ptr, GXAFlag);
}

/* ----------------------------------------------------------------------
 * clears the area around a via or pin on the groundplane
 */
#ifndef NEW_THERNALS
static void GBX_PrintClearPinOrViaOnGroundplane(PinTypePtr Pin, int GXAFlag)
{
	if (GXAFlag) {
		fprintf(GBX_Flags.FP,
			"G54D%d*X%ldY%ldD03*\015\012",
			findApertureCode(&GBX_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));
	} else {
		findApertureCode(&GBX_Apertures,
				(int) Pin->Thickness,
			TEST_FLAG(SQUAREFLAG, Pin) ?
					(int) Pin->Thickness : 0,
			TEST_FLAG(SQUAREFLAG, Pin) ? SQUARE : ROUND);
	}
}
#else /* NEW_THERNALS */
static void GBX_PrintClearPinOrViaOnGroundplane(PinTypePtr Pin, int GXAFlag)
{
	if (!ClearLayerStarted)
	{
		fprintf(GBX_Flags.FP, "%%LNCUTS*%%\015\012");
		fprintf(GBX_Flags.FP, "%%LPC*%%\015\012");
		ClearLayerStarted = True;
	}
	GBX_ClearPin(Pin, TEST_FLAG(USETHERMALFLAG, Pin) != 0);
	CLEAR_FLAG(USETHERMALFLAG, Pin);
	CLEAR_FLAG(PININPOLYFLAG, Pin);
}
#endif /* NEW_THERNALS */

/* ----------------------------------------------------------------------
 * prints a pad mask, and square TOO!!!
 */
static void GBX_PrintPadMask(PadTypePtr Pad, int GXAFlag)
{
	if (GXAFlag) {
		fprintf(GBX_Flags.FP,
			"G54D%d*X%ldY%ldD02*X%ldY%ldD01*\015\012",
			findApertureCode(&GBX_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));
	} else {
		findApertureCode(&GBX_Apertures,
				Pad->Thickness,
			TEST_FLAG(SQUAREFLAG, Pad) ?
					Pad->Thickness : 0,
			TEST_FLAG(SQUAREFLAG, Pad) ? SQUARE : ROUND);
	}
}

/* ----------------------------------------------------------------------
 * prints a via or pin mask
 */
static void GBX_PrintPinOrViaMask(PinTypePtr Pin, int GXAFlag)
{
	GBX_FPrintPinOrVia(GBX_Flags.FP, Pin, GXAFlag);
}

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

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

		fprintf(FP,
			"G54D%d*",
			findApertureCode(&GBX_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));
	 	}
	} else {
		findApertureCode(&GBX_Apertures, 5, 5, SQUARE);
	}
}

/* ---------------------------------------------------------------------------
 * draws a filled rectangle for the ground plane and/or solder mask.
 */
static void GBX_PrintMaskOrGroundplaneRectangle(Position X1, Position Y1,
	Position X2, Position Y2, int GXAFlag)
{
	if (GXAFlag) {
		fprintf(GBX_Flags.FP,"%%LNPLANE*%%\015\012");
		fprintf(GBX_Flags.FP,"%%LPD*%%\015\012");
		fprintf(GBX_Flags.FP,"G36*\015\012");
		fprintf(GBX_Flags.FP,"X%ldY%ldD02*\015\012",
			gerberX(PCB, X1), gerberY(PCB, Y1));
		fprintf(GBX_Flags.FP,"X%ldY%ldD01*\015\012",
			gerberX(PCB, X2), gerberY(PCB, Y1));
		fprintf(GBX_Flags.FP,"X%ldY%ldD01*\015\012",
			gerberX(PCB, X2), gerberY(PCB, Y2));
		fprintf(GBX_Flags.FP,"X%ldY%ldD01*\015\012",
			gerberX(PCB, X1), gerberY(PCB, Y2));
		fprintf(GBX_Flags.FP,"X%ldY%ldD01*\015\012",
			gerberX(PCB, X1), gerberY(PCB, Y1));
		fprintf(GBX_Flags.FP,"G37*\015\012");
		fprintf(GBX_Flags.FP,"%%LNCUTS*%%\015\012");
		fprintf(GBX_Flags.FP,"%%LPC*%%\015\012");
	}
}

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

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

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

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

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

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

	if (GXAFlag) {
		fprintf(GBX_Flags.FP, "*G04 Alignment Targets ***\015\012");

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

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

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

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

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

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

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

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

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

/* ----------------------------------------------------------------------
 * queries color from X11 database and generates Gerber/RS-274X compatible
 * comment.
 */
static void GBX_SetColor(XColor RGB, int GXAFlag)
{
	int i;

	if (GXAFlag) {
		fprintf(GBX_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);
	}
}
