ansilove

ANSI and ASCII art to PNG converter in C
Log | Files | Refs | README | LICENSE

ansilove.c (8407B)


      1 /*
      2  * ansilove.c
      3  * Ansilove 4.1.2
      4  * https://www.ansilove.org
      5  *
      6  * Copyright (c) 2011-2020 Stefan Vogt, Brian Cassidy, and Frederic Cambus
      7  * All rights reserved.
      8  *
      9  * Ansilove is licensed under the BSD 2-Clause License.
     10  * See LICENSE file for details.
     11  */
     12 
     13 #include <sys/time.h>
     14 #include <ansilove.h>
     15 #include <err.h>
     16 #include <getopt.h>
     17 #include <stdbool.h>
     18 #include <stdint.h>
     19 #include <stdio.h>
     20 #include <stdlib.h>
     21 #include <string.h>
     22 #include <time.h>
     23 
     24 #ifdef HAVE_SECCOMP
     25 #include <sys/prctl.h>
     26 #include <linux/seccomp.h>
     27 #include "seccomp.h"
     28 #endif
     29 
     30 #include "compat.h"
     31 #include "config.h"
     32 #include "fonts.h"
     33 #include "sauce.h"
     34 #include "strtolower.h"
     35 #include "types.h"
     36 
     37 /* prototypes */
     38 static void synopsis(void);
     39 static void version(void);
     40 
     41 static void
     42 synopsis(void)
     43 {
     44 	fprintf(stdout, "SYNOPSIS\n"
     45 	    "     ansilove [-dhiqrsv] [-b bits] [-c columns] [-f font]"
     46 	    " [-m mode] [-o file]\n"
     47 	    "              [-R factor] [-t type] file\n");
     48 }
     49 
     50 static void
     51 version(void)
     52 {
     53 	fprintf(stdout, "AnsiLove/C %s - ANSI / ASCII art to PNG converter\n"
     54 	    "Copyright (c) 2011-2020 Stefan Vogt, Brian Cassidy, and "
     55 	    "Frederic Cambus.\n", VERSION);
     56 }
     57 
     58 int
     59 main(int argc, char *argv[])
     60 {
     61 	FILE *messages = NULL;
     62 
     63 	/* SAUCE record related bool types */
     64 	bool justDisplaySAUCE = false;
     65 	bool fileHasSAUCE = false;
     66 
     67 	int getoptFlag;
     68 
     69 	char *input = NULL, *output = NULL;
     70 	char *fileName = NULL;
     71 	char *font = NULL;
     72 	char *type = NULL;
     73 	int filetype = 0;
     74 
     75 	struct timespec begin, end, elapsed;
     76 
     77 	static struct ansilove_ctx ctx;
     78 	static struct ansilove_options options;
     79 
     80 	const char *errstr;
     81 
     82 	if (pledge("stdio cpath rpath wpath", NULL) == -1)
     83 		err(EXIT_FAILURE, "pledge");
     84 
     85 #ifdef HAVE_SECCOMP
     86 	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
     87 		perror("Can't initialize seccomp");
     88 		return EXIT_FAILURE;
     89 	}
     90 
     91 	if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &ansilove)) {
     92 		perror("Can't load seccomp filter");
     93 		return EXIT_FAILURE;
     94 	}
     95 #endif
     96 
     97 	if (ansilove_init(&ctx, &options) == -1)
     98 		errx(EXIT_FAILURE, "%s", ansilove_error(&ctx));
     99 
    100 	while ((getoptFlag = getopt(argc, argv, "b:c:df:him:o:qrR:st:v")) != -1) {
    101 		switch (getoptFlag) {
    102 		case 'b':
    103 			options.bits = strtonum(optarg, 8, 9, &errstr);
    104 
    105 			if (errstr)
    106 				errx(EXIT_FAILURE, "Invalid value for bits (must be 8 or 9).");
    107 
    108 			break;
    109 		case 'c':
    110 			options.columns = strtonum(optarg, 1, 4096, &errstr);
    111 
    112 			if (errstr)
    113 				errx(EXIT_FAILURE, "\nInvalid value for columns (must range from 1 to 4096).");
    114 
    115 			break;
    116 		case 'd':
    117 			options.dos = true;
    118 			break;
    119 		case 'f':
    120 			font = optarg;
    121 			for (size_t loop = 0; loop < FONTS; loop++) {
    122 				if (!strcmp(fonts[loop], font)) {
    123 					options.font = fontsId[loop];
    124 					break;
    125 				}
    126 			}
    127 			break;
    128 		case 'h':
    129 			synopsis();
    130 			return EXIT_SUCCESS;
    131 		case 'i':
    132 			options.icecolors = true;
    133 			break;
    134 		case 'm':
    135 			if (!strcmp(optarg, "ced")) {
    136 				options.mode = ANSILOVE_MODE_CED;
    137 			} else if (!strcmp(optarg, "transparent")) {
    138 				options.mode = ANSILOVE_MODE_TRANSPARENT;
    139 			} else if (!strcmp(optarg, "workbench")) {
    140 				options.mode = ANSILOVE_MODE_WORKBENCH;
    141 			}
    142 			break;
    143 		case 'o':
    144 			output = optarg;
    145 			break;
    146 		case 'q':
    147 			messages = fopen("/dev/null", "w");
    148 			break;
    149 		case 'r':
    150 			options.scale_factor = 2;
    151 			break;
    152 		case 'R':
    153 			options.scale_factor = strtonum(optarg, 2, 8, &errstr);
    154 
    155 			if (errstr)
    156 				errx(EXIT_FAILURE, "Invalid value for retina scale factor (must range from 2 to 8).");
    157 
    158 			break;
    159 		case 's':
    160 			justDisplaySAUCE = true;
    161 			break;
    162 		case 't':
    163 			type = strtolower(optarg);
    164 			break;
    165 		case 'v':
    166 			version();
    167 			return EXIT_SUCCESS;
    168 		}
    169 	}
    170 
    171 	if (optind < argc) {
    172 		input = argv[optind];
    173 	} else {
    174 		synopsis();
    175 		return EXIT_SUCCESS;
    176 	}
    177 
    178 	argc -= optind;
    179 	argv += optind;
    180 
    181 	/* if -q flag was not set, default to stdout */
    182 	if (!messages)
    183 		messages = stdout;
    184 
    185         /* Starting timer */
    186         clock_gettime(CLOCK_MONOTONIC, &begin);
    187 
    188 	/* let's check the file for a valid SAUCE record */
    189 	struct sauce *record = sauceReadFileName(input);
    190 
    191 	/* record == NULL also means there is no file, we can stop here */
    192 	if (record == NULL) {
    193 		errx(EXIT_FAILURE, "File %s not found.", input);
    194 	} else {
    195 		/* if we find a SAUCE record, update bool flag */
    196 		if (!strcmp(record->ID, SAUCE_ID)) {
    197 			fileHasSAUCE = true;
    198 		}
    199 	}
    200 
    201 	if (!justDisplaySAUCE) {
    202 		/* create output file name if output is not specified */
    203 		if (!output) {
    204 			/* appending ".png" extension to output file name */
    205 			if (asprintf(&fileName, "%s%s", input, ".png") == -1)
    206 				errx(EXIT_FAILURE, "Memory allocation error.");
    207 		} else {
    208 			fileName = strdup(output);
    209 		}
    210 
    211 		/* display name of input and output files */
    212 		fprintf(messages, "Input File: %s\n", input);
    213 		fprintf(messages, "Output File: %s\n", fileName);
    214 
    215 		/* get file extension */
    216 		char *fext = strrchr(input, '.');
    217 		fext = fext ? strtolower(strdup(++fext)) : strdup("");
    218 
    219 		/* check if current file has a .diz extension */
    220 		if (!strcmp(fext, "diz"))
    221 			options.diz = true;
    222 
    223 		if (ansilove_loadfile(&ctx, input) == -1)
    224 			errx(EXIT_FAILURE, "%s", ansilove_error(&ctx));
    225 
    226 		/* adjust the file size if file contains a SAUCE record */
    227 		if (fileHasSAUCE)
    228 			ctx.length -= 129 - (record->comments > 0 ? 5 + 64 * record->comments : 0);
    229 
    230 		/* set icecolors to true if defined in SAUCE record ANSiFlags */
    231 		if (fileHasSAUCE && (record->flags & 1))
    232 			options.icecolors = true;
    233 
    234 		int (*loader)(struct ansilove_ctx *, struct ansilove_options *) = NULL;
    235 
    236 		/* if type was specified, attempt to find a loader */
    237 		if (type) {
    238 			for (size_t loop = 0; loop < TYPES; loop++) {
    239 				if (!strcmp(types[loop], type)) {
    240 					loader = loaders[loop];
    241 					filetype = filetypes[loop];
    242 					break;
    243 				}
    244 			}
    245 
    246 			if (!loader)
    247 				errx(EXIT_FAILURE, "Unknown file type.");
    248 		}
    249 
    250 		/* use file extension to find a suitable loader */
    251 		if (!loader) {
    252 			for (size_t loop = 0; loop < TYPES; loop++) {
    253 				if (!strcmp(types[loop], fext)) {
    254 					loader = loaders[loop];
    255 					filetype = filetypes[loop];
    256 					break;
    257 				}
    258 			}
    259 		}
    260 
    261 		/* default to ANSI if file extension is unknown */
    262 		if (!loader) {
    263 			loader = ansilove_ansi;
    264 			filetype = ANSILOVE_FILETYPE_ANS;
    265 		}
    266 
    267 		if (loader(&ctx, &options) == -1)
    268 			errx(EXIT_FAILURE, "%s", ansilove_error(&ctx));
    269 
    270 		/* create the output file */
    271 		if (ansilove_savefile(&ctx, fileName) == -1)
    272 			errx(EXIT_FAILURE, "%s", ansilove_error(&ctx));
    273 
    274 		/* gather information and report to the command line */
    275 		switch(filetype) {
    276 		case ANSILOVE_FILETYPE_ANS:
    277 		case ANSILOVE_FILETYPE_BIN:
    278 			if (options.icecolors)
    279 				fprintf(messages, "iCE Colors: enabled\n");
    280 
    281 			/* FALLTHROUGH */
    282 		case ANSILOVE_FILETYPE_PCB:
    283 		case ANSILOVE_FILETYPE_TND:
    284 			fprintf(messages, "Font: %s\n", font ? font : "80x25");
    285 			fprintf(messages, "Bits: %d\n", options.bits);
    286 			fprintf(messages, "Columns: %d\n", options.columns);
    287 		}
    288 
    289 		if (options.scale_factor)
    290 			fprintf(messages, "Scale factor: %d\n", options.scale_factor);
    291 
    292 		free(fileName);
    293 		free(fext);
    294 	}
    295 
    296 	/* either display SAUCE or tell us if there is no record */
    297 	if (!fileHasSAUCE) {
    298 		fprintf(messages, "\nFile %s does not have a SAUCE record.\n", input);
    299 	} else {
    300 		fprintf(messages, "\nId: %s v%s\n", record->ID, record->version);
    301 		fprintf(messages, "Title: %s\n", record->title);
    302 		fprintf(messages, "Author: %s\n", record->author);
    303 		fprintf(messages, "Group: %s\n", record->group);
    304 		fprintf(messages, "Date: %s\n", record->date);
    305 		fprintf(messages, "Datatype: %d\n", record->dataType);
    306 		fprintf(messages, "Filetype: %d\n", record->fileType);
    307 
    308 		if (record->flags)
    309 			fprintf(messages, "Flags: %d\n", record->flags);
    310 
    311 		if (record->tinfo1)
    312 			fprintf(messages, "Tinfo1: %d\n", record->tinfo1);
    313 
    314 		if (record->tinfo2)
    315 			fprintf(messages, "Tinfo2: %d\n", record->tinfo2);
    316 
    317 		if (record->tinfo3)
    318 			fprintf(messages, "Tinfo3: %d\n", record->tinfo3);
    319 
    320 		if (record->tinfo4)
    321 			fprintf(messages, "Tinfo4: %d\n", record->tinfo4);
    322 
    323 		fprintf(messages, "Tinfos: %s\n", record->tinfos);
    324 		if (record->comments > 0) {
    325 			fprintf(messages, "Comments: ");
    326 			for (int32_t i = 0; i < record->comments; i++)
    327 				fprintf(messages, "%s\n", record->comment_lines[i]);
    328 		}
    329 	}
    330 
    331 	/* Stopping timer */
    332 	clock_gettime(CLOCK_MONOTONIC, &end);
    333 
    334 	timespecsub(&end, &begin, &elapsed);
    335 
    336 	fprintf(messages, "\nProcessed in %f seconds.\n",
    337 	    elapsed.tv_sec + elapsed.tv_nsec / 1E9);
    338 
    339 	ansilove_clean(&ctx);
    340 
    341 	free(record->comment_lines);
    342 	free(record);
    343 
    344 	return EXIT_SUCCESS;
    345 }