CreateFont + GetGlyphOutline



  • Hallo,

    folgendes Problem:
    Mittles CreateFont erstelle ich mir einen TrueType-Font. Von diesem möchte ich mir die Glyphmetrics zurückgeben lassen.
    In der Schleife sammele ich die GLYPHMETRICS-Objekte und packe sie in ein Array.

    GLYPHMETRICS gmf[256];
    int pPointSize = 8;
    
    HFONT font = CreateFont(-MulDiv(pPointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72),
    			0,
    			0,
    			0,
    			FW_NORMAL,
    			FALSE,
    			FALSE,
    			FALSE,
    			ANSI_CHARSET,
    			OUT_TT_PRECIS,
    			CLIP_DEFAULT_PRECIS,
    			ANTIALIASED_QUALITY,
    			FF_DONTCARE | DEFAULT_PITCH,
    			TEXT("Tahoma"));
    
    HFONT oldfont = (HFONT)SelectObject(hDC, font);
    
    for (int i = 0; i < 256; i++){
    	GetGlyphOutline(hDC, i, GGO_METRICS, &gmf[i], NULL, NULL, NULL);
    }
    

    Wenn ich mir allerdings den Inhalt von gmf im Debugger ansehe, sind alle GLYPHMETRICS-Elemente bzw. deren Member
    mit 0 initialisiert. Scheinbar werden die Metrics nicht richtig ausgelesen.

    Hat jemand eine Idee, woran das liegen könnte bzw. was man ändern könnte?

    grüße
    Martin



  • Kann dir da nur raten dir mal den freetype source anzuschaun.

    http://freetype.sourceforge.net/



  • Danke für den Tip, mal sehen, was da so abgeht.

    Vielleicht noch mein Anliegen:
    Ich möchte zu einem String die maximale Höhe und die Länge errechnen, abhängig vom gewählten Font plus Größe.

    Z.B:
    "Hello World", 8pt, Arial => 53 Pixel lang, 8 Pixel hoch.

    Habs halt über die GLYPHMETRICS probiert, aber scheinbar will das nicht. Die GDI ist aber auch so unstrukturiert und schlecht dokumentiert, dass mir bis jetzt noch keine Lösung aufgefallen ist.

    gruß
    Martin



  • ich hab dass selbst noch nicht gemacht aber ich hätte zuhause code der das erledigt, den kann ich dir nachher nachm schaffen ausgraben.



  • Im Spiele Forum isn recht aktueller Thread. Zwar im OpenGL context aber vielleicht hilfts.
    http://www.c-plusplus.net/forum/viewtopic-var-p-is-1342077.html#1342077



  • @mad_martin
    Ich empfehle dir mal die Rückgabewerte der einzelnen Funktionen zu prüfen. Bei GetGlyphOutline auf GDI_ERROR überprüfen und ggf. GetLastError aufzurufen. Denke mal, dass es am 2. Parameter von GetGlyphOutline liegen wird. Hier wird ein 'Zeichen' verlangt. Allerdings sind nicht alle Zeichen darstellbar (Steuerzeichen), wie z.B. '\0' welches den Wert 0 hat (spiele damit auf deine for-Schleife an).

    Die Breite und Höhe einer Zeichenkette, abhängig vom 'gewählten' Font bekommst du über die Funktion GetTextExtentPoint32



  • So, habs ausgegraben, zwar auch für opengl und bisl mehr als nötig aber ich dachte ich poste mal den ganzen source, may freut sich jemand 😃
    Is auf jeden funktionierender Sample Source, kann leider wenig zu Tribes.h und opengl.h sagen aber ich geh davon aus das die uninteressant sind und opengl.h etc. nur gewrappte funktionalität für genau dieses bietet da der code in nem reverse engeneered sourrounding läuft um das beste game der welt n bisschen aufzupeppen 😉

    Hab den Code selbst nicht nachvollzogen aber wenn er ein bisschen komisch wirken mag liegt dass daran das die Font Funktionen in die Scripting Oberflche von Starsiege::Tribes eingebunden wurde und deswegen eventuell noch zusätzlichen weirdo kram enthält.

    and for the guys that find this via google. THIS IS NO TRIBES SOURCE CODE! It's part of andrews scriptgl extension in hudbot 0.5

    .cpp

    #include "Tribes.h"
    #include "Font.h"
    #include "OpenGL.h"
    
    #include "Profile.h"
    
    #pragma warning( disable : 4244 ) // conversion from 'double' to 'float', possible loss of data
    
    void Font::Close( ) {	
    	if ( !mCreatedDC )
    		return;
    	DeleteObject( mFont );
    	DeleteObject( mDib );
    	DeleteDC( mDC );
    
    	mDC = ( NULL );
    	mFont = ( NULL ); 
    	mDib = ( NULL );
    	mLoaded = ( false );
    }
    
    // get the font information and create windows handles
    bool Font::Create( const char *name, int pixel_height, Rendering mode, int glow_radius  ) {
    	Close( );
    
    	// rendering
    	mMode = ( mode );
    	mGlowAdd = ( glow_radius > 16 ) ? 16 : glow_radius;
    	mBlurRadius = ( glow_radius );
    
    	mDC = ( CreateCompatibleDC( NULL ) );
    	if ( !mDC )
    		return ( false );
    	SetMapMode( mDC, MM_TEXT );	
    
    	mFont = CreateFont( 
    		-pixel_height,
    		0, /* width */
    		0, /* escapement */
    		0, /* orientation */
    		FW_NORMAL, /* weight */
    		0, /* italic */
    		0, /* underline */
    		0, /* strikeout */
    		ANSI_CHARSET,
    		OUT_DEFAULT_PRECIS, 
    		CLIP_DEFAULT_PRECIS, 
    		ANTIALIASED_QUALITY,
    		DEFAULT_PITCH | FF_DONTCARE, 
    		name );
    
    	if ( !mFont ) {
    		DeleteDC( mDC );
    		mDC = ( NULL );
    		return ( false );
    	} else {	
    		SelectObject( mDC, mFont );
    	}
    
    	// Font height
    	TEXTMETRIC metric;
    	GetTextMetrics( mDC, &metric );
    	mHeight = ( metric.tmHeight );
    	mAscent = ( metric.tmAscent );
    	mMaxWidth = ( metric.tmMaxCharWidth );
    	mTrueType = ( ( metric.tmPitchAndFamily & TMPF_TRUETYPE ) != 0 );
    
    	// Char widths
    	ABC widths[ 256 ];
    
    	if ( !mTrueType || !GetCharABCWidths( mDC, 0, 255, widths ) ) {
    		int widths2[ 256 ];
    		GetCharWidth( mDC, 0, 255, widths2 );
    		for ( int i = 0; i < 256; i++ ) {
    			widths[ i ].abcA = ( 0 );
    			widths[ i ].abcB = ( widths2[ i ] );
    			widths[ i ].abcC = ( 0 );
    		}
    	}			
    
    	// DibSection
    	mDibSize = Texture::CeilPow2( max( mMaxWidth, mHeight ) );
    
    	BITMAPINFO header;
    	memset( &header, 0, sizeof( header ) );
    	header.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
    	header.bmiHeader.biWidth = ( mDibSize );
    	header.bmiHeader.biHeight = -( mDibSize ); // negative means top down
    	header.bmiHeader.biPlanes = ( 1 );
    	header.bmiHeader.biBitCount = ( 32 );
    	header.bmiHeader.biCompression = ( BI_RGB );
    	mDib = CreateDIBSection( mDC, &header, DIB_RGB_COLORS, (void **)&mDibPixels, 0, 0 );
    	SelectObject( mDC, mDib );
    
    	// Color
    	SetTextColor( mDC, (COLORREF )0x00FFFFFF );
    	SetBkColor( mDC, (COLORREF )0x00000000 );
    	SetBkMode( mDC, TRANSPARENT );
    
    	// Init character widths
    	for ( int i = 0, x = 0, y = 0; i < 256; i++ ) {
    		Letter &l = ( mLetters[ i ] );
    		ABC &abc = ( widths[ i ] );
    		l.mWidth = LetterWidth( abc.abcA, abc.abcB, abc.abcC );
    	}
    
    	mCreatedDC = ( true );
    	mLoaded = ( true );
    	return ( true );
    }
    
    // render a letter for use
    bool Font::CreateLetter( Letter &l, char ch ) {
    	if ( l.mTexture.Loaded( ) )
    		return ( true );
    
    	// dont draw these
    	if ( ch < 31 || ch > 126 || ( l.GlyphWidth( ) <= 0 ) )
    		return ( false );
    
    	PROFILE_START( )
    
    	if ( mMode == Smooth )
    		RenderCharacterSmooth( l, ch );
    	else
    		RenderCharacter( l, ch );
    
    	if ( mGlowAdd )
    		l.mTexture.AlphaBloom( mBlurRadius, l.mWidth.mB, mHeight );
    
    	PROFILE_STOP( )
    
    	return ( true );
    }
    
    void Font::Draw( const char *str, int x, int y ) {
    	if ( !mLoaded )
    		return;
    
    	PROFILE_START( )
    
    	glDisable( GL_ALPHA_TEST );	
    	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
    	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
    
    	Vector2i start( x, y );
    
    	RGBA color( 255, 255, 255, 255 );
    	while ( *str ) {		
    		unsigned char ch = ( *str++ );
    
    		if ( ch == '\n' ) {
    			x = ( start.x );
    			y += ( mHeight );
    			continue;
    		} else if ( ch == '<' ) {
    			if ( Tribes::Atoh( str, (char *)&color, 4 ) == 4 ) {
    				str += ( 8 );
    				if ( *str )
    					str++;
    				glColor4ub( color.r, color.g, color.b, color.a );
    				continue;
    			}
    		}
    
    		Letter &l = ( mLetters[ ch ] );
    		if ( CreateLetter( l, ch ) ) {
    			x += ( l.mWidth.mA );
    			l.Draw( x, y, mHeight, mGlowAdd ); 
    			x += ( l.mWidth.mB + l.mWidth.mC );
    		}
    	}
    
    	PROFILE_STOP( )
    }
    
    /*
    	Renders a character with no smoothing
    */
    void Font::RenderCharacter( Letter &l, char ch ) {
    	if ( !l.CreateTexture( mHeight, mGlowAdd ) )
    		return;
    
    	memset( mDibPixels, 0, mDibSize * mDibSize * 4 );
    	TextOut( mDC, -l.mWidth.mA, 0, (char *)&ch, 1 );
    	for ( int y = 0; y < mHeight; y++ )  {
    		for ( int x = 0; x < l.GlyphWidth( ); x++ ) {
    			RGBA &quad = ( mDibPixels[ x + y * mDibSize ] );
    			if ( quad.r )
    				quad.a = 255;
    			else
    				quad = ( RGBA( 255, 255, 255, 0 ) );
    			l.mTexture( x + mGlowAdd, y + mGlowAdd ) = ( quad );
    		}
    	}
    }
    
    /*
    	Attempts to create an AA character with GetGlyphOutline
    */
    void Font::RenderCharacterSmooth( Letter &l, char ch ) {
    	GLYPHMETRICS metrics;
    	MAT2 mat2 = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } };
    
    	int bytes = ( GetGlyphOutline( mDC, ch, GGO_GRAY8_BITMAP, &metrics, 0, NULL, &mat2 ) );
    	if ( bytes == 0 ) {
    		// fallback to non-aa
    		RenderCharacter( l, ch );
    		return;
    	}
    
    	unsigned char *gray = new unsigned char[ bytes ];
    	GetGlyphOutline( mDC, ch, GGO_GRAY8_BITMAP, &metrics, bytes, gray, &mat2 );
    
    	// character dimensions may change with anti-aliasing
    	int pad = ( metrics.gmBlackBoxX - l.mWidth.mB ), half = ( pad >> 1 );
    
    	l.mWidth.mA -= ( pad - half );
    	l.mWidth.mB = ( metrics.gmBlackBoxX );
    	l.mWidth.mC -= ( half );
    
    	// resize the texture, it wont fail!
    	l.CreateTexture( mHeight, mGlowAdd ); 
    
    	int width = ( metrics.gmBlackBoxX + 3 ) & ~3; // dword aligned	
    	int start_y = ( mAscent - metrics.gmptGlyphOrigin.y );
    	int start_x = 0;
    
    	for ( unsigned int j = 0; j < metrics.gmBlackBoxY; j++ ) {
    		for ( unsigned int i = start_x; i < metrics.gmBlackBoxX; i++ ) {
    			int x = ( i - start_x ) + mGlowAdd;
    			int y = ( j + start_y ) + mGlowAdd;
    
    			if ( ( x >= l.mTexture.mWidth ) || ( y >= l.mTexture.mHeight ) )
    				continue;
    
    			// format is 64 shades of gray
    			l.mTexture( x, y ) = RGBA( 255, 255, 255, gray[ ( j * width ) + i ] * 255 / 64 );
    		}
    	}
    
    	delete[] gray;
    }
    
    Vector2i Font::StringDimensions( const char *str ) {
    	Vector2i dimensions( 0, mHeight );
    
    	if ( !mLoaded )
    		return ( dimensions );
    
    	int x = 0;
    
    	while ( *str ) {
    		unsigned char ch = ( *str++ );
    
    		if ( ch == '\n' ) {			
    			dimensions.y += ( mHeight );
    			continue;
    		} else if ( ch == '<' ) {
    			RGBA color;
    			if ( Tribes::Atoh( str, (char *)&color, 4 ) == 4 ) {
    				str += ( 8 );
    				if ( *str )
    					str++;
    				continue;
    			}
    		}
    
    		Letter &l = ( mLetters[ ch ] );
    		x += ( l.mWidth.mA + l.mWidth.mB + l.mWidth.mC );
    		dimensions.x = ( max( dimensions.x, x ) );
    	}
    
    	return ( dimensions );
    }
    

    .h

    #ifndef __FONT_H__
    #define __FONT_H__
    
    #include "Texture.h"
    #include "OpenGL.h"
    
    #include "Profile.h"
    
    class LetterWidth {
    public:
    	LetterWidth() {}
    	LetterWidth( int a, int b, int c ) : mA(a), mB(b), mC(c) {}	
    	int mA, mB, mC;
    };
    
    class Letter {
    public:
    	Letter( ) { }
    
        // letter loads itself to opengl on demand
    	void Draw( int x, int y, int font_height, int glow_radius ) {
    		PROFILE_START( )
    
    		if ( !mTexture.Loaded( ) || !mTexture.BindToGraphicsCard( ) ) {
    			PROFILE_STOP( )
    			return;
    		}
    
    		x -= ( glow_radius );
    		y -= ( glow_radius );
    
    		int glow = ( glow_radius * 2 );
    		int width = ( mWidth.mB + glow );
    		int height = ( font_height + glow );		
    
    		glBegin( GL_QUADS );
    			glTexCoord2f(         0,         0 ); glVertex2i(         x, y );
    			glTexCoord2f( mExtent.x,         0 ); glVertex2i( x + width, y );
    			glTexCoord2f( mExtent.x, mExtent.y ); glVertex2i( x + width, y + height );
    			glTexCoord2f(         0, mExtent.y ); glVertex2i(         x, y + height );
    		glEnd( );
    
    		PROFILE_STOP( )
    
    /*
    		glDisable( GL_TEXTURE_2D );
    		glBegin( GL_LINE_STRIP );
    			glVertex2f(         x, y );
    			glVertex2f( x + width, y );
    			glVertex2f( x + width, y + height );
    			glVertex2f(         x, y + height );
    			glVertex2f(         x, y );
    		glEnd( );
    		glEnable( GL_TEXTURE_2D );
    */
    	}
    
    	// size of actual character, not trailing/leading rendering space
    	int GlyphWidth( ) const { return ( mWidth.mB ); }
    
    	bool CreateTexture( int font_height, int glow_radius ) {
    		int width = ( GlyphWidth( ) + glow_radius * 2 );
    		int height = ( font_height + glow_radius * 2 );
    
    		mTexture.New( Texture::CeilPow2( width ), Texture::CeilPow2( height ) );
    		if ( !mTexture.Loaded( ) )
    			return ( false );
    
    		// clear to pure white, no alpha
    		mTexture.Clear( 255, 255, 255, 0 );
    		mExtent.x = ( (float )width / (float )mTexture.mWidth );
    		mExtent.y = ( (float )height / (float )mTexture.mHeight );		
    		return ( true );
    	}
    
    	Texture mTexture;
    	Vector2f mExtent;
    	LetterWidth mWidth;
    };
    
    class Font {
    public:
    	enum Rendering {
    		Pixel,
    		Smooth,
    	};
    
    	Font( ) : mLoaded(false), mLastUsed(GetTickCount()), mCreatedDC(false) { }
    	~Font( ) { Close( ); }
    
    	void Close( );
    	bool Create( const char *name, int pixel_height, Rendering mode, int glow_radius );
    	void Draw( const char *str, int x, int y );	
    	int LastUsed( ) { return ( mLastUsed ); }	
    	Vector2i StringDimensions( const char *str );
    	void UpdateLastUsed( ) { mLastUsed = ( GetTickCount( ) ); }
    
    private:
    	bool CreateLetter( Letter &l, char ch );
    	void RenderCharacter( Letter &l, char ch );
    	void RenderCharacterSmooth( Letter &l, char ch );
    
    	// font info
    	Letter mLetters[ 256 ];
    	int mHeight;
    	bool mLoaded;
    	int mLastUsed;
    
    	// only for rendering
    	int mGlowAdd, mBlurRadius;
    	Rendering mMode;
    	bool mCreatedDC;
    	RGBA *mDibPixels;
    	HDC mDC;
    	HFONT mFont;
    	HBITMAP mDib;
    	int mDibSize, mAscent, mMaxWidth;
    	bool mTrueType;
    
    };
    
    #endif // __FONT_H__
    


  • Vergiss den Kram mit OpenGL und verwende Analog Bit's Vorschlag: GetTextExtentPoint32 bzw. GetTabbedTextExtent. Dafür sind diese Funktionen schließlich da 😉 .



  • Danke an alle. Werde das jetzt mal probieren.

    grüße
    Martin



  • Yeah, passt. Macht genau das, was ich will. Danke nochmal.

    @Analog-Bit:
    Zu der Schleife meines ersten Versuchs:
    Das es bei einzelnen Zeichen Probleme gibt, vor allem bei den ersten 30, war mir klar. Aber selbst wenn ich die Steuerzeichen rauslasse und nur z.B. das normale Alphabet nutze, kommt nichts dabei rum. Ist aber jetzt auch egal 🙂

    @Greyhound:
    Woher genau kommt das denn? Ein Mod für Tribes?

    gruß
    Martin



  • mad_martin schrieb:

    @Greyhound:
    Woher genau kommt das denn? Ein Mod für Tribes?

    http://www.team5150.com/~andrew/project.hudbot/

    Is ne dll die von tribes mitgeladen wird und support für 32 bit texturen anbietet etc. und unter anderem auch ein paar gl functionen freigibt sodass man sich huds usw. auf diese art zeichnen kann.



  • Aha. Das werd ich mir mal anschauen.

    gruß
    Martin


Anmelden zum Antworten