/* showfont.c++ -- written by Alexis WILKE for Made to Order Software Corp. (c) 2003-2009 */

/*

Copyright (c) 2003-2009 Made to Order Software Corp.

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and
associated documentation files (the "Software"), to
deal in the Software without restriction, including
without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<time.h>
#include	<ctype.h>
#include	<stdarg.h>


/*** File handling ***/
class File
{
public:

File(const char *filename, const char *ext = NULL)
{
	const char		*mode;
	char			*s, *pext, newname[256];

	if(ext == NULL) {
		mode = "rb";
	}
	else {
		mode = "wb";
		if(*ext == '.') {
			ext++;
		}
		if(strlen(filename) + strlen(ext) >= sizeof(newname) - 2) {
			fprintf(stderr, "ERROR: filename \"%s\" too long.\n", filename);
			exit(1);
		}
		pext = NULL;
		s = newname;
		while(*filename != '\0') {
			if(*filename == '.') {
				pext = s;
			}
			else {
				while(*filename == '/' || *filename == '\\' || *filename == ':') {
					pext = NULL;
					*s++ = *filename++;
					if(*filename == '\0') {
						break;
					}
				}
				/* copy anything including a '.' next */
			}
			if(*filename != '\0') {
				*s++ = *filename++;
			}
		}
		if(pext != NULL) {
			s = pext + 1;
		}
		else {
			*s++ = '.';
		}
		strcpy(s, ext);
		filename = newname;
	}

//printf("Open [%s] [%s]\n", filename, mode);

	f_file = fopen(filename, mode);
	if(f_file == NULL) {
		fprintf(stderr, "FATAL ERROR: can't open file \"%s\".\n", filename);
		exit(1);
	}
	f_name = strdup(filename);
	f_line = 0;
}

~File()
{
	if(f_file != NULL) {
		fclose(f_file);
	}
	delete f_name;
}

long GetC(void)
{
	// we will need the iconv_t support here
	return fgetc(f_file);
}

bool ReadLine(unsigned long *line, long size)
{
	long		c;

	// assume that size > 0

	/* get one character, if end of file, quit */
	c = GetC();
	if(c == EOF) {
		f_line = (unsigned long) -1;	// we're done
		*line = '\0';			// return an empty line, just in case
		return false;
	}

	f_line++;
	size--;			// make room for the nul terminator

	/* read one line */
	while(c != EOF && c != '\n' && c != '\r' && size > 0) {
		*line++ = c;
		size--;
		c = GetC();
	}
	if(c == '\r') {
		c = GetC();
		if(c != '\n') {
			ungetc(c, f_file);
		}
	}
	/* in case someone has had fun creating a super long line */
	if(c != EOF && c != '\n' && c != '\r') {
		fprintf(stderr, "FATAL ERROR: line #%ld of file \"%s\" is too long.\n", f_line, f_name);
		exit(1);
	}

	*line = '\0';

	return true;
}

void Print(const char *format, ...)
{
	va_list		args;

	va_start(args, format);
	vfprintf(f_file, format, args);
	va_end(args);
}

void Print(const unsigned long *s)
{
	while(*s != '\0') {
		fputc(*s, f_file);
		s++;
	}
}

void Flush(void) const
{
	fflush(f_file);
}

const char *Filename(void) const
{
	return f_name;
}

unsigned long Line(void) const
{
	return f_line;
}


private:
	const char *	f_name;
	FILE *		f_file;
	unsigned long	f_line;
	// we will need an iconv_t for unicode fonts
};


class Chars
{
public:
	/* one table represents up to 256 chars */
	struct Table {
		char	f_table[256 / 8];

		Table(void)
		{
			memset(f_table, 0, sizeof(f_table));
		}

		bool Add(unsigned long c)
		{
			unsigned long	bit;
			bool		result;

			bit = 1 << (c & 7);
			result = (f_table[(c / 8) & 31] & bit) != 0;
			f_table[(c / 8) & 31] |= bit;
			return result;
		}

		bool IsDefined(unsigned long c)
		{
			unsigned long		bit;

			bit = 1 << (c & 7);
			return (f_table[(c / 8) & 31] & bit) != 0;
		}
	};

Chars(void)
{
	memset(f_tables, 0, sizeof(f_tables));
	f_count = 0;
}

~Chars()
{
	unsigned long		i;

	for(i = 0; i < 256; i++) {
		delete f_tables[i];
	}
}

bool Add(unsigned long c)
{
	unsigned long		i;
	bool			r;

	i = c >> 8;
	if(i >= 256 || i < 0) {
		fprintf(stderr, "FATAL ERROR: character outside range (only 65536 chars supported)\n");
		exit(1);
	}
	if(f_tables[i] == 0) {
		f_tables[i] = new Table;
	}
	r = f_tables[i]->Add(c);
	if(!r) {
		f_count++;
	}
	return r;
}

unsigned long Count(void) const
{
	return f_count;
}

bool IsDefined(unsigned long c)
{
	unsigned long		i;

	i = c >> 8;
	if(i >= 256 || i < 0) {
		fprintf(stderr, "FATAL ERROR: character outside range (only 65536 chars supported)\n");
		exit(1);
	}
	if(f_tables[i] == 0) {
		return false;
	}
	return f_tables[i]->IsDefined(c);
}

bool IsRowDefined(unsigned long c)
{
	unsigned long		i;

	i = c >> 8;
	if(i >= 256 || i < 0) {
		return false;
	}

	return f_tables[i] != 0;
}

static unsigned long StrToChar(const unsigned long *str)
{
	unsigned long		c;

	if(str[0] == '\\') {
		if(str[1] == 'x') {
			str += 2;
			c = 0;
			while(*str != '\0') {
				if(*str >= '0' && *str <= '9') {
					c = c * 16 + *str - '0';
				}
				else if((*str >= 'A' && *str <= 'F') || (*str >= 'a' && *str <= 'f')) {
					c = c * 16 + (*str & 0x5F) - 'A' + 10;
				}
				else {
					fprintf(stderr, "ERROR: bad hexadecimal value for a character.\n");
					return (unsigned long) -1;
				}
				str++;
			}
			return c;
		}
		if(str[1] != '\0') {
			str++;
		}
	}

	return *str;
}


private:
	// we create a tree of 256 entries pointing to 256 entries
	// each; the lower level is an array of struc Table objects
	Table *		f_tables[256];	// any character will fit!
	unsigned long	f_count;
};


class Text
{
public:

Text(int max)
{
	f_table = new unsigned long *[max];
	f_max = max;
	f_count = 0;
}

~Text()
{
	Empty();
}

void Empty(void)
{
	unsigned long	i;

	i = f_count;
	while(i > 0) {
		i--;
		delete f_table[i];
	}
	f_count = 0;
}

void Add(const unsigned long *str)
{
	const unsigned long	*end;
	unsigned long		length;

	if(f_count >= f_max) {
		return;	// table full already
	}

	end = str;
	while(*end != '\0') {
		end++;
	}
	length = end - str + 1;

	f_table[f_count] = new unsigned long[length];
	memcpy(f_table[f_count], str, length * sizeof(unsigned long));

	f_count++;
}

unsigned long Count(void) const
{
	return f_count;
}

unsigned long *Get(unsigned long idx)
{
	if(idx >= f_count) {
		return 0;
	}
	return f_table[idx];
}


private:
	// no copy operators
	Text(const Text& txt) {}
	Text& operator = (const Text& txt) { return *this; }

	unsigned long		f_count;
	unsigned long		f_max;
	unsigned long **	f_table;
};



bool isspace(unsigned long c)
{
	return c == ' ' || c == '\t';
}

int strncmp(const unsigned long *s1, const char *s2, int length)
{
	if(length == 0) {
		return 0;
	}

	while(*s1 == (unsigned char) *s2) {
		length--;
		if(*s1 == '\0' || length == 0) {
			return 0;
		}
		s1++;
		s2++;
	}

	return *s1 < (unsigned char) *s2 ? -1 : 1;
}


unsigned long strlen(const unsigned long *s)
{
	const unsigned long	*e;

	e = s;
	while(*e != '\0') {
		e++;
	}

	return e - s;
}

unsigned long *strstr(const unsigned long *s1, const char *s2)
{
	int		l, m;

	l = strlen(s2);
	m = strlen(s1) - l;

	while(m >= 0) {
		if(strncmp(s1, s2, l) == 0) {
			return const_cast<unsigned long *>(s1);
		}
		s1++;
		m--;
	}

	return 0;
}


void ReadFont(File& font, Chars& chars)
{
	unsigned long	buf[1024], *s, *d, c, r, quote;

	while(font.ReadLine(buf, sizeof(buf) / sizeof(buf[0]))) {
		s = buf;
		while(isspace(*s)) {
			s++;
		}
		if(strncmp(s, "char:", 5) == 0) {
			s += 5;
			while(isspace(*s)) {
				s++;
			}
			quote = *s;
			if(quote != '"' && quote != '\'' && quote != '`') {
				fprintf(stderr, "ERROR: in \"%s\" at line %lu: char label found but not followed by a string.\n", font.Filename(), font.Line());
			}
			else {
				s++;
				d = buf;
				while(*s != quote) {
					if(*s == '\0') {
fprintf(stderr, "ERROR: in \"%s\" at line %lu: char name (string) not terminated.\n", font.Filename(), font.Line());
						break;
					}
					if(*s == '\\') {
						s++;
						if(*s == '\0') {
							break;
						}
						c = *s++;
						switch(c) {
						case 'a': c =  7; break;
						case 'b': c =  8; break;
						case 't': c =  9; break;
						case 'n': c = 10; break;
						case 'r': c = 11; break;
						case 'f': c = 12; break;
						case 'v': c = 13; break;

						case 'X':
						case 'x':
							c = *s++;
							if(c >= '0' && c <= '9') {
								c -= '0';
							}
							else if(c >= 'a' && c <= 'f') {
								c -= 'a' + 10;
							}
							else if(c >= 'A' && c <= 'F') {
								c -= 'A' + 10;
							}
							else {
								s--;
								c = 'x';
								break;
							}
							r = *s++;
							if(r >= '0' && r <= '9') {
								c = c * 16 + r - '0';
							}
							else if(r >= 'a' && r <= 'f') {
								c = c * 16 + r - 'a' + 10;
							}
							else if(r >= 'A' && r <= 'F') {
								c = c * 16 + r - 'A' + 10;
							}
							else {
								s--;
							}
							break;

						case '0':
						case '1':
						case '2':
						case '3':
						case '4':
						case '5':
						case '6':
						case '7':
							c = c - '0';
							r = *s++;
							if(r < '0' || r > '7') {
								s--;
								break;
							}
							c = c * 8 + r - '0';
							r = *s++;
							if(r < '0' || r > '7') {
								s--;
								break;
							}
							c = c * 8 + r - '0';
							break;

						// default: -- any other chars is taken as is
						}
						*d++ = c;
					}
					else {
						*d++ = *s++;
					}
				}
				*d = '\0';
				c = chars.StrToChar(buf);
				if(c != (unsigned long) -1) {

//printf("Adding char %04lX\n", c);

					if(chars.Add(c)) {
fprintf(stderr, "ERROR: in \"%s\" at line %lu: the character \\x%04lX was defined twice.\n", font.Filename(), font.Line(), c);
					}
				}
			}
		}
	}
}

void InsertChar(File& sswf, Text& txt, unsigned long c)
{
	unsigned long		idx, *s, *p, l, buf[256];

	idx = 0;
	for(;;) {
		s = txt.Get(idx);
		if(s == 0) {
			break;
		}
		p = strstr(s, "<char/>");
		if(p != 0) {
			l = p - s;
			memcpy(buf, s, l * sizeof(unsigned long));
			buf[l] = '\0';
			sswf.Print(buf);
			if(c == '"' || c == '\\') {
				sswf.Print("\\%c", c);
			}
			else if(c > ' ' && c < 0x7F) {
				sswf.Print("%c", c);
			}
			else {
				// Since version 1.7.0 we can write just one \ instead of two
				sswf.Print("\\x%04lX", c);
			}
			sswf.Print(s + l + 7);
			goto next;
		}
		p = strstr(s, "<name/>");
		if(p != 0) {
			l = p - s;
			memcpy(buf, s, l * sizeof(unsigned long));
			buf[l] = '\0';
			sswf.Print(buf);
			sswf.Print("name_%04lX", c);
			sswf.Print(s + l + 7);
			goto next;
		}
		sswf.Print(s);
next:
		sswf.Print("\n");
		idx++;
	}
}


