Source file: /~heha/hs/spe.zip/src/escp.cpp

#include "escp.h"
#include <shlwapi.h>
#include <windowsx.h>
#include <malloc.h>

#define elemof(x) (sizeof(x)/sizeof(*(x)))	// Elemente eines Arrays

BYTE ESCP::ReadChar() const{
 BYTE c;
 DWORD br;
 if (!ReadFile(f,&c,1,&br,NULL) || !br) longjmp(env,1);
 if (msb&1) c=c&127|msb&128;	// Zwangsbeeinflussung des MSB
 if (msb&2 && (128<=c && c<160 || c==255 || msb&4)) c&=127;	// mögliches Steuerzeichen oder während ESC-Verarbeitung?
 return c;
}

void ESCP::ReadChar(BYTE*p, int len) const{
 do *p++=ReadChar(); while(--len);
}

static const WCHAR CharSubst[]=
 L"#$@[\\]^`{|}~\0"	// 0 USA
 L"#$à°ç§^`éùè¨\0"	// 1 Frankreich
 L"#$§ÄÖÜ^`äöüß\0"	// 2 Deutschland
 L"£$@[\\]^`{|}~\0"	// 3 Großbritannien
 L"#$@ÆØÅ^`æøå~\0"	// 4 Dänemark
 L"#¤ÉÄÖÅÜéäöåü\0"	// 5 Schweden
 L"#$@°\\é^ùàòèì\0"	// 6 Italien
 L"\x20A7$@¡Ñ¿^`¨ñ}~\0"	// 7 Spanien
 L"#$@[¥]^`{|}~\0"	// 8 Japan
 L"#¤ÉÆØÅÜéæøåü\0"	// 9 Norwegen
 L"#$ÉÆØÅÜéæøåü\0"	// 10 Dänemark II
 L"#$á¡Ñ¿é`íñóú\0"	// 11 Spanien II
 L"#$á¡Ñ¿éüíñóú\0"	// 12 Lateinamerika
 L"#$@[\x20A9]^`{|}~\0"	// 13 Korea
 L"#$§°’”¶`©®†™";	// 64 US-Gesetztext

// Angesammelte Zeichen ausgeben
void ESCP::Print() {
 if (!ci) return;
 if (!(pagectl&1)) {
  StartPage(dc);
  pagectl|=1;
  SetMapMode(dc,MM_ANISOTROPIC);
  SetWindowOrgEx(dc,-Offset.x,-Offset.y,NULL);
  SetWindowExtEx(dc,2160,2160,NULL);
  SetViewportOrgEx(dc,-GetDeviceCaps(dc,PHYSICALOFFSETX),-GetDeviceCaps(dc,PHYSICALOFFSETY),NULL);
  SetViewportExtEx(dc,GetDeviceCaps(dc,LOGPIXELSX),GetDeviceCaps(dc,LOGPIXELSY),NULL);
  SetBkMode(dc,TRANSPARENT);
 }
// Font auswählen
 int weight=400;
 if (font&8) weight+=300;
 if (font&16) weight+=200;	// Doppeldruck-Wiedergabe mit SEMIBOLD (eigentlich müsste man Graustufen verwenden :-)
 int j=GetCw();
 HFONT f=CreateFontA(font&2?720:ss?270:360,j,0,0,
   weight,
   (font>>6)&1,		// kursiv
   (font>>7)&1,		// unterstrichen
   (lq>>3)&1,		// durchgestrichen
   ANSI_CHARSET,
   OUT_DEFAULT_PRECIS,
   CLIP_DEFAULT_PRECIS,
   lq&3?PROOF_QUALITY:DRAFT_QUALITY,
   lq&2?VARIABLE_PITCH:FIXED_PITCH,
   lq&2?"Proportional":lq&3?"Courier":font&1?"Elite":"Pica");
 HFONT of=SelectFont(dc,f);
// Zeichen konvertieren
 WCHAR s[256];
 UINT CodePage=cp[cpi];
 if (CodePage<2) CodePage=437;
 int l=MultiByteToWideChar(CodePage,0,(char*)cb,ci,s,256);
 int i=charset;
 if (i==64) i=14;
 if (i<15) for (PWSTR ps=s; ps=wcspbrk(ps,CharSubst); ps++) {
  *ps=wcschr(CharSubst,*ps)[i*13];
 }
// Zusätzlichen Leerraum ausgeben (entfällt wegen Distanz-Array)
// i=lq&3?espace:0;
// if (charextra!=i) {
//  charextra=i;
//  SetTextCharacterExtra(dc,i);
// }
// Zeichenkette ausgeben
 int cy=pos.y;
 if (font&2) cy-=ly;	// Doppelte Höhe? (Ausrichtung auf Basislinie ??)
 else if (ss&2) cy+=90;	// Tiefstellung (Hochstellung ohne Anpassung)
#ifdef _DEBUG
 s[l]=0;		// terminieren für erleichtertes Debuggen
#endif
// Ohne das Distanz-Array ist die PostScript-Ausgabe nicht zufrieden stellend!!
// Deshalb ExtTextOut() statt TextOut(). Clipping wird nicht benötigt.
 int *d=(int*)_alloca(l*sizeof(int));
 if (lq&3) j+=espace;
 for (i=0; i<l; i++) d[i]=j;
 ExtTextOutW(dc,margin.left+cx,margin.top+cy,0,NULL,s,l,d);
 SelectFont(dc,of);
 DeleteFont(f);
 ci=0;
}

void ESCP::FormFeed() {
 Print();
 if (!(pagectl&1)) StartPage(dc);	// leere Seite produzieren
 EndPage(dc);
 pagectl&=~1;			// neue Seite ist leer
 lq&=~32;
 pos.y=0;
}

void ESCP::SetPosX(int i) {
 if ((unsigned)i>(unsigned)margin.Width()) return;
 Print();
 pos.x=i;
}

void ESCP::SetPosY(int i) {
 Print();
 if (i<0) return;
 lq&=~32;
 pos.y=i;
 if (i>margin.Height()) FormFeed();
}

void ESCP::LineFeed() {SetPosY(pos.y+ly);}

void ESCP::Printable(BYTE c) {
 if (crlf&4 && pos.x+GetCx()>margin.Width()) {	// Zeichen passt nicht?
  LineFeed();		// neue Zeile beginnen (nur wenn AUTOMATIC LINE FEED = TRUE!!)
  pos.x=0;
 }
 if (!ci) cx=pos.x;	// Startposition für Print() merken
 if (ci<elemof(cb)) cb[ci++]=c;
 pos.x+=GetCx();
}

// Berechne momentane Zeichenbreite in 1/240 Zoll
int ESCP::GetCw() const{
 int cx=216;		// 10 Zeichen pro Zoll ("Pica", NLQ)
 BYTE f=font|lq&32;
 if (!(lq&2)) {		// bei Proportionalschrift stets 10 cpi
  if (f&1) cx=180;	// "Elite", 12 Zeichen pro Zoll
  if (!(f&2)) {		// keine Kompression bei doppelter Höhe
   if (f&4) cx=126;	// Condensed Pica, ca. 17,14 Zeichen pro Zoll
   if ((f&5)==5) cx=108;// Condensed Elite, 20 Zeichen pro Zoll
  }
  if (lq&4) cx=144;	// 15 Zeichen pro Zoll (keine komprimierte Version; hat Vorrang)
 }
 if (f&32) cx<<=1;	// doppelte Breite (für alle Schriften)
 return cx;
}

// Berechne momentann Zeichenabstand in 1/240 Zoll
int ESCP::GetCx() const{
 int cx=GetCw();
 if (lq&3) cx+=espace;
 return cx;
}

// Unterfunktion: ESC '('
// Danach folgt immer eine 2-Byte-Datenlänge 0..32767 und ein entsprechend langer Datenblock
bool ESCP::HandleEscK() {
 char c=ReadChar();
 WORD dlen;
 ReadChar((BYTE*)&dlen,2);
 BYTE*p=(BYTE*)_alloca(dlen);
 ReadChar(p,dlen);
 int i;
 switch (c) {
  case '-': {
   if (dlen!=3) return false;
   switch (p[1]) {
    case 1: SetFontBit(7,p[2]?1:0); break;	// unterstreichen
    case 2: lq=lq&~8|(p[2]?8:0); break;		// durchstreichen (überstreichen gibts bei Windows erst mal nicht)
   }
  }break;			
  case 'B': break;				// Strichkode
  case 'C': {
   if (dlen!=2) return false;
   i=scale(*(short*)p,6);
   if (!i) return false;
// if (i>22*2160) return false;			// nicht mehr als 22" (warum eigentlich nicht??)
   paper.cy=i;
  }break;					// Papierlänge
  case 'G': /*GRAFIK*/ break;			// Grafikmodus
  case 'U': {
   if (dlen!=1) return false;
   switch (*p) {
    case 5: case 10: case 20: case 30: case 40: case 50: case 60: unit=*p/5;
    default: return false;
   }
  }break;					// Maßeinheit
  case 'V': if (dlen!=2) return false; SetPosY(margin.top+scale(SH(p),6)); break;	// vertikale Druckposition in 1/360"
  case '^': {
   if (i=dlen) do Printable(*p++); while (--i);
  }break;					// Steuerzeichen als druckbare Zeichen ausgeben
  case 'c': {
  }break;					// Seitenformat-Auswahl
  case 'i': break;
  case 't': {
   if (dlen!=3) return false;
   p[0]=AB(p[0]);
   if (p[0]>=4) return false;
   WORD *cpp=cp+p[0];
   static const BYTE cp1[]={132,50,51,53,55,60,63,65,52,57,62,64,66,69};
   if (2<=p[1] && p[1]<16) *cpp=800+cp1[p[1]-2];	// Liste nicht vollständig! Geht bis Index 42.
   else if (p[1]==127 && p[2]<16) *cpp=28590+p[2];	// ISO-Latin1..ISO-Latin15
   else *cpp=437;
  }break;				// Zeichentabellen-Zuordnung
  case 'v': if (dlen!=2) return false; SetPosY(pos.y+scale(SH(p),6)); break;	// relative vertikale Druckposition in 1/360"
  default: return false;			// unbekanntes Escape
 }
 return true;
}

int ESCP::scale(int i, int def) const{
 if (unit) return i*unit;
 return i*def;
}

void ESCP::SetFontBit(BYTE bitno,BYTE state) {
 if (state==255) state=AB(ReadChar());
 if (state>1) return;	// bei falschen Zahlen nichts tun!
 Print();
 BYTE mask=1<<bitno;
 font=font&~mask|state<<bitno;
}

// Perforationssprung (also oberen und unteren Seitenrand; hier: gemittelt) setzen
void ESCP::SetFormSkip(int skip) {
 margin.top=skip>>1; margin.bottom=paper.cy-skip+margin.top;
}

// Setzt Tabs entsprechend ReadChar() (muss aufsteigende Zahlenreihe liefern)
void ESCP::SetTabs(int t[], int tlen, int factor) {
 char c,cb;
 for (cb=0; (c=ReadChar())>cb; tlen--,cb=c)
   if (tlen>0) *t++=c*factor;
 if (tlen>0) *t=0;
}

// Setzt gleichmäßige (horizontale oder vertikale) Tabs
void ESCP::SetEqualTabs(int t[], int tlen, int delta) {
 int a=0;
 do{
  a+=delta; 
  *t++=a;
 }while(--tlen);
}

// Nächsten Tabulator zur gegebenen Position suchen (rechter/unterer Rand muss danach geprüft werden)
int ESCP::FindTab(const int t[], int tlen, int pos) {
 do{
  if (pos<*t) return *t;
  t++;
 }while(--tlen);
 return 32767;
}

void ESCP::HandleESCe() {
 switch (AB(ReadChar())) {
  case 0: SetTabs(vfu[vtc],16,ReadChar()*ly); break;
  case 1: SetTabs(ht,32,ReadChar()*GetCx()); break;
 }
}

void ESCP::HandleESCf() {
 BYTE hv=AB(ReadChar());
 for (int i=ReadChar(); i; i--) switch (hv) {
  case 0: Printable(' '); break;
  case 1: LineFeed(); break;
 }
}

bool ESCP::HandleEsc() {
 int i;
 BYTE b[2], c=ReadChar();
 switch (c) {
  case 14: Print(); lq|=32; break;			// SO  = doppelte Breite für eine Zeile
  case 15: SetFontBit(2,1); break;			// SI  = komprimierte Zeichen
  case 25: ReadChar(); break;				// EM  = Einzelblatteinzug einschalten (EM 4) / ausschalten (EM 0)
  case ' ': espace=ReadChar(); break;			// SP  = Extra-Platz nach jedem Zeichen
  case '!': Print(); c=ReadChar(); font=font&2|c&~2; lq=lq&~2|c&2; break;	// Schriftart (Bit 1 = proportional: umsetzen!)
  case '#': msb&=~129; break;				// MSB-Steuerung AUS
  case '$': ReadChar(b,2); SetPosX(margin.left+scale(SH(b),36)); break;	// horizontale Druckposition setzen
  case '%': Print(); /*TODO*/ break;			// Benutzerdefinierter Zeichensatz
  case '&': /*TODO*/ break;				// Benutzerdefinierte Zeichen
  case '(': return HandleEscK();			// Verschiedenes
  case '*': /*GRAFIK*/ break;				// Bitmap-Grafikdruck
  case '+': ly=ReadChar()*6; break;			// Zeilenabstand n/360"
  case '-': SetFontBit(7,255); break;			// Unterstreichung ein/aus
  case '.': /*GRAFIK*/ break;
  case '/': c=ReadChar(); if (c<8) vtc=c; break;	// Vertikal-Tabulatoren-Kanal auswählen
  case '0': ly=270; break;				// Zeilenabstand 1/8"
  case '1': ly=210; break;				// Zeilenabstand 7/72"
  case '2': ly=360; break;				// Zeilenabstand 1/6"
  case '3': ly=ReadChar()*12; break;			// Zeilenabstand n/180" (9-Nadler: n/216", ergibt Faktor 10)
  case '4': SetFontBit(6,1); break;			// Kursiv EIN
  case '5': SetFontBit(6,0); break;			// Kursiv AUS
  case '6': msb&=~2; break;				// Steuerzeichen mit Bit 7 drucken
  case '7': msb|=2; break;				// Steuerzeichen mit Bit 7 interpretieren (also Bit 7 löschen)
  case '8': break;					// Papierendesensor ausschalten
  case '9': break;					// Papierendesensor einschalten
  case ':': /*TODO*/ break;				// ROM in RAM kopieren
  case '<': break;					// Unidirektionaler Druck der aktuellen Zeile
  case '=': msb|=1; msb&=~128; break;			// Bit 7 der Daten löschen
  case '>': msb|=129; break;				// Bit 7 der Daten setzen
  case '?': /*GRAFIK*/ break;
  case '@': msb=0; break;				// Drucker initialisieren
  case 'A': ly=ReadChar()*36; break;			// Zeilenabstand n/60" (9-Nadler: n/72", ergibt Faktor 30)
  case 'B': SetTabs(vfu[vtc],16,ly); break;		// Vertikale Tabs löschen/setzen
  case 'C': c=ReadChar(); paper.cy=c?c*ly:ReadChar()*2160; break;	// Seitenlänge setzen (in Zeilen oder Zoll)
  case 'D': SetTabs(ht,32,GetCx()); break;		// Horizontale Tabs setzen/löschen
  case 'E': SetFontBit(3,1); break;			// Fett EIN
  case 'F': SetFontBit(3,0); break;			// Fett AUS
  case 'G': SetFontBit(4,1); break;			// Durchgestrichen EIN
  case 'H': SetFontBit(4,0); break;			// Durchgestrichen AUS
  case 'I': if (ReadChar()&1) msb=msb&~24|32; else msb=msb&~32|24; break;	// Steuerkodes drucken EIN/AUS
  case 'J': SetPosY(pos.y+ReadChar()*10); break;	// Vertikal relativ positionieren
  case 'K': /*GRAFIK*/ break;
  case 'L': /*GRAFIK*/ break;
  case 'M': SetFontBit(0,1); break;			// Elite-Schrift
  case 'N': SetFormSkip(ReadChar()*ly); break;		// Perforationssprung in Zeilen (hier: mittelt oberen und unteren Rand)
  case 'O': SetFormSkip(0); break;			// Perforationssprung AUS
  case 'P': SetFontBit(0,0); break;			// Pica-Schrift (10 cpi)
  case 'Q': i=ReadChar()*GetCx(); if (margin.left<i && i<paper.cx) margin.right=i; break;
  case 'R': Print(); charset=ReadChar(); break;		// Sprachspezifische Zeichenersetzung
  case 'S': Print(); ss=(ReadChar()&1)+1; break;	// Hoch- oder Tiefstellung EIN
  case 'T': Print(); ss=0; break;			// Hoch- oder Tiefstellung AUS
  case 'U': ReadChar(); break;				// Unidirektional ein/aus
  case 'W': SetFontBit(5,255); break;			// doppelte Breite ein/aus
  case 'X': /*TODO*/ break;
  case 'Y': /*GRAFIK*/ break;
  case 'Z': /*GRAFIK*/ break;
  case '\\': ReadChar(b,2); SetPosX(pos.x+scale(SH(b),lq&3?12:18)); break; // Relative horizontale Druckposition, NLQ: 1/180", sonst 1/120"
  case '^': /*GRAFIK*/ break;
  case 'a': ReadChar(); break;				// Ausrichtung
  case 'b': c=ReadChar(); if (c<8) SetTabs(vfu[c],16,ly); break;	// Tabulatoren eines VTAB-Kanals setzen
  case 'c': break;					// horizontal motion index ??
  case 'e': HandleESCe(); break;			// Tabs mit konstantem Abstand setzen
  case 'f': HandleESCf(); break;			// Horizontaler/vertikaler Sprung
  case 'g': lq=lq&~4|AB(ReadChar())<<2&4; break;	// 15 cpi
  case 'i': ReadChar(); break;				// Sofortausdruck (Schreibmaschinen-Ersatz)
  case 'j': SetPosY(pos.y-ReadChar()*10); break;	// Walze zurückdrehen
  case 'k': /*TODO*/ ReadChar(); break;			// Typeface ??
  case 'l': Print(); i=ReadChar()*GetCx(); if ((unsigned)i<(unsigned)margin.right) margin.left=i; break;
  case 'm': c=AB(ReadChar()); if (!c) msb&=~32; else if (c==4) msb|=32; else return false; break; // noch eine MSB-Steuerung
  case 'p': Print(); lq=lq&~2|AB(ReadChar())<<1&2; break;	// Proportionalschrift
  case 'q': /*TODO*/ ReadChar(); break;			// Zeichenstil
  case 'r': color=ReadChar(); break;			// Farbe (Farbband-Auswahl)
  case 's': ReadChar(); break;				// Halbe Geschwindigkeit ein/aus (ignorieren:-)
  case 't': c=AB(ReadChar()); if (c>=4) return false; Print(); cpi=c; break;	// italic/Epson ??
  case 'w': SetFontBit(1,255); break;			// Ausdruck in doppelter Höhe
  case 'x': Print(); lq=lq&~1|AB(ReadChar())&1; break;	// Answahl (N)LQ oder Draft
  default: return false;
 }
 return true;
}

void ESCP::Save(DEFAULTS*def) const{
 memcpy(def,&font,8);
 def->skip=short(paper.cy-margin.bottom+margin.top);
 def->ly=short(ly);
 def->paper.x=short(paper.cx);
 def->paper.y=short(paper.cy);
}

void ESCP::Default(DEFAULTS*def) {
 static const DEFAULTS d={
  0,0,0,0,0,0,0,4,		// PICA, US-englisch, Autofeed
  2160,				// 1/2" oberer und unterer Rand
  360,				// Zeilenabstand 1/6 Zoll
  {8*2160,12*2160}		// Papiergröße 8 x 12 Zoll
 };
 *def=d;
 TCHAR s[8];
 GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_ICOUNTRY,s,elemof(s));	// ermittelt Telefonvorwahl
 static const char someCountries[]={33,49,44,45,46,39,34,81,47,0};	// fr,de,uk,dk,se,it,es,jp,no
 const char *p=strchr(someCountries,StrToInt(s));
 if (p) def->charset=(BYTE)(p-someCountries+1);			// diese Vorgabe ist systemspezifisch
}

void ESCP::Init(DEFAULTS*def) {
 ZeroMemory(this,sizeof(*this));
 paper.cx=margin.right=def->paper.x;
 paper.cy=def->paper.y;
 SetFormSkip(def->skip);
 ly=def->ly;
 cp[1]=437;
 cp[2]=1;
 cp[3]=437;
 memcpy(&font,def,8);
 SetEqualTabs(ht,32,1728);	// Standard-Tabstopps aller 8/10 Zoll
}

bool ESCP::Filter() {
 if (!setjmp(env)) for (;;) {
  BYTE c=ReadChar();
  switch (c) {
   case 7: Beep(440,100); break;		// BEL = Glocke
   case 8: SetPosX(pos.x-GetCx()); break;	// BS  = Rückschritt
   case 9: SetPosX(FindTab(ht,32,pos.x)); break; // HT  = Horizontaler Tabulator
   case 10: LineFeed(); if (crlf&1) pos.x=0; break;	// LF  = Zeilenvorschub (manchmal mit Wagenrücklauf!)
   case 11: SetPosY(FindTab(vfu[vtc],16,pos.y)); break; // VT  = Vertikaler Tabulator
   case 12: FormFeed(); break;			// FF  = Formularvorschub (neue Seite)
   case 13: if (crlf&2) LineFeed(); else Print(); pos.x=0; break;	// CR  = Wagenrücklauf
   case 14: Print(); lq|=32; break;		// SO  = Doppelte Breite für eine Zeile
   case 15: SetFontBit(2,1); break;		// SI  = Komprimiert (17 cpi)
   case 17: break;				// DC1 = Drucker auswählen
   case 18: SetFontBit(2,0); break;		// DC2 = Komprimiert (17 cpi) EIN
   case 19: break;				// DC3 = Drucker nicht auswählen
   case 20: Print(); lq&=~32; break;		// DC4 = Doppelte Breite in aktueller Zeile AUS
   case 24: ci=0; pos.x=0; break;		// CAN = Zeichenpuffer löschen
   case 27: msb|=4; HandleEsc(); msb&=~4; break;// ESC = Präfix für komplexe Steuerkodes
   case 127: if (ci) ci--; break;		// DEL = 1 Zeichen in Zeichenpuffer löschen
   default: if (!(msb&8) && c<' ') break;	// 0..31 verwerfen
   if (msb&16 && 128<=c && c<160) break;	// 128..159 verwerfen
   Printable(c);				// druckbares Zeichen ausgeben
  }
 }else{
  Print();
  if (pagectl&1) {
   EndPage(dc);
   pagectl&=~1;
  }
 }
 return true;
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded