text: /*
   * Copyright 2012 Canonical Ltd.
   * Copyright 2013 ALT Linux, Andrew V. Stepanov <stanv@altlinux.com>
   *
   * This program is free software: you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 3, as published
   * by the Free Software Foundation.
   *
   * This program is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranties of
   * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
   * PURPOSE.  See the GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License along
   * with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  #include <config.h>
  #include <stdio.h>
  #include <stdarg.h>
  #include <math.h>
  
  #ifndef HAVE_OPEN_MEMSTREAM
  #include <fcntl.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <errno.h>
  #endif
  
  #include <cups/cups.h>
  #include <cups/ppd.h>
  #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 6)
  #define HAVE_CUPS_1_7 1
  #endif
  #ifdef HAVE_CUPS_1_7
  #include <cups/pwg.h>
  #endif /* HAVE_CUPS_1_7 */
  
  #include "banner.h"
  #include "pdf.h"
  
  
  static float get_float_option(const char *name,
                                int noptions,
                                cups_option_t *options,
                                float def)
  {
      const char *value = cupsGetOption(name, noptions, options);
      return value ? atof(value) : def;
  }
  
  
  static int get_int_option(const char *name,
                            int noptions,
                            cups_option_t *options,
                            int def)
  {
      const char *value = cupsGetOption(name, noptions, options);
      return value ? atoi(value) : def;
  }
  
  
  static void get_pagesize(ppd_file_t *ppd,
                           int noptions,
                           cups_option_t *options,
                           float *width,
                           float *length,
                           float media_limits[4])
  {
      static const ppd_size_t defaultsize = {
          0,          /* marked */
          "",         /* name */
          612.0,      /* width */
          792.0,      /* length */
          18.0,       /* left */
          36.0,       /* bottom */
          594.0,      /* right */
          756.0,      /* top */
      };
      const ppd_size_t *pagesize;
  #ifdef HAVE_CUPS_1_7
      pwg_media_t      *size_found;          /* page size found for given name */
      const char       *val;                 /* Pointer into value */
      char             *ptr1, *ptr2,         /* Pointer into string */
                       s[255];               /* Temporary string */
  #endif /* HAVE_CUPS_1_7 */
  
      if (!ppd || !(pagesize = ppdPageSize(ppd, NULL)))
          pagesize = &defaultsize;
  
      *width = pagesize->width;
      *length = pagesize->length;
  
      media_limits[0] = get_float_option("page-left",
                                         noptions, options,
                                         pagesize->left);
      media_limits[1] = get_float_option("page-bottom",
                                         noptions, options,
                                         pagesize->bottom);
      media_limits[2] = get_float_option("page-right",
                                         noptions, options,
                                         fabs(pagesize->right));
      media_limits[3] = get_float_option("page-top",
                                         noptions, options,
                                         fabs(pagesize->top));
  
  #ifdef HAVE_CUPS_1_7
      if (!ppd) {
        if ((val = cupsGetOption("media-size", noptions, options)) != NULL ||
  	  (val = cupsGetOption("MediaSize", noptions, options)) != NULL ||
  	  (val = cupsGetOption("page-size", noptions, options)) != NULL ||
  	  (val = cupsGetOption("PageSize", noptions, options)) != NULL ||
  	  (val = cupsGetOption("media", noptions, options)) != NULL) {
  	for (ptr1 = (char *)val; *ptr1;) {
  	  for (ptr2 = s; *ptr1 && *ptr1 != ',' && (ptr2 - s) < (sizeof(s) - 1);)
  	    *ptr2++ = *ptr1++;
  	  *ptr2++ = '\0';
  	  if (*ptr1 == ',')
  	    ptr1 ++;
  	  size_found = NULL;
  	  if ((size_found = pwgMediaForPWG(s)) == NULL)
  	    if ((size_found = pwgMediaForPPD(s)) == NULL)
  	      size_found = pwgMediaForLegacy(s);
  	  if (size_found != NULL) {
  	    *width = size_found->width * 72.0 / 2540.0;
  	    *length = size_found->length * 72.0 / 2540.0;
  	    media_limits[2] += (*width - 612.0);
  	    media_limits[3] += (*length - 792.0);
  	  }
  	}
        }
        if ((val = cupsGetOption("media-left-margin", noptions, options))
  	  != NULL)
  	media_limits[0] = atol(val) * 72.0 / 2540.0; 
        if ((val = cupsGetOption("media-bottom-margin", noptions, options))
  	  != NULL)
  	media_limits[1] = atol(val) * 72.0 / 2540.0; 
        if ((val = cupsGetOption("media-right-margin", noptions, options))
  	  != NULL)
  	media_limits[2] = *width - atol(val) * 72.0 / 2540.0; 
        if ((val = cupsGetOption("media-top-margin", noptions, options))
  	  != NULL)
  	media_limits[3] = *length - atol(val) * 72.0 / 2540.0; 
      }
  #endif /* HAVE_CUPS_1_7 */
  }
  
  
  static int duplex_marked(ppd_file_t *ppd,
                           int noptions,
                           cups_option_t *options)
  {
      const char       *val;                 /* Pointer into value */
      return
        (ppd &&
         (ppdIsMarked(ppd, "Duplex", "DuplexNoTumble") ||
          ppdIsMarked(ppd, "Duplex", "DuplexTumble") ||
          ppdIsMarked(ppd, "JCLDuplex", "DuplexNoTumble") ||
          ppdIsMarked(ppd, "JCLDuplex", "DuplexTumble") ||
          ppdIsMarked(ppd, "EFDuplex", "DuplexNoTumble") ||
          ppdIsMarked(ppd, "EFDuplex", "DuplexTumble") ||
          ppdIsMarked(ppd, "KD03Duplex", "DuplexNoTumble") ||
          ppdIsMarked(ppd, "KD03Duplex", "DuplexTumble"))) ||
        ((val = cupsGetOption("Duplex", noptions, options))
         != NULL &&
         (!strcasecmp(val, "DuplexNoTumble") ||
  	!strcasecmp(val, "DuplexTumble"))) ||
        ((val = cupsGetOption("sides", noptions, options))
         != NULL &&
         (!strcasecmp(val, "two-sided-long-edge") ||
  	!strcasecmp(val, "two-sided-short-edge")));
  }
  
  
  static void info_linef(FILE *s,
                         const char *key,
                         const char *valuefmt, ...)
  {
      va_list ap;
  
      va_start(ap, valuefmt);
      fprintf(s, "(%s: ", key);
      vfprintf(s, valuefmt, ap);
      fprintf(s, ") Tj T*\n");
      va_end(ap);
  }
  
  
  static void info_line(FILE *s,
                        const char *key,
                        const char *value)
  {
      info_linef(s, key, "%s", value);
  }
  
  
  static void info_line_time(FILE *s,
                        const char *key,
                        const char *timestamp)
  {
      char buf[40];
      time_t time;
  
      if (timestamp) {
          time = (time_t)atoll(timestamp);
          strftime(buf, sizeof buf, "%c", localtime(&time));
          info_line(s, key, buf);
      }
      else
          info_line(s, key, "unknown");
  }
  
  static const char *human_time(const char *timestamp)
  {
      time_t time;
      int size = sizeof(char) * 40;
      char *buf = malloc(size);
      strcpy(buf, "unknown");
  
      if (timestamp) {
          time = (time_t)atoll(timestamp);
          strftime(buf, size, "%c", localtime(&time));
      }
  
      return buf;
  }
  
  /*
   * Add new key & value.
   */
  static opt_t* add_opt(opt_t *in_opt, const char *key, const char *val) {
      if ( ! key || ! val ) {
          return in_opt;
      }
  
      if ( !strlen(key) || !strlen(val) ) {
          return in_opt;
      }
  
      opt_t *entry = malloc(sizeof(opt_t));
      if ( ! entry ) {
          return in_opt;
      }
  
      entry->key = key;
      entry->val = val;
      entry->next = in_opt;
  
      return entry;
  }
  
  /*
   * Collect all known info about current task.
   * Bond PDF form field name with collected info.
   *
   * Create PDF form's field names according above.
   */
  opt_t *get_known_opts(
          ppd_file_t *ppd,
          const char *jobid,
          const char *user,
          const char *jobtitle,
          int noptions,
          cups_option_t *options) {
  
      ppd_attr_t *attr;
      opt_t *opt = NULL;
  
      /* Job ID */
      opt = add_opt(opt, "job-id", jobid);
  
      /* Job title */
      opt = add_opt(opt, "job-title", jobtitle);
  
      /* Printer by */
      opt = add_opt(opt, "user", user);
  
      /* Printer name */
      opt = add_opt(opt, "printer-name", getenv("PRINTER"));
  
      /* Printer info */
      opt = add_opt(opt, "printer-info", getenv("PRINTER_INFO"));
  
      /* Time at creation */
      opt = add_opt(opt, "time-at-creation",
              human_time(cupsGetOption("time-at-creation", noptions, options)));
  
      /* Processing time */
      opt = add_opt(opt, "time-at-processing",
              human_time(cupsGetOption("time-at-processing", noptions, options)));
  
      /* Billing information */
      opt = add_opt(opt, "job-billing",
              cupsGetOption("job-billing", noptions, options));
  
      /* Source hostname */
      opt = add_opt(opt, "job-originating-host-name",
              cupsGetOption("job-originating-host-name", noptions, options));
  
      /* Banner font */
      opt = add_opt(opt, "banner-font",
              cupsGetOption("banner-font", noptions, options));
  
      /* Banner font size */
      opt = add_opt(opt, "banner-font-size",
              cupsGetOption("banner-font-size", noptions, options));
  
      /* Job UUID */
      opt = add_opt(opt, "job-uuid",
              cupsGetOption("job-uuid", noptions, options));
  
      /* Security context */
      opt = add_opt(opt, "security-context",
              cupsGetOption("security-context", noptions, options));
  
      /* Security context range part */
      opt = add_opt(opt, "security-context-range",
              cupsGetOption("security-context-range", noptions, options));
  
      /* Security context current range part */
      const char * full_range = cupsGetOption("security-context-range", noptions, options);
      if ( full_range ) {
          size_t cur_size = strcspn(full_range, "-");
          char * cur_range = strndup(full_range, cur_size);
          opt = add_opt(opt, "security-context-range-cur", cur_range);
      }
  
      /* Security context type part */
      opt = add_opt(opt, "security-context-type",
              cupsGetOption("security-context-type", noptions, options));
  
      /* Security context role part */
      opt = add_opt(opt, "security-context-role",
              cupsGetOption("security-context-role", noptions, options));
  
      /* Security context user part */
      opt = add_opt(opt, "security-context-user",
              cupsGetOption("security-context-user", noptions, options));
  
      if (ppd) {
        /* Driver */
        opt = add_opt(opt, "driver", ppd->pcfilename);
  
        /* Driver version */
        opt = add_opt(opt, "driver-version", 
  		    (attr = ppdFindAttr(ppd, "FileVersion", NULL)) ? 
  		    attr->value : "");
  
        /* Make and model */
        opt = add_opt(opt, "make-and-model", ppd->nickname);
      }
  
      return opt;
  }
  
  static int generate_banner_pdf(banner_t *banner,
                                 ppd_file_t *ppd,
                                 const char *jobid,
                                 const char *user,
                                 const char *jobtitle,
                                 int noptions,
                                 cups_option_t *options)
  {
      char *buf;
      size_t len;
      FILE *s;
      pdf_t *doc;
      float page_width, page_length;
      float media_limits[4];
      float page_scale;
      ppd_attr_t *attr;
      int copies;
  #ifndef HAVE_OPEN_MEMSTREAM
      struct stat st;
  #endif
  
      if (!(doc = pdf_load_template(banner->template_file)))
          return 1;
  
      get_pagesize(ppd, noptions, options,
                   &page_width, &page_length, media_limits);
  
      pdf_resize_page (doc, 1, page_width, page_length, &page_scale);
  
      pdf_add_type1_font(doc, 1, "Courier");
  
  #ifdef HAVE_OPEN_MEMSTREAM
      s = open_memstream(&buf, &len);
  #else
      if ((s = tmpfile()) == NULL) {
          fprintf (stderr, "ERROR: bannertopdf: cannot create temp file: %s\n",
                   strerror (errno));
          return 1;
      }
  #endif
  
      if (banner->infos & INFO_IMAGEABLE_AREA) {
          fprintf(s, "q\n");
          fprintf(s, "0 0 0 RG\n");
          fprintf(s, "%f %f %f %f re S\n", media_limits[0] + 1.0,
                                           media_limits[1] + 1.0,
                                           media_limits[2] - media_limits[0] - 2.0,
                                           media_limits[3] - media_limits[1] - 2.0);
          fprintf(s, "Q\n");
      }
  
      fprintf(s, "%f 0 0 %f 0 0 cm\n", page_scale, page_scale);
  
      fprintf(s, "0 0 0 rg\n");
      fprintf(s, "BT\n");
      fprintf(s, "/bannertopdf-font 14 Tf\n");
      fprintf(s, "83.662 335.0 Td\n");
      fprintf(s, "17 TL\n");
  
      if (banner->infos & INFO_IMAGEABLE_AREA)
          info_linef(s, "Media Limits", "%.2f x %.2f to %.2f x %.2f inches",
                     media_limits[0] / 72.0,
                     media_limits[1] / 72.0,
                     media_limits[2] / 72.0,
                     media_limits[3] / 72.0);
  
      if (banner->infos & INFO_JOB_BILLING)
          info_line(s, "Billing Information\n",
                    cupsGetOption("job-billing", noptions, options));
  
      if (banner->infos & INFO_JOB_ID)
          info_linef(s, "Job ID", "%s-%s", getenv("PRINTER"), jobid);
  
      if (banner->infos & INFO_JOB_NAME)
          info_line(s, "Job Title", jobtitle);
  
      if (banner->infos & INFO_JOB_ORIGINATING_HOST_NAME)
          info_line(s, "Printed from",
                    cupsGetOption("job-originating-host-name",
                                  noptions, options));
  
      if (banner->infos & INFO_JOB_ORIGINATING_USER_NAME)
          info_line(s, "Printed by", user);
  
      if (banner->infos & INFO_JOB_UUID)
          info_line(s, "Job UUID",
                    cupsGetOption("job-uuid", noptions, options));
  
      if (ppd && banner->infos & INFO_PRINTER_DRIVER_NAME)
          info_line(s, "Driver", ppd->pcfilename);
  
      if (ppd && banner->infos & INFO_PRINTER_DRIVER_VERSION)
          info_line(s, "Driver Version",
                    (attr = ppdFindAttr(ppd, "FileVersion", NULL)) ? attr->value : "");
  
      if (banner->infos & INFO_PRINTER_INFO)
          info_line(s, "Description", getenv("PRINTER_INFO"));
  
      if (banner->infos & INFO_PRINTER_LOCATION)
          info_line(s, "Printer Location", getenv("PRINTER_LOCATION"));
  
      if (ppd && banner->infos & INFO_PRINTER_MAKE_AND_MODEL)
          info_line(s, "Make and Model", ppd->nickname);
  
      if (banner->infos & INFO_PRINTER_NAME)
          info_line(s, "Printer", getenv("PRINTER"));
  
      if (banner->infos & INFO_TIME_AT_CREATION)
          info_line_time(s, "Created at",
                         cupsGetOption("time-at-creation", noptions, options));
  
      if (banner->infos & INFO_TIME_AT_PROCESSING)
          info_line_time(s, "Printed at",
                         cupsGetOption("time-at-processing", noptions, options));
  
      fprintf(s, "ET\n");
  #ifndef HAVE_OPEN_MEMSTREAM
      fflush (s);
      if (fstat (fileno (s), &st) < 0) {
          fprintf (stderr, "ERROR: bannertopdf: cannot fstat(): %s\n", strerror(errno));
          return 1 ;
      }
      fseek (s, 0L, SEEK_SET);
      if ((buf = malloc(st.st_size + 1)) == NULL) {
          fprintf (stderr, "ERROR: bannertopdf: cannot malloc(): %s\n", strerror(errno));
          return 1 ;
      }
      size_t nbytes = fread (buf, 1, st.st_size, s);
      buf[st.st_size] = '\0';
      len = strlen (buf);
  #endif /* !HAVE_OPEN_MEMSTREAM */
      fclose(s);
  
      opt_t * known_opts = get_known_opts(ppd,
              jobid,
              user,
              jobtitle,
              noptions,
              options);
  
      /*
       * Try to find a PDF form in PDF template and fill it.
       */
      int ret = pdf_fill_form(doc, known_opts);
  
      /*
       * Could we fill a PDF form? If no, just add PDF stream.
       */
      if ( ! ret ) {
          pdf_prepend_stream(doc, 1, buf, len);
      }
  
      copies = get_int_option("number-up", noptions, options, 1);
      if (duplex_marked(ppd, noptions, options))
          copies *= 2;
  
      if (copies > 1)
          pdf_duplicate_page(doc, 1, copies);
  
      pdf_write(doc, stdout);
      free(buf);
      pdf_free(doc);
      return 0;
  }
  
  
  int main(int argc, char *argv[])
  {
      banner_t *banner;
      int noptions;
      cups_option_t *options;
      ppd_file_t *ppd;
      int ret;
  
      if (argc < 6) {
          fprintf(stderr,
                  "Usage: %s job-id user job-title nr-copies options [file]\n",
                  argv[0]);
          return 1;
      }
  
      ppd = ppdOpenFile(getenv("PPD"));
      if (!ppd)
        fprintf(stderr, "DEBUG: Could not open PPD file '%s'\n", getenv("PPD"));
  
      noptions = cupsParseOptions(argv[5], 0, &options);
      if (ppd) {
        ppdMarkDefaults(ppd);
        cupsMarkOptions(ppd, noptions, options);
      }
  
      banner = banner_new_from_file(argc == 7 ? argv[6] : "-", &noptions, &options);
      if (!banner) {
          fprintf(stderr, "Error: could not read banner file\n");
          return 1;
      }
  
      ret = generate_banner_pdf(banner,
                                ppd,
                                argv[1],
                                argv[2],
                                argv[3],
                                noptions,
                                options);
  
      banner_free(banner);
      cupsFreeOptions(noptions, options);
      return ret;
  }
  