/*
 *                            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 RS-274D driver by Albert John
 *  FitzPatrick. It was written by harry eaton
 *
 *  Contact address for Email:
 *  harry eaton <haceaton@aplcomm.jhuapl.edu>
 *
 */

static	char	*rcsid = "$Id: dev_rs274x.c,v 144.1 1997/7/16 07:07:52 nau Exp $";

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

/* 
 * 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_rs274x.h"
#include "drill.h"
#include "error.h"
#include "misc.h"
#include "mymem.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

#define GBX_MAXAPERTURECOUNT 256
/*----------------------------------------------------------------------------*/
/* Private data structures                                                    */
/*----------------------------------------------------------------------------*/

enum ApertureShape {
	ROUND,					/* Shaped like a circle */
	SQUARE,					/* Shaped like a square */
	THERMAL,				/* Special Thermal Cross */
	SQUARETHERMAL,				/* Special Square Thermal Cross */
	SQUARECLEAR,				/* A thin square for pad Clearance */
	CLEAR					/* A thin round-ring for pad Clearance */
};
typedef enum ApertureShape ApertureShape;

enum ApertureFunction {
	FLASH,					/* Suitable for flashing */
	DRAW,					/* Suitable for drawing */
	DRILL					/* Suitable for drilling */
};
typedef enum ApertureFunction ApertureFunction;

typedef struct Aperture {
	int dCode;				/* The RS-274D D code */
	int apertureSize;			/* Size in mils */
	ApertureShape apertureShape;		/* ROUND/SQUARE */
	ApertureFunction apertureFunction;	/* FLASH/DRAW/DRILL */
} Aperture; 

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


/*----------------------------------------------------------------------------*/
/* 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	void	GBX_Init(PrintInitTypePtr);
static	void	GBX_Postamble(void);
static	char	*GBX_Preamble(PrintInitTypePtr, char *);
static  void    GBX_Exit(void);
static  void    GBX_SetColor(XColor, int);
static  void    GBX_Drill(PinTypePtr, int);
static  void    GBX_PrintLayer(LayerTypePtr, int, Boolean, int);
static  void    GBX_LayerPreamble(LayerTypePtr, int, 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);
static  void    GBX_FPrintPinOrVia(FILE *, PinTypePtr);
static  void    GBX_PrintText(FILE *, TextTypePtr);
static  void    GBX_PrintLine(FILE *, LineTypePtr);
static  void    GBX_ClearPin(PinTypePtr, Boolean);
static	void	findTextApertures(TextTypePtr);

/* ----------------------------------------------------------------------
 * 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_Preamble,				/* creates file header */
			GBX_Postamble,				/* cleans up per file */
			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 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,		/* allows mirroring */
			False,		/* no rotate */
			False,					/* needs preprint calls */
			False
			};

static	PrintInitType	GBX_Flags;

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

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

static Boolean GBX_ErrorOccurred;

static DynamicStringType	appList;
static int lastX, lastY;			/* the last X and Y coordinate */
static int lastdCode = 11;			/* dCodes start at 11 */
static int lastTherm = 1;
static int lastAperture = 0;
static int ClearLayerStarted = 0;
static int drillIndex = 1;
static int lastDrill = 0;
static Boolean AmDrilling = False;

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

		/* FIXME: Is it legitimate to use Message() from within a
			driver? */

	va_start(args);
	vsprintf(s, format, args);
	fputs(s, fp);
/*
	FIXME: to much error output...
	Message("%s", s);
*/
	va_end(args);
}


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

static int findApertureCode(
	Apertures *apertures,
	int width,
	ApertureShape shape,
	ApertureFunction function)
{
	int i;
	Aperture *ap;
	char	appMacro[256];

	/* Search for an appropriate aperture. */

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

		if (ap->apertureSize == width &&
		    ap->apertureShape == shape)
/* function doesn't really matter since macro only defines geometry anyway
			&& ap->apertureFunction == function)
 */
			return (ap->dCode);
	}
	appMacro[0] = '\0';

	/* Not found, create a new aperture and add it to the list */
	if (apertures->nextAvailable < GBX_MAXAPERTURECOUNT)
	{
	    float clearance;

	    i = apertures->nextAvailable++;
	    ap = &apertures->aperture[i];
	    ap->dCode = lastdCode++;
	    ap->apertureSize = width;
	    ap->apertureShape = shape;
	    ap->apertureFunction = function;
	    clearance = (width + 2 * GROUNDPLANEFRAME)/1000.0;
	    switch (shape)
	    {
	      case ROUND:
			sprintf(appMacro, "%%ADD%dC,%.3f*%%\015\012",ap->dCode,width/1000.0);
			break;
	      case SQUARE:
			sprintf(appMacro, "%%ADD%dR,%.3fX%.3f*%%\015\012",ap->dCode,width/1000.0,width/1000.0);
			break;
	      case THERMAL:
			sprintf(appMacro, "%%AMTHERM%d*7,0,0,%.3f,%.3f,%.3f,45*%%\015\012"
			                  "%%ADD%dTHERM%d*%%\015\012", lastTherm,
					  clearance,width/1000.0,GROUNDPLANEFRAME/1000.0,
			    		  ap->dCode, lastTherm);
			lastTherm++;
		        break;
	      case CLEAR:
			sprintf(appMacro, "%%ADD%dC,%.3fX%.3f*%%\015\012",ap->dCode,
				clearance, width/1000.0);
			break;
	      case SQUARECLEAR:
			sprintf(appMacro, "%%ADD%dR,%.3fX%.3fX%.3fX%.3f*%%\015\012",ap->dCode,
				clearance, clearance, width/1000.0, width/1000.0);
			break;
	      case SQUARETHERMAL:
			sprintf(appMacro, "%%AMTHERM%d*7,0,0,%.3f,%.3f,%.3f,45*%%\015\012"
			                  "%%ADD%dTHERM%d*%%\015\012", lastTherm,
					  clearance,width/1000.0,GROUNDPLANEFRAME/1000.0,
			    		  ap->dCode, lastTherm);
			lastTherm++;
		        break;
	     }
	     DSAddString(&appList, appMacro);
	     return (ap->dCode);
	}
	else
	{
	     GBX_ErrorOccurred = True;
	     logError(GBX_Flags.FP,"G04 Too many apertures needed *\015\012");
	     return (10);
	}
}


static void initApertures(Apertures *apertures)
{
	apertures->nextAvailable = 0;
	lastdCode = 11;
	lastTherm = 1;
	lastAperture = 0;
	appList.Data =NULL;
	appList.MaxLength = 0;
}

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

/* ---------------------------------------------------------------------------
 * global initialization
 */
static void GBX_Init(PrintInitTypePtr Flags)
{
	int	flags;

	initApertures(&GBX_Apertures);
		/* gather all aperture macros */
	ALLLINE_LOOP(PCB->Data,
		findApertureCode(&GBX_Apertures, line->Thickness, ROUND, DRAW);
	);
	ELEMENT_LOOP(PCB->Data,
		PIN_LOOP(element,
			findApertureCode(&GBX_Apertures, pin->Thickness,
			 	(TEST_FLAG(SQUAREFLAG, pin) ? SQUARE : ROUND), FLASH);
				/* mask layers always need clearance */
			findApertureCode(&GBX_Apertures, pin->Thickness + 2*GROUNDPLANEFRAME,
					(TEST_FLAG(SQUAREFLAG, pin) ?
						SQUARE : ROUND), FLASH);
				/* check if a thermal is being used */
			if (TEST_FLAG(ALLTHERMFLAGS, pin))
				findApertureCode(&GBX_Apertures, pin->Thickness,
					TEST_FLAG(SQUAREFLAG, pin) ?
						SQUARETHERMAL : THERMAL, FLASH);
				/* check if there is a non-thermal clearance */
			flags = (pin->Flags << 8) ^ pin->Flags; 
			if (flags & ALLPIPFLAGS)
				findApertureCode(&GBX_Apertures, pin->Thickness,
					TEST_FLAG(SQUAREFLAG, pin) ?
						SQUARECLEAR : CLEAR, FLASH);

		);
		PAD_LOOP(element,
			findApertureCode(&GBX_Apertures, pad->Thickness,
				TEST_FLAG(SQUAREFLAG, pad) ? SQUARE : ROUND, DRAW);
				/* pads always need clear masks */
			findApertureCode(&GBX_Apertures, pad->Thickness + 2*GROUNDPLANEFRAME,
				TEST_FLAG(SQUAREFLAG, pad) ? SQUARE : ROUND, DRAW);
		);
		ELEMENTLINE_LOOP(element,
			findApertureCode(&GBX_Apertures, line->Thickness, ROUND, DRAW));
		ARC_LOOP(element,
			findApertureCode(&GBX_Apertures, arc->Thickness, ROUND, DRAW));
		findTextApertures(&ELEMENT_TEXT(PCB, element));
	);
	ALLTEXT_LOOP(PCB->Data,
		findTextApertures(text);
	);
	VIA_LOOP(PCB->Data,
		findApertureCode(&GBX_Apertures, via->Thickness,
		 	TEST_FLAG(SQUAREFLAG, via) ? SQUARE : ROUND, FLASH);
			/* mask layers always need clearance */
		findApertureCode(&GBX_Apertures, via->Thickness + 2*GROUNDPLANEFRAME,
				TEST_FLAG(SQUAREFLAG, via) ?
					SQUARE : ROUND, FLASH);
			/* check if a thermal is being used */
		if (TEST_FLAG(ALLTHERMFLAGS, via))
			findApertureCode(&GBX_Apertures, via->Thickness,
				TEST_FLAG(SQUAREFLAG, via) ?
					SQUARETHERMAL : THERMAL, FLASH);
			/* check if there is a non-thermal clearance */
		flags = (via->Flags << 8) ^ via->Flags; 
		if (flags & ALLPIPFLAGS)
			findApertureCode(&GBX_Apertures, via->Thickness,
				TEST_FLAG(SQUAREFLAG, via) ?
					SQUARECLEAR : CLEAR, FLASH);
	);
}

static void GBX_Exit()
{
	/* Why can't I free the appList.Data????
		Sure, none of the other Dynamic strings ever do,
		even so .....
	free(appList.Data);
	appList.Data = NULL;
	appList.MaxLength = 0;
	*/
	DSClearString(&appList);
}

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

		/* save passed-in data */
	GBX_Flags = *Flags;

	if (strcmp(Description, "drill information") == 0)
	{
		DrillInfoTypePtr	usedDrills;
		int	index = 1;

		fprintf(GBX_Flags.FP, "M48\015\012"
			"INCH,TZ\015\012");
		usedDrills = GetDrillInfo(PCB->Data);
		DRILL_LOOP(usedDrills,
			fprintf(GBX_Flags.FP,"T%02dF00S00C%.3f\015\012",
				index++, drill->DrillSize/1000.0);
		);
		FreeDrillInfo(usedDrills);
		fprintf(GBX_Flags.FP, "%%\015\012");
		drillIndex = 1;
		lastDrill = 0;
		AmDrilling = True;
		return(NULL);
	}
			
	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 *\015\012");
	fprintf(GBX_Flags.FP, "G04 PCB-Dimensions: %ld %ld *\015\012",
		(long) PCB->MaxWidth,
		(long) PCB->MaxHeight
		);
	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 Leading zero suppression, Absolute Data, 2.3 format */
	fprintf(GBX_Flags.FP,
        	"%%FSLAX23Y23*%%\015\012");

       		/* Signal data in inches. */
	fprintf(GBX_Flags.FP,
       		"%%MOIN*%%\015\012");
		/* print the aperture list at the top */
	fprintf(GBX_Flags.FP,"%s",appList.Data);
		/* indicate a positive image */
	if (strncmp(Description, "solder mask", 11) != 0)
		fprintf(GBX_Flags.FP,"%%IPPOS*%%\015\012");
	else
		fprintf(GBX_Flags.FP,"%%IPNEG*%%\015\012");
       		/* Signal Straight Line Data. */
	fprintf(GBX_Flags.FP,
       		"G01*\015\012");

	ClearLayerStarted = 0;

	return(NULL);
}

/* ---------------------------------------------------------------------------
 * exit code for this driver is empty
 */
static void GBX_Postamble(void)
{
		/* print trailing commands */
	if (AmDrilling)
	{
		AmDrilling = False;
		fprintf(GBX_Flags.FP,
			"M30\015\012");
	}
	else
	{
        		/* Turn off the light */
		fprintf(GBX_Flags.FP,
       			"D02*\015\012");

			/* Signal End-of-Plot. */
		fprintf(GBX_Flags.FP,
			"M02*\015\012");
	}
	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 unused)
{
	if (lastDrill != PinOrVia->DrillingHole)
	{
		lastDrill = PinOrVia->DrillingHole;
		fprintf(GBX_Flags.FP, "T%02d\015\012", drillIndex++);
	}
	fprintf(GBX_Flags.FP,
		"X%ldY%ld\015\012",
		gerberX(PCB, PinOrVia->X),
		gerberY(PCB, PinOrVia->Y)
		);
}

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

	FP = GBX_Flags.FP;
	if (TEST_FLAG(SQUAREFLAG, Pin))
	{
		if (thermal)
			Dcode = findApertureCode(&GBX_Apertures, Pin->Thickness,
				SQUARETHERMAL, FLASH);
		else
			Dcode = findApertureCode(&GBX_Apertures, Pin->Thickness, 
				SQUARECLEAR, FLASH);
	}
	else
	{
		if (thermal)
			Dcode = findApertureCode(&GBX_Apertures, Pin->Thickness, 
				THERMAL, FLASH);
		else
			Dcode = findApertureCode(&GBX_Apertures, Pin->Thickness, 
				CLEAR, FLASH);
	}
	if (lastAperture != Dcode)
	{
		lastAperture = Dcode;
		fprintf(FP, "G54D%d*", lastAperture);
	}
	if (Pin->X != lastX)
	{
		lastX = Pin->X;
		fprintf(FP,"X%ld",gerberX(PCB, lastX));
	}
	if (Pin->Y != lastY)
	{
		lastY = Pin->Y;
		fprintf(FP,"Y%ld",gerberY(PCB, lastY));
	}
	fprintf(FP,"D03*\015\012");
}

/* ----------------------------------------------------------------------
 * prints preamble to layer's data
 */
static void GBX_LayerPreamble(LayerTypePtr Layer, int GroupNumber, int unused)
{
	fprintf(GBX_Flags.FP,
		"%%LN%s*%%\015\012%%LPD*%%\015\012", UNKNOWN(Layer->Name));
	fprintf(GBX_Flags.FP,
		"G04 LayerGroup: %i *\015\012",
		GroupNumber + 1);
	fprintf(GBX_Flags.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 unused)
{
	FILE *FP;
	int ThermLayerFlag, LayerPIPFlag;
	Boolean something = False;

	/* FIXME: Check layer bounds. */
	FP = GBX_Flags.FP;
	LINE_LOOP(Layer, {
		if (!something)
		{
			something = True;
			GBX_LayerPreamble(Layer, GroupNumber, 0);
		}
		GBX_PrintLine(FP, line);
	});
	TEXT_LOOP(Layer, {
		if (!TEST_FLAG(ONSILKFLAG, text))
		{
			if (!something)
			{
				something = True;
				GBX_LayerPreamble(Layer, GroupNumber, 0);
			}
			GBX_PrintText(FP, text);
		}
	});
	ThermLayerFlag = L0THERMFLAG << GetLayerNumber(PCB->Data, Layer);
	LayerPIPFlag = L0PIPFLAG << GetLayerNumber(PCB->Data, Layer);
	POLYGON_LOOP(Layer, {
		if (!something)
		{
			something = True;
			GBX_LayerPreamble(Layer, GroupNumber, 0);
		}
		GBX_PrintPolygon(FP, polygon, 0);
	});
	ALLPIN_LOOP(PCB->Data,
		if (TEST_FLAG(LayerPIPFlag, pin))
		{
			SET_FLAG(PININPOLYFLAG, pin);
			if (TEST_FLAG(ThermLayerFlag, pin))
				SET_FLAG(USETHERMALFLAG, pin);
		}
	);
	VIA_LOOP(PCB->Data,
		if (TEST_FLAG(LayerPIPFlag, via))
		{
			SET_FLAG(PININPOLYFLAG, via);
			if (TEST_FLAG(ThermLayerFlag, via))
				SET_FLAG(USETHERMALFLAG, via);
		}
	);
}

/* ----------------------------------------------------------------------
 * prints a line
 */
static void GBX_PrintLine(FILE *FP, LineTypePtr Line)
{
	int apCode;
	
	apCode = findApertureCode(&GBX_Apertures, Line->Thickness, ROUND, DRAW);
	if (lastAperture != apCode)
	{
		lastAperture = apCode;
		fprintf(FP, "G54D%d*",lastAperture);
	}
	if (Line->Point1.X != lastX)
	{
		lastX = Line->Point1.X;
		fprintf(FP,"X%ld",gerberX(PCB, lastX));
	}
	if (Line->Point1.Y != lastY)
	{
		lastY = Line->Point1.Y;
		fprintf(FP,"Y%ld",gerberY(PCB, lastY));
	}
	if ((Line->Point1.X == Line->Point2.X) && (Line->Point1.Y == Line->Point2.Y))
		fprintf(FP, "D03*\015\012");
	else
	{
		fprintf(FP,"D02*");
		if (Line->Point2.X != lastX)
		{
			lastX = Line->Point2.X;
			fprintf(FP,"X%ld",gerberX(PCB, lastX));
		}
		if (Line->Point2.Y != lastY)
		{
			lastY = Line->Point2.Y;
			fprintf(FP,"Y%ld",gerberY(PCB, lastY));
		
		}
		fprintf(FP,"D01*\015\012");
	}
}

/* ----------------------------------------------------------------------
 * 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 unused)
{
	static int isRectangle(PolygonTypePtr Ptr);
	int firstTime = 1;
	Position startX, startY;
#if 0
	if (isRectangle(Ptr)) {
		GBX_FPrintFilledRectangle(
			FP,
			Ptr->BoundingBox.X1,
			Ptr->BoundingBox.Y1,
			Ptr->BoundingBox.X2,
			Ptr->BoundingBox.Y2
			);
	} else
#endif
	{
		fprintf(FP,"G36*\015\012");
		POLYGONPOINT_LOOP(Ptr,
			{
				if (point->X != lastX)
				{
					lastX = point->X;
					fprintf(FP,"X%ld",gerberX(PCB, lastX));
				}
				if (point->Y != lastY)
				{
					lastY = point->Y;
					fprintf(FP,"Y%ld",gerberY(PCB, lastY));
				}
				fprintf(FP,"%s*\015\012",(firstTime ? "D02" : "D01"));
				if (firstTime)
				{
					firstTime = 0;
					startX = point->X;
					startY = point->Y;
				}
			}
		);
		if (startX != lastX)
		{
			lastX = startX;
			fprintf(FP,"X%ld",gerberX(PCB, startX));
		}
		if (startY != lastY)
		{
			lastY = startY;
			fprintf(FP,"Y%ld",gerberY(PCB, lastY));
		}
		fprintf(FP,"D01*\015\012");
		fprintf(FP,"G37*\015\012");
	}
}

/*-----------------------------------------------------------------
 * find any aperture that might be used by the text
 */
static void findTextApertures(TextTypePtr Text)
{
	unsigned char	*string = (unsigned char *) Text->TextString;
	FontTypePtr		font = &PCB->Font;
	Cardinal	n;

	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;

			for (n = font->Symbol[*string].LineN; n; n--, line++)
				findApertureCode(&GBX_Apertures, line->Thickness *Text->Scale /100, ROUND, DRAW);

		}
		else
		{
			findApertureCode(&GBX_Apertures, 5, ROUND, DRAW);
		}
		string++;
	}
}

/* ----------------------------------------------------------------------
 * 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)
{
	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;
				GBX_PrintLine(FP, &newline);
			}

				/* 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
				);

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

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

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

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

	FP = GBX_Flags.FP;

	ELEMENTLINE_LOOP(Element, GBX_PrintLine(FP, line););
	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));
		apCode = findApertureCode(&GBX_Apertures, arc->Thickness, ROUND, DRAW);
		if (lastAperture != apCode)
		{
			lastAperture = apCode;
			fprintf(FP, "G54D%d*",lastAperture);
		}
		if (arcStartX != lastX)
		{
			lastX = arcStartX;
			fprintf(FP,"X%ld", gerberX(PCB, lastX));
		}
		if (arcStartY != lastY)
		{
			lastY = arcStartY;
			fprintf(FP,"Y%ld", gerberY(PCB, lastY));
		}
		fprintf(FP,
			"D02*G75G03I%ldJ%ldX%ldY%ldD01*G01*\015\012",
			gerberXOffset(PCB, arc->X - arcStartX),
			gerberYOffset(PCB, arc->Y - arcStartY),
			gerberX(PCB, arcStopX),
			gerberY(PCB, arcStopY)
			);
		lastX = arcStopX;
		lastY = arcStopY;
	}
	);
	if (!TEST_FLAG(HIDENAMEFLAG, Element))
		GBX_PrintText(FP, &ELEMENT_TEXT(PCB, Element));
}

/* ----------------------------------------------------------------------
 * prints a pad
 */
static void GBX_PrintPad(PadTypePtr Pad, int unused)
{
	int apCode;
	FILE *FP;

	FP = GBX_Flags.FP;	
	apCode = findApertureCode(&GBX_Apertures, Pad->Thickness,
	          (Pad->Flags & SQUAREFLAG) ? SQUARE : ROUND, DRAW);
	if (lastAperture != apCode)
	{
		lastAperture = apCode;
		fprintf(FP, "G54D%d*", lastAperture);
	}
	if (Pad->Point1.X != lastX)
	{
		lastX = Pad->Point1.X;
		fprintf(FP,"X%ld",gerberX(PCB, lastX));
	}
	if (Pad->Point1.Y != lastY)
	{
		lastY = Pad->Point1.Y;
		fprintf(FP,"Y%ld",gerberY(PCB, lastY));
	}
	if ((Pad->Point1.X == Pad->Point2.X) && (Pad->Point1.Y == Pad->Point2.Y))
		fprintf(FP,"D03*\015\012");
	else
	{
		fprintf(FP,"D02*");
		if (Pad->Point2.X != lastX)
		{
			lastX = Pad->Point2.X;
			fprintf(FP,"X%ld",gerberX(PCB, lastX));
		}
		if (Pad->Point2.Y != lastY)
		{
			lastY = Pad->Point2.Y;
			fprintf(FP,"Y%ld",gerberY(PCB, lastY));
		}
		fprintf(FP,"D01*\015\012");
	}
}

/* ----------------------------------------------------------------------
 * prints a via or pin to a specified file
 */
static void GBX_FPrintPinOrVia(FILE *FP, PinTypePtr Ptr)
{
	int apCode;
	
	apCode = findApertureCode(&GBX_Apertures, Ptr->Thickness,
				(Ptr->Flags & SQUAREFLAG) ? SQUARE : ROUND, FLASH);
	if (lastAperture != apCode)
	{
		lastAperture = apCode;
		fprintf(FP, "G54D%d*", lastAperture);
	}
	if (Ptr->X != lastX)
	{
		lastX = Ptr->X;
		fprintf(FP,"X%ld",gerberX(PCB, lastX));
	}
	if (Ptr->Y != lastY)
	{
		lastY = Ptr->Y;
		fprintf(FP,"Y%ld",gerberY(PCB, lastY));
	}
	fprintf(FP,"D03*\015\012");

}

/* ----------------------------------------------------------------------
 * prints a via or pin
 */
static void GBX_PrintPinOrVia(PinTypePtr Ptr, int unused)
{
		/* Add the pin/via to the layer file. */
	GBX_FPrintPinOrVia(GBX_Flags.FP, Ptr);
}

/* ----------------------------------------------------------------------
 * clears the area around a via or pin on the groundplane
 */
static void GBX_PrintClearPinOrViaOnGroundplane(PinTypePtr Pin, int unused)
{
	if (!ClearLayerStarted)
	{
		fprintf(GBX_Flags.FP, "%%LNCUTS*%%\015\012%%LPC*%%\015\012");
		ClearLayerStarted = True;
	}
	GBX_ClearPin(Pin, TEST_FLAG(USETHERMALFLAG, Pin) != 0);
	CLEAR_FLAG(USETHERMALFLAG, Pin);
	CLEAR_FLAG(PININPOLYFLAG, Pin);
}

/* ----------------------------------------------------------------------
 * prints a pad to the solder mask
 */
static void GBX_PrintPadMask(PadTypePtr Pad, int unused)
{
	int apCode;
	FILE *FP;
	
	FP = GBX_Flags.FP;
	apCode = findApertureCode(&GBX_Apertures, Pad->Thickness +2*GROUNDPLANEFRAME,
	          (Pad->Flags & SQUAREFLAG) ? SQUARE : ROUND, DRAW);
	if (lastAperture != apCode)
	{
		lastAperture = apCode;
		fprintf(FP, "G54D%d*", lastAperture);
	}
	if (Pad->Point1.X != lastX)
	{
		lastX = Pad->Point1.X;
		fprintf(FP,"X%ld",gerberX(PCB, lastX));
	}
	if (Pad->Point1.Y != lastY)
	{
		lastY = Pad->Point1.Y;
		fprintf(FP,"Y%ld",gerberY(PCB, lastY));
	}
	if ((Pad->Point1.X == Pad->Point2.X) && (Pad->Point1.Y == Pad->Point2.Y))
		fprintf(FP,"D03*\015\012");
	else
	{
		fprintf(FP,"D02*");
		if (Pad->Point2.X != lastX)
		{
			lastX = Pad->Point2.X;
			fprintf(FP,"X%ld",gerberX(PCB, lastX));
		}
		if (Pad->Point2.Y != lastY)
		{
			lastY = Pad->Point2.Y;
			fprintf(FP,"Y%ld",gerberY(PCB, lastY));
		}
		fprintf(FP,"D01*\015\012");
	}
}

/* ----------------------------------------------------------------------
 * prints a via or pin on the solder mask
 */
static void GBX_PrintPinOrViaMask(PinTypePtr Pin, int unused)
{
	int apCode;
	
	apCode = findApertureCode(&GBX_Apertures, Pin->Thickness +2*GROUNDPLANEFRAME,
				(Pin->Flags & SQUAREFLAG) ? SQUARE : ROUND, FLASH);
	if (lastAperture != apCode)
	{
		lastAperture = apCode;
		fprintf(GBX_Flags.FP, "G54D%d*", lastAperture);
	}
	if (Pin->X != lastX)
	{
		lastX = Pin->X;
		fprintf(GBX_Flags.FP,"X%ld",gerberX(PCB, lastX));
	}
	if (Pin->Y != lastY)
	{
		lastY = Pin->Y;
		fprintf(GBX_Flags.FP,"Y%ld",gerberY(PCB, lastY));
	}
	fprintf(GBX_Flags.FP,"D03*\015\012");
}

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

	fprintf(FP,
		"G54D%d",
		findApertureCode(&GBX_Apertures, 5, ROUND, DRAW));

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

/* ---------------------------------------------------------------------------
 * 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 unused)
{
	/* nothing to do since image is already negative */
}

/* ---------------------------------------------------------------------------
 * 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 unused)
{
	/* FIXME: Implement this function? */

	GBX_ErrorOccurred = True;
	logError(GBX_Flags.FP, "G04 %d %d %d %d Outline *\015\012",
		(int) X1, (int) Y1, (int) X2, (int) Y2);
}

/* ---------------------------------------------------------------------------
 * 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 unused)
{
	/* FIXME: Implement this function? */

	GBX_ErrorOccurred = True;
	logError(GBX_Flags.FP, "G04 %d %d %d %d %d Alignment *\015\012",
		(int) X1, (int) Y1, (int) X2, (int) Y2,
		(int) Settings.AlignmentDistance);
}

/* ---------------------------------------------------------------------------
 * prints a via or pin
 */
static void GBX_PrintDrillingHelper(PinTypePtr Pin, int unused)
{
	/* FIXME: Implement this function? */

	GBX_ErrorOccurred = True;
	logError(GBX_Flags.FP, "G04 %d %d DrillingHelper *\015\012",
		(int) Pin->X, (int) Pin->Y);
}

/* ----------------------------------------------------------------------
 * queries color from X11 database and generates Gerber/RS-274X compatible
 * comment.
 */
static void GBX_SetColor(XColor RGB, int unused)
{
	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);
}
