Sophie

Sophie

distrib > Mandriva > 9.1 > ppc > by-pkgid > d28aa3e8cda971dba1970008aa666944 > files > 196

printer-filters-1.0-102mdk.ppc.rpm

/*
 * Copyright 2003 Roberto Ragusa <r.ragusa@libero.it>
 *
 * This program converts a 600dpi bitmap representation of an A4 sized
 * page (4768x6796 pbm file with *exactly* 13 bytes of header) into a
 * binary stream of data.
 * If this data is sent to a Epson EPL-5700L, EPL-5800L or EPL-5900L
 * laser printer the original image will be printed.
 *
 * This code is currently highly experimental and tested only on an
 * EPL-5900L.
 * All the parameters (dpi, paper size are hard coded into the
 * binary header).
 * 
 * The purpose of this program is to build the fundamentals of an
 * Epson EPL5x00L printer driver.
 *
 * History:
 *
 *    2003-01-01 Roberto Ragusa <r.ragusa@libero.it>
 *
 *             * First version, the code is not commented and the
 *             protocol is not described at all.
 *             Preliminar tests successful!
 *             Running speed and compression ratio are very good.
 *
 *    2003-01-02 Roberto Ragusa <r.ragusa@libero.it>
 *
 *             * Added copy-2 and copy-3 commands.
 *             * Little fixes and polishing.
 *             * Added huge comment text describing protocol.
 *             The program is now understandable by humans.
 *
 *    2003-01-06 Roberto Ragusa <r.ragusa@libero.it>
 *
 *             * General reorganization of the code.
 *             * Added debug features (stderr).
 *             * Improved C89 compliance.
 *
 *    2003-01-07 Roberto Ragusa <r.ragusa@libero.it>
 *
 *             * Memory allocations.
 *             * Eliminated some hard coded values,
 *             better cropping of the bitmap.
 *             * Tests on error conditions.
 *
 *    2003-01-08 Roberto Ragusa <r.ragusa@libero.it>
 *
 *             * More tests on error conditions.
 *
 *    2003-02-02 Roberto Ragusa <r.ragusa@libero.it>
 *
 *             * 4-bit code now implemented! Added
 *               explanation in the protocol description.
 *
 * ToDo:
 *             * Many things.
 *
 */

/*
 * --- Explanation of how it works (the though part) ---
 * 
 * The Epson EPL-5700L, EPL-5800L and EPL-5900L printers are a low cost
 * version of the equivalent "not-L" printers. The difference is that L
 * versions have less memory and CPU power on-board and some of the
 * firmware functionalities are moved to the host sending the file to
 * print. There is no Postscript support and no support for widely known
 * bitmap printing protocols such as PCL or ESC/P2.
 * The printers want the bitmap in a specific binary format and not in
 * a simple raw format. The format is designed to give a good
 * compression of typical images so to have a smaller data file going
 * from host to printer.
 * 
 * No documentation on the format is publicly available, so a lot of
 * guess work had to be done to understand the meaning of spool files
 * generated by official drivers (that is, closed-source drivers).
 * 
 * There are some job-header, page-header and page-footer byte sequences
 * which will not be discussed here. They contain information about
 * papersize, resolution and other options. Some documentation about
 * the headers can be found rather easily elsewhere.
 * The bitmap is divided in horizontal stripes, each composed by 64
 * rows.
 * Every stripe is encoded independently from the others.
 * The stripe description starts with a stripe header with information
 * about the length of the following binary data.
 * 
 * Now, the actual thing: how the stripe data is encoded.
 * The binary data is a set of 16bit words. The bit stream starts with
 * the less significant bit and after the most significant bit we pass
 * to the next 16bit word, again LSB first.
 * Example:
 * a binary data like this (letters are just marks to help you tracking
 * the bits)
 *  01110001 10101111  00001000 01001110 ...
 *  a      b c     de  f      g h      i
 * 
 * has 2 16 bit words before the dots.
 * The first word is to be considered first. But not starting from the
 * MSB, instead from LSB.
 * So the real bit stream should be interpreted as
 *  11110101100011100111001000010000...
 *  ed     cb      ai      hg      f
 * 
 * Looking at things this way is not very comfortable because pixel
 * patterns and binary counters encoding needs to be bit-reversed (that
 * is, a 8bit value of 128 is not 1000000 but 00000001).
 * 
 * So, we face the situation differently. We imagine the bit stream grows
 * in the left direction (not right). So the data is for us
 *   ... 00001000 01001110 01110001 10101111
 *       f      g h      i a      b c     de
 * Note that the dots are now on the right and if we read from right
 * to left we have a correct bit order (we encounter marks "edcbaihgf").
 * 
 * We start with an empty 32bit temp variable and populate the LSB bit
 * first and than the others. When we have more than 16 bits ready
 * we copy bit 15-8 and 7-0 (LSB is bit 0) in the first two bytes of the
 * output buffer and shift the temp word 16 bit right, ready to populate
 * other bits.
 * 
 * There is a last thing to be careful about. Bits are not inserted
 * into the temp word one by one, but on groups of variable length.
 * These groups are represented by numeric constants just ready for
 * insertion and so are to be interpreted from right to left.
 * 
 * The bit stream above could have been generated by an insertion
 * of 101111 and then 0110 and then 011100 and then 10 and then 0011 and
 * so on.
 * So if I say we emit a 001 code followed by a 101 code I'm
 * speaking of this:
 *    ...101001
 * and if I say we emit a 111 code followed by the 8 bit representation
 * of the number 64 I'm speaking of this
 *    ...01000000111
 * and if I say we emit a 110 code followed by a bitmap bbwwbbbb
 * (b=black bit=1, w=white bit=0) I'm speaking of this
 *    ...00110000110
 * Note that we can simply paste the 64 and the bitmap in the stream
 * this way.
 * 
 * The above description is fundamental to quickly understand the
 * following.
 *  
 * We are going to encode the stripe.
 * we assume we have the bitmap data in the simple bit by bit
 * left-to-right order, so 3 white pixels followed by 6 black, 2 white
 * and 5 black is
 *   00011111 10011111 (0x1f,0x9f)
 * Note that I delimited byte boundaries.
 * This is the format commonly used in PBM format files.
 * 
 * The stripe is formed by 64 rows.
 * The description of one row can use data present at the row just above
 * the current one. If we are in the first row (row 0) we *can* use
 * an hypothetical row -1 which is assumed completely white.
 * 
 * Now the description of a row. It is all byte based.
 * There are two types of possible commands: literal and copy.
 * A literal command is followed by the bitmap data, while a
 * copy command tells that we can copy one of the bytes already sent
 * (the specific copy command tells where it is, as there are
 * more than one copy command).
 * After a copy command there is *always* a repetition command
 * which indicates how many times the copy has to be done.
 * Literal commands are *not* repeatable.
 * 
 * Here is the full table:
 * 
 *   LITERAL
 * 
 *     10 xxxxxxxx     The bitmap data is the byte xxxxxxxx
 *     00 xxxx         (this is not completely understood and is not used)
 *                     (UPDATE: this 4-bit code is now understood,
 *                     explanation at the end of this section)
 * 
 *   COPY
 * 
 *     01              Copy the byte from the row above
 *     011             Copy the byte from our left (last byte sent)
 *     0111            Copy the second byte from our left (current-2)
 *     1111            Copy the third byte from our left (current-3)
 * 
 *     REPETITIONS (only after copy)
 *       0             Copy only one time (no repetition)
 *       01            Copy 2 times
 *       0011          Copy 3 times
 *       1011          Copy 4 times
 *       01111         Copy 5 times
 *       011111        Copy 6 times
 *       111111        Copy 7 times
 *       0111 yyyyyyy  Copy more than 7 times. yyyyyyy is a 7 bit
 *                       value containing the number of repetitions.
 * 		         This number can be:
 * 		         - 0       special case: copy until end of row
 * 		         - 1..7    not accepted (crash on 5900L),
 * 		                     you have to use the short form
 * 		         - 8..126  repeat said times
 * 		         - 127     repeat 127 times, and immediately
 * 		                     read another 7 bit yyyyyyy value
 * 				     again. This way long repetitions
 * 				     can be specified 127 by 127 and
 * 				     finally the rest.
 * 
 * That's all, folks.
 * 
 * Note that 0111 yyyyyyy means emit 0111 and then yyyyyyy so the
 * bit stream is
 *   ...yyyyyyy0111
 * as explained above.
 * 
 * When a full stripe has been encoded a certain number of 0 bits
 * is emitted to complete the last 16bit word.
 * 
 * EXAMPLES:
 * 
 * Suppose we want to encode a white row and assume the row is
 * 260 byte long (260*8 pixels).
 * 
 * We can do:
 *   10 00000000 10 00000000 10 00000000 10 00000000 ... 260 times
 * that is
 *   literal 0x00 260 times (awful!)
 * 
 * Or we can do:
 *   10 00000000 011 0111 1111111 1111111 0000101
 * that is
 *   literal 0x00, copy last byte repeating copy 127+127+5 times
 *   
 * Or just:
 *   10 00000000 011 0111 0000000
 * that is
 *   literal 0x00, copy last byte until the end of the row
 *   
 * Or just (if this is the first row of the stripe or the row
 * above is completely white):
 *   01 0111 0000000
 * that is
 *   copy from above repeating until row end.
 * 
 * For a totally black row (without using the row above):
 *   10 11111111 011 0111 0000000
 * that is
 *   literal 0xff, copy last byte until row end
 * 
 * If we have a totally white stripe we can do:
 *   01 0111 0000000 ...        64 times
 * that is
 *   64 commands "copy from above until row end"
 * 
 * A more complex example.
 * We want to code a white stripe with a black box inside;
 * 2 white rows above the box and 10 below the box (which
 * is 52 rows tall); 15 white pixels on the left of the
 * box (which is 6 pixels wide) and white on the right
 * of it.
 * The bitmap in hexadecimal is:
 *   0x00 0x00 0x00 0x00 0x00 0x00 0x00 ...
 *   0x00 0x00 0x00 0x00 0x00 0x00 0x00 ...
 *   0x00 0x01 0xf8 0x00 0x00 0x00 0x00 ... 52 times
 *   0x00 0x00 0x00 0x00 0x00 0x00 0x00 ... 10 times
 * A good coding is:
 *   01 0111 0000000
 *   01 0111 0000000
 *   01 0 10 00000001 10 11111000 1111 0 01 0111 0000000
 *   01 0111 0000000         51 times
 *   01 0 011 0111 0000000
 *   01 0111 0000000         9 times
 * that is
 *   copy above (white) row
 *   copy above row
 *   copy 1 byte from above (don't repeat), literal 0x1,
 *      literal 0xf8, copy from current-3 (don't repeat),
 *      copy from above until row end
 *   copy above row      (51 times)
 *   copy 1 byte from above (don't repeat), copy last
 *      until row end
 *   copy above row      (9 times)
 * Note that the current-3 copy is a waste because without
 * it we should have copied from above the white byte
 * we needed.
 * A final detail.
 * When a copy is repeating both the source and destination
 * pointers move forward. So a
 *   01 0111 0000000
 * at the beginning of a row copies all the row above,
 * it doesn't fill the row with a repetition of the first
 * byte of the row above.
 * If we had used "copy current-3 repeating until end"
 * in the last example we wouldn't have had a white
 * sequence but a repeated pattern of 0x00 0x01 0xf8.
 *
 *
 * --- UPDATE: 4 bit code not a mistery anymore. ---
 *
 * After much experimentation (including high-res scanning
 * of printings from manually generated spool files), the
 * meaning of the 4 bit code has been discovered.
 *
 * It's a cache.
 * 
 * The printer has 16 bytes of cache and a register with
 * 4 bits which we will call "lri".
 * When a stripe begins, the cache is initialized to
 * 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,
 * 0x0b,0x0c,0x0d,0x0e,0x0f and the lri is set to 0.
 * If we use the 4-bit code number i (0<=i<=15) while encoding
 * a stripe, we will print the byte at cache position i.
 * Until now, it's a simple lookup table.
 * But the table is updated every time we use an 8-bit literal;
 * the byte is placed at cache position lri and lri is
 * incremented by one (modulo 16).
 *
 * So, this a cache with a LRI replace policy; yes, not LRU
 * (Least Recently Used), but LRI (Least Recently Inserted).
 * This is why we call the additional register "lri".
 *
 * While the implementation of this algorithm on the printer
 * is really simple, the driver has to check the cache
 * content every time a literal byte is going out.
 * An efficient implementation can be obtained by means of
 * an additional lookup-table (reversed cache).
 *
 * The data size reduction is moderate for text pages (about 15%)
 * and minimal for complex graphics; the encoding process
 * is a little slower because of the additional work.
 *
 * But, after so much work to discovery the logic (the
 * replacement policy in particular way), this can't
 * be left out of the driver. :-)
 *
 *
 *   Roberto Ragusa
 *
 */

/* INCLUDE */

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>


/* DEBUG */

#define GLOBALDEBUG
#ifdef GLOBALDEBUG
void debug(int flag,...){
  va_list args;
  const char *fmt;
  const char *name;
  name="";
  if(flag){
    va_start(args,flag);
    fmt=va_arg(args,const char *);
    vfprintf(stderr,fmt,args);
  }
}
#else
void debug(int flag,...){
  /* nothing!!! */
}
#endif

#define DGENERIC 1
#define DSTREAM 1
#define DFILEIO 1


/* STREAM HANDLING */

unsigned char *stream_ptr;
int stream_temp,stream_bits;
    
inline void stream_begin(unsigned char *outbuffer){
  stream_ptr=outbuffer;
  stream_temp=0;
  stream_bits=0;
}

inline void stream_emit_nocheck(int d, int l){
  stream_temp|=(d)<<stream_bits;
  stream_bits+=(l);
}

inline void stream_check(){
  if(stream_bits>=16){
    *(stream_ptr++)=(stream_temp>>8)&0xff;
    *(stream_ptr++)=stream_temp&0xff;
    stream_temp>>=16;
    stream_bits-=16;
  }
}

inline void stream_emit(int d, int l){
  stream_emit_nocheck(d,l);
  stream_check();
}

inline unsigned char *stream_end(){
  stream_emit(0,(16-stream_bits)%16);
  return stream_ptr;
}



/* RUN LENGTH */

inline int run_length_find(unsigned char *b1, unsigned char *b2, int maxl){
  int run_length_table_d[]={/**/0,/*0*/0,/*01*/1,/*0011*/3,/*1011*/0xb,/*01111*/0xf,/*011111*/0x1f,/*111111*/0x3f};
  int run_length_table_l[]={    0,     1,      2,        4,          4,           5,             6,             6};
  int l;
  int returned_l;

  for(l=1;l<maxl&&b1[l]==b2[l];l++);
  returned_l=l;
  if(l==maxl){
    stream_emit(/*0000000 0111*/0x007,4+7);
    debug(DSTREAM,"REP2END[%x(%i)] ",l,l);
  }
  else if(l>=8){
    stream_emit(/*0111*/0x7,4);
    debug(DSTREAM,"REPBIG[%x(%i)] ",l,l);
    while(l>=127){
      stream_emit(127,7);
      debug(DSTREAM,"%x(%i)+",127,127);
      l-=127;
    }
    stream_emit(l,7);
    debug(DSTREAM,"%x(%i) ",l,l);
  }
  else{
    stream_emit(run_length_table_d[l],run_length_table_l[l]);
    debug(DSTREAM,"REPSMALL%x(%i) ",l,l);
  }
  return returned_l;
}


/* STRIPE PROCESSING */

int process_stripe(unsigned char *inbuffer, unsigned char *outbuffer, int s, int wbytes, int stripe_size){
  char cache[16];
  char byte2cache[256];
  int lri; /* least recently inserted */
  unsigned char *whiterow;
  unsigned char *thisrow, *rowabove;
  int x,y;
  int len;
  int i;

  whiterow=(unsigned char *)malloc(wbytes);
  if(whiterow==NULL) return -1;

  for(i=0;i<wbytes;i++) whiterow[i]=0;

  /* cache initialization */
  for(i=0;i<16;i++){
    cache[i]=i;
    byte2cache[i]=i;
  }
  for(i=16;i<256;i++){
    byte2cache[i]=-1;
  }
  lri=0;

  rowabove=&whiterow[0];			/* special case */
  thisrow=&inbuffer[0];
  debug(DSTREAM,"s=%05i\n",s);
  stream_begin(outbuffer);
  for(y=0;y<stripe_size;y++){
    debug(DSTREAM,"y=%05i: ",y);
    for(x=0;x<wbytes;){
      if(thisrow[x]==rowabove[x]){
        stream_emit(/*01*/0x1,2);
        debug(DSTREAM,"CABOVE ");
        x+=run_length_find(&thisrow[x],&rowabove[x],wbytes-x);
      }
      else if(x-1>=0&&thisrow[x]==thisrow[x-1]){
        stream_emit(/*011*/0x3,3);
        debug(DSTREAM,"C-1 ");
        x+=run_length_find(&thisrow[x],&thisrow[x-1],wbytes-x);
      }
      else if(x-2>=0&&thisrow[x]==thisrow[x-2]){
        stream_emit(/*0111*/0x7,4);
        debug(DSTREAM,"C-2 ");
        x+=run_length_find(&thisrow[x],&thisrow[x-2],wbytes-x);
      }
      else if(x-3>=0&&thisrow[x]==thisrow[x-3]){
        stream_emit(/*1111*/0xf,4);
        debug(DSTREAM,"C-3 ");
        x+=run_length_find(&thisrow[x],&thisrow[x-3],wbytes-x);
      }
      else if(byte2cache[thisrow[x]]>=0){ /* cache hit */
        stream_emit(/*00*/0x0,2);
        debug(DSTREAM,"LIT4CACHE");
	stream_emit(byte2cache[thisrow[x]],4);
        debug(DSTREAM,"pos_%01x %02x ",byte2cache[thisrow[x]],thisrow[x]);
        x++;
      }
      else{
        stream_emit(/*10*/0x2,2);
        debug(DSTREAM,"LIT8 ");
        stream_emit(thisrow[x],8);
        debug(DSTREAM,"%02x ",thisrow[x]);
        debug(DSTREAM,"to_pos_%01x ",lri);
        byte2cache[(unsigned char)cache[lri]]=-1;
        cache[lri]=thisrow[x];
        byte2cache[thisrow[x]]=lri;
        lri=(lri+1)&0xf;
        x++;
      }
    }
    stream_emit(1,2);
    stream_emit(7,11);
    debug(DSTREAM,"\n",y);
    rowabove=thisrow;
    thisrow+=wbytes;
  }
  len=stream_end()-outbuffer;
  debug(DSTREAM,"\n",y);

  free(whiterow);

  return len;
}


/* MAIN */

int main(void){
  int dpi_x,dpi_y;
  int wpix,hpix;
  int stripe_size;
  int wbytes,hstripes;
  int pbm_wpix,pbm_hpix;
  int pbm_wbytes;
  unsigned char *bitmap;
  unsigned char *outbuffer;
  char temp_string[256];	/* temp string buffer */
  char *ts;
  int s;
  int y;
  int slen;
  int e;
  unsigned char dpi_code1,dpi_code2;


  dpi_x=600;
  dpi_y=600;

  /* A4 paper 210x297mm */
  wpix=(210-4-4)/25.4*dpi_x; /* 4771 @ 600 dpi */
  hpix=(297-4-4)/25.4*dpi_y; /* 6826 @ 600 dpi */
  debug(DGENERIC,"wpix=%i,hpix=%i (calculated)",wpix,hpix);
  /* manual override */wpix=4768;hpix=6796;
  stripe_size=64;

  pbm_wpix=4768;
  pbm_hpix=6796;

  wbytes=wpix/8;wbytes=(wbytes+4-1)/4*4;
  hstripes=(hpix+stripe_size-1)/stripe_size;

  pbm_wbytes=(pbm_wpix+8-1)/8;

  bitmap=(unsigned char *)malloc(wbytes*stripe_size*hstripes);
  if(bitmap==NULL) return 10;
  outbuffer=(unsigned char *)malloc(wbytes*stripe_size+wbytes*stripe_size/2); /* 50% safety margin */
  if(outbuffer==NULL) return 10;

  /* start job */
  ts=temp_string;
  ts+=sprintf(ts,"\x01b\x001");
  ts+=sprintf(ts,"@EJL \x00a");
  ts+=sprintf(ts,"@EJL STARTJOB MACHINE=\"experiment\" USER=\"epl5x00l_driver\"\x00a");
  ts+=sprintf(ts,"@EJL EN LA=ESC/PAGE\x00a");
  ts+=sprintf(ts,"\x01d");
  ts+=sprintf(ts,"4eps{I");
  ts+=sprintf(ts,"%c%c%c%c",0x00,0x00,0x00,0x00);
  ts+=sprintf(ts,"\x01d");
  ts+=sprintf(ts,"9eps{I");
  if(dpi_x==300&&dpi_y==300){
    dpi_code1=0;dpi_code2=0;
  }
  else if(dpi_x==600&&dpi_y==300){
    dpi_code1=0;dpi_code2=1;
  }
  else if(dpi_x==600&&dpi_y==600){
    dpi_code1=1;dpi_code2=0;
  }
  else if(dpi_x==1200&&dpi_y==600){
    dpi_code1=1;dpi_code2=1;
  }
  else{
    return 10;
  }
  ts+=sprintf(ts,"%c%c%c%c%c%c%c%c%c",
    0x02,
    0x00,
    dpi_code1,dpi_code2,
    0x01, /* RITech */
    0x00, /* Toner save */
    0x00, /* Paper type? */
    0x03, /* density */
    0x00  
    );
  ts+=sprintf(ts,"\x01d");
  e=fwrite(temp_string,1,ts-temp_string,stdout);
  if(e!=ts-temp_string) return 10;

  /* start page */
  ts=temp_string;
  ts+=sprintf(ts,"26eps{I");
  ts+=sprintf(ts,"%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c",
    0x04,0x00,
    0x0e, /* paper code */
    stripe_size/*0x40*//*0x20*/, /* stripe height */
    ((wbytes)>>8)&0xff/*0x02*/,(wbytes)&0xff/*0x54*/, /* bytes per row (multiple of 4) */
    0x00,
    0x00,
    0x00,
    0x00,
    (hpix>>8)&0xff/*0x1a*/,hpix&0xff/*0x8c*/, /* vertical pixels */
    (wpix>>8)&0xff/*0x12*/,wpix&0xff/*0x84*//*0xa0*/, /* horizontal pixels */
    (hstripes>>8)&0xff/*0x00*/,hstripes&0xff/*0x6b*//*0xd5*/, /* stripe per page */
    0x00, /* tray */
    0x00,
    0x01, /* copies */
    0xff,
    0xfe,
    0x00,
    0x00,
    0x00,
    0x00,
    0x01);
  e=fwrite(temp_string,1,ts-temp_string,stdout);
  if(e!=ts-temp_string) return 10;

  memset(bitmap,0,wbytes*stripe_size*hstripes);
  e=fread(bitmap,1,0xd,stdin);			/* skip 13 bytes (horror!)*/
  if(e!=0xd) return 10;
  for(y=0;y<pbm_hpix;y++){
    debug(DFILEIO,"%i\n",y);
    e=fread(bitmap+wbytes*y,1,pbm_wbytes,stdin);
    if(e!=pbm_wbytes) return 10;
  }

  for(s=0;s<hstripes;s++){

    slen=process_stripe(bitmap+wbytes*stripe_size*s,outbuffer,s,wbytes,stripe_size);
    if(slen==-1) return 10;

    ts=temp_string;
    ts+=sprintf(ts,"\x1d%deps{I%c%c%c%c%c%c%c",slen+7,6,0,1,(slen>>24)&0xff,(slen>>16)&0xff,(slen>>8)&0xff,slen&0xff);
    e=fwrite(temp_string,1,ts-temp_string,stdout);
    if(e!=ts-temp_string) return 10;

    e=fwrite(outbuffer,1,slen,stdout);
    if(e!=slen) return 10;
  }

  /* end page */
  ts=temp_string;
  ts+=sprintf(ts,"\x01d");
  ts+=sprintf(ts,"2eps{I");
  ts+=sprintf(ts,"%c%c",
    0x05,
    0x00
    );
  e=fwrite(temp_string,1,ts-temp_string,stdout);
  if(e!=ts-temp_string) return 10;

  /* end job */
  ts=temp_string;
  ts+=sprintf(ts,"\x01d");
  ts+=sprintf(ts,"2eps{I");
  ts+=sprintf(ts,"%c%c",
    0x03,
    0x00
    );
  ts+=sprintf(ts,"\x01d");
  ts+=sprintf(ts,"2eps{I");
  ts+=sprintf(ts,"%c%c",
    0x01, /* 1=last page 2=next page??? maybe not */
    0x00
    );
  ts+=sprintf(ts,"\x01b\x001");
  ts+=sprintf(ts,"@EJL \x00a");
  ts+=sprintf(ts,"@EJL EJ \x00a");
  ts+=sprintf(ts,"\x01b\x001");
  ts+=sprintf(ts,"@EJL \x00a");
  e=fwrite(temp_string,1,ts-temp_string,stdout);
  if(e!=ts-temp_string) return 10;

  free(outbuffer);
  free(bitmap);

  return 0;
}