Designing Printer Drivers

31 minute read

This document contains a complete tutorial as well as information for manufacturers with examples for designing printer drivers. If you are looking for information regarding the use of printer drivers, kindly refer to User Manual

Introduction

A driver is a code or data specific to a certain model or group of hardware devices, needed to make the hardware work with the hardware-model-independent code of the operating system. Printing in Linux has moved towards Driverless Printing, which means there is no need for any hardware-model-specific code or data. However, there are some problems with the current framework. For example, some printers(especially the old ones) that cannot handle IPP requests are devoid of driverless printing capability. Printer Applications help to address these issues. Kindly refer Printer Applications - A new way to print in Linux to learn more about Printer Applications, its working and benefits.

For Designing the Printer Application Driver, it would be a lot of re-inventing the wheel if everyone who wants to create a printer driver has to implement all things from scratch. Therefore Michael Sweet has developed PAPPL, a library that provides all the common functionality which is required in every Printer Application.

The flowchart below mentions the components of the driver that needs to be designed by you (boxes in blue color), along with the PAPPL utilities (boxes in red color) that would be used by your designed components.

Flowchart

The following tutorial helps you to understand how to design each component and integrate the PAPPL Utilities to reduce your work.

Components for PAPPL-based Printer Driver

  • papplMainLoop

    papplMainLoop is the main entry point for the application. It runs a standard main loop with a system callback. Also allows the printer application to define its own usage callback and have an application specific subcommand.

    The papplMainloop function runs until all processing for the current sub-command is complete, returning the exit status for the program.

    int					        // O - Exit status
    papplMainloop(
        int                   argc,		// I - Number of command line arguments
        char                  *argv[],	        // I - Command line arguments
        const char            *version,	        // I - Version number
        const char            *footer_html,	// I - Footer HTML or `NULL` for none
        int                   num_drivers,	// I - Number of drivers
        pappl_pr_driver_t     *drivers,	        // I - Drivers
        pappl_pr_driver_cb_t  driver_cb,	        // I - Driver callback
        pappl_ml_autoadd_cb_t autoadd_cb,	        // I - Auto-add callback or `NULL` for none
        const char            *subcmd_name,	// I - Sub-command name or `NULL` for none
        pappl_ml_subcmd_cb_t  subcmd_cb,	        // I - Sub-command callback or `NULL` for none
        pappl_ml_system_cb_t  system_cb,	        // I - System callback or `NULL` for default
        pappl_ml_usage_cb_t   usage_cb,	        // I - Usage callback or `NULL` for default
        void                  *data               // I - Context pointer
    )
    

    Before looking at the arguments, you must be aware that there are two ways to configure the papplMainloop:

    1. Use system callback argument

      The system is an object of type pappl_system_t that manages client and device connections, listeners, the log, printers, and resources. In addition, it provides an optional embedded web interface, raw socket printing, and USB printer gadget (Linux only).

      The system callback argument specifies a function that will create a new system, i.e. a pappl_system_t object. You can use the callback function to customisably configure system properties using the PAPPL System Utilities. This includes configuring Footer HTML on your web interface and setting up drivers.

      You can refer designing system callback guidelines for retrieving more information about the System Callback function.

    2. Without system callback function

      PAPPL allows the manufacture to refrain from the hardships of designing a system callback to create a system. You can specify the system callback as NULL and pass on only a few arguments:

      If the "system_cb" argument is passed as NULL, PAPPL automatically sets up the list of drivers passed as "drivers" arguments and add the Footer HTML for the web interface (in "footer" argument is not NULL). A default system object is created for the printer application, which supports multiple printers and a web interface.


    The argc and argv arguments for papplMainLoop are those provided to the main function. Other arguments are given below.

    1. Version number

      This argument is simply a string literal, denoting the numeric version of the printer driver that conforms to semantic versioning guidelines with up to four numbers, for example "1.2.3.4"

      Examples of valid version number are "1.0", "2.1", etc.

    2. The "footer_html" argument can be provided to override the default footer text that appears at the bottom of the web interface.

      As mentioned above, you can pass this argument as NULL and add the footer in system callback function. If system callback and "footer_html" argument both are NULL the default is used.

    3. Number of drivers

      The "num_drivers" argument specifies the number of drivers for printers. Specify 0 if the drivers are configured in the system callback.

    4. Drivers

      The drivers list is a collection of names, descriptions, IEEE-1284 device IDs, and extension pointers. You can pass this argument as NULL if the drivers are configured in the system callback.

      A valid example for HP DeskJet, HP LaserJet, and a generic PCL driver, looks like this:

      static pappl_pr_driver_t pcl_drivers[] =// Driver information
      {   /* name */          /* description */       /* device ID */ /* extension */
      { "hp_deskjet",       "HP Deskjet",           NULL,           NULL },
      { "hp_generic",       "Generic PCL",          "CMD:PCL;",     NULL },
      { "hp_laserjet",      "HP LaserJet",          NULL,           NULL }
      };
      
    5. Driver Callback

      The driver callback is responsible for providing the data associated with each driver and is called when the system is creating a new printer. It also responsible for letting the application know what functions to call when performing job functions like starting a job, starting a page, writing a line, ending a page, ending a job, printing a raw job, etc.

      You can pass this argument as NULL if the drivers are configured in the system callback. Else, for designing the driver callback function, refer to the design driver callback guidelines.

    6. Autoadd Callback

      The "autoadd_cb" argument specifies a callback for automatically adding new printers with the "autoadd" sub-command. It tells PAPPL which driver to use for the printers it finds.

    7. Sub-command name

      This argument is a string literal that denotes the additional custom sub-command supported by your printer driver. By default, PAPPL helps each driver to support the following sub-commands:

      • add
      • cancel
      • default
      • delete
      • devices
      • drivers
      • jobs
      • modify
      • options
      • printers
      • server
      • shutdown
      • status
      • submit

      If your printer doesn't support any additional custom sub-command, you may specify the argument as NULL.

    8. Sub-command Callback

      This callback argument is the function that is to be executed when the sub-command(other than the default sub-commands) is specified.

      Since each driver is not bound to support additional sub-command, one may specify this argument as NULL in case the printer driver does not support any additional sub-command.

      If you wish to support additional sub-command, then specify the name of the sub-command in the sub-command name argument and design a function using the design sub-command callback guidelines.

    9. System Callback

      The System Callback function creates a system object and allows restoring the previous system configuration(if any). Kindly refer to the Design system callback guidelines for retrieving more information about the System Callback function.

    10. Usage Callback

      This function helps the user to know about the capabilities of your printer by showing the set of sub-commands and options supported by your printer. For retrieving the output of this function, the user needs to pass the --help argument.

      One may specify the usage callback argument as NULL in the papplMainloop function. The default function is utilized in this situation. The default function shows the default list of sub-commands and options. The output of the default usage callback function is shown below.

      Usage: <basename> SUB-COMMAND [OPTIONS] [FILENAME]
              <basename> [OPTIONS] [FILENAME]
              <basename> [OPTIONS] -
      
      Sub-commands:
          add PRINTER      Add a printer.
          [autoadd          Automatically add supported printers.]
          cancel           Cancel one or more jobs.
          default          Set the default printer.
          delete           Delete a printer.
          devices          List devices.
          drivers          List drivers.
          jobs             List jobs.
          modify           Modify a printer.
          options          List printer options.
          printers         List printers.
          server           Run a server.
          shutdown         Shutdown a running server.
          status           Show server/printer/job status.
          submit           Submit a file for printing.
      
      Options:
          -a               Cancel all jobs (cancel).
          -d PRINTER       Specify printer.
          -j JOB-ID        Specify job ID (cancel).
          -m DRIVER-NAME   Specify driver (add/modify).
          -n COPIES        Specify number of copies (submit).
          -o NAME=VALUE    Specify option (add,modify,server,submit).
          -u URI           Specify ipp: or ipps: printer/server.
          -v DEVICE-URI    Specify socket: or usb: device (add/modify).
      

      If your printer supports only the default set of sub-commands, you can use the default usage callback function. If you wish to output different information when the user uses --help command-line argument, follow the designing usage callback function guidelines.

    11. Data

      This argument is a string literal and provides the application-specific data for any of the callback functions.

Designing Components

  • Usage Callback

    pappl_ml_usage_cb_t  usage_cb (
        void *data
    );
    

    The usage callback function receives only one argument, i.e. the callback data.

    The function objective is to show the usage details of the printer. Hence, the design guidelines for this function is quite trivial. It consists of a series of printf statements describing each supported sub-command and option. It can then return.


  • Sub-Command Callback

    This function allows the printer application to have an application-specific subcommand.

    pappl_ml_subcmd_cb_t subcmd_cb (
        const char      *base_name,
        int             options,
        cups_option_t   *options,
        int             num_files,
        char            **files,
        void            *data
    );
    

    The Sub-Command callback function receives six arguments Basename, Number of options, Options, Number of files, Name of files, and Callback data. It then returns a new sub-command object.


  • System Callback

    pappl_ml_system_cb_t *system_cb (
        int             num_options,
        cups_option_t   *options,
        void            *data
    );
    

    The system callback function receives three arguments Number of options, Options, and Callback data. It then returns a new system object.

    Design Guidelines:

    1. Declare Objects and Variables

      The following objects and variables have to be declared:

      DataTypeVariable NameSignificance
      pappl_system_t*systemSystem object
      char*valCurrent option value
      char*hostnameHostname
      char*logfileLog file
      char*system_nameSystem name
      char*spooldir_namespool-directory
      char*authserviceauth-service
      pappl_loglevel_tloglevelLog level
      intportPort number
      pappl_soptions_tsoptionsSystem options
      pappl_version_tversionsSoftware versions
      Note
      Naturally, you can define other names for variables. We have assumed them as given in the table to reduce ambiguity in the subsequent steps.
    2. Fetch values using cupsGetOption

      From the above listed objects/variables, the following variables can get their values from corressponding option name, using cupsGetOption PAPPL utility:

      Variable NameOption name
      loglevellog-level
      logfilelog-file
      hostnameserver-hostname
      system_namesystem-name
      portserver-port

      The syntax for using cupsGetOption is:

      <variable_name> = cupsGetOption(<option_name>, num_options, options)
      

      Notes:

      • The return type for cupsGetOption utility is char*. Hence for "log-level" and "server-port" options, first fetch them into the val variable.

      • The values taken by loglevel variable based on value returned by "log-level" cupsGetOption can be summarised using the following table:

        log-level OptionValue for loglevel variable
        fatalPAPPL_LOGLEVEL_FATAL
        errorPAPPL_LOGLEVEL_ERROR
        warnPAPPL_LOGLEVEL_WARN
        infoPAPPL_LOGLEVEL_INFO
        debugPAPPL_LOGLEVEL_DEBUG
      • The port variable can be retrived from "server-port" option using atoi function.

      • Don't forget to add a check for the port number. It must be between 0 to 65535(both inclusive).

    3. Create a system using papplSystemCreate

      The variables defined and fetched above are passed to papplSystemCreate utility to create a system.

      The system is an object of type pappl_system_t that manages client and device connections, listeners, the log, printers, and resources. It implements a subset of the IPP System Service (PWG 5100.22) with each printer implementing IPP Everywhere™ (PWG 5100.14) and some extensions to provide compatibility with the full range of mobile and desktop client devices. In addition, it provides an optional embedded web interface, raw socket printing, and USB printer gadget (Linux only).

      pappl_system_t* papplSystemCreate(
          pappl_soptions_t    options,
          const char          *name,
          int                 port,
          const char          *subtypes,
          const char          *spooldir,
          const char          *logfile,
          pappl_loglevel_t    loglevel,
          const char          *auth_service,
          bool                tls_only
      );
      
      • The soptions variable is passed as the first argument, which is an bitwise ORed of the following options:

        System OptionSignificance
        PAPPL_SOPTIONS_DNSSD_HOSTUse hostname in DNS-SD service names instead of serial number/UUID
        PAPPL_SOPTIONS_LOGInclude link to log file
        PAPPL_SOPTIONS_MULTI_QUEUESupport multiple printers
        PAPPL_SOPTIONS_NETWORKInclude network settings page
        PAPPL_SOPTIONS_NONENo options
        PAPPL_SOPTIONS_RAW_SOCKETAccept jobs via raw sockets
        PAPPL_SOPTIONS_REMOTE_ADMINAllow remote queue management (vs. localhost only)
        PAPPL_SOPTIONS_SECURITYInclude user/password settings page
        PAPPL_SOPTIONS_STANDARDInclude the standard web pages
        PAPPL_SOPTIONS_TLSInclude TLS settings page
      • The system_name variable fetched in 2nd Step is passed as second argument. Note that the system-name might be NULL. So, don't forget to add a check and pass a default value, in case the fetched system-name is NULL.

      • The port number is passed as the third argument. You may pass it as 0 for auto.

      • The 4th argument is a string literal that signifies DNS-SD sub-types. One may pass NULL for none.

      • Pass the spooldir, logfile and loglevel variable fetched in 2nd Step as fifth, sixth and seventh argument respectively. You can pass NULL for the fifth and sixth arguments for default values.

      • The 8th argument signifies the PAM authentication service. You may pass auth_service variable fetched in 2nd Step or NULL for none.

      • If the system only supports TLS connection, pass true in the 9th argument, else false.

    4. Add system configurations

      The system object has tons of configurable attributes and correspondingly a huge number of PAPPL utilities to configure them. These include utilities like Setting Hostname, Setting the footer HTML for the web interface, etc. A detailed list of these function can be found at PAPPL System Utilities.

    5. Call the Driver setup function

      Pass the system object created in previous steps as an argument to the Driver setup function. See the design guidlines for knowing about the Driver setup function.

    6. Return System Object

      The system object created and set up in the previous steps is returned in this step.


  • Driver setup Function

    This function defines the list of printer drivers and driver callback function.

    void pcl_setup (
        pappl_system_t *system
    );
    

    The Driver setup function receives only one argument, i.e. system object.

    Design Guidelines:

    1. Define Drivers name and description

      Create two arrays of string literals, one for the names of the drivers and the other for their corresponding descriptions. Initialise them suitably and use as directed in 2nd Step.

    2. Call papplSystemSetPrintDrivers

      void papplSystemSetPrintDrivers(
          pappl_system_t      *system,
          int                 num_names,
          const char * const  *names,
          const char * const  *desc,
          pappl_pdriver_cb_t  cb,
          void                *data
      );
      
      • The received pappl_system_t object is passed as the first argument.
      • Pass the number of drivers as the second argument
      • The defined driver name and description array are passed as the third and fourth arguments respectively.
      • Pass the Driver Callback Function as fifth argument.
      • Pass the callback data as the 6th argument.

  • Driver Callback Function

    This function tells the printer application what functions to call when performing job functions like starting a job, starting a page, writing a line, ending a page, ending a job, printing a raw job. Driver capability information and defaults(such as resolution, color, etc.) are also provided here.

    bool pcl_callback (
        pappl_system_t          *system,
        const char              *driver_name,
        const char              *device_uri,
        const char              *device_id,
        pappl_pdriver_data_t    *driver_data,
        ipp_t                   **driver_attrs,
        void                    *data
    );
    

    The callback function receives six arguments System object, Driver name, Device URI, Driver data, Driver Attributes, and Callback data. It then returns either true on success or false on failure.

    Design Guidelines:

    1. Add suitable checks

      You may check that the passed values of driver_name, device_uri, driver_data, and data variable is non-NULL. Further, you must verify that the callback data is the same as the data variable.

    2. Assign values to common driver_data members

      All the required information is stored in the pappl_pdriver_data_t [1] structure. These are a few examples of common driver attributes. For the entire list of attributes that can be provided, please look at the pappl_pdriver_data_t [1] structure.

      MemberSignificance
      identifyIdentify-Printer function
      identify_default"identify-actions-default" values
      identify_supported"identify-actions-supported" values
      printPrint (file) function
      rendjobEnd raster job function
      rendpageEnd raster page function
      rstartjobStart raster job function
      rstartpageStart raster page function
      rwriteWrite raster line function
      statusStatus function
      formatPrinter-specific format
      orient_default"orientation-requested-default" value
      quality_default"print-quality-default" value
    3. Assign rest of the values driver_data members based on driver_name

      Here is the list of all the attributes of pappl_pdriver_data_t structure, used to describe driver capability information and defaults. The ones that were not assigned in the previous step may be assigned depending on the name of the driver.

      MemberSignificance
      bin[PAPPL_MAX_BIN]Output bins
      bin_defaultDefault output bin
      borderlessBorderless margins supported?
      bottom_topBottom and top margins in hundredths of millimeters
      color_default"print-color-mode-default" value
      content_default"print-content-default" value
      darkness_supportedprinter/print-darkness-supported (0 for none)
      duplexDuplex printing modes supported
      features[PAPPL_MAX_VENDOR]"ipp-features-supported" values
      finishings"finishings-supported" values
      force_raster_typeForce a particular raster type?
      formatPrinter-specific format
      gdither, 'text', and 'graphic' dither array
      icons[3]"printer-icons" values
      identifyIdentify-Printer function
      identify_supported"identify-actions-supported" values
      kind"printer-kind" values
      make_and_model[128]"printer-make-and-model" value
      media[PAPPL_MAX_MEDIA]Supported media
      media_ready[PAPPL_MAX_SOURCE]Ready media
      mode_supportedlabel-mode-supported
      num_binNumber of output bins
      num_featuresNumber of "ipp-features-supported" values
      num_mediaNumber of supported media
      num_sourceNumber of media sources (trays/rolls)
      num_typeNumber of media types
      num_vendorNumber of vendor attributes
      orient_default"orientation-requested-default" value
      output_face_upDoes output media come out face-up?
      pditherdither array
      ppm_color"pages-per-minute-color" value, if any
      printPrint (file) function
      quality_default"print-quality-default" value
      raster_types"pwg-raster-document-type-supported" values
      rendjobEnd raster job function
      rendpageEnd raster page function
      rstartjobStart raster job function
      rstartpageStart raster page function
      rwriteWrite raster line function
      scaling_default"print-scaling-default" value
      sides_default"sides-default" value
      source[PAPPL_MAX_SOURCE]Media sources
      speed_defaultprint-speed-default
      statusStatus function
      tear_offset_supported[2]label-tear-offset-supported (0,0 for none)
      top_offset_supported[2]media-top-offset-supported (0,0 for none)
      tracking_supportedmedia-tracking-supported
      type[PAPPL_MAX_TYPE]Media types
      vendor[PAPPL_MAX_VENDOR]Vendor attribute names
      y_defaultDefault resolution

    Difference between print and write function

    Both the print and write functions execute a similar task, but they are invoked by the printer application in a different situation.

    If the printer-specific format, i.e. the format which is understood by the printer and the format of the Job supplied is the same, then the print function is invoked. One may rightly guess that this is the case of raw print since no format conversion or processing is required.

    In the other case, the write function is used. This is complemented using other functions such as rstartpage, rendpage, rstartjob, and rendjob.

    rwriteVSrprint


  • Identify Function

    The function helps to identify a printer using display, flash, sound, or speech.

    void pcl_identify(
        pappl_printer_t          *printer,
        pappl_identify_actions_t actions,
        const char               *message
    );
    

    The Identify function receives three arguments that are the Printer, Actions to take, and Messages (if any).

    The Identification methods are summarised in the table:

    Identification MethodAction
    PAPPL_IDENTIFY_ACTIONS_SOUNDMake a sound
    PAPPL_IDENTIFY_ACTIONS_DISPLAYdisplay a message (argument)
    PAPPL_IDENTIFY_ACTIONS_SPEAKspeak a message (argument)
    PAPPL_IDENTIFY_ACTIONS_FLASHflashes a light on the printer

  • Print Function

    It is used to print a raw job (printer-ready) file, i.e. if the job format is the same as the format specified by the driver callback.

    bool pcl_print(
        pappl_job_t      *job,
        pappl_poptions_t *options,
        pappl_device_t   *device
    );
    

    The Print function receives three arguments that are Job, Job Options, and device.

    • The "job" argument provides the current job object.
    • The "options" pointer provides the current print job options.
    • The "device" argument is a pointer used to send data to the printer.

    The function returns true on success and false on failure.

    This callback will sometimes send some printer initialization commands followed by the job file and then any cleanup commands. It may also be able to count the number of pages (impressions) in the file, although that is not a requirement.


  • End Job Function

    This function is called at the end of each page where the driver will typically eject the current page.

    bool pcl_rendjob(
        pappl_job_t      *job,
        pappl_poptions_t *options,
        pappl_device_t   *device
    );
    

    The End Job function receives three arguments that are Job, Job Options, and device.

    • The "job" argument provides the current job object.
    • The "options" pointer provides the current print job options.
    • The "device" argument is a pointer used to send data to the printer.

    The function returns true on success and false on failure.

    Note that the job data set by Start Job Function must be freed in this function.


  • End Page Function

    This function is called each time a page is completed. It helps in resetting the buffers used by the driver.

    bool pcl_rendpage(
        pappl_job_t      *job,
        pappl_poptions_t *options,
        pappl_device_t   *device,
        unsigned         page
    )
    

    The End Page function receives four arguments that are Job, Job Options, Device, and Page Number.

    • The "job" argument provides the current job object.
    • The "options" pointer provides the current print job options.
    • The "device" argument is a pointer used to send data to the printer.
    • The "page" argument specifies the current page number staring at 0.

    The function returns true on success and false on failure.


  • Start Job Function

    This function is called at the beginning of a job to allow the driver to initialize the printer for the current job.

    bool pcl_rstartjob(
        pappl_job_t      *job,
        pappl_poptions_t *options,
        pappl_device_t   *device
    );
    

    The Start-Job function, like the End Job Function, receives three arguments that are Job, Job Options, and device.

    • The "job" argument provides the current job object.
    • The "options" pointer provides the current print job options.
    • The "device" argument is a pointer used to send data to the printer.

    The function returns true on success and false on failure.


  • Start Page Function

    This function is called when starting a page to allow the driver to do any per-page initialization and/or memory allocations and send any printer commands that are necessary to start a new page. Information regarding the page is obtained from the page header and attributes like resolution, margins, page size, orientation, and graphics are set appropriately.

    bool pcl_rstartpage(
        pappl_job_t      *job,
        pappl_poptions_t *options,
        pappl_device_t   *device,
        unsigned         page
    )
    

    The Start Page function receives four arguments that are Job, Job Options, Device, and Page Number.

    • The "job" argument provides the current job object.
    • The "options" pointer provides the current print job options.
    • The "device" argument is a pointer used to send data to the printer.
    • The "page" argument specifies the current page number staring at 0.

    The function returns true on success and false on failure.


  • Write Line Function

    This function writes a line of graphics and is called for each raster line on the page. It is typically responsible for dithering and compressing the raster data for the printer.

    bool pcl_rwrite(
        pappl_job_t         *job,
        pappl_poptions_t    *options,
        pappl_device_t      *device,
        unsigned            y,
        const unsigned char *pixels
    )
    

    The Write Line function receives five arguments that are Job, Job Options, Device, Line number, and Line.

    • The "job" argument provides the current job object.
    • The "options" pointer provides the current print job options.
    • The "device" argument is a pointer used to send data to the printer.
    • The "y" argument specifies the current line number.
    • The "pixels" argument is a pointer to the content of current line.

    The function returns true on success and false on failure.


  • Printer Status Function

    The PAPPL status callback is used to update the printer state, supply levels, and/or ready media for the printer:

    bool pcl_status(
        pappl_printer_t *printer
    )
    

    The Printer Status Function receives only one argument and that is the printer. It returns true on success and false on failure.

    The callback can open a connection to the printer using the papplPrinterOpenDevice function.

Add Support for Non-Raster Printers

Currently, PAPPL supports only raster printers and that too for very few specific input formats like JPEG and PNG. For adding support for non-raster printers like PDF and PostScript printers, you need to supply an external utility that converts the whole job's data into a data stream which the printer understands. Refer to the below-mentioned steps to know how to implement the same.

  • Set the printer-specific format in callback function.

    You need to set driver_data.format in callback function to the printer-specific format, i.e. the format/language accepted by the printer. A few examples could be "application/postscript", "application/pdf", etc.

  • Add a filter callback

    You need to add filter callback from format received by the printer application(formats which you wish your printer can support and accept the job in) to printer-specific format(format/languages that the printer actually understands) using papplSystemAddMIMEFilter utility.

    void papplSystemAddMIMEFilter(
        pappl_system_t *system,
        const char *srctype,
        const char *dsttype,
        pappl_mime_filter_cb_t cb,
        void *data
    );
    
    ParameterSignificance
    systemSystem
    srctypeSource MIME media type (constant) string
    dsttypeDestination MIME media type (constant) string
    cbFilter callback function
    dataFilter callback data

    This utility is added in system callback after calling setup function in Step 5.

  • Define filter callback function

    The filter callback function converts the whole job's data into a data stream which the printer understands.

    bool papplJobFilter(
        pappl_job_t    *job,
        pappl_device_t *device,
        void           *data
    )
    

    The filter callback function receives 3 parameters, that are the Job, the Device, and the Filter data.

    Depending upon the features, stability, ease of use, documentation, pricing, and license you may use any third party API to perform this task of conversion.

    The default filter callback function added in PAPPL are _papplJobFilterJPEG and _papplJobFilterPNG which can be found in the file Job-Filter.c and can be referenced for writing your own callback function.

Template for PAPPL-based Printer Driver

//
// Include necessary headers...
//
# include <pappl/pappl.h>


// Declare structure for Job Data


// Declare supported media sizes for different models of printers


//
// Declare local functions
//

static const char *pcl_autoadd(const char *device_info, const char *device_uri, const char *device_id, void *data);
static bool   pcl_callback(pappl_system_t *system, const char *driver_name, const char *device_uri, const char *device_id, pappl_pr_driver_data_t *driver_data, ipp_t **driver_attrs, void *data);
static void   pcl_compress_data(pappl_job_t *job, pappl_device_t *device, unsigned char *line, unsigned length, unsigned plane, unsigned type);
static bool   pcl_print(pappl_job_t *job, pappl_pr_options_t *options, pappl_device_t *device);
static bool   pcl_rendjob(pappl_job_t *job, pappl_pr_options_t *options, pappl_device_t *device);
static bool   pcl_rendpage(pappl_job_t *job, pappl_pr_options_t *options, pappl_device_t *device, unsigned page);
static bool   pcl_rstartjob(pappl_job_t *job, pappl_pr_options_t *options, pappl_device_t *device);
static bool   pcl_rstartpage(pappl_job_t *job, pappl_pr_options_t *options, pappl_device_t *device, unsigned page);
static bool   pcl_rwriteline(pappl_job_t *job, pappl_pr_options_t *options, pappl_device_t *device, unsigned y, const unsigned char *pixels);
static void   pcl_setup(pappl_system_t *system);
static bool   pcl_status(pappl_printer_t *printer);
static void   setup(pappl_system_t *system);
static pappl_system_t   *system_cb(int num_options, cups_option_t *options, void *data);


//
// 'main()' - Main entry for the application.
//

int
main(int  argc,				// I - Number of command-line arguments
     char *argv[])			// I - Command-line arguments
{
  return (papplMainloop(argc, argv,
                        /*version*/"1.0",
                        /*footer_html*/NULL,
                        (int)(sizeof(pcl_drivers) / sizeof(pcl_drivers[0])),
                        pcl_drivers, pcl_callback, pcl_autoadd,
                        /*subcmd_name*/NULL, /*subcmd_cb*/NULL,
                        /*system_cb*/NULL,
                        /*usage_cb*/NULL,
                        /*data*/NULL));
}


//
// 'pcl_autoadd()' - Auto-add PCL printers.
//

static const char *			// O - Driver name or `NULL` for none
pcl_autoadd(const char *device_info,	// I - Device name
            const char *device_uri,	// I - Device URI
            const char *device_id,	// I - IEEE-1284 device ID
            void       *data)		// I - Callback data (not used)
{

}


//
// 'pcl_callback()' - PCL callback.
//

static bool				// O - `true` on success, `false` on failure
pcl_callback(
    pappl_system_t         *system,	// I - System
    const char             *driver_name,
					// I - Driver name
    const char             *device_uri,// I - Device URI (not used)
    const char             *device_id,	// I - IEEE-1284 device ID (not used)
    pappl_pr_driver_data_t *driver_data,
					// O - Driver data
    ipp_t                  **driver_attrs,
					// O - Driver attributes (not used)
    void                   *data)	// I - Callback data (not used)
{

}


//
// 'pcl_compress_data()' - Compress a line of graphics.
//

static void
pcl_compress_data(
    pappl_job_t    *job,		// I - Job object
    pappl_device_t *device,		// I - Device
    unsigned char  *line,		// I - Data to compress
    unsigned       length,		// I - Number of bytes
    unsigned       plane,		// I - Color plane
    unsigned       type)		// I - Type of compression
{

}


//
// 'identify()' - Identify the printer.
//

static void
identify(
    pappl_printer_t          *printer,  // I - Printer
    pappl_identify_actions_t actions,   // I - Actions to take
    const char               *message)  // I - Message, if any
{
// Identify a printer using display, flash, sound, or speech.
}


//
// 'pcl_print()' - Print file.
//

static bool				// O - `true` on success, `false` on failure
pcl_print(
    pappl_job_t      *job,		// I - Job
    pappl_pr_options_t *options,		// I - Options
    pappl_device_t   *device)		// I - Device
{

}


//
// 'pcl_rendjob()' - End a job.
//

static bool				// O - `true` on success, `false` on failure
pcl_rendjob(
    pappl_job_t      *job,		// I - Job
    pappl_pr_options_t *options,		// I - Options
    pappl_device_t   *device)		// I - Device
{

}


//
// 'pcl_rendpage()' - End a page.
//

static bool				// O - `true` on success, `false` on failure
pcl_rendpage(
    pappl_job_t      *job,		// I - Job
    pappl_pr_options_t *options,		// I - Job options
    pappl_device_t   *device,		// I - Device
    unsigned         page)		// I - Page number
{

}


//
// 'pcl_rstartjob()' - Start a job.
//

static bool				// O - `true` on success, `false` on failure
pcl_rstartjob(
    pappl_job_t      *job,		// I - Job
    pappl_pr_options_t *options,		// I - Job options
    pappl_device_t   *device)		// I - Device
{

}


//
// 'pcl_rstartpage()' - Start a page.
//

static bool				// O - `true` on success, `false` on failure
pcl_rstartpage(
    pappl_job_t      *job,		// I - Job
    pappl_pr_options_t *options,		// I - Job options
    pappl_device_t   *device,		// I - Device
    unsigned         page)		// I - Page number
{

}


//
// 'pcl_rwriteline()' - Write a line.
//

static bool				// O - `true` on success, `false` on failure
pcl_rwriteline(
    pappl_job_t         *job,		// I - Job
    pappl_pr_options_t    *options,	// I - Job options
    pappl_device_t      *device,	// I - Device
    unsigned            y,		// I - Line number
    const unsigned char *pixels)	// I - Line
{

}


//
// 'pcl_status()' - Get printer status.
//

static bool				// O - `true` on success, `false` on failure
pcl_status(
    pappl_printer_t *printer)		// I - Printer
{

}


//
// 'setup()' - Setup PCL drivers.
//

static void
setup(
    pappl_system_t *system)      // I - System
{

}


//
// 'system_cb()' - System callback.
//

pappl_system_t *            // O - New system object
system_cb(int           num_options,    // I - Number of options
    cups_option_t *options, // I - Options
    void          *data)        // I - Callback data
{

}

Design Guidelines

  • 1 Printer/Scanner Application = 1 Snap: Don't make a Snap which contains tons of different printer and scanner applications. This is to ensure that you don't occupy lots of ports and network resources when you have many printer and scanner applications.

  • Printer/Scanner/Fax support can be in a single application: To support multi-function devices you can put all the printer, scanner, and fax support into one application. For example, we recommend HPLIP to be put into one single application.

  • Recommended: 1 Printer/Scanner Application per project or manufacturer/product line: It may be possible that a multi-function device may be served by different applications for printing and scanning. For example for legacy devices, scanning may be supported by retrofitted sane-airscan scanner application while printing is supported by the Gutenprint printer application. But here it is easier and better for the project, organization, and management that each project has its own printer/scanner application snap. So SANE will maintain a SANE scanner application snap and they put it on the Snap Store. Similarly, Gutenprint, HPLIP, foo2zjs, Epson, Canon, Ricoh, and so on will do the same.

  • NOT make 1 Printer/Scanner Application for each device: For example, HP Laserjet 4 and HP Laserjet 5 should not have different applications. Otherwise, Snap Store will be cluttered with thousands of applications, and spotting the real application would be difficult. Also, it would result in a lot of code duplication, requiring more storage on the user's machine.

  • 1 Printer/Scanner Application = 1 Port: If there are several devices connected to the system and they are served by one printer/scanner application, do not open ports for each device. Because you may run out of ports. Also, ports are not always the same on each boot as other applications may start up before on the next boot.

  • For more than 1 device on 1 Application use URI: ipp://localhost:<PORT>/ipp/print/<NAME> This is the recommended way to cope up with several devices by only using a single port.

  • DNS-SD service names must be always the same: They must be independent of order application start at boot or of device discovery. To make sure that a printer application can serve several devices of the same model include the DNS-SD service name the CEON number of the devices.

  • Web admin interface should allow:

    1. suppressing auto-setup for selected devices: Auto detecting devices might be unsuitable for some cases. For example, if two printer applications support the same device, the user must be able to select with which application he wishes to print. So web interface must contain the option to somehow blacklist a printer application.

    2. manual setup of additional devices/instances Necessary for legacy printers that cannot be auto-discovered in the network.

    3. configuration of options not accessible via IPP Many manufacturers have options that cannot be translated into IPP attributes. So web interface has to provide the possibility to set up these options.

  • sane-airscan in SANE Scanner Application must be built without IPP Scan to avoid recursive discovery infinite loop (“Scanner bomb”)

Resources

[1] Printer Application
[2] HP Printer App Example
[3] PAPPL
[4] PAPPL System Utilities
[5] Packaging Drivers and Uploading them to Snap Store
[6] User Manual
[7] PS Printer App Example