void InsertText(File& tsswf, File& sswf, Chars& chars)
{
	Text		txt(256);	/* max of 256 lines (Text can't currently grow) */
	unsigned long	buf[256], x, y;

	for(;;) {
		tsswf.ReadLine(buf, sizeof(buf) / sizeof(buf[0]));
		if(strncmp(buf, "</text>", 7) == 0) {
			break;
		}
		txt.Add(buf);
	}
	sswf.Flush();
	for(y = 0; y < 0x10000; y += 0x100) {
		if(chars.IsRowDefined(y)) {
			for(x = 0; x < 0x100; x++) {
				if(chars.IsDefined(x + y)) {
					InsertChar(sswf, txt, x + y);
				}
			}
		}
	}
}

void InsertPlace(File& sswf, Text& txt, unsigned long x, unsigned long y)
{
	unsigned long		idx, *s, *start, *p, l, buf[256];

	idx = 0;
	for(;;) {
		s = txt.Get(idx);
		if(s == NULL) {
			break;
		}
		do {
			start = s;
			p = strstr(s, "<char_name/>");
			if(p != 0) {
				l = p - s;
				memcpy(buf, s, l * sizeof(unsigned long));
				buf[l] = '\0';
				sswf.Print(buf);
				sswf.Print("name_%04lX", x + y);
				s += l + 12;
			}
			p = strstr(s, "<depth/>");
			if(p != 0) {
				l = p - s;
				memcpy(buf, s, l * sizeof(unsigned long));
				buf[l] = '\0';
				sswf.Print(buf);
				sswf.Print("%ld", x + 1);
				s += l + 8;
			}
			p = strstr(s, "<x/>");
			if(p != 0) {
				l = p - s;
				memcpy(buf, s, l * sizeof(unsigned long));
				buf[l] = '\0';
				sswf.Print(buf);
				sswf.Print("%ld", x % 16);
				s += l + 4;
			}
			p = strstr(s, "<y/>");
			if(p != 0) {
				l = p - s;
				memcpy(buf, s, l * sizeof(unsigned long));
				buf[l] = '\0';
				sswf.Print(buf);
				sswf.Print("%ld", x / 16);
				s += l + 4;
			}
		} while(s != start);
		sswf.Print(s);
		sswf.Print("\n");
		idx++;
	}
}


void InsertRowChars(File& sswf, Chars& chars, Text& place_chars, Text& place_no_char, unsigned long y)
{
	unsigned long	x;

	for(x = 0; x < 0x100; x++) {
		if(chars.IsDefined(x + y)) {
			InsertPlace(sswf, place_chars, x, y);
		}
		else {
			InsertPlace(sswf, place_no_char, x, y);
		}
	}
}

void InsertRow(File& tsswf, File& sswf, Chars& chars)
{
	Text		row(32), place_chars(32), place_no_char(32);
	unsigned long	buf[256], *s, *p, l, idx, y, state, gotchars;

	state = 0;
	gotchars = false;
	for(;;) {
		tsswf.ReadLine(buf, sizeof(buf) / sizeof(buf[0]));
		if(strncmp(buf, "</row>", 6) == 0) {
			break;
		}
		if(strncmp(buf, "<chars>", 7) == 0) {
			if(!gotchars) {
				row.Add(buf);		// we need to know when to switch
				gotchars = true;
			}
			state = 1;
			continue;
		}
		else if(strncmp(buf, "<no_char>", 9) == 0) {
			if(!gotchars) {
				row.Add(buf);		// we need to know when to switch
				gotchars = true;
			}
			state = 2;
			continue;
		}
		else if(strncmp(buf, "</chars>", 8) == 0 || strncmp(buf, "</no_char>", 10) == 0) {
			state = 0;
			continue;
		}
		switch(state) {
		default:	//case 0:
			row.Add(buf);
			break;

		case 1:
			place_chars.Add(buf);
			break;

		case 2:
			place_no_char.Add(buf);
			break;

		}
	}

	for(y = 0; y < 0x10000; y += 0x100) {
		if(chars.IsRowDefined(y)) {
			idx = 0;
			for(;;) {
				s = row.Get(idx);
				if(s == NULL) {
					break;
				}
				idx++;
				if(strncmp(s, "<chars>", 7) == 0 || strncmp(s, "<no_char>", 9) == 0) {
					InsertRowChars(sswf, chars, place_chars, place_no_char, y);
				}
				else if((p = strstr(s, "<row_name/>")) != 0) {
					l = p - s;
					memcpy(buf, s, l * sizeof(unsigned long));
					buf[l] = '\0';
					sswf.Print(buf);
					sswf.Print("row_%04lX", y);
					sswf.Print(s + l + 11);
					sswf.Print("\n");
				}
				else if((p = strstr(s, "<depth/>")) != 0) {
					l = p - s;
					memcpy(buf, s, l * sizeof(unsigned long));
					buf[l] = '\0';
					sswf.Print(buf);
					sswf.Print("%ld", y / 256 + 1);
					sswf.Print(s + l + 8);
					sswf.Print("\n");
				}
				else {
					sswf.Print(s);
					sswf.Print("\n");
				}
			}
		}
	}

	sswf.Flush();
}

void InsertFont(File& sswf, unsigned long *buf, unsigned long l, const char *fontname)
{
	char		*name, *s;
	unsigned long	*p;
	const char	*fn;
	size_t		len;

// first what's before <font/>
	buf[l] = '\0';
	sswf.Print(buf);

// write the font name
	len = strlen(fontname) + 1;
	name = new char[len];
	s = name;
	fn = fontname;
	while(*fn != '\0') {
		if(*fn == '-') {
			*s = '_';
		}
		else {
			*s = *fn;
		}
		fn++;
		s++;
	}
	*s = '\0';
	sswf.Print("%s", name);
	delete [] name;

// write what's after <font/> or <fotinos/>
	buf += l + 1;
	while(*buf != '\0') {
		if(*buf == '>') {
			buf++;
			break;
		}
		buf++;
	}

// Now we check for a <div/> at the end because fotinos need to be
// printed as is, whereas other fonts need to be printed much smaller
// this is flaky, really flaky! but it works for us...
	p = strstr(buf, "<div/>");
	if(p != 0) {
		*p = '\0';
		sswf.Print(buf);
		buf = p + 6;
		if(strcasecmp("fotinos", fontname) != 0) {
			sswf.Print(" / 40");
		}
	}
	sswf.Print(buf);

// add a new line
	sswf.Print("\n");
}

void CreateSSWF(File& tsswf, File& sswf, Chars& chars, const char *fontname)
{
	unsigned long	buf[256], *p;

	while(tsswf.ReadLine(buf, sizeof(buf) / sizeof(buf[0]))) {
		if(strncmp(buf, "<text>", 6) == 0) {
			InsertText(tsswf, sswf, chars);
		}
		else if(strncmp(buf, "<row>", 5) == 0) {
			InsertRow(tsswf, sswf, chars);
		}
		else if((p = strstr(buf, "<font/>")) != 0) {
			InsertFont(sswf, buf, p - buf, fontname);
		}
		else if((p = strstr(buf, "<fotinos/>")) != 0) {
			if(strcmp(fontname, "fotinos") != 0) {
				InsertFont(sswf, buf, p - buf, "fotinos");
			}
		}
		else {
			sswf.Print(buf);
			sswf.Print("\n");
		}
	}
}


void CreateHTML(File& xhtml, File& html, Chars& chars, const char *fontpath, const char *fontname)
{
	unsigned long	buf[256], *p;
	time_t		now;
	struct tm	t;

	while(xhtml.ReadLine(buf, sizeof(buf) / sizeof(buf[0]))) {
		if((p = strstr(buf, "<name/>")) != 0) {
			buf[p - buf] = '\0';
			html.Print(buf);
			html.Print("%s", fontname);
			html.Print(p + 7);
		}
		else if((p = strstr(buf, "<font/>")) != 0) {
			buf[p - buf] = '\0';
			html.Print(buf);
			html.Print("%s", fontpath);
			html.Print(p + 7);
		}
		else if((p = strstr(buf, "<count/>")) != 0) {
			buf[p - buf] = '\0';
			html.Print(buf);
			html.Print("%ld", chars.Count());
			html.Print(p + 8);
		}
		else if((p = strstr(buf, "<date/>")) != 0) {
			buf[p - buf] = '\0';
			html.Print(buf);
			time(&now);
			gmtime_r(&now, &t);
			strftime((char *) buf, sizeof(buf), "%m/%d/%Y", &t);
			html.Print("%s", (char *) buf);
			html.Print(p + 7);
		}
		else {
			html.Print(buf);
		}
		html.Print("\n");
	}
}


char *name_of_font(const char *fontpath)
{
	const char	*ext, *s;
	char		*fontname;
	long		len;

	// skip the path, "font-..." and extension
	ext = 0;
	s = fontpath;
	while(*s != '\0') {
		if(*s == '/') {
			fontpath = s + 1;
			ext = 0;
		}
		else if(*s == '.') {
			ext = s;
		}
		s++;
	}
	if(strncmp(fontpath, "font-", 5) == 0) {
		fontpath += 5;
	}
	if(ext == NULL) {
		ext = s;
	}
	len = ext - fontpath;
	fontname = new char[len + 1];
	memcpy(fontname, fontpath, len);
	fontname[len] = '\0';

	return fontname;
}


void usage(void)
{
	fprintf(stderr, "ShowFont -- Copyright (c) 2003-2009 Made to Order Software Corp.\n");
	fprintf(stderr, "Written by Alexis Wilke\n\n");
	fprintf(stderr, "Usage: showfont [-opt] <font.sswf> <showfont.tswf> <showfont.xtml>\n\n");
	fprintf(stderr, "Where -opt is one or more of the following:\n");
	fprintf(stderr, "   -h            prints out this help screen\n");
	exit(1);
}


int main(int argc, char *argv[])
{
	int		i, j, errcnt;
	char		*fontpath, *sswfname, *htmlname, *fontname;

	fontpath = NULL;
	sswfname = NULL;
	htmlname = NULL;
	errcnt = 0;

	i = 1;
	while(i < argc) {
		if(argv[i][0] == '-') {
			j = 1;
			while(argv[i][j] != '\0') {
				switch(argv[i][j]) {
				case 'h':
					usage();
					/*NOTREACHED*/

				}
				j++;
			}
		}
		else if(fontpath == NULL) {
			fontpath = argv[i];
		}
		else if(sswfname == NULL) {
			sswfname = argv[i];
		}
		else if(htmlname == NULL) {
			htmlname = argv[i];
		}
		else {
			errcnt++;
			fprintf(stderr, "FATAL ERROR: only three filenames supported at this time.\n");
		}
		i++;
	}

	if(errcnt > 0) {
		fprintf(stderr, "\n");
		usage();
		/*NOTREACHED*/
	}

	fontname = name_of_font(fontpath);

	/* open all the files to make sure we can do so */
	File font(fontpath);
	File tsswf(sswfname);
	File xhtml(htmlname);
	File sswf(fontname, "sswf");
	File html(fontname, "html");

	/*
	 * Read the input font. We search for all the chars and put a flag
	 * in the chars array when a character is found.
	 * Errors are generated when two characters with the same number are
	 * found and if a character is not properly defined. Note that this
	 * tool expects sswf files as created by the ft2sswf tool. In other
	 * words, a line with a character definition has the following syntax:
	 *		char: "<name>", ...
	 */
	Chars chars;
	ReadFont(font, chars);
	printf("%lu characters found for \"%s\".\n", chars.Count(), fontname);

	/*
	 * We read the tswf file (template) until we find the place where we
	 * want the list of font referenced.
	 */
	CreateSSWF(tsswf, sswf, chars, fontname);

	/*
	 * Finally we can create the HTML file from the XHTML
	 */
	CreateHTML(xhtml, html, chars, fontpath, fontname);

	exit(0);
}

