Source file: /~heha/hs/sch2wmf/sch2emf.zip/Eagle4/sch2emf.ulp

#usage	"Ausgabe eines <b>Sch</b>altplanes (oder Boards) als "
	"<b>E</b>rweiterte Windows-<b>M</b>eta-Datei (<b>F</b>ile) "
	"für bequemen Dokument-Import sowie String-Suchmöglichkeit in PDFs<p>\n"
	"Es werden nur die gerade sichtbaren Layer ausgegeben.<br>"
        "Die Pins (dünne Kreise, Richtungs- und Pinswap-Angaben) auf Layer 93 "
        "(„Pins“) werden nicht ausgegeben.<p>\n"
	"Kommandozeilenargumente: [Argumente] [Dateiname]<br>\n"
	"Argumente siehe SCH2EMF.TXT.\n"
	"<hr>"
	"<author>Autor: <A href=mailto:henrik.haftmann@e-technik.tu-chemnitz.de>"
	"henrik.haftmann@e-technik.tu-chemnitz.de</A>, <br>"
	"URL: <A href='http://www.tu-chemnitz.de/~heha/hs_freeware/sch2wmf/'>"
	"http://www.tu-chemnitz.de/~heha/hs_freeware/sch2wmf/</A></author>"

// Hinweis: EMF-Dateien bieten gegenüber WMF folgende Vorteile:
// Unter Win9x/Me:
// * Linien mit Pinselfüllung [bei SCH2WMF per Polygonausgabe simulierbar]
// * Strichellinien auch bei Darstellstrichbreiten > 1 Pixel [... Einzelausgabe]
// * Bögen mit flachen Enden [... Polygon- oder Tortenstück-Ausgabe]
// * Unicode-Zeichenersetzung [... Linienausgabe Vektorfont]
// Zusätzlich unter WinNT/2k/XP/Vista:
// * Gespiegelten Text [... Linienausgabe Vektorfont]
// * 32-bit-Koordinatensystem [... Teiler erforderlich]
// * Pfade mit Bögen als Begrenzung [... Polygonausgabe] - hier nicht benutzt!
// Angeblich sind EMF-Dateien in StarOffice/OpenOffice auch unter Linux
// verarbeitbar; vermutlich werden diese Programme an diesen EMFs elendig
// verrecken (SCH2EMF zieht alle Register, um kleine EMFs zu produzieren!).
// Nachteile:
// * Wegen 32-bit-Ausrichtung etwa doppelt so groß wie gleiche .WMF-Dateien
// * Unklare Skalierungsverhältnisse, bspw. bei Total Commander F3-Viewer,
//   ACDSee oder IrfanView
// * Überraschungen bei der PostScript/PDF-Ausgabe breiter Linien und Bögen

// EMF-Dateien unterstützen kein Alpha-Blending! Das kann erst EMF+.
// Ist erst seit kurzem dokumentiert, und mir ist kein geeignetes Importprogramm bekannt.
// Deshalb erfolgt hier die Pseudotransparenz, wie gehabt, mittels ROP-Kodes.

// Diese Version von SCH2EMF ist für Eagle 4.x und gibt keine Koordinatentransformation aus.
// Sie läuft auch unter Eagle 5.x.
// Für Eagle 5.x nehme man besser die gleichnamige ULP-Datei für Eagle 5.x.
// Diese unterstützt den Layer „Frames“ und ist zweisprachig (deutsch + englisch)

// Zur problemspezifischen Anpassung des Dialogs
int NumSheet;		// Anzahl Seiten des Schaltplans
int NumHatched;		// Anzahl gestrichelte Layer (bei Schaltplan üblich: Null)
int NumExact;		// Anzahl gestrichelte Layer _ohne_ Windows-Entsprechung
int NumFont[];		// Anzahl Vorkommen von Vektor/Prop/Fixed-Fonts
int NumCopperVector;	// Anzahl Vorkommen Vektorfonts im Kupfer (nur Board)
string SName;		// Schaltplan- oder Board-Name (ohne Pfad)

// **** Dialog ****
	// Voreinstellungen
int Debug=0;		// Kreis am Text-Ursprung
int PaletteType=palette(-1)+1;	// Farbmodell, 2. Parameter für palette()
			// PaletteType=0 bedeutet Schwarzweiß-Ausgabe (1 Layer)
int HatchType=1;	// Füllstil für gestrichelt definierte Layer (!Polygone)
int AllPages=0;		// Automatisch alle Seiten generieren (mehrseitige Schaltpläne)
int Filled=1;		// Gestrichelt definierte Layer gefüllt (!Polygone)
int Flipped=0;		// gespiegelt (an X-Achse, nur für Board)
int Trans=0;		// Transparenz-Simulation (mittels ROP-Kodes, wie Eagle<5)
int OutBkgnd=0;		// Hintergrund ausgeben
int DoNegate=0;		// Negationen (bei Strings) ausgeben
int DupStrReplace=1;	// Übereinander gesetzten Text nur 1x ausgeben mit Präfix
int Dialog=1;		// =2: Erweiterter Dialog (nur für Füchse)
string LastLayers;	// Zuletzt auszugebende Layer (stets opak, nur Board)
string FName;		// Dateiname (ggf. Kommandozeilenargument)

int FontSel[]={1,1,2};	// Font-Nr. für Vektor-Proportional-Fixed
int FontBold[]={1,0,0};
int FontItalic[]={0,0,0};
string FontSrc[]={"Vektor","Proportional","Fixed"};	// Eagle-Namen
string FontName[]={"Vektor","Arial","Courier New","Times New Roman"};
string FontLabel[];
// Normalerweise ist für EMF-Dateien kein Teiler notwendig.
// Allerdings kommt Windows 9x/Me nicht mit 32-bit-Koordinaten zurecht.

// zum Entdecken mehrfach übereinander platzierter Strings
// Werden mit "2x " usw. ausgegeben.
// Optimal für gleiche Werte im Schaltplan
string DupStrHash[];
int DupStrCount[];

int AnyDupStr() {
 for (int i=0; DupStrCount[i]; i++) if (DupStrCount[i]>1) return 1;
 return 0;
}

// berechnet aus String LastLayers das Char-Array LastLayers(!)
// meckert und liefert 0 bei Fehler, sonst !=0
int CalcLastLayers(void) {
 string A;
 int i,j,layer,layere;
 for (i=j=0; LastLayers[i]; i++) {
// Startwert einlesen
  layer=strtol(strsub(LastLayers,i));
  if (layer<=0 || layer>255) break;	// Fehler
  while ('0'<=LastLayers[i] && LastLayers[i]<='9') i++;
// ggf. Endwert einlesen
  if (LastLayers[i]=='-') {
   i++;
   layere=strtol(strsub(LastLayers,i));
   if (layere<=0 || layere>255) break;	// Fehler
   while ('0'<=LastLayers[i] && LastLayers[i]<='9') i++;
  }else layere=layer;
// auf- oder absteigend LastLayers füllen
  if (layer<=layere) for (; layer<=layere; layer++) A[j++]=layer;
  else for (; layer>=layere; layer--) A[j++]=layer;
// Folgezeichen auswerten
  if (!LastLayers[i]) break;		// Ende
  if (LastLayers[i]!=','
  || !LastLayers[i+1]) break;		// Fehler, wenn kein Komma oder ",\0"
 }
 A[j]=0;				// terminieren
 if (LastLayers[i]) {	// Nicht am Ende angekommen?
  string s;
  sprintf(s,"!Falsche Syntax für Layer-Angabe!<pre style=color:red><b>%s\n%*s^---hier!",
    LastLayers,i,"");
  dlgMessageBox(s);
  return 0;
 }
 LastLayers=A;
 return 1;
}

/***********************
 *** Dialog-Routinen ***
 ***********************/
// globale Variablen: Farben und Füllstile per Layer
int col[],fil[];

// liefert TRUE, wenn Layers aktiv sind, die von unten zu sehen sind
// (d.h. solche, auf denen platzierter Text tatsächlich gespiegelt erscheint)
int AnyBottomLayers() {
 return col[LAYER_BOTTOM]
      + col[LAYER_BPLACE]
      + col[LAYER_BORIGINS]
      + col[LAYER_BNAMES]
      + col[LAYER_BVALUES]
      + col[LAYER_BSTOP]
      + col[LAYER_BCREAM]
      + col[LAYER_BFINISH]
      + col[LAYER_BGLUE]
      + col[LAYER_BTEST]
      + col[LAYER_BKEEPOUT]
      + col[LAYER_BRESTRICT]
      + col[LAYER_BDOCU];
}

// Font-Namen nur für SetFontLabel (Leerzeichen führen zu Fehlern)
string FontSty[]={"Verdana","Arial","Courier","Times"};
void SetFontLabel(void) {
 for (int idx=0; idx<3; idx++) sprintf(FontLabel[idx],
   "<span style=font-family:%s;font-weight:%d;font-style:%s>%s</span>:",
   FontSty[FontSel[idx]],
   FontBold[idx]?700:400,
   FontItalic[idx]?"italic":"normal",
   FontSrc[idx]);
}

void FontSubstLine(int idx) {
 dlgCell(idx,0) {dlgLabel(FontLabel[idx],1); dlgSpacing(10);}
 dlgCell(idx,1) {
  dlgComboBox(FontName,FontSel[idx]) {
   if (!FontSel[0]) dlgMessageBox(
     "Die Vektor-Schriftart ergibt <b style=color:darkgreen>layout-treue</b> Ausgabedateien.<p>"+
     "Allerdings ist die WMF-Datei <b style=color:darkred>wesentlich größer</b> "+
     "und <b style=color:darkred>nicht durchsuchbar</b> nach Text.<p>"+
     "Die Einstellungen <b>Fett</b> und <b>Kursiv</b> sind wirkungslos.");
   SetFontLabel();
  }
  dlgSpacing(10);
 }
 dlgCell(idx,2) dlgCheckBox("Fett",FontBold[idx]) SetFontLabel();
 dlgCell(idx,3) dlgCheckBox("Kursiv",FontItalic[idx]) SetFontLabel();
 dlgCell(idx,4) dlgStretch(1);
}

void OnSetPaletteType(void) {
 switch (PaletteType) {
  case 1: {
   OutBkgnd=1;
   if (board) Trans=1;		// voreinstellen wie Eagle
   if (NumExact) HatchType=2;
  }break;
  case 3: {
   OutBkgnd=!Trans;	// voreinstellen wie Eagle (wenn Trans=0)
  }break;
 }
}

int DialogBox(void) {
 string DeckLabel=Trans?"(transparent)":"(deckend)";
 status("Dialog");
 return dlgDialog("EMF-Export-Optionen") {
  dlgLabel("Dateiname: <b>"+SName+"</b>");
  dlgGroup("Ausgabe-&Farben   (siehe Eagle-Palette)") {
   dlgRadioButton("Schwarz auf Weiß (Schwarzweiß)",PaletteType) OnSetPaletteType();
   dlgRadioButton("Bunt auf Schwarz",PaletteType) OnSetPaletteType();
   dlgRadioButton("Bunt auf Weiß",PaletteType) OnSetPaletteType();
   dlgRadioButton("Bunt auf farbigem Hintergrund",PaletteType) OnSetPaletteType();
  }
  dlgGroup("Schrift-&Ersetzung   (verwendete Schriften gelistet)") {
   dlgGridLayout{
    SetFontLabel();
    for (int i=0; i<3; i++) if (NumFont[i]) FontSubstLine(i);
   }
   if (NumCopperVector) {
    dlgSpacing(4);
    dlgLabel("<b>Hinweis:</b> Vektorschrift auf Kupfer wird niemals ersetzt.");
   }
   if (AnyDupStr()) {
    dlgSpacing(4);
    dlgCheckBox("n×-Präfix für übereinander gesetzten Text",DupStrReplace);
   }
  }
  if (NumHatched) {
   string s;
   sprintf(s,"Füll&muster (%d schraffierte%s Layer vorhanden)",
    NumHatched,NumHatched==1?"r":"");
   dlgGroup(s) {
    dlgRadioButton("Ausgefüllt",HatchType);
    dlgRadioButton("Windows-Füllmuster (hat nur 6 Muster: = || // \\\\\\\\ ++ xx)",HatchType);
    dlgHBoxLayout{
     dlgRadioButton("Exakte Füllmuster wie in Eagle",HatchType);
     dlgLabel(DeckLabel,1);	// "transparent" oder "deckend"
     dlgStretch(1);
    }
   }
  }
  if (board) {
   if (AnyBottomLayers()) {
    dlgCheckBox("&Gespiegelt (an Y-Achse, zur Seite)",Flipped);
   }
   dlgCheckBox("Simulierte Tr&ansparenz (mit Rasteroperationskodes)",Trans) {
    DeckLabel=Trans?"(transparent)":"(deckend)";
    if (Trans) switch (PaletteType) {
     case 3: dlgMessageBox(
      "Bei »Bunt auf farbigem Hintergrund« gibt es in Eagle &lt; 5 keine Transparenz.<p>"
      "Hier werden Komplementärfarben ausgegeben.<p>"+
      "Hässliches Aussehen bei PostScript- und PDF-Weiterverarbeitung (Windows-Bug)");
     break;
    }
   }
   if (Dialog>1 || LastLayers) { // Nur bei "Dialog=2" oder LastLayer-Vorgabe
    dlgGroup("&Umordnung der Layer-Ausgabe") {
     dlgHBoxLayout{
      dlgLabel("Zuletzt ausgeben (stets deckend):");
      dlgSpacing(4);
      dlgStringEdit(LastLayers);
     }
    }
   }
  }
  if (DoNegate)
    dlgCheckBox("&Negationsstriche statt »!« (wie Eagle 5)",DoNegate);
  dlgCheckBox("&Hintergrund ausmalen (Vektorgrafik nicht transparent)",OutBkgnd);
  if (NumSheet>1) {
   dlgSpacing(4);
   dlgCheckBox("&Alle Seiten (mehrere Dateien)",AllPages);
  }
  dlgSpacing(4);
  dlgHBoxLayout {
   dlgPushButton(FName?"+O&K":"+&Weiter, zum Speichern ...") {
    if (Dialog<2 || CalcLastLayers()) dlgAccept(1);
   }
   dlgPushButton("Abbrechen") dlgReject();
  }
 };
}

int DupStrFind(UL_TEXT O) {
 string hash;
 sprintf(hash,"%d %d %f %d %d %d %d %s",	// "spin" und "ratio" sind weggelassen
  O.x,O.y,O.angle,O.font,O.layer,O.mirror,O.size,O.value);
 int i=0;
 for (; DupStrCount[i]; i++) {
  if (DupStrHash[i]==hash) return i;
 }
 DupStrHash[i]=hash;		// Neuen String einsetzen, Array verlängern
 return i;
}

void CountFont(UL_TEXT O) {
 if (!col[O.layer]) return;	// unsichtbare Schrift nicht mitzählen
 NumFont[O.font]++;
 DupStrCount[DupStrFind(O)]++;	// Dubletten finden
 if (!DoNegate && strchr(O.value,'!')!=-1) DoNegate++;
}

/* Suche nach verwendeten Text-Fonts, zur Vorbereitung des Dialogs */
void SchCountFonts(UL_SCHEMATIC SC) {
 SC.sheets(S){
  string e;
  sprintf(e,"Seite %d",S.number);
  status(e);
  NumSheet++;
  S.busses(B) {
   B.segments(SE) {
    SE.texts(T) CountFont(T);
   }
  }
  S.nets(N) {
   N.segments(SE) {
    SE.texts(T) CountFont(T);
   }
  }
  S.parts(P) {
   P.instances(I) {
    I.gate.symbol.pins(P) {
     P.texts(T) CountFont(T);
    }
    I.gate.symbol.texts(T) CountFont(T);
    I.texts(T) CountFont(T);
   }
  }
  S.texts(T) CountFont(T);
 }
}

// das gleiche für's Board
void BrdCountFonts(UL_BOARD B) {
 B.elements(E) {
  E.texts(T) CountFont(T);
  E.package.texts(T) CountFont(T);
 }
 B.texts(T) {
  if (T.layer>LAYER_BOTTOM || T.font!=FONT_VECTOR) CountFont(T);
  else if (col[T.layer]) NumCopperVector++;
 }
}	// Vektor-Schrift im Kupfer wird NICHT gezählt (bleibt stets Vektor)

int IsRatsnestPoly(UL_POLYGON P) {
 P.fillings(F) return 1;
 return 0;
}

// Prüft auf Vorhandensein sichtbarer, nicht geRATSNESTer Polygone (nur Kupfer)
int BrdAreRipupPolys(UL_BOARD B) {
 B.signals(S) S.polygons(P)
   if (P.layer<=LAYER_BOTTOM 
   && col[P.layer]
   && !IsRatsnestPoly(P)) return 1;
 return 0;
}

// **** globale Variablen ****
/* für den EMF-Kopf */
int nDwords;	// Länge EMF-Datei
int nRecords;	// (Künftige) Anzahl Metadatei-Records
int nHandles;	// (Künftige) Anzahl GDI-Handles
/* EMF-Daten (Ansammlung im RAM, DWORD-Array ("unsigned" gibt's nicht) */
int FileData[];

int CurLayer;

int LayerColor;		// Windows-RGB
int LayerFill;		// Windows-Füllstil (Low-Nibble, High-Nibble)
int LayerRop;		// aktueller Windows-Rasteroperationskode
int LastX,LastY;	// (kleinere Datei, schnelleres Rendern)
int TextColor;
int TextAlign;
int BackgroundColor;
int DrawHoles=0;	// Löcher statt Pads/Vias zeichnen

/* Ausgabe 4 Byte in Intel-Notation (Little Endian) */
void PrintDword(int d) {
 printf("%c%c%c%c",d&0xFF,(d>>8)&0xFF,(d>>16)&0xFF,(d>>24)&0xFF);
}
/* Umwandeln in binäre Repräsentation des FLOAT(=float,Single)-Datentyps */
//int CastFloat(real f) {
// int ret=0;
// if (f<0) {
//  ret=0x80000000;
//  f=-f;
// }
// if (f) {
//  int expo=log(f)/log(2)+127;
//  ret|=int(f*pow(2,150-expo))&0x7FFFFF|expo<<23;
// }
// return ret;
//}
/* Ausgabe 4 Byte in Datenpuffer */
void OutDword(int d) {
 FileData[nDwords++]=d;
}
//void OutFloat(real f) {
// OutDword(CastFloat(f));
//}
int VectorSubst[]={
'?',	0x2593,	'?',	'?',	'?',	'?',	'?',	'?',		/*80*/
'?',	'?',	'?',	'?',	'?',	0x250C,	'?',	0x2265,
0x2580,	'?',	'?',	'?',	'?',	'?',	'?',	'?',		/*90*/
'?',	'?',	'?',	'?',	'?',	0x2554,	'?',	'?',
'_',	'¡',	0x255C,	'£',	0x2567,	0x255B,	0x2590,	0x2321,		/*A0*/
0x2584,	0x2555,	'ª',	'«',	'¬',	0x2261,	0x2310,	0x2208,
'°',	'±',	'²',	0x207F,	0x2229,	'µ',	0x2320,	'·',		/*B0*/
0x2248,	0x221A,	'º',	'»',	'¼',	'½',	0x2264,	'¿',
0x2556,	0x2561,	0x2562,	0x255F,	'Ä',	'Å',	'Æ',	'Ç',		/*C0*/
0x2558,	'É',	0x2565,	0x2559,	0x2590,	0x2553,	0x256B,	0x256A,
0x2564,	'Ñ',	0x03C0,	0x03B1,	0x0393,	0x03C3,	'Ö',	0x20A7,		/*D0*/
'Ø',	0x03B4,	0x03B8,	0x03A9,	'Ü',	'ø',	0x03A6,	'ß',
'à',	'á',	'â',	0x255E,	'ä',	'å',	'æ',	'ç',		/*E0*/
'è',	'é',	'ê',	' ',	'ì',	'í',	'î',	'ï',
0x2568,	'ñ',	'ò',	'ó',	'ô',	0x03A3,	'ö',	'÷',		/*F0*/
'ø',	'ù',	'ú',	'û',	'ü',	0x221E,	0x03C4,	'ÿ'};
int ToUnicode(char c, int font) {
 if (font!=FONT_VECTOR) return c;
 if (c<128) return c;
 return VectorSubst[c-128];
}
/* Ausgabe String in Unicode, ggf. mit Konvertierung bei FONT_VECTOR */
void OutString(string s, int font) {
 int i, len=strlen(s);
 for (i=0; i<len; i+=2) {
  OutDword(ToUnicode(s[i],font)|ToUnicode(s[i+1],font)<<16);
 }
}
int nRecordStart;
void BeginRecord(int recordtype) {
 nRecordStart=nDwords;	// Anfang merken
 nRecords++;
 OutDword(recordtype);
 nDwords++;		// Platz für Länge lassen
}
void EndRecord(void) {
 FileData[nRecordStart+1]=(nDwords-nRecordStart)*4;
}
void OutMassX(int eaglepixel) {
 OutDword(Flipped?-eaglepixel:eaglepixel);
}
void OutMassY(int eaglepixel) {
 OutDword(-eaglepixel);
}

void OutRop(int NewRop) {
 if (NewRop==LayerRop) return;
 BeginRecord(20/*EMR_SETROP2*/);
 OutDword(NewRop);
 EndRecord();
 LayerRop=NewRop;
}

// die Füllstile könnten auch per CreatePatternBrush exakt angepasst werden
// Da jedoch Schaltpläne äußerst selten Gebrauch davon machen, werden hier
// nur die Standard-Windows-Füllstile verwendet.
int FillStyles[]={	// bestmögliche Annäherung an Windows-Füllstile
    1/*BS_NULL*/,	// 0 Eagle-Füllung:
    0/*BS_SOLID*/,	// 1
 0x02/*HS_HORIZONTAL*/,	// 2 breite ===	50%
 0x32/*HS_BDIAGONAL*/,	// 3 dünne ///	10%
 0x32/*HS_BDIAGONAL*/,	// 4 breite ///	50%
 0x22/*HS_FDIAGONAL*/,	// 5 breite \\\	50%
 0x22/*HS_FDIAGONAL*/,	// 6 dünne \\\\	10%
 0x42/*HS_CROSS*/,	// 7 dünne +++	30%
 0x52/*HS_DIAGCROSS*/,	// 8 dünne xxx	30%
 0x12/*HS_VERTICAL*/,	// 9 Punkte	50%
 0x12/*HS_VERTICAL*/,	// A Punkte	10%
 0x12/*HS_VERTICAL*/,	// B Punkte	30%
 0x02/*HS_HORIZONTAL*/,	// C dünne ===	50%
 0x02/*HS_HORIZONTAL*/,	// D dünne ===	50%
 0x02/*HS_HORIZONTAL*/,	// E dünne ===	50%
 0x02/*HS_HORIZONTAL*/};// F dünne ===	50%
// Für genauere Füllstil-Simulation muss man entsprechende Bitmaps laden
// Bei PostScript-Ausgabe werden unschön große Schraffuren ausgegeben

int EaglePattern[]={	// ab Index 2
 0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,	// breite waagerechte Linien
 0x04,0x08,0x10,0x20,0x40,0x80,0x01,0x02,	// HS_BDIAGONAL
 0xE0,0xC1,0x83,0x07,0x0E,0x1C,0x38,0x70,	// // dick
 0x1C,0x0E,0x07,0x83,0xC1,0xE0,0x70,0x38,	// \\ dick
 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01,	// HS_FDIAGONAL
 0x11,0x11,0x11,0xFF,0x11,0x11,0x11,0xFF,	// ++
 0x48,0x84,0x03,0x03,0x84,0x48,0x30,0x30,	// xx
 0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,	// Schachbrett
 0x00,0x10,0x00,0x01,0x00,0x10,0x00,0x01,	// Punkte dünn
 0x00,0x44,0x00,0x11,0x00,0x44,0x00,0x11,	// Punkte dichter
 0x00,0xAA,0x00,0xAA,0x00,0xAA,0x00,0xAA,	// Punkte angeordnet
 0x00,0x55,0x00,0x55,0x00,0x55,0x00,0x55,
 0x55,0x00,0x55,0x00,0x55,0x00,0x55,0x00,
 0xAA,0x00,0xAA,0x00,0xAA,0x00,0xAA,0x00};
// Bei PostScript-Ausgabe erscheint eine grobe Klötzchenfüllung.
 
// Umwandeln RGB in Windows-Farbe
int rgb2bgr(int rgb) {
 return ((rgb&0xFF)<<16)|(rgb&0xFF00)|((rgb>>16)&0xFF);
}

// Füllstil-Code für Layer <n> berechnen
int CalcFill(int n) {
 int ret;
 switch (HatchType) {
  case 0: ret=0; break;
  case 1: {
   ret=FillStyles[fil[n]];
   if (Flipped) switch (ret) {
    case 0x22/*HS_FDIAGONAL*/:
    case 0x32/*HS_BDIAGONAL*/: ret^=0x32^0x22;	// Richtungen tauschen
   }
  }break;
  case 2: switch (fil[n]) {
   case 0: ret=1/*BS_NULL*/; break;
   case 1: ret=0/*BS_SOLID*/; break;
   default: ret=((fil[n]-2)<<4)|3;
  }break;
 }
 return ret;
}

// Funktion liefert 0 (FALSE) wenn Eagle-Layer <n> unsichtbar
int SetLayer(int n) {
 int c=col[n];	// c=0 wenn Layer nicht sichtbar
 if (c) {
  int rop=13/*R2_COPYPEN = P*/;
  if (Trans) switch (PaletteType) {
   case 0:
   case 2: rop=9/*R2_MASKPEN = DPa*/; break;
   case 1: rop=15/*R2_MERGEPEN = DPo*/; break;
   case 3: rop=3/*R2_MASKNOTPEN = DPna*/; break;	// Komplementärfarben
  }
  OutRop(rop);	// Für Schrift ist die ROP-Einstellung irrelevant!
  int rgb=PaletteType?palette(c,PaletteType-1):0;
  if (rop==3 && !rgb) rgb=0xFFFFFF;
  LayerColor=rgb2bgr(rgb);
  LayerFill=CalcFill(n);
 }
 return c;
}

// globale Variablen zur Verwaltung der sogenannten GDI-Objekte
// Die Arrays wachsen mit der Verwendung verschiedener Pinsel, Stifte und Schriften.
int OD_type[];
int OD_param1[];
int OD_param2[];
int OD_color[];

int FindObj(int type, int p1, int p2, int color) {
 int j;
 for (j=1; j<nHandles; j++) {
  if (type==OD_type[j]
  &&    p1==OD_param1[j]
  &&    p2==OD_param2[j]
  && color==OD_color[j]) return j;	// vorhandenes Objekt benutzen
 }
 OD_type[j]  =type;	// neues Objekt vermerken (j == nHandles)
 OD_param1[j]=p1;
 OD_param2[j]=p2;
 OD_color[j] =color;
 return j;
}

void OutSelectObject(int idx) {
 BeginRecord(37/*EMR_SELECTOBJECT*/);
 OutDword(idx);
 EndRecord();
}

void OutDeleteObject(int idx) {
 BeginRecord(40/*EMR_DELETEOBJECT*/);
 OutDword(idx);
 EndRecord();
}

int ObjInUse[];	// Momentanes verwendetes Objekt aus den drei u.g. Typen
enum {ObjPen,ObjBrush,ObjFont};

//Debugging:
string ObjDescription(int idx) {
 string ret="ungültiges idx";
 if (idx<=0 || idx>=nHandles) return ret;
 switch (OD_type[idx]) {
  case ObjPen: {
   string stil="unbekannt";
   switch (OD_param1[idx]) {
    case 0: stil="durchgezogen"; break;
    case 1: stil="Striche"; break;
    case 2: stil="Punkte"; break;
    case 3: stil="Strich-Punkt"; break;
    case 5: stil="leer"; break;
   }
   sprintf(ret,"<b>Stift</b>, Stil=%s(%d), Breite=%d, Farbe=%06X",
     stil,OD_param1[idx],OD_param2[idx],OD_color[idx]);
  }break;
  case ObjBrush: {
   string stil="unbekannt";
   string hatch="unbekannt";
   switch (OD_param1[idx]) {
    case 0: stil="voll"; break;
    case 1: stil="leer"; break;
    case 2: stil="gestreift"; break;
   }
   switch (OD_param2[idx]) {
    case 0: hatch="keine"; break;
    case 4: hatch="+++"; break;
   }
   sprintf(ret,"<b>Pinsel</b>, Stil=%s(%d), Streifen=%s(%d), Farbe=%06X",
     stil,OD_param1[idx],hatch,OD_param2[idx],OD_color[idx]);
  }break;
  case ObjFont: {
   sprintf(ret,"<b>Schrift</b> Art=%s(%d), Drehung=%d°, Größe=%d",
     FontName[OD_param1[idx]&3],OD_param1[idx]&3,(OD_param1[idx]>>2&3)*90,OD_param2[idx]);
  }break;
 }
 return ret;
}

void ShowHandleTable(void) {
 string msg="";
 for (int i=1; i<nHandles; i++) {
  string t;
  sprintf(t,"%d: %s<br>\n",i,ObjDescription(i));
  msg+=t;
 }
 dlgMessageBox(msg);
}

void ClearObjects(void) {
// Handletabelle löschen, sonstige GDI-Attribute invalidieren
 nHandles=1;
 TextColor=-1;
 TextAlign=-1;
 LastX=INT_MAX;
 LastY=INT_MAX;
 LayerRop=13/*R2_COPYPEN*/;
}

void OutWindow(void/*int w, int h*/) {
 if (0) {
  int w,h;
  BeginRecord(10/*EMR_SETWINDOWORG*/);
  OutDword(0);
  OutDword(0);
  EndRecord();
  BeginRecord(9/*EMR_SETWINDOWEXT*/);
  OutMassX(w);
  OutMassY(h);
  EndRecord();
 }
 BeginRecord(18/*EMR_SETBKMODE*/);
 OutDword(1/*TRANSPARENT*/);
 EndRecord();
 if (HatchType==2) {
  BeginRecord(25/*EMR_SETBKCOLOR*/);
  int col=BackgroundColor;
  if (Trans && PaletteType==3) col^=0xFFFFFF;
  OutDword(col);	// für Pattern-Schraffur (ist immer deckend - leider!)
  EndRecord();
 }
}

// Liefert Index in Handle-Tabelle (für schnelle EMF-Darstellung),
// liefert 0, wenn kein neues CreateXxx erforderlich ist.
int GetNewHandleIndex(int objtype, int h1, int h2, int color) {
 int j=FindObj(objtype,h1,h2,color);
 if (j==nHandles) {	// nicht gefunden: neues Objekt
  ObjInUse[objtype]=j;	// Aufrufer muss Create() und SelectObject() rufen
  nHandles++;
  return j;
 }	// gefunden: ggf. als aktuelles Objekt aktivieren
 if (ObjInUse[objtype]!=j) {
  OutSelectObject(j);
  ObjInUse[objtype]=j;
 }
 return 0;
}

// BoundingBox-Dummy (4 Nullen) ausgeben
void OutBBox(void) {
 for (int i=0; i<4; i++) OutDword(0);
}

void SetTextColor(int color) {
 if (TextColor!=color) {
  BeginRecord(24/*EMR_SETTEXTCOLOR*/);
  OutDword(color);
  EndRecord();
  TextColor=color;
 }
}

// wird für SetPen() und SetBrush() gebraucht!
void OutBitmapData(int hatch) {
 OutDword(40);		// sizeof(header)
 OutDword(8);		// Breite
 OutDword(8);		// Höhe
 OutDword(0x10001);	// Planes und Bit pro Pixel
 OutDword(0);
 OutDword(32);		// Länge der Daten in Bytes
 int i;
 OutBBox();		// sonstige Header-Angaben
 for (i=0; i<8; i++) OutDword(~EaglePattern[hatch*8+(Flipped?7-i:i)]&0xFF);
}

void SetPen(int style, int width, int color) {
 if (style==5) width=color=0;	// PS_NULL: keine Breite, keine Farbe
 int idx=GetNewHandleIndex(ObjPen,style,width,color);
 if (idx<0) return;
 if (Debug>1) {
  string msg;
  sprintf(msg,"SetPen(%d,%d,%06X) idx=%d nHandles=%d",style,width,color,idx,nHandles);
  dlgMessageBox(msg);
 }
 if (!idx) return;	// bereits selektiert
 if (!width){
  BeginRecord(38/*EMR_CREATEPEN*/);
  OutDword(idx);
  OutDword(style);
  OutDword(width);
  OutDword(width);
  OutDword(color);
 }else{
  style|=0x10000/*PS_GEOMETRIC*/;
  int brushstyle=LayerFill&0x0F;
  if (brushstyle==3) SetTextColor(color);
  int brushhatch=LayerFill>>4;
  BeginRecord(95/*EMR_EXTCREATEPEN*/);
  OutDword(idx);	// ihPen
  if (brushstyle<3) {
   OutBBox();
   OutDword(style);	// elpPenStyle;
   OutDword(width);	// elpWidth;
   OutDword(brushstyle);// elpBrushStyle;
   OutDword(color);	// elpColor;
   OutDword(brushhatch);// elpHatch;
   OutDword(0);		// elpNumEntries;
   OutDword(0);		// elpStyleEntry[1];
  }else{
   OutDword(56);	// offBmi (BitMapInfo)
   OutDword(40);	// cbBmi
   OutDword(96);	// offBits (BitMapBits)
   OutDword(32);	// cbBits
   OutDword(style);	// elpPenStyle;
   OutDword(width);	// elpWidth;
   OutDword(brushstyle);// elpBrushStyle;
   OutDword(2);		// elpColor;
   OutDword(0);		// elpHatch;
   OutDword(0);		// elpNumEntries;
   OutDword(0);// elpStyleEntry[1];
   OutBitmapData(brushhatch);
  }
 }
 EndRecord();
 OutSelectObject(idx);
}

void SetBrush(int style, int hatch, int color) {
 if (style<2) hatch=0;	// BS_SOLID,BS_NULL: kein Muster
 if (style==1) color=0;	// BS_NULL: keine Farbe
 int idx=GetNewHandleIndex(ObjBrush,style,hatch,color);
 if (Debug>1) {
  string msg;
  sprintf(msg,"SetBrush(%d,%d,%06X) idx=%d nHandles=%d",style,hatch,color,idx,nHandles);
  dlgMessageBox(msg);
 }
 if (!idx) return;
 if (style<3) {		// BS_SOLID,BS_NULL,BS_HATCHED
  BeginRecord(39/*EMR_CREATEBRUSHINDIRECT*/);
  OutDword(idx);
  OutDword(style);
  OutDword(color);
  OutDword(hatch);
  EndRecord();
 }else{			// BS_PATTERN
  BeginRecord(93/*EMR_CREATEMONOBRUSH*/);
  OutDword(idx);
  OutDword(2);		// iUsage
  OutDword(36);		// offBmi;
  OutDword(40);		// cbBmi;
  OutDword(76);		// offBits;
  OutDword(32);		// cbBits;
  OutDword(0x20000000);	// weiß keiner!
  OutBitmapData(hatch);
  EndRecord();
  SetTextColor(color);
 }
 OutSelectObject(idx);
}

void SetFont(int font, int size, int angle, int color,int align) {
 int sel=FontSel[font];
 if (!sel) return;	// VEKTOR - das geht hier nicht!
 size*=1.6;
 int idx=GetNewHandleIndex(ObjFont,font,size,angle);
 if (Debug>1) {
  string msg;
  sprintf(msg,"SetFont(nr=%d,rot=%d,ali=%d,size=%d,%06X) idx=%d nHandles=%d",
    font,angle,align,size,color,idx,nHandles);
  dlgMessageBox(msg);
 }
 if (idx) {
  BeginRecord(82/*EMR_EXTCREATEFONTINDIRECTW*/);
  OutDword(idx);
  OutDword(size);	//nHeight
  OutDword(0);		//nWidth
  OutDword(angle);	//nEscapement
  OutDword(angle);	//nOrientation
  OutDword(FontBold[font]?700:400);	//fnWeight
  OutDword(FontItalic[font]);	//fItalic,fUnderline,fStrikeout,fCharset
  OutDword(0);		// fOutPrecision,fClipPrecision,fQuality,fPitchAndFamily
  OutString(FontName[font],-1);
  EndRecord();
  OutSelectObject(idx);
 }
 SetTextColor(color);
 if (TextAlign!=align) {
  BeginRecord(22/*EMR_SETTEXTALIGN*/);
  OutDword(align);
  EndRecord();
  TextAlign=align;
 }
}

// Koordinaten sortieren und zwei Punktkordinaten (eines Boxrechtecks) ausgeben
void OutRect(int left, int top, int right, int bottom) {
 if (!Flipped && left>right
 || Flipped && left<right) {int t=left; left=right; right=t;}
 if (top<bottom) {int t=top; top=bottom; bottom=t;}
 OutMassX(left);	// rclBox
 OutMassY(top);
 OutMassX(right);
 OutMassY(bottom);
}

// Gedrehtes Rechteck ausgeben
void OutRectangle(int mx,int my,int x, int y, real angle) {
 int w=angle*10;
 x/=2; y/=2;	// jetzt Abstand vom Ursprung
 switch (w) {
  case 0:
  case 900:
  case 1800:
  case 2700: {
   BeginRecord(43/*EMR_RECTANGLE*/);
   if (w==0 || w==1800) {
    OutRect(mx-x,my-y,mx+x,my+y);
   }else{	// 90° / 270°:
    OutRect(mx-y,my-x,mx+y,my+x);
   }
   EndRecord();
  }break;
  default: {
// Für Board: vollständige Koordinatentransformation und Ausgabe eines Polygons!
// Alternative: GDI-Koordinatentrafo...
   angle*=PI/180;
   real c=cos(angle);	// Drehmatrix-Elemente	| +cos +sin |
   real s=sin(angle);	//			| -sin +cos |
   BeginRecord(3/*EMR_POLYGON*/);
   OutBBox();
   OutDword(4);   
   OutMassX(mx+c*x-s*y); OutMassY(my+s*x+c*y);	// I.
   OutMassX(mx-c*x-s*y); OutMassY(my-s*x+c*y);	// II.
   OutMassX(mx-c*x+s*y); OutMassY(my-s*x-c*y);	// III.
   OutMassX(mx+c*x+s*y); OutMassY(my+s*x-c*y);	// IV. Quadrant
   EndRecord();
  }
 }
}

// Gedrehtes RoundRect ausgeben (r!=0), nur für Board, SMDs
void OutRoundRect(int mx, int my, int x, int y, int r, real angle) {
 int w=angle*10;
 x/=2; y/=2;	// jetzt Abstand vom Ursprung
 switch (w) {
  case 0:
  case 900:
  case 1800:
  case 2700: {
   BeginRecord(44/*EMR_ROUNDRECT*/);
   if (w==0 || w==1800) {
    OutRect(mx-x,my-y,mx+x,my+y);
   }else{	// 90° / 270°:
    OutRect(mx-y,my-x,mx+y,my+x);
   }
   OutMassX(2*r);
   OutMassY(2*r);
  }break;
  default: {
// Polygonausgabe mit Kreisapproximation: als Zwölfeck (sollte reichen)
   angle*=PI/180;
   real c=cos(angle);	// Drehmatrix-Elemente	| +cos +sin |
   real s=sin(angle);	//			| -sin +cos |
   real z=1-sin(PI/4);	// 45°
   BeginRecord(3/*EMR_POLYGON*/);
   OutBBox();
   OutDword(12);
   OutMassX(mx+c*x-s*(y-r)); OutMassY(my+s*x+c*(y-r));
   OutMassX(mx+c*(x-z*r)-s*(y-z*r)); OutMassY(my+s*(x-z*r)+c*(y-z*r));
   OutMassX(mx+c*(x-r)-s*y); OutMassY(my+s*(x-r)+c*y);
   
   OutMassX(mx-c*(x-r)-s*y); OutMassY(my-s*(x-r)+c*y);
   OutMassX(mx-c*(x-z*r)-s*(y-z*r)); OutMassY(my-s*(x-z*r)+c*(y-z*r));
   OutMassX(mx-c*x-s*(y-r)); OutMassY(my-s*x+c*(y-r));

   OutMassX(mx-c*x+s*(y-r)); OutMassY(my-s*x-c*(y-r));
   OutMassX(mx-c*(x-z*r)+s*(y-z*r)); OutMassY(my-s*(x-z*r)-c*(y-z*r));
   OutMassX(mx-c*(x-r)+s*y); OutMassY(my-s*(x-r)-c*y);
   
   OutMassX(mx+c*(x-r)+s*y); OutMassY(my+s*(x-r)-c*y);
   OutMassX(mx+c*(x-z*r)+s*(y-z*r)); OutMassY(my+s*(x-z*r)-c*(y-z*r));
   OutMassX(mx+c*x+s*(y-r)); OutMassY(my+s*x-c*(y-r));
  }
 }
 EndRecord();
}

// Gedrehten(:-) Kreis ausgeben
void OutCircle(int mx, int my, int r) {
 BeginRecord(42/*EMR_ELLIPSE*/);
 OutRect(mx-r,my-r,mx+r,my+r);
 EndRecord();
}

// Gedrehtes Achteck ausgeben (für Pads), type=0: normal, type=1: lang, type=2:exzentrisch
void OutOctagon(int mx, int my, real d, real angle, int type) {
 d/=2;
 int i;
 real x[],y[];
 real j=tan(22.5*PI/180);
 x[0]=d*j;	y[0]=d;		// Achteck im Uhrzeigersinn bauen
 x[1]=d;	y[1]=d*j;
 x[2]=d;	y[2]=-d*j;
 x[3]=d*j;	y[3]=-d;
 x[4]=-d*j;	y[4]=-d;
 x[5]=-d;	y[5]=-d*j;
 x[6]=-d;	y[6]=d*j;
 x[7]=-d*j;	y[7]=d;
 if (type) for (i=0; i<4; i++) x[i]+=2*d;	// lang machen (für Typ 1 und 2)
 if (type==1) for (i=0; i<8; i++) x[i]-=d;	// zentrieren (für Typ 1)
 angle*=PI/180;
 real c=cos(angle);	// Drehmatrix-Elemente	| +cos +sin |
 real s=sin(angle);	//			| -sin +cos |
 BeginRecord(3/*EMR_POLYGON*/);
 OutBBox();
 OutDword(8);
 for (i=0; i<8; i++) {
  OutMassX(mx+c*x[i]-s*y[i]);
  OutMassY(my+s*x[i]+c*y[i]);
 }
 EndRecord();
}

/* Abarbeitung von Eagle-Zeichenprimitiven */
int DontProcess(int ElemLayer) {
 if (PaletteType || board) return ElemLayer!=CurLayer;	// Farbmodus
 else return !col[ElemLayer];		// Schwarzweiß-Modus
}

// wie SetBrush(), jedoch mit _einem_ Füllstil-Attribut
void SetBrush2(int Fill) {
 SetBrush(Fill&0x0F,Fill>>4,LayerColor);
}

// Zum angegebenen Layer passenden Stift (minimale Strichbreite) erzeugen
void SetLayerPen() {
 SetPen(0/*PS_SOLID*/,0,LayerColor);
}

// Zum angegebenen Layer passenden Pinsel erzeugen
void SetLayerBrush() {
 SetBrush2(LayerFill);
}

void HandleCircle(UL_CIRCLE O) {
 if (DontProcess(O.layer)) return;
 SetPen(0/*PS_SOLID*/,O.width,LayerColor);
 if (O.width) SetBrush(1/*BS_NULL*/,0,0);
 else SetLayerBrush();
 OutCircle(O.x,O.y,O.radius);
}

void HandleJunction(UL_JUNCTION O) {
 if (DontProcess(LAYER_NETS)) return;
 SetLayerPen();
 SetLayerBrush();
 OutCircle(O.x,O.y,O.diameter/2);
}

void HandlePolygon(UL_POLYGON O) {
// Obwohl Polygone mit Kurven-Begrenzung exakt mittels Path-Funktionen
// gezeichnet werden können, wird hier nur ein näherungsweises Polygon
// gezeichnet. Erstens läuft es so mit freigerechneten Signalpolygonen,
// und zweitens bleibt das EMF auch unter Windows 9x/Me benutzbar.
// Vorausgesetzt, die Koordinaten bleiben 16bittrig.
// Und es wird kein SetWorldTransform() für gespiegelten Text verwendet.
 if (DontProcess(O.layer)) return;
 SetPen(0/*PS_SOLID*/,O.width,LayerColor);
 if (O.pour==POLYGON_POUR_HATCH) SetBrush(2,4/*HS_CROSS*/,LayerColor);
 else{
  int Fill=0;
  if (board) Fill=LayerFill;
  SetBrush2(Fill);
 }
 int np=0,nk[],n=0,x0,y0;
 nk[0]=0;
 O.contours(W) {
// Hoppla! "contours" macht aus Bögen Geradenstücke, auch im Schaltplan!
  if (!nk[np]) x0=W.x1, y0=W.y1;	// Linienzug-Anfang
  nk[np]++;	// Kanten abzählen (per Polygon)
  n++;		// Kanten abzählen (Gesamtanzahl)
  if (x0==W.x2 && y0==W.y2) nk[++np]=0;	// Linienzug-Ende am Anfang
 }
 if (np==1) {
  BeginRecord(3/*EMR_POLYGON*/);
  OutBBox();
  OutDword(n);		// cptl
 }else{
  BeginRecord(8/*EMR_POLYPOLYGON*/);
  OutBBox();
  OutDword(np);		// Anzahl Einzelpolygone
  OutDword(n);		// Anzahl aller Punkte
  for (n=0; n<np; n++) OutDword(nk[n]);	// Punktzahl der Einzelpolygone
 }
 O.contours(W) {
  OutMassX(W.x1);	// aptl
  OutMassY(W.y1);
 }
 EndRecord();
}

void HandleRectangle(UL_RECTANGLE O) {
 if (DontProcess(O.layer)) return;
 SetLayerPen();
 SetLayerBrush();
 OutRectangle((O.x1+O.x2)/2,(O.y1+O.y2)/2,O.x2-O.x1,O.y2-O.y1,O.angle);
}

void OutLineTo(int x, int y) {
 BeginRecord(54/*EMR_LINETO*/);
 OutMassX(x);
 OutMassY(y);
 EndRecord();
 LastX=x; LastY=y;
}

void OutLine(int x1, int y1, int x2, int y2) {
 if (LastX!=x1 || LastY!=y1) {
  BeginRecord(27/*EMR_MOVETOEX*/);
  OutMassX(x1);
  OutMassY(y1);
  EndRecord();
 }
 OutLineTo(x2,y2);
}

void HandleWire(UL_WIRE O) {
 if (DontProcess(O.layer)) return;
 int st;
 switch (O.style) {
  case WIRE_STYLE_CONTINUOUS:	st=0/*PS_SOLID*/; break;
  case WIRE_STYLE_LONGDASH:	st=1/*PS_DASH*/; break;
  case WIRE_STYLE_SHORTDASH:	st=2/*PS_DOT*/; break;
  case WIRE_STYLE_DASHDOT:	st=3/*PS_DASHDOT*/; break;
 }
 if (O.arc && O.arc.cap==CAP_FLAT) st|=0x200/*PS_ENDCAP_FLAT*/;
 SetPen(st,O.width,LayerColor);
 if (O.arc) {
  int rad=O.arc.radius;
  BeginRecord(45/*EMR_ARC*/);
  OutRect(O.arc.xc-rad,O.arc.yc-rad,
 	  O.arc.xc+rad,O.arc.yc+rad);
  if (Flipped) {
   OutMassX(O.arc.x2);
   OutMassY(O.arc.y2);
   OutMassX(O.arc.x1);
   OutMassY(O.arc.y1);
  }else{
   OutMassX(O.arc.x1);
   OutMassY(O.arc.y1);
   OutMassX(O.arc.x2);
   OutMassY(O.arc.y2);
  }
  EndRecord();
 }else{
  OutLine(O.x1,O.y1,O.x2,O.y2);
 }
}

// Textfarbe dunkler machen (auffälliger bei transparenter Ausgabe)
int DarkerColor(int col) {
 int rgb[];
 rgb[0]=col&0xFF;
 rgb[1]=(col>>8)&0xFF;
 rgb[2]=(col>>16)&0xFF;
 rgb[0]>>=2;	// vierteln
 rgb[1]>>=2;
 rgb[2]>>=2;
 return (rgb[2]<<16)|(rgb[1]<<8)|rgb[0];
}

string CharWidth[]={
 "8<>OOlY0<<AR8<88OOOOOOOOOO88RRROvYY]]YTb]8KYOg]bYb]YT]YuXXT888FO<OOKOO8OO34L3hOOOO<K8OK]KII<6<R`O`3O<uOO<vY<u`T``33<<>Ou9uK<p`IY8<OOOO6O<_?OR<_OBO<<<QN8<<?OgggTYYYYYYu]YYYY8888]]bbbbbRb]]]]YYTOOOOOOlKOOOO8888OOOOOOOOTOOOOKOK",
 "",
 "5;CJJfa.<<JO5;57JJJJJJJJJJ77OOOFm]XX]SO]]<B\Rk]]O]XOS]]o]]S<6<GJ<EJEJE=IJ78I7_JJJJ<A7JI\JHEH/HMaJa<JFtJK<tO<kaSaa<<FE=Jt<rA<]aE]5;JJJJ/J>`7HO;`JAN99;PF599;H___E]]]]]]iXSSSS<<<<]]]]]]]O]]]]]]OJEEEEEEXEEEEE7777JJJJJJJNJJJJJJJJ"};

int GetVectorLineWidth(UL_TEXT O) {
 O.wires(W) return W.width;
}

// Entfernt Ausrufezeichen sowie Escape-Rückwärtsstriche,
// malt die Überstreichungs-Striche möglichst genau über die (proportionalen,
// True-Type-) Buchstaben in der Linienbreite der alternativen Vektorschrift
string HandleNegation(UL_TEXT O, real angle, int textalign) {
 string s=O.value;
 int i=DupStrFind(O);
 int j=DupStrCount[i];	// gezählte Vorkommen (sollte hier !=0 sein)
 if (j>=0) {		// Sollte hier nie Null sein, falls doch, dann String unverändert ausgeben
  if (j>=2 && DupStrReplace) sprintf(s,"%d×%s",j,s);
  DupStrCount[i]=-j;	// Negativ: String erledigt
 }else return "";	// nicht mehrfach ausspucken
 if (!DoNegate) return s;
 int n;			// n gerade wenn kein Strich
 int sel=FontSel[O.font];	// muss !=0 sein!
 real mult=O.size*1.6/96;
 real x,xx[],y;		// laufende Pixel, Pixel für Linie, Höhe
 char c;
// Modifizieren des Strings, Finden der Überstreichpositionen
 for (i=j=x=n=0; c=s[i]; i++) {
  switch (c) {
   case '\\': {
    c=s[++i];	// Folgezeichen buchstäblich nehmen
    if (!c) {
     --i;
     continue;	// Backslash herausnehmen
    }
   }break;
   
   case '!': if (!(n&1)) {
    switch (s[i+1]) {	// Folgezeichen untersuchen
     case 0:
     case ' ':
     case '!':
     case '\'':
     case '"':
     case ')':
     case ']':
     case '}': break;
     default: n++;
    }
    if (n&1) {
     xx[n-1]=x;	// Startposition vermerken
     continue;	// Ausrufezeichen herausnehmen
    }
   }else{
    xx[n++]=x;	// Endposition vermerken
    continue;	// Ausrufezeichen herausnehmen
   }break;
   
   case ',': if (n&1) {
    xx[n++]=x;	// Endposition vermerken
   }break;
  }
  
  if (c<32) {
  }
  s[j++]=c;
  if (sel==2) x+=53*mult;	// Zeichenbreite für 96 Punkt "Courier New"
  else x+=(CharWidth[sel-1][c-32]-32)*mult; // Stringbreite in Pixel mitschneiden
 }
 if (n&1) xx[n++]=x;
 s[j]=0;	// Ziel (nie länger als Quelle) abhacken
 if (n) {
// Striche malen
  angle*=PI/180;
  real c=cos(angle);	// Drehmatrix-Elemente	| +cos +sin |
  real s=sin(angle);	//			| -sin +cos |
  real x0=O.x;
  real y0=O.y;
  if (textalign&2/*TA_RIGHT*/) {
   x0-=x*c;
   y0-=x*s;
  }
  if (textalign&24/*TA_BASELINE*/) {
   y=O.size*1.25;	// empirischer Abstand
   x0-=y*s;
   y0+=y*c;
  }
  SetPen(0/*PS_SOLID*/,GetVectorLineWidth(O),LayerColor);
  for (i=0; i<n; i+=2) {
   int x1=x0+xx[i]*c;
   int y1=y0+xx[i]*s;
   int x2=x0+xx[i+1]*c;
   int y2=y0+xx[i+1]*s;
   OutLine(x1,y1,x2,y2);
  }
 }
 return s;
}

void HandleText(UL_TEXT O) {
 if (DontProcess(O.layer)) return;
 if (O.value==" ") return;		// unterdrückte Values bei Bauelementen
 if (!FontSel[O.font]			// Ersetzung durch Vektor gewünscht
 || O.font==FONT_VECTOR && O.layer<=LAYER_BOTTOM
 					// Vektorfont auf Kupfer
 || board && Flipped && !O.mirror	// gespiegelten Text stets als Vektoren
 || board && !Flipped && O.mirror) {	// auch gespiegelt
// Hier sollte eigentlich Eagle 5 die Überstreichungen generieren, ist das so?
  O.wires(W) HandleWire(W);		// Text als Striche ausgeben
  return;
 }
 real w=O.angle;
 int a=24/*TA_BASELINE*/;
 if (schematic && O.mirror) {
  if (w==90 || w==270) a^=24;
  else a^=2/*TA_RIGHT*/;
 }
 if (!O.spin && w>90 && w<=270) {	// Änderung von Ausrichtung bei spin=0?
  w-=180; if (w<0) w+=360;		// (für Ausführung im Board)
  a^=24^2;				// Ausrichtung genau anders herum
 }
 string s=HandleNegation(O,w,a);
 int l=strlen(s);
 if (!l) return;
 int col=LayerColor;
 if (Trans && PaletteType==3 && CurLayer>=21) col=DarkerColor(col);
 SetFont(O.font,O.size,w*10,col,a);
 if (Debug) OutCircle(O.x,O.y,10000);
// Eine Zusammenfassung mehrerer Texte zu PolyTextOut ist naheliegend...
// ExtTextOutA wäre interessant, weil's besser packt und evtl. chinesisch läuft
 BeginRecord(84/*EMR_EXTTEXTOUTW*/);
 OutBBox();	// rclBounds (unbenutzt)
 OutDword(2);	// iGraphicsMode=GM_ADVANCED
 OutDword(0);	// exScale (unbenutzt bei GM_ADVANCED)
 OutDword(0);	// eyScale
 OutMassX(O.x);	// ptlReference
 OutMassY(O.y);
 OutDword(l);	// nChars
 OutDword(76);	// offString
 OutDword(0);	// fOptions
 OutBBox();	// rcl
 OutDword(0);	// offDx: Welch ein Glück: Windows wertet das "korrekt" aus!
 OutString(s,O.font);
 EndRecord();
}

/* Nur für Boards: */
void OutDrillSymbol(int x, int y, int type) {
 int D=40*254;
 switch (type) {
  case 0: OutLine(x-D,y-D,x+D,y+D); OutCircle(x,y,D); break;		// Ø
  case 1: OutLine(x-D,y,x+D,y); OutLine(x,y-D,x,y+D); break;		// +
  case 2: OutLine(x-D,y-D,x+D,y+D); OutLine(x-D,y+D,x+D,y-D); break;	// x
  case 3: OutLine(x,y,x,y+D); OutRectangle(x,y,D*2,D*2,0); break;
  case 4: OutLine(x,y,x,y+D); OutLineTo(x-D,y); OutLineTo(x,y-D); OutLineTo(x+D,y); OutLineTo(x,y+D); break;
  case 5: OutLine(x-D,y-D,x+D,y+D); OutLineTo(x-D,y+D); OutLineTo(x+D,y-D); OutLineTo(x-D,y-D); break;
  case 6: OutLine(x-D,y+D,x+D,y-D); OutLineTo(x+D,y+D); OutLineTo(x-D,y-D); OutLineTo(x-D,y+D); break;
  case 7: OutLine(x-D,y,x+D,y); OutLineTo(x,y+D); OutLineTo(x,y-D); OutLineTo(x-D,y); break;
  case 8: OutLine(x-D,y,x+D,y); OutLineTo(x,y-D); OutLineTo(x,y+D); OutLineTo(x-D,y); break;
  case 9: OutLine(x-D,y+D,x+D,y-D); OutLine(x,y-D,x,y+D); OutLine(x+D,y+D,x-D,y-D); break;
  case 10: OutLine(x-D,y,x+D,y); OutLine(x+D,y+D,x-D,y-D); OutLine(x-D,y+D,x+D,y-D); break;
  case 11: OutLine(x,y-D,x,y); OutLineTo(x+D,y+D); OutLineTo(x-D,y+D); OutLineTo(x,y); break;
  case 12: OutLine(x,y+D,x,y); OutLineTo(x+D,y-D); OutLineTo(x-D,y-D); OutLineTo(x,y); break;
  case 13: OutLine(x-D,y,x,y); OutLineTo(x+D,y+D); OutLineTo(x+D,y-D); OutLineTo(x,y); break;
  case 14: OutLine(x+D,y,x,y); OutLineTo(x-D,y-D); OutLineTo(x-D,y+D); OutLineTo(x,y); break;
  case 16: OutCircle(x,y,D/2); /*nobreak;*/
  case 15: OutLine(x-D,y,x+D,y); OutLine(x,y+D,x,y-D); OutCircle(x,y,D); break;
  case 18: OutRectangle(x,y,D,D,0); /*nobreak;*/
  case 17: OutLine(x-D,y,x+D,y); OutLine(x,y+D,x,y-D); OutRectangle(x,y,D*2,D*2,0); break;
 }
}

void DrawHole(int x, int y, int d) {
 if (DrawHoles) {
  SetPen(5/*PS_NULL*/,0,0);
  SetBrush(0/*BS_SOLID*/,0,BackgroundColor);
  OutCircle(x,y,d/2);
 }
}

// ermittelt Bezug zum zu verwendenden Layer für PADs
// Liefert 0 für "don't process"
int GetPadLayer(int flags) {
 switch (CurLayer) {
  case LAYER_TSTOP:
  case LAYER_BSTOP: if (flags&PAD_FLAG_STOP) return CurLayer; break;
  case LAYER_PADS: return LAYER_TOP;
 }
 return 0;
}

void HandlePad(UL_PAD O) {
 if (!O) return;
 if (CurLayer==LAYER_DRILLS) {
  SetPen(0/*PS_SOLID*/,3*254,LayerColor);
  SetBrush(1/*BS_NULL*/,0,0);
  OutDrillSymbol(O.x,O.y,O.drillsymbol);
  return;
 }
 int d=0, PadLayer=GetPadLayer(O.flags);
 if (!PadLayer) return;
 d=O.diameter[PadLayer];
 if (!d) return;	// raus wenn null geblieben
 if (!DrawHoles) {
  SetLayerPen();
  SetLayerBrush();
  switch (O.shape[PadLayer]) {
// im LAYER_PADS ist O.shape immer PAD_SHAPE_ROUND, dann von LAYER_TOP nehmen
   case PAD_SHAPE_SQUARE:	OutRectangle(O.x,O.y,d,d,O.angle); break;
// hier künftig ARC ausgeben! (aber nur, wenn Layer SOLID)
   case PAD_SHAPE_ROUND:	OutCircle(O.x,O.y,d/2); break;
   case PAD_SHAPE_OCTAGON:	OutOctagon(O.x,O.y,d,O.angle,0); break;
   case PAD_SHAPE_LONG:		OutOctagon(O.x,O.y,d,O.angle,1); break;
   case PAD_SHAPE_OFFSET:	OutOctagon(O.x,O.y,d,O.angle,2); break;
   default: dlgMessageBox("??");
  }
 }
 if (PadLayer==LAYER_TOP) DrawHole(O.x,O.y,O.drill);
}

// ermittelt Bezug zum zu verwendenden Layer für PADs
// Liefert 0 für "don't process"
int GetViaLayer(int flags) {
 switch (CurLayer) {
  case LAYER_TSTOP:
  case LAYER_BSTOP: if (flags&VIA_FLAG_STOP) return CurLayer; break;
  case LAYER_VIAS: return LAYER_TOP;
 }
 return 0;
}

void HandleVia(UL_VIA O) {
 if (!O) return;
 if (CurLayer==LAYER_DRILLS) {
  SetPen(0/*PS_SOLID*/,3*254,LayerColor);
  SetBrush(1/*BS_NULL*/,0,0);
  OutDrillSymbol(O.x,O.y,O.drillsymbol);
  return;
 }
 int d=0, ViaLayer=GetViaLayer(O.flags);
 if (!ViaLayer) return;
 d=O.diameter[ViaLayer];
 if (!d) return;	// raus wenn null geblieben
 if (!DrawHoles) {
  SetLayerPen();
  SetLayerBrush();
  switch (O.shape[ViaLayer]) {
   case VIA_SHAPE_SQUARE:	OutRectangle(O.x,O.y,d,d,0); break;
// hier künftig ARC ausgeben! (aber nur, wenn Layer SOLID)
   case VIA_SHAPE_ROUND:	OutCircle(O.x,O.y,d/2); break;
   case VIA_SHAPE_OCTAGON:	OutOctagon(O.x,O.y,d,0,0); break;
   default: dlgMessageBox("???");
  }
 }
 if (ViaLayer==LAYER_TOP) DrawHole(O.x,O.y,O.drill);
}

int GetSmdLayer(int SmdLayer, int flags) {
 switch (SmdLayer) {
  case LAYER_BOTTOM: switch (CurLayer) { 	// SMD-Pad unten
   case LAYER_BSTOP:  if (flags&SMD_FLAG_STOP) return LAYER_BSTOP; break;
   case LAYER_BCREAM: if (flags&SMD_FLAG_CREAM) return LAYER_BCREAM; break;
   case LAYER_BOTTOM: return LAYER_BOTTOM;
  }break;
  case LAYER_TOP: switch (CurLayer) {	// SMD-Pad oben
   case LAYER_TSTOP:  if (flags&SMD_FLAG_STOP) return LAYER_TSTOP; break;
   case LAYER_TCREAM: if (flags&SMD_FLAG_CREAM) return LAYER_TCREAM; break;
   case LAYER_TOP: return LAYER_TOP;
  }break;
 }
 return 0;
}

void HandleSmd(UL_SMD O) {
 if (!O) return;
 int SmdLayer=GetSmdLayer(O.layer,O.flags);
 if (!SmdLayer) return;
 int dx=O.dx[SmdLayer], dy=O.dy[SmdLayer];
 if (!dx) return;	// raus wenn null geblieben
 SetLayerPen();
 SetLayerBrush();
 if (O.roundness) {	// Angabe in Prozent der kürzeren Seite
  if (O.roundness==100 && dx==dy) OutCircle(O.x,O.y,dx/2);
  else{
   int r=dx<dy?dx:dy;	// kürzere der beiden Seiten
   r=r*O.roundness/200;	// Radius der Abrundung
   OutRoundRect(O.x,O.y,dx,dy,r,O.angle);
  }
 }else OutRectangle(O.x,O.y,dx,dy,O.angle);
}

void HandleHole(UL_HOLE O) {
 switch (CurLayer) {
  case LAYER_DRILLS:
  case LAYER_HOLES: {
   SetPen(0/*PS_SOLID*/,3*254,LayerColor);
   SetBrush(1/*BS_NULL*/,0,0);
   OutDrillSymbol(O.x,O.y,O.drillsymbol);
  }break;
  case LAYER_DIMENSION: {
   SetLayerPen();
   SetBrush(1/*BS_NULL*/,0,0);
   OutCircle(O.x,O.y,O.drill/2);
  }break;
 }
}

/* Abarbeitung eines Layers (schwarzweiß: alle) */
void SchWalkLayer(UL_SHEET S) {
 string e;
 sprintf(e,"Seite %d Layer %d",S.number,CurLayer);
 status(e);
 S.busses(B) {
  B.segments(SE) {
   SE.texts(T) HandleText(T);
   SE.wires(W) HandleWire(W);
  }
 }
 S.circles(C) HandleCircle(C);
 S.nets(N) {
  N.segments(SE) {
   SE.junctions(J) HandleJunction(J);
   SE.texts(T) HandleText(T);
   SE.wires(W) HandleWire(W);
  }
 }
 S.parts(P) {
  P.instances(I) {
   I.gate.symbol.circles(C) HandleCircle(C);
   I.gate.symbol.pins(P) {
    P.circles(C) HandleCircle(C);
    P.texts(T) HandleText(T);
    P.wires(W) HandleWire(W);
   }
   I.gate.symbol.polygons(P) HandlePolygon(P);
   I.gate.symbol.rectangles(R) HandleRectangle(R);
   I.gate.symbol.texts(T) HandleText(T);	// Unsmashed
   I.gate.symbol.wires(W) HandleWire(W);
   I.texts(T) HandleText(T);	// Smashed
  }
 }
 S.polygons(P) HandlePolygon(P);
 S.rectangles(R) HandleRectangle(R);
 S.texts(T) HandleText(T);
 S.wires(W) HandleWire(W);
}

void BrdWalkLayer(UL_BOARD B) {
 string e;
 sprintf(e,"Layer %d",CurLayer);
 status(e);
 B.circles(C) HandleCircle(C);
 B.elements(E) {
  E.package.circles(C) HandleCircle(C);
  E.package.contacts(X) {
   HandlePad(X.pad);
   HandleSmd(X.smd);
  }
  E.package.holes(H) HandleHole(H);
  E.package.polygons(P) HandlePolygon(P);
  E.package.rectangles(R) HandleRectangle(R);
  E.package.texts(T) HandleText(T);	// unsmashed
  E.package.wires(W) HandleWire(W);
  E.texts(T) HandleText(T);	// smashed
 }
 B.holes(H) HandleHole(H);
 B.polygons(P) HandlePolygon(P);
 B.rectangles(R) HandleRectangle(R);
 B.signals(S) {
  S.polygons(P) if (IsRatsnestPoly(P)) HandlePolygon(P);
// Eventuell hier wie in Eagle5 ein gestricheltes, ungefülltes Polygon zeichnen?
  S.vias(V) HandleVia(V);
  S.wires(W) HandleWire(W);
 }
 B.texts(T) HandleText(T);
 B.wires(W) HandleWire(W);
}

/* Abarbeitung layer-weise von hinten nach vorn, wenn bunt (sonst unexakte Darstellung) */
void SchWalkLayers(UL_SHEET S) {
 if (PaletteType) for (CurLayer=255; CurLayer>=0; CurLayer--) {
  if (SetLayer(CurLayer)) SchWalkLayer(S);
 }else SchWalkLayer(S);	// Schwarzweiß NICHT im Sandwich-Betrieb abarbeiten (schneller)
}

void BrdWalkLayer2(UL_BOARD B, int DoLast) {
 if (!SetLayer(CurLayer)) return;
 if (!DoLast && strchr(LastLayers,CurLayer)>=0) return;	// überspringen
 BrdWalkLayer(B);
// Zweiter Durchlauf für die Löcher (In einem einzelnen Durchlauf würde,
// wegen der vielen Kontextumschaltungen, die .WMF-Datei viel größer werden.)
 if (CurLayer==LAYER_VIAS || CurLayer==LAYER_PADS) {
  DrawHoles=1;
  OutRop(13/*R2_COPYPEN*/);	// Löcher zum Hintergrund "durchstanzen"
  BrdWalkLayer(B);
  DrawHoles=0;
 }
}

/* Abarbeitung layer-weise, sogar wenn schwarz-weiß */
void BrdWalkLayers(UL_BOARD B) {
// Eagle gibt die Kupferlagen von unten nach oben aus
 if (Trans) {
// Löcher ausstanzen lassen, bevor Kupferlinien hineingezogen werden (wie Eagle)
  for (CurLayer=17; CurLayer<21; CurLayer++) BrdWalkLayer2(B,0);
  for (CurLayer=16; CurLayer>=1; CurLayer--) BrdWalkLayer2(B,0);
 }else{
  for (CurLayer=16; CurLayer>=1; CurLayer--) BrdWalkLayer2(B,0);
  for (CurLayer=17; CurLayer<21; CurLayer++) BrdWalkLayer2(B,0);
 }
 for (CurLayer=29; CurLayer<51; CurLayer++)  BrdWalkLayer2(B,0);
 for (CurLayer=53; CurLayer<256; CurLayer++) BrdWalkLayer2(B,0);
//Typische Text-Layer zuletzt
 for (CurLayer=51; CurLayer<53; CurLayer++)  BrdWalkLayer2(B,0);
 for (CurLayer=21; CurLayer<29; CurLayer++)  BrdWalkLayer2(B,0);
 Trans=0;	// Transparenz für LastLayers ausschalten
 for (int i=0; CurLayer=LastLayers[i]; i++)  BrdWalkLayer2(B,1);
}

void OutStart() {
 nDwords=0;		// Kopf beginnen
 nRecords=0;
 ClearObjects();
// Normalen EMF-Kopf ausgeben
 BeginRecord(1/*EMR_HEADER*/);
 nDwords+=8;		// rclBounds, rclFrame übergehen (wird später eingesetzt)
 OutDword(0x464D4520);	// dSignature	ENHMETA_SIGNATURE
 OutDword(0x00010000);	// nVersion
 nDwords+=3;		// nBytes, nRecords, nHandles übergehen
 OutDword(0);		// nDescription
 OutDword(0);		// offDescription (Unicode!)
 OutDword(0);		// nPalEntries
 nDwords+=4;		// szlDevice, szlMillimeters übergehen
 EndRecord();
 OutWindow();
}

void OutWrite(int left, int top, int right, int bottom) {
 BeginRecord(14/*EMR_EOF*/);
 OutDword(0);		// nPalEntries
 OutDword(0);		// offPalEntries
 OutDword(0);		// nSizeLast
 EndRecord();
 status("Datei-Ausgabe ("+FName+")");
// Windows-Metafile-Kopfdaten ergänzen
 FileData[2]=left/254;	// rclBounds
 FileData[3]=top/254;
 FileData[4]=right/254;
 FileData[5]=bottom/254;
// Ohne Teiler verreckt ACDSee; ansonsten scheint der Teiler ohne Belang.
// Eine 1:1-Anzeige mit dem Total Commander F3-Viewer ist nicht möglich,
// ggf. "F" drücken. Möglicherweise ein Bug/Un-Feature im Total Commander.
 FileData[6]=left/100;	// rclFrame [HIMETRIC = 10µm]
 FileData[7]=top/100;
 FileData[8]=right/100;
 FileData[9]=bottom/100;
 FileData[12]=nDwords*4;// nBytes
 FileData[13]=nRecords;	// nRecords
 FileData[14]=nHandles;	// nHandles + sReserved
 int w=right-left;
 int h=bottom-top;
 FileData[18]=w;	// szlDevice [pel], diese Angabe muss stimmen
 FileData[19]=h;
 FileData[20]=u2mm(w);	// szlMillimeters [mm], muss mit rclFrame korrelieren
 FileData[21]=u2mm(h);
 for (int i=0; i<nDwords; i++) PrintDword(FileData[i]); 	// Daten ausspucken
// Mit ToClipboard.exe passieren seltsame Dinge:
// Obwohl jenes Programm die Datei gar nicht verändert, werden CAP_FLAT
// zu CAP_RECTANGLE, und alle ExtTextOut-Zeichen erscheinen übereinander.
// Kreise und Bögen sind nach unten versetzt.
}

void DrawBkgnd(int x1, int y1, int x2, int y2) {
 if (!OutBkgnd) return;
 SetPen(5/*PS_NULL*/,0,0);
 SetBrush(0/*BS_SOLID*/,0,BackgroundColor);
 BeginRecord(43/*EMR_RECTANGLE*/);
 OutRect(x1,y1,x2,y2);
 EndRecord();
}

/* Ausgabe einer Metadatei */
void SchPrintMF(UL_SHEET S) {
 OutStart();
 DrawBkgnd(S.area.x1,S.area.y1,S.area.x2,S.area.y2);
 SchWalkLayers(S);
 if (Debug) ShowHandleTable();
 OutWrite(S.area.x1,-S.area.y2,S.area.x2,-S.area.y1);
}

void BrdPrintMF(UL_BOARD B) {
 OutStart();
 DrawBkgnd(B.area.x1,B.area.y1,B.area.x2,B.area.y2);
 BrdWalkLayers(B);
 if (Debug) ShowHandleTable();
 if (Flipped) OutWrite(-B.area.x2,-B.area.y2,-B.area.x1,-B.area.y1);
 else OutWrite(B.area.x1,-B.area.y2,B.area.x2,-B.area.y1);
}

// Einen Schriftart-Parameter parsen
// Dieser muss den regexp-Aufbau haben: (Vector|Arial|Courier|Times)(,b?i?)?
// oder [VACT][,bi]*
void ParseFont(int idx, string val) {
 int p=strchr(val,',');
 if (strlen(val)<=3) p=1;	// in der Kurzform ohne Komma zulässig
 string suffix;
 if (p>=0) {
  suffix=strsub(val,p);
  val=strsub(val,0,p);
 }
 if (val=="") ;		// ohne Schriftname Voreinstellung lassen
 else if (val=="Vector" || val=="V") FontSel[idx]=0;
 else if (val=="Arial" || val=="A") FontSel[idx]=1;
 else if (val=="Courier" || val=="C") FontSel[idx]=2;
 else if (val=="Times" || val=="T") FontSel[idx]=3;
 else{
  dlgMessageBox("Unbekannte Schriftartbezeichnung "+val+"!");
  exit(EXIT_FAILURE);
 }
 FontBold[idx]=0;
 FontItalic[idx]=0;
 for (p=0; p<strlen(suffix); p++) switch (suffix[p]) {
  case 'b': FontBold[idx]=1; break;
  case 'i': FontItalic[idx]=1; break;
  case ',': break;
  default:
  dlgMessageBox("Unbekannter Schriftstil "+suffix[p]+"! (Nur »b« [fett] oder »i« [kursiv])");
  exit(EXIT_FAILURE);
 }
}

int CheckRipupPoly=0;	// Prüfung auf nicht geRATSNESTe Signal-Polygone

string GetWorkFileName(void) {
 if (schematic) schematic(S) return S.name;
 if (board) board(B) return B.name;
}

string AutomaticFileName(void) {
 return filesetext(GetWorkFileName(),schematic?".emf":Flipped?"b.emf":"t.emf");
}

// Die Kommandozeile ist an AWK-Syntax angelegt:
// Entweder <dateiname> oder <variable>=<wert>.
// Nur ein <dateiname> ist erlaubt, der kein '=' enthalten darf.
// Alle im Dialog einstellbaren Variablennamen sind erlaubt.
void ParseCommandLine(void) {
 int i,j,p;
 string arg,key,val;
 for (i=1; i<argc; i++) {
  arg=argv[i];
  p=strchr(arg,'=');
  if (p>=0) {	// normales Argument
   key=strsub(arg,0,p);
   val=strsub(arg,p+1);
   if (key=="Debug") Debug=strtol(val);
   else if (key=="PaletteType" || key=="p") {
    PaletteType=strtol(val);
    OnSetPaletteType();
   }else if (key=="HatchType" || key=="h") HatchType=strtol(val);
   else if ((key=="AllPages" || key=="a") && schematic) AllPages=strtol(val);
   else if ((key=="Flipped" || key=="f") && board) Flipped=strtol(val);
   else if (key=="Trans" || key=="t") Trans=strtol(val);
   else if (key=="OutBkgnd" || key=="b") OutBkgnd=strtol(val);
   else if (key=="DoNegate" || key=="n") DoNegate=strtol(val);
   else if (key=="DupStrReplace") DupStrReplace=strtol(val);
   else if (key=="Dialog" || key=="d") Dialog=strtol(val);
   else if ((key=="LastLayers" || key=="y") && board) LastLayers=val;
   else if (key=="Vector" || key=="V") ParseFont(0,val);
   else if (key=="Proportional" || key=="P") ParseFont(1,val);
   else if (key=="Fixed" || key=="F") ParseFont(2,val);
   else if ((key=="CheckRipupPoly" || key=="r") && board) CheckRipupPoly=strtol(val);
   else{
    dlgMessageBox("Unbekannte Variable "+key+"!");
    exit(EXIT_FAILURE);
   }
  }else{	// Dateiname
   if (FName) {
    dlgMessageBox("Mehrere Dateinamen ("+arg+") sind nicht möglich!");
    exit(EXIT_FAILURE);
   }
   if (arg=="*") FName=AutomaticFileName();
   else FName=arg;
  }
 }
 if (!Dialog && !CalcLastLayers()) exit(EXIT_FAILURE);
}

/* Hauptprogramm */
if (EAGLE_VERSION==5 && EAGLE_RELEASE==0) {
 dlgMessageBox("Dieses ULP läuft wegen eines Fehlers in Eagle 5.00 nicht!\n"+
   "Bitte neueres oder älteres Eagle verwenden.");
 exit(EXIT_FAILURE);
}
if (!schematic && !board) {
 dlgMessageBox("Muss aus Schaltplan oder Board gestartet werden!");
 exit(EXIT_FAILURE);
}

// Informationen über Schaltplan und Layer einsammeln
if (schematic) schematic(S) {
 S.layers(L) if (L.visible) {
  col[L.number]=L.color;	// Layer vermerken
  fil[L.number]=L.fill;		// Füll-Stile (1 = komplett gefüllt)
  if (L.fill!=1) NumHatched++;
 }
 SName=filename(S.name);
 SchCountFonts(S);
}else board(B) {
 B.layers(L) if (L.visible) {
  col[L.number]=L.color;	// Layer vermerken
  fil[L.number]=L.fill;		// Füll-Stile (1 = komplett gefüllt)
  if (L.fill!=1) NumHatched++;
  if (L.fill==2 || L.fill==4 || L.fill==5 || L.fill>=9) NumExact++;
 }
 SName=filename(B.name);
 BrdCountFonts(B);
 CheckRipupPoly=1;
	// diese Layer sind wesentliches Kriterium zur Voreinstellung
 Flipped=col[LAYER_BPLACE] && !col[LAYER_TPLACE];
}
if (schematic) HatchType=0;
if (NumExact) HatchType=2;	// Vorgabe wenn solche Schraffuren sichtbar (nur Board)
OnSetPaletteType();
// Kommandozeile auswerten
ParseCommandLine();
if (CheckRipupPoly) board(B) if (BrdAreRipupPolys(B)) switch (dlgMessageBox(
  "!Es sind Signal-Polygone vorhanden, die nur als Außenkontur "+
  "(nicht freigerechnet) erscheinen.\n"+
  "Für korrekte Polygondarstellung vorher RATSNEST ausführen!",
  "&RATSNEST starten","Polygone &ignorieren", "&Abbrechen")) {
 case 0: exit("RATSNEST;run "+strjoin(argv,' ')); break;
 case 2: exit(0); break;
}

// Nutzer-Dialog anzeigen
if (Dialog && DialogBox()!=1) exit(EXIT_FAILURE);

// Hintergrundfarbe (für Löcher!) einstellen
if (!PaletteType || PaletteType==3 && Trans) BackgroundColor=0xFFFFFF;
else BackgroundColor=rgb2bgr(palette(0,PaletteType-1));
// Dateiname erfragen; Vorgabe für Boards ist Suffix "t" (TOP) oder "b" (BOTTOM)
if (!FName) {
 FName=filesetext(GetWorkFileName(),schematic?".emf":Flipped?"b.emf":"t.emf");
 FName=dlgFileSave("Speichern Erweiterte Metadatei",FName,"*.emf");
}else if (FName==filename(FName)) FName=filedir(GetWorkFileName())+FName;
// Pfad ergänzen! Sonst landet's im BIN-Verzeichnis!
if (!FName) exit(EXIT_FAILURE);
  
if (AllPages) {
 schematic(S) S.sheets(SH) {
  string e;
  sprintf(e,"-%d%s",SH.number,fileext(FName));
  output(filesetext(FName,e),"wb") SchPrintMF(SH);
 }
}else{
 if (board) board(B) {
  output(FName,"wb") BrdPrintMF(B);
 }else sheet(S) {
  output(FName,"wb") SchPrintMF(S);
 }
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded