[Solved] Rendering text in lwjgl without slick

I was looking around on how to draw text and allow for resizing, fonts, etc. All i could find was extremely complicated code and it can only handle ether capital or not. Anyone got some code i can work from to achieve my desired goal or any res i might have overlooked that could help?

Solved, im using TheBoneJarmer example:


import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

import static org.lwjgl.opengl.GL11.*;

public class Font {
   
   //Constants
   private final Map<Integer,String> CHARS = new HashMap<Integer,String>() {{
        put(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        put(1, "abcdefghijklmnopqrstuvwxyz");
        put(2, "0123456789");
        put(3, "ÄÖÜäöüß");
        put(4, " $+-*/=%\"'#@&_(),.;:?!\\|<>[]§`^~");
    }};
   
   //Variables
    private java.awt.Font font;
    private FontMetrics fontMetrics;
    private BufferedImage bufferedImage;
    private int fontTextureId;
    
    //Getters
    public float getFontImageWidth() {
        return (float) CHARS.values().stream().mapToDouble(e -> fontMetrics.getStringBounds(e, null).getWidth()).max().getAsDouble();
    }
    public float getFontImageHeight() {
        return (float) CHARS.keySet().size() * (this.getCharHeight());
    }
    public float getCharX(char c) {
        String originStr = CHARS.values().stream().filter(e -> e.contains("" + c)).findFirst().orElse("" + c);
        return (float) fontMetrics.getStringBounds(originStr.substring(0, originStr.indexOf(c)), null).getWidth();
    }
    public float getCharY(char c) {
        float lineId = (float) CHARS.keySet().stream().filter(i -> CHARS.get(i).contains("" + c)).findFirst().orElse(0);
        return this.getCharHeight() * lineId;
    }
    public float getCharWidth(char c) {
        return fontMetrics.charWidth(c);
    }
    public float getCharHeight() {
        return (float) (fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent());
    }
    
    //Constructors
    public Font(String path, float size) throws Exception {
        this.font = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, new File(path)).deriveFont(size);
        
        //Generate buffered image
        GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        Graphics2D graphics = gc.createCompatibleImage(1, 1, Transparency.TRANSLUCENT).createGraphics();
        graphics.setFont(font);
        
        fontMetrics = graphics.getFontMetrics();
        bufferedImage = graphics.getDeviceConfiguration().createCompatibleImage((int) getFontImageWidth(),(int) getFontImageHeight(),Transparency.TRANSLUCENT);
        
      //Generate texture
      fontTextureId = glGenTextures();
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, fontTextureId);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,(int) getFontImageWidth(),(int) getFontImageHeight(),0, GL_RGBA, GL_UNSIGNED_BYTE, asByteBuffer());
 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }
    
    //Functions
    public void drawText(String text, int x, int y) {
        glBindTexture(GL_TEXTURE_2D, this.fontTextureId);
        glBegin(GL_QUADS);
        
        int xTmp = x;
        for (char c : text.toCharArray()) {
            float width = getCharWidth(c);
            float height = getCharHeight();
            float cw = 1f / getFontImageWidth() * width;
            float ch = 1f / getFontImageHeight() * height;
            float cx = 1f / getFontImageWidth() * getCharX(c);
            float cy = 1f / getFontImageHeight() * getCharY(c);
 
            glTexCoord2f(cx, cy);
            glVertex3f(xTmp, y, 0);
 
            glTexCoord2f(cx + cw, cy);
            glVertex3f(xTmp + width, y, 0);
 
            glTexCoord2f(cx + cw, cy + ch);
            glVertex3f(xTmp + width, y + height, 0);
 
            glTexCoord2f(cx, cy + ch);
            glVertex3f(xTmp, y + height, 0);
 
            xTmp += width;
        }
        
        glEnd();
    }
    
    //Conversions
    public ByteBuffer asByteBuffer() {
 
        ByteBuffer byteBuffer;
      
        //Draw the characters on our image
        Graphics2D imageGraphics = (Graphics2D) bufferedImage.getGraphics();
        imageGraphics.setFont(font);
        imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        imageGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 
        // draw every CHAR by line...
        imageGraphics.setColor(java.awt.Color.WHITE);
        CHARS.keySet().stream().forEach(i -> imageGraphics.drawString(CHARS.get(i), 0, fontMetrics.getMaxAscent() + (this.getCharHeight() * i)));
        
        //Generate texture data
        int[] pixels = new int[bufferedImage.getWidth() * bufferedImage.getHeight()];
        bufferedImage.getRGB(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), pixels, 0, bufferedImage.getWidth());
        byteBuffer = ByteBuffer.allocateDirect((bufferedImage.getWidth() * bufferedImage.getHeight() * 4));
 
        for (int y = 0; y < bufferedImage.getHeight(); y++) {
            for (int x = 0; x < bufferedImage.getWidth(); x++) {
                int pixel = pixels[y * bufferedImage.getWidth() + x];
                byteBuffer.put((byte) ((pixel >> 16) & 0xFF));   // Red component
                byteBuffer.put((byte) ((pixel >> 8) & 0xFF));    // Green component
                byteBuffer.put((byte) (pixel & 0xFF));           // Blue component
                byteBuffer.put((byte) ((pixel >> 24) & 0xFF));   // Alpha component. Only for RGBA
            }
        }
        
        byteBuffer.flip();
 
        return byteBuffer;
    }
}

Are you looking only for Windows or also other platforms?

Mac, Linux and Windows


(more complex but not slick dependent)

It depends on what format you want.

Bitmap is pretty easy to implement if you are going monospace. Thecodinguniverse made an excellent tutorial on this here.

If you don’t want monospace and need to resize, TrueTypeFont is the way to go. You can use ttf libraries for java, or render the font to a BufferedImage using AWT, then convert it to a texture like bitmaps. I did this in Mercury, based off of Slick2D’s original implementation, and it works quite well, although is probably slower than using a full library for it.

Just remember for each of these techniques that you should throw a pixel or two of padding between all the characters in the font texture. OpenGL works differently on different platforms, it seems, and can result in texture bleeding.

Okay thanks for the replies i shall take a look now and see how it goes

Is it okay if i use your font code and adapt it for my project?

My second link is more efficient than my first link, because my second link doesn’t create a buffered image per letter.

Mercury is under the MIT license, so as long as you print the copyright, go crazy. However, @CopyableCougar4 just pointed out an improvement on the system that I could make; I would recommend his right now for performance. I will update Mercury’s code on the next commit.

UPDATE:
The improved system is up on the unstable branch. Thanks Cougar!

Thanks ill check it out

Hey Ed!

I asked the same question a couple of weeks ago on the LWJGL forums and managed to write my own truetypefont class thanks to the help of some other users. Here’s the url http://forum.lwjgl.org/index.php?topic=5573.0. I’ve attached to code as well. I don’t know if you already settled down with something but maybe it can help others as well.


import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

import static org.lwjgl.opengl.GL11.*;

public class Font {
	
	//Constants
	private final Map<Integer,String> CHARS = new HashMap<Integer,String>() {{
        put(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        put(1, "abcdefghijklmnopqrstuvwxyz");
        put(2, "0123456789");
        put(3, "ÄÖÜäöüß");
        put(4, " $+-*/=%\"'#@&_(),.;:?!\\|<>[]§`^~");
    }};
	
	//Variables
    private java.awt.Font font;
    private FontMetrics fontMetrics;
    private BufferedImage bufferedImage;
    private int fontTextureId;
    
    //Getters
    public float getFontImageWidth() {
        return (float) CHARS.values().stream().mapToDouble(e -> fontMetrics.getStringBounds(e, null).getWidth()).max().getAsDouble();
    }
    public float getFontImageHeight() {
        return (float) CHARS.keySet().size() * (this.getCharHeight());
    }
    public float getCharX(char c) {
        String originStr = CHARS.values().stream().filter(e -> e.contains("" + c)).findFirst().orElse("" + c);
        return (float) fontMetrics.getStringBounds(originStr.substring(0, originStr.indexOf(c)), null).getWidth();
    }
    public float getCharY(char c) {
        float lineId = (float) CHARS.keySet().stream().filter(i -> CHARS.get(i).contains("" + c)).findFirst().orElse(0);
        return this.getCharHeight() * lineId;
    }
    public float getCharWidth(char c) {
        return fontMetrics.charWidth(c);
    }
    public float getCharHeight() {
        return (float) (fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent());
    }
    
    //Constructors
    public Font(String path, float size) throws Exception {
        this.font = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, new File(path)).deriveFont(size);
        
        //Generate buffered image
        GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        Graphics2D graphics = gc.createCompatibleImage(1, 1, Transparency.TRANSLUCENT).createGraphics();
        graphics.setFont(font);
        
        fontMetrics = graphics.getFontMetrics();
        bufferedImage = graphics.getDeviceConfiguration().createCompatibleImage((int) getFontImageWidth(),(int) getFontImageHeight(),Transparency.TRANSLUCENT);
        
		//Generate texture
		fontTextureId = glGenTextures();
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, fontTextureId);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,(int) getFontImageWidth(),(int) getFontImageHeight(),0, GL_RGBA, GL_UNSIGNED_BYTE, asByteBuffer());
 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }
    
    //Functions
    public void drawText(String text, int x, int y) {
        glBindTexture(GL_TEXTURE_2D, this.fontTextureId);
        glBegin(GL_QUADS);
        
        int xTmp = x;
        for (char c : text.toCharArray()) {
            float width = getCharWidth(c);
            float height = getCharHeight();
            float cw = 1f / getFontImageWidth() * width;
            float ch = 1f / getFontImageHeight() * height;
            float cx = 1f / getFontImageWidth() * getCharX(c);
            float cy = 1f / getFontImageHeight() * getCharY(c);
 
            glTexCoord2f(cx, cy);
            glVertex3f(xTmp, y, 0);
 
            glTexCoord2f(cx + cw, cy);
            glVertex3f(xTmp + width, y, 0);
 
            glTexCoord2f(cx + cw, cy + ch);
            glVertex3f(xTmp + width, y + height, 0);
 
            glTexCoord2f(cx, cy + ch);
            glVertex3f(xTmp, y + height, 0);
 
            xTmp += width;
        }
        
        glEnd();
    }
    
    //Conversions
    public ByteBuffer asByteBuffer() {
 
        ByteBuffer byteBuffer;
		
        //Draw the characters on our image
        Graphics2D imageGraphics = (Graphics2D) bufferedImage.getGraphics();
        imageGraphics.setFont(font);
        imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        imageGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 
        // draw every CHAR by line...
        imageGraphics.setColor(java.awt.Color.WHITE);
        CHARS.keySet().stream().forEach(i -> imageGraphics.drawString(CHARS.get(i), 0, fontMetrics.getMaxAscent() + (this.getCharHeight() * i)));
        
        //Generate texture data
        int[] pixels = new int[bufferedImage.getWidth() * bufferedImage.getHeight()];
        bufferedImage.getRGB(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), pixels, 0, bufferedImage.getWidth());
        byteBuffer = ByteBuffer.allocateDirect((bufferedImage.getWidth() * bufferedImage.getHeight() * 4));
 
        for (int y = 0; y < bufferedImage.getHeight(); y++) {
            for (int x = 0; x < bufferedImage.getWidth(); x++) {
                int pixel = pixels[y * bufferedImage.getWidth() + x];
                byteBuffer.put((byte) ((pixel >> 16) & 0xFF));   // Red component
                byteBuffer.put((byte) ((pixel >> 8) & 0xFF));    // Green component
                byteBuffer.put((byte) (pixel & 0xFF));           // Blue component
                byteBuffer.put((byte) ((pixel >> 24) & 0xFF));   // Alpha component. Only for RGBA
            }
        }
        
        byteBuffer.flip();
 
        return byteBuffer;
    }
}

I hope this can help you out!
TBJ

I still could not find anything that would work to what i need, ill give your code a try, thanks

Thanks man, i got it working using this! ill add you as a contributor to http://www.java-gaming.org/topics/iconified/35090/view.html

How on earth could you forget my implementation? Don’t you want support for over-hanging glyphs?

The solution I went with, is to use Texture pages for the font, to add account for the padding to support overhanging glyphs. The padding is calculated automatically based on the font average width and the font size.

https://github.com/sriharshachilakapati/SilenceEngine/blob/master/src/main/java/com/shc/silenceengine/graphics/TrueTypeFont.java

Another thing, since this is using texture-pages, it is easy enough to convert this to a BitmapFont, and load from BMFont format. The only issue for you is the dependency on batcher, but I think you can easily convert it, because my Batcher simulates the immediate mode.

Hey Ed!

Thanks for the medal and a place as contributor! I appreciate that! But please, refer to Leycarno as well. He’s a member from the LWJGL community and wrote the original font code for me. The code I gave to you was just a cleaned up version of it.

@SHC

I didn’t refer to your code because he did not mentioned he want features like over-hanging glyphs. As far as I know, he just wanted to display small readable text on the screen for things like dialogs, hp, player names. No fancy fonts. That is why I posted my code and not yours because my code only contains required methods and variables to render text.

Yeah thats right, i could not for the life of me find a simple implementation for rendered text anywhere xD

Im noticing that the sample code in this thread is all old openGL.
Do you want a sample using shaders + modern openGL?

Msg me if thats what you need and ill post up a chunk here.

Most of sample codes don’t handle unicode charsets that is not trivial with theses 250K valid codepoints (1 000 000 total codepoints).
If you want a general fonts system, it’s a pain to write it.

i will give it a try soon !!!

Yeah man, that would be appropriated, the code works but has some performance issues

Is there any reason that drawing more than 10 strings effects FPS so dramatically? I went from 60fps to 3fps drawing 50 test strings. The rest of the application I tested this in is well written so I don’t know why the font class would effect frame count so much.