libansilove

Library for converting ANSI, ASCII, and other formats to PNG
Log | Files | Refs | README | LICENSE

ansi.c (13233B)


      1 /*
      2  * ansi.c
      3  * libansilove 1.2.4
      4  * https://www.ansilove.org
      5  *
      6  * Copyright (c) 2011-2020 Stefan Vogt, Brian Cassidy, and Frederic Cambus
      7  * All rights reserved.
      8  *
      9  * libansilove is licensed under the BSD 2-Clause License.
     10  * See LICENSE file for details.
     11  */
     12 
     13 #include <gd.h>
     14 #include <limits.h>
     15 #include <math.h>
     16 #include <stdbool.h>
     17 #include <stdint.h>
     18 #include <stdlib.h>
     19 #include <string.h>
     20 #include "ansilove.h"
     21 #include "config.h"
     22 #include "drawchar.h"
     23 #include "fonts.h"
     24 #include "output.h"
     25 
     26 #ifndef HAVE_STRTONUM
     27 #include "strtonum.h"
     28 #endif
     29 
     30 #define ANSI_SEQUENCE_MAX_LENGTH 14
     31 #define ANSI_BUFFER_SIZE 65536
     32 
     33 #define LF	'\n'
     34 #define CR	'\r'
     35 #define TAB	'\t'
     36 #define SUB	26
     37 #define ESC	27
     38 
     39 /* Character structure */
     40 struct ansiChar {
     41 	int32_t column;
     42 	int32_t row;
     43 	uint32_t background;
     44 	uint32_t foreground;
     45 	uint8_t character;
     46 };
     47 
     48 int
     49 ansilove_ansi(struct ansilove_ctx *ctx, struct ansilove_options *options)
     50 {
     51 	if (ctx == NULL || options == NULL) {
     52 		if (ctx)
     53 			ctx->error = ANSILOVE_INVALID_PARAM;
     54 
     55 		return -1;
     56 	}
     57 
     58 	/* ladies and gentlemen, it's type declaration time */
     59 	struct fontStruct fontData;
     60 
     61 	/* Default to 80 columns if columns option wasn't set */
     62 	options->columns = options->columns ? options->columns : 80;
     63 
     64 	int16_t columns = options->columns;
     65 
     66 	bool ced = false;
     67 	bool workbench = false;
     68 
     69 	const char *errstr;
     70 
     71 	/* font selection */
     72 	memset(&fontData, 0, sizeof(struct fontStruct));
     73 	select_font(&fontData, options->font);
     74 
     75 	switch (options->mode) {
     76 	case ANSILOVE_MODE_CED:
     77 		ced = true;
     78 		break;
     79 	case ANSILOVE_MODE_WORKBENCH:
     80 		workbench = true;
     81 		break;
     82 	}
     83 
     84 	/* libgd image pointers */
     85 	gdImagePtr canvas;
     86 
     87 	/* ANSi processing loops */
     88 	size_t loop = 0, ansi_sequence_loop;
     89 
     90 	/* character definitions */
     91 	uint8_t current_character, character;
     92 	uint8_t ansi_sequence_character;
     93 
     94 	/* default color values */
     95 	uint32_t background = 0, foreground = 7;
     96 	uint32_t background24 = 0, foreground24 = 0;
     97 
     98 	/* text attributes */
     99 	bool bold = false, blink = false, invert = false;
    100 
    101 	/* positions */
    102 	int32_t column = 0, row = 0, columnMax = 0, rowMax = 0;
    103 	int32_t saved_row = 0, saved_column = 0;
    104 
    105 	/* sequence parsing variables */
    106 	uint32_t seqValue, seq_line, seq_column;
    107 	char *seqGrab = NULL;
    108 	char *seqTok = NULL;
    109 
    110 	/* ANSi buffer structure array definition */
    111 	size_t structIndex = 0;
    112 	struct ansiChar *ptr, *ansi_buffer;
    113 
    114 	size_t ansi_buffer_size = ANSI_BUFFER_SIZE;
    115 
    116 	/* ANSi buffer dynamic memory allocation */
    117 	ansi_buffer = malloc(ansi_buffer_size * sizeof(struct ansiChar));
    118 
    119 	/* ANSi interpreter */
    120 	while (loop < ctx->length) {
    121 		current_character = ctx->buffer[loop];
    122 
    123 		if (column == options->columns) {
    124 			row++;
    125 			column = 0;
    126 		}
    127 
    128 		switch (current_character) {
    129 		case LF:
    130 			row++;
    131 			column = 0;
    132 			break;
    133 		case CR:
    134 			break;
    135 		case TAB:
    136 			column += 8;
    137 			break;
    138 		case SUB:
    139 			loop = ctx->length;
    140 			break;
    141 		case ESC: /* ANSi sequence */
    142 			if ((loop+1 < ctx->length) && ctx->buffer[loop + 1] == 91) {
    143 
    144 				uint32_t maxlength = fmin(ctx->length - loop + 1, ANSI_SEQUENCE_MAX_LENGTH);
    145 				for (ansi_sequence_loop = 0; ansi_sequence_loop < maxlength; ansi_sequence_loop++) {
    146 					ansi_sequence_character = ctx->buffer[loop + 2 + ansi_sequence_loop];
    147 
    148 					/* cursor position */
    149 					if (ansi_sequence_character == 'H' || ansi_sequence_character == 'f') {
    150 						/* create substring from the sequence's content */
    151 						seq_line = 1;
    152 						seq_column = 1;
    153 						seqGrab = strndup((char *)ctx->buffer + loop + 2, ansi_sequence_loop);
    154 
    155 						if (!strncmp(seqGrab, ";", 1)) {
    156 							seq_line = 1;
    157 							seqTok = strtok(seqGrab, ";");
    158 
    159 							if (seqTok)
    160 								seq_column = strtonum(seqTok, 0, UINT32_MAX, &errstr);
    161 						} else {
    162 							seqTok = strtok(seqGrab, ";");
    163 							if (seqTok)
    164 								seq_line = strtonum(seqTok, 0, UINT32_MAX, &errstr);
    165 
    166 							seqTok = strtok(NULL, ";");
    167 							if (seqTok)
    168 								seq_column = strtonum(seqTok, 0, UINT32_MAX, &errstr);
    169 						}
    170 
    171 						/* set the positions */
    172 						row = seq_line-1;
    173 						column = seq_column-1;
    174 
    175 						loop += ansi_sequence_loop+2;
    176 						free(seqGrab);
    177 						break;
    178 					}
    179 
    180 					/* cursor up */
    181 					if (ansi_sequence_character == 'A') {
    182 						/* create substring from the sequence's content */
    183 						seqGrab = strndup((char *)ctx->buffer + loop + 2, ansi_sequence_loop);
    184 
    185 						/* now get escape sequence's position value */
    186 						uint32_t seq_line = strtonum(seqGrab, 0, UINT32_MAX, &errstr);
    187 						free(seqGrab);
    188 
    189 						row -= seq_line ? seq_line : 1;
    190 
    191 						if (row < 0)
    192 							row = 0;
    193 
    194 						loop += ansi_sequence_loop+2;
    195 						break;
    196 					}
    197 
    198 					/* cursor down */
    199 					if (ansi_sequence_character == 'B') {
    200 						/* create substring from the sequence's content */
    201 						seqGrab = strndup((char *)ctx->buffer + loop + 2, ansi_sequence_loop);
    202 
    203 						/* now get escape sequence's position value */
    204 						uint32_t seq_line = strtonum(seqGrab, 0, UINT32_MAX, &errstr);
    205 						free(seqGrab);
    206 
    207 						row += seq_line ? seq_line : 1;
    208 
    209 						loop += ansi_sequence_loop+2;
    210 						break;
    211 					}
    212 
    213 					/* cursor forward */
    214 					if (ansi_sequence_character == 'C') {
    215 						/* create substring from the sequence's content */
    216 						seqGrab = strndup((char *)ctx->buffer + loop + 2, ansi_sequence_loop);
    217 
    218 						/* now get escape sequence's position value */
    219 						uint32_t seq_column = strtonum(seqGrab, 0, UINT32_MAX, &errstr);
    220 						free(seqGrab);
    221 
    222 						column += seq_column ? seq_column : 1;
    223 
    224 						if (column > options->columns)
    225 							column = options->columns;
    226 
    227 						loop += ansi_sequence_loop+2;
    228 						break;
    229 					}
    230 
    231 					/* cursor backward */
    232 					if (ansi_sequence_character == 'D') {
    233 						/* create substring from the sequence's content */
    234 						seqGrab = strndup((char *)ctx->buffer + loop + 2, ansi_sequence_loop);
    235 
    236 						/* now get escape sequence's content length */
    237 						uint32_t seq_column = strtonum(seqGrab, 0, UINT32_MAX, &errstr);
    238 						free(seqGrab);
    239 
    240 						column -= seq_column ? seq_column : 1;
    241 
    242 						if (column < 0)
    243 							column = 0;
    244 
    245 						loop += ansi_sequence_loop+2;
    246 						break;
    247 					}
    248 
    249 					/* save cursor position */
    250 					if (ansi_sequence_character == 's') {
    251 						saved_row = row;
    252 						saved_column = column;
    253 
    254 						loop += ansi_sequence_loop+2;
    255 						break;
    256 					}
    257 
    258 					/* restore cursor position */
    259 					if (ansi_sequence_character == 'u') {
    260 						row = saved_row;
    261 						column = saved_column;
    262 
    263 						loop += ansi_sequence_loop+2;
    264 						break;
    265 					}
    266 
    267 					/* erase display */
    268 					if (ansi_sequence_character == 'J') {
    269 						/* create substring from the sequence's content */
    270 						seqGrab = strndup((char *)ctx->buffer + loop + 2, ansi_sequence_loop);
    271 
    272 						/* convert grab to an integer */
    273 						uint32_t eraseDisplayInt = strtonum(seqGrab, 0, UINT32_MAX, &errstr);
    274 						free(seqGrab);
    275 
    276 						if (eraseDisplayInt == 2) {
    277 							column = 0;
    278 							row = 0;
    279 
    280 							columnMax = 0;
    281 							rowMax = 0;
    282 
    283 							/* reset ansi buffer */
    284 							structIndex = 0;
    285 						}
    286 						loop += ansi_sequence_loop+2;
    287 						break;
    288 					}
    289 
    290 					/* set graphics mode */
    291 					if (ansi_sequence_character == 'm') {
    292 						/* create substring from the sequence's content */
    293 						seqGrab = strndup((char *)ctx->buffer + loop + 2, ansi_sequence_loop);
    294 
    295 						seqTok = strtok(seqGrab, ";");
    296 						while (seqTok) {
    297 							seqValue = strtonum(seqTok, 0, UINT32_MAX, &errstr);
    298 
    299 							if (seqValue == 0) {
    300 								background = 0;
    301 								background24 = 0;
    302 								foreground = 7;
    303 								foreground24 = 0;
    304 								bold = false;
    305 								blink = false;
    306 								invert = false;
    307 							}
    308 
    309 							if (seqValue == 1) {
    310 								if (!workbench) {
    311 									foreground += 8;
    312 								}
    313 								bold = true;
    314 								foreground24 = 0;
    315 							}
    316 
    317 							if (seqValue == 5) {
    318 								if (!workbench && options->icecolors)
    319 									background += 8;
    320 
    321 								blink = true;
    322 								background24 = 0;
    323 							}
    324 
    325 							if (seqValue == 7)
    326 								invert = true;
    327 
    328 							if (seqValue == 27)
    329 								invert = false;
    330 
    331 							if (seqValue > 29 && seqValue < 38) {
    332 								foreground = seqValue - 30;
    333 								foreground24 = 0;
    334 
    335 								if (bold)
    336 									foreground += 8;
    337 							}
    338 
    339 							if (seqValue > 39 && seqValue < 48) {
    340 								background = seqValue - 40;
    341 								background24 = 0;
    342 
    343 								if (blink && options->icecolors)
    344 									background += 8;
    345 							}
    346 
    347 							seqTok = strtok(NULL, ";");
    348 						}
    349 
    350 						loop += ansi_sequence_loop+2;
    351 						free(seqGrab);
    352 						break;
    353 					}
    354 
    355 					/* cursor (de)activation (Amiga ANSi) */
    356 					if (ansi_sequence_character == 'p') {
    357 						loop += ansi_sequence_loop+2;
    358 						break;
    359 					}
    360 
    361 					/* skipping set mode and reset mode sequences */
    362 					if (ansi_sequence_character == 'h' || ansi_sequence_character == 'l') {
    363 						loop += ansi_sequence_loop+2;
    364 						break;
    365 					}
    366 
    367 					/* skipping erase in line (EL) sequences */
    368 					if (ansi_sequence_character == 'K') {
    369 						loop += ansi_sequence_loop+2;
    370 						break;
    371 					}
    372 
    373 					/* PabloDraw 24-bit ANSI sequences */
    374 					if (ansi_sequence_character == 't') {
    375 						uint32_t color_R = 0, color_G = 0, color_B = 0;
    376 
    377 						/* create substring from the sequence's content */
    378 						seqGrab = strndup((char *)ctx->buffer + loop + 2, ansi_sequence_loop);
    379 
    380 						seqTok = strtok(seqGrab, ";");
    381 						if (seqTok) {
    382 							seqValue = strtonum(seqTok, 0, UCHAR_MAX, &errstr);
    383 
    384 							seqTok = strtok(NULL, ";");
    385 							color_R = seqTok ? strtonum(seqTok, 0, UCHAR_MAX, &errstr) & 0xff : 0;
    386 							seqTok = strtok(NULL, ";");
    387 							color_G = seqTok ? strtonum(seqTok, 0, UCHAR_MAX, &errstr) & 0xff : 0;
    388 							seqTok = strtok(NULL, ";");
    389 							color_B = seqTok ? strtonum(seqTok, 0, UCHAR_MAX, &errstr) & 0xff : 0;
    390 
    391 							switch (seqValue) {
    392 							case 0:
    393 								background24 = color_R << 16 | color_G << 8 | color_B;
    394 								break;
    395 							case 1:
    396 								foreground24 = color_R << 16 | color_G << 8 | color_B;
    397 								break;
    398 							}
    399 
    400 							options->truecolor = true;
    401 						}
    402 
    403 						loop += ansi_sequence_loop+2;
    404 						free(seqGrab);
    405 						break;
    406 					}
    407 				}
    408 			}
    409 			break;
    410 		default:
    411 			/* record number of columns and lines used */
    412 			if (column > columnMax)
    413 				columnMax = column;
    414 
    415 			if (row > rowMax)
    416 				rowMax = row;
    417 
    418 			/* write current character in ansiChar structure */
    419 			if (!fontData.isAmigaFont || (current_character != 12 && current_character != 13)) {
    420 				/* reallocate structure array memory */
    421 				if (structIndex == ansi_buffer_size) {
    422 					ansi_buffer_size += ANSI_BUFFER_SIZE;
    423 
    424 					ptr = realloc(ansi_buffer, ansi_buffer_size * sizeof(struct ansiChar));
    425 
    426 					if (ptr == NULL) {
    427 						ctx->error = ANSILOVE_MEMORY_ERROR;
    428 						free(ansi_buffer);
    429 						return -1;
    430 					} else {
    431 						ansi_buffer = ptr;
    432 					}
    433 				}
    434 
    435 				if (invert) {
    436 					ansi_buffer[structIndex].background = foreground % 8;
    437 					ansi_buffer[structIndex].foreground = background + (foreground & 8);
    438 				} else {
    439 					ansi_buffer[structIndex].background =
    440 					    background24 ? background24 : background;
    441 
    442 					ansi_buffer[structIndex].foreground =
    443 					    foreground24 ? foreground24 : foreground;
    444 				}
    445 				ansi_buffer[structIndex].character = current_character;
    446 				ansi_buffer[structIndex].column = column;
    447 				ansi_buffer[structIndex].row = row;
    448 
    449 				structIndex++;
    450 				column++;
    451 			}
    452 		}
    453 
    454 		loop++;
    455 	}
    456 
    457 	/* allocate image buffer memory */
    458 	columnMax++;
    459 	rowMax++;
    460 
    461 	if (ced)
    462 		columns = 78;
    463 
    464 	if (options->diz)
    465 		columns = fmin(columnMax, options->columns);
    466 
    467 	/* create that damn thingy */
    468 	if (!options->truecolor)
    469 		canvas = gdImageCreate(columns * options->bits, rowMax * fontData.height);
    470 	else
    471 		canvas = gdImageCreateTrueColor(columns * options->bits, rowMax * fontData.height);
    472 
    473 	if (!canvas) {
    474 		ctx->error = ANSILOVE_GD_ERROR;
    475 		free(ansi_buffer);
    476 		return -1;
    477 	}
    478 
    479 	uint32_t colors[16];
    480 
    481 	uint32_t ced_background = 0, ced_foreground = 0;
    482 
    483 	if (ced) {
    484 		ced_background = gdImageColorAllocate(canvas, 170, 170, 170);
    485 		ced_foreground = gdImageColorAllocate(canvas, 0, 0, 0);
    486 		gdImageFill(canvas, 0, 0, ced_background);
    487 	} else if (workbench) {
    488 		for (size_t i = 0; i < 16; i++)
    489 			colors[i] = gdImageColorAllocate(canvas,
    490 			    workbench_palette_red[i],
    491 			    workbench_palette_green[i],
    492 			    workbench_palette_blue[i]);
    493 	} else {
    494 		/* Allocate standard ANSi color palette */
    495 
    496 		for (size_t i = 0; i < 16; i++)
    497 			colors[i] = gdImageColorAllocate(canvas,
    498 			    ansi_palette_red[i], ansi_palette_green[i],
    499 			    ansi_palette_blue[i]);
    500 	}
    501 
    502 	/* render ANSi */
    503 	for (loop = 0; loop < structIndex; loop++) {
    504 		/* grab ANSi char from our structure array */
    505 		background = ansi_buffer[loop].background;
    506 		foreground = ansi_buffer[loop].foreground;
    507 		character = ansi_buffer[loop].character;
    508 		column = ansi_buffer[loop].column;
    509 		row = ansi_buffer[loop].row;
    510 
    511 		if (ced) {
    512 			drawchar(canvas, fontData.font_data, options->bits, fontData.height,
    513 			    column, row, ced_background, ced_foreground, character);
    514 		} else {
    515 			if (background < 16)
    516 				background = colors[background];
    517 
    518 			if (foreground < 16)
    519 				foreground = colors[foreground];
    520 
    521 			drawchar(canvas, fontData.font_data, options->bits, fontData.height,
    522 			    column, row, background, foreground, character);
    523 		}
    524 
    525 	}
    526 
    527 	/* create output image */
    528 	if (output(ctx, options, canvas) != 0) {
    529 		free(ansi_buffer);
    530 		return -1;
    531 	}
    532 
    533 	/* free memory */
    534 	free(ansi_buffer);
    535 
    536 	return 0;
    537 }