Select Git revision
-
Martin Mareš authoredMartin Mareš authored
xcpt.c 12.81 KiB
/*
* 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;
}