Using IBM i? Need to create Excel, CSV, HTML, JSON, PDF, SPOOL reports? Learn more about the fastest and least expensive tool for the job: SQL iQuery.
The Epson TM-88 is used in more Retail cash register applications than any other. However unlike PC-based operating systems, IBM i does not provide an SDK to build Printer Drivers.
To meet that challenge, we needed to write our own printer driver for the TM-88iii and later printers. The goal was to enable printing of graphic images (bitmaps) to the device so that the company logo, and customer signatures would print from RPG IV.
Note: I was working with EPSON on producing this, and since no one had done this type of thing before, they requested to include it in their developer library for other IBM i customers. I gave them permission to do so. But because of that, this is a single-stand-alone source member. It can be compiled and run directly without other tools or utilities. I did this to provide a single source member to EPSON without requiring them to also package up a bunch of /COPY members..
The program works by basically calling it from your RPG program (any RPG program) and passing in the IFS path where the BMP file is located. You can also pass the image in directly to the parameter if you've already read it into a variable (such as a BLOB) which is how I use it.The 2nd parameter is a flag that tells the program how the data is coming in, but default is assumes you're passing in the actual image bytes. To tell it you're passing in a file name, specify 1 for the 2nd parameter.
The EPSON only prints BMP files natively so if your images are stored in another format, just as JPG, you will have to convert them to BMP first, then pass them in. I expect the Image Transform API could be helpful to perform that conversion but I haven't needed to do that yet.
You also have to OVERRIDE the Print File (OVRPRTF) used in the code below to point at your Epson Print Device/OutQ.
The DDS for the EPSON88V print file description is as follows:
A R ASCIIPRT A DATA 56A 1 TRNSPY SPACEA(0) SPACEB(0) A R NORMALPRT A DATA 56A 1 SPACEA(1)
You could do this without the DDS for the Printer file by sending the EPSON "transparency" code before every bit of data you send to the printer. But having both formats made sending graphic data and plain text much easier.
[I will update this post as I have time.]
************************************************************************** ** (c) 2011 by Bob Cozzi -- All rights reserved. ** ** ------------------------------------------------------------------ ** ** This program sends a BMP (bitmap) file to an Epson Printer directly ** from the host without using a PC-based program. ** ** The concept: ** 1) Map the BMP to the header and pixel array structures. ** 2) Re-arrange the bmp pixel array bits as required by EPSON. ** 3) Then send the ESC/Pos Store-Image command sequence to the printer. ** 4) Then send the translated bmp pixel array to the printer. ** 5) Then send the ESC/Pos "Print the image I just sent to you" command. ************************************************************************** H DFTACTGRP(*NO) ACTGRP(*CALLER) H BNDDIR('QC2LE') H COPYRIGHT('(c) 2011 Robert Cozzi, Jr. All rights reserved.') FEPSON88V O E PRINTER D BMPtoEPSON PR EXTPGM('BMPTOEPSON') D image 32760A Const Varying D nFlags 10U 0 Const OPTIONS(*NOPASS) D ADV_THE_PAPER DS Qualified D esc 3u 0 Inz(27) D escj 3u 0 Inz(74) D units 3U 0 Inz(90) D CENTER_TEXT DS Qualified D esc 3u 0 Inz(27) D cmd 3u 0 Inz(97) D parm 3U 0 Inz(1) D LEFT_TEXT DS Qualified D esc 3u 0 Inz(27) D cmd 3u 0 Inz(97) D parm 3U 0 Inz(0) D RIGHT_TEXT DS Qualified D esc 3u 0 Inz(27) D cmd 3u 0 Inz(97) D parm 3U 0 Inz(1) D PRINT_LINE_FEED... D DS Qualified D esc 3u 0 Inz(27) D escd 3u 0 Inz(100) D lines 3U 0 Inz(3) D asciiESC DS Qualified D code 3U 0 Inz(3) D cmdLength 3U 0 Inz D crlf C Const(X'0D0A') D xVal S 10I 0 D yVal S 10I 0 D xValMod S 10I 0 D xValFrame S 10I 0 D xValByte S 10I 0 D lastByte S 10I 0 D bitByte S 10I 0 D byteInc S 10I 0 D bmpImg S 32760A Varying D bmpImg2 S 32760A Varying D bmpStrip S 255A Varying D idx S 10I 0 D iX S 10I 0 D i S 10I 0 D maxBytes S 10I 0 D nullValues S 64A Inz(*ALLX'00') D szOutput S 56A Varying D szDataOut S 32760A Varying D ptr S * D tiny DS Qualified D value 2A Inz(X'0000') D left 1A overlay(value:1) D right 1A overlay(value:2) D char 1A overlay(value:2) D int 5I 0 overlay(value) D uint 5U 0 overlay(value) D LittleEndianShort_T... D DS Qualified Inz D Value 5U 0 D L 3U 0 Overlay(value) D H 3U 0 Overlay(value:*NEXT) D BigEndianShort_T... D DS Qualified Inz D Value 5U 0 D H 3U 0 Overlay(value) D L 3U 0 Overlay(value:*NEXT) D littleEndianLong_T... D DS Qualified Inz D Value 10U 0 D L 5U 0 Overlay(value) D LL 3U 0 Overlay(L) D LH 3U 0 Overlay(L:*NEXT) D H 5U 0 Overlay(value:*NEXT) D HL 3U 0 Overlay(H) D HH 3U 0 Overlay(H:*NEXT) D bigEndianLong_T... D DS Qualified Inz D Value 10U 0 D H 5U 0 Overlay(Value) D HH 3U 0 Overlay(H) D HL 3U 0 Overlay(H:*NEXT) D L 5U 0 Overlay(value:*NEXT) D LH 3U 0 Overlay(L) D LL 3U 0 Overlay(L:*NEXT) D escPrintImgV DS Qualified D GS 3U 0 Inz(29) D lParen 3U 0 Inz(40) D L 3U 0 Inz(76) D Pl 3U 0 Inz(2) D Ph 3U 0 Inz(0) D M 3U 0 Inz(48) D Fn 3U 0 Inz(50) D escLoadImgV DS Qualified D GS 3U 0 Inz(29) D lParen 3U 0 Inz(40) D L 3U 0 Inz(76) D Pl 3U 0 Inz D Ph 3U 0 Inz D M 3U 0 Inz(48) D Fn 3U 0 Inz(112) D A 3U 0 Inz(48) // BX and BY are multipliers for signature size/scale (I think) // Values are: BX(1) size is 180px wide and BX(2) size is 360px wide D Bx 3U 0 Inz(1) D By 3U 0 Inz(1) D C 3U 0 Inz(49) D XL 3U 0 Inz(0) D XH 3U 0 Inz(0) D YL 3U 0 Inz(0) D YH 3U 0 Inz(0) // Load Bitmap image using TM-88II/III ESC/Pos commands D escLoadImgII DS Qualified D GS 3U 0 Inz(29) D asterisk 3U 0 Inz(42) D X 3U 0 Inz(0) D Y 3U 0 Inz(0) D escPrintImgII DS Qualified D GS 3U 0 Inz(29) D forwardSlash 3U 0 Inz(47) D M 3U 0 Inz(0) D escLoadImgIINV DS Qualified D FS 3U 0 Inz(28) D q 3U 0 Inz(113) D n 3U 0 Inz(12) D XL 3U 0 Inz(0) D XH 3U 0 Inz(0) D YL 3U 0 Inz(0) D YH 3U 0 Inz(0) D escPrintImgIINV... D DS Qualified D FS 3U 0 Inz(28) D p 3U 0 Inz(112) D n 3U 0 Inz(12) D m 3U 0 Inz(0) D BMP_Struct_T DS Qualified Inz D hdr LikeDS(BitmapFileHeader) D info LikeDS(BitmapInfoHeader) D***data 32765A D BITMAPfileHeader... D DS Qualified Inz D magicID 2A D file_Size 10U 0 D reserved1 5U 0 D reserved2 5U 0 D image_Offset 10U 0 D BITMAPinfoHeader... D DS Qualified Inz D info_Size 10u 0 D width 10u 0 D height 10u 0 D nWidth 10i 0 Overlay(width) D nHeight 10i 0 Overlay(height) D nPlanes 5u 0 D bitsPerPixel 5u 0 D compress_type 10u 0 D Image_Size 10u 0 D XPixsPerMeter 10u 0 D yPixsPerMeter 10u 0 D nColors 10u 0 D nImpColors 10u 0 D loWord PR 3U 0 D shortValue 5U 0 Const D hiWord PR 3U 0 D shortValue 5U 0 Const D LtoBShort... D PR 5U 0 D shortValue 5U 0 Const D LtoBLong... D PR 10U 0 D longValue 10U 0 Const D cvtBmpHdr PR D reverse PR D ptr * Value D len 10I 0 Const D XORBits 1N Const OPTIONS(*NOPASS) D LoadFromFile PR 10I 0 D bmpData 32760A Varying D ifsFile 2000A Const Varying D bmp DS LikeDS(bmp_Struct_T) Inz D bmpData S 32760A Varying D BMPtoEPSON PI D image 32760A Const Varying D nFlags 10U 0 Const OPTIONS(*NOPASS) D align S 56A Varying D flags S 10U 0 D BMP_FLAGS DS Qualified D FILE 10I 0 Inz(1) D ALIGNLEFT 10I 0 Inz(2) D ALIGNRIGHT 10I 0 Inz(4) D ALIGNCENTER 10I 0 Inz(8) D TM88II 10I 0 Inz(16) D TM88IINV 10I 0 Inz(32) D LEFT 10I 0 OVERLAY(AlignLeft) D RIGHT 10I 0 OVERLAY(AlignRight) D CENTER 10I 0 OVERLAY(AlignCenter) D TM88III 10I 0 OVERLAY(TM88II) D TM88IIINV 10I 0 OVERLAY(TM88IINV) C MOVE *ON *INLR /free if (%Parms()>=2); flags = nFlags; endif; if (((%Parms()>=2) and %BITAND(nFlags:BMP_FLAGS.FILE)=BMP_FLAGS.FILE) or (%subst(image:1:2) <> X'424D') ); LoadFromFile(bmpData : image); // Load it from the IFS else; // Otherwise, assume the image data is passed in. bmpData = image; endif; bmp = %subst(bmpData:1:%size(bmp_Struct_T)); if (bmp.hdr.magicID <> X'424D'); // Must = ascii('BM') return; endif; cvtBmpHdr(); // Convert BMP header from little-endian to BIG-Endian // Extract the BMP pixel array bmpData = %subst(bmpData:bmp.hdr.image_Offset:bmp.info.image_size); xVal = bmp.info.width; yVal = bmp.info.height; xValMod = %rem(xVal : 32); XValFrame = %DIV(xVal + (32 - xValMod) : 8); xValByte = %DIV(XVal + (8 - %REM(xVal : 8)) : 8); bitByte = %REM(xVal : 8); // If you can read and understand the follow loop/conversion // you are a genious. It converts the bits/bytes of a bmp // image file's pixel array to what is needed by an // Epson TM series printer. That is, it flips the bits around, // gets rid the the padding, and then reverses (xor) the bits. // This allows the BMP to be printed directly from RPG IV. // You're welcome! iX = 1; for idX = 1 to yVal; if (ix < %len(bmpData)); if (ix + (xValByte-1) > %len(bmpData) ); bmpStrip = %subst(bmpData : ix); else; bmpStrip = %subst(bmpData : iX : xValByte); endif; tiny.uint = 0; lastByte = 0; if (bitByte > 0); tiny.uint = 0; tiny.char = %subst(bmpStrip:%len(bmpStrip)); if (bitByte = 1); lastByte = tiny.uint + 127; elseif (bitByte = 2); lastByte = tiny.uint + 63; elseif (bitByte = 3); lastByte = tiny.uint + 31; elseif (bitByte = 4); lastByte = tiny.uint + 15; elseif (bitByte = 5); lastByte = tiny.uint + 7; elseif (bitByte = 6); lastByte = tiny.uint + 3; elseif (bitByte = 7); lastByte = tiny.uint + 1; endif; endif; if (bitByte > 0); tiny.uint = lastByte; endif; reverse(%addr(bmpStrip) + 2 : %len(bmpStrip):*ON); if ((xValByte-1) > %len(bmpStrip)); bmpImg += bmpStrip; else; if (bitByte = 0); bmpImg += %subst(bmpStrip : 1 : xValByte-1) ; else; bmpImg += tiny.char + %subst(bmpStrip :1: xValByte-1); endif; endif; endif; iX += xValFrame; endfor; reverse(%addr(bmpImg)+2 : %len(bmpImg)); // Flip the image over align = ''; if (%Parms() >= 2); // Flags specified? if (%bitAND(nFlags:BMP_FLAGS.LEFT )=BMP_FLAGS.LEFT); align = LEFT_TEXT; elseif (%bitAND(nFlags:BMP_FLAGS.RIGHT)=BMP_FLAGS.RIGHT); align = RIGHT_TEXT; elseif (%bitAND(nFlags:BMP_FLAGS.CENTER)=BMP_FLAGS.CENTER); align = CENTER_TEXT; endif; endif; if ( align = ''); align = LEFT_TEXT; endif; data = align + nullValues; write ASCIIPRT; // Try to center the signature on the page if (%BITAND(flags : BMP_FLAGS.TM88ii)=BMP_FLAGS.TM88ii); // Use TM-88II/III esc codes? escLoadImgii.x = xVal/8; escLoadImgii.y = yVal/8; szDataOut = escLoadImgii; szDataOut += bmpImg; // Add the img to the output buffer szDataOut += escPrintImgii; elseif (%BITAND(flags : BMP_FLAGS.TM88iiNV)=BMP_FLAGS.TM88iiNV); escLoadImgiiNV.xL = loWord( xVal ); escLoadImgiiNV.xH = hiWord( xVal ); escLoadImgiiNV.yL = loWord( yVal ); escLoadImgiiNV.yH = hiWord( yVal ); szDataOut = escLoadImgiiNV; szDataOut += bmpImg; szDataOut = escPrintImgiiNV; else; // Default is to use TM-88V Esc/Pos codes escLoadImgV.pL = loWord(%len(bmpImg)+10); escLoadImgV.pH = hiWord(%len(bmpImg)+10); escLoadImgV.xL = loWord( xVal ); escLoadImgV.xH = hiWord( xVal ); escLoadImgV.yL = loWord( yVal ); escLoadImgV.yH = hiWord( yVal ); szDataOut = escLoadImgV; szDataOut += bmpImg; // Add the img to the output buffer szDataOut += escPrintImgV; endif; // Write the data to the Epson ASCII Printer maxBytes = %size(szOutput)-2; dow (szDataOut <> ''); if (%len(szDataOut) <= maxBytes); if (%len(szDataOut) > 0); szOutput = szDataOut; else; szOutput = ''; endif; szDataOut = ''; else; szOutput = %subst(szDataOut:1: maxBytes); szDataOut = %subst(szDataOut : %Len(szOutput)+1); endif; if (%len(szOutput) > 0); data = szOutput; if (%len(szOutput) < %len(data)); // data = %trimR(data) + nullValues; endif; write ASCIIPRT; endif; enddo; // PRINT_LINE_FEED.lines = 5; // data = PRINT_LINE_FEED + LEFT_TEXT + nullValues; // write ASCIIPRT; return; /end-free P LtoBLong... P B D LtoBLong... D PI 10U 0 D longValue 10U 0 Const D le DS LikeDS(LittleEndianLong_T) Inz D be DS LikeDS(BigEndianLong_T) Inz /free le.value = longValue; be.ll = le.ll; be.lh = le.lh; be.hl = le.hl; be.hh = le.hh; return be.value; /end-free P LtoBLong... P E P LtoBShort... P B D LtoBShort... D PI 5U 0 D shortValue 5U 0 Const D le DS LikeDS(LittleEndianShort_T) Inz D be DS LikeDS(BigEndianShort_T) Inz /free le.value = shortValue; be.l = le.l; be.h = le.h; return be.value; /end-free P LtoBShort... P E P hiWord B D hiWord PI 3U 0 D shortValue 5U 0 Const ************************************************** ** Returns the HIword from a ** a 2-byte big-endian integer value. ************************************************** D short DS Qualified D value 5U 0 D hiWord 3U 0 Overlay(value) D loWord 3U 0 Overlay(value:*NEXT) /free short.value = shortValue; return short.hiWord; /end-free P hiWord E P loWord B D loWord PI 3U 0 D shortValue 5U 0 Const ************************************************** ** Returns the LOword from a ** a 2-byte big-endian integer value. ************************************************** D short DS Qualified D value 5U 0 D hiWord 3U 0 Overlay(value) D loWord 3U 0 Overlay(value:*NEXT) /free short.value = shortValue; return short.loWord; /end-free P loWord E P cvtBmpHdr B D cvtBmpHdr PI D litEnd DS LikeDS(littleEndianLong_T) Inz D bigEnd DS LikeDS(BigEndianLong_T) Inz /free bmp.hdr.image_Offset = LtoBLong(bmp.hdr.image_Offset); bmp.hdr.file_size = LtoBLong(bmp.hdr.file_size); bmp.info.info_size = LtoBLong(bmp.info.info_size); litEnd.value = bmp.info.image_size; bmp.info.image_size = LtoBLong(bmp.info.image_size); bigEnd.value = bmp.info.image_size; bmp.info.Width = LtoBLong(bmp.info.Width); bmp.info.height = LtoBLong(bmp.info.height); bmp.info.compress_Type = LtoBLong(bmp.info.compress_Type); bmp.info.XPixsPerMeter = LtoBLong(bmp.info.XPixsPerMeter); bmp.info.YPixsPerMeter = LtoBLong(bmp.info.YPixsPerMeter); bmp.info.nColors = LtoBLong(bmp.info.nColors); bmp.info.nImpColors = LtoBLong(bmp.info.nImpColors); bmp.info.nPlanes = LtoBShort(bmp.info.nPlanes); bmp.info.bitsPerPixel = LtoBShort(bmp.info.bitsPerPixel); /end-free P cvtBmpHdr E P reverse B D reverse PI D ptr * Value D len 10I 0 Const D XORBits 1N Const OPTIONS(*NOPASS) D start S * D end S * D strEnd S 1A Based(end) D strStart S 1A Based(start) /free start = ptr; end = ptr + (len-1); dow ( start < end ); // Flip (reverse-order) the bytes in the input string strStart = %BITXOR(strStart : strEnd); strEnd = %BITXOR(strEnd : strStart); strStart = %BITXOR(strStart : strEnd); // Need to xor the bits? if (%parms() >= 3 and XORbits); strStart = %BITXOR(strStart : X'FF'); strEnd = %BITXOR(strEnd : X'FF'); endif; start += 1; end -= 1; enddo; return; /end-free P reverse E P LoadFromFile B D LoadFromFile PI 10I 0 D bmpData 32760A Varying D ifsFile 2000A Const Varying D O_RDONLY C Const(1) D openIFS PR 10I 0 extProc('open64') D szIFSFileName * Value options(*string) D openFlags 10I 0 Value D fMod 10U 0 Value options(*nopass) D CCSID 10U 0 Value options(*nopass) D readIFS PR 10I 0 ExtProc('read') D hFile 10I 0 Value D szBuffer * Value D nBufLen 10U 0 Value D closeIFS PR 10I 0 ExtProc('close') D hFile 10I 0 Value D imgBuffer S 32760A D nBytes S 20I 0 D hFile S 10I 0 /free hFile = openIfs(%trimR(ifsFile): O_RDONLY ); if (hFile < 0); return 0; endif; nBytes = readIfs(hFile:%addr(imgBuffer): %size(imgBuffer)); if (nBytes < 0); return 0; endif; bmpData = %subst(imgBuffer:1:nBytes); closeIFS(hFile); return nBytes; /end-free P LoadFromFile E