#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: UTF-8 | 0
|