LWJGL Forum

Programming => General Java Game Development => Topic started by: bobjob on June 28, 2009, 00:54:50

Title: Complete TrueTypeFont class that only requires LWJGL
Post by: bobjob on June 28, 2009, 00:54:50
I went through the slick implentation for TrueTypeFonts
and made some changes:
* does not require the slick package
* added scaling as well as its artifact correction
* removed dependency on blending
* alignment (added: right and center)
* takes into account the newline character: '\n'
* now works with BACK face CULLING on, if used in 3D.


import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
import java.awt.GraphicsEnvironment;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;


/**
* A TrueType font implementation originally for Slick, edited for Bobjob's Engine
*
* @original author James Chambers (Jimmy)
* @original author Jeremy Adams (elias4444)
* @original author Kevin Glass (kevglass)
* @original author Peter Korzuszek (genail)
*
* @new version edited by David Aaron Muhar (bobjob)
*/
public class TrueTypeFont {
public final static int
ALIGN_LEFT = 0,
ALIGN_RIGHT = 1,
ALIGN_CENTER = 2;
/** Array that holds necessary information about the font characters */
private IntObject[] charArray = new IntObject[256];

/** Map of user defined font characters (Character <-> IntObject) */
private Map customChars = new HashMap();

/** Boolean flag on whether AntiAliasing is enabled or not */
private boolean antiAlias;

/** Font's size */
private int fontSize = 0;

/** Font's height */
private int fontHeight = 0;

/** Texture used to cache the font 0-255 characters */
private int fontTextureID;

/** Default font texture width */
private int textureWidth = 512;

/** Default font texture height */
private int textureHeight = 512;

/** A reference to Java's AWT Font that we create our font texture from */
private Font font;

/** The font metrics for our Java AWT font */
private FontMetrics fontMetrics;


private int correctL = 9, correctR = 8;

private class IntObject {
/** Character's width */
public int width;

/** Character's height */
public int height;

/** Character's stored x position */
public int storedX;

/** Character's stored y position */
public int storedY;
}


public TrueTypeFont(Font font, boolean antiAlias, char[] additionalChars) {
this.font = font;
this.fontSize = font.getSize()+3;
this.antiAlias = antiAlias;

createSet( additionalChars );

fontHeight -= 1;
if (fontHeight <= 0) fontHeight = 1;
}

public TrueTypeFont(Font font, boolean antiAlias) {
this( font, antiAlias, null );
}
public void setCorrection(boolean on) {
if (on) {
correctL = 2;
correctR = 1;
} else {
correctL = 0;
correctR = 0;
}
}
private BufferedImage getFontImage(char ch) {
// Create a temporary image to extract the character's size
BufferedImage tempfontImage = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) tempfontImage.getGraphics();
if (antiAlias == true) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
g.setFont(font);
fontMetrics = g.getFontMetrics();
int charwidth = fontMetrics.charWidth(ch)+8;

if (charwidth <= 0) {
charwidth = 7;
}
int charheight = fontMetrics.getHeight()+3;
if (charheight <= 0) {
charheight = fontSize;
}

// Create another image holding the character we are creating
BufferedImage fontImage;
fontImage = new BufferedImage(charwidth, charheight,
BufferedImage.TYPE_INT_ARGB);
Graphics2D gt = (Graphics2D) fontImage.getGraphics();
if (antiAlias == true) {
gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
gt.setFont(font);

gt.setColor(Color.WHITE);
int charx = 3;
int chary = 1;
gt.drawString(String.valueOf(ch), (charx), (chary)
+ fontMetrics.getAscent());

return fontImage;

}

private void createSet( char[] customCharsArray ) {
// If there are custom chars then I expand the font texture twice
if (customCharsArray != null && customCharsArray.length > 0) {
textureWidth *= 2;
}

// In any case this should be done in other way. Texture with size 512x512
// can maintain only 256 characters with resolution of 32x32. The texture
// size should be calculated dynamicaly by looking at character sizes.

try {

BufferedImage imgTemp = new BufferedImage(textureWidth, textureHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) imgTemp.getGraphics();

g.setColor(new Color(0,0,0,1));
g.fillRect(0,0,textureWidth,textureHeight);

int rowHeight = 0;
int positionX = 0;
int positionY = 0;

int customCharsLength = ( customCharsArray != null ) ? customCharsArray.length : 0;

for (int i = 0; i < 256 + customCharsLength; i++) {

// get 0-255 characters and then custom characters
char ch = ( i < 256 ) ? (char) i : customCharsArray[i-256];

BufferedImage fontImage = getFontImage(ch);

IntObject newIntObject = new IntObject();

newIntObject.width = fontImage.getWidth();
newIntObject.height = fontImage.getHeight();

if (positionX + newIntObject.width >= textureWidth) {
positionX = 0;
positionY += rowHeight;
rowHeight = 0;
}

newIntObject.storedX = positionX;
newIntObject.storedY = positionY;

if (newIntObject.height > fontHeight) {
fontHeight = newIntObject.height;
}

if (newIntObject.height > rowHeight) {
rowHeight = newIntObject.height;
}

// Draw it here
g.drawImage(fontImage, positionX, positionY, null);

positionX += newIntObject.width;

if( i < 256 ) { // standard characters
charArray[i] = newIntObject;
} else { // custom characters
customChars.put( new Character( ch ), newIntObject );
}

fontImage = null;
}

fontTextureID = loadImage(imgTemp);



//.getTexture(font.toString(), imgTemp);

} catch (Exception e) {
System.err.println("Failed to create font.");
e.printStackTrace();
}
}

private void drawQuad(float drawX, float drawY, float drawX2, float drawY2,
float srcX, float srcY, float srcX2, float srcY2) {
float DrawWidth = drawX2 - drawX;
float DrawHeight = drawY2 - drawY;
float TextureSrcX = srcX / textureWidth;
float TextureSrcY = srcY / textureHeight;
float SrcWidth = srcX2 - srcX;
float SrcHeight = srcY2 - srcY;
float RenderWidth = (SrcWidth / textureWidth);
float RenderHeight = (SrcHeight / textureHeight);

GL11.glTexCoord2f(TextureSrcX, TextureSrcY);
GL11.glVertex2f(drawX, drawY);
GL11.glTexCoord2f(TextureSrcX, TextureSrcY + RenderHeight);
GL11.glVertex2f(drawX, drawY + DrawHeight);
GL11.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY + RenderHeight);
GL11.glVertex2f(drawX + DrawWidth, drawY + DrawHeight);
GL11.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY);
GL11.glVertex2f(drawX + DrawWidth, drawY);
}

public int getWidth(String whatchars) {
int totalwidth = 0;
IntObject intObject = null;
int currentChar = 0;
for (int i = 0; i < whatchars.length(); i++) {
currentChar = whatchars.charAt(i);
if (currentChar < 256) {
intObject = charArray[currentChar];
} else {
intObject = (IntObject)customChars.get( new Character( (char) currentChar ) );
}

if( intObject != null )
totalwidth += intObject.width;
}
return totalwidth;
}

public int getHeight() {
return fontHeight;
}


public int getHeight(String HeightString) {
return fontHeight;
}

public int getLineHeight() {
return fontHeight;
}

public void drawString(float x, float y,
String whatchars, float scaleX, float scaleY) {
drawString(x,y,whatchars, 0, whatchars.length()-1, scaleX, scaleY, ALIGN_LEFT);
}
public void drawString(float x, float y,
String whatchars, float scaleX, float scaleY, int format) {
drawString(x,y,whatchars, 0, whatchars.length()-1, scaleX, scaleY, format);
}


public void drawString(float x, float y,
String whatchars, int startIndex, int endIndex,
float scaleX, float scaleY,
int format
) {

IntObject intObject = null;
int charCurrent;


int totalwidth = 0;
int i = startIndex, d, c;
float startY = 0;



switch (format) {
case ALIGN_RIGHT: {
d = -1;
c = correctR;

while (i < endIndex) {
if (whatchars.charAt(i) == '\n') startY -= fontHeight;
i++;
}
break;
}
case ALIGN_CENTER: {
for (int l = startIndex; l <= endIndex; l++) {
charCurrent = whatchars.charAt(l);
if (charCurrent == '\n') break;
if (charCurrent < 256) {
intObject = charArray[charCurrent];
} else {
intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
}
totalwidth += intObject.width-correctL;
}
totalwidth /= -2;
}
case ALIGN_LEFT:
default: {
d = 1;
c = correctL;
break;
}

}

GL11.glBindTexture(GL11.GL_TEXTURE_2D, fontTextureID);
GL11.glBegin(GL11.GL_QUADS);

while (i >= startIndex && i <= endIndex) {

charCurrent = whatchars.charAt(i);
if (charCurrent < 256) {
intObject = charArray[charCurrent];
} else {
intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
}

if( intObject != null ) {
if (d < 0) totalwidth += (intObject.width-c) * d;
if (charCurrent == '\n') {
startY -= fontHeight * d;
totalwidth = 0;
if (format == ALIGN_CENTER) {
for (int l = i+1; l <= endIndex; l++) {
charCurrent = whatchars.charAt(l);
if (charCurrent == '\n') break;
if (charCurrent < 256) {
intObject = charArray[charCurrent];
} else {
intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
}
totalwidth += intObject.width-correctL;
}
totalwidth /= -2;
}
//if center get next lines total width/2;
}
else {
drawQuad((totalwidth + intObject.width) * scaleX + x, startY * scaleY + y,
totalwidth * scaleX + x,
(startY + intObject.height) * scaleY + y, intObject.storedX + intObject.width,
intObject.storedY + intObject.height,intObject.storedX,
intObject.storedY);
if (d > 0) totalwidth += (intObject.width-c) * d ;
}
i += d;

}
}
GL11.glEnd();
}
public static int loadImage(BufferedImage bufferedImage) {
    try {
    short width       = (short)bufferedImage.getWidth();
    short height      = (short)bufferedImage.getHeight();
    //textureLoader.bpp = bufferedImage.getColorModel().hasAlpha() ? (byte)32 : (byte)24;
    int bpp = (byte)bufferedImage.getColorModel().getPixelSize();
    ByteBuffer byteBuffer;
    DataBuffer db = bufferedImage.getData().getDataBuffer();
    if (db instanceof DataBufferInt) {
    int intI[] = ((DataBufferInt)(bufferedImage.getData().getDataBuffer())).getData();
    byte newI[] = new byte[intI.length * 4];
    for (int i = 0; i < intI.length; i++) {
    byte b[] = intToByteArray(intI[i]);
    int newIndex = i*4;
   
    newI[newIndex]   = b[1];
    newI[newIndex+1] = b[2];
    newI[newIndex+2] = b[3];
    newI[newIndex+3] = b[0];
    }
   
    byteBuffer  = ByteBuffer.allocateDirect(
    width*height*(bpp/8))
                           .order(ByteOrder.nativeOrder())
                            .put(newI);
    } else {
    byteBuffer  = ByteBuffer.allocateDirect(
    width*height*(bpp/8))
                           .order(ByteOrder.nativeOrder())
                            .put(((DataBufferByte)(bufferedImage.getData().getDataBuffer())).getData());
    }
    byteBuffer.flip();
   
   
    int internalFormat = GL11.GL_RGBA8,
format = GL11.GL_RGBA;
IntBuffer   textureId =  BufferUtils.createIntBuffer(1);;
GL11.glGenTextures(textureId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId.get(0));


GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);

GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);

GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);



GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D,
      internalFormat,
      width,
      height,
      format,
      GL11.GL_UNSIGNED_BYTE,
      byteBuffer);
return textureId.get(0);
   
} catch (Exception e) {
    e.printStackTrace();
    System.exit(-1);
    }

return -1;
}
public static boolean isSupported(String fontname) {
Font font[] = getFonts();
for (int i = font.length-1; i >= 0; i--) {
if (font[i].getName().equalsIgnoreCase(fontname))
return true;
}
return false;
}
public static Font[] getFonts() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
}
public static byte[] intToByteArray(int value) {
        return new byte[] {
                (byte)(value >>> 24),
                (byte)(value >>> 16),
                (byte)(value >>> 8),
                (byte)value};
}

public void destroy() {
IntBuffer scratch = BufferUtils.createIntBuffer(1);
scratch.put(0, fontTextureID);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GL11.glDeleteTextures(scratch);
}
}

Title: Re: Complete TrueTypeFont class that only required LWJGL
Post by: bobjob on June 28, 2009, 00:55:49
Test case

import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;


import java.awt.Font;

public class TTFTest {
public static int width = 800, height = 600;
protected static final String TITLE = "Template v1";
static boolean running = true;
final public static int FRAME_RATE = 60;
static FPSCounter fpsCounter;
public static void main(String args[]) {
new TTFTest();
}
public TTFTest() {
try {
initDisplay(false);
initGL();
loadResources();
run();
cleanup();
} catch (Exception e) {
e.printStackTrace();
Sys.alert(TITLE, "Error 101: " + e.toString());
}
}
private static void initDisplay(boolean fullscreen) throws Exception {

Display.setTitle(TITLE); // sets aplication name
Display.setFullscreen(fullscreen);  // create a full screen window if possible

        DisplayMode displayMode = null;
        DisplayMode d[] = Display.getAvailableDisplayModes();
        for (int i = d.length - 1; i >= 0; i--) {
            displayMode = d[i];
            if (d[i].getWidth() == width
                && d[i].getHeight() == height
                && (d[i].getBitsPerPixel() >= 24 &&  d[i].getBitsPerPixel() <= 24 + 8 )
                && d[i].getFrequency() == FRAME_RATE) {
                break;
            }
        } // Finds a suitable resolution
        Display.setDisplayMode(displayMode); // sets display resoulation
        if (fullscreen) Display.setVSyncEnabled(true); // enable vsync if poosible
Display.create();
}
public static void initGL() {

       
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
        GL11.glEnable(GL11.GL_TEXTURE_2D); // Enable Texture Mapping
        GL11.glClearColor(0.5f,0.5f,0.5f,0f); // Black Background
        GL11.glDisable(GL11.GL_DITHER);
        GL11.glDepthFunc(GL11.GL_LESS); // Depth function less or equal
        GL11.glEnable(GL11.GL_NORMALIZE); // calculated normals when scaling
        GL11.glEnable(GL11.GL_CULL_FACE); // prevent render of back surface   
        GL11.glEnable(GL11.GL_BLEND); // Enabled blending
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // selects blending method
        GL11.glEnable(GL11.GL_ALPHA_TEST); // allows alpha channels or transperancy
        GL11.glAlphaFunc(GL11.GL_GREATER, 0.1f); // sets aplha function
        GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST); // High quality visuals
        GL11.glHint(GL11.GL_POLYGON_SMOOTH_HINT, GL11.GL_NICEST); //  Really Nice Perspective Calculations
        GL11.glShadeModel(GL11.GL_SMOOTH); // Enable Smooth Shading
GL11.glViewport(0, 0, 800, 600);
        GL11.glMatrixMode(GL11.GL_PROJECTION); // Select The Projection Matrix
        GL11.glLoadIdentity(); // Reset The Projection Matrix
        GLU.gluPerspective(30, width/(float)height, 1f, 300f);  //Aspect Ratio Of The Window
        GL11.glMatrixMode(GL11.GL_MODELVIEW); // Select The Modelview Matrix
        GL11.glDepthMask(true); // Enable Depth Mask
}
static TrueTypeFont trueTypeFont;
private static void loadResources() {
Keyboard.enableRepeatEvents(false);
// initialise the font
String fontName = "Agent Orange";
if (!TrueTypeFont.isSupported(fontName)) fontName = "serif";
Font font = new Font(fontName, Font.ITALIC | Font.BOLD, 40);
trueTypeFont = new TrueTypeFont(font, true);
fpsCounter = new FPSCounter();
fpsCounter.init();
// render some text to the screen
//trueTypeFont.drawString(20.0f, 20.0f, "Slick displaying True Type Fonts", Color.green);

}

public static void run() {
while (running) {
if (Display.isCloseRequested()) running = false;

// Display.sync(FRAME_RATE);
logic();
if (Display.isActive() && Display.isVisible()){
render();
Display.update();
} else {
Display.update();
}
}
}
    static String lastFPS;
public static void logic() {
if (fpsCounter != null) {
if (fpsCounter.update()) lastFPS = "FPS " + fpsCounter.getFPS();
Display.setTitle(lastFPS);
}
int x = Mouse.getX();
int y = Mouse.getY();


while (Mouse.next()) {
if (Mouse.getEventButtonState()) {
int mouse = Mouse.getEventButton();
switch (mouse) {
case 0: {
System.out.println("" + x + " " + y);

break;
}
case 1: {
System.out.println("" + x + " " + y);

break;
}
case 2: {
break;
}
}
}
}

while (Keyboard.next()) {
if (Keyboard.getEventKeyState()) {
int key =  Keyboard.getEventKey();

switch (key) {
case Keyboard.KEY_SPACE: {

break;
}
case Keyboard.KEY_RETURN: {

break;
}
}
}
}


}


public static void render() {
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT |GL11.GL_DEPTH_BUFFER_BIT);     // Clear Screen And Depth Buffer
//3D Render Code


//2D Render Code
set2DMode(0, width, 0, height);




//DRAW STRING FROM THE - DEFAULT
//SCALE OF 1.5
trueTypeFont.drawString(0, trueTypeFont.getHeight()*10, "I wrote this song about you!\nIsn't that cliche of me, to do?", 1.5f,1.5f
);

//DRAW STRING FROM THE - RIGHT
//DEFAULT SCALE OF 1
trueTypeFont.drawString(width, trueTypeFont.getHeight()*6, "But its nothing for you,\n" +
"the band just needed something more to play.\n" +
"So dont blush or hooray,\n"
, 1f, 1f, TrueTypeFont.ALIGN_RIGHT);

//DRAW STRING FROM THE - CENTER
//HALF SCALE OF 0.5f
trueTypeFont.drawString(width/2, trueTypeFont.getHeight()*3,
"at the possible sound of your name.\n" +
"No I wouldnt go that far.\n" +
"No.", 0.5f, 0.5f, TrueTypeFont.ALIGN_CENTER);

//End 2D Render Code
set3DMode();
}
public static void cleanup() {
trueTypeFont.destroy();
Keyboard.destroy();
Mouse.destroy();
Display.destroy();
System.gc();
try {
Thread.sleep(1000);
} catch (Exception e) {}

System.exit(0);
}
    public static void set2DMode(float x, float width, float y, float height) {
    //GL11.glDisable(GL11.GL_DEPTH_TEST);
    GL11.glMatrixMode(GL11.GL_PROJECTION);                        // Select The Projection Matrix
        GL11.glPushMatrix();                                     // Store The Projection Matrix
        GL11.glLoadIdentity();                                   // Reset The Projection Matrix
        GL11.glOrtho(x, width, y, height, -1, 1);                          // Set Up An Ortho Screen
        GL11.glMatrixMode(GL11.GL_MODELVIEW);                         // Select The Modelview Matrix
        GL11.glPushMatrix();                                     // Store The Modelview Matrix
        GL11.glLoadIdentity();                                   // Reset The Modelview Matrix
    }
    public static void set3DMode() {
    GL11.glMatrixMode(GL11.GL_PROJECTION);                        // Select The Projection Matrix
        GL11.glPopMatrix();                                      // Restore The Old Projection Matrix
        GL11.glMatrixMode(GL11.GL_MODELVIEW);                         // Select The Modelview Matrix
        GL11.glPopMatrix();                                      // Restore The Old Projection Matrix
        //GL11.glEnable(GL11.GL_DEPTH_TEST);
    }
}
class FPSCounter {
private float FPS = 0, fc = 0;

    private long updateFrequency,
                 currentTime,
                 elapsedTime,
                 lastTime;
   
    void init() {
        updateFrequency = Sys.getTimerResolution()>>1;
        currentTime     = 0;
        elapsedTime     = Sys.getTime();
        lastTime        = Sys.getTime();

    }
    boolean update() {
currentTime = Sys.getTime();

      fc++;
     
      if((elapsedTime = currentTime - lastTime) >= updateFrequency){
        FPS         = (fc/elapsedTime)*updateFrequency*2;
        fc         = 0;
        lastTime    = currentTime;
        elapsedTime = 0;
        return true;
      }
      return false;
    }
    float getFPS() {
    return FPS;
    }
}




relavent lines of code:

Font font = new Font("Serif", Font.BOLD, 32);
trueTypeFont = new TrueTypeFont(font, true);
trueTypeFont.drawString();
trueTypeFont.destroy();

//static utility function
TrueTypeFont.isSupported(String fontName);


Please tell me if you find this useful. Or any other improvement ideas.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: MarneusCalgarXP on July 08, 2009, 21:20:43
Nice, I just brought a simple improvement:


public static Font getFont(String fontname, int style, float size) {
Font result = null;
GraphicsEnvironment graphicsenvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
for ( Font font :  graphicsenvironment.getAllFonts() ) {
if (font.getName().equalsIgnoreCase(fontname)) {
result = font.deriveFont(style, size);
break;
}
}
return result;
}


usable this way:


Font font = TrueTypeFont.getFont("Courier New", Font.BOLD, 32);
if (font == null) {
font = new Font("Serif", Font.BOLD, 32);
}
trueTypeFont = new TrueTypeFont(font, true);

Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: bobjob on July 10, 2009, 10:15:43
Quote from: MarneusCalgarXP on July 08, 2009, 21:20:43
Nice, I just brought a simple improvement:

Cool. I put it in as:
boolean isSupported(String fontName)
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: dunno on July 25, 2009, 20:22:27
Nice work. Thanks a lot, very useful. I just looked for a simple implementation of writing text under opengl
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: bobjob on July 26, 2009, 00:31:10
glad someone got some use out of it.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: manji on July 26, 2009, 11:19:47
The example you posted hangs my whole system. Don't know why yet.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: bobjob on July 26, 2009, 21:44:25
if you can setup your own LWJGL display do that, then just use the relavent lines of code for the TrueTypeFont class.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: manji on July 28, 2009, 15:51:20
I tried it again, within my framework, and it works fine. I will use it for now, and I will let you know if any problem occurs. Great work, thnx a lot.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: leanid_chaika on August 12, 2009, 18:25:21
Nice!
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: Evil-Devil on August 20, 2009, 08:00:31
Haven't tried it yet, but its very neat :)

The main thing i am missing while reading the source is the possibility to save/load into any given texture format or at least saving it into any reusable format and just calling the load/draw functions afterall.

Anyway, have to try it myself and see how TT look alike :)
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: dunno on August 20, 2009, 17:13:28
Is there any way to change TrueTypeFont class or smth to represent non-english alphabet (I'm interesting in cyrillic actually)?
Now when I trying to pass cyrillic string to the drawString method, program halts and doesn't respond (with no error messages).
There is only one file in my jre/lib/fonts folder: LucidaSansRegular.ttf and it seems to have cyrillic symbols... Although, I'm not very sure.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: broumbroum on August 20, 2009, 19:05:50
TrueTypeFont does link new textures for a character, doesn^'t it? there's actually another way to render fonts, with bezier curves and that's faster for as long as I've tested it against texture-render.
I may post the sample code for bezier-curves, but you can find it in the package sf3jswing-jigaxtended RenderingScene.class _GLRenderText2D method with renderGlyphs enabled (see the link to the sf3jswing project below) !
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: bobjob on August 20, 2009, 19:44:32
Quote from: Evil-Devil on August 20, 2009, 08:00:31
The main thing i am missing while reading the source is the possibility to save/load into any given texture format or at least saving it into any reusable format and just calling the load/draw functions afterall.

Even though it would be simple enough to implement saving/loading, in/from a texture aswell as a script file for character offsets.

My intention for this class was to just give a simple self containted text implementation that doesnt require other resources (mainly for new LWJGL users).

I remember when i was first started to learn LWJGL and found a texture loader class for devIL somewere, it made a world of difference having a self contained class. It was extremly easy for me to get through alot of opengl lessons after that.

I honestly still find navigating through new packages intimidating  ::) especially when the classes in the package needs other classes in the same package.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: dunno on August 20, 2009, 20:58:19
Oh, I finally get it. For the first time I missed that constructor with char[] additionalChars:
public TrueTypeFont(Font font, boolean antiAlias, char[] additionalChars)
I used another one.
So, one must create such char[] array with specific characters and pass it to that TrueTypeFont constructor and it's work!
Thanks anyway, guys! :)

2broumbroum
>I may post the sample code for bezier-curves
would be great, if you do it, please)

upd:
oh, I found your thread, thanks!
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: Evil-Devil on August 21, 2009, 08:43:39
Quote from: bobjob on August 20, 2009, 19:44:32
Quote from: Evil-Devil on August 20, 2009, 08:00:31
The main thing i am missing while reading the source is the possibility to save/load into any given texture format or at least saving it into any reusable format and just calling the load/draw functions afterall.
My intention for this class was to just give a simple self containted text implementation that doesnt require other resources (mainly for new LWJGL users).

I remember when i was first started to learn LWJGL and found a texture loader class for devIL somewere, it made a world of difference having a self contained class. It was extremly easy for me to get through alot of opengl lessons after that.
Yay, i know what you mean. I remember the time investing how to load DDS files just to came up reading some papers at nvidia/ati with the c++ implementation...

Guess it will be ok to credit the creators and use your class as basecode for a custom implementation.

Still need time to test it anyway....some holidays would be great....gonna ask my boss... ;)

//edit: Found the time to test it. Great stuff. Hopefully java will support opentype fonts someday too =)
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: Ciardhubh on September 08, 2009, 12:49:57
Nice lightweight font class.

I just tried it and came across a few minor issues:
* - artifacts, e.g. bottom right on 'i' in an italic font
* - unclean background, slight discolouration (fixed by g.setColor(new Color(0,0,0,0)); in createSet(...))
* - black border around chars (probably due to AA when drawing on BufferedImage in TrueTypeFont), e.g. red chars on green background
* - font sizes above around 40 produce garbage or nothing at all
* - manipulates global glTexEnvf state

Most of these are in Slick's TrueTypeFont class, too.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: CommanderKeith on December 07, 2009, 01:45:13
This is great, thanks bobjob. I couldn't believe how hard it is to render text in openGL. I was so glad to find this code.

Just a few things that I noticed, for some reason I see some feint black lines in the TTFTest class demo - there's a 5 pixel high feint vertical gray line a small distance under the question mark in the second line of text. There's also the same feint vertical lines under the t and the u's in the first line of text.

Also the 'f', 'j' and 'y' have their lower tails cut off.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: bobjob on December 07, 2009, 03:21:08
Quote from: CommanderKeith on December 07, 2009, 01:45:13
This is great, thanks bobjob. I couldn't believe how hard it is to render text in openGL. I was so glad to find this code.

Just a few things that I noticed, for some reason I see some feint black lines in the TTFTest class demo - there's a 5 pixel high feint vertical gray line a small distance under the question mark in the second line of text. There's also the same feint vertical lines under the t and the u's in the first line of text.

Also the 'f', 'j' and 'y' have their lower tails cut off.
I didnt really do much on this. All I did is remove the need for slick.
if you want to fix faint lines you may want to change the tex paremeters in loadImage() to:

GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: CommanderKeith on December 07, 2009, 04:39:43
Thanks, that worked. But the large-scaled text at the top now looks pixelated/blocky. The other lines of text still look fine.

Cheers,
Keith

EDIT: interestingly, when italics is off the text renders without those feint lines (using the original textute rendering options).
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: bobjob on December 07, 2009, 05:19:58
if you plan to have large scale text in your program, its probably best to select a large font when building the truetypefont. the reason i added the scale is just for the sake animation and effects.

if you still want scaled fonts without artifacts, you may want to try increase the value of
GL11.glAlphaFunc(GL11.GL_GREATER, 0.25f) // or even 0.5f
in initGL() in the test case class.

but first remember to put back the texture perameters to the way they were.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: bobjob on January 12, 2010, 13:59:31
for those interested. I have updated my version of the TrueTypeFont, to save and load, so that if you use a saved version it will look the same on all OS's.

Ill update the post as soon as I port it over to the stand alone version. It Also means you can minipulate the saved texture to fix up any artificats or other specific problems relating to a single character.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: charstar on March 05, 2010, 07:41:14
Seriously rad!  Thanks for publishing your code!  It's nice to be able to just get some text on screen and not have to write yet-another-HUD system.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: zovirl on May 03, 2010, 02:58:19
I'm having trouble getting this to work along side the rest of my code.  Specifically, using TrueTypeFont makes the rest of my polygons disappear.

I have a simple example that draws a triangle to the screen.  If I start using TrueTypeFont, I can get text to draw to the window but the triangle no longer appears. I can't get both the triangle and the text, it is strictly either/or.

I see the same behavior in the test case from the first post on this thread...it draws text but I can't get it to also draw polygons at the same time.

Strangely, simply constructing a TrueTypeObject is enough to hide the triangle. I don't even have to call drawString().  I think I've narrowed it down to the gluBuild2DMipmaps call inside TrueTypeFont.  If I comment that out, then my triangle shows up (though obviously the text doesn't).  I'm at a loss as to why, though.  Anyone have any ideas?

I assume I must be missing something fairly elementary, since I don't see anyone else having trouble.


Here's my code:

import java.awt.Font;

import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;


public class Game {

public static void main(String[] args) throws Exception {
Game game = new Game();
game.Init();
game.Run();
}

private void Init() throws LWJGLException {
Display.setDisplayMode(new DisplayMode(800, 600));
Display.create();

// Switch to ortho view
    GL11.glMatrixMode(GL11.GL_PROJECTION);
    GL11.glLoadIdentity();
    GL11.glOrtho(0.0, Display.getDisplayMode().getWidth(), 0.0, Display.getDisplayMode().getHeight(), -1.0, 1.0);
    GL11.glMatrixMode(GL11.GL_MODELVIEW);
    GL11.glLoadIdentity();
    GL11.glViewport(0, 0, Display.getDisplayMode().getWidth(), Display.getDisplayMode().getHeight());

    // Set up for text
GL11.glEnable(GL11.GL_TEXTURE_2D); // exture Mapping, needed for text
GL11.glEnable(GL11.GL_BLEND); // Enabled blending for text
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
}

private void Run() throws LWJGLException {
Font font = new Font("Serif", Font.BOLD, 30);

/*
* If I remove this next line, the triangle is drawn. 
* With the line, the triangle isn't drawn
*/
TrueTypeFont trueTypeFont = new TrueTypeFont(font, true);

while (true) {
if (Display.isCloseRequested() || Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
break;
}

GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
GL11.glLoadIdentity();

GL11.glBegin(GL11.GL_TRIANGLES);
GL11.glVertex2f(100, 100);
GL11.glVertex2f(500, 100);
GL11.glVertex2f(300, 500);
GL11.glEnd();

// trueTypeFont.drawString(0, 0, "Hello", 1, 1);
Display.update();
}
}
}
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: ryanm on May 03, 2010, 09:16:57
I would imagine that constructing the truetype font enables textures. Since you're not specifying texture coordinates, they'll be (0,0) for the triangle vertices and you won't see anything. Stick a GL11.glDisable( GL11.GL_TEXTURE_2D ) in after the font constructor.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: zovirl on May 04, 2010, 03:01:56
That was it, thanks!
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: Klems on August 29, 2011, 14:00:42
Hi !
Some necroposting here, but anyway, it's for the greater good !

I've made some improvement on the code.

//Klems : in case the current font can't draw a char, we look in the system for the first capable font
private Font getFontFromChar(char ch) {
if (font.canDisplay(ch)) {
return font;
} else {
for (Font currentFont : getFonts()) {
if (currentFont.canDisplay(ch)) {
//Don't forget to derive the new font to match this.font size
return currentFont.deriveFont(font.getSize2D());
}
}
}
//Worst case scenario :
return font;
}

This code may run slowly if there is a lot of custom char. Using a static Set could be useful.
Use this method on "private BufferedImage getFontImage(char ch)". It allow the text renderer to display virtually ANY character by searching in the system fonts if the current font is unable to display this char.
Very useful when you want to display chinese, thai and stuff.


You may also want to change this around line 400 :

/* Klems : only white is needed
newI[newIndex] = b[1];
newI[newIndex+1] = b[2];
newI[newIndex+2] = b[3];
newI[newIndex+3] = b[0]; */
newI[newIndex] = -1;
newI[newIndex+1] = -1;
newI[newIndex+2] = -1;
newI[newIndex+3] = b[0];

Only white is needed : let OpenGL handle the color tainting. The old code could result in visual artifact.
Title: Re: Complete TrueTypeFont class that only requires LWJGL
Post by: mick1114 on April 11, 2012, 19:47:35
Sorry that i reply on this old topic, but I have a question depending on your code and i don't want to start a new one.
At first i want to thank you for this nice work, but can it be that the getWidht() method doesn't work correctly, or is it my fault?

I have tried to render text in d middle of the screen with this line
this.font.drawString(Display.getWidth()/2 - font.getWidth("sampleText")/2, 20, "sampleText"), 1,1);

but it is not in the center.
It seems getWitdh return wrong values (I think they are too big). Contrary to this the original ttf code from slick works:
font.drawString(Display.getWidth()/2 - font.getWidth("sampleText"), Display.getHeight()/2, "sampleText");

Your and the original getWidth method are the same, so i don't have any clue what is wrong.

Can anybody help me?
Thank you in advance.