1 | /** |
---|
2 | * The Paper Tape Project -- Font subproject |
---|
3 | * PaperTapeFont C Library / Stand alone frontend |
---|
4 | * |
---|
5 | * This file contains library routines (see papertapefont.h) for |
---|
6 | * reading and writing "papertape font" files, as well as a |
---|
7 | * stand alone program. |
---|
8 | * |
---|
9 | * To compile the CLI program, define STANDALONE, e.g. |
---|
10 | * with |
---|
11 | * |
---|
12 | * gcc papertapefont.c -DSTANDALONE=1 |
---|
13 | * |
---|
14 | * Initially written in a few hours (500 lines code) between |
---|
15 | * 02:00 and 06:00 at 04.09.2008 |
---|
16 | * |
---|
17 | * (c) 2008 Sven Köppel |
---|
18 | * |
---|
19 | * This program is free software; you can redistribute |
---|
20 | * it and/or modify it under the terms of the GNU General |
---|
21 | * Public License as published by the Free Software |
---|
22 | * Foundation; either version 3 of the License, or (at |
---|
23 | * your option) any later version. |
---|
24 | * |
---|
25 | * This program is distributed in the hope that it will |
---|
26 | * be useful, but WITHOUT ANY WARRANTY; without even the |
---|
27 | * implied warranty of MERCHANTABILITY or FITNESS FOR A |
---|
28 | * PARTICULAR PURPOSE. See the GNU General Public License |
---|
29 | * for more details. |
---|
30 | * |
---|
31 | * You should have received a copy of the GNU General |
---|
32 | * Public License along with this program; if not, see |
---|
33 | * http://www.gnu.org/licenses/. |
---|
34 | * |
---|
35 | **/ |
---|
36 | |
---|
37 | #include "papertapefont.h" |
---|
38 | #include <stdio.h> |
---|
39 | #include <stdlib.h> |
---|
40 | #include <string.h> |
---|
41 | |
---|
42 | #define PAPERTAPE_FONT_MAX_LINE_LENGTH 100 |
---|
43 | |
---|
44 | byte_t papertape_font_line2byte(const char *line); |
---|
45 | |
---|
46 | PAPERTAPE_FONT* papertape_font_new_from_file(FILE* fh) { |
---|
47 | PAPERTAPE_FONT* font; |
---|
48 | char line[PAPERTAPE_FONT_MAX_LINE_LENGTH]; |
---|
49 | char ascii_id; |
---|
50 | byte_t* bytes; |
---|
51 | int bytes_n; |
---|
52 | int x; |
---|
53 | |
---|
54 | // allocate new font object |
---|
55 | font = malloc(sizeof(PAPERTAPE_FONT)); |
---|
56 | if(!font || !fh) |
---|
57 | return NULL; |
---|
58 | |
---|
59 | // set up as ensurance, against segfaults or simple fonts: |
---|
60 | font->ascii_id = PAPERTAPE_FONT_CHARACTER_SPACING_ASCII_ID; |
---|
61 | font->bytes = PAPERTAPE_FONT_CHARACTER_SPACING_DEFAULT_BYTES; |
---|
62 | font->bytes_n = sizeof(PAPERTAPE_FONT_CHARACTER_SPACING_DEFAULT_BYTES); |
---|
63 | |
---|
64 | /*{ |
---|
65 | byte_t[] def_spacing = PAPERTAPE_FONT_CHARACTER_SPACING_DEFAULT_BYTES; |
---|
66 | papertape_font_set_char(font, PAPERTAPE_FONT_CHARACTER_SPACING_ASCII_ID, |
---|
67 | def_spacing, sizeof(def_spacing)); |
---|
68 | |
---|
69 | }*/ |
---|
70 | |
---|
71 | // now parse file. |
---|
72 | |
---|
73 | // for each character package... |
---|
74 | while(fgets(line, PAPERTAPE_FONT_MAX_LINE_LENGTH, fh)) { |
---|
75 | // skip comment lines. They are only allowed at |
---|
76 | // index positions, not in the data byte lines. |
---|
77 | if(line[0] == '#') |
---|
78 | continue; |
---|
79 | |
---|
80 | ascii_id = line[0]; |
---|
81 | bytes_n = atoi(&line[1]); // atoi will remove spaces |
---|
82 | bytes = calloc(sizeof(byte_t), bytes_n); // allocate place for bytes |
---|
83 | |
---|
84 | // parse the bytes notation |
---|
85 | for(x=0; x < bytes_n; x++) { |
---|
86 | // break at end of line |
---|
87 | if(!fgets(line, PAPERTAPE_FONT_MAX_LINE_LENGTH, fh)) |
---|
88 | break; |
---|
89 | // add parsed line to byte array |
---|
90 | bytes[x] = papertape_font_line2byte(line); |
---|
91 | } |
---|
92 | |
---|
93 | papertape_font_set_char(font, ascii_id, bytes, bytes_n); |
---|
94 | // papertape_font_set_char copies the byte, so we free them here |
---|
95 | free(bytes); |
---|
96 | } |
---|
97 | } |
---|
98 | |
---|
99 | /** |
---|
100 | * Parse a line to a byte. This is only used internally. |
---|
101 | * Yeah, and it would be more performant to define this as a Macro, but that |
---|
102 | * can be performed using gcc -O2, too. |
---|
103 | **/ |
---|
104 | byte_t papertape_font_line2byte(const char *line) { |
---|
105 | byte_t value = 0; |
---|
106 | #define isPunched(pos, add) if(line[pos] != ' ') { value += add; } |
---|
107 | isPunched(0 , 0); |
---|
108 | isPunched(1 , 1); |
---|
109 | isPunched(2, 2); |
---|
110 | isPunched(3, 4); |
---|
111 | isPunched(4, 8); |
---|
112 | isPunched(5, 16); |
---|
113 | isPunched(6, 32); |
---|
114 | isPunched(7, 64); |
---|
115 | isPunched(8, 128); |
---|
116 | return value; |
---|
117 | } |
---|
118 | |
---|
119 | |
---|
120 | /** |
---|
121 | * This is the main function how to add or change a character in the papertape_font. |
---|
122 | * Internally, the papertape_font works with a ascii ordered single linked list. So |
---|
123 | * there's no other write access to that list than this function. |
---|
124 | * |
---|
125 | * @param font The Papertape Font object, e.g. made by papertape_font_new_...() |
---|
126 | * @param ascii_id The ASCII value of the character you want to add. |
---|
127 | * @param bytes Your byte array which is supposed to represent the character. The function copies it. |
---|
128 | * @param bytes_n The length of your byte array (counting from 1, of course) |
---|
129 | * @return 0 if successfully added, 1 otherwise. (It will never return 1 ;-) ) |
---|
130 | **/ |
---|
131 | int papertape_font_set_char(PAPERTAPE_FONT* font, char ascii_id, const byte_t* bytes, int bytes_n) { |
---|
132 | PAPERTAPE_FONT_CHAR *current; |
---|
133 | PAPERTAPE_FONT_CHAR *next; |
---|
134 | |
---|
135 | // allocate the new character structure |
---|
136 | PAPERTAPE_FONT_CHAR *buf; |
---|
137 | buf = malloc(sizeof(PAPERTAPE_FONT_CHAR)); |
---|
138 | |
---|
139 | // copy all values to structure |
---|
140 | buf->ascii_id = ascii_id; |
---|
141 | buf->bytes_n = bytes_n; |
---|
142 | buf->bytes = malloc(bytes_n); |
---|
143 | memcpy(buf->bytes, bytes, bytes_n); |
---|
144 | buf->next = NULL; |
---|
145 | |
---|
146 | // Magic ascii id (typically a non printable character) for character spacing |
---|
147 | if(ascii_id == PAPERTAPE_FONT_CHARACTER_SPACING_ASCII_ID) { |
---|
148 | font->bytes = buf->bytes; |
---|
149 | font->bytes_n = buf->bytes_n; |
---|
150 | free(buf); // don't need it. |
---|
151 | return 0; |
---|
152 | } |
---|
153 | |
---|
154 | // Start going throught the list |
---|
155 | current = font; // Start with the font object as first list member. |
---|
156 | while(current->next != NULL) { |
---|
157 | if(current->next->ascii_id == ascii_id) { |
---|
158 | // the next one has the same ascii_id as we have (perhaps |
---|
159 | // we are 'c' and the next is 'c', too). So replace the next |
---|
160 | // one. |
---|
161 | next = current->next; // copy the pointer somewhere else |
---|
162 | current->next = buf; // replace the next pointer with our char |
---|
163 | free(next); // delete the old next pointer. |
---|
164 | return 0; |
---|
165 | } else if(current->next->ascii_id > ascii_id) { |
---|
166 | // the next one has a bigger ascii_id as we have (perhapse |
---|
167 | // we are 'c' while the next is 'e'). So pack our character |
---|
168 | // right here, between current and next. |
---|
169 | next = current->next; // copy the pointer somewhere else |
---|
170 | current->next = buf; // replace the next pointer with our char |
---|
171 | buf->next = next; // pack the next pointer after our pointer |
---|
172 | return 0; |
---|
173 | } else { |
---|
174 | // the next one has a lower ascii_id as we have, so we have |
---|
175 | // to go on throught the list |
---|
176 | current = current->next; |
---|
177 | } |
---|
178 | } |
---|
179 | |
---|
180 | // we have reached the end of the list and haven't found any ascii_id biggger |
---|
181 | // or equal to ours. So we simply add our character to the list. |
---|
182 | current->next = buf; |
---|
183 | return 0; |
---|
184 | } |
---|
185 | |
---|
186 | /** |
---|
187 | * This is the main function from the papertape_font package. Using this function, you |
---|
188 | * can render any ASCII string to a papertape label, using the current font. |
---|
189 | * |
---|
190 | * The return byte array will be malloced, and you'll have after calling the only |
---|
191 | * link to that array. So feel free to free() it, if you don't need it any more. |
---|
192 | * |
---|
193 | * If there's a non printable character in your string, this function will print out an |
---|
194 | * error message at stderr and skip that character. Use papertape_font_string_is_printable |
---|
195 | * if you want to check wheter your string contains non printable characters. |
---|
196 | * |
---|
197 | * Internally speaken, this is the only read access function to the internal single |
---|
198 | * linked font character list. |
---|
199 | * |
---|
200 | **/ |
---|
201 | byte_t* papertape_font_get_label(PAPERTAPE_FONT* font, const char* string) { |
---|
202 | PAPERTAPE_FONT_CHAR* current; |
---|
203 | //int string_len = strlen(string); |
---|
204 | int x; // to count throught the string |
---|
205 | int size = 0; // the size of the output byte array |
---|
206 | byte_t* output; |
---|
207 | int pos; // current position in output array |
---|
208 | |
---|
209 | // first step: Count the neccessary size for the output byte array |
---|
210 | for(x=0; string[x] != '\0'; x++) { |
---|
211 | // skip the font spacing at one character in the string |
---|
212 | // (because it's only between the letters) |
---|
213 | if(x != 0) |
---|
214 | size += font->bytes_n; |
---|
215 | |
---|
216 | // go throught the list and try to find the current character. |
---|
217 | current = font; |
---|
218 | while(1) { |
---|
219 | if(current->ascii_id == string[x]) { |
---|
220 | size += current->bytes_n; |
---|
221 | break; |
---|
222 | } else if(current->next != NULL) { |
---|
223 | current = current->next; |
---|
224 | continue; |
---|
225 | } else { |
---|
226 | // We have reached the end of the character |
---|
227 | // list, but the current character is not available! |
---|
228 | fprintf(stderr, "papertape_font_get_label: Character \"%c\" in \"%s\" not printable\n", |
---|
229 | string[x], string); |
---|
230 | break; |
---|
231 | } |
---|
232 | } |
---|
233 | } |
---|
234 | |
---|
235 | // now malloc the output byte array and generate it |
---|
236 | output = malloc(size); |
---|
237 | for(x=0,pos=0; string[x] != '\0'; x++) { |
---|
238 | // go throught the list, another time |
---|
239 | current = font; |
---|
240 | while(1) { |
---|
241 | if(current->ascii_id == string[x]) { |
---|
242 | memcpy(output+pos, current->bytes, current->bytes_n); |
---|
243 | pos += current->bytes_n; |
---|
244 | break; |
---|
245 | } else if(current->next != NULL) { |
---|
246 | current = current->next; |
---|
247 | continue; |
---|
248 | } // else: Character not found. Ignore it, |
---|
249 | // because error already printed at first run. |
---|
250 | } |
---|
251 | // add the character spacer after each character |
---|
252 | if(x+1 < strlen(string)) { // but not at the last character |
---|
253 | memcpy(output+pos, font->bytes, font->bytes_n); |
---|
254 | pos += font->bytes_n; |
---|
255 | } |
---|
256 | } |
---|
257 | |
---|
258 | // we are done. |
---|
259 | return output; |
---|
260 | } |
---|
261 | |
---|
262 | /** |
---|
263 | * Checks if there's any non printable character in your string. Papertape fonts don't have |
---|
264 | * to have bit masks for every character, so use this function to check your string. |
---|
265 | * papertape_font_get_label will output errors to stderr if it finds unprintable |
---|
266 | * characters. |
---|
267 | * |
---|
268 | * @returns 1 if all characters are printable, 0 otherwise. |
---|
269 | **/ |
---|
270 | int papertape_font_string_is_printable(PAPERTAPE_FONT *font, const char* string) { |
---|
271 | PAPERTAPE_FONT_CHAR* current; |
---|
272 | int x; |
---|
273 | |
---|
274 | for(x=0; string[x] != '\0'; x++) { |
---|
275 | current = font; |
---|
276 | while(1) { |
---|
277 | if(current->ascii_id == string[x]) |
---|
278 | // this character is clean. |
---|
279 | break; |
---|
280 | else if(current->next != NULL) |
---|
281 | // have not found character yet. |
---|
282 | current = current->next; |
---|
283 | else |
---|
284 | // at the end of list -- character not found! |
---|
285 | return 0; |
---|
286 | } |
---|
287 | } |
---|
288 | |
---|
289 | return 1; // No non printable char found. |
---|
290 | } |
---|
291 | |
---|
292 | /** |
---|
293 | * Write the current PAPERTAPE_FONT (back) out to a file. |
---|
294 | * FILE mus be a writeable handle. |
---|
295 | **/ |
---|
296 | int papertape_font_write_to_file(PAPERTAPE_FONT* font, FILE *fh) { |
---|
297 | PAPERTAPE_FONT_CHAR* current; |
---|
298 | int x; |
---|
299 | |
---|
300 | // Write first lines. |
---|
301 | fputs(PAPERTAPE_FONT_WRITE_FIRST_LINES, fh); |
---|
302 | |
---|
303 | // now go throught list and write all characters out. |
---|
304 | current = font; |
---|
305 | while(1) { |
---|
306 | // write index line |
---|
307 | fprintf(fh, "%c %i\n", current->ascii_id, current->bytes_n); |
---|
308 | // write data binary things. |
---|
309 | for(x=0; x<current->bytes_n; x++) { |
---|
310 | #define paintPunched(check) fputc((current->bytes[x] & check) ?\ |
---|
311 | fputc(PAPERTAPE_FONT_WRITE_LOGICAL_1_CHARACTER, fh) : fputc(' ', fh), fh) |
---|
312 | paintPunched( 0); |
---|
313 | paintPunched( 1); |
---|
314 | paintPunched( 2); |
---|
315 | paintPunched( 4); |
---|
316 | paintPunched( 8); |
---|
317 | paintPunched( 16); |
---|
318 | paintPunched( 32); |
---|
319 | paintPunched( 64); |
---|
320 | paintPunched(128); |
---|
321 | fputc('\n', fh); |
---|
322 | } |
---|
323 | |
---|
324 | // and go on. Or break, if at end. |
---|
325 | if(current->next != NULL) |
---|
326 | current = current->next; |
---|
327 | else |
---|
328 | break; |
---|
329 | } |
---|
330 | } |
---|
331 | |
---|
332 | /* If we want it to be a standalone program */ |
---|
333 | #ifdef STANDALONE |
---|
334 | int file_get_contents(FILE *stream, byte_t **content) { |
---|
335 | byte_t buf[4096]; |
---|
336 | size_t bytes; // gerade eben eingelesene bytes |
---|
337 | byte_t *str = NULL; |
---|
338 | size_t total_bytes = 0; // alle bis jetzt eingelesenen bytes |
---|
339 | size_t total_allocated = 0; |
---|
340 | byte_t *tmp; // fuers realloc |
---|
341 | |
---|
342 | while(!feof(stream)) { |
---|
343 | bytes = fread(buf, 1, sizeof(buf), stream); |
---|
344 | |
---|
345 | while( (total_bytes + bytes) > total_allocated) { |
---|
346 | if(str) |
---|
347 | total_allocated *= 2; |
---|
348 | else |
---|
349 | total_allocated = bytes > sizeof(buf) ? bytes : sizeof(buf); |
---|
350 | |
---|
351 | tmp = realloc(str, total_allocated); |
---|
352 | |
---|
353 | if(tmp == NULL) { |
---|
354 | fprintf(stderr, "*** file_get_contents ERROR*** Could not reallocate\n"); |
---|
355 | //return length; // Fehler - das eingelesene zumindest zurueckgeben. |
---|
356 | // nee, gar nichts zurückgeben. Das geht so nicht. |
---|
357 | return 0; |
---|
358 | } |
---|
359 | |
---|
360 | str = tmp; |
---|
361 | } // while innen |
---|
362 | |
---|
363 | memcpy(str + total_bytes, buf, bytes); |
---|
364 | total_bytes += bytes; |
---|
365 | } // while |
---|
366 | |
---|
367 | if(total_allocated == 0) |
---|
368 | str = malloc(1); // something empty. Just to be not NULL... |
---|
369 | //str[total_bytes] = '\0'; // wenns ein string wäre. |
---|
370 | |
---|
371 | *content = str; |
---|
372 | return total_bytes; |
---|
373 | } |
---|
374 | |
---|
375 | int main(int argc, char **argv) { |
---|
376 | enum { |
---|
377 | RENDER_TEXT, |
---|
378 | WRITE_CHAR, |
---|
379 | EXPORT, |
---|
380 | IMPORT, |
---|
381 | NOTHING |
---|
382 | } action; |
---|
383 | action = NOTHING; |
---|
384 | char *schematics = PAPERTAPE_FONT_SCHEMATICS_DEFAULT; |
---|
385 | char *render_text; |
---|
386 | char write_char; |
---|
387 | char *font_name; |
---|
388 | FILE *font_file; |
---|
389 | PAPERTAPE_FONT *font; |
---|
390 | int c; |
---|
391 | |
---|
392 | // poor man's getopt, for windows portability |
---|
393 | for(c=1; c<argc; c++) { |
---|
394 | if(strlen(argv[c]) == 2 && *argv[c] == '-') { |
---|
395 | argv[c]++; // shift "-" flag thingy. |
---|
396 | switch(*argv[c]) { |
---|
397 | case 'h': |
---|
398 | fprintf(stderr, "Usage: %s [OPTIONS...] [A PAPERTAPE FONT FILE]\n" |
---|
399 | "The Papertape font utilitie program.\n\n" |
---|
400 | " optional parameters:\n" |
---|
401 | " -h display this help message\n" |
---|
402 | " -l [TEXT] Render this text to STDOUT\n" |
---|
403 | " -w [ASCII ID] Set the character with that ASCII ID, read from STDIN\n" |
---|
404 | " -o Export the font file, according to schematics, to STDOUT\n" |
---|
405 | " -i Import to font file (overwrite it), according to schematics, from STDIN\n\n" |
---|
406 | " -s [Sch string] Define own schematics for -i/-o. Default is:\n" |
---|
407 | " %s\n", |
---|
408 | argv[0], PAPERTAPE_FONT_SCHEMATICS_DEFAULT); |
---|
409 | return 0; |
---|
410 | |
---|
411 | case 'l': |
---|
412 | action = RENDER_TEXT; |
---|
413 | render_text = argv[++c]; |
---|
414 | break; |
---|
415 | |
---|
416 | case 'w': |
---|
417 | action = WRITE_CHAR; |
---|
418 | write_char = *argv[++c]; |
---|
419 | break; |
---|
420 | |
---|
421 | case 'o': action = EXPORT; break; |
---|
422 | case 'i': action = IMPORT; break; |
---|
423 | |
---|
424 | case 's': |
---|
425 | schematics = argv[++c]; |
---|
426 | } // switch |
---|
427 | } // if flag |
---|
428 | else { |
---|
429 | // not flag but... Papertape Font file! |
---|
430 | font_name = argv[c]; |
---|
431 | } |
---|
432 | } // for each argv |
---|
433 | |
---|
434 | // Open Font File |
---|
435 | if(font_name) { |
---|
436 | char *mode; |
---|
437 | switch(action) { |
---|
438 | case RENDER_TEXT: |
---|
439 | case EXPORT: mode = "r"; break; |
---|
440 | case WRITE_CHAR: mode = "rw"; break; |
---|
441 | case IMPORT: mode = "w"; break; |
---|
442 | default: |
---|
443 | fprintf(stderr, "Error: No action specified. Call %s -h\n", argv[0]); |
---|
444 | return 1; |
---|
445 | } |
---|
446 | |
---|
447 | font_file = fopen(font_name, mode); |
---|
448 | if(!font_file) { |
---|
449 | perror("Opening font file"); |
---|
450 | return 2; |
---|
451 | } |
---|
452 | } else { |
---|
453 | fprintf(stderr, "Error: No font file specified. Call %s -h\n", argv[0]); |
---|
454 | } |
---|
455 | |
---|
456 | // Stupid: papertapefont.c:431: Warnung: Zuweisung erzeugt Zeiger von Ganzzahl ohne Typkonvertierung |
---|
457 | // Adding (PAPERTAPE_FONT*) did the job. |
---|
458 | font = (PAPERTAPE_FONT*) papertape_new_from_file(font_file); |
---|
459 | if(font == NULL) { |
---|
460 | fprintf(stderr, "Error creating PAPERTAPE_FONT object\n"); |
---|
461 | return 3; |
---|
462 | } |
---|
463 | |
---|
464 | // Perform action |
---|
465 | if(action == RENDER_TEXT && render_text != NULL) { |
---|
466 | papertape_font_get_label(font, render_text); |
---|
467 | } else if(action == WRITE_CHAR) { |
---|
468 | byte_t *contents; |
---|
469 | int bytes = file_get_contents(stdin, &contents); |
---|
470 | printf("Read in %i bytes for letter %c (seen as byte: 0x%x)\n", bytes, write_char, write_char); |
---|
471 | papertape_font_set_char(font, write_char, contents, bytes); |
---|
472 | } else if(action == EXPORT) { |
---|
473 | fprintf(stderr, "Printing file %s with schematics %s\n", font_name, schematics); |
---|
474 | puts(papertape_font_get_label(font, schematics)); |
---|
475 | } else if(action == IMPORT) { |
---|
476 | byte_t *contents; |
---|
477 | int bytes, x; |
---|
478 | |
---|
479 | fprintf(stderr, "Reading from stdin, expecting schematics %s\n", schematics); |
---|
480 | bytes = file_get_contents(stdin, &contents); |
---|
481 | |
---|
482 | fprintf(stderr, "To be implemented...\n"); |
---|
483 | |
---|
484 | // Parse schematics |
---|
485 | /*for(x=0; x<strlen(schematics); x++) { |
---|
486 | papertape_font_set_char(font, schematics[x], |
---|
487 | }*/ |
---|
488 | |
---|
489 | } else { |
---|
490 | fprintf(stderr, "No correct call! See %s -h\n", argv[0]); |
---|
491 | } |
---|
492 | return 0; |
---|
493 | } |
---|
494 | #endif /* STANDALONE */ |
---|