// To get required headers, run // sudo apt-get install libcups2-dev libcupsimage2-dev #include #include #include #include #include #include #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 and * where *2ms is pulse on time, *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