瀏覽代碼

add devterm_thermal_printer_cups

cuu 2 年之前
父節點
當前提交
e12dac0af2

+ 20 - 0
Code/devterm_thermal_printer_cups/Makefile

@@ -0,0 +1,20 @@
+CC = gcc 
+BIN = rastertocpi
+
+all:
+	${CC} rastertocpi.c -o ${BIN} -I /usr/include `cups-config --cflags` `cups-config --image --libs`
+
+debug:
+	${CC} rastertocpi.c -o ${BIN} -DSAFEDEBUG -I /usr/include `cups-config --cflags` `cups-config --image --libs`
+
+#sudo make -B install
+install:
+	/etc/init.d/cups stop
+	cp rastertocpi /usr/lib/cups/filter/
+	mkdir -p /usr/share/cups/model/clockworkpi
+	cp cpi58.ppd /usr/share/cups/model/clockworkpi/
+	cd /usr/lib/cups/filter
+	chmod 755 rastertocpi
+	chown root:root rastertocpi
+	cd -
+	/etc/init.d/cups start

+ 14 - 0
Code/devterm_thermal_printer_cups/README.md

@@ -0,0 +1,14 @@
+STM32duino / wiringPi gpio  thermal printer filter for cups 
+
+Clockworkpi CPI-58 (grayscale)
+
+serial:/dev/ttyACM0?baud=115200+bits=8+parity=none+flow=none
+
+serial:/tmp/DEVTERM\_PRINTER\_IN?baud=115200+bits=8+parity=none+flow=none
+
+## build
+sudo apt -y install libcups2-dev
+make
+sudo make install
+
+

+ 95 - 0
Code/devterm_thermal_printer_cups/cpi.drv

@@ -0,0 +1,95 @@
+// CUPS PPD Compiler CUPS v2.1.3
+// (Don't edit .ppd directly, edit this file instead, then use 
+//	ppdc cpi.drv to generate cpi58.ppd and others)
+
+// common for all thermal printers
+Version "1.2"
+DriverType custom
+ManualCopies Yes
+Throughput 1
+Attribute "PSVersion" "" "(3010.000) 550"
+Attribute "LanguageLevel" "" "3"
+Attribute "DefaultColorSpace" "" "Gray"
+Attribute "FileSystem" "" "False"
+Attribute "LandscapeOrientation" "" "Plus90"
+Attribute "TTRasterizer" "" "Type42"
+Attribute "VariablePaperSize" "" "True"
+Filter "application/vnd.cups-raster 100 rastertocpi"
+ColorDevice False
+VariablePaperSize Yes
+
+// cutter
+Option "CutMedia/Cut Media" PickOne AnySetup 10
+ *Choice "None/No cutting" "<</CutMedia 0>>setpagedevice"
+  Choice "EndOfPage/Cut at every page" "<</CutMedia 4>>setpagedevice"
+  Choice "EndOfJob/Cut at every job" "<</CutMedia 2>>setpagedevice"
+
+Option "Resolution/Resolution" PickOne AnySetup 10
+ *Choice "203x203dpi/203 DPI Grayscale" "<</HWResolution[203 203]/cupsColorSpace 3>>setpagedevice"
+
+HWMargins 14 0 14 0
+
+// feeding, spacing
+Group "BlankGroup/Blank Options"
+  Option "FeedDist/Feed distance" PickOne AnySetup 10
+    Choice "0feed3mm/3mm" "<</AdvanceDistance 24>>setpagedevice"
+    Choice "1feed6mm/6mm" "<</AdvanceDistance 48>>setpagedevice"
+   *Choice "2feed9mm/9mm" "<</AdvanceDistance 72>>setpagedevice"
+    Choice "3feed12mm/12mm" "<</AdvanceDistance 96>>setpagedevice"
+    Choice "4feed15mm/15mm" "<</AdvanceDistance 120>>setpagedevice"
+    Choice "5feed18mm/18mm" "<</AdvanceDistance 144>>setpagedevice"
+    Choice "6feed21mm/21mm" "<</AdvanceDistance 168>>setpagedevice"
+    Choice "7feed24mm/24mm" "<</AdvanceDistance 192>>setpagedevice"
+    Choice "8feed27mm/27mm" "<</AdvanceDistance 216>>setpagedevice"
+    Choice "9feed30mm/30mm" "<</AdvanceDistance 240>>setpagedevice"
+    Choice "10feed33mm/33mm" "<</AdvanceDistance 264>>setpagedevice"
+    Choice "11feed36mm/36mm" "<</AdvanceDistance 288>>setpagedevice"
+    Choice "12feed39mm/39mm" "<</AdvanceDistance 312>>setpagedevice"
+    Choice "13feed42mm/42mm" "<</AdvanceDistance 336>>setpagedevice"
+    Choice "14feed45mm/45mm" "<</AdvanceDistance 360>>setpagedevice"
+
+  Option "FeedWhere/When to feed" PickOne AnySetup 10
+    Choice "None/Never" ""
+    Choice "AfterPage/After each page" "<</AdvanceMedia 4>>setpagedevice"
+   *Choice "AfterJob/After whole printing" "<</AdvanceMedia 2>>setpagedevice"
+
+  Option "BlankSpace/Blank space at page's end" Boolean AnySetup 10
+    Choice "True/Print" "<</cupsInteger6 1>>setpagedevice"
+   *Choice "False/None" "<</cupsInteger6 0>>setpagedevice"
+
+Installable "OptionCutter/Cutter"
+    UIConstraints "*CutMedia *OptionCutter False"
+
+//	Attribute cupsUIConstraints CutterOff "*OptionCutter False *CutMedia"
+//    Attribute cupsUIResolver CutterOff "*CutMedia None"
+
+// 58mm printers
+{
+  #define POINTS 164
+  #define PIXELS 384
+
+  // model num is used by filter to determine raster width. 384 for 48mm, 560 for 70mm
+  ModelNumber $PIXELS
+  MinSize $POINTS 56
+  MaxSize $POINTS 9286
+
+ *CustomMedia "X48MMY65MM/58mm x 65mm" $POINTS 182 14 0 14 0 "<</PageSize[$POINTS 182]>>setpagedevice" "<</PageSize[$POINTS 182]>>setpagedevice"
+  CustomMedia "X48MMY105MM/58mm x 105mm" $POINTS 298 14 0 14 0 "<</PageSize[$POINTS 298]>>setpagedevice" "<</PageSize[$POINTS 298]>>setpagedevice"
+  CustomMedia "X48MMY210MM/58mm x 210mm" $POINTS 595 14 0 14 0 "<</PageSize[$POINTS 595]>>setpagedevice" "<</PageSize[$POINTS 595]>>setpagedevice"
+  CustomMedia "X48MMY297MM/58mm x 297mm" $POINTS 842 14 0 14 0 "<</PageSize[$POINTS 842]>>setpagedevice" "<</PageSize[$POINTS 842]>>setpagedevice"
+  CustomMedia "X48MMY3276MM/58mm x 3276mm" $POINTS 9286 14 0 14 0 "<</PageSize[$POINTS 9286]>>setpagedevice" "<</PageSize[$POINTS 9286]>>setpagedevice"
+
+  // Clockworkpi CPI-58
+  {
+    Manufacturer "Clockworkpi"
+    ModelName "CPI-58"
+    PCFileName "cpi58.ppd"
+    Attribute "NickName" "" "Clockworkpi CPI-58"
+    Attribute "ShortNickName" "" "CPI-58"
+    Attribute "Product" "" "(cpi-58)"
+    Attribute "1284DeviceID" "" "MFG:Clockworkpi;CMD:Clockworkpi;MDL:CPI-58;CLS:PRINTER;"
+  }
+
+}
+
+

+ 126 - 0
Code/devterm_thermal_printer_cups/cpi58.ppd

@@ -0,0 +1,126 @@
+*PPD-Adobe: "4.3"
+*%%%% PPD file for CPI-58 with CUPS.
+*%%%% Created by the CUPS PPD Compiler CUPS v2.2.12.
+*FormatVersion: "4.3"
+*FileVersion: "1.2"
+*LanguageVersion: English
+*LanguageEncoding: ISOLatin1
+*PCFileName: "cpi58.ppd"
+*Product: "(cpi-58)"
+*Manufacturer: "Clockworkpi"
+*ModelName: "Clockworkpi CPI-58"
+*ShortNickName: "CPI-58"
+*NickName: "Clockworkpi CPI-58"
+*PSVersion: "(3010.000) 550"
+*LanguageLevel: "3"
+*ColorDevice: False
+*DefaultColorSpace: Gray
+*FileSystem: False
+*Throughput: "1"
+*LandscapeOrientation: Plus90
+*TTRasterizer: Type42
+*% Driver-defined attributes...
+*VariablePaperSize: True
+*1284DeviceID: "MFG:Clockworkpi;CMD:Clockworkpi;MDL:CPI-58;CLS:PRINTER;"
+*cupsVersion: 2.2
+*cupsModelNumber: 384
+*cupsManualCopies: True
+*cupsFilter: "application/vnd.cups-raster 100 rastertocpi"
+*cupsLanguages: "en"
+*UIConstraints: *CutMedia *OptionCutter False
+*UIConstraints: *OptionCutter False *CutMedia
+*OpenUI *PageSize/Media Size: PickOne
+*OrderDependency: 10 AnySetup *PageSize
+*DefaultPageSize: X48MMY65MM
+*PageSize X48MMY65MM/58mm x 65mm: "<</PageSize[164 182]>>setpagedevice"
+*PageSize X48MMY105MM/58mm x 105mm: "<</PageSize[164 298]>>setpagedevice"
+*PageSize X48MMY210MM/58mm x 210mm: "<</PageSize[164 595]>>setpagedevice"
+*PageSize X48MMY297MM/58mm x 297mm: "<</PageSize[164 842]>>setpagedevice"
+*PageSize X48MMY3276MM/58mm x 3276mm: "<</PageSize[164 9286]>>setpagedevice"
+*CloseUI: *PageSize
+*OpenUI *PageRegion/Media Size: PickOne
+*OrderDependency: 10 AnySetup *PageRegion
+*DefaultPageRegion: X48MMY65MM
+*PageRegion X48MMY65MM/58mm x 65mm: "<</PageSize[164 182]>>setpagedevice"
+*PageRegion X48MMY105MM/58mm x 105mm: "<</PageSize[164 298]>>setpagedevice"
+*PageRegion X48MMY210MM/58mm x 210mm: "<</PageSize[164 595]>>setpagedevice"
+*PageRegion X48MMY297MM/58mm x 297mm: "<</PageSize[164 842]>>setpagedevice"
+*PageRegion X48MMY3276MM/58mm x 3276mm: "<</PageSize[164 9286]>>setpagedevice"
+*CloseUI: *PageRegion
+*DefaultImageableArea: X48MMY65MM
+*ImageableArea X48MMY65MM/58mm x 65mm: "14 0 150 182"
+*ImageableArea X48MMY105MM/58mm x 105mm: "14 0 150 298"
+*ImageableArea X48MMY210MM/58mm x 210mm: "14 0 150 595"
+*ImageableArea X48MMY297MM/58mm x 297mm: "14 0 150 842"
+*ImageableArea X48MMY3276MM/58mm x 3276mm: "14 0 150 9286"
+*DefaultPaperDimension: X48MMY65MM
+*PaperDimension X48MMY65MM/58mm x 65mm: "164 182"
+*PaperDimension X48MMY105MM/58mm x 105mm: "164 298"
+*PaperDimension X48MMY210MM/58mm x 210mm: "164 595"
+*PaperDimension X48MMY297MM/58mm x 297mm: "164 842"
+*PaperDimension X48MMY3276MM/58mm x 3276mm: "164 9286"
+*MaxMediaWidth: "164"
+*MaxMediaHeight: "9286"
+*HWMargins: 14 0 14 0
+*CustomPageSize True: "pop pop pop <</PageSize[5 -2 roll]/ImagingBBox null>>setpagedevice"
+*ParamCustomPageSize Width: 1 points 164 164
+*ParamCustomPageSize Height: 2 points 56 9286
+*ParamCustomPageSize WidthOffset: 3 points 0 0
+*ParamCustomPageSize HeightOffset: 4 points 0 0
+*ParamCustomPageSize Orientation: 5 int 0 0
+*OpenUI *CutMedia/Cut Media: PickOne
+*OrderDependency: 10 AnySetup *CutMedia
+*DefaultCutMedia: None
+*CutMedia None/No cutting: "<</CutMedia 0>>setpagedevice"
+*CutMedia EndOfPage/Cut at every page: "<</CutMedia 4>>setpagedevice"
+*CutMedia EndOfJob/Cut at every job: "<</CutMedia 2>>setpagedevice"
+*CloseUI: *CutMedia
+*OpenUI *Resolution/Resolution: PickOne
+*OrderDependency: 10 AnySetup *Resolution
+*DefaultResolution: 203x203dpi
+*Resolution 203x203dpi/203 DPI Grayscale: "<</HWResolution[203 203]/cupsColorSpace 3>>setpagedevice"
+*CloseUI: *Resolution
+*OpenGroup: InstallableOptions/Installable Options
+*OpenUI *OptionCutter/Cutter: Boolean
+*OrderDependency: 10 AnySetup *OptionCutter
+*DefaultOptionCutter: False
+*OptionCutter False/Not Installed: ""
+*OptionCutter True/Installed: ""
+*CloseUI: *OptionCutter
+*CloseGroup: InstallableOptions
+*OpenGroup: BlankGroup/Blank Options
+*OpenUI *FeedDist/Feed distance: PickOne
+*OrderDependency: 10 AnySetup *FeedDist
+*DefaultFeedDist: 2feed9mm
+*FeedDist 0feed3mm/3mm: "<</AdvanceDistance 24>>setpagedevice"
+*FeedDist 1feed6mm/6mm: "<</AdvanceDistance 48>>setpagedevice"
+*FeedDist 2feed9mm/9mm: "<</AdvanceDistance 72>>setpagedevice"
+*FeedDist 3feed12mm/12mm: "<</AdvanceDistance 96>>setpagedevice"
+*FeedDist 4feed15mm/15mm: "<</AdvanceDistance 120>>setpagedevice"
+*FeedDist 5feed18mm/18mm: "<</AdvanceDistance 144>>setpagedevice"
+*FeedDist 6feed21mm/21mm: "<</AdvanceDistance 168>>setpagedevice"
+*FeedDist 7feed24mm/24mm: "<</AdvanceDistance 192>>setpagedevice"
+*FeedDist 8feed27mm/27mm: "<</AdvanceDistance 216>>setpagedevice"
+*FeedDist 9feed30mm/30mm: "<</AdvanceDistance 240>>setpagedevice"
+*FeedDist 10feed33mm/33mm: "<</AdvanceDistance 264>>setpagedevice"
+*FeedDist 11feed36mm/36mm: "<</AdvanceDistance 288>>setpagedevice"
+*FeedDist 12feed39mm/39mm: "<</AdvanceDistance 312>>setpagedevice"
+*FeedDist 13feed42mm/42mm: "<</AdvanceDistance 336>>setpagedevice"
+*FeedDist 14feed45mm/45mm: "<</AdvanceDistance 360>>setpagedevice"
+*CloseUI: *FeedDist
+*OpenUI *FeedWhere/When to feed: PickOne
+*OrderDependency: 10 AnySetup *FeedWhere
+*DefaultFeedWhere: AfterJob
+*FeedWhere None/Never: ""
+*FeedWhere AfterPage/After each page: "<</AdvanceMedia 4>>setpagedevice"
+*FeedWhere AfterJob/After whole printing: "<</AdvanceMedia 2>>setpagedevice"
+*CloseUI: *FeedWhere
+*OpenUI *BlankSpace/Blank space at page's end: Boolean
+*OrderDependency: 10 AnySetup *BlankSpace
+*DefaultBlankSpace: False
+*BlankSpace True/Print: "<</cupsInteger6 1>>setpagedevice"
+*BlankSpace False/None: "<</cupsInteger6 0>>setpagedevice"
+*CloseUI: *BlankSpace
+*CloseGroup: BlankGroup
+*DefaultFont: Courier
+*% End of cpi58.ppd, 05572 bytes.

+ 511 - 0
Code/devterm_thermal_printer_cups/rastertocpi.c

@@ -0,0 +1,511 @@
+// 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