#include #include #include #include #include #include #include #define MAX(x, y) (((x) > (y)) ? (x) : (y)) enum cmd { CMD_INIT, CMD_PRINTABLE_WIDTH, CMD_RASTER_MODE_START, CMD_RASTER_PAGE_LENGTH, CMD_START_PAGE, CMD_END_PAGE, CMD_END_JOB, }; static void putcmd(FILE *f, const enum cmd cmd) { assert(f); const struct { const uint8_t *bytes; size_t len; } map[] = { #define CMD(...) { .bytes = (uint8_t[]){__VA_ARGS__ }, .len = sizeof((uint8_t[]){__VA_ARGS__}) } CMD(0x1b, '@'), // CMD_INIT CMD(0x1b, 0x1e, 'A', 0x00), // CMD_PRINTABLE_WIDTH CMD(0x1b, '*', 'r', 'R', 0x1b, '*', 'r', 'A'), // CMD_RASTER_MODE_START CMD(0x1b, '*', 'r', 'P', '0', 0x00), // CMD_SET_RASTER_PAGE_LENGTH CMD(0x00), // CMD_START_PAGE CMD(0x1b, '*', 'r', 'Y', '1', 0x00, 0x1b, 0x0c), // CMD_END_PAGE CMD(0x04, 0x1b, '*', 'r', 'B'), // CMD_END_JOB #undef CMD }; if (fwrite(map[cmd].bytes, 1, map[cmd].len, f) != map[cmd].len) err(EXIT_FAILURE, "fwrite"); } static FILE* fopen_or_die(const char *path, const char *mode) { assert(path && mode); FILE *f; if (!(f = fopen(path, mode))) err(EXIT_FAILURE, "fopen(%s, %s)", path, mode); return f; } static uint8_t* find_pixel_not_rev(const uint8_t *data, const size_t len, const uint8_t pixel) { for (const uint8_t *p = data + MAX(len, 1) - 1; p != data; --p) { if (*p != pixel) return (uint8_t*)p; } return NULL; } uint8_t* pixels_from_pbm(FILE *f, size_t *out_w, size_t *out_h) { // NOTE: This does not support comments or extra whitespaces // Assumes PBM is structured! uint8_t magic[3]; if (fread(magic, 1, sizeof(magic), f) != sizeof(magic)) return NULL; if (memcmp(magic, "P4", 2) || !isspace(magic[2])) errx(EXIT_FAILURE, "not a valid pbm (P4) file"); uint32_t cfg[2]; for (uint8_t i = 0; i < sizeof(cfg) / sizeof(cfg[0]); ++i) { char buf[32]; size_t x = 0; do { if (fread(buf + x, 1, 1, f) != 1) err(EXIT_FAILURE, "fread"); } while (!isspace(buf[x]) && ++x < sizeof(buf)); if (!isspace(buf[x])) errx(EXIT_FAILURE, "not a valid pbm (P4) file"); cfg[i] = strtol(buf, NULL, 10); } if (!cfg[0] || !cfg[1]) errx(EXIT_FAILURE, "invalid width / height"); uint8_t *bw; #define div_round_up(x, y) (1 + ((x - 1) / y)) const size_t bw_sz = div_round_up(cfg[0], 8) * cfg[1]; if (!(bw = calloc(bw_sz, 1))) err(EXIT_FAILURE, "calloc"); for (size_t read = 0, r; read < bw_sz; read += r) { if (!(r = fread(bw + read, 1, bw_sz - read, f))) errx(EXIT_FAILURE, "fread"); } *out_w = div_round_up(cfg[0], 8), *out_h = cfg[1]; return bw; } int main(int argc, char *argv[]) { FILE *input = (argc >= 2 ? fopen_or_die(argv[1], "rb") : stdin); FILE *output = (argc >= 3 ? fopen_or_die(argv[2], "wb") : stdout); putcmd(output, CMD_INIT); putcmd(output, CMD_PRINTABLE_WIDTH); putcmd(output, CMD_RASTER_MODE_START); putcmd(output, CMD_RASTER_PAGE_LENGTH); size_t w, h; uint8_t *pixels; while ((pixels = pixels_from_pbm(input, &w, &h))) { putcmd(output, CMD_START_PAGE); size_t blank = 0; for (size_t y = 0; y < h; ++y) { const uint8_t *last_black; if (!(last_black = find_pixel_not_rev(&pixels[y * w], w, 0))) { ++blank; continue; } if (blank) { fprintf(output, "\x01b*rY%zu%c", blank, 0); blank = 0; } const size_t pos = (last_black - &pixels[y * w]) + 1; assert(y * w + pos <= w * h); fprintf(output, "b%c%c", (char)(pos % 256), (char)(pos / 256)); fwrite(&pixels[y * w], 1, pos, output); } putcmd(output, CMD_END_PAGE); free(pixels); } putcmd(output, CMD_END_JOB); return EXIT_SUCCESS; }