123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- // 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.
- 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.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);
- }
- /*
- * cpi-58 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 cutMedia()
- {
- SendCommand(escCut);
- }
- // sent on the beginning of print job
- void setupJob() {
- SendCommand(escInit);
- }
- // sent at the very end of print job
- void finalizeJob() {
- // 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);
- sendRasterHeader(width8, height);
- outputarray((char *)pBuf, width8 * height);
- flushBuffer();
- }
- #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: rastertocpi 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 = 0x180;
- // 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); // 0x180 for 58mm (48mm 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 rastertocpi.c
|