/*
 *  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 *name_list = NULL;
Control *total_label = NULL;
Control *made_label = NULL;
Font *screen_font;
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 *dpi100  = "DPI100/";
char *dpi075  = "DPI075/";
char *page_prefix  = "page";
char *photo_prefix = "photo";

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

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 *	number;	/* 32/110 */
	char *	rulings;/* <multi-line set of rulings> */
	Card *	flip;	/* NULL, or the upside-down card data */
};

/*
 *  Some globals, to simplify things.
 */
Card *card = NULL;
Card **cards = NULL;
char **names = NULL;
int num_cards = 0;
int completed = 0;
int proceeding = 1;

int current = -1;

int make_all = 1;
int make_current = 0;
int make_300dpi = 1;
int make_150dpi = 0;
int make_100dpi = 0;
int make_075dpi = 0;
int make_9_page = 0;
int make_2_page = 0;

MenuItem *mi_make;
MenuItem *mi_stop;
MenuItem *mi_all;
MenuItem *mi_current;
MenuItem *mi_300dpi;
MenuItem *mi_150dpi;
MenuItem *mi_100dpi;
MenuItem *mi_075dpi;
MenuItem *mi_9_page;
MenuItem *mi_2_page;

/*
 *  Conversions to readable characters:
 */
#define NELEM(arr) (sizeof((arr))/sizeof((arr)[0]))

char * open_single_quote  = "\xc2\x91";
char * close_single_quote = "\xc2\x92";
char * open_double_quote  = "\xc2\x93";
char * close_double_quote = "\xc2\x94";

#define en_dash  "\xc2\x96"
#define em_dash  "\xc2\x97"

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"}		/*  */
};

/*
 *  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,
	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, word_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 = word_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 */
			word_width = 0;
			if (line_width == 0)
				r.height /= 5;
			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 ((gb == 1) && (*glyph == ' '))
			word_width = 0;

		if (glyph_width + line_width <= pixel_width)
		{
			/* if the glyph fits, keep it */
			word_width += glyph_width;
			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;
		line_width -= word_width;
	}

	*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, width;
	char *line;
	Rect r;

	if (nb == 0)
		return 0;

	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;
		line_height = line_stats.height;

		/* discard spaces at start of line */
		end = line+nl;
		for (s=line; (s < end) && (*s == ' '); s++, start_styles++)
			nl--;
		/* discard spaces at end of line */
		while ((--end > s) && (*end == ' '))
			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;
	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 + r.width;
		if (! right_to_left)
			p.x -= line_stats.width;
		line_height = line_stats.height;

		/* discard spaces at start of line */
		end = line+nl;
		for (s=line; (s < end) && (*s == ' '); s++, start_styles++)
			nl--;
		/* discard spaces at end of line */
		while ((--end > s) && (*end == ' '))
			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_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;
	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);
		line_height = line_stats.height;
		w = line_stats.width;
		p.x = r.x + (r.width - w) / 2;
		if (right_to_left)
			p.x += w;

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

		style_utf8(g, fonts, p, s, 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", ANTI_ALIAS, 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);
	}
}

/*
 *  Return the largest centred rectangle within the given image
 *  which has the same aspect ratio as the given width:height ratio.
 */
Rect largest_rect(Rect sr, int width, int height)
{
	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;
}

void show_card_in_window(Window *w, Graphics *g)
{
	Rect ir, dr;
	Rect r = app_get_window_area(w);

	r.y += 30;
	r.height -= 50;
	if (full) {
		ir = app_get_image_area(full);
		dr = largest_rect(r, ir.width, ir.height);
		dr.x = r.x + r.width - dr.width - 2;
		app_draw_image(g, dr, full, ir);
	}

	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 ir, dr;
	Rect r = app_get_window_area(w);

	/* Resize the name listbox. */
	ir = rect(0, 0, 744, 1042);
	r = rect(2, 30, r.width - 4, r.height - 50);
	dr = largest_rect(r, ir.width, ir.height);
	if (dr.width < 40)
		dr.width = 40;
	if (dr.height < 40)
		dr.height = 40;
	dr.x = 2;
	dr.width = r.width - dr.width - 6;
	app_size_control(name_list, dr);

	/* Move the labels. */
	r = app_get_window_area(w);
	app_set_control_area(total_label, rect(2, r.height-20, 120, 20));
	app_set_control_area(made_label, rect(r.width-122, r.height-20, 120, 20));
}

void close_window(Window *w)
{
	proceeding = 0;
	app_hide_window(w);
}

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

	if ((textbytes == 1) && (flavbytes == 0)) {
		app_set_font(g, mana_font);
		app_draw_text(g, r, ALIGN_CENTRE | VALIGN_CENTRE,
				text, textbytes);
		return;
	}

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

/*
 *  Some image utilities:
 */

void rotate_image_32bpp_180_degrees(Image *img)
{
	/* Rotate in-place. */
	int x1, y1, x2, y2;
	int w, h;
	Colour c;
	Colour *row1, *row2;

	if (img == NULL)
		return;

	w = img->width - 1;
	h = img->height - 1;

	for (y1 = 0, y2 = h; y1 < y2; y1++, y2--)
	{
		row1 = img->data32[y1];
		row2 = img->data32[y2];
		for (x1 = 0, x2 = w; 0 <= x2; x1++, x2--)
		{
			c = row1[x1];
			row1[x1] = row2[x2];
			row2[x2] = c;
		}
	}
	if (y1 == y2) {
		row1 = row2 = img->data32[y1];
		for (x1 = 0, x2 = w; x1 < x2; x1++, x2--)
		{
			c = row1[x1];
			row1[x1] = row2[x2];
			row2[x2] = c;
		}
	}
}

Image *image_32bpp_rotated_180_degrees(Image *img)
{
	/* Rotate in-place. */
	int x, y;
	int w, h;
	Colour *row1, *row2;
	Image *rot;

	if (img == NULL)
		return NULL;

	rot = app_new_image(img->width, img->height, 32);
	if (rot == NULL)
		return NULL;

	w = img->width - 1;
	h = img->height - 1;

	for (y = 0; y <= h; y++)
	{
		row1 = img->data32[y];
		row2 = rot->data32[h - y];
		for (x = 0; x <= w; x++)
		{
			row2[w - x] = row1[x];
		}
	}
}

Image *image_32bpp_rotated_90_degrees(Image *img) /* anticlockwise */
{
	int x, y;
	int w, h;
	Colour c;
	Image *rot;

	if (img == NULL)
		return NULL;

	rot = app_new_image(img->height, img->width, 32);
	if (rot == NULL)
		return NULL;

	w = img->width;
	h = img->height;

	for (y = 0; y < h; y++)
	{
		for (x = 0; x < w; x++)
		{
			rot->data32[x][y] = img->data32[y][x];
		}
	}

	return rot;
}

/*
 *  Produce a split-colour card effect. img1 is modified.
 *  img1 will remain on the left, but img2's pixels will
 *  start appearing as you move right, until on the right
 *  only img2's pixels will be seen.
 *  Assume the two images are 32bpp and the same size.
 */
void blend_two_images_32bpp_horizontally(Image *img1, Image *img2)
{
	int x, y;
	int start, end;
	int freq, max, len;
	Colour c1, c2, c;
	int size = 64;
	int middle = img1->width / 2;

	for (y = 0; y < img1->height; y++)
	{
		/* slight zig-zag blend */
		if ((y % 8) < 4)
			len = size + (y % 8) - 2;
		else
			len = size + (6 - (y % 8));

		/* centre the blend region */
		start = middle - len;
		end = middle + len;
		max = end - start;

		for (x = start; x <= end; x++)
		{
			c1 = img1->data32[y][x];
			c2 = img2->data32[y][x];

			freq = x-start;
			c.alpha = 0;
			c.red   = (c2.red   * freq + c1.red   * (max - freq)) / max;
			c.green = (c2.green * freq + c1.green * (max - freq)) / max;
			c.blue  = (c2.blue  * freq + c1.blue  * (max - freq)) / max;
			img1->data32[y][x] = c;
		}
		for (x = end + 1; x < img1->width; x++)
		{
			img1->data32[y][x] = img2->data32[y][x];
		}
	}
}

/*
 *  Produce a split-colour card effect down the card.
 *  img1 is modified.
 *  img1 will remain on the top, but img2's pixels will
 *  start appearing as you move down, until at the bottom
 *  only img2's pixels will be seen.
 *  Assume the two images are 32bpp and the same size.
 */
void blend_two_images_32bpp_vertically(Image *img1, Image *img2)
{
	int x, y;
	int start, end;
	int freq, max, len;
	Colour c1, c2, c;
	int size = 64;
	int middle = img1->height / 2;

	for (x = 0; x < img1->width; x++)
	{
		/* slight zig-zag blend */
		if ((x % 8) < 4)
			len = size + (x % 8) - 2;
		else
			len = size + (6 - (x % 8));

		/* centre the blend region */
		start = middle - len;
		end = middle + len;
		max = end - start;

		for (y = start; y <= end; y++)
		{
			c1 = img1->data32[y][x];
			c2 = img2->data32[y][x];

			freq = y-start;
			c.alpha = 0;
			c.red   = (c2.red   * freq + c1.red   * (max - freq)) / max;
			c.green = (c2.green * freq + c1.green * (max - freq)) / max;
			c.blue  = (c2.blue  * freq + c1.blue  * (max - freq)) / max;
			img1->data32[y][x] = c;
		}
		for (y = end + 1; y < img1->height; y++)
		{
			img1->data32[y][x] = img2->data32[y][x];
		}
	}
}


/*
 *  Composite together the pieces of a Magic card.
 */
Image * create_card_image_normal(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 && card->artfile[0])
	{
		art = app_read_image(card->artfile, 32);
		if (art != NULL)
		{
			scaled_art = app_scale_image(art,
					rect(0,0,570,460),
					largest_rect(app_get_image_area(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));
	}

	app_del_graphics(g);

	return full;
}

Image * create_card_image_flipped(Card *card)
{
	Graphics *g;
	Image *full;
	Image *art;
	Image *scaled_art;
	int vertical_blend;
	Image *rot;
	Card *flip = card->flip;

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

	if (flip->bg[0]) {
		vertical_blend = 1;
		rot = app_read_image(flip->bg, 32);
		if (rot)
			rotate_image_32bpp_180_degrees(rot);
		else
			vertical_blend = 0;
	}
	else {
		vertical_blend = 0;
		rot = NULL;
	}

	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 && card->artfile[0])
	{
		art = app_read_image(card->artfile, 32);
		if (art != NULL)
		{
			scaled_art = app_scale_image(art,
					rect(0,0,570,460),
					largest_rect(app_get_image_area(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)
	{
		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));
	}
*/

	if (rot) {
		app_del_graphics(g);
		g = app_get_image_graphics(rot);
	}
	else {
		rotate_image_32bpp_180_degrees(full);
	}

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

	if (rot) {
		rotate_image_32bpp_180_degrees(rot);
		blend_two_images_32bpp_vertically(full, rot);
		app_del_image(rot);
	}
	else {
		rotate_image_32bpp_180_degrees(full);
	}

	app_del_graphics(g);

	return full;
}

Image * create_card_image(Card *card)
{
	if (card->flip)
		return create_card_image_flipped(card);
	else
		return create_card_image_normal(card);
}

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

/*
 *  Return a new string where boring quotes like " have
 *  been turned into fancy open and close double quotes.
 *  Because the string might change length, a new string
 *  is returned, and the old one destroyed.
 */
char *	quotes_to_fancy_quotes(char *src)
{
	int i, length, extras, ch, prev;
	char *original;
	char *dst;
	char *result;
	int in_double_quotes;

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

	/* Count pattern occurrences to calculate new length. */
	/* Then allocate a buffer to store the new string. */
	length = 0;
	for (i = 0; src[i] != '\0'; i++)
	{
		length++;
		if ((src[i] == '"') || (src[i] == '\''))
			extras++;
	}
	if (extras == 0)
		return src;
	result = app_zero_alloc(length + extras + 1);
	if (result == NULL)
		return NULL; /* failed: insufficient memory */

	in_double_quotes = 0;
	prev = ' ';
	original = src;
	for (dst = result; (ch = *src) != '\0'; src++)
	{
		if (ch == '"') {
			if (! in_double_quotes) {
				*dst++ = open_double_quote[0];
				*dst++ = open_double_quote[1];
			}
			else {
				*dst++ = close_double_quote[0];
				*dst++ = close_double_quote[1];
			}

			/* invert status */
			in_double_quotes = 1 - in_double_quotes;
		}
		else if (ch == '\'') {
			if ((prev == ' ') || (prev == '"')) {
				*dst++ = open_single_quote[0];
				*dst++ = open_single_quote[1];
			}
			else {
				*dst++ = close_single_quote[0];
				*dst++ = close_single_quote[1];
			}
		}
		else {
			*dst++ = ch;
		}
		prev = ch;
	}
	app_free(original);
	*dst = '\0';
	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;
	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 = app_open_file(filename, "rb")) != NULL)
	{
		app_close_file(f);
		return filename;
	}

	flen = strlen(filename);
	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 = app_open_file(trythis, "rb")) != NULL)
		{
			app_close_file(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 = app_open_file(trythis, "rb")) != NULL)
		{
			app_close_file(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 = app_open_file(trythis, "rb")) != NULL)
		{
			app_close_file(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 src, dst;
	int inside_parentheses = 0;
	int inside_tildes = 0;

	/* Everything inside matching parentheses becomes italic. */
	for (src = 0; text[src] != '\0'; src++)
	{
		if (text[src] == '(')
			inside_parentheses++;

		if (inside_parentheses)
			textfnt[src] = ITALIC_FONT;

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

	/* Everything inside matching tildes reverses its italicisation. */
	/* The tildes are removed. An exception is " ~ " which is left alone. */
	for (src = dst = 0; text[src] != '\0'; )
	{
		if ((src > 0)
		 && (text[src-1] == ' ')
		 && (text[src]   == '~')
		 && (text[src+1] == ' '))
		{
			/* no change to status */
		}
		else if (text[src] == '~')
		{
			inside_tildes = 1 - inside_tildes;
			src++;
			continue;
		}

		if (inside_tildes) {
			if (textfnt[src] == NORMAL_FONT)
				textfnt[dst] = ITALIC_FONT;
			else if (textfnt[src] == ITALIC_FONT)
				textfnt[dst] = NORMAL_FONT;
		}
		else {
			textfnt[dst] = textfnt[src];
		}

		text[dst++]  = text[src++];
	}
	text[dst] = '\0';
}

/*
 *  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 > 5)
				      && (strncmp(line+i-5, "plus ", 5) == 0))
					found = 0;
				else if ((i > 6)
				      && (strncmp(line+i-6, "minus ", 6) == 0))
					found = 0;
				else if ((i > 6)
				      && (strncmp(line+i-6, "deals ", 6) == 0))
					found = 0;
				else if ((i > 7)
				      && (strncmp(line+i-7, "roll a ", 7) == 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 == '/') {
			if (found == 1) {
				found = 0;
				prev = 'a';
			}
			/* otherwise 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)
			      || (strncmp(line+i, " land", 5) == 0)
			      || (strncmp(line+i, " cards", 6) == 0)
			      || (strncmp(line+i, " damage", 7) == 0)
			      || (strncmp(line+i, " tokens", 7) == 0)
			      || (strncmp(line+i, " non", 4) == 0)
			      || (strncmp(line+i, " +", 2) == 0)
			      || (strncmp(line+i, " -", 2) == 0)
			      || (strncmp(line+i, " 0", 2) == 0)
			      || (strncmp(line+i, " 1", 2) == 0)
			      || (strncmp(line+i, " 2", 2) == 0)
			      || (strncmp(line+i, " 3", 2) == 0)
			      || (strncmp(line+i, " 4", 2) == 0)
			      || (strncmp(line+i, " 5", 2) == 0)
			      || (strncmp(line+i, " 6", 2) == 0)
			      || (strncmp(line+i, " 7", 2) == 0)
			      || (strncmp(line+i, " 8", 2) == 0)
			      || (strncmp(line+i, " 9", 2) == 0)
			      || (strncmp(line+i, " *", 2) == 0)
			      || (strncmp(line+i, " white ", 7) == 0)
			      || (strncmp(line+i, " blue ", 6) == 0)
			      || (strncmp(line+i, " black ", 7) == 0)
			      || (strncmp(line+i, " red ", 5) == 0)
			      || (strncmp(line+i, " green " , 7) == 0)
			      || (strncmp(line+i, " target", 7) == 0)
			      || (strncmp(line+i, " tapped", 7) == 0)
			      || (strncmp(line+i, " charge", 7) == 0)
			      || (strncmp(line+i, " must be", 8) == 0)
			      || (strncmp(line+i, " untapped", 9) == 0)
			      || (strncmp(line+i, " counters", 9) == 0)
			      || (strncmp(line+i, " is rolled", 10) == 0)
			      || (strncmp(line+i, " attacking", 10) == 0)
			      || (strncmp(line+i, " creatures", 10) == 0)
			      || (strncmp(line+i, " permanents", 11) == 0)
			      || (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)))
		{
			/* trim spaces from start of line */
			while ((start[0] == ' ') || (start[0] == '\t'))
			{
				start++;
				length--;
			}

			/* ensure we have this line */
			line = app_realloc(line, total + length + 2);
			strncpy(line + total, start, length + 1);
			total += length + 1;
			line[total] = '\0';

			/* check if this is the end of the line */
			if (start[length] == '\0')
				break;
			if ((length > 0) && (start[length - 1] == '.'))
				;
			else if ((length > 1)
			      && (in(start[length - 2], ".!?"))
			      && (in(start[length - 1], ")\"~")))
				;
			else if (length < 16)
				; /* treat it as a single line */
			else
				line[total - 1] = ' '; /* replaces \n */

			/* trim spaces from end */
			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 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/Tou: */

	card->powtgh = find_line(block, "Pow/Tou:");
	if (! card->powtgh)
		card->powtgh = find_line(block, "Pow/Tgh:");
	if (! card->powtgh)
		card->powtgh = find_line(block, "Pow/Tough:");
	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->name, 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 Rarity: */

	card->rarity = find_line(block, "Rarity:");
	if (card->rarity && (!strcmp(card->rarity, "rare 6")))
		strcpy(card->rarity, "U");
	else if (card->rarity && (!strcmp(card->rarity, "Rare 6")))
		strcpy(card->rarity, "U");
	else if (card->rarity && strstr(card->rarity, "rare"))
		strcpy(card->rarity, "R");
	else if (card->rarity && strstr(card->rarity, "Rare"))
		strcpy(card->rarity, "R");
	else if (card->rarity && strstr(card->rarity, "uncommon"))
		strcpy(card->rarity, "U");
	else if (card->rarity && strstr(card->rarity, "Uncommon"))
		strcpy(card->rarity, "U");
	else if (card->rarity && strstr(card->rarity, "common"))
		strcpy(card->rarity, "C");
	else if (card->rarity && strstr(card->rarity, "Common"))
		strcpy(card->rarity, "C");
	else if (card->rarity && strstr(card->rarity, "land"))
		strcpy(card->rarity, "C");
	else if (card->rarity && strstr(card->rarity, "Land"))
		strcpy(card->rarity, "C");
	else if (card->rarity && card->rarity[0] == 'r')
		strcpy(card->rarity, "R");
	else if (card->rarity && card->rarity[0] == 'R')
		strcpy(card->rarity, "R");
	else if (card->rarity && card->rarity[0] == 'u')
		strcpy(card->rarity, "U");
	else if (card->rarity && card->rarity[0] == 'U')
		strcpy(card->rarity, "U");
	else if (card->rarity && card->rarity[0] == 'c')
		strcpy(card->rarity, "C");
	else if (card->rarity && card->rarity[0] == 'C')
		strcpy(card->rarity, "C");
	else if (card->rarity && card->rarity[0] == 'l')
		strcpy(card->rarity, "C");
	else if (card->rarity && card->rarity[0] == 'L')
		strcpy(card->rarity, "C");
	if (! card->rarity)
		card->rarity = app_copy_string("C");

	/* 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 Card Number: */

	card->number = find_line(block, "Card Number:");
	if (! card->number)
		card->number = find_line(block, "Number:");
	if (! card->number)
		card->number = find_line(block, "Card #:");
	if (! card->number)
		card->number = app_zero_alloc(1);

	/* Parse Rulings: */

	card->rulings = find_line(block, "Rulings:");
	if (! card->rulings)
		card->rulings = app_zero_alloc(1);

	/* 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 mana symbols in the rules text. */

	card->text = find_replace(card->text, "[X]",  "X");
	card->text = find_replace(card->text, "[W]",  "W");
	card->text = find_replace(card->text, "[U]",  "U");
	card->text = find_replace(card->text, "[B]",  "B");
	card->text = find_replace(card->text, "[R]",  "R");
	card->text = find_replace(card->text, "[G]",  "G");
	card->text = find_replace(card->text, "[0]",  "0");
	card->text = find_replace(card->text, "[1]",  "1");
	card->text = find_replace(card->text, "[2]",  "2");
	card->text = find_replace(card->text, "[3]",  "3");
	card->text = find_replace(card->text, "[4]",  "4");
	card->text = find_replace(card->text, "[5]",  "5");
	card->text = find_replace(card->text, "[6]",  "6");
	card->text = find_replace(card->text, "[7]",  "7");
	card->text = find_replace(card->text, "[8]",  "8");
	card->text = find_replace(card->text, "[9]",  "9");
	card->text = find_replace(card->text, "[10]",  "10");
	card->text = find_replace(card->text, "[11]",  "11");
	card->text = find_replace(card->text, "[12]",  "12");
	card->text = find_replace(card->text, "[13]",  "13");
	card->text = find_replace(card->text, "[14]",  "14");
	card->text = find_replace(card->text, "[15]",  "15");
	card->text = find_replace(card->text, "[16]",  "16");
	card->text = find_replace(card->text, "[17]",  "17");
	card->text = find_replace(card->text, "[18]",  "18");
	card->text = find_replace(card->text, "[19]",  "19");
	card->text = find_replace(card->text, "[20]",  "20");
	card->text = find_replace(card->text, "{X}",  "X");
	card->text = find_replace(card->text, "{W}",  "W");
	card->text = find_replace(card->text, "{U}",  "U");
	card->text = find_replace(card->text, "{B}",  "B");
	card->text = find_replace(card->text, "{R}",  "R");
	card->text = find_replace(card->text, "{G}",  "G");
	card->text = find_replace(card->text, "{0}",  "0");
	card->text = find_replace(card->text, "{1}",  "1");
	card->text = find_replace(card->text, "{2}",  "2");
	card->text = find_replace(card->text, "{3}",  "3");
	card->text = find_replace(card->text, "{4}",  "4");
	card->text = find_replace(card->text, "{5}",  "5");
	card->text = find_replace(card->text, "{6}",  "6");
	card->text = find_replace(card->text, "{7}",  "7");
	card->text = find_replace(card->text, "{8}",  "8");
	card->text = find_replace(card->text, "{9}",  "9");
	card->text = find_replace(card->text, "{10}",  "10");
	card->text = find_replace(card->text, "{11}",  "11");
	card->text = find_replace(card->text, "{12}",  "12");
	card->text = find_replace(card->text, "{13}",  "13");
	card->text = find_replace(card->text, "{14}",  "14");
	card->text = find_replace(card->text, "{15}",  "15");
	card->text = find_replace(card->text, "{16}",  "16");
	card->text = find_replace(card->text, "{17}",  "17");
	card->text = find_replace(card->text, "{18}",  "18");
	card->text = find_replace(card->text, "{19}",  "19");
	card->text = find_replace(card->text, "{20}",  "20");
	card->text = find_replace(card->text, "(X)",  "X");
	card->text = find_replace(card->text, "(W)",  "W");
	card->text = find_replace(card->text, "(U)",  "U");
	card->text = find_replace(card->text, "(B)",  "B");
	card->text = find_replace(card->text, "(R)",  "R");
	card->text = find_replace(card->text, "(G)",  "G");
	card->text = find_replace(card->text, "(0)",  "0");
	card->text = find_replace(card->text, "(1)",  "1");
	card->text = find_replace(card->text, "(2)",  "2");
	card->text = find_replace(card->text, "(3)",  "3");
	card->text = find_replace(card->text, "(4)",  "4");
	card->text = find_replace(card->text, "(5)",  "5");
	card->text = find_replace(card->text, "(6)",  "6");
	card->text = find_replace(card->text, "(7)",  "7");
	card->text = find_replace(card->text, "(8)",  "8");
	card->text = find_replace(card->text, "(9)",  "9");
	card->text = find_replace(card->text, "(10)",  "10");
	card->text = find_replace(card->text, "(11)",  "11");
	card->text = find_replace(card->text, "(12)",  "12");
	card->text = find_replace(card->text, "(13)",  "13");
	card->text = find_replace(card->text, "(14)",  "14");
	card->text = find_replace(card->text, "(15)",  "15");
	card->text = find_replace(card->text, "(16)",  "16");
	card->text = find_replace(card->text, "(17)",  "17");
	card->text = find_replace(card->text, "(18)",  "18");
	card->text = find_replace(card->text, "(19)",  "19");
	card->text = find_replace(card->text, "(20)",  "20");

	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->artist = find_replace(card->artist, cp1252, ascii);
		card->type   = find_replace(card->type,   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", " ");

	/* Add a newline to the end of the rules text if there is flavour text. */
	/* This just forces a whole line gap to exist between them. */
	if ((card->text[0]) && (card->flavor[0]))
		card->text = append_string(card->text, "\n");

	/* Duplicate the newlines in the rules text. */
	/* This forces a mini line gap between logically separate rules. */
	card->text = find_replace(card->text, "\n", "\n\n");

	/* Make the quotes fancier. */
	card->text   = quotes_to_fancy_quotes(card->text);
	card->flavor = quotes_to_fancy_quotes(card->flavor);

	/* Turn " - " and "--" into em-dashes. */
	card->text   = find_replace(card->text,   " - ", " " en_dash " ");
	card->text   = find_replace(card->text,   "--",  em_dash);
	card->flavor = find_replace(card->flavor, " - ", " " en_dash " ");
	card->flavor = find_replace(card->flavor, "--",  em_dash);
	card->type   = find_replace(card->type,   " - ", " " en_dash " ");
	card->type   = find_replace(card->type,   "--",  em_dash);

	/* Set up font styles for the text. */
	/* Note, set_mana_font must occur after set_italic_font. */
	/* Reason: set_italic_font interprets and removes ~ characters. */
	/* Also, set_mana_font changes the number 10 into ~ as a mana symbol. */
	/* The reverse order would cause interference between the two. */

	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;
	set_italic_font(card->flavor, card->flavfnt); /* normal font for some */

	/* 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 print_card(FILE *f, Card *card)
{
	int i, length;

	fprintf(f, "  Card Name: %s\n", card->name);
	fprintf(f, " Background: %s\n", card->bg);
	fprintf(f, "       Cost: %s\n", card->cost);
	fprintf(f, "      Color: %s\n", card->color);
	fprintf(f, "     Rarity: %s\n", card->rarity);
	fprintf(f, "    Artfile: %s\n", card->artfile);
	fprintf(f, "     Artist: %s\n", card->artist);
	fprintf(f, "       Type: %s\n", card->type);

	fprintf(f, " Rules Text: %s\n", card->text);

	fprintf(f, "  Text Font: ");
	length = strlen(card->text);
	for (i=0; i < length; i++)
	{
		if (card->textfnt[i] == NORMAL_FONT)
			fprintf(f, "N");
		else if (card->textfnt[i] == ITALIC_FONT)
			fprintf(f, "i");
		else if (card->textfnt[i] == MANA_FONT)
			fprintf(f, "m");
		else
			fprintf(f, " ");
	}
	fprintf(f, "\n");

	fprintf(f, "Flavor Text: %s\n", card->flavor);

	fprintf(f, "Flavor Font: ");
	length = strlen(card->flavor);
	for (i=0; i < length; i++)
	{
		if (card->flavfnt[i] == NORMAL_FONT)
			fprintf(f, "N");
		else if (card->flavfnt[i] == ITALIC_FONT)
			fprintf(f, "i");
		else if (card->flavfnt[i] == MANA_FONT)
			fprintf(f, "m");
		else
			fprintf(f, " ");
	}
	fprintf(f, "\n");

	fprintf(f, "    Pow/Tgh: %s\n", card->powtgh);
	fprintf(f, "  Expansion: %s\n", card->expsym);
	fprintf(f, "Card Number: %s\n", card->number);
	fprintf(f, "    Rulings: %s\n", card->rulings);
	if (card->flip) {
		fprintf(f, "---\n");
		print_card(f, card->flip);
	}
	fprintf(f, "\n");
}

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->number);
	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;
	char *flip;
	Card *card;
	long nbytes, nchars;
	int numbytes;
	int n = 0;
	Card **list = NULL;

	if (count != NULL)
		*count = 0;
	if (filepath[0] == '\0')
		return NULL;
	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;
		}
		if ((flip = strstr(data, "\n---\n")) != NULL)
			card->flip = parse_card(flip+5);
		else if ((flip = strstr(data, "\n///\n")) != NULL)
			card->flip = parse_card(flip+5);

		if (log != NULL)
			print_card(log, card);

		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 *to_safe_filename(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_300dpi(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 = to_safe_filename(card_name);

	if (full)
		img = full;
	else if (bmap)
		img = app_bitmap_to_image(bmap);
	if (img) {
		if (log != NULL) {
			fprintf(log, "Saving at 300 DPI 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);
}

void save_card_150dpi(Image *full, char *name, int index)
{
	Image *img = NULL;

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

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

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

	card_name = to_safe_filename(card_name);

	if (full) {
		Rect dr, sr;
		dr = sr = app_get_image_area(full);
		dr.width /= 2;
		dr.height /= 2;
		img = app_scale_image(full, dr, sr);
	}

	if (img) {
		if (log != NULL) {
			fprintf(log, "Saving at 150 DPI file %d %s\n", index+1, card_name);
			fflush(log);
		}

		app_write_image(img, card_name);
		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);
}

void save_card_100dpi(Image *full, char *name, int index)
{
	Image *img = NULL;

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

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

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

	card_name = to_safe_filename(card_name);

	if (full) {
		Rect dr, sr;
		dr = sr = app_get_image_area(full);
		dr.width /= 3;
		dr.height /= 3;
		img = app_scale_image(full, dr, sr);
	}

	if (img) {
		if (log != NULL) {
			fprintf(log, "Saving at 100 DPI file %d %s\n", index+1, card_name);
			fflush(log);
		}

		app_write_image(img, card_name);
		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);
}

void save_card_075dpi(Image *full, char *name, int index)
{
	Image *img = NULL;

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

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

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

	card_name = to_safe_filename(card_name);

	if (full) {
		Rect dr, sr;
		dr = sr = app_get_image_area(full);
		dr.width /= 4;
		dr.height /= 4;
		img = app_scale_image(full, dr, sr);
	}

	if (img) {
		if (log != NULL) {
			fprintf(log, "Saving at  75 DPI file %d %s\n", index+1, card_name);
			fflush(log);
		}

		app_write_image(img, card_name);
		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);
}

void save_card_9_page(Image *cards[9], char *name, int index)
{
	Image *img;
	Colour c;
	int x, y, row, w, h;

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

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

	sprintf(file_name, "%s%s-%02d.png", dpi300, name, index+1);

	file_name = to_safe_filename(file_name);

	/* Create a large 3x3 card page at 300 DPI from the source images. */
	w = cards[0]->width;
	h = cards[0]->height;
	c = WHITE;
	img = app_new_image(w * 3 + 2, h * 3 + 2, 32);
	for (y = 0; y < h * 3 + 2; y++)
	{
		for (x = 0; x < w * 3 + 2; x++)
		{
			img->data32[y][x] = c;
		}
	}

	for (row = 0; row < 3; row++)
	{
	  for (y = 0; y < h; y++)
	  {
	    for (x = 0; x < w; x++)
	    {
		if (cards[row*3])
		  img->data32[row * (h+1) + y][x    ] = cards[row*3  ]->data32[y][x];

		if (cards[row*3+1])
		  img->data32[row * (h+1) + y][x+w+1] = cards[row*3+1]->data32[y][x];

		if (cards[row*3+2])
		  img->data32[row * (h+1) + y][x+w+w+2] = cards[row*3+2]->data32[y][x];
	    }
	  }
	}

	if (img) {
		if (log != NULL) {
			fprintf(log, "Saving at 300 DPI file %s\n", file_name);
			fflush(log);
		}

		app_write_image(img, file_name);
		app_del_image(img);

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

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

	app_free(file_name);
}

void save_card_2_page(Image *cards[9], char *name, int index)
{
	Image *img;
	Colour c;
	int x, y, row, col, w, h;

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

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

	sprintf(file_name, "%s%s-%02d.png", dpi300, name, index+1);

	file_name = to_safe_filename(file_name);

	/* Create a large 2 card 4"x6" photo at 300 DPI from the source images. */
	w = cards[0]->width;
	h = cards[0]->height;
	c = WHITE;
	img = app_new_image(1800, 1200, 32);
	for (y = 0; y < 1200; y++)
	{
		for (x = 0; x < 1800; x++)
		{
			img->data32[y][x] = c;
		}
	}

	if (cards[0])
	{
	  Rect dr = rect(104, 79, 744, 1042);
	  int gap = 20;
	  c = GREY;

	  /* Draw the card image. */
	  for (y = 0; y < h; y++)
	  {
	    for (x = 0; x < w; x++)
	    {
		img->data32[dr.y + y][dr.x + x] = cards[0]->data32[y][x];
	    }
	  }

	  /* Draw some alignment marks to aid cutting. */
	  for (y = 0; y < dr.y - gap; y++)
	  {
		img->data32[y][dr.x-1] = c;
	  }
	  for (y = dr.y + dr.height + gap; y < 1200; y++)
	  {
		img->data32[y][dr.x-1] = c;
	  }
	  for (y = 0; y < dr.y - gap; y++)
	  {
		img->data32[y][dr.x+dr.width+1] = c;
	  }
	  for (y = dr.y + dr.height + gap; y < 1200; y++)
	  {
		img->data32[y][dr.x+dr.width+1] = c;
	  }
	  for (x = 0; x < dr.x - gap; x++)
	  {
		img->data32[dr.y-1][x] = c;
	  }
	  for (x = 0; x < dr.x - gap; x++)
	  {
		img->data32[dr.y+dr.height+1][x] = c;
	  }
	  for (x = dr.x+dr.width+gap; x < 952 - gap; x++)
	  {
		img->data32[dr.y-1][x] = c;
	  }
	  for (x = dr.x+dr.width+gap; x < 952 - gap; x++)
	  {
		img->data32[dr.y+dr.height+1][x] = c;
	  }
	}

	if (cards[1])
	{
	  Rect dr = rect(952, 79, 744, 1042);
	  int gap = 20;
	  c = GREY;

	  /* Draw the card image. */
	  for (y = 0; y < h; y++)
	  {
	    for (x = 0; x < w; x++)
	    {
		img->data32[dr.y + y][dr.x + x] = cards[1]->data32[y][x];
	    }
	  }

	  /* Draw some alignment marks to aid cutting. */
	  for (y = 0; y < dr.y - gap; y++)
	  {
		img->data32[y][dr.x-1] = c;
	  }
	  for (y = dr.y + dr.height + gap; y < 1200; y++)
	  {
		img->data32[y][dr.x-1] = c;
	  }
	  for (y = 0; y < dr.y - gap; y++)
	  {
		img->data32[y][dr.x+dr.width+1] = c;
	  }
	  for (y = dr.y + dr.height + gap; y < 1200; y++)
	  {
		img->data32[y][dr.x+dr.width+1] = c;
	  }
	  for (x = dr.x+dr.width+gap; x < 1800; x++)
	  {
		img->data32[dr.y-1][x] = c;
	  }
	  for (x = dr.x+dr.width+gap; x < 1800; x++)
	  {
		img->data32[dr.y+dr.height+1][x] = c;
	  }
	}

	if (img) {
		if (log != NULL) {
			fprintf(log, "Saving at 300 DPI file %s\n", file_name);
			fflush(log);
		}

		app_write_image(img, file_name);
		app_del_image(img);

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

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

	app_free(file_name);
}

int process(int first, int last)
{
	int i;
	Image *page_cards[9];
	Image *photo_cards[2];
	int num_page_cards = 0;
	int num_photo_cards = 0;

	for (completed=first; completed < last; completed++)
	{
		app_draw_window(w);
		app_draw_all(app);
		app_process_events(app);
		if (proceeding == 0)
			return 0;

		if (full != NULL) {
			int in_use = 0;
			for (i = 0; i < num_page_cards; i++)
				if (page_cards[i] == full)
					in_use = 1;
			for (i = 0; i < num_photo_cards; i++)
				if (photo_cards[i] == full)
					in_use = 1;
			if (! in_use)
				app_del_image(full);
		}

		full = create_card_image(cards[completed]);
		if (full == NULL)
			continue;

		if (make_300dpi && (make_all || (completed == current)))
			save_card_300dpi(full, cards[completed]->name, completed);

		if (make_150dpi && (make_all || (completed == current)))
			save_card_150dpi(full, cards[completed]->name, completed);

		if (make_100dpi && (make_all || (completed == current)))
			save_card_100dpi(full, cards[completed]->name, completed);

		if (make_075dpi && (make_all || (completed == current)))
			save_card_075dpi(full, cards[completed]->name, completed);

		if (make_9_page) {
			page_cards[num_page_cards++] = full;
			if ((num_page_cards == 9) || (completed == last - 1))
			{
				for (i = num_page_cards; i < 9; i++)
					page_cards[i] = NULL;
				save_card_9_page(page_cards, page_prefix, completed / 9);
				for (i = 0; i < num_page_cards; i++)
				{
					if (page_cards[i] == full)
						continue;
					if ((num_photo_cards > 0)
					 && (page_cards[i] == photo_cards[0]))
						continue;
					if ((num_photo_cards > 1)
					 && (page_cards[i] == photo_cards[1]))
						continue;
					app_del_image(page_cards[i]);
					page_cards[i] = NULL;
				}
				num_page_cards = 0;
			}
		}

		if (make_2_page) {
			photo_cards[num_photo_cards++] = full;
			if ((num_photo_cards == 2) || (completed == last - 1))
			{
				for (i = num_photo_cards; i < 2; i++)
					photo_cards[i] = NULL;
				save_card_2_page(photo_cards, photo_prefix, completed / 2);
				for (i = 0; i < num_photo_cards; i++)
				{
					int j, in_use;

					if (photo_cards[i] == full)
						continue;
					for (j = in_use = 0; j < num_page_cards; j++)
					{
						if ((num_page_cards > j)
						 && (photo_cards[i] == page_cards[j]))
						{
							in_use = 1;
							break;
						}
					}
					if (! in_use)
						app_del_image(photo_cards[i]);
					photo_cards[i] = NULL;
				}
				num_photo_cards = 0;
			}
		}

		if (full != NULL) {
			char message[80];

			sprintf(message, "Made: %d/%d", completed+1, num_cards);
			app_set_control_text(made_label, message);
			app_redraw_control(made_label);
		}
	}

	app_draw_window(w);
	app_draw_all(app);
	return 1;
}

void start_making_cards(MenuItem *mi)
{
	proceeding = 1;

	app_disable_menu_item(mi_make);
	app_enable_menu_item(mi_stop);

	if (make_all)
	{
		process(0, num_cards);
	}
	else if (make_current)
	{
		int first = current;
		int last = first + 1;
		int make_9 = make_9_page;
		int make_2 = make_2_page;
		int cur = current;

		make_9_page = 0;
		make_2_page = 0;

		process(first, last);

		if (make_9)
		{
			make_9_page = 1;
			first = current / 9 * 9;
			last = current / 9 * 9 + 9;
			if (last >= num_cards)
				last = num_cards;

			current = -1;
			process(first, last);
			current = cur;
		}

		if (make_2)
		{
			make_2_page = 1;
			make_9_page = 0;
			first = current / 2 * 2;
			last = current / 2 * 2 + 2;
			if (last >= num_cards)
				last = num_cards;

			current = -1;
			process(first, last);
			current = cur;
		}

		make_9_page = make_9;
		make_2_page = make_2;
	}

	app_disable_menu_item(mi_stop);
	app_enable_menu_item(mi_make);
}

void stop_making_cards(MenuItem *mi)
{
	proceeding = 0;
}

void read_spoiler(char *spoiler)
{
	int i;

	if (spoiler == NULL)
		return;

	for (i=0; i < num_cards; i++)
	{
		del_card(cards[i]);
		app_free(names[i]);
	}
	app_free(cards);
	app_free(names);
	cards = NULL;
	names = NULL;
	num_cards = 0;

	cards = load_cards(spoiler, &num_cards);

	if (cards)
	{
		names = app_zero_alloc(sizeof(char *) * (num_cards + 1));
		for (i=0; i < num_cards; i++)
			names[i] = app_copy_string(cards[i]->name);
		if (name_list) {
			app_change_list_box(name_list, names);
			app_set_list_box_item(name_list, 0);
		}
	}

	if (cards && cards[0])
	{
		if (full != NULL) {
			app_del_image(full);
			full = NULL;
		}
		if (cards[0])
			full = create_card_image(cards[0]);
	}
}

void open_spoiler(MenuItem *mi)
{
	read_spoiler(app_ask_file_open(app, "Choose a spoiler", "Open", ""));
	app_redraw_window(w);
}

void quit_program(MenuItem *mi)
{
	app_del_all_windows(app);
	exit(0);
}

void select_all(MenuItem *mi)
{
	make_all = 1;
	make_current = 0;
	app_check_menu_item(mi_all);
	app_uncheck_menu_item(mi_current);
}

void select_current(MenuItem *mi)
{
	make_all = 0;
	make_current = 1;
	app_uncheck_menu_item(mi_all);
	app_check_menu_item(mi_current);
}

void select_300dpi(MenuItem *mi)
{
	if (app_menu_item_is_checked(mi)) {
		make_300dpi = 0;
		app_uncheck_menu_item(mi);
	}
	else {
		make_300dpi = 1;
		app_check_menu_item(mi);
	}
}

void select_150dpi(MenuItem *mi)
{
	if (app_menu_item_is_checked(mi)) {
		make_150dpi = 0;
		app_uncheck_menu_item(mi);
	}
	else {
		make_150dpi = 1;
		app_check_menu_item(mi);
	}
}

void select_100dpi(MenuItem *mi)
{
	if (app_menu_item_is_checked(mi)) {
		make_100dpi = 0;
		app_uncheck_menu_item(mi);
	}
	else {
		make_100dpi = 1;
		app_check_menu_item(mi);
	}
}

void select_075dpi(MenuItem *mi)
{
	if (app_menu_item_is_checked(mi)) {
		make_075dpi = 0;
		app_uncheck_menu_item(mi);
	}
	else {
		make_075dpi = 1;
		app_check_menu_item(mi);
	}
}

void select_9_page(MenuItem *mi)
{
	if (app_menu_item_is_checked(mi)) {
		make_9_page = 0;
		app_uncheck_menu_item(mi);
	}
	else {
		make_9_page = 1;
		app_check_menu_item(mi);
	}
}

void select_2_page(MenuItem *mi)
{
	if (app_menu_item_is_checked(mi)) {
		make_2_page = 0;
		app_uncheck_menu_item(mi);
	}
	else {
		make_2_page = 1;
		app_check_menu_item(mi);
	}
}

void select_by_name(Control *c)
{
	int i = app_get_control_value(name_list);

	if (i == -1)
		i = 0;

	if (current != i)
	{
		if (cards && cards[i]) {
			current = i;
			if (full != NULL) {
				app_del_image(full);
				full = NULL;
			}
			full = create_card_image(cards[current]);
			app_draw_window(w);
		}
		if (name_list) {
			char message[80];

			sprintf(message, "Total: %d/%d", current+1, num_cards);
			app_set_control_text(total_label, message);
			app_redraw_control(total_label);
		}
	}
}

void key_press(Window *w, unsigned long key)
{
	int i = app_get_control_value(name_list);

	switch (key)
	{
		case PGUP: i -= 9; break;
		case PGDN: i += 9; break;
		case UP:   i -= 1; break;
		case DOWN: i += 1; break;
		default: break;
	}

	if (i < 0)
		i = 0;
	if (i >= num_cards)
		i = num_cards - 1;

	if (current != i)
		app_set_list_box_item(name_list, i);
}

int main(int argc, char *argv[])
{
	char *spoiler;
	Rect r;
	MenuBar *mb;
	Menu *m;
	MenuItem *mi;

	app = app_new_app(argc, argv);

	screen_font = app_new_font(app, "cp1252", PLAIN, 16);

	if (argc < 2)
	{
		if (app == NULL)
			return 1;

		spoiler = app_ask_file_open(app, "Choose a spoiler", "Open", "");
		if (spoiler == NULL)
			return 0; /* Cancelled */

		if (spoiler[0] == '\0')
			return 1; /* Empty string is not a valid filename! */
	}
	else
	{
		spoiler = argv[1];
	}

	if (spoiler)
	{
		char *folder = app_copy_string(spoiler);
		char *filename = app_get_file_name(folder);
		if (filename) {
			int offset = filename - folder;
			if (offset > 0) {
				filename[0] = '\0';
				app_set_current_folder(folder);
				spoiler += offset;
			}
		}
		app_free(folder);
	}

	log = app_open_file(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 (spoiler)
	{
		char *folder = app_current_folder();
		if (log != NULL) {
			fprintf(log, "Working directory is %s\n", folder);
			fflush(log);
		}
	}

	r = rect(20, 20, 520, 445);
	w = app_new_window(app, r,
				"GenCard - Magic Card Generator",
				STANDARD_WINDOW | CENTERED);
	app_set_window_background(w, rgb(0xD0, 0xE0, 0xF7));

	mb = app_new_menu_bar(w);

	m = app_new_menu(mb, "File");
	mi = app_new_menu_item(m, "Open...", 'O', open_spoiler);
	mi = app_new_menu_item(m, "-",        0,  NULL);
	mi = app_new_menu_item(m, "Quit",    'Q', quit_program);

	m = app_new_menu(mb, "Generate");
	mi_make     = app_new_menu_item(m, "Make!",        'M', start_making_cards);
	mi_stop     = app_new_menu_item(m, "Stop",         'S', stop_making_cards);
	mi          = app_new_menu_item(m, "-",             0,  NULL);
	mi_all      = app_new_menu_item(m, "All cards",    'A', select_all);
	mi_current  = app_new_menu_item(m, "Current card", 'C', select_current);
	mi          = app_new_menu_item(m, "-",             0,  NULL);
	mi_300dpi   = app_new_menu_item(m, "At 300 DPI",    0,  select_300dpi);
	mi_150dpi   = app_new_menu_item(m, "At 150 DPI",    0,  select_150dpi);
	mi_100dpi   = app_new_menu_item(m, "At 100 DPI",    0,  select_100dpi);
	mi_075dpi   = app_new_menu_item(m, "At  75 DPI",    0,  select_075dpi);
	mi          = app_new_menu_item(m, "-",             0,  NULL);
	mi_9_page   = app_new_menu_item(m, "9 card pages at 300 DPI",  0, select_9_page);
	mi_2_page   = app_new_menu_item(m, "2 card photos at 300 DPI", 0, select_2_page);
	app_check_menu_item(mi_all);
	app_check_menu_item(mi_300dpi);
	app_disable_menu_item(mi_stop);

	init_fonts(app);

	read_spoiler(spoiler);

	name_list = app_new_list_box(w, rect(2, 30, r.width/2, r.height-34),
			names, select_by_name);
	app_set_control_font(name_list, screen_font);
	total_label = app_new_label(w, rect(2, r.height-20, 120, 20),
				"Total:", ALIGN_LEFT);
	made_label = app_new_label(w, rect(r.width-122, r.height-20, 120, 20),
				"Made:", ALIGN_LEFT);
	app_on_window_redraw(w, show_card_in_window);
	app_on_window_resize(w, reshape_window);
	app_on_window_close(w, close_window);
	app_on_window_key_action(w, key_press);

	app_show_window(w);
	app_process_events(app);

	select_by_name(name_list);

	app_main_loop(app);
	app_del_app(app);

	if (log != NULL)
		app_close_file(log);
	if (num_cards > 0)
		fprintf(stderr, "\n");

	return 0;
}
