OpenGL/GDI Font

A9102969e779768e6f0b8cb87e864c94
0
dave_ 101 Dec 21, 2006 at 15:00

Here is an OpenGL/GDI Font, inspired by D3DFont. Its pretty straight forward to use.
Just create the font object specifying the name of the typeface and the rest of the attributes and then call initialise.
When you’re rendering you scene call render with your string and the coordinates you require.
As usual, I’m aiming for simplicity, and tried to keep dependencies down to a minimum.
I only really use this as a debugging tool so its not the most efficient possible. However its not bad, with very few tweaks you can use this in a more efficient way.
I havent bothered to do any checking for large font sizes. It will probably crash.

You could export the texture file (look at where the texture is uploaded at gluBuild2DMipmaps) and use that on non-windows platforms. Of course, you’d have to use public domain typeface.

Let me know what you think!

Here is the code which you can hopefully just drag and drop into your Windows/OpenGL project.

Font.h

#ifndef INCLUDE_FONT_H
#define INCLUDE_FONT_H

#include <Windows.h>
#include <string>

class Font
{
public:

    Font(const char* font, unsigned int size, bool bold, bool italic);
    ~Font();

    bool initialise();

    void render(const char* pstr, float x, float y);
    void getTextSize(const char* pstr, float &xOut, float &yOut) const;

private:
    float renderChar (int ch, float x, float y) const;

    unsigned int width_;
    unsigned int height_;
    unsigned int spacing_;
    unsigned int texture_;
    unsigned int fontSize_;

    std::string name_;
    float   texCoords_[128-32][4];

    bool bold_;
    bool italic_;

    enum PaintResult {
        Fail,
        MoreData,
        Success,

    };

    bool createGDIFont( HDC hDC, HFONT* pFont );
    PaintResult paintAlphabet( HDC hDC, bool bMeasureOnly );
};
#endif

Font.cpp

#include "Font.h"

#include <math.h>
#include <gl/GLU.h>
#include <fstream>

Font::Font(const char* font, unsigned int size, bool bold, bool italic) : 
    bold_(bold), italic_(italic), name_(font), texture_(0), width_(128),
    height_(128), fontSize_(size), spacing_(fontSize_ / 3)
{

}

Font::~Font()
{
    if (texture_)
    {
        glDeleteTextures(1, &texture_);
    }
}

bool Font::initialise()
{
    HDC hDC = CreateCompatibleDC( NULL );
    SetMapMode( hDC, MM_TEXT );

    HFONT hFont = NULL;
    bool ok = createGDIFont( hDC, &hFont );

    if (ok)
    {
        HFONT hFontOld = (HFONT) SelectObject( hDC, hFont );

        PaintResult p;
        while(  MoreData == (p = paintAlphabet( hDC, true )) )
        {
            width_ *= 2;
            height_ *= 2;
        }
        ok = p == Success;
        if (ok)
        {
            // Prepare to create a bitmap
            DWORD*      pBitmapBits;
            BITMAPINFO bmi;
            ZeroMemory( &bmi.bmiHeader, sizeof(BITMAPINFOHEADER) );
            bmi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
            bmi.bmiHeader.biWidth       =  (int)width_;
            bmi.bmiHeader.biHeight      = -(int)height_;
            bmi.bmiHeader.biPlanes      = 1;
            bmi.bmiHeader.biCompression = BI_RGB;
            bmi.bmiHeader.biBitCount    = 32;

            // Create a bitmap for the font
            HBITMAP hbmBitmap = CreateDIBSection( hDC, &bmi, DIB_RGB_COLORS,
                (void**)&pBitmapBits, NULL, 0 );

            HGDIOBJ hbmOld = SelectObject( hDC, hbmBitmap );

            // Set text properties
            SetTextColor( hDC, RGB(255,255,255) );
            SetBkColor(   hDC, RGB(0,0,0) );
            SetTextAlign( hDC, TA_TOP );

            // Paint the alphabet onto the selected bitmap
            ok = paintAlphabet( hDC, false ) == Success;

            if (ok)
            {
                glGenTextures(1, &texture_);
                BITMAP  bitmap;
                GetObject(hbmBitmap,sizeof(BITMAP), &bitmap);
                glBindTexture(GL_TEXTURE_2D, texture_); // Bind Our Texture
                    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);

                // Its actually BGRA, but since we're rendering white...
                gluBuild2DMipmaps(GL_TEXTURE_2D, 4, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, bitmap.bmBits);

            }

            SelectObject( hDC, hbmOld );
            SelectObject( hDC, hFontOld );
            DeleteObject( hbmBitmap );
            DeleteObject( hFont );
            DeleteDC( hDC );
        }
    }

    return ok;
}

bool Font::createGDIFont( HDC hDC, HFONT* pFont )
{
    int nHeight    = fontSize_;
    DWORD dwBold   = bold_   ? FW_BOLD : FW_NORMAL;
    DWORD dwItalic = italic_ ? TRUE    : FALSE;
    *pFont         = CreateFont( nHeight, 0, 0, 0, dwBold, dwItalic,
        FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY,
        VARIABLE_PITCH, name_.c_str() );

    return ( *pFont != NULL );
}

Font::PaintResult Font::paintAlphabet( HDC hDC, bool bMeasureOnly )
{
    SIZE size;
    char str[2] = "x";

    // Calculate the spacing between characters based on line height
    if( 0 == GetTextExtentPoint32( hDC, str, 1, &size ) )
        return Fail;
    spacing_ = (int)ceil(size.cy * 0.3f);

    // Set the starting point for the drawing
    DWORD x = spacing_;
    DWORD y = 0;

    // For each character, draw text on the DC and advance the current position
    for( char c = 32; c < 127; c++ )
    {
        str[0] = c;
        if( 0 == GetTextExtentPoint32( hDC, str, 1, &size ) )
            return Fail;

        if( (DWORD)(x + size.cx + spacing_) > width_ )
        {
            x  = spacing_;
            y += size.cy + 1;
        }

        // Check to see if there's room to write the character here
        if( y + size.cy > height_ )
            return MoreData;

        if( !bMeasureOnly )
        {
            // Perform the actual drawing
            if( 0 == ExtTextOut( hDC, x+0, y+0, ETO_OPAQUE, NULL, str, 1, NULL ) )
                return Fail;

            texCoords_[c-32][0] = ((FLOAT)(x))/width_;
            texCoords_[c-32][1] = ((FLOAT)(y))/height_;
            texCoords_[c-32][2] = ((FLOAT)(x + size.cx))/width_;
            texCoords_[c-32][3] = ((FLOAT)(y + size.cy))/height_;
        }
        x += size.cx + (2 * spacing_);
    }

    return Success;
}

float Font::renderChar (int ch, float x, float y) const
{
    float tx1 = texCoords_[ch-32][0];
    float ty1 = texCoords_[ch-32][1];
    float tx2 = texCoords_[ch-32][2];
    float ty2 = texCoords_[ch-32][3];

    float w = (tx2-tx1) * width_ ;
    float h = (ty2-ty1) * height_;

    glTexCoord2f(tx1, ty2);
    glVertex2f(x, y);

    glTexCoord2f(tx2, ty2);
    glVertex2f(x+w, y);

    glTexCoord2f(tx2, ty1);
    glVertex2f(x+w, y+h);

    glTexCoord2f(tx1, ty1);
    glVertex2f(x, y+h);

    return w;
}

void Font::render(const char* pstr, float xs, float ys)
{
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_COLOR, GL_ONE);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, texture_);
    glBegin (GL_QUADS);
    float x = xs;
    float y = ys - fontSize_;
    for (const char *ptr = pstr; *ptr; ++ptr)
    {
        if (*ptr == ' ')
        {
            x += spacing_;
        }
        else if (*ptr == '\n')
        {
            y -= fontSize_;
            x = xs;
        }   
        else if( (*ptr-32) < 0 || (*ptr-32) >= 128-32 )
        {
        }
        else
        {
            x += renderChar(*ptr, x, y);        
        }
    }   
    glEnd();
    glDisable(GL_TEXTURE_2D);

    glDisable(GL_BLEND);
}


void Font::getTextSize(const char* pstr, float &xOut, float &yOut) const
{
    float x = 0;
    float y = 0;
    for (const char *ptr = pstr; *ptr; ++ptr
    {
        if (*ptr == ' ')
        {
            x += spacing_;
        }
        else if (*ptr == '\n')
        {
            y += fontSize_;
            xOut = std::max<float>(xOut, x);
        }   
        else if( (*ptr-32) < 0 || (*ptr-32) >= 128-32 )
        {
        }
        else
        {
            float tx1 = texCoords_[*ptr-32][0];
            float tx2 = texCoords_[*ptr-32][2];
            x += (tx2-tx1) * width_ ;       
        }
    }   
    yOut = y;

}

31 Replies

Please log in or register to post a reply.

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 12, 2007 at 13:28

How about a simple demo using it?

A9102969e779768e6f0b8cb87e864c94
0
dave_ 101 Feb 12, 2007 at 22:29

Its pretty simple, but just for you, I’ve adapted a glut example

// Dave Hillier, adapted from GLUT example

/* Copyright (c) Mark J. Kilgard, 1994. */

/* This program is freely distributable without licensing fees 
and is provided without guarantee or warrantee expressed or 
implied. This program is -not- in the public domain. */

#include <string.h>
#include <GL/glut.h>

#include "Font.h"

Font *font = NULL;
Font *fonts[3];

char defaultMessage[] = "GLUT means OpenGL.";
char *message = defaultMessage;

void
selectFont(int newfont)
{
    font = fonts[newfont];
    glutPostRedisplay();
}

void
selectMessage(int msg)
{
    switch (msg) {
  case 1:
      message = "abcdefghijklmnop";
      break;
  case 2:
      message = "ABCDEFGHIJKLMNOP";
      break;
    }
}

void
selectColor(int color)
{
    switch (color) {
  case 1:
      glColor3f(0.0, 1.0, 0.0);
      break;
  case 2:
      glColor3f(1.0, 0.0, 0.0);
      break;
  case 3:
      glColor3f(1.0, 1.0, 1.0);
      break;
    }
    glutPostRedisplay();
}

void
tick(void)
{
    glutPostRedisplay();
}

void
output(int x, int y, char *string)
{

    font->render(string, x, 150-y);
}

void
display(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    output(0, 0, "This is written in a GLUT bitmap font.");
    output(100, 65, message);
    output(50, 120, "(positioned in pixels blah blah)");
    glutSwapBuffers();
}

void
reshape(int w, int h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, w, 0, h);
    glMatrixMode(GL_MODELVIEW);
}

int
main(int argc, char **argv)
{
    int msg_submenu, color_submenu;

    glutInit(&argc, argv);


    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(500, 150);
    glutCreateWindow("Font.cpp bitmap font example");
    glClearColor(0.0, 0.0, 0.0, 1.0);

    fonts[0] = new Font("Arial", 15, false, false);
    fonts[1] = new Font("Times New Roman", 10, false, false);
    fonts[2] = new Font("Verdana", 24, false, false);
    for (int i = 0; i < 3; ++i)
        fonts[i]->initialise();

    font = fonts[2];


    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutIdleFunc(tick);
    msg_submenu = glutCreateMenu(selectMessage);
    glutAddMenuEntry("abc", 1);
    glutAddMenuEntry("ABC", 2);
    color_submenu = glutCreateMenu(selectColor);
    glutAddMenuEntry("Green", 1);
    glutAddMenuEntry("Red", 2);
    glutAddMenuEntry("White", 3);
    glutCreateMenu(selectFont);
    glutAddMenuEntry("Arial 15", 0);
    glutAddMenuEntry("Times New Roman 10", 1);
    glutAddMenuEntry("Verdana 24", 2);
    glutAddSubMenu("Messages", msg_submenu);
    glutAddSubMenu("Color", color_submenu);
    glutAttachMenu(GLUT_RIGHT_BUTTON);
    glutMainLoop();

    for (int i = 0; i < 3; ++i)
        delete fonts[i];

    return 0;             /* ANSI C requires main to return int. */
}
F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 13, 2007 at 07:48

Do you know what’s going on?

fontva4.png

I just changed the background color of font and display to see outline of characters better.
Didn’t change anything else.

A9102969e779768e6f0b8cb87e864c94
0
dave_ 101 Feb 13, 2007 at 08:22

It should look like this:

font-full.jpg

If I change the background colour, like this:

font2-full.jpg

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 13, 2007 at 15:42

Ah, I think I see the problem. The posted code is bad.

You posted:
else if (*ptr == ‘n’)

and it should be:
else if (*ptr == ‘\n’)

(*ptr == '\n')

Code tags work ok for me. Is there a reason why the code was posted bad?
Any other errors I miss?

A9102969e779768e6f0b8cb87e864c94
0
dave_ 101 Feb 13, 2007 at 17:23

Very odd… I’ve not made any changes to the code since posting it.
Perhaps it was the way I cut and paste it…. maybe.
There are two instances of ‘n’ that need changing to ‘\n’ (one in getTextSize, the other in render)

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 168 Feb 13, 2007 at 17:41

Hmm…I made the changes. I think this might have to do with the string not being properly MySQL escaped when submitted through the COTD form. I always have to add backslashes on quotes and things when I use it.

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 13, 2007 at 21:36

What I would find most useful in a OpenGL Font renderer, would be a function that
would mimic the parameters of TextOut(). Basically, an OpenGL version of TextOut().
Both functions should render text in the exactly the same location on screen, at
exactly the same scale and size.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 168 Feb 13, 2007 at 22:29

Lost, what does TextOut do that this sample does not? Both of them just take a string and a location at which to display it. The only other thing I can see that TextOut does is responds to left/center/right alignment settings, but that could easily be added to this class.

EDIT: another interesting extention I can think of is to let the render function take printf-style varargs…this would be pretty easy to glom on with vsnprintf :)

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 14, 2007 at 01:42

TexOut() takes logical coordinates, while the above code takes pixel coordinates,
so they don’t render text at the same location on the screen.

The exact location, size and scale is important for me, because I might want
to print it out. In other words, you could use OpenGL’s TextOut() as a preview,
and then use GDI’s TextOut() for printing.

If I could, I would use TextOut() directly on the backbuffer, but you can’t.

http://support.microsoft.com/kb/131024

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 14, 2007 at 03:23

I rendered GDI’s TextOut() on the top, and font.cpp on the bottom, same font style and font size,
spacing seems to be off by some pixels:

font2dh6.png

I am serious about trying to create an exact copy of GDI TextOut() down to the last pixel. :)

I’m not sure if this has to do with OpenGL’s pixel rasterization?
Microsoft has some recommendations.

The rasterization rules are specific:

To obtain exact two-dimensional rasterization, carefully specify both the orthographic projection and the vertices of the primitives that are to be rasterized. Specify the orthographic projection with integer coordinates, as shown in the following example:
gluOrtho2D(0, width, 0, height);
The parameters width and height are the dimensions of the viewport. Given this projection matrix, place polygon vertices and pixel image positions at integer coordinates to rasterize predictably. For example, glRecti(0, 0, 1, 1) reliably fills the lower-left pixel of the viewport, and glRasterPos2i(0, 0) reliably positions an unzoomed image at the lower-left pixel of the viewport. However, point vertices, line vertices, and bitmap positions should be placed at half-integer locations. For example, a line drawn from (x (1) , 0.5) to (x (2) , 0.5) will be reliably rendered along the bottom row of pixels in the viewport, and a point drawn at (0.5, 0.5) will reliably fill the same pixel as glRecti(0, 0, 1, 1).

An optimum compromise that allows all primitives to be specified at integer positions, while still ensuring predictable rasterization, is to translate x and y by 0.375, as shown in the following code sample. Such a translation keeps polygon and pixel image edges safely away from the centers of pixels, while moving line vertices close enough to the pixel centers.

glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity( );
gluOrtho2D(0, width, 0, height);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity( );
glTranslatef(0.375, 0.375, 0.0);
/* render all primitives at integer positions */

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 168 Feb 14, 2007 at 05:28

Well, I admit I’m “lost” as to why you would care so much about a few single-pixel errors - I don’t see why you can’t just use TextOut itself. But, if you want, feel free to take dave’s code and modify it to fix the offsets.

A9102969e779768e6f0b8cb87e864c94
0
dave_ 101 Feb 14, 2007 at 08:42

If you want an exact copy of what you get in text out, you should probably render the entire string to texture then display that instead of the technique I’m using. But in the case you’ve shown, it looks like the space is just 1 pixel to large. Perhaps spacing_ should be (fontSize_ / 3) - 1

The font probably renders the same as D3DFont.

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 14, 2007 at 09:51

@Reedbeta

Well, I admit I’m “lost” as to why you would care so much about a few single-pixel errors

A few pixel errors will probably turn into many pixel errors when the font size is enlarged.
@Reedbeta

I don’t see why you can’t just use TextOut itself.

Because Microsoft won’t let me do it. I posted a technical link above.
@dave_

If you want an exact copy of what you get in text out, you should probably render the entire string to texture then display that instead of the technique I’m using. But in the case you’ve shown, it looks like the space is just 1 pixel to large. Perhaps spacing_ should be (fontSize_ / 3) - 1

Subtracting 1 worked for Arial and Verdana, but not for Times New Roman,
so spacing seems to be complicated. And rendering out an entire line is not
practical for me, as length will always be an issue. Per character is best.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 168 Feb 14, 2007 at 15:43

@Lost

Because Microsoft won’t let me do it. I posted a technical link above.

Right, but why would you want to use TextOut on an OpenGL backbuffer anyway? You mentioned using it to construct some screen preview for printing, but if that’s the case all your printing has to be done using GDI anyway, so your screen preview can be done in GDI too, and you could just use TextOut itself. :)

And I don’t get your point about multi-pixel errors when the text is enlarged. Proportional to the size of the text the errors will still be very, very small. Really, why bother worrying about such small offsets? It’s not like TextOut is some paragon of typographical perfection anyway.

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 14, 2007 at 19:08

I want to use elements of OpenGL and TextOut() together (interactively), and the
only way to do that is to render TextOut() to the backbuffer. But this is prohibited.
The exact position, size and scale of the font is important. The OpenGL version
of TextOut() must match its GDI equivalent exactly.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 168 Feb 14, 2007 at 21:09

@Lost

The exact position, size and scale of the font is important. The OpenGL version
of TextOut() must match its GDI equivalent exactly.

You still haven’t answered my question: why is it so important? Honestly, I can’t think of a good reason.

A9102969e779768e6f0b8cb87e864c94
0
dave_ 101 Feb 14, 2007 at 23:14

@Lost

only way to do that is to render TextOut() to the backbuffer.

It’s not the only way, you can render to texture.

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 15, 2007 at 07:18

You still haven’t answered my question: why is it so important? Honestly, I can’t think of a good reason.

Needless to say, I need it, so let’s just agree to that. :)
WYSIWYG. It doesn’t make much sense to print something out
that doesn’t look exactly what you created. I hope you understand that.

It’s not the only way, you can render to texture.

I could probably create a pbuffer the same size as the backbuffer, and copy it
over at the end of every render. It just depends on how much video memory is
available, as all buffers must be created in video memory.

I might have a screen display of 1024x768x32 or larger, and might also want to
create some extra pbuffers for other work in addition to the 3 buffers already
being used for flipping (font/back/pbuffer copy).

I’m guessing most 3D cards nowadays come with 128MB or more?

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 168 Feb 15, 2007 at 07:41

@Lost

WYSIWYG. It doesn’t make much sense to print something out
that doesn’t look exactly what you created.

True, but my original points remain. Printers don’t support OpenGL. Therefore, if you’re printing something, either (a) you are using GDI to construct the printed image, in which case you could just use GDI to construct the screen preview as well and use TextOut in both cases, or (;) you’re printing a bitmap rendered by OpenGL, in which case the bitmap would be a pixel-for-pixel copy (or maybe a high-res rerendering) of the bitmap on the screen including the text by whatever method it was drawn. So, your request still doesn’t make any sense to me. :)

A9102969e779768e6f0b8cb87e864c94
0
dave_ 101 Feb 15, 2007 at 10:24

@Lost

I could probably create a pbuffer the same size as the backbuffer, and copy it
over at the end of every render. It just depends on how much video memory is
available, as all buffers must be created in video memory. I might have a screen display of 1024x768x32 or larger, and might also want to
create some extra pbuffers for other work in addition to the 3 buffers already
being used for flipping (font/back/pbuffer copy).

I really dont understand what you’re trying to do but I mean the other way round. I mean use GDI to draw your document/TextOut onto a bitmap then use that bitmap as a texture, like I’m doing with the fonts.

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 15, 2007 at 19:36

or (b) you’re printing a bitmap rendered by OpenGL, in which case the bitmap would be a pixel-for-pixel copy (or maybe a high-res rerendering) of the bitmap on the screen including the text by whatever method it was drawn.

(c) Bitmap rendered by OpenGL and TextOut()

If I can get TextOut() to the backbuffer somehow, then my problems will be solved.

I really dont understand what you’re trying to do but I mean the other way round. I mean use GDI to draw your document/TextOut onto a bitmap then use that bitmap as a texture, like I’m doing with the fonts.

The problem is, the end result must be displayed interactively, so creating a buffer
in software, and then copying to video memory would be extremely slow. All buffers
should really be created in video memory. And also, there’s an issue with layering
the elements of the scene. Text is generally the last element to be rendered, so
by that alone, it wouldn’t work out very well.

B91eae75cd6245bd8074bd0c3f1cc495
0
Nils_Pipenbrinck 101 Feb 16, 2007 at 00:29

If you need the same kerning as Win32 TextOut I might have what you need.

This litte test-code generates the same output. You can use it to extract the kerning tables from ttf-fonts. Note that most fonts have different kerning tables for different font sizes, so you can’t just extract one table and use it for scaled fonts (it will look like shit).

char * img;

void putpixel (int x, int y, int a) { img[1024*y+x] = a; }

void PlacingTest (void)
{
  HDC dc = CreateCompatibleDC(0);
  int size = 32;
  img = new char[1024*1024];
  memset (img, 0, 1024*1024);
  wchar_t * string = L"This is a test";

  GLYPHMETRICS gm;
  MAT2 mat;
  mat.eM11.fract = 0;   mat.eM11.value = 1;  mat.eM21.fract = 0;    mat.eM21.value = 0;
  mat.eM12.fract = 0;   mat.eM12.value = 0;  mat.eM22.fract = 0;    mat.eM22.value = 1;
  
  HFONT fnt = CreateFont(size,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,FF_DONTCARE,"Arial");
  SelectObject (dc, fnt);

  int nKerningPairs = GetKerningPairs(dc, 0, 0);
  KERNINGPAIR * kerningpairs = new KERNINGPAIR[nKerningPairs];
  GetKerningPairs(dc, nKerningPairs, kerningpairs);

  int xpos = 0;  int ypos = 40;

  wchar_t last = 0;
  for (int i=0; i<wcslen(string); i++) {
    // get image from gdi
    DWORD BytesReq = GetGlyphOutlineW(dc, string[i], GGO_GRAY8_BITMAP, &gm, 0, 0, &mat);
    sU8 * glyphImg= new sU8[BytesReq];
    DWORD r = GetGlyphOutlineW(dc, string[i], GGO_GRAY8_BITMAP, &gm, BytesReq, glyphImg, &mat);
    for (int j=0; j<BytesReq; j++) glyphImg[j]= 255*int(glyphImg[j])/65;

    // apply kerning 
    for (int k=0; k<nKerningPairs; k++) {
      if ((kerningpairs[k].wFirst == last) && (kerningpairs[k].wSecond == string[i])) {
        xpos += kerningpairs[k].iKernAmount;
        break;
      }
    }

    // blit glyph onto bitmap:
    if (string[i]!= ' ') {
      int w = (gm.gmBlackBoxX+3)&~3;
      int h = gm.gmBlackBoxY;
      for (int y=0; y<gm.gmBlackBoxY; y++)
      for (int x=0; x<gm.gmBlackBoxX; x++)
        putpixel (x+xpos+gm.gmptGlyphOrigin.x, y+ypos-gm.gmptGlyphOrigin.y, glyphImg[y*w+x]);
    }

    delete [] glyphImg;
    xpos += gm.gmCellIncX;
    last = string[i];
  }
  delete [] kerningpairs;

  // save the image (so you can test it)
  FILE *f = fopen ("c://test.raw","wb");
  if (f) {
    fwrite (img, 1024,1024,f);
    fclose (f);
  }
  delete [] img;
}
F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 16, 2007 at 02:05

Oops, got it compiled. Thanks.
Missing:

typedef unsigned char sU8;

GGO_GRAY8_BITMAP returns 65 levels of gray (0 to 64).
Shouldn’t this line be:

glyphImg[j] = (255*(int)(glyphImg[j])) / 64;

to convert (0 to 64) into (0 to 255).

B91eae75cd6245bd8074bd0c3f1cc495
0
Nils_Pipenbrinck 101 Feb 16, 2007 at 10:45

@Lost

Oops, got it compiled. Thanks.
Missing:

typedef unsigned char sU8;

GGO_GRAY8_BITMAP returns 65 levels of gray (0 to 64).
Shouldn’t this line be:

glyphImg[j] = (255*(int)(glyphImg[j])) / 64;

to convert (0 to 64) into (0 to 255).

I use these sU an sI types since years so it’s hard to spot them :-) But you figured it out. You’re right with the gray scaling as well.

Don’t worry to much about the glyph extraction code. The code here was just my experimental testbed when I wrote my font packing tool and font rendering class. The key element is really only the kerning information and the positional info inside the glyphs.

These should be extracted at asset generation time and stored in a file for later use. You don’t want to call GetGlyphOutline inside your game.

A9102969e779768e6f0b8cb87e864c94
0
dave_ 101 Feb 16, 2007 at 11:53

If you do come up with a way that’s consistent with text out, please post it here, in the spirit of sharing! :D

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 16, 2007 at 17:13

I’m no font expert, but the problem I see with the font.cpp code is how it does
its spacing. It seems to be constant for every character. While spacing obtained
from kerning has extra spacing info between two characters. Maybe somehow combine
your method + kerning spacing?

B91eae75cd6245bd8074bd0c3f1cc495
0
Nils_Pipenbrinck 101 Feb 16, 2007 at 21:36

The code I’ve posted does exactly that: Pixel perfect placement as TextOut.

F5b67273b9fc71edf8b1824c8f915e48
0
Lost 101 Feb 17, 2007 at 11:56

But, how to get it to an OpenGL backbuffer?
You are writing the fonts as per pixel.

Does glDrawPixels() write to the backbuffer?

And still need to position it correctly on the screen, with glRasterPos().

B91eae75cd6245bd8074bd0c3f1cc495
0
Nils_Pipenbrinck 101 Feb 17, 2007 at 12:31

@Lost

But, how to get it to an OpenGL backbuffer?
You are writing the fonts as per pixel.

Does glDrawPixels() write to the backbuffer?

And still need to position it correctly on the screen, with glRasterPos().

Well - my code is just a “proof of concept” thing to show how to do the positioning. It’s not to be used in an application.

Put all the glyphs into a bitmap and extract the kerning tables using my code. Then you can draw the glyphs via textued quads.

8238993412b94c2b94983decdaeb0ad6
0
barman 101 Dec 23, 2009 at 19:53

I know this is an old post. But you you seem to be pretty fluent with this topic. I have some basic questions:

  1. How do we know how many characters are there in a character set.
    example: the number of asian characters is more than the number of characters in english.

  2. I obtained the kerning pais using GetKerningPairs. Sometimes kerning amount is 0 which means there is no kerning. Why is it there then?
    Why other pairs of first & second not there i nthe kerning pairs list?

Any pointers will be appreciated,
Barman