rastertocpi.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. // To get required headers, run
  2. // sudo apt-get install libcups2-dev libcupsimage2-dev
  3. #include <cups/cups.h>
  4. #include <cups/ppd.h>
  5. #include <cups/raster.h>
  6. #include <fcntl.h>
  7. #include <signal.h>
  8. #include <stdlib.h>
  9. #ifndef DEBUGFILE
  10. #define DEBUGFILE "/tmp/debugraster.txt"
  11. #endif
  12. static inline int min(int a, int b) {
  13. if (a > b)
  14. return b;
  15. return a;
  16. }
  17. // settings and their stuff
  18. struct settings_ {
  19. int modelNum; // the only setting we get from PPD.
  20. cups_bool_t InsertSheet;
  21. cups_adv_t AdvanceMedia;
  22. cups_cut_t CutMedia;
  23. unsigned int AdvanceDistance;
  24. };
  25. struct settings_ settings;
  26. static void initializeSettings(char *commandLineOptionSettings, struct settings_ *pSettings) {
  27. ppd_file_t *pPpd = ppdOpenFile(getenv("PPD"));
  28. // char* sDestination = getenv("DestinationPrinterID");
  29. memset(pSettings, 0, sizeof(struct settings_));
  30. pSettings->modelNum = pPpd->model_number;
  31. ppdClose(pPpd);
  32. }
  33. static void update_settings_from_job (cups_page_header2_t * pHeader)
  34. {
  35. if (!pHeader)
  36. return;
  37. settings.InsertSheet = pHeader->cupsInteger[6];
  38. settings.AdvanceMedia = pHeader->AdvanceMedia;
  39. settings.CutMedia = pHeader->CutMedia;
  40. settings.AdvanceDistance = pHeader->AdvanceDistance;
  41. }
  42. #ifndef DEBUGP
  43. static inline void mputchar(char c) { putchar(c); }
  44. #define DEBUGSTARTPRINT()
  45. #define DEBUGFINISHPRINT()
  46. #define DEBUGPRINT(...)
  47. #else
  48. FILE *lfd = 0;
  49. // putchar with debug wrappers
  50. static inline void mputchar(char c) {
  51. unsigned char m = c;
  52. if (lfd)
  53. fprintf(lfd, "%02x ", m);
  54. putchar(m);
  55. }
  56. // on macos cups filters works in a sandbox and cant write
  57. // filtes everywhere. We'll use $TMPDIR/debugraster.txt for them
  58. #ifdef SAFEDEBUG
  59. static inline void DEBUGSTARTPRINT() {
  60. char * tmpfolder = getenv("TMPDIR");
  61. const char *filename = "/debugraster.txt";
  62. char *dbgpath = (char *)malloc(strlen(tmpfolder) + strlen(filename) + 1);
  63. strcpy(dbgpath,tmpfolder);
  64. strcat(dbgpath,filename);
  65. lfd = fopen(dbgpath,"w");
  66. free(dbgpath);
  67. }
  68. #else
  69. #define DEBUGSTARTPRINT() lfd = fopen(DEBUGFILE, "w")
  70. #endif
  71. #define DEBUGFINISHPRINT() \
  72. if (lfd) \
  73. fclose(lfd)
  74. #define DEBUGPRINT(...) if (lfd) fprintf(lfd, "\n" __VA_ARGS__)
  75. #endif
  76. // procedure to output an array
  77. static inline void outputarray(const char *array, int length) {
  78. int i = 0;
  79. for (; i < length; ++i)
  80. mputchar(array[i]);
  81. }
  82. // output a command. -1 is because we determine them as string literals,
  83. // so \0 implicitly added at the end, but we don't need it at all
  84. #define SendCommand(COMMAND) outputarray((COMMAND),sizeof((COMMAND))-1)
  85. static inline void mputnum(unsigned int val) {
  86. mputchar(val&0xFFU);
  87. mputchar((val>>8)&0xFFU);
  88. }
  89. /*
  90. * cpi-58 uses kind of epson ESC/POS dialect code. Here is subset of commands
  91. *
  92. * initialize - esc @
  93. * cash drawer 1 - esc p 0 @ P
  94. * cash drawer 2 - esc p 1 @ P // @ =0x40 and P=0x50 <N> and <M>
  95. * where <N>*2ms is pulse on time, <M>*2ms is pulse off.
  96. * start raster - GS v 0 // 0 is full-density, may be also 1, 2, 4
  97. * skip lines - esc J // then N [0..255], each value 1/44 inches (0.176mm)
  98. * // another commands out-of-spec:
  99. * esc 'i' - cutter; xprinter android example shows as GS V \1 (1D 56 01)
  100. * esc '8' - wait{4, cutter also (char[4]){29,'V','A',20}}; GS V 'A' 20
  101. */
  102. // define printer initialize command
  103. static const char escInit[] = "\x1b@";
  104. // define cashDrawerEjector command
  105. static const char escCashDrawerEject[] = "\x1bp";
  106. // define raster mode start command
  107. static const char escRasterMode[] = "\x1dv0\0";
  108. // define flush command
  109. static const char escFlush[] = "\x1bJ";
  110. // define cut command
  111. //static const char escCut[] = "\x1bi";
  112. static const char escCut[] = "\x1dV\1";
  113. // enter raster mode and set up x and y dimensions
  114. static inline void sendRasterHeader(int xsize, int ysize) {
  115. // outputCommand(rasterModeStartCommand);
  116. SendCommand(escRasterMode);
  117. mputnum(xsize);
  118. mputnum(ysize);
  119. }
  120. static inline void flushLines(unsigned short lines)
  121. {
  122. SendCommand(escFlush);
  123. mputchar (lines);
  124. }
  125. // print all unprinted (i.e. flush the buffer)
  126. static inline void flushBuffer() {
  127. flushLines(0);
  128. }
  129. // flush, then feed 24 lines
  130. static inline void flushManyLines(int iLines)
  131. {
  132. DEBUGPRINT("Skip %d empty lines: ", iLines);
  133. while ( iLines )
  134. {
  135. int iStride = min (iLines, 24);
  136. flushLines ( iStride );
  137. iLines -= iStride;
  138. }
  139. }
  140. inline static void cutMedia()
  141. {
  142. SendCommand(escCut);
  143. }
  144. // sent on the beginning of print job
  145. void setupJob() {
  146. SendCommand(escInit);
  147. }
  148. // sent at the very end of print job
  149. void finalizeJob() {
  150. // SendCommand(escInit);
  151. }
  152. // sent at the end of every page
  153. #ifndef __sighandler_t
  154. typedef void (*__sighandler_t)(int);
  155. #endif
  156. __sighandler_t old_signal;
  157. void finishPage() {
  158. signal(SIGTERM, old_signal);
  159. }
  160. // sent on job canceling
  161. void cancelJob() {
  162. int i = 0;
  163. for (; i < 0x258; ++i)
  164. mputchar(0);
  165. finishPage();
  166. }
  167. // invoked before starting to print a page
  168. void startPage() { old_signal = signal(SIGTERM, cancelJob); }
  169. void DebugPrintHeader (cups_page_header2_t* pHeader)
  170. {
  171. DEBUGPRINT(
  172. "MediaClass '%s'\n"
  173. "MediaColor '%s'\n"
  174. "MediaType '%s'\n"
  175. "OutputType '%s'\n"
  176. "AdvanceDistance %d\n"
  177. "AdvanceMedia %d\n"
  178. "Collate %d\n"
  179. "CutMedia %d\n"
  180. "Duplex %d\n"
  181. "HWResolution %d %d\n"
  182. "ImagingBoundingBox %d %d %d %d\n"
  183. "InsertSheet %d\n"
  184. "Jog %d\n"
  185. "LeadingEdge %d\n"
  186. "Margins %d %d\n"
  187. "ManualFeed %d\n"
  188. "MediaPosition %d\n"
  189. "MediaWeight %d\n"
  190. "MirrorPrint %d\n"
  191. "NegativePrint %d\n"
  192. "NumCopies %d\n"
  193. "Orientation %d\n"
  194. "OutputFaceUp %d\n"
  195. "PageSize %d %d\n"
  196. "Separations %d\n"
  197. "TraySwitch %d\n"
  198. "Tumble %d\n"
  199. "cupsWidth %d\n"
  200. "cupsHeight %d\n"
  201. "cupsMediaType %d\n"
  202. "cupsBitsPerColor %d\n"
  203. "cupsBitsPerPixel %d\n"
  204. "cupsBytesPerLine %d\n"
  205. "cupsColorOrder %d\n"
  206. "cupsColorSpace %d\n"
  207. "cupsCompression %d\n"
  208. "cupsRowCount %d\n"
  209. "cupsRowFeed %d\n"
  210. "cupsRowStep %d\n"
  211. "cupsNumColors %d\n"
  212. "cupsBorderlessScalingFactor %f\n"
  213. "cupsPageSize %f %f\n"
  214. "cupsImagingBBox %f %f %f %f\n"
  215. "cupsInteger %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n"
  216. "cupsReal %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n"
  217. "cupsString '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s' '%s'\n"
  218. "cupsMarkerType '%s'\n"
  219. "cupsRenderingIntent '%s'\n"
  220. "cupsPageSizeName '%s'\n",
  221. pHeader->MediaClass, pHeader->MediaColor, pHeader->MediaType,
  222. pHeader->OutputType, pHeader->AdvanceDistance, pHeader->AdvanceMedia,
  223. pHeader->Collate, pHeader->CutMedia, pHeader->Duplex,
  224. pHeader->HWResolution[0], pHeader->HWResolution[1],
  225. pHeader->ImagingBoundingBox[0], pHeader->ImagingBoundingBox[1],
  226. pHeader->ImagingBoundingBox[2], pHeader->ImagingBoundingBox[3],
  227. pHeader->InsertSheet, pHeader->Jog, pHeader->LeadingEdge, pHeader->Margins[0],
  228. pHeader->Margins[1], pHeader->ManualFeed, pHeader->MediaPosition,
  229. pHeader->MediaWeight, pHeader->MirrorPrint, pHeader->NegativePrint,
  230. pHeader->NumCopies, pHeader->Orientation, pHeader->OutputFaceUp,
  231. pHeader->PageSize[0], pHeader->PageSize[1], pHeader->Separations,
  232. pHeader->TraySwitch, pHeader->Tumble, pHeader->cupsWidth, pHeader->cupsHeight,
  233. pHeader->cupsMediaType, pHeader->cupsBitsPerColor, pHeader->cupsBitsPerPixel,
  234. pHeader->cupsBytesPerLine, pHeader->cupsColorOrder, pHeader->cupsColorSpace,
  235. pHeader->cupsCompression, pHeader->cupsRowCount, pHeader->cupsRowFeed,
  236. pHeader->cupsRowStep, pHeader->cupsNumColors,
  237. pHeader->cupsBorderlessScalingFactor, pHeader->cupsPageSize[0],
  238. pHeader->cupsPageSize[1], pHeader->cupsImagingBBox[0],
  239. pHeader->cupsImagingBBox[1], pHeader->cupsImagingBBox[2],
  240. pHeader->cupsImagingBBox[3], pHeader->cupsInteger[0],
  241. pHeader->cupsInteger[1], pHeader->cupsInteger[2], pHeader->cupsInteger[3],
  242. pHeader->cupsInteger[4], pHeader->cupsInteger[5], pHeader->cupsInteger[6],
  243. pHeader->cupsInteger[7], pHeader->cupsInteger[8], pHeader->cupsInteger[9],
  244. pHeader->cupsInteger[10], pHeader->cupsInteger[11], pHeader->cupsInteger[12],
  245. pHeader->cupsInteger[13], pHeader->cupsInteger[14], pHeader->cupsInteger[15],
  246. pHeader->cupsReal[0], pHeader->cupsReal[1], pHeader->cupsReal[2],
  247. pHeader->cupsReal[3], pHeader->cupsReal[4], pHeader->cupsReal[5],
  248. pHeader->cupsReal[6], pHeader->cupsReal[7], pHeader->cupsReal[8],
  249. pHeader->cupsReal[9], pHeader->cupsReal[10], pHeader->cupsReal[11],
  250. pHeader->cupsReal[12], pHeader->cupsReal[13], pHeader->cupsReal[14],
  251. pHeader->cupsReal[15], pHeader->cupsString[0], pHeader->cupsString[1],
  252. pHeader->cupsString[2], pHeader->cupsString[3], pHeader->cupsString[4],
  253. pHeader->cupsString[5], pHeader->cupsString[6], pHeader->cupsString[7],
  254. pHeader->cupsString[8], pHeader->cupsString[9], pHeader->cupsString[10],
  255. pHeader->cupsString[11], pHeader->cupsString[12], pHeader->cupsString[13],
  256. pHeader->cupsString[14], pHeader->cupsString[15], pHeader->cupsMarkerType,
  257. pHeader->cupsRenderingIntent, pHeader->cupsPageSizeName);
  258. }
  259. // rearrange (compress) rows in pBuf, discarding tails of them
  260. static inline unsigned compress_buffer(unsigned char *pBuf, unsigned iSize,
  261. unsigned int iWideStride, unsigned int iStride) {
  262. const unsigned char *pEnd = pBuf + iSize;
  263. unsigned char *pTarget = pBuf;
  264. while (pBuf < pEnd) {
  265. int iBytes = min(pEnd - pBuf, iStride);
  266. memmove(pTarget, pBuf, iBytes);
  267. pTarget += iBytes;
  268. pBuf += iWideStride;
  269. }
  270. return min(iSize, pTarget - pBuf);
  271. }
  272. // returns -1 if whole line iz filled by zeros. Otherwise 0.
  273. static inline int line_is_empty(const unsigned char *pBuf, unsigned iSize) {
  274. int i;
  275. for (i = 0; i < iSize; ++i)
  276. if (pBuf[i])
  277. return 0;
  278. return -1;
  279. }
  280. static inline void send_raster(const unsigned char *pBuf, int width8,
  281. int height) {
  282. if (!height)
  283. return;
  284. DEBUGPRINT("Raster block %dx%d pixels\n", width8 * 8, height);
  285. sendRasterHeader(width8, height);
  286. outputarray((char *)pBuf, width8 * height);
  287. flushBuffer();
  288. }
  289. #define EXITPRINT(CODE) \
  290. { \
  291. if (pRasterSrc) \
  292. cupsRasterClose(pRasterSrc); \
  293. if (pRasterBuf) \
  294. free(pRasterBuf); \
  295. if (fd) \
  296. close(fd); \
  297. return (CODE); \
  298. }
  299. //////////////////////////////////////////////
  300. //////////////////////////////////////////////
  301. int main(int argc, char *argv[]) {
  302. signal(SIGPIPE, SIG_IGN);
  303. if (argc < 6 || argc > 7) {
  304. fputs("ERROR: rastertocpi job-id user title copies options [file]\n",
  305. stderr);
  306. return EXIT_FAILURE;
  307. }
  308. int fd = STDIN_FILENO; // File descriptor providing CUPS raster data
  309. if (argc == 7) {
  310. if ((fd = open(argv[6], O_RDONLY)) == -1) {
  311. perror("ERROR: Unable to open raster file - ");
  312. sleep(1);
  313. return EXIT_FAILURE;
  314. }
  315. }
  316. DEBUGSTARTPRINT();
  317. int iCurrentPage = 0;
  318. // CUPS Page tHeader
  319. cups_page_header2_t tHeader;
  320. unsigned char *pRasterBuf = NULL; // Pointer to raster data from CUPS
  321. // Raster stream for printing
  322. cups_raster_t *pRasterSrc = cupsRasterOpen(fd, CUPS_RASTER_READ);
  323. initializeSettings(argv[5],&settings);
  324. DEBUGPRINT("ModelNumber from PPD '%d'\n", settings.modelNum);
  325. // set max num of pixels per line depended from model number (from ppd)
  326. int iMaxWidth = settings.modelNum;
  327. if (!iMaxWidth)
  328. iMaxWidth = 0x180;
  329. // postpone start of the job until we parse first header and get necessary values from there.
  330. int iJobStarted = 0;
  331. // loop over the whole raster, page by page
  332. while (cupsRasterReadHeader2(pRasterSrc, &tHeader)) {
  333. if ((!tHeader.cupsHeight) || (!tHeader.cupsBytesPerLine))
  334. break;
  335. update_settings_from_job( &tHeader );
  336. if (!iJobStarted)
  337. {
  338. setupJob();
  339. iJobStarted = 1;
  340. }
  341. DebugPrintHeader ( &tHeader );
  342. if (!pRasterBuf) {
  343. pRasterBuf = malloc(tHeader.cupsBytesPerLine * 24);
  344. if (!pRasterBuf) // hope it never goes here...
  345. EXITPRINT(EXIT_FAILURE)
  346. }
  347. fprintf(stderr, "PAGE: %d %d\n", ++iCurrentPage, tHeader.NumCopies);
  348. startPage();
  349. // calculate num of bytes to print given width having 1 bit per pixel.
  350. int foo = min(tHeader.cupsWidth, iMaxWidth); // 0x180 for 58mm (48mm printable)
  351. foo = (foo + 7) & 0xFFFFFFF8;
  352. int width_bytes = foo >> 3; // in bytes, [0..0x30]
  353. DEBUGPRINT("cupsWidth=%d, cupsBytesPerLine=%d; foo=%d, width_bytes=%d",
  354. tHeader.cupsWidth, tHeader.cupsBytesPerLine, foo, width_bytes );
  355. int iRowsToPrint = tHeader.cupsHeight;
  356. int zeroy = 0;
  357. // loop over one page, top to bottom by blocks of most 24 scan lines
  358. while (iRowsToPrint) {
  359. fprintf(stderr, "INFO: Printing iCurrentPage %d, %d%% complete...\n",
  360. iCurrentPage,
  361. (100 * (tHeader.cupsHeight - iRowsToPrint) / tHeader.cupsHeight));
  362. int iBlockHeight = min(iRowsToPrint, 24);
  363. DEBUGPRINT("--------Processing block of %d, starting from %d lines",
  364. iBlockHeight, tHeader.cupsHeight - iRowsToPrint);
  365. iRowsToPrint -= iBlockHeight;
  366. unsigned iBytesChunk = 0;
  367. // first, fetch whole block from the image
  368. if (iBlockHeight)
  369. iBytesChunk = cupsRasterReadPixels(
  370. pRasterSrc, pRasterBuf, tHeader.cupsBytesPerLine * iBlockHeight);
  371. DEBUGPRINT("--------Got %d from %d requested bytes",
  372. iBytesChunk,
  373. tHeader.cupsBytesPerLine *
  374. iBlockHeight);
  375. // if original image is wider - rearrange buffer so that our calculated
  376. // lines come one-by-one without extra gaps
  377. if (width_bytes < tHeader.cupsBytesPerLine) {
  378. DEBUGPRINT("--------Compress line from %d to %d bytes", tHeader.cupsBytesPerLine, width_bytes);
  379. iBytesChunk = compress_buffer(pRasterBuf, iBytesChunk,
  380. tHeader.cupsBytesPerLine, width_bytes);
  381. }
  382. // runaround for sometimes truncated output of cupsRasterReadPixels
  383. if (iBytesChunk < width_bytes * iBlockHeight) {
  384. DEBUGPRINT("--------Restore truncated gap of %d bytes",
  385. width_bytes * iBlockHeight - iBytesChunk);
  386. memset(pRasterBuf + iBytesChunk, 0,
  387. width_bytes * iBlockHeight - iBytesChunk);
  388. iBytesChunk = width_bytes * iBlockHeight;
  389. }
  390. // lazy output of current raster. First check current line if it is zero.
  391. // if there were many zeroes and met non-zero - flush zeros by 'feed' cmd
  392. // if opposite - send non-zero chunk as raster.
  393. unsigned char *pBuf = pRasterBuf;
  394. unsigned char *pChunk = pBuf;
  395. const unsigned char *pEnd = pBuf + iBytesChunk;
  396. int nonzerolines = 0;
  397. while ( pBuf<pEnd ) {
  398. if (line_is_empty(pBuf, width_bytes)) {
  399. if (nonzerolines) { // met zero, need to flush collected raster
  400. send_raster(pChunk, width_bytes, nonzerolines);
  401. nonzerolines = 0;
  402. }
  403. ++zeroy;
  404. } else {
  405. if (zeroy) { // met non-zero, need to feed calculated num of zero lines
  406. flushManyLines(zeroy);
  407. zeroy=0;
  408. }
  409. if (!nonzerolines)
  410. pChunk = pBuf;
  411. ++nonzerolines;
  412. }
  413. pBuf += width_bytes;
  414. }
  415. send_raster(pChunk, width_bytes, nonzerolines);
  416. //flushBuffer();
  417. } // loop over page
  418. // page is finished.
  419. // m.b. we have to print empty tail at the end
  420. if (settings.InsertSheet)
  421. flushManyLines (zeroy);
  422. if (settings.AdvanceMedia == CUPS_ADVANCE_PAGE)
  423. flushManyLines(settings.AdvanceDistance);
  424. if (settings.CutMedia == CUPS_CUT_PAGE)
  425. cutMedia();
  426. finishPage();
  427. } // loop over all pages pages
  428. if (settings.AdvanceMedia==CUPS_ADVANCE_JOB)
  429. flushManyLines(settings.AdvanceDistance);
  430. if (settings.CutMedia == CUPS_CUT_JOB)
  431. cutMedia();
  432. finalizeJob();
  433. fputs(iCurrentPage ? "INFO: Ready to print.\n" : "ERROR: No pages found!\n",
  434. stderr);
  435. DEBUGFINISHPRINT();
  436. EXITPRINT(iCurrentPage ? EXIT_SUCCESS : EXIT_FAILURE)
  437. }
  438. // end of rastertocpi.c