/* * libcucul Canvas for ultrafast compositing of Unicode letters * Copyright (c) 2002-2006 Sam Hocevar * All Rights Reserved * * $Id$ * * This library is free software; you can redistribute it and/or * modify it under the terms of the Do What The Fuck You Want To * Public License, Version 2, as published by Sam Hocevar. See * http://sam.zoy.org/wtfpl/COPYING for more details. */ /* * This file contains various import functions. */ #include "config.h" #include "common.h" #if !defined(__KERNEL__) # if defined(HAVE_ERRNO_H) # include # endif # include # include # include #endif #include "cucul.h" #include "cucul_internals.h" /* ANSI Graphic Rendition Combination Mode */ struct ansi_grcm { uint8_t fg, bg; uint8_t bold, negative, concealed; }; static cucul_canvas_t *import_caca(void const *, unsigned int); static cucul_canvas_t *import_text(void const *, unsigned int); static cucul_canvas_t *import_ansi(void const *, unsigned int); static void ansi_parse_grcm(cucul_canvas_t *, struct ansi_grcm *, unsigned int, unsigned int const *); /** \brief Import a buffer into a canvas * * This function imports a libcucul buffer as returned by cucul_load_memory() * or cucul_load_file() into an internal libcucul canvas. * * Valid values for \c format are: * * \li \c "": attempt to autodetect the file format. * * \li \c "ansi": import ANSI files. * * \li \c "caca": import native libcaca files. * * If an error occurs, NULL is returned and \b errno is set accordingly: * - \c ENOMEM Not enough memory to allocate canvas. * - \c EINVAL Invalid format requested. * * \param buffer A \e libcucul buffer containing the data to be loaded * into a canvas. * \param format A string describing the input format. * \return A libcucul canvas, or NULL in case of error. */ cucul_canvas_t * cucul_import_canvas(cucul_buffer_t *buffer, char const *format) { char const *buf = (char const*)buffer->data; if(!strcasecmp("caca", format)) return import_caca(buffer->data, buffer->size); if(!strcasecmp("text", format)) return import_text(buffer->data, buffer->size); if(!strcasecmp("ansi", format)) return import_ansi(buffer->data, buffer->size); /* Autodetection */ if(!strcasecmp("", format)) { unsigned int i; /* If 4 first letters are CACA */ if(buffer->size >= 4 && buf[0] == 'C' && buf[1] == 'A' && buf[2] == 'C' && buf[3] != 'A') return import_caca(buffer->data, buffer->size); /* If we find ESC[ argv, we guess it's an ANSI file */ for(i = 0; i < buffer->size - 1; i++) if((buf[i] == 0x1b) && (buf[i + 1] == '[')) return import_ansi(buffer->data, buffer->size); /* Otherwise, import it as text */ return import_text(buffer->data, buffer->size); } #if defined(HAVE_ERRNO_H) errno = EINVAL; #endif return NULL; } /** \brief Get available import formats * * Return a list of available import formats. The list is a NULL-terminated * array of strings, interleaving a string containing the internal value for * the import format, to be used with cucul_import_canvas(), and a string * containing the natural language description for that import format. * * This function never fails. * * \return An array of strings. */ char const * const * cucul_get_import_list(void) { static char const * const list[] = { "", "autodetect", "text", "plain text", "caca", "native libcaca format", "ansi", "ANSI coloured text", NULL, NULL }; return list; } /* * XXX: the following functions are local. */ static cucul_canvas_t *import_caca(void const *data, unsigned int size) { cucul_canvas_t *cv; uint8_t const *buf = (uint8_t const *)data; unsigned int width, height, n; if(size < 16) goto invalid_caca; if(buf[0] != 'C' || buf[1] != 'A' || buf[2] != 'C' || buf[3] != 'A') goto invalid_caca; if(buf[4] != 'C' || buf[5] != 'A' || buf[6] != 'N' || buf[7] != 'V') goto invalid_caca; width = ((uint32_t)buf[8] << 24) | ((uint32_t)buf[9] << 16) | ((uint32_t)buf[10] << 8) | (uint32_t)buf[11]; height = ((uint32_t)buf[12] << 24) | ((uint32_t)buf[13] << 16) | ((uint32_t)buf[14] << 8) | (uint32_t)buf[15]; if(!width || !height) goto invalid_caca; if(size != 16 + width * height * 8) goto invalid_caca; cv = cucul_create_canvas(width, height); if(!cv) { #if defined(HAVE_ERRNO_H) errno = ENOMEM; #endif return NULL; } for(n = height * width; n--; ) { cv->chars[n] = ((uint32_t)buf[16 + 0 + 8 * n] << 24) | ((uint32_t)buf[16 + 1 + 8 * n] << 16) | ((uint32_t)buf[16 + 2 + 8 * n] << 8) | (uint32_t)buf[16 + 3 + 8 * n]; cv->attr[n] = ((uint32_t)buf[16 + 4 + 8 * n] << 24) | ((uint32_t)buf[16 + 5 + 8 * n] << 16) | ((uint32_t)buf[16 + 6 + 8 * n] << 8) | (uint32_t)buf[16 + 7 + 8 * n]; } return cv; invalid_caca: #if defined(HAVE_ERRNO_H) errno = EINVAL; #endif return NULL; } static cucul_canvas_t *import_text(void const *data, unsigned int size) { cucul_canvas_t *cv; char const *text = (char const *)data; unsigned int width = 1, height = 1, x = 0, y = 0, i; cv = cucul_create_canvas(width, height); if(!cv) { #if defined(HAVE_ERRNO_H) errno = ENOMEM; #endif return NULL; } cucul_set_color(cv, CUCUL_COLOR_DEFAULT, CUCUL_COLOR_TRANSPARENT); for(i = 0; i < size; i++) { unsigned char ch = *text++; if(ch == '\r') continue; if(ch == '\n') { x = 0; y++; continue; } if(x >= width || y >= height) { if(x >= width) width = x + 1; if(y >= height) height = y + 1; cucul_set_canvas_size(cv, width, height); } cucul_putchar(cv, x, y, ch); x++; } return cv; } static cucul_canvas_t *import_ansi(void const *data, unsigned int size) { struct ansi_grcm grcm; unsigned char const *buffer = (unsigned char const*)data; cucul_canvas_t *cv; unsigned int i, j, skip, dummy = 0; unsigned int width = 1, height = 1; int x = 0, y = 0, save_x = 0, save_y = 0; cv = cucul_create_canvas(width, height); if(!cv) { #if defined(HAVE_ERRNO_H) errno = ENOMEM; #endif return NULL; } ansi_parse_grcm(cv, &grcm, 1, &dummy); for(i = 0; i < size; i += skip) { skip = 1; /* Wrap long lines */ if((unsigned int)x >= 80) { x = 0; y++; } if(buffer[i] == '\x1a' && size - i >= 8 && !memcmp(buffer + i + 1, "SAUCE00", 7)) break; /* End before SAUCE data */ if(buffer[i] == '\r') continue; /* DOS sucks */ if(buffer[i] == '\n') { x = 0; y++; continue; } /* Interpret escape commands, as per Standard ECMA-48 "Control * Functions for Coded Character Sets", 5.4. Control sequences. */ if(buffer[i] == '\x1b' && buffer[i + 1] == '[') { unsigned int argc = 0, argv[101]; unsigned int param, inter, final; /* Compute offsets to parameter bytes, intermediate bytes and * to the final byte. Only the final byte is mandatory, there * can be zero of the others. * 0 param=2 inter final final+1 * +-----+------------------+---------------------+-----------------+ * | CSI | parameter bytes | intermediate bytes | final byte | * | | 0x30 - 0x3f | 0x20 - 0x2f | 0x40 - 0x7e | * | ^[[ | 0123456789:;<=>? | SPC !"#$%&'()*+,-./ | azAZ@[\]^_`{|}~ | * +-----+------------------+---------------------+-----------------+ */ param = 2; for(inter = param; i + inter < size; inter++) if(buffer[i + inter] < 0x30 || buffer[i + inter] > 0x3f) break; for(final = inter; i + final < size; final++) if(buffer[i + final] < 0x20 || buffer[i + final] > 0x2f) break; if(buffer[i + final] < 0x40 || buffer[i + final] > 0x7e) break; /* Invalid Final Byte */ skip += final; /* Sanity checks */ if(param < inter && buffer[i + param] >= 0x3c) { fprintf(stderr, "private sequence \"^[[%.*s\"\n", final - param + 1, buffer + i + param); continue; /* Private sequence, skip it entirely */ } if(final - param > 100) continue; /* Suspiciously long sequence, skip it */ /* Parse parameter bytes as per ECMA-48 5.4.2: Parameter string * format */ if(param < inter) { argv[0] = 0; for(j = param; j < inter; j++) { if(buffer[i + j] == ';') argv[++argc] = 0; else if(buffer[i + j] >= '0' && buffer[i + j] <= '9') argv[argc] = 10 * argv[argc] + (buffer[i + j] - '0'); } argc++; } /* Interpret final byte. The code representations are given in * ECMA-48 5.4: Control sequences, and the code definitions are * given in ECMA-48 8.3: Definition of control functions. */ switch(buffer[i + final]) { case 'f': /* CUP - Cursor Position */ case 'H': /* HVP - Character And Line Position */ x = (argc > 1 && argv[1] > 0) ? argv[1] - 1 : 0; y = (argc > 0 && argv[0] > 0) ? argv[0] - 1 : 0; break; case 'A': /* CUU - Cursor Up */ y -= argc ? argv[0] : 1; if(y < 0) y = 0; break; case 'B': /* CUD - Cursor Down */ y += argc ? argv[0] : 1; break; case 'C': /* CUF - Cursor Right */ x += argc ? argv[0] : 1; break; case 'D': /* CUB - Cursor Left */ x -= argc ? argv[0] : 1; if(x < 0) x = 0; break; case 's': /* Private (save cursor position) */ save_x = x; save_y = y; break; case 'u': /* Private (reload cursor position) */ x = save_x; y = save_y; break; case 'J': /* ED - Erase In Page */ if(argv[0] == 2) x = y = 0; break; case 'K': /* EL - Erase In Line */ if(width < 80) cucul_set_canvas_size(cv, width = 80, height); for(j = x; j < 80; j++) _cucul_putchar32(cv, j, y, (uint32_t)' '); x = 80; break; case 'm': /* SGR - Select Graphic Rendition */ ansi_parse_grcm(cv, &grcm, argc, argv); break; default: fprintf(stderr, "unknown command %c\n", buffer[i + final]); break; } continue; } /* We're going to paste a character. First make sure the canvas * is big enough. */ if((unsigned int)x >= width) cucul_set_canvas_size(cv, width = x + 1, height); if((unsigned int)y >= height) cucul_set_canvas_size(cv, width, height = y + 1); /* Now paste our character */ _cucul_putchar32(cv, x, y, _cucul_cp437_to_utf32(buffer[i])); x++; } return cv; } /* XXX : ANSI loader helper */ static void ansi_parse_grcm(cucul_canvas_t *cv, struct ansi_grcm *g, unsigned int argc, unsigned int const *argv) { static uint8_t const ansi2cucul[] = { CUCUL_COLOR_BLACK, CUCUL_COLOR_RED, CUCUL_COLOR_GREEN, CUCUL_COLOR_BROWN, CUCUL_COLOR_BLUE, CUCUL_COLOR_MAGENTA, CUCUL_COLOR_CYAN, CUCUL_COLOR_LIGHTGRAY }; unsigned int j; uint8_t myfg, mybg; for(j = 0; j < argc; j++) { /* Defined in ECMA-48 8.3.117: SGR - SELECT GRAPHIC RENDITION */ if(argv[j] >= 30 && argv[j] <= 37) g->fg = ansi2cucul[argv[j] - 30]; else if(argv[j] >= 40 && argv[j] <= 47) g->bg = ansi2cucul[argv[j] - 40]; else if(argv[j] >= 90 && argv[j] <= 97) g->fg = ansi2cucul[argv[j] - 90] + 8; else if(argv[j] >= 100 && argv[j] <= 107) g->bg = ansi2cucul[argv[j] - 100] + 8; else switch(argv[j]) { case 0: /* default rendition */ g->fg = CUCUL_COLOR_DEFAULT; g->bg = CUCUL_COLOR_DEFAULT; g->bold = g->negative = g->concealed = 0; break; case 1: /* bold or increased intensity */ g->bold = 1; break; case 4: /* singly underlined */ break; case 5: /* slowly blinking (less then 150 per minute) */ break; case 7: /* negative image */ g->negative = 1; break; case 8: /* concealed characters */ g->concealed = 1; break; case 22: /* normal colour or normal intensity (neither bold nor faint) */ g->bold = 0; break; case 28: /* revealed characters */ g->concealed = 0; break; case 39: /* default display colour (implementation-defined) */ g->fg = CUCUL_COLOR_DEFAULT; break; case 49: /* default background colour (implementation-defined) */ g->bg = CUCUL_COLOR_DEFAULT; break; default: fprintf(stderr, "unknown sgr %i\n", argv[j]); break; } } if(g->concealed) { myfg = mybg = CUCUL_COLOR_TRANSPARENT; } else { myfg = g->negative ? g->bg : g->fg; mybg = g->negative ? g->fg : g->bg; if(g->bold) { if(myfg < 8) myfg += 8; else if(myfg == CUCUL_COLOR_DEFAULT) myfg = CUCUL_COLOR_WHITE; } } cucul_set_color(cv, myfg, mybg); }