You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

561 lines
18 KiB

// To get required headers, run
// sudo apt-get install libcups2-dev libcupsimage2-dev
#include <cups/cups.h>
#include <cups/ppd.h>
#include <cups/raster.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#ifndef DEBUGFILE
#define DEBUGFILE "/tmp/debugraster.txt"
#endif
static inline int min(int a, int b) {
if (a > b)
return b;
return a;
}
// settings and their stuff
struct settings_ {
int modelNum; // the only setting we get from PPD.
unsigned CashDrawer1;
unsigned CashDrawer2;
unsigned CashDrawer1On;
unsigned CashDrawer1Off;
unsigned CashDrawer2On;
unsigned CashDrawer2Off;
cups_bool_t InsertSheet;
cups_adv_t AdvanceMedia;
cups_cut_t CutMedia;
unsigned int AdvanceDistance;
};
struct settings_ settings;
static void initializeSettings(char *commandLineOptionSettings, struct settings_ *pSettings) {
ppd_file_t *pPpd = ppdOpenFile(getenv("PPD"));
// char* sDestination = getenv("DestinationPrinterID");
memset(pSettings, 0, sizeof(struct settings_));
pSettings->modelNum = pPpd->model_number;
ppdClose(pPpd);
}
static void update_settings_from_job (cups_page_header2_t * pHeader)
{
if (!pHeader)
return;
settings.CashDrawer1 = pHeader->cupsInteger[0];
settings.CashDrawer2 = pHeader->cupsInteger[1];
settings.CashDrawer1On = pHeader->cupsInteger[2];
settings.CashDrawer1Off = pHeader->cupsInteger[3];
settings.CashDrawer2On = pHeader->cupsInteger[4];
settings.CashDrawer2Off = pHeader->cupsInteger[5];
settings.InsertSheet = pHeader->cupsInteger[6];
settings.AdvanceMedia = pHeader->AdvanceMedia;
settings.CutMedia = pHeader->CutMedia;
settings.AdvanceDistance = pHeader->AdvanceDistance;
}
#ifndef DEBUGP
static inline void mputchar(char c) { putchar(c); }
#define DEBUGSTARTPRINT()
#define DEBUGFINISHPRINT()
#define DEBUGPRINT(...)
#else
FILE *lfd = 0;
// putchar with debug wrappers
static inline void mputchar(char c) {
unsigned char m = c;
if (lfd)
fprintf(lfd, "%02x ", m);
putchar(m);
}
// on macos cups filters works in a sandbox and cant write
// filtes everywhere. We'll use $TMPDIR/debugraster.txt for them
#ifdef SAFEDEBUG
static inline void DEBUGSTARTPRINT() {
char * tmpfolder = getenv("TMPDIR");
const char *filename = "/debugraster.txt";
char *dbgpath = (char *)malloc(strlen(tmpfolder) + strlen(filename) + 1);
strcpy(dbgpath,tmpfolder);
strcat(dbgpath,filename);
lfd = fopen(dbgpath,"w");
free(dbgpath);
}
#else
#define DEBUGSTARTPRINT() lfd = fopen(DEBUGFILE, "w")
#endif
#define DEBUGFINISHPRINT() \
if (lfd) \
fclose(lfd)
#define DEBUGPRINT(...) if (lfd) fprintf(lfd, "\n" __VA_ARGS__)
#endif
// procedure to output an array
static inline void outputarray(const char *array, int length) {
int i = 0;
for (; i < length; ++i)
mputchar(array[i]);
}
// output a command. -1 is because we determine them as string literals,
// so \0 implicitly added at the end, but we don't need it at all
#define SendCommand(COMMAND) outputarray((COMMAND),sizeof((COMMAND))-1)
static inline void mputnum(unsigned int val) {
mputchar(val&0xFFU);
mputchar((val>>8)&0xFFU);
}
/*
* zj uses kind of epson ESC/POS dialect code. Here is subset of commands
*
* initialize - esc @
* cash drawer 1 - esc p 0 @ P
* cash drawer 2 - esc p 1 @ P // @ =0x40 and P=0x50 <N> and <M>
* where <N>*2ms is pulse on time, <M>*2ms is pulse off.
* start raster - GS v 0 // 0 is full-density, may be also 1, 2, 4
* skip lines - esc J // then N [0..255], each value 1/44 inches (0.176mm)
* // another commands out-of-spec:
* esc 'i' - cutter; xprinter android example shows as GS V \1 (1D 56 01)
* esc '8' - wait{4, cutter also (char[4]){29,'V','A',20}}; GS V 'A' 20
*/
// define printer initialize command
static const char escInit[] = "\x1b@";
// define cashDrawerEjector command
static const char escCashDrawerEject[] = "\x1bp";
// define raster mode start command
static const char escRasterMode[] = "\x1dv0\0";
// define flush command
static const char escFlush[] = "\x1bJ";
// define cut command
//static const char escCut[] = "\x1bi";
static const char escCut[] = "\x1dV\1";
// enter raster mode and set up x and y dimensions
static inline void sendRasterHeader(int xsize, int ysize) {
// outputCommand(rasterModeStartCommand);
SendCommand(escRasterMode);
mputnum(xsize);
mputnum(ysize);
}
static inline void flushLines(unsigned short lines)
{
SendCommand(escFlush);
mputchar (lines);
}
// print all unprinted (i.e. flush the buffer)
static inline void flushBuffer() {
flushLines(0);
}
// flush, then feed 24 lines
static inline void flushManyLines(int iLines)
{
DEBUGPRINT("Skip %d empty lines: ", iLines);
while ( iLines )
{
int iStride = min (iLines, 24);
flushLines ( iStride );
iLines -= iStride;
}
}
inline static void CashDrawerEject (unsigned iDrawer, unsigned iOn,
unsigned iOff)
{
SendCommand(escCashDrawerEject);
mputchar(iDrawer);
mputchar(iOn);
mputchar(iOff);
}
inline static void cutMedia()
{
SendCommand(escCut);
}
// sent on the beginning of print job
void setupJob() {
SendCommand(escInit);
if (settings.CashDrawer1 == 1)
CashDrawerEject(0,settings.CashDrawer1On,settings.CashDrawer1Off);
if (settings.CashDrawer2 == 1)
CashDrawerEject(1, settings.CashDrawer2On, settings.CashDrawer2Off);
}
// sent at the very end of print job
void finalizeJob() {
if (settings.CashDrawer1 == 2)
CashDrawerEject(0, settings.CashDrawer1On, settings.CashDrawer1Off);
if (settings.CashDrawer2 == 2)
CashDrawerEject(1, settings.CashDrawer2On, settings.CashDrawer2Off);
// SendCommand(escInit);
}
// sent at the end of every page
#ifndef __sighandler_t
typedef void (*__sighandler_t)(int);
#endif
__sighandler_t old_signal;
void finishPage() {
signal(SIGTERM, old_signal);
}
// sent on job canceling
void cancelJob() {
int i = 0;
for (; i < 0x258; ++i)
mputchar(0);
finishPage();
}
// invoked before starting to print a page
void startPage() { old_signal = signal(SIGTERM, cancelJob); }
void DebugPrintHeader (cups_page_header2_t* pHeader)
{
DEBUGPRINT(
"MediaClass '%s'\n"
"MediaColor '%s'\n"
"MediaType '%s'\n"
"OutputType '%s'\n"
"AdvanceDistance %d\n"
"AdvanceMedia %d\n"
"Collate %d\n"
"CutMedia %d\n"
"Duplex %d\n"
"HWResolution %d %d\n"
"ImagingBoundingBox %d %d %d %d\n"
"InsertSheet %d\n"
"Jog %d\n"
"LeadingEdge %d\n"
"Margins %d %d\n"
"ManualFeed %d\n"
"MediaPosition %d\n"
"MediaWeight %d\n"
"MirrorPrint %d\n"
"NegativePrint %d\n"
"NumCopies %d\n"
"Orientation %d\n"
"OutputFaceUp %d\n"
"PageSize %d %d\n"
"Separations %d\n"
"TraySwitch %d\n"
"Tumble %d\n"
"cupsWidth %d\n"
"cupsHeight %d\n"
"cupsMediaType %d\n"
"cupsBitsPerColor %d\n"
"cupsBitsPerPixel %d\n"
"cupsBytesPerLine %d\n"
"cupsColorOrder %d\n"
"cupsColorSpace %d\n"
"cupsCompression %d\n"
"cupsRowCount %d\n"
"cupsRowFeed %d\n"
"cupsRowStep %d\n"
"cupsNumColors %d\n"
"cupsBorderlessScalingFactor %f\n"
"cupsPageSize %f %f\n"
"cupsImagingBBox %f %f %f %f\n"
"cupsInteger %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n"
"cupsReal %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n"
"cupsString '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s'\n"
"cupsMarkerType '%s'\n"
"cupsRenderingIntent '%s'\n"
"cupsPageSizeName '%s'\n",
pHeader->MediaClass, pHeader->MediaColor, pHeader->MediaType,
pHeader->OutputType, pHeader->AdvanceDistance, pHeader->AdvanceMedia,
pHeader->Collate, pHeader->CutMedia, pHeader->Duplex,
pHeader->HWResolution[0], pHeader->HWResolution[1],
pHeader->ImagingBoundingBox[0], pHeader->ImagingBoundingBox[1],
pHeader->ImagingBoundingBox[2], pHeader->ImagingBoundingBox[3],
pHeader->InsertSheet, pHeader->Jog, pHeader->LeadingEdge, pHeader->Margins[0],
pHeader->Margins[1], pHeader->ManualFeed, pHeader->MediaPosition,
pHeader->MediaWeight, pHeader->MirrorPrint, pHeader->NegativePrint,
pHeader->NumCopies, pHeader->Orientation, pHeader->OutputFaceUp,
pHeader->PageSize[0], pHeader->PageSize[1], pHeader->Separations,
pHeader->TraySwitch, pHeader->Tumble, pHeader->cupsWidth, pHeader->cupsHeight,
pHeader->cupsMediaType, pHeader->cupsBitsPerColor, pHeader->cupsBitsPerPixel,
pHeader->cupsBytesPerLine, pHeader->cupsColorOrder, pHeader->cupsColorSpace,
pHeader->cupsCompression, pHeader->cupsRowCount, pHeader->cupsRowFeed,
pHeader->cupsRowStep, pHeader->cupsNumColors,
pHeader->cupsBorderlessScalingFactor, pHeader->cupsPageSize[0],
pHeader->cupsPageSize[1], pHeader->cupsImagingBBox[0],
pHeader->cupsImagingBBox[1], pHeader->cupsImagingBBox[2],
pHeader->cupsImagingBBox[3], pHeader->cupsInteger[0],
pHeader->cupsInteger[1], pHeader->cupsInteger[2], pHeader->cupsInteger[3],
pHeader->cupsInteger[4], pHeader->cupsInteger[5], pHeader->cupsInteger[6],
pHeader->cupsInteger[7], pHeader->cupsInteger[8], pHeader->cupsInteger[9],
pHeader->cupsInteger[10], pHeader->cupsInteger[11], pHeader->cupsInteger[12],
pHeader->cupsInteger[13], pHeader->cupsInteger[14], pHeader->cupsInteger[15],
pHeader->cupsReal[0], pHeader->cupsReal[1], pHeader->cupsReal[2],
pHeader->cupsReal[3], pHeader->cupsReal[4], pHeader->cupsReal[5],
pHeader->cupsReal[6], pHeader->cupsReal[7], pHeader->cupsReal[8],
pHeader->cupsReal[9], pHeader->cupsReal[10], pHeader->cupsReal[11],
pHeader->cupsReal[12], pHeader->cupsReal[13], pHeader->cupsReal[14],
pHeader->cupsReal[15], pHeader->cupsString[0], pHeader->cupsString[1],
pHeader->cupsString[2], pHeader->cupsString[3], pHeader->cupsString[4],
pHeader->cupsString[5], pHeader->cupsString[6], pHeader->cupsString[7],
pHeader->cupsString[8], pHeader->cupsString[9], pHeader->cupsString[10],
pHeader->cupsString[11], pHeader->cupsString[12], pHeader->cupsString[13],
pHeader->cupsString[14], pHeader->cupsString[15], pHeader->cupsMarkerType,
pHeader->cupsRenderingIntent, pHeader->cupsPageSizeName);
}
// rearrange (compress) rows in pBuf, discarding tails of them
static inline unsigned compress_buffer(unsigned char *pBuf, unsigned iSize,
unsigned int iWideStride, unsigned int iStride) {
const unsigned char *pEnd = pBuf + iSize;
unsigned char *pTarget = pBuf;
while (pBuf < pEnd) {
int iBytes = min(pEnd - pBuf, iStride);
memmove(pTarget, pBuf, iBytes);
pTarget += iBytes;
pBuf += iWideStride;
}
return min(iSize, pTarget - pBuf);
}
// returns -1 if whole line iz filled by zeros. Otherwise 0.
static inline int line_is_empty(const unsigned char *pBuf, unsigned iSize) {
int i;
for (i = 0; i < iSize; ++i)
if (pBuf[i])
return 0;
return -1;
}
static inline void send_raster(const unsigned char *pBuf, int width8,
int height) {
if (!height)
return;
DEBUGPRINT("Raster block %dx%d pixels\n", width8 * 8, height);
#ifdef NEWRASTER
// experimental - output raster according to new ESC/POS specification,
// using graphic function.
// It may work with kind of epson, but zijiang fails, oops...
// download raster - function 112
SendCommand("\x1d(L"); // GS ( L
mputnum(width8 * height + 10); // pL, pH
mputchar ('0'); // m=48
mputchar (112); // fn
mputchar ('0'); // monochrome digital
mputchar ('\1'); // bx
mputchar ('\1'); // by
mputchar ('1'); // с (color)= 1
mputnum(width8*8); // xL, xH
mputnum(height); // yL, yH
outputarray((char *)pBuf, width8 * height);
// print raster - function 50
SendCommand("\x1d(L");
mputnum (2); // pL, pH
mputchar (48); // m
mputchar (50); // function = 50
#else
sendRasterHeader(width8, height);
outputarray((char *)pBuf, width8 * height);
flushBuffer();
#endif
}
#define EXITPRINT(CODE) \
{ \
if (pRasterSrc) \
cupsRasterClose(pRasterSrc); \
if (pRasterBuf) \
free(pRasterBuf); \
if (fd) \
close(fd); \
return (CODE); \
}
//////////////////////////////////////////////
//////////////////////////////////////////////
int main(int argc, char *argv[]) {
signal(SIGPIPE, SIG_IGN);
if (argc < 6 || argc > 7) {
fputs("ERROR: rastertozj job-id user title copies options [file]\n",
stderr);
return EXIT_FAILURE;
}
int fd = STDIN_FILENO; // File descriptor providing CUPS raster data
if (argc == 7) {
if ((fd = open(argv[6], O_RDONLY)) == -1) {
perror("ERROR: Unable to open raster file - ");
sleep(1);
return EXIT_FAILURE;
}
}
DEBUGSTARTPRINT();
int iCurrentPage = 0;
// CUPS Page tHeader
cups_page_header2_t tHeader;
unsigned char *pRasterBuf = NULL; // Pointer to raster data from CUPS
// Raster stream for printing
cups_raster_t *pRasterSrc = cupsRasterOpen(fd, CUPS_RASTER_READ);
initializeSettings(argv[5],&settings);
DEBUGPRINT("ModelNumber from PPD '%d'\n", settings.modelNum);
// set max num of pixels per line depended from model number (from ppd)
int iMaxWidth = settings.modelNum;
if (!iMaxWidth)
iMaxWidth = 0x240;
// postpone start of the job until we parse first header and get necessary values from there.
int iJobStarted = 0;
// loop over the whole raster, page by page
while (cupsRasterReadHeader2(pRasterSrc, &tHeader)) {
if ((!tHeader.cupsHeight) || (!tHeader.cupsBytesPerLine))
break;
update_settings_from_job( &tHeader );
if (!iJobStarted)
{
setupJob();
iJobStarted = 1;
}
DebugPrintHeader ( &tHeader );
if (!pRasterBuf) {
pRasterBuf = malloc(tHeader.cupsBytesPerLine * 24);
if (!pRasterBuf) // hope it never goes here...
EXITPRINT(EXIT_FAILURE)
}
fprintf(stderr, "PAGE: %d %d\n", ++iCurrentPage, tHeader.NumCopies);
startPage();
// calculate num of bytes to print given width having 1 bit per pixel.
int foo = min(tHeader.cupsWidth, iMaxWidth); // 0x240 for 80mm (72mm printable)
foo = (foo + 7) & 0xFFFFFFF8;
int width_bytes = foo >> 3; // in bytes, [0..0x30]
DEBUGPRINT("cupsWidth=%d, cupsBytesPerLine=%d; foo=%d, width_bytes=%d",
tHeader.cupsWidth, tHeader.cupsBytesPerLine, foo, width_bytes );
int iRowsToPrint = tHeader.cupsHeight;
int zeroy = 0;
// loop over one page, top to bottom by blocks of most 24 scan lines
while (iRowsToPrint) {
fprintf(stderr, "INFO: Printing iCurrentPage %d, %d%% complete...\n",
iCurrentPage,
(100 * (tHeader.cupsHeight - iRowsToPrint) / tHeader.cupsHeight));
int iBlockHeight = min(iRowsToPrint, 24);
DEBUGPRINT("--------Processing block of %d, starting from %d lines",
iBlockHeight, tHeader.cupsHeight - iRowsToPrint);
iRowsToPrint -= iBlockHeight;
unsigned iBytesChunk = 0;
// first, fetch whole block from the image
if (iBlockHeight)
iBytesChunk = cupsRasterReadPixels(
pRasterSrc, pRasterBuf, tHeader.cupsBytesPerLine * iBlockHeight);
DEBUGPRINT("--------Got %d from %d requested bytes",
iBytesChunk,
tHeader.cupsBytesPerLine *
iBlockHeight);
// if original image is wider - rearrange buffer so that our calculated
// lines come one-by-one without extra gaps
if (width_bytes < tHeader.cupsBytesPerLine) {
DEBUGPRINT("--------Compress line from %d to %d bytes", tHeader.cupsBytesPerLine, width_bytes);
iBytesChunk = compress_buffer(pRasterBuf, iBytesChunk,
tHeader.cupsBytesPerLine, width_bytes);
}
// runaround for sometimes truncated output of cupsRasterReadPixels
if (iBytesChunk < width_bytes * iBlockHeight) {
DEBUGPRINT("--------Restore truncated gap of %d bytes",
width_bytes * iBlockHeight - iBytesChunk);
memset(pRasterBuf + iBytesChunk, 0,
width_bytes * iBlockHeight - iBytesChunk);
iBytesChunk = width_bytes * iBlockHeight;
}
// lazy output of current raster. First check current line if it is zero.
// if there were many zeroes and met non-zero - flush zeros by 'feed' cmd
// if opposite - send non-zero chunk as raster.
unsigned char *pBuf = pRasterBuf;
unsigned char *pChunk = pBuf;
const unsigned char *pEnd = pBuf + iBytesChunk;
int nonzerolines = 0;
while ( pBuf<pEnd ) {
if (line_is_empty(pBuf, width_bytes)) {
if (nonzerolines) { // met zero, need to flush collected raster
send_raster(pChunk, width_bytes, nonzerolines);
nonzerolines = 0;
}
++zeroy;
} else {
if (zeroy) { // met non-zero, need to feed calculated num of zero lines
flushManyLines(zeroy);
zeroy=0;
}
if (!nonzerolines)
pChunk = pBuf;
++nonzerolines;
}
pBuf += width_bytes;
}
send_raster(pChunk, width_bytes, nonzerolines);
//flushBuffer();
} // loop over page
// page is finished.
// m.b. we have to print empty tail at the end
if (settings.InsertSheet)
flushManyLines (zeroy);
if (settings.AdvanceMedia == CUPS_ADVANCE_PAGE)
flushManyLines(settings.AdvanceDistance);
if (settings.CutMedia == CUPS_CUT_PAGE)
cutMedia();
finishPage();
} // loop over all pages pages
if (settings.AdvanceMedia==CUPS_ADVANCE_JOB)
flushManyLines(settings.AdvanceDistance);
if (settings.CutMedia == CUPS_CUT_JOB)
cutMedia();
finalizeJob();
fputs(iCurrentPage ? "INFO: Ready to print.\n" : "ERROR: No pages found!\n",
stderr);
DEBUGFINISHPRINT();
EXITPRINT(iCurrentPage ? EXIT_SUCCESS : EXIT_FAILURE)
}
// end of rastertozj.c