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 [] = { |
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 [] = { |
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 | |
540 | |