Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
1 result

xcpt.c

Blame
  • 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("&lt;");
    	break;
          case '>':
    	printf("&gt;");
    	break;
          case '&':
    	printf("&amp;");
    	break;
          case '"':
    	printf("&quot;");
    	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;
    }