Created
July 27, 2026 22:20
-
-
Save Techwizz-somboo/ffe4ad77af3ce652002e2c89b9460b1d to your computer and use it in GitHub Desktop.
D450BT cups filter, probably good for other Omezizy printers, made with Claude. This makes in possible to use their label printers on musl systems or if you would rather a FOSS implementation of the CUPS filter.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * rastertolabeltspl - CUPS raster filter for TSPL-based label printers | |
| * | |
| * Reimplementation of the vendor's (glibc-only) filter for use on musl | |
| * systems. Reads CUPS Raster from stdin (or the filename CUPS passes as | |
| * argv[6]) and writes TSPL commands to stdout, which CUPS then sends on | |
| * to the backend/device. | |
| * | |
| * Standard System V CUPS filter calling convention: | |
| * argv[1] = job-id | |
| * argv[2] = user | |
| * argv[3] = title | |
| * argv[4] = copies | |
| * argv[5] = options | |
| * argv[6] = filename (optional; if absent, read stdin) | |
| * | |
| * Build: | |
| * gcc -O2 -Wall -o rastertolabeltspl rastertolabeltspl.c $(cups-config --cflags --libs) | |
| * (or: gcc -O2 -Wall -o rastertolabeltspl rastertolabeltspl.c -lcups) | |
| * clang -O2 -Wall -Wextra -o rastertolabeltspl rastertolabeltspl.c $(cups-config --cflags --libs) | |
| * | |
| * Install: | |
| * cp rastertolabeltspl /usr/libexec/cups/filter/ | |
| * chmod 755 /usr/libexec/cups/filter/rastertolabeltspl | |
| * | |
| * TUNING NOTES (read before first real print): | |
| * - GAP_MM below assumes a small gap between die-cut labels. If you're | |
| * using continuous/black-mark stock, change GAP_MODE accordingly. | |
| * - DENSITY (0-15 on most TSPL clones) affects darkness; start low and | |
| * increase if prints are too light. | |
| * - Bit polarity: CUPS_CSPACE_K (black channel) treats 1=ink=black, | |
| * which matches TSPL BITMAP's 1=print-dot convention directly. | |
| * CUPS_CSPACE_W (whiteness) is inverted, so we flip bits in that case. | |
| * If your first test print comes out as a solid black rectangle with | |
| * white text/graphics (i.e. inverted), toggle INVERT_OVERRIDE below. | |
| */ | |
| #include <cups/raster.h> | |
| #include <cups/cups.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <fcntl.h> | |
| #include <signal.h> | |
| /* ---- Tunables: adjust to match your label stock / printer ---- */ | |
| #define GAP_MM_LEN 2.0 /* gap length between labels, mm */ | |
| #define GAP_MM_OFFSET 0.0 /* gap offset, mm */ | |
| #define DEFAULT_DENSITY 8 /* 0-15, darkness */ | |
| #define DEFAULT_SPEED 4 /* print speed, printer-dependent scale */ | |
| #define DIRECTION 0 /* 0 or 1 depending on feed orientation */ | |
| #define INVERT_OVERRIDE 1 /* -1 = auto-detect from colorspace, 0 = never invert, 1 = always invert */ | |
| /* ---------------------------------------------------------------- */ | |
| static void write_tspl_header(FILE *out, double width_mm, double height_mm) | |
| { | |
| fprintf(out, "SIZE %.2f mm, %.2f mm\r\n", width_mm, height_mm); | |
| fprintf(out, "GAP %.2f mm, %.2f mm\r\n", GAP_MM_LEN, GAP_MM_OFFSET); | |
| fprintf(out, "DIRECTION %d\r\n", DIRECTION); | |
| fprintf(out, "DENSITY %d\r\n", DEFAULT_DENSITY); | |
| fprintf(out, "SPEED %d\r\n", DEFAULT_SPEED); | |
| fprintf(out, "CLS\r\n"); | |
| } | |
| int main(int argc, char *argv[]) | |
| { | |
| if (argc < 6 || argc > 7) { | |
| fprintf(stderr, "ERROR: %s job-id user title copies options [file]\n", | |
| argv[0]); | |
| return 1; | |
| } | |
| int copies = atoi(argv[4]); | |
| if (copies < 1) copies = 1; | |
| int fd = 0; /* stdin by default */ | |
| if (argc == 7) { | |
| fd = open(argv[6], O_RDONLY); | |
| if (fd < 0) { | |
| perror("ERROR: unable to open input file"); | |
| return 1; | |
| } | |
| } | |
| cups_raster_t *ras = cupsRasterOpen(fd, CUPS_RASTER_READ); | |
| if (!ras) { | |
| fprintf(stderr, "ERROR: unable to open raster stream\n"); | |
| return 1; | |
| } | |
| cups_page_header2_t header; | |
| int page = 0; | |
| while (cupsRasterReadHeader2(ras, &header)) { | |
| page++; | |
| double width_mm = header.PageSize[0] / 72.0 * 25.4; | |
| double height_mm = header.PageSize[1] / 72.0 * 25.4; | |
| unsigned bytes_per_line = header.cupsBytesPerLine; | |
| unsigned image_height = header.cupsHeight; | |
| unsigned image_width = header.cupsWidth; | |
| unsigned bpp = header.cupsBitsPerPixel; | |
| fprintf(stderr, "DEBUG: page %d: %ux%u px, %u bpp, %.2fx%.2f mm, " | |
| "%u bytes/line, cupsColorSpace=%u\n", | |
| page, image_width, image_height, bpp, width_mm, height_mm, | |
| bytes_per_line, header.cupsColorSpace); | |
| /* Decide bit polarity */ | |
| int invert; | |
| if (INVERT_OVERRIDE != -1) { | |
| invert = INVERT_OVERRIDE; | |
| } else { | |
| /* CUPS_CSPACE_W (0) is "whiteness" -> inverted vs TSPL's | |
| * 1=print-dot. CUPS_CSPACE_K (3) is "black ink" -> matches | |
| * TSPL directly. Default to no invert for anything else. */ | |
| invert = (header.cupsColorSpace == CUPS_CSPACE_W) ? 1 : 0; | |
| } | |
| fprintf(stderr, "DEBUG: page %d: invert=%d (override=%d)\n", | |
| page, invert, INVERT_OVERRIDE); | |
| write_tspl_header(stdout, width_mm, height_mm); | |
| if (bpp == 1) { | |
| /* Native 1-bit: read and (maybe) invert, write straight through */ | |
| unsigned char *line = malloc(bytes_per_line); | |
| unsigned char *bitmap = malloc((size_t)bytes_per_line * image_height); | |
| if (!line || !bitmap) { | |
| fprintf(stderr, "ERROR: out of memory\n"); | |
| return 1; | |
| } | |
| for (unsigned y = 0; y < image_height; y++) { | |
| if (cupsRasterReadPixels(ras, line, bytes_per_line) < 1) { | |
| fprintf(stderr, "ERROR: short read at line %u\n", y); | |
| break; | |
| } | |
| if (invert) { | |
| for (unsigned i = 0; i < bytes_per_line; i++) | |
| line[i] = ~line[i]; | |
| } | |
| memcpy(bitmap + (size_t)y * bytes_per_line, line, bytes_per_line); | |
| } | |
| fprintf(stdout, "BITMAP 0,0,%u,%u,0,", bytes_per_line, image_height); | |
| fwrite(bitmap, 1, (size_t)bytes_per_line * image_height, stdout); | |
| fprintf(stdout, "\r\n"); | |
| free(line); | |
| free(bitmap); | |
| } else if (bpp == 8) { | |
| /* 8-bit grayscale: threshold to 1bpp ourselves */ | |
| unsigned char *gray = malloc(image_width); | |
| unsigned out_bpl = (image_width + 7) / 8; | |
| unsigned char *bitmap = calloc((size_t)out_bpl * image_height, 1); | |
| if (!gray || !bitmap) { | |
| fprintf(stderr, "ERROR: out of memory\n"); | |
| return 1; | |
| } | |
| const unsigned char THRESHOLD = 128; | |
| for (unsigned y = 0; y < image_height; y++) { | |
| if (cupsRasterReadPixels(ras, gray, image_width) < 1) { | |
| fprintf(stderr, "ERROR: short read at line %u\n", y); | |
| break; | |
| } | |
| for (unsigned x = 0; x < image_width; x++) { | |
| /* Base assumption: for CUPS_CSPACE_W (whiteness), low | |
| * values are dark/black; for anything else (e.g. K), | |
| * high values are dark/black. The invert flag is then | |
| * applied on top so it actually has an effect here | |
| * (previously this branch ignored invert entirely). */ | |
| int base_dark = header.cupsColorSpace == CUPS_CSPACE_W | |
| ? (gray[x] < THRESHOLD) | |
| : (gray[x] >= THRESHOLD); | |
| int dark = invert ? !base_dark : base_dark; | |
| if (dark) { | |
| bitmap[(size_t)y * out_bpl + x / 8] |= (0x80 >> (x % 8)); | |
| } | |
| } | |
| } | |
| fprintf(stdout, "BITMAP 0,0,%u,%u,0,", out_bpl, image_height); | |
| fwrite(bitmap, 1, (size_t)out_bpl * image_height, stdout); | |
| fprintf(stdout, "\r\n"); | |
| free(gray); | |
| free(bitmap); | |
| } else { | |
| fprintf(stderr, "ERROR: unsupported cupsBitsPerPixel=%u " | |
| "(only 1 and 8 are handled)\n", bpp); | |
| return 1; | |
| } | |
| fprintf(stdout, "PRINT %d,1\r\n", copies); | |
| } | |
| if (page == 0) { | |
| fprintf(stderr, "ERROR: no pages found in raster stream\n"); | |
| cupsRasterClose(ras); | |
| return 1; | |
| } | |
| cupsRasterClose(ras); | |
| fflush(stdout); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment