[Printing-architecture] Use of filter functions in CUPS 2.x and 3.x

Till Kamppeter till.kamppeter at gmail.com
Mon Dec 13 21:06:52 UTC 2021


Hi,

with the background of CUPS 3.x, planned to get released 2 years from 
now, not supporting PPD files any more and only considering driverless 
IPP printers I have changed the architecture of cups-filters, converting 
filter executables (CUPS filters) into filter functions (library 
functions doing a filter's task, with standardized call 
scheme/interface) so that the code of the filters is preserved but they 
get more universally usable.

https://openprinting.github.io/OpenPrinting-News-October-2021/#cups

In addition I have created some auxiliary functions (partially filter 
functions by themselves) to call filter functions in a chain, feed data 
into filter through a pipe, call classic CUPS external filter/backend 
executable wrapped into a filter function, save data stream between 
chained filter function calls into a file for debugging, ...

https://openprinting.github.io/news/
https://github.com/OpenPrinting/cups-filters

With this I was especially able to create Printer Applications 
retro-fitting classic CUPS printer drivers, an important step to be able 
to switch to a PPD-less and driverless CUPS without dropping support for 
legacy printers.

https://snapcraft.io/search?q=OpenPrinting
https://github.com/OpenPrinting/pappl-retrofit

Now I am also thinking about making use of the filter functions somehow 
in CUPS, both to get improvement in the upcoming 2 years where CUPS 
still supports PPD files and classic drivers and also in PPD-less CUPS 
3.x, where data-format conversions are still needed, as for example 
print jobs usually come in PDF but it is not required for a driverless 
IPP printer to support PDF. Also implementation of functionality like 
N-up or flattening filled PDF forms (both tasks currently done by 
pdftopdf) is needed in CUPS 3.x.

We should do our best to avoid duplicate code in OpenPrinting and not 
re-invent the wheel.


"universal" filter for CUPS
---------------------------

https://gist.github.com/pranshukharkwal/9413499a6744049ef549159948392023

As a first approach to improve PPDish CUPS 2.4.x and 2.5.x I have run 
the GSoC project of a universal CUPS filter, where one single CUPS 
filter executable does all the filtering for a job by calling filter 
functions, in a chain if needed. This reduces the number of external 
executable calls by CUPS vastly, as normally CUPS calls for each filter 
to run first the cups-exec helper program and cups-exec then calls the 
filter executable, making up 2 external executable calls per filter.

The filter function chaining in the universal CUPS filter still does a 
fork for each filter function though and pipes the print data from 
filter function to filter function. Also filters of printer drivers and 
CUPS backends are not part of the universal filter and need to get 
called separately by CUPS.

The disk space saved is low, as with cups-filters 2.x each filter 
executable in /usr/lib/cups/filters/ is only a little code stub calling 
the actual filter function in libcupsfilters,

Also the universal filter still needs a fix to work correctly with PPD 
files which use "cupsFilter2" instead of "cupsFilter" lines.

So it is a little bit of a question whether it is really worth the 
effort to replace the individual filters called by CUPS 2.4.x and 2.5.x 
by this universal filter, especially also that we have only more 2 years 
where CUPS uses PPD files.

So before I complete this (if not, the universal filter will at least be 
used to make cups-browsed's "implicitclass" backend use filter functions 
instead of external filters) I want to hear some opinions about this, 
especially also whether actually saving external executable calls is 
more resource-saving than for example saving forked parallel tasks and 
pipes between them.


Forking and piping vs. one filter after the other
-------------------------------------------------

In general if forking and piping for a filter chain consumes much more 
than calling each filter function one after the other directly and let 
them write their output into temporary files for the next filter 
functions in the chain reading from the previous filter function's temp 
file I am also thinking about adding a second mode to filterChain() to 
let it operate this way. WDYT?


Using filter functions directly in CUPS
---------------------------------------

And, finally, should we make the filter functions be directly used by 
CUPS, either that in CUPS 2.5.x in scheduler/job.c we call filter 
functions instead of external filter executables (at least for standard, 
non-driver filters), or that for CUPS 3.x we do the needed file format 
conversions by filter function calls (there are no driver filters)? 
Backends could also be converted to filter functions and be directly 
called. WDYT?

Also, do we need extra functionality in the filter function concept for 
using it directly in CUPS? For example add a field to the records of the 
filter functions called by filterChain() to tell as which user each 
individual filter function should get run?

Also, if we want CUPS to use filter functions, we need to do 
re-structuring to avoid circular dependencies, as libcupsfilters uses 
libcups and if a function in libcups would call a filter function of 
libcupsfilters, libcups is using libcupsfilters, ... The planned 
splitting of libcups, local CUPS daemon, sharing CUPS daemon of CUPS 3.x 
could help here as if the daemons call filter functions but not libcups, 
we will not get a circular dependency. WDYT?


It would be great if we could discuss these topics before finalizing 
cups-filters 2.x

    Till


More information about the Printing-architecture mailing list