2

opengl を使用して、lwjgl ディスプレイにカスタム フォントでテキストを表示しようとしています。現在、カスタム ビットマップ フォント クラスを使用して、png ファイルからフォントを読み込み、タイルセットと同じ方法で表示しています。問題なく動作しますが、テキストを画面に描画すると、文字 M が文字 I よりも幅が広く、すべての文字の幅が異なるため、文字間の間隔がすべて異なります。各文字の幅を検出する方法はありますか? または、lwjgl のフォントに固有のライブラリはありますか? slick2d を使用してそれを行う方法はありますが、テキストを表示するためだけにライブラリ全体をロードするのは無意味です!

4

1 に答える 1

9

I once had the same problem, so here is what I did to solve the problem.

I usually make a lot of small example programs so I can actually still find the program which have the exact problem, so here is a screenshot.

before

As you already described the problem lies in that we're using the same width for each character which gives those awful spacing.

Basically what I ended up doing was writing each character and symbol's x, y, width, height, xoffset, yoffset, xadvance to a file.

  • x/y = The x/y coordinate of the character on the image.
  • width/height = The width/height of the character.
  • xoffset/yoffset = This is extra spacing only given to the rendered character.
  • xadvance = This is the extra spacing that will be added after the character is rendered.
  • All of the values are given in pixels.

Here is an example:

file data/materials/font/font1.png

char default width=5 height=7 xoffset=0 yoffset=0 xadvance=1

char id= x=0 y=16

char id=a x=8 y=48
char id=b x=16 y=48
char id=c x=24 y=48
... etc ...
char id=i x=72 y=48 width=1
... etc ...

char id=A x=8 y=32
char id=B x=16 y=32
char id=C x=24 y=32
... etc ...
char id=I x=72 y=32 width=3
... etc ...

Understanding The File

Let me just explain shortly what each line does.

file data/materials/font/font1.png

Tells the parser that we want to load and and use the following given path as the font texture. You can of course do that manually, this is simply how I've done it.

char default width=5 height=7 xoffset=0 yoffset=0 xadvance=1

This sets the default values for a new char, so if we define a char and it doesn't contain any of the above it will be given the default value.

char id=a x=8 y=48

This defines the character 'a' because of course the ìd=a tells that it's 'a' and x=8 y=48 is the top-left coordinate of where the character is located on the texture.

Again, because we defined char default width=5 height=5 ..... above it will by default be assigned to the character as well. So basically the parser reads char id=a x=8 y=48 as char id=a x=8 y=48 width=5 height=7 xoffset=0 yoffset=0 xadvance=1

That is also why I added the char id=i x=72 y=48 width=1 to the example, there you can actually see that we define the width as only being 1 instead of the default 5

char id= x=0 y=16

This actually defines the whitespace, so if you would like it so render a letter or symbol instead of a whitespace, that could also be done.

Loading The File

First, I mainly have 2 classes BitmapFont and BitmapGlyph. I won't give you all the code of both my classes, because the point of Stack Overflow isn't that we should do all your work.

The BitmapGlyph class stores infomation about a characters/symbol, since it's just a class for storing information it's pretty simple.

public class BitmapGlyph {
    public int id;
    public int x, y, width, height;
    public int xoffset, yoffset, xadvance;
    public float u, v, u2, v2;
}

The BitmapFont class mainly contains 2 mehods static load(final File file) and render(String text, float x, float y, float size)

It contains more methods, like dispose(), getTexture(), etc. though for your problem they are irrelevant.

Parsing The File

public final static BitmapFont load(final File file) {
    BitmapFont font = new BitmapFont();

    final ArrayList<BitmapGlyph> glyphs = new ArrayList<BitmapGlyph>();

    try (final BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
        String line;

        while ((line = br.readLine()) != null) {
            if (line.isEmpty()) { continue; }

            // Now parse the file, example:

            if (line.startsWith("file ")) {
                // I don't know if you have a Texture class, and if you
                // can load an image from a path to a texture,
                // though since this is just an example and it's easy
                // to understand. Then you will just have to adapt it
                // to your own program.
                font.texture = new Texture(new File(line.substring(5)));
            }

            // PARSE THE REST OF THE FILE
        }

        br.close();
    }
    catch (Exception ex) {}

    return font;
}

After I've parsed the file I also convert the characters x, y, width, height, to actual u, v, u2, v2, coordinates on the texture. I do that by looping through each BitmapGlyph like this.

int width = texture.getWidth(); // The width should of course be the actual pixel width of the image.
int height = texture.getHeight(); // The height should of course be the actual pixel height of the image.

for (BitmapGlyph glyph : glyphs) {
    glyph.u = glyph.x / (float) width;
    glyph.v = glyph.y / (float) height;
    glyph.u2 = (glyph.x + glyph.width) / (float) width;
    glyph.v2 = (glyph.y + glyph.height) / (float) height;
}

Rendering

You probably already know this, though to be on safe side I will guide you.

public final void render(String text, float x, float y, float size) {
    // BIND FONT TEXTURE

    glBegin(GL_QUADS);

    for (int i = 0, istop = text.length(); i < istop; i++) {
        char ascii = text.charAt(i);

        BitmapGlyph g = ...; // Get the stored glyph according to the char, check against the BitmapGlyph id

        float ww = g.width * size;
        float hh = g.height * size;

        float xx = x + g.xoffset * size;
        float yy = y + g.yoffset * size;

        glTexCoord2f(g.u, g.v);
        glVertex2f(xx, yy);

        glTexCoord2f(g.u, g.v2);
        glVertex2f(xx, yy + hh);

        glTexCoord2f(g.u2, g.v2);
        glVertex2f(xx + ww, yy + hh);

        glTexCoord2f(g.u2, g.v);
        glVertex2f(xx + ww, yy);

        x += (g.width + g.xadvance) * size;
    }

    glEnd();
}

Important: I'm only using the deprecated methods because it makes it easier to explain.

When using the way I just described you will end up with this result. (Again this is a screenshot of the actual program I made for fixing this problem)

after

Comparison

Before

before

After

after

Extra

Here are some points on why you should choose to do it this way.

  1. This way you don't have to arrange all the characters the same way.
  2. You can always add new characters and/or symbols.
  3. The characters can be any size you want them to be.

This was probably the longest answer I've ever made, though I hope you can use it!

于 2013-10-27T01:21:29.667 に答える