/*****************************************************************************/ /* */ /* main.c */ /* */ /* Main program of the chrcvt vector font converter */ /* */ /* */ /* */ /* (C) 2000-2011, Ullrich von Bassewitz */ /* Roemerstrasse 52 */ /* D-70794 Filderstadt */ /* EMail: uz@cc65.org */ /* */ /* */ /* This software is provided 'as-is', without any expressed or implied */ /* warranty. In no event will the authors be held liable for any damages */ /* arising from the use of this software. */ /* */ /* Permission is granted to anyone to use this software for any purpose, */ /* including commercial applications, and to alter it and redistribute it */ /* freely, subject to the following restrictions: */ /* */ /* 1. The origin of this software must not be misrepresented; you must not */ /* claim that you wrote the original software. If you use this software */ /* in a product, an acknowledgment in the product documentation would be */ /* appreciated but is not required. */ /* 2. Altered source versions must be plainly marked as such, and must not */ /* be misrepresented as being the original software. */ /* 3. This notice may not be removed or altered from any source */ /* distribution. */ /* */ /*****************************************************************************/ #include #include #include #include /* common */ #include "cmdline.h" #include "fname.h" #include "print.h" #include "strbuf.h" #include "xmalloc.h" #include "version.h" /* chrcvt */ #include "error.h" /* * The following is a corrected doc from the BGI font editor toolkit: * * BGI Stroke File Format * * The structure of Borland .CHR (stroke) files is as follows: * * ; offset 0h is a Borland header: * ; * HeaderSize equ 080h * DataSize equ (size of font file) * descr equ "Triplex font" * fname equ "TRIP" * MajorVersion equ 1 * MinorVersion equ 0 * * db 'PK',8,8 * db 'BGI ',descr,' V' * db MajorVersion+'0' * db (MinorVersion / 10)+'0',(MinorVersion mod 10)+'0' * db ' - 19 October 1987',0DH,0AH * db 'Copyright (c) 1987 Borland International', 0dh,0ah * db 0,1ah ; null & ctrl-Z = end * * dw HeaderSize ; size of header * db fname ; font name * dw DataSize ; font file size * db MajorVersion,MinorVersion ; version #'s * db 1,0 ; minimal version #'s * * db (HeaderSize - $) DUP (0) ; pad out to header size * * At offset 80h starts data for the file: * * ; 80h '+' flags stroke file type * ; 81h-82h number chars in font file (n) * ; 83h undefined * ; 84h ASCII value of first char in file * ; 85h-86h offset to stroke definitions (8+3n) * ; 87h scan flag (normally 0) * ; 88h distance from origin to top of capital * ; 89h distance from origin to baseline * ; 8Ah distance from origin to bottom descender * ; 8Bh-8Fh undefined * ; 90h offsets to individual character definitions * ; 90h+2n width table (one word per character) * ; 90h+3n start of character definitions * ; * The individual character definitions consist of a variable number of words * describing the operations required to render a character. Each word * consists of an (x,y) coordinate pair and a two-bit opcode, encoded as shown * here: * * Byte 1 7 6 5 4 3 2 1 0 bit # * op1 * * Byte 2 7 6 5 4 3 2 1 0 bit # * op2 * * * Opcodes * * op1=0 op2=0 End of character definition. * op1=1 op2=0 Move the pointer to (x,y) * op1=1 op2=1 Draw from current pointer to (x,y) */ /* The target file format is designed to be read by a cc65 compiled program * more easily. It should not be necessary to load the whole file into a * buffer to parse it, or seek within the file. Also using less memory if * possible would be fine. Therefore we use the following structure: * * Header portion: * .byte $54, $43, $48, $00 ; "TCH" version * .word * Data portion: * .byte ; Baseline to top * .byte ; Baseline to bottom * .byte ; Maximum char height * .byte , ... ; $5F width bytes * .word , ... ; $5F char def offsets * Character definitions: * .word , ... * .byte $80 * * The baseline of the character is assume to be at position zero. top and * bottom are both positive values. The former extends in positive, the other * in negative direction of the baseline. height contains the sum of top and * bottom and is stored here just for easier handling. * * The opcodes get converted for easier handling: END is marked by bit 7 * set in the first byte. The second byte of this opcode is not needed. * Bit 7 of the second byte marks a MOVE (bit 7 = 0) or DRAW (bit 7 = 1). * * The number of characters is fixed to $20..$7E (space to tilde), so character * widths and offsets can be stored in fixed size preallocated tables. The * space for the character definitions is allocated on the heap, it's size * is stored in the header. * * Above structure allows a program to read the header portion of the file, * validate it, then read the remainder of the file into memory in one chunk. * The character definition offsets will then be converted into pointers by * adding the character definition base pointer to each. */ /*****************************************************************************/ /* Data */ /*****************************************************************************/ static unsigned FilesProcessed = 0; /*****************************************************************************/ /* Code */ /*****************************************************************************/ static void Usage (void) /* Print usage information and exit */ { fprintf (stderr, "Usage: %s [options] file [options] [file]\n" "Short options:\n" " -h\t\t\tHelp (this text)\n" " -v\t\t\tBe more verbose\n" " -V\t\t\tPrint the version number and exit\n" "\n" "Long options:\n" " --help\t\tHelp (this text)\n" " --verbose\t\tBe more verbose\n" " --version\t\tPrint the version number and exit\n", ProgName); } static void OptHelp (const char* Opt attribute ((unused)), const char* Arg attribute ((unused))) /* Print usage information and exit */ { Usage (); exit (EXIT_SUCCESS); } static void OptVerbose (const char* Opt attribute ((unused)), const char* Arg attribute ((unused))) /* Increase verbosity */ { ++Verbosity; } static void OptVersion (const char* Opt attribute ((unused)), const char* Arg attribute ((unused))) /* Print the assembler version */ { fprintf (stderr, "%s V%s - (C) Copyright 2009, Ullrich von Bassewitz\n", ProgName, GetVersionAsString ()); } static void ConvertChar (StrBuf* Data, const unsigned char* Buf, int Remaining) /* Convert data for one character. Original data is in Buf, converted data * will be placed in Data. */ { /* Convert all drawing vectors for this character */ while (1) { unsigned Op; /* Check if we have enough data left */ if (Remaining < 2) { Error ("End of file while parsing character definitions"); } /* Get the next op word */ Op = (Buf[0] + (Buf[1] << 8)) & 0x8080; /* Check the opcode */ switch (Op) { case 0x0000: /* End */ if (SB_IsEmpty (Data)) { /* No ops. We need to add an empty one */ SB_AppendChar (Data, 0x00); SB_AppendChar (Data, 0x00); } /* Add an end marker to the last op in the buffer */ SB_GetBuf (Data)[SB_GetLen (Data) - 2] |= 0x80; return; case 0x0080: /* Move */ SB_AppendChar (Data, Buf[0] & 0x7F); SB_AppendChar (Data, Buf[1] & 0x7F); break; case 0x8000: /* Invalid opcode */ Error ("Input file contains invalid opcode 0x8000"); break; case 0x8080: /* Draw */ SB_AppendChar (Data, Buf[0] & 0x7F); SB_AppendChar (Data, Buf[1] | 0x80); break; } /* Next Op */ Buf += 2; Remaining -= 2; } } static void ConvertFile (const char* Input, const char* Output) /* Convert one vector font file */ { /* The header of a BGI vector font file */ static const unsigned char ChrHeader[] = { /* According to the Borland docs, the following should work, but it * doesn't. Seems like there are fonts that work, but don't have the * "BGI" string in the header. So we use just the PK\b\b mark as * a header. * * 0x50, 0x4B, 0x08, 0x08, 0x42, 0x47, 0x49, 0x20 */ 0x50, 0x4B, 0x08, 0x08 }; /* The header of a TGI vector font file */ unsigned char TchHeader[] = { 0x54, 0x43, 0x48, 0x00, /* "TCH" version */ 0x00, 0x00, /* size of char definitions */ 0x00, /* Top */ 0x00, /* Baseline */ 0x00, /* Bottom */ }; long Size; unsigned char* Buf; unsigned char* MsgEnd; unsigned FirstChar; unsigned CharCount; unsigned LastChar; unsigned Char; unsigned Offs; const unsigned char* OffsetBuf; const unsigned char* WidthBuf; const unsigned char* VectorBuf; StrBuf Offsets = AUTO_STRBUF_INITIALIZER; StrBuf VectorData = AUTO_STRBUF_INITIALIZER; /* Try to open the file for reading */ FILE* F = fopen (Input, "rb"); if (F == 0) { Error ("Cannot open input file `%s': %s", Input, strerror (errno)); } /* Seek to the end and determine the size */ fseek (F, 0, SEEK_END); Size = ftell (F); /* Seek back to the start of the file */ fseek (F, 0, SEEK_SET); /* Check if the size is reasonable */ if (Size > 32*1024) { Error ("Input file `%s' is too large (max = 32k)", Input); } else if (Size < 0x100) { Error ("Input file `%s' is too small to be a vector font file", Input); } /* Allocate memory for the file */ Buf = xmalloc ((size_t) Size); /* Read the file contents into the buffer */ if (fread (Buf, 1, (size_t) Size, F) != (size_t) Size) { Error ("Error reading from input file `%s'", Input); } /* Close the file */ (void) fclose (F); /* Verify the header */ if (memcmp (Buf, ChrHeader, sizeof (ChrHeader)) != 0) { Error ("Invalid format for `%s': invalid header", Input); } MsgEnd = memchr (Buf + sizeof (ChrHeader), 0x1A, 0x80); if (MsgEnd == 0) { Error ("Invalid format for `%s': description not found", Input); } if (MsgEnd[1] != 0x80 || MsgEnd[2] != 0x00) { Error ("Invalid format for `%s': wrong header size", Input); } /* We expect the file to hold chars from 0x20 (space) to 0x7E (tilde) */ FirstChar = Buf[0x84]; CharCount = Buf[0x81] + (Buf[0x82] << 8); LastChar = FirstChar + CharCount - 1; if (FirstChar > 0x20 || LastChar < 0x7E) { Print (stderr, 1, "FirstChar = $%04X, CharCount = %u\n", FirstChar, CharCount); Error ("File `%s' doesn't contain the chars we need", Input); } else if (LastChar >= 0x100) { Error ("File `%s' contains too many character definitions", Input); } /* Print the copyright from the header */ Print (stderr, 1, "%.*s\n", (int) (MsgEnd - Buf - 4), Buf+4); /* Get pointers to the width table, the offset table and the vector data * table. The first two corrected for 0x20 as first entry. */ OffsetBuf = Buf + 0x90 + ((0x20 - FirstChar) * 2); WidthBuf = Buf + 0x90 + (CharCount * 2) + (0x20 - FirstChar); VectorBuf = Buf + 0x90 + (CharCount * 3); /* Convert the characters */ for (Char = 0x20; Char <= 0x7E; ++Char, OffsetBuf += 2) { int Remaining; /* Add the offset to the offset table */ Offs = SB_GetLen (&VectorData); SB_AppendChar (&Offsets, Offs & 0xFF); SB_AppendChar (&Offsets, (Offs >> 8) & 0xFF); /* Get the offset of the vector data in the BGI data buffer */ Offs = OffsetBuf[0] + (OffsetBuf[1] << 8); /* Calculate the remaining data in the buffer for this character */ Remaining = Size - (Offs + (VectorBuf - Buf)); /* Check if the offset is valid */ if (Remaining <= 0) { Error ("Invalid data offset in input file `%s'", Input); } /* Convert the vector data and place it into the buffer */ ConvertChar (&VectorData, VectorBuf + Offs, Remaining); } /* Complete the TCH header */ Offs = 3 + 0x5F + 2*0x5F + SB_GetLen (&VectorData); TchHeader[4] = Offs & 0xFF; TchHeader[5] = (Offs >> 8) & 0xFF; TchHeader[6] = Buf[0x88]; TchHeader[7] = (unsigned char) -(signed char)(Buf[0x8A]); TchHeader[8] = TchHeader[6] + TchHeader[7]; /* The baseline must be zero, otherwise we cannot convert */ if (Buf[0x89] != 0) { Error ("Baseline of font in `%s' is not zero", Input); } /* If the output file is NULL, use the name of the input file with ".tch" * appended. */ if (Output == 0) { Output = MakeFilename (Input, ".tch"); } /* Open the output file */ F = fopen (Output, "wb"); if (F == 0) { Error ("Cannot open output file `%s': %s", Output, strerror (errno)); } /* Write the header to the output file */ if (fwrite (TchHeader, 1, sizeof (TchHeader), F) != sizeof (TchHeader)) { Error ("Error writing to `%s' (disk full?)", Output); } /* Write the width table to the output file */ if (fwrite (WidthBuf, 1, 0x5F, F) != 0x5F) { Error ("Error writing to `%s' (disk full?)", Output); } /* Write the offsets to the output file */ if (fwrite (SB_GetConstBuf (&Offsets), 1, 0x5F * 2, F) != 0x5F * 2) { Error ("Error writing to `%s' (disk full?)", Output); } /* Write the data to the output file */ Offs = SB_GetLen (&VectorData); if (fwrite (SB_GetConstBuf (&VectorData), 1, Offs, F) != Offs) { Error ("Error writing to `%s' (disk full?)", Output); } /* Close the output file */ if (fclose (F) != 0) { Error ("Error closing to `%s': %s", Output, strerror (errno)); } /* Done */ } int main (int argc, char* argv []) /* Assembler main program */ { /* Program long options */ static const LongOpt OptTab[] = { { "--help", 0, OptHelp }, { "--verbose", 0, OptVerbose }, { "--version", 0, OptVersion }, }; unsigned I; /* Initialize the cmdline module */ InitCmdLine (&argc, &argv, "chrcvt"); /* Check the parameters */ I = 1; while (I < ArgCount) { /* Get the argument */ const char* Arg = ArgVec[I]; /* Check for an option */ if (Arg [0] == '-') { switch (Arg [1]) { case '-': LongOption (&I, OptTab, sizeof(OptTab)/sizeof(OptTab[0])); break; case 'h': OptHelp (Arg, 0); break; case 'v': OptVerbose (Arg, 0); break; case 'V': OptVersion (Arg, 0); break; default: UnknownOption (Arg); break; } } else { /* Filename. Dump it. */ ConvertFile (Arg, 0); ++FilesProcessed; } /* Next argument */ ++I; } /* Print a message if we did not process any files */ if (FilesProcessed == 0) { fprintf (stderr, "%s: No input files\n", ProgName); } /* Success */ return EXIT_SUCCESS; }