source: projects/paper-tape-project/trunk/visualisator/cli.c @ 73

Last change on this file since 73 was 73, checked in by sven, 6 years ago

PaperTape Restauration: Fixed various bugs to bring back Web and visualisation CLI application:

  • Documentation: Fixed Links and added "Page superseded" box
  • Web-Frontend: Replaced buggy no-more-working-in-modern-browsers JavaScript ajax-POST-library by contemporary jquery
  • Visualisator: lochstreifen.c and cli.c, Makefile: Fixed bugs for modern compiler warnings. Now again the code compiles and works.
File size: 15.1 KB
Line 
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
43LOCHSTREIFEN *l;
44enum _surface_type {
45        PNG_SURFACE,
46        SVG_SURFACE
47} surface_type;
48char *output_file;
49int verbosity = 0;
50int null_bytes_start = 0;
51int null_bytes_end = 0;
52enum {
53        SCALE_BY_LENGTH,
54        SCALE_BY_WIDTH,
55        SCALE_BY_CODE_HOLE,
56} scale_by;
57// scale_target == 0 implies: no scaling!
58int scale_target = 0;
59
60#define DPRINTF(msg...) if(verbosity!=0) fprintf(stderr,msg);
61
62error_t parse_option (int key, char *arg, struct argp_state *state);
63cairo_pattern_t *hex2cairo_pattern(const char *string);
64
65const char *argp_program_version = "Punch card visualisator - CLI frontend";
66
67static 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
110static 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
118error_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 **/
247cairo_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 **/
299int 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 **/
344cairo_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 **/
353int 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
Note: See TracBrowser for help on using the repository browser.
© 2008 - 2013 technikum29 • Sven Köppel • Some rights reserved
Powered by Trac
Expect where otherwise noted, content on this site is licensed under a Creative Commons 3.0 License