Source file: /~heha/ewa/Reluktanzmotor/cdesk-201007.zip/staticx.cpp

#include "compat.h"
#include "staticx.h"
#include "gdix.h"	// Rect
#include <tchar.h>
#include <richedit.h>
#include <shlwapi.h>
#include <math.h>
#include <vector>
#include <stack>
#include "text.h"	// DrawTextXW

WNDPROC StaticX::oldproc;
int StaticX::oldwndextra;
HMODULE StaticX::hDll;

/*static*/ BOOL StaticX::init() {
// if (!hDll) hDll=LoadLibrary(TEXT("riched32.dll"));	// Version 1 reicht! Oder??
#pragma pack(push,1)
  struct JumpInstr{
    typedef BYTE buf_t[5];	// byte buffer for saving and restoring
    BYTE opcode;			// 0xE9: same for x86 and x64
    LONG distance;		// distance between target and end-of-opcode, same for x86 and x64
    const void*end() const {return this+1;}	// end-of-opcode
//    void save(buf_t buf) const {memcpy(buf,this,5);}
//    void restore(const buf_t buf) {memcpy(this,buf,5);}
//    void swap(buf_t buf) {std::swap(*this,*(JumpInstr*)buf);}
    void setjmp(const void*target) {opcode=0xE9; distance=LONG((BYTE*)target-(BYTE*)end());}
  }*pDrawText=(JumpInstr*)DrawText,
   *pDrawTextEx=(JumpInstr*)DrawTextEx;
#pragma pack(pop)
  DWORD oldprot[2];
  VirtualProtect(pDrawText,sizeof*pDrawText,PAGE_EXECUTE_WRITECOPY,oldprot);
  pDrawText->setjmp(hehaDrawText);
  VirtualProtect(pDrawTextEx,sizeof*pDrawTextEx,PAGE_EXECUTE_WRITECOPY,oldprot+1);
  pDrawTextEx->setjmp(hehaDrawTextEx);
  WNDCLASSEX wc;
  wc.cbSize=sizeof wc;
// GetClassInfoEx(0,TEXT(RICHEDIT_CLASS10A),&wc);
  GetClassInfoEx(0,TEXT("Static"),&wc);
// oldproc=wc.lpfnWndProc;
// oldwndextra=wc.cbWndExtra;
// wc.cbWndExtra+=sizeof(StaticX*);
// wc.lpfnWndProc=wndproc;
  wc.lpszClassName=TEXT("StaticX");
  return RegisterClassEx(&wc);
}

/*static*/ LRESULT CALLBACK StaticX::wndproc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 StaticX*self=(StaticX*)GetWindowLongPtr(wnd,oldwndextra);
 if (!self) {
  self=new StaticX(wnd);
  SetWindowLongPtr(wnd,oldwndextra,(LPARAM)self);
 }
 LRESULT ret=self->wndproc(msg,wParam,lParam);
 if (msg==WM_NCDESTROY) delete self;
 return ret;
}
#if 0
void StaticX::setText(const TCHAR*s) {
 TCHAR buf[128],*p=buf,*e=buf+128;
//    SetWindowText(wnd,TEXT("{\\rtf1 i{\\sub a}\\u915Gvalue}"));
 p+=_sntprintf(p,e-p,TEXT("{\\rtf1 "));
 for(TCHAR c;c=*s++;) switch (c) {
  case '_': {
   const TCHAR*q=_tcschr(s,'_');
   if (q) {p+=_sntprintf(p,e-p,TEXT("{\\ul %s}"),s); s=q+1;}
   else {p+=_sntprintf(p,e-p,TEXT("{\\sub %s}"),s); s+=_tcslen(s);}	// Nee!!
  }break;
  case '~': {p+=_sntprintf(p,e-p,TEXT("{\\super %s}"),s); s+=_tcslen(s);} break;
// Hm, unterstreichen, durchstreichen ohne Wirkung!
  case '&': p+=_sntprintf(p,e-p,TEXT("{\\uldb %c}"),*s++); break;	// Folgezeichen unterstrichen (Hotkey)
  default: {
   /*if ((unsigned)c>=128) p+=_sntprintf(p,e-p,TEXT("\\u%d?"),(short)c);
   else*/ p+=_sntprintf(p,e-p,TEXT("%c"),c);	// Aha! Unicode darf literal eingebaut werden, "\uxxxy" ist nicht erforderlich
  }
 }
 p+=_sntprintf(p,e-p,TEXT("}"));
// Nichts davon funktioniert mit RichEdit20W!!
 CallWindowProc(oldproc,wnd,WM_SETTEXT,0,(LPARAM)buf);
// SetWindowText(wnd,buf);
// SETTEXTEX ste={ST_DEFAULT,1200};
// SendMessage(wnd,EM_SETTEXTEX,(WPARAM)&ste,(LPARAM)buf);
}
#endif
LRESULT StaticX::wndproc(UINT msg, WPARAM wParam, LPARAM lParam) {
 switch (msg) {
//  case WM_NCCREATE: {
//   CREATESTRUCT*cs=(CREATESTRUCT*)lParam;
//   cs->lpszName=TEXT("{\\rtf1\\ansi\\u1065{\\super int}}");
//  }break;
  case WM_CREATE: {
   CREATESTRUCT*cs=(CREATESTRUCT*)lParam;
//    cs->lpszName=TEXT("{\\rtf1\\u1065{\\sub int}}");
//    static SETTEXTEX ste={ST_DEFAULT,1200};
//    PostMessage(wnd,WM_SETTEXT,0,(LPARAM)TEXT("{\\rtf1\\u1065J{\\sub int1}}"));
//    SetWindowText(wnd,TEXT("{\\rtf1 i{\\sub a}\\u915Gvalue}"));
   //setText(cs->lpszName);
//   makefont((HFONT)GetStockObject(SYSTEM_FONT),false);
   //SendMessage(wnd,EM_SETBKGNDCOLOR,0,GetSysColor(COLOR_3DFACE));
  }break;
  case WM_SETTEXT: {
   OutputDebugString((LPCTSTR)lParam);
  }break;
//  case WM_SETFONT:if (wParam){
//   Font of(wnd);	// Rekursion (unkritisch)
//   makefont((HFONT)wParam,LOWORD(lParam));
   // of wird vom Destruktor zerstört
//  }return TRUE;		// nicht durchleiten!
//  case WM_DESTROY: {
//   Font of(wnd);	// Destruktor macht DeleteFont
//  }break;
    case WM_PAINT: {
      PAINTSTRUCT ps;
      BeginPaint(wnd,&ps);
      SendMessage(wnd,WM_PRINTCLIENT,(WPARAM)ps.hdc,0);
      EndPaint(wnd,&ps);
    }return 0;
    case WM_PRINTCLIENT: {
      TCHAR s[64];
      GetWindowText(wnd,s,elemof(s));
      Rect rc;
      GetClientRect(wnd,&rc);
      HDC dc=(HDC)wParam;
      HFONT ofont=SelectFont(dc,GetWindowFont(wnd));
      SetBkMode(dc,TRANSPARENT);
      hehaDrawText(dc,s,-1,&rc,DT_SUPERSUB);
    }return 0;
  }
  return CallWindowProc(oldproc,wnd,msg,wParam,lParam);
}

/*************
 * DrawTextX *
 *************/
class DrawTextStack;
// 1 Text-Lauf
struct TextRun{
  SIZE sz;	// Größe des Laufs (nur Text, nicht Tabstopps)
  int ascent;	// tm.tmAscent der zugehörigen Schrift oder des Bildchens
  int descent() const {return sz.cy-ascent;}
  int dy;	// y-Versatz für Subscript und Superscript bzgl. Grundlinie der Normalschrift
  int tabx[2];	// Extra-X für vorausgehende / angehängte Tabstopps
  TCHAR*s;	// zeigt in von "&" befreiten und mit "..." präparierten Arbeits-String (nicht nullterminiert)
  int slen;	// Zeichenzahl
  HFONT f;	// Der Destruktor ~TextRun sollte DeleteObject() aufrufen!
  Color fore,back;
  void measure(const DrawTextStack&);
  void paint(const DrawTextStack&) const;
  TextRun() {memset(this,0,sizeof*this);}
//  ~TextRun() {if (f) DeleteFont(f);}
};
// 1 Text-Zeile
struct TextLine{
  SIZE sz;	// Gesamtgröße der Zeile
  int dy;	// Gemeinsame Grundlinie für Normaltext
  std::vector<TextRun>runs;	// Alle Text-Läufe der Zeile, kann leer sein für Leerzeile
  int startx(const DrawTextStack&) const;
  void measure(const DrawTextStack&);
  int paint(const DrawTextStack&,int yy) const;
  void complete(DrawTextStack&);
  TextLine() {sz.cx=0;}
};

class DrawTextStack{
  friend struct TextRun;
  friend struct TextLine;
// Eingangsparameter
  HDC dc;
  const TCHAR*s,*s_end;	// source string and source-string end (onto '\0' when -1 was given for slen argument)
  TCHAR*buf;
  UINT uFormat;
  Rect rect;
  DRAWTEXTPARAMS dtp;
// Prozessparameter
  TCHAR*underscore_pos;
  void paintUnderscore();
  void rotate(int&dx,int&dy) const;
  void setPosition(int x, int y, int dx, int dy, bool=false) const;
  void offsetPosition(int dx,int dy) const;
  std::vector<TextLine> lines;	// Alle Text-Zeilen
  void newLine()	{TextLine tmp; lines.insert(lines.end(),tmp);}
  void completeLine()	{lines.back().complete(*this);}
  void lines_and_runs();// Zerstückelt den Text in Zeilen und Läufe
  TextRun run;		// Textlauf im Aufbau (zu Ablage in lines.back().runs)
  LOGFONT lf;		// Aktuelle logische Schrift
  TCHAR*bufptr;		// Schreibzeiger
  unsigned flags;	// Aktuelle Schalter (Fettschrift usw.)
  int tabs[2];		// Aktuelle Anzahl von Tabs vor und nach dem Lauf
  void putChar(TCHAR c) {*bufptr++=c;}	// bufptr zeigt garantiert auf einen genügend großen Puffer
  void startRun() {run.s=bufptr;tabs[1]=tabs[0]=0;}	// vermerkt 1. Zeichen
  void saveRun();	// speichert den gefundenen Textlauf
  bool invert_x,invert_y,swap_xy;
  int tabwidth;		// in Pixel
  int oheight;		// Höhe der Standardschrift (für Leerzeilen)
  int sincos[2];	// Textrotation*65536, >>16 ansetzen
  HFONT ofont;		// Schrift beim Funktionsaufruf
  COLORREF ocolor;	// Textfarbe beim Funktionsaufruf
public:
  DrawTextStack(HDC,TCHAR*,int,RECT*,UINT,DRAWTEXTPARAMS*);
  int getHeight() const {return rect.height();}
};

void TextRun::measure(const DrawTextStack&dts) {
  SelectFont(dts.dc,f);
  TEXTMETRIC tm;
  GetTextMetrics(dts.dc,&tm);
  ascent=tm.tmAscent;
  GetTextExtentPoint32(dts.dc,s,slen,&sz);
  if (sz.cy!=tm.tmHeight) DebugBreak();
  if (dts.uFormat&DT_EXTERNALLEADING) sz.cy+=tm.tmExternalLeading;	// Durchschuss: Hier: unten anfügen
  const TextLine&tl=dts.lines.back();
  if (dts.tabs[0]) {
    int w=tl.sz.cx+tm.tmAveCharWidth+sz.cx;	// Bisherige Ausgabebreite + Mindestabstand
    int n=w%dts.tabwidth+dts.tabs[0];	// Ziel-Tabstopp (TODO: Anwender-Tabstoppliste)
    tabx[0]=n*dts.tabwidth-w;	// Tabweite davor (>=0)
    sz.cx+=tabx[0];		// Tabweite einrechnen
  }
  if (dts.tabs[1]) {
    int w=tl.sz.cx+sz.cx+tm.tmAveCharWidth;	// Bisherige Ausgabebreite + Mindestabstand
    int n=w%dts.tabwidth+dts.tabs[1];	// Ziel-Tabstopp (TODO: Anwender-Tabstoppliste)
    tabx[1]=n*dts.tabwidth-w;	// Tabweite danach (>=0)
    sz.cx+=tabx[1];		// Tabweite einrechnen
  }
}

void TextRun::paint(const DrawTextStack&dts) const{
  SelectFont(dts.dc,f);
  SetTextColor(dts.dc,fore);
  SetBkMode(dts.dc,back!=CLR_INVALID?OPAQUE:TRANSPARENT);
  if (back!=CLR_INVALID) SetBkColor(dts.dc,back);
// Abhängig von der Textrichtung (bei gedrehtem Text)
  dts.offsetPosition(tabx[0],dy);	// Tab davor und auf Exponent/Index-Grundlinie
  ExtTextOut(dts.dc,0,0,	// TA_UPDATECP is in effect
	/*(dts.uFormat&DT_NOCLIP ?*/ 0 /*: ETO_CLIPPED)*/ |
	(dts.uFormat&DT_RTLREADING ? ETO_RTLREADING : 0),
	&dts.rect,s,slen,0);
// TODO: Überstreichung zeichnen
  dts.offsetPosition(tabx[1],-dy);	// Tab danach und zurück zur Textzeilen-Grundlinie
}
void TextLine::measure(const DrawTextStack&dts) {
  int width=0, ascent=0, descent=0;
  for (std::vector<TextRun>::iterator tr=runs.begin();tr!=runs.end();tr++) {
    width+=tr->sz.cx;		// Breite noch einmal zusammenzählen (Tabs am Rand sind verschwunden)
    int h=tr->ascent-tr->dy;	// Platzbedarf über Grundlinie
    if (ascent<h) ascent=h;
    h=tr->descent()+tr->dy;	// Platzbedarf unter Grundlinie
    if (descent<h) descent=h;
  }
  sz.cx=width;
  sz.cy=ascent+descent;
  dy=ascent;			// Position der Grundlinie in der Gesamt-Textzeilenbox
}

void TextLine::complete(DrawTextStack&dts) {
  if (dts.uFormat&DT_RIGHT) runs.front().tabx[0]=0;	// kein Tab am Anfang mitzählen
  else runs.back().tabx[1]=0;				// kein Tab am Ende mitzählen
  measure(dts);			// Abmessungen bestimmen
  if (sz.cx>dts.rect.width()) {	// Zeile zu lang?
   if (dts.uFormat&DT_END_ELLIPSIS) ;
   if (dts.uFormat&DT_PATH_ELLIPSIS) ;
  }
}

int TextLine::startx(const DrawTextStack&dts) const{
// relativ zu dts.rect.left!
  if (!(dts.uFormat&(DT_CENTER|DT_RIGHT))) return 0;
  int x=dts.rect.width()-sz.cx;
  if (dts.uFormat&DT_CENTER) return x>>1;
  return x;
}

int TextLine::paint(const DrawTextStack&dts,int yy) const{
  int dx=startx(dts);
  dts.setPosition(dts.rect.left,dts.rect.top,dx,yy+dy);		// nach unten und ggf. nach rechts in Schreibrichtung
  for (std::vector<TextRun>::const_iterator tr=runs.begin();tr!=runs.end();tr++) tr->paint(dts);
  return yy+sz.cy;		// Nächste Zeile darunter
}
  
// Zum Lesen und Zerstückeln des Vorgabe-Strings
struct Parser{
 const TCHAR*s,*end;
 Parser(const TCHAR*_s,const TCHAR*_end):s(_s),end(_end) {}
 TCHAR*getName(TCHAR*name,TCHAR*end);	// Fontname, mit 0..0x1F oder ';' terminiert
 void getSize(long&size);	// Schriftgröße im Folgebyte, negativ = absolut, positiv = relativ zur Basis 32
 void getRGB(Color&color);	// Vorder- oder Hintergrundfarbe, 3 Bytes (bei Unicode: LowBytes) folgen
 inline bool eos() const {return s==end;}
 TCHAR peekChar(int d=0) const {return s+d<end?s[d]:0;}
 TCHAR getChar() {return eos()?0:*s++;}
 bool suitableEnd(int d=0,bool extra=false) {
   TCHAR e=peekChar(d); if (e<=' ') return true;
   if (extra && (e=='^' || e=='_') && peekChar(d+1)>' ') return true;	// end of italic may be start of ^ and _
   if ((e==',' || e==';' || e==':' || e=='.')	// punktuation
    && peekChar(d+1)<=' ') return true;
   return false;
 }
private:
 Parser(const Parser&);		// kein Copy-Konstruktor!
};

TCHAR*Parser::getName(TCHAR*name,TCHAR*end) {
 for(;;) {
  TCHAR c=peekChar();
  if (c<32 || c>=127) break;	// Das nächste Zeichen ist Teil des nächsten Runs
  s++;
  if (c==';') break;		// Das ';' ist /nicht/ Teil des nächsten Runs
  if ((end-name)>1) {*name++=c;}	// Name Zeichen für Zeichen abspeichern, Platz für '\0' lassen
 }
// Nun ist s entsprechend vorgerückt und der Schriftname kopiert
 if (name!=end) *name=0;
 return name;
}

void Parser::getSize(long&size) {
 char arg=(char)getChar();
 if (!arg) return;
 if (arg>0) arg=char((size*arg)>>5);
 size=arg;
}

void Parser::getRGB(Color&color) {
  if ((end-s)<3) return;
  color=RGB(s[0],s[1],s[2]);
  s+=3;
}

void DrawTextStack::rotate(int&dx,int&dy) const{
  int t=dx;
  dx=(dx*sincos[1]+dy*sincos[0])>>16;
  dy=(-t*sincos[0]+dy*sincos[1])>>16;
}

void DrawTextStack::setPosition(int x, int y, int dx, int dy, bool line) const{
  rotate(dx,dy);
  x+=dx; y+=dy;
  if (line) LineTo(dc,x,y);
  else MoveToEx(dc,x,y,0);
}

void DrawTextStack::offsetPosition(int dx, int dy) const{
  if (!dx && !dy) return;
  POINT pt;
  GetCurrentPositionEx(dc,&pt);
  setPosition(pt.x,pt.y,dx,dy);
}

void DrawTextStack::paintUnderscore() {
  if (!underscore_pos) return;
  int idx;
  int xx,yy=0;	// Position des Textlaufs
  std::vector<TextLine>::iterator tl;
  std::vector<TextRun>::iterator tr;	// Zu suchender Textlauf
  for (tl=lines.begin();tl!=lines.end();tl++) {
    xx=tl->startx(*this);
    for (tr=tl->runs.begin();tr!=tl->runs.end();tr++) {
      idx = int(underscore_pos - tr->s);
      if (idx < tr->slen) goto found;
      xx+=tr->sz.cx;
    }
    yy+=tl->sz.cy;
  }
  return;
found:
// Measure start and end position
  SIZE sz;
  SelectFont(dc,tr->f);
  int xa,xe;
  GetTextExtentPoint32(dc,tr->s,idx,&sz);
  xa=xx+sz.cx;
  GetTextExtentPoint32(dc,tr->s,idx+1,&sz);
  xe=xx+sz.cx;
  yy+=tl->dy+tr->dy+1;	// really inside descent, above the '_' character
// draw the 1 pixel wide line
  setPosition(rect.left,rect.top,xa,yy);
  Pen pen(0,ocolor);
  HPEN open=SelectPen(dc,pen);
  setPosition(rect.left,rect.top,xe,yy,true);
  SelectPen(dc,open);
}

struct Scope{
  const TCHAR*end;	// pointer to end of " *bold* ", " /italic/ ", " _underline_ ", " `code` ", "a_i ", and "b^j "
  unsigned mask;	// type of scope; end is:   ^		  ^		   ^	       ^        ^	    ^
};
// These 4 paired controls must be surrounded by whitespace: spaces, tabs, line breaks, string start/end or control codes,
// but these can span even multiple lines. Nesting and combining is allowed: " /*bolditalic*/ " is bold+italic.

bool isBreakChar(TCHAR c) {	// Ex: C_i, K_j: Comma should not be part of index
 if (c<=' ') return true;	// Ex: *bold*, and /!bold/. : , and . should be „acceptable valid whitespace“ for ending
 if (c==',') return true;
 if (c==';') return true;
 return false;
}

void DrawTextStack::lines_and_runs() {
  underscore_pos=0;
  flags=0;
  bufptr=buf;
  newLine();
  startRun();
  run.fore=ocolor;
  run.back=GetBkMode(dc)==TRANSPARENT?CLR_INVALID:GetBkColor(dc);
  TCHAR char_before=' ';
  Scope sc={s_end,0};
  std::stack<Scope,std::vector<Scope> >scopes;	// eigentlich als Stack
  scopes.push(sc);	// Gesamter Eingabestring als Scope
// The passage must not contain control characters nor TABs.
// Scopes can nest but cannot crossover: "*a _b* c_" doesn's make "b" bold+underlined but emits "a _b" in bold
// Subscript/Superscript inside are supported: "*A_i*" emits "Ai" (i as subscript) all in bold
// But that's not possible: "/A/_i" meaning italic A with subscript i. Use control characters to achieve this.
// "&" processing: All single "&" are eaten up, but "&&" leads to "&". The first "&" controls the next letter to underscored.
// The "*/_`" characters are removed (i.e. not shown) when DT_KEEPCONTROLS is not present.
// It's not recommended to intermix private control charactes (e.g. 0x10..0x1F) and that form above.

// Moreover, "_" and "^" activate subscript / superscript in following cases:
// - Not prefaced by whitespace or start-of-text
// - Not followed by whitespace or end-of-text
  for (Parser p(s,s_end);;) {
    while ((sc=scopes.top()).end==p.s) {
      if (sc.mask&0x87) {
        TCHAR c=p.getChar();	// take scope-ending character (there MUST be one)
        if (uFormat&DT_KEEPCONTROLS) putChar(c);
      }
      saveRun();
      flags^=sc.mask;
      scopes.pop();
      if (scopes.empty()) return;
//      char_before=' ';
    }
    TCHAR c=p.getChar();
    switch (c) {
      case '&': if (!(uFormat&DT_NOPREFIX) && p.peekChar()>' ') {	// don't accept & as prefix at no-printable characters here
        c=p.getChar();		// Unconditionally take next character literally, even '&'
        if (c!='&' && !underscore_pos) underscore_pos=bufptr;	// Take the first occurence for underscoring
        goto keep_char;
      }break;
	// newline can be "\r", "\n", "\r\n", and "\n\r", however "\n\n" are two newlines
      case '\r': if (p.peekChar()=='\n') ++p.s; goto newline;
      case '\n': if (p.peekChar()=='\r') ++p.s; newline:
      if (!(uFormat&DT_SINGLELINE)) {
        saveRun();		// Textlauf hier abbrechen
	completeLine();		// Zeile abschließen, vermessen, kürzen
	newLine();		// Neue Zeile erstellen
	char_before=c;
	continue;
      }break;
      case '\t': if (uFormat&DT_EXPANDTABS) {
        ++tabs[1];		// count right-side = left-align (standard) tabs
        while (p.peekChar()==c) {
	  ++p.s;
	  ++tabs[1];
	}
	saveRun();		// Textlauf hier beenden
        char_before=c;
	continue;
      }break;
      case '\b': if (uFormat&DT_EXPANDTABS) {
	saveRun();		// Textlauf hier beenden
        ++tabs[0];		// count left-side = right-align (extra) tabs
        while (p.peekChar()==c) {
	  ++p.s;
	  ++tabs[0];
	}
        char_before=c;
	continue;
      }break;
    }
    if ((c=='*' || c=='/' || c=='_' || c=='`')	// candidate for passage start?
     && char_before<=' '
     && p.peekChar()>' ') {
      switch (c) {
        case '*': sc.mask=1; break;
        case '/': sc.mask=2; break;
        case '_': sc.mask=4; break;
	default: sc.mask=0x80;
      }
      if (flags&sc.mask) goto keep_char;	// cannot nest same attribute
      TCHAR e,b=0;	// character before terminating */_` (must be a displayable character)
      for (Parser q(p.s,scopes.top().end);;b=e) {
	e=q.peekChar();		// Nicht auf Null testen; Farbkodes enthalten regulär Nullen
	if (b>' ' && e==c && q.suitableEnd(1,true)) {	// found suitable end (non-greedy, i.e. the first one)
	  sc.end=q.s;		// end of bold/italic/underlined/code passage
	  scopes.push(sc);
	  break;
	}
	q.s++;
	if (q.eos()) goto keep_char;	// Can span anything, but cannot nest with the same attribute ("_a *b _c_ b* a_" ends at "c_")
      }
      saveRun();
      flags^=sc.mask;
      if (uFormat&DT_KEEPCONTROLS) putChar(c);	// let charBefore be <=' '
      continue;
    }
    if ((c=='_' || c=='^')	// Candidate for subscript/superscript?
     && char_before>' '		// not a leading _ or ^
     && p.peekChar()>' '	// not a trailing _ or ^
     && uFormat&DT_SUPERSUB	// support enabled?
     && !(flags&0x300)) {	// Nesting is not allowed as there is only one level of super/subscript
      sc.mask= c=='^' ? 0x200 : 0x100;
      saveRun();
      flags^=sc.mask;
      for (Parser q(p.s,scopes.top().end);;q.s++) {
	if (q.suitableEnd()) {	// also at parent scope end
	  sc.end=q.s;		// this scope ends here
	  break;
	}
      }
      scopes.push(sc);
      continue;			// don't store/show that character
    }
    if (c==0x0E) {
      saveRun();
      int nest=1;
      sc.end=0;
      for (Parser q(p.s,scopes.top().end);!q.eos();q.s++) {
        TCHAR c=q.peekChar();
	switch (c) {
	  case 0x0E: nest++; break;
	  case 0x0F: if (--nest) break;
	  case 0: sc.end=q.s; goto exitloop;
	  case 0x1E:
	  case 0x1F: q.s+=3; break;	// Farbkodes: Überspringen
	}
      }
exitloop:
      if (sc.end) {
        sc.mask=0;	// TODO: Irgendetwas sinnvolles!
        scopes.push(sc);
      }
      continue;
    }
    if (c==0x0F) continue;	// solche ohne vorheriges 0x0E ignorieren
    if (0x10<=c && c<=0x1F) {
      saveRun();
      unsigned m=1<<(c-0x10);
      flags^=m;
      switch (c) {
        case 0x1C: p.getName(lf.lfFaceName,lf.lfFaceName+sizeof lf.lfFaceName); break;
        case 0x1D: p.getSize(lf.lfHeight); break;
        case 0x1E: p.getRGB(run.fore); break;
        case 0x1F: p.getRGB(run.back); break;
      }
      char_before=' ';
      continue;
    }
keep_char:
    putChar(char_before=c);
  }
}

void DrawTextStack::saveRun() {
  run.slen=int(bufptr-run.s);
  if (!run.slen) return;
  LOGFONT lf2=lf;		// Struktur kopieren (Schriftart und Größe behalten)
  if (flags&0x0001) lf2.lfWeight^=FW_BOLD^FW_NORMAL;;	// fett
  if (flags&0x0002) lf2.lfItalic^=TRUE;	// kursiv
  if (flags&0x0004) lf2.lfUnderline^=TRUE;	// unterstreichen
  if (flags&0x0008) lf2.lfStrikeOut^=TRUE;	// durchstreichen
  if (flags&0x0010) lf2.lfHeight=MulDiv(lf2.lfHeight,125,100);// 25% größer
  if (flags&0x0320) lf2.lfHeight=MulDiv(lf2.lfHeight,75,100);	// 25% kleiner (auch bei Super- und Subscript)
  if (flags&0x0040) {
    lf2.lfPitchAndFamily|=FF_ROMAN;
    lstrcpyn(lf2.lfFaceName,TEXT("Times New Roman"),sizeof lf2.lfFaceName);
  }
  if (flags&0x0080) {
//    lf2.lfCharSet=SYMBOL_CHARSET;
    lf2.lfPitchAndFamily|=FIXED_PITCH;
    lstrcpyn(lf2.lfFaceName,TEXT("Courier New"),sizeof lf2.lfFaceName);
  }
  run.dy=0;
  if (flags&0x0100) 
   run.dy=MulDiv(lf2.lfHeight,100,100)-lf.lfHeight;	// tiefgestellt, dy positiv
  else if (flags&0x0200) run.dy=MulDiv(lf2.lfHeight,60,100);	// hochgestellt, dy negativ
  run.f=CreateFontIndirect(&lf2);
  run.measure(*this);
  TextLine&tl=lines.back();
  tl.runs.push_back(run);
  tl.sz.cx+=run.sz.cx;	// Zeilenlänge aktuell halten, um Tabweiten zu berechnen und Wortumbruch zu realisieren
  startRun();	// Neuer Start
}

DrawTextStack::DrawTextStack(HDC _dc,TCHAR*_s,int slen,RECT*rc,UINT _uFormat,DRAWTEXTPARAMS*_dtp):
 dc(_dc),
 s(_s),
 uFormat(_uFormat) {
  if (uFormat&DT_SINGLELINE) uFormat&=~DT_WORDBREAK;
  if (uFormat&DT_CALCRECT) uFormat|=DT_NOCLIP;
  if (uFormat&DT_CENTER) uFormat&=~DT_RIGHT;	// DT_CENTER has precedence over DT_RIGHT
//  if (!(~uFormat&(DT_VCENTER|DT_BOTTOM))) uFormat&=~(DT_BOTTOM|DT_VCENTER);	// Both flags make DT_TOP (Win10)
  if (uFormat&DT_VCENTER) uFormat&=~DT_BOTTOM;	// 100% compatibility is not needed: DT_VCENTER has precedence over DT_BOTTOM
// Note that - in this implementation - vertical alignment applies to multiline text too!
  if (uFormat&DT_HIDEPREFIX) uFormat&=~DT_PREFIXONLY;
  CopyRect(&rect,rc);
  TEXTMETRIC tm;
  GetTextMetrics(dc,&tm);
  oheight = tm.tmHeight;
  if (uFormat&DT_EXTERNALLEADING) oheight+=tm.tmExternalLeading;	// Durchschuss für Zeilenhöhe dazu
  ocolor=GetTextColor(dc);
  int l=slen<0?lstrlen(s):slen;
  s_end=s+l;
  buf=new TCHAR[l+4];		// auch für _alloca geeignet
  memset(&dtp,0,sizeof dtp);
  if (_dtp) {
    memcpy(&dtp,_dtp,min(_dtp->cbSize,sizeof dtp));	// copy structure
    _dtp->uiLengthDrawn=0;     // This param RECEIVES number of chars processed
  }
  rect.left+=dtp.iLeftMargin;
  rect.right-=dtp.iRightMargin;
  invert_y=false;
  if (GetGraphicsMode(dc)==GM_COMPATIBLE) {
    SIZE window_ext, viewport_ext;
    GetWindowExtEx(dc, &window_ext);
    GetViewportExtEx(dc, &viewport_ext);
    if ((window_ext.cy^viewport_ext.cy)<0) invert_y=true;
  }
  int tabstop = ((uFormat&DT_TABSTOP) && dtp.iTabLength) ? dtp.iTabLength : 8;
  tabwidth = tm.tmAveCharWidth * tabstop;

  ofont=(HFONT)GetCurrentObject(dc,OBJ_FONT);
  GetObject(ofont,sizeof lf,&lf);
  double arg=lf.lfEscapement*(3.14159265/1800);
  sincos[0]=lrint(sin(arg)*65536);	// Vorzeichen ist noch zu testen!! Kommt auf den Umlaufsinn von lfEscapement an!
  sincos[1]=lrint(cos(arg)*65536);

// 1. Bei MultiLine an Leerzeichen auf mehrere Zeilen verteilen
//    Bei SingleLine und EndEllipsis kürzen
//    Kaufmannsund entfernen
  lines_and_runs();
  if (!(uFormat&DT_SINGLELINE))
   while (lines.size() && !(lines.back().sz.cx)) lines.pop_back();	// Leerzeilen am Ende entfernen, sogar 0 Zeilen sind möglich
// 2. Zeilenhöhen und (nochmal) -längen messen, Gesamtabmessungen bestimmen
// Die Läufe sind bereits vermessen
  SIZE box={0,0};
  for (std::vector<TextLine>::iterator tl=lines.begin(); tl!=lines.end(); tl++) {
    tl->measure(*this);	// Zeilen vermessen: Vor allem Höhe bestimmen, Breite ist schon vorher bearbeitet
    if (box.cx<tl->sz.cx) box.cx=tl->sz.cx;	// Maximum
    box.cy+=tl->sz.cy;	// Summe
  }
  if (uFormat&DT_RIGHT) rect.left=rect.right-box.cx;
  else{
    if (uFormat&DT_CENTER) rect.left+=(rect.width()-box.cx)>>1;
    rect.right=rect.left+box.cx;
  }
  if (uFormat&DT_BOTTOM) rect.top=rect.bottom-box.cy;
  else{
    if (uFormat&DT_VCENTER) rect.top+=(rect.height()-box.cy)>>1;
    rect.bottom=rect.top+box.cy;
  }

  if (uFormat&DT_CALCRECT) CopyRect(rc,&rect);
  else{
    SaveDC(dc);
    if (!(uFormat&DT_NOCLIP)) IntersectClipRect(dc,rc->left,rc->top,rc->right,rc->bottom);
    if (!(uFormat&DT_PREFIXONLY)) {
      MoveToEx(dc,rect.left,rect.top,0);
      SetTextAlign(dc,TA_UPDATECP|TA_BASELINE);
      int yy=0;
      for (std::vector<TextLine>::iterator tl=lines.begin(); tl!=lines.end(); tl++) yy=tl->paint(*this,yy);
    }
    if (!(uFormat&DT_HIDEPREFIX)) {
      paintUnderscore();
    }
    RestoreDC(dc,-1);
  }
  SelectFont(dc,ofont);
// TODO: Font-Handles im Cache halten
  for (std::vector<TextLine>::iterator tl2=lines.begin();tl2!=lines.end();tl2++) {
    for (std::vector<TextRun>::iterator tr=tl2->runs.begin();tr!=tl2->runs.end();tr++) {
      DeleteFont(tr->f);	// Wegen fehlendem Raubcopy-Konstruktor und std::vector.emplace() Schrift zu Fuß löschen (MSVC6-Problem)
    }
  }
  delete[] buf;
}

// Written with the help of wine source code
// Support for free angle (rectangle used as if non-rotated)
// TODO: Support for 90-degree angles (rectangle as true box)
// Support for inline user graphics, possibly calling WM_MEASUREITEM and WM_DRAWITEM with HWND==0
int WINAPI hehaDrawTextEx(HDC dc,TCHAR*s,int slen,RECT*rc,UINT uFormat,DRAWTEXTPARAMS*dtp) {
  if (!s) return 0;
  DrawTextStack dts(dc,s,slen,rc,uFormat|DT_SUPERSUB,dtp);
  return dts.getHeight();
}

int WINAPI hehaDrawText(HDC dc,const TCHAR*s,int slen,RECT*rc,UINT uFormat) {
 return hehaDrawTextEx(dc,const_cast<TCHAR*>(s),slen,rc,uFormat,0);
}

/***************
 * Test-Dialog *
 ***************/

static void Parse(TCHAR*s,int*ip,int ilen) {
  memset(ip,0,ilen*sizeof(int));
  do{
    *ip++=_tcstol(s,&s,0);
    if (!*s) return;
    ++s;
  }while(--ilen);
}

static void Parse(TCHAR*s,TCHAR*name,int nlen,int&size,unsigned&flags,int&rot) {
  TCHAR*p=_tcschr(s,',');	// 1. Teilausdruck: Schriftname
  if (p) *p=0;
  lstrcpyn(name,s,nlen);
  if (!p) return;
  s=p+1;
  int i[3];
  Parse(s,i,elemof(i));
  size=i[0];		// 2. Teilausdruck: Schriftgröße
  flags=i[1];		// 3. Teilausdruck: Flags siehe plotxy.h
  rot=i[2];		// 4. Teilausdruck: Rotation
}

static void Parse(TCHAR*s,RECT&rc) {
  Parse(s,(int*)&rc.left,4);
}
static void Parse(TCHAR*s,DRAWTEXTPARAMS&dtp) {
  dtp.cbSize=sizeof dtp;
  Parse(s,&dtp.iTabLength,4);
}
static unsigned Parse(TCHAR*&s,int atmost,int base) {
  TCHAR*t,m=s[atmost];
  s[atmost]=0;
  unsigned c=_tcstoul(s,&t,base);
  s[atmost]=m;
  s=t;
  return c;
}
static void Parse(TCHAR*s,TCHAR*d) {
  unsigned c;
  do{
    if ((c=*s++)=='\\') switch (c=*s++) {
      case 'a': c=7; break;
      case 'b': c=8; break;
      case 't': c=9; break;
      case 'n': c=10; break;
      case 'v': c=11; break;
      case 'f': c=12; break;
      case 'r': c=13; break;
      case 'e': c=27; break;
      case 'x': c=Parse(s,2,16); break;
      case 'u': c=Parse(s,4,16); break;
      case 'U': c=Parse(s,8,16); break;
      default: if ('0'<=c && c<='7') {--s; c=Parse(s,3,8);}
    }
#ifdef UNICODE
    if (c>0x10000) {		// only possible in case of "\Uxxxxxxxx"
      c-=0x10000;
      *d++=0xD800|c>>10;	// save high surrogate first
      c=0xDC00|c&0x03FF;	// then construct low surrogate
    }
#endif
    *d++=c;
  }while(c);
}

INT_PTR WINAPI DrawTextTestDlg(HWND Wnd,UINT msg,WPARAM wParam,LPARAM lParam) {
  switch (msg) {
    case WM_INITDIALOG: {
      SetDlgItemText(Wnd,10,TEXT("&A_b\\nC_b"));
      SetDlgItemText(Wnd,11,TEXT("Arial,-20,0x00,0"));
      SetDlgItemText(Wnd,12,TEXT("100,100,300,200"));
      SetDlgItemText(Wnd,13,TEXT("8,0,0,0"));
      CheckDlgButton(Wnd,48,BST_CHECKED);
      CheckDlgButton(Wnd,24,BST_CHECKED);	// DT_NOCLIP
    }return TRUE;
    case WM_DRAWITEM: {
      TCHAR s[128];
      GetDlgItemText(Wnd,11,s,elemof(s));
      struct FontDesc{
        TCHAR name[32];
        int size;
        unsigned flags;
        int rotation;
      }fs;
      Parse(s,fs.name,elemof(fs.name),fs.size,fs.flags,fs.rotation);
      GetDlgItemText(Wnd,12,s,elemof(s));
      Rect rc;
      Parse(s,rc);
      GetDlgItemText(Wnd,13,s,elemof(s));
      DRAWTEXTPARAMS dtp;
      Parse(s,dtp);
      GetDlgItemText(Wnd,10,s,elemof(s));
      Parse(s,s);
      UINT uFormat=0;
      for (int i=0; i<32; i++) {
        if (IsDlgButtonChecked(Wnd,16+i)==BST_CHECKED) uFormat|=1U<<i;
      }
      Font f(fs.name,fs.size,fs.flags,fs.rotation);
      DRAWITEMSTRUCT&dis=*(DRAWITEMSTRUCT*)lParam;
      int omode=SetBkMode(dis.hDC,TRANSPARENT);	// um das Rechteck zu sehen
      HFONT ofont=SelectFont(dis.hDC,f);
      HBRUSH obr=SelectBrush(dis.hDC,GetStockBrush(HOLLOW_BRUSH));
      {
        Pen p(0,RGB(255,0,0));	// rot
	HPEN open=SelectPen(dis.hDC,p);
        Rectangle(dis.hDC,rc.left,rc.top,rc.right,rc.bottom);
	SelectPen(dis.hDC,open);
      }
      int h=(IsDlgButtonChecked(Wnd,50)?wineDrawTextEx:IsDlgButtonChecked(Wnd,49)?hehaDrawTextEx:DrawTextEx)	// zum Vergleich
       (dis.hDC,s,-1,&rc,uFormat,&dtp);	// Test der Funktion
      if (uFormat&DT_CALCRECT) {
        Pen p(0,RGB(0,255,0));	// grün
	HPEN open=SelectPen(dis.hDC,p);
        Rectangle(dis.hDC,rc.left,rc.top,rc.right,rc.bottom);
	SelectPen(dis.hDC,open);
      }
      _sntprintf(s,elemof(s),TEXT("{%d,%d,%d,%d},%d"),
       rc.left,rc.top,rc.right,rc.bottom,h);
      SetDlgItemText(Wnd,14,s);
      _vsntprintf(s,elemof(s),TEXT("{%d,%d,%d,%d,%d}"),(va_list)&dtp);
      SetDlgItemText(Wnd,15,s);
      SetBkMode(dis.hDC,omode);
      SelectBrush(dis.hDC,obr);
      SelectFont(dis.hDC,ofont);
    }break;
    case WM_COMMAND: switch (wParam) {
      case IDOK:
      case IDCANCEL: EndDialog(Wnd,wParam); break;
      case MAKELONG(10,EN_CHANGE):
      case MAKELONG(11,EN_CHANGE):
      case MAKELONG(12,EN_CHANGE):
      case MAKELONG(13,EN_CHANGE): InvalidateRect(GetDlgItem(Wnd,64),0,TRUE); break;
      default: if (16<=wParam && wParam<51) InvalidateRect(GetDlgItem(Wnd,64),0,TRUE);
    }break;
  }
  return FALSE;
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded