selphy_print/backend_dnpds40.c

5035 lines
128 KiB
C

/*
* Citizen / DNP Photo Printer CUPS backend
*
* (c) 2013-2024 Solomon Peachy <pizza@shaftnet.org>
*
* Development of this backend was sponsored by:
*
* Marco Di Antonio and [ ilgruppodigitale.com ]
* LiveLink Technology [ www.livelinktechnology.net ]
* A generous benefactor who wishes to remain anonymous
*
* The latest version of this program can be found at:
*
* https://git.shaftnet.org/gitea/slp/selphy_print.git
*
* This program 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 3 of the License, or (at your option)
* any later version.
*
* This program 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 this program; if not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0+
*
*/
//#define DNP_ONLY
//#define CITIZEN_ONLY
/* Enables caching of last print type to speed up
job pipelining. Without this we always have to
assume the worst */
//#define STATE_DIR "/tmp"
#define BACKEND dnpds40_backend
#include "backend_common.h"
#include <time.h>
/* Private data structure */
struct dnpds40_printjob {
struct dyesub_job_common common;
uint8_t *databuf;
int datalen;
uint32_t dpi;
int matte;
int cutter;
uint32_t multicut;
int fullcut;
int printspeed;
int can_rewind;
int buffcntrl;
int rows;
int is_pano;
int buf_needed;
int cut_paper;
};
#define MAX_PRINTJOB_LEN (((ctx->native_width*ctx->max_height+1024+54+10))*3+1024) /* Worst-case, YMC */
#define MAX_PANOPRINTJOB_LEN ((((ctx->native_width*ctx->max_height+1024+54+10))*3+1024)*3) /* Worst-case, YMC */
#define MFG_DNP 0
#define MFG_CITIZEN 1
#define MFG_MITSUBISHI 2
#define MFG_FUJIFILM 3
#define MFG_OTHER 4
#define USE_PANODATA_FILES
struct dnpds40_ctx {
struct dyesub_connection *conn;
int mfg; /* see MFG_* */
/* Version and whatnot */
char *serno;
char *version;
int ver_major;
int ver_minor;
int flash_mode;
/* State */
uint32_t media;
uint32_t media_subtype;
char media_text[32];
uint32_t duplex_media;
int duplex_media_status;
uint16_t media_count_new;
uint32_t last_multicut;
int last_matte;
int partialmatte;
int media_sticker;
int mediaoffset;
int correct_count;
int needs_mlot;
int pano;
struct marker marker[2];
int marker_count;
/* Printer capabilities */
uint32_t native_width;
uint32_t max_height;
int supports_600dpi;
int supports_6x9;
int supports_2x6;
int supports_3x5x2;
int supports_a4x6;
int supports_45_34;
int supports_matte;
int supports_finematte;
int supports_luster;
int supports_advmatte;
int supports_fullcut;
int supports_rewind;
int supports_standby;
int supports_keepmode;
int supports_6x4_5;
int supports_mqty_default;
int supports_iserial; /* 0 == no, 1 == configurable, 2 == always enabled */
int supports_6x6;
int supports_5x5;
int supports_counterp;
int supports_adv_fullcut;
int supports_mediaoffset;
int supports_ctrld_ext;
int supports_media_ext;
int supports_printspeed;
int supports_lowspeed;
int supports_highdensity;
int supports_systime;
int supports_mediaclassrfid;
int supports_gamma;
int supports_pano;
int supports_contpano;
};
struct dnpds40_cmd {
uint8_t esc; /* Fixed at ascii ESC, aka 0x1B */
uint8_t p; /* Fixed at ascii 'P' aka 0x50 */
uint8_t arg1[6];
uint8_t arg2[16];
uint8_t arg3[8]; /* Decimal value of arg4's length, or empty */
uint8_t arg4[]; /* Extra payload if arg3 is non-empty
Doesn't have to be sent in the same URB */
/* All unused elements are set to 0x20 (ie ascii space) */
};
#define MULTICUT_5x3_5 1
#define MULTICUT_6x4 2
#define MULTICUT_5x7 3
#define MULTICUT_6x8 4
#define MULTICUT_6x9 5
#define MULTICUT_8x10 6
#define MULTICUT_8x12 7
#define MULTICUT_8x4 8
#define MULTICUT_8x5 9
#define MULTICUT_8x6 10
#define MULTICUT_8x8 11
#define MULTICUT_6x4X2 12
#define MULTICUT_8x4X2 13
#define MULTICUT_8x5X2 14
#define MULTICUT_8x6X2 15
#define MULTICUT_8x5_8x4 16
#define MULTICUT_8x6_8x4 17
#define MULTICUT_8x6_8x5 18
#define MULTICUT_8x8_8x4 19
#define MULTICUT_8x4X3 20
#define MULTICUT_8xA4LEN 21
#define MULTICUT_5x3_5X2 22
#define MULTICUT_6x6 27
#define MULTICUT_5x5 29
#define MULTICUT_6x4_5 30
#define MULTICUT_6x4_5X2 31
#define MULTICUT_8x7 32
#define MULTICUT_8x9 33
#define MULTICUT_A5 34
#define MULTICUT_A5X2 35
#define MULTICUT_A4x4 36
#define MULTICUT_A4x5 37
#define MULTICUT_A4x6 38
#define MULTICUT_A4x8 39
#define MULTICUT_A4x10 40
#define MULTICUT_A4 41
#define MULTICUT_A4x5X2 43
#define MULTICUT_4x4 47
#define MULTICUT_4x6 48
#define MULTICUT_4x8 49
#define MULTICUT_4_5x4_5 50
#define MULTICUT_4_5x6 51
#define MULTICUT_4_5x8 52
#define MULTICUT_4x3 53
#define MULTICUT_4x4_5 54
#define MULTICUT_4_5x3 55
// #define MULTICUT_??? 56 // XXX WTF is missing?
#define MULTICUT_4_5x4 57
#if 0
// XXX do these exist? Or are they just the larger print cut in two?
// #define MULTICUT_4x3X2
// #define MULTICUT_4x4X2
// #define MULTICUT_4_5x3X2
// #define MULTICUT_4_5x4X2
#endif
#define MULTICUT_S_SIMPLEX 100
#define MULTICUT_S_FRONT 200
#define MULTICUT_S_BACK 300
#define MULTICUT_S_8x10 6
#define MULTICUT_S_8x12 7
#define MULTICUT_S_8x4 8
#define MULTICUT_S_8x5 9
#define MULTICUT_S_8x6 10
#define MULTICUT_S_8x8 11
#define MULTICUT_S_8x4X2 13
#define MULTICUT_S_8x5X2 14
#define MULTICUT_S_8x6X2 15
#define MULTICUT_S_8x10_5 25
#define MULTICUT_S_8x10_75 26
#define MULTICUT_S_8x4X3 28 // different than roll type.
/* Special panorama sizes */
#define MULTICUT_PANO_6x14 9000
#define MULTICUT_PANO_6x20 9001
#define MULTICUT_PANO_8x18 9010
#define MULTICUT_PANO_8x26 9011
#define MULTICUT_PANO_8x22 9012
#define MULTICUT_PANO_8x32 9013
//#define MULTICUT_PANO_A4x21 9014
//#define MULTICUT_PANO_A4x31 9015
#define MULTICUT_PANO_6x16 9020
#define MULTICUT_PANO_6x24 9021
#define MULTICUT_PANO_8x20 9030
#define MULTICUT_PANO_8x30 9031
#define MULTICUT_PANO_8x24 9032
#define MULTICUT_PANO_8x36 9033
#define MULTICUT_PANO_A4x2 9034
#define MULTICUT_PANO_A4x3 9035
#ifndef min
#define min(__x, __y) ((__x) < (__y)) ? __x : __y
#endif
/* Legacy spool file support */
static int legacy_cw01_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data);
static int legacy_dnp_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data);
static int legacy_dnp620_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data);
static int legacy_dnp820_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data);
static int legacy_qw410_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data);
static void dnpds40_cleanup_job(const void *vjob);
static int dnpds40_query_markers(void *vctx, struct marker **markers, int *count);
/* Panorama crap */
#ifdef USE_PANODATA_FILES
#define PANODATA_DS620 "LUTData_0010.csv"
#define PANODATA_DS820 "LUTData820_0010.csv"
static struct dnp_panodata *dnp_read_panodata(const char *filename)
{
struct dnp_panodata *pano = NULL;
FILE *f;
char buf[4096];
int line;
char *ptr;
const char *delim = " ,\t\n";
if (!filename)
return NULL;
pano = malloc(sizeof(struct dnp_panodata));
if (!pano)
return NULL;
pano->elements = 0;
snprintf(buf, sizeof(buf), "%s/%s", corrtable_path, filename);
f = fopen(buf, "r");
if (!f)
goto done_free;
/* Skip the first four lines */
for (line = 0; line < DNP_PANO_MAXROWS ; line++) {
if (fgets(buf, sizeof(buf), f) == NULL)
break;
ptr = strtok(buf, delim);
if (!ptr)
goto abort;
if (strcmp("REC", ptr))
continue;
ptr = strtok(NULL, delim);
if (!ptr)
goto abort;
pano->rows[pano->elements].start_row = strtol(ptr, NULL, 10);
ptr = strtok(NULL, delim);
if (!ptr)
goto abort;
pano->rows[pano->elements].rhYMC[0] = strtod(ptr,NULL);
ptr = strtok(NULL, delim);
if (!ptr)
goto abort;
pano->rows[pano->elements].rhYMC[1] = strtod(ptr,NULL);
ptr = strtok(NULL, delim);
if (!ptr)
goto abort;
pano->rows[pano->elements].rhYMC[2] = strtod(ptr,NULL);
ptr = strtok(NULL, delim);
if (!ptr)
goto abort;
pano->rows[pano->elements].lhYMC[0] = strtod(ptr,NULL);
ptr = strtok(NULL, delim);
if (!ptr)
goto abort;
pano->rows[pano->elements].lhYMC[1] = strtod(ptr,NULL);
ptr = strtok(NULL, delim);
if (!ptr)
goto abort;
pano->rows[pano->elements].lhYMC[2] = strtod(ptr,NULL);
pano->elements++;
}
if (pano->elements < 20)
goto abort;
fclose(f);
#if 0
f = fopen("/tmp/panodata.h", "w");
fprintf(f, "static struct dnp_panodata panodata = {\n");
fprintf(f, "\t.elements = %d,\n", pano->elements);
for (int i = 0 ; i < pano->elements ; i++) {
fprintf(f, "\t.rows[%2u] = { %4u, { %1.10f, %1.10f, %1.10f }, { %1.10f, %1.10f, %1.10f} },\n",
i,
pano->rows[i].start_row,
pano->rows[i].rhYMC[0],
pano->rows[i].rhYMC[1],
pano->rows[i].rhYMC[2],
pano->rows[i].lhYMC[0],
pano->rows[i].lhYMC[1],
pano->rows[i].lhYMC[2]);
}
fprintf(f, "};\n");
fclose(f);
exit(1);
#endif
return pano;
abort:
fclose(f);
done_free:
free(pano);
return NULL;
}
void dnp_free_panodata (struct dnp_panodata *pano)
{
if (pano)
free(pano);
}
#else /* !USE_PANODATA_FILES */
#include "backend_panodata.h"
#define dnp_read_panodata(__fname) &panodata
#define dnp_free_panodata(__pano)
#endif
#define PROCESS_PIXEL(__corr) \
outdata[r * cols + c] = 255 - ((255 - (double)indata[r * cols + c]) * (__corr))
static void dnp_applypano_plane(const struct dnp_panodata *pano,
const uint8_t *indata, uint8_t *outdata,
uint16_t rows, const uint16_t cols, const uint16_t pad_rows,
const uint16_t dpi, int overlap, const int plane,
const int rh, const int lh)
{
uint16_t r, c;
/* Fill the start margin with white */
if (pad_rows) {
memset(outdata, 0xff, pad_rows * cols);
outdata += pad_rows * cols;
}
for (r = 0 ; r < rows; r++) {
const struct panodata_row *lhc = pano ? &pano->rows[0] : NULL;
const struct panodata_row *rhc = pano ? &pano->rows[pano->elements - 1] : NULL;
if (rh && r < overlap) {
/* Row is in RH overlap portion of panel */
int i;
int row = (overlap - r);
if (dpi == 600)
row /= 2;
for (i = 0 ; i < pano->elements-1 ; i++) {
if (row >= pano->rows[i].start_row && row < pano->rows[i+1].start_row)
break;
}
rhc = &pano->rows[i];
} else if (lh && (rows -r) < overlap) {
/* Row is in LH overlap portion of panel */
int i;
int row = (rows -r);
if (dpi == 600)
row /= 2;
for (i = 0 ; i < pano->elements-1 ; i++) {
if (row >= pano->rows[i].start_row && row < pano->rows[i+1].start_row)
break;
}
lhc = &pano->rows[i];
} else {
/* No processing on row, pass through as-is */
memcpy(&outdata[r * cols], &indata[r * cols], cols);
continue;
}
if (rh && rhc && r < overlap) {
/* Fade RH row */
if (plane == 'Y')
for (c = 0 ; c < cols ; c++) {
PROCESS_PIXEL(rhc->rhYMC[0]);
}
else if (plane == 'M') {
for (c = 0 ; c < cols ; c++) {
PROCESS_PIXEL(rhc->rhYMC[1]);
}
} else {
for (c = 0 ; c < cols ; c++) {
PROCESS_PIXEL(rhc->rhYMC[2]);
}
}
} else if (lh && lhc && (rows -r) < overlap) {
/* Fade LH row */
if (plane == 'Y')
for (c = 0 ; c < cols ; c++) {
PROCESS_PIXEL(lhc->lhYMC[0]);
}
else if (plane == 'M') {
for (c = 0 ; c < cols ; c++) {
PROCESS_PIXEL(lhc->lhYMC[1]);
}
} else {
for (c = 0 ; c < cols ; c++) {
PROCESS_PIXEL(lhc->lhYMC[2]);
}
}
}
}
/* Fill the tail margin with white */
if (pad_rows)
memset(&outdata[rows * cols], 0xff, pad_rows * cols);
}
static int dnp_panorama_splitjob(struct dnpds40_ctx *ctx,
const struct dnpds40_printjob *injob,
struct dnpds40_printjob **newjobs)
{
struct dnp_panodata *pano = NULL;
int overlap = injob->dpi * 2;
uint16_t out_rows = 0;
uint16_t in_rows = 0;
uint8_t num_panels = 0;
uint32_t new_multicut = 0;
uint16_t pad_rows = 0;
in_rows = injob->dpi == 600 ? injob->rows / 2 : injob->rows;
if (ctx->conn->type == P_DNP_DS620) {
out_rows = 2436;
new_multicut = MULTICUT_6x8;
if (in_rows == 4236) {
num_panels = 2;
pad_rows = 18;
} else if (in_rows == 6036) {
num_panels = 3;
pad_rows = 18;
} else if (in_rows == 2436*2) {
num_panels = 2;
overlap = 0;
} else if (in_rows == 2436*3) {
num_panels = 3;
overlap = 0;
} else {
ERROR("Invalid panorama size (%d rows)\n", injob->rows);
goto bail;
}
} else if (ctx->conn->type == P_DNP_DS820) {
if (in_rows == 5436) {
new_multicut = ctx->media == 600 ? MULTICUT_A4x10 : MULTICUT_8x10;
out_rows = 3036;
pad_rows = 18;
num_panels = 2;
} else if (in_rows == 7836) {
new_multicut = ctx->media == 600 ? MULTICUT_A4x10 : MULTICUT_8x10;
out_rows = 3036;
pad_rows = 18;
num_panels = 3;
} else if (in_rows == 3036*2) {
new_multicut = ctx->media == 600 ? MULTICUT_A4x10 : MULTICUT_8x10;
num_panels = 2;
out_rows = 3036;
overlap = 0;
} else if (in_rows == 3036*3) {
new_multicut = ctx->media == 600 ? MULTICUT_A4x10 : MULTICUT_8x10;
num_panels = 3;
out_rows = 3036;
overlap = 0;
} else if (in_rows == 6452) {
new_multicut = ctx->media == 600 ? MULTICUT_A4 : MULTICUT_8xA4LEN;
num_panels = 2;
pad_rows = 18;
out_rows = 3544;
} else if (in_rows == 9360) {
new_multicut = ctx->media == 600 ? MULTICUT_A4 : MULTICUT_8xA4LEN;
num_panels = 3;
pad_rows = 18;
out_rows = 3544;
} else if (in_rows == 3544*2) {
new_multicut = ctx->media == 600 ? MULTICUT_A4 : MULTICUT_8xA4LEN;
num_panels = 2;
out_rows = 3544;
overlap = 0;
} else if (in_rows == 3544*3) {
new_multicut = ctx->media == 600 ? MULTICUT_A4 : MULTICUT_8xA4LEN;
num_panels = 3;
out_rows = 3544;
overlap = 0;
} else if (in_rows == 6636) {
new_multicut = MULTICUT_8x12;
num_panels = 2;
pad_rows = 18;
out_rows = 3636;
} else if (in_rows == 9636) {
new_multicut = MULTICUT_8x12;
num_panels = 3;
pad_rows = 18;
out_rows = 3636;
} else if (in_rows == 3636*2) {
new_multicut = MULTICUT_8x12;
num_panels = 2;
out_rows = 3636;
overlap = 0;
} else if (in_rows == 3636*3) {
new_multicut = MULTICUT_8x12;
num_panels = 3;
out_rows = 3636;
overlap = 0;
} else {
ERROR("Invalid panorama size (%d rows)\n", injob->rows);
goto bail;
}
}
/* Double everything up for >300dpi */
if (injob->dpi == 600) {
out_rows *= 2;
overlap *= 2;
pad_rows *= 2;
}
if (overlap) {
#ifdef USE_PANODATA_FILES
const char *filename = NULL;
if (ctx->conn->type == P_DNP_DS620)
filename = PANODATA_DS620;
else if (ctx->conn->type == P_DNP_DS820)
filename = PANODATA_DS820;
if (!filename || !(pano = dnp_read_panodata(filename))) {
ERROR("Failure to load panorama LUT file (%s)\n", filename);
goto bail;
}
#else
pano = dnp_read_panodata(NULL);
#endif
DEBUG("Splitting continuous panorama job... (%d panels of %d+%d rows, %d overlap)\n", num_panels, out_rows - 2*pad_rows, 2*pad_rows, overlap);
} else {
DEBUG("Splitting discrete panel panorama job...(%d panels of %d rows)\n", num_panels, out_rows);
}
for (int panel = 0 ; panel < num_panels ; panel++) {
int lh = (panel < (num_panels -1));
int rh = (panel > 0);
/* Create new job */
newjobs[panel] = malloc(sizeof(struct dnpds40_printjob));
if (!newjobs[panel]) {
ERROR("Memory allocation failure");
return CUPS_BACKEND_RETRY_CURRENT;
}
struct dnpds40_printjob *newjob = newjobs[panel];
memcpy(newjob, injob, sizeof(struct dnpds40_printjob));
newjob->common.copies = 1;
newjob->is_pano = lh; /* All but last */
newjob->databuf = malloc(MAX_PRINTJOB_LEN);
if (!newjob->databuf) {
dnpds40_cleanup_job(newjob);
newjobs[panel] = NULL;
ERROR("Memory allocation failure!\n");
return CUPS_BACKEND_RETRY_CURRENT;
}
newjob->datalen = 0;
newjob->multicut = new_multicut;
newjob->rows = out_rows;
uint32_t plane_len = out_rows * ctx->native_width + 1088;
/* Copy data blocks from injob */
uint8_t *ptr = injob->databuf;
while(ptr && ptr < (injob->databuf + injob->datalen)) {
uint32_t i;
int copy = 1;
if(!memcmp("IMAGE YPLANE", ptr +2, 12) ||
!memcmp("IMAGE MPLANE", ptr +2, 12) ||
!memcmp("IMAGE CPLANE", ptr +2, 12) ) {
if (ptr[8] == 'Y') {
/* Insert CONT_PANORAMA _before_ the image data */
int cont = (panel == num_panels-1) ? 0 : 1;
int lap = overlap;
if (injob->dpi == 600)
lap /= 2;
lap /= 3; /* Convert pixels into inches * 100 */
if (overlap) {
/* Continuous panorama */
newjob->datalen += sprintf((char*)newjob->databuf + newjob->datalen, "\033PCNTRL CONT_PANORAMA 00000008%04u%04u", cont, lap);
} else {
/* Discrete panel */
newjob->cutter = 1000;
}
}
uint8_t *bmp_hdr;
DEBUG("Panel %d plane %c\n", panel, ptr[8]);
newjob->datalen += sprintf((char*)newjob->databuf + newjob->datalen,
"\033PIMAGE %cPLANE %08u", ptr[8], plane_len);
/* Copy over old bitmap header*/
memcpy(newjob->databuf + newjob->datalen, ptr + 32, 1088);
bmp_hdr = newjob->databuf + newjob->datalen;
/* Mangle BMP header for new size and row count */
i = cpu_to_le32(plane_len);
memcpy(bmp_hdr + 2, &i, sizeof(i));
i = cpu_to_le32(out_rows);
memcpy(bmp_hdr + 22, &i, sizeof(i));
/* Split panorama */
dnp_applypano_plane(pano,
ptr + 32 + 1088 + (pad_rows * ctx->native_width) + (panel * (out_rows - pad_rows*2 - overlap) * ctx->native_width),
newjob->databuf + newjob->datalen + 1088,
out_rows - pad_rows*2, ctx->native_width, pad_rows, injob->dpi, overlap, ptr[8], rh, lh);
newjob->datalen += plane_len;
/* Don't copy anything else from source */
copy = 0;
} else if(!memcmp("CNTRL QTY", ptr + 2, 9)) {
/* Ignore copy count */
copy = 0;
} else if(!memcmp("CNTRL CUTTER", ptr + 2, 12) ||
!memcmp("CNTRL FULL_CUTTER_SET", ptr + 2, 21)) {
/* Ignore all cutter commands */
copy = 0;
}
/* Work out how much to copy from source, and increment source pointer */
char buf[10];
buf[8] = 0;
memcpy(buf, ptr + 24, 8);
i = atoi(buf) + 32;
if (copy) {
memcpy(newjob->databuf + newjob->datalen, ptr, i);
newjob->datalen += i;
}
ptr += i;
}
#if 0
{
char buf[32];
sprintf(buf, "/tmp/dump%d.raw", panel);
FILE *f = fopen(buf, "wb");
fwrite(newjob->databuf, newjob->datalen, 1, f);
fclose(f);
}
#endif
}
dnp_free_panodata(pano);
return CUPS_BACKEND_OK;
bail:
dnp_free_panodata(pano);
return CUPS_BACKEND_FAILED;
}
#define JOB_EQUIV(__x) if (job1->__x != job2->__x) goto done
/* NOTE: Does _not_ free the input jobs */
static void *dnp_combine_jobs(const void *vjob1,
const void *vjob2)
{
const struct dnpds40_printjob *job1 = vjob1;
const struct dnpds40_printjob *job2 = vjob2;
struct dnpds40_printjob *newjob = NULL;
uint32_t new_multicut;
uint16_t new_w, new_h;
int32_t gap_bytes;
/* Sanity check */
if (!job1 || !job2)
goto done;
/* Make sure pertinent paremeters are the same */
JOB_EQUIV(dpi);
JOB_EQUIV(matte);
JOB_EQUIV(cutter);
JOB_EQUIV(fullcut);
JOB_EQUIV(multicut); // TODO: Support fancier modes for 8" models (eg 8x4+8x6, etc)
JOB_EQUIV(datalen); // <-- cheating a little?
// JOV_EQUIV(printspeed); <-- does it matter?
/* Any fancy cutter action means we pass */
if (job1->fullcut || job1->cutter > 120)
goto done;
/* Partial matte is no bueno */
if (job1->matte > 100 ||
job2->matte > 100)
goto done;
/* Make sure we can combine these two prints */
switch (job1->multicut) {
case MULTICUT_5x3_5:
new_multicut = MULTICUT_5x3_5X2;
new_w = 1920;
new_h = 2176;
gap_bytes = 0;
break;
case MULTICUT_6x4:
if (job1->cutter == 120) {
new_multicut = MULTICUT_6x8;
new_h = 2436;
gap_bytes = -44; /* Chop out the middle 44 rows */
} else {
new_multicut = MULTICUT_6x4X2;
new_h = 2498;
gap_bytes = 18;
}
new_w = 1920;
break;
case MULTICUT_6x4_5:
new_multicut = MULTICUT_6x4_5X2;
new_w = 1920;
new_h = 2802;
gap_bytes = 30;
break;
case MULTICUT_8x4:
new_multicut = MULTICUT_8x4X2;
new_w = 2560;
new_h = 2502;
gap_bytes = 30;
break;
case MULTICUT_8x5:
new_multicut = MULTICUT_8x5X2;
new_w = 2560;
new_h = 3102;
gap_bytes = 30;
break;
case MULTICUT_A4x5:
new_multicut = MULTICUT_A4x5X2;
new_w = 2560;
new_h = 3102;
gap_bytes = 30;
break;
case MULTICUT_A5:
new_multicut = MULTICUT_A5X2;
new_w = 2560;
new_h = 3598;
gap_bytes = 30;
break;
case MULTICUT_8x6:
new_multicut = MULTICUT_8x6X2;
new_w = 2560;
new_h = 3702;
gap_bytes = 30;
break;
#if 0 // XXX Do these printers handle automatic multicut?
// if not, implement using FULL_CUTTER_CONTROL!
case MULTICUT_4x3:
new_multicut = MULTICUT_4x6;
new_w = 1408;
new_h = 1836;
gap_bytes = 36;
break;
case MULTICUT_4x4:
new_multicut = MULTICUT_4x8;
new_w = 1408;
new_h = 2436;
gap_bytes = 36;
break;
case MULTICUT_4_5x3:
new_multicut = MULTICUT_4_5x6;
new_w = 1408;
new_h = 1836;
gap_bytes = 36;
break;
case MULTICUT_4_5x4:
new_multicut = MULTICUT_4_5x8;
new_w = 1408;
new_h = 2436;
gap_bytes = 36;
break;
#endif
default:
/* Everything else is NOT handled */
goto done;
}
gap_bytes *= new_w;
if (job1->dpi == 600) {
gap_bytes *= 2;
new_h *= 2;
}
DEBUG("Combining jobs to save media\n");
/* Okay, it's kosher to proceed */
newjob = malloc(sizeof(*newjob));
if (!newjob) {
ERROR("Memory allocation failure!\n");
goto done;
}
memcpy(newjob, job1, sizeof(*newjob));
newjob->databuf = malloc(((new_w*new_h+1024+54+10))*3+1024 + abs(gap_bytes));
newjob->datalen = 0;
newjob->multicut = new_multicut;
newjob->can_rewind = 0;
newjob->common.can_combine = 0;
if (!newjob->databuf) {
dnpds40_cleanup_job(newjob);
newjob = NULL;
ERROR("Memory allocation failure!\n");
goto done;
}
/* Copy data blocks from job1 */
uint8_t *ptr, *ptr2;
char buf[10];
ptr = job1->databuf;
while(ptr && ptr < (job1->databuf + job1->datalen)) {
int i;
buf[8] = 0;
memcpy(buf, ptr + 24, 8);
i = atoi(buf) + 32;
memcpy(newjob->databuf + newjob->datalen, ptr, i);
/* If we're on a plane data block... */
if (!memcmp("PLANE", newjob->databuf + newjob->datalen + 9, 5)) {
long planelen = (new_w * new_h) + 1088;
uint32_t newlen;
/* Fix up length in command */
snprintf(buf, sizeof(buf), "%08lu", planelen);
memcpy(newjob->databuf + newjob->datalen + 24, buf, 8);
/* Alter BMP header */
newlen = cpu_to_le32(planelen);
memcpy(newjob->databuf + newjob->datalen + 32 + 2, &newlen, 4);
/* alter DIB header */
newlen = cpu_to_le32(new_h);
memcpy(newjob->databuf + newjob->datalen + 32 + 22, &newlen, 4);
if (gap_bytes > 0) {
/* Insert gap/padding after first image */
memset(newjob->databuf + newjob->datalen + i, 0xff, gap_bytes);
newjob->datalen += gap_bytes;
} else {
// uint8_t *ptrA = newjob->databuf + newjob->datalen + 1088;
// /* Back off by 1/2 the gap */
// memmove(ptrA, ptrA - (gap_bytes / 2), (i - 1088) + gap_bytes/2);
/* And chop the end off by half the gap */
newjob->datalen += gap_bytes / 2;
}
/* Locate job2's PLANE -- Assume it's in the same place! */
ptr2 = job2->databuf + (ptr - job1->databuf);
/* Copy over job2's image data */
memcpy(newjob->databuf + newjob->datalen + i,
ptr2 + 32 + 1088, i - 32 - 1088);
if (gap_bytes < 0) {
uint8_t *ptrA = newjob->databuf + newjob->datalen + i;
/* Back off by 1/2 the gap */
memmove(ptrA, ptrA - (gap_bytes / 2), (i - 1088) + gap_bytes/2);
/* And chop the end off by half the gap */
newjob->datalen += gap_bytes / 2;
}
newjob->datalen += i - 32 - 1088; /* add in job2 length */
}
newjob->datalen += i;
ptr += i;
}
done:
return newjob;
}
#undef JOB_EQUIV
static void dnpds40_build_cmd(struct dnpds40_cmd *cmd, const char *arg1, const char *arg2, uint32_t arg3_len)
{
memset(cmd, 0x20, sizeof(*cmd));
cmd->esc = 0x1b;
cmd->p = 0x50;
memcpy(cmd->arg1, arg1, min(strlen(arg1), sizeof(cmd->arg1)));
memcpy(cmd->arg2, arg2, min(strlen(arg2), sizeof(cmd->arg2)));
if (arg3_len) {
char buf[11]; /* Extra padding to shut up GCC 10 */
snprintf(buf, sizeof(buf), "%08u", arg3_len);
memcpy(cmd->arg3, buf, 8);
}
}
static void dnpds40_cleanup_string(char *start, int len)
{
char *ptr = strchr(start, 0x0d);
if (ptr && (ptr - start < len)) {
*ptr = 0x00; /* If there is a <CR>, terminate there */
len = ptr - start;
} else {
start[--len] = 0x00; /* force null-termination */
}
/* Trim trailing spaces */
while (len && start[len-1] == ' ') {
start[--len] = 0;
}
}
static const char *dnpds40_printer_type(int type, int mfg)
{
switch(type) {
case P_DNP_DS40: return mfg == MFG_CITIZEN? "CX" : "DS40";
case P_DNP_DS80: return mfg == MFG_CITIZEN? "CW" : (mfg == MFG_MITSUBISHI ? "CP3800" : "DS80");
case P_DNP_DS80D: return "DS80DX";
case P_DNP_DSRX1: return mfg == MFG_CITIZEN ? "CY" : "DSRX1";
case P_DNP_DS620: return mfg == MFG_CITIZEN ? "CX-02" : (mfg == MFG_FUJIFILM ? "ASK400" : "DS620");
case P_DNP_DS820: return mfg == MFG_CITIZEN ? "CX-02W" : "DS820";
case P_DNP_QW410: return mfg == MFG_CITIZEN ? "CZ-01" : "QW410";
case P_CITIZEN_CW01: return "CW01";
case P_CITIZEN_OP900II: return "CW-02 / OP900ii";
default: break;
}
return "Unknown";
}
static const char *dnpds40_media_types(int media, int sticker)
{
switch (media) {
case 100: return "UNKNOWN100"; // seen in driver dumps
case 110: return "UNKNOWN110"; // seen in driver dumps
case 150: return "4x6 (PC)";
case 151: return "4x8";
case 160: return "4.5x6";
case 161: return "4.5x8";
case 200: return sticker ? "5x3.5 (L): Sticker" : "5x3.5 (L)";
case 210: return sticker ? "5x7 (2L) Sticker" : "5x7 (2L)";
case 300: return sticker ? "6x4 (PC) Sticker" : "6x4 (PC)";
case 310: return sticker ? "6x8 (A5) Sticker" : "6x8 (A5)";
case 400: return sticker ? "6x9 (A5W) Sticker" : "6x9 (A5W)";
case 500: return "8x10";
case 510: return "8x12";
case 600: return "A4";
default:
break;
}
return "Unknown";
}
static const char *dnpds620_media_extension_code(int media)
{
switch (media) {
case 0: return "Normal Paper";
case 1: return "Sticker Paper";
case 99: return "Unknown Paper";
default:
break;
}
return "Unknown";
}
static const char *rfid_media_subtypes(int media)
{
switch (media) {
case 1: return "SD";
case 2: return "PD";
case 3: return "PP";
default:
break;
}
return "Unknown";
}
static const char *dnpds80_duplex_media_types(int media)
{
switch (media) {
case 100: return "8x10.75";
case 200: return "8x12";
default:
break;
}
return "Unknown";
}
#define DUPLEX_UNIT_PAPER_NONE 0
#define DUPLEX_UNIT_PAPER_PROTECTIVE 1
#define DUPLEX_UNIT_PAPER_PRESENT 2
static const char *dnpds80_duplex_paper_status(int media)
{
switch (media) {
case DUPLEX_UNIT_PAPER_NONE: return "No Paper";
case DUPLEX_UNIT_PAPER_PROTECTIVE: return "Protective Sheet";
case DUPLEX_UNIT_PAPER_PRESENT: return "Cut Paper Present";
default:
return "Unknown";
}
}
static const char *dnpds80_duplex_statuses(int status)
{
switch (status) {
case 5000: return "No Error";
case 5500: return "Duplex Unit Not Connected";
case 5017: return "Paper Jam: Supply Sensor On";
case 5018: return "Paper Jam: Supply Sensor Off";
case 5019: return "Paper Jam: Slot Sensor On";
case 5020: return "Paper Jam: Slot Sensor Off";
case 5021: return "Paper Jam: Pass Sensor On";
case 5022: return "Paper Jam: Pass Sensor Off";
case 5023: return "Paper Jam: Shell Sensor 1 On";
case 5024: return "Paper Jam: Shell Sensor 1 Off";
case 5025: return "Paper Jam: Shell Sensor 2 On";
case 5026: return "Paper Jam: Shell Sensor 2 Off";
case 5027: return "Paper Jam: Eject Sensor On";
case 5028: return "Paper Jam: Eject Sensor Off";
case 5029: return "Paper Jam: Slot FG Sensor";
case 5030: return "Paper Jam: Shell FG Sensor";
case 5033: return "Paper Supply Sensor Off";
case 5034: return "Printer Feed Slot Sensor Off";
case 5035: return "Pinch Pass Sensor Off";
case 5036: return "Shell Pass Sensor 1 Off";
case 5037: return "Shell Pass Sensor 2 Off";
case 5038: return "Eject Sensor Off";
case 5049: return "Capstan Drive Control Error";
case 5065: return "Shell Roller Error";
case 5081: return "Pinch Open Error";
case 5082: return "Pinch Close Error";
case 5083: return "Pinch Init Error";
case 5084: return "Pinch Position Error";
case 5097: return "Pass Guide Supply Error";
case 5098: return "Pass Guide Shell Error";
case 5099: return "Pass Guide Eject Error";
case 5100: return "Pass Guide Init Error";
case 5101: return "Pass Guide Position Error";
case 5113: return "Side Guide Home Error";
case 5114: return "Side Guide Position Error";
case 5115: return "Side Guide Init Error";
case 5129: return "Act Guide Home Error";
case 5145: return "Shell Rotate Home Error";
case 5146: return "Shell Rotate Rev Error";
case 5161: return "Paper Feed Lever Down Error";
case 5162: return "Paper Feed Lever Lock Error";
case 5163: return "Paper Feed Lever Up Error";
case 5177: return "Cutter Home Error";
case 5178: return "Cutter Away Error";
case 5179: return "Cutter Init Error";
case 5180: return "Cutter Position Error";
case 5193: return "Paper Tray Removed";
case 5209: return "Cover Opened";
case 5241: return "System Error";
default:
break;
}
return "Unknown Duplexer Error";
}
static const char *dnpds40_statuses(int status)
{
if (status >= 5000 && status <= 5999)
return dnpds80_duplex_statuses(status);
switch (status) {
case 0: return "Idle";
case 1: return "Printing";
case 500: return "Cooling Print Head";
case 510: return "Cooling Paper Motor";
case 900: return "Standby Mode";
case 1000: return "Cover Open";
case 1010: return "No Scrap Box";
case 1100: return "Paper End";
case 1200: return "Ribbon End";
case 1300: return "Paper Jam";
case 1400: return "Ribbon Error";
case 1500: return "Paper Definition Error";
case 1600: return "Data Error";
case 2000: return "Head Voltage Error";
case 2010: return "USB Power Supply Error";
case 2100: return "Head Position Error";
case 2200: return "Power Supply Fan Error";
case 2300: return "Cutter Error";
case 2400: return "Pinch Roller Error";
case 2500: return "Abnormal Head Temperature";
case 2600: return "Abnormal Media Temperature";
case 2610: return "Abnormal Paper Motor Temperature";
case 2700: return "Ribbon Tension Error";
case 2800: return "RF-ID Module Error";
case 2900: return "RS422 Communiation Error";
case 3000: return "System Error";
case 9999: return "Communication Failure"; /* Special */
default:
break;
}
return "Unknown Error";
}
static const char *dnpds40_panorama_status(char *resp)
{
/* 00000 == okay to print
1xxxx == not okay to print
xxxx1 == head temp high
xxx2x == media temp low
xx1xx == humidity too high
*/
if (resp[4] == '0')
return "Yes";
if (resp[0] == '1')
return "No, High Head temp";
if (resp[1] == '2')
return "No, Low media temp";
if (resp[2] == '1')
return "No, High humidity";
return "No, Unknown reason";
}
static int dnpds40_do_cmd(struct dnpds40_ctx *ctx,
const struct dnpds40_cmd *cmd,
const uint8_t *data, int len)
{
int ret;
if ((ret = send_data(ctx->conn,
(uint8_t*)cmd, sizeof(*cmd))))
return ret;
if (data && len)
if ((ret = send_data(ctx->conn,
data, len)))
return ret;
return CUPS_BACKEND_OK;
}
static uint8_t *dnpds40_resp_cmd2(struct dnpds40_ctx *ctx,
struct dnpds40_cmd *cmd,
int *len,
uint8_t *buf, uint32_t buf_len)
{
char tmp[9];
uint8_t *respbuf;
int ret, i, num = 0;
memset(tmp, 0, sizeof(tmp));
if ((ret = dnpds40_do_cmd(ctx, cmd, buf, buf_len)))
return NULL;
/* Read in the response header */
ret = read_data(ctx->conn,
(uint8_t*)tmp, 8, &num);
if (ret < 0)
return NULL;
if (num != 8) {
ERROR("Short read! (%d/%d)\n", num, 8);
return NULL;
}
i = atoi(tmp); /* Length of payload in bytes, possibly padded */
respbuf = malloc(i + 1);
if (!respbuf) {
ERROR("Memory allocation failure (%d bytes)!\n", i);
return NULL;
}
respbuf[i] = 0; /* Explicitly null-pad */
/* Read in the actual response */
ret = read_data(ctx->conn,
respbuf, i, &num);
if (ret < 0) {
free(respbuf);
return NULL;
}
if (num != i) {
ERROR("Short read! (%d/%d)\n", num, i);
free(respbuf);
return NULL;
}
*len = num;
return respbuf;
}
#define dnpds40_resp_cmd(__ctx, __cmd, __len) dnpds40_resp_cmd2(__ctx, __cmd, __len, NULL, 0)
static int dnpds40_query_serno(struct dyesub_connection *conn, char *buf, int buf_len)
{
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
struct dnpds40_ctx ctx = {
.conn = conn,
};
/* Get Serial Number */
dnpds40_build_cmd(&cmd, "INFO", "SERIAL_NUMBER", 0);
resp = dnpds40_resp_cmd(&ctx, &cmd, &len);
if (!resp)
return CUPS_BACKEND_FAILED;
dnpds40_cleanup_string((char*)resp, len);
strncpy(buf, (char*)resp, buf_len);
buf[buf_len-1] = 0;
free(resp);
return CUPS_BACKEND_OK;
}
static void *dnpds40_init(void)
{
struct dnpds40_ctx *ctx = malloc(sizeof(struct dnpds40_ctx));
if (!ctx) {
ERROR("Memory allocation failure (%d bytes)!\n", (int)sizeof(struct dnpds40_ctx));
return NULL;
}
memset(ctx, 0, sizeof(struct dnpds40_ctx));
return ctx;
}
#define FW_VER_CHECK(__major, __minor) \
((ctx->ver_major > (__major)) || \
(ctx->ver_major == (__major) && ctx->ver_minor >= (__minor)))
static int dnpds40_query_mqty(struct dnpds40_ctx *ctx)
{
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0, count;
/* Get Media remaining */
dnpds40_build_cmd(&cmd, "INFO", "MQTY", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (!resp)
return -1;
dnpds40_cleanup_string((char*)resp, len);
count = atoi((char*)resp+4);
free(resp);
if (count) {
/* Old-sk00l models report one less than they should */
if (!ctx->correct_count)
count++;
count -= ctx->mediaoffset;
if (count < 0) /* Just in case */
count = 0;
}
return count;
}
static int dnpds80dx_query_paper(struct dnpds40_ctx *ctx)
{
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
/* Query Duplex Media Info */
dnpds40_build_cmd(&cmd, "INFO", "UNIT_CUT_PAPER", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
char tmp[5];
char status;
dnpds40_cleanup_string((char*)resp, len);
memcpy(tmp, resp + 4, 4);
status = tmp[3];
tmp[3] = '0';
tmp[4] = 0;
ctx->duplex_media = atoi(tmp);
tmp[0] = tmp[1] = tmp[2] = '0';
tmp[3] = status;
ctx->duplex_media_status = atoi(tmp);
free(resp);
} else {
return CUPS_BACKEND_FAILED;
}
return CUPS_BACKEND_OK;
}
static int dnpds40_attach(void *vctx, struct dyesub_connection *conn, uint8_t jobid)
{
struct dnpds40_ctx *ctx = vctx;
UNUSED(jobid);
ctx->conn = conn;
/* Nearly all models support 600dpi */
ctx->supports_600dpi = 1;
if (test_mode < TEST_MODE_NOATTACH) {
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
/* Get Firmware Version */
dnpds40_build_cmd(&cmd, "INFO", "FVER", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
char *ptr;
dnpds40_cleanup_string((char*)resp, len);
ctx->version = strdup((char*) resp);
/* See if we're in FW update mode */
if (strstr(ctx->version, "RW")) {
ctx->flash_mode = 1;
ctx->ver_major = 0;
ctx->ver_minor = 0;
} else {
/* Parse version */
/* ptr = */ strtok((char*)resp, " .");
ptr = strtok(NULL, ".");
ctx->ver_major = atoi(ptr);
ptr = strtok(NULL, ".");
ctx->ver_minor = atoi(ptr);
}
free(resp);
} else {
return CUPS_BACKEND_FAILED;
}
/* Figure out actual Manufacturer */
{
struct libusb_device_descriptor desc;
struct libusb_device *udev;
udev = libusb_get_device(ctx->conn->dev);
libusb_get_device_descriptor(udev, &desc);
char buf[STR_LEN_MAX + 1];
buf[0] = 0;
buf[STR_LEN_MAX] = 0;
libusb_get_string_descriptor_ascii(ctx->conn->dev, desc.iManufacturer, (unsigned char*)buf, STR_LEN_MAX);
if (!strncmp(buf, "Dai", 3)) /* "Dai Nippon Printing" */
ctx->mfg = MFG_DNP;
else if (!strncmp(buf, "CIT", 3)) /* "CITIZEN SYSTEMS" */
ctx->mfg = MFG_CITIZEN;
else if (!strncmp(buf, "M", 1)) /* "Mitsubishi" */
ctx->mfg = MFG_MITSUBISHI;
else if (!strncmp(buf, "F", 1)) /* "Fujifilm" */
ctx->mfg = MFG_FUJIFILM;
else
ctx->mfg = MFG_OTHER;
#ifdef DNP_ONLY /* Only allow DNP printers to work. */
if (ctx->mfg != MFG_DNP)
return CUPS_BACKEND_FAILED;
#endif
#ifdef CITIZEN_ONLY /* Only allow CITIZEN printers to work. */
if (ctx->mfg != MFG_CITIZEN)
return CUPS_BACKEND_FAILED
#endif
}
} else {
ctx->ver_major = 3;
ctx->ver_minor = 0;
ctx->version = strdup("UNKNOWN");
}
/* Per-printer options */
switch (ctx->conn->type) {
case P_DNP_DS40:
ctx->native_width = 1920;
ctx->max_height = 5480;
ctx->supports_6x9 = 1;
if (FW_VER_CHECK(1,04))
ctx->supports_counterp = 1;
if (FW_VER_CHECK(1,30))
ctx->supports_matte = 1;
if (FW_VER_CHECK(1,40))
ctx->supports_2x6 = 1;
if (FW_VER_CHECK(1,50))
ctx->supports_3x5x2 = 1;
if (FW_VER_CHECK(1,60))
ctx->supports_fullcut = ctx->supports_6x6 = 1; // No 5x5!
if (FW_VER_CHECK(1,70))
/* Always reports iSerial, not configurable */
ctx->supports_iserial = 2;
break;
case P_DNP_DS80:
case P_DNP_DS80D:
ctx->native_width = 2560;
ctx->max_height = 7536;
if (FW_VER_CHECK(1,02))
ctx->supports_counterp = 1;
if (FW_VER_CHECK(1,30))
ctx->supports_matte = 1;
if (FW_VER_CHECK(1,42))
/* Always reports iSerial, not configurable */
ctx->supports_iserial = 2;
break;
case P_DNP_DSRX1:
ctx->native_width = 1920;
ctx->max_height = 5480;
ctx->supports_counterp = 1;
ctx->supports_matte = 1;
if (FW_VER_CHECK(1,10))
ctx->supports_2x6 = ctx->supports_mqty_default = 1;
if (FW_VER_CHECK(1,20))
ctx->supports_3x5x2 = 1;
if (FW_VER_CHECK(2,00)) { /* AKA RX1HS */
ctx->needs_mlot = 1;
ctx->supports_mediaoffset = 1;
ctx->supports_iserial = 1;
}
if (FW_VER_CHECK(2,06)) {
ctx->supports_5x5 = ctx->supports_6x6 = 1;
}
break;
case P_CITIZEN_OP900II:
ctx->native_width = 1920;
ctx->max_height = 5480;
ctx->supports_counterp = 1;
ctx->supports_matte = 1;
ctx->supports_mqty_default = 1;
ctx->supports_6x9 = 1;
if (FW_VER_CHECK(1,11))
ctx->supports_2x6 = 1;
break;
case P_CITIZEN_CW01:
ctx->native_width = 2048;
ctx->max_height = 5480;
ctx->supports_6x9 = 1;
break;
case P_DNP_DS620:
ctx->native_width = 1920;
ctx->max_height = 5604;
ctx->correct_count = 1;
ctx->supports_counterp = 1;
ctx->supports_matte = 1;
ctx->supports_2x6 = 1;
ctx->supports_fullcut = 1;
ctx->supports_mqty_default = 1;
ctx->supports_standby = 1;
ctx->supports_keepmode = 1;
ctx->supports_iserial = 1;
ctx->supports_6x6 = 1;
ctx->supports_5x5 = 1;
ctx->supports_lowspeed = 1;
ctx->supports_3x5x2 = 1;
if (ctx->mfg == MFG_CITIZEN) { /* Citizen and DNP firmware diverge */
ctx->supports_adv_fullcut = 1;
ctx->supports_advmatte = 1;
ctx->supports_luster = 1;
ctx->supports_rewind = 1;
ctx->supports_pano = 1; // XXX what version?
if (FW_VER_CHECK(1,01)) {
ctx->supports_finematte = 1;
ctx->supports_contpano = 1; // XXX what version?
}
if (FW_VER_CHECK(1,10))
ctx->supports_6x9 = ctx->supports_6x4_5 = 1;
} else if (ctx->mfg == MFG_DNP) {
if (strchr(ctx->version, 'A'))
ctx->supports_rewind = 0;
else
ctx->supports_rewind = 1;
if (FW_VER_CHECK(1,10))
ctx->supports_6x9 = ctx->supports_6x4_5 = 1;
if (FW_VER_CHECK(1,20))
ctx->supports_adv_fullcut = ctx->supports_advmatte = ctx->supports_pano = 1;
if (FW_VER_CHECK(1,30))
ctx->supports_luster = 1;
if (FW_VER_CHECK(1,32))
ctx->supports_contpano = 1;
if (FW_VER_CHECK(1,33))
ctx->supports_media_ext = 1;
if (FW_VER_CHECK(1,52))
ctx->supports_finematte = 1;
} else if (ctx->mfg == MFG_FUJIFILM) {
ctx->supports_finematte = 1;
ctx->supports_luster = 1;
ctx->supports_media_ext = 1;
ctx->supports_6x4_5 = 1;
ctx->supports_6x9 = 1;
ctx->supports_rewind = 1;
// XXX Need to confirm these
ctx->supports_adv_fullcut = 1;
ctx->supports_advmatte = 1;
}
break;
case P_DNP_DS820:
ctx->native_width = 2560;
ctx->max_height = 7536;
ctx->correct_count = 1;
ctx->supports_counterp = 1;
ctx->supports_matte = 1;
ctx->supports_fullcut = 1;
ctx->supports_mqty_default = 1;
ctx->supports_standby = 1;
ctx->supports_keepmode = 1;
ctx->supports_iserial = 1;
ctx->supports_adv_fullcut = 1;
ctx->supports_advmatte = 1;
ctx->supports_luster = 1;
ctx->supports_finematte = 1;
ctx->supports_printspeed = 1;
ctx->supports_lowspeed = 1;
ctx->supports_highdensity = 1;
ctx->supports_ctrld_ext = 1;
ctx->supports_mediaoffset = 1;
ctx->supports_mediaclassrfid = 1;
ctx->supports_gamma = 1;
ctx->supports_pano = 1;
ctx->supports_contpano = 1;
if (ctx->mfg == MFG_CITIZEN) { /* Citizen and DNP firmware diverge */
ctx->supports_rewind = 1;
ctx->supports_a4x6 = 1; // XXX need to confirm this!
} else {
if (strchr(ctx->version, 'A'))
ctx->supports_rewind = 0;
else
ctx->supports_rewind = 1;
if (FW_VER_CHECK(1,06))
ctx->supports_a4x6 = 1;
}
break;
case P_DNP_QW410:
ctx->native_width = 1408;
ctx->max_height = 2436;
ctx->correct_count = 1;
ctx->supports_600dpi = 0;
ctx->supports_counterp = 1;
ctx->supports_matte = 1;
ctx->supports_fullcut = 1;
ctx->supports_mqty_default = 1;
ctx->supports_keepmode = 1;
ctx->supports_iserial = 1;
ctx->supports_adv_fullcut = 1;
ctx->supports_advmatte = 1;
ctx->supports_printspeed = 1;
ctx->supports_lowspeed = 1;
ctx->supports_ctrld_ext = 1;
ctx->supports_mediaoffset = 1;
ctx->supports_mediaclassrfid = 1;
ctx->supports_systime = 1;
if (FW_VER_CHECK(1,10))
ctx->supports_45_34 = 1;
break;
default:
ERROR("Unknown printer type %d\n", ctx->conn->type);
return CUPS_BACKEND_FAILED;
}
if (test_mode < TEST_MODE_NOATTACH) {
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
if (ctx->flash_mode) {
goto skip_queries;
}
/* Get Serial Number */
dnpds40_build_cmd(&cmd, "INFO", "SERIAL_NUMBER", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
dnpds40_cleanup_string((char*)resp, len);
ctx->serno = (char*) resp;
/* Do NOT free resp! */
} else {
return CUPS_BACKEND_FAILED;
}
/* Query Media Info */
dnpds40_build_cmd(&cmd, "INFO", "MEDIA", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
char tmp[4];
dnpds40_cleanup_string((char*)resp, len);
memcpy(tmp, resp + 4, 3);
tmp[3] = 0;
ctx->media = atoi(tmp);
if (ctx->conn->type != P_DNP_QW410) {
/* Subtract out the "mark" type */
if (ctx->media & 1)
ctx->media--;
}
free(resp);
} else {
return CUPS_BACKEND_FAILED;
}
/* Try to figure out media subtype */
if (ctx->supports_mediaclassrfid) {
dnpds40_build_cmd(&cmd, "INFO", "MEDIA_CLASS_RFID", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (!resp)
return CUPS_BACKEND_FAILED;
dnpds40_cleanup_string((char*)resp, len);
ctx->media_subtype = atoi((char*)resp);
free(resp);
}
/* And sticker */
if (ctx->supports_media_ext) {
int type;
dnpds40_build_cmd(&cmd, "INFO", "MEDIA_EXT_CODE", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (!resp)
return CUPS_BACKEND_FAILED;
dnpds40_cleanup_string((char*)resp, len);
type = atoi((char*)resp+7);
ctx->media_sticker = (type == 1);
free(resp);
}
if (ctx->conn->type == P_DNP_DS80D) {
if (dnpds80dx_query_paper(ctx))
return CUPS_BACKEND_FAILED;
}
} else {
switch(ctx->conn->type) {
case P_DNP_DS80D:
ctx->duplex_media = 200;
/* Intentional fallthrough */
case P_DNP_DS80:
case P_DNP_DS820:
ctx->media = 510; /* 8x12 */
break;
case P_DNP_DSRX1:
ctx->media = 310; /* 6x8 */
break;
default:
ctx->media = 400; /* 6x9 */
break;
}
if (getenv("MEDIA_CODE"))
ctx->media = strtol(getenv("MEDIA_CODE"), NULL, 10);
}
ctx->last_matte = -1;
#ifdef STATE_DIR
/* Check our current job's lamination vs previous job. */
{
/* Load last matte status from file */
char buf[64];
FILE *f;
snprintf(buf, sizeof(buf), STATE_DIR "/%s-last", ctx->serno);
f = fopen(buf, "r");
if (f) {
fscanf(f, "%d", &ctx->last_matte);
fclose(f);
}
}
#endif
if (test_mode < TEST_MODE_NOATTACH && ctx->supports_mediaoffset) {
/* Get Media Offset */
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
dnpds40_build_cmd(&cmd, "INFO", "MEDIA_OFFSET", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
ctx->mediaoffset = atoi((char*)resp+4);
free(resp);
} else {
return CUPS_BACKEND_FAILED;
}
} else if (!ctx->correct_count) {
ctx->mediaoffset = 50;
}
if (test_mode < TEST_MODE_NOATTACH && ctx->supports_mqty_default) {
struct dnpds40_cmd cmd;
uint8_t *resp;
int len = 0;
dnpds40_build_cmd(&cmd, "INFO", "MQTY_DEFAULT", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (resp) {
dnpds40_cleanup_string((char*)resp, len);
ctx->media_count_new = atoi((char*)resp+4);
free(resp);
ctx->media_count_new -= ctx->mediaoffset;
} else {
return CUPS_BACKEND_FAILED;
}
} else {
/* Look it up for legacy models & FW */
switch (ctx->conn->type) {
case P_DNP_DS40:
switch (ctx->media) {
case 200: // L
ctx->media_count_new = 460;
break;
case 210: // 2L
ctx->media_count_new = 230;
break;
case 300: // PC
ctx->media_count_new = 400;
break;
case 310: // A5
ctx->media_count_new = 200;
break;
case 400: // A5W
ctx->media_count_new = 180;
break;
default:
ctx->media_count_new = 0;
break;
}
break;
case P_DNP_DSRX1:
switch (ctx->media) {
case 210: // 2L
ctx->media_count_new = 350;
break;
case 300: // PC
ctx->media_count_new = 700;
break;
case 310: // A5
ctx->media_count_new = 350;
break;
default:
ctx->media_count_new = 0;
break;
}
break;
case P_CITIZEN_OP900II:
switch (ctx->media) {
case 210: // 2L
ctx->media_count_new = 350;
break;
case 300: // PC
ctx->media_count_new = 600;
break;
case 310: // A5
ctx->media_count_new = 300;
break;
case 400: // A5W
ctx->media_count_new = 280;
break;
default:
ctx->media_count_new = 0;
break;
}
break;
case P_CITIZEN_CW01:
switch (ctx->media) {
case 300: // PC
ctx->media_count_new = 600;
break;
case 350: // 2L
ctx->media_count_new = 230;
break;
case 400: // A5W
ctx->media_count_new = 280;
break;
default:
ctx->media_count_new = 0;
break;
}
break;
case P_DNP_DS80:
case P_DNP_DS80D:
switch (ctx->media) {
case 500: // 8x10
ctx->media_count_new = 130;
break;
case 510: // 8x12
ctx->media_count_new = 110;
break;
default:
ctx->media_count_new = 0;
break;
}
break;
case P_DNP_DS620:
switch (ctx->media) {
case 200: // L
ctx->media_count_new = 420;
break;
case 210: // 2L
ctx->media_count_new = 230;
break;
case 300: // PC
ctx->media_count_new = 400;
break;
case 310: // A5
ctx->media_count_new = 200;
break;
case 400: // A5W
ctx->media_count_new = 180;
break;
default:
ctx->media_count_new = 0;
break;
}
break;
case P_DNP_DS820:
ctx->media_subtype = 1;
switch (ctx->media) {
case 500: // 8x10
ctx->media_count_new = 260; // ???
break;
case 510: // 8x12
ctx->media_count_new = 220; // ???
break;
case 600: // A4
ctx->media_count_new = 220; // ???
break;
default:
ctx->media_count_new = 0;
break;
}
break;
case P_DNP_QW410:
switch (ctx->media) {
case 150: // 4x6
case 160: // 4.5x6
ctx->media_count_new = 150;
break;
case 151: // 4x8
case 161: // 4.5x8
ctx->media_count_new = 110;
break;
default:
ctx->media_count_new = 0;
break;
}
break;
default:
ctx->media_count_new = 0;
break;
}
}
if (test_mode < TEST_MODE_NOATTACH && ctx->supports_systime) {
/* Set Printer Time */
struct dnpds40_cmd cmd;
char buf[16];
struct tm *tm;
time_t now = time(NULL);
tm = localtime(&now);
strftime(buf, sizeof(buf), "%Y%m%d%H%M%S\r", tm); /* YYYYMMDDHHMMSS\n\0 */
dnpds40_build_cmd(&cmd, "CNTRL", "SET_SYS_TIME", 0);
if ((dnpds40_do_cmd(ctx, &cmd, (uint8_t*) buf, sizeof(buf))) != 0)
return CUPS_BACKEND_FAILED;
}
skip_queries:
/* Fill out marker message */
if (ctx->supports_mediaclassrfid) {
snprintf(ctx->media_text, sizeof(ctx->media_text),
"%s %s", dnpds40_media_types(ctx->media, ctx->media_sticker),
rfid_media_subtypes(ctx->media_subtype));
} else {
snprintf(ctx->media_text, sizeof(ctx->media_text),
"%s", dnpds40_media_types(ctx->media, ctx->media_sticker));
}
/* Fill out marker structure */
ctx->marker[0].color = "#00FFFF#FF00FF#FFFF00";
ctx->marker[0].name = ctx->media_text;
ctx->marker[0].numtype = ctx->media;
ctx->marker[0].levelmax = ctx->media_count_new;
ctx->marker[0].levelnow = CUPS_MARKER_UNKNOWN;
ctx->marker_count = 1;
if (ctx->conn->type == P_DNP_DS80D) {
ctx->marker[1].color = "#00FFFF#FF00FF#FFFF00";
ctx->marker[1].name = dnpds80_duplex_media_types(ctx->duplex_media);
ctx->marker[1].numtype = ctx->duplex_media;
ctx->marker[1].levelmax = ctx->marker[0].levelmax/2;
ctx->marker[1].levelnow = CUPS_MARKER_UNKNOWN;
ctx->marker_count++;
}
return CUPS_BACKEND_OK;
}
static void dnpds40_cleanup_job(const void *vjob) {
const struct dnpds40_printjob *job = vjob;
if (job->databuf)
free(job->databuf);
free((void*)job);
}
static void dnpds40_teardown(void *vctx) {
struct dnpds40_ctx *ctx = vctx;
if (!ctx)
return;
if (test_mode < TEST_MODE_NOATTACH && ctx->conn->type == P_DNP_DS80D) {
struct dnpds40_cmd cmd;
/* Check to see if last print was the front side
of a duplex job, and if so, cancel things so we're done */
if (ctx->last_multicut >= 200 &&
ctx->last_multicut < 300) {
dnpds40_build_cmd(&cmd, "CNTRL", "DUPLEX_CANCEL", 0);
if ((dnpds40_do_cmd(ctx, &cmd, NULL, 0)) != 0)
return;
}
}
if (ctx->serno)
free(ctx->serno);
if (ctx->version)
free(ctx->version);
free(ctx);
}
static int dnpds40_query_status(struct dnpds40_ctx *ctx)
{
struct dnpds40_cmd cmd;
uint8_t *resp;
int count, len;
if (test_mode >= TEST_MODE_NOATTACH)
return 9999;
/* Generate command */
dnpds40_build_cmd(&cmd, "STATUS", "", 0);
resp = dnpds40_resp_cmd(ctx, &cmd, &len);
if (!resp)
return 9999;
dnpds40_cleanup_string((char*)resp, len);
count = atoi((char*)resp);
free(resp);
return count;
}
static int dnpds40_read_parse(void *vctx, const void **vjob, int data_fd, int copies) {
struct dnpds40_ctx *ctx = vctx;
int run = 1;
char buf[9] = { 0 };
struct dnpds40_printjob *job = NULL;
if (!ctx)
return CUPS_BACKEND_FAILED;
job = malloc(sizeof(*job));
if (!job) {
ERROR("Memory allocation failure!\n");
return CUPS_BACKEND_RETRY_CURRENT;
}
memset(job, 0, sizeof(*job));
job->common.jobsize = sizeof(*job);
job->printspeed = -1;
job->buffcntrl = -1;
/* There's no way to figure out the total job length in advance, we
have to parse the stream until we get to the image plane data,
and even then the stream can contain arbitrary commands later.
So instead, we allocate a buffer of the maximum possible length,
then parse the incoming stream until we hit the START command at
the end of the job.
*/
job->databuf = malloc(MAX_PANOPRINTJOB_LEN);
if (!job->databuf) {
dnpds40_cleanup_job(job);
ERROR("Memory allocation failure!\n");
return CUPS_BACKEND_RETRY_CURRENT;
}
while (run) {
int i, j;
uint32_t remain;
/* Make sure we won't overflow... */
if (job->datalen + sizeof(struct dnpds40_cmd) > MAX_PANOPRINTJOB_LEN) {
ERROR("Buffer overflow when parsing printjob! (%d+%lu)\n",
job->datalen, sizeof(struct dnpds40_cmd));
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* Read in command header */
i = read(data_fd, job->databuf + job->datalen,
sizeof(struct dnpds40_cmd));
if (i < 0) {
dnpds40_cleanup_job(job);
return i;
} else if (i == 0) {
break;
} else if (i < (int) sizeof(struct dnpds40_cmd)) {
int r = i;
i = read(data_fd, job->databuf + job->datalen + r, sizeof(struct dnpds40_cmd) - r);
if (i < 0) {
dnpds40_cleanup_job(job);
return i;
} else if (i == 0) {
break;
} else if (i < (int)(sizeof(struct dnpds40_cmd) - r)) {
ERROR("Double short read (%d + %d vs %d)\n", r, i, (int)sizeof(struct dnpds40_cmd));
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
}
/* Special case handling for beginning of job */
if (job->datalen == 0) {
/* See if job lacks the standard ESC-P start sequence */
if (job->databuf[job->datalen + 0] != 0x1b ||
job->databuf[job->datalen + 1] != 0x50) {
switch(ctx->conn->type) {
case P_DNP_QW410:
i = legacy_qw410_read_parse(job, data_fd, i);
break;
case P_CITIZEN_CW01:
i = legacy_cw01_read_parse(job, data_fd, i);
break;
case P_DNP_DS620:
i = legacy_dnp620_read_parse(job, data_fd, i);
break;
case P_DNP_DS820:
i = legacy_dnp820_read_parse(job, data_fd, i);
break;
case P_DNP_DSRX1:
case P_DNP_DS40:
case P_DNP_DS80:
case P_DNP_DS80D:
default:
i = legacy_dnp_read_parse(job, data_fd, i);
break;
}
if (i == CUPS_BACKEND_OK) {
goto parsed;
}
dnpds40_cleanup_job(job);
return i;
}
}
/* Parse out length of data chunk, if any */
memcpy(buf, job->databuf + job->datalen + 24, 8);
j = atoi(buf);
/* Read in data chunk as quickly as possible */
remain = j;
/* Make sure we won't overflow... */
if (job->datalen + remain > MAX_PANOPRINTJOB_LEN) {
ERROR("Buffer overflow when parsing printjob! (%d+%d)\n",
job->datalen, remain);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
while (remain > 0) {
i = read(data_fd, job->databuf + job->datalen + sizeof(struct dnpds40_cmd),
remain);
if (i < 0) {
ERROR("Data Read Error: %d (%d/%d @%d/%d)\n", i, remain, j, job->datalen,MAX_PANOPRINTJOB_LEN);
dnpds40_cleanup_job(job);
return i;
}
if (i == 0) {
dnpds40_cleanup_job(job);
return 1;
}
job->datalen += i;
remain -= i;
}
job->datalen -= j; /* Back it off */
/* Check for some offsets */
if(!memcmp("CNTRL QTY", job->databuf + job->datalen+2, 9)) {
memcpy(buf, job->databuf + job->datalen + 32, 8);
job->common.copies = atoi(buf);
continue;
}
if(!memcmp("CNTRL CUTTER", job->databuf + job->datalen+2, 12)) {
memcpy(buf, job->databuf + job->datalen + 32, 8);
job->cutter = atoi(buf);
/* We'll insert it ourselves later */
if (job->cutter == 1000)
job->is_pano = 1;
continue;
}
if(!memcmp("CNTRL BUFFCNTRL", job->databuf + job->datalen+2, 15)) {
memcpy(buf, job->databuf + job->datalen + 32, 8);
job->buffcntrl = atoi(buf);
continue;
}
if(!memcmp("CNTRL OVERCOAT", job->databuf + job->datalen+2, 14)) {
if (ctx->supports_matte) {
memcpy(buf, job->databuf + job->datalen + 32, 8);
job->matte = atoi(buf);
} else {
WARNING("Printer FW does not support matte prints, using glossy mode\n");
}
if (job->matte && ctx->media_sticker) {
WARNING("Sticker media, forcing glossy mode\n");
job->matte = 0;
}
/* We'll insert our own later, if appropriate */
continue;
}
if(!memcmp("IMAGE MULTICUT", job->databuf + job->datalen+2, 14)) {
memcpy(buf, job->databuf + job->datalen + 32, 8);
job->multicut = atoi(buf);
/* Backend automatically handles rewind support, so
ignore application requests to use it. */
if (job->multicut > 400 && job->multicut < 1000)
job->multicut -= 400;
/* We'll insert this ourselves later. */
continue;
}
if(!memcmp("CNTRL FULL_CUTTER_SET", job->databuf + job->datalen+2, 21)) {
if (!ctx->supports_fullcut) {
WARNING("Printer FW does not support full cutter control!\n");
continue;
}
if (ctx->conn->type == P_DNP_DS820) {
if (j != 24) {
WARNING("Full cutter argument length incorrect, ignoring!\n");
continue;
}
} else if (j != 16) {
WARNING("Full cutter argument length incorrect, ignoring!\n");
continue;
} else if (!ctx->supports_adv_fullcut) {
if (job->databuf[job->datalen + 32 + 12] != '0' ||
job->databuf[job->datalen + 32 + 13] != '0' ||
job->databuf[job->datalen + 32 + 14] != '0') {
WARNING("Full cutter scrap setting not supported on this firmware, ignoring!\n");
continue;
}
}
// XXX enforce cut counts/sizes?
job->fullcut = 1;
}
if(!memcmp("IMAGE YPLANE", job->databuf + job->datalen + 2, 12)) {
uint32_t y_ppm; /* Pixels Per Meter */
/* Validate vertical resolution */
memcpy(&y_ppm, job->databuf + job->datalen + 32 + 42, sizeof(y_ppm));
y_ppm = le32_to_cpu(y_ppm);
switch (y_ppm) {
case 11808:
job->dpi = 300;
break;
case 13146:
job->dpi = 334;
break;
case 23615:
case 23616:
job->dpi = 600;
break;
default:
ERROR("Unrecognized printjob resolution (%u ppm)\n", y_ppm);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* Validate horizontal size */
memcpy(&y_ppm, job->databuf + job->datalen + 32 + 18, sizeof(y_ppm));
y_ppm = le32_to_cpu(y_ppm);
if (y_ppm != ctx->native_width) {
ERROR("Incorrect horizontal resolution (%u), aborting!\n", y_ppm);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* Capture number of rows */
memcpy(&y_ppm, job->databuf + job->datalen + 32 + 22, sizeof(y_ppm));
job->rows = le32_to_cpu(y_ppm);
}
if(!memcmp("CNTRL PRINTSPEED", job->databuf + job->datalen + 2, 16)) {
if (!ctx->supports_printspeed) {
WARNING("Printer does not support PRINTSPEED\n");
continue;
}
memcpy(buf, job->databuf + job->datalen + 32, 8);
job->printspeed = atoi(buf) / 10;
/* We'll insert this ourselves later. */
continue;
}
if(!memcmp("CNTRL CONT_PANORAMA", job->databuf + job->datalen + 2, 18)) {
if (!ctx->supports_contpano) {
ERROR("Printer does not support Continuous Panorama, aborting!\n");
continue;
}
memcpy(buf, job->databuf + job->datalen + 32 + 4, 4);
job->is_pano = atoi(buf);
}
/* This is the last block.. */
if(!memcmp("CNTRL START", job->databuf + job->datalen + 2, 11))
run = 0;
/* Add in the size of this chunk */
job->datalen += sizeof(struct dnpds40_cmd) + j;
}
/* Figure out the number of buffers we need. */
job->buf_needed = 1;
if (job->dpi == 600) {
switch(ctx->conn->type) {
case P_DNP_DS620:
if (job->multicut == MULTICUT_6x9 ||
job->multicut == MULTICUT_6x4_5X2)
job->buf_needed = 2;
break;
case P_DNP_DS80: /* DS80/CX-W */
if (job->matte && (job->multicut == MULTICUT_8xA4LEN ||
job->multicut == MULTICUT_8x4X3 ||
job->multicut == MULTICUT_8x8_8x4 ||
job->multicut == MULTICUT_8x6X2 ||
job->multicut == MULTICUT_8x12))
job->buf_needed = 2;
break;
case P_DNP_DS80D:
if (job->matte) {
int mcut = job->multicut;
if (mcut > MULTICUT_S_BACK)
mcut -= MULTICUT_S_BACK;
else if (mcut > MULTICUT_S_FRONT)
mcut -= MULTICUT_S_FRONT;
if (mcut == MULTICUT_8xA4LEN ||
mcut == MULTICUT_8x4X3 ||
mcut == MULTICUT_8x8_8x4 ||
mcut == MULTICUT_8x6X2 ||
mcut == MULTICUT_8x12)
job->buf_needed = 2;
if (mcut == MULTICUT_S_8x12 ||
mcut == MULTICUT_S_8x6X2 ||
mcut == MULTICUT_S_8x4X3)
job->buf_needed = 2;
}
break;
case P_DNP_DS820:
// Nothing; all sizes only need 1 buffer
break;
case P_CITIZEN_CW01:
job->buf_needed = 2;
break;
default: /* DS40/CX/RX1/CY/everything else */
if (job->matte) {
if (job->multicut == MULTICUT_6x8 ||
job->multicut == MULTICUT_6x9 ||
job->multicut == MULTICUT_6x4X2 ||
job->multicut == MULTICUT_5x7 ||
job->multicut == MULTICUT_5x3_5X2)
job->buf_needed = 2;
} else {
if (job->multicut == MULTICUT_6x8 ||
job->multicut == MULTICUT_6x9 ||
job->multicut == MULTICUT_6x4X2)
job->buf_needed = 1;
}
break;
}
}
if (job->multicut >= MULTICUT_PANO_6x14)
{
int rval = dnp_panorama_splitjob(ctx, job, (struct dnpds40_printjob**)vjob);
/* Clean up original job regardless */
dnpds40_cleanup_job(job);
if (rval)
return rval;
/* And continue validating everything based on the 1st job */
job = (struct dnpds40_printjob*) *vjob;
}
parsed:
/* If we have no data.. don't bother */
if (!job->datalen) {
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* Use the larger of the copy arguments */
if (!job->is_pano && job->common.copies < copies)
job->common.copies = copies;
/* Make sure advanced matte modes are supported */
if (job->matte > 100) {
if (!ctx->supports_advmatte) {
ERROR("Printer does not support advanced matte modes, aborting!\n");
return CUPS_BACKEND_CANCEL;
}
if (ctx->partialmatte == 1) {
ctx->partialmatte = 2;
} else {
INFO("Partial matte lamination layer!\n");
ctx->partialmatte = 1;
}
} else {
if (ctx->partialmatte == 1)
ctx->partialmatte = 2;
}
/* Sanity check matte mode */
if ((job->matte == 21 || job->matte == 121) && !ctx->supports_finematte) {
WARNING("Printer FW does not support Fine Matte mode, downgrading to normal matte\n");
job->matte = 1;
} else if ((job->matte == 22 || job->matte == 122) && !ctx->supports_luster) {
WARNING("Printer FW does not support Luster mode, downgrading to normal matte\n");
job->matte = 1;
} else if (job->matte > 1 && !ctx->supports_advmatte) {
WARNING("Printer FW does not support advanced matte modes, downgrading to normal matte\n");
job->matte = 1;
}
/* QW410 only supports 0 and 3, supposedly. */
if (ctx->conn->type == P_DNP_QW410 && job->printspeed > 1) {
job->printspeed = 3;
}
/* Pick a sane default value for printspeed if not specified */
if (job->printspeed == -1 || job->printspeed > 3)
{
if (job->dpi == 600)
job->printspeed = 1;
else
job->printspeed = 0;
}
if (job->dpi == 600 && !ctx->supports_600dpi) {
ERROR("Printer does not support 600dpi!\n");
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* And sanity-check whatever value is there */
if (job->printspeed == 0 && job->dpi == 600) {
job->printspeed = 1;
} else if (job->printspeed >= 1 && job->dpi == 300) {
job->printspeed = 0;
}
/* Make sure MULTICUT is sane, most validation needs this */
if (!job->multicut && ctx->conn->type != P_CITIZEN_CW01) {
WARNING("Missing or illegal MULTICUT command!\n");
if (job->dpi == 300)
job->buf_needed = 1;
else
job->buf_needed = 2;
goto skip_checks;
}
/* Only DS80D supports Cut Paper types */
if (job->multicut > 100) {
if ( ctx->conn->type == P_DNP_DS80D) {
job->cut_paper = 1;
} else {
ERROR("Only DS80D supports cut-paper sizes!\n");
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
}
if (job->dpi == 334 && ctx->conn->type != P_CITIZEN_CW01)
{
ERROR("Illegal resolution (%u) for printer!\n", job->dpi);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* Sanity-check type vs loaded media */
if (job->multicut == 0)
goto skip_multicut;
/* Extra sanity checks */
if (ctx->media == 0) {
int status = dnpds40_query_status(ctx);
if (status > 1000) {
ERROR("User-Recoverable Printer Error: %d => %s, halting queue!\n", status, dnpds40_statuses(status));
return CUPS_BACKEND_STOP;
}
if (status > 2000) {
ERROR("Fatal Printer Hardware Error: %d => %s, halting queue!\n", status, dnpds40_statuses(status));
return CUPS_BACKEND_STOP;
}
}
if (job->multicut < 100) {
switch(ctx->media) {
case 150: // 4x6, QW410
if(job->multicut != MULTICUT_4x3 &&
job->multicut != MULTICUT_4x4 &&
job->multicut != MULTICUT_4x4_5 &&
// XXX job->multicut != MULTICUT_4x3X2 &&
job->multicut != MULTICUT_4x6) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
break;
case 151: // 4x8, QW410
if (job->multicut != MULTICUT_4x3 &&
job->multicut != MULTICUT_4x4 &&
job->multicut != MULTICUT_4x4_5 &&
job->multicut != MULTICUT_4x6 &&
// XXX job->multicut != MULTICUT_4x3X2 &&
// XXX job->multicut != MULTICUT_4x4X2 &&
job->multicut != MULTICUT_4x8) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
break;
case 160: // 4.5x6, QW410
if (job->multicut != MULTICUT_4_5x3 &&
job->multicut != MULTICUT_4_5x4 &&
job->multicut != MULTICUT_4_5x4_5 &&
// XXX job->multicut != MULTICUT_4_5x3X2 &&
job->multicut != MULTICUT_4_5x6) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
break;
case 161: // 4.5x8, QW410
if (job->multicut != MULTICUT_4_5x3 &&
job->multicut != MULTICUT_4_5x4 &&
job->multicut != MULTICUT_4_5x4_5 &&
job->multicut != MULTICUT_4_5x6 &&
// XXX job->multicut != MULTICUT_4_5x4X2 &&
// XXX job->multicut != MULTICUT_4_5x3X2 &&
job->multicut != MULTICUT_4_5x8) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
break;
case 200: //"5x3.5 (L)"
if (job->multicut != MULTICUT_5x3_5) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
break;
case 210: //"5x7 (2L)"
if (job->multicut != MULTICUT_5x3_5 && job->multicut != MULTICUT_5x7 &&
job->multicut != MULTICUT_5x3_5X2 && job->multicut != MULTICUT_5x5) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* Only 3.5x5 on 7x5 media can be rewound */
if (job->multicut == MULTICUT_5x3_5)
job->can_rewind = 1;
break;
case 300: //"6x4 (PC)"
if (job->multicut != MULTICUT_6x4) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
break;
case 310: //"6x8 (A5)"
if (job->multicut != MULTICUT_6x4 && job->multicut != MULTICUT_6x8 &&
job->multicut != MULTICUT_6x4X2 &&
job->multicut != MULTICUT_6x6 && job->multicut != 30) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* Only 6x4 on 6x8 media can be rewound */
if (job->multicut == MULTICUT_6x4)
job->can_rewind = 1;
break;
case 400: //"6x9 (A5W)"
if (job->multicut != MULTICUT_6x4 && job->multicut != MULTICUT_6x8 &&
job->multicut != MULTICUT_6x9 && job->multicut != MULTICUT_6x4X2 &&
job->multicut != MULTICUT_6x6 &&
job->multicut != MULTICUT_6x4_5 && job->multicut != MULTICUT_6x4_5X2) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* Only 6x4 or 6x4.5 on 6x9 media can be rewound */
if (job->multicut == MULTICUT_6x4 || job->multicut == MULTICUT_6x4_5)
job->can_rewind = 1;
break;
case 500: //"8x10"
if (ctx->conn->type == P_DNP_DS820 &&
(job->multicut == MULTICUT_8x7 || job->multicut == MULTICUT_8x9)) {
/* These are okay */
} else if (job->multicut < MULTICUT_8x10 || job->multicut == MULTICUT_8x12 ||
job->multicut == MULTICUT_8x6X2 || job->multicut >= MULTICUT_8x6_8x5 ) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* 8x4, 8x5 can be rewound */
if (job->multicut == MULTICUT_8x4 ||
job->multicut == MULTICUT_8x5)
job->can_rewind = 1;
break;
case 510: //"8x12"
if (job->multicut < MULTICUT_8x10 || (job->multicut > MULTICUT_8xA4LEN && !(job->multicut == MULTICUT_8x7 || job->multicut == MULTICUT_8x9))) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* 8x4, 8x5, 8x6 can be rewound */
if (job->multicut == MULTICUT_8x4 ||
job->multicut == MULTICUT_8x5 ||
job->multicut == MULTICUT_8x6)
job->can_rewind = 1;
break;
case 600: //"A4"
if (job->multicut < MULTICUT_A5 || job->multicut > MULTICUT_A4x5X2) {
ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
dnpds40_cleanup_job(job);
return CUPS_BACKEND_CANCEL;
}
/* Only A4x4 and A5 can be rewound */
// XXX we can fake more with fancy multicuts...
if (job->multicut == MULTICUT_A4x5 ||
job<