1 | /** |
---|
2 | * cli.c: An exemplar, but fully functional and highly configurable |
---|
3 | * command line interface (CLI) to the paper tape low level drawing |
---|
4 | * routines (lochstreifen.c), which uses the famous cairo graphics |
---|
5 | * library for drawing. |
---|
6 | * |
---|
7 | * See ./program --help for an overview about the self-explanatory |
---|
8 | * arguments. By default, the program will read in any files in |
---|
9 | * stdin and print the genereated PNG file on stdout. |
---|
10 | * |
---|
11 | * This program is written in english only (but the sourcecode |
---|
12 | * contains some german comments). See an exemplar usage of this |
---|
13 | * program in a PHP web program in the web-frontend subproject. |
---|
14 | * |
---|
15 | * This program uses the argp.h argument parser from the glibc. |
---|
16 | * Thus it unfortunately won't compile with any other libc. |
---|
17 | * |
---|
18 | * Copyright (C) 2008 Sven Köppel |
---|
19 | * |
---|
20 | * This program is free software; you can redistribute it and/or |
---|
21 | * modify it under the terms of the GNU General Public License as |
---|
22 | * published by the Free Software Foundation; either version 3 of |
---|
23 | * the License, or (at your option) any later version. |
---|
24 | * |
---|
25 | * This program is distributed in the hope that it will be useful, |
---|
26 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
27 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
28 | * GNU General Public License for more details. |
---|
29 | * |
---|
30 | * You should have received a copy of the GNU General Public License |
---|
31 | * along with this program; if not, see |
---|
32 | * <http://www.gnu.org/licenses/>. |
---|
33 | * |
---|
34 | **/ |
---|
35 | |
---|
36 | #include <stdio.h> |
---|
37 | #include <stdlib.h> |
---|
38 | #include <string.h> |
---|
39 | #include <argp.h> |
---|
40 | |
---|
41 | #include "lochstreifen.h" |
---|
42 | |
---|
43 | LOCHSTREIFEN *l; |
---|
44 | enum _surface_type { |
---|
45 | PNG_SURFACE, |
---|
46 | SVG_SURFACE |
---|
47 | } surface_type; |
---|
48 | char *output_file; |
---|
49 | int verbosity = 0; |
---|
50 | int null_bytes_start = 0; |
---|
51 | int null_bytes_end = 0; |
---|
52 | enum { |
---|
53 | SCALE_BY_LENGTH, |
---|
54 | SCALE_BY_WIDTH, |
---|
55 | SCALE_BY_CODE_HOLE, |
---|
56 | } scale_by; |
---|
57 | // scale_target == 0 implies: no scaling! |
---|
58 | int scale_target = 0; |
---|
59 | |
---|
60 | #define DPRINTF(msg...) if(verbosity!=0) fprintf(stderr,msg); |
---|
61 | |
---|
62 | error_t parse_option (int key, char *arg, struct argp_state *state); |
---|
63 | cairo_pattern_t *hex2cairo_pattern(const char *string); |
---|
64 | |
---|
65 | const char *argp_program_version = "Punch card visualisator - CLI frontend"; |
---|
66 | |
---|
67 | static struct argp_option options[] = { |
---|
68 | {"verbose", 'v', 0, 0, "Produce verbose output on stderr" }, |
---|
69 | //{"quiet", 'q', 0, 0, "Don't produce any output" }, |
---|
70 | //{"silent", 's', 0, OPTION_ALIAS }, |
---|
71 | |
---|
72 | {"output", 'o', "FILE", 0, "Output to FILE (instead of standard output)" }, |
---|
73 | {"image", 'i', 0, OPTION_ALIAS }, |
---|
74 | {"format", 'f', "SVG|PNG",0, "Set desired output image format (PNG or SVG)" }, |
---|
75 | {"", 0, 0, OPTION_DOC, ""}, |
---|
76 | |
---|
77 | {"Dimensions", 0, 0, OPTION_DOC, "Dimensions are integers without units"}, |
---|
78 | {"width", 'w', "NUM", 0, "Set desired width for output image (in px or points, according to output format)", 1 }, |
---|
79 | {"height", 'h', "NUM", 0, "Set desired height for output image (unit like width argument)", 1 }, |
---|
80 | {"diameter", 'd', "NUM", 0, "Set dimensions for output image by punched hole diameter (pixel)", 1 }, |
---|
81 | {"", 0, 0, OPTION_DOC, "",1}, |
---|
82 | |
---|
83 | {"Empty bytes", 1, 0, OPTION_DOC, "Bytes with value=0x00 which are not content of files", 1}, |
---|
84 | {"empty-start", -1, "NUM", 0, "Set number of empty bytes at beginning (default=0)", 2 }, |
---|
85 | {"empty-end", -2, "NUM", 0, "Set number of empty bytes at end (default=0)", 2 }, |
---|
86 | {"", 0, 0, OPTION_DOC, "",2}, |
---|
87 | |
---|
88 | {"Colors", 0, 0, OPTION_DOC, "Color format: #RGB[A], #RRGGBB[AA]", 2 }, |
---|
89 | {"hide-imagebg", -3, 0, 0, "Make the image background (space around paper tape) transparent", 3 }, |
---|
90 | {"color-imagebg", -4, "#RGBA", 0, "Set image background color", 3 }, |
---|
91 | {"hide-tapebg", -5, 0, 0, "Hide the paper tape background", 3 }, |
---|
92 | {"color-tapebg", -6, "#RGBA", 0, "Set tape background color", 3 }, |
---|
93 | {"hide-punched", -7, 0, 0, "Hide the holes (only the punched ones)", 3 }, |
---|
94 | {"color-punched", -8, "#RGBA", 0, "Set color of holes (punched bits)", 3 }, |
---|
95 | {"hide-notpunched", -9, 0, 0, "Hide the holes which aren't punched on real tapes", 3 }, |
---|
96 | {"color-notpunched",-10, "#RGBA", 0, "Set color of bits with boolean value \"false\"", 3 }, |
---|
97 | {"hide-feedholes", -11, 0, 0, "Hide the feed holes (which means they wouldn't be punched)", 3 }, |
---|
98 | {"color-feedholes", -12, "#RGBA", 0, "Set color of feed holes (the small ones)", 3 }, |
---|
99 | {"", 0, 0, OPTION_DOC, "",3}, |
---|
100 | |
---|
101 | {"Transformations and Rotations", 0, 0, OPTION_DOC, "", 3}, |
---|
102 | {"rotation", -13, "right|bottom|left|top", 0, "Rotation of punched tape (alternative short notation: 0=right, 1=bottom, 2=left, 3=top)", 4 }, |
---|
103 | {"reflection-byte-direction", -14, 0, 0, "Enables horizontal reflecion on the data axis (inverts data direction)", 4 }, |
---|
104 | {"reflection-bit-direction", -15, 0, 0, "Enables vertical reflection on the other axis (inverts bit direction)", 4 }, |
---|
105 | {"", 0, 0, OPTION_DOC, "",-2}, |
---|
106 | |
---|
107 | { 0 } |
---|
108 | }; |
---|
109 | |
---|
110 | static struct argp argp = { options, parse_option, "[FILE TO READ FROM]", // Als Argument in der ersten Zeile |
---|
111 | "This program uses cairo to draw a punched tape as a PNG or SVG file. Any binary " // Hilfe oben |
---|
112 | "data are accepted via stdin or read in from the file given as argument. " |
---|
113 | "The produced image is written into stdout or in the filename given by -o." |
---|
114 | // mit \v danach koennte man ausfuehrliche Hilfe unten anzeigen. |
---|
115 | }; // static struct argp |
---|
116 | |
---|
117 | |
---|
118 | error_t parse_option (int key, char *arg, struct argp_state *state) { |
---|
119 | //printf("OPTION %x (%c = %i)...\n", key, key, key); |
---|
120 | switch(key) { |
---|
121 | case 'v': |
---|
122 | verbosity = 1; |
---|
123 | break; |
---|
124 | case 'o': case 'i': |
---|
125 | // Ausgabedatei setzen. |
---|
126 | output_file = arg; |
---|
127 | break; |
---|
128 | case 'w': |
---|
129 | // set length (yeah, the legacy parameters call this width) |
---|
130 | scale_by = SCALE_BY_LENGTH; |
---|
131 | scale_target = atoi(arg); |
---|
132 | //lochstreifen_set_scaling_by_length(l, atoi(arg)); |
---|
133 | break; |
---|
134 | case 'h': |
---|
135 | // set width (yeah, the legacy parameters call this height) |
---|
136 | scale_by = SCALE_BY_WIDTH; |
---|
137 | scale_target = atoi(arg); |
---|
138 | //lochstreifen_set_scaling_by_width(l, atoi(arg)); |
---|
139 | break; |
---|
140 | case 'd': |
---|
141 | // set diameter of code holes |
---|
142 | scale_by = SCALE_BY_CODE_HOLE; |
---|
143 | scale_target = atoi(arg); |
---|
144 | //lochstreifen_set_scaling_by_code_hole(l, atoi(arg)); |
---|
145 | break; |
---|
146 | case 'f': |
---|
147 | // set file format |
---|
148 | if(strcasecmp(arg, "png") == 0) |
---|
149 | surface_type = PNG_SURFACE; |
---|
150 | else if(strcasecmp(arg, "svg") == 0) |
---|
151 | surface_type = SVG_SURFACE; |
---|
152 | else |
---|
153 | argp_error(state, "Only PNG and SVG are supported as file formats.\n"); |
---|
154 | break; |
---|
155 | case -1: |
---|
156 | // set empty bytes at start |
---|
157 | null_bytes_start = atoi(arg); |
---|
158 | break; |
---|
159 | case -2: |
---|
160 | // set empty bytes at end |
---|
161 | null_bytes_end = atoi(arg); |
---|
162 | break; |
---|
163 | case -3: |
---|
164 | // hide imagebg |
---|
165 | l->outer_background_color = NULL; |
---|
166 | break; |
---|
167 | case -4: |
---|
168 | // color imagebg |
---|
169 | l->outer_background_color = hex2cairo_pattern(arg); |
---|
170 | break; |
---|
171 | case -5: |
---|
172 | // hide tape |
---|
173 | l->papertape_background_color = NULL; |
---|
174 | break; |
---|
175 | case -6: |
---|
176 | // color tapebg |
---|
177 | l->papertape_background_color = hex2cairo_pattern(arg); |
---|
178 | break; |
---|
179 | case -7: |
---|
180 | // hide punched |
---|
181 | l->one_code_hole_color = NULL; |
---|
182 | break; |
---|
183 | case -8: |
---|
184 | // color punched |
---|
185 | l->one_code_hole_color = hex2cairo_pattern(arg); |
---|
186 | break; |
---|
187 | case -9: |
---|
188 | // hide notpunched |
---|
189 | l->zero_code_hole_color = NULL; |
---|
190 | break; |
---|
191 | case -10: |
---|
192 | // color notpunched |
---|
193 | l->zero_code_hole_color = hex2cairo_pattern(arg); |
---|
194 | break; |
---|
195 | case -11: |
---|
196 | // hide feedholes |
---|
197 | l->feed_hole_color = NULL; |
---|
198 | break; |
---|
199 | case -12: |
---|
200 | // color fuerhung |
---|
201 | l->feed_hole_color = hex2cairo_pattern(arg); |
---|
202 | break; |
---|
203 | case -13: |
---|
204 | // rotation |
---|
205 | if (strcasecmp(arg, "right" ) == 0) arg = "0"; |
---|
206 | else if(strcasecmp(arg, "bottom") == 0) arg = "1"; |
---|
207 | else if(strcasecmp(arg, "left" ) == 0) arg = "2"; |
---|
208 | else if(strcasecmp(arg, "top" ) == 0) arg = "3"; |
---|
209 | arg[1] = '\0'; // shorten string to one character |
---|
210 | //lochstreifen_set_direction(l, atoi(arg)); |
---|
211 | lochstreifen_set_rotation(l, atoi(arg)); |
---|
212 | break; |
---|
213 | case -14: |
---|
214 | // horizontal spiegeln |
---|
215 | //lochstreifen_set_direction(l, -1, 1, -1); |
---|
216 | break; |
---|
217 | case -15: |
---|
218 | // vertikal spiegeln |
---|
219 | //lochstreifen_set_direction(l, -1, -1, 1); |
---|
220 | break; |
---|
221 | //case ARGP_KEY_END: |
---|
222 | //printf("bla..."); |
---|
223 | default: |
---|
224 | return ARGP_ERR_UNKNOWN; |
---|
225 | } // switch |
---|
226 | return 0; // success |
---|
227 | } // function parse_option |
---|
228 | |
---|
229 | |
---|
230 | /** |
---|
231 | * Simple helper function to get a SOLID cairo pattern from a hex color string |
---|
232 | * with formats like |
---|
233 | * #RGB for example: #FF0 |
---|
234 | * #RGBA #A88F |
---|
235 | * #RRGGBB #CD438F |
---|
236 | * #RRGGBBAA #CD438FA0 |
---|
237 | * RGB FF0 |
---|
238 | * RGBA A88F |
---|
239 | * RRGGBB CD438F |
---|
240 | * RRGGBBAA CD438FA0 |
---|
241 | * |
---|
242 | * If this method cannot parse the hex color string, it will print an error message |
---|
243 | * at stderr and exit the complete program. |
---|
244 | * |
---|
245 | * @return a dynamically allocated cairo patern |
---|
246 | **/ |
---|
247 | cairo_pattern_t *hex2cairo_pattern(const char *string) { |
---|
248 | int string_len; // length of string without "#" |
---|
249 | int x, color; // iterators |
---|
250 | long color_value[4]; // interpreted numbers |
---|
251 | char buf[3] = "xy"; // Buffer for strtol <- one color value |
---|
252 | |
---|
253 | // remove a "#" char if present |
---|
254 | if(string[0] == '#') |
---|
255 | string++; |
---|
256 | string_len = strlen(string); |
---|
257 | |
---|
258 | // go throught string |
---|
259 | for(x=0,color=0; x < string_len && color < 5; x++,color++) { |
---|
260 | // copy the current character to buffer, first position |
---|
261 | buf[0] = string[x]; |
---|
262 | |
---|
263 | // if short notation (shorter than AABBCC), dublicate |
---|
264 | // current character to buffer second position, else |
---|
265 | // copy next character to second position |
---|
266 | buf[1] = string_len < 6 ? string[x] : string[++x]; |
---|
267 | // parse buffer contents and save them as one color |
---|
268 | color_value[color] = strtol(buf, NULL, 16); |
---|
269 | } |
---|
270 | |
---|
271 | DPRINTF("Allocating '%s' as #%02x%02x%02x%02x\n", string, |
---|
272 | (unsigned int) color_value[0], (unsigned int) color_value[1], (unsigned int)color_value[2], (color == 4) ? (unsigned int) color_value[3] : 0xFF); |
---|
273 | |
---|
274 | return cairo_pattern_create_rgba( |
---|
275 | (double) color_value[0] / (double) 0xFF, |
---|
276 | (double) color_value[1] / (double) 0xFF, |
---|
277 | (double) color_value[2] / (double) 0xFF, |
---|
278 | (color == 4) ? (double) color_value[3] / (double) 0xFF : 1 |
---|
279 | ); |
---|
280 | } |
---|
281 | |
---|
282 | /** |
---|
283 | * Helper function: Read contents from a stream (e.g. stdin or a file) into |
---|
284 | * a byte array. |
---|
285 | * Expects: stream (FILE) and a pointer to a pointer (for the target array) |
---|
286 | * It will allocate an array whereby you get the pointer back. |
---|
287 | * @return length of data array, counting from 1 |
---|
288 | * |
---|
289 | * I've inspired a bit from glib's g_file_get_contents because my first |
---|
290 | * version was quite buggy: |
---|
291 | ***** |
---|
292 | * Neugeschrieben nach etwas Inspiration von der glib am 05.04.2008. |
---|
293 | * Funktion get_contents_stdio, gfileutils.c im Sourcecode von glib-2.14.3. |
---|
294 | * Router natürlich aus (03:11!), aber da sieht man mal wieder den Vorteil von Gentoo: |
---|
295 | * Gleich alle Sourcecodes auf der Platte =) |
---|
296 | ***** |
---|
297 | * |
---|
298 | **/ |
---|
299 | int file_get_contents(FILE *stream, byte_t **content) { |
---|
300 | byte_t buf[4096]; |
---|
301 | size_t bytes; // gerade eben eingelesene bytes |
---|
302 | byte_t *str = NULL; |
---|
303 | size_t total_bytes = 0; // alle bis jetzt eingelesenen bytes |
---|
304 | size_t total_allocated = 0; |
---|
305 | byte_t *tmp; // fuers realloc |
---|
306 | |
---|
307 | while(!feof(stream)) { |
---|
308 | bytes = fread(buf, 1, sizeof(buf), stream); |
---|
309 | |
---|
310 | while( (total_bytes + bytes) > total_allocated) { |
---|
311 | if(str) |
---|
312 | total_allocated *= 2; |
---|
313 | else |
---|
314 | total_allocated = bytes > sizeof(buf) ? bytes : sizeof(buf); |
---|
315 | |
---|
316 | tmp = realloc(str, total_allocated); |
---|
317 | |
---|
318 | if(tmp == NULL) { |
---|
319 | fprintf(stderr, "*** file_get_contents ERROR*** Could not reallocate\n"); |
---|
320 | //return length; // Fehler - das eingelesene zumindest zurueckgeben. |
---|
321 | // nee, gar nichts zurückgeben. Das geht so nicht. |
---|
322 | return 0; |
---|
323 | } |
---|
324 | |
---|
325 | str = tmp; |
---|
326 | } // while innen |
---|
327 | |
---|
328 | memcpy(str + total_bytes, buf, bytes); |
---|
329 | total_bytes += bytes; |
---|
330 | } // while |
---|
331 | |
---|
332 | if(total_allocated == 0) |
---|
333 | str = malloc(1); // something empty. Just to be not NULL... |
---|
334 | //str[total_bytes] = '\0'; // wenns ein string wäre. |
---|
335 | |
---|
336 | *content = str; |
---|
337 | return total_bytes; |
---|
338 | } |
---|
339 | |
---|
340 | /** |
---|
341 | * Helper function: A simple closure for a cairo_surface_t (PNG and SVG) to write out data |
---|
342 | * either to a file or to stdout (given in first parameter) |
---|
343 | **/ |
---|
344 | cairo_status_t lochstreifen_out_closure(void *closure, unsigned char *data, unsigned int length) { |
---|
345 | // einfach nur in das uebergebene Dateihandle schreiben |
---|
346 | fwrite(data, length, 1, (FILE *)closure); |
---|
347 | return CAIRO_STATUS_SUCCESS; |
---|
348 | } |
---|
349 | |
---|
350 | /** |
---|
351 | * The main routine. |
---|
352 | **/ |
---|
353 | int main(int argc, char *argv[]) { |
---|
354 | cairo_t *cr; ///< A cairo context, given from... |
---|
355 | cairo_surface_t *surface; ///< ...this generic cairo surface |
---|
356 | byte_t *data; ///< the data array, will be filled by file_get_contents |
---|
357 | int length; ///< the length of that data array |
---|
358 | FILE *out; ///< an output stream handle (stdout or a file) |
---|
359 | int input_argc; ///< argp: index of argv argument where the filenames are stored |
---|
360 | |
---|
361 | // now starting... |
---|
362 | l = lochstreifen_new(); |
---|
363 | argp_parse(&argp, argc, argv, 0, &input_argc, NULL); |
---|
364 | |
---|
365 | // read input data to data array |
---|
366 | if(input_argc < argc && argv[input_argc][0] != '-') { |
---|
367 | // open a file (which name is not "-", because this shall read from STDIN) |
---|
368 | FILE *fh; |
---|
369 | DPRINTF("Reading from file %s\n", argv[input_argc]); |
---|
370 | fh = fopen(argv[input_argc], "r"); |
---|
371 | |
---|
372 | if(fh == NULL) { |
---|
373 | perror("opening input file"); |
---|
374 | exit(1); |
---|
375 | } |
---|
376 | |
---|
377 | length = file_get_contents(fh, &data); |
---|
378 | fclose(fh); |
---|
379 | } else { |
---|
380 | DPRINTF("Reading from stdin, type [STRG]+[D] to generate paper tape from data\n"); |
---|
381 | length = file_get_contents(stdin, &data); |
---|
382 | } |
---|
383 | |
---|
384 | DPRINTF("Successfully read in %d bytes to RAM\n", length); |
---|
385 | |
---|
386 | // now after bytes are read in, we can setup the LOCHSTREIFEN |
---|
387 | // correctly: |
---|
388 | lochstreifen_set_data(l, length, data); |
---|
389 | if(scale_target != 0) { // initialized |
---|
390 | switch(scale_by) { |
---|
391 | case SCALE_BY_LENGTH: |
---|
392 | lochstreifen_set_scaling_by_length(l, scale_target); |
---|
393 | break; |
---|
394 | case SCALE_BY_WIDTH: |
---|
395 | lochstreifen_set_scaling_by_width(l, scale_target); |
---|
396 | break; |
---|
397 | case SCALE_BY_CODE_HOLE: |
---|
398 | lochstreifen_set_scaling_by_code_hole(l, scale_target); |
---|
399 | } |
---|
400 | } |
---|
401 | // add null bytes |
---|
402 | lochstreifen_add_null_bytes(l, null_bytes_start, null_bytes_end); |
---|
403 | |
---|
404 | // open output stream |
---|
405 | if(output_file != NULL) { // check if there was an argv argument |
---|
406 | out = fopen(output_file, "w"); |
---|
407 | |
---|
408 | if(out == NULL) { |
---|
409 | perror("opening output file"); |
---|
410 | exit(1); |
---|
411 | } |
---|
412 | DPRINTF("Opened file '%s' for writing\n", output_file); |
---|
413 | } else { |
---|
414 | DPRINTF("Writing output data to stdout\n"); |
---|
415 | out = stdout; |
---|
416 | } |
---|
417 | |
---|
418 | if(verbosity!=0) |
---|
419 | lochstreifen_print_debug(l); |
---|
420 | |
---|
421 | // setting up the surface and painting... |
---|
422 | if(surface_type == PNG_SURFACE) { |
---|
423 | cairo_status_t status; |
---|
424 | surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, |
---|
425 | lochstreifen_get_target_width(l), |
---|
426 | lochstreifen_get_target_height(l) |
---|
427 | ); |
---|
428 | cr = cairo_create(surface); |
---|
429 | lochstreifen_draw(l, cr); |
---|
430 | |
---|
431 | status = cairo_surface_write_to_png_stream(surface, (cairo_write_func_t)lochstreifen_out_closure, out); |
---|
432 | DPRINTF("PNG file generated: %s\n", cairo_status_to_string(status)); |
---|
433 | return 0; |
---|
434 | } else if(surface_type == SVG_SURFACE) { |
---|
435 | surface = cairo_svg_surface_create_for_stream( |
---|
436 | (cairo_write_func_t)lochstreifen_out_closure, out, |
---|
437 | lochstreifen_get_target_width(l), |
---|
438 | lochstreifen_get_target_height(l) |
---|
439 | ); |
---|
440 | cr = cairo_create(surface); |
---|
441 | lochstreifen_draw(l, cr); |
---|
442 | |
---|
443 | DPRINTF("SVG file generated: %s\n", cairo_status_to_string(cairo_surface_status(surface))); |
---|
444 | return 0; |
---|
445 | } else { |
---|
446 | fprintf(stderr, "This surface is not possible, because surface_typ is an enum.\n"); |
---|
447 | return -42; |
---|
448 | } // if surface_type |
---|
449 | } // main |
---|