#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 5.01
// Für EAGLE 4.x nehmen Sie bitte SCH2WMF.
// For EAGLE 4.x, use SCH2WMF instead.
// 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[]={"Vektor","Proportional","Fixed"}; // Eagle-Namen
string FontName[]={"Vektor","Arial","Courier New","Times New Roman"};
string FontLabel[];
// Normalerweise ist für EMF-Dateien kein Teiler notwendig.
// Allerdings kommt Windows 9x/Me nicht mit 32-bit-Koordinaten zurecht.
// berechnet aus String LastLayers das Char-Array LastLayers(!)
// meckert und liefert 0 bei Fehler, sonst !=0
int CalcLastLayers(void) {
string A;
int i,j,layer,layere;
for (i=j=0; LastLayers[i]; i++) {
// Startwert einlesen
layer=strtol(strsub(LastLayers,i));
if (layer<=0 || layer>255) break; // Fehler
while ('0'<=LastLayers[i] && LastLayers[i]<='9') i++;
// ggf. Endwert einlesen
if (LastLayers[i]=='-') {
i++;
layere=strtol(strsub(LastLayers,i));
if (layere<=0 || layere>255) break; // Fehler
while ('0'<=LastLayers[i] && LastLayers[i]<='9') i++;
}else layere=layer;
// auf- oder absteigend LastLayers füllen
if (layer<=layere) for (; layer<=layere; layer++) A[j++]=layer;
else for (; layer>=layere; layer--) A[j++]=layer;
// Folgezeichen auswerten
if (!LastLayers[i]) break; // Ende
if (LastLayers[i]!=','
|| !LastLayers[i+1]) break; // Fehler, wenn kein Komma oder ",\0"
}
A[j]=0; // terminieren
if (LastLayers[i]) { // Nicht am Ende angekommen?
string s;
sprintf(s,"!Falsche Syntax für Layer-Angabe!<pre style=color:red><b>%s\n%*s^---hier!",
LastLayers,i,"");
dlgMessageBox(s);
return 0;
}
LastLayers=A;
return 1;
}
/***********************
*** Dialog-Routinen ***
***********************/
// globale Variablen: Farben und Füllstile per Layer
int col[],fil[];
// liefert TRUE, wenn Layers aktiv sind, die von unten zu sehen sind
// (d.h. solche, auf denen platzierter Text tatsächlich gespiegelt erscheint)
int AnyBottomLayers() {
return col[LAYER_BOTTOM]
+ col[LAYER_BPLACE]
+ col[LAYER_BORIGINS]
+ col[LAYER_BNAMES]
+ col[LAYER_BVALUES]
+ col[LAYER_BSTOP]
+ col[LAYER_BCREAM]
+ col[LAYER_BFINISH]
+ col[LAYER_BGLUE]
+ col[LAYER_BTEST]
+ col[LAYER_BKEEPOUT]
+ col[LAYER_BRESTRICT]
+ col[LAYER_BDOCU];
}
// Font-Namen nur für SetFontLabel (Leerzeichen führen zu Fehlern)
string FontSty[]={"Verdana","Arial","Courier","Times"};
void SetFontLabel(void) {
for (int idx=0; idx<3; idx++) sprintf(FontLabel[idx],
"<span style=font-family:%s;font-weight:%d;font-style:%s>%s</span>:",
FontSty[FontSel[idx]],
FontBold[idx]?700:400,
FontItalic[idx]?"italic":"normal",
FontSrc[idx]);
}
void FontSubstLine(int idx) {
dlgCell(idx,0) {dlgLabel(FontLabel[idx],1); dlgSpacing(10);}
dlgCell(idx,1) {
dlgComboBox(FontName,FontSel[idx]) {
if (!FontSel[0]) dlgMessageBox(
"Die Vektor-Schriftart ergibt <b style=color:darkgreen>layout-treue</b> Ausgabedateien.<p>"+
"Allerdings ist die WMF-Datei <b style=color:darkred>wesentlich größer</b> "+
"und <b style=color:darkred>nicht durchsuchbar</b> nach Text.<p>"+
"Die Einstellungen <b>Fett</b> und <b>Kursiv</b> sind wirkungslos.");
SetFontLabel();
}
dlgSpacing(10);
}
dlgCell(idx,2) dlgCheckBox("Fett",FontBold[idx]) SetFontLabel();
dlgCell(idx,3) dlgCheckBox("Kursiv",FontItalic[idx]) SetFontLabel();
dlgCell(idx,4) dlgStretch(1);
}
void OnSetPaletteType(void) {
switch (PaletteType) {
case 1: {
OutBkgnd=1;
if (board) Trans=1; // voreinstellen wie Eagle
if (NumExact) HatchType=2;
}break;
case 3: {
OutBkgnd=!Trans; // voreinstellen wie Eagle (wenn Trans=0)
}break;
}
}
int DialogBox(void) {
string DeckLabel=Trans?"(transparent)":"(deckend)";
status("Dialog");
return dlgDialog("EMF-Export-Optionen") {
dlgLabel("Dateiname: <b>"+SName+"</b>");
dlgGroup("Ausgabe-&Farben (siehe Eagle-Palette)") {
dlgRadioButton("Schwarz auf Weiß (Schwarzweiß)",PaletteType) OnSetPaletteType();
dlgRadioButton("Bunt auf Schwarz",PaletteType) OnSetPaletteType();
dlgRadioButton("Bunt auf Weiß",PaletteType) OnSetPaletteType();
dlgRadioButton("Bunt auf farbigem Hintergrund",PaletteType) OnSetPaletteType();
}
dlgGroup("Schrift-&Ersetzung (verwendete Schriften gelistet)") {
dlgGridLayout{
SetFontLabel();
for (int i=0; i<3; i++) if (NumFont[i]) FontSubstLine(i);
}
if (NumCopperVector) {
dlgSpacing(4);
dlgLabel("<b>Hinweis:</b> Vektorschrift auf Kupfer wird niemals ersetzt.");
}
}
if (NumHatched) {
string s;
sprintf(s,"Füll&muster (%d schraffierte%s Layer vorhanden)",
NumHatched,NumHatched==1?"r":"");
dlgGroup(s) {
dlgRadioButton("Ausgefüllt",HatchType);
dlgRadioButton("Windows-Füllmuster (hat nur 6 Muster: = || // \\\\\\\\ ++ xx)",HatchType);
dlgHBoxLayout{
dlgRadioButton("Exakte Füllmuster wie in Eagle",HatchType);
dlgLabel(DeckLabel,1); // "transparent" oder "deckend"
dlgStretch(1);
}
}
}
if (board) {
if (AnyBottomLayers()) {
dlgCheckBox("&Gespiegelt (an Y-Achse, zur Seite)",Flipped);
}
dlgCheckBox("Simulierte Tr&ansparenz (mit Rasteroperationskodes)",Trans) {
DeckLabel=Trans?"(transparent)":"(deckend)";
if (Trans) switch (PaletteType) {
case 3: dlgMessageBox(
"Bei »Bunt auf farbigem Hintergrund« gibt es in Eagle < 5 keine Transparenz.<p>"
"Hier werden Komplementärfarben ausgegeben.<p>"+
"Hässliches Aussehen bei PostScript- und PDF-Weiterverarbeitung (Windows-Bug)");
break;
}
}
if (Dialog>1 || LastLayers) { // Nur bei "Dialog=2" oder LastLayer-Vorgabe
dlgGroup("&Umordnung der Layer-Ausgabe") {
dlgHBoxLayout{
dlgLabel("Zuletzt ausgeben (stets deckend):");
dlgSpacing(4);
dlgStringEdit(LastLayers);
}
}
}
}
if (DoNegate)
dlgCheckBox("&Negationsstriche statt »!« (wie Eagle 5)",DoNegate);
dlgCheckBox("&Hintergrund ausmalen (Vektorgrafik nicht transparent)",OutBkgnd);
if (NumSheet>1) {
dlgSpacing(4);
dlgCheckBox("&Alle Seiten (mehrere Dateien)",AllPages);
}
dlgSpacing(4);
dlgHBoxLayout {
dlgPushButton(FName?"+O&K":"+&Weiter, zum Speichern ...") {
if (Dialog<2 || CalcLastLayers()) dlgAccept(1);
}
dlgPushButton("Abbrechen") dlgReject();
}
};
}
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*/
'?', '?', '?', '?', '?', '┌', '?', '≥',
'▀', '?', '?', '?', '?', '?', '?', '?', /*90*/
'?', '?', '?', '?', '?', '╔', '?', '?',
'_', '¡', '╜', '£', '╧', '╛', '▐', '⌡', /*A0*/
'▄', '╕', 'ª', '«', '¬', '≡', '⌐', '∈',
'°', '±', '²', 'ⁿ', '∩', 'µ', '⌠', '·', /*B0*/
'≈', '√', 'º', '»', '¼', '½', '≤', '¿',
'╖', '╡', '╢', '╟', 'Ä', 'Å', 'Æ', 'Ç', /*C0*/
'╘', 'É', '╥', '╙', '▐', '╓', '╫', '╪',
'╤', 'Ñ', 'π', 'α', 'Γ', 'σ', 'Ö', '₧', /*D0*/
'Ø', 'δ', 'θ', 'Ω', 'Ü', 'ø', 'Φ', 'ß',
'à', 'á', 'â', '╞', 'ä', 'å', 'æ', 'ç', /*E0*/
'è', 'é', 'ê', ' ', 'ì', 'í', 'î', 'ï',
'╨', 'ñ', 'ò', 'ó', 'ô', 'Σ', 'ö', '÷', /*F0*/
'ø', 'ù', 'ú', 'û', 'ü', '∞', 'τ', ' '};
int ToUnicode(char c, int font) {
if (font!=FONT_VECTOR) return c;
if (c<128) return c;
return VectorSubst[c-128];
}
/* Ausgabe String in Unicode, ggf. mit Konvertierung bei FONT_VECTOR */
void OutString(string s, int font) {
int i, len=strlen(s);
for (i=0; i<len; i+=2) {
OutDword(ToUnicode(s[i],font)|ToUnicode(s[i+1],font)<<16);
}
}
int nRecordStart;
void BeginRecord(int recordtype) {
nRecordStart=nDwords; // Anfang merken
nRecords++;
OutDword(recordtype);
nDwords++; // Platz für Länge lassen
}
void EndRecord(void) {
FileData[nRecordStart+1]=(nDwords-nRecordStart)*4;
}
void OutMassX(int eaglepixel) {
OutDword(Flipped?-eaglepixel:eaglepixel);
}
void OutMassY(int eaglepixel) {
OutDword(-eaglepixel);
}
void OutRop(int NewRop) {
if (NewRop==LayerRop) return;
BeginRecord(20/*EMR_SETROP2*/);
OutDword(NewRop);
EndRecord();
LayerRop=NewRop;
}
// die Füllstile könnten auch per CreatePatternBrush exakt angepasst werden
// Da jedoch Schaltpläne äußerst selten Gebrauch davon machen, werden hier
// nur die Standard-Windows-Füllstile verwendet.
int FillStyles[]={ // bestmögliche Annäherung an Windows-Füllstile
1/*BS_NULL*/, // 0 Eagle-Füllung:
0/*BS_SOLID*/, // 1
0x02/*HS_HORIZONTAL*/, // 2 breite === 50%
0x32/*HS_BDIAGONAL*/, // 3 dünne /// 10%
0x32/*HS_BDIAGONAL*/, // 4 breite /// 50%
0x22/*HS_FDIAGONAL*/, // 5 breite \\\ 50%
0x22/*HS_FDIAGONAL*/, // 6 dünne \\\\ 10%
0x42/*HS_CROSS*/, // 7 dünne +++ 30%
0x52/*HS_DIAGCROSS*/, // 8 dünne xxx 30%
0x12/*HS_VERTICAL*/, // 9 Punkte 50%
0x12/*HS_VERTICAL*/, // A Punkte 10%
0x12/*HS_VERTICAL*/, // B Punkte 30%
0x02/*HS_HORIZONTAL*/, // C dünne === 50%
0x02/*HS_HORIZONTAL*/, // D dünne === 50%
0x02/*HS_HORIZONTAL*/, // E dünne === 50%
0x02/*HS_HORIZONTAL*/};// F dünne === 50%
// Für genauere Füllstil-Simulation muss man entsprechende Bitmaps laden
// Bei PostScript-Ausgabe werden unschön große Schraffuren ausgegeben
int EaglePattern[]={ // ab Index 2
0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF, // breite waagerechte Linien
0x04,0x08,0x10,0x20,0x40,0x80,0x01,0x02, // HS_BDIAGONAL
0xE0,0xC1,0x83,0x07,0x0E,0x1C,0x38,0x70, // // dick
0x1C,0x0E,0x07,0x83,0xC1,0xE0,0x70,0x38, // \\ dick
0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01, // HS_FDIAGONAL
0x11,0x11,0x11,0xFF,0x11,0x11,0x11,0xFF, // ++
0x48,0x84,0x03,0x03,0x84,0x48,0x30,0x30, // xx
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55, // Schachbrett
0x00,0x10,0x00,0x01,0x00,0x10,0x00,0x01, // Punkte dünn
0x00,0x44,0x00,0x11,0x00,0x44,0x00,0x11, // Punkte dichter
0x00,0xAA,0x00,0xAA,0x00,0xAA,0x00,0xAA, // Punkte angeordnet
0x00,0x55,0x00,0x55,0x00,0x55,0x00,0x55,
0x55,0x00,0x55,0x00,0x55,0x00,0x55,0x00,
0xAA,0x00,0xAA,0x00,0xAA,0x00,0xAA,0x00};
// Bei PostScript-Ausgabe erscheint eine grobe Klötzchenfüllung.
// Umwandeln RGB in Windows-Farbe
int rgb2bgr(int rgb) {
return ((rgb&0xFF)<<16)|(rgb&0xFF00)|((rgb>>16)&0xFF);
}
// Füllstil-Code für Layer <n> berechnen
int CalcFill(int n) {
int ret;
switch (HatchType) {
case 0: ret=0; break;
case 1: {
ret=FillStyles[fil[n]];
if (Flipped) switch (ret) {
case 0x22/*HS_FDIAGONAL*/:
case 0x32/*HS_BDIAGONAL*/: ret^=0x32^0x22; // Richtungen tauschen
}
}break;
case 2: switch (fil[n]) {
case 0: ret=1/*BS_NULL*/; break;
case 1: ret=0/*BS_SOLID*/; break;
default: ret=((fil[n]-2)<<4)|3;
}break;
}
return ret;
}
// Funktion liefert 0 (FALSE) wenn Eagle-Layer <n> unsichtbar
int SetLayer(int n) {
int c=col[n]; // c=0 wenn Layer nicht sichtbar
if (c) {
int rop=13/*R2_COPYPEN = P*/;
if (Trans) switch (PaletteType) {
case 0:
case 2: rop=9/*R2_MASKPEN = DPa*/; break;
case 1: rop=15/*R2_MERGEPEN = DPo*/; break;
case 3: rop=3/*R2_MASKNOTPEN = DPna*/; break; // Komplementärfarben
}
OutRop(rop); // Für Schrift ist die ROP-Einstellung irrelevant!
int rgb=PaletteType?palette(c,PaletteType-1):0;
if (rop==3 && !rgb) rgb=0xFFFFFF;
LayerColor=rgb2bgr(rgb);
LayerFill=CalcFill(n);
}
return c;
}
// globale Variablen zur Verwaltung der sogenannten GDI-Objekte
// Die Arrays wachsen mit der Verwendung verschiedener Pinsel, Stifte und Schriften.
int OD_type[];
int OD_param1[];
int OD_param2[];
int OD_color[];
int FindObj(int type, int p1, int p2, int color) {
int j;
for (j=1; j<nHandles; j++) {
if (type==OD_type[j]
&& p1==OD_param1[j]
&& p2==OD_param2[j]
&& color==OD_color[j]) return j; // vorhandenes Objekt benutzen
}
OD_type[j] =type; // neues Objekt vermerken (j == nHandles)
OD_param1[j]=p1;
OD_param2[j]=p2;
OD_color[j] =color;
return j;
}
void OutSelectObject(int idx) {
BeginRecord(37/*EMR_SELECTOBJECT*/);
OutDword(idx);
EndRecord();
}
void OutDeleteObject(int idx) {
BeginRecord(40/*EMR_DELETEOBJECT*/);
OutDword(idx);
EndRecord();
}
int ObjInUse[]; // Momentanes verwendetes Objekt aus den drei u.g. Typen
enum {ObjPen,ObjBrush,ObjFont};
//Debugging:
string ObjDescription(int idx) {
string ret="ungültiges idx";
if (idx<=0 || idx>=nHandles) return ret;
switch (OD_type[idx]) {
case ObjPen: {
string stil="unbekannt";
switch (OD_param1[idx]) {
case 0: stil="durchgezogen"; break;
case 1: stil="Striche"; break;
case 2: stil="Punkte"; break;
case 3: stil="Strich-Punkt"; break;
case 5: stil="leer"; break;
}
sprintf(ret,"<b>Stift</b>, Stil=%s(%d), Breite=%d, Farbe=%06X",
stil,OD_param1[idx],OD_param2[idx],OD_color[idx]);
}break;
case ObjBrush: {
string stil="unbekannt";
string hatch="unbekannt";
switch (OD_param1[idx]) {
case 0: stil="voll"; break;
case 1: stil="leer"; break;
case 2: stil="gestreift"; break;
}
switch (OD_param2[idx]) {
case 0: hatch="keine"; break;
case 4: hatch="+++"; break;
}
sprintf(ret,"<b>Pinsel</b>, Stil=%s(%d), Streifen=%s(%d), Farbe=%06X",
stil,OD_param1[idx],hatch,OD_param2[idx],OD_color[idx]);
}break;
case ObjFont: {
sprintf(ret,"<b>Schrift</b> Art=%s(%d), 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;
}
void OutWindow(void/*int w, int h*/) {
if (0) {
int w,h;
BeginRecord(10/*EMR_SETWINDOWORG*/);
OutDword(0);
OutDword(0);
EndRecord();
BeginRecord(9/*EMR_SETWINDOWEXT*/);
OutMassX(w);
OutMassY(h);
EndRecord();
}
BeginRecord(18/*EMR_SETBKMODE*/);
OutDword(1/*TRANSPARENT*/);
EndRecord();
if (HatchType==2) {
BeginRecord(25/*EMR_SETBKCOLOR*/);
int col=BackgroundColor;
if (Trans && PaletteType==3) col^=0xFFFFFF;
OutDword(col); // für Pattern-Schraffur (ist immer deckend - leider!)
EndRecord();
}
}
// Liefert Index in Handle-Tabelle (für schnelle EMF-Darstellung),
// liefert 0, wenn kein neues CreateXxx erforderlich ist.
int GetNewHandleIndex(int objtype, int h1, int h2, int color) {
int j=FindObj(objtype,h1,h2,color);
if (j==nHandles) { // nicht gefunden: neues Objekt
ObjInUse[objtype]=j; // Aufrufer muss Create() und SelectObject() rufen
nHandles++;
return j;
} // gefunden: ggf. als aktuelles Objekt aktivieren
if (ObjInUse[objtype]!=j) {
OutSelectObject(j);
ObjInUse[objtype]=j;
}
return 0;
}
// BoundingBox-Dummy (4 Nullen) ausgeben
void OutBBox(void) {
for (int i=0; i<4; i++) OutDword(0);
}
void SetTextColor(int color) {
if (TextColor!=color) {
BeginRecord(24/*EMR_SETTEXTCOLOR*/);
OutDword(color);
EndRecord();
TextColor=color;
}
}
// wird für SetPen() und SetBrush() gebraucht!
void OutBitmapData(int hatch) {
OutDword(40); // sizeof(header)
OutDword(8); // Breite
OutDword(8); // Höhe
OutDword(0x10001); // Planes und Bit pro Pixel
OutDword(0);
OutDword(32); // Länge der Daten in Bytes
int i;
OutBBox(); // sonstige Header-Angaben
for (i=0; i<8; i++) OutDword(~EaglePattern[hatch*8+(Flipped?7-i:i)]&0xFF);
}
void SetPen(int style, int width, int color) {
if (style==5) width=color=0; // PS_NULL: keine Breite, keine Farbe
int idx=GetNewHandleIndex(ObjPen,style,width,color);
if (idx<0) return;
if (Debug>1) {
string msg;
sprintf(msg,"SetPen(%d,%d,%06X) idx=%d nHandles=%d",style,width,color,idx,nHandles);
dlgMessageBox(msg);
}
if (!idx) return; // bereits selektiert
if (!width){
BeginRecord(38/*EMR_CREATEPEN*/);
OutDword(idx);
OutDword(style);
OutDword(width);
OutDword(width);
OutDword(color);
}else{
style|=0x10000/*PS_GEOMETRIC*/;
int brushstyle=LayerFill&0x0F;
if (brushstyle==3) SetTextColor(color);
int brushhatch=LayerFill>>4;
BeginRecord(95/*EMR_EXTCREATEPEN*/);
OutDword(idx); // ihPen
if (brushstyle<3) {
OutBBox();
OutDword(style); // elpPenStyle;
OutDword(width); // elpWidth;
OutDword(brushstyle);// elpBrushStyle;
OutDword(color); // elpColor;
OutDword(brushhatch);// elpHatch;
OutDword(0); // elpNumEntries;
OutDword(0); // elpStyleEntry[1];
}else{
OutDword(56); // offBmi (BitMapInfo)
OutDword(40); // cbBmi
OutDword(96); // offBits (BitMapBits)
OutDword(32); // cbBits
OutDword(style); // elpPenStyle;
OutDword(width); // elpWidth;
OutDword(brushstyle);// elpBrushStyle;
OutDword(2); // elpColor;
OutDword(0); // elpHatch;
OutDword(0); // elpNumEntries;
OutDword(0);// elpStyleEntry[1];
OutBitmapData(brushhatch);
}
}
EndRecord();
OutSelectObject(idx);
}
void SetBrush(int style, int hatch, int color) {
if (style<2) hatch=0; // BS_SOLID,BS_NULL: kein Muster
if (style==1) color=0; // BS_NULL: keine Farbe
int idx=GetNewHandleIndex(ObjBrush,style,hatch,color);
if (Debug>1) {
string msg;
sprintf(msg,"SetBrush(%d,%d,%06X) idx=%d nHandles=%d",style,hatch,color,idx,nHandles);
dlgMessageBox(msg);
}
if (!idx) return;
if (style<3) { // BS_SOLID,BS_NULL,BS_HATCHED
BeginRecord(39/*EMR_CREATEBRUSHINDIRECT*/);
OutDword(idx);
OutDword(style);
OutDword(color);
OutDword(hatch);
EndRecord();
}else{ // BS_PATTERN
BeginRecord(93/*EMR_CREATEMONOBRUSH*/);
OutDword(idx);
OutDword(2); // iUsage
OutDword(36); // offBmi;
OutDword(40); // cbBmi;
OutDword(76); // offBits;
OutDword(32); // cbBits;
OutDword(0x20000000); // weiß keiner!
OutBitmapData(hatch);
EndRecord();
SetTextColor(color);
}
OutSelectObject(idx);
}
void SetFont(int font, int size, int 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. Erstens läuft es so mit freigerechneten Signalpolygonen,
// und zweitens bleibt das EMF auch unter Windows 9x/Me benutzbar.
// Vorausgesetzt, die Koordinaten bleiben 16bittrig.
// Und es wird kein SetWorldTransform() für gespiegelten Text verwendet.
if (DontProcess(O.layer)) return;
SetPen(0/*PS_SOLID*/,O.width,LayerColor);
if (O.pour==POLYGON_POUR_HATCH) SetBrush(2,4/*HS_CROSS*/,LayerColor);
else{
int Fill=0;
if (board) Fill=LayerFill;
SetBrush2(Fill);
}
int np=0,nk[],n=0,x0,y0;
nk[0]=0;
O.contours(W) {
// Hoppla! "contours" macht aus Bögen Geradenstücke, auch im Schaltplan!
if (!nk[np]) x0=W.x1, y0=W.y1; // Linienzug-Anfang
nk[np]++; // Kanten abzählen (per Polygon)
n++; // Kanten abzählen (Gesamtanzahl)
if (x0==W.x2 && y0==W.y2) nk[++np]=0; // Linienzug-Ende am Anfang
}
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];
}
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 x+=(CharWidth[sel-1][c-32]-32)*mult; // Stringbreite in Pixel mitschneiden
}
if (n&1) xx[n++]=x;
s[j]=0; // Ziel (nie länger als Quelle) abhacken
if (n) {
// Striche malen
angle*=PI/180;
real c=cos(angle); // Drehmatrix-Elemente | +cos +sin |
real s=sin(angle); // | -sin +cos |
real x0=O.x;
real y0=O.y;
if (textalign&2/*TA_RIGHT*/) {
x0-=x*c;
y0-=x*s;
}
if (textalign&24/*TA_BASELINE*/) {
y=O.size*1.25; // empirischer Abstand
x0-=y*s;
y0+=y*c;
}
SetPen(0/*PS_SOLID*/,GetVectorLineWidth(O),LayerColor);
for (i=0; i<n; i+=2) {
int x1=x0+xx[i]*c;
int y1=y0+xx[i]*s;
int x2=x0+xx[i+1]*c;
int y2=y0+xx[i+1]*s;
OutLine(x1,y1,x2,y2);
}
}
return s;
}
void HandleText(UL_TEXT O) {
if (DontProcess(O.layer)) return;
if (O.value==" ") return; // unterdrückte Values bei Bauelementen
if (!FontSel[O.font] // Ersetzung durch Vektor gewünscht
|| O.font==FONT_VECTOR && O.layer<=LAYER_BOTTOM) {
// Vektorfont auf Kupfer
// Hier sollte eigentlich Eagle 5 die Überstreichungen generieren, ist das so?
O.wires(W) HandleWire(W); // Text als Striche ausgeben
return;
}
real w=O.angle;
int a=24/*TA_BASELINE*/;
if (schematic && O.mirror) {
if (w==90 || w==270) a^=24;
else a^=2/*TA_RIGHT*/;
}
if (!O.spin && w>90 && w<=270) { // Änderung von Ausrichtung bei spin=0?
w-=180; if (w<0) w+=360; // (für Ausführung im Board)
a^=24^2; // Ausrichtung genau anders herum
}
if (board && Flipped && !O.mirror // gespiegelter Text?
|| board && !Flipped && O.mirror) {
w=-w;
if (!w) w=-360;
}
if (Debug) OutCircle(O.x,O.y,10000);
SetRotate(w);
CalcCenter(O.x,O.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);
SetFont(O.font,O.size,col,a);
// Eine Zusammenfassung mehrerer Texte zu PolyTextOut ist naheliegend...
// ExtTextOutA wäre interessant, weil's besser packt und evtl. chinesisch läuft
BeginRecord(84/*EMR_EXTTEXTOUTW*/);
OutBBox(); // rclBounds (unbenutzt)
OutDword(2); // iGraphicsMode=GM_ADVANCED
OutDword(0); // exScale (unbenutzt bei GM_ADVANCED)
OutDword(0); // eyScale
OutMassX(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=40*254;
switch (type) {
case 0: OutLine(x-D,y-D,x+D,y+D); OutCircle(x,y,D); break; // Ø
case 1: OutLine(x-D,y,x+D,y); OutLine(x,y-D,x,y+D); break; // +
case 2: OutLine(x-D,y-D,x+D,y+D); OutLine(x-D,y+D,x+D,y-D); break; // x
case 3: OutLine(x,y,x,y+D); OutRectangle(x,y,D*2,D*2,0); break;
case 4: OutLine(x,y,x,y+D); OutLineTo(x-D,y); OutLineTo(x,y-D); OutLineTo(x+D,y); OutLineTo(x,y+D); break;
case 5: OutLine(x-D,y-D,x+D,y+D); OutLineTo(x-D,y+D); OutLineTo(x+D,y-D); OutLineTo(x-D,y-D); break;
case 6: OutLine(x-D,y+D,x+D,y-D); OutLineTo(x+D,y+D); OutLineTo(x-D,y-D); OutLineTo(x-D,y+D); break;
case 7: OutLine(x-D,y,x+D,y); OutLineTo(x,y+D); OutLineTo(x,y-D); OutLineTo(x-D,y); break;
case 8: OutLine(x-D,y,x+D,y); OutLineTo(x,y-D); OutLineTo(x,y+D); OutLineTo(x-D,y); break;
case 9: OutLine(x-D,y+D,x+D,y-D); OutLine(x,y-D,x,y+D); OutLine(x+D,y+D,x-D,y-D); break;
case 10: OutLine(x-D,y,x+D,y); OutLine(x+D,y+D,x-D,y-D); OutLine(x-D,y+D,x+D,y-D); break;
case 11: OutLine(x,y-D,x,y); OutLineTo(x+D,y+D); OutLineTo(x-D,y+D); OutLineTo(x,y); break;
case 12: OutLine(x,y+D,x,y); OutLineTo(x+D,y-D); OutLineTo(x-D,y-D); OutLineTo(x,y); break;
case 13: OutLine(x-D,y,x,y); OutLineTo(x+D,y+D); OutLineTo(x+D,y-D); OutLineTo(x,y); break;
case 14: OutLine(x+D,y,x,y); OutLineTo(x-D,y-D); OutLineTo(x-D,y+D); OutLineTo(x,y); break;
case 16: OutCircle(x,y,D/2); /*nobreak;*/
case 15: OutLine(x-D,y,x+D,y); OutLine(x,y+D,x,y-D); OutCircle(x,y,D); break;
case 18: OutRectangle(x,y,D,D,0); /*nobreak;*/
case 17: OutLine(x-D,y,x+D,y); OutLine(x,y+D,x,y-D); OutRectangle(x,y,D*2,D*2,0); break;
}
}
void DrawHole(int x, int y, int d) {
if (DrawHoles) {
SetPen(5/*PS_NULL*/,0,0);
SetBrush(0/*BS_SOLID*/,0,BackgroundColor);
OutCircle(x,y,d/2);
}
}
// ermittelt Bezug zum zu verwendenden Layer für PADs
// Liefert 0 für "don't process"
int GetPadLayer(int flags) {
switch (CurLayer) {
case LAYER_TSTOP:
case LAYER_BSTOP: if (flags&PAD_FLAG_STOP) return CurLayer; break;
case LAYER_PADS: return LAYER_TOP;
}
return 0;
}
void HandlePad(UL_PAD O) {
if (!O) return;
if (CurLayer==LAYER_DRILLS) {
SetPen(0/*PS_SOLID*/,3*254,LayerColor);
SetBrush(1/*BS_NULL*/,0,0);
OutDrillSymbol(O.x,O.y,O.drillsymbol);
return;
}
int d=0, PadLayer=GetPadLayer(O.flags);
if (!PadLayer) return;
d=O.diameter[PadLayer];
if (!d) return; // raus wenn null geblieben
if (!DrawHoles) {
SetLayerPen();
SetLayerBrush();
switch (O.shape[PadLayer]) {
// im LAYER_PADS ist O.shape immer PAD_SHAPE_ROUND, dann von LAYER_TOP nehmen
case PAD_SHAPE_SQUARE: OutRectangle(O.x,O.y,d,d,O.angle); break;
// hier künftig ARC ausgeben! (aber nur, wenn Layer SOLID)
case PAD_SHAPE_ROUND: OutCircle(O.x,O.y,d/2); break;
case PAD_SHAPE_OCTAGON: OutOctagon(O.x,O.y,d,O.angle,0); break;
case PAD_SHAPE_LONG: OutOctagon(O.x,O.y,d,O.angle,1); break;
case PAD_SHAPE_OFFSET: OutOctagon(O.x,O.y,d,O.angle,2); break;
default: dlgMessageBox("??");
}
}
if (PadLayer==LAYER_TOP) DrawHole(O.x,O.y,O.drill);
}
// ermittelt Bezug zum zu verwendenden Layer für PADs
// Liefert 0 für "don't process"
int GetViaLayer(int flags) {
switch (CurLayer) {
case LAYER_TSTOP:
case LAYER_BSTOP: if (flags&VIA_FLAG_STOP) return CurLayer; break;
case LAYER_VIAS: return LAYER_TOP;
}
return 0;
}
void HandleVia(UL_VIA O) {
if (!O) return;
if (CurLayer==LAYER_DRILLS) {
SetPen(0/*PS_SOLID*/,3*254,LayerColor);
SetBrush(1/*BS_NULL*/,0,0);
OutDrillSymbol(O.x,O.y,O.drillsymbol);
return;
}
int d=0, ViaLayer=GetViaLayer(O.flags);
if (!ViaLayer) return;
d=O.diameter[ViaLayer];
if (!d) return; // raus wenn null geblieben
if (!DrawHoles) {
SetLayerPen();
SetLayerBrush();
switch (O.shape[ViaLayer]) {
case VIA_SHAPE_SQUARE: OutRectangle(O.x,O.y,d,d,0); break;
// hier künftig ARC ausgeben! (aber nur, wenn Layer SOLID)
case VIA_SHAPE_ROUND: OutCircle(O.x,O.y,d/2); break;
case VIA_SHAPE_OCTAGON: OutOctagon(O.x,O.y,d,0,0); break;
default: dlgMessageBox("???");
}
}
if (ViaLayer==LAYER_TOP) DrawHole(O.x,O.y,O.drill);
}
int GetSmdLayer(int SmdLayer, int flags) {
switch (SmdLayer) {
case LAYER_BOTTOM: switch (CurLayer) { // SMD-Pad unten
case LAYER_BSTOP: if (flags&SMD_FLAG_STOP) return LAYER_BSTOP; break;
case LAYER_BCREAM: if (flags&SMD_FLAG_CREAM) return LAYER_BCREAM; break;
case LAYER_BOTTOM: return LAYER_BOTTOM;
}break;
case LAYER_TOP: switch (CurLayer) { // SMD-Pad oben
case LAYER_TSTOP: if (flags&SMD_FLAG_STOP) return LAYER_TSTOP; break;
case LAYER_TCREAM: if (flags&SMD_FLAG_CREAM) return LAYER_TCREAM; break;
case LAYER_TOP: return LAYER_TOP;
}break;
}
return 0;
}
void HandleSmd(UL_SMD O) {
if (!O) return;
int SmdLayer=GetSmdLayer(O.layer,O.flags);
if (!SmdLayer) return;
int dx=O.dx[SmdLayer], dy=O.dy[SmdLayer];
if (!dx) return; // raus wenn null geblieben
SetLayerPen();
SetLayerBrush();
if (O.roundness) { // Angabe in Prozent der kürzeren Seite
if (O.roundness==100 && dx==dy) OutCircle(O.x,O.y,dx/2);
else{
int r=dx<dy?dx:dy; // kürzere der beiden Seiten
r=r*O.roundness/200; // Radius der Abrundung
OutRoundRect(O.x,O.y,dx,dy,r,O.angle);
}
}else OutRectangle(O.x,O.y,dx,dy,O.angle);
}
void HandleHole(UL_HOLE O) {
switch (CurLayer) {
case LAYER_DRILLS:
case LAYER_HOLES: {
SetPen(0/*PS_SOLID*/,3*254,LayerColor);
SetBrush(1/*BS_NULL*/,0,0);
OutDrillSymbol(O.x,O.y,O.drillsymbol);
}break;
case LAYER_DIMENSION: {
SetLayerPen();
SetBrush(1/*BS_NULL*/,0,0);
OutCircle(O.x,O.y,O.drill/2);
}break;
}
}
/* Abarbeitung eines Layers (schwarzweiß: alle) */
void SchWalkLayer(UL_SHEET S) {
string e;
sprintf(e,"Seite %d Layer %d",S.number,CurLayer);
status(e);
S.busses(B) {
B.segments(SE) {
SE.texts(T) HandleText(T);
SE.wires(W) HandleWire(W);
}
}
S.circles(C) HandleCircle(C);
S.frames(FR) { // ab Eagle 5
FR.texts(T) HandleText(T);
FR.wires(W) HandleWire(W);
}
S.nets(N) {
N.segments(SE) {
SE.junctions(J) HandleJunction(J);
SE.texts(T) HandleText(T);
SE.wires(W) HandleWire(W);
}
}
S.parts(P) {
P.instances(I) {
I.gate.symbol.circles(C) HandleCircle(C);
I.gate.symbol.frames(FR) { // ab Eagle 5
FR.texts(T) HandleText(T);
FR.wires(W) HandleWire(W);
}
I.gate.symbol.pins(P) {
P.circles(C) HandleCircle(C);
P.texts(T) HandleText(T);
P.wires(W) HandleWire(W);
}
I.gate.symbol.polygons(P) HandlePolygon(P);
I.gate.symbol.rectangles(R) HandleRectangle(R);
I.gate.symbol.texts(T) HandleText(T); // Unsmashed
I.gate.symbol.wires(W) HandleWire(W);
I.texts(T) HandleText(T); // Smashed
}
}
S.polygons(P) HandlePolygon(P);
S.rectangles(R) HandleRectangle(R);
S.texts(T) HandleText(T);
S.wires(W) HandleWire(W);
}
void BrdWalkLayer(UL_BOARD B) {
string e;
sprintf(e,"Layer %d",CurLayer);
status(e);
B.circles(C) HandleCircle(C);
B.elements(E) {
E.package.circles(C) HandleCircle(C);
E.package.contacts(X) {
HandlePad(X.pad);
HandleSmd(X.smd);
}
E.package.holes(H) HandleHole(H);
E.package.polygons(P) HandlePolygon(P);
E.package.rectangles(R) HandleRectangle(R);
E.package.texts(T) HandleText(T); // unsmashed
E.package.wires(W) HandleWire(W);
E.texts(T) HandleText(T); // smashed
}
B.holes(H) HandleHole(H);
B.polygons(P) HandlePolygon(P);
B.rectangles(R) HandleRectangle(R);
B.signals(S) {
S.polygons(P) if (IsRatsnestPoly(P)) HandlePolygon(P);
// Eventuell hier wie in Eagle5 ein gestricheltes, ungefülltes Polygon zeichnen?
S.vias(V) HandleVia(V);
S.wires(W) HandleWire(W);
}
B.texts(T) HandleText(T);
B.wires(W) HandleWire(W);
}
/* Abarbeitung layer-weise von hinten nach vorn, wenn bunt (sonst unexakte Darstellung) */
void SchWalkLayers(UL_SHEET S) {
if (PaletteType) for (CurLayer=255; CurLayer>=0; CurLayer--) {
if (SetLayer(CurLayer)) SchWalkLayer(S);
}else SchWalkLayer(S); // Schwarzweiß NICHT im Sandwich-Betrieb abarbeiten (schneller)
}
void BrdWalkLayer2(UL_BOARD B, int DoLast) {
if (!SetLayer(CurLayer)) return;
if (!DoLast && strchr(LastLayers,CurLayer)>=0) return; // überspringen
BrdWalkLayer(B);
// Zweiter Durchlauf für die Löcher (In einem einzelnen Durchlauf würde,
// wegen der vielen Kontextumschaltungen, die .WMF-Datei viel größer werden.)
if (CurLayer==LAYER_VIAS || CurLayer==LAYER_PADS) {
DrawHoles=1;
OutRop(13/*R2_COPYPEN*/); // Löcher zum Hintergrund "durchstanzen"
BrdWalkLayer(B);
DrawHoles=0;
}
}
/* Abarbeitung layer-weise, sogar wenn schwarz-weiß */
void BrdWalkLayers(UL_BOARD B) {
// Eagle gibt die Kupferlagen von unten nach oben aus
if (Trans) {
// Löcher ausstanzen lassen, bevor Kupferlinien hineingezogen werden (wie Eagle)
for (CurLayer=17; CurLayer<21; CurLayer++) BrdWalkLayer2(B,0);
for (CurLayer=16; CurLayer>=1; CurLayer--) BrdWalkLayer2(B,0);
}else{
for (CurLayer=16; CurLayer>=1; CurLayer--) BrdWalkLayer2(B,0);
for (CurLayer=17; CurLayer<21; CurLayer++) BrdWalkLayer2(B,0);
}
for (CurLayer=29; CurLayer<51; CurLayer++) BrdWalkLayer2(B,0);
for (CurLayer=53; CurLayer<256; CurLayer++) BrdWalkLayer2(B,0);
//Typische Text-Layer zuletzt
for (CurLayer=51; CurLayer<53; CurLayer++) BrdWalkLayer2(B,0);
for (CurLayer=21; CurLayer<29; CurLayer++) BrdWalkLayer2(B,0);
Trans=0; // Transparenz für LastLayers ausschalten
for (int i=0; CurLayer=LastLayers[i]; i++) BrdWalkLayer2(B,1);
}
void OutStart() {
nDwords=0; // Kopf beginnen
nRecords=0;
ClearObjects();
// Normalen EMF-Kopf ausgeben
BeginRecord(1/*EMR_HEADER*/);
nDwords+=8; // rclBounds, rclFrame übergehen (wird später eingesetzt)
OutDword(0x464D4520); // dSignature ENHMETA_SIGNATURE
OutDword(0x00010000); // nVersion
nDwords+=3; // nBytes, nRecords, nHandles übergehen
OutDword(0); // nDescription
OutDword(0); // offDescription (Unicode!)
OutDword(0); // nPalEntries
nDwords+=4; // szlDevice, szlMillimeters übergehen
EndRecord();
OutWindow();
}
void OutWrite(int left, int top, int right, int bottom) {
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+")");
// Windows-Metafile-Kopfdaten ergänzen
FileData[2]=left/254; // rclBounds
FileData[3]=top/254;
FileData[4]=right/254;
FileData[5]=bottom/254;
// Ohne Teiler verreckt ACDSee; ansonsten scheint der Teiler ohne Belang.
// Eine 1:1-Anzeige mit dem Total Commander F3-Viewer ist nicht möglich,
// ggf. "F" drücken. Möglicherweise ein Bug/Un-Feature im Total Commander.
FileData[6]=left/100; // rclFrame [HIMETRIC = 10µm]
FileData[7]=top/100;
FileData[8]=right/100;
FileData[9]=bottom/100;
FileData[12]=nDwords*4;// nBytes
FileData[13]=nRecords; // nRecords
FileData[14]=nHandles; // nHandles + sReserved
int w=right-left;
int h=bottom-top;
FileData[18]=w; // szlDevice [pel], diese Angabe muss stimmen
FileData[19]=h;
FileData[20]=u2mm(w); // szlMillimeters [mm], muss mit rclFrame korrelieren
FileData[21]=u2mm(h);
for (int i=0; i<nDwords; i++) PrintDword(FileData[i]); // Daten ausspucken
// Mit ToClipboard.exe passieren seltsame Dinge:
// Obwohl jenes Programm die Datei gar nicht verändert, werden CAP_FLAT
// zu CAP_RECTANGLE, und alle ExtTextOut-Zeichen erscheinen übereinander.
// Kreise und Bögen sind nach unten versetzt.
}
void DrawBkgnd(int x1, int y1, int x2, int y2) {
if (!OutBkgnd) return;
SetPen(5/*PS_NULL*/,0,0);
SetBrush(0/*BS_SOLID*/,0,BackgroundColor);
BeginRecord(43/*EMR_RECTANGLE*/);
OutRect(x1,y1,x2,y2);
EndRecord();
}
/* Ausgabe einer Metadatei */
void SchPrintMF(UL_SHEET S) {
OutStart();
DrawBkgnd(S.area.x1,S.area.y1,S.area.x2,S.area.y2);
SchWalkLayers(S);
if (Debug) ShowHandleTable();
OutWrite(S.area.x1,-S.area.y2,S.area.x2,-S.area.y1);
}
void BrdPrintMF(UL_BOARD B) {
OutStart();
DrawBkgnd(B.area.x1,B.area.y1,B.area.x2,B.area.y2);
BrdWalkLayers(B);
if (Debug) ShowHandleTable();
if (Flipped) OutWrite(-B.area.x2,-B.area.y2,-B.area.x1,-B.area.y1);
else OutWrite(B.area.x1,-B.area.y2,B.area.x2,-B.area.y1);
}
// Einen Schriftart-Parameter parsen
// Dieser muss den regexp-Aufbau haben: (Vector|Arial|Courier|Times)(,b?i?)?
// oder [VACT][,bi]*
void ParseFont(int idx, string val) {
int p=strchr(val,',');
if (strlen(val)<=3) p=1; // in der Kurzform ohne Komma zulässig
string suffix;
if (p>=0) {
suffix=strsub(val,p);
val=strsub(val,0,p);
}
if (val=="") ; // ohne Schriftname Voreinstellung lassen
else if (val=="Vector" || val=="V") FontSel[idx]=0;
else if (val=="Arial" || val=="A") FontSel[idx]=1;
else if (val=="Courier" || val=="C") FontSel[idx]=2;
else if (val=="Times" || val=="T") FontSel[idx]=3;
else{
dlgMessageBox("Unbekannte Schriftartbezeichnung "+val+"!");
exit(EXIT_FAILURE);
}
FontBold[idx]=0;
FontItalic[idx]=0;
for (p=0; p<strlen(suffix); p++) switch (suffix[p]) {
case 'b': FontBold[idx]=1; break;
case 'i': FontItalic[idx]=1; break;
case ',': break;
default:
dlgMessageBox("Unbekannter Schriftstil "+suffix[p]+"! (Nur »b« [fett] oder »i« [kursiv])");
exit(EXIT_FAILURE);
}
}
int CheckRipupPoly=0; // Prüfung auf nicht geRATSNESTe Signal-Polygone
string GetWorkFileName(void) {
if (schematic) schematic(S) return S.name;
if (board) board(B) return B.name;
}
string AutomaticFileName(void) {
return filesetext(GetWorkFileName(),schematic?".emf":Flipped?"b.emf":"t.emf");
}
// Die Kommandozeile ist an AWK-Syntax angelegt:
// Entweder <dateiname> oder <variable>=<wert>.
// Nur ein <dateiname> ist erlaubt, der kein '=' enthalten darf.
// Alle im Dialog einstellbaren Variablennamen sind erlaubt.
void ParseCommandLine(void) {
int i,j,p;
string arg,key,val;
for (i=1; i<argc; i++) {
arg=argv[i];
p=strchr(arg,'=');
if (p>=0) { // normales Argument
key=strsub(arg,0,p);
val=strsub(arg,p+1);
if (key=="Debug") Debug=strtol(val);
else if (key=="PaletteType" || key=="p") {
PaletteType=strtol(val);
OnSetPaletteType();
}else if (key=="HatchType" || key=="h") HatchType=strtol(val);
else if ((key=="AllPages" || key=="a") && schematic) AllPages=strtol(val);
else if ((key=="Flipped" || key=="f") && board) Flipped=strtol(val);
else if (key=="Trans" || key=="t") Trans=strtol(val);
else if (key=="OutBkgnd" || key=="b") OutBkgnd=strtol(val);
else if (key=="DoNegate" || key=="n") DoNegate=strtol(val);
else if (key=="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
|