/* * CUPS Filter Generating XCPT * * (c) 2016 Martin Mares <mj@ucw.cz> */ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #define PRINTF(i,j) __attribute__((format(printf,i,j))) #define NONRET __attribute__((noreturn)) #if 1 #define DEBUG(...) debug(__VA_ARGS__) #else #define DEBUG(...) do { } while(0) #endif /*** Utility functions ***/ static void PRINTF(1,2) NONRET bug(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "ERROR: XCPT: "); vfprintf(stderr, fmt, args); fputc('\n', stderr); va_end(args); exit(1); } #define ASSERT(_cond) do { if (!(_cond)) bug("Assertion failed: %s", #_cond); } while (0) #if 0 static void PRINTF(1,2) error(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "ERROR: XCPT: "); vfprintf(stderr, fmt, args); fputc('\n', stderr); va_end(args); } #endif static void PRINTF(1,2) debug(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "DEBUG: XCPT: "); vfprintf(stderr, fmt, args); fputc('\n', stderr); va_end(args); } static char *xstrdup(const char *x) { char *p = strdup(x); if (!p) bug("Out of memory"); return p; } /*** Options ***/ #define OPTIONS \ O(JOB_ID, "?") \ O(USERNAME, "?") \ O(JOB_TITLE, "?") \ O(COPIES, "1") \ O(LANGUAGE, "?") \ O(COLOR, "color") \ O(ORIENTATION, "") \ O(PRIVATE, "") \ O(OFFSET, "none") \ O(SIDES, "one-sided") \ O(COLLATE, "uncollated") \ O(OUTPUT, "automatic") \ O(MEDIACOLOR, "white") \ O(INPUTTRAY, "automatic") \ O(TRAYFEED, "stack") \ O(MEDIATYPE, "system-default") \ O(MEDIAXSIZE, "595") \ O(MEDIAYSIZE, "842") \ O(FINISH_STAPLE, "") \ O(FINISH_PUNCH, "") \ O(FINISH_FOLD, "") \ O(FEED_ORIENTATION, "automatic") \ O(QUALITY, "standard") enum option { #define O(name, val) OPT_##name, OPTIONS #undef O OPT_MAX }; const char *opt_names[] = { #define O(name, val) #name, OPTIONS #undef O }; char *opt[] = { #define O(name, val) [OPT_##name] = val, OPTIONS #undef O }; #define OPT(name) opt[OPT_##name] #define OPT_INT(name) atoi(opt[OPT_##name]) #define OPT_IS(name, val) !strcasecmp(OPT(name), val) #define OPT_IS_SET(name) *OPT(name) #define OPT_IS_TRUE(name) OPT_IS(name, "yes") static void set_option(char *key, char *val) { for (int i=0; i<OPT_MAX; i++) if (!strcmp(opt_names[i], key)) { debug("Setting %s=<%s>", key, val); opt[i] = xstrdup(val); return; } debug("Unrecognized option <%s>", key); } /** * This is our parser of PJL. A hacky one, indeed, but it is expected * to parse only PJL directives generated by our PPD or by CUPS itself, * so we need not pay attention to all obscure details of the language. **/ #define LINESIZE 256 static char *skip_spaces(char *p) { while (*p == ' ' || *p == '\t') p++; return p; } static int my_toupper(int c) { if (c >= 'a' && c <= 'z') return c - 32; else return c; } static char *token_buf; static char *get_token(char **pp) { char *token_start = token_buf; char *tok = token_start; char *p = skip_spaces(*pp); if (!*p) return NULL; int c = my_toupper(*p); if (c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_') { while (c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_') { *tok++ = c; c = my_toupper(*++p); } } else *tok++ = *p++; *tok++ = 0; *pp = p; token_buf = tok; return token_start; } static void parse_pjl(void) { static const char uel[] = "\e%-12345X"; for (int i=0; uel[i]; i++) if (getchar() != uel[i]) bug("PJL error: No UEL found (pos %u)", i); char line[LINESIZE], tokens[2*LINESIZE]; for (;;) { int i = 0; for (;;) { int c = getchar(); if (c < 0) bug("PJL error: Premature EOF"); if (i < 4 && c != "@PJL"[i]) bug("PJL error: Unrecognized line"); if (c == '\r') continue; if (c == '\n') break; if (i >= LINESIZE-1) bug("PJL error: Line too long"); line[i++] = c; } while (i > 0 && (line[i-1] == ' ' || line[i-1] == '\t')) i--; line[i] = 0; DEBUG("PJL: %s", line); char *p = line+1; token_buf = tokens; char *t, *t2, *t3; if (!(t = get_token(&p)) || strcasecmp(t, "PJL")) bug("PJL error: Malformed line"); if (!(t = get_token(&p))) continue; if (!strcasecmp(t, "ENTER")) { if ((t2 = get_token(&p)) && !strcasecmp(t2, "LANGUAGE")) { if ((t3 = get_token(&p)) && !strcasecmp(t3, "=")) { set_option("LANGUAGE", skip_spaces(p)); return; } } } if (!strcasecmp(t, "SET")) { if (t2 = get_token(&p)) { if ((t3 = get_token(&p)) && !strcasecmp(t3, "=")) { p = skip_spaces(p); if (*p == '"') { *p++ = 0; char *q = p; while (*q && *q != '"') q++; if (*q) *q = 0; } set_option(t2, p); } } } } } /*** XCPT generator ***/ #define STACK_DEPTH 10 static int xcpt_level; static const char *xcpt_stack[STACK_DEPTH]; static void xcpt_start_line(void) { printf("@PJL XCPT "); for (int i=0; i<xcpt_level; i++) putchar('\t'); } static void xcpt_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); xcpt_start_line(); vprintf(fmt, args); putchar('\n'); va_end(args); } static void xcpt_push(const char *name) { ASSERT(xcpt_level < STACK_DEPTH); xcpt_stack[xcpt_level++] = xstrdup(name); } static void xcpt_open(const char *name) { xcpt_printf("<%s>", name); xcpt_push(name); } static void xcpt_close(void) { ASSERT(xcpt_level); const char *name = xcpt_stack[--xcpt_level]; xcpt_printf("</%s>", name); free((char *) name); } static void xcpt_integer(const char *name, int value) { xcpt_printf("<%s syntax=\"integer\">%d</%s>", name, value, name); } static void xcpt_dimension(const char *name, int pt) { /* * We get dimensions in PostScript points (1/72 in). * Xerox printers expect them in 100th's of mm, but * last two digits must be 0. */ double m = pt * (25.4 / 72.) + 0.5; xcpt_integer(name, 100 * (int)m); } static void xcpt_keyword(const char *name, const char *value) { xcpt_printf("<%s syntax=\"keyword\">%s</%s>", name, value, name); } static void xcpt_enum(const char *name, int value) { xcpt_printf("<%s syntax=\"enum\">%d</%s>", name, value, name); } static void xcpt_media_type(const char *name, const char *value) { xcpt_printf("<%s syntax=\"mimeMediaType\">%s</%s>", name, value, name); } static void xcpt_collection_open(const char *name) { xcpt_printf("<%s syntax=\"collection\">", name); xcpt_push(name); } static void xcpt_set_open(const char *name) { xcpt_printf("<%s syntax=\"1setOf\">", name); xcpt_push(name); } static void xcpt_cdata(const char *text) { int c; while (c = *text++) switch (c) { case '<': printf("<"); break; case '>': printf(">"); break; case '&': printf("&"); break; case '"': printf("""); break; default: putchar(c); } } static void xcpt_text(const char *name, const char *text) { xcpt_start_line(); printf("<%s syntax=\"text\" xml:space=\"preserve\">", name); xcpt_cdata(text); printf("</%s>\n", name); } static void xcpt_name(const char *name, const char *text) { xcpt_start_line(); printf("<%s syntax=\"name\" xml:space=\"preserve\">", name); xcpt_cdata(text); printf("</%s>\n", name); } static void gen_finishings(const char *opt) { int fin, n; while (sscanf(opt, "%d%n", &fin, &n) > 0) { xcpt_enum("value", fin); opt += n; } } static void gen_job_template(void) { // Color adjustments xcpt_integer("adjust-contrast", 0); // FIXME: all of them xcpt_integer("adjust-cyan-red", 0); xcpt_integer("adjust-lightness", 0); xcpt_integer("adjust-magenta-green", 0); xcpt_integer("adjust-saturation", 0); xcpt_integer("adjust-yellow-blue", 0); if (OPT_IS(COLOR, "color")) { xcpt_keyword("color-adjustment-set", "automatic"); xcpt_keyword("color-effects-type", "color"); } else { xcpt_keyword("color-adjustment-set", "monochrome-grayscale"); xcpt_keyword("color-effects-type", "monochrome-grayscale"); } xcpt_integer("copies", OPT_INT(COPIES)); if (OPT_IS_SET(ORIENTATION)) xcpt_keyword("document-reading-orientation", OPT(ORIENTATION)); // XCPT specs allow feed-orientation either globaly, or inside media-col. // The latter makes more sense, but it does not work on the WC7845. xcpt_keyword("feed-orientation", OPT(FEED_ORIENTATION)); xcpt_set_open("finishings"); if (OPT_IS_SET(FINISH_FOLD)) gen_finishings(OPT(FINISH_FOLD)); else if (OPT_IS_SET(FINISH_STAPLE) || OPT_IS_SET(FINISH_PUNCH)) { gen_finishings(OPT(FINISH_STAPLE)); gen_finishings(OPT(FINISH_PUNCH)); } else xcpt_enum("value", 3); xcpt_close(); xcpt_keyword("halftone-text", "ignore-pdl"); xcpt_keyword("halftone-graphics", "ignore-pdl"); xcpt_keyword("halftone-images", "ignore-pdl"); if (OPT_IS_SET(PRIVATE)) xcpt_keyword("hold-for-authentication", "user-id"); else xcpt_keyword("hold-for-authentication", "none"); // Interleaving not supported at the moment xcpt_collection_open("interleaved-sheets-col"); xcpt_keyword("interleaved-sheets-type", "none"); xcpt_close(); xcpt_set_open("job-offset"); xcpt_keyword("value", OPT(OFFSET)); xcpt_close(); // Saved jobs are not supported at the moment xcpt_collection_open("job-save-disposition"); xcpt_keyword("save", "none"); xcpt_close(); // We leave printing of banner sheets to CUPS itself xcpt_keyword("job-sheets", "none"); xcpt_collection_open("client-default-attributes-col"); xcpt_collection_open("media-col"); xcpt_keyword("input-tray", OPT(INPUTTRAY)); xcpt_keyword("tray-feed", OPT(TRAYFEED)); xcpt_keyword("media-color", OPT(MEDIACOLOR)); xcpt_collection_open("media-size"); xcpt_dimension("x-dimension", OPT_INT(MEDIAXSIZE)); xcpt_dimension("y-dimension", OPT_INT(MEDIAYSIZE)); xcpt_close(); xcpt_keyword("media-type", OPT(MEDIATYPE)); xcpt_close(); xcpt_keyword("print-quality-level", OPT(QUALITY)); xcpt_keyword("sides", OPT(SIDES)); xcpt_close(); xcpt_keyword("output-bin", OPT(OUTPUT)); xcpt_keyword("sheet-collate", OPT(COLLATE)); // Rendering intents and color sources: so far, nothing can be set xcpt_keyword("embedded-profiles", "automatic"); xcpt_keyword("print-settings", "none"); xcpt_keyword("rendering-intent-graphics", "automatic"); xcpt_keyword("rendering-intent-images", "automatic"); xcpt_keyword("rendering-intent-text", "automatic"); xcpt_keyword("spot-color-mapping", "use-local-printer-values"); xcpt_keyword("undefined-source-cmyk-images", "automatic"); xcpt_keyword("undefined-source-cmyk-text", "automatic"); xcpt_keyword("undefined-source-gray-images", "automatic"); xcpt_keyword("undefined-source-gray-text", "automatic"); xcpt_keyword("undefined-source-rgb-images", "automatic"); xcpt_keyword("undefined-source-rgb-text", "automatic"); xcpt_keyword("undefined-spot-color-images", "automatic"); xcpt_keyword("undefined-spot-color-text", "automatic"); } static void gen_job_operation(void) { xcpt_keyword("creator-name-attributes", "cups-xcpt"); xcpt_keyword("creator-name-pdl", "unknown"); xcpt_text("creator-version-attributes", "0.1"); // FIXME xcpt_text("creator-version-pdl", "0.0"); if (OPT_IS(LANGUAGE, "PDF")) xcpt_media_type("document-format", "application/pdf"); else xcpt_media_type("document-format", "application/postscript"); xcpt_name("job-id-from-client", OPT(JOB_ID)); xcpt_name("job-name", OPT(JOB_TITLE)); xcpt_name("job-originating-user-domain", "KAM"); // FIXME const char *user = OPT(USERNAME); xcpt_name("job-originating-user-name", user); xcpt_name("requesting-user-name", user); } static void gen_xcpt(void) { printf("\e%%-12345X@PJL JOB\n"); xcpt_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); xcpt_printf("<!DOCTYPE xpif SYSTEM \"xpif-v02081.dtd\">"); xcpt_printf("<xpif version=\"1.0\" cpss-version=\"2.07\" xml:lang=\"en-US\">"); xcpt_push("xpif"); xcpt_open("job-template-attributes"); gen_job_template(); xcpt_close(); xcpt_open("job-operation-attributes"); gen_job_operation(); xcpt_close(); xcpt_close(); ASSERT(!xcpt_level); printf("@PJL ENTER LANGUAGE = %s\n", OPT(LANGUAGE)); } static void copy_body(void) { char buf[1024]; size_t n; while (n = fread(buf, 1, sizeof(buf), stdin)) { if (fwrite(buf, 1, n, stdout) != n) bug("Write error"); } } /*** Main ***/ int main(int argc, char **argv) { if (argc < 5) { fprintf(stderr, "Usage: xcpt <job> <user> <title> <num-copies> [<options>]\n"); return 1; } set_option("JOB_ID", argv[1]); set_option("USERNAME", argv[2]); set_option("JOB_TITLE", argv[3]); set_option("COPIES", argv[4]); parse_pjl(); for (int i=0; i<OPT_MAX; i++) DEBUG("Option %s=<%s>", opt_names[i], opt[i]); gen_xcpt(); copy_body(); return 0; }