#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, hash, x, y, s, i, j, k, c, offsets[22] = { 4352, 95, 4554, 1, 2902, 446, 2, 29839, 1841, 11171, 8541, 511, 785, 9, 23, 63, 145, 96, 128, 6, 61466, 135167 }, 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; 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 + 2 > z ) characters = (Char*)realloc(characters, (z *= 2) * 2 * sizeof(Char)); 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) { buffer[j = 0] = c = code; if( code > 127 ) { for(; code > 63 >> j; code >>= 6) buffer[j++] = (code & 63) | 128; buffer[j] = (1920 >> j & 240) | code; } for(; j > -1;) fputc(buffer[j--], handle); } /* Write a single escape sequence */ void WriteEscape(int offset, int code) { offset ? fprintf(handle, "\33[%d%c", abs(offset), code + (offset > 0)) : 0; } /* Open file handle for reading or writing */ char mode[2] = "r"; int OpenHandle(char *name) { return !(handle = fopen(name, mode)) ? perror(name), 1 : 0; } int main(int argc, char **argv) { handle = stdin; if( argc > 1 && (*argv[1] - 45 || argv[1][1]) && OpenHandle(argv[1]) ) goto fail; *mode |= 5; /* Load input characters to global buffer */ for(s = x = y = k = min_y = max_y = character_count = 0; (c = fgetc(handle)) - EOF;) /* Hash the input and use that to seed the random number generator. This makes the output deterministic, but we get to lose a header in exchange. Users can reshuffle the output by piping it through again, so there is a workaround to get more variations. */ hash += c, hash += hash << 10, hash ^= hash >> 6, s = s > 0 ? c - 91 ? c < 48 || c > 59 ? /* End escape sequence */ k = c % 4 < 2 ? -k : k, c > 64 && c < 67 ? y += k, 0 : c < 69 && (x += k) < 0 ? x = 0 : 0 : /* Update escape parameter */ (k = k * 10 + c - 48, s) : /* Second byte of escape sequence */ s : s < 0 ? /* Continue UTF-8 bytes */ (c = k = k << 6 | (c & 63), !++s) ? /* End UTF-8 sequence */ AddChar(), s : s : /* Normal input state */ (c & 224) - 192 ? (c & 240) - 224 ? (c & 248) - 240 ? c - 27 ? c == 10 ? y++, x = 0 : c - 32 ? c == 8 ? x = (x + 8) % 8 : c > 32 ? AddChar(), s : s : x++, s : /* Start of escape sequence */ (k = 0, 1) : (k = c & 7, -3) : (k = c & 15, -2) : (k = c & 31, -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(10); for(; x < characters[s].x; x++) WriteUTF8(32); /* 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(10); } else { /* Shuffle input */ srand(hash); for(x = character_count; --x; SwapChar()) y = rand() % -~x; /* Output characters in shuffled order */ if( (argc -= 2) ? OpenHandle(argv[2]) : argc++ ) goto fail; for(z = s = 0, y = max_y + 1; s < character_count && z < argc; z++) { /* Open output file for new layer */ if( z && OpenHandle(argv[z + 2]) ) goto fail; if( !z ) /* Preallocate vertical space for first layer */ for(s = max_y - min_y + 2; --s;) WriteUTF8(10); for(; s < character_count * (z + 1) / argc; y = characters[s++].y) WriteEscape(characters[s].y - y, 65), WriteEscape(x - characters[s].x, 67), WriteUTF8(characters[s].code), x = characters[s].x, UpdateCharWidth(); /* Move cursor to last line at the end of each layer */ WriteEscape(max_y - y, 65); WriteUTF8(10); x = fclose(handle); y = max_y + 1; } } } argc = 0; fail: return argc; }