/* rastertoptch is a filter to convert CUPS raster data into a Brother
* P-touch label printer command byte stream.
*
* Copyright (c) 2006 Arne John Glenstrup <panic@itu.dk>
*
* This file is part of ptouch-driver
*
* ptouch-driver is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* ptouch-driver is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY 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 ptouch-driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*
* Please note: This software is not in any way related to Brother
* Industries, Ltd., except that it is intended for use with their
* products. Any comments to this software should NOT be directed to
* Brother Industries, Ltd.
*/
/** @file
* This filter processes CUPS raster data, converting it into a byte
* stream on stdout suitable for sending directly to a label printer
* of the Brother P-touch family.
*
* @version 1.2
* @author Arne John Glenstrup <panic@itu.dk>
* @date 2006
* <h2>Invocation</h2>
* The filter is invoked thus:
*
* rastertoptch [options] {job-options}
*
* @param options See "rastertoptch --help".
* @param job-options String representations of the job template
* parameters, separated by spaces. Boolean attributes
* are provided as "name" for true values and "noname"
* for false values. All other attributes are provided
* as "name=value".
*
* Available job-options (default values in [brackets]):
*
* @param PixelXfer=ULP|RLE|BIP Use uncompressed line printing (ULP),
* run-length encoding (RLE) or bit
* image printing (BIP) when emitting
* pixel data [ULP]
* @param PrintQuality=High|Fast Use high quality or fast printing [High]
* @param HalfCut Perform half-cut (crack & peel) when
* cutting [noHalfCut]
* @param BytesPerLine=N Emit N bytes per line [90]
* @param Align=Right|Center Pixel data alignment on tape [Right]
* @param MediaType=Tape|Labels Media Type
* @param PrintDensity=1|...|5 Print density level: 1=light, 5=dark
* @param ConcatPages Output all pages in one page [noConcatPages]
* @param SoftwareMirror Make the filter mirror pixel data
* if MirrorPrint is requested [noSoftwareMirror]
* @param LabelPreamble Emit preamble containing print quality,
* roll/label type, tape width, label height,
* and pixel lines [noLabelPreamble]
*
* Information about resolution, mirror print, negative
* print, cut media, advance distance (feed) is extracted from the
* CUPS raster page headers given in the input stream.
*
* LabelPreamble should usually not be used for the PT series printers.
*
* <h2>Output</h2>
* Each invocation of this filter is one job, containing a number of
* pages, each page containing a number of lines, each line consisting
* of a number of pixel bytes.
*
* Output consists of job-related printer initialisation commands,
* followed by a number of pages, each page consisting of page-related
* commands, followed by raster line data. Each page is followed by a
* finish page or (after the final page) finish job command.
*
* The following printer command language, printer, and tape
* information has been deduced from many sources, but is not official
* Brother documentation and may thus contain errors. Please send any
* corrections based on actual experience with these printers to the
* maintainer.
*
* <h3>Job-related commands</h3>
* <table>
* <tr><th>Byte sequence</th><th>Function</th><th>Description</th></tr>
* <tr><td>ESC @ (1b 40)</td>
<td>Initialise</td><td>Clear print buffer</td></tr>
* <tr><td>ESC i D # (1b 69 44 ##)
* <td>Set print density</td>
* <td>bit 0-3: 0=no change, 1-5=density level</td></tr>
* <tr><td>ESC i K # (1b 69 4b ##)
* <td>Set half cut</td>
* <td>bit 2: 0=full cut, 1=half cut</td></tr>
* <tr><td>ESC i a ## (1b 69 52 ##)</td>
* <td>Set transfer mode (legacy: ESC i R ##)</td>
* <td>##: Transfer mode: 1=Raster mode</td></tr>
* <tr><td>M ## (4d ##)</td>
* <td>Set compression</td>
* <td>##: Compression type: 2=RLE</td></tr>
* </table>
*
* <h3>Page-related commands</h3>
* <table>
* <tr><th>Byte sequence</th><th>Function</th><th>Description</th></tr>
* <tr><td>ESC i c #1 #2 #3 NUL #4 <br>(1b 63 #1 #2 #3 00 #4)
* <td>Set width & resolution</td>
* <td>360x360DPI: #1 #2 #4 = 0x84 0x00 0x00<br>
* 360x720DPI: #1 #2 #4 = 0x86 0x09 0x01<br>
* #3: Tape width in mm</td></tr>
* <tr><td>ESC i M # <br>(1b 69 4d ##)</td>
* <td>Set mode</td>
* <td>bit 0-4: Feed amount (default=large): 0-7=none, 8-11=small,
* 12-25=medium, 26-31=large<br>
* bit 6: Auto cut/cut mark (default=on): 0=off, 1=on<br>
* bit 7: Mirror print (default=off): 0=off, 1=on.
* (note that it seems that QL devices do not reverse the
* data stream themselves, but rely on the driver doing
* it!)</td></tr>
* <tr><td>ESC i z #1 #2 #3 #4 #5 #6 NUL NUL NUL NUL<br>
* (1b 69 7a #1 #2 #3 #4 #5 #6 00 00 00 00)</td>
* <td>Set media & quality</td>
* <td>#1, bit 6: Print quality: 0=fast, 1=high<br>
* #2, bit 0: Media type: 0=continuous roll,
* 1=pre-cut labels<br>
* #3: Tape width in mm<br>
* #4: Label height in mm (0 for continuous roll)<br>
* #5 #6: Page consists of N=#5+256*#6 pixel lines</td></tr>
* <tr><td>ESC i d #1 #2 <br>(1b 69 64 #1 #2)</td>
* <td>Set margin</td>
* <td>Set size of top and bottom margin to N=#1+256*#2 pixels</td></tr>
* <tr><td>FF (0c)</td>
* <td>Form feed</td>
* <td>Print buffer data without ejecting.</td></tr>
* <tr><td>SUB (1a)</td>
* <td>Eject</td>
* <td>Print buffer data and ejects.</td></tr>
* </table>
*
* <h3>Line-related commands</h3>
* <table>
* <tr><th>Byte sequence</th><th>Function</th><th>Description</th></tr>
* <tr><td>G #1 #2 ...data... <br>(47 #1 #2 ...data...)</td>
* <td>Send raster line</td>
* <td>data consists of
* N=#1+256*#2 bytes of RLE compressed raster data.
* </td></tr>
* <tr><td>Z (5a)</td>
* <td>Advance tape</td><td>Print 1 empty line</td></tr>
* <tr><td>g #1 #2 ...data... <br>(67 #1 #2 ...data...)</td>
* <td>Send raster line</td>
* <td>data consists of
* N=#2 bytes of uncompressed raster data.</td></tr>
* <tr><td>ESC * ' #1 #2 ...data... <br>(1b 2a 27 #1 #2 ...data...)</td>
* <td>Bit image printing (BIP)</td>
* <td>Print N=#1+256*#2 lines of 24 pixels; data consists of 3*N
* bytes</td></tr>
* </table>
*
* <h3>Compressed-data-related commands (RLE)</h3>
* <table>
* <tr><th>Byte sequence</th><th>Function</th><th>Description</th></tr>
* <tr><td>#1 ...data...</td>
* <td>#1 >= 0: Print uncompressed</td>
* <td>data consists of 1+#1 uncompressed bytes</td></tr>
* <tr><td>#1 #2</td>
* <td>#1 < 0: Print compressed</td>
* <td>#2 should be printed 1-#1 times</td></tr>
* </table>
* #1 is represented as a 2-complement signed integer.
*
* <h2>Printer model characteristics</h2>
* The following table lists for each model what kind of cutter it has
* (manual, auto, half cut), what kind of pixel data transfer mode it
* requires, its resolution, number of print head pixels, number of
* bytes of pixel data that must be transmitted per line (regardless
* of actual tape width!), and what kinds of tape it can take.
*
* For PC models, pixel data must be centered, so narrow tapes require
* padding raster data with zero bits on each side. For QL models,
* labels are left-aligned, so pixel data must be right aligned, so
* narrow tapes require padding raster data with zero bits at the end.
*
* For PC-PT, only the central 24 pixels (= 3,4mm!) can be used for
* pixel-based graphics. It might be possible to print several strips
* of 24 pixels side-by side by issuing CR and line-positioning
* commands. That is currently not supported, let alone attempted,
* with this driver.
*
* <table>
* <tr><th>Model <th>Cutter <th>Xfer<th>DPI<th>Pixels<th>Bytes<th>Tape
* <tr><td>QL-500 <td>manual <td>ULP<td>300<td>720<td>90<td>DK12-62mm
* <tr><td>QL-550 <td>auto <td>ULP<td>300<td>720<td>90<td>DK12-62mm
* <tr><td>QL-570 <td>auto <td>ULP<td>300<td>720<td>90<td>DK12-62mm
* <tr><td>QL-650TD <td>auto <td>ULP<td>300<td>720<td>90<td>DK12-62mm
* <tr><td>PT-PC <td>auto <td>BIP<td>180<td>128<td> 3<td>TZ6-24mm
* <tr><td>PT-18R <td>auto <td>RLE<td>180<td>128<td>16<td>TZ6-18mm
* <tr><td>PT-550A <td>auto <td>RLE<td>180<td>128<td>16<td>TZ6-36mm
* <tr><td>PT-P700 <td>auto <td>RLE<td>180<td>128<td>16<td>TZ4-24mm
* <tr><td>PT-P900W <td>auto <td>RLE<td>
* 360x720<td>384<td>70<td>TZ4-36mm
* <tr><td>PT-1500PC<td>manual <td>RLE<td>180<td>112<td>14<td>TZ6-24mm
* <tr><td>PT-1950 <td>auto <td>RLE<td>180<td>128<td>16<td>TZ6-18mm
* <tr><td>PT-1950VP<td>auto <td>RLE<td>180<td>112<td>14<td>TZ6-18mm
* <tr><td>PT-1960 <td>auto <td>RLE<td>180<td> 96<td>12<td>TZ6-18mm
* <tr><td>PT-2300 <td>auto <td>RLE<td>180<td>112<td>14<td>TZ6-18mm
* <tr><td>PT-2420PC<td>manual <td>RLE<td>180<td>128<td>16<td>TZ6-24mm
* <tr><td>PT-2430PC<td>auto <td>RLE<td>180<td>128<td>16<td>TZ6-24mm
* <tr><td>PT-2450DX<td>auto <td>RLE<td>180<td>128<td>16<td>TZ6-24mm
* <tr><td>PT-2500PC<td>auto <td>RLE<td>180<td>128<td>16<td>TZ6-24mm
* <tr><td>PT-2600 <td>auto <td>RLE<td>180<td>128<td>16<td>TZ,AV6-24mm
* <tr><td>PT-2610 <td>auto <td>RLE<td>180<td>128<td>16<td>TZ,AV6-24mm
* <tr><td>PT-3600 <td>auto/half<td>RLE<td>360<td>384<td>48<td>TZ,AV6-36mm
* <tr><td>PT-9200DX<td>auto/half<td>RLE<td>360<td>384<td>48<td>TZ6-36mm
* <tr><td>PT-9200PC<td>auto/half<td>RLE<td>360<td>384<td>48<td>TZ6-36mm
* <tr><td>PT-9400 <td>auto/half<td>RLE<td>360<td>384<td>48<td>TZ6-36mm
* <tr><td>PT-9500PC<td>auto/half<td>RLE<td>360<br>
360x720<td>384<td>48<td>TZ,AV6-36mm
* <tr><td>PT-9600 <td>auto/half<td>RLE<td>360<td>384<td>48<td>TZ,AV6-36mm
* </table>
*
* <h2>Tape characteristics</h2>
* <table>
* <tr><th>Tape width
* <th colspan=2>Print area <th>Margins</th><th>DPI</th></tr>
* <tr><td>62mm<td>61.0mm<td>720pixels</td><td>0.5mm</td><td>300</td></tr>
* <tr><td>36mm<td>27.1mm<td>384pixels</td><td>4.5mm</td><td>360</td></tr>
* <tr><td>24mm<td>18.0mm<td>128pixels</td><td>3mm</td><td>180</td></tr>
* <tr><td>18mm<td>12.0mm<td> 85pixels</td><td>3mm</td><td>180</td></tr>
* <tr><td>12mm<td> 8.0mm<td> 57pixels</td><td>2mm</td><td>180</td></tr>
* <tr><td> 9mm<td> 6.9mm<td> 49pixels</td><td>1mm</td><td>180</td></tr>
* <tr><td> 6mm<td> 3.9mm<td> 28pixels</td><td>1mm</td><td>180</td></tr>
* </table>
*
* <h2>Notes</h2>
* - Pixels bytes sent are printed from right to left, with bit 7
* rightmost!
* - Bit image printing (BIP) using "ESC * ' #1 #2 ...data..."
* probably only works for the PT-PC model.
* - QL Printer documentation might state that the print area is less
* than 61mm, which is probably to ensure that printed pixels stay
* within the tape even if it is not precisely positioned. The
* print head really IS 720 pixels.
* - The Brother Raster Command Reference for various printers document that
* the "ESC i a 0x01" command sequence should be used to switch printers into
* raster mode. That's the command sequence at least some of the Windows
* drivers use (tested on PT-P700 and PT-P900W). It's unclear which (if any)
* printers require "ESC i R 0x01", so keep using that for old printers and
* switch to "ESC i a 0x01" for printers documented to use "ESC i a 0x01".
*/
#include <config.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <math.h>
#include <float.h>
#include <cups/raster.h>
#include <cups/cups.h>
#include <limits.h>
#include <stdbool.h>
#include <getopt.h>
#include <libgen.h>
static const char* progname;
/** Length of a PostScript point in mm */
#define MM_PER_PT (25.4 / 72.0)
/** Printer code: Eject */
#define PTC_EJECT 0x1a
/** Printer code: Form feed */
#define PTC_FORMFEED 0x0c
/** ASCII escape value */
#define ESC 0x1b
/**
* Pixel transfer mode type.
*/
typedef enum {
ULP, /**< Uncompressed line printing */
RLE, /**< Run-length encoding */
BIP, /**< Bit image printing */
} xfer_t;
/**
* Pixel data alignment type.
*/
typedef enum {RIGHT, CENTER} align_t;
typedef enum {TAPE, LABELS} media_t;
/** CUPS Raster line buffer. */
unsigned char* buffer;
/** Buffer holding line data to emit to the printer. */
unsigned char* emit_line_buffer;
/** Buffer holding RLE line data to emit to the printer. */
unsigned char* rle_buffer = NULL;
/** Pointer to first free pos in rle_buffer. */
unsigned char* rle_buffer_next = NULL;
/** Size of rle_buffer. */
unsigned long rle_alloced = 0;
/** Number of empty lines (input data only zeros) waiting to be stored */
int empty_lines = 0;
/** Number of pixel lines waiting to be emitted. */
unsigned lines_waiting = 0;
/** Threshold for flushing waiting lines to printer. */
unsigned max_lines_waiting = INT_MAX;
struct progress {
unsigned int page;
unsigned int height;
unsigned int completed;
} progress;
void report_progress (int signal) {
static struct progress old;
char buffer[64];
int len;
if ((old.page == progress.page &&
old.completed == progress.completed) ||
progress.height == 0)
return;
old = progress;
len = snprintf (buffer, sizeof (buffer),
"INFO: printing page %u, %u%% done\n",
progress.page,
progress.completed * 100 / progress.height);
if (len > 0) {
if (write (2, buffer, len) != len)
/* ignore*/ ;
}
}
/**
* Struct type for holding all the job options.
*/
typedef struct {
xfer_t pixel_xfer; /**< pixel transfer mode */
cups_bool_t print_quality_high; /**< print quality is high */
bool auto_cut; /**< auto cut */
bool half_cut; /**< half cut */
bool cut_mark; /**< cut mark */
int cut_label; /**< cut each N label */
bool chain_printing; /**< chain printing */
bool mirror_print; /**< mirror print */
bool pt_series; /**< pt series printer */
bool ql_series; /**< ql series printer */
int bytes_per_line; /**< bytes per line (print head width) */
align_t align; /**< pixel data alignment */
media_t media; /**< media type (TAPE or LABELS) */
bool software_mirror; /**< mirror pixel data if mirror printing */
int print_density; /**< printing density (1=light, ..., 5=dark, 0=don't change) */
int legacy_xfer_mode; /**< legacy transfer mode (-1 = don't set) */
int xfer_mode; /**< transfer mode (-1 = don't set) */
bool label_preamble; /**< emit ESC i z ... */
bool label_recoyery; /**< set PI_RECOVER flag */
bool last_page_flag; /**< flag last page in ESC i z */
bool legacy_hires;
bool concat_pages; /**< remove interlabel margins */
float min_margin; /**< minimum top and bottom margin */
float margin; /**< top and bottom margin */
int status_notification; /**< automatic status notification */
unsigned int page; /**< The current page number */
bool last_page; /**< This is the last page */
} job_options_t;
/**
* Parse command-line cups options
* @param cups_options cups option string
* @param options default options to use
* @return parsed options
*/
job_options_t
parse_job_options (const char* str) {
job_options_t options = {
/* pixel_xfer */ RLE,
/* print_quality_high */ true,
/* auto_cut */ false,
/* half_cut */ false,
/* cut_mark */ false,
/* cut_label */ -1,
/* chain_printing */ true,
/* mirror_print */ false,
/* pt_series */ false,
/* ql_series */ false,
/* bytes_per_line */ 90,
/* align */ RIGHT,
/* media */ TAPE,
/* software_mirror*/ false,
/* print_density */ 0,
/* legacy_xfer_mode (don't set) */ -1,
/* xfer_mode (don't set) */ -1,
/* label_preamble */ false,
/* label_recovery */ false,
/* last_page_flag */ false,
/* legacy_hires */ false,
/* concat_pages */ false,
/* min_margin */ 0.0,
/* margin */ 0.0,
/* status_notification (don't set) */ -1,
};
struct int_option {
const char *name;
int *value;
int min;
int max;
};
struct int_option int_options [] = {
{ "BytesPerLine", &options.bytes_per_line, 1, 255 },
{ "CutLabel", &options.cut_label, 0, 255 },
{ "PrintDensity", &options.print_density, 0, 5 },
{ "LegacyTransferMode", &options.legacy_xfer_mode, 0, 255 },
{ "TransferMode", &options.xfer_mode, 0, 255 },
{ "StatusNotification", &options.status_notification, 0, 1 },
{ }
};
struct bool_option {
const char *name;
bool *value;
};
struct bool_option bool_options [] = {
{ "AutoCut", &options.auto_cut },
{ "ChainPrinting", &options.chain_printing },
{ "ConcatPages", &options.concat_pages },
{ "CutMark", &options.cut_mark },
{ "HalfCut", &options.half_cut },
{ "LabelPreamble", &options.label_preamble },
{ "LabelRecovery", &options.label_recoyery },
{ "LastPageFlag", &options.last_page_flag },
{ "LegacyHires", &options.legacy_hires },
{ "MirrorPrint", &options.mirror_print },
{ "PT", &options.pt_series },
{ "QL", &options.ql_series },
{ "SoftwareMirror", &options.software_mirror },
{ }
};
struct float_option {
const char *name;
float *value;
float min;
float max;
};
struct float_option float_options [] = {
{ "MinMargin", &options.min_margin, 0, FLT_MAX },
{ "Margin", &options.margin, 0, FLT_MAX },
{ }
};
cups_option_t* cups_options;
int num_options = cupsParseOptions (str, 0, &cups_options);
while (num_options-- > 0) {
const char *name = cups_options[num_options].name;
const char *value = cups_options[num_options].value;
if (strcasecmp (name, "PixelXfer") == 0) {
if (value) {
if (strcasecmp (value, "RLE") == 0) {
options.pixel_xfer = RLE;
continue;
}
if (strcasecmp (value, "BIP") == 0) {
options.pixel_xfer = BIP;
continue;
}
if (strcasecmp (value, "ULP") == 0) {
options.pixel_xfer = ULP;
continue;
}
}
fprintf (stderr, "ERROR: The value of %s "
"must be RLE, BIP or ULP\n", name);
exit (2);
continue;
}
if (strcasecmp (name, "PrintQuality") == 0) {
if (value) {
if (strcasecmp (value, "High") == 0) {
options.print_quality_high = true;
continue;
}
if (strcasecmp (value, "Fast") == 0) {
options.print_quality_high = false;
continue;
}
}
fprintf (stderr, "ERROR: The value of %s "
"must be High or Fast\n", name);
exit (2);
continue;
}
if (strcasecmp (name, "Align") == 0) {
if (value) {
if (strcasecmp (value, "Right") == 0) {
options.align = RIGHT;
continue;
}
if (strcasecmp (value, "Center") == 0) {
options.align = CENTER;
continue;
}
}
fprintf (stderr, "ERROR: the value of %s "
"must be Right or Center\n", name);
exit (2);
continue;
}
if (strcasecmp (name, "MediaType") == 0) {
if (value) {
if (strcasecmp (value, "Tape") == 0) {
options.media = TAPE;
continue;
}
}
if (value) {
if (strcasecmp (value, "Labels") == 0) {
options.media = LABELS;
continue;
}
}
fprintf (stderr, "ERROR: the value of %s "
"must be Tape or Labels\n", name);
exit (2);
continue;
}
struct int_option *int_option;
for (int_option = int_options; int_option->name; int_option++) {
if (strcasecmp (name, int_option->name) == 0) {
if (value) {
errno = 0;
char *rest;
long v = strtol (value, &rest, 0);
if (!errno && rest != value && *rest == '\0' &&
v >= int_option->min && v <= int_option->max) {
*int_option->value = v;
break;
}
}
fprintf (stderr, "%s\n", value);
fprintf (stderr, "ERROR: The value of %s must be an "
"integer between %d and %d\n",
name, int_option->min, int_option->max);
exit (2);
}
}
if (int_option->name)
continue;
struct bool_option *bool_option;
for (bool_option = bool_options; bool_option->name; bool_option++) {
if (strcasecmp (name, bool_option->name) == 0) {
*bool_option->value = strcasecmp (value, "true") == 0;
break;
}
}
if (bool_option->name)
continue;
struct float_option *float_option;
for (float_option = float_options; float_option->name; float_option++) {
if (strcasecmp (name, float_option->name) == 0) {
if (value) {
errno = 0;
char *rest;
float v = strtof (value, &rest);
if (!errno && rest != value && *rest == '\0' &&
v >= float_option->min && v <= float_option->max) {
*float_option->value = v;
break;
}
}
fprintf (stderr, "%s\n", value);
fprintf (stderr, "ERROR: The value of %s must be an "
"integer between %f and %f\n",
name, float_option->min, float_option->max);
exit (2);
}
}
if (float_option->name)
continue;
fprintf (stderr, "ERROR: Unknown option %s\n", name);
exit (2);
}
/* Release memory allocated for CUPS options struct */
cupsFreeOptions (num_options, cups_options);
return options;
}
void cancel_job (int signal);
/**
* Prepare for a new page by setting up signalling infrastructure and
* memory allocation.
* @param cups_buffer_size Required size of CUPS raster line buffer
* @param device_buffer_size Required size of device pixel line buffer
*/
void
page_prepare (unsigned cups_buffer_size, unsigned device_buffer_size) {
/* Set up signalling to handle print job cancelling */
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
struct sigaction action;
#endif
#ifdef HAVE_SIGSET
sigset (SIGTERM, cancel_job);
#elif defined(HAVE_SIGACTION)
memset (&action, 0, sizeof (action));
sigemptyset (&action.sa_mask);
action.sa_handler = cancel_job;
sigaction (SIGTERM, &action, NULL);
#else
signal (SIGTERM, cancel_job);
#endif
/* Allocate line buffer */
buffer = malloc (cups_buffer_size);
emit_line_buffer = malloc (device_buffer_size);
if (!buffer || !emit_line_buffer) {
fprintf
(stderr,
"ERROR: Cannot allocate memory for raster line buffer\n");
exit (1);
}
}
/**
* Clean up signalling and memory after emitting a page
*/
void
page_end () {
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
struct sigaction action;
#endif
#ifdef HAVE_SIGSET
sigset (SIGTERM, SIG_IGN);
#elif defined(HAVE_SIGACTION)
memset (&action, 0, sizeof (action));
sigemptyset (&action.sa_mask);
action.sa_handler = SIG_IGN;
sigaction (SIGTERM, &action, NULL);
#else
signal (SIGTERM, SIG_IGN);
#endif
/* Release line buffer memory */
free (buffer);
free (emit_line_buffer);
}
/**
* Cancel print job.
*/
void
cancel_job (int signal) {
/* Emit page end & eject marker */
putchar (PTC_EJECT);
page_end ();
if (rle_buffer) free (rle_buffer);
exit (0);
}
/**
* Emit printer command codes at start of print job.
* This function does not emit P-touch page specific codes.
* @param job_options Job options
*/
void
emit_job_cmds (job_options_t* job_options) {
/* Send 350 bytes of NULL to clear print buffer in case an error occurred
* previously. The printer ignores 0x00 bytes if it's waiting for a command.
*/
int i;
for (i=0; i<350; i++) {
putchar(0x00);
}
/* Initialise printer */
putchar (ESC); putchar ('@');
/* Emit transfer mode selection command if required */
int legacy_xfer_mode = job_options->legacy_xfer_mode;
if (legacy_xfer_mode >= 0 && legacy_xfer_mode < 0x100) {
putchar (ESC); putchar ('i'); putchar ('R'); putchar (legacy_xfer_mode);
}
int xfer_mode = job_options->xfer_mode;
if (xfer_mode >= 0 && xfer_mode < 0x100) {
putchar (ESC); putchar ('i'); putchar ('a'); putchar (xfer_mode);
}
if (job_options->status_notification != -1) {
putchar (ESC); putchar ('i'); putchar ('!'), putchar (job_options->status_notification);
}
}
/**
* Emit quality, roll fed media, and label size command codes.
* @param job_options Current job options
* @param header Current page header
* @param image_height_px Number of pixel lines in current page
*/
void
emit_quality_rollfed_size (job_options_t* job_options,
cups_page_header2_t* header,
unsigned image_height_px) {
const unsigned char PI_KIND = 0x02; // Paper type (roll fed media bit) is valid
const unsigned char PI_WIDTH = 0x04; // Paper width is valid
const unsigned char PI_LENGTH = 0x08; // Paper length is valid
const unsigned char PI_QUALITY = 0x40;
const unsigned char PI_RECOVER = 0x80;
unsigned char valid = PI_WIDTH;
if (job_options->label_recoyery)
valid |= PI_RECOVER;
/* Get tape width in mm */
unsigned int tape_width_mm = lrint (header->cupsPageSize [0] * MM_PER_PT);
if (tape_width_mm > 0xff) {
fprintf (stderr,
"ERROR: Page width (%umm) exceeds 255mm\n",
tape_width_mm);
tape_width_mm = 0xff;
}
unsigned char media_type = 0;
unsigned int tape_length_mm = 0;
if (job_options->ql_series) {
if (job_options->print_quality_high == CUPS_TRUE)
valid |= PI_QUALITY;
valid |= PI_KIND;
switch (job_options->media) {
case TAPE:
media_type = 0x0A;
break;
case LABELS:
media_type = 0x0B;
valid |= PI_LENGTH;
tape_length_mm = lrint (header->cupsPageSize [1] * MM_PER_PT);
break;
}
if (tape_length_mm > 0xff) {
fprintf (stderr,
"ERROR: Page height (%umm) exceeds 255mm; use continuous-length tape\n",
tape_length_mm);
tape_length_mm = 0xff;
}
}
if (job_options->pt_series) {
/*
* On PT series printers, the media type must be set to 0x09 for
* high-resolution and draft printing, but not for normal resolution
* printing.
*/
if (header->HWResolution [0] == 360 &&
(header->HWResolution [1] == 180 ||
header->HWResolution [1] == 720)) {
valid |= PI_KIND;
media_type = 0x09;
}
}
unsigned char which_page = job_options->page > 1;
if (job_options->last_page_flag && job_options->last_page)
which_page = 2;
/* Combine & emit printer command code */
putchar (ESC); putchar ('i'); putchar ('z');
putchar (valid);
putchar (media_type);
putchar (tape_width_mm & 0xff);
putchar (tape_length_mm);
putchar (image_height_px & 0xff);
putchar ((image_height_px >> 8) & 0xff);
putchar ((image_height_px >> 16) & 0xff);
putchar ((image_height_px >> 24) & 0xff);
putchar (which_page);
putchar (0x00); // n10, always 0
}
/**
* Emit printer command codes at start of page for options that have
* changed.
* @param job_options Job options
* @param header Current page header
*/
void
emit_page_cmds (job_options_t* job_options,
cups_page_header2_t* header) {
float pt2px = header->HWResolution [1] / 72.0;
int tape_width_mm = -1;
/* Emit print density selection command if required */
int density = job_options->print_density;
switch (density) {
case 1: case 2: case 3: case 4: case 5:
putchar (ESC); putchar ('i'); putchar ('D'); putchar (density);
break;
default: break;
}
if (job_options->legacy_hires) {
/* Set width and resolution */
/* We only know how to select 360x360DPI or 360x720DPI */
if (header->HWResolution [0] == 360
&& (header->HWResolution [1] == 360
|| header->HWResolution [1] == 720)) {
/* Get tape width in mm */
tape_width_mm = lrint (header->cupsPageSize [0] * MM_PER_PT);
if (tape_width_mm > 0xff) {
fprintf (stderr,
"ERROR: Page width (%umm) exceeds 255mm\n",
tape_width_mm);
tape_width_mm = 0xff;
}
/* Emit printer commands */
putchar (ESC); putchar ('i'); putchar ('c');
if (header->HWResolution [1] == 360) {
putchar (0x84); putchar (0x00); putchar (tape_width_mm & 0xff);
putchar (0x00); putchar (0x00);
} else {
putchar (0x86); putchar (0x09); putchar (tape_width_mm & 0xff);
putchar (0x00); putchar (0x01);
}
}
}
char various_mode = 0;
if (job_options->auto_cut || job_options->cut_mark)
various_mode |= 0x40;
if (job_options->mirror_print && !job_options->software_mirror)
various_mode |= 0x80;
putchar (ESC); putchar ('i'); putchar ('M'); putchar (various_mode);
char advanced_mode = 0;
if (!job_options->legacy_hires) {
if (header->HWResolution [0] == 360) {
if (header->HWResolution [1] == 180)
advanced_mode |= 0x01; /* draft printing */
if (header->HWResolution [1] == 720)
advanced_mode |= 0x40; /* hires printing */
}
if (header->HWResolution [0] == 300) {
if (header->HWResolution [1] == 600)
advanced_mode |= 0x40; /* hires printing */
}
}
if (job_options->half_cut)
advanced_mode |= 0x04;
if (!job_options->chain_printing)
advanced_mode |= 0x08;
putchar (ESC); putchar ('i'); putchar ('K'); putchar (advanced_mode);
if (job_options->cut_label != -1) {
putchar (ESC); putchar ('i'); putchar('A'); putchar (job_options->cut_label);
}
float margin = 0.0;
if (job_options->media != LABELS)
margin += job_options->min_margin + job_options->margin;
unsigned feed = lrint (margin * pt2px);
putchar (ESC); putchar ('i'); putchar ('d');
putchar (feed & 0xff); putchar ((feed >> 8) & 0xff);
/* Set pixel data transfer compression */
if (job_options->pixel_xfer == RLE) {
putchar ('M'); putchar (0x02);
}
/* Emit number of raster lines to follow if using BIP */
if (job_options->pixel_xfer == BIP) {
unsigned image_height_px = lrint (header->cupsPageSize [1] * pt2px);
putchar (ESC); putchar (0x2a); putchar (0x27);
putchar (image_height_px & 0xff);
putchar ((image_height_px >> 8) & 0xff);
}
}
/** mirror [i] = bit mirror image of i.
* I.e., (mirror [i] >> j) & 1 == (i >> (7 - j)) & 1 for 0 <= j <= 7
*/
const unsigned char mirror [0x100] = {
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
};
/**
* Generate a buffer of pixel data ready to emit.
* Requirement: buflen + right_padding_bytes
* + (shift > 0 ? 1 : 0) <= bytes_per_line
* @param in_buffer Buffer containing raster data in
* left-to-right order
* @param out_buffer Buffer for returning generated line in
* right-to-left order; must be
* bytes_per_line long
* @param buflen in_buffer length
* @param bytes_per_line Number of pixel bytes to generate
* @param right_padding_bytes Number of zero bytes to pad
* with to the right of pixels
* @param shift Number of bits to shift left
* If do_mirror is false and shift < 0
* Then shift right -shift bits
* @param do_mirror Mirror in_buffer pixel data
* @param xormask The XOR mask for negative printing
* @return 0 if entire line is empty (zeros)
* nonzero if line contains nonzero pixels
*/
static inline int
generate_emit_line (unsigned char* in_buffer,
unsigned char* out_buffer,
int buflen,
unsigned char bytes_per_line,
int right_padding_bytes,
int shift,
int do_mirror,
unsigned char xormask) {
/* Generate right padding zero bytes */
memset (out_buffer, xormask, right_padding_bytes);
unsigned int nonzero = 0;
int j = right_padding_bytes;
/* Copy pixel data from in_buffer to out_buffer, */
/* shifted and mirrored if required */
unsigned int box = 0; /* Box for shifting pixel data left */
int i;
if (do_mirror)
if (shift) {
for (i = 0; i < buflen; i++) {
unsigned int data = in_buffer [i]; nonzero |= data;
box |= data << shift;
out_buffer [j++] = (box & 0xff) ^ xormask;
box >>= 8;
}
out_buffer [j++] = box & 0xff;
} else
for (i = 0; i < buflen; i++) {
unsigned char data = in_buffer [i]; nonzero |= data;
out_buffer [j++] = data ^ xormask;
}
else
if (shift) {
if (buflen > 0) {
if (shift < 0) {
box = in_buffer [buflen - 1] >> -shift; nonzero |= box;
shift += 8;
} else {
box = in_buffer [buflen - 1] << shift; nonzero |= box;
out_buffer [j++] = (mirror [box & 0xff]) ^ xormask;
box >>= 8;
}
for (i = buflen - 2; i >= 0; i--) {
unsigned data = in_buffer [i]; nonzero |= data;
box |= data << shift;
out_buffer [j++] = (mirror [box & 0xff]) ^ xormask;
box >>= 8;
}
out_buffer [j++] = (mirror [box & 0xff]) ^ xormask;
}
} else
for (i = buflen - 1; i >= 0; i--) {
unsigned char data = in_buffer [i]; nonzero |= data;
out_buffer [j++] = (mirror [data]) ^ xormask;
}
/* Generate left padding bytes */
memset (out_buffer + j, xormask, bytes_per_line - j);
return nonzero != 0;
}
/**
* Emit lines waiting in RLE buffer.
* Resets global variable rle_buffer_next to rle_buffer,
* and lines_waiting to zero.
* @param job_options Job options
* @param header Page header
*/
static inline void
flush_rle_buffer (job_options_t* job_options,
cups_page_header2_t* header) {
if (lines_waiting > 0) {
if (job_options->label_preamble)
emit_quality_rollfed_size (job_options, header, lines_waiting);
xfer_t pixel_xfer = job_options->pixel_xfer;
int bytes_per_line = job_options->bytes_per_line;
switch (pixel_xfer) {
case RLE: {
fwrite (rle_buffer, sizeof (char), rle_buffer_next - rle_buffer, stdout);
break;
}
case ULP:
case BIP: {
unsigned char* p = rle_buffer;
unsigned emitted_lines = 0;
while (rle_buffer_next - p > 0) {
if (pixel_xfer == ULP) {
/* ULP is only used by QL printers */
putchar ('g'); putchar (0x00); putchar (bytes_per_line);
}
int emitted = 0;
int linelen;
switch (*p++) {
case 'G':
linelen = *p++;
linelen += ((int)(*p++)) << 8;
goto expand_g_or_G;
case 'g':
linelen = ((int)(*p++)) << 8;
linelen += *p++;
expand_g_or_G:
while (linelen > 0) {
signed char l = *p++; linelen--;
if (l < 0) { /* emit repeated data */
char data = *p++; linelen--;
emitted -= l; emitted++;
for (; l <= 0; l++) putchar (data);
} else { /* emit the l + 1 following bytes of data */
fwrite (p, sizeof (char), l + 1, stdout);
p += l; p++;
linelen -= l; linelen--;
emitted += l; emitted++;
}
}
if (emitted > bytes_per_line)
fprintf (stderr,
"ERROR: Emitted %d > %d bytes for one pixel line!\n",
emitted, bytes_per_line);
/* No break; fall through to next case: */
case 'Z':
for (; emitted < bytes_per_line; emitted++) putchar (0x00);
break;
default:
fprintf (stderr, "ERROR: Unknown RLE flag at %p: '0x%02x'\n",
p - 1, (int)*(p - 1));
}
emitted_lines++;
}
break;
}
default:
fprintf (stderr, "ERROR: Unknown pixel transfer mode: '%d'\n",
pixel_xfer);
}
rle_buffer_next = rle_buffer;
lines_waiting = 0;
}
}
/**
* Ensure sufficient memory available in rle buffer.
* If rle buffer needs to be extended, global variables rle_buffer and
* rle_buffer_next might be altered.
* @param job_options Job options
* @param header Page header
* @param bytes Number of bytes required.
*/
static inline void
ensure_rle_buf_space (job_options_t* job_options,
cups_page_header2_t* header,
unsigned bytes) {
unsigned long nextpos = rle_buffer_next - rle_buffer;
if (nextpos + bytes > rle_alloced) {
/* Exponential size increase avoids too frequent reallocation */
unsigned long new_alloced = rle_alloced * 2 + 0x4000;
void* p = NULL;
if (new_alloced <= 1000000)
p = realloc (rle_buffer, new_alloced * sizeof (char));
if (p) {
rle_buffer = p;
rle_buffer_next = rle_buffer + nextpos;
rle_alloced = new_alloced;
} else { /* Gain memory by flushing buffer to printer */
flush_rle_buffer (job_options, header);
if (rle_buffer_next - rle_buffer + bytes > rle_alloced) {
fprintf (stderr,
"ERROR: Out of memory when attempting to increase RLE "
"buffer from %ld to %ld bytes\n",
rle_alloced * sizeof (char),
new_alloced * sizeof (char));
exit (-1);
}
}
}
}
/** @def APPEND_MIXED_BYTES
* Macro for appending mixed-bytes run to rle_buffer */
/** @def APPEND_REPEATED_BYTE
* Macro for appending repeated-byte run to rle_buffer */
/**
* Store buffer data in rle buffer using run-length encoding.
* @param job_options Job options
* @param header Page header
* @param buf Buffer containing data to store
* @param buf_len Length of buffer
*
* Global variable rle_buffer_next is a pointer into buffer for holding RLE data.
* Must have room for at least 3 + buf_len + buf_len/128 + 1
* bytes (ensured by reallocation).
* On return, rle_buffer_next points to first unused buffer byte.
*
* This implementation enjoys the property that
* the resulting RLE is at most buf_len + buf_len/128 + 1 bytes
* long, because:
* # a repeated-byte run has a repeat factor of at least 3
* # two mixed-bytes runs never follow directly after each other,
* unless the first one is 128 bytes long
* The first property ensures that a repeated-run output sequence is
* always at least 1 byte shorter than the input sequence it
* represents. This combined with the second property means that only
* - a terminating mixed-bytes run, and
* - a mixed-bytes run of 128 bytes
* can cause the RLE representation to be longer (by 1 byte) than the
* corresponding input sequence in buf.
*/
static inline void
RLE_store_line (job_options_t* job_options,
cups_page_header2_t* header,
const unsigned char* buf, unsigned buf_len) {
ensure_rle_buf_space (job_options, header,
4 + buf_len + buf_len / 128);
unsigned char* rle_next = rle_buffer_next + 3;
/* Make room for 3 initial meta data bytes, */
/* written when actual length is known */
const unsigned char* buf_end = buf + buf_len; /* Buffer end */
const unsigned char* mix_start; /* Start of mixed bytes run */
const unsigned char* rep_start; /* End + 1 of mixed bytes run,
and start of repeated byte run */
const unsigned char* next; /* Next byte pointer,
and end + 1 of repeated byte run */
unsigned char next_val; /* Next byte value to consider */
unsigned char rep_val; /* Repeated byte value */
unsigned char nonzero = 0; /* OR of all buffer bytes */
#define APPEND_MIXED_BYTES \
if (mix_len > 128) mix_len = 128; \
*rle_next++ = mix_len - 1; \
memcpy (rle_next, mix_start, mix_len); \
rle_next += mix_len;
#define APPEND_REPEATED_BYTE \
unsigned rep_len = next - rep_start; \
*rle_next++ = (signed char)(1 - rep_len); \
*rle_next++ = rep_val;
for (mix_start = rep_start = next = buf, rep_val = next_val = *next;
next != buf_end;
next++, next_val = *next) {
/* Loop invariants at this point:
* 1) [mix_start..rep_start - 1] contains mixed bytes waiting
* to be appended to rle_buffer,
* 2) [rep_start..next - 1] contains repeats of rep_val
* waiting to be appended to rle_buffer
* 3) If next - rep_start > 2 then mix_start == rep_start
* 4) next - rep_start <= 129
* 5) rep_start - mix_start < 128
* 6) [rle_buffer_next..rle_next - 1] = RLE ([buf..mix_start - 1])
* 7) rep_val = *rep_start
* 8) next_val = *next
*/
nonzero |= next_val;
if (next - rep_start >= 129) {
/* RLE cannot represent repeated runs longer than 129 bytes */
APPEND_REPEATED_BYTE;
rep_start += rep_len;
rep_val = *rep_start;
mix_start = rep_start;
}
if (next_val == rep_val) { /* Run of repeated byte values */
if (next - rep_start == 2) {
unsigned mix_len = rep_start - mix_start;
if (mix_len > 0) {
APPEND_MIXED_BYTES;
mix_start = rep_start;
}
}
} else {
if (next - rep_start > 2) { /* End of repeated run found */
APPEND_REPEATED_BYTE;
mix_start = next;
}
rep_start = next;
rep_val = next_val;
unsigned mix_len = rep_start - mix_start;
if (mix_len >= 128) {
/* RLE cannot represent mixed runs longer than 128 bytes */
APPEND_MIXED_BYTES;
mix_start += mix_len;
}
}
}
/* Handle final bytes */
if (next - rep_start > 2) { /* Handle final repeated byte run */
APPEND_REPEATED_BYTE;
mix_start = next;
}
rep_start = next;
unsigned mix_len = rep_start - mix_start;
if (mix_len > 0) { /* Handle any remaining final mixed run */
APPEND_MIXED_BYTES;
mix_start += mix_len;
}
mix_len = rep_start - mix_start;
if (mix_len > 0) { /* Case where final mixed run is 129 bytes */
APPEND_MIXED_BYTES;
}
unsigned rle_len = rle_next - rle_buffer_next - 3;
/* Store rle line meta data (length and (non)zero status) */
if (nonzero) { /* Check for nonempty (no black pixels) line */
if (job_options->ql_series) {
rle_buffer_next [0] = 'g';
rle_buffer_next [1] = (rle_len >> 8) & 0xff;
rle_buffer_next [2] = rle_len & 0xff;
} else {
rle_buffer_next [0] = 'G';
rle_buffer_next [1] = rle_len & 0xff;
rle_buffer_next [2] = (rle_len >> 8) & 0xff;
}
rle_buffer_next = rle_next;
} else {
rle_buffer_next [0] = 'Z';
rle_buffer_next++;
}
lines_waiting++;
if (lines_waiting >= max_lines_waiting)
flush_rle_buffer (job_options, header);
}
/**
* Store a number of empty lines in rle_buffer using RLE.
* @param job_options Job options
* @param header Page header
* @param empty_lines Number of empty lines to store
* @param xormask The XOR mask for negative printing
*/
static inline void
RLE_store_empty_lines (job_options_t* job_options,
cups_page_header2_t* header,
int empty_lines,
unsigned char xormask) {
int bytes_per_line = job_options->bytes_per_line;
lines_waiting += empty_lines;
if (xormask) {
int blocks = (bytes_per_line + 127) / 128;
ensure_rle_buf_space (job_options, header,
empty_lines * blocks);
for (; empty_lines--; ) {
/* leave space for the command prefix */
rle_buffer_next += 3;
unsigned char *start = rle_buffer_next;
int len, rep_len;
for (len = bytes_per_line; len > 0; len -= rep_len) {
rep_len = len;
if (rep_len > 129) rep_len = 129;
*(rle_buffer_next++) = (signed char) (1 - rep_len);
*(rle_buffer_next++) = xormask;
}
unsigned int rle_len = rle_buffer_next - start;
if (job_options->ql_series) {
start [-3] = 'g';
start [-2] = (rle_len >> 8) & 0xff;
start [-1] = rle_len & 0xff;
} else {
start [-3] = 'G';
start [-2] = rle_len & 0xff;
start [-1] = (rle_len >> 8) & 0xff;
}
}
} else {
ensure_rle_buf_space (job_options, header, empty_lines);
for (; empty_lines--; ) *(rle_buffer_next++) = 'Z';
}
}
/**
* Emit raster lines for current page.
* @param job_options Job options
* @param ras Raster data stream
* @param header Page header
* @return 0 on success, nonzero otherwise
*/
int
emit_raster_lines (job_options_t* job_options,
cups_raster_t* ras,
cups_page_header2_t* header) {
unsigned char xormask = (header->NegativePrint ? ~0 : 0);
/* Determine whether we need to mirror the pixel data */
int do_mirror = job_options->software_mirror && job_options->mirror_print;
unsigned cupsBytesPerLine = header->cupsBytesPerLine;
unsigned cupsHeight = header->cupsHeight;
unsigned cupsWidth = header->cupsWidth;
int bytes_per_line = job_options->bytes_per_line;
unsigned buflen = cupsBytesPerLine;
/* Make sure buflen can be written as a byte */
if (buflen > 0xff) buflen = 0xff;
/* Truncate buflen if greater than bytes_per_line */
if (buflen >= bytes_per_line) buflen = bytes_per_line;
/* Calculate extra horizontal spacing pixels if the right side of */
/* cupsImagingBBox doesn't touch the cupsPageSize box */
float pt2px[] = {
header->HWResolution [0] / 72.0,
header->HWResolution [1] / 72.0
};
unsigned right_spacing_px = 0;
if (header->cupsImagingBBox[2] < header->cupsPageSize[0]) {
right_spacing_px =
(header->cupsPageSize[0] - header->cupsImagingBBox[2]) * pt2px [0];
}
/* Calculate right_padding_bytes and shift */
unsigned right_padding_bits;
if (job_options->align == CENTER) {
unsigned left_spacing_px =
header->cupsImagingBBox[0] * pt2px [0];
unsigned width = left_spacing_px + cupsWidth + right_spacing_px;
if (bytes_per_line * 8 < width)
right_padding_bits = 0;
else
right_padding_bits = (bytes_per_line * 8 - width) / 2 + right_spacing_px;
} else
right_padding_bits = right_spacing_px;
int right_padding_bytes = right_padding_bits / 8;
int shift = right_padding_bits % 8;
/* If width is not an integral number of bytes, we must shift */
/* right if we don't mirror, to ensure printing starts leftmost */
if (!do_mirror) shift -= (8 - cupsWidth % 8) % 8;
int shift_positive = (shift > 0 ? 1 : 0);
/* We cannot allow buffer+padding to exceed device width */
if (buflen + right_padding_bytes + shift_positive > bytes_per_line) {
/* We cannot allow padding to exceed device width */
if (right_padding_bytes + shift_positive > bytes_per_line)
right_padding_bytes = bytes_per_line - shift_positive;
/* Truncate buffer to fit device width */
buflen = bytes_per_line - right_padding_bytes - shift_positive;
}
/* Generate and store empty lines if the top of cupsImagingBBox */
/* doesn't touch the cupsPageSize box */
unsigned top_empty_lines = 0;
float page_size_y = header->cupsPageSize [1];
if (header->cupsImagingBBox [3] != 0
&& (!job_options->concat_pages || job_options->page == 1)) {
float top_distance_pt
= page_size_y - header->cupsImagingBBox [3];
top_empty_lines = lrint (top_distance_pt * pt2px [1]);
}
unsigned image_height_px = lrint (page_size_y * pt2px [1]);
unsigned bot_empty_lines = 0;
if (image_height_px >= top_empty_lines + cupsHeight)
bot_empty_lines = image_height_px - top_empty_lines - cupsHeight;
/*
* QL printers have a specific top and bottom margin that must be left blank
* to allow printers to skip to the next label. For continuous-length tape,
* this margin is defined as the minimum value allowed for the "ESC i d"
* command as defined in the manuals; if a smaller value is passed, the
* printer rounds that up. For die-cut labels, the margin is implicit (the
* "ESC i d" command is always passed a value of 0). For most die-cut
* labels, the margin is the same as the minimum continuous-length tape
* margin, but there are a few exceptions.
*
* PT printers only support continuous-length tape. They are documented to
* work like QL printers, but in practice, they all seem to allow a minimum
* margin of 0 as well. The minimum margin still makes sure the tape is cut
* in a blank space, so use the minimum margin amounts on those printers for
* which we have a specification.
*
* Below, we ensure that printers for which a min_margin is defined will
* always have a margin at lest that wide for continuous-length tape. For
* die-cut labels, we assume that the page margins are equal to the implicit
* margins. For page margins that are empty, we assume min_margin; in that
* case, we'll end up skipping lines at the beginning and/or end of the
* bitmap to allow for that minimum margin.
*/
unsigned top_skip = 0, bot_skip = 0;
unsigned min_feed = lrint (job_options->min_margin * pt2px [1]);
if (job_options->media == LABELS && top_empty_lines) {
top_empty_lines = 0;
} else if (top_empty_lines >= min_feed) {
top_empty_lines -= min_feed;
} else {
top_skip = min_feed - top_empty_lines;
top_empty_lines = 0;
}
if (job_options->media == LABELS && bot_empty_lines) {
bot_empty_lines = 0;
} else if (bot_empty_lines >= min_feed) {
bot_empty_lines -= min_feed;
} else {
bot_skip = min_feed - bot_empty_lines;
bot_empty_lines = 0;
}
progress.page = job_options->page;
progress.height = cupsHeight;
/* Generate and store actual page data */
empty_lines += top_empty_lines;
int y;
for (y = 0; y < cupsHeight; y++) {
/* Feedback to the user */
progress.completed = y;
/* Read one line of pixels */
if (cupsRasterReadPixels (ras, buffer, cupsBytesPerLine) < 1)
break; /* Escape if no pixels read */
if (y < top_skip || y + bot_skip >= cupsHeight)
continue;
bool nonempty_line =
generate_emit_line (buffer, emit_line_buffer, buflen, bytes_per_line,
right_padding_bytes, shift, do_mirror, xormask);
if (nonempty_line) {
if (empty_lines) {
RLE_store_empty_lines
(job_options, header, empty_lines, xormask);
empty_lines = 0;
}
RLE_store_line (job_options, header,
emit_line_buffer, bytes_per_line);
} else
empty_lines++;
}
progress.completed = cupsHeight;
report_progress (0);
if (bot_empty_lines != 0 && !job_options->concat_pages)
empty_lines += bot_empty_lines;
return 0;
}
/**
* Process CUPS raster data from input file, emitting printer data on
* stdout.
* @param job_options Pointer to print options
*/
void
process_rasterdata (job_options_t* job_options) {
cups_raster_t* ras; /* Raster stream for printing */
cups_page_header2_t headers[2]; /* Page headers */
int bytes_per_line = job_options->bytes_per_line;
cups_page_header2_t *next_header = headers;
cups_page_header2_t *header = headers + 1;
cups_page_header2_t *tmp_header;
ras = cupsRasterOpen (0, CUPS_RASTER_READ);
for (job_options->page = 1,
job_options->last_page = ! cupsRasterReadHeader2 (ras, header);
! job_options->last_page;
tmp_header = next_header,
next_header = header,
header = tmp_header,
job_options->page++) {
float pt2px[] = {
header->HWResolution [0] / 72.0,
header->HWResolution [1] / 72.0
};
fprintf (stderr, "DEBUG: %s: PageSize: %.2fx%.2f pt / %.2fx%.2f mm / %.2fx%.2f px\n",
progname,
header->cupsPageSize [0], header->cupsPageSize [1],
header->cupsPageSize [0] * MM_PER_PT, header->cupsPageSize [1] * MM_PER_PT,
header->cupsPageSize [0] * pt2px [0], header->cupsPageSize [1] * pt2px [1]);
float *bbox = header->cupsImagingBBox;
fprintf (stderr, "DEBUG: %s: ImagingBoundingBox: %.2f %.2f %.2f %.2f pt / "
"%.2f %.2f %.2f %.2f mm /"
"%.2f %.2f %.2f %.2f px\n",
progname,
bbox [0], bbox [1], bbox [2], bbox [3],
bbox [0] * MM_PER_PT, bbox [1] * MM_PER_PT,
bbox [2] * MM_PER_PT, bbox [3] * MM_PER_PT,
bbox [0] * pt2px [0], bbox [1] * pt2px [1],
bbox [2] * pt2px [0], bbox [3] * pt2px [1]);
fprintf (stderr, "DEBUG: %s: HWResolution: %dx%ddpi\n",
progname,
header->HWResolution [0], header->HWResolution [1]);
fprintf (stderr, "DEBUG: %s: Width Height: %u %u\n",
progname,
header->cupsWidth, header->cupsHeight);
fprintf (stderr, "DEBUG: %s: NegativePrint: %d\n",
progname, header->NegativePrint);
page_prepare (header->cupsBytesPerLine, bytes_per_line);
if (job_options->page == 1) {
emit_job_cmds (job_options);
emit_page_cmds (job_options, header);
}
emit_raster_lines (job_options, ras, header);
unsigned char xormask = (header->NegativePrint ? ~0 : 0);
/* Determine whether this is the last page (fetch next) */
job_options->last_page = ! cupsRasterReadHeader2 (ras, next_header);
if (! job_options->last_page) {
if (!job_options->concat_pages) {
RLE_store_empty_lines
(job_options, header, empty_lines, xormask);
empty_lines = 0;
flush_rle_buffer (job_options, header);
/* Emit page end marker without feed */
putchar (PTC_FORMFEED);
}
} else {
if (!job_options->concat_pages) {
RLE_store_empty_lines
(job_options, header, empty_lines, xormask);
empty_lines = 0;
flush_rle_buffer (job_options, header);
} else {
unsigned bot_empty_lines
= lrint (header->cupsImagingBBox [1] * pt2px [1]);
empty_lines = bot_empty_lines;
RLE_store_empty_lines
(job_options, header, empty_lines, xormask);
empty_lines = 0;
flush_rle_buffer (job_options, header);
}
/* Emit end-of-job command */
putchar (PTC_EJECT);
}
page_end ();
/* Emit page count according to CUPS requirements */
fprintf (stderr, "PAGE: %d 1\n", job_options->page);
}
}
static void help (void) {
printf ("Usage: %s [options] {job-options}\n"
"\n"
"Options:\n"
" -i, --input=NAME read from NAME instead of standard input\n"
" -o, --output=NAME write to NAME instead of standard output\n"
" -h, --help display this help and exit\n",
progname);
}
static void fail_bad_options () {
fprintf (stderr, "Try '%s --help' for more information\n", progname);
exit (2);
}
/**
* Main entry function.
* @param argc number of command line arguments plus one
* @param argv command line arguments
* @return 0 if success, nonzero otherwise
*/
int
main (int argc, char* argv []) {
progname = basename (argv[0]);
const char *input_filename = NULL;
const char *output_filename = NULL;
for (;;) {
static struct option long_options[] = {
{ "input", 1, NULL, 'i' },
{ "output", 1, NULL, 'o' },
{ "help", 0, NULL, 'h' },
{ }
};
int c = getopt_long (argc, argv, "hi:o:", long_options, NULL);
if (c == -1)
break;
switch (c) {
case 'h': /* --help */
help();
exit(0);
case 'i': /* --input=NAME */
input_filename = optarg;
break;
case 'o': /* --output=NAME */
output_filename = optarg;
break;
case '?': /* unknown option or missing argument */
fail_bad_options ();
}
}
if (optind >= argc) {
fprintf (stderr, "%s: {job-options} argument missing\n", progname);
fail_bad_options ();
}
job_options_t job_options = parse_job_options (argv [optind]);
fprintf(stderr, "DEBUG: %s: job options: %s\n", progname, argv [optind]);
if (input_filename) {
int fd = open (input_filename, O_RDONLY);
if (fd < 0) {
fprintf (stderr, "%s: %s: %s\n", progname, input_filename, strerror(errno));
exit (1);
}
dup2 (fd, 0);
close (fd);
}
if (output_filename) {
int fd = open (output_filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd < 0) {
fprintf (stderr, "%s: %s: %s\n", progname, output_filename, strerror(errno));
exit (1);
}
dup2 (fd, 1);
close (fd);
}
signal (SIGALRM, report_progress);
struct itimerval it = { };
it.it_value.tv_sec = 1;
it.it_interval.tv_sec = 1;
setitimer (ITIMER_REAL, &it, NULL);
process_rasterdata (&job_options);
return 0;
}