/*****************************************************************************/ /* */ /* main.c */ /* */ /* Main program of the chrcvt65 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" /* chrcvt65 */ #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\n", ProgName, GetVersionAsString ()); exit(EXIT_SUCCESS); } 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, "chrcvt65"); /* 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; }