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
169static unsigned FilesProcessed = 0;
170
171
172
173/*****************************************************************************/
174/* Code */
175/*****************************************************************************/
176
177
178
179static 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
198static 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
208static void OptVerbose (const char* Opt attribute ((unused)),
209 const char* Arg attribute ((unused)))
210/* Increase verbosity */
211{
212 ++Verbosity;
213}
214
215
216
217static 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
228static 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
286static 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
472int 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