]> git.sur5r.net Git - cc65/blob - src/chrcvt65/main.c
7e7183e0ad6d55ace5f98070f3bdcc40777e1cbd
[cc65] / src / chrcvt65 / main.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                  main.c                                   */
4 /*                                                                           */
5 /*            Main program of the chrcvt65 vector font converter             */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 2000-2011, Ullrich von Bassewitz                                      */
10 /*                Roemerstrasse 52                                           */
11 /*                D-70794 Filderstadt                                        */
12 /* EMail:         uz@cc65.org                                                */
13 /*                                                                           */
14 /*                                                                           */
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.                                    */
18 /*                                                                           */
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:                            */
22 /*                                                                           */
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              */
30 /*    distribution.                                                          */
31 /*                                                                           */
32 /*****************************************************************************/
33
34
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <errno.h>
40
41 /* common */
42 #include "cmdline.h"
43 #include "fname.h"
44 #include "print.h"
45 #include "strbuf.h"
46 #include "xmalloc.h"
47 #include "version.h"
48
49 /* chrcvt65 */
50 #include "error.h"
51
52
53
54 /*
55 ** The following is a corrected doc from the BGI font editor toolkit:
56 **
57 **                      BGI Stroke File Format
58 **
59 ** The structure of Borland .CHR (stroke) files is as follows:
60 **
61 ** ;  offset 0h is a Borland header:
62 ** ;
63 **         HeaderSize      equ     080h
64 **         DataSize        equ     (size of font file)
65 **         descr           equ     "Triplex font"
66 **         fname           equ     "TRIP"
67 **         MajorVersion    equ     1
68 **         MinorVersion    equ     0
69 **
70 **         db      'PK',8,8
71 **         db      'BGI ',descr,'  V'
72 **         db      MajorVersion+'0'
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
77 **
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
83 **
84 **         db      (HeaderSize - $) DUP (0)        ; pad out to header size
85 **
86 ** At offset 80h starts data for the file:
87 **
88 ** ;               80h     '+'  flags stroke file type
89 ** ;               81h-82h  number chars in font file (n)
90 ** ;               83h      undefined
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
97 ** ;               8Bh-8Fh  undefined
98 ** ;               90h      offsets to individual character definitions
99 ** ;               90h+2n   width table (one word per character)
100 ** ;               90h+3n   start of character definitions
101 ** ;
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
105 ** here:
106 **
107 ** Byte 1          7   6   5   4   3   2   1   0     bit #
108 **                op1  <seven bit signed X coord>
109 **
110 ** Byte 2          7   6   5   4   3   2   1   0     bit #
111 **                op2  <seven bit signed Y coord>
112 **
113 **
114 **           Opcodes
115 **
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)
119 */
120
121
122
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:
127 **
128 ** Header portion:
129 **      .byte   $54, $43, $48, $00              ; "TCH" version
130 **      .word   <size of data portion>
131 ** 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>, ...
139 **      .byte   $80
140 **
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.
145 **
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).
149 **
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.
154 **
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.
159 */
160
161
162
163 /*****************************************************************************/
164 /*                                   Data                                    */
165 /*****************************************************************************/
166
167
168
169 static unsigned FilesProcessed = 0;
170
171
172
173 /*****************************************************************************/
174 /*                                   Code                                    */
175 /*****************************************************************************/
176
177
178
179 static void Usage (void)
180 /* Print usage information and exit */
181 {
182     fprintf (stderr,
183              "Usage: %s [options] file [options] [file]\n"
184              "Short options:\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"
188              "\n"
189              "Long options:\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",
193              ProgName);
194 }
195
196
197
198 static void OptHelp (const char* Opt attribute ((unused)),
199                      const char* Arg attribute ((unused)))
200 /* Print usage information and exit */
201 {
202     Usage ();
203     exit (EXIT_SUCCESS);
204 }
205
206
207
208 static void OptVerbose (const char* Opt attribute ((unused)),
209                         const char* Arg attribute ((unused)))
210 /* Increase verbosity */
211 {
212     ++Verbosity;
213 }
214
215
216
217 static void OptVersion (const char* Opt attribute ((unused)),
218                         const char* Arg attribute ((unused)))
219 /* Print the assembler version */
220 {
221     fprintf (stderr,
222              "%s V%s\n", ProgName, GetVersionAsString ());
223     exit(EXIT_SUCCESS);
224 }
225
226
227
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.
231 */
232 {
233     /* Convert all drawing vectors for this character */
234     while (1) {
235
236         unsigned Op;
237
238         /* Check if we have enough data left */
239         if (Remaining < 2) {
240             Error ("End of file while parsing character definitions");
241         }
242
243         /* Get the next op word */
244         Op = (Buf[0] + (Buf[1] << 8)) & 0x8080;
245
246         /* Check the opcode */
247         switch (Op) {
248
249             case 0x0000:
250                 /* End */
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);
255                 }
256                 /* Add an end marker to the last op in the buffer */
257                 SB_GetBuf (Data)[SB_GetLen (Data) - 2] |= 0x80;
258                 return;
259
260             case 0x0080:
261                 /* Move */
262                 SB_AppendChar (Data, Buf[0] & 0x7F);
263                 SB_AppendChar (Data, Buf[1] & 0x7F);
264                 break;
265
266             case 0x8000:
267                 /* Invalid opcode */
268                 Error ("Input file contains invalid opcode 0x8000");
269                 break;
270
271             case 0x8080:
272                 /* Draw */
273                 SB_AppendChar (Data, Buf[0] & 0x7F);
274                 SB_AppendChar (Data, Buf[1] | 0x80);
275                 break;
276         }
277
278         /* Next Op */
279         Buf += 2;
280         Remaining -= 2;
281     }
282 }
283
284
285
286 static void ConvertFile (const char* Input, const char* Output)
287 /* Convert one vector font file */
288 {
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
294         ** a header.
295         **
296         ** 0x50, 0x4B, 0x08, 0x08, 0x42, 0x47, 0x49, 0x20
297         */
298         0x50, 0x4B, 0x08, 0x08
299     };
300
301     /* The header of a TGI vector font file */
302     unsigned char TchHeader[] = {
303         0x54, 0x43, 0x48, 0x00,         /* "TCH" version */
304         0x00, 0x00,                     /* size of char definitions */
305         0x00,                           /* Top */
306         0x00,                           /* Baseline */
307         0x00,                           /* Bottom */
308     };
309
310     long           Size;
311     unsigned char* Buf;
312     unsigned char* MsgEnd;
313     unsigned       FirstChar;
314     unsigned       CharCount;
315     unsigned       LastChar;
316     unsigned       Char;
317     unsigned       Offs;
318     const unsigned char* OffsetBuf;
319     const unsigned char* WidthBuf;
320     const unsigned char* VectorBuf;
321     StrBuf         Offsets  = AUTO_STRBUF_INITIALIZER;
322     StrBuf         VectorData = AUTO_STRBUF_INITIALIZER;
323
324
325     /* Try to open the file for reading */
326     FILE* F = fopen (Input, "rb");
327     if (F == 0) {
328         Error ("Cannot open input file `%s': %s", Input, strerror (errno));
329     }
330
331     /* Seek to the end and determine the size */
332     fseek (F, 0, SEEK_END);
333     Size = ftell (F);
334
335     /* Seek back to the start of the file */
336     fseek (F, 0, SEEK_SET);
337
338     /* Check if the size is reasonable */
339     if (Size > 32*1024) {
340         Error ("Input file `%s' is too large (max = 32k)", Input);
341     } else if (Size < 0x100) {
342         Error ("Input file `%s' is too small to be a vector font file", Input);
343     }
344
345     /* Allocate memory for the file */
346     Buf = xmalloc ((size_t) Size);
347
348     /* Read the file contents into the buffer */
349     if (fread (Buf, 1, (size_t) Size, F) != (size_t) Size) {
350         Error ("Error reading from input file `%s'", Input);
351     }
352
353     /* Close the file */
354     (void) fclose (F);
355
356     /* Verify the header */
357     if (memcmp (Buf, ChrHeader, sizeof (ChrHeader)) != 0) {
358         Error ("Invalid format for `%s': invalid header", Input);
359     }
360     MsgEnd = memchr (Buf + sizeof (ChrHeader), 0x1A, 0x80);
361     if (MsgEnd == 0) {
362         Error ("Invalid format for `%s': description not found", Input);
363     }
364     if (MsgEnd[1] != 0x80 || MsgEnd[2] != 0x00) {
365         Error ("Invalid format for `%s': wrong header size", Input);
366     }
367
368     /* We expect the file to hold chars from 0x20 (space) to 0x7E (tilde) */
369     FirstChar = Buf[0x84];
370     CharCount = Buf[0x81] + (Buf[0x82] << 8);
371     LastChar  = FirstChar + CharCount - 1;
372     if (FirstChar > 0x20 || LastChar < 0x7E) {
373         Print (stderr, 1, "FirstChar = $%04X, CharCount = %u\n",
374                FirstChar, CharCount);
375         Error ("File `%s' doesn't contain the chars we need", Input);
376     } else if (LastChar >= 0x100) {
377         Error ("File `%s' contains too many character definitions", Input);
378     }
379
380     /* Print the copyright from the header */
381     Print (stderr, 1, "%.*s\n", (int) (MsgEnd - Buf - 4), Buf+4);
382
383     /* Get pointers to the width table, the offset table and the vector data
384     ** table. The first two corrected for 0x20 as first entry.
385     */
386     OffsetBuf = Buf + 0x90 + ((0x20 - FirstChar) * 2);
387     WidthBuf  = Buf + 0x90 + (CharCount * 2) + (0x20 - FirstChar);
388     VectorBuf = Buf + 0x90 + (CharCount * 3);
389
390     /* Convert the characters */
391     for (Char = 0x20; Char <= 0x7E; ++Char, OffsetBuf += 2) {
392
393         int Remaining;
394
395         /* Add the offset to the offset table */
396         Offs = SB_GetLen (&VectorData);
397         SB_AppendChar (&Offsets, Offs & 0xFF);
398         SB_AppendChar (&Offsets, (Offs >> 8) & 0xFF);
399
400         /* Get the offset of the vector data in the BGI data buffer */
401         Offs = OffsetBuf[0] + (OffsetBuf[1] << 8);
402
403         /* Calculate the remaining data in the buffer for this character */
404         Remaining = Size - (Offs + (VectorBuf - Buf));
405
406         /* Check if the offset is valid */
407         if (Remaining <= 0) {
408             Error ("Invalid data offset in input file `%s'", Input);
409         }
410
411         /* Convert the vector data and place it into the buffer */
412         ConvertChar (&VectorData, VectorBuf + Offs, Remaining);
413     }
414
415     /* Complete the TCH header */
416     Offs = 3 + 0x5F + 2*0x5F + SB_GetLen (&VectorData);
417     TchHeader[4] = Offs & 0xFF;
418     TchHeader[5] = (Offs >> 8) & 0xFF;
419     TchHeader[6] = Buf[0x88];
420     TchHeader[7] = (unsigned char) -(signed char)(Buf[0x8A]);
421     TchHeader[8] = TchHeader[6] + TchHeader[7];
422
423     /* The baseline must be zero, otherwise we cannot convert */
424     if (Buf[0x89] != 0) {
425         Error ("Baseline of font in `%s' is not zero", Input);
426     }
427
428     /* If the output file is NULL, use the name of the input file with ".tch"
429     ** appended.
430     */
431     if (Output == 0) {
432         Output = MakeFilename (Input, ".tch");
433     }
434
435     /* Open the output file */
436     F = fopen (Output, "wb");
437     if (F == 0) {
438         Error ("Cannot open output file `%s': %s", Output, strerror (errno));
439     }
440
441     /* Write the header to the output file */
442     if (fwrite (TchHeader, 1, sizeof (TchHeader), F) != sizeof (TchHeader)) {
443         Error ("Error writing to `%s' (disk full?)", Output);
444     }
445
446     /* Write the width table to the output file */
447     if (fwrite (WidthBuf, 1, 0x5F, F) != 0x5F) {
448         Error ("Error writing to `%s' (disk full?)", Output);
449     }
450
451     /* Write the offsets to the output file */
452     if (fwrite (SB_GetConstBuf (&Offsets), 1, 0x5F * 2, F) != 0x5F * 2) {
453         Error ("Error writing to `%s' (disk full?)", Output);
454     }
455
456     /* Write the data to the output file */
457     Offs = SB_GetLen (&VectorData);
458     if (fwrite (SB_GetConstBuf (&VectorData), 1, Offs, F) != Offs) {
459         Error ("Error writing to `%s' (disk full?)", Output);
460     }
461
462     /* Close the output file */
463     if (fclose (F) != 0) {
464         Error ("Error closing to `%s': %s", Output, strerror (errno));
465     }
466
467     /* Done */
468 }
469
470
471
472 int main (int argc, char* argv [])
473 /* Assembler main program */
474 {
475     /* Program long options */
476     static const LongOpt OptTab[] = {
477         { "--help",             0,      OptHelp                 },
478         { "--verbose",          0,      OptVerbose              },
479         { "--version",          0,      OptVersion              },
480     };
481
482     unsigned I;
483
484     /* Initialize the cmdline module */
485     InitCmdLine (&argc, &argv, "chrcvt65");
486
487     /* Check the parameters */
488     I = 1;
489     while (I < ArgCount) {
490
491         /* Get the argument */
492         const char* Arg = ArgVec[I];
493
494         /* Check for an option */
495         if (Arg [0] == '-') {
496             switch (Arg [1]) {
497
498                 case '-':
499                     LongOption (&I, OptTab, sizeof(OptTab)/sizeof(OptTab[0]));
500                     break;
501
502                 case 'h':
503                     OptHelp (Arg, 0);
504                     break;
505
506                 case 'v':
507                     OptVerbose (Arg, 0);
508                     break;
509
510                 case 'V':
511                     OptVersion (Arg, 0);
512                     break;
513
514                 default:
515                     UnknownOption (Arg);
516                     break;
517
518             }
519         } else {
520             /* Filename. Dump it. */
521             ConvertFile (Arg, 0);
522             ++FilesProcessed;
523         }
524
525         /* Next argument */
526         ++I;
527     }
528
529     /* Print a message if we did not process any files */
530     if (FilesProcessed == 0) {
531         fprintf (stderr, "%s: No input files\n", ProgName);
532     }
533
534     /* Success */
535     return EXIT_SUCCESS;
536 }
537
538
539