1 /*****************************************************************************/
5 /* Main program of the chrcvt vector font converter */
9 /* (C) 2000-2011, Ullrich von Bassewitz */
10 /* Roemerstrasse 52 */
11 /* D-70794 Filderstadt */
12 /* EMail: uz@cc65.org */
15 /* This software is provided 'as-is', without any expressed or implied */
16 /* warranty. In no event will the authors be held liable for any damages */
17 /* arising from the use of this software. */
19 /* Permission is granted to anyone to use this software for any purpose, */
20 /* including commercial applications, and to alter it and redistribute it */
21 /* freely, subject to the following restrictions: */
23 /* 1. The origin of this software must not be misrepresented; you must not */
24 /* claim that you wrote the original software. If you use this software */
25 /* in a product, an acknowledgment in the product documentation would be */
26 /* appreciated but is not required. */
27 /* 2. Altered source versions must be plainly marked as such, and must not */
28 /* be misrepresented as being the original software. */
29 /* 3. This notice may not be removed or altered from any source */
32 /*****************************************************************************/
55 * The following is a corrected doc from the BGI font editor toolkit:
57 * BGI Stroke File Format
59 * The structure of Borland .CHR (stroke) files is as follows:
61 * ; offset 0h is a Borland header:
64 * DataSize equ (size of font file)
65 * descr equ "Triplex font"
71 * db 'BGI ',descr,' V'
73 * db (MinorVersion / 10)+'0',(MinorVersion mod 10)+'0'
74 * db ' - 19 October 1987',0DH,0AH
75 * db 'Copyright (c) 1987 Borland International', 0dh,0ah
76 * db 0,1ah ; null & ctrl-Z = end
78 * dw HeaderSize ; size of header
79 * db fname ; font name
80 * dw DataSize ; font file size
81 * db MajorVersion,MinorVersion ; version #'s
82 * db 1,0 ; minimal version #'s
84 * db (HeaderSize - $) DUP (0) ; pad out to header size
86 * At offset 80h starts data for the file:
88 * ; 80h '+' flags stroke file type
89 * ; 81h-82h number chars in font file (n)
91 * ; 84h ASCII value of first char in file
92 * ; 85h-86h offset to stroke definitions (8+3n)
93 * ; 87h scan flag (normally 0)
94 * ; 88h distance from origin to top of capital
95 * ; 89h distance from origin to baseline
96 * ; 8Ah distance from origin to bottom descender
98 * ; 90h offsets to individual character definitions
99 * ; 90h+2n width table (one word per character)
100 * ; 90h+3n start of character definitions
102 * The individual character definitions consist of a variable number of words
103 * describing the operations required to render a character. Each word
104 * consists of an (x,y) coordinate pair and a two-bit opcode, encoded as shown
107 * Byte 1 7 6 5 4 3 2 1 0 bit #
108 * op1 <seven bit signed X coord>
110 * Byte 2 7 6 5 4 3 2 1 0 bit #
111 * op2 <seven bit signed Y coord>
116 * op1=0 op2=0 End of character definition.
117 * op1=1 op2=0 Move the pointer to (x,y)
118 * op1=1 op2=1 Draw from current pointer to (x,y)
123 /* The target file format is designed to be read by a cc65 compiled program
124 * more easily. It should not be necessary to load the whole file into a
125 * buffer to parse it, or seek within the file. Also using less memory if
126 * possible would be fine. Therefore we use the following structure:
129 * .byte $54, $43, $48, $00 ; "TCH" version
130 * .word <size of data portion>
132 * .byte <top> ; Baseline to top
133 * .byte <bottom> ; Baseline to bottom
134 * .byte <height> ; Maximum char height
135 * .byte <width>, ... ; $5F width bytes
136 * .word <char definition offset>, ... ; $5F char def offsets
137 * Character definitions:
138 * .word <converted opcode>, ...
141 * The baseline of the character is assume to be at position zero. top and
142 * bottom are both positive values. The former extends in positive, the other
143 * in negative direction of the baseline. height contains the sum of top and
144 * bottom and is stored here just for easier handling.
146 * The opcodes get converted for easier handling: END is marked by bit 7
147 * set in the first byte. The second byte of this opcode is not needed.
148 * Bit 7 of the second byte marks a MOVE (bit 7 = 0) or DRAW (bit 7 = 1).
150 * The number of characters is fixed to $20..$7E (space to tilde), so character
151 * widths and offsets can be stored in fixed size preallocated tables. The
152 * space for the character definitions is allocated on the heap, it's size
153 * is stored in the header.
155 * Above structure allows a program to read the header portion of the file,
156 * validate it, then read the remainder of the file into memory in one chunk.
157 * The character definition offsets will then be converted into pointers by
158 * adding the character definition base pointer to each.
163 /*****************************************************************************/
165 /*****************************************************************************/
169 static unsigned FilesProcessed = 0;
173 /*****************************************************************************/
175 /*****************************************************************************/
179 static void Usage (void)
180 /* Print usage information and exit */
183 "Usage: %s [options] file [options] [file]\n"
185 " -h\t\t\tHelp (this text)\n"
186 " -v\t\t\tBe more verbose\n"
187 " -V\t\t\tPrint the version number and exit\n"
190 " --help\t\tHelp (this text)\n"
191 " --verbose\t\tBe more verbose\n"
192 " --version\t\tPrint the version number and exit\n",
198 static void OptHelp (const char* Opt attribute ((unused)),
199 const char* Arg attribute ((unused)))
200 /* Print usage information and exit */
208 static void OptVerbose (const char* Opt attribute ((unused)),
209 const char* Arg attribute ((unused)))
210 /* Increase verbosity */
217 static void OptVersion (const char* Opt attribute ((unused)),
218 const char* Arg attribute ((unused)))
219 /* Print the assembler version */
222 "%s V%s - (C) Copyright 2009, Ullrich von Bassewitz\n",
223 ProgName, GetVersionAsString ());
228 static void ConvertChar (StrBuf* Data, const unsigned char* Buf, int Remaining)
229 /* Convert data for one character. Original data is in Buf, converted data
230 * will be placed in Data.
233 /* Convert all drawing vectors for this character */
238 /* Check if we have enough data left */
240 Error ("End of file while parsing character definitions");
243 /* Get the next op word */
244 Op = (Buf[0] + (Buf[1] << 8)) & 0x8080;
246 /* Check the opcode */
251 if (SB_IsEmpty (Data)) {
252 /* No ops. We need to add an empty one */
253 SB_AppendChar (Data, 0x00);
254 SB_AppendChar (Data, 0x00);
256 /* Add an end marker to the last op in the buffer */
257 SB_GetBuf (Data)[SB_GetLen (Data) - 2] |= 0x80;
262 SB_AppendChar (Data, Buf[0] & 0x7F);
263 SB_AppendChar (Data, Buf[1] & 0x7F);
268 Error ("Input file contains invalid opcode 0x8000");
273 SB_AppendChar (Data, Buf[0] & 0x7F);
274 SB_AppendChar (Data, Buf[1] | 0x80);
286 static void ConvertFile (const char* Input, const char* Output)
287 /* Convert one vector font file */
289 /* The header of a BGI vector font file */
290 static const unsigned char ChrHeader[] = {
291 /* According to the Borland docs, the following should work, but it
292 * doesn't. Seems like there are fonts that work, but don't have the
293 * "BGI" string in the header. So we use just the PK\b\b mark as
296 * 0x50, 0x4B, 0x08, 0x08, 0x42, 0x47, 0x49, 0x20 */
297 0x50, 0x4B, 0x08, 0x08
300 /* The header of a TGI vector font file */
301 unsigned char TchHeader[] = {
302 0x54, 0x43, 0x48, 0x00, /* "TCH" version */
303 0x00, 0x00, /* size of char definitions */
311 unsigned char* MsgEnd;
317 const unsigned char* OffsetBuf;
318 const unsigned char* WidthBuf;
319 const unsigned char* VectorBuf;
320 StrBuf Offsets = AUTO_STRBUF_INITIALIZER;
321 StrBuf VectorData = AUTO_STRBUF_INITIALIZER;
324 /* Try to open the file for reading */
325 FILE* F = fopen (Input, "rb");
327 Error ("Cannot open input file `%s': %s", Input, strerror (errno));
330 /* Seek to the end and determine the size */
331 fseek (F, 0, SEEK_END);
334 /* Seek back to the start of the file */
335 fseek (F, 0, SEEK_SET);
337 /* Check if the size is reasonable */
338 if (Size > 32*1024) {
339 Error ("Input file `%s' is too large (max = 32k)", Input);
340 } else if (Size < 0x100) {
341 Error ("Input file `%s' is too small to be a vector font file", Input);
344 /* Allocate memory for the file */
345 Buf = xmalloc ((size_t) Size);
347 /* Read the file contents into the buffer */
348 if (fread (Buf, 1, (size_t) Size, F) != (size_t) Size) {
349 Error ("Error reading from input file `%s'", Input);
355 /* Verify the header */
356 if (memcmp (Buf, ChrHeader, sizeof (ChrHeader)) != 0) {
357 Error ("Invalid format for `%s': invalid header", Input);
359 MsgEnd = memchr (Buf + sizeof (ChrHeader), 0x1A, 0x80);
361 Error ("Invalid format for `%s': description not found", Input);
363 if (MsgEnd[1] != 0x80 || MsgEnd[2] != 0x00) {
364 Error ("Invalid format for `%s': wrong header size", Input);
367 /* We expect the file to hold chars from 0x20 (space) to 0x7E (tilde) */
368 FirstChar = Buf[0x84];
369 CharCount = Buf[0x81] + (Buf[0x82] << 8);
370 LastChar = FirstChar + CharCount - 1;
371 if (FirstChar > 0x20 || LastChar < 0x7E) {
372 Print (stderr, 1, "FirstChar = $%04X, CharCount = %u\n",
373 FirstChar, CharCount);
374 Error ("File `%s' doesn't contain the chars we need", Input);
375 } else if (LastChar >= 0x100) {
376 Error ("File `%s' contains too many character definitions", Input);
379 /* Print the copyright from the header */
380 Print (stderr, 1, "%.*s\n", (int) (MsgEnd - Buf - 4), Buf+4);
382 /* Get pointers to the width table, the offset table and the vector data
383 * table. The first two corrected for 0x20 as first entry.
385 OffsetBuf = Buf + 0x90 + ((0x20 - FirstChar) * 2);
386 WidthBuf = Buf + 0x90 + (CharCount * 2) + (0x20 - FirstChar);
387 VectorBuf = Buf + 0x90 + (CharCount * 3);
389 /* Convert the characters */
390 for (Char = 0x20; Char <= 0x7E; ++Char, OffsetBuf += 2) {
394 /* Add the offset to the offset table */
395 Offs = SB_GetLen (&VectorData);
396 SB_AppendChar (&Offsets, Offs & 0xFF);
397 SB_AppendChar (&Offsets, (Offs >> 8) & 0xFF);
399 /* Get the offset of the vector data in the BGI data buffer */
400 Offs = OffsetBuf[0] + (OffsetBuf[1] << 8);
402 /* Calculate the remaining data in the buffer for this character */
403 Remaining = Size - (Offs + (VectorBuf - Buf));
405 /* Check if the offset is valid */
406 if (Remaining <= 0) {
407 Error ("Invalid data offset in input file `%s'", Input);
410 /* Convert the vector data and place it into the buffer */
411 ConvertChar (&VectorData, VectorBuf + Offs, Remaining);
414 /* Complete the TCH header */
415 Offs = 3 + 0x5F + 2*0x5F + SB_GetLen (&VectorData);
416 TchHeader[4] = Offs & 0xFF;
417 TchHeader[5] = (Offs >> 8) & 0xFF;
418 TchHeader[6] = Buf[0x88];
419 TchHeader[7] = (unsigned char) -(signed char)(Buf[0x8A]);
420 TchHeader[8] = TchHeader[6] + TchHeader[7];
422 /* The baseline must be zero, otherwise we cannot convert */
423 if (Buf[0x89] != 0) {
424 Error ("Baseline of font in `%s' is not zero", Input);
427 /* If the output file is NULL, use the name of the input file with ".tch"
431 Output = MakeFilename (Input, ".tch");
434 /* Open the output file */
435 F = fopen (Output, "wb");
437 Error ("Cannot open output file `%s': %s", Output, strerror (errno));
440 /* Write the header to the output file */
441 if (fwrite (TchHeader, 1, sizeof (TchHeader), F) != sizeof (TchHeader)) {
442 Error ("Error writing to `%s' (disk full?)", Output);
445 /* Write the width table to the output file */
446 if (fwrite (WidthBuf, 1, 0x5F, F) != 0x5F) {
447 Error ("Error writing to `%s' (disk full?)", Output);
450 /* Write the offsets to the output file */
451 if (fwrite (SB_GetConstBuf (&Offsets), 1, 0x5F * 2, F) != 0x5F * 2) {
452 Error ("Error writing to `%s' (disk full?)", Output);
455 /* Write the data to the output file */
456 Offs = SB_GetLen (&VectorData);
457 if (fwrite (SB_GetConstBuf (&VectorData), 1, Offs, F) != Offs) {
458 Error ("Error writing to `%s' (disk full?)", Output);
461 /* Close the output file */
462 if (fclose (F) != 0) {
463 Error ("Error closing to `%s': %s", Output, strerror (errno));
471 int main (int argc, char* argv [])
472 /* Assembler main program */
474 /* Program long options */
475 static const LongOpt OptTab[] = {
476 { "--help", 0, OptHelp },
477 { "--verbose", 0, OptVerbose },
478 { "--version", 0, OptVersion },
483 /* Initialize the cmdline module */
484 InitCmdLine (&argc, &argv, "chrcvt");
486 /* Check the parameters */
488 while (I < ArgCount) {
490 /* Get the argument */
491 const char* Arg = ArgVec[I];
493 /* Check for an option */
494 if (Arg [0] == '-') {
498 LongOption (&I, OptTab, sizeof(OptTab)/sizeof(OptTab[0]));
519 /* Filename. Dump it. */
520 ConvertFile (Arg, 0);
528 /* Print a message if we did not process any files */
529 if (FilesProcessed == 0) {
530 fprintf (stderr, "%s: No input files\n", ProgName);