/*
 *  Load a spoiler list of cards, display and draw them to files.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "app.h"

Image *full = NULL;
Bitmap *bmap = NULL;

App *app;
Window *w;
Control *c;
Font *name_font;
Font *mana_font;
Font *type_font;
Font *artist_font;

FILE *log = NULL;

char *logfile = "GenCard.log";
char *artwork = "Artwork/";
char *symbols = "Symbols/";
char *dpi300  = "DPI300/";
char *dpi150  = "DPI150/";
char *dpi075  = "DPI075/";

enum {
	NORMAL_FONT = 0,
	ITALIC_FONT = 1,
	MANA_FONT   = 2
};

Font *normal_fonts[50] = {0};
Font *italic_fonts[50] = {0};
Font *mana_fonts[50]   = {0};

enum {
	BORDER_BLACK = 0,
	BORDER_WHITE = 1
};

#define NELEM(arr) (sizeof((arr))/sizeof((arr)[0]))

typedef struct UTF8_to_ASCII  UTF8_to_ASCII;

struct UTF8_to_ASCII
{
	char *	utf8;  /* substring encoded in UTF-8 */
	char *	ascii; /* they all become ASCII single-bytes */
};

UTF8_to_ASCII CP1252_to_ASCII[32] =
{
	{"\xc2\x80",	"E"},		/* Euro currency symbol */
	{"\xc2\x81",	""},		/* N/A */
	{"\xc2\x82",	","},		/* fancy comma */
	{"\xc2\x83",	"f"},		/* fancy f */
	{"\xc2\x84",	",,"},		/* fancy double-comma */
	{"\xc2\x85",	"..."},		/* ellipsis */
	{"\xc2\x86",	"+"},		/* dagger */
	{"\xc2\x87",	"*"},		/* double-dagger */
	{"\xc2\x88",	"^"},		/* circumflex */
	{"\xc2\x89",	"%"},		/* permille */
	{"\xc2\x8a",	"S"},		/* capital S with CARON */
	{"\xc2\x8b",	"<"},		/* single open dialog */
	{"\xc2\x8c",	"OE"},		/* capital OE ligature */
	{"\xc2\x8d",	""},		/* N/A */
	{"\xc2\x8e",	"Z"},		/* capital Z with CARON */
	{"\xc2\x8f",	""},		/* N/A */
	{"\xc2\x90",	""},		/* N/A */
	{"\xc2\x91",	"`"},		/* open single-quote */
	{"\xc2\x92",	"'"},		/* close single-quote */
	{"\xc2\x93",	"``"},		/* open double-quotes */
	{"\xc2\x94",	"''"},		/* close double-quotes */
	{"\xc2\x95",	"."},		/* bullet */
	{"\xc2\x96",	"-"},		/* endash */
	{"\xc2\x97",	"-"},		/* emdash  */
	{"\xc2\x98",	"~"},		/* tilde */
	{"\xc2\x99",	"TM"},		/* trademark TM symbol */
	{"\xc2\x9a",	"s"},		/* small s with CARON */
	{"\xc2\x9b",	">"},		/* single close dialog */
	{"\xc2\x9c",	"oe"},		/* small oe ligature */
	{"\xc2\x9d",	""},		/* N/A */
	{"\xc2\x9e",	"z"},		/* small z with CARON */
	{"\xc2\x9f",	"Y"}		/* Y diaresis */
};

UTF8_to_ASCII Latin1_to_ASCII[96] =
{
	{"\xc2\xa0",	" "},		/*  */
	{"\xc2\xa1",	"i"},		/*  */
	{"\xc2\xa2",	"c"},		/* Cent */
	{"\xc2\xa3",	"L"},		/* Pound */
	{"\xc2\xa4",	"o"},		/*  */
	{"\xc2\xa5",	"Y"},		/* Yen */
	{"\xc2\xa6",	"|"},		/* vertical bar */
	{"\xc2\xa7",	"S"},		/* section */
	{"\xc2\xa8",	"\""},		/* diaresis */
	{"\xc2\xa9",	"c"},		/* copyright */
	{"\xc2\xaa",	"a"},		/* a underbar */
	{"\xc2\xab",	"<<"},		/* double open dialog */
	{"\xc2\xac",	"-"},		/*  */
	{"\xc2\xad",	"-"},		/*  */
	{"\xc2\xae",	"R"},		/* registered */
	{"\xc2\xaf",	"-"},		/*  */
	{"\xc2\xb0",	"o"},		/*  */
	{"\xc2\xb1",	"+-"},		/*  */
	{"\xc2\xb2",	"2"},		/* squared */
	{"\xc2\xb3",	"3"},		/* cubed */
	{"\xc2\xb4",	"'"},		/*  */
	{"\xc2\xb5",	"u"},		/* micro */
	{"\xc2\xb6",	"P"},		/* pilcrow */
	{"\xc2\xb7",	"."},		/*  */
	{"\xc2\xb8",	","},		/*  */
	{"\xc2\xb9",	"1"},		/*  */
	{"\xc2\xba",	"0"},		/*  */
	{"\xc2\xbb",	">>"},		/* double close dialog */
	{"\xc2\xbc",	"1/4"},		/* fraction quarter */
	{"\xc2\xbd",	"1/2"},		/* fraction half */
	{"\xc2\xbe",	"3/4"},		/* fraction three quarters */
	{"\xc2\xbf",	"?"},		/*  */
	{"\xc3\x80",	"A"},		/* accented characters */
	{"\xc3\x81",	"A"},		/*  */
	{"\xc3\x82",	"A"},		/*  */
	{"\xc3\x83",	"A"},		/*  */
	{"\xc3\x84",	"A"},		/*  */
	{"\xc3\x85",	"A"},		/*  */
	{"\xc3\x86",	"AE"},		/*  */
	{"\xc3\x87",	"C"},		/*  */
	{"\xc3\x88",	"E"},		/*  */
	{"\xc3\x89",	"E"},		/*  */
	{"\xc3\x8a",	"E"},		/*  */
	{"\xc3\x8b",	"E"},		/*  */
	{"\xc3\x8c",	"I"},		/*  */
	{"\xc3\x8d",	"I"},		/*  */
	{"\xc3\x8e",	"I"},		/*  */
	{"\xc3\x8f",	"I"},		/*  */
	{"\xc3\x90",	"D"},		/* ETH */
	{"\xc3\x91",	"N"},		/*  */
	{"\xc3\x92",	"O"},		/*  */
	{"\xc3\x93",	"O"},		/*  */
	{"\xc3\x94",	"O"},		/*  */
	{"\xc3\x95",	"O"},		/*  */
	{"\xc3\x96",	"O"},		/*  */
	{"\xc3\x97",	"x"},		/* multiplication sign */
	{"\xc3\x98",	"O"},		/*  */
	{"\xc3\x99",	"U"},		/*  */
	{"\xc3\x9a",	"U"},		/*  */
	{"\xc3\x9b",	"U"},		/*  */
	{"\xc3\x9c",	"U"},		/*  */
	{"\xc3\x9d",	"Y"},		/*  */
	{"\xc3\x9e",	"TH"},		/* THORN */
	{"\xc3\x9f",	"ss"},		/*  */
	{"\xc3\xa0",	"a"},		/*  */
	{"\xc3\xa1",	"a"},		/*  */
	{"\xc3\xa2",	"a"},		/*  */
	{"\xc3\xa3",	"a"},		/*  */
	{"\xc3\xa4",	"a"},		/*  */
	{"\xc3\xa5",	"a"},		/*  */
	{"\xc3\xa6",	"ae"},		/*  */
	{"\xc3\xa7",	"c"},		/*  */
	{"\xc3\xa8",	"e"},		/*  */
	{"\xc3\xa9",	"e"},		/*  */
	{"\xc3\xaa",	"e"},		/*  */
	{"\xc3\xab",	"e"},		/*  */
	{"\xc3\xac",	"i"},		/*  */
	{"\xc3\xad",	"i"},		/*  */
	{"\xc3\xae",	"i"},		/*  */
	{"\xc3\xaf",	"i"},		/*  */
	{"\xc3\xb0",	"d"},		/* eth */
	{"\xc3\xb1",	"n"},		/*  */
	{"\xc3\xb2",	"o"},		/*  */
	{"\xc3\xb3",	"o"},		/*  */
	{"\xc3\xb4",	"o"},		/*  */
	{"\xc3\xb5",	"o"},		/*  */
	{"\xc3\xb6",	"o"},		/*  */
	{"\xc3\xb7",	"/"},		/* division sign */
	{"\xc3\xb8",	"o"},		/*  */
	{"\xc3\xb9",	"u"},		/*  */
	{"\xc3\xba",	"u"},		/*  */
	{"\xc3\xbb",	"u"},		/*  */
	{"\xc3\xbc",	"u"},		/*  */
	{"\xc3\xbd",	"y"},		/*  */
	{"\xc3\xbe",	"th"},		/* thorn */
	{"\xc3\xbf",	"y"}		/*  */
};

typedef struct Card  Card;

struct Card
{
	int 	border;	/* BORDER_WHITE */
	char *	bg;	/* Black_Border/Blue.png */
	char *	name;	/* Prodigal Sorcerer */
	char *	cost;	/* 2U */
	char *	color;	/* Blue */
	char *	rarity;	/* C */
	char *	artfile;/* Artwork/Prodigal_Sorcerer_4th_Ed.jpg */
	char *	artist;	/* Melissa Benson */
	char *	type;	/* Creature - Wizard */
	char *	text;	/* T: Deal 1 damage to target creature or player. */
	char *	textfnt;/* 2000000000000000000000000000000000000000000000 */
	char *	flavor;	/* "I really don't like unexpected visitors." */
	char *	flavfnt;/* 111111111111111111111111111111111111111111 */
	char *	powtgh;	/* 1/1 */
	char *	expsym;	/* Symbols/4th_Ed.png or just 4th_Ed */
	char *	rulings;/* <multi-line set of rulings> */
	Card *	flip;	/* NULL, or the upside-down card data */
};

/*
 *  Is the given character within the list of characters given?
 *  Implicitly, this function assumes ASCII for all parameters.
 */
int in(char ch, char *chars)
{
	char *p = chars;

	while (*p) {
		if (ch == *p)
			return (p - chars) + 1;
		p++;
	}
	return 0;
}


enum StyleTextConstants
{
	TAB_SIZE = 1,
	MAX_WORD = 256,
	MIN_BUF  = 192
};

#define IS_UTF8_START_BYTE(ch) ((((ch)>>7)&1)==1) /* is 1xxxxxxx */
#define IS_UTF8_CONT_BYTE(ch)  ((((ch)>>6)&3)==2) /* is 10xxxxxx */

#undef MAX
#define MAX(x,y) (((x)>(y))?(x):(y))

/*
 *  Internal function for finding a line of text to draw.
 *  A line is defined as whatever words fit in a certain
 *  pixel width, up to and not including a newline character.
 *  If half a word fits, the word will be wrapped to the
 *  next line, unless the word fills the entire line, in
 *  which case the word is truncated to fit.
 *
 *  If the pixel width runs out before reaching a newline,
 *  the line ends. The minimum number of characters on
 *  a line will be 1, even if an entire character will not
 *  fit in the given pixel width. This ensures that the
 *  function doesn't infinite loop on small inputs.
 *
 *  The line may be realloc'd larger during this function.
 *  To begin with, the line should be set to NULL and this
 *  function will allocate an appropriate block. After
 *  all calls to this function are finished, the calling
 *  function should call app_free to destroy the line.
 */
static Rect get_next_line(Font **fonts, int pixel_width,
	char *line[], int *line_bytes,
	const char *utf8[], int *nbytes, const char *styles[])
{
	char ch, *glyph, *dst;
	const char *src;
	int max;
	int gb, lb, sb; /* byte sizes for glyph, line, source */
	int sword, dword; /* byte sizes for the word, in source or dest */
	int glyph_width, line_width; /* in pixels */
	int tab, column; /* for expanding tabs into spaces */
	int alloc_size; /* how many bytes allocated in line */
	Rect r;
	Font *f;
	int font_index;

	r.x = r.y = r.width = r.height = 0;

	ch = ' ';
	tab = column = sword = dword = lb = sb = 0;
	max = *nbytes;
	line_width = 0;
	src = *utf8;

	alloc_size = MIN_BUF;
	if (*line == NULL)
		dst = *line = app_alloc(MIN_BUF);
	else
		dst = *line;

	if (*nbytes == 0) {
		(*line)[0] = '\0';
		return r;
	}

	/* Keep track of which font we're using. */
	font_index = (*styles)[0];
	f = fonts[font_index];
	if (r.height < f->height)
		r.height = f->height;

	do {
		/* grab a single glyph */
		gb = 0;
		glyph = dst;

		/* if there might be no space in dest line, resize it */
		if (lb + MAX(8,TAB_SIZE) > alloc_size) {
			alloc_size *= 2;
			dst = app_realloc(*line, alloc_size);
			if (dst == NULL)
				break;
			*line = dst;
			dst += lb;
		}

		/* whitespace and separators break words */
		if ((ch == ' ') || (ch == ','))
			sword = dword = 0; /* reset length of word */

		/* hyphens might break words */
		if (ch == '-')
		{
			/* Peek at next character: is it punctuation? */
			/* If so, don't hyphenate, just in case. */
			if (! in(*src, "X/0123456789~!@#$%^&*()-_+=|\\/:;.,<>?[]{}\"`'"))
				sword = dword = 0; /* reset length */
		}

		ch = *src;

		/* whitespaces are treated as words on their own */
		if (ch == ' ') {
			sword = dword = 0; /* reset length of word */
		}
		else if (ch == '\t') { /* expand tabs in destination */
			ch = ' ';
			sword = dword = 0;
			for (tab=column%TAB_SIZE; tab < TAB_SIZE-1; tab++)
			{
				*dst++ = ch;
				lb++;
				dword++;
				column++;
			}
		}
		else if (ch == '\n') { /* newlines end the line */
			sword = dword = 0;
			*dst++ = ch; /* save the newline to dest */
			sb++; /* skip the newline in the source */
			break; /* but don't count the newline in lb */
		}

		/* grab first (and maybe only) byte of UTF-8 sequence */
		*dst++ = ch;
		src++;

		/* Keep track of which font we're using. */
		font_index = (*styles)[sb];
		f = fonts[font_index];
		if (r.height < f->height)
			r.height = f->height;

		/* Update source-bytes and glyph-bytes counters. */
		gb++;
		sb++;

		/* grab UTF-8 continuation bytes */
		if (IS_UTF8_START_BYTE(ch)) {
			while ((sb < max) && IS_UTF8_CONT_BYTE(*src)) {
				*dst++ = *src++;
				gb++;
				sb++;
			}
		}

		glyph_width = app_font_width(f, glyph, gb);

		if (tab) {
			glyph_width *= (dword+1); /* tabs = wide spaces */
			tab = 0;
		}

		if (glyph_width + line_width <= pixel_width)
		{
			/* if the glyph fits, keep it */
			line_width += glyph_width;
			lb += gb;
			sword += gb;
			dword += gb;
			column++;
		}
		else {
			/* the glyph doesn't fit on the line */
			if (line_width == 0) {
				/* put at least one glyph on the line! */
				line_width += glyph_width;
				lb += gb;
				sword += gb;
				dword += gb;
			}
			else {
				/* else, don't keep the glyph */
				sb -= gb;
			}
			break; /* either way, nothing else fits */
		}
	} while (sb < max);

	/* remove final word on line, unless it is the whole line */
	if ((sb < max) && (sword < sb)) {
		sb -= sword;
		lb -= dword;
	}

	*utf8 += sb;
	*styles += sb;
	*nbytes -= sb;
	*line_bytes = lb;
	(*line)[lb] = '\0';
	r.width = line_width;
	return r;
}

/*
 *  Measure the width in bytes of a single line of text.
 */
int text_line_length(Font **fonts, int pixel_width,
	const char *utf8, int nb, const char *styles)
{
	int nl, nbytes;
	char *line;

	nbytes = nb;
	line = NULL;

	get_next_line(fonts, pixel_width, &line, &nl, &utf8, &nb, &styles);

	app_free(line);
	return nbytes - nb;
}

/*
 *  Measure text width in pixels up to a maximum line width in pixels
 *  (expanding tabs).
 */
int text_width(Font **fonts, int pixel_width,
	const char *utf8, int nb, const char *styles)
{
	int nl, nbytes, width;
	char *line;
	Rect r;

	if (nb == 0)
		return 0;

	nbytes = nb;
	line = NULL;

	r = get_next_line(fonts, pixel_width, &line, &nl, &utf8, &nb, &styles);

	app_free(line);
	return r.width;
}

/*
 *  Measure text height in pixels.
 */
int text_height(Font **fonts, int pixel_width,
	const char *utf8, int nb, const char *styles)
{
	int y, nl;
	char *line;
	Rect r;

	line = NULL;

	for (y=nl=0; nb > 0; y += r.height)
	{
		r = get_next_line(fonts, pixel_width, &line, &nl, &utf8, &nb, &styles);
	}

	app_free(line);
	return y;
}

/*
 *  Draw a UTF-8 string using many fonts.
 *  This wraps draw_utf8.
 */
const char * style_utf8(Graphics *g, Font **fonts, Point p,
		const char *utf8, int nb, const char *styles)
{
	const char *sp;
	Font *f;
	int font_index;
	int same_font;

	if (nb == 0)
		return utf8;

	font_index = styles[0];
	f = fonts[font_index];
	same_font = 0;

	for (sp = styles; nb >= 0; sp++, nb--)
	{
		if (nb == 0)
		{
			app_set_font(g, f);
			app_draw_utf8(g, p, utf8, same_font);
			p.x += app_font_width(f, utf8, same_font);
			utf8 += same_font;
			break;
		}
		if (*sp != font_index)
		{
			app_set_font(g, f);
			app_draw_utf8(g, p, utf8, same_font);
			p.x += app_font_width(f, utf8, same_font);
			utf8 += same_font;
			same_font = 0;
			font_index = *sp; /* Change to the next font. */
			f = fonts[font_index];
		}
		same_font++;
	}
	return utf8;
}

/*
 *  Drawing text functions, for various text alignments.
 */
static const char *style_text_left(Graphics *g, Font **fonts,
	Rect r, int line_height,
	const char *utf8, int nb, const char *styles)
{
	int nl;
	int right_to_left;
	char *line;
	char *s, *end;
	Point p;
	Rect line_stats;
	const char *start_styles;

	p.x = r.x;
	p.y = r.y;
	line = NULL;

	if (g->text_direction & RL_TB)
		right_to_left = 1;
	else
		right_to_left = 0;

	for (nl=0; (p.y <= r.y+r.height) && (nb > 0); p.y += line_height)
	{
		start_styles = styles;
		line_stats = get_next_line(fonts, r.width, &line, &nl, &utf8, &nb, &styles);
		p.x = r.x;
		if (right_to_left)
			p.x += line_stats.width;

		/* discard spaces at start of line */
		end = line+nl;
		for (s=line; (s < end) && (*s == ' '); s++, start_styles++)
			nl--;

		style_utf8(g, fonts, p, s, nl, start_styles);
	}

	app_free(line);
	if (nb == 0)
		return NULL;
	return utf8;
}

static const char *style_text_right(Graphics *g, Font **fonts,
	Rect r, int line_height,
	const char *utf8, int nb, const char *styles)
{
	int nl;
	int right_to_left;
	char *line;
	Point p;
	Rect line_stats;
	const char *start_styles;

	p.x = r.x;
	p.y = r.y;
	line = NULL;

	if (g->text_direction & RL_TB)
		right_to_left = 1;
	else
		right_to_left = 0;

	for (nl=0; (p.y <= r.y+r.height) && (nb > 0); p.y += line_height)
	{
		start_styles = styles;
		line_stats = get_next_line(fonts, r.width, &line, &nl, &utf8, &nb, &styles);
		p.x = r.x + r.width;
		if (! right_to_left)
			p.x -= line_stats.width;
		style_utf8(g, fonts, p, line, nl, start_styles);
	}

	app_free(line);
	if (nb == 0)
		return NULL;
	return utf8;
}

static const char *style_text_centered(Graphics *g, Font **fonts,
	Rect r, int line_height,
	const char *utf8, int nb, const char *styles)
{
	int nl, w;
	int right_to_left;
	char *line;
	Point p;
	Rect line_stats;
	const char *start_styles;

	p.x = r.x;
	p.y = r.y;
	line = NULL;

	if (g->text_direction & RL_TB)
		right_to_left = 1;
	else
		right_to_left = 0;

	for (nl=0; (p.y <= r.y+r.height) && (nb > 0); p.y += line_height)
	{
		start_styles = styles;
		line_stats = get_next_line(fonts, r.width, &line, &nl, &utf8, &nb, &styles);
		w = line_stats.width;
		p.x = r.x + (r.width - w) / 2;
		if (right_to_left)
			p.x += w;
		style_utf8(g, fonts, p, line, nl, start_styles);
	}

	app_free(line);
	if (nb == 0)
		return NULL;
	return utf8;
}

static int find_word_size(const char *utf8, int nbytes)
{
	int i;

	if (nbytes > 0)
		if (*utf8 == ' ')
			return 1; /* spaces are words 1 byte wide */

	for (i=0; i < nbytes; i++) {
		if (*utf8++ == ' ')
			return i; /* a word is all bytes up to a space */
	}
	return i;
}

static const char *style_text_justified(Graphics *g, Font **fonts,
	Rect r, int line_height,
	const char *utf8, int nb, const char *styles)
{
	int nl, w, ns, sw, xw;
	int width, spaces, last;
	int right_to_left;
	char *line, *s, *start, *end;
	Point p;
	Rect line_stats;
	const char *start_styles;

	p.x = r.x;
	p.y = r.y;
	line = NULL;

	if (g->text_direction & RL_TB)
		right_to_left = 1;
	else
		right_to_left = 0;

	for (nl=0; (p.y <= r.y+r.height) && (nb > 0); p.y += line_height)
	{
		start_styles = styles;
		line_stats = get_next_line(fonts, r.width, &line, &nl, &utf8, &nb, &styles);

		p.x = r.x;
		width = r.width;

		/* if we have reached a newline line, don't justify it */
		if (line[nl] == '\n')	/* yes, line[nl] looks wrong */
			last = 1;	/* but it's correct */
		else
			last = 0;

		/* discard spaces at start of line */
		end = line+nl;
		for (s=line; (s < end) && (*s == ' '); s++, start_styles++)
			nl--;
		start = s;

		/* discard spaces at the end of the line */
		for (s=end-1; (s >= start) && (*s == ' '); s--)
			nl--;
		end = s+1;

		/* count the number of scalable spaces */
		for (spaces=0, s=start; s < end; s++)
			if (*s == ' ')
				spaces++;

		if ((spaces == 0) || last || (nb == 0))
		{
			/* align this line normally, don't justify it */
			if (right_to_left)
				p.x += r.width;
			style_utf8(g, fonts, p, line, nl, start_styles);
			continue; /* go to next line */
		}

		/* otherwise, each word must be drawn individually */

		w = text_width(fonts, 1000000, start, (int)(end-start), start_styles);
		sw = (width-w) / spaces;
		xw = (width-w) % spaces;

		if (right_to_left) {
			p.x += r.width; /* draw from right-most point */
			while (start < end)
			{
				ns = find_word_size(start, (int)(end-start));
				w = text_width(fonts, 1000000, start, ns, start_styles);

				style_utf8(g, fonts, p, start, ns, start_styles);
				p.x -= w;
				if (*start == ' ')
					p.x -= sw;
				if (xw) {
					p.x--;
					xw--;
				}
				start += ns;
			}
		}
		else { /* draw left to right */
			while (start < end)
			{
				ns = find_word_size(start, (int)(end-start));
				w = text_width(fonts, 1000000, start, ns, start_styles);

				style_utf8(g, fonts, p, start, ns, start_styles);
				p.x += w;
				if (*start == ' ')
					p.x += sw;
				if (xw) {
					p.x++;
					xw--;
				}
				start += ns;
			}
		}
	}

	app_free(line);
	if (nb == 0)
		return NULL;
	return utf8;
}

/*
 *  The function which calls the above alignment-specific functions.
 */
char *style_text(Graphics *g, Font **fonts, Rect r, int align,
	const char *utf8, int nbytes, const char *styles)
{
	int i, h, lh, nlines, td;
	const char *remains;
	Region *old, *clip;

	old = g->clip;
	g->clip = NULL;
	if (old) {
		clip = app_copy_region(old);
		app_move_region(clip, -g->offset.x, -g->offset.y);
		app_intersect_region_with_rect(clip, r, clip);
		app_set_clip_region(g, clip);
		app_del_region(clip);
	} else
		app_set_clip_rect(g, r);

	lh = 0;
	for (i = 0; fonts[i] != NULL; i++)
	{
		if (lh < app_font_height(fonts[i]))
			lh = app_font_height(fonts[i]);
	}

	if ((align & VALIGN_CENTER) == VALIGN_CENTER) {
		h = text_height(fonts, r.width, utf8, nbytes, styles);
		if (h < r.height)
			r.y += (r.height-h)/2;
	}
	else if ((align & VALIGN_JUSTIFY) == VALIGN_JUSTIFY) {
		h = text_height(fonts, r.width, utf8, nbytes, styles);
		if (h < r.height) {
			nlines = h / lh;
			if (nlines > 1)
				lh += ((r.height-h) / (nlines-1));
		}
	}
	else if ((align & VALIGN_BOTTOM) == VALIGN_BOTTOM) {
		h = text_height(fonts, r.width, utf8, nbytes, styles);
		if (h < r.height)
			r.y += (r.height-h);
	}

	td = g->text_direction;
	if (align & LR_TB) {
		g->text_direction &= ~RL_TB;
		g->text_direction |= LR_TB;
	}
	else if (align & RL_TB) {
		g->text_direction &= ~LR_TB;
		g->text_direction |= RL_TB;
	}

	if ((align & ALIGN_CENTER) == ALIGN_CENTER)
		remains = style_text_centered(g, fonts, r, lh, utf8, nbytes, styles);
	else if ((align & ALIGN_JUSTIFY) == ALIGN_JUSTIFY)
		remains = style_text_justified(g, fonts, r, lh, utf8, nbytes, styles);
	else if ((align & ALIGN_RIGHT) == ALIGN_RIGHT)
		remains = style_text_right(g, fonts, r, lh, utf8, nbytes, styles);
	else
		remains = style_text_left(g, fonts, r, lh, utf8, nbytes, styles);

	app_del_region(g->clip);
	g->clip = old;
	g->text_direction = td;

	return (char *) remains; /* cast away const */
}





/*
 *  Initialise the fonts we need to use to draw cards.
 */
void init_fonts(App *app)
{
	int i;

	//name_font = app_new_font(app, "perrygothic", PLAIN, 56);
	//name_font = app_new_font(app, "Times", PLAIN, 56);
	name_font = app_new_font(app, "MagicTG", PLAIN, 60);
	mana_font = app_new_font(app, "Mana", PLAIN, 56);
	type_font = app_new_font(app, "Times", PLAIN, 46);
	//text_font = app_new_font(app, "Times", PLAIN | ANTI_ALIAS, 40);
	//flavor_font = app_new_font(app, "Times", ITALIC | ANTI_ALIAS, 40);
	artist_font = app_new_font(app, "Times", PLAIN, 30);

	for (i=16; i <= 40; i++)
	{
		normal_fonts[i] = app_new_font(app, "Times",
					PLAIN | ANTI_ALIAS, i);
		italic_fonts[i] = app_new_font(app, "Times",
					ITALIC | ANTI_ALIAS, i);
		mana_fonts[i] = app_new_font(app, "Mana",
					PLAIN | ANTI_ALIAS, i);
	}
}

void show_card_in_control(Control *c, Graphics *g)
{
	Rect r = app_get_control_area(c);

	if (full)
		app_draw_image(g, r, full, app_get_image_area(full));

	if (bmap)
	{
		Graphics *src = app_get_bitmap_graphics(bmap);
		app_copy_rect(g, pt(r.x, r.y),
				src, app_get_bitmap_area(bmap));
		app_del_graphics(src);
	}
}

void reshape_window(Window *w)
{
	Rect r = app_get_window_area(w);
	app_size_control(c, app_inset_rect(r, 2));
}

void draw_white_shadow_text(Graphics *g, Rect r, int align, char *utf8, int nbytes)
{
	app_set_colour(g, BLACK);
	r.x += 2; r.y += 2;
	app_draw_text(g, r, align, utf8, nbytes);
	r.x -= 1; r.y -= 1;
	app_draw_text(g, r, align, utf8, nbytes);
	app_set_colour(g, WHITE);
	r.x -= 1; r.y -= 1;
	app_draw_text(g, r, align, utf8, nbytes);
	app_set_colour(g, BLACK);
}

void fit_text(Graphics *g, Rect r, int align,
		char *text, int textbytes, char *textfnt,
		char *flavor, int flavbytes, char *flavfnt)
{
	int height = r.height;
	int font_size = 40;
	Font *fonts[4];

	while (font_size >= 16)
	{
		fonts[0] = normal_fonts[font_size];
		fonts[1] = italic_fonts[font_size];
		fonts[2] = mana_fonts[font_size];
		fonts[3] = NULL;

		height = text_height(fonts, r.width, text, textbytes, textfnt);
		if (flavbytes)
			height += text_height(fonts, r.width, flavor, flavbytes, flavfnt);
		if (height <= r.height)
			break;
		font_size--;
	}

	if (font_size <= 35) {
		if (log != NULL) {
			fprintf(log,
			"\tFont size smaller than recommended (only %d pixels high)\n",
			font_size);
		}
	}

	if (flavbytes) {
		height = (r.height - height) / 3;
		if (height >= 0) {
			r.y += height;
			r.height -= height * 2;
		}
	}

	if (flavbytes) {
		style_text(g, fonts, r, align | VALIGN_TOP,
				text, textbytes, textfnt);
		style_text(g, fonts, r, align | VALIGN_BOTTOM | ALIGN_CENTRE,
				flavor, flavbytes, flavfnt);
	}
	else {
		style_text(g, fonts, r, align | VALIGN_CENTRE,
				text, textbytes, textfnt);
	}
}

void fit_text_old(Graphics *g, Rect r, int align,
		char *text, int textbytes,
		char *flavor, int flavbytes)
{
	int height = r.height;
	int font_size = 40;
	Font *f1, *f2;

	f1 = normal_fonts[font_size];
	if (flavbytes)
		f2 = italic_fonts[font_size];

	while (font_size >= 16)
	{
		height = app_text_height(f1, r.width, text, textbytes);
		if (flavbytes)
			height += app_text_height(f2, r.width, flavor, flavbytes);
		if (height <= r.height)
			break;
		font_size--;
		f1 = normal_fonts[font_size];
		if (flavbytes)
			f2 = italic_fonts[font_size];
	}

	if (flavbytes) {
		height = (r.height - height) / 3;
		if (height >= 0) {
			r.y += height;
			r.height -= height * 2;
		}
	}

	app_set_font(g, f1);
	if (flavbytes) {
		app_draw_text(g, r, align | VALIGN_TOP, text, textbytes);
		app_set_font(g, f2);
		app_draw_text(g, r, align | VALIGN_BOTTOM | ALIGN_CENTRE,
				flavor, flavbytes);
	}
	else {
		app_draw_text(g, r, align | VALIGN_CENTRE, text, textbytes);
	}
}

/*
 *  Return the largest centred rectangle within the given image
 *  which has the same aspect ratio as the given width:height ratio.
 */
Rect largest_rect(Image *img, int width, int height)
{
	Rect sr = app_get_image_area(img);
	double ratio, scale;

	ratio = width * 1.0 / height;		// say 1.24
	scale = sr.width * 1.0 / sr.height;	// say 1.33, i.e. wider
	if (ratio < scale)			// use whole image height
	{
		width = sr.height * ratio;
		sr.x = (sr.width - width) / 2;
		sr.width = width;
	}
	else					// use whole image width
	{
		height = sr.width / ratio;
		sr.y = (sr.height - height) / 2;
		sr.height = height;
	}
	return sr;
}

/*
 *  Composite together the pieces of a Magic card.
 */
Image * create_card_image(Card *card)
{
	Graphics *g;
	Image *full;
	Image *art;
	Image *scaled_art;

	full = app_read_image(card->bg, 32);

	if (full == NULL) {
		if (log != NULL) {
			fprintf(log,
				"\tCouldn't read template image file %s\n", card->bg);
		}
		return NULL;
	}

	/* Draw into the image now. */
	g = app_get_image_graphics(full);

	if (card->artfile)
	{
		art = app_read_image(card->artfile, 32);
		if (art != NULL)
		{
			scaled_art = app_scale_image(art,
					rect(0,0,570,460),
					largest_rect(art, 570, 460));
			app_draw_image(g, rect(87,107,570,460),
				scaled_art, rect(0,0,570,460));
			app_del_image(scaled_art);
			app_del_image(art);
		}
	}

	app_set_font(g, name_font);
	draw_white_shadow_text(g, rect(58, 35, full->width-60, 64),
				(ALIGN_LEFT | VALIGN_TOP | LR_TB),
				card->name, (int) strlen(card->name));

	if (card->cost)
	{
		app_set_font(g, mana_font);
		app_draw_text(g, rect(58, 35, full->width-115, 64),
				(ALIGN_RIGHT | VALIGN_TOP | LR_TB),
				card->cost, (int) strlen(card->cost));
	}

	app_set_font(g, type_font);
	draw_white_shadow_text(g, rect(74, 575, full->width-60, 60),
				(ALIGN_LEFT | VALIGN_CENTRE | LR_TB),
				card->type, (int) strlen(card->type));

	if (card->text)
	{
		//app_set_font(g, text_font);
		fit_text(g, rect(95, 653, 554, 282),
			(ALIGN_LEFT | LR_TB),
			card->text, (int) strlen(card->text), card->textfnt,
			card->flavor, (int) strlen(card->flavor), card->flavfnt);
	}

	if (card->powtgh)
	{
		app_set_font(g, type_font);
		draw_white_shadow_text(g,
				rect(full->width - 500, 950, 450, 50),
				(ALIGN_RIGHT | VALIGN_CENTRE | LR_TB),
				card->powtgh, (int) strlen(card->powtgh));
	}

	if (card->artist && card->artist[0])
	{
		char *artcopyright = app_alloc(10 + strlen(card->artist));
		strcpy(artcopyright, "Illus. © ");
		strcat(artcopyright, card->artist);

		app_set_font(g, artist_font);
		draw_white_shadow_text(g, rect(58, 950, 550, 50),
				(ALIGN_LEFT | VALIGN_CENTRE | LR_TB),
				artcopyright, (int) strlen(artcopyright));
	}

	/* Tidy up. */
	app_del_graphics(g);

	return full;
}


/*
 *  Some string and file utilities:
 */

void convert_CRNL_to_NL(char *data)
{
	int src, dst;

	for (src = dst = 0; data[src] != '\0'; src++)
	{
		if ((data[src] == '\r') && (data[src+1] == '\n'))
			++src;
		data[dst++] = data[src];
	}
	data[dst] = '\0';
}

char *	assign_string(char *dst, char *s)
{
	app_free(dst);
	return app_copy_string(s);
}

char *	append_string(char *dst, const char *s)
{
	char *result = NULL;
	int length = 0;

	if (dst)
		length += strlen(dst);
	if (s)
		length += strlen(s);
	result = app_zero_alloc(length + 1);
	if (dst)
		strcpy(result, dst);
	if (s)
		strcat(result, s);
	app_free(dst);
	return result;
}

char *	prepend_string(const char *s, char *dst)
{
	char *result = NULL;
	int length = 0;

	if (s)
		length += strlen(s);
	if (dst)
		length += strlen(dst);
	result = app_zero_alloc(length + 1);
	if (s)
		strcpy(result, s);
	if (dst)
		strcat(result, dst);
	app_free(dst);
	return result;
}

/*
 *  Replace all instances of 'pattern' by 'replace' in string 'dst'.
 *  If the lengths of 'pattern' and 'replace' are equal, the
 *  replacement occurs in-place, so 'dst' is returned.
 *  Otherwise a new string is allocated and returned, and 'dst' is freed.
 *
 *  Call this function like this:
 *   dst = find_replace(dst, pattern, replacement);
 *
 *  The string 'dst' must be allocated with app_alloc (or be NULL).
 */
char *	find_replace(char *dst, const char *pattern, const char *replace)
{
	int plen;
	int rlen;
	int length;
	char *src;
	char *patpos;
	char *result;

	if (! dst)
		return NULL; /* nothing to replace! */

	plen = strlen(pattern);
	rlen = strlen(replace);
	if (rlen == plen)
	{
		/* There will be no change in the string's length! */
		/* Thus, replace pattern in-place, avoiding malloc. */
		result = dst;
	}
	else {
		/* Count pattern occurrences to calculate new length. */
		/* Then allocate a buffer to store the new string. */
		length = strlen(dst);
		for (patpos = strstr(dst, pattern); patpos != NULL; )
		{
			length -= plen; /* omit space used by pattern */
			length += rlen; /* add space for replacement */
			patpos += plen; /* skip over pattern */
			patpos = strstr(patpos, pattern);
		}
		result = app_zero_alloc(length + 1);
		if (result == NULL)
			return NULL; /* failed: insufficient memory */
	}

	length = 0;
	src = dst;
	for (patpos = strstr(src, pattern); patpos != NULL; )
	{
		/* copy up to found pattern */
		if (result != dst)
			strncpy(result + length, src, (patpos - src));
		length += (patpos - src);

		/* append replacement */
		strncpy(result + length, replace, rlen);
		length += rlen;

		/* skip over pattern */
		src = patpos + plen;

		/* find next instance of pattern */
		patpos = strstr(src, pattern);
	}
	if (result != dst) {
		strcpy(result + length, src);
		app_free(dst);
	}
	return result;
}

/*
 *  Convert a partial image filename such as "Thing" into
 *  a full relative path, such as "Artwork/Thing.jpg".
 *  This function searches within the named subdir first,
 *  and appends suffixes in order (unless one was already given).
 *  Else it tries looking in the current directory.
 *
 *  The given filename is app_free'd and a new version returned
 *  (unless the filename worked first time).
 *  If the file can't be found, NULL is returned and the
 *  filename is unchanged.
 */
char *	find_image_file(char *subdir, char *filename)
{
	int i;
	FILE *f;
	int slen, flen, dlen;
	char *trythis;
	int has_suffix = 0;
	char *suffixes[] = {	".png", ".jpg", ".gif",
				".PNG", ".JPG", ".GIF",
				".jpe", ".JPE", ".jpeg", ".JPEG" };

	if (filename[0] == '\0')
		return filename;

	if ((f = fopen(filename, "rb")) != NULL)
	{
		fclose(f);
		return filename;
	}

	flen = strlen(filename);
	dlen = strlen(subdir);
	for (i=0; i < NELEM(suffixes); i++)
	{
		slen = strlen(suffixes[i]);
		if (slen >= flen)
			continue;
		if (strcmp(filename+flen-slen, suffixes[i]) == 0)
		{
			has_suffix = 1;
			break;
		}
	}

	trythis = prepend_string(subdir, app_copy_string(filename));

	if (has_suffix)
	{
		if ((f = fopen(trythis, "rb")) != NULL)
		{
			fclose(f);
			app_free(filename);
			return trythis;
		}
		else {
			app_free(trythis);

			/* We tried the filename as is, and with a subdir. */
			/* filename is left untouched in this case */
			return NULL; /* Failed to find the file! */
		}
	}

	/* If we reach here, we have a non-suffixed filename. */
	/* So, let's try each suffix with the subdir prefix. */
	for (i=0; i < NELEM(suffixes); i++)
	{
		trythis = prepend_string(subdir, app_copy_string(filename));
		trythis = append_string(trythis, suffixes[i]);
		if ((f = fopen(trythis, "rb")) != NULL)
		{
			fclose(f);
			app_free(filename);
			return trythis;
		}
		app_free(trythis);
	}

	/* Try each suffix without the subdir prefix now. */
	for (i=0; i < NELEM(suffixes); i++)
	{
		trythis = app_copy_string(filename);
		trythis = append_string(trythis, suffixes[i]);
		if ((f = fopen(trythis, "rb")) != NULL)
		{
			fclose(f);
			app_free(filename);
			return trythis;
		}
		app_free(trythis);
	}

	/* We tried the filename as is, and with a subdir, and with suffixes. */
	/* filename is left untouched in this case */
	return NULL; /* Failed to find the file! */
}

/*
 *  Convert rules text to use the italic font in the appropriate places.
 */
void set_italic_font(char *text, char *textfnt)
{
	int i;
	int inside_parentheses = 0;

	for (i = 0; text[i] != '\0'; i++)
	{
		if (text[i] == '(')
			inside_parentheses++;

		if (inside_parentheses)
			textfnt[i] = ITALIC_FONT;

		if (text[i] == ')')
			inside_parentheses--;
	}
}

/*
 *  Return the offset into line where the next mana symbol occurs.
 *  A mana symbol could be:
 *   T on its own,
 *   W or U or B or R or G on their own,
 *   0 to 9 on its own,
 *   / is also allowed, for split mana symbols,
 *   X on its own except for certain circumstances (e.g. Pay X or X life).
 *   Combinations of the above, in a distinct word (e.g. X1RG or 10W)
 */
int find_next_mana_symbol(char *line, int start, int *len)
{
	char ch;
	char prev = ' '; /* ' ' means space or punct, 'M' for mana, else 'a' */
	int found = 0;
	int i = start;

	while ((ch = line[i]) != '\0')
	{
		if (in(ch, "WUBRGT")) {
			if ((prev == ' ') || (prev == 'M'))
			{
				if (! found)
					start = i;

				found = 2; /* has mana symbols */
				prev = 'M';
			}
		}
		else if (in(ch, "0123456789X")) {
			if (prev == 'a')
				;
			else if (! found) {
				if ((ch == 'X')
				 && (strncmp(line+i, "X is ", 5) == 0))
					found = 0;
				else if ((i > 5)
				      && (strncmp(line+i-5, "cost ", 5) == 0))
					found = 0;
				else if ((i > 8)
				      && (strncmp(line+i-8, "costing ", 8) == 0))
					found = 0;
				else {
					start = i;
					found = 1; /* only has numbers so far */
					prev = 'M';
				}
			}
			else if ((found == 1) && (ch == 'X')) {
				found = 2;
				prev = 'M';
			}
		}
		else if (ch == '/') {
			/* don't change the state either way */
		}
		else if ((ch == ' ') || (in(ch, ",.():\n\t\"\'"))) {
			prev = ' ';
			if (found == 2)
				break;
			else if (found == 0) {
				i++;
				continue;
			}
			/* found must be 1, i.e. only digits or one X */

			if (ch != ' ')
				break;
			else if (strncmp(line+i, " life", 5) == 0)
				found = 0;
			else if (strncmp(line+i, " land", 5) == 0)
				found = 0;
			else if (strncmp(line+i, " cards", 6) == 0)
				found = 0;
			else if (strncmp(line+i, " damage", 7) == 0)
				found = 0;
			else if (strncmp(line+i, " tokens", 7) == 0)
				found = 0;
			else if (strncmp(line+i, " non", 4) == 0)
				found = 0;
			else if (strncmp(line+i, " +", 2) == 0)
				found = 0;
			else if (strncmp(line+i, " -", 2) == 0)
				found = 0;
			else if (strncmp(line+i, " white ", 7) == 0)
				found = 0;
			else if (strncmp(line+i, " blue ", 6) == 0)
				found = 0;
			else if (strncmp(line+i, " black ", 7) == 0)
				found = 0;
			else if (strncmp(line+i, " red ", 5) == 0)
				found = 0;
			else if (strncmp(line+i, " green " , 7) == 0)
				found = 0;
			else if (strncmp(line+i, " target", 7) == 0)
				found = 0;
			else if (strncmp(line+i, " tapped", 7) == 0)
				found = 0;
			else if (strncmp(line+i, " charge", 7) == 0)
				found = 0;
			else if (strncmp(line+i, " untapped", 9) == 0)
				found = 0;
			else if (strncmp(line+i, " counters", 9) == 0)
				found = 0;
			else if (strncmp(line+i, " attacking", 10) == 0)
				found = 0;
			else if (strncmp(line+i, " creatures", 10) == 0)
				found = 0;
			else if (strncmp(line+i, " permanents", 11) == 0)
				found = 0;
			else if (strncmp(line+i, " enchantments", 13) == 0)
				found = 0;
			else
				break;
		}
		else { /* some other letter, e.g. the a in Xantid, or + or - */
			if (found)
				found = 0;
			prev = 'a';
		}
		i++;
	}

	if (found) {
		if (len != NULL)
			*len = i - start;
		return start;
	}
	return -1; /* not found */
}

/*
 *  Convert rules text to use the mana font in the appropriate places.
 */
void set_mana_font(char *text, char *textfnt)
{
	int i, len, offset, gap;
	int start = 0;

	while ((start = find_next_mana_symbol(text, start, &len)) != -1)
	{
		for (i=start; i < start+len; i++) {
			textfnt[i] = MANA_FONT;
		}
		i = start;
		while (len >= 2) {
			     if (!strncmp(text+i, "10", 2)) text[i] = '~', gap=1;
			else if (!strncmp(text+i, "11", 2)) text[i] = '!', gap=1;
			else if (!strncmp(text+i, "12", 2)) text[i] = '@', gap=1;
			else if (!strncmp(text+i, "13", 2)) text[i] = '#', gap=1;
			else if (!strncmp(text+i, "14", 2)) text[i] = '$', gap=1;
			else if (!strncmp(text+i, "15", 2)) text[i] = '%', gap=1;
			else if (!strncmp(text+i, "16", 2)) text[i] = '^', gap=1;
			else if (!strncmp(text+i, "17", 2)) text[i] = '&', gap=1;
			else if (!strncmp(text+i, "18", 2)) text[i] = '*', gap=1;
			else if (!strncmp(text+i, "19", 2)) text[i] = '(', gap=1;
			else if (!strncmp(text+i, "20", 2)) text[i] = ')', gap=1;
			else if (!strncmp(text+i, "W/U", 3)) text[i] = 'Q', gap=2;
			else if (!strncmp(text+i, "U/W", 3)) text[i] = 'Q', gap=2;
			else if (!strncmp(text+i, "W/B", 3)) text[i] = 'q', gap=2;
			else if (!strncmp(text+i, "B/W", 3)) text[i] = 'q', gap=2;
			else if (!strncmp(text+i, "U/B", 3)) text[i] = 'Y', gap=2;
			else if (!strncmp(text+i, "B/U", 3)) text[i] = 'Y', gap=2;
			else if (!strncmp(text+i, "U/R", 3)) text[i] = 'y', gap=2;
			else if (!strncmp(text+i, "R/U", 3)) text[i] = 'y', gap=2;
			else if (!strncmp(text+i, "B/R", 3)) text[i] = 'V', gap=2;
			else if (!strncmp(text+i, "R/B", 3)) text[i] = 'V', gap=2;
			else if (!strncmp(text+i, "B/G", 3)) text[i] = 'v', gap=2;
			else if (!strncmp(text+i, "G/B", 3)) text[i] = 'v', gap=2;
			else if (!strncmp(text+i, "R/G", 3)) text[i] = 'E', gap=2;
			else if (!strncmp(text+i, "G/R", 3)) text[i] = 'E', gap=2;
			else if (!strncmp(text+i, "R/W", 3)) text[i] = 'e', gap=2;
			else if (!strncmp(text+i, "W/R", 3)) text[i] = 'e', gap=2;
			else if (!strncmp(text+i, "G/W", 3)) text[i] = 'F', gap=2;
			else if (!strncmp(text+i, "W/G", 3)) text[i] = 'F', gap=2;
			else if (!strncmp(text+i, "G/U", 3)) text[i] = 'f', gap=2;
			else if (!strncmp(text+i, "U/G", 3)) text[i] = 'f', gap=2;
			else {
				len--;
				start++;
				i++;
				continue;
			}
			/* If we get here, a substitution must have happened. */
			/* So copy the rest of the string down. */
			for (offset=i+1; text[offset] != '\0'; offset++) {
				text[offset]    = text[offset+gap];
				textfnt[offset] = textfnt[offset+gap];
			}
		}
		start += len;
	}
}

char *	find_line(char *block, char *key)
{
	int length;
	char *start;
	int total = 0;
	char *line = NULL;

	start = strstr(block, key);
	if (start == NULL)
		return NULL;
	length = strlen(key);
	start += length;
	while ((*start == ' ') || (*start == '\t'))
		start++;
	for (length=0; ; length++)
	{
		if ((start[length] != '\n') && (start[length] != '\0'))
			continue;

		if ((line == NULL)
		 || (!strncmp(start, "\t",          1))
		 || (!strncmp(start, "          ", 10)))
		{
			while ((start[0] == ' ') || (start[0] == '\t'))
			{
				start++;
				length--;
			}
			line = app_realloc(line, total + length + 2);
			strncpy(line + total, start, length + 1);
			total += length + 1;
			line[total] = '\0';
			if (start[length] == '\0')
				break;
			if ((length > 0) && (start[length - 1] == '.'))
				;
			else if ((length > 1)
			      && (in(start[length - 2], ".!?"))
			      && (in(start[length - 1], ")\"")))
				;
			else
				line[total - 1] = ' ';
			while ((total >= 2)
			    && (line[total - 1] == ' ')
			    && (line[total - 2] == ' '))
				line[--total] = '\0';
			start += length + 1;
			length = -1;
		}
		else {
			break;
		}
	}
	if (line == NULL)
		return line;
	length = strlen(line);
	while ((length > 0) && in(line[length-1], " \t\n"))
		line[--length] = '\0';
	return line;
}

Card *	parse_card(char *block)
{
	Card *card;
	char *temp;
	int i;

	card = app_zero_alloc(sizeof(Card));
	if (card == NULL)
		return NULL;

	/* Parse Card Name: */

	card->name = find_line(block, "Card Name:");
	if (! card->name)
		card->name = find_line(block, "Card Title:");
	if (! card->name)
		card->name = find_line(block, "Name:");
	if (! card->name)
		card->name = find_line(block, "Title:");
	if (! card->name)
		return card;

	/* Parse Rarity: */

	card->rarity = find_line(block, "Rarity:");
	if (card->rarity && card->rarity[0])
		card->rarity[1] = '\0';
	if (! card->rarity)
		card->rarity = app_zero_alloc(1);

	/* Parse Casting Cost: */

	card->cost = find_line(block, "Mana Cost:");
	if (! card->cost)
		card->cost = find_line(block, "Casting Cost:");
	if (! card->cost)
		card->cost = find_line(block, "Cost:");
	if (! card->cost)
		card->cost = app_zero_alloc(1);
	else if (strcmp(card->cost, "n/a") == 0)
		card->cost[0] = '\0';
	else if (strcmp(card->cost, "N/A") == 0)
		card->cost[0] = '\0';
	else if (strcmp(card->cost, "Land") == 0)
		card->cost[0] = '\0';

	/* Parse Type: */

	card->type = find_line(block, "Type:");
	if (! card->type)
		card->type = find_line(block, "Type & Class:");
	if (! card->type)
		card->type = find_line(block, "Card Type:");
	if (! card->type)
		card->type = app_zero_alloc(1);

	/* Parse Rules Text: */

	card->text = find_line(block, "Rules Text:");
	if (! card->text)
		card->text = find_line(block, "Card Text:");
	if (! card->text)
		card->text = app_zero_alloc(1);
	if (strcmp(card->text, "N/A") == 0)
		card->text[0] = '\0';
	else if (strcmp(card->text, "n/a") == 0)
		card->text[0] = '\0';
	card->textfnt = app_zero_alloc(strlen(card->text) + 1);

	/* Parse Flavor Text: */

	card->flavor = find_line(block, "Flavor Text:");
	if (! card->flavor)
		card->flavor = find_line(block, "Flavour Text:");
	if (! card->flavor)
		card->flavor = find_line(block, "Flavor:");
	if (! card->flavor)
		card->flavor = find_line(block, "Flavour:");
	if (! card->flavor)
		card->flavor = app_zero_alloc(1);
	if (strcmp(card->flavor, "N/A") == 0)
		card->flavor[0] = '\0';
	else if (strcmp(card->flavor, "n/a") == 0)
		card->flavor[0] = '\0';
	card->flavfnt = app_zero_alloc(strlen(card->flavor) + 1);

	/* Parse Pow/Tgh: */

	card->powtgh = find_line(block, "Pow/Tgh:");
	if (! card->powtgh)
		card->powtgh = find_line(block, "Pow/Tou:");
	if (! card->powtgh)
		card->powtgh = app_zero_alloc(1);
	else if (strcmp(card->powtgh, "n/a") == 0)
		card->powtgh[0] = '\0';
	else if (strcmp(card->powtgh, "N/A") == 0)
		card->powtgh[0] = '\0';

	/* Parse Artwork: */

	card->artfile = find_line(block, "Artfile:");
	if (! card->artfile)
		card->artfile = find_line(block, "Art File:");
	if (! card->artfile)
		card->artfile = find_line(block, "Artwork:");
	if (! card->artfile)
		card->artfile = find_line(block, "Art:");
	if (! card->artfile)
		card->artfile = app_zero_alloc(1);
	temp = find_image_file(artwork, card->artfile);
	if (temp == NULL) {
		if (log != NULL) {
			fprintf(log,
				"Card \"%s\" couldn't find artwork image file %s\n",
				card->artfile);
		}
		app_free(card->artfile);
		temp = app_zero_alloc(1);
	}
	card->artfile = temp;

	/* Parse Artist: */

	card->artist = find_line(block, "Artist:");
	if (! card->artist)
		card->artist = find_line(block, "Artist Name:");
	if (! card->artist)
		card->artist = find_line(block, "Artist's Name:");
	if (! card->artist)
		card->artist = app_zero_alloc(1);

	/* Parse Expansion: */

	card->expsym = find_line(block, "Expansion:");
	if (! card->expsym)
		card->expsym = find_line(block, "Set:");
	if (! card->expsym)
		card->expsym = app_zero_alloc(1);
	temp = find_image_file(symbols, card->expsym);
	if (temp == NULL) {
		if (log != NULL) {
			fprintf(log,
				"Card \"%s\" couldn't find expansion symbol image file %s\n",
				card->expsym);
		}
		app_free(card->expsym);
		temp = app_zero_alloc(1);
	}
	card->expsym = temp;

	/* Parse Color: and deduce if necessary */

	card->color = find_line(block, "Color:");
	if (! card->color)
		card->color = find_line(block, "Colour:");
	if ((strstr(card->type, "Land"))
	 && ((! card->color)
	  || (strcmp(card->color, "L") == 0)
	  || (strncmp(card->color, "Land - ", 7) == 0)
	  || (strcmp(card->color, "n/a") == 0)
	  || (strcmp(card->color, "N/A") == 0)))
	{
		/* Deduce from land text. */
		card->color = assign_string(card->color, "");
		if (strstr(card->text, "W"))
			card->color = append_string(card->color, "W");
		if (strstr(card->text, "U"))
			card->color = append_string(card->color, "U");
		if (strstr(card->text, "B"))
			card->color = append_string(card->color, "B");
		if (strstr(card->text, "R"))
			card->color = append_string(card->color, "R");
		if (strstr(card->text, "G"))
			card->color = append_string(card->color, "G");
		if (strstr(card->text, "mana of any colo"))
			card->color = append_string(card->color, "Multi");
		if (strlen(card->color) > 2)
			card->color = assign_string(card->color, "Multi");
		card->color = prepend_string("Land", card->color);
	}
	if ((! card->color) && (card->cost[0]))
	{
		/* Deduce from casting cost. */
		if (strstr(card->cost, "W"))
			card->color = append_string(card->color, "W");
		if (strstr(card->cost, "U"))
			card->color = append_string(card->color, "U");
		if (strstr(card->cost, "B"))
			card->color = append_string(card->color, "B");
		if (strstr(card->cost, "R"))
			card->color = append_string(card->color, "R");
		if (strstr(card->cost, "G"))
			card->color = append_string(card->color, "G");

		if (! card->color)
			card->color = assign_string(card->color, "Silver");
		else if (strlen(card->color) > 2)
			card->color = assign_string(card->color, "Gold");
	}
	if (card->color)
	{
		/* Now normalise the color strings used. */
		if (! card->color[0])
			card->color = assign_string(card->color, "Silver");
		else if (strcmp(card->color, "A") == 0)
			card->color = assign_string(card->color, "Silver");
		else if (strcmp(card->color, "Artifact") == 0)
			card->color = assign_string(card->color, "Silver");
		else if (strcmp(card->color, "Z") == 0)
			card->color = assign_string(card->color, "Gold");
		else if (strcmp(card->color, "Multicolor") == 0)
			card->color = assign_string(card->color, "Gold");
		else if (strcmp(card->color, "Multi-color") == 0)
			card->color = assign_string(card->color, "Gold");
		else if (strcmp(card->color, "Multicolored") == 0)
			card->color = assign_string(card->color, "Gold");
		else if (strcmp(card->color, "Multi-colored") == 0)
			card->color = assign_string(card->color, "Gold");
		else if (strcmp(card->color, "Multicolour") == 0)
			card->color = assign_string(card->color, "Gold");
		else if (strcmp(card->color, "Multi-colour") == 0)
			card->color = assign_string(card->color, "Gold");
		else if (strcmp(card->color, "Multicoloured") == 0)
			card->color = assign_string(card->color, "Gold");
		else if (strcmp(card->color, "Multi-coloured") == 0)
			card->color = assign_string(card->color, "Gold");
		else if (strcmp(card->color, "W") == 0)
			card->color = assign_string(card->color, "White");
		else if (strcmp(card->color, "U") == 0)
			card->color = assign_string(card->color, "Blue");
		else if (strcmp(card->color, "B") == 0)
			card->color = assign_string(card->color, "Black");
		else if (strcmp(card->color, "R") == 0)
			card->color = assign_string(card->color, "Red");
		else if (strcmp(card->color, "G") == 0)
			card->color = assign_string(card->color, "Green");
		else if (strcmp(card->color, "WU") == 0)
			card->color = assign_string(card->color, "WhiteBlue");
		else if (strcmp(card->color, "WB") == 0)
			card->color = assign_string(card->color, "WhiteBlack");
		else if (strcmp(card->color, "UB") == 0)
			card->color = assign_string(card->color, "BlueBlack");
		else if (strcmp(card->color, "UR") == 0)
			card->color = assign_string(card->color, "BlueRed");
		else if (strcmp(card->color, "BR") == 0)
			card->color = assign_string(card->color, "BlackRed");
		else if (strcmp(card->color, "BG") == 0)
			card->color = assign_string(card->color, "BlackGreen");
		else if (strcmp(card->color, "RG") == 0)
			card->color = assign_string(card->color, "RedGreen");
		else if (strcmp(card->color, "RW") == 0)
			card->color = assign_string(card->color, "RedWhite");
		else if (strcmp(card->color, "GW") == 0)
			card->color = assign_string(card->color, "GreenWhite");
		else if (strcmp(card->color, "GU") == 0)
			card->color = assign_string(card->color, "GreenBlue");
	}

	/* Fix mana symbols */

	card->cost = find_replace(card->cost, " ", "");
	card->cost = find_replace(card->cost, "\t", "");
	card->cost = find_replace(card->cost, "\n", "");
	card->cost = find_replace(card->cost, "w", "W");
	card->cost = find_replace(card->cost, "u", "U");
	card->cost = find_replace(card->cost, "b", "B");
	card->cost = find_replace(card->cost, "r", "R");
	card->cost = find_replace(card->cost, "g", "G");
	card->cost = find_replace(card->cost, "x", "X");
	card->cost = find_replace(card->cost, "10", "~");
	card->cost = find_replace(card->cost, "11", "!");
	card->cost = find_replace(card->cost, "12", "@");
	card->cost = find_replace(card->cost, "13", "#");
	card->cost = find_replace(card->cost, "14", "$");
	card->cost = find_replace(card->cost, "15", "%");
	card->cost = find_replace(card->cost, "16", "^");
	card->cost = find_replace(card->cost, "17", "&");
	card->cost = find_replace(card->cost, "18", "*");
	card->cost = find_replace(card->cost, "19", "(");
	card->cost = find_replace(card->cost, "20", ")");

	/* Convert split-mana symbols to letters in the font. */

	card->cost = find_replace(card->cost, "(WU)",  "Q");
	card->cost = find_replace(card->cost, "(W/U)", "Q");
	card->cost = find_replace(card->cost, "W/U",   "Q");

	card->cost = find_replace(card->cost, "(UW)",  "Q");
	card->cost = find_replace(card->cost, "(U/W)", "Q");
	card->cost = find_replace(card->cost, "U/W",   "Q");

	card->cost = find_replace(card->cost, "(WB)",  "q");
	card->cost = find_replace(card->cost, "(W/B)", "q");
	card->cost = find_replace(card->cost, "W/B",   "q");

	card->cost = find_replace(card->cost, "(BW)",  "q");
	card->cost = find_replace(card->cost, "(B/W)", "q");
	card->cost = find_replace(card->cost, "B/W",   "q");

	card->cost = find_replace(card->cost, "(UB)",  "Y");
	card->cost = find_replace(card->cost, "(U/B)", "Y");
	card->cost = find_replace(card->cost, "U/B",   "Y");

	card->cost = find_replace(card->cost, "(BU)",  "Y");
	card->cost = find_replace(card->cost, "(B/U)", "Y");
	card->cost = find_replace(card->cost, "B/U",   "Y");

	card->cost = find_replace(card->cost, "(UR)",  "y");
	card->cost = find_replace(card->cost, "(U/R)", "y");
	card->cost = find_replace(card->cost, "U/R",   "y");

	card->cost = find_replace(card->cost, "(RU)",  "y");
	card->cost = find_replace(card->cost, "(R/U)", "y");
	card->cost = find_replace(card->cost, "R/U",   "y");

	card->cost = find_replace(card->cost, "(BR)",  "V");
	card->cost = find_replace(card->cost, "(B/R)", "V");
	card->cost = find_replace(card->cost, "B/R",   "V");

	card->cost = find_replace(card->cost, "(RB)",  "V");
	card->cost = find_replace(card->cost, "(R/B)", "V");
	card->cost = find_replace(card->cost, "R/B",   "V");

	card->cost = find_replace(card->cost, "(BG)",  "v");
	card->cost = find_replace(card->cost, "(B/G)", "v");
	card->cost = find_replace(card->cost, "B/G",   "v");

	card->cost = find_replace(card->cost, "(GB)",  "v");
	card->cost = find_replace(card->cost, "(G/B)", "v");
	card->cost = find_replace(card->cost, "G/B",   "v");

	card->cost = find_replace(card->cost, "(RG)",  "E");
	card->cost = find_replace(card->cost, "(R/G)", "E");
	card->cost = find_replace(card->cost, "R/G",   "E");

	card->cost = find_replace(card->cost, "(GR)",  "E");
	card->cost = find_replace(card->cost, "(G/R)", "E");
	card->cost = find_replace(card->cost, "G/R",   "E");

	card->cost = find_replace(card->cost, "(RW)",  "e");
	card->cost = find_replace(card->cost, "(R/W)", "e");
	card->cost = find_replace(card->cost, "R/W",   "e");

	card->cost = find_replace(card->cost, "(WR)",  "e");
	card->cost = find_replace(card->cost, "(W/R)", "e");
	card->cost = find_replace(card->cost, "W/R",   "e");

	card->cost = find_replace(card->cost, "(GW)",  "F");
	card->cost = find_replace(card->cost, "(G/W)", "F");
	card->cost = find_replace(card->cost, "G/W",   "F");

	card->cost = find_replace(card->cost, "(WG)",  "F");
	card->cost = find_replace(card->cost, "(W/G)", "F");
	card->cost = find_replace(card->cost, "W/G",   "F");

	card->cost = find_replace(card->cost, "(GU)",  "f");
	card->cost = find_replace(card->cost, "(G/U)", "f");
	card->cost = find_replace(card->cost, "G/U",   "f");

	card->cost = find_replace(card->cost, "(UG)",  "f");
	card->cost = find_replace(card->cost, "(U/G)", "f");
	card->cost = find_replace(card->cost, "U/G",   "f");

	/* Standardise split-mana symbols in the rules text. */

	card->text = find_replace(card->text, "(WU)",  "W/U");
	card->text = find_replace(card->text, "(W/U)", "W/U");

	card->text = find_replace(card->text, "(UW)",  "U/W");
	card->text = find_replace(card->text, "(U/W)", "U/W");

	card->text = find_replace(card->text, "(WB)",  "W/B");
	card->text = find_replace(card->text, "(W/B)", "W/B");

	card->text = find_replace(card->text, "(BW)",  "B/W");
	card->text = find_replace(card->text, "(B/W)", "B/W");

	card->text = find_replace(card->text, "(UB)",  "U/B");
	card->text = find_replace(card->text, "(U/B)", "U/B");

	card->text = find_replace(card->text, "(BU)",  "B/U");
	card->text = find_replace(card->text, "(B/U)", "B/U");

	card->text = find_replace(card->text, "(UR)",  "U/R");
	card->text = find_replace(card->text, "(U/R)", "U/R");

	card->text = find_replace(card->text, "(RU)",  "R/U");
	card->text = find_replace(card->text, "(R/U)", "R/U");

	card->text = find_replace(card->text, "(BR)",  "B/R");
	card->text = find_replace(card->text, "(B/R)", "B/R");

	card->text = find_replace(card->text, "(RB)",  "R/B");
	card->text = find_replace(card->text, "(R/B)", "R/B");

	card->text = find_replace(card->text, "(BG)",  "B/G");
	card->text = find_replace(card->text, "(B/G)", "B/G");

	card->text = find_replace(card->text, "(GB)",  "G/B");
	card->text = find_replace(card->text, "(G/B)", "G/B");

	card->text = find_replace(card->text, "(RG)",  "R/G");
	card->text = find_replace(card->text, "(R/G)", "R/G");

	card->text = find_replace(card->text, "(GR)",  "G/R");
	card->text = find_replace(card->text, "(G/R)", "G/R");

	card->text = find_replace(card->text, "(RW)",  "R/W");
	card->text = find_replace(card->text, "(R/W)", "R/W");

	card->text = find_replace(card->text, "(WR)",  "W/R");
	card->text = find_replace(card->text, "(W/R)", "W/R");

	card->text = find_replace(card->text, "(GW)",  "G/W");
	card->text = find_replace(card->text, "(G/W)", "G/W");

	card->text = find_replace(card->text, "(WG)",  "W/G");
	card->text = find_replace(card->text, "(W/G)", "W/G");

	card->text = find_replace(card->text, "(GU)",  "G/U");
	card->text = find_replace(card->text, "(G/U)", "G/U");

	card->text = find_replace(card->text, "(UG)",  "U/G");
	card->text = find_replace(card->text, "(U/G)", "U/G");

	/* Standardise rules text TAP symbols as a single T. */

	card->text = find_replace(card->text, "TAP", "T");
	card->text = find_replace(card->text, "{T}", "T");
	card->text = find_replace(card->text, "Tap, ", "T, ");
	card->text = find_replace(card->text, "Tap: ", "T: ");

	/* Convert Microsoft code page 1252 to ASCII for now. */

	for (i=0; i < 32; i++)
	{
		char *cp1252 = CP1252_to_ASCII[i].utf8;
		char *ascii = CP1252_to_ASCII[i].ascii;

		card->text   = find_replace(card->text,   cp1252, ascii);
		card->flavor = find_replace(card->flavor, cp1252, ascii);
		card->type   = find_replace(card->type,   cp1252, ascii);
		card->artist = find_replace(card->artist, cp1252, ascii);
	}

	/* Remove tabs. */

	card->text   = find_replace(card->text,   "\t", " ");
	card->flavor = find_replace(card->flavor, "\t", " ");
	card->type   = find_replace(card->type,   "\t", " ");
	card->artist = find_replace(card->artist, "\t", " ");

	/* Set up font styles for the text. */

	card->textfnt = app_zero_alloc(strlen(card->text));
	set_italic_font(card->text, card->textfnt);
	set_mana_font(card->text, card->textfnt);

	/* A special work-around in case the algorithms don't display X right. */
	/* In that case, use lower-case x, which will never be converted to */
	/* a mana-symbol, and the below code changes it to a plain uppercase X. */

	card->text   = find_replace(card->text,   " x ", " X ");
	card->text   = find_replace(card->text,   " x,", " X,");
	card->text   = find_replace(card->text,   " x.", " X.");
	if ((card->text[0] == 'x') && (card->text[1] == ' '))
		card->text[0] = 'X';

	/* Make entire flavour text italic. */

	card->flavfnt = app_zero_alloc(strlen(card->flavor));
	for (i=0; card->flavor[i] != '\0'; i++)
		card->flavfnt[i] = ITALIC_FONT;

	/* Ensure card background uses the right kind of border. */

	card->bg = assign_string(card->bg, "Black_Border/");
	card->bg = append_string(card->bg, card->color);
	card->bg = append_string(card->bg, ".png");

	return card;
}

void del_card(Card *card)
{
	app_free(card->bg);
	app_free(card->name);
	app_free(card->cost);
	app_free(card->color);
	app_free(card->rarity);
	app_free(card->artfile);
	app_free(card->artist);
	app_free(card->type);
	app_free(card->text);
	app_free(card->textfnt);
	app_free(card->flavor);
	app_free(card->flavfnt);
	app_free(card->powtgh);
	app_free(card->expsym);
	app_free(card->rulings);
	if (card->flip)
		del_card(card->flip);
	app_free(card);
}

Card ** load_cards(char *filepath, int *count)
{
	FILE *f;
	char *data, *utf8, *end;
	Card *card;
	long nbytes, nchars;
	int numbytes;
	int n = 0;
	Card **list = NULL;

	if (count != NULL)
		*count = 0;
	f = app_open_file(filepath, "rb");
	if (f == NULL) {
		if (log != NULL) {
			fprintf(log, "Couldn't open spoiler file %s\n", filepath);
			fflush(log);
		}
		return NULL;
	}

	data = app_read_utf8_file(f, &nbytes, &nchars);
	numbytes = nbytes;
	utf8 = app_correct_utf8(data, &numbytes);
	if (utf8 == NULL)
		utf8 = data;
	else if (utf8 != data)
		app_free(data);
	convert_CRNL_to_NL(utf8);

	for (data = utf8; data != NULL; data = end)
	{
		while (*data == '\n')
			++data;
		if (*data == '\0')
			break;
		end = strstr(data, "\n\n");
		if (end)
			*end++ = '\0';

		card = parse_card(data);
		if (! card)
			break;
		else if ((! card->name) || (! card->name[0]))
		{
			del_card(card);
			continue;
		}

		list = app_realloc(list, sizeof(Card) * (n+2));
		list[n] = card;
		list[n+1] = NULL;
		n++;

		if (log != NULL) {
			fprintf(log, "Parsed card %s\n", card->name);
			fflush(log);
		}
	}

	app_free(utf8);
	app_close_file(f);

	if (log != NULL) {
		fprintf(log, "%d cards loaded from file %s\n\n", n, filepath);
		fflush(log);
	}
	fprintf(stderr, "%d cards loaded from file %s\n", n, filepath);

	if (count != NULL)
		*count = n;
	return list;
}

/*
 *  Convert the string to a safe form (for creating a file name).
 *  If necessary, create a new string and delete the old one.
 */
char *escape(char *dst)
{
	int i;
	unsigned char ch;
	char *src;
	char *d;

	for (i = 0; i < NELEM(CP1252_to_ASCII); i++)
	{
		dst = find_replace(dst, CP1252_to_ASCII[i].utf8, CP1252_to_ASCII[i].ascii);
	}

	for (i = 0; i < NELEM(Latin1_to_ASCII); i++)
	{
		dst = find_replace(dst, Latin1_to_ASCII[i].utf8, Latin1_to_ASCII[i].ascii);
	}

	src = dst;
	d = dst;

	while ((ch = *src++) != '\0')
	{
		if (ch == ' ')
			*dst++ = '_';
		else if (ch == '\t')
			*dst++ = '_';
		else if (ch == '_')
			*dst++ = '_';
		else if (ch == '-')
			*dst++ = '_';
		else if (ch == '.')
			*dst++ = '.';
		else if (ch == '/')
			*dst++ = '/';
		else if ((ch >= 1) && (ch <= '/'))
			;
		else if ((ch >= ':') && (ch <= '@'))
			;
		else if ((ch >= '[') && (ch <= '`'))
			;
		else if ((ch >= '{') && (ch <= 0x7F))
			;
		else
			*dst++ = ch;
	}
	*dst = '\0';
	return d;
}

void save_card(Image *full, char *name, int index)
{
	Image *img;

	char *card_name = app_alloc(20 + strlen(name));

	strcpy(card_name, dpi300);
	app_make_folder(card_name, 0755);

	strcat(card_name, name);
	strcat(card_name, ".png");

	card_name = escape(card_name);

	if (full)
		img = full;
	else if (bmap)
		img = app_bitmap_to_image(bmap);
	if (img) {
		if (log != NULL) {
			fprintf(log, "Saving file %d %s\n", index+1, card_name);
			fflush(log);
		}

		app_write_image(img, card_name);
		if (img != full)
			app_del_image(img);

		fprintf(stderr, "%d ", index+1);
		fflush(stderr);
	}
	else {
		if (log != NULL) {
			fprintf(log, "Error with %s\n", card_name);
			fflush(log);
		}

		fprintf(stderr, "x");
		fflush(stderr);
	}

	app_free(card_name);
}

int main(int argc, char *argv[])
{
	Card *card;
	Card **cards;
	int num_cards;
	int i;

	log = fopen(logfile, "w");
	if (log != NULL) {
		time_t t = time(NULL);
		struct tm *tm = localtime(&t);
		char *nowtext = asctime(tm);
		fprintf(log, "Output from GenCard run on %s\n", nowtext);
		fflush(log);
	}

	if (argc < 2)
	{
		if (log != NULL) {
			fprintf(log, "usage: gencard spoiler_list_filename\n");
			fflush(log);
		}
		fprintf(stderr, "usage: gencard spoiler_list_filename\n");
		return 1;
	}

	app = app_new_app(argc, argv);
	w = app_new_window(app, rect(20, 20, 760, 1064),
				"Card Constructor",
				STANDARD_WINDOW);
	c = app_new_control(w, rect(2, 2, 756, 1060));

	init_fonts(app);

	cards = load_cards(argv[1], &num_cards);

	for (i=0; i < num_cards; i++) {
		full = create_card_image(cards[i]);
		if (full == NULL)
			return -1;
		save_card(full, cards[i]->name, i);
	}
	fprintf(stderr, "\n");

/*
	app_on_control_redraw(c, show_card_in_control);
	app_on_window_resize(w, reshape_window);
	app_show_window(w);
	app_main_loop(app);
	app_del_app(app);
*/
	if (log != NULL)
		fclose(log);

	return 0;
}
