/*
 * 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 $
 */

#ifdef STANDALONE

#include <stdio.h>
#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", 0,0,0,0);

	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, 0, 0, 0);

	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, 0, 0, 0);
	}

	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, 0, 0, 0, 0);

	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, 0, 0, 0, 0);
				else
					pm_message("%s", stage = optable[opcode].name, 0, 0, 0, 0);
			}

			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", 0, 0, 0, 0, 0);
			stage = "HeaderOp";
			skip(24);
		}
		else if (opcode >= 0xa2 && opcode <= 0xaf) {
			stage = "skipping reserved";
			if (verbose)
				pm_message("%s 0x%x", stage, opcode, 0, 0, 0);
			skip(read_word());
		}
		else if (opcode >= 0xb0 && opcode <= 0xcf) {
			/* just a reserved opcode, no data */
			if (verbose)
				pm_message("reserved 0x%x", opcode, 0, 0, 0);
		}
		else if (opcode >= 0xd0 && opcode <= 0xfe) {
			stage = "skipping reserved";
			if (verbose)
				pm_message("%s 0x%x", stage, opcode, 0, 0, 0);
			skip(read_long());
		}
		else if (opcode >= 0x100 && opcode <= 0x7fff) {
			stage = "skipping reserved";
			if (verbose)
				pm_message("%s 0x%x", stage, opcode, 0, 0, 0);
			skip((opcode >> 7) & 255);
		}
		else if (opcode >= 0x8000 && opcode <= 0x80ff) {
			/* just a reserved opcode */
			if (verbose)
				pm_message("reserved 0x%x", opcode, 0, 0, 0, 0);
		}
		else if (opcode >= 8100 && opcode <= 0xffff) {
			stage = "skipping reserved";
			if (verbose)
				pm_message("%s 0x%x", stage, opcode, 0, 0, 0);
			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, 0, 0, 0);
		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", "w");
	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),0,0,0,0);

	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, 0, 0, 0);

	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),0,0,0,0);

	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, 0, 0, 0);

	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),0,0,0,0);

	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), 0, 0, 0, 0);
		return srcCopy;
	}
}

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;
{
	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;

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

	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;
#else
		FILE*	pnmscale;
		char*	tmpfile = tmpnam((char*)0);
		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, "w")))
                        pm_error("cannot create temporary file '%s'", ami_tmpfile);
#else /* AMIGA or VMS */
		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), 0);

		if (!(pnmscale = _popen(command, "w"))) {
			pm_message("cannot execute command '%s'", command, 0, 0, 0, 0);
			pm_perror("popen");
		}
#endif /* AMIGA or VMS */

/* This should really be PPM_MAXMAXVAL, but that can be big, and then
 * I'd have to conditionally output raw/not-raw PPM, which is a pain.
 */
#define MY_MAXVAL (255)

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

#define REDEPTH(c, oldmax)  ((c) * ((MY_MAXVAL) + 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_perror("write error");
                }
                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);
#ifndef VMS
                if( ami_result != 0 ) {
#else
                if( ami_result == 0 ) {
#endif
                    unlink(tmpfile);
                    pm_perror("pnmscale failed");
                }
#else /* AMIGA or VMS */
		if (_pclose(pnmscale)) {
			pm_message("pnmscale failed", 0, 0, 0, 0, 0);
			pm_perror("pclose");
		}
#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;
#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, 0, 0, 0, 0);

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

			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, 0, 0, 0, 0);
		pm_message("pixelSize: %d", p->pixelSize, 0, 0, 0, 0);
		pm_message("cmpCount:  %d", p->cmpCount, 0, 0, 0, 0);
		pm_message("cmpSize:   %d", p->cmpSize, 0, 0, 0, 0);
	}

	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:  %d", ctSeed, 0, 0, 0, 0);
		pm_message("ctFlags: %d", ctFlags, 0, 0, 0, 0);
		pm_message("ctSize:  %d", ctSize, 0, 0, 0, 0);
	}

	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, 0);
	}

	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], 0);
		pm_message("pattern: %02x%02x%02x%02x",
			buf[4], buf[5], buf[6], buf[7], 0);
	}
	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, 0, 0, 0);
}

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), 0, 0, 0, 0);
	
	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, 0, 0);
}

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

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

/* absolute value of a */
#define ABS(a)		(((a)<0) ? -(a) : (a))
/* take binary sign of a, either -1, or 1 if >= 0 */
#define SGN(a)		(((a)<0) ? -1 : 1)

/*
 * 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(x1,y1,x2,y2)
     short x1,y1,x2,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, 0);
  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, 0);

  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, 0);
  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, 0);
  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,k,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, 0, 0, 0, 0);
}

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

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

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

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, 0, 0, 0);

	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(type, length)
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, 0);
		}
		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
rectequal(r1, r2)
struct Rect* r1;
struct Rect* r2;
{
	return r1->top == r2->top &&
		   r1->bottom == r2->bottom &&
		   r1->left == r2->left &&
		   r1->right == r2->right;
}

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, "r")))
		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
