source: projects/visualisator/cli.c @ 18

Last change on this file since 18 was 18, checked in by sven, 11 years ago

This is a preview of the new visualisator subsystem: The new LOCHSTREIFEN
files. The CLI interface from cli.c is still the same like before, so it
should be compatible to the old LOCHSTREIFEN interface. All new routines
are still written in Plain C and well explained.

-- Sven @ workstation.

File size: 15.0 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 = "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                // if short notation (shorter than AABBCC), dublicate
263                // current character to buffer second position, else
264                // copy next character to second position
265                buf[1] = string_len < 6 ? string[x] : string[++x];
266                // parse buffer contents and save them as one color
267                color_value[color] = strtol(buf, NULL, 16);
268        }
269       
270        DPRINTF("Allocating '%s' as #%x%x%x%x\n", string,
271                color_value[0], color_value[1], color_value[2], (color == 4) ? color_value[3] : 0xFF);
272       
273        return cairo_pattern_create_rgba(
274                (double) color_value[0] / (double) 0xFF,
275                (double) color_value[1] / (double) 0xFF,
276                (double) color_value[2] / (double) 0xFF,
277                (color == 4) ? (double) color_value[3] / (double) 0xFF : 1
278        );
279}
280
281/**
282 * Helper function: Read contents from a stream (e.g. stdin or a file) into
283 * a byte array.
284 * Expects: stream (FILE) and a pointer to a pointer (for the target array)
285 * It will allocate an array whereby you get the pointer back.
286 * @return length of data array, counting from 1
287 *
288 * I've inspired a bit from glib's g_file_get_contents because my first
289 * version was quite buggy:
290 *****
291     * Neugeschrieben nach etwas Inspiration von der glib am 05.04.2008.
292     * Funktion get_contents_stdio, gfileutils.c im Sourcecode von glib-2.14.3.
293     * Router natürlich aus (03:11!), aber da sieht man mal wieder den Vorteil von Gentoo:
294     * Gleich alle Sourcecodes auf der Platte =)
295 *****
296 *
297 **/
298int file_get_contents(FILE *stream, byte_t **content) {
299        byte_t buf[4096];
300        size_t bytes; // gerade eben eingelesene bytes
301        byte_t *str = NULL;
302        size_t total_bytes = 0;     // alle bis jetzt eingelesenen bytes
303        size_t total_allocated = 0;
304        byte_t *tmp; // fuers realloc
305
306        while(!feof(stream)) {
307                bytes = fread(buf, 1, sizeof(buf), stream);
308
309                while( (total_bytes + bytes) > total_allocated) {
310                        if(str)
311                                total_allocated *= 2;
312                        else
313                                total_allocated = bytes > sizeof(buf) ? bytes : sizeof(buf);
314
315                        tmp = realloc(str, total_allocated);
316
317                        if(tmp == NULL) {
318                                fprintf(stderr, "*** file_get_contents ERROR*** Could not reallocate\n");
319                                //return length; // Fehler - das eingelesene zumindest zurueckgeben.
320                                // nee, gar nichts zurückgeben. Das geht so nicht.
321                                return 0;
322                        }
323
324                        str = tmp;
325                } // while innen
326
327                memcpy(str + total_bytes, buf, bytes);
328                total_bytes += bytes;
329        } // while
330
331        if(total_allocated == 0)
332                str = malloc(1); // something empty. Just to be not NULL...
333        //str[total_bytes] = '\0'; // wenns ein string wäre.
334
335        *content = str;
336        return total_bytes;
337}
338
339/**
340 * Helper function: A simple closure for a cairo_surface_t (PNG and SVG) to write out data
341 * either to a file or to stdout (given in first parameter)
342 **/
343cairo_status_t lochstreifen_out_closure(void *closure, unsigned char *data, unsigned int length) {
344        // einfach nur in das uebergebene Dateihandle schreiben
345        fwrite(data, length, 1, (FILE *)closure);
346        return CAIRO_STATUS_SUCCESS;
347}
348
349/**
350 * The main routine.
351 **/
352int main(int argc, char *argv[]) {
353        cairo_t *cr;                ///< A cairo context, given from...
354        cairo_surface_t *surface;   ///< ...this generic cairo surface
355        byte_t *data;               ///< the data array, will be filled by file_get_contents
356        int length;                 ///< the length of that data array
357        FILE *out;                  ///< an output stream handle (stdout or a file)
358        int input_argc;             ///< argp: index of argv argument where the filenames are stored
359       
360        // now starting...
361        l = lochstreifen_new();
362        argp_parse(&argp, argc, argv, 0, &input_argc, NULL);
363       
364        // read input data to data array
365        if(input_argc < argc && argv[input_argc][0] != '-') {
366                // open a file (which name is not "-", because this shall read from STDIN)
367                FILE *fh;
368                DPRINTF("Reading from file %s\n", argv[input_argc]);
369                fh = fopen(argv[input_argc], "r");
370
371                if(fh == NULL) {
372                        perror("opening input file");
373                        exit(1);
374                }
375
376                length = file_get_contents(fh, &data);
377                fclose(fh);
378        } else {
379                DPRINTF("Reading from stdin, type [STRG]+[D] to generate paper tape from data\n");
380                length = file_get_contents(stdin, &data);
381        }
382
383        DPRINTF("Successfully read in %d bytes to RAM\n", length);
384       
385        // now after bytes are read in, we can setup the LOCHSTREIFEN
386        // correctly:
387        lochstreifen_set_data(l, length, data);
388        if(scale_target != 0) { // initialized
389                switch(scale_by) {
390                        case SCALE_BY_LENGTH:
391                                lochstreifen_set_scaling_by_length(l, scale_target);
392                                break;
393                        case SCALE_BY_WIDTH:
394                                lochstreifen_set_scaling_by_width(l, scale_target);
395                                break;
396                        case SCALE_BY_CODE_HOLE:
397                                lochstreifen_set_scaling_by_code_hole(l, scale_target);
398                }
399        }
400        // add null bytes
401        lochstreifen_add_null_bytes(l, null_bytes_start, null_bytes_end);
402
403        // open output stream
404        if(output_file != NULL) { // check if there was an argv argument
405                out = fopen(output_file, "w");
406
407                if(out == NULL) {
408                        perror("opening output file");
409                        exit(1);
410                }
411                DPRINTF("Opened file '%s' for writing\n", output_file);
412        } else {
413                DPRINTF("Writing output data to stdout\n");
414                out = stdout;
415        }
416       
417        lochstreifen_print_debug(l);
418        exit(0);
419
420        // setting up the surface and painting...
421        if(surface_type == PNG_SURFACE) {
422                cairo_status_t status;
423                surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
424                        lochstreifen_get_target_width(l),
425                        lochstreifen_get_target_height(l)
426                );
427                cr = cairo_create(surface);
428                lochstreifen_draw(l, cr);
429
430                status = cairo_surface_write_to_png_stream(surface, (cairo_write_func_t)lochstreifen_out_closure, out);
431                DPRINTF("PNG file generated: %s\n", cairo_status_to_string(status));
432                return 0;
433        } else if(surface_type == SVG_SURFACE) {
434                surface = cairo_svg_surface_create_for_stream(
435                        (cairo_write_func_t)lochstreifen_out_closure, out,
436                        lochstreifen_get_target_width(l),
437                        lochstreifen_get_target_height(l)
438                );
439                cr = cairo_create(surface);
440                lochstreifen_draw(l, cr);
441
442                DPRINTF("SVG file generated: %s\n", cairo_status_to_string(cairo_surface_status(surface)));
443                return 0;
444        } else {
445                fprintf(stderr, "This surface is not possible, because surface_typ is an enum.\n");
446                return -42;
447        } // if surface_type
448} // 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