MeasureString und DrawString - GDI auf die harte Tour



  • vieleicht hilfts:

    /*
    ===============================================================================
    MeassureText_NitasTrickMethod
    
    ===============================================================================
    */
    void MeassureText_NitasTrickMethod(long *width, long *height, char *text, HDC hDCMem){
    
    	SIZE			sizeText, sizeLastCharacter;
    	RECT			rect;
    	HBITMAP		hBitmap, hOldBitmap;
    	int			i, iXmax, iYmed;
    	boolean			bFound;
    
    	GetTextExtentPoint32(hDCMem, text, lstrlen(text), &sizeText);
    	*height = sizeText.cy;
    	*width = sizeText.cx;
    	if((text == NULL) || (text[0] == '\0')){
    		TEXTMETRIC	tm;
    		GetTextMetrics(hDCMem,&tm);
    		*height = tm.tmHeight;
    		return;
    	}
    	GetTextExtentPoint32(hDCMem, &text[-1+lstrlen(text)], 1, &sizeLastCharacter);
    	rect.left = 0; 
    	rect.top = 0; 
    	rect.right = *width + sizeLastCharacter.cx; 
    	rect.bottom = *height;
    	hBitmap= CreateCompatibleBitmap(hDCMem, rect.right-rect.left, rect.bottom-rect.top);
    	hOldBitmap = (HBITMAP)SelectObject(hDCMem, hBitmap);
    	FillRect(hDCMem, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
    	SetBkColor(hDCMem,RGB(0,0,0)); 
    	DrawText(hDCMem, text, -1, &rect, (DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOPREFIX));
    	iXmax = 0;
    	bFound = FALSE;
    	iYmed = (rect.bottom + rect.top)/2;
    	for(i = rect.right-1; i >= 0 && !bFound; i--){
    		COLORREF	rgbColor = GetPixel(hDCMem, i, iYmed);
    		if(rgbColor != RGB(255,255,255)){
    			iXmax = i;
    			bFound = TRUE;
    		}
    	}
    	*width = iXmax + 1;
    	SelectObject(hDCMem, hOldBitmap);
    	DeleteObject(hBitmap);
    }
    
    /*
    ===============================================================================
    MeassureText_SmartMethod
    
    ===============================================================================
    */
    void MeassureText_SmartMethod(long *width, long *height, char *text, HDC hDCMem){
    
    	SIZE			sizeText, sizeLastCharacter;
    	RECT			rect;
    	boolean			bFound;
    	int			x, y, iXmax;
    	HBITMAP		hBitmap, hOldBitmap;
    
    	GetTextExtentPoint32(hDCMem, text, lstrlen(text), &sizeText);
    	*height = sizeText.cy;																		//height==0 if the text is empty, so try GetTextMetrics below!
    	*width = sizeText.cx;
    	if((text == NULL) || (text[0] == '\0')){
    		TEXTMETRIC	tm;
    		GetTextMetrics(hDCMem,&tm);
    		*height = tm.tmHeight;
    		return;
    	}
    	GetTextExtentPoint32(hDCMem, &text[lstrlen(text) - 1], 1, &sizeLastCharacter);
    	rect.left = 0; 
    	rect.top = 0; 
    	rect.right = *width + sizeLastCharacter.cx; 
    	rect.bottom = *height;
    	hBitmap = CreateCompatibleBitmap(hDCMem, rect.right-rect.left, rect.bottom-rect.top);
    	hOldBitmap = (HBITMAP)SelectObject(hDCMem, hBitmap);
    	FillRect(hDCMem, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
    	DrawText(hDCMem, text, -1, &rect, (DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOPREFIX));
    	iXmax = 0;
    	bFound = FALSE;
    	for(x = rect.right-1; x >= 0 && !bFound; x--){
    		for(y = 0; y <= rect.bottom-1 && !bFound; y++){
    			COLORREF	rgbColor = GetPixel(hDCMem,x,y);
    			if(rgbColor != RGB(255,255,255)){
    				iXmax = x;
    				bFound = TRUE;
    			}
    		}
    	}	
    	*width = iXmax + 1;
    	SelectObject(hDCMem, hOldBitmap);
    	DeleteObject(hBitmap);
    }
    
    /*
    ===============================================================================
    MeassureText_ClassicMethod
    
    ===============================================================================
    */
    void MeassureText_ClassicMethod(long *width, long *height, char *text, HDC hDC, double *dOverhangTrailing, boolean bAdjustOverhang){
    
    	SIZE			sizeText;
    	TEXTMETRIC		tm;
    
    	GetTextExtentPoint32(hDC, text, lstrlen(text), &sizeText);
    	*height = sizeText.cy;
    	*width = sizeText.cx;
    	GetTextMetrics(hDC,&tm);
    	*height = tm.tmHeight;
    	if(lstrlen(text) == 0){
    		return;
    	}
    	if(bAdjustOverhang){
    		ABCFLOAT	WidthsABC[256];
    		boolean		bResult;
    
    		*dOverhangTrailing = 0;
    		bResult = GetCharABCWidthsFloat(hDC,0,255, WidthsABC);
    		if(bResult){
    			*dOverhangTrailing = WidthsABC[text[lstrlen(text)-1]].abcfC;
    			if(*dOverhangTrailing < 0){
    				*width -= (LONG)*dOverhangTrailing;
    			}
    		}
    	}
    }
    
    /*
    ===============================================================================
    MeassureText_FontHandle
    
    misst die Textlänge und Breite, es wird ein Handle auf einen Font benötigt 
    ===============================================================================
    */
    boolean MeassureText_FontHandle(long *width, long *height, char *text, HFONT hFont, u_int method){
    
    	HDC			hDC, hDCMem;
    	HFONT			hFontOld;
    
    	hDC = GetDC(NULL);
    	switch(method){
    		case MESSURETEXT_NITASTRICK:
    			hDCMem = CreateCompatibleDC(hDC);
    			hFontOld = (HFONT)SelectObject(hDCMem, hFont);
    			MeassureText_NitasTrickMethod(width, height, text, hDCMem);
    			SelectObject(hDCMem, hFontOld);
    			DeleteDC(hDCMem);
    			break;
    		case MESSURETEXT_SMART:
    			hDCMem = CreateCompatibleDC(hDC);
    			hFontOld  = (HFONT)SelectObject(hDCMem, hFont);
    			MeassureText_SmartMethod(width, height, text, hDCMem);
    			SelectObject(hDCMem, hFontOld);
    			DeleteDC(hDCMem);
    			break;
    		case MESSURETEXT_CLASSIC:{
    			hFontOld = (HFONT)SelectObject(hDC, hFont);
    			MeassureText_ClassicMethod(width, height, text, hDC, 0, 0);
    			SelectObject(hDC, hFontOld);
    			break;
    		}
    	}
    	ReleaseDC(NULL, hDC);	
    	return TRUE;
    }
    
    /*
    ===============================================================================
    MeassureText_LogFont
    
    ===============================================================================
    */
    boolean MeassureText_LogFont(long *width, long *height, char *text, LOGFONT *lf, u_int method){
    
    	HFONT			hFont;
    	boolean			back;
    
    	hFont = CreateFontIndirect(lf);
    	if(!hFont){
    		return FALSE;
    	}
    	back = MeassureText_FontHandle(width, height, text, hFont, method);
    	DeleteObject(hFont);
    	return back;
    }
    


  • hustbaer schrieb:

    Ich verstehe nicht wo hier kein Bezug zu WYSIWYG zu sehen sein soll 😉

    Naja, das Dokument schaut auf dem Bildschirm so gut aus, wie es auf dem Bildschirm ausschauen kann, weil pixelgenau gerechnet wird. Auf dem Drucker schaut es so gut aus, wie es auf dem Drucker ausschauen kann, weil die Pixel kleiner sind. Aber das Problem hat man ja grundsätzlich. Das ist immer noch WYSIWYG. Aber ich besteh nicht drauf 😉 Entweder steh ich komplett auf dem Schlauch oder das ist Interpretationssache.

    Bei DirectWrite sind einige interessante Funktionen erst mit Windows 8 reingekommen, das würde den Nutzerkreis evtl. einschränken.



  • WYSIWYG heisst in dem Zusammenhang dass du das bekommst (=der Ausdruck) was du im Editor (=am Bildschirm) siehst.
    Wenn das Layouting deviceabhängig ist, und dadurch auf dem Ausdruck die Zeilenumbrücke/Seitenumbrüche etc. anders sind ... dann ist das ja wohl ganz klar und krass überhaupt nicht mehr das selbe.
    Und kann daher auch kein WYSIWYG sein.

    Und nur damit wir uns nicht falsch verstehen...
    Es kann durch deviceabhängigs Layouting sein dass es am Bildschirm so aussieht:

    The quick brown|
    fox jumped over|
    the lazy dog.  |
    

    Und beim Drucker kommt das dann so raus:

    The quick      |
    brown fox      |
    jumped over    |
    the lazy dog.  |
    

    Dadurch kann sich *alles* ändern was die Layout-/Flow-Engine so "flowt".

    Und da check grad echt nicht wie man das noch als WYSIWYG bezeichnen wollen würde 🙂



  • Ok, dass dabei mehr oder weniger Zeilen rauskommen können, habe ich nicht berücksichtigt, da hast du Recht. Ich dachte bisher immer an etwas unsauberes Kerning, aber nicht daran, dass der Unterschied natürlich so groß sein kann, dass die Zeile umbrochen wird.



  • @.-)

    .-) schrieb:

    vieleicht hilfts:

    Ja, schon möglich, dass ich gute Messergebnisse für die Textbreite bekomme. Aber das alles hilft nichts, wenn ich nicht - nachvollziehbar - dasselbe Ausgabeergebnis bekomme.

    @hustbaer

    hustbaer schrieb:

    Verstehe ich jetzt nicht ganz. Wo siehst du da ein Problem?
    Die Berechnung der Positionen für die einzelnen Glyphen muss in einem Rutsch gemacht werden, ja. Aber rausschreiben kann man die dann hübsch einzeln.

    Nein, das würde leider nicht zufriedenstellend funktionieren.
    Wenn ein Glyph innerhalb einer Pixelspalte endet und der nächste Glyph unmittelbar daran anschließend geschrieben werden soll, weiß der Renderer nicht, was beim vorangegangenen Glyph geschrieben wurde und beschreibt die Pixelspalte so, als ob der vorangegangene Rendering-Vorgang nicht stattgefunden hätte.



  • WishfulThinking schrieb:

    Wenn ein Glyph innerhalb einer Pixelspalte endet und der nächste Glyph unmittelbar daran anschließend geschrieben werden soll, weiß der Renderer nicht, was beim vorangegangenen Glyph geschrieben wurde und beschreibt die Pixelspalte so, als ob der vorangegangene Rendering-Vorgang nicht stattgefunden hätte.

    Meinst du den Fall wo...
    * Der nachfolgende Glyph "nahtlos" an den vorhergehenden anschliessen soll
    * Die Trennlinie mitten in einem Pixel liegt (statt genau zwischen zwei Pixeln)
    * Man ohne Grid-Fitting rendert
    ...und dadurch dann kein Nahtloser Zusammenschluss erfolgt? Sondern man statt dessen eine nicht vollständig schwarze Trennlinie bekommt.

    Der Fall ist in der Tat doof.

    Beheben liesse sich das indem man ala 3D Karten mit FSAA rendert. Also z.B. die 4-fache Auflösung horizontal und vertikal verwendet, dafür ohne klassischem Antialiasing, und dann zum schluss das gesamte Bild runterskaliert. Dabei könnte man die gerasterten Glyphen auch cachen. Die subpixel Genauigkeit bei der Positionierung wäre dann natürlich durch den Oversampling-Faktor begrenzt, aber ich denke das sollte kein Problem sein. Und vermutlich ist es performanter mit 16-facher (4x4) Auflösung Glyphen aus nem Cache zu kopieren als alles jedes mal neu zu rastern.

    Bzw. eine "nicht perfekt aber vermutlich gut genug" Lösung wäre wenn man die Pixel einfach additiv blendet (bzw. subtraktiv - additiv wenn man den Hintergrund mit 0.0 abbildet und die Glyphen mit 1.0, subtraktiv wenn man es ala "schwarz auf weiss" umgekehrt macht). Wobei der Renderer dazu natürlich halbwegs gute und vor allem lineare (=nicht Gamma-korrigierte) Coverage-Werte ausspucken muss.

    Aber ist schon irgendwie interessant wie nicht-trivial sowas triviales wie Anzeige von Text sein kann, wenn man es "ordentlich" machen will 🙂



  • Ja, WYSIWYG kann ich nur bekommen, wenn ich auf GridFitting verzichte. Ziel ist es, DASSELBE Layout auf dem Bildschirm (z.B. 96dpi) und auf dem Drucker (z.B. 1200dpi) zu bekommen. Der Zusammenhang ist nicht ganz einfach, ich muss ein wenig ausholen und hoffe, dass ich das so vollständig und korrekt darstelle:

    Eine Schrift wird in einem Designraster (i.d.R. 1024x1024 oder 2048x2048 Punkte) definiert. Wenn nun die Glyphs für eine konkrete Font-Instanz berechnet werden, muss bei GridFitting natürlich gerundet werden, und jetzt macht die Auflösung tatsächlich einen bedeutenden Unterschied. Nehmen wir mal an, die Breite eines bestimmten Glyph, der in 2048x2048 entworfen wurde, beträgt 800 Design-Einheiten; mit diesem Zeichen soll in der Schriftgröße 24 Punkt (auf dem Bildschirm bei 96dpi) und auf dem Drucker (1200dpi) eine Zeile der Breite von 10,0 Inch gefüllt werden.

    Unabhängig davon, welches Maßsystem ich bei GDI, GDI+,... verwende -- es läuft immer darauf hinaus, dass ich letztendlich Ausgabe-Pixel habe. Der Einfachheit halber will ich mich jetzt auch nur um die Zeichen-Breiten kümmern. Für die Umrechnung von Points in Pixel soll die allgemeine akzeptierte Formel {points = pixels * 72 / dpi} bzw. {pixels = points * dpi / 72} gelten.

    Ausgabe auf dem Bildschirm mit 96dpi:
    Die Schriftgröße 24Pt entspricht {24*96/72=32,0}Pixeln; 32 Pixel entsprechen also den 2048 Designraster-Punkten. Die 800 Designeinheiten des Beispiel-Zeichens rechnen sich um in {32/2048*800} 12,5 Pixel. Ich nehme mal an, dass dieser Wert fürs Gridfitting auf 13 Pixel aufgerundet wird -- der Glyph wird entsprechend skaliert.
    Die Ausgabezeile hat eine Breite von 10,0 Inch; das sind bei 96dpi 960 Pixel. Das Zeichen mit der Breite von 13 Pixel geht also 960/13=73,85 mal in die Zeile.

    Jetzt möchte ich die Ausgabe auf dem Drucker (bei 1200 dpi) wiederholen:
    Die Schriftgröße 24Pt entspricht {24*1200/72=400}Pixel; 400 Pixel entsprechen also den 2048 Designraster-Punkten. Die 800 Designeinheiten des Beispiel-Zeichens rechnen sich um in {400/2048*800} 156,25 Pixel. Ich nehme mal an, dass dieser Wert fürs Gridfitting auf 156 Pixel abgerundet wird -- der Glyph wird entsprechend skaliert.
    Die Ausgabezeile hat eine Breite von 10,0 Inch; das sind bei 1200dpi 12000 Pixel. Das Zeichen mit der Breite von 156 Pixel geht also 12000/156=76,92 mal in die Zeile.

    GridFit?
    Das ist ein willkürlich herausgegriffenes Beispiel mit einem einzigen Zeichen. Je nachdem, wie der Mix der Zeichen in einer Zeile ist, kann das Layout durch Auf- und Abrunden sich mehr oder weniger stark verändern.
    Ich glaube, damit konnte ich zeigen, dass GridFit nicht die taugliche Mess- und Ausgabemethode ist, wenn man dasselbe Layout auf dem Bildschirm und auf dem Drucker (also: WYSIWYG) erzielen möchte. Das kann ich nur dann zufriedenstellend hinbekommen, wenn ich auf das Auf- und Abrunden der GridFit-Methode verzichte. Damit läge die Trennlinie zwischen zwei Zeichen wohl immer innerhalb einer Pixel-Spalte. Oder sieht da noch jemand einen Fehler in meiner Darstellung?
    (Vielleicht sollte ich in der Darstellung nicht den Begriff "Glyph" sondern lieber den Begriff "Zeichen" verwenden; das "Zeichen" enthält zusätzlich noch den designbedigten Lerrraum vor und nach dem Glyph)



  • WishfulThinking schrieb:

    Damit läge die Trennlinie zwischen zwei Zeichen wohl immer innerhalb einer Pixel-Spalte. Oder sieht da noch jemand einen Fehler in meiner Darstellung?

    Nö das wird schon hinkommen.
    Ist aber Wurst, wenn ohne Antialiasing gerendert wird.
    Mit klassischem Antialiasing wie schon erwähnt ein Problem.



  • Naja, jetzt haben wir nachgewiesen, warum der Schuh drückt -- es tut aber immer noch weh. Den "Schmerz" versuche ich jetzt auf der Suche nach einer Mess- und Renderlogik, die dieselben Ergebnisse liefern sollen, zu lindern. Ich mache also weiter mit meiner Exploration von [c]DirectWrite[/c].



  • Die DirectWrite-Methode IDWriteFactory::CreateTextLayout erlaubt, soweit ich sehe, nur die Anwendung eines einzigen Font innerhalb des Layout (=Zeile?). In einem solchen Layout kann ich alle möglichen Parameter der Schrift verändern (Fett, kursiv, unterstrichen, Größe), aber ich habe noch keine Methode gefunden, mit der ich den Font selbst für einen Teil des Layout ändern kann. Habe ich da etwas übersehen? 😃



  • Hat sich schon erledigt ..... IDWriteTextLayout::SetFontFamilyName 😃

    Es steht ja alles in der Dokumentation ..... man muss es eben nur finden (Nachdem man es genügend intensiv gesucht hat).



  • So, jetzt bin ich ein gutes Stück weiter. Ich habe in den vergangenen Tagen intensiv mit DirectWrite experimentiert (Dank an hustbaer für den Tipp) und weiß jetzt schon, dass alle meine Anforderungen mit dieser Engine erfüllt werden.

    Für alle, die auch an der Anwendung von DirectWrite interessiert sind, hier habe ich einige Links, die mir bei meinen Experimenten erheblich weitergeholfen haben:

    Aber alle diese Quellen drücken sich um das Thema TABulation. Lediglich in der MSDN-Dokumentation wird die Existenz der Funktionen SetIncrementalTab und GetIncrementalTab sehr sparsam zugegeben. Man erfährt leider nicht, wie diese TABs in der Praxis angewendet werden können: So würde ich z.B. sehr gerne (und dringend) wissen, wie man das Text-Alignment für die verschiedenen TAB-Spalten einstellt.
    Weiß jemand, wie das funktioniert; oder kennt jemand eine Quelle, in der dieses offenbar sehr spezielle DirectWrite-Thema näher beleuchtet wird?



  • Hast Du auch einen Link zu dem Petzold-Artikel?



  • PaulB48 schrieb:

    Hast Du auch einen Link zu dem Petzold-Artikel?

    Sorry -- vergessen.

    Hier ist der Link (ich werde ihn auch im Original-Post nacheditieren):
    http://www.charlespetzold.com/blog/2014/01/Character-Formatting-Extensions-with-DirectWrite.html



  • Ich hab' da mal reingesehen, das sind klasse Quellen, die ich auch nutzen werde.



  • Mechanics schrieb:

    Du könntest vielleicht mal versuchen, dir das Buch "Windows Graphics Programming" zu besorgen. Da steht auch einiges zu dem Thema drin. Hab jetzt z.B. noch die Funktion GetCharacterPlacement gefunden, die hatte ich gar nicht mehr in Erinnerung. Bin mir nicht sicher, ob sie dir jezt weiterhilft, könnte aber sein.

    Inzwischen hatte ich mir mal die Zeit genommen, nach käuflicher Fachliteratur zu suchen, denn auch ich bin nicht zu 100% überzeugt, dass die kostenlosen Informationen aus dem WEB immer zielführend sind. Allerdings: Das Feld der Literatur zu Grafik-Programmierung, insbesondere Berechnen und Rendern von Text, ist sehr schwach vertreten. Auch Deinen Buchtipp glaube ich geortet zu haben: http://www.amazon.de/Windows-Graphics-Programming-Hewlett-Packard-Professional/dp/0130869856/ref=sr_1_4?ie=UTF8&qid=1416059685&sr=8-4&keywords=Windows+Graphics+Programming -- ist es das? Dieses Buch ist aus dem Jahr 2000 und damit - programmiertechnisch - sehr alt; der Preis von gut 200€ für ein neues Exemplar (und gebraucht für knapp unter 100€) animiert nicht gerade zum Spontankauf.

    @Mechanics: Würdest Du mir dieses Buch bei dem aktuellen Stand der Diskussion hier immer noch empfehlen?

    Hat sonst noch jemand dieses Buch und kann mir eventuell eine Kaufempfehlung geben?



  • Nein, wie gesagt, das war seinerzeit ein gutes Buch zum Thema GDI Programmierung. Da geht es eben auch nur um GDI und natürlich nicht um DirectWrite. Du musst auch bedenken, dass es zu der Zeit auch im Internet bei weitem nicht so viele Informationen gab, wie jetzt. Ich habs seinerzeit recht günstig gebraucht bekommen.



  • Ist schon wahr: Inzwischen sind die im Internet verfügbaren Informationen sehr viel umfangreicher als noch zu Beginn des Jahrtausends. Der Buchtipp war aber für den Stand der Diskussion schon sehr sinnvoll.

    Aber leider gibt es im Internet über DirectWrite, speziell bei der Verwendung von TABs, noch erheblichen Informationsbedarf. Ich habe sogar den Verdacht, dass Microsoft das Thema noch nicht zu Ende gedacht hat.



  • Ich finde, mittlerweile findet man grundsätzlich zu jedem Thema immer weniger sinnvolles, dafür immer mehr Müll... Wenn ich z.B. eine bestimmte Info zu einem Spiel suche, werd ich kaum noch fündig. Dafür kommen 50 Ergebnisseiten mit immer gleichen Reviews, die mich überhaupt nicht interessieren, wo aber evtl. die Wörter vorkommen, die ich eingegeben habe.

    Dein Problem ist eher, dass sowas kaum jemand nutzt. Gibt vielleicht paar Leute, die mal damit rumgespielt haben. Nicht genug, um Forenbeiträge und Blogs zu dem Thema zu schreiben. Deswegen hab ich in dem Buch damals auch viele Infos und Beispiele gefunden, die ich seinerzeit im Internet nicht gefunden hatte.

    Bist du sicher, dass du die Funktionen richtig geschrieben hast? Wenn ich nach GetIncrementalTab suche, finde ich gar nichts. Nicht mal diesen Thread hier 😉



  • Ist wahr, das war mein nachlässiger Schreibfehler - entschuldige.

    Die Funktion heißt GetIncrementalTabStop bzw. SetIncrementalTabStop. Wenn man das als Suchwort eingibt, bekommt man doch etliche Hits, aber die verweisen alle aud dieselbe (dürftige) Beschreibung.


Anmelden zum Antworten