/**
* cli.c: An exemplar, but fully functional and highly configurable
* command line interface (CLI) to the paper tape low level drawing
* routines (lochstreifen.c), which uses the famous cairo graphics
* library for drawing.
*
* See ./program --help for an overview about the self-explanatory
* arguments. By default, the program will read in any files in
* stdin and print the genereated PNG file on stdout.
*
* This program is written in english only (but the sourcecode
* contains some german comments). See an exemplar usage of this
* program in a PHP web program in the web-frontend subproject.
*
* This program uses the argp.h argument parser from the glibc.
* Thus it unfortunately won't compile with any other libc.
*
* Copyright (C) 2008 Sven Köppel
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see
* .
*
**/
#include
#include
#include
#include
#include "lochstreifen.h"
LOCHSTREIFEN *l;
enum _surface_type {
PNG_SURFACE,
SVG_SURFACE
} surface_type;
char *output_file;
int verbosity = 0;
int null_bytes_start = 0;
int null_bytes_end = 0;
#define DPRINTF(msg...) if(verbosity!=0) fprintf(stderr,msg);
error_t parse_option (int key, char *arg, struct argp_state *state);
cairo_pattern_t *hex2cairo_pattern(const char *string);
const char *argp_program_version = "Punch card visualisator - CLI frontend";
static struct argp_option options[] = {
{"verbose", 'v', 0, 0, "Produce verbose output on stderr" },
//{"quiet", 'q', 0, 0, "Don't produce any output" },
//{"silent", 's', 0, OPTION_ALIAS },
{"output", 'o', "FILE", 0, "Output to FILE (instead of standard output)" },
{"image", 'i', 0, OPTION_ALIAS },
{"format", 'f', "SVG|PNG",0, "Set desired output image format (PNG or SVG)" },
{"", 0, 0, OPTION_DOC, ""},
{"Dimensions", 0, 0, OPTION_DOC, "Dimensions are integers without units"},
{"width", 'w', "NUM", 0, "Set desired width for output image (in px or points, according to output format)", 1 },
{"height", 'h', "NUM", 0, "Set desired height for output image (unit like width argument)", 1 },
{"diameter", 'd', "NUM", 0, "Set dimensions for output image by punched hole diameter (pixel)", 1 },
{"", 0, 0, OPTION_DOC, "",1},
{"Empty bytes", 1, 0, OPTION_DOC, "Bytes with value=0x00 which are not content of files", 1},
{"empty-start", -1, "NUM", 0, "Set number of empty bytes at beginning (default=0)", 2 },
{"empty-end", -2, "NUM", 0, "Set number of empty bytes at end (default=0)", 2 },
{"", 0, 0, OPTION_DOC, "",2},
{"Colors", 0, 0, OPTION_DOC, "Color format: #RGB[A], #RRGGBB[AA]", 2 },
{"hide-imagebg", -3, 0, 0, "Make the image background (space around paper tape) transparent", 3 },
{"color-imagebg", -4, "#RGBA", 0, "Set image background color", 3 },
{"hide-tapebg", -5, 0, 0, "Hide the paper tape background", 3 },
{"color-tapebg", -6, "#RGBA", 0, "Set tape background color", 3 },
{"hide-punched", -7, 0, 0, "Hide the holes (only the punched ones)", 3 },
{"color-punched", -8, "#RGBA", 0, "Set color of holes (punched bits)", 3 },
{"hide-notpunched", -9, 0, 0, "Hide the holes which aren't punched on real tapes", 3 },
{"color-notpunched",-10, "#RGBA", 0, "Set color of bits with boolean value \"false\"", 3 },
{"hide-feedholes", -11, 0, 0, "Hide the feed holes (which means they wouldn't be punched)", 3 },
{"color-feedholes", -12, "#RGBA", 0, "Set color of feed holes (the small ones)", 3 },
{"", 0, 0, OPTION_DOC, "",3},
{"Transformations and Rotations", 0, 0, OPTION_DOC, "", 3},
{"rotation", -13, "right|bottom|left|top", 0, "Rotation of punched tape (alternative short notation: 0=right, 1=bottom, 2=left, 3=top)", 4 },
{"reflection-byte-direction", -14, 0, 0, "Enables horizontal reflecion on the data axis (inverts data direction)", 4 },
{"reflection-bit-direction", -15, 0, 0, "Enables vertical reflection on the other axis (inverts bit direction)", 4 },
{"", 0, 0, OPTION_DOC, "",-2},
{ 0 }
};
static struct argp argp = { options, parse_option, "[FILE TO READ FROM]", // Als Argument in der ersten Zeile
"This program uses cairo to draw a punched tape as a PNG or SVG file. Any binary " // Hilfe oben
"data are accepted via stdin or read in from the file given as argument. "
"The produced image is written into stdout or in the filename given by -o."
// mit \v danach koennte man ausfuehrliche Hilfe unten anzeigen.
}; // static struct argp
error_t parse_option (int key, char *arg, struct argp_state *state) {
//printf("OPTION %x (%c = %i)...\n", key, key, key);
switch(key) {
case 'v':
verbosity = 1;
break;
case 'o': case 'i':
// Ausgabedatei setzen.
output_file = arg;
break;
case 'w':
// set length (yeah, the legacy parameters call this width)
lochstreifen_set_scaling_by_length(l, atoi(arg));
break;
case 'h':
// set width (yeah, the legacy parameters call this height)
lochstreifen_set_scaling_by_width(l, atoi(arg));
break;
case 'd':
// set diameter of code holes
lochstreifen_set_scaling_by_code_hole(l, atoi(arg));
break;
case 'f':
// set file format
if(strcasecmp(arg, "png") == 0)
surface_type = PNG_SURFACE;
else if(strcasecmp(arg, "svg") == 0)
surface_type = SVG_SURFACE;
else
argp_error(state, "Only PNG and SVG are supported as file formats.\n");
break;
case -1:
// set empty bytes at start
null_bytes_start = atoi(arg);
break;
case -2:
// set empty bytes at end
null_bytes_end = atoi(arg);
break;
case -3:
// hide imagebg
l->outer_background_color = NULL;
break;
case -4:
// color imagebg
l->outer_background_color = hex2cairo_pattern(arg);
break;
case -5:
// hide tape
l->papertape_background_color = NULL;
break;
case -6:
// color tapebg
l->papertape_background_color = hex2cairo_pattern(arg);
break;
case -7:
// hide punched
l->one_code_hole_color = NULL;
break;
case -8:
// color punched
l->one_code_hole_color = hex2cairo_pattern(arg);
break;
case -9:
// hide notpunched
l->zero_code_hole_color = NULL;
break;
case -10:
// color notpunched
l->zero_code_hole_color = hex2cairo_pattern(arg);
break;
case -11:
// hide feedholes
l->feed_hole_color = NULL;
break;
case -12:
// color fuerhung
l->feed_hole_color = hex2cairo_pattern(arg);
break;
case -13:
// rotation
if (strcasecmp(arg, "right" ) == 0) arg = "0";
else if(strcasecmp(arg, "bottom") == 0) arg = "1";
else if(strcasecmp(arg, "left" ) == 0) arg = "2";
else if(strcasecmp(arg, "top" ) == 0) arg = "3";
arg[1] = '\0'; // shorten string to one character
//lochstreifen_set_direction(l, atoi(arg));
lochstreifen_set_rotation(l, atoi(arg));
break;
case -14:
// horizontal spiegeln
//lochstreifen_set_direction(l, -1, 1, -1);
break;
case -15:
// vertikal spiegeln
//lochstreifen_set_direction(l, -1, -1, 1);
break;
//case ARGP_KEY_END:
//printf("bla...");
default:
return ARGP_ERR_UNKNOWN;
} // switch
return 0; // success
} // function parse_option
/**
* Simple helper function to get a SOLID cairo pattern from a hex color string
* with formats like
* #RGB for example: #FF0
* #RGBA #A88F
* #RRGGBB #CD438F
* #RRGGBBAA #CD438FA0
* RGB FF0
* RGBA A88F
* RRGGBB CD438F
* RRGGBBAA CD438FA0
*
* If this method cannot parse the hex color string, it will print an error message
* at stderr and exit the complete program.
*
* @return a dynamically allocated cairo patern
**/
cairo_pattern_t *hex2cairo_pattern(const char *string) {
int string_len; // length of string without "#"
int x, color; // iterators
long color_value[4]; // interpreted numbers
char *buf = "xy"; // Buffer for strtol <- one color value
// remove a "#" char if present
if(string[0] == '#')
string++;
string_len = strlen(string);
// go throught string
for(x=0,color=0; x < string_len && color < 5; x++,color++) {
// copy the current character to buffer, first position
buf[0] = string[x];
// if short notation (shorter than AABBCC), dublicate
// current character to buffer second position, else
// copy next character to second position
buf[1] = string_len < 6 ? string[x] : string[++x];
// parse buffer contents and save them as one color
color_value[color] = strtol(buf, NULL, 16);
}
DPRINTF("Allocating '%s' as #%x%x%x%x\n", string,
color_value[0], color_value[1], color_value[2], (color == 4) ? color_value[3] : 0xFF);
return cairo_pattern_create_rgba(
(double) color_value[0] / (double) 0xFF,
(double) color_value[1] / (double) 0xFF,
(double) color_value[2] / (double) 0xFF,
(color == 4) ? (double) color_value[3] / (double) 0xFF : 1
);
}
/**
* Helper function: Read contents from a stream (e.g. stdin or a file) into
* a byte array.
* Expects: stream (FILE) and a pointer to a pointer (for the target array)
* It will allocate an array whereby you get the pointer back.
* @return length of data array, counting from 1
*
* I've inspired a bit from glib's g_file_get_contents because my first
* version was quite buggy:
*****
* Neugeschrieben nach etwas Inspiration von der glib am 05.04.2008.
* Funktion get_contents_stdio, gfileutils.c im Sourcecode von glib-2.14.3.
* Router natürlich aus (03:11!), aber da sieht man mal wieder den Vorteil von Gentoo:
* Gleich alle Sourcecodes auf der Platte =)
*****
*
**/
int file_get_contents(FILE *stream, byte_t **content) {
byte_t buf[4096];
size_t bytes; // gerade eben eingelesene bytes
byte_t *str = NULL;
size_t total_bytes = 0; // alle bis jetzt eingelesenen bytes
size_t total_allocated = 0;
byte_t *tmp; // fuers realloc
while(!feof(stream)) {
bytes = fread(buf, 1, sizeof(buf), stream);
while( (total_bytes + bytes) > total_allocated) {
if(str)
total_allocated *= 2;
else
total_allocated = bytes > sizeof(buf) ? bytes : sizeof(buf);
tmp = realloc(str, total_allocated);
if(tmp == NULL) {
fprintf(stderr, "*** file_get_contents ERROR*** Could not reallocate\n");
//return length; // Fehler - das eingelesene zumindest zurueckgeben.
// nee, gar nichts zurückgeben. Das geht so nicht.
return 0;
}
str = tmp;
} // while innen
memcpy(str + total_bytes, buf, bytes);
total_bytes += bytes;
} // while
if(total_allocated == 0)
str = malloc(1); // something empty. Just to be not NULL...
//str[total_bytes] = '\0'; // wenns ein string wäre.
*content = str;
return total_bytes;
}
/**
* Helper function: A simple closure for a cairo_surface_t (PNG and SVG) to write out data
* either to a file or to stdout (given in first parameter)
**/
cairo_status_t lochstreifen_out_closure(void *closure, unsigned char *data, unsigned int length) {
// einfach nur in das uebergebene Dateihandle schreiben
fwrite(data, length, 1, (FILE *)closure);
return CAIRO_STATUS_SUCCESS;
}
/**
* The main routine.
**/
int main(int argc, char *argv[]) {
cairo_t *cr; ///< A cairo context, given from...
cairo_surface_t *surface; ///< ...this generic cairo surface
byte_t *data; ///< the data array, will be filled by file_get_contents
int length; ///< the length of that data array
FILE *out; ///< an output stream handle (stdout or a file)
int input_argc; ///< argp: index of argv argument where the filenames are stored
// now starting...
l = lochstreifen_new();
argp_parse(&argp, argc, argv, 0, &input_argc, NULL);
// read input data to data array
if(input_argc < argc && argv[input_argc][0] != '-') {
// open a file (which name is not "-", because this shall read from STDIN)
FILE *fh;
DPRINTF("Reading from file %s\n", argv[input_argc]);
fh = fopen(argv[input_argc], "r");
if(fh == NULL) {
perror("opening input file");
exit(1);
}
length = file_get_contents(fh, &data);
fclose(fh);
} else {
DPRINTF("Reading from stdin, type [STRG]+[D] to generate paper tape from data\n");
length = file_get_contents(stdin, &data);
}
DPRINTF("Successfully read in %d bytes to RAM\n", length);
lochstreifen_set_data(l, length, data);
// add null bytes
lochstreifen_add_null_bytes(l, null_bytes_start, null_bytes_end);
// open output stream
if(output_file != NULL) { // check if there was an argv argument
out = fopen(output_file, "w");
if(out == NULL) {
perror("opening output file");
exit(1);
}
DPRINTF("Opened file '%s' for writing\n", output_file);
} else {
DPRINTF("Writing output data to stdout\n");
out = stdout;
}
// setting up the surface and painting...
if(surface_type == PNG_SURFACE) {
cairo_status_t status;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
lochstreifen_get_target_width(l),
lochstreifen_get_target_height(l)
);
cr = cairo_create(surface);
lochstreifen_draw(l, cr);
status = cairo_surface_write_to_png_stream(surface, (cairo_write_func_t)lochstreifen_out_closure, out);
DPRINTF("PNG file generated: %s\n", cairo_status_to_string(status));
return 0;
} else if(surface_type == SVG_SURFACE) {
surface = cairo_svg_surface_create_for_stream(
(cairo_write_func_t)lochstreifen_out_closure, out,
lochstreifen_get_target_width(l),
lochstreifen_get_target_height(l)
);
cr = cairo_create(surface);
lochstreifen_draw(l, cr);
DPRINTF("SVG file generated: %s\n", cairo_status_to_string(cairo_surface_status(surface)));
return 0;
} else {
fprintf(stderr, "This surface is not possible, because surface_typ is an enum.\n");
return -42;
} // if surface_type
} // main