| 1 | |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 4 | |
|---|
| 5 | |
|---|
| 6 | |
|---|
| 7 | |
|---|
| 8 | |
|---|
| 9 | |
|---|
| 10 | |
|---|
| 11 | |
|---|
| 12 | |
|---|
| 13 | |
|---|
| 14 | |
|---|
| 15 | |
|---|
| 16 | |
|---|
| 17 | |
|---|
| 18 | |
|---|
| 19 | |
|---|
| 20 | |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | |
|---|
| 24 | |
|---|
| 25 | |
|---|
| 26 | |
|---|
| 27 | |
|---|
| 28 | |
|---|
| 29 | |
|---|
| 30 | |
|---|
| 31 | |
|---|
| 32 | |
|---|
| 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 | |
|---|
| 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 | |
|---|
| 70 | |
|---|
| 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]", |
|---|
| 111 | "This program uses cairo to draw a punched tape as a PNG or SVG file. Any binary " |
|---|
| 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 | |
|---|
| 115 | }; |
|---|
| 116 | |
|---|
| 117 | |
|---|
| 118 | error_t parse_option (int key, char *arg, struct argp_state *state) { |
|---|
| 119 | |
|---|
| 120 | switch(key) { |
|---|
| 121 | case 'v': |
|---|
| 122 | verbosity = 1; |
|---|
| 123 | break; |
|---|
| 124 | case 'o': case 'i': |
|---|
| 125 | |
|---|
| 126 | output_file = arg; |
|---|
| 127 | break; |
|---|
| 128 | case 'w': |
|---|
| 129 | |
|---|
| 130 | scale_by = SCALE_BY_LENGTH; |
|---|
| 131 | scale_target = atoi(arg); |
|---|
| 132 | |
|---|
| 133 | break; |
|---|
| 134 | case 'h': |
|---|
| 135 | |
|---|
| 136 | scale_by = SCALE_BY_WIDTH; |
|---|
| 137 | scale_target = atoi(arg); |
|---|
| 138 | |
|---|
| 139 | break; |
|---|
| 140 | case 'd': |
|---|
| 141 | |
|---|
| 142 | scale_by = SCALE_BY_CODE_HOLE; |
|---|
| 143 | scale_target = atoi(arg); |
|---|
| 144 | |
|---|
| 145 | break; |
|---|
| 146 | case 'f': |
|---|
| 147 | |
|---|
| 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 | |
|---|
| 157 | null_bytes_start = atoi(arg); |
|---|
| 158 | break; |
|---|
| 159 | case -2: |
|---|
| 160 | |
|---|
| 161 | null_bytes_end = atoi(arg); |
|---|
| 162 | break; |
|---|
| 163 | case -3: |
|---|
| 164 | |
|---|
| 165 | l->outer_background_color = NULL; |
|---|
| 166 | break; |
|---|
| 167 | case -4: |
|---|
| 168 | |
|---|
| 169 | l->outer_background_color = hex2cairo_pattern(arg); |
|---|
| 170 | break; |
|---|
| 171 | case -5: |
|---|
| 172 | |
|---|
| 173 | l->papertape_background_color = NULL; |
|---|
| 174 | break; |
|---|
| 175 | case -6: |
|---|
| 176 | |
|---|
| 177 | l->papertape_background_color = hex2cairo_pattern(arg); |
|---|
| 178 | break; |
|---|
| 179 | case -7: |
|---|
| 180 | |
|---|
| 181 | l->one_code_hole_color = NULL; |
|---|
| 182 | break; |
|---|
| 183 | case -8: |
|---|
| 184 | |
|---|
| 185 | l->one_code_hole_color = hex2cairo_pattern(arg); |
|---|
| 186 | break; |
|---|
| 187 | case -9: |
|---|
| 188 | |
|---|
| 189 | l->zero_code_hole_color = NULL; |
|---|
| 190 | break; |
|---|
| 191 | case -10: |
|---|
| 192 | |
|---|
| 193 | l->zero_code_hole_color = hex2cairo_pattern(arg); |
|---|
| 194 | break; |
|---|
| 195 | case -11: |
|---|
| 196 | |
|---|
| 197 | l->feed_hole_color = NULL; |
|---|
| 198 | break; |
|---|
| 199 | case -12: |
|---|
| 200 | |
|---|
| 201 | l->feed_hole_color = hex2cairo_pattern(arg); |
|---|
| 202 | break; |
|---|
| 203 | case -13: |
|---|
| 204 | |
|---|
| 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'; |
|---|
| 210 | |
|---|
| 211 | lochstreifen_set_rotation(l, atoi(arg)); |
|---|
| 212 | break; |
|---|
| 213 | case -14: |
|---|
| 214 | |
|---|
| 215 | |
|---|
| 216 | break; |
|---|
| 217 | case -15: |
|---|
| 218 | |
|---|
| 219 | |
|---|
| 220 | break; |
|---|
| 221 | |
|---|
| 222 | |
|---|
| 223 | default: |
|---|
| 224 | return ARGP_ERR_UNKNOWN; |
|---|
| 225 | } |
|---|
| 226 | return 0; |
|---|
| 227 | } |
|---|
| 228 | |
|---|
| 229 | |
|---|
| 230 | |
|---|
| 231 | |
|---|
| 232 | |
|---|
| 233 | |
|---|
| 234 | |
|---|
| 235 | |
|---|
| 236 | |
|---|
| 237 | |
|---|
| 238 | |
|---|
| 239 | |
|---|
| 240 | |
|---|
| 241 | |
|---|
| 242 | |
|---|
| 243 | |
|---|
| 244 | |
|---|
| 245 | |
|---|
| 246 | |
|---|
| 247 | cairo_pattern_t *hex2cairo_pattern(const char *string) { |
|---|
| 248 | int string_len; |
|---|
| 249 | int x, color; |
|---|
| 250 | long color_value[4]; |
|---|
| 251 | char *buf = "xy"; |
|---|
| 252 | |
|---|
| 253 | |
|---|
| 254 | if(string[0] == '#') |
|---|
| 255 | string++; |
|---|
| 256 | string_len = strlen(string); |
|---|
| 257 | |
|---|
| 258 | |
|---|
| 259 | for(x=0,color=0; x < string_len && color < 5; x++,color++) { |
|---|
| 260 | |
|---|
| 261 | buf[0] = string[x]; |
|---|
| 262 | |
|---|
| 263 | |
|---|
| 264 | |
|---|
| 265 | buf[1] = string_len < 6 ? string[x] : string[++x]; |
|---|
| 266 | |
|---|
| 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 | |
|---|
| 283 | |
|---|
| 284 | |
|---|
| 285 | |
|---|
| 286 | |
|---|
| 287 | |
|---|
| 288 | |
|---|
| 289 | |
|---|
| 290 | |
|---|
| 291 | |
|---|
| 292 | |
|---|
| 293 | |
|---|
| 294 | |
|---|
| 295 | |
|---|
| 296 | |
|---|
| 297 | |
|---|
| 298 | int file_get_contents(FILE *stream, byte_t **content) { |
|---|
| 299 | byte_t buf[4096]; |
|---|
| 300 | size_t bytes; |
|---|
| 301 | byte_t *str = NULL; |
|---|
| 302 | size_t total_bytes = 0; |
|---|
| 303 | size_t total_allocated = 0; |
|---|
| 304 | byte_t *tmp; |
|---|
| 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 | |
|---|
| 320 | |
|---|
| 321 | return 0; |
|---|
| 322 | } |
|---|
| 323 | |
|---|
| 324 | str = tmp; |
|---|
| 325 | } |
|---|
| 326 | |
|---|
| 327 | memcpy(str + total_bytes, buf, bytes); |
|---|
| 328 | total_bytes += bytes; |
|---|
| 329 | } |
|---|
| 330 | |
|---|
| 331 | if(total_allocated == 0) |
|---|
| 332 | str = malloc(1); |
|---|
| 333 | |
|---|
| 334 | |
|---|
| 335 | *content = str; |
|---|
| 336 | return total_bytes; |
|---|
| 337 | } |
|---|
| 338 | |
|---|
| 339 | |
|---|
| 340 | |
|---|
| 341 | |
|---|
| 342 | |
|---|
| 343 | cairo_status_t lochstreifen_out_closure(void *closure, unsigned char *data, unsigned int length) { |
|---|
| 344 | |
|---|
| 345 | fwrite(data, length, 1, (FILE *)closure); |
|---|
| 346 | return CAIRO_STATUS_SUCCESS; |
|---|
| 347 | } |
|---|
| 348 | |
|---|
| 349 | |
|---|
| 350 | |
|---|
| 351 | |
|---|
| 352 | int main(int argc, char *argv[]) { |
|---|
| 353 | cairo_t *cr; |
|---|
| 354 | cairo_surface_t *surface; |
|---|
| 355 | byte_t *data; |
|---|
| 356 | int length; |
|---|
| 357 | FILE *out; |
|---|
| 358 | int input_argc; |
|---|
| 359 | |
|---|
| 360 | |
|---|
| 361 | l = lochstreifen_new(); |
|---|
| 362 | argp_parse(&argp, argc, argv, 0, &input_argc, NULL); |
|---|
| 363 | |
|---|
| 364 | |
|---|
| 365 | if(input_argc < argc && argv[input_argc][0] != '-') { |
|---|
| 366 | |
|---|
| 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 | |
|---|
| 386 | |
|---|
| 387 | lochstreifen_set_data(l, length, data); |
|---|
| 388 | if(scale_target != 0) { |
|---|
| 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 | |
|---|
| 401 | lochstreifen_add_null_bytes(l, null_bytes_start, null_bytes_end); |
|---|
| 402 | |
|---|
| 403 | |
|---|
| 404 | if(output_file != NULL) { |
|---|
| 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 | |
|---|
| 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 | } |
|---|
| 448 | } |
|---|