]> git.ipfire.org Git - thirdparty/cups-filters.git/commitdiff
libcupsfilters: Let universal() correctly work with "*cupsFilter2:" in PPD
authorTill Kamppeter <till.kamppeter@gmail.com>
Thu, 17 Mar 2022 12:55:26 +0000 (13:55 +0100)
committerTill Kamppeter <till.kamppeter@gmail.com>
Thu, 17 Mar 2022 12:55:26 +0000 (13:55 +0100)
If we use the universal() filter function as CUPS filter (to replace
the chain of individual filters) we must tell from which format to
which other format universal() has to convert. For this universal()
grabs the CONTENT_TYPE and FINAL_CONTENT_TYPE environment variables to
get input format and output format resp.

But this does not work if the PPD file uses "*cupsFilter2: ..." lines
to tell which format is needed and what is the last filter to get the
printer's native data format. Problem is that the line specifies the
output format of the last filter in addition to its input format and
CUPS then sets FINAL_CONTENT_TYPE to the output format of the last
filter. This means that universal() has to find out what the input
format of the last filter is and generate this format.

So we do the following:

We check whether our output format (under CUPS it is taken from the
FINAL_CONTENT_TYPE env variable) is the destination format (2nd word)
of a "*cupsFilter2: ..." line (string has 4 words). In this case the
specified filter (4th word) does the last step, converting from the
input format (1st word) of the line to the destination format and so
we only need to convert to the input format. In this case we need to
correct our output format.

If there is more than one line with the given output format and an
input format we can produce, we select the one with the lowest cost
value (3rd word) as this is the one which CUPS should have chosen for
this job.

If we have "*cupsFilter: ..." lines (without "2", string with 3 words)
we do not need to do anything special, as the input format specified
is the FIMAL_CONTENT_TYPE which CUPS supplies to us and into which we
have to convert. So we quit parsing if the first line has only 3
words, as if CUPS uses the "*cupsFilter: ..."  lines only if there is
no "*cupsFilter2: ..." line in the PPD, so if we encounter a line with
only 3 words the other lines will have only 3 words, too and nothing
has to be done.

We also take care that if our output format has to be CUPS Raster but
the final output format (of the last filter) is Apple Raster or PWG
Raster that we generate CUPS Raster which smoothly converts to
Apple/PWG Raster, by telling filters like ghostscript() and
imagetoraster() that the final format is Apple/PWG Raster.

In addition, some small string variables are now static and not
dynamic variables any more.

cupsfilters/universal.c

index ca0b62263aa85017bcdb28c1d4da30e928a14010..90061179329d10df19b40a4652a3a2d344feda59 100644 (file)
@@ -9,7 +9,10 @@
 #include <stdlib.h>
 #include <string.h>
 #include <signal.h>
+#include <ctype.h>
+#include <limits.h>
 #include <cups/cups.h>
+#include <ppd/ppd.h>
 
 int                            /* O - Error status */
 universal(int inputfd,         /* I - File descriptor input stream */
@@ -20,21 +23,165 @@ universal(int inputfd,         /* I - File descriptor input stream */
                                      (input/output format) */
 {
   char *input;
-  char *output;
-  char *input_super = malloc(16);
-  char *input_type = malloc(256);
-  char *output_super = malloc(16);
-  char *output_type = malloc(256);
+  char *final_output;
+  char output[256];
+  char input_super[16];
+  char input_type[256];
+  char output_super[16];
+  char output_type[256];
   filter_out_format_t *outformat;
   filter_filter_in_chain_t *filter, *next;
   filter_input_output_format_t input_output_format;
+  ppd_file_t *ppd;
+  ppd_cache_t *cache;
   filter_logfunc_t log = data->logfunc;
   void *ld = data->logdata;
   int ret = 0;
 
   input_output_format = *(filter_input_output_format_t *)parameters;
   input = input_output_format.input_format;
-  output = input_output_format.output_format;
+  final_output = input_output_format.output_format;
+  strncpy(output, final_output, sizeof(output) - 1);
+
+  /* If we have a PPD, load it and its cache, so that we can access the
+     "*cupsFilter(2): ..." lines */
+  if (data->ppd == NULL && data->ppdfile)
+    data->ppd = ppdOpenFile(data->ppdfile);
+  ppd = data->ppd;
+  if (ppd && !ppd->cache)
+    ppd->cache = ppdCacheCreateWithPPD(ppd);
+  cache = ppd ? ppd->cache : NULL;
+
+  /* Check whether our output format (under CUPS it is taken from the
+     FINAL_CONTENT_TYPE env variable) is the destination format (2nd
+     word) of a "*cupsFilter2: ..." line (string has 4 words), in this
+     case the specified filter (4th word) does the last step,
+     converting from the input format (1st word) of the line to the
+     destination format and so we only need to convert to the input
+     format. In this case we need to correct our output format.
+
+     If there is more than one line with the given output format and
+     an inpout format we can produce, we select the one with the
+     lowest cost value (3rd word) as this is the one which CUPS should have
+     chosen for this job.
+
+     If we have "*cupsFilter: ..." lines (without "2", string with 3
+     words) we do not need to do anything special, as the input format
+     specified is the FIMAL_CONTENT_TYPE which CUPS supplies to us and
+     into which we have to convert. So we quit parsing if the first
+     line has only 3 words, as if CUPS uses the "*cupsFilter: ..."
+     lines only if there is no "*cupsFilter2: ..." line min the PPD,
+     so if we encounter a line with only 3 words the other lines will
+     have only 3 words, too and nothing has to be done. */
+
+  if (ppd && ppd->num_filters && cache)
+  {
+    int lowest_cost = INT_MAX;
+    char *filter;
+
+    if (log) log(ld, FILTER_LOGLEVEL_DEBUG,
+                "universal: \"*cupsFilter(2): ...\" lines in the PPD file:");
+
+    for (filter = (char *)cupsArrayFirst(cache->filters);
+        filter;
+        filter = (char *)cupsArrayNext(cache->filters))
+    {
+      char buf[256];
+      char *ptr,
+          *in = NULL,
+          *out = NULL,
+          *coststr = NULL;
+      int cost;
+
+      if (log) log(ld, FILTER_LOGLEVEL_DEBUG,
+                  "universal:    %s", filter);
+
+      /* String of the "*cupsfilter:" or "*cupsfilter2:" line */
+      strncpy(buf, filter, sizeof(buf) - 1);
+
+      /* Separate the words */
+      in = ptr = buf;
+      while (*ptr && !isspace(*ptr)) ptr ++;
+      if (!*ptr) goto error;
+      *ptr = '\0';
+      ptr ++;
+      while (*ptr && isspace(*ptr)) ptr ++;
+      if (!*ptr) goto error;
+      out = ptr;
+      while (*ptr && !isspace(*ptr)) ptr ++;
+      if (!*ptr) goto error;
+      *ptr = '\0';
+      ptr ++;
+      while (*ptr && isspace(*ptr)) ptr ++;
+      if (!*ptr) goto error;
+      coststr = ptr;
+      if (!isdigit(*ptr)) goto error;
+      while (*ptr && !isspace(*ptr)) ptr ++;
+      if (!*ptr) goto error;
+      *ptr = '\0';
+      ptr ++;
+      while (*ptr && isspace(*ptr)) ptr ++;
+      if (!*ptr) goto error;
+      cost = atoi(coststr);
+
+      /* Valid "*cupsFilter2: ..." line ... */
+      if (/* Must be of lower cost than what we selected before */
+         cost < lowest_cost &&
+         /* Must have our FINAL_CONTENT_TYPE as output */
+         strcasecmp(out, final_output) == 0 &&
+         /* Must have as input a format we are able to produce */
+         (strcasecmp(in, "application/vnd.cups-raster") == 0 ||
+          strcasecmp(in, "application/vnd.cups-pdf") == 0 ||
+          strcasecmp(in, "application/vnd.cups-postscript") == 0 ||
+          strcasecmp(in, "application/pdf") == 0 ||
+          strcasecmp(in, "image/pwg-raster") == 0 ||
+          strcasecmp(in, "image/urf") == 0 ||
+          strcasecmp(in, "application/PCLm") == 0 ||
+          strcasecmp(in, "application/postscript") == 0))
+      {
+       if (log) log(ld, FILTER_LOGLEVEL_DEBUG,
+                    "universal:       --> Selecting this line");
+       /* Take the input format of the line as output format for us */
+       strncpy(output, in, sizeof(output));
+       /* Update the minimum cost found */
+       lowest_cost = cost;
+       /* We cannot find a "better" solution ... */
+       if (lowest_cost == 0)
+       {
+         if (log) log(ld, FILTER_LOGLEVEL_DEBUG,
+                      "universal:    Cost value is down to zero, stopping reading further lines");
+         break;
+       }
+      }
+
+      continue;
+
+    error:
+      if (lowest_cost == INT_MAX && coststr)
+      {
+       if (log) log(ld, FILTER_LOGLEVEL_DEBUG,
+                    "universal: PPD uses \"*cupsFilter: ...\" lines, so we always convert to format given by FINAL_CONTENT_TYPE");
+       break;
+      }
+      if (log) log(ld, FILTER_LOGLEVEL_ERROR,
+                  "universal: Invalid \"*cupsFilter2: ...\" line in PPD: %s",
+                  filter);
+    }
+
+  }
+
+  if (strcasecmp(output, final_output) != 0)
+  {
+    if (log) log(ld, FILTER_LOGLEVEL_DEBUG,
+                "universal: Converting from %s to %s, final output will be %s",
+                input, output, final_output);
+  }
+  else
+  {
+    if (log) log(ld, FILTER_LOGLEVEL_DEBUG,
+                "universal: Converting from %s to %s", input, output);
+  }
+
   sscanf(input, "%15[^/]/%255s", input_super, input_type);
   sscanf(output, "%15[^/]/%255s", output_super, output_type);
 
@@ -49,9 +196,23 @@ universal(int inputfd,         /* I - File descriptor input stream */
        !strcmp(output_type, "pwg-raster") ||
        !strcmp(output_type, "PCLm"))
     {
+      outformat = malloc(sizeof(filter_out_format_t));
+      *outformat = OUTPUT_FORMAT_CUPS_RASTER;
+      if (!strcmp(output_type, "pwg-raster") ||
+         (!strcmp(output_type, "vnd.cups-raster") &&
+          !strcmp(final_output, "image/pwg-raster")))
+       *outformat = OUTPUT_FORMAT_PWG_RASTER;
+      else if (!strcmp(output_type, "urf") ||
+              (!strcmp(output_type, "vnd.cups-raster") &&
+               !strcmp(final_output, "image/urf")))
+       *outformat = OUTPUT_FORMAT_APPLE_RASTER;
+      else if (!strcmp(output_type, "PCLm") ||
+              (!strcmp(output_type, "vnd.cups-raster") &&
+               !strcmp(final_output, "applicationn/PCLm")))
+       *outformat = OUTPUT_FORMAT_PCLM;
       filter = malloc(sizeof(filter_filter_in_chain_t));
       filter->function = imagetoraster;
-      filter->parameters = NULL;
+      filter->parameters = outformat;
       filter->name = "imagetoraster";
       cupsArrayAdd(filter_chain, filter);
       if (log) log(ld, FILTER_LOGLEVEL_DEBUG,
@@ -174,7 +335,9 @@ universal(int inputfd,         /* I - File descriptor input stream */
       *outformat = OUTPUT_FORMAT_CUPS_RASTER;
       if (!strcmp(output_type, "pwg-raster"))
        *outformat = OUTPUT_FORMAT_PWG_RASTER;
-      else if(!strcmp(output_type, "urf"))
+      else if(!strcmp(output_type, "urf") ||
+             (!strcmp(output_type, "vnd.cups-raster") &&
+              !strcmp(final_output, "image/urf")))
        *outformat = OUTPUT_FORMAT_APPLE_RASTER;
       else if(!strcmp(output_type, "PCLm"))
        *outformat = OUTPUT_FORMAT_PCLM;
@@ -262,7 +425,9 @@ universal(int inputfd,         /* I - File descriptor input stream */
          *outformat = OUTPUT_FORMAT_CUPS_RASTER;
          if (!strcmp(output_type, "pwg-raster"))
            *outformat = OUTPUT_FORMAT_PWG_RASTER;
-         else if(!strcmp(output_type, "urf"))
+         else if (!strcmp(output_type, "urf") ||
+                  (!strcmp(output_type, "vnd.cups-raster") &&
+                   !strcmp(final_output, "image/urf")))
            *outformat = OUTPUT_FORMAT_APPLE_RASTER;
          else if(!strcmp(output_type, "PCLm"))
            *outformat = OUTPUT_FORMAT_PCLM;
@@ -318,12 +483,9 @@ universal(int inputfd,         /* I - File descriptor input stream */
                 input, output);
   }
   else
+    /* Do the dirty work ... */
     ret = filterChain(inputfd, outputfd, inputseekable, data, filter_chain);
 
-  free(input_super);
-  free(input_type);
-  free(output_super);
-  free(output_type);
   for (filter = (filter_filter_in_chain_t *)cupsArrayFirst(filter_chain);
        filter; filter = next)
   {