]> git.sur5r.net Git - cc65/blob - src/chrcvt/main.c
8b402655b756f70585e5e032d3f210402922754d
[cc65] / src / chrcvt / main.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                  main.c                                   */
4 /*                                                                           */
5 /*             Main program of the chrcvt vector font converter              */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 2000-2009, 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 /* chrcvt */
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>                           ; Value from $88
133  *      .byte   <baseline>                      ; Value from $89
134  *      .byte   <bottom>                        ; Negative value from $8A
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 opcodes get converted for easier handling: END is marked by bit 7
142  * set in the first byte. The second byte of this opcode is not needed.
143  * Bit 7 of the second byte marks a MOVE (bit 7 = 0) or DRAW (bit 7 = 1).
144  *
145  * The number of characters is fixed to $20..$7E (space to tilde), so character
146  * widths and offsets can be stored in fixed size preallocated tables. The
147  * space for the character definitions is allocated on the heap, it's size
148  * is stored in the header.
149  *
150  * Above structure allows a program to read the header portion of the file,
151  * validate it, then read the remainder of the file into memory in one chunk.
152  * The character definition offsets will then be converted into pointers by
153  * adding the character definition base pointer to each.
154  */
155
156
157
158 /*****************************************************************************/
159 /*                                   Data                                    */
160 /*****************************************************************************/
161
162
163
164 static unsigned FilesProcessed = 0;
165
166
167
168 /*****************************************************************************/
169 /*                                   Code                                    */
170 /*****************************************************************************/
171
172
173
174 static void Usage (void)
175 /* Print usage information and exit */
176 {
177     fprintf (stderr,
178              "Usage: %s [options] file [options] [file]\n"
179              "Short options:\n"
180              "  -h\t\t\tHelp (this text)\n"
181              "  -v\t\t\tBe more verbose\n"
182              "  -V\t\t\tPrint the version number and exit\n"
183              "\n"
184              "Long options:\n"
185              "  --help\t\tHelp (this text)\n"
186              "  --verbose\t\tBe more verbose\n"
187              "  --version\t\tPrint the version number and exit\n",
188              ProgName);
189 }
190
191
192
193 static void OptHelp (const char* Opt attribute ((unused)),
194                      const char* Arg attribute ((unused)))
195 /* Print usage information and exit */
196 {
197     Usage ();
198     exit (EXIT_SUCCESS);
199 }
200
201
202
203 static void OptVerbose (const char* Opt attribute ((unused)),
204                         const char* Arg attribute ((unused)))
205 /* Increase verbosity */
206 {
207     ++Verbosity;
208 }
209
210
211
212 static void OptVersion (const char* Opt attribute ((unused)),
213                         const char* Arg attribute ((unused)))
214 /* Print the assembler version */
215 {
216     fprintf (stderr,
217              "%s V%s - (C) Copyright 2009, Ullrich von Bassewitz\n",
218              ProgName, GetVersionAsString ());
219 }
220
221
222
223 static void ConvertChar (StrBuf* Data, const unsigned char* Buf)
224 /* Convert data for one character. Original data is in Buf, converted data
225  * will be placed in Data.
226  */
227 {
228     /* We should check here for reading past the end of the buffer in case the
229      * input data is not sane.
230      */
231     while (1) {
232
233         /* Get the next op word */
234         unsigned Op = (Buf[0] + (Buf[1] << 8)) & 0x8080;
235
236         /* Check the opcode */
237         switch (Op) {
238
239             case 0x0000:
240                 /* End */
241                 if (SB_IsEmpty (Data)) {
242                     /* No ops. We need to add an empty one */
243                     SB_AppendChar (Data, 0x00);
244                     SB_AppendChar (Data, 0x00);
245                 }
246                 /* Add an end marker to the last op in the buffer */
247                 SB_GetBuf (Data)[SB_GetLen (Data) - 2] |= 0x80;
248                 return;
249
250             case 0x0080:
251                 /* Move */
252                 SB_AppendChar (Data, Buf[0] & 0x7F);
253                 SB_AppendChar (Data, Buf[1] & 0x7F);
254                 break;
255
256             case 0x8000:
257                 /* Invalid opcode */
258                 Error ("Input file contains invalid opcode 0x8000");
259                 break;
260
261             case 0x8080:
262                 /* Draw */
263                 SB_AppendChar (Data, Buf[0] & 0x7F);
264                 SB_AppendChar (Data, Buf[1] | 0x80);
265                 break;
266         }
267
268         /* Next Op */
269         Buf += 2;
270     }
271 }
272
273
274
275 static void ConvertFile (const char* Input, const char* Output)
276 /* Convert one vector font file */
277 {
278     /* The header of a BGI vector font file */
279     static const unsigned char ChrHeader[] = {
280         0x50, 0x4B, 0x08, 0x08, 0x42, 0x47, 0x49, 0x20
281     };
282
283     /* The header of a TGI vector font file */
284     unsigned char TchHeader[] = {
285         0x54, 0x43, 0x48, 0x00,         /* "TCH" version */
286         0x00, 0x00,                     /* size of char definitions */
287         0x00,                           /* Top */
288         0x00,                           /* Baseline */
289         0x00,                           /* Bottom */
290     };
291
292     long           Size;
293     unsigned char* Buf;
294     unsigned char* MsgEnd;
295     unsigned       FirstChar;
296     unsigned       CharCount;
297     unsigned       LastChar;
298     unsigned       Char;
299     unsigned       Offs;
300     const unsigned char* OffsetBuf;
301     const unsigned char* WidthBuf;
302     const unsigned char* VectorBuf;
303     StrBuf         Offsets  = AUTO_STRBUF_INITIALIZER;
304     StrBuf         VectorData = AUTO_STRBUF_INITIALIZER;
305
306
307     /* Try to open the file for reading */
308     FILE* F = fopen (Input, "rb");
309     if (F == 0) {
310         Error ("Cannot open input file `%s': %s", Input, strerror (errno));
311     }
312
313     /* Seek to the end and determine the size */
314     fseek (F, 0, SEEK_END);
315     Size = ftell (F);
316
317     /* Seek back to the start of the file */
318     fseek (F, 0, SEEK_SET);
319
320     /* Check if the size is reasonable */
321     if (Size > 32*1024) {
322         Error ("Input file `%s' is too large (max = 32k)", Input);
323     } else if (Size < 0x100) {
324         Error ("Input file `%s' is too small to be a vector font file", Input);
325     }
326
327     /* Allocate memory for the file */
328     Buf = xmalloc ((size_t) Size);
329
330     /* Read the file contents into the buffer */
331     if (fread (Buf, 1, (size_t) Size, F) != (size_t) Size) {
332         Error ("Error reading from input file `%s'", Input);
333     }
334
335     /* Close the file */
336     (void) fclose (F);
337
338     /* Verify the header */
339     if (memcmp (Buf, ChrHeader, sizeof (ChrHeader)) != 0) {
340         Error ("Invalid format for `%s': invalid header", Input);
341     }
342     MsgEnd = memchr (Buf + sizeof (ChrHeader), 0x1A, 0x80);
343     if (MsgEnd == 0) {
344         Error ("Invalid format for `%s': description not found", Input);
345     }
346     if (MsgEnd[1] != 0x80 || MsgEnd[2] != 0x00) {
347         Error ("Invalid format for `%s': wrong header size", Input);
348     }
349
350     /* We expect the file to hold chars from 0x20 (space) to 0x7E (tilde) */
351     FirstChar = Buf[0x84];
352     CharCount = Buf[0x81] + (Buf[0x82] << 8);
353     LastChar  = FirstChar + CharCount - 1;
354     if (FirstChar > 0x20 || LastChar < 0x7E) {
355         Print (stderr, 1, "FirstChar = $%04X, CharCount = %u\n",
356                FirstChar, CharCount);
357         Error ("File `%s' doesn't contain the chars we need", Input);
358     } else if (LastChar >= 0x100) {
359         Error ("File `%s' contains too many character definitions", Input);
360     }
361
362     /* Print the copyright from the header */
363     Print (stderr, 1, "%.*s\n", (int) (MsgEnd - Buf - 4), Buf+4);
364
365     /* Get pointers to the width table, the offset table and the vector data
366      * table. The first two corrected for 0x20 as first entry.
367      */
368     OffsetBuf = Buf + 0x90 + ((0x20 - FirstChar) * 2);
369     WidthBuf  = Buf + 0x90 + (CharCount * 2) + (0x20 - FirstChar);
370     VectorBuf = Buf + 0x90 + (CharCount * 3);
371
372     /* Convert the characters */
373     for (Char = 0x20; Char <= 0x7E; ++Char, OffsetBuf += 2) {
374
375         /* Add the offset to the offset table */
376         Offs = SB_GetLen (&VectorData);
377         SB_AppendChar (&Offsets, Offs & 0xFF);
378         SB_AppendChar (&Offsets, (Offs >> 8) & 0xFF);
379
380         /* Get the offset of the vector data in the BGI data buffer */
381         Offs = OffsetBuf[0] + (OffsetBuf[1] << 8);
382
383         /* Check if the offset is valid */
384         if (Offs + (VectorBuf - Buf) > Size) {
385             Error ("Invalid data offset in input file `%s'", Input);
386         }
387
388         /* Convert the vector data and place it into the buffer */
389         ConvertChar (&VectorData, VectorBuf + Offs);
390     }
391
392     /* Complete the TCH header */
393     Offs = 3 + 0x5F + 2*0x5F + SB_GetLen (&VectorData);
394     TchHeader[4] = Offs & 0xFF;
395     TchHeader[5] = (Offs >> 8) & 0xFF;
396     TchHeader[6] = Buf[0x88];
397     TchHeader[7] = Buf[0x89];
398     TchHeader[8] = (unsigned char) -(signed char)(Buf[0x8A]);
399
400     /* If the output file is NULL, use the name of the input file with ".tch"
401      * appended.
402      */
403     if (Output == 0) {
404         Output = MakeFilename (Input, ".tch");
405     }
406
407     /* Open the output file */
408     F = fopen (Output, "wb");
409     if (F == 0) {
410         Error ("Cannot open output file `%s': %s", Output, strerror (errno));
411     }
412
413     /* Write the header to the output file */
414     if (fwrite (TchHeader, 1, sizeof (TchHeader), F) != sizeof (TchHeader)) {
415         Error ("Error writing to `%s' (disk full?)", Output);
416     }
417
418     /* Write the width table to the output file */
419     if (fwrite (WidthBuf, 1, 0x5F, F) != 0x5F) {
420         Error ("Error writing to `%s' (disk full?)", Output);
421     }
422
423     /* Write the offsets to the output file */
424     if (fwrite (SB_GetConstBuf (&Offsets), 1, 0x5F * 2, F) != 0x5F * 2) {
425         Error ("Error writing to `%s' (disk full?)", Output);
426     }
427
428     /* Write the data to the output file */
429     Offs = SB_GetLen (&VectorData);
430     if (fwrite (SB_GetConstBuf (&VectorData), 1, Offs, F) != Offs) {
431         Error ("Error writing to `%s' (disk full?)", Output);
432     }
433
434     /* Close the output file */
435     if (fclose (F) != 0) {
436         Error ("Error closing to `%s': %s", Output, strerror (errno));
437     }
438
439     /* Done */
440 }
441
442
443
444 int main (int argc, char* argv [])
445 /* Assembler main program */
446 {
447     /* Program long options */
448     static const LongOpt OptTab[] = {
449         { "--help",             0,      OptHelp                 },
450         { "--verbose",          0,      OptVerbose              },
451         { "--version",          0,      OptVersion              },
452     };
453
454     unsigned I;
455
456     /* Initialize the cmdline module */
457     InitCmdLine (&argc, &argv, "chrcvt");
458
459     /* Check the parameters */
460     I = 1;
461     while (I < ArgCount) {
462
463         /* Get the argument */
464         const char* Arg = ArgVec[I];
465
466         /* Check for an option */
467         if (Arg [0] == '-') {
468             switch (Arg [1]) {
469
470                 case '-':
471                     LongOption (&I, OptTab, sizeof(OptTab)/sizeof(OptTab[0]));
472                     break;
473
474                 case 'h':
475                     OptHelp (Arg, 0);
476                     break;
477
478                 case 'v':
479                     OptVerbose (Arg, 0);
480                     break;
481
482                 case 'V':
483                     OptVersion (Arg, 0);
484                     break;
485
486                 default:
487                     UnknownOption (Arg);
488                     break;
489
490             }
491         } else {
492             /* Filename. Dump it. */
493             ConvertFile (Arg, 0);
494             ++FilesProcessed;
495         }
496
497         /* Next argument */
498         ++I;
499     }
500
501     /* Print a message if we did not process any files */
502     if (FilesProcessed == 0) {
503         fprintf (stderr, "%s: No input files\n", ProgName);
504     }
505
506     /* Success */
507     return EXIT_SUCCESS;
508 }
509
510
511