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

#usage	"Ausgabe eines <b>Sch</b>altplanes (oder Boards) als "
	"<b>W</b>indows-<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>"
	"Kommandozeilenargumente: [Argumente] [Dateiname]<br>\n"
	"Argumente siehe SCH2WMF.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: WMF-Dateien können mit Internet Explorer 6+ direkt im WWW als Bild
// angezeigt werden (das wird wohl die Mac-Version nicht betreffen).
// Leider druckt der IE das WMF als Klötzchengrafik (wie auch bei verkleinerten
// Rasterbilddateien). Man kann sie auch nicht im Browser skalieren.
// Ordentliche Betrachter, bspw. ACDSee, skalieren und drucken WMFs sauber als
// Vektorgrafik.

// Zur Analyse von WMF- und EMF-Dateien habe ich im Zuge von SCH2WMF das Programm
// WMFVIEW geschrieben, welches auf meiner Freeware-Seite zur Verfügung steht.
// (Suchmaschinen-Stichworte: haftmann freeware wmfview)

// Versionsgeschichte:
// Oktober 2005:  SCH2CGM (Computer Graphics Metafile, kann in MS Office
//		  importiert werden, sofern Importfilter installiert
// August 2006:   SCH2EMF gescheitert
// Oktober 2006:  SCH2WMF funktionsfähig
// Dezember 2006: experimentelle BRD-Unterstützung
// Juli 2007:	  Bugfix: ausgeblendete Ebenen sichtbar bei Schwarzweiß-Ausgabe
//		  Abschaffung der zwei Läufe (schneller)
//		  Korrektur bei Bögen, brauchbare Funktion im .BRD
// Mai 2008:	  Hinweis: Proportional-Schrift in Kupfer wird niemals ersetzt
//		  Warnung, wenn kein RATSNEST
//		  Ausgabe pseudotransparent (nur Board) mittels ROP-Kodes
//		  Bugfix: Fehlermeldung bei TCREAM
//       	  exakte Füllmuster, Textlayer (nicht: Text) obenauf (Board)
// Juni 2008:	  Schraffuren beim Spiegeln umgedreht
//		  Ausstanzen von Löchern, Hintergrundfüllung
//		  im Board stets lagenweise Ausgabe (auch bei Schwarzweiß)
//		  Überstreichungen entsprechend Eagle 5
//		  Löcher und Bohrsymbole
//		  Schraffierte Linien als Achtecke, schraffierte Polygonränder
//		  Schraffierte Bögen, Bohrsymbole
//		  LastLayers, Kommandozeile, erweiterter Dialog (d=2)
// Oktober 2008:  Bugfix bei Schaltplanausgabe mit schraffiertem Layer
//		  Umgestaltung der Recordausgabe, kürzere Createfont-Records
//		  Ausgabe Layer 93 (Pins), selektierbare Ausgabe
//		  Symbolzeichenausgabe via Symbol-Schriftart
//		  Symbolischer Dateiname "*" in Kommandozeile
// November 2008: Bugfix: Kreise mit Strichstärke Null sind ausgefüllt
//		  Bugfix: SMDs mit Abrundungen ausgeben (beides Guido Clausner)
// Dezember 2009: Bugfix: Löcher-Darstellung
// Februar 2012:  Bugfix: Eagle6-Kompatibilität, Total-Commander-und-IE-Kompatibilität
//		  (Nebeneffekt: Fehldarstellung der Schrift in Total Commander 6.03,
//		   habe mich vorher davon ins Bockshorn jagen lassen …)
//		  FlatArcReplace: nun Verhältnis zwischen Strichbreite und Bogenradius
//		  Korrektur der Schriftpositionierung bei Ausrichtung an Oberkante
//		  Bugfix: Auswahlrechteck-Vorgabe nun in aktuellen Raster-Einheiten
//		  Dateinamen-Vorgabe beim Board mit Bindestrich bei Buchstaben-Ende
//		  (Buchstaben stoßen nicht aufeinander, statt boardt.wmf nun board-t.wmf)
// April 2016:	  Spiegelung vertikal und Rotation dazu (nur bei d=2 zu sehen).
//		  Damit können alle 8 denkbaren Ausrichtungen ausgespuckt werden.
// Mai 2016:	  Rechtsbündiger Text mit Ohm-Zeichen nun korrekt
// September 2016:Übereinander gesetzter Text mit n×-Präfix
// PROBLEM: pseudotransparente Ausgabe auf Bildschirm prima, beim PDF Murks
// PROBLEM: Einfügen in .docx führt zu falscher Farbwiedergabe,
//	bei Betrieb von Winword im Kompatibilitätsmodus (.doc) alles korrekt.
//	PDF-Ausgabe pseudotransparenter Board-Layer: Falschfarben
// Oktober 2021:  Bugfix: Ausschlüsse im Supply-Layer

// 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)
int NumFlatArc;		// Anzahl Bögen mit flachen Enden (außer Tortenstücke)
int NumPieced;		// Anzahl Strichellinien (auch Bögen)
string SName;		// Schaltplan- oder Board-Name (mit 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 Flipped=0;		// gespiegelt (Bit 0: an X-Achse, Bit 1: an Y-Achse, Bit 2: X/Y-Tausch, 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 LineReplace=1;	// Schraffierte Linien durch (längliche) Achtecke ersetzen
real FlatArcReplace=0.8;// Bögen mit flachen Enden ersetzen ab Verhältnis Breite:Radius
			// (Ab Verhältnis 1,9 wird stets ein Tortenstück ausgegeben)
int PiecedReplace=1;	// Strichellinien-Ersetzung (Standard: nur ersetzte Polygone Eagle-stricheln)
int DupStrReplace=1;	// Übereinander gesetzten Text nur 1x ausgeben mit Präfix
int Dialog=1;		// =2: Erweiterter Dialog (nur für Füchse), 0 = kein Dialog
int GroupOnly=0;	// Gruppe (hier ausgewählt durch Rechteck) ausgeben
int GroupRect[];	// Auswahlrechteck (ersetzt fehlendes InGroup(), ab Eagle 5)
string LastLayers;	// Zuletzt auszugebende Layer (stets deckend, nur Board)
string FName;		// Dateiname (ggf. Kommandozeilenargument)
real Scale=1;		// Ausgabe-(Fehl-)Skalierung (besonders für Total Commander und Internet Explorer)

// Die Vorgabe-Schriftersetzung ist, Vektor-Schrift durch Arial fett
// ersetzen zu lassen, um die Ausgabedatei klein und durchsuchbar zu machen.
// Beim ULP-Start wird der Schaltplan nach speziellen Zeichen im Vektorfont,
// bspw. dem Omega-Zeichen, durchsucht.
// (Die meisten Eagle-Anwender werden's wohl gar nicht wissen:-)
// In diesem Fall erfolgt die Ausgabe in Symbol-Schriftart.
// Bei Zeichen, die nicht in der Symbol-Schriftart enthalten sind
// (Rahmenzeichen; extrem selten verwendet) erfolgt standardmäßig
// keine Schriftersetzung, sondern schnöde Linienausgabe.
// Das ist der Nachteil, wenn man WMF (und kein EMF - Unicode) ausgibt!
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","Symbol"};
string FontLabel[];

// Um die 32-bit-Koordinaten von Eagle ins .WMF zu pressen (16-bit-Koordinaten),
// müssen alle Koordinatenangaben durch einen gemeinsamen Teiler dividiert werden.
real Teiler=25.4;	// µm pro Pixel, Vorgabe: 1 mil WMF-Raster: max. Seitengröße 32767mil=0,83m
real MinBreite=50.8;	// Minimale Linienbreite für Ersetzungsvorgänge in µm
// * Strichellinien durch Eagle-Strichelung
// * Schraffierte Linien und Polygonränder durch Achtecke
// * Bögen mit ENDCAP_FLAT durch Polygone

// 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 sowie Name per Layer
int col[],fil[];
string nam[];

// 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;
 }
}

// Hilfsvariablen zum Zusammensetzen von Flipped
int TopDown;
int Rotated;

int DialogBox(void) {
 string DeckLabel=Trans?"(transparent)":"(deckend)";
 status("Dialog");
 return dlgDialog("WMF-Export-Optionen") {
  dlgLabel("Dateiname: <b>"+filename(SName)+"</b>");
  if (FName) dlgLabel("Ausgabe-Datei: <b>"+filename(FName)+"</b>");
  if (Dialog>1) dlgGridLayout{
   dlgCell(0,0) dlgLabel("WMF-Einheit (alles 16-Bit-Koordinaten): ");
   dlgCell(0,1) dlgRealEdit(Teiler,1,300);
   dlgCell(0,2) dlgLabel(" µm");
   dlgCell(1,0) dlgLabel("Minimale Linienbreite für Ersetzungen: ");
   dlgCell(1,1) dlgRealEdit(MinBreite,0.1,3000);
   dlgCell(1,2) dlgLabel(" µm");
   dlgCell(2,0) dlgLabel("Ausgabe-Skalierung (»falsche« DPI-Angabe): ");
   dlgCell(2,1) dlgRealEdit(Scale,0.1,100);
  }
  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 (Dialog>1) {
   if (NumFlatArc) {
    string s;
    sprintf(s,"Behandlung von %d B%sgen mit flachen Enden",
      NumFlatArc, NumFlatArc==1?"o":"ö");
    dlgGroup(s) {
     dlgHBoxLayout{
      dlgLabel("als Polygon interpolieren ab Breite:Radius: ");
      dlgRealEdit(FlatArcReplace,0,2);
     }
     dlgLabel("<b>0</b> = immer (größere Datei), <b>2</b> = nie (sichtbare »Wurst-Enden«)");
    }
   }
   if (NumPieced) {
    string s;
    sprintf(s,"Behandlung von %d Strichellinie%s",
      NumPieced,NumPieced==1?"":"n");
    dlgGroup(s) {
     dlgRadioButton("Linien mit Windows-Strichelung ausgeben",PiecedReplace);
     dlgRadioButton("Windows für Linien und Bögen, Eagle für Polygone",PiecedReplace);
     dlgRadioButton("Eagle-Strichelung verwenden (Einzelsegmente)",PiecedReplace);
    }
   }
  }
  if (NumHatched) {
   string s;
   sprintf(s,"Füll&muster (%d schraffierte%s Layer vorhanden)",
    NumHatched,NumHatched==1?"r":"");
   dlgGroup(s) {
    dlgRadioButton("Ausgefüllt",HatchType) if (Dialog>1) LineReplace=0;
    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);
    }
// Vorher ermitteln, ob überhaupt problematische Linien oder Polygone
// mit Strichbreite>0 vorhanden sind!?
    if (Dialog>1) {
     dlgSpacing(4);
     dlgGroup("Ersetzung von schraffierten &Linien und Bögen") {
      dlgRadioButton("Keine Ersetzung (dann ausgefüllt, nicht schraffiert)", LineReplace);
      dlgRadioButton("Linien und Bögen durch Polygone ersetzen", LineReplace);
      dlgRadioButton("Auch Polygonränder ersetzen", LineReplace);
     }
    }
   }
  }
  if (schematic && Dialog>1) {
   dlgGroup("Rotation") {
    dlgRadioButton("keine",Flipped);
    dlgRadioButton("90° nach links",Flipped);
    dlgRadioButton("180°",Flipped);
    dlgRadioButton("90° nach rechts",Flipped);
   }
  }
  if (board) {
   if (AnyBottomLayers() || Dialog>1) {
    dlgCheckBox("&Gespiegelt (an Y-Achse, zur Seite)",Flipped);
   }
   if (Dialog>1) {
    dlgCheckBox("Na&ch unten gespiegelt (an X-Achse)",TopDown);
    dlgCheckBox("&90° nach links rotiert (nach den Spiegelungen)",Rotated);
   }
   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(EAGLE_VERSION<5?"&Negationsstriche statt »!« (wie Eagle 5)":"&Negationsstriche (sonst »!«)",DoNegate);
  if (GroupOnly==1) dlgCheckBox("Selektierte G&ruppe (sonst ganze Seite)",GroupOnly);
  else GroupOnly=0;
  dlgCheckBox("&Hintergrund ausmalen (Vektorgrafik nicht transparent)",OutBkgnd);
  if (NumSheet>1) {
   dlgSpacing(4);
   dlgCheckBox("&Alle Seiten (mehrere Dateien)",AllPages) {
    if (AllPages) GroupOnly=0;
   }
  }
  dlgSpacing(4);
  dlgHBoxLayout {
   dlgPushButton(FName?"+O&K":"+&Weiter, zum Speichern ...") {
    if (Dialog<2 || CalcLastLayers()) dlgAccept(1);
   }
   dlgPushButton("Abbrechen") dlgReject();
  }
 };
}
/* Die Vektor-Schriftart und die (später) möglichen Zeichen-Ersetzungen
Kode	Zeichen (CP1252)	CP437	Symbol	Unicode
80 128	?
81 129	Raster			B1 177
82 130	?
83 131	?
84 132	?
85 133	?
86 134	?
87 135	?
88 136	?
89 137	?
8A 138	?
8B 139	?
8C 140	?
8D 141	Rahmen:0-0-1-1		DA 218		U+250C
8E 142	?
8F 143	größer-gleich		F2 242	B3 179
90 144	Block oben		DF 223
91 145	?
92 146	?
93 147	?
94 148	?
95 149	?
96 150	?
97 151	?
98 152	?
99 153	?
9A 154	?
9B 155	?
9C 156	?
9D 157	Rahmen:0-0-2-2		C9 201		U+2554
9E 158	?
9F 159	?

A0 160	_ (anderer Kode)
A1 161	¡
A2 162	Rahmen:1-2-0-0		BD 189		U+255C
A3 163	£
A4 164	Rahmen:2-1-2-0		D0 208		U+2567
A5 165	Rahmen:2-1-0-0		BE 190		U+255B
A6 166	Block rechts?		DE 222
A7 167	Integral untere Hälfte	F5 245
A8 168	Block? unten		DC 220
A9 169	Rahmen:2-0-0-1		B8 184		U+2555
AA 170	ª
AB 171	«
AC 172	¬
AD 173	identisch		F0 240	BA 186
AE 174	umgekehrte Negation	A9 169
AF 175	Element-von		EE 238	CE 206
B0 176	°
B1 177	±
B2 178	²
B3 179	hochgestelltes n	FC 252
B4 180	Schnittmenge		EF 239	C7 199
B5 181	µ
B6 182	Integral obere Hälfte	F4 244
B7 183	· (aber tiefer)
B8 184	Rund (zwei Wellen)	F7 247	BB 187
B9 185	Wurzel			FB 251	D6 214
BA 186	º
BB 187	»
BC 188	¼
BD 189	½
BE 190	kleiner-gleich		F3 243	A3 163
BF 191	¿

C0 192	Rahmen:1-0-0-2		B7 183		U+2556
C1 193	Rahmen:2-1-0-1		B5 181		U+2561
C2 194	Rahmen:1-2-0-2		B6 182		U+2562
C3 195	Rahmen:0-2-1-2		C7 199		U+255F
C4 196	Ä
C5 197	Å
C6 198	Æ
C7 199	Ç
C8 200	Rahmen:0-1-2-0		D4 212		U+2558
C9 201	É
CA 202	Rahmen:1-0-1-2		D2 210		U+2565
CB 203	Rahmen:0-2-1-0		D3 211		U+2559
CC 204	Block rechts?		DE 222
CD 205	Rahmen:0-0-1-2		D6 214		U+2553
CE 206	Rahmen:1-2-1-2		D7 215		U+256B
CF 207	Rahmen:2-1-2-1		D8 216		U+256A
D0 208	Rahmen:2-0-2-1		D1 209		U+2564
D1 209	Ñ
D2 210	pi			E3 227	70 112
D3 211	alpha			E0 224	61  97
D4 212	Gamma			E2 226	47  71
D5 213	rho			E5 229	73 115
D6 214	Ö
D7 215	Peseta			9D?
D8 216	Ø
D9 217	delta			EB 235	64 100
DA 218	Theta			E9 233	51  81
DB 219	Ohm			EA 234	57  87
DC 220	Ü
DD 221	ø (anderer Kode!)	ED?        198
DE 222	Phi			E8 232	46  70
DF 223	ß

E0 224	à
E1 225	á
E2 226	â
E3 227	Rahmen:0-1-2-1		C6 198		U+255E
E4 228	ä
E5 229	å
E6 230	æ
E7 231	ç
E8 232	è
E9 233	é
EA 234	ê
EB 235	Leerzeichen "nbsp"
EC 236	ì
ED 237	í
EE 238	î
EF 239	ï
F0 240	Rahmen:1-2-1-0		D0 208		U+2568
F1 241	ñ
F2 242	ò
F3 243	ó
F4 244	ô
F5 245	Sigma			E4 228	53  83
F6 246	ö
F7 247	÷
F8 248	ø
F9 249	ù
FA 250	ú
FB 251	û
FC 252	ü
FD 253	unendlich		EC 236	A5 165
FE 254	tau			E7 231	74 116
FF 255	ÿ
 */

string Problem160=			// Ausrufezeichen bei Problemstellen,
  "!¡!£!!¦!!!ª«¬º!ΰ±²!ǵ!·»Öº»¼½£¿"	// ungleiche Zeichen bei Einsatz
 +"!!!!ÄÅÆÇ!É!!!!!!!ÑpaGsÖ!ØdQWÜÆFß"	// der Schriftart „Symbol“
 +"àáâ!äåæçèéê ìíîï!ñòóôSö÷øùúûü¥tÿ";
int ProblemChars(string s) {
 int len=strlen(s);
 for (int i=0; i<len; i++) {
  char c=s[i];
  if (c<32) return 1;
  if (c>=128 && c<160 && c!=143) return 1;	//immer Problem
  if (c>=160 && Problem160[c-160]=='!') return 1;	// bspw. Rahmenzeichen
 }
 return 0;
}
/* liefert TRUE bei problematischem Text:
   * Schriftart == Vector
     UND Ersatzschrift != Vector
     UND enthält Zeichen im Nicht-Latin1-Font
   * Ersatzschrift != Vector
     UND (Schrift gespiegelt XOR Ausgabe nicht gespiegelt)
     UND im Board
--- WMF erlaubt leider keine Ausgabe von gespiegeltem Text ---
*/
int ProblemText(UL_TEXT O) {
 if (O.font==FONT_VECTOR/*0*/
 && FontSel[0]
 && ProblemChars(O.value)) return 1;
// Zweiter Fall nicht implementiert
 return 0;
}

// Kode für „Symbol“-Zeichensatz ermitteln, 0 wenn keine Symbol-Ersetzung
int GetSymbolChar(char c) {
 if (c==143) return 179;
 if (c>=160) {
  char r=Problem160[c-160];
  if (r!='!' && r!=c) return r;
 }
 return 0;
}
// Test des Strings auf Symbol-Zeichenersetzung
int HasSymbolChars(string s) {
 int len=strlen(s);
 for (int i=0; i<len; i++) {
  if (GetSymbolChar(s[i])) return 1;
 }
 return 0;
}
string strrev(string s) {
 string ret="";
 for (int i=strlen(s);i;) ret+=s[--i];
 return ret;
}
// liefert einen „Lauf“ gleicher Zeichentypen aus <s> ab <pos>.
// Ist <symb>!=0, dann SYMBOL-Zeichensatz, sonst ANSI-Zeichensatz
// Bei Symbol-Zeichensatz liefert die Funktion die Kodes
// des Symbol-Zeichensatzes.
string GetCharRun(string s, int pos, int symb) {
 string ret="";
 if (symb) for (;;) {
  char c=GetSymbolChar(s[pos++]);
  if (!c) break;
  ret+=c;
 }else for (;;) {
  char c=s[pos++];
  if (!c) break;
  if (GetSymbolChar(c)) break;
  ret+=c;
 }
 return ret;
}

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
 if (!strlen(O.value)) return;	// Leere Strings nicht mitzählen
 NumFont[O.font]++;
 DupStrCount[DupStrFind(O)]++;	// Dubletten finden
 if (ProblemText(O)) FontSel[0]=0;	// Vektor-Schrift voreinstellen (Default: Arial)
 if (O.font==FONT_VECTOR && FontSel[0] && FontSel[0]==FontSel[1] && HasSymbolChars(O.value)) {
  FontBold[0]=FontBold[1];		// Vorgabe: Soll gleich aussehen (typisch für Ohm-Zeichen)
  FontItalic[0]=FontItalic[1];
 }
 if (!DoNegate) {
  int i=strchr(O.value,'!');	// Schnellsuche (unexakt)
  if (i!=-1 && i!=strlen(O.value)-1) DoNegate++;	// Wenigstens Ausrufezeichen am Ende nicht beachten
 }
}

int IsPie(UL_ARC O) {
 return O.cap==CAP_FLAT && O.width>O.radius*1.9;
}

void CountFlatArc(UL_WIRE O) {
 if (O.cap==CAP_FLAT && O.arc && !IsPie(O.arc)) NumFlatArc++;
 if (O.style!=WIRE_STYLE_CONTINUOUS) NumPieced++;
}

void CatchGroupRect(UL_RECTANGLE O) {
 if (O.angle) return;
 GroupOnly++;		// Geeignete Rechtecke mitzählen (Auswertung später)
 GroupRect[0]=O.x1;
 GroupRect[1]=O.y1;
 GroupRect[2]=O.x2;
 GroupRect[3]=O.y2;
}

/* Suche nach verwendeten Text-Fonts, zur Vorbereitung des Dialogs */
// Weiterhin wird (beim Vektor-Font) nach problematischen Zeichen gesucht.
// Bei Problemen wird die Voreinstellung für Vektor-Font
//  von "Arial" auf "Vektor" zurückgesetzt.
// (Für alle, die nicht wissen, worum es geht:
//  Eagle unterstützt bspw. das Omega-Zeichen, aber nur in der Vektor-Schrift.)
// In nicht-westeuropäischen Windows-Versionen sind größere Teile der
//  Vektor-Schriftart problematisch ...
// Außerdem wird nach Bögen mit CAP_FLAT gesucht - sowie nach einem Auswahlrechteck
void SchCountFonts(UL_SCHEMATIC O) {
 O.sheets(O){
  string e;
  sprintf(e,"Seite %d",O.number);
  status(e);
  NumSheet++;
  O.busses(O) {
   O.segments(O) {
    O.texts(O) CountFont(O);
    O.wires(O) CountFlatArc(O);
   }
  }
  O.nets(O) {
   O.segments(O) {
    O.texts(O) CountFont(O);
    O.wires(O) CountFlatArc(O);
   }
  }
  O.parts(O) {
   O.instances(O) {
    O.gate.symbol.pins(O) {
     O.texts(O) CountFont(O);
     O.wires(O) CountFlatArc(O);
    }
    O.gate.symbol.texts(O) CountFont(O);
    O.gate.symbol.wires(O) CountFlatArc(O);
    O.texts(O) CountFont(O);
   }
  }
  O.texts(O) CountFont(O);
  O.wires(O) CountFlatArc(O);
  O.rectangles(Q) if (Q.layer==LAYER_NETS) CatchGroupRect(Q);
 }
}

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

int IsRatsnestPoly(UL_POLYGON O) {
 O.fillings(Q) return 1;
 return 0;
}

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

// **** globale Variablen ****
/* für den WMF-Kopf */
int nWords;	// Länge WMF-Datei
int nMaxRecSize;// Länge längster WMF-Record
int nHandles;
/* WMF-Daten (Ansammlung im RAM, WORD-Array) */
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 2 Byte in Intel-Notation (Little Endian) */
void PrintWord(int w) {
 printf("%c%c",w&0xFF,(w>>8)&0xFF);
}
/* Ausgabe 2 oder 4 Byte in Intel-Notation in Datenpuffer */
void OutWord(int w) {
 FileData[nWords++]=w;
}
void OutDword(int d) {
 FileData[nWords++]=d;
 FileData[nWords++]=d>>16;
}
// <term>=1: String garantiert nullterminieren, sonst nicht zwangsweise nullterminiert
void OutString(string s, int term) {
 int i, len=strlen(s)+term;
 for (i=0; i<len; i+=2) {
  OutWord(s[i]&0xFF|s[i+1]<<8);
 }
}
int nRecordStart;
void BeginRecord(int recordtype) {
 nRecordStart=nWords;	// Anfang merken
 nWords+=2;		// Platz für Länge lassen
 OutWord(recordtype);
}
void EndRecord(void) {
 int b=nWords-nRecordStart;	// Länge in WORDs
 if (nMaxRecSize<b) nMaxRecSize=b;
 FileData[nRecordStart]=b;	// Länge eintragen
 FileData[nRecordStart+1]=b>>16;
}
int Mass(int eaglepixel) {
// 1 Eagle-Pixel sind 0,1 µm (254000 dpi), damit gibts Probleme mit 16 bit.
// (Bei Eagle 6+ gar 1/320 µm, 8128000 dpi)
// Division durch 254 ergibt 25,4 µm als Ersatzraster, 1/1000 Zoll = 1 mil, ergibt 1000 dpi,
// das scheint für alle gängigen Schaltpläne (und auch Boards) ausreichend zu sein.
// Die Maximalausdehnung wäre dann 1 mil * 32767 ~ 0,8 m
// Für 1200 dpi (Laserdrucker) müsste der Teiler 211,66 sein (Eagle 6+: 6773,3), alle krumm.
 return round(u2mic(eaglepixel)/Teiler);
}
void OutMassX(int eaglepixel) {
 OutWord(Mass(Flipped&1?-eaglepixel:eaglepixel));
}
void OutMassY(int eaglepixel) {
 OutWord(Mass(Flipped&2?eaglepixel:-eaglepixel));
}
void OutXY(int x, int y) {
 if (Flipped&4) {int t=x; x=y; y=t;}
 OutMassY(y);	// im WMF verkehrherum weil's auf Pascal-Aufrufkonvention basiert
 OutMassX(x);
}
// Ausgabe für Polygone: „richtigherum“, gleich mit Koordinatentransformation
real poly_mx=0;	// Verschiebung
real poly_my=0;
real poly_c=1;	// Drehmatrix-Elemente	| +cos +sin |
real poly_s=0;	//			| -sin +cos |

void SetPolyTr(real mx,real my,real angle/*in Grad*/) {
 angle*=PI/180;
 poly_mx=mx;
 poly_my=my;
 poly_c=cos(angle);
 poly_s=sin(angle);
}
void OutPolyXY(real x, real y) {
 int xx=poly_mx+poly_c*x-poly_s*y;
 int yy=poly_my+poly_s*x+poly_c*y;
 if (Flipped&4) {x=xx;xx=yy;yy=x;}
 OutMassX(xx);	// In richtiger Reihenfolge ausgeben
 OutMassY(yy);
}
void OutPolyRA(real r, real a) {
 a*=PI/180;
 OutPolyXY(r*cos(a),r*sin(a));
}

void OutRop(int NewRop) {
 if (NewRop==LayerRop) return;
 BeginRecord(0x0104/*META_SETROP2*/);
 OutWord(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);
}

int odd(int v) {
 while (v&~1) v=v&1^(v>>=1);
 return v;
}

// 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 (odd(Flipped&3)) 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=0; 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(0x12D/*META_SELECTOBJECT*/);
 OutWord(idx);
 EndRecord();
}

void OutDeleteObject(int idx) {
 BeginRecord(0x1F0/*META_DELETEOBJECT*/);
 OutWord(idx);
 EndRecord();
}

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

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

// Liefert Index in Handle-Tabelle (für schnelle WMF-Darstellung),
// liefert -1, 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;
  nHandles++;
  return j;
 }	// gefunden: ggf. als aktuelles Objekt aktivieren
 if (ObjInUse[objtype]!=j) {
  OutSelectObject(j);
  ObjInUse[objtype]=j;
 }
 return -1;
}

void SetPen(int style, int width, int color) {
 width=Mass(width);
 if (style==5) width=color=0;	// PS_NULL: keine Breite, keine Farbe
 int idx=GetNewHandleIndex(ObjPen,style,width,color);
 if (idx<0) return;
 BeginRecord(0x02FA/*META_CREATEPENINDIRECT*/);
 OutWord(style);
 OutWord(width);
 OutWord(width);
 OutDword(color);
 EndRecord();
 OutSelectObject(idx);
}

void SetTextColor(int color) {
 if (TextColor!=color) {
  BeginRecord(0x0209/*META_SETTEXTCOLOR*/);
  OutDword(color);
  EndRecord();
  TextColor=color;
 }
}

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 (idx<0) return;
 if (style<3) {
  BeginRecord(0x02FC/*META_CREATEBRUSHINDIRECT*/);
  OutWord(style);
  OutDword(color);
  OutWord(hatch);
  EndRecord();
 }else{
  int i;
  BeginRecord(0x01F9/*META_CREATEPATTERNBRUSH*/);
  OutWord(0/*BitmapType ??*/);
  OutWord(8);		// Breite
  OutWord(8);		// Höhe
  OutWord(2);		// Breite in Bytes (muss geradzahlig sein)
  OutWord(0x0101);	// Planes und Bit pro Pixel
  for (i=0; i<11; i++) OutWord(0);	// reservierte WORDs
  for (i=0; i<8; i++) OutWord(~EaglePattern[hatch*8+(odd(Flipped&3)?7-i:i)]);
  EndRecord();
  SetTextColor(color);
 }
 OutSelectObject(idx);
}

void SetFont(int font, int size, int angle, int color, int align) {
 int sel=4;
 if (font&4) {		// Symbol-Bit gesetzt?
  font&=~4;
 }else{
  sel=FontSel[font];
 }
 if (!sel) return;	// VEKTOR - das geht hier nicht!
 size=Mass(size*1.6);
 int idx=GetNewHandleIndex(ObjFont,
   sel|FontBold[font]<<8|FontItalic[font]<<9,size,angle);
 if (idx>=0) {
  BeginRecord(0x02FB/*META_CREATEFONTINDIRECT*/);
  OutWord(size);
  OutWord(0);
  OutWord(angle);
  OutWord(angle);	// wird von Win16 ignoriert
  OutWord(FontBold[font]?700:400);
  OutWord(FontItalic[font]);
  OutWord(sel==4?0x200:0);	// HIBYTE = LF_CHARSET
  OutWord(4/*OUT_TT_PRECIS*/);	// sonst kleine Schriften immer 0°
  OutWord(0/*FontFamily[font]<<8*/);
  OutString(FontName[sel],1);
  EndRecord();
  OutSelectObject(idx);
 }
 SetTextColor(color);
 if (TextAlign!=align) {
  BeginRecord(0x012E/*META_SETTEXTALIGN*/);
  OutWord(align);
  EndRecord();
  TextAlign=align;
 }
}

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

// 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(0x041B/*META_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);
   }
  }break;
  default: {
// Für Board: vollständige Koordinatentransformation und Ausgabe eines Polygons!
   BeginRecord(0x0324/*META_POLYGON*/);
   OutWord(4);
   SetPolyTr(mx,my,angle);
   OutPolyXY( x, y);	// I.
   OutPolyXY(-x, y);	// II.
   OutPolyXY(-x,-y);	// III.
   OutPolyXY( x,-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(0x061C/*META_ROUNDRECT*/);
   OutWord(Mass(2*r));
   OutWord(Mass(2*r));
   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);
   }
  }break;
  default: {
// Polygonausgabe mit Kreisapproximation: als Zwölfeck (sollte reichen)
   real z=1-sin(PI/4);	// 45°
   BeginRecord(0x0324/*META_POLYGON*/);
   OutWord(12);
   SetPolyTr(mx,my,angle);
   OutPolyXY( x    , y-r  );
   OutPolyXY( x-r*z, y-r*z);
   OutPolyXY( x-r  , y    );

   OutPolyXY(-x+r  , y    );   
   OutPolyXY(-x+r*z, y-r*z);
   OutPolyXY(-x    , y-r  );

   OutPolyXY(-x    ,-y+r  );
   OutPolyXY(-x+r*z,-y+r*z);
   OutPolyXY(-x+r  ,-y    );
   
   OutPolyXY( x-r  ,-y    );
   OutPolyXY( x-r*z,-y+r*z);
   OutPolyXY( x    ,-y+r  );
  }
 }
 EndRecord();
}

// Gedrehten(:-) Kreis ausgeben
void OutCircle(int mx, int my, int r) {
 BeginRecord(0x0418/*META_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)
 BeginRecord(0x0324/*META_POLYGON*/);
 OutWord(8);
 SetPolyTr(mx,my,angle);
 for (i=0; i<8; i++) OutPolyXY(x[i],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);
}

// Längliches Achteck anstelle Linie ausgeben (schraffierte Linien)
// Keine Unterstützung für gestrichelte Linien!
void OutOctLine(int x1, int y1, int x2, int y2, int d) {
 BeginRecord(0x0324/*META_POLYGON*/);
 d/=2;
 int i;
 real x[],y[];
 real j=tan(22.5*PI/180);	// Achteck-Ecken
 real c=x2-x1;		// (c,s) = Richtungsvektor und
 real s=y2-y1;		// zu real casten (Überlauf beim Quadrieren vermeiden)
 real l=sqrt(c*c+s*s);	// Länge (Pythagoras)
 x[0]=l+d*j;	y[0]=d;		// Achteck nach rechts bauen
 x[1]=l+d;	y[1]=d*j;
 x[2]=l+d;	y[2]=-d*j;
 x[3]=l+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;
 OutWord(8);
 SetPolyTr(x1,y1,0);
 poly_c=c/l;		// Hier: Mit möglicher Skalierung ohne Winkelangabe
 poly_s=s/l;
 for (i=0; i<8; i++) OutPolyXY(x[i],y[i]);
 EndRecord();
}

void HandlePolygon(UL_POLYGON O) {
// Polygone mit Kurven-Begrenzung können mit WMF nicht (oder höchstens
// näherungsweise) unterstützt werden.
 if (DontProcess(O.layer)) return;
 int ExtraBorder=LayerFill && LineReplace>=2 && u2mic(O.width)>=MinBreite;
 if (ExtraBorder) SetLayerPen();	// dünner Rand
 else 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!
// Das erspart hier eine Menge Arbeit.
  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 (!first) return;	// Ende != Anfang (sollte in Eagle nicht vorkommen)
// if (nk[0]<3) return;	// Entartetes Polygon (sollte auch nie vorkommen)
 if (np==1) {
  BeginRecord(0x0324/*META_POLYGON*/);
  OutWord(n);		// cptl
 }else{
  BeginRecord(0x0538/*META_POLYPOLYGON*/);
  OutWord(np);		// Anzahl Einzelpolygone
  for (n=0; n<np; n++) OutWord(nk[n]);	// Punktzahl der Einzelpolygone
 }
 SetPolyTr(0,0,0);
 O.contours(W) OutPolyXY(W.x1,W.y1);	// aptl
 EndRecord();
 if (ExtraBorder) O.contours(W) {
  OutOctLine(W.x1,W.y1,W.x2,W.y2,O.width);
 }
}

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(0x0213/*META_LINETO*/);
 OutXY(x,y);
 EndRecord();
 LastX=x; LastY=y;
}

void OutMoveTo(int x, int y) {
 if (LastX!=x || LastY!=y) {
  BeginRecord(0x0214/*META_MOVETO*/);
  OutXY(x,y);
  EndRecord();
 }
}

void OutLine(int x1, int y1, int x2, int y2) {
 OutMoveTo(x1,y1);
 OutLineTo(x2,y2);
}
// Sinus und Kosinus mit Winkelgrad
real Cos(real scale, real angle) {
 return scale*cos(angle*PI/180);
}
real Sin(real scale, real angle) {
 return scale*sin(angle*PI/180);
}

// Bogen als Polygon-Näherung ausgeben
void OutPolyArc(UL_ARC O) {
 real INCREMENT=5;			// Grad Schrittweite
 int n=ceil((O.angle2-O.angle1-0.1)/INCREMENT);	// 1 Bogen ohne Ende
 int NumPoints=n+1;			// Endpunkt dazu
 if (O.cap!=CAP_FLAT) NumPoints+=4;	// Achteck-Ende dazu
 NumPoints*=2;				// beide Seiten
 BeginRecord(0x0324/*META_POLYGON*/);
 OutWord(NumPoints);
 real a,b;
 real rad=O.width*0.5412;		// Radius für "runde" Enden
 int i;
 int r=O.radius-O.width/2;		// innerer Bogen (entgegen Uhrzeiger)
 SetPolyTr(O.xc,O.yc,0);
 for (a=O.angle1, i=0; i<n; a+=INCREMENT, i++) {
  OutPolyRA(r,a);
 }
 OutPolyRA(r,O.angle2);			// Endpunkt innen
 SetPolyTr(O.x2,O.y2,O.angle2);
 if (O.cap!=CAP_FLAT) for (b=157.5; b>0; b-=45) {
  OutPolyRA(rad,b);
 }
 r+=O.width;				// äußerer Bogen (Uhrzeiger)
 SetPolyTr(O.xc,O.yc,0);
 OutPolyRA(r,O.angle2);			// Endpunkt außen
 do{
  a-=INCREMENT;				// Stützpunkte gegenüberstehend!
  OutPolyRA(r,a);
 }while(--i);
 SetPolyTr(O.x1,O.y1,O.angle1);
 if (O.cap!=CAP_FLAT) for (b=-22.5; b>-180; b-=45) {
  OutPolyRA(rad,b);
 }
 EndRecord();
}

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) {
// Die Ausgabe von Bögen ist bei großen Strichbreiten unbefriedigend,
// weil bei Eagle glatte Kurvenenden gezeichnet werden (PS_ENDCAP_FLAT);
// WMF (Windows 3.1) unterstützt nur PS_ENDCAP_ROUND.
// In diesem Fall ist ein „Tortenstück“ (Pie) die bessere Ausgabe.
// TODO:
// Die Bogenausgabe für gestrichelte Layer erfordert
// Polygonausgabe mit einer großen Anzahl Stützstellen.
// Das ENDCAP-Problem ist hierbei keins.
  int rad;
  if (IsPie(O.arc)) {
   SetLayerPen();
   SetLayerBrush();
   BeginRecord(0x081A/*META_PIE*/);
   rad=O.arc.radius+O.width/2;
  }else if (u2mic(O.width)>=MinBreite && (
    O.arc.cap==CAP_FLAT && O.arc.width>=FlatArcReplace*O.arc.radius || LayerFill && LineReplace)) {
   SetLayerPen();
   SetLayerBrush();
   if (PiecedReplace && O.style!=WIRE_STYLE_CONTINUOUS)
     O.pieces(W) OutPolyArc(W.arc);
   else OutPolyArc(O.arc);
   return;
  }else{
   SetPen(st,O.width,LayerColor);
   BeginRecord(0x0817/*META_ARC*/);
   rad=O.arc.radius;
  }
  if (odd(Flipped&7)) {
   OutXY(O.arc.x1,O.arc.y1);
   OutXY(O.arc.x2,O.arc.y2);
  }else{
   OutXY(O.arc.x2,O.arc.y2);
   OutXY(O.arc.x1,O.arc.y1);
  }
  OutRect(O.arc.xc-rad,O.arc.yc-rad,
 	  O.arc.xc+rad,O.arc.yc+rad);
  EndRecord();
 }else{
  if (LayerFill && LineReplace && u2mic(O.width)>=MinBreite) {
   SetLayerPen();
   SetLayerBrush();
   if (PiecedReplace && O.style!=WIRE_STYLE_CONTINUOUS)
     O.pieces(W) OutOctLine(W.x1,W.y1,W.x2,W.y2,W.width);
   else OutOctLine(O.x1,O.y1,O.x2,O.y2,O.width);
  }else if (PiecedReplace>=2 && O.style!=WIRE_STYLE_CONTINUOUS
	 && u2mic(O.width)>=MinBreite) {
   SetPen(0,O.width,LayerColor);
   O.pieces(W) OutLine(W.x1,W.y1,W.x2,W.y2);
  }else{
   SetPen(st,O.width,LayerColor);
   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;
  }
  s[j++]=c;
  if (sel==2) x+=53*mult;	// Zeichenbreite für 96 Punkt "Courier New"
  else if (c<32) x+=40*mult;	// falls Sonderzeichen auftauchen (Eagle6: falsches UTF-8)
  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
  else y=O.size*0.25;
  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) {
// Sehr schade: Win16 kann keinen gespiegelten Text (für Board) ausgeben!
// Auf dem Schaltplan hat O.mirror eine andere Bedeutung als auf dem Board.
 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 &&  odd(Flipped&7) && !O.mirror	// gespiegelten Text stets als Vektoren
 || board && !odd(Flipped&7) &&  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;
 }
// Hier ggf. auf Symbol-Zeichensatz umschalten und mit TA_UPDATECP arbeiten!
 if (ProblemText(O)) dlgMessageBox(
   "Problematischer Text: »"+O.value+"«<p>Zeichen werden falsch wiedergegeben.");
 real w=O.angle;
 int a=24/*TA_BASELINE*/;
// Nur für Schaltplan: 
 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
 }
 if (Flipped&2) w+=180;			// huh? Reicht das?
 if (Flipped&4) w+=90;
 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);
 if (Debug) OutCircle(O.x,O.y,508/u2mic(1));	// 20 mil (0,5 mm) Radius
 int mx=O.x;
 int my=O.y;
 if (!(a&8)) {		// TA_TOP? Etwas nach oben korrigieren
  mx-=O.size*Sin(0.28,w);
  my+=O.size*Cos(0.28,w);
 }
 if (O.font==FONT_VECTOR && HasSymbolChars(s)) {
  a|=1/*TA_UPDATECP*/;
  OutMoveTo(mx,my);
  string s1;
  int pos=0, sublen;
  int font=O.font;
  if (a&2) s=strrev(s);	// TA_RIGHT
  for(;;) {
   s1=GetCharRun(s,pos,font&4);
   sublen=strlen(s1);
   if (pos && !sublen) break;	// abbrechen bei Ende
   if (sublen) {
    if (a&2) s1=strrev(s1);	// TA_RIGHT
    SetFont(font,O.size,w*10,col,a);
    BeginRecord(0x0521/*META_TEXTOUT*/);
    OutWord(sublen);
    OutString(s1,0);
    OutDword(0);	// Position hier nicht ausgewertet!
// Ohne diese Null hat der F3-Viewer von Total Commander ein ernstes Problem!
// Ansonsten kann man diese auch weglassen.
    EndRecord();
   }
   font^=4;		// umschalten Symbol<->ANSI-Schriftart
   pos+=sublen;
  }
  LastX=INT_MAX;	// Position ungültig
  return;
 }
 SetFont(O.font,O.size,w*10,col,a);
 BeginRecord(0x0521/*META_TEXTOUT*/);
 OutWord(l);
 OutString(s,0);
 OutXY(mx,my);
 EndRecord();
}

/* Nur für Schaltplan */
string DirNames[]={"Nc","In","Out","Io","Oc","Pwr","Pas","Hiz","Sup"};
void HandlePin(UL_PIN O) {
 if (DontProcess(LAYER_PINS)) return;
 SetLayerPen();
 SetBrush(1/*BS_NULL*/,0,0);
 OutCircle(O.x,O.y,1016/u2mic(1));
 int w=O.angle;
 int a=8|2/*TA_BOTTOM|TA_RIGHT*/;
 if (w>=180) {
  w-=180;
  a^=2/*TA_RIGHT*/;
 }
 SetFont(1/*Proportional*/,950/u2mic(1),w*10,LayerColor,a);
 string s;
 BeginRecord(0x0521/*META_TEXTOUT*/);
 OutWord(sprintf(s,"%s %d",DirNames[O.direction],O.swaplevel));
 OutString(s,0);
 OutXY(O.x,O.y);
 EndRecord();
}

/* Nur für Boards: */
void OutDrillSymbol(int x, int y, int type) {
 int D=4*254/u2mic(1);
 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;
 }
}

// Die Wärmefallen im Supply-Layer werden wesentlich von den Entwurfsregeln bestimmt.
// Da ist aus ULP-Sicht IMHO kein Zugriff möglich.
// Daher sind hier die Wärmefallen nur Demo und nicht produktionstauglich.
void OutThermal(int x, int y, int r) {
 OutOctagon(x-r,y,r>>2,90,1);
 OutOctagon(x+r,y,r>>2,90,1);
 OutOctagon(x,y-r,r>>2,0,1);
 OutOctagon(x,y+r,r>>2,0,1);
}

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);
 }
}

int SupplyLayer() {
 return 2<=CurLayer && CurLayer<=15 && nam[CurLayer][0]=='$';
}

// ermittelt Bezug zum zu verwendenden Layer für PADs
// Liefert 0 für "don't process"
int GetPadLayer(int flags) {
 if (SupplyLayer()) return CurLayer;
 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;
   case PAD_SHAPE_ANNULUS:	OutCircle(O.x,O.y,d/2); break;
   case PAD_SHAPE_THERMAL:	OutThermal(O.x,O.y,d/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) {
 if (SupplyLayer()) return CurLayer;
 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;
   case VIA_SHAPE_ANNULUS:	OutCircle(O.x,O.y,d/2); break;
   case VIA_SHAPE_THERMAL:	OutThermal(O.x,O.y,d/2); 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;
  default: if (SupplyLayer()) {
   int d=O.diameter[LAYER_TSTOP];	// Kein Zugriff auf Spacing von den Entwurfsregeln
   if (!d) d=O.diameter[LAYER_BSTOP];
   if (!d) d=O.drill;
   SetLayerPen();
   SetLayerBrush();
   OutCircle(O.x,O.y,d/2);
  }
 }
}

/* Auswahlprüfung für 4 Objekt-Anker-Typen */
int PtInSel(int x, int y) {	// alles mit Mittelpunkten
 if (!GroupOnly) return 1;
 if (GroupRect[0]<=x && x<=GroupRect[2]
 &&  GroupRect[1]<=y && y<=GroupRect[3]) return 1;
 return 0;
}

int WiInSel(UL_WIRE O) {	// zwei Enden
 return PtInSel(O.x1,O.y1)||PtInSel(O.x2,O.y2);
}

int RcInSel(UL_RECTANGLE O) {	// vier Ecken
 if (!GroupOnly) return 1;
 if (O.x1==GroupRect[0]
 &&  O.y1==GroupRect[1]
 &&  O.x2==GroupRect[2]
 &&  O.y2==GroupRect[3]
 && !O.angle) return 0;		// Auswahlrechteck selbst nie ausgeben
 int mx=(O.x1+O.x2)/2;		// Rechteck drehen (lästig!)
 int my=(O.y1+O.y2)/2;
 int x=(O.x2-O.x1)/2;
 int y=(O.y2-O.y1)/2;
 int xx[],yy[];
 real angle=O.angle*PI/180;
 real c=cos(angle);	// Drehmatrix-Elemente	| +cos +sin |
 real s=sin(angle);	//			| -sin +cos |
 xx[0]=mx+c*x-s*y; yy[0]=my+s*x+c*y;
 xx[1]=mx-c*x-s*y; yy[1]=my-s*x+c*y;
 xx[2]=mx-c*x+s*y; yy[2]=my-s*x-c*y;
 xx[3]=mx+c*x+s*y; yy[3]=my+s*x-c*y;
 return PtInSel(xx[0],yy[0])||PtInSel(xx[1],yy[1])
      ||PtInSel(xx[2],yy[2])||PtInSel(xx[3],yy[3]);
}

int PoInSel(UL_POLYGON O) {	// alle Ecken
 O.contours(W) if (PtInSel(W.x1,W.y1)) return 1;
 return 0;
}

/* Abarbeitung eines Layers (schwarzweiß: alle) */
void SchWalkLayer(UL_SHEET O) {
 string e;
 sprintf(e,"Seite %d Layer %d (%s)",O.number,CurLayer,nam[CurLayer]);
 status(e);
 O.busses(O) {
  O.segments(O) {
   O.texts(Q) if (PtInSel(Q.x,Q.y)) HandleText(Q);
   O.wires(Q) if (WiInSel(Q)) HandleWire(Q);
  }
 }
 O.circles(Q) if (PtInSel(Q.x,Q.y)) HandleCircle(Q);
 O.nets(O) {
  O.segments(O) {
   O.junctions(Q) if (PtInSel(Q.x,Q.y)) HandleJunction(Q);
   O.texts(Q) if (PtInSel(Q.x,Q.y)) HandleText(Q);
   O.wires(Q) if (WiInSel(Q)) HandleWire(Q);
  }
 }
 O.parts(O) {
  O.instances(O) if (PtInSel(O.x,O.y)) {
   O.gate.symbol.circles(O) HandleCircle(O);
   O.gate.symbol.pins(O) {
    HandlePin(O);
    O.circles(O) HandleCircle(O);
    O.texts(O) HandleText(O);
    O.wires(O) HandleWire(O);
   }
   O.gate.symbol.polygons(O) HandlePolygon(O);
   O.gate.symbol.rectangles(O) HandleRectangle(O);
   O.gate.symbol.texts(O) HandleText(O);	// Unsmashed
   O.gate.symbol.wires(O) HandleWire(O);
   O.texts(O) HandleText(O);	// Smashed
  }
 }
 O.polygons(Q) if (PoInSel(Q)) HandlePolygon(Q);
 O.rectangles(Q) if (RcInSel(Q)) HandleRectangle(Q);
 O.texts(Q) if (PtInSel(Q.x,Q.y)) HandleText(Q);
 O.wires(Q) if (WiInSel(Q)) HandleWire(Q);
}

void BrdWalkLayer(UL_BOARD O) {
 string e;
 sprintf(e,"Layer %d (%s)",CurLayer,nam[CurLayer]);
 status(e);
 O.circles(Q) if (PtInSel(Q.x,Q.y)) HandleCircle(Q);
 O.elements(Q) if (PtInSel(Q.x,Q.y)) {
  Q.package.circles(O) HandleCircle(O);
  Q.package.contacts(O) {
   HandlePad(O.pad);
   HandleSmd(O.smd);
  }
  Q.package.holes(O) HandleHole(O);
  Q.package.polygons(O) HandlePolygon(O);
  Q.package.rectangles(O) HandleRectangle(O);
  Q.package.texts(O) HandleText(O);	// unsmashed
  Q.package.wires(O) HandleWire(O);
  Q.texts(O) HandleText(O);	// smashed
 }
 O.holes(Q) if (PtInSel(Q.x,Q.y)) HandleHole(Q);
 O.polygons(Q) if (PoInSel(Q)) HandlePolygon(Q);
 O.rectangles(Q) if (RcInSel(Q)) HandleRectangle(Q);
 O.signals(O) {
  O.polygons(Q) if (IsRatsnestPoly(Q) && PoInSel(Q)) HandlePolygon(Q);
// Eventuell hier wie in Eagle5 ein gestricheltes, ungefülltes Polygon zeichnen?
  O.vias(Q) if (PtInSel(Q.x,Q.y)) HandleVia(Q);
  O.wires(Q) if (WiInSel(Q)) HandleWire(Q);
 }
 O.texts(Q) if (PtInSel(Q.x,Q.y)) HandleText(Q);
 O.wires(Q) if (WiInSel(Q)) HandleWire(Q);
}

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

void BrdWalkLayer2(UL_BOARD O, int DoLast) {
 if (!SetLayer(CurLayer)) return;
 if (!DoLast && strchr(LastLayers,CurLayer)>=0) return;	// überspringen
 BrdWalkLayer(O);
// 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(O);
  DrawHoles=0;
 }
}

/* Abarbeitung layer-weise, sogar wenn schwarz-weiß */
void BrdWalkLayers(UL_BOARD O) {
// 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(O,0);
  for (CurLayer=16; CurLayer>=1; CurLayer--) BrdWalkLayer2(O,0);
 }else{
  for (CurLayer=16; CurLayer>=1; CurLayer--) BrdWalkLayer2(O,0);
  for (CurLayer=17; CurLayer<21; CurLayer++) BrdWalkLayer2(O,0);
 }
 for (CurLayer=29; CurLayer<51; CurLayer++)  BrdWalkLayer2(O,0);
 for (CurLayer=53; CurLayer<256; CurLayer++) BrdWalkLayer2(O,0);
//Typische Text-Layer zuletzt
 for (CurLayer=51; CurLayer<53; CurLayer++)  BrdWalkLayer2(O,0);
 for (CurLayer=21; CurLayer<29; CurLayer++)  BrdWalkLayer2(O,0);
 Trans=0;	// Transparenz für LastLayers ausschalten
 for (int i=0; CurLayer=LastLayers[i]; i++)  BrdWalkLayer2(O,1);
}

int Area[];	// Bereich (X1,Y1,X2,Y2; sortiert), in WMF-Einheiten

void FatalExit(string s) {
 dlgMessageBox(s);
 exit(EXIT_FAILURE);
}

void FatalExit1(string format, string info) {
 string s;
 sprintf(s,format,info);
 FatalExit(s);
}

void OutStart(UL_AREA O) {
 if (!GroupOnly) {
  GroupRect[0]=O.x1;
  GroupRect[1]=O.y1;
  GroupRect[2]=O.x2;
  GroupRect[3]=O.y2;
 }
 for (int i=0; i<4; i++) Area[i]=Mass(GroupRect[i]);
 if   (Flipped&4)  {int t=Area[0]; Area[0]=Area[1]; Area[1]=t; t=Area[2]; Area[2]=Area[3]; Area[3]=t;}	//X<->Y tauschen (zuerst!)
 if (!(Flipped&2)) {int t=Area[1]; Area[1]=-Area[3]; Area[3]=-t;}	// Y tauschen und Vorzeichenwechsel (WMF zählt von unten)
 if   (Flipped&1)  {int t=Area[0]; Area[0]=-Area[2]; Area[2]=-t;}	// X tauschen und Vorzeichenwechsel
 if (max(Area[2]-Area[0],Area[3]-Area[1])>32767)	// Notbremse! Dateileiche bleibt liegen.
   FatalExit("Teiler zu klein; resultierende Abmessungen übersteigen 16 bit!");
 nWords=0;		// Kopf beginnen
 nMaxRecSize=0;
 ClearObjects();
// Normalen Windows-Metafile-Kopf ausgeben
 OutWord(2);		// FileType = Disk
 OutWord(9);		// HeaderSize
 OutWord(0x0100);	// Version
 nWords=8;		// noch unbekannte WORDs überspringen
 OutWord(0);		// NumOfParams
// Ohne die 3 folgenden Records haben Total Commander sowie Internet Explorer ein Problem!
 BeginRecord(0x0103/*META_SETMAPMODE*/);
 OutWord(8/*MM_ANISOTROPIC*/);
 EndRecord();
 BeginRecord(0x020B/*META_SETWINDOWORG*/);
 OutWord(Area[1]);
 OutWord(Area[0]);
 EndRecord();
 BeginRecord(0x020C/*META_SETWINDOWEXT*/);
 OutWord(Area[3]-Area[1]+1);
 OutWord(Area[2]-Area[0]+1);
 EndRecord();
 BeginRecord(0x0102/*META_SETBKMODE*/);
 OutWord(1/*TRANSPARENT*/);	// für Schrift und normale Schraffur
 EndRecord();
 if (HatchType==2) {
  BeginRecord(0x0201/*META_SETBKCOLOR*/);
  int col=BackgroundColor;
  if (Trans && PaletteType==3) col^=0xFFFFFF;
  OutDword(col);	// für Pattern-Schraffur (ist immer deckend - leider!)
  EndRecord();
 }
 if (OutBkgnd) {
  SetPen(5/*PS_NULL*/,0,0);	// Hintergrundrechteck mit leichtem Übermaß zeichnen
  SetBrush(0/*BS_SOLID*/,0,BackgroundColor);
  BeginRecord(0x041B/*META_RECTANGLE*/);
  int o=254/u2mic(1);	// oversize: 10 mil = 0,25 mm
  OutRect(GroupRect[0]-o,GroupRect[1]-o,GroupRect[2]+o,GroupRect[3]+o);
  EndRecord();
 }
}

void OutWrite() {
 int PHeader[];		// Vorhaltung zur Berechnung der Prüfsumme
 int i;
 BeginRecord(0/*META_EOF*/);
 EndRecord();
 status("Datei-Ausgabe ("+FName+")");
// Aldus-Placeable-Metafile-Kopf ausgeben
 PHeader[0]=0xCDD7;		// DWORD Key
 PHeader[1]=0x9AC6;
 /*0*/				// WORD Handle
 PHeader[3]=Area[0];		// SHORT Left
 PHeader[4]=Area[1];		// SHORT Top
 PHeader[5]=Area[2]+1;		// SHORT Right
 PHeader[6]=Area[3]+1;		// SHORT Bottom
// Manche Programme interpretieren diese Grenzen als inklusive,
// manche andere als exklusive unteren rechten Rand.
// Das +1 vermeidet Abschneideprobleme.
 PHeader[7]=round(25400/Teiler/Scale);	// ergibt 1:1-Import in Word
// Beim Standard-Teiler von 25,4 µm/Pixel ergibt das 1000 dpi
// Eagle <6 hat 254000 dpi, also 256 dot per mil und 10 dot per mm
// Eagle 6 hat 8128000 dpi, das 32-fache von Eagle <6
// Die Angaben im SDK scheinen allesamt irreführend zu sein!
// Richtig (und auch logisch) scheint: <inch> WMF-Einheiten ergeben 1 Zoll.
// Es ist also eine dpi-Angabe, betrachtet man eine WMF-Einheit als Pixel.
 /*0,0*/			// DWORD Reserved
 /*0*/				// WORD Checksum
 for (i=0; i<10; i++) PHeader[10]^=PHeader[i];	// Prüfsumme ermitteln
 for (i=0; i<11; i++) PrintWord(PHeader[i]);
// Windows-Metafile-Kopfdaten ergänzen
 FileData[3]=nWords&0xFFFF;	// FileSize
 FileData[4]=nWords>>16;
 FileData[5]=nHandles;		// NumOfObjects
 FileData[6]=nMaxRecSize&0xFFFF;// MaxRecordSize
 FileData[7]=nMaxRecSize>>16;
 for (i=0; i<nWords; i++) PrintWord(FileData[i]); 	// Daten ausspucken
}

/* Ausgabe einer Metadatei */
void SchPrintMF(UL_SHEET O) {
 OutStart(O.area);
 SchWalkLayers(O);
 OutWrite();
}

void BrdPrintMF(UL_BOARD O) {
 OutStart(O.area);
 BrdWalkLayers(O);
 OutWrite();
}

// 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 FatalExit1("Unbekannte Schriftartbezeichnung %s!",val);
 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:
  FatalExit1("Unbekannter Schriftstil %s! (Nur »b« [fett] oder »i« [kursiv])",""+suffix[p]);
 }
}

int gridunit;	// GRID_UNIT_xxx

void ParseGroupRect(string val) {
 string s[];
 if (strsplit(s,val,',')!=4) FatalExit1(
   "Ungültige Rechteckdefinition '%s'! (Vier kommaseparierte Zahlen)",val);
 int fak=1;
 switch (gridunit) {
  case GRID_UNIT_MIC:	fak=u2mic(1); break;	// 10 (Werte für Eagle4)
  case GRID_UNIT_MM:	fak=u2mm(1);  break;	// 10000
  case GRID_UNIT_MIL:	fak=u2mil(1); break;	// 254
  case GRID_UNIT_INCH:	fak=u2inch(1);break;	// 254000, typisch Schaltplan
 }
 for (int i=0; i<4; i++) GroupRect[i]=round(strtod(s[i])*fak);
 if (GroupRect[0]>=GroupRect[2] || GroupRect[1]>=GroupRect[3]) FatalExit1(
   "Falsche Reihenfolge der Zahlen bei Rechteckdefinition '%s'!",val);
 GroupOnly=1;
}

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

string AutomaticFileName(void) {
 string e;
 if (board) {
  int i=strrchr(SName,'.');
  if (i<0) i=strlen(SName);
  if (isalpha(SName[i-1])) e="-";	// Bindestrich um es vom Dateinamen abzugrenzen
  e+=odd(Flipped&7)?"b":"t";		// "b" für "bottom" und "t" für "top", später "d" für "Drill"
 }
 e+=".wmf";
 return filesetext(SName,e);
}

// 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.
// Der Dateiname "*" spezifiziert den Standard-Dateinamen.
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=="WMF-Unit") Teiler=strtod(val);
   else if (key=="MinWidth") MinBreite=strtod(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=="LineReplace" || key=="l") LineReplace=strtol(val);
   else if (key=="FlatArcReplace" || key=="c") FlatArcReplace=strtod(val);
   else if (key=="PiecedReplace") PiecedReplace=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 if (key=="Group" || key=="g") ParseGroupRect(val);
   else if (key=="Scale" || key=="s") Scale=strtod(val);
   else FatalExit1("Unbekannte Variable %s!",key);
  }else{	// Dateiname
   if (FName) FatalExit1("Mehrere Dateinamen (%s) sind nicht möglich!",arg);
   if (arg=="*") FName=AutomaticFileName();
   else{
    FName=arg;
    if (FName[0]!='\\' && FName[0]!='/' && FName[1]!=':')
// Relative Pfade sollen sich aufs Schaltplan- bzw. Board-Verzeichnis beziehen.
// Ohne diese Aktion wäre es relativ zum Eagle/bin-Verzeichnis.
      FName=filedir(SName)+FName;
   }
  }
 }
 if (!Dialog && !CalcLastLayers()) exit(EXIT_FAILURE);
}

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

// Informationen über Schaltplan und Layer einsammeln
if (schematic) {
 schematic(O) {
  gridunit=O.grid.unit;
  O.layers(O) if (O.visible) {
   col[O.number]=O.color;	// Layer vermerken
   fil[O.number]=O.fill;	// Füll-Stile (1 = komplett gefüllt)
   nam[O.number]=O.name;	// Name (für Status-Anzeige)
   if (O.fill!=1) NumHatched++;
  }
  SName=O.name;
  SchCountFonts(O);
 }
 HatchType=0;
 Scale=1.3888888;
// ausprobiert für gute 1:1-Bildschirmdarstellung, bspw. im IE6
// Beim Standard-Teiler von 25,4 µm/Pixel ergibt das 720 dpi
}else board(O) {
 gridunit=O.grid.unit;
 O.layers(O) if (O.visible) {
  col[O.number]=O.color;	// Layer vermerken
  fil[O.number]=O.fill;		// Füll-Stile (1 = komplett gefüllt)
  nam[O.number]=O.name;		// Name (für Status-Anzeige und Supply-Layer)
  if (O.fill!=1) NumHatched++;
  if (O.fill==2 || O.fill==4 || O.fill==5 || O.fill>=9) NumExact++;
 }
 SName=O.name;
 BrdCountFonts(O);
 CheckRipupPoly=1;
	// diese Layer sind wesentliches Kriterium zur Voreinstellung
 Flipped=col[LAYER_BPLACE] && !col[LAYER_TPLACE];
}
if (GroupOnly>1) GroupOnly=0;	// mehrere Rechtecke gefunden: Keine Selektion möglich
if (NumExact) HatchType=2;	// Vorgabe wenn solche Schraffuren sichtbar (nur Board)
OnSetPaletteType();

// Kommandozeile auswerten
ParseCommandLine();
if (CheckRipupPoly) board(O) if (BrdAreRipupPolys(O)) 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 (board) {
 Rotated=Flipped>>2&1;
 TopDown=Flipped>>1&1;
 Flipped&=1;
}
if (Dialog && DialogBox()!=1) exit(EXIT_FAILURE);
if (board) {
 if (TopDown) Flipped|=2;
 if (Rotated) Flipped^=5;	// Rotation nach links (nach Spiegelung)
}else switch (Flipped) {
 case 1: Flipped=5; break;	// 90° links
 case 2: Flipped=3; break;	// 180° = 2x Flip
 case 3: Flipped=6; break;	// 90° rechts
}

// 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=AutomaticFileName();
 FName=dlgFileSave("Speichern Windows-Metadatei",FName,"*.wmf");
}
if (!FName) exit(EXIT_FAILURE);
  
if (AllPages) {
 schematic(O) O.sheets(O) {
  string e;
  sprintf(e,"-%d%s",O.number,fileext(FName));
  output(filesetext(FName,e),"wb") SchPrintMF(O);
 }
}else{
 if (board) board(O) {
  output(FName,"wb") BrdPrintMF(O);
 }else sheet(O) {
  output(FName,"wb") SchPrintMF(O);
 }
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded