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

#usage "en: Output of a <b>Sch</b>ematic (or Board) as "
	"<b>E</b>nhanced Windows <b>M</b>eta <b>F</b>ile "
	"for convenient embedding into office documents and searchable strings in resulting PDFs<p>\n"
	"Only visible layers will be processed.<p>"
	"The resulting EMF file requires a 32-bit Windows for processing. "
	"Use SCH2WMF if you need Win3x/9x/Me compatibility.<p>"
	"Command-line arguments are: [arguments] [filename]<br>\n"
	"Possible arguments see SCH2EMF.TXT.\n"
	"<hr>"
	"<author>Author: <A href='mailto:henrik.haftmann@e-technik.tu-chemnitz.de'"
	">Henrik Haftmann</A>,<br>"
	"latest version: <A href=http://www.tu-chemnitz.de/~heha/hs_freeware/sch2wmf/"
	">TU Chemnitz</A></author>",
       "de: 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.<p>"
	"Die entstehende EMF-Datei ist nur unter 32-bit-Windows verarbeitbar. "
	"Für Kompabilität zu Win3x/9x/Me nehmen Sie bitte SCH2WMF.<p>"
	"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</A>,<br>"
	"neueste Version: <A href='http://www.tu-chemnitz.de/~heha/hs_freeware/sch2wmf/'"
	">TU Chemnitz</A></author>"
#require 6.00

string I18N[] = {
 "en	de",
 "+Ok	+Ok",
 "-Cancel	-Abbrechen",
 /*3*/	"Using vector font results in a <b style=color:darkgreen>perfectly scaleable</b> output file.<p>"
 	"But the resulting file is <b style=color:darkred>much larger in size</b> "
 	"and texts are <b style=color:darkred>not searchable</b> because vectorized.<p>"
 	"The settings <b>Bold</b> and <b>Italic</b> are not applicable.\t"
	"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.",
 "Vector	Vektor",
 "Bold	Fett",
 "Italic	Kursiv",
 /*7*/	"(transparent)	(transparent)",
 /*8*/	"(opaque)	(deckend)",
 /*9*/	"EMF export options	EMF-Export-Optionen",
 /*10*/	"file name: <b>%s</b>	Dateiname: <b>%s</b>",
 /*11*/	"Output colors   (see Eagle palette)	Ausgabe-&Farben   (siehe Eagle-Palette)",
	"Black on white (for monochrome printers)	Schwarz auf Weiß (Schwarzweiß)",
	"Colors on black	Bunt auf Schwarz",
	"Colors on white	Bunt auf Weiß",
	"Colors on colored background	Bunt auf farbigem Hintergrund",
 /*16*/	"Font &replacement   (used fonts are listed here)	"
	"Schrift-&Ersetzung   (verwendete Schriften gelistet)",
 /*17*/	"<b>Note:</b> Vector font on copper will be never replaced	"
	"<b>Hinweis:</b> Vektorschrift auf Kupfer wird niemals ersetzt.",
 /*18*/	"&Hatch style (%d hatched layer(s) present)	"
	"Füll&muster (%d schraffierte(r) Layer vorhanden)",
 /*19*/	"Filled	Ausgefüllt",
 /*20*/	"Windows hatch style (6 predefined: = || // \\\\\\\\ ++ xx)	"
	"Windows-Füllmuster (hat nur 6 Muster: = || // \\\\\\\\ ++ xx)",
 /*21*/	"Exact hatch like in Eagle	"
	"Exakte Füllmuster wie in Eagle",
 /*22*/	"Page %d Layer %d	"
	"Seite %d Layer %d",
 /*23*/	"Layer %d"
};
int Language = strstr(I18N[0], language()) / 3;
string tr(string s) {
 string t = lookup(I18N, s, Language);
 return t ? t : s;
}
string LoadString(int n) {
 string a[];
 n=strsplit(a,I18N[n],'\t');
 return a[Language<n?Language:0];
}
string LoadStringS(int n, string subst) {
 string ret=LoadString(n);
 sprintf(ret,ret,subst);
 return ret;
}
string LoadStringI(int n, int i1) {
 string ret=LoadString(n);
 sprintf(ret,ret,i1);
 return ret;
}
string LoadStringII(int n, int i1, int i2) {
 string ret=LoadString(n);
 sprintf(ret,ret,i1,i2);
 return ret;
}

// 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 aber undokumentiert, und mir ist kein geeignetes Importprogramm bekannt.
// Deshalb erfolgt hier die Pseudotransparenz, wie gehabt, mittels ROP-Kodes.

// 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 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[]={tr("Vector"),"Proportional","Fixed"};	// Eagle-Namen
string FontName[]={tr("Vector"),"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.

// 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(LoadString(3));
   SetFontLabel();
  }
  dlgSpacing(10);
 }
 dlgCell(idx,2) dlgCheckBox(tr("Bold"),FontBold[idx]) SetFontLabel();
 dlgCell(idx,3) dlgCheckBox(tr("Italic"),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=LoadString(Trans?7:8);
 status(tr("Dialog"));
 return dlgDialog(LoadString(9)) {
  dlgLabel(LoadStringS(10,SName));
  dlgGroup(LoadString(11)) {
   dlgRadioButton(LoadString(12),PaletteType) OnSetPaletteType();
   dlgRadioButton(LoadString(13),PaletteType) OnSetPaletteType();
   dlgRadioButton(LoadString(14),PaletteType) OnSetPaletteType();
   dlgRadioButton(LoadString(15),PaletteType) OnSetPaletteType();
  }
  dlgGroup(LoadString(16)) {
   dlgGridLayout{
    SetFontLabel();
    for (int i=0; i<3; i++) if (NumFont[i]) FontSubstLine(i);
   }
   if (NumCopperVector) {
    dlgSpacing(4);
    dlgLabel(LoadString(17));
   }
  }
  if (NumHatched) {
   dlgGroup(LoadStringI(18,NumHatched)) {
    dlgRadioButton(LoadString(19),HatchType);
    dlgRadioButton(LoadString(20),HatchType);
    dlgHBoxLayout{
     dlgRadioButton(LoadString(21),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 »!«",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(tr("-Cancel")) dlgReject();
  }
 };
}

void CountFont(UL_TEXT O) {
 if (!col[O.layer]) return;	// unsichtbare Schrift nicht mitzählen
 NumFont[O.font]++;
 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.frames(FR) { 	// ab Eagle 5
   FR.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[]={
'?',	'“',	'?',	'?',	'?',	'?',	'?',	'?',		/*80*/
'?',	'?',	'?',	'?',	'?',	'',	'?',	'e',
'€',	'?',	'?',	'?',	'?',	'?',	'?',	'?',		/*90*/
'?',	'?',	'?',	'?',	'?',	'T',	'?',	'?',
'_',	'¡',	'\\',	'£',	'g',	'[',	'',	'!',		/*A0*/
'„',	'U',	'ª',	'«',	'¬',	'a',	'',	'',
'°',	'±',	'²',	'',	')',	'µ',	' ',	'·',		/*B0*/
'H',	'→',	'º',	'»',	'¼',	'½',	'd',	'¿',
'V',	'a',	'b',	'_',	'Ä',	'Å',	'Æ',	'Ç',		/*C0*/
'X',	'É',	'e',	'Y',	'',	'S',	'k',	'j',
'd',	'Ñ',	'À',	'±',	'“',	'Ã',	'Ö',	'§',		/*D0*/
'Ø',	'´',	'¸',	'©',	'Ü',	'ø',	'¦',	'ß',
'à',	'á',	'â',	'^',	'ä',	'å',	'æ',	'ç',		/*E0*/
'è',	'é',	'ê',	' ',	'ì',	'í',	'î',	'ï',
'h',	'ñ',	'ò',	'ó',	'ô',	'£',	'ö',	'÷',		/*F0*/
'ø',	'ù',	'ú',	'û',	'ü',	'',	'Ä',	' '};
int ToUnicode(char c, int font) {
 if (font!=FONT_VECTOR) return c;
 if (c<128) return c;
 if (c>=256) return 0;
 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;
}
// Eine ganze Reihe von Programmen kommt mit gespiegeltem Mapping (im Header) nicht zurecht.
// Deshalb werden die Koordinaten immer steigend von links oben nach rechts unten ausgegeben.
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), Größe=%d",
     FontName[OD_param1[idx]&3],OD_param1[idx]&3,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*/;
}

real WorldRotate;
real WorldC=1, WorldS;

// Weltkoordinatenrotation setzen für 0 <= angle < 360;
// zusätzlich Spiegelung (für Text) setzen für -360 <= angle < 0
void SetRotate(real angle) {
 if (WorldRotate!=angle) {
  WorldRotate=angle;
  BeginRecord(35/*EMR_SETWOLRDTRANSFORM*/);
  angle*=PI/180;
  WorldC=cos(angle);	// Drehmatrix-Elemente	| +cos +sin |
  WorldS=sin(angle);	//			| -sin +cos |
  OutFloat(angle<0?-WorldC:WorldC);
  OutFloat(angle<0?WorldS:-WorldS);
  OutFloat(WorldS);
  OutFloat(WorldC);
  OutFloat(0);
  OutFloat(0);
  EndRecord();
 }
}

// Mittelpunkt bzgl. rotiertem Koordinatensystem berechnen.
// Statt Rückgabewerte setzt diese Funktion die globalen
// Hilfsvariablen tx und ty
int tx,ty;
void CalcCenter(int mx, int my) {
 tx=WorldC*mx+WorldS*my;
 ty=-WorldS*mx+WorldC*my;
 if (WorldRotate<0) tx=-tx;
}

// 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 color,int align) {
 int sel=FontSel[font];
 if (!sel) return;	// VEKTOR - das geht hier nicht!
 size*=1.6;
 int idx=GetNewHandleIndex(ObjFont,font,size,0);
 if (Debug>1) {
  string msg;
  sprintf(msg,"SetFont(nr=%d,ali=%d,size=%d,%06X) idx=%d nHandles=%d",
    font,align,size,color,idx,nHandles);
  dlgMessageBox(msg);
 }
 if (idx) {
  BeginRecord(82/*EMR_EXTCREATEFONTINDIRECTW*/);
  OutDword(idx);
  OutDword(size);	//nHeight
  OutDword(0);		//nWidth
  OutDword(0);		//nEscapement (Schriftrichtung per SetWorldTransform)
  OutDword(0);		//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: {
   SetRotate(0);
   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: {
   SetRotate(angle);
   CalcCenter(mx,my);
   BeginRecord(43/*EMR_RECTANGLE*/);
   OutRect(tx-x,ty-y,tx+x,ty+y);
   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) {
 x/=2; y/=2; r+=r;	// jetzt Abstand vom Ursprung; Durchmesser
 SetRotate(angle);
 CalcCenter(mx,my);
 BeginRecord(44/*EMR_ROUNDRECT*/);
 OutRect(tx-x,ty-y,tx+x,ty+y);
 OutDword(r);
 OutDword(r);
 EndRecord();
}

// Gedrehten(:-) Kreis ausgeben
void OutCircle(int mx, int my, int r) {
 CalcCenter(mx,my);
 BeginRecord(42/*EMR_ELLIPSE*/);
 OutRect(tx-r,ty-r,tx+r,ty+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)
 SetRotate(angle);
 CalcCenter(mx,my);
 BeginRecord(3/*EMR_POLYGON*/);
 OutBBox();
 OutDword(8);
 for (i=0; i<8; i++) {
  OutMassX(tx+x[i]);
  OutMassY(ty+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. So läuft es mit freigerechneten Signalpolygonen.
// 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
 }
 SetRotate(0);
 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);
 SetRotate(0);
 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];
}

// Ein initialisiertes "const int"-Array zu initialisieren ist in ULP (und auch in C) viel aufwändiger!
// Die etwaigen Zeichenbreiten sind so als Zahlen zwischen 32 und 126 abrufbar.
// Der Einfachheit gilt das nur für ISO-Latin-1, folglich nur für westeuropäische Unicode-Zeichen.
// Natürlich ist das alles eine Schätzerei, weil die Zeichenbreiten bei TrueType konstruktionsbedingt schwanken.
// Liegt ja nur daran, dass sich M$ permanent weigert, das Schriftattribut "Überstreichen" einzuführen.
// Wieso nicht? Eben, kommt im Bankwesen ($$) nicht vor. In HTML (CSS) ist's ja nun endlich drin.
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) {
 if (!DoNegate) return O.value;
 string s=O.value;
 int i,j,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 if (c<32 || c>255) x+=40*mult;	// Exotische Zeichen mit einer mitteren Schätzbreite verrechnen
  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&8/*TA_BOTTOM*/) {
   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) {
// TODO: Globale Textleserichtung verarbeiten
 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
// Hier sollte eigentlich Eagle die Überstreichungen generieren, ist das so?
  O.wires(W) HandleWire(W);		// Text als Striche ausgeben
  return;
 }
 real w=O.angle;
 int a;
 switch (O.align) {
// Dieses Eagle wimmelt von Bugs!
  case 0/*ALIGN_BOTTOM_LEFT*/  : a=0x08; break;	// TA_LEFT|TA_BOTTOM
  case 1/*ALIGN_BOTTOM_CENTER*/: a=0x0E; break;	// TA_CENTER|TA_BOTTOM
  case 2/*ALIGN_BOTTOM_RIGHT*/ : a=0x0A; break;	// TA_RIGHT|TA_BOTTOM
  case 3/*ALIGN_CENTER_LEFT*/  : a=0x18; break;	// TA_LEFT|TA_BASELINE
  case 4/*ALIGN_CENTER*/       : a=0x1E; break;	// TA_CENTER|TA_BASELINE
  case 5/*ALIGN_CENTER_RIGHT*/ : a=0x1A; break;	// TA_RIGHT|TA_BASELINE
  case 6/*ALIGN_TOP_LEFT*/     : a=0x00; break;	// TA_LEFT|TA_TOP
  case 7/*ALIGN_TOP_CENTER*/   : a=0x06; break;	// TA_CENTER|TA_TOP
  case 8/*ALIGN_TOP_RIGHT*/    : a=0x02; break;	// TA_RIGHT|TA_TOP
 }
 if (schematic && O.mirror) {
  if (w==90 || w==270) a^=8;		// TA_TOP <-> TA_BOTTOM
  else a^=2;				// TA_LEFT <-> 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^=8^2;				// Ausrichtung genau anders herum
 }
 if (board && Flipped && !O.mirror	// gespiegelter Text?
  || board && !Flipped && O.mirror) {
  w=-w;
  if (!w) w=-360;
 }
 if (Debug) OutCircle(O.x,O.y,mil2u(10));
 SetRotate(w);
 int x=O.x;
 int y=O.y;
 if (a&16) {		// Mittelpunkt verschieben (Das Dilemma: GDI bietet kein TA_VCENTER)
 }
 CalcCenter(x,y);
 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 (a&0x04) a|=0x06;	// Alignment für Windows korrigieren
 if (a&0x18) a|=0x18;	// Aus TA_BOTTOM stets TA_BASELINE machen
 SetFont(O.font,O.size,col,a);
// Eine Zusammenfassung mehrerer Texte zu PolyTextOut ist naheliegend...
 BeginRecord(84/*EMR_EXTTEXTOUTW*/);
 OutBBox();	// rclBounds (unbenutzt)
 OutDword(2);	// iGraphicsMode=GM_ADVANCED
 OutDword(0);	// exScale (unbenutzt bei GM_ADVANCED)
 OutDword(0);	// eyScale
 OutMassX(tx);	// ptlReference
 OutMassY(ty);
 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=mil2u(40);
 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 (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;
 }
}

void HandleAttribute(UL_ATTRIBUTE O) {
 if (O.display) HandleText(O.text);
}
void HandleDimension(UL_DIMENSION O) {
 O.texts(O) HandleText(O);
 O.wires(O) HandleWire(O);	// Warum zum Geier kein Polygon für die Pfeile!?
}
void HandleFrame(UL_FRAME O) {
 O.texts(O) HandleText(O);
 O.wires(O) HandleWire(O);
}
void HandleLabel(UL_LABEL O) {
 HandleText(O.text);
 O.wires(O) HandleWire(O);	// nur bei xref
}
void HandleSymbol(UL_SYMBOL O) {
 O.circles(O) HandleCircle(O);
 O.dimensions(O) HandleDimension(O);
 O.frames(O) HandleFrame(O);
 O.rectangles(O) HandleRectangle(O);
 O.pins(O) {
  O.circles(O) HandleCircle(O);
  O.texts(O) HandleText(O);
  O.wires(O) HandleWire(O);
 }
 O.polygons(O) HandlePolygon(O);
 O.texts(O) HandleText(O);	// Unsmashed
 O.wires(O) HandleWire(O);
}

/* Abarbeitung eines Layers (schwarzweiß: alle) */
void SchWalkLayer(UL_SHEET O) {
 status(LoadStringII(22,O.number,CurLayer));
 O.busses(O) {
  O.segments(O) {
   O.labels(O) HandleLabel(O);
   O.wires(O) HandleWire(O);
  }
 }
 O.circles(O) HandleCircle(O);
 O.dimensions(O) HandleDimension(O);
 O.frames(O) HandleFrame(O);
 O.parts(O) {		// Dokumentationsfehler!
  O.instances(O) {
   O.attributes(O) HandleAttribute(O);
   HandleSymbol(O.gate.symbol);
   O.texts(O) HandleText(O);	// Smashed
   O.xrefs(O) HandleSymbol(O.symbol);
  }
 }
 O.nets(O) {
  O.segments(O) {
   O.junctions(O) HandleJunction(O);
   O.labels(O) HandleLabel(O);
   O.wires(O) HandleWire(O);
  }
 }
 O.polygons(O) HandlePolygon(O);
 O.rectangles(O) HandleRectangle(O);
 O.texts(O) HandleText(O);
 O.wires(O) HandleWire(O);
}

void BrdWalkLayer(UL_BOARD O) {
 status(LoadStringI(23,CurLayer));
 O.circles(O) HandleCircle(O);
 O.dimensions(O) HandleDimension(O);
 O.elements(O) {
  O.attributes(O) HandleAttribute(O);
  O.package.circles(O) HandleCircle(O);
  O.package.contacts(O) {
   HandlePad(O.pad);
   HandleSmd(O.smd);
  }
  O.package.dimensions(O) HandleDimension(O);
  O.package.frames(O) HandleFrame(O);
  O.package.holes(O) HandleHole(O);
  O.package.polygons(O) HandlePolygon(O);
  O.package.rectangles(O) HandleRectangle(O);
  O.package.texts(O) HandleText(O);	// unsmashed
  O.package.wires(O) HandleWire(O);
  O.texts(O) HandleText(O);	// smashed
 }
 O.frames(O) HandleFrame(O);
 O.holes(O) HandleHole(O);
 O.polygons(O) HandlePolygon(O);
 O.rectangles(O) HandleRectangle(O);
 O.signals(O) {
  O.polygons(O) {	// Bug im ULP-Interpreter (6.1.0): Zeigt sich, wenn man die geschweiften Klammern entfernt!
   if (IsRatsnestPoly(O)) HandlePolygon(O);
// Eventuell hier ein gestricheltes, ungefülltes Polygon zeichnen?
  }
  O.vias(O) HandleVia(O);
  O.wires(O) HandleWire(O);
 }
 O.texts(O) HandleText(O);
 O.wires(O) HandleWire(O);
}

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

int Area[];		// Bereich (X1,Y1,X2,Y2), in Eagle-Einheiten (links,unten,rechts,oben)
string Description;	// EMF-Beschriftung

void OutStart() {
 nDwords=0;		// Kopf beginnen
 nRecords=0;
 ClearObjects();
// Normalen EMF-Kopf ausgeben
 int l=Flipped?-Area[2]:Area[0];
 int t=-Area[3];
 int r=Flipped?-Area[0]:Area[2];
 int b=-Area[1];
 BeginRecord(1/*EMR_HEADER*/);
 OutDword(l);		// rclBounds (riesige Zahlenwerte)
 OutDword(t);		// bei ACDSee dummerweise ein Vorschlag
 OutDword(r);		// für die Bitmap-Zeichenfläche
 OutDword(b);
// 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.
 OutDword(u2mm(l)*100);		// rclFrame [HIMETRIC = 10µm]
 OutDword(u2mm(t)*100);
 OutDword(u2mm(r)*100);
 OutDword(u2mm(b)*100);
 OutDword(0x464D4520);		// dSignature	ENHMETA_SIGNATURE
 OutDword(0x00010000);		// nVersion
 nDwords+=3;			// nBytes, nRecords, nHandles übergehen
 l=strlen(Description);
 OutDword(l);			// nDescription
 OutDword(l ? 22*4 : 0);	// offDescription (UTF-16)
 OutDword(0);			// nPalEntries
 OutDword(mm2u(1));		// szlDevice [pel], diese Angabe muss stimmen
 OutDword(mm2u(1));
 OutDword(1);			// szlMillimeters [mm], muss mit rclFrame korrelieren
 OutDword(1);
 OutString(Description,-1);	// Beschreibung
 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();
 }
 if (OutBkgnd) {
  SetPen(5/*PS_NULL*/,0,0);
  SetBrush(0/*BS_SOLID*/,0,BackgroundColor);
  BeginRecord(43/*EMR_RECTANGLE*/);
  OutRect(Area[0],Area[1],Area[2],Area[3]);
  EndRecord();
 }
}

void OutWrite() {
 if (Debug) ShowHandleTable();
 SetRotate(0);		// falls es Programme gibt, die kein SaveDC aufrufen?
 BeginRecord(14/*EMR_EOF*/);
 OutDword(0);		// nPalEntries
 OutDword(0);		// offPalEntries
 OutDword(0);		// nSizeLast
 EndRecord();
 status("Datei-Ausgabe ("+FName+")");
 FileData[12]=nDwords*4;// nBytes
 FileData[13]=nRecords;	// nRecords
 FileData[14]=nHandles;	// nHandles + sReserved
 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.
}

/* Ausgabe einer Metadatei */
void SchPrintMF(UL_SHEET S) {
 Area[0]=S.area.x1;
 Area[1]=S.area.y1;
 Area[2]=S.area.x2;
 Area[3]=S.area.y2;
 Description=S.headline;
 OutStart();
 SchWalkLayers(S);
 OutWrite();
}

void BrdPrintMF(UL_BOARD B) {
 Area[0]=B.area.x1;
 Area[1]=B.area.y1;
 Area[2]=B.area.x2;
 Area[3]=B.area.y2;
 Description=B.headline;
 OutStart();
 BrdWalkLayers(B);
 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{
  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=="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 (!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: ASCII (7 bit)8