/*
 * picttoppm.c -- convert a MacIntosh PICT file to PPM format.
 *
 * This program is normally part of the PBM+ utilities, but you
 * can compile a slightly crippled version without PBM+ by
 * defining STANDALONE (e.g., cc -DSTANDALONE picttoppm.c).
 * However, if you want this you probably want PBM+ sooner or
 * later so grab it now.
 *
 * Copyright 1989,1992,1993 George Phillips
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  This software is provided "as is" without express or
 * implied warranty.
 *
 * George Phillips <phillips@cs.ubc.ca>
 * Department of Computer Science
 * University of British Columbia
 *
 * $Id: picttoppm.c,v 1.7 1993/10/26 22:40:31 phillips Exp phillips $
 */

#define _BSD_SOURCE 1
    /* This makes sure mkstemp() is in unistd.h */

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#if defined(__MINGW32__) && !defined(__CYGWIN__)
/* use libiberty replacement function */
#define mkstemp mkstemps
#endif

#ifdef STANDALONE

#ifdef __STDC__
#define ARGS(x) x
#else
#define ARGS(x) ()
#endif /* __STDC__ */
#define PPM_ASSIGN(p, R, G, B) (p).r = R; (p).g = G; (p).b = B
#define max(x, y) ((x) > (y) ? (x) : (y))
#define min(x, y) ((x) < (y) ? (x) : (y))
typedef unsigned char pixval;
typedef struct { pixval r, g, b; } pixel;
void ppm_init();
FILE* pm_openr();
void pm_usage();
void pm_message();
void pm_error();
int pm_keymatch();
void ppm_writeppminit();
void ppm_writeppmrow();
void pm_close();
pixel* ppm_allocrow();

#else

#include "ppm.h"

#endif /* STANDALONE */

#include "pbmfont.h"

/*
 * Typical byte, 2 byte and 4 byte integers.
 */
typedef unsigned char byte;
typedef char signed_byte; /* XXX */
typedef unsigned short word;
typedef unsigned long longword;

/*
 * Data structures for QuickDraw (and hence PICT) stuff.
 */

struct Rect {
	word top;
	word left;
	word bottom;
	word right;
};

struct pixMap {
	struct Rect Bounds;
	word version;
	word packType;
	longword packSize;
	longword hRes;
	longword vRes;
	word pixelType;
	word pixelSize;
	word cmpCount;
	word cmpSize;
	longword planeBytes;
	longword pmTable;
	longword pmReserved;
};

struct RGBColour {
	word red;
	word green;
	word blue;
};

struct Point {
	word x;
	word y;
};

struct Pattern {
	byte pix[64];
};

typedef void (*transfer_func) ARGS(( struct RGBColour* src, struct RGBColour* dst ));

static char* stage;
static struct Rect picFrame;
static word* red;
static word* green;
static word* blue;
static word rowlen;
static word collen;
static longword planelen;
static int verbose;
static int fullres;
static int recognize_comment;

static struct RGBColour black = { 0, 0, 0 };
static struct RGBColour white = { 0xffff, 0xffff, 0xffff };

/* various bits of drawing state */
static struct RGBColour foreground = { 0, 0, 0 };
static struct RGBColour background = { 0xffff, 0xffff, 0xffff };
static struct RGBColour op_colour;
static struct Pattern bkpat;
static struct Pattern fillpat;
static struct Rect clip_rect;
static struct Rect cur_rect;
static struct Point current;
static struct Pattern pen_pat;
static word pen_width;
static word pen_height;
static word pen_mode;
static transfer_func pen_trf;
static word text_font;
static byte text_face;
static word text_mode;
static transfer_func text_trf;
static word text_size;
static struct font* tfont;

/* state for magic printer comments */
static int ps_text;
static byte ps_just;
static byte ps_flip;
static word ps_rotation;
static byte ps_linespace;
static int ps_cent_x;
static int ps_cent_y;
static int ps_cent_set;

struct opdef {
	char* name;
	int len;
	void (*impl) ARGS((int));
	char* description;
};

struct blit_info {
	struct Rect	srcRect;
	struct Rect	srcBounds;
	int		srcwid;
	byte*		srcplane;
	int		pixSize;
	struct Rect	dstRect;
	struct RGBColour* colour_map;
	int		mode;
	struct blit_info* next;
};

static struct blit_info* blit_list = 0;
static struct blit_info** last_bl = &blit_list;

#define WORD_LEN (-1)

static void interpret_pict ARGS(( void ));
static void alloc_planes ARGS(( void ));
static void compact_plane ARGS(( word* plane, int planelen ));
static void output_ppm ARGS(( int version ));
static void Opcode_9A ARGS(( int version ));
static void BitsRect ARGS(( int version ));
static void BitsRegion ARGS(( int version ));
static void do_bitmap ARGS(( int version, int rowBytes, int is_region ));
static void do_pixmap ARGS(( int version, word rowBytes, int is_region ));
static transfer_func transfer ARGS(( int ));
static void draw_pixel ARGS (( int, int, struct RGBColour*, transfer_func ));
static int blit ARGS((
    struct Rect* srcRect, struct Rect* srcBounds, int srcwid, byte* srcplane,
    int pixSize, struct Rect* dstRect, struct Rect* dstBounds, int dstwid,
    struct RGBColour* colour_map, int mode ));
static struct blit_info* add_blit_list ARGS(( void ));
static void do_blits ARGS(( void ));
static byte* unpackbits ARGS(( struct Rect* bounds, word rowBytes, int pixelSize ));
static byte* expand_buf ARGS(( byte* buf, int* len, int bits_per_pixel ));
static void Clip ARGS(( int version ));
static void read_pixmap ARGS(( struct pixMap* p, word* rowBytes ));
static struct RGBColour* read_colour_table ARGS(( void ));
static void BkPixPat ARGS(( int version ));
static void PnPixPat ARGS(( int version ));
static void FillPixPat ARGS(( int version ));
static void read_pattern ARGS(( void ));
static void read_8x8_pattern ARGS(( struct Pattern* ));
static void BkPat ARGS(( int version ));
static void PnPat ARGS(( int version ));
static void FillPat ARGS(( int version ));
static void PnSize ARGS(( int version ));
static void PnMode ARGS(( int version ));
static void OpColor ARGS(( int version ));
static void RGBFgCol ARGS(( int version ));
static void RGBBkCol ARGS(( int version ));

static void Line ARGS(( int version ));
static void LineFrom ARGS(( int version ));
static void ShortLine ARGS(( int version ));
static void ShortLineFrom ARGS(( int version ));

static void PnLocHFrac ARGS(( int version ));
static void TxFont ARGS(( int version ));
static void TxFace ARGS(( int version ));
static void TxMode ARGS(( int version ));
static void TxSize ARGS(( int version ));
static void skip_text ARGS(( void ));
static void LongText ARGS(( int version ));
static void DHText ARGS(( int version ));
static void DVText ARGS(( int version ));
static void DHDVText ARGS(( int version ));
static void do_text ARGS(( word x, word y ));
static void do_ps_text ARGS(( word x, word y ));
static void rotate ARGS(( int* x, int* y));
static void skip_poly_or_region ARGS(( int version ));
static void LongComment ARGS(( int version ));
static void ShortComment ARGS(( int version ));

static int rectwidth ARGS(( struct Rect* r ));
static int rectheight ARGS(( struct Rect* r ));
static int rectsamesize ARGS(( struct Rect* r1, struct Rect* r2 ));
static void rectinter ARGS(( struct Rect* r1, struct Rect* r2, struct Rect* r3 ));
static void rectscale ARGS(( struct Rect* r, double xscale, double yscale ));

static void read_rect ARGS(( struct Rect* r ));
static void dump_rect ARGS(( char* s, struct Rect* r ));

static void do_paintRect ARGS(( struct Rect* r ));
static void paintRect ARGS(( int version ));
static void paintSameRect ARGS(( int version ));
static void do_frameRect ARGS(( struct Rect* r ));
static void frameRect ARGS(( int version ));
static void frameSameRect ARGS(( int version ));
static void paintPoly ARGS(( int version ));

static word get_op ARGS(( int version ));

static longword read_long ARGS(( void ));
static word read_word ARGS(( void ));
static byte read_byte ARGS(( void ));
static signed_byte read_signed_byte ARGS(( void ));

static void skip ARGS(( int n ));
static void read_n ARGS(( int n, char* buf ));

static struct font* get_font ARGS(( int font, int size, int style ));

static int load_fontdir ARGS((char *file));
static void read_rgb ARGS((struct RGBColour *rgb));
static void draw_pen_rect ARGS((struct Rect *r));
static void draw_pen ARGS((int x, int y));
static void read_point ARGS((struct Point *p));
static void read_short_point ARGS((struct Point *p));
static void scan_line ARGS((short x1, short y1, short x2, short y2));
static void scan_poly ARGS((int np, struct Point pts[]));
static void poly_sort ARGS((int sort_index, struct Point points[]));
static void picComment ARGS((word type, int length));
static int abs_value ARGS((int x));
/*
 * a table of the first 194(?) opcodes.  The table is too empty.
 *
 * Probably could use an entry specifying if the opcode is valid in version
 * 1, etc.
 */

/* for reserved opcodes of known length */
#define res(length) \
{ "reserved", (length), NULL, "reserved for Apple use" }

/* for reserved opcodes of length determined by a function */
#define resf(skipfunction) \
{ "reserved", NA, (skipfunction), "reserved for Apple use" }

/* seems like RGB colours are 6 bytes, but Apple says they're variable */
/* I'll use 6 for now as I don't care that much. */
#define RGB_LEN (6)

#define NA (0)

static struct opdef optable[] = {
/* 0x00 */	{ "NOP", 0, NULL, "nop" },
/* 0x01 */	{ "Clip", NA, Clip, "clip" },
/* 0x02 */	{ "BkPat", 8, BkPat, "background pattern" },
/* 0x03 */	{ "TxFont", 2, TxFont, "text font (word)" },
/* 0x04 */	{ "TxFace", 1, TxFace, "text face (byte)" },
/* 0x05 */	{ "TxMode", 2, TxMode, "text mode (word)" },
/* 0x06 */	{ "SpExtra", 4, NULL, "space extra (fixed point)" },
/* 0x07 */	{ "PnSize", 4, PnSize, "pen size (point)" },
/* 0x08 */	{ "PnMode", 2, PnMode, "pen mode (word)" },
/* 0x09 */	{ "PnPat", 8, PnPat, "pen pattern" },
/* 0x0a */	{ "FillPat", 8, FillPat, "fill pattern" },
/* 0x0b */	{ "OvSize", 4, NULL, "oval size (point)" },
/* 0x0c */	{ "Origin", 4, NULL, "dh, dv (word)" },
/* 0x0d */	{ "TxSize", 2, TxSize, "text size (word)" },
/* 0x0e */	{ "FgColor", 4, NULL, "foreground color (longword)" },
/* 0x0f */	{ "BkColor", 4, NULL, "background color (longword)" },
/* 0x10 */	{ "TxRatio", 8, NULL, "numerator (point), denominator (point)" },
/* 0x11 */	{ "Version", 1, NULL, "version (byte)" },
/* 0x12 */	{ "BkPixPat", NA, BkPixPat, "color background pattern" },
/* 0x13 */	{ "PnPixPat", NA, PnPixPat, "color pen pattern" },
/* 0x14 */	{ "FillPixPat", NA, FillPixPat, "color fill pattern" },
/* 0x15 */	{ "PnLocHFrac", 2, PnLocHFrac, "fractional pen position" },
/* 0x16 */	{ "ChExtra", 2, NULL, "extra for each character" },
/* 0x17 */	res(0),
/* 0x18 */	res(0),
/* 0x19 */	res(0),
/* 0x1a */	{ "RGBFgCol", RGB_LEN, RGBFgCol, "RGB foreColor" },
/* 0x1b */	{ "RGBBkCol", RGB_LEN, RGBBkCol, "RGB backColor" },
/* 0x1c */	{ "HiliteMode", 0, NULL, "hilite mode flag" },
/* 0x1d */	{ "HiliteColor", RGB_LEN, NULL, "RGB hilite color" },
/* 0x1e */	{ "DefHilite", 0, NULL, "Use default hilite color" },
/* 0x1f */	{ "OpColor", NA, OpColor, "RGB OpColor for arithmetic modes" },
/* 0x20 */	{ "Line", 8, Line, "pnLoc (point), newPt (point)" },
/* 0x21 */	{ "LineFrom", 4, LineFrom, "newPt (point)" },
/* 0x22 */	{ "ShortLine", 6, ShortLine, "pnLoc (point, dh, dv (-128 .. 127))" },
/* 0x23 */	{ "ShortLineFrom", 2, ShortLineFrom, "dh, dv (-128 .. 127)" },
/* 0x24 */	res(WORD_LEN),
/* 0x25 */	res(WORD_LEN),
/* 0x26 */	res(WORD_LEN),
/* 0x27 */	res(WORD_LEN),
/* 0x28 */	{ "LongText", NA, LongText, "txLoc (point), count (0..255), text" },
/* 0x29 */	{ "DHText", NA, DHText, "dh (0..255), count (0..255), text" },
/* 0x2a */	{ "DVText", NA, DVText, "dv (0..255), count (0..255), text" },
/* 0x2b */	{ "DHDVText", NA, DHDVText, "dh, dv (0..255), count (0..255), text" },
/* 0x2c */	res(WORD_LEN),
/* 0x2d */	res(WORD_LEN),
/* 0x2e */	res(WORD_LEN),
/* 0x2f */	res(WORD_LEN),
/* 0x30 */	{ "frameRect", 8, frameRect, "rect" },
/* 0x31 */	{ "paintRect", 8, paintRect, "rect" },
/* 0x32 */	{ "eraseRect", 8, NULL, "rect" },
/* 0x33 */	{ "invertRect", 8, NULL, "rect" },
/* 0x34 */	{ "fillRect", 8, NULL, "rect" },
/* 0x35 */	res(8),
/* 0x36 */	res(8),
/* 0x37 */	res(8),
/* 0x38 */	{ "frameSameRect", 0, frameSameRect, "rect" },
/* 0x39 */	{ "paintSameRect", 0, paintSameRect, "rect" },
/* 0x3a */	{ "eraseSameRect", 0, NULL, "rect" },
/* 0x3b */	{ "invertSameRect", 0, NULL, "rect" },
/* 0x3c */	{ "fillSameRect", 0, NULL, "rect" },
/* 0x3d */	res(0),
/* 0x3e */	res(0),
/* 0x3f */	res(0),
/* 0x40 */	{ "frameRRect", 8, NULL, "rect" },
/* 0x41 */	{ "paintRRect", 8, NULL, "rect" },
/* 0x42 */	{ "eraseRRect", 8, NULL, "rect" },
/* 0x43 */	{ "invertRRect", 8, NULL, "rect" },
/* 0x44 */	{ "fillRRrect", 8, NULL, "rect" },
/* 0x45 */	res(8),
/* 0x46 */	res(8),
/* 0x47 */	res(8),
/* 0x48 */	{ "frameSameRRect", 0, NULL, "rect" },
/* 0x49 */	{ "paintSameRRect", 0, NULL, "rect" },
/* 0x4a */	{ "eraseSameRRect", 0, NULL, "rect" },
/* 0x4b */	{ "invertSameRRect", 0, NULL, "rect" },
/* 0x4c */	{ "fillSameRRect", 0, NULL, "rect" },
/* 0x4d */	res(0),
/* 0x4e */	res(0),
/* 0x4f */	res(0),
/* 0x50 */	{ "frameOval", 8, NULL, "rect" },
/* 0x51 */	{ "paintOval", 8, NULL, "rect" },
/* 0x52 */	{ "eraseOval", 8, NULL, "rect" },
/* 0x53 */	{ "invertOval", 8, NULL, "rect" },
/* 0x54 */	{ "fillOval", 8, NULL, "rect" },
/* 0x55 */	res(8),
/* 0x56 */	res(8),
/* 0x57 */	res(8),
/* 0x58 */	{ "frameSameOval", 0, NULL, "rect" },
/* 0x59 */	{ "paintSameOval", 0, NULL, "rect" },
/* 0x5a */	{ "eraseSameOval", 0, NULL, "rect" },
/* 0x5b */	{ "invertSameOval", 0, NULL, "rect" },
/* 0x5c */	{ "fillSameOval", 0, NULL, "rect" },
/* 0x5d */	res(0),
/* 0x5e */	res(0),
/* 0x5f */	res(0),
/* 0x60 */	{ "frameArc", 12, NULL, "rect, startAngle, arcAngle" },
/* 0x61 */	{ "paintArc", 12, NULL, "rect, startAngle, arcAngle" },
/* 0x62 */	{ "eraseArc", 12, NULL, "rect, startAngle, arcAngle" },
/* 0x63 */	{ "invertArc", 12, NULL, "rect, startAngle, arcAngle" },
/* 0x64 */	{ "fillArc", 12, NULL, "rect, startAngle, arcAngle" },
/* 0x65 */	res(12),
/* 0x66 */	res(12),
/* 0x67 */	res(12),
/* 0x68 */	{ "frameSameArc", 4, NULL, "rect, startAngle, arcAngle" },
/* 0x69 */	{ "paintSameArc", 4, NULL, "rect, startAngle, arcAngle" },
/* 0x6a */	{ "eraseSameArc", 4, NULL, "rect, startAngle, arcAngle" },
/* 0x6b */	{ "invertSameArc", 4, NULL, "rect, startAngle, arcAngle" },
/* 0x6c */	{ "fillSameArc", 4, NULL, "rect, startAngle, arcAngle" },
/* 0x6d */	res(4),
/* 0x6e */	res(4),
/* 0x6f */	res(4),
/* 0x70 */	{ "framePoly", NA, skip_poly_or_region, "poly" },
/* 0x71 */	{ "paintPoly", NA, paintPoly, "poly" },
/* 0x72 */	{ "erasePoly", NA, skip_poly_or_region, "poly" },
/* 0x73 */	{ "invertPoly", NA, skip_poly_or_region, "poly" },
/* 0x74 */	{ "fillPoly", NA, skip_poly_or_region, "poly" },
/* 0x75 */	resf(skip_poly_or_region),
/* 0x76 */	resf(skip_poly_or_region),
/* 0x77 */	resf(skip_poly_or_region),
/* 0x78 */	{ "frameSamePoly", 0, NULL, "poly (NYI)" },
/* 0x79 */	{ "paintSamePoly", 0, NULL, "poly (NYI)" },
/* 0x7a */	{ "eraseSamePoly", 0, NULL, "poly (NYI)" },
/* 0x7b */	{ "invertSamePoly", 0, NULL, "poly (NYI)" },
/* 0x7c */	{ "fillSamePoly", 0, NULL, "poly (NYI)" },
/* 0x7d */	res(0),
/* 0x7e */	res(0),
/* 0x7f */	res(0),
/* 0x80 */	{ "frameRgn", NA, skip_poly_or_region, "region" },
/* 0x81 */	{ "paintRgn", NA, skip_poly_or_region, "region" },
/* 0x82 */	{ "eraseRgn", NA, skip_poly_or_region, "region" },
/* 0x83 */	{ "invertRgn", NA, skip_poly_or_region, "region" },
/* 0x84 */	{ "fillRgn", NA, skip_poly_or_region, "region" },
/* 0x85 */	resf(skip_poly_or_region),
/* 0x86 */	resf(skip_poly_or_region),
/* 0x87 */	resf(skip_poly_or_region),
/* 0x88 */	{ "frameSameRgn", 0, NULL, "region (NYI)" },
/* 0x89 */	{ "paintSameRgn", 0, NULL, "region (NYI)" },
/* 0x8a */	{ "eraseSameRgn", 0, NULL, "region (NYI)" },
/* 0x8b */	{ "invertSameRgn", 0, NULL, "region (NYI)" },
/* 0x8c */	{ "fillSameRgn", 0, NULL, "region (NYI)" },
/* 0x8d */	res(0),
/* 0x8e */	res(0),
/* 0x8f */	res(0),
/* 0x90 */	{ "BitsRect", NA, BitsRect, "copybits, rect clipped" },
/* 0x91 */	{ "BitsRgn", NA, BitsRegion, "copybits, rgn clipped" },
/* 0x92 */	res(WORD_LEN),
/* 0x93 */	res(WORD_LEN),
/* 0x94 */	res(WORD_LEN),
/* 0x95 */	res(WORD_LEN),
/* 0x96 */	res(WORD_LEN),
/* 0x97 */	res(WORD_LEN),
/* 0x98 */	{ "PackBitsRect", NA, BitsRect, "packed copybits, rect clipped" },
/* 0x99 */	{ "PackBitsRgn", NA, BitsRegion, "packed copybits, rgn clipped" },
/* 0x9a */	{ "Opcode_9A", NA, Opcode_9A, "the mysterious opcode 9A" },
/* 0x9b */	res(WORD_LEN),
/* 0x9c */	res(WORD_LEN),
/* 0x9d */	res(WORD_LEN),
/* 0x9e */	res(WORD_LEN),
/* 0x9f */	res(WORD_LEN),
/* 0xa0 */	{ "ShortComment", 2, ShortComment, "kind (word)" },
/* 0xa1 */	{ "LongComment", NA, LongComment, "kind (word), size (word), data" }
};

struct const_name {
	int	value;
	char* name;
};

static char* const_name ARGS(( struct const_name* table, int ct));

struct const_name transfer_name[] = {
	{ 0,	"srcCopy" },
	{ 1,	"srcOr" },
	{ 2,	"srcXor" },
	{ 3,	"srcBic" },
	{ 4,	"notSrcCopy" },
	{ 5,	"notSrcOr" },
	{ 6,	"notSrcXor" },
	{ 7,	"notSrcBic" },
	{ 32,	"blend" },
	{ 33,	"addPin" },
	{ 34,	"addOver" },
	{ 35,	"subPin" },
	{ 36,	"transparent" },
	{ 37,	"adMax" },
	{ 38,	"subOver" },
	{ 39,	"adMin" },
	{ -1,	0 }
};

struct const_name font_name[] = {
	{ 0,	"systemFont" },
	{ 1,	"applFont" },
	{ 2,	"newYork" },
	{ 3,	"geneva" },
	{ 4,	"monaco" },
	{ 5,	"venice" },
	{ 6,	"london" },
	{ 7,	"athens" },
	{ 8,	"sanFran" },
	{ 9,	"toronto" },
	{ 11,	"cairo" },
	{ 12,	"losAngeles" },
	{ 20,	"times" },
	{ 21,	"helvetica" },
	{ 22,	"courier" },
	{ 23,	"symbol" },
	{ 24,	"taliesin" },
	{ -1,	0 }
};

struct const_name ps_just_name[] = {
	{ 0,	"no" },
	{ 1,	"left" },
	{ 2,	"center" },
	{ 3,	"right" },
	{ 4,	"full" },
	{ -1,	0 }
};

struct const_name ps_flip_name[] = {
	{ 0,	"no" },
	{ 1,	"horizontal" },
	{ 2,	"vertical" },
	{ -1,	0 }
};

#define FNT_BOLD	(1)
#define FNT_ITALIC	(2)
#define FNT_ULINE	(4)
#define FNT_OUTLINE	(8)
#define FNT_SHADOW	(16)
#define FNT_CONDENSE	(32)
#define FNT_EXTEND	(64)

static int align = 0;
static FILE* ifp;

int
main(argc, argv)
int argc;
char* argv[];
{
	int argn;
	int header;
	char* usage =
"[-verbose] [-fullres] [-noheader] [-quickdraw] [-fontdir file] [pictfile]";

	ppm_init( &argc, argv );

	argn = 1;
	verbose = 0;
	fullres = 0;
	header = 1;
	recognize_comment = 1;

	while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') {
		if (pm_keymatch(argv[argn], "-verbose", 2))
			verbose++;
		else if (pm_keymatch(argv[argn], "-fullres", 3))
			fullres = 1;
		else if (pm_keymatch(argv[argn], "-noheader", 2))
			header = 0;
		else if (pm_keymatch(argv[argn], "-quickdraw", 2))
			recognize_comment = 0;
		else if (pm_keymatch(argv[argn], "-fontdir", 3)) {
			argn++;
			if (!argv[argn])
				pm_usage(usage);
			else
				load_fontdir(argv[argn]);
		}
		else
			pm_usage(usage);
		++argn;
	}

	if (load_fontdir("fontdir") < 0)
		pm_message("warning: can't load font directory 'fontdir'\n");

	if (argn < argc) {
		ifp = pm_openr(argv[argn]);
		++argn;
	} else
		ifp = stdin;

	if (argn != argc)
		pm_usage(usage);

	if (header) {
		stage = "Reading 512 byte header";
		skip(512);
	}

	interpret_pict();
	exit(0);
}

static void
interpret_pict()
{
	byte ch;
	word picSize;
	word opcode;
	word len;
	int version;
	int i;

	for (i = 0; i < 64; i++)
		pen_pat.pix[i] = bkpat.pix[i] = fillpat.pix[i] = 1;
	pen_width = pen_height = 1;
	pen_mode = 0; /* srcCopy */
	pen_trf = transfer(pen_mode);
	text_mode = 0; /* srcCopy */
	text_trf = transfer(text_mode);

	stage = "Reading picture size";
	picSize = read_word();

	if (verbose)
		pm_message("picture size = %d (0x%x)", picSize, picSize);

	stage = "reading picture frame";
	read_rect(&picFrame);

	if (verbose) {
		dump_rect("Picture frame:", &picFrame);
		pm_message("Picture size is %d x %d",
			picFrame.right - picFrame.left,
			picFrame.bottom - picFrame.top);
	}

	if (!fullres)
		alloc_planes();

	while ((ch = read_byte()) == 0)
		;
	if (ch != 0x11)
		pm_error("No version number");

	switch (read_byte()) {
	case 1:
		version = 1;
		break;
	case 2:
		if (read_byte() != 0xff)
			pm_error("can only do version 2, subcode 0xff");
		version = 2;
		break;
	default:
		pm_error("Unknown version");
	}

	if (verbose)
		pm_message("PICT version %d", version);

	while((opcode = get_op(version)) != 0xff) {
		if (opcode < 0xa2) {
			if (verbose) {
				stage = optable[opcode].name;
				if (!strcmp(stage, "reserved"))
					pm_message("reserved opcode=0x%x", opcode);
				else
					pm_message("%s", stage = optable[opcode].name);
			}

			if (optable[opcode].impl != NULL)
				(*optable[opcode].impl)(version);
			else if (optable[opcode].len >= 0)
				skip(optable[opcode].len);
			else switch (optable[opcode].len) {
			case WORD_LEN:
				len = read_word();
				skip(len);
				break;
			default:
				pm_error("can't do length of %d",
					optable[opcode].len);
			}
		}
		else if (opcode == 0xc00) {
			if (verbose)
				pm_message("HeaderOp");
			stage = "HeaderOp";
			skip(24);
		}
		else if (opcode >= 0xa2 && opcode <= 0xaf) {
			stage = "skipping reserved";
			if (verbose)
				pm_message("%s 0x%x", stage, opcode);
			skip(read_word());
		}
		else if (opcode >= 0xb0 && opcode <= 0xcf) {
			/* just a reserved opcode, no data */
			if (verbose)
				pm_message("reserved 0x%x", opcode);
		}
		else if (opcode >= 0xd0 && opcode <= 0xfe) {
			stage = "skipping reserved";
			if (verbose)
				pm_message("%s 0x%x", stage, opcode);
			skip(read_long());
		}
		else if (opcode >= 0x100 && opcode <= 0x7fff) {
			stage = "skipping reserved";
			if (verbose)
				pm_message("%s 0x%x", stage, opcode);
			skip((opcode >> 7) & 255);
		}
		else if (opcode >= 0x8000 && opcode <= 0x80ff) {
			/* just a reserved opcode */
			if (verbose)
				pm_message("reserved 0x%x", opcode);
		}
		else if (opcode >= 8100 && opcode <= 0xffff) {
			stage = "skipping reserved";
			if (verbose)
				pm_message("%s 0x%x", stage, opcode);
			skip(read_long());
		}
		else
			pm_error("can't handle opcode of %x", opcode);
	}
	output_ppm(version);
}

/* allocation is same for version 1 or version 2.  We are super-duper
 * wasteful of memory for version 1 picts.  Someday, we'll separate
 * things and only allocate a byte per pixel for version 1 (or heck,
 * even only a bit, but that would require even more extra work).
 */

static void alloc_planes()
{
	rowlen = picFrame.right - picFrame.left;
	collen = picFrame.bottom - picFrame.top;

	clip_rect.top = picFrame.top;
	clip_rect.left = picFrame.left;
	clip_rect.bottom = picFrame.bottom;
	clip_rect.top = picFrame.top;

	planelen = rowlen * collen;
	if ((red = (word*)malloc(planelen * sizeof(word))) == NULL ||
		(green = (word*)malloc(planelen * sizeof(word))) == NULL ||
		(blue = (word*)malloc(planelen * sizeof(word))) == NULL)
	{

		pm_error("not enough memory to hold picture");
	}
	/* initialize background to white */
	memset(red, 255, planelen * sizeof(word));
	memset(green, 255, planelen * sizeof(word));
	memset(blue, 255, planelen * sizeof(word));
}

static void
compact_plane(plane, planelen)
register word* plane;
register int planelen;
{
	register byte* p;

	for (p = (byte*)plane; planelen-- > 0; )
		*p++ = (*plane++ >> 8) & 255;
}

static void
output_ppm(version)
int version;
{
	int width;
	int height;
	register char* r;
	register char* g;
	register char* b;
	pixel* pixelrow;
	register pixel* pP;
	int row;
	register int col;

	if (fullres)
		do_blits();

	stage = "writing PPM";

	width = picFrame.right - picFrame.left;
	height = picFrame.bottom - picFrame.top;
	r = (char*) red;
	compact_plane((word*) r, width * height);
	g = (char*) green;
	compact_plane((word*) g, width * height);
	b = (char*) blue;
	compact_plane((word*) b, width * height);

	ppm_writeppminit(stdout, width, height, (pixval) 255, 0 );
	pixelrow = ppm_allocrow(width);
	for (row = 0; row < height; ++row) {
		for (col = 0, pP = pixelrow; col < width;
		     ++col, ++pP, ++r, ++g, ++b) {
			PPM_ASSIGN(*pP, *r, *g, *b);
		}

		ppm_writeppmrow(stdout, pixelrow, width, (pixval) 255, 0 );
	}
	pm_close(stdout);
}

static void
do_blits()
{
	struct blit_info* bi;
	int	srcwidth, dstwidth, srcheight, dstheight;
	double	scale, scalelow, scalehigh;
	double	xscale = 1.0;
	double	yscale = 1.0;
	double	lowxscale, highxscale, lowyscale, highyscale;
	int		xscalecalc = 0, yscalecalc = 0;

	if (!blit_list) return;

	fullres = 0;

	for (bi = blit_list; bi; bi = bi->next) {
		srcwidth = rectwidth(&bi->srcRect);
		dstwidth = rectwidth(&bi->dstRect);
		srcheight = rectheight(&bi->srcRect);
		dstheight = rectheight(&bi->dstRect);
		if (srcwidth > dstwidth) {
			scalelow  = (double)(srcwidth      ) / (double)dstwidth;
			scalehigh = (double)(srcwidth + 1.0) / (double)dstwidth;
			switch (xscalecalc) {
			case 0:
				lowxscale = scalelow;
				highxscale = scalehigh;
				xscalecalc = 1;
				break;
			case 1:
				if (scalelow < highxscale && scalehigh > lowxscale) {
					if (scalelow > lowxscale) lowxscale = scalelow;
					if (scalehigh < highxscale) highxscale = scalehigh;
				}
				else {
					scale = (lowxscale + highxscale) / 2.0;
					xscale = (double)srcwidth / (double)dstwidth;
					if (scale > xscale) xscale = scale;
					xscalecalc = 2;
				}
				break;
			case 2:
				scale = (double)srcwidth / (double)dstwidth;
				if (scale > xscale) xscale = scale;
				break;
			}
		}

		if (srcheight > dstheight) {
			scalelow =  (double)(srcheight      ) / (double)dstheight;
			scalehigh = (double)(srcheight + 1.0) / (double)dstheight;
			switch (yscalecalc) {
			case 0:
				lowyscale = scalelow;
				highyscale = scalehigh;
				yscalecalc = 1;
				break;
			case 1:
				if (scalelow < highyscale && scalehigh > lowyscale) {
					if (scalelow > lowyscale) lowyscale = scalelow;
					if (scalehigh < highyscale) highyscale = scalehigh;
				}
				else {
					scale = (lowyscale + highyscale) / 2.0;
					yscale = (double)srcheight / (double)dstheight;
					if (scale > yscale) yscale = scale;
					yscalecalc = 2;
				}
				break;
			case 2:
				scale = (double)srcheight / (double)dstheight;
				if (scale > yscale) yscale = scale;
				break;
			}
		}
	}

	if (xscalecalc == 1) {
		if (1.0 >= lowxscale && 1.0 <= highxscale)
			xscale = 1.0;
		else
			xscale = lowxscale;
	}
	if (yscalecalc == 1) {
		if (1.0 >= lowyscale && 1.0 <= lowyscale)
			yscale = 1.0;
		else
			yscale = lowyscale;
	}

	if (xscale != 1.0 || yscale != 1.0) {
		for (bi = blit_list; bi; bi = bi->next)
			rectscale(&bi->dstRect, xscale, yscale);

		pm_message("Scaling output by %f in X and %f in Y",
                   xscale, yscale);
		rectscale(&picFrame, xscale, yscale);
	}

	alloc_planes();

	for (bi = blit_list; bi; bi = bi->next) {
		blit(&bi->srcRect, &bi->srcBounds, bi->srcwid, bi->srcplane,
		     bi->pixSize,
		     &bi->dstRect, &picFrame, rowlen,
		     bi->colour_map,
		     bi->mode);
	}
}

/*
 * This could use read_pixmap, but I'm too lazy to hack read_pixmap.
 */

static void
Opcode_9A(version)
int version;
{
#ifdef DUMP
	FILE *fp = fopen("data", "wb");
	int ch;
	if (fp == NULL) exit(1);
	while ((ch = fgetc(ifp)) != EOF) fputc(ch, fp);
	exit(0);
#else
	struct pixMap	p;
	struct Rect		srcRect;
	struct Rect		dstRect;
	byte*			pm;
	int				pixwidth;
	word			mode;

	/* skip fake len, and fake EOF */
	skip(4);
	read_word();	/* version */
	read_rect(&p.Bounds);
	pixwidth = p.Bounds.right - p.Bounds.left;
	p.packType = read_word();
	p.packSize = read_long();
	p.hRes = read_long();
	p.vRes = read_long();
	p.pixelType = read_word();
	p.pixelSize = read_word();
	p.pixelSize = read_word();
	p.cmpCount = read_word();
	p.cmpSize = read_word();
	p.planeBytes = read_long();
	p.pmTable = read_long();
	p.pmReserved = read_long();

	if (p.pixelSize == 16)
		pixwidth *= 2;
	else if (p.pixelSize == 32)
		pixwidth *= 3;

	read_rect(&srcRect);
	if (verbose)
		dump_rect("source rectangle:", &srcRect);

	read_rect(&dstRect);
	if (verbose)
		dump_rect("destination rectangle:", &dstRect);

	mode = read_word();
	if (verbose)
		pm_message("transfer mode = %s", const_name(transfer_name, mode));

	pm = unpackbits(&p.Bounds, 0, p.pixelSize);

	if (blit(&srcRect, &(p.Bounds), pixwidth, pm, p.pixelSize,
		 &dstRect, &picFrame, rowlen,
		 (struct RGBColour*)0,
		 mode))
	{
		free(pm);
	}
#endif
}

static void
BitsRect(version)
int version;
{
	word rowBytes;

	stage = "Reading rowBytes for bitsrect";
	rowBytes = read_word();

	if (verbose)
		pm_message("rowbytes = 0x%x (%d)", rowBytes, rowBytes & 0x7fff);

	if (rowBytes & 0x8000)
		do_pixmap(version, rowBytes, 0);
	else
		do_bitmap(version, rowBytes, 0);
}

static void
BitsRegion(version)
int version;
{
	word rowBytes;

	stage = "Reading rowBytes for bitsregion";
	rowBytes = read_word();

	if (rowBytes & 0x8000)
		do_pixmap(version, rowBytes, 1);
	else
		do_bitmap(version, rowBytes, 1);
}

static void
do_bitmap(version, rowBytes, is_region)
int version;
int rowBytes;
int is_region;
{
	struct Rect Bounds;
	struct Rect srcRect;
	struct Rect dstRect;
	word mode;
	byte* pm;
	static struct RGBColour colour_table[] = { {65535L, 65535L, 65535L}, {0, 0, 0} };

	read_rect(&Bounds);
	read_rect(&srcRect);
	read_rect(&dstRect);
	mode = read_word();
	if (verbose)
		pm_message("transfer mode = %s", const_name(transfer_name, mode));

	if (is_region)
		skip_poly_or_region(version);

	stage = "unpacking rectangle";

	pm = unpackbits(&Bounds, rowBytes, 1);

	if (blit(&srcRect, &Bounds, Bounds.right - Bounds.left, pm, 8,
		 &dstRect, &picFrame, rowlen,
		 colour_table,
		 mode))
	{
		free(pm);
	}
}

#if __STDC__
static void
do_pixmap( int version, word rowBytes, int is_region )
#else /*__STDC__*/
static void
do_pixmap(version, rowBytes, is_region)
int version;
word rowBytes;
int is_region;
#endif /*__STDC__*/
{
	word mode;
	struct pixMap p;
	word pixwidth;
	byte* pm;
	struct RGBColour* colour_table;
	struct Rect srcRect;
	struct Rect dstRect;

	read_pixmap(&p, NULL);

	pixwidth = p.Bounds.right - p.Bounds.left;

	if (verbose)
		pm_message("%d x %d rectangle", pixwidth,
			p.Bounds.bottom - p.Bounds.top);

	colour_table = read_colour_table();

	read_rect(&srcRect);

	if (verbose)
		dump_rect("source rectangle:", &srcRect);

	read_rect(&dstRect);

	if (verbose)
		dump_rect("destination rectangle:", &dstRect);

	mode = read_word();

	if (verbose)
		pm_message("transfer mode = %s", const_name(transfer_name, mode));

	if (is_region)
		skip_poly_or_region(version);

	stage = "unpacking rectangle";

	pm = unpackbits(&p.Bounds, rowBytes, p.pixelSize);

	if (blit(&srcRect, &(p.Bounds), pixwidth, pm, 8,
		 &dstRect, &picFrame, rowlen,
		 colour_table,
		 mode))
	{
		free(colour_table);
		free(pm);
	}
}

static struct blit_info* add_blit_list()
{
	struct blit_info* bi;

	if (!(bi = (struct blit_info*)malloc(sizeof(struct blit_info))))
		pm_error("out of memory for blit list");
	
	bi->next = 0;
	*last_bl = bi;
	last_bl = &(bi->next);

	return bi;
}

/* Various transfer functions for blits.
 *
 * Note src[Not]{Or,Xor,Copy} only work if the source pixmap was originally
 * a bitmap.
 * There's also a small bug that the foreground and background colours
 * are not used in a srcCopy; this wouldn't be hard to fix.
 * It IS a problem since the foreground and background colours CAN be changed.
 */

#define rgb_all_same(x, y) \
	((x)->red == (y) && (x)->green == (y) && (x)->blue == (y))
#define rgb_is_white(x) rgb_all_same((x), 0xffff)
#define rgb_is_black(x) rgb_all_same((x), 0)

static void srcCopy(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if (rgb_is_black(src))
		*dst = foreground;
	else
		*dst = background;
}

static void srcOr(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if (rgb_is_black(src))
		*dst = foreground;
}

static void srcXor(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	dst->red ^= ~src->red;
	dst->green ^= ~src->green;
	dst->blue ^= ~src->blue;
}

static void srcBic(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if (rgb_is_black(src))
		*dst = background;
}

static void notSrcCopy(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if (rgb_is_white(src))
		*dst = foreground;
	else if (rgb_is_black(src))
		*dst = background;
}

static void notSrcOr(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if (rgb_is_white(src))
		*dst = foreground;
}

static void notSrcBic(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if (rgb_is_white(src))
		*dst = background;
}

static void notSrcXor(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	dst->red ^= src->red;
	dst->green ^= src->green;
	dst->blue ^= src->blue;
}

static void addOver(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	dst->red += src->red;
	dst->green += src->green;
	dst->blue += src->blue;
}

static void addPin(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if ((long)dst->red + (long)src->red > (long)op_colour.red)
		dst->red = op_colour.red;
	else
		dst->red = dst->red + src->red;

	if ((long)dst->green + (long)src->green > (long)op_colour.green)
		dst->green = op_colour.green;
	else
		dst->green = dst->green + src->green;

	if ((long)dst->blue + (long)src->blue > (long)op_colour.blue)
		dst->blue = op_colour.blue;
	else
		dst->blue = dst->blue + src->blue;
}

static void subOver(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	dst->red -= src->red;
	dst->green -= src->green;
	dst->blue -= src->blue;
}

/* or maybe its src - dst; my copy of Inside Mac is unclear */
static void subPin(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if ((long)dst->red - (long)src->red < (long)op_colour.red)
		dst->red = op_colour.red;
	else
		dst->red = dst->red - src->red;

	if ((long)dst->green - (long)src->green < (long)op_colour.green)
		dst->green = op_colour.green;
	else
		dst->green = dst->green - src->green;

	if ((long)dst->blue - (long)src->blue < (long)op_colour.blue)
		dst->blue = op_colour.blue;
	else
		dst->blue = dst->blue - src->blue;
}

static void adMax(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if (src->red > dst->red) dst->red = src->red;
	if (src->green > dst->green) dst->green = src->green;
	if (src->blue > dst->blue) dst->blue = src->blue;
}

static void adMin(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if (src->red < dst->red) dst->red = src->red;
	if (src->green < dst->green) dst->green = src->green;
	if (src->blue < dst->blue) dst->blue = src->blue;
}

static void blend(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
#define blend_component(cmp)	\
	((long)src->cmp * (long)op_colour.cmp) / 65536 +	\
	((long)dst->cmp * (long)(65536 - op_colour.cmp) / 65536)

	dst->red = blend_component(red);
	dst->green = blend_component(green);
	dst->blue = blend_component(blue);
}

static void transparent(src, dst)
struct RGBColour*	src;
struct RGBColour*	dst;
{
	if (src->red != background.red || src->green != background.green ||
		src->blue != background.blue)
	{
		*dst = *src;
	}
}

static transfer_func transfer(mode)
int mode;
{
	switch (mode) {
	case  0: return srcCopy;
	case  1: return srcOr;
	case  2: return srcXor;
	case  3: return srcBic;
	case  4: return notSrcCopy;
	case  5: return notSrcOr;
	case  6: return notSrcXor;
	case  7: return notSrcBic;
	case 32: return blend;
	case 33: return addPin;
	case 34: return addOver;
	case 35: return subPin;
	case 36: return transparent;
	case 37: return adMax;
	case 38: return subOver;
	case 39: return adMin;
	default:
		if (mode != 0)
			pm_message("no transfer function for code %s, using srcCopy",
				const_name(transfer_name, mode));
		return srcCopy;
	}
}

#ifndef VMS
#define AMI_OP != 
#else
#define AMI_OP ==
#endif


static int
blit(srcRect, srcBounds, srcwid, srcplane, pixSize, dstRect, dstBounds, dstwid, colour_map, mode)
struct Rect* srcRect;
struct Rect* srcBounds;
int srcwid;
byte* srcplane;
int pixSize;
struct Rect* dstRect;
struct Rect* dstBounds;
int dstwid;
struct RGBColour* colour_map;
int mode;
{
/* I can't tell what the result value of this function is supposed to mean,
   but I found several return statements that did not set it to anything,
   and several calls that examine it.  I'm guessing that "1" is the 
   appropriate thing to return in those cases, so I made it so.
   -Bryan 00.03.02
*/

	struct Rect clipsrc;
	struct Rect clipdst;
	register byte* src;
	register word* reddst;
	register word* greendst;
	register word* bluedst;
	register int i;
	register int j;
	int dstoff;
	int xsize;
	int ysize;
	int srcadd;
	int dstadd;
	struct RGBColour* ct;
	int pkpixsize;
	struct blit_info* bi;
	struct RGBColour src_c, dst_c;
	transfer_func trf;

	if (ps_text)
		return(1);

	/* almost got it.  clip source rect with source bounds.
	 * clip dest rect with dest bounds.  if source and
	 * destination are not the same size, use pnmscale
	 * to get a nicely sized rectangle.
	 */
	rectinter(srcBounds, srcRect, &clipsrc);
	rectinter(dstBounds, dstRect, &clipdst);

	if (fullres) {
		bi = add_blit_list();
		bi->srcRect = clipsrc;
		bi->srcBounds = *srcBounds;
		bi->srcwid = srcwid;
		bi->srcplane = srcplane;
		bi->pixSize = pixSize;
		bi->dstRect = clipdst;
		bi->colour_map = colour_map;
		bi->mode = mode;
		return(0);
	}

	if (verbose) {
		dump_rect("copying from:", &clipsrc);
		dump_rect("to:          ", &clipdst);
		pm_message("a %d x %d area to a %d x %d area",
			rectwidth(&clipsrc), rectheight(&clipsrc),
			rectwidth(&clipdst), rectheight(&clipdst));
	}

	pkpixsize = 1;
	if (pixSize == 16)
		pkpixsize = 2;

	src = srcplane + (clipsrc.top - srcBounds->top) * srcwid +
		(clipsrc.left - srcBounds->left) * pkpixsize;
	xsize = clipsrc.right - clipsrc.left;
	ysize = clipsrc.bottom - clipsrc.top;
	srcadd = srcwid - xsize * pkpixsize;

	dstoff = (clipdst.top - dstBounds->top) * dstwid +
		(clipdst.left - dstBounds->left);
	reddst = red + dstoff;
	greendst = green + dstoff;
	bluedst = blue + dstoff;
	dstadd = dstwid - (clipdst.right - clipdst.left);

	/* get rid of Text mask mode bit, if (erroneously) set */
	if ((mode & ~64) == 0)
		trf = 0;	/* optimized srcCopy */
	else
		trf = transfer(mode & ~64);

	if (!rectsamesize(&clipsrc, &clipdst)) {
#ifdef STANDALONE
		fprintf(stderr,
		  "picttoppm: standalone version can't scale rectangles yet, sorry.\n");
		fprintf(stderr, "picttoppm: skipping this rectangle.\n");
		return(1);
#else
		FILE*	pnmscale;
#define TMPFILE_TEMPLATE "/tmp/picttoppm.XXXXXX"
		char	tmpfile[sizeof(TMPFILE_TEMPLATE)];
        int     fd;
		char	command[1024];
		register byte* redsrc;
		register byte* greensrc;
		register byte* bluesrc;
		int	greenpix;
		FILE*	scaled;
		int	cols, rows, format;
		pixval maxval;
		pixel* row;
		pixel* rowp;

#if (defined(AMIGA) || defined(VMS))
                char ami_tmpfile[L_tmpnam];
                int ami_result;
                tmpnam(ami_tmpfile);
                if (!(pnmscale = fopen(ami_tmpfile, "wb")))
                        pm_error("cannot create temporary file '%s'", ami_tmpfile);
#else /* AMIGA or VMS */
        strcpy(tmpfile, TMPFILE_TEMPLATE);
        fd = mkstemp(tmpfile);
        if (fd == -1) 
            pm_error("Unable to create temporary file.");
        close(fd);

		sprintf(command, "pnmscale -xsize %d -ysize %d > %s",
			rectwidth(&clipdst), rectheight(&clipdst), tmpfile);
		
		pm_message("running 'pnmscale -xsize %d -ysize %d' on a %d x %d image",
			rectwidth(&clipdst), rectheight(&clipdst),
			rectwidth(&clipsrc), rectheight(&clipsrc));

		if (!(pnmscale = popen(command, "w"))) {
			pm_error("cannot execute command '%s'  popen() errno = %s (%d)",
                     command, strerror(errno), errno);
		}
#endif /* AMIGA or VMS */

		fprintf(pnmscale, "P6\n%d %d\n%d\n",
			rectwidth(&clipsrc), rectheight(&clipsrc), PPM_MAXMAXVAL);

#define REDEPTH(c, oldmax)  ((c) * ((PPM_MAXMAXVAL) + 1) / (oldmax + 1))

		switch (pixSize) {
		case 8:
			for (i = 0; i < ysize; ++i) {
				for (j = 0; j < xsize; ++j) {
					ct = colour_map + *src++;
					fputc(REDEPTH(ct->red, 65535L), pnmscale);
					fputc(REDEPTH(ct->green, 65535L), pnmscale);
					fputc(REDEPTH(ct->blue, 65535L), pnmscale);
				}
				src += srcadd;
			}
			break;
		case 16:
			for (i = 0; i < ysize; ++i) {
				for (j = 0; j < xsize; ++j) {
					fputc(REDEPTH((*src & 0x7c) >> 2, 32), pnmscale);
					greenpix = (*src++ & 3) << 3;
					greenpix |= (*src & 0xe0) >> 5;
					fputc(REDEPTH(greenpix, 32), pnmscale);
					fputc(REDEPTH((*src++ & 0x1f) << 11, 32), pnmscale);
				}
				src += srcadd;
			}
			break;
		case 32:
			srcadd = srcwid - xsize;
			redsrc = src;
			greensrc = src + (srcwid / 3);
			bluesrc = greensrc + (srcwid / 3);

			for (i = 0; i < ysize; ++i) {
				for (j = 0; j < xsize; ++j) {
					fputc(REDEPTH(*redsrc++, 256), pnmscale);
					fputc(REDEPTH(*greensrc++, 256), pnmscale);
					fputc(REDEPTH(*bluesrc++, 256), pnmscale);
				}
				redsrc += srcadd;
				greensrc += srcadd;
				bluesrc += srcadd;
			}
		}

#if (defined(AMIGA) || defined(VMS))
                if( fclose(pnmscale) != 0 ) {
                    unlink(ami_tmpfile);
                    pm_error("Error closing file.  fclose() returns "
                             "errno %s (%d).", strerror(errno), errno);
                }
                sprintf(command, "pnmscale -xsize %d -ysize %d %s > %s",
                        rectwidth(&clipdst), rectheight(&clipdst), ami_tmpfile, tmpfile);
                pm_message("running 'pnmscale -xsize %d -ysize %d' on a %d x %d image",
                        rectwidth(&clipdst), rectheight(&clipdst),
                        rectwidth(&clipsrc), rectheight(&clipsrc), 0);
                ami_result = system(command);
                unlink(ami_tmpfile);
                if( ami_result AMI_OP 0 ) {
                    pm_error("pnmscale failed");
                }
#else /* AMIGA or VMS */
		if (pclose(pnmscale)) {
			pm_error("pnmscale failed.  pclose() returned Errno %s (%d)",
                     strerror(errno), errno);
		}
#endif /* AMIGA or VMS */
		ppm_readppminit(scaled = pm_openr(tmpfile), &cols, &rows,
			&maxval, &format);
		row = ppm_allocrow(cols);
		/* couldn't hurt to assert cols, rows and maxval... */	

		if (trf == 0) {
			while (rows-- > 0) {
				ppm_readppmrow(scaled, row, cols, maxval, format);
				for (i = 0, rowp = row; i < cols; ++i, ++rowp) {
					*reddst++ = PPM_GETR(*rowp) * 65536L / (maxval + 1); 
					*greendst++ = PPM_GETG(*rowp) * 65536L / (maxval + 1); 
					*bluedst++ = PPM_GETB(*rowp) * 65536L / (maxval + 1); 
				}
				reddst += dstadd;
				greendst += dstadd;
				bluedst += dstadd;
			}
		}
		else {
			while (rows-- > 0) {
				ppm_readppmrow(scaled, row, cols, maxval, format);
				for (i = 0, rowp = row; i < cols; i++, rowp++) {
					dst_c.red = *reddst;
					dst_c.green = *greendst;
					dst_c.blue = *bluedst;
					src_c.red = PPM_GETR(*rowp) * 65536L / (maxval + 1); 
					src_c.green = PPM_GETG(*rowp) * 65536L / (maxval + 1); 
					src_c.blue = PPM_GETB(*rowp) * 65536L / (maxval + 1); 
					(*trf)(&src_c, &dst_c);
					*reddst++ = dst_c.red;
					*greendst++ = dst_c.green;
					*bluedst++ = dst_c.blue;
				}
				reddst += dstadd;
				greendst += dstadd;
				bluedst += dstadd;
			}
		}

		pm_close(scaled);
		ppm_freerow(row);
		unlink(tmpfile);
		return(1);
#endif /* ! STANDALONE */
	}

	if (trf == 0) {
		/* optimized srcCopy blit ('cause it was implemented first) */
		switch (pixSize) {
		case 8:
			for (i = 0; i < ysize; ++i) {
				for (j = 0; j < xsize; ++j) {
					ct = colour_map + *src++;
					*reddst++ = ct->red;
					*greendst++ = ct->green;
					*bluedst++ = ct->blue;
				}
				src += srcadd;
				reddst += dstadd;
				greendst += dstadd;
				bluedst += dstadd;
			}
			break;
		case 16:
			for (i = 0; i < ysize; ++i) {
				for (j = 0; j < xsize; ++j) {
					*reddst++ = (*src & 0x7c) << 9;
					*greendst = (*src++ & 3) << 14;
					*greendst++ |= (*src & 0xe0) << 6;
					*bluedst++ = (*src++ & 0x1f) << 11;
				}
				src += srcadd;
				reddst += dstadd;
				greendst += dstadd;
				bluedst += dstadd;
			}
			break;
		case 32:
			srcadd = (srcwid / 3) - xsize;
			for (i = 0; i < ysize; ++i) {
				for (j = 0; j < xsize; ++j)
					*reddst++ = *src++ << 8;

				reddst += dstadd;
				src += srcadd;

				for (j = 0; j < xsize; ++j)
					*greendst++ = *src++ << 8;

				greendst += dstadd;
				src += srcadd;

				for (j = 0; j < xsize; ++j)
					*bluedst++ = *src++ << 8;

				bluedst += dstadd;
				src += srcadd;
			}
		}
	}
	else {
#define grab_destination()		\
		dst_c.red = *reddst;		\
		dst_c.green = *greendst;	\
		dst_c.blue = *bluedst

#define put_destination()		\
		*reddst++ = dst_c.red;	\
		*greendst++ = dst_c.green;	\
		*bluedst++ = dst_c.blue

		/* generalized (but slow) blit */
		switch (pixSize) {
		case 8:
			for (i = 0; i < ysize; ++i) {
				for (j = 0; j < xsize; ++j) {
					grab_destination();
					(*trf)(colour_map + *src++, &dst_c);
					put_destination();
				}
				src += srcadd;
				reddst += dstadd;
				greendst += dstadd;
				bluedst += dstadd;
			}
			break;
		case 16:
			for (i = 0; i < ysize; ++i) {
				for (j = 0; j < xsize; ++j) {
					grab_destination();
					src_c.red = (*src & 0x7c) << 9;
					src_c.green = (*src++ & 3) << 14;
					src_c.green |= (*src & 0xe0) << 6;
					src_c.blue = (*src++ & 0x1f) << 11;
					(*trf)(&src_c, &dst_c);
					put_destination();
				}
				src += srcadd;
				reddst += dstadd;
				greendst += dstadd;
				bluedst += dstadd;
			}
			break;
		case 32:
			srcadd = srcwid / 3;
			for (i = 0; i < ysize; i++) {
				for (j = 0; j < xsize; j++) {
					grab_destination();
					src_c.red = *src << 8;
					src_c.green = src[srcadd] << 8;
					src_c.blue = src[srcadd * 2] << 8;
					(*trf)(&src_c, &dst_c);
					put_destination();
					src++;
				}
				src += srcwid - xsize;
				reddst += dstadd;
				greendst += dstadd;
				bluedst += dstadd;
			}
		}
	}
	return(1);
}



#if __STDC__
static byte*
unpackbits( struct Rect* bounds, word rowBytes, int pixelSize )
#else /*__STDC__*/
static byte*
unpackbits(bounds, rowBytes, pixelSize)
struct Rect* bounds;
word rowBytes;
int pixelSize;
#endif /*__STDC__*/
{
	byte* linebuf;
	byte* pm;
	byte* pm_ptr;
	register int i,j,k,l;
	word pixwidth;
	int linelen;
	int len;
	byte* bytepixels;
	int buflen;
	int pkpixsize;
	int rowsize;

	if (pixelSize <= 8)
		rowBytes &= 0x7fff;

	stage = "unpacking packbits";

	pixwidth = bounds->right - bounds->left;

	pkpixsize = 1;
	if (pixelSize == 16) {
		pkpixsize = 2;
		pixwidth *= 2;
	}
	else if (pixelSize == 32)
		pixwidth *= 3;
	
	if (rowBytes == 0)
		rowBytes = pixwidth;

	rowsize = pixwidth;
	if (rowBytes < 8)
		rowsize = 8 * rowBytes;	/* worst case expansion factor */

	/* we're sloppy and allocate some extra space because we can overshoot
	 * by as many as 8 bytes when we unpack the raster lines.  Really, I
	 * should be checking to see if we go over the scan line (it is
	 * possible) and complain of a corrupt file.  That fix is more complex
	 * (and probably costly in CPU cycles) and will have to come later.
	 */
	if ((pm = (byte*)malloc((rowsize * (bounds->bottom - bounds->top) + 8) * sizeof(byte))) == NULL)
		pm_error("no mem for packbits rectangle");

	/* Sometimes we get rows with length > rowBytes.  I'll allocate some
	 * extra for slop and only die if the size is _way_ out of wack.
	 */
	if ((linebuf = (byte*)malloc(rowBytes + 100)) == NULL)
		pm_error("can't allocate memory for line buffer");

	if (rowBytes < 8) {
		/* ah-ha!  The bits aren't actually packed.  This will be easy */
		for (i = 0; i < bounds->bottom - bounds->top; i++) {
			pm_ptr = pm + i * pixwidth;
			read_n(buflen = rowBytes, (char*) linebuf);
			bytepixels = expand_buf(linebuf, &buflen, pixelSize);
			for (j = 0; j < buflen; j++)
				*pm_ptr++ = *bytepixels++;
		}
	}
	else {
		for (i = 0; i < bounds->bottom - bounds->top; i++) {
			pm_ptr = pm + i * pixwidth;
			if (rowBytes > 250 || pixelSize > 8)
				linelen = read_word();
			else
				linelen = read_byte();

			if (verbose > 1)
				pm_message("linelen: %d", linelen);

			if (linelen > rowBytes) {
				pm_message("linelen > rowbytes! (%d > %d) at line %d",
					linelen, rowBytes, i);
			}

			read_n(linelen, (char*) linebuf);

			for (j = 0; j < linelen; ) {
				if (linebuf[j] & 0x80) {
					len = ((linebuf[j] ^ 255) & 255) + 2;
					buflen = pkpixsize;
					bytepixels = expand_buf(linebuf + j+1, &buflen, pixelSize);
					for (k = 0; k < len; k++) {
						for (l = 0; l < buflen; l++)
							*pm_ptr++ = *bytepixels++;
						bytepixels -= buflen;
					}
					j += 1 + pkpixsize;
				}
				else {
					len = (linebuf[j] & 255) + 1;
					buflen = len * pkpixsize;
					bytepixels = expand_buf(linebuf + j+1, &buflen, pixelSize);
					for (k = 0; k < buflen; k++)
						*pm_ptr++ = *bytepixels++;
					j += len * pkpixsize + 1;
				}
			}
		}
	}

	free(linebuf);

	return pm;
}

static byte*
expand_buf(buf, len, bits_per_pixel)
byte* buf;
int* len;
int bits_per_pixel;
{
	static byte pixbuf[256 * 8];
	register byte* src;
	register byte* dst;
	register int i;

	src = buf;
	dst = pixbuf;

	switch (bits_per_pixel) {
	case 8:
	case 16:
	case 32:
		return buf;
	case 4:
		for (i = 0; i < *len; i++) {
			*dst++ = (*src >> 4) & 15;
			*dst++ = *src++ & 15;
		}
		*len *= 2;
		break;
	case 2:
		for (i = 0; i < *len; i++) {
			*dst++ = (*src >> 6) & 3;
			*dst++ = (*src >> 4) & 3;
			*dst++ = (*src >> 2) & 3;
			*dst++ = *src++ & 3;
		}
		*len *= 4;
		break;
	case 1:
		for (i = 0; i < *len; i++) {
			*dst++ = (*src >> 7) & 1;
			*dst++ = (*src >> 6) & 1;
			*dst++ = (*src >> 5) & 1;
			*dst++ = (*src >> 4) & 1;
			*dst++ = (*src >> 3) & 1;
			*dst++ = (*src >> 2) & 1;
			*dst++ = (*src >> 1) & 1;
			*dst++ = *src++ & 1;
		}
		*len *= 8;
		break;
	default:
		pm_error("bad bits per pixel in expand_buf");
	}
	return pixbuf;
}

static void
Clip(version)
int version;
{
	word len;

	len = read_word();

	if (len == 0x000a) {	/* null rgn */
		read_rect(&clip_rect);
		/* XXX should clip this by picFrame */
		if (verbose)
			dump_rect("clipping to", &clip_rect);
	}
	else
		skip(len - 2);
}

static void
read_pixmap(p, rowBytes)
struct pixMap* p;
word* rowBytes;
{
	stage = "getting pixMap header";

	if (rowBytes != NULL)
		*rowBytes = read_word();

	read_rect(&p->Bounds);
	p->version = read_word();
	p->packType = read_word();
	p->packSize = read_long();
	p->hRes = read_long();
	p->vRes = read_long();
	p->pixelType = read_word();
	p->pixelSize = read_word();
	p->cmpCount = read_word();
	p->cmpSize = read_word();
	p->planeBytes = read_long();
	p->pmTable = read_long();
	p->pmReserved = read_long();

	if (verbose) {
		pm_message("pixelType: %d", p->pixelType);
		pm_message("pixelSize: %d", p->pixelSize);
		pm_message("cmpCount:  %d", p->cmpCount);
		pm_message("cmpSize:   %d", p->cmpSize);
	}

	if (p->pixelType != 0)
		pm_error("sorry, I only do chunky format");
	if (p->cmpCount != 1)
		pm_error("sorry, cmpCount != 1");
	if (p->pixelSize != p->cmpSize)
		pm_error("oops, pixelSize != cmpSize");
}

static struct RGBColour*
read_colour_table()
{
	longword ctSeed;
	word ctFlags;
	word ctSize;
	word val;
	int i;
	struct RGBColour* colour_table;

	stage = "getting color table info";

	ctSeed = read_long();
	ctFlags = read_word();
	ctSize = read_word();

	if (verbose) {
		pm_message("ctSeed:  %ld", ctSeed);
		pm_message("ctFlags: %d", ctFlags);
		pm_message("ctSize:  %d", ctSize);
	}

	stage = "reading colour table";

	if ((colour_table = (struct RGBColour*) malloc(sizeof(struct RGBColour) * (ctSize + 1))) == NULL)
		pm_error("no memory for colour table");

	for (i = 0; i <= ctSize; i++) {
		val = read_word();
		/* The indicies in a device colour table are bogus and usually == 0.
		 * so I assume we allocate up the list of colours in order.
		 */
		if (ctFlags & 0x8000)
			val = i;
		if (val > ctSize)
			pm_error("pixel value greater than colour table size");
		colour_table[val].red = read_word();
		colour_table[val].green = read_word();
		colour_table[val].blue = read_word();

		if (verbose > 1)
			pm_message("%d: [%d,%d,%d]", val,
				colour_table[val].red,
				colour_table[val].green,
				colour_table[val].blue);
	}

	return colour_table;
}

static void
OpColor(version)
int version;
{
	op_colour.red = read_word();
	op_colour.green = read_word();
	op_colour.blue = read_word();
}

/* these 3 do nothing but skip over their data! */
static void
BkPixPat(version)
int version;
{
	read_pattern();
}

static void
PnPixPat(version)
int version;
{
	read_pattern();
}

static void
FillPixPat(version)
int version;
{
	read_pattern();
}

/* this just skips over a version 2 pattern.  Probabaly will return
 * a pattern in the fabled complete version.
 */
static void
read_pattern()
{
	word PatType;
	word rowBytes;
	struct pixMap p;
	byte* pm;
	struct RGBColour* ct;

	stage = "Reading a pattern";

	PatType = read_word();

	switch (PatType) {
	case 2:
		skip(8); /* old pattern data */
		skip(5); /* RGB for pattern */
		break;
	case 1:
		skip(8); /* old pattern data */
		read_pixmap(&p, &rowBytes);
		ct = read_colour_table();
		pm = unpackbits(&p.Bounds, rowBytes, p.pixelSize);
		free(pm);
		free(ct);
		break;
	default:
		pm_error("unknown pattern type in read_pattern");
	}
}

static void BkPat(version)
int version;
{
	read_8x8_pattern(&bkpat);
}

static void PnPat(version)
int version;
{
	read_8x8_pattern(&pen_pat);
}

static void FillPat(version)
int version;
{
	read_8x8_pattern(&fillpat);
}

static void
read_8x8_pattern(pat)
struct Pattern* pat;
{
	byte buf[8], *exp;
	int len, i;

	read_n(len = 8, (char*)buf);
	if (verbose) {
		pm_message("pattern: %02x%02x%02x%02x",
			buf[0], buf[1], buf[2], buf[3]);
		pm_message("pattern: %02x%02x%02x%02x",
			buf[4], buf[5], buf[6], buf[7]);
	}
	exp = expand_buf(buf, &len, 1);
	for (i = 0; i < 64; i++)
		pat->pix[i] = *exp++;
}

static void PnSize(version)
int version;
{
	pen_height = read_word();
	pen_width = read_word();
	if (verbose)
		pm_message("pen size %d x %d", pen_width, pen_height);
}

static void PnMode(version)
int version;
{
	pen_mode = read_word();

	if (pen_mode >= 8 && pen_mode < 15)
		pen_mode -= 8;
	if (verbose)
		pm_message("pen transfer mode = %s",
			const_name(transfer_name, pen_mode));
	
	pen_trf = transfer(pen_mode);
}

static void read_rgb(rgb)
struct RGBColour* rgb;
{
	rgb->red = read_word();
	rgb->green = read_word();
	rgb->blue = read_word();
}

static void RGBFgCol(v)
int v;
{
	read_rgb(&foreground);
	if (verbose)
		pm_message("foreground now [%d,%d,%d]", 
			foreground.red, foreground.green, foreground.blue);
}

static void RGBBkCol(v)
int v;
{
	read_rgb(&background);
	if (verbose)
		pm_message("background now [%d,%d,%d]", 
			background.red, background.green, background.blue);
}

static void read_point(p)
struct Point* p;
{
	p->y = read_word();
	p->x = read_word();
}

static void read_short_point(p)
struct Point* p;
{
	p->x = read_signed_byte();
	p->y = read_signed_byte();
}

#define PIXEL_INDEX(x,y) ((y) - picFrame.top) * rowlen + (x) - picFrame.left

static void draw_pixel(x, y, clr, trf)
int x, y;
struct RGBColour* clr;
transfer_func trf;
{
	register int i;
	struct RGBColour dst;

	if (x < clip_rect.left || x >= clip_rect.right ||
		y < clip_rect.top || y >= clip_rect.bottom)
	{
		return;
	}

	i = PIXEL_INDEX(x, y);
	dst.red = red[i];
	dst.green = green[i];
	dst.blue = blue[i];
	(*trf)(clr, &dst);
	red[i] = dst.red;
	green[i] = dst.green;
	blue[i] = dst.blue;
}

static void draw_pen_rect(r)
struct Rect* r;
{
	register int i = PIXEL_INDEX(r->left, r->top);
	register int x, y;
	struct RGBColour dst;
	int rowadd = rowlen - (r->right - r->left);

	for (y = r->top; y < r->bottom; y++) {
		for (x = r->left; x < r->right; x++) {
			dst.red = red[i];
			dst.green = green[i];
			dst.blue = blue[i];
			if (pen_pat.pix[(x & 7) + (y & 7) * 8])
				(*pen_trf)(&black, &dst);
			else
				(*pen_trf)(&white, &dst);
			red[i] = dst.red;
			green[i] = dst.green;
			blue[i] = dst.blue;

			i++;
		}
		i += rowadd;
	}
}

static void draw_pen(x, y)
int x, y;
{
	struct Rect penrect;

	penrect.left = x;
	penrect.right = x + pen_width;
	penrect.top = y;
	penrect.bottom = y + pen_height;

	rectinter(&penrect, &clip_rect, &penrect);

	draw_pen_rect(&penrect);
}

/*
 * Digital Line Drawing
 * by Paul Heckbert
 * from "Graphics Gems", Academic Press, 1990
 */

/*
 * digline: draw digital line from (x1,y1) to (x2,y2),
 * calling a user-supplied procedure at each pixel.
 * Does no clipping.  Uses Bresenham's algorithm.
 *
 * Paul Heckbert	3 Sep 85
 */
static void scan_line(short x1, short y1, short x2, short y2) {
    int d, x, y, ax, ay, sx, sy, dx, dy;

	if (pen_width == 0 && pen_height == 0)
		return;

    dx = x2-x1;  ax = ABS(dx)<<1;  sx = SGN(dx);
    dy = y2-y1;  ay = ABS(dy)<<1;  sy = SGN(dy);

    x = x1;
    y = y1;
    if (ax>ay) {		/* x dominant */
	d = ay-(ax>>1);
	for (;;) {
	    draw_pen(x, y);
	    if (x==x2) return;
	    if ((x > rowlen) && (sx > 0)) return;
	    if (d>=0) {
		y += sy;
		d -= ax;
	    }
	    x += sx;
	    d += ay;
	}
    }
    else {			/* y dominant */
	d = ax-(ay>>1);
	for (;;) {
	    draw_pen(x, y);
	    if (y==y2) return;
	    if ((y > collen) && (sy > 0)) return;
	    if (d>=0) {
		x += sx;
		d -= ay;
	    }
	    y += sy;
	    d += ax;
	}
    }
}

static void Line(v)
     int v;
{
  struct Point p1;
  read_point(&p1);
  read_point(&current);
  if (verbose)
    pm_message("(%d,%d) to (%d, %d)",
	       p1.x,p1.y,current.x,current.y);
  scan_line(p1.x,p1.y,current.x,current.y);
}

static void LineFrom(v)
     int v;
{
  struct Point p1;
  read_point(&p1);
  if (verbose)
    pm_message("(%d,%d) to (%d, %d)",
	       current.x,current.y,p1.x,p1.y);

  if (!fullres)
	  scan_line(current.x,current.y,p1.x,p1.y);

  current.x = p1.x;
  current.y = p1.y;
}

static void ShortLine(v)
     int v;
{
  struct Point p1;
  read_point(&p1);
  read_short_point(&current);
  if (verbose)
    pm_message("(%d,%d) delta (%d, %d)",
	       p1.x,p1.y,current.x,current.y);
  current.x += p1.x;
  current.y += p1.y;

  if (!fullres)
	  scan_line(p1.x,p1.y,current.x,current.y);
}

static void ShortLineFrom(v)
     int v;
{
  struct Point p1;
  read_short_point(&p1);
  if (verbose)
    pm_message("(%d,%d) delta (%d, %d)",
               current.x,current.y,p1.x,p1.y);
  p1.x += current.x;
  p1.y += current.y;
  if (!fullres)
	  scan_line(current.x,current.y,p1.x,p1.y);
  current.x = p1.x;
  current.y = p1.y;
}

static void paintRect(v)
int v;
{
	read_rect(&cur_rect);
	do_paintRect(&cur_rect);
}

static void paintSameRect(v)
int v;
{
	do_paintRect(&cur_rect);
}

static void do_paintRect(prect)
struct Rect* prect;
{
	struct Rect rect;
  
	if (fullres)
		return;

	if (verbose)
		dump_rect("painting", prect);

	rectinter(&clip_rect, prect, &rect);

	draw_pen_rect(&rect);
}

static void frameRect(v)
int v;
{
	read_rect(&cur_rect);
	do_frameRect(&cur_rect);
}

static void frameSameRect(v)
int v;
{
	do_frameRect(&cur_rect);
}

static void do_frameRect(rect)
struct Rect* rect;
{
	register int x, y;

	if (fullres)
		return;
  
	if (verbose)
		dump_rect("framing", rect);

	if (pen_width == 0 || pen_height == 0)
		return;

	for (x = rect->left; x <= rect->right - pen_width; x += pen_width) {
		draw_pen(x, rect->top);
		draw_pen(x, rect->bottom - pen_height);
	}

	for (y = rect->top; y <= rect->bottom - pen_height ; y += pen_height) {
		draw_pen(rect->left, y);
		draw_pen(rect->right - pen_width, y);
	}
}

/* a stupid shell sort - I'm so embarassed  */

static void poly_sort(sort_index, points)
     int sort_index;
     struct Point points[];
{
  int d, i, j, temp;

  /* initialize and set up sort interval */
  d = 4;
  while (d<=sort_index) d <<= 1;
  d -= 1;

  while (d > 1) {
    d >>= 1;
    for (j = 0; j <= (sort_index-d); j++) {
      for(i = j; i >= 0; i -= d) {
	if ((points[i+d].y < points[i].y) ||
	    ((points[i+d].y == points[i].y) &&
	     (points[i+d].x <= points[i].x))) {
	  /* swap x1,y1 with x2,y2 */
	  temp = points[i].y;
	  points[i].y = points[i+d].y;
	  points[i+d].y = temp;
	  temp = points[i].x;
	  points[i].x = points[i+d].x;
	  points[i+d].x = temp;
	}
      }
    }
  }
}

/* Watch out for the lack of error checking in the next two functions ... */

static void scan_poly(np, pts)
     int np;
     struct Point pts[];
{
  int dx,dy,dxabs,dyabs,i,scan_index,j,k,px,py;
  int sdx,sdy,x,y,toggle,old_sdy,sy0;

  /* This array needs to be at least as large as the largest dimension of
     the bounding box of the poly (but I don't check for overflows ...) */
  struct Point coord[5000];

  scan_index = 0;

  /* close polygon */
  px = pts[np].x = pts[0].x;
  py = pts[np].y = pts[0].y;

  /*  This section draws the polygon and stores all the line points
   *  in an array. This doesn't work for concave or non-simple polys.
   */
  /* are y levels same for first and second points? */
  if (pts[1].y == pts[0].y) {
    coord[scan_index].x = px;
    coord[scan_index].y = py;
    scan_index++;
  }

#define sign(x) ((x) > 0 ? 1 : ((x)==0 ? 0:(-1)) )   

  old_sdy = sy0 = sign(pts[1].y - pts[0].y);
  for (j=0; j<np; j++) {
    /* x,y difference between consecutive points and their signs  */
    dx = pts[j+1].x - pts[j].x;
    dy = pts[j+1].y - pts[j].y;
    sdx = SGN(dx);
    sdy = SGN(dy);
    dxabs = abs(dx);
    dyabs = abs(dy);
    x = y = 0;

    if (dxabs >= dyabs)
      {
	for (k=0; k < dxabs; k++) {
	  y += dyabs;
	  if (y >= dxabs) {
	    y -= dxabs;
	    py += sdy;
	    if (old_sdy != sdy) {
	      old_sdy = sdy;
	      scan_index--;
	    }
	    coord[scan_index].x = px+sdx;
	    coord[scan_index].y = py;
	    scan_index++;
	  }
	  px += sdx;
	  draw_pen(px, py);
	}
      }
    else
      {
	for (k=0; k < dyabs; k++) {
	  x += dxabs;
	  if (x >= dyabs) {
	    x -= dyabs;
	    px += sdx;
	  }
	  py += sdy;
	  if (old_sdy != sdy) {
	    old_sdy = sdy;
	    if (sdy != 0) scan_index--;
	  }
	  draw_pen(px,py);
	  coord[scan_index].x = px;
	  coord[scan_index].y = py;
	  scan_index++;
	}
      }
  }

  /* after polygon has been drawn now fill it */

  scan_index--;
  if (sy0 + sdy == 0) scan_index--;

  poly_sort(scan_index, coord);
  
  toggle = 0;
  for (i = 0; i < scan_index; i++) {
    if ((coord[i].y == coord[i+1].y) && (toggle == 0))
      {
	for (j = coord[i].x; j <= coord[i+1].x; j++)
	  draw_pen(j, coord[i].y);
	toggle = 1;
      }
    else
      toggle = 0;
  }
}
  
  
static void paintPoly(v)
     int v;
{
  struct Rect bb;
  struct Point pts[100];
  int i, np = (read_word() - 10) >> 2;

  read_rect(&bb);
  for (i=0; i<np; ++i)
    read_point(&pts[i]);

  /* scan convert poly ... */
  if (!fullres)
	  scan_poly(np, pts);
}

static void PnLocHFrac(version)
int version;
{
	word frac = read_word();

	if (verbose)
		pm_message("PnLocHFrac = %d", frac);
}

static void TxMode(version)
int version;
{
	text_mode = read_word();

	if (text_mode >= 8 && text_mode < 15)
		text_mode -= 8;
	if (verbose)
		pm_message("text transfer mode = %s",
			const_name(transfer_name, text_mode));
	
	/* ignore the text mask bit 'cause we don't handle it yet */
	text_trf = transfer(text_mode & ~64);
}

static void TxFont(version)
int version;
{
	text_font = read_word();
	if (verbose)
		pm_message("text font %s", const_name(font_name, text_font));
}

static void TxFace(version)
int version;
{
	text_face = read_byte();
	if (verbose)
		pm_message("text face %d", text_face);
}

static void TxSize(version)
int version;
{
	text_size = read_word();
	if (verbose)
		pm_message("text size %d", text_size);
}

static void
skip_text()
{
	skip(read_byte());
}

static void
LongText(version)
int version;
{
	struct Point p;

	read_point(&p);
	do_text(p.x, p.y);
}

static void
DHText(version)
int version;
{
	current.x += read_byte();
	do_text(current.x, current.y);
}

static void
DVText(version)
int version;
{
	current.y += read_byte();
	do_text(current.x, current.y);
}

static void
DHDVText(version)
int version;
{
	byte dh, dv;

	dh = read_byte();
	dv = read_byte();

	if (verbose)
		pm_message("dh, dv = %d, %d", dh, dv);

	current.x += dh;
	current.y += dv;
	do_text(current.x, current.y);
}

static void
#ifdef __STDC__
do_text(word x, word y)
#else
do_text(x, y)
word x;
word y;
#endif
{
	int len, dy, w, h;
	struct glyph* glyph;

	if (fullres) {
		skip_text();
		return;
	}

	if (!(tfont = get_font(text_font, text_size, text_face)))
		tfont = pbm_defaultfont("bdf");

	if (ps_text) {
		do_ps_text(x, y);
		return;
	}

	for (len = read_byte(); len > 0; len--) {
		if (!(glyph = tfont->glyph[read_byte()]))
			continue;
		
		dy = y - glyph->height - glyph->y;
		for (h = 0; h < glyph->height; h++) {
			for (w = 0; w < glyph->width; w++) {
				if (glyph->bmap[h * glyph->width + w])
					draw_pixel(x + w + glyph->x, dy, &black, text_trf);
				else
					draw_pixel(x + w + glyph->x, dy, &white, text_trf);
			}
			dy++;
		}
		x += glyph->xadd;
	}


	current.x = x;
	current.y = y;
}

static void
#ifdef __STDC__
do_ps_text(word tx, word ty)
#else
do_ps_text(tx, ty)
word tx;
word ty;
#endif
{
	int len, width, i, w, h, x, y, rx, ry, o;
	byte str[256], ch;
	struct glyph* glyph;

	current.x = tx;
	current.y = ty;

	if (!ps_cent_set) {
		ps_cent_x += tx;
		ps_cent_y += ty;
		ps_cent_set = 1;
	}

	len = read_byte();

	/* XXX this width calculation is not completely correct */
	width = 0;
	for (i = 0; i < len; i++) {
		ch = str[i] = read_byte();
		if (tfont->glyph[ch])
			width += tfont->glyph[ch]->xadd;
	}

	if (verbose) {
		str[len] = '\0';
		pm_message("ps text: %s", str);
	}

	/* XXX The width is calculated in order to do different justifications.
	 * However, I need the width of original text to finish the job.
	 * In other words, font metrics for Quickdraw fonts
	 */

	x = tx;

	for (i = 0; i < len; i++) {
		if (!(glyph = tfont->glyph[str[i]]))
			continue;
		
		y = ty - glyph->height - glyph->y;
		for (h = 0; h < glyph->height; h++) {
			for (w = 0; w < glyph->width; w++) {
				rx = x + glyph->x + w;
				ry = y;
				rotate(&rx, &ry);
				if ((rx >= picFrame.left) && (rx < picFrame.right) &&
					(ry >= picFrame.top) && (ry < picFrame.bottom))
				{
					o = PIXEL_INDEX(rx, ry);
					if (glyph->bmap[h * glyph->width + w]) {
						red[o] = foreground.red;
						green[o] = foreground.green;
						blue[o] = foreground.blue;
					}
				}
			}
			y++;
		}
		x += glyph->xadd;
	}
}

/* This only does 0, 90, 180 and 270 degree rotations */

static void rotate(x, y)
int *x;
int *y;
{
	int tmp;

	if (ps_rotation >= 315 || ps_rotation <= 45)
		return;

	*x -= ps_cent_x;
	*y -= ps_cent_y;

	if (ps_rotation > 45 && ps_rotation < 135) {
		tmp = *x;
		*x = *y;
		*y = tmp;
	}
	else if (ps_rotation >= 135 && ps_rotation < 225) {
		*x = -*x;
	}
	else if (ps_rotation >= 225 && ps_rotation < 315) {
		tmp = *x;
		*x = *y;
		*y = -tmp;
	}
	*x += ps_cent_x;
	*y += ps_cent_y;
}

static void
skip_poly_or_region(version)
int version;
{
	stage = "skipping polygon or region";
	skip(read_word() - 2);
}

static
void picComment(word type, int length) {
	switch (type) {
	case 150:
		if (verbose) pm_message("TextBegin");
		if (length < 6)
			break;
		ps_just = read_byte();
		ps_flip = read_byte();
		ps_rotation = read_word();
		ps_linespace = read_byte();
		length -= 5;
		if (recognize_comment)
			ps_text = 1;
		ps_cent_set = 0;
		if (verbose) {
			pm_message("%s justification, %s flip, %d degree rotation, %d/2 linespacing",
				const_name(ps_just_name, ps_just),
				const_name(ps_flip_name, ps_flip),
				ps_rotation, ps_linespace);
		}
		break;
	case 151:
		if (verbose) pm_message("TextEnd");
		ps_text = 0;
		break;
	case 152:
		if (verbose) pm_message("StringBegin");
		break;
	case 153:
		if (verbose) pm_message("StringEnd");
		break;
	case 154:
		if (verbose) pm_message("TextCenter");
		if (length < 8)
			break;
		ps_cent_y = read_word();
		if (ps_cent_y > 32767)
			ps_cent_y -= 65536;
		skip(2); /* ignore fractional part */
		ps_cent_x = read_word();
		if (ps_cent_x > 32767)
			ps_cent_x -= 65536;
		skip(2); /* ignore fractional part */
		length -= 8;
		if (verbose)
			pm_message("offset %d %d", ps_cent_x, ps_cent_y);
		break;
	case 155:
		if (verbose) pm_message("LineLayoutOff");
		break;
	case 156:
		if (verbose) pm_message("LineLayoutOn");
		break;
	case 160:
		if (verbose) pm_message("PolyBegin");
		break;
	case 161:
		if (verbose) pm_message("PolyEnd");
		break;
	case 163:
		if (verbose) pm_message("PolyIgnore");
		break;
	case 164:
		if (verbose) pm_message("PolySmooth");
		break;
	case 165:
		if (verbose) pm_message("picPlyClo");
		break;
	case 180:
		if (verbose) pm_message("DashedLine");
		break;
	case 181:
		if (verbose) pm_message("DashedStop");
		break;
	case 182:
		if (verbose) pm_message("SetLineWidth");
		break;
	case 190:
		if (verbose) pm_message("PostScriptBegin");
		break;
	case 191:
		if (verbose) pm_message("PostScriptEnd");
		break;
	case 192:
		if (verbose) pm_message("PostScriptHandle");
		break;
	case 193:
		if (verbose) pm_message("PostScriptFile");
		break;
	case 194:
		if (verbose) pm_message("TextIsPostScript");
		break;
	case 195:
		if (verbose) pm_message("ResourcePS");
		break;
	case 200:
		if (verbose) pm_message("RotateBegin");
		break;
	case 201:
		if (verbose) pm_message("RotateEnd");
		break;
	case 202:
		if (verbose) pm_message("RotateCenter");
		break;
	case 210:
		if (verbose) pm_message("FormsPrinting");
		break;
	case 211:
		if (verbose) pm_message("EndFormsPrinting");
		break;
	default:
		if (verbose) pm_message("%d", type);
		break;
	}
	if (length > 0)
		skip(length);
}

static void
ShortComment(version)
int version;
{
	picComment(read_word(), 0);
}

static void
LongComment(version)
int version;
{
	word type;

	type = read_word();
	picComment(type, read_word());
}

static int
rectwidth(r)
struct Rect* r;
{
	return r->right - r->left;
}

static int
rectheight(r)
struct Rect* r;
{
	return r->bottom - r->top;
}

static int
rectsamesize(r1, r2)
struct Rect* r1;
struct Rect* r2;
{
	return r1->right - r1->left == r2->right - r2->left &&
		   r1->bottom - r1->top == r2->bottom - r2->top ;
}

static void
rectinter(r1, r2, r3)
struct Rect* r1;
struct Rect* r2;
struct Rect* r3;
{
	r3->left = MAX(r1->left, r2->left);
	r3->top = MAX(r1->top, r2->top);
	r3->right = MIN(r1->right, r2->right);
	r3->bottom = MIN(r1->bottom, r2->bottom);
}

static void
rectscale(r, xscale, yscale)
struct Rect* r;
double	     xscale;
double	     yscale;
{
	r->left *= xscale;
	r->right *= xscale;
	r->top *= yscale;
	r->bottom *= yscale;
}

static void
read_rect(r)
struct Rect* r;
{
	r->top = read_word();
	r->left = read_word();
	r->bottom = read_word();
	r->right = read_word();
}

static void
dump_rect(s, r)
char* s;
struct Rect* r;
{
	pm_message("%s (%d,%d) (%d,%d)",
		s, r->left, r->top, r->right, r->bottom);
}

static char*
const_name(table, ct)
struct const_name* table;
int ct;
{
	static char numbuf[32];
	int i;

	for (i = 0; table[i].name; i++)
		if (table[i].value == ct)
			return table[i].name;
	
	sprintf(numbuf, "%d", ct);
	return numbuf;
}

/*
 * All data in version 2 is 2-byte word aligned.  Odd size data
 * is padded with a null.
 */
static word
get_op(version)
int version;
{
	if ((align & 1) && version == 2) {
		stage = "aligning for opcode";
		read_byte();
	}

	stage = "reading opcode";

	if (version == 1)
		return read_byte();
	else
		return read_word();
}

static longword
read_long()
{
	word i;

	i = read_word();
	return (i << 16) | read_word();
}

static word
read_word()
{
	byte b;

	b = read_byte();

	return (b << 8) | read_byte();
}

static byte
read_byte()
{
	int c;

	if ((c = fgetc(ifp)) == EOF)
		pm_error("EOF / read error while %s", stage);

	++align;
	return c & 255;
}

static signed_byte
read_signed_byte()
{
	return (signed_byte)read_byte();
}

static void
skip(n)
int n;
{
	static byte buf[1024];

	align += n;

	for (; n > 0; n -= 1024)
		if (fread(buf, n > 1024 ? 1024 : n, 1, ifp) != 1)
			pm_error("EOF / read error while %s", stage);
}

static void
read_n(n, buf)
int n;
char* buf;
{
	align += n;

	if (fread(buf, n, 1, ifp) != 1)
		pm_error("EOF / read error while %s", stage);
}

#ifdef STANDALONE

/* glue routines if you don't have PBM+ handy; these are only good enough
 * for picttoppm's purposes!
 */

static char* outfile;

void pm_message(fmt, p1, p2, p3, p4, p5)
char* fmt;
int p1, p2, p3, p4, p5;
{
	fprintf(stderr, "picttoppm: ");
	fprintf(stderr, fmt, p1, p2, p3, p4, p5);
	fprintf(stderr, "\n");
}

void pm_error(fmt, p1, p2, p3, p4, p5)
char* fmt;
int p1, p2, p3, p4, p5;
{
	pm_message(fmt, p1, p2, p3, p4, p5);
	exit(1);
}

int pm_keymatch(arg, opt, minlen)
char* arg, *opt;
int minlen;
{
	for (; *arg && *arg == *opt; arg++, opt++)
		minlen--;
	return !*arg && minlen <= 0;
}

void ppm_init(argc, argv)
int* argc;
char** argv;
{
	outfile = "standard output";
}

FILE* pm_openr(file)
char* file;
{
	FILE* fp;

	if (!(fp = fopen(file, "rb"))) {
		fprintf(stderr, "picttoppm: can't read file %s\n", file);
		exit(1);
	}
	outfile = file;
	return fp;
}

void writerr()
{
	fprintf(stderr, "picttoppm: write error on %s\n", outfile);
	exit(1);
}

void pm_usage(u)
char* u;
{
	fprintf(stderr, "usage: picttoppm %s\n", u);
	exit(1);
}

void ppm_writeppminit(fp, width, height, maxval, forceplain)
FILE* fp;
int width, height, maxval, forceplain;
{
	if (fprintf(fp, "P6\n%d %d\n%d\n", width, height, maxval) == EOF)
		writerr();
}

pixel* ppm_allocrow(width)
int width;
{
	pixel* r;
	
	if (!(r = (pixel*)malloc(width * sizeof(pixel)))) {
		fprintf(stderr, "picttoppm: out of memory\n");
		exit(1);
	}
	return r;
}

void ppm_writeppmrow(fp, row, width, maxval, forceplain)
FILE* fp;
pixel* row;
int width, maxval, forceplain;
{
	while (width--) {
		if (fputc(row->r, fp) == EOF) writerr();
		if (fputc(row->g, fp) == EOF) writerr();
		if (fputc(row->b, fp) == EOF) writerr();
		row++;
	}
}

void pm_close(fp)
FILE* fp;
{
	if (fclose(fp) == EOF)
		writerr();
}

#endif /* STANDALONE */

/* Some font searching routines */

struct fontinfo {
	int font;
	int size;
	int style;
	char* filename;
	struct font* loaded;
	struct fontinfo* next;
};

static struct fontinfo* fontlist = 0;
static struct fontinfo** fontlist_ins = &fontlist;

int load_fontdir(dirfile)
char* dirfile;
{
	FILE* fp;
	int n, nfont;
	char* arg[5], line[1024];
	struct fontinfo* fontinfo;

	if (!(fp = fopen(dirfile, "rb")))
		return -1;
	
	nfont = 0;
	while (fgets(line, 1024, fp)) {
		if ((n = mk_argvn(line, arg, 5)) == 0 || arg[0][0] == '#')
			continue;
		if (n != 4)
			continue;
		if (!(fontinfo = (struct fontinfo*)malloc(sizeof(struct fontinfo))) ||
			!(fontinfo->filename = (char*)malloc(strlen(arg[3]) + 1)))
		{
			pm_error("out of memory for font information");
		}

		fontinfo->font = atoi(arg[0]);
		fontinfo->size = atoi(arg[1]);
		fontinfo->style = atoi(arg[2]);
		strcpy(fontinfo->filename, arg[3]);
		fontinfo->loaded = 0;

		fontinfo->next = 0;
		*fontlist_ins = fontinfo;
		fontlist_ins = &fontinfo->next;
		nfont++;
	}

	return nfont;
}

static int abs_value(x)
int x;
{
	if (x < 0)
		return -x;
	else
		return x;
}

static struct font* get_font(font, size, style)
int font;
int size;
int style;
{
	int closeness, bestcloseness;
	struct fontinfo* fi, *best;

	best = 0;
	for (fi = fontlist; fi; fi = fi->next) {
		closeness = abs_value(fi->font - font) * 10000 +
			abs_value(fi->size - size) * 100 +
			abs_value(fi->style - style);
		if (!best || closeness < bestcloseness) {
			best = fi;
			bestcloseness = closeness;
		}
	}

	if (best) {
		if (best->loaded)
			return best->loaded;

		if ((best->loaded = pbm_loadbdffont(best->filename)))
			return best->loaded;
	}

	/* It would be better to go looking for the nth best font, really */
	return 0;
}

#ifdef VMS
unlink(p)
     char *p;
{delete(p);}
#endif
