From e12dac0af2b177ebc6ee07eb84acebadefd8942c Mon Sep 17 00:00:00 2001 From: cuu Date: Mon, 23 Aug 2021 14:34:13 +0800 Subject: [PATCH] add devterm_thermal_printer_cups --- Code/devterm_thermal_printer_cups/Makefile | 20 + Code/devterm_thermal_printer_cups/README.md | 14 + Code/devterm_thermal_printer_cups/cpi.drv | 95 ++++ Code/devterm_thermal_printer_cups/cpi58.ppd | 126 +++++ .../rastertocpi.c | 511 ++++++++++++++++++ 5 files changed, 766 insertions(+) create mode 100644 Code/devterm_thermal_printer_cups/Makefile create mode 100644 Code/devterm_thermal_printer_cups/README.md create mode 100644 Code/devterm_thermal_printer_cups/cpi.drv create mode 100644 Code/devterm_thermal_printer_cups/cpi58.ppd create mode 100644 Code/devterm_thermal_printer_cups/rastertocpi.c diff --git a/Code/devterm_thermal_printer_cups/Makefile b/Code/devterm_thermal_printer_cups/Makefile new file mode 100644 index 0000000..1025c69 --- /dev/null +++ b/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 diff --git a/Code/devterm_thermal_printer_cups/README.md b/Code/devterm_thermal_printer_cups/README.md new file mode 100644 index 0000000..c6d924d --- /dev/null +++ b/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 + + diff --git a/Code/devterm_thermal_printer_cups/cpi.drv b/Code/devterm_thermal_printer_cups/cpi.drv new file mode 100644 index 0000000..844ae4f --- /dev/null +++ b/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" "<>setpagedevice" + Choice "EndOfPage/Cut at every page" "<>setpagedevice" + Choice "EndOfJob/Cut at every job" "<>setpagedevice" + +Option "Resolution/Resolution" PickOne AnySetup 10 + *Choice "203x203dpi/203 DPI Grayscale" "<>setpagedevice" + +HWMargins 14 0 14 0 + +// feeding, spacing +Group "BlankGroup/Blank Options" + Option "FeedDist/Feed distance" PickOne AnySetup 10 + Choice "0feed3mm/3mm" "<>setpagedevice" + Choice "1feed6mm/6mm" "<>setpagedevice" + *Choice "2feed9mm/9mm" "<>setpagedevice" + Choice "3feed12mm/12mm" "<>setpagedevice" + Choice "4feed15mm/15mm" "<>setpagedevice" + Choice "5feed18mm/18mm" "<>setpagedevice" + Choice "6feed21mm/21mm" "<>setpagedevice" + Choice "7feed24mm/24mm" "<>setpagedevice" + Choice "8feed27mm/27mm" "<>setpagedevice" + Choice "9feed30mm/30mm" "<>setpagedevice" + Choice "10feed33mm/33mm" "<>setpagedevice" + Choice "11feed36mm/36mm" "<>setpagedevice" + Choice "12feed39mm/39mm" "<>setpagedevice" + Choice "13feed42mm/42mm" "<>setpagedevice" + Choice "14feed45mm/45mm" "<>setpagedevice" + + Option "FeedWhere/When to feed" PickOne AnySetup 10 + Choice "None/Never" "" + Choice "AfterPage/After each page" "<>setpagedevice" + *Choice "AfterJob/After whole printing" "<>setpagedevice" + + Option "BlankSpace/Blank space at page's end" Boolean AnySetup 10 + Choice "True/Print" "<>setpagedevice" + *Choice "False/None" "<>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 "<>setpagedevice" "<>setpagedevice" + CustomMedia "X48MMY105MM/58mm x 105mm" $POINTS 298 14 0 14 0 "<>setpagedevice" "<>setpagedevice" + CustomMedia "X48MMY210MM/58mm x 210mm" $POINTS 595 14 0 14 0 "<>setpagedevice" "<>setpagedevice" + CustomMedia "X48MMY297MM/58mm x 297mm" $POINTS 842 14 0 14 0 "<>setpagedevice" "<>setpagedevice" + CustomMedia "X48MMY3276MM/58mm x 3276mm" $POINTS 9286 14 0 14 0 "<>setpagedevice" "<>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;" + } + +} + + diff --git a/Code/devterm_thermal_printer_cups/cpi58.ppd b/Code/devterm_thermal_printer_cups/cpi58.ppd new file mode 100644 index 0000000..1af02c4 --- /dev/null +++ b/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: "<>setpagedevice" +*PageSize X48MMY105MM/58mm x 105mm: "<>setpagedevice" +*PageSize X48MMY210MM/58mm x 210mm: "<>setpagedevice" +*PageSize X48MMY297MM/58mm x 297mm: "<>setpagedevice" +*PageSize X48MMY3276MM/58mm x 3276mm: "<>setpagedevice" +*CloseUI: *PageSize +*OpenUI *PageRegion/Media Size: PickOne +*OrderDependency: 10 AnySetup *PageRegion +*DefaultPageRegion: X48MMY65MM +*PageRegion X48MMY65MM/58mm x 65mm: "<>setpagedevice" +*PageRegion X48MMY105MM/58mm x 105mm: "<>setpagedevice" +*PageRegion X48MMY210MM/58mm x 210mm: "<>setpagedevice" +*PageRegion X48MMY297MM/58mm x 297mm: "<>setpagedevice" +*PageRegion X48MMY3276MM/58mm x 3276mm: "<>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 <>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: "<>setpagedevice" +*CutMedia EndOfPage/Cut at every page: "<>setpagedevice" +*CutMedia EndOfJob/Cut at every job: "<>setpagedevice" +*CloseUI: *CutMedia +*OpenUI *Resolution/Resolution: PickOne +*OrderDependency: 10 AnySetup *Resolution +*DefaultResolution: 203x203dpi +*Resolution 203x203dpi/203 DPI Grayscale: "<>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: "<>setpagedevice" +*FeedDist 1feed6mm/6mm: "<>setpagedevice" +*FeedDist 2feed9mm/9mm: "<>setpagedevice" +*FeedDist 3feed12mm/12mm: "<>setpagedevice" +*FeedDist 4feed15mm/15mm: "<>setpagedevice" +*FeedDist 5feed18mm/18mm: "<>setpagedevice" +*FeedDist 6feed21mm/21mm: "<>setpagedevice" +*FeedDist 7feed24mm/24mm: "<>setpagedevice" +*FeedDist 8feed27mm/27mm: "<>setpagedevice" +*FeedDist 9feed30mm/30mm: "<>setpagedevice" +*FeedDist 10feed33mm/33mm: "<>setpagedevice" +*FeedDist 11feed36mm/36mm: "<>setpagedevice" +*FeedDist 12feed39mm/39mm: "<>setpagedevice" +*FeedDist 13feed42mm/42mm: "<>setpagedevice" +*FeedDist 14feed45mm/45mm: "<>setpagedevice" +*CloseUI: *FeedDist +*OpenUI *FeedWhere/When to feed: PickOne +*OrderDependency: 10 AnySetup *FeedWhere +*DefaultFeedWhere: AfterJob +*FeedWhere None/Never: "" +*FeedWhere AfterPage/After each page: "<>setpagedevice" +*FeedWhere AfterJob/After whole printing: "<>setpagedevice" +*CloseUI: *FeedWhere +*OpenUI *BlankSpace/Blank space at page's end: Boolean +*OrderDependency: 10 AnySetup *BlankSpace +*DefaultBlankSpace: False +*BlankSpace True/Print: "<>setpagedevice" +*BlankSpace False/None: "<>setpagedevice" +*CloseUI: *BlankSpace +*CloseGroup: BlankGroup +*DefaultFont: Courier +*% End of cpi58.ppd, 05572 bytes. diff --git a/Code/devterm_thermal_printer_cups/rastertocpi.c b/Code/devterm_thermal_printer_cups/rastertocpi.c new file mode 100644 index 0000000..f6126fd --- /dev/null +++ b/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 +#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. + 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 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 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