CAPS Universe documentation  1.0.4
All you need to know to be successful
Data Structures | Macros | Typedefs | Enumerations | Functions
ql-series.h File Reference

Collection of macros and functions shared by all QLxxx printer drivers. More...

Go to the source code of this file.

Data Structures

struct  halftone_converter
 Halftone converter run-time data. More...
 
struct  ql_trim
 Trim the input raster to the available media. More...
 
struct  ql_job
 Collected data for the current job to process. More...
 
struct  ql_drv
 The main QL driver instance description. More...
 
struct  dk_roll_types
 QL printers roll types. More...
 
struct  ql_simple_command
 A simple QL printer command declaration. More...
 

Macros

#define caps_print_debug(fmt, ...)
 
#define ARRAY_SIZE(x)   (sizeof(x)/sizeof(x[0]))
 
#define DOTS_TO_BYTES(x)   ((x + 7U) / 8U)
 
#define DOTS_TO_WORDS(x)   ((x + 15U) / 16U)
 
#define BIT(x)   (1 << (x))
 
#define _(string)   (string)
 
#define NLS_(string)   (string)
 
#define COLOUR_VAL_DARK   0x00
 
#define COLOUR_VAL_BRIGHT   0xff
 
#define PRINT_DOT_VAL   0x00
 
#define DEFAULT_QL_LINE_SIZE   90
 
#define QL_MEDIA_TYPE_CONTINUOUS   0xa
 
#define QL_MEDIA_TYPE_DIE_CUT   0xb
 
#define QL_QUALITY_PRINT   0x40 /* never use while bi-colour printing (according to the datasheet) */
 
#define QL_FAST_PRINT   0x00 /* always use with bi-colour printing (according to the datasheet) */
 
#define QL_MAX_SELECTIONS   10
 
#define QL_FIRST_PAGE   1
 
#define QL_NEXT_PAGE   0
 

Typedefs

typedef unsigned char conv_type
 

Enumerations

enum  ql_half_tone {
  QL_UNKNOWN = 0 ,
  QL_NONE = 1 ,
  QL_ORDERED = 2 ,
  QL_ERROR_DIFFUSION = 4
}
 
enum  dk_caps {
  DK_CAP_UNKNOWN = 0 ,
  DK_CAP_CONTINUOUS = 1 ,
  DK_CAP_DIE_CUT = 2 ,
  DK_CAP_RED = 4
}
 

Functions

static struct ql_drvto_ql_drv (void *d)
 
void sliding_halftone_get (struct halftone_converter *cnv)
 
void sliding_halftone_put (struct halftone_converter *cnv)
 
void halftone_line_no_dither (struct halftone_converter *cnv)
 
void halftone_line_ordered (struct halftone_converter *cnv)
 
void halftone_line_with_error_diffusion (struct halftone_converter *cnv)
 
void move_in_next_line (struct halftone_converter *cnv, const struct caps_dot_grey raw[cnv->pixel_count])
 
void move_in_empty_line (struct halftone_converter *cnv, conv_type val)
 
int ql_dk_information_get (struct dk_roll_types *medium, const char *name)
 
int ql_reset (struct ql_drv *t) __wur
 
int ql_page_print (struct ql_drv *t) __wur
 
int ql_last_page_print (struct ql_drv *t) __wur
 
int ql_invalidate_and_initialize (struct ql_drv *t) __wur
 
int ql_status_request (struct ql_drv *t, unsigned char buffer[32]) __wur
 
void ql_driver_halftone_selection_setup (struct ql_drv *t, struct caps_ppd_base *base)
 
void ql_driver_power_selection_setup (struct ql_drv *t, struct caps_ppd_base *base)
 
void ql_driver_bi_colour_medium_selection_setup (struct ql_drv *t, struct caps_ppd_base *base)
 
void ql_driver_base_setup (struct ql_drv *t)
 
int ql_numerical_setting_read (struct ql_drv *t, const char *section, const char *keyword)
 
int ql_job_header_generate (struct ql_drv *t)
 
int ql_driver_send (struct ql_drv *t, size_t count, const void *data, int flush) __wur
 
int ql_driver_data_send (struct ql_drv *t, size_t count, const void *data, int flush) __wur
 
void ql_driver_raster_crop (struct ql_drv *t)
 
int ql_page_header_generate (struct ql_drv *t, int first_page) __wur
 
int ql_auto_powerdown (struct ql_drv *t, unsigned short time_out) __wur
 
int ql_auto_powerup (struct ql_drv *t, unsigned short mode) __wur
 
static void transform_to_monochrome (size_t input_cnt, const conv_type input[input_cnt], unsigned char *data)
 
static unsigned pts_to_pixel (double pts, unsigned resolution)
 
static unsigned mm_to_pixel (unsigned mm, unsigned resolution)
 
static double pts_to_mm (double pts)
 
static double mm_to_pts (double mm)
 
static double pixel_to_mm (unsigned pixel, unsigned resolution)
 
enum ql_half_tone ql_driver_job_halftone_get (struct ql_drv *t)
 
void ql_driver_job_bi_colour_medium_get (struct ql_drv *t)
 
int ql_monochome_page_print (struct ql_drv *t)
 

Detailed Description

Author
Jürgen Borleis
Warning
Use as experimental

Macro Definition Documentation

◆ caps_print_debug

#define caps_print_debug (   fmt,
  ... 
)

◆ ARRAY_SIZE

#define ARRAY_SIZE (   x)    (sizeof(x)/sizeof(x[0]))

always useful

◆ DOTS_TO_BYTES

#define DOTS_TO_BYTES (   x)    ((x + 7U) / 8U)

Convert an amount of (monochrome) dots into its corresponding amount of bytes and round up

◆ DOTS_TO_WORDS

#define DOTS_TO_WORDS (   x)    ((x + 15U) / 16U)

Convert an amount of (monochrome) dots into its corresponding amount of shorts and round up

◆ BIT

#define BIT (   x)    (1 << (x))

Just a little helper to write more readable code

◆ _

#define _ (   string)    (string)

◆ NLS_

#define NLS_ (   string)    (string)

◆ COLOUR_VAL_DARK

#define COLOUR_VAL_DARK   0x00

Value of black. The minimal value of a byte

◆ COLOUR_VAL_BRIGHT

#define COLOUR_VAL_BRIGHT   0xff

Value of white. The maximal value of a byte

◆ PRINT_DOT_VAL

#define PRINT_DOT_VAL   0x00

The value of a to be printed dot

◆ DEFAULT_QL_LINE_SIZE

#define DEFAULT_QL_LINE_SIZE   90

The generic count of bytes of one line of these QL printers

Note
This is valid only for a couple types of these printers. It should be set in the PPD instead.

◆ QL_MEDIA_TYPE_CONTINUOUS

#define QL_MEDIA_TYPE_CONTINUOUS   0xa

◆ QL_MEDIA_TYPE_DIE_CUT

#define QL_MEDIA_TYPE_DIE_CUT   0xb

◆ QL_QUALITY_PRINT

#define QL_QUALITY_PRINT   0x40 /* never use while bi-colour printing (according to the datasheet) */

◆ QL_FAST_PRINT

#define QL_FAST_PRINT   0x00 /* always use with bi-colour printing (according to the datasheet) */

◆ QL_MAX_SELECTIONS

#define QL_MAX_SELECTIONS   10

This is just a short-cut to simplify the addition of selections to the printer description

It is used to reserve a static selection table, which can be extended on demand at run-time.

◆ QL_FIRST_PAGE

#define QL_FIRST_PAGE   1

Used in ql_page_header_generate() to mark the first page in a print job

◆ QL_NEXT_PAGE

#define QL_NEXT_PAGE   0

Used in ql_page_header_generate() to mark the the next page in a print job (in contrast to the QL_FIRST_PAGE)

Typedef Documentation

◆ conv_type

typedef unsigned char conv_type

Enumeration Type Documentation

◆ ql_half_tone

Half tone method to be used for the raster

Enumerator
QL_UNKNOWN 

not defined, or use some kind of default (let the user select one)

QL_NONE 

Don't use any kind of half tone method

QL_ORDERED 

Use an ordered half tone method

QL_ERROR_DIFFUSION 

Use the error diffusion half tone method (Floyd-Steinberg)

◆ dk_caps

enum dk_caps
Enumerator
DK_CAP_UNKNOWN 
DK_CAP_CONTINUOUS 
DK_CAP_DIE_CUT 
DK_CAP_RED 

additionally red colour possible

Function Documentation

◆ to_ql_drv()

static struct ql_drv * to_ql_drv ( void *  d)
inlinestatic

Convert the anonymous pointer from the libcapsdriver into our information collection

Parameters
[in]dAnonymous structure pointer
Returns
Pointer to our struct ql_drv

◆ sliding_halftone_get()

void sliding_halftone_get ( struct halftone_converter cnv)

Prepare the sliding line buffers

Parameters
[in,out]cnvThe converter structure to initialiaize

In order to run some kind of dithering we need signed values per pixel which must be larger than the incoming pixels data. Incoming pixels are of type 'unsigned char' with their grey values. Due to this conversion we do not need to honor any kind of saturation while calculating the dithered output.

Note
Does not return in case of memory failure
Precondition
halftone_converter::pixel_count must be already set and not '0'

◆ sliding_halftone_put()

void sliding_halftone_put ( struct halftone_converter cnv)

Clean up image processing via sliding line buffers

Parameters
[in,out]cnvThe converter structure to destroy

◆ halftone_line_no_dither()

void halftone_line_no_dither ( struct halftone_converter cnv)

Quantize the top line without any half tone algorithm

Parameters
[in,out]cnvThe converter structure to use

http://www.visgraf.impa.br/Courses/ip00/proj/Dithering1/average_dithering.html

◆ halftone_line_ordered()

void halftone_line_ordered ( struct halftone_converter cnv)

Quantize the top line according to an "ordered dithering matrix"

Parameters
[in,out]cnvThe converter structure to use

This is a simple algorithm with a 2x2 "ordered dithering matrix":

   | 1/10  3/10 | <-- cnv->sliding_lines[0]
   | 4/10  2/10 | <-- cnv->sliding_lines[1]

http://www.visgraf.impa.br/Courses/ip00/proj/Dithering1/ordered_dithering.html

Precondition
halftone_converter::sliding_lines[0] and halftone_converter::sliding_lines[1] must be valid

◆ halftone_line_with_error_diffusion()

void halftone_line_with_error_diffusion ( struct halftone_converter cnv)

Quantize the top line according to the "Floyd-Steinberg error diffusion" and distribute the error around

Parameters
[in,out]cnvThe converter structure to use

From: http://www.visgraf.impa.br/Courses/ip00/proj/Dithering1/floyd_steinberg_dithering.html

   |  N     *   7/16 | <-- cnv->sliding_lines[0]
   | 3/16  5/16 1/16 | <-- cnv->sliding_lines[1]

N previous pixel, * current pixel to process (e.g. X/Y position)

Precondition
halftone_converter::sliding_lines[0] and halftone_converter::sliding_lines[1] must be valid
At least three pixels must be in the lines

◆ move_in_next_line()

void move_in_next_line ( struct halftone_converter cnv,
const struct caps_dot_grey  raw[cnv->pixel_count] 
)

Move in the next line with raw raster data into the process

Parameters
[in,out]cnvThe converter structure to destroy
[in]rawBuffer with raw pixel data (grey scale) in bytes

Throw away the content of the current top line, move all lines below one line up and fill the bottom line with new content from the given raw line.

Currently there are only two lines in the calculation buffer, so "moving" means "swapping" instead.

The byte based pixel data is copied into the sliding half tone structure and gets converted to signed short to be able run the half tone algorithm afterwards.

Note
In order to be able to calculate the bottom line of the image, you need to input an empty line instead. Empty means: it must contain a value, which isn't printed. Refer move_in_empty_line() for details
Precondition
raw must point to a buffer with halftone_converter::pixel_count bytes.

◆ move_in_empty_line()

void move_in_empty_line ( struct halftone_converter cnv,
conv_type  val 
)

Move in an empty line into the process

Parameters
[in,out]cnvThe converter structure to use
[in]val'Empty' value (0…255)

'Empty' means val must have a value which should not print anything. For example COLOUR_VAL_BRIGHT.

◆ ql_dk_information_get()

int ql_dk_information_get ( struct dk_roll_types medium,
const char *  name 
)

Retrieve settings/information about a specific DK roll/cassette

Parameters
[out]mediumThe settings for the DK roll named by name
[in]nameThe DK roll name the settings should be retrieved for
Return values
0On success, *medium is valid
-EINVALRoll with the given name not found

DK rolls can inherit settings from other rolls. This routine walks through the table and uses the dk_roll_types::like member to re-use their settings.

◆ ql_reset()

int ql_reset ( struct ql_drv t)

Initialize/reset the printer

Parameters
[in]tFull job description
Return values
0On success
NegativeError code from the ql_driver_data_send() call

This command can also be used every time to terminate a current print

Note
To be sent once at the beginning of a print job (according to the manual)

◆ ql_page_print()

int ql_page_print ( struct ql_drv t)

Finish the current page

Parameters
[in]tFull job description
Return values
0On success
NegativeError code from the ql_driver_data_send() call

This code is to be sent after a page.

It seems, this command is only to be sent, if more than one page in one job should be printed. If the job contains only one page, the ql_last_page_print() is enough to do the right thing.

Note
To be sent at the end of a page's raster, if it isn't the last page to be print (according to the manual)

◆ ql_last_page_print()

int ql_last_page_print ( struct ql_drv t)

Finish the current print job

Parameters
[in]tFull job description
Return values
0On success
NegativeError code from the ql_driver_data_send() call

This code is to be sent after the last page to finish the whole print job.

Note
To be sent at the end of a page's raster, if it is the last page of the job (according to the manual)

◆ ql_invalidate_and_initialize()

int ql_invalidate_and_initialize ( struct ql_drv t)

Start a new print job from the printer's point of view

Parameters
[in]tFull job description
Return values
0On success
NegativeError code from the ql_driver_data_send() call

To be sent at the beginning of a new print job or if the current processing has to be stopped at the printer's side.

From the datasheet: "If data transmission is to be stopped midway, send the “initialize” command after sending the “invalidate” command for the appropriate number of bytes to return to the receiving state, where the print buffer is cleared."

Note
invalidate means the specified amount of zero bytes
initialize means the *ESC,@* command in ql_reset()
To be sent once at the beginning of a print job (according to the manual)

◆ ql_status_request()

int ql_status_request ( struct ql_drv t,
unsigned char  buffer[32] 
)

Request and read status from printer.

Parameters
[in]tFull job description
[out]bufferStorage for the 32 status bytes read from the printer
Return values
0Status read successfully
-ENODATANo status information from the printer. buffer content is invalid
-ENODEVIf the printer is gone in the meanwhile. This is serious!
-errnoAll error values from ql_driver_data_send(). They are serious!

Some run-time experiences:

  • the printer reports always the inserted cassette, even if the cover is opened (QL800 tested)
  • if no cassette is inserted, it returns 0x00 as the 'media_type' and width and length as 0.
  • whenever an error occur, the report is marked as an error report (field 'status_type')
  • if the cover is closed but whithout an inserted cassette the report isn't marked as an error report
  • it seems, the 'automatic status notification mode' works differently. At my side it didn't work at all
Precondition
Printer must be idle

◆ ql_driver_halftone_selection_setup()

void ql_driver_halftone_selection_setup ( struct ql_drv t,
struct caps_ppd_base base 
)

Add the power feature control selections to the printer description based on INI settings

Parameters
[in]tFull job description
[in,out]baseAdd selections to this printer description on demand

◆ ql_driver_power_selection_setup()

void ql_driver_power_selection_setup ( struct ql_drv t,
struct caps_ppd_base base 
)

Add the power feature control selections to the printer description based on INI settings

Parameters
[in]tFull job description
[in,out]baseAdd selections to this printer description on demand

Uses the INI boolean key powerctl in section [features] to setup a possible power setting of the printer. In this case, selections defining these features will be added to the printer description PPD.

◆ ql_driver_bi_colour_medium_selection_setup()

void ql_driver_bi_colour_medium_selection_setup ( struct ql_drv t,
struct caps_ppd_base base 
)

Add bi-colour medium selection to the printer description based on INI settings

Parameters
[in]tFull job description
[in,out]baseAdd selections to this printer description on demand

The bi-colour capability of the loaded cassette cannot be detected. It must be selected by the user. This function adds a corresponding selection to the printer description, if the printer has bi-colour capabilities.

Precondition
Printer's colour capabilities must be already set (by calling ql_driver_colour_from_INI() first)

◆ ql_driver_base_setup()

void ql_driver_base_setup ( struct ql_drv t)

Read in the main INI file settings for the driver

Parameters
[in]tFull job description

◆ ql_numerical_setting_read()

int ql_numerical_setting_read ( struct ql_drv t,
const char *  section,
const char *  keyword 
)

Helper to read in a positive integer INI setting

Parameters
[in,out]tFull job description
[in]sectionName of the section, the setting should be read from
[in]keywordThe keyword/setting to read in
Return values
PositiveThe setting read from the INI
-ENODATANo setting found
-EINVALSetting found, but doesn't look like an ASCII number
-ERANGESetting contains an invalid number for an 'int'

◆ ql_job_header_generate()

int ql_job_header_generate ( struct ql_drv t)

Send the heading commands to setup the printer prior a print job

Parameters
[in]tFull job description

Called once per print job at the beginning

◆ ql_driver_send()

int ql_driver_send ( struct ql_drv t,
size_t  count,
const void *  data,
int  flush 
)

Send some data to the printer's stream

Parameters
[in,out]tFull job description
[in]countCount of bytes data points to
[in]dataPointer to the to be sent data
[in]flush'1' if the stream should be flushed
Return values
0Data buffered (and send, if flush is nonzero)
-EINVALPrinter device is unsuitable for writing
-EBADFReally bad internal failure
-EFAULTReally bad internal failure made by the caller
-EIOLow-level I/O error
Note
It seems to me, if this call returns with an error code the data wasn't sent.

◆ ql_driver_data_send()

int ql_driver_data_send ( struct ql_drv t,
size_t  count,
const void *  data,
int  flush 
)

Send some data to the printer's stream or the development/debugging file

Parameters
[in,out]tFull job description
[in]countCount of bytes data points to
[in]dataPointer to the to be sent data
[in]flush'1' if the stream should be flushed
Return values
0Data buffered (and send, if flush is nonzero)
-EINVALPrinter device is unsuitable for writing
-EBADFReally bad internal failure
-EFAULTReally bad internal failure made by the caller
-EIOLow-level I/O error

This sends the real printer data to the printer's stream like ql_driver_send() does, but of the debug option –writeto is in use, it writes it to the file instead.

This is for debugging/development purposes, to extract the generated wire data and check its content and format. It is required this way, since the driver depends on interactive data exchange with the printer and if something went wrong, there is no other easy to use chance to see the data in this case.

Todo:
Write a printer emulator instead.

◆ ql_driver_raster_crop()

void ql_driver_raster_crop ( struct ql_drv t)

Crop the input raster to meet the printer's expectations/requirements

Parameters
[in]tFull job description

The printer device has some - hmm, lets it call - interesting requirements about the print data it accepts. Continuous labels are easy, but pre-cut labels aren't. In order to ensure the printer always cuts inbetween two pre-cut labels, we need to tweak the amount of lines to be print.

Read how_the_driver_deals_with_margins about details.

◆ ql_page_header_generate()

int ql_page_header_generate ( struct ql_drv t,
int  first_page 
)
Parameters
[in]tFull job description
[in]first_pageQL_FIRST_PAGE if it is the page header for the first page, QL_NEXT_PAGE else
Return values
0On success
-ENODEVDevice seems gone (e.g. channel to the next stage is gone)

Called once per page at the beginning

Note
In case of -ENODEV it makes no sense trying to send any further data or commands
Precondition
The ql_job::trim member must be up to date, e.g. its content matches the current raster page header
Todo:

Deal with QL_FAST_PRINT/QL_QUALITY_PRINT for monochrome use case

Do the QL_QUALITY_PRINT and QL_HIGH_RESOLUTION bits depend on each other?

Deal with auto cut setting and when to cut (and if the printer has a cutter). To make it simple: only "cut at end" and "cut each" should be supported

◆ ql_auto_powerdown()

int ql_auto_powerdown ( struct ql_drv t,
unsigned short  time_out 
)

Set the printer's auto power down behaviour if it is idle

Parameters
[in]tFull job description
[in]time_outTime out when idle to enter power down mode

time_out can be:

  • 0x0000: disable auto power off
  • 0x0001: auto power off after 10 minutes
  • 0x0002: auto power off after 20 minutes
  • 0x0003: auto power off after 30 minutes
  • 0x0004: auto power off after 40 minutes
  • 0x0005: auto power off after 50 minutes
  • 0x0006: auto power off after 60 minutes

If the printer enters the power off mode, it disconnects itself from the USB and can be re-enabled only manually with its local power button. This is a really bad idea for remotly used printers.

Note
This command is reverse-engineered, it isn't listed in the programming manual
The big endian format for the subcommand's data (here time_val) is guessed only. All other printer commands use little endian for multibyte parameter. So, the command's data might have a different format/content than listed here

◆ ql_auto_powerup()

int ql_auto_powerup ( struct ql_drv t,
unsigned short  mode 
)

Set the printer's power up behaviour when connecting to external power

Parameters
[in]tFull job description
[in]modePower On mode

mode can be:

  • 0x0000 Keep offline when connecting to external power
  • 0x0001 Go online when connecting to external power
Note
This command is reverse-engineered, it isn't listed in the programming manual
The big endian format for the subcommand's data (here mode) is guessed only. All other commands use little endian for multibyte parameter. So, the command's data might have a different format/content than listed here

◆ transform_to_monochrome()

static void transform_to_monochrome ( size_t  input_cnt,
const conv_type  input[input_cnt],
unsigned char *  data 
)
inlinestatic

Convert an array of signed shorts into a monochrome pattern (one conv_type into one bit)

Parameters
[in]input_cntCount of pixel in input
[in]inputWhere to get the quantized input data from
[out]dataWhere to store the converted monochrome output data to

Let the compiler optimize it when used.

Note
Since the printer prints from right to left, the input pattern gets mirrored(!) here. First pixel printed (at the right border) is the MSB of the first byte
Precondition
PRINT_DOT_VAL means: print a dot, everything else means: do not print a dot, e.g. the input data must already be quantized
input and data must point to different memories (no overlap, no side effects)
input_cnt must be a multiple of 8

◆ pts_to_pixel()

static unsigned pts_to_pixel ( double  pts,
unsigned  resolution 
)
inlinestatic

Convert Postscript [pts] (1/72 inch) to [dots] at the given resolution

Parameters
[in]ptsPostscript Points to convert
[in]resolutionDots Per Inch (DPI)
Returns
Dot count

◆ mm_to_pixel()

static unsigned mm_to_pixel ( unsigned  mm,
unsigned  resolution 
)
inlinestatic

Convert millimeter [mm] to [dots] at the given resolution

Parameters
[in]mmMillimeter to convert
[in]resolutionDots Per Inch (DPI)
Returns
Dot count

◆ pts_to_mm()

static double pts_to_mm ( double  pts)
inlinestatic

Convert Postscript [pts] (1/72 inch) to [mm]

Parameters
[in]ptsPostscript Points to convert
Returns
Value in [mm]

◆ mm_to_pts()

static double mm_to_pts ( double  mm)
inlinestatic

Convert millimeter [mm] to points [pts]

Parameters
[in]mmMillimeter to convert
Returns
Length in [pts]

◆ pixel_to_mm()

static double pixel_to_mm ( unsigned  pixel,
unsigned  resolution 
)
inlinestatic

Convert an amount of pixel at a specific resolution into [millimeter]

Parameters
[in]pixelPixel count to convert
[in]resolutionDots Per Inch (DPI)
Returns
Value in [mm]

◆ ql_driver_job_halftone_get()

enum ql_half_tone ql_driver_job_halftone_get ( struct ql_drv t)

FIXME

◆ ql_driver_job_bi_colour_medium_get()

void ql_driver_job_bi_colour_medium_get ( struct ql_drv t)

Get the current job related selection for the bi-colour medium capabilities

Parameters
[in]tFull job description

The bi-colour capability cannot be detected at runtime. So the user mus select it on a per job base to enable bi-colour print. This function checks the current selection and modifies ql_drv::bi_colour_medium_present accordingly.

◆ ql_monochome_page_print()

int ql_monochome_page_print ( struct ql_drv t)

Convert the current page into the printer's wire data format

Parameters
[in]tFull job description
Return values
0On success
-EINVALUnsupported raster input format
-ECANCELEDTermination request from outerspace

The routine loops through all lines of the current page and converts them into the printer's monochrome wire format

Note
The ql_trim::top_keep amount of lines of the input raster is printed.
In case of an error or a termination request, the print job gets terminated at the printer's side as well.
Precondition
The format was already checked, e.g. it fits into the printer's media.