2
* libcaca Colour ASCII-Art library
3
* Copyright (c) 2002-2009 Sam Hocevar <sam@hocevar.net>
4
* 2006 Jean-Yves Lamoureux <jylam@lnxscene.org>
7
* This library is free software. It comes without any warranty, to
8
* the extent permitted by applicable law. You can redistribute it
9
* and/or modify it under the terms of the Do What The Fuck You Want
10
* To Public License, Version 2, as published by Sam Hocevar. See
11
* http://sam.zoy.org/wtfpl/COPYING for more details.
15
* This file contains text import and export functions
20
#if !defined(__KERNEL__)
27
#include "caca_internals.h"
34
/* ANSI Graphic Rendition Combination Mode */
35
uint8_t fg, bg; /* ANSI-context fg/bg */
36
uint8_t dfg, dbg; /* Default fg/bg */
37
uint8_t bold, blink, italics, negative, concealed, underline;
38
uint8_t faint, strike, proportional; /* unsupported */
41
static void ansi_parse_grcm(caca_canvas_t *, struct import *,
42
unsigned int, unsigned int const *);
44
ssize_t _import_text(caca_canvas_t *cv, void const *data, size_t size)
46
char const *text = (char const *)data;
47
unsigned int width = 0, height = 0, x = 0, y = 0, i;
49
caca_set_canvas_size(cv, width, height);
51
for(i = 0; i < size; i++)
53
unsigned char ch = *text++;
65
if(x >= width || y >= height)
73
caca_set_canvas_size(cv, width, height);
76
caca_put_char(cv, x, y, ch);
81
caca_set_canvas_size(cv, width, height = y);
86
ssize_t _import_ansi(caca_canvas_t *cv, void const *data, size_t size, int utf8)
89
unsigned char const *buffer = (unsigned char const*)data;
90
unsigned int i, j, skip, growx = 0, growy = 0, dummy = 0;
91
unsigned int width, height;
93
int x = 0, y = 0, save_x = 0, save_y = 0;
101
x = cv->frames[cv->frame].x;
102
y = cv->frames[cv->frame].y;
106
caca_set_canvas_size(cv, width = 80, height = 0);
113
im.dfg = CACA_DEFAULT;
114
im.dbg = CACA_TRANSPARENT;
118
im.dfg = CACA_LIGHTGRAY;
122
caca_set_color_ansi(cv, im.dfg, im.dbg);
123
im.clearattr = caca_get_attr(cv, -1, -1);
125
ansi_parse_grcm(cv, &im, 1, &dummy);
127
for(i = 0; i < size; i += skip)
134
if(!utf8 && buffer[i] == '\x1a' && i + 7 < size
135
&& !memcmp(buffer + i + 1, "SAUCE00", 7))
136
break; /* End before SAUCE data */
138
else if(buffer[i] == '\r')
143
else if(buffer[i] == '\n')
149
else if(buffer[i] == '\t')
154
else if(buffer[i] == '\x08')
160
/* If there are not enough characters to parse the escape sequence,
161
* wait until the next try. We require 3. */
162
else if(buffer[i] == '\033' && i + 2 >= size)
165
/* XXX: What the fuck is this shit? */
166
else if(buffer[i] == '\033' && buffer[i + 1] == '('
167
&& buffer[i + 2] == 'B')
172
/* Interpret escape commands, as per Standard ECMA-48 "Control
173
* Functions for Coded Character Sets", 5.4. Control sequences. */
174
else if(buffer[i] == '\033' && buffer[i + 1] == '[')
176
unsigned int argc = 0, argv[101];
177
unsigned int param, inter, final;
179
/* Compute offsets to parameter bytes, intermediate bytes and
180
* to the final byte. Only the final byte is mandatory, there
181
* can be zero of the others.
182
* 0 param=2 inter final final+1
183
* +-----+------------------+---------------------+-----------------+
184
* | CSI | parameter bytes | intermediate bytes | final byte |
185
* | | 0x30 - 0x3f | 0x20 - 0x2f | 0x40 - 0x7e |
186
* | ^[[ | 0123456789:;<=>? | SPC !"#$%&'()*+,-./ | azAZ@[\]^_`{|}~ |
187
* +-----+------------------+---------------------+-----------------+
191
for(inter = param; i + inter < size; inter++)
192
if(buffer[i + inter] < 0x30 || buffer[i + inter] > 0x3f)
195
for(final = inter; i + final < size; final++)
196
if(buffer[i + final] < 0x20 || buffer[i + final] > 0x2f)
200
|| buffer[i + final] < 0x40 || buffer[i + final] > 0x7e)
201
break; /* Invalid Final Byte */
206
if(param < inter && buffer[i + param] >= 0x3c)
208
/* Private sequence, only parse what we know */
209
debug("ansi import: private sequence \"^[[%.*s\"",
210
final - param + 1, buffer + i + param);
211
continue; /* Private sequence, skip it entirely */
214
if(final - param > 100)
215
continue; /* Suspiciously long sequence, skip it */
217
/* Parse parameter bytes as per ECMA-48 5.4.2: Parameter string
222
for(j = param; j < inter; j++)
224
if(buffer[i + j] == ';')
226
else if(buffer[i + j] >= '0' && buffer[i + j] <= '9')
227
argv[argc] = 10 * argv[argc] + (buffer[i + j] - '0');
232
/* Interpret final byte. The code representations are given in
233
* ECMA-48 5.4: Control sequences, and the code definitions are
234
* given in ECMA-48 8.3: Definition of control functions. */
235
switch(buffer[i + final])
237
case 'H': /* CUP (0x48) - Cursor Position */
238
x = (argc > 1 && argv[1] > 0) ? argv[1] - 1 : 0;
239
y = (argc > 0 && argv[0] > 0) ? argv[0] - 1 : 0;
241
case 'A': /* CUU (0x41) - Cursor Up */
242
y -= argc ? argv[0] : 1;
246
case 'B': /* CUD (0x42) - Cursor Down */
247
y += argc ? argv[0] : 1;
249
case 'C': /* CUF (0x43) - Cursor Right */
250
x += argc ? argv[0] : 1;
252
case 'D': /* CUB (0x44) - Cursor Left */
253
x -= argc ? argv[0] : 1;
257
case 'G': /* CHA (0x47) - Cursor Character Absolute */
258
x = (argc && argv[0] > 0) ? argv[0] - 1 : 0;
260
case 'J': /* ED (0x4a) - Erase In Page */
261
savedattr = caca_get_attr(cv, -1, -1);
262
caca_set_attr(cv, im.clearattr);
263
if(!argc || argv[0] == 0)
265
caca_draw_line(cv, x, y, width, y, ' ');
266
caca_fill_box(cv, 0, y + 1, width - 1, height - 1, ' ');
268
else if(argv[0] == 1)
270
caca_fill_box(cv, 0, 0, width - 1, y - 1, ' ');
271
caca_draw_line(cv, 0, y, x, y, ' ');
273
else if(argv[0] == 2)
275
caca_fill_box(cv, 0, 0, width - 1, height - 1, ' ');
276
caca_set_attr(cv, savedattr);
278
case 'K': /* EL (0x4b) - Erase In Line */
279
if(!argc || argv[0] == 0)
280
caca_draw_line(cv, x, y, width, y, ' ');
281
else if(argv[0] == 1)
282
caca_draw_line(cv, 0, y, x, y, ' ');
283
else if(argv[0] == 2)
284
if((unsigned int)x < width)
285
caca_draw_line(cv, x, y, width - 1, y, ' ');
288
case 'P': /* DCH (0x50) - Delete Character */
289
if(!argc || argv[0] == 0)
290
argv[0] = 1; /* echo -ne 'foobar\r\e[0P\n' */
291
for(j = 0; (unsigned int)(j + argv[0]) < width; j++)
293
caca_put_char(cv, j, y,
294
caca_get_char(cv, j + argv[0], y));
295
caca_put_attr(cv, j, y,
296
caca_get_attr(cv, j + argv[0], y));
299
savedattr = caca_get_attr(cv, -1, -1);
300
caca_set_attr(cv, im.clearattr);
301
for( ; (unsigned int)j < width; j++)
302
caca_put_char(cv, j, y, ' ');
303
caca_set_attr(cv, savedattr);
305
case 'X': /* ECH (0x58) - Erase Character */
308
savedattr = caca_get_attr(cv, -1, -1);
309
caca_set_attr(cv, im.clearattr);
310
caca_draw_line(cv, x, y, x + argv[0] - 1, y, ' ');
311
caca_set_attr(cv, savedattr);
313
case 'd': /* VPA (0x64) - Line Position Absolute */
314
y = (argc && argv[0] > 0) ? argv[0] - 1 : 0;
316
case 'f': /* HVP (0x66) - Character And Line Position */
317
x = (argc > 1 && argv[1] > 0) ? argv[1] - 1 : 0;
318
y = (argc > 0 && argv[0] > 0) ? argv[0] - 1 : 0;
320
case 'h': /* SM (0x68) - FIXME */
321
debug("ansi import: set mode %i", argc ? (int)argv[0] : -1);
323
case 'l': /* RM (0x6c) - FIXME */
324
debug("ansi import: reset mode %i", argc ? (int)argv[0] : -1);
326
case 'm': /* SGR (0x6d) - Select Graphic Rendition */
328
ansi_parse_grcm(cv, &im, argc, argv);
330
ansi_parse_grcm(cv, &im, 1, &dummy);
332
case 's': /* Private (save cursor position) */
336
case 'u': /* Private (reload cursor position) */
341
debug("ansi import: unknown command \"^[[%.*s\"",
342
final - param + 1, buffer + i + param);
347
/* Parse OSC stuff. */
348
else if(buffer[i] == '\033' && buffer[i + 1] == ']')
351
unsigned int command = 0;
352
unsigned int mode = 2, semicolon, final;
354
for(semicolon = mode; i + semicolon < size; semicolon++)
356
if(buffer[i + semicolon] < '0' || buffer[i + semicolon] > '9')
358
command = 10 * command + (buffer[i + semicolon] - '0');
361
if(i + semicolon >= size || buffer[i + semicolon] != ';')
362
break; /* Invalid Mode */
364
for(final = semicolon + 1; i + final < size; final++)
365
if(buffer[i + final] < 0x20)
368
if(i + final >= size || buffer[i + final] != '\a')
369
break; /* Not enough data or no bell found */
370
/* FIXME: XTerm also reacts to <ESC><backslash> and <ST> */
371
/* FIXME: differenciate between not enough data (try again)
372
* and invalid data (print shit) */
376
string = malloc(final - (semicolon + 1) + 1);
377
memcpy(string, buffer + (semicolon + 1), final - (semicolon + 1));
378
string[final - (semicolon + 1)] = '\0';
379
debug("ansi import: got OSC command %i string '%s'", command,
384
/* Form feed means a new frame */
385
else if(buffer[i] == '\f' && buffer[i + 1] == '\n')
387
int f = caca_get_frame_count(cv);
388
caca_create_frame(cv, f);
389
caca_set_frame(cv, f);
394
/* Get the character we’re going to paste */
400
ch = caca_utf8_to_utf32((char const *)(buffer + i), &bytes);
403
/* Add a trailing zero to what we're going to read */
405
memcpy(tmp, buffer + i, size - i);
406
tmp[size - i] = '\0';
407
ch = caca_utf8_to_utf32(tmp, &bytes);
412
/* If the Unicode is invalid, assume it was latin1. */
416
wch = caca_utf32_is_fullwidth(ch) ? 2 : 1;
417
skip += (int)(bytes - 1);
421
ch = caca_cp437_to_utf32(buffer[i]);
425
/* Wrap long lines or grow horizontally */
426
while((unsigned int)x + wch > width)
430
savedattr = caca_get_attr(cv, -1, -1);
431
caca_set_attr(cv, im.clearattr);
432
caca_set_canvas_size(cv, width = x + wch, height);
433
caca_set_attr(cv, savedattr);
442
/* Scroll or grow vertically */
443
if((unsigned int)y >= height)
445
savedattr = caca_get_attr(cv, -1, -1);
446
caca_set_attr(cv, im.clearattr);
449
caca_set_canvas_size(cv, width, height = y + 1);
453
int lines = (y - height) + 1;
455
for(j = 0; j + lines < height; j++)
457
memcpy(cv->attrs + j * cv->width,
458
cv->attrs + (j + lines) * cv->width, cv->width * 4);
459
memcpy(cv->chars + j * cv->width,
460
cv->chars + (j + lines) * cv->width, cv->width * 4);
462
caca_fill_box(cv, 0, height - lines,
463
cv->width - 1, height - 1, ' ');
466
caca_set_attr(cv, savedattr);
469
/* Now paste our character, if any */
472
caca_put_char(cv, x, y, ch);
477
if(growy && (unsigned int)y > height)
479
savedattr = caca_get_attr(cv, -1, -1);
480
caca_set_attr(cv, im.clearattr);
481
caca_set_canvas_size(cv, width, height = y);
482
caca_set_attr(cv, savedattr);
485
cv->frames[cv->frame].x = x;
486
cv->frames[cv->frame].y = y;
489
// caca_set_attr(cv, savedattr);
494
/* Generate UTF-8 representation of current canvas. */
495
void *_export_utf8(caca_canvas_t const *cv, size_t *bytes, int cr)
497
static uint8_t const palette[] =
499
0, 4, 2, 6, 1, 5, 3, 7,
500
8, 12, 10, 14, 9, 13, 11, 15
506
/* 23 bytes assumed for max length per pixel ('\e[5;1;3x;4y;9x;10ym' plus
507
* 4 max bytes for a UTF-8 character).
508
* Add height*9 to that (zeroes color at the end and jump to next line) */
509
*bytes = (cv->height * 9) + (cv->width * cv->height * 23);
510
cur = data = malloc(*bytes);
512
for(y = 0; y < cv->height; y++)
514
uint32_t *lineattr = cv->attrs + y * cv->width;
515
uint32_t *linechar = cv->chars + y * cv->width;
517
uint8_t prevfg = 0x10;
518
uint8_t prevbg = 0x10;
520
for(x = 0; x < cv->width; x++)
522
uint32_t attr = lineattr[x];
523
uint32_t ch = linechar[x];
524
uint8_t ansifg, ansibg, fg, bg;
526
if(ch == CACA_MAGIC_FULLWIDTH)
529
ansifg = caca_attr_to_ansi_fg(attr);
530
ansibg = caca_attr_to_ansi_bg(attr);
532
fg = ansifg < 0x10 ? palette[ansifg] : 0x10;
533
bg = ansibg < 0x10 ? palette[ansibg] : 0x10;
535
/* TODO: the [0 could be omitted in some cases */
536
if(fg != prevfg || bg != prevbg)
538
cur += sprintf(cur, "\033[0");
541
cur += sprintf(cur, ";3%d", fg);
543
cur += sprintf(cur, ";1;3%d;9%d", fg - 8, fg - 8);
546
cur += sprintf(cur, ";4%d", bg);
548
cur += sprintf(cur, ";5;4%d;10%d", bg - 8, bg - 8);
550
cur += sprintf(cur, "m");
553
cur += caca_utf32_to_utf8(cur, ch);
559
if(prevfg != 0x10 || prevbg != 0x10)
560
cur += sprintf(cur, "\033[0m");
562
cur += sprintf(cur, cr ? "\r\n" : "\n");
565
/* Crop to really used size */
566
debug("utf8 export: alloc %lu bytes, realloc %lu",
567
(unsigned long int)*bytes, (unsigned long int)(cur - data));
568
*bytes = (uintptr_t)(cur - data);
569
data = realloc(data, *bytes);
574
/* Generate ANSI representation of current canvas. */
575
void *_export_ansi(caca_canvas_t const *cv, size_t *bytes)
577
static uint8_t const palette[] =
579
0, 4, 2, 6, 1, 5, 3, 7,
580
8, 12, 10, 14, 9, 13, 11, 15
589
/* 16 bytes assumed for max length per pixel ('\e[5;1;3x;4ym' plus
590
* 1 byte for a CP437 character).
591
* Add height*9 to that (zeroes color at the end and jump to next line) */
592
*bytes = (cv->height * 9) + (cv->width * cv->height * 16);
593
cur = data = malloc(*bytes);
595
for(y = 0; y < cv->height; y++)
597
uint32_t *lineattr = cv->attrs + y * cv->width;
598
uint32_t *linechar = cv->chars + y * cv->width;
600
for(x = 0; x < cv->width; x++)
602
uint8_t ansifg = caca_attr_to_ansi_fg(lineattr[x]);
603
uint8_t ansibg = caca_attr_to_ansi_bg(lineattr[x]);
604
uint8_t fg = ansifg < 0x10 ? palette[ansifg] : CACA_LIGHTGRAY;
605
uint8_t bg = ansibg < 0x10 ? palette[ansibg] : CACA_BLACK;
606
uint32_t ch = linechar[x];
608
if(ch == CACA_MAGIC_FULLWIDTH)
611
if(fg != prevfg || bg != prevbg)
613
cur += sprintf(cur, "\033[0;");
617
cur += sprintf(cur, "3%d;4%dm", fg, bg);
619
cur += sprintf(cur, "5;3%d;4%dm", fg, bg - 8);
622
cur += sprintf(cur, "1;3%d;4%dm", fg - 8, bg);
624
cur += sprintf(cur, "5;1;3%d;4%dm", fg - 8, bg - 8);
627
*cur++ = caca_utf32_to_cp437(ch);
635
cur += sprintf(cur, "\033[s\n\033[u");
639
cur += sprintf(cur, "\033[0m\r\n");
645
/* Crop to really used size */
646
debug("ansi export: alloc %lu bytes, realloc %lu",
647
(unsigned long int)*bytes, (unsigned long int)(cur - data));
648
*bytes = (uintptr_t)(cur - data);
649
data = realloc(data, *bytes);
654
/* Export a text file with IRC colours */
655
void *_export_irc(caca_canvas_t const *cv, size_t *bytes)
657
static uint8_t const palette[] =
659
1, 2, 3, 10, 5, 6, 7, 15, /* Dark */
660
14, 12, 9, 11, 4, 13, 8, 0, /* Light */
666
/* 14 bytes assumed for max length per pixel. Worst case scenario:
670
* 3 bytes for max length per line. Worst case scenario:
671
* <spc> 1 byte (for empty lines)
673
* In real life, the average bytes per pixel value will be around 5.
676
*bytes = 2 + cv->height * (3 + cv->width * 14);
677
cur = data = malloc(*bytes);
679
for(y = 0; y < cv->height; y++)
681
uint32_t *lineattr = cv->attrs + y * cv->width;
682
uint32_t *linechar = cv->chars + y * cv->width;
684
uint8_t prevfg = 0x10;
685
uint8_t prevbg = 0x10;
687
for(x = 0; x < cv->width; x++)
689
uint32_t attr = lineattr[x];
690
uint32_t ch = linechar[x];
691
uint8_t ansifg, ansibg, fg, bg;
693
if(ch == CACA_MAGIC_FULLWIDTH)
696
ansifg = caca_attr_to_ansi_fg(attr);
697
ansibg = caca_attr_to_ansi_bg(attr);
699
fg = ansifg < 0x10 ? palette[ansifg] : 0x10;
700
bg = ansibg < 0x10 ? palette[ansibg] : 0x10;
702
/* TODO: optimise series of same fg / same bg
703
* don't change fg value if ch == ' '
704
* make sure the \x03,%d trick works everywhere */
705
if(bg != prevbg || fg != prevfg)
712
cur += sprintf(cur, "\x0f");
716
cur += sprintf(cur, "\x03%d", fg);
718
cur += sprintf(cur, "\x0f\x03%d", fg);
720
if(ch == (uint32_t)',')
727
cur += sprintf(cur, "\x0f\x03,%d", bg);
729
cur += sprintf(cur, "\x03%d,%d", fg, bg);
732
if(ch >= (uint32_t)'0' && ch <= (uint32_t)'9')
736
cur += sprintf(cur, "\x02\x02");
739
cur += caca_utf32_to_utf8(cur, ch);
744
/* TODO: do the same the day we optimise whole lines above */
752
/* Crop to really used size */
753
debug("IRC export: alloc %lu bytes, realloc %lu",
754
(unsigned long int)*bytes, (unsigned long int)(cur - data));
755
*bytes = (uintptr_t)(cur - data);
756
data = realloc(data, *bytes);
761
/* XXX : ANSI loader helper */
763
static void ansi_parse_grcm(caca_canvas_t *cv, struct import *im,
764
unsigned int argc, unsigned int const *argv)
766
static uint8_t const ansi2caca[] =
768
CACA_BLACK, CACA_RED, CACA_GREEN, CACA_BROWN,
769
CACA_BLUE, CACA_MAGENTA, CACA_CYAN, CACA_LIGHTGRAY
773
uint8_t efg, ebg; /* Effective (libcaca) fg/bg */
775
for(j = 0; j < argc; j++)
777
/* Defined in ECMA-48 8.3.117: SGR - SELECT GRAPHIC RENDITION */
778
if(argv[j] >= 30 && argv[j] <= 37)
779
im->fg = ansi2caca[argv[j] - 30];
780
else if(argv[j] >= 40 && argv[j] <= 47)
781
im->bg = ansi2caca[argv[j] - 40];
782
else if(argv[j] >= 90 && argv[j] <= 97)
783
im->fg = ansi2caca[argv[j] - 90] + 8;
784
else if(argv[j] >= 100 && argv[j] <= 107)
785
im->bg = ansi2caca[argv[j] - 100] + 8;
788
case 0: /* default rendition */
791
im->bold = im->blink = im->italics = im->negative
792
= im->concealed = im->underline = im->faint = im->strike
793
= im->proportional = 0;
795
case 1: /* bold or increased intensity */
798
case 2: /* faint, decreased intensity or second colour */
801
case 3: /* italicized */
804
case 4: /* singly underlined */
807
case 5: /* slowly blinking (less then 150 per minute) */
808
case 6: /* rapidly blinking (150 per minute or more) */
811
case 7: /* negative image */
814
case 8: /* concealed characters */
817
case 9: /* crossed-out (characters still legible but marked as to be
821
case 21: /* doubly underlined */
824
case 22: /* normal colour or normal intensity (neither bold nor
826
im->bold = im->faint = 0;
828
case 23: /* not italicized, not fraktur */
831
case 24: /* not underlined (neither singly nor doubly) */
834
case 25: /* steady (not blinking) */
837
case 26: /* (reserved for proportional spacing as specified in CCITT
838
* Recommendation T.61) */
839
im->proportional = 1;
841
case 27: /* positive image */
844
case 28: /* revealed characters */
847
case 29: /* not crossed out */
850
case 38: /* (reserved for future standardization, intended for setting
851
* character foreground colour as specified in ISO 8613-6
852
* [CCITT Recommendation T.416]) */
854
case 39: /* default display colour (implementation-defined) */
857
case 48: /* (reserved for future standardization, intended for setting
858
* character background colour as specified in ISO 8613-6
859
* [CCITT Recommendation T.416]) */
861
case 49: /* default background colour (implementation-defined) */
864
case 50: /* (reserved for cancelling the effect of the rendering
865
* aspect established by parameter value 26) */
866
im->proportional = 0;
869
debug("ansi import: unknown sgr %i", argv[j]);
876
efg = ebg = CACA_TRANSPARENT;
880
efg = im->negative ? im->bg : im->fg;
881
ebg = im->negative ? im->fg : im->bg;
887
else if(efg == CACA_DEFAULT)
892
caca_set_color_ansi(cv, efg, ebg);