#include #include #include #include /* Global buffer of characters */ typedef struct { int x, y, /* Character position */ code; /* Character code point */ } Char; Char *characters = NULL; int character_count, z = 1, /* Character capacity */ min_y, max_y, x, y, s, i, j, k, c, offsets[22] = { 0x1100, 0x115f - 0x1100, 0x2329 - 0x115f, 0x232a - 0x2329, 0x2e80 - 0x232a, 0x303e - 0x2e80, 0x3040 - 0x303e, 0xa4cf - 0x3040, 0xac00 - 0xa4cf, 0xd7a3 - 0xac00, 0xf900 - 0xd7a3, 0xfaff - 0xf900, 0xfe10 - 0xfaff, 0xfe19 - 0xfe10, 0xfe30 - 0xfe19, 0xfe6f - 0xfe30, 0xff00 - 0xfe6f, 0xff60 - 0xff00, 0xffe0 - 0xff60, 0xffe6 - 0xffe0, 0x1f000 - 0xffe6, 0x3ffff - 0x1f000 }, buffer[4]; FILE *handle; /* Swap characters[x] and characters[y] */ void SwapChar() { s = characters[x].x; characters[x].x = characters[y].x; characters[y].x = s; s = characters[x].y; characters[x].y = characters[y].y; characters[y].y = s; s = characters[x].code; characters[x].code = characters[y].code; characters[y].code = s; } /* MergeSort all characters by position */ void MergeSort(int p, int q) { int m = (p + q) / 2; assert(p >= 0); assert(q <= character_count); if( m - p ) { MergeSort(p, m); MergeSort(m, q); for(i = p, j = m, y = character_count + p; y < character_count + q; y++) x = i >= m || (j < q && (characters[i].y > characters[j].y || (characters[i].y == characters[j].y && characters[i].x > characters[j].x))) ? j++ : i++, SwapChar(); for(y = p, x = character_count + p; y < q; x++, y++) SwapChar(); } } /* Update x with width of character */ void UpdateCharWidth() { /* Note that the final index is incremented after the conditional expression, such that it's only incremented if the condition passes. This is needed to check for characters that falls in the final range. */ for(i = k = 0; i < 22 && (j = k + offsets[i++], k = j + offsets[i], c < j || c > k); i++); x += 2 - i / 22; } /* Add a single character to global buffer */ void AddChar() { /* Need to reserve twice as much memory as maximum number of characters, to account for mergesort. We only double when the number of characters reach capacity, so the amount allocated is z * 2. */ if( character_count + 1 >= z ) { z *= 2; characters = (Char*)realloc(characters, z * 2 * sizeof(Char)); if( characters == NULL ) { fputs("Out of memory\n", stderr); exit(EXIT_FAILURE); } } characters[character_count].x = x; characters[character_count].y = y; characters[character_count++].code = c; min_y = min_y > y ? y : min_y; max_y = max_y < y ? y : max_y; UpdateCharWidth(); } /* Write UTF-8 bytes */ void WriteUTF8(int code) { assert(code >= 0); buffer[j = 0] = c = code; if( code >= 0x80 ) { for(; code > (0x3f >> j); code >>= 6) buffer[j++] = (code & 0x3f) | 0x80; buffer[j] = ((0x780 >> j) & 0xf0) | code; } for(; j >= 0;) fputc(buffer[j--], handle); } /* Write a single escape sequence */ void WriteEscape(int offset, int code) { offset ? fprintf(handle, "\x1b[%d%c", abs(offset), code + (offset > 0)) : 0; } int main(int argc, char **argv) { handle = stdin; if( argc > 1 && (argv[1][0] - '-' || argv[1][1]) ) { if( (handle = fopen(argv[1], "rb")) == NULL ) return printf("Can not open %s for reading\n", argv[1]); } /* Load input characters to global buffer */ for (s = x = y = k = min_y = max_y = character_count = 0; (c = fgetc(handle)) != EOF;) s = s > 0 ? c - '[' ? c < '0' || c > ';' ? /* End escape sequence */ k = c % 4 < 2 ? -k : k, c >= 'A' && c <= 'D' ? c > 'B' ? (x += k) < 0 ? x = 0 : 0 : (y += k), 0 : 0 : /* Update escape parameter */ (k = k * 10 + c - '0', s) : /* Second byte of escape sequence */ s : s < 0 ? /* Continue UTF-8 bytes */ (c = k = (k << 6) | (c & 0x3f), !++s) ? /* End UTF-8 sequence */ AddChar(), s : s : /* Normal input state */ (c & 0xe0) - 0xc0 ? (c & 0xf0) - 0xe0 ? (c & 0xf8) - 0xf0 ? c - '\x1b' ? c - '\n' ? c - ' ' ? c - '\t' ? c > 32 ? AddChar(), s : s : (x = (x + 8) % 8) : x++ : (y++, x = 0), s : /* Start of escape sequence */ (k = 0, 1) : (k = c & 0x07, -3) : (k = c & 0x0f, -2) : (k = c & 0x1f, -1); fclose(handle); handle = stdout; if( characters ) { if( argc < 2 ) { /* Assemble stdin to stdout */ MergeSort(0, character_count); /* Output characters in sorted order */ /* Write one sentinel character to mark the end. This for seeking one character ahead when checking duplicates. We don't have to worry about out of bounds writes since the buffer is always at least twice as large as number of characters. */ characters[character_count].x = -1; for(s = x = 0, y = min_y; s < character_count; s++) { for(; y < characters[s].y; y++, x = 0) WriteUTF8('\n'); for(; x < characters[s].x; x++) WriteUTF8(' '); /* Seek ahead in case if there are overlaps. We want to take the last character that is drawn at this position to simulate overlaps. */ for(; characters[s + 1].y == characters[s].y && characters[s + 1].x == characters[s].x;) s++; WriteUTF8(characters[s].code); UpdateCharWidth(); } /* Always end with newline */ if( x ) WriteUTF8('\n'); } else { /* Shuffle input */ srand(time(NULL)); for(x = character_count; --x > 0; SwapChar()) y = rand() % (x + 1); /* Output characters in shuffled order */ if( !(argc -= 2) ) argc = 1; else if( (handle = fopen(argv[2], "wb")) == NULL ) { printf("Can not open %s for writing\n", argv[2]); return 1; } for(z = s = x = 0, y = max_y + 1; s < character_count && z < argc; z++) { if( z ) { /* Open output file for new layer */ if( (handle = fopen(argv[z + 2], "wb")) == NULL ) { printf("Can not open %s for writing\b", argv[z + 2]); return 1; } } else { /* Preallocate vertical space for first layer */ for(s = 0; s < max_y - min_y + 1; s++) WriteUTF8('\n'); s = 0; } for(; s < character_count * (z + 1) / argc; y = characters[s++].y) WriteEscape(characters[s].y - y, 'A'), WriteEscape(x - characters[s].x, 'C'), WriteUTF8(characters[s].code), x = characters[s].x, UpdateCharWidth(); /* Move cursor to last line at the end of each layer */ WriteEscape(max_y - y, 'A'); WriteUTF8('\n'); fclose(handle); x = 0; y = max_y + 1; } } } return 0; }