You didnt find anything about openGL? This sounds highly dubious. There are more tutorials about openGL then you have cells in your body.
The premise of a font renderer is simple. You have a texture with the glyphs, and you have to map those glyphs on quads.
I would suggest using some kind of a map, for example a hashmap, to map characters and texture cutouts.
Then, iterate through the string you want to render, for each letter you fetch the cutout from the map and save it in a vbo.
Here is an example code. I wrote this code in 10 minutes and I didnt test it, but it should be enough to give you a general idea:
public class FontRenderer {
// Both a float and an int use 4 bytes.
private static final int FLOAT_BYTE_SIZE = 4;
private static final int INT_BYTE_SIZE = 4;
// Each quad is one glyph and needs 4 vertices.
private static final int VERTICES_PER_QUAD = 4;
// Each quad is split up into 2 triangles, thus we need 6 indices.
private static final int INDICES_PER_QUAD = 6;
private static final int VERTEX_COUNT = 4;
private static final int TEXCOORD_COUNT = 4;
// These correspond to the cutouts for the glyphs in our map.
private static final int CUTOUT_X = 0;
private static final int CUTOUT_Y = 1;
private static final int CUTOUT_WIDTH = 2;
private static final int CUTOUT_HEIGHT = 3;
// We save our glyphs in this map. It maps character to a float array with 4 elements = x, y, width and height.
private HashMap<Character, float[]> cutouts = new HashMap<>();
private int vboID = 0;
private int iboID = 0;
private int size = 0;
private int stride = (VERTEX_COUNT + TEXCOORD_COUNT) * FLOAT_BYTE_SIZE;
public FontRenderer() {
vboID = GL15.glGenBuffers();
iboID = GL15.glGenBuffers();
}
public void addGlyph(Character character, float x, float y, float width, float height) {
cutouts.put(character, new float[] {x, y, width, height});
}
public void makeVBO(String text, float x, float y, float z) {
/*
* If you want to draw a quad with 2 triangles you need 6 indices.
* These are 0, 1, 2 for the first triangle, and 2, 3, 0 for the second one.
*/
size = text.length() * INDICES_PER_QUAD;
/*
* For each vertex we put:
* x, y, z, w = 1
* u, v, 0, 1
* Each letter needs 4 vertices.
*/
float[] vertices = new float[text.length() * (VERTEX_COUNT + TEXCOORD_COUNT) * VERTICES_PER_QUAD];
int[] indices = new int[size];
int offsetVertices = 0;
int offsetIndices = 0;
for (int letterID = 0; letterID < text.length(); letterID++) {
Character letter = text.charAt(letterID);
// This array is always 4 elements in size: x, y, width and height.
float[] cutout = cutouts.get(letter);
// Upper left corner
vertices[offsetVertices++] = x;
vertices[offsetVertices++] = y;
vertices[offsetVertices++] = z;
vertices[offsetVertices++] = 1;
vertices[offsetVertices++] = cutout[CUTOUT_X];
vertices[offsetVertices++] = cutout[CUTOUT_Y];
vertices[offsetVertices++] = 0;
vertices[offsetVertices++] = 1;
// Lower left corner
vertices[offsetVertices++] = x;
vertices[offsetVertices++] = y + cutout[CUTOUT_HEIGHT];
vertices[offsetVertices++] = z;
vertices[offsetVertices++] = 1;
vertices[offsetVertices++] = cutout[CUTOUT_X];
vertices[offsetVertices++] = cutout[CUTOUT_Y] + cutout[CUTOUT_HEIGHT];
vertices[offsetVertices++] = 0;
vertices[offsetVertices++] = 1;
// Lower right corner
vertices[offsetVertices++] = x + cutout[CUTOUT_WIDTH];
vertices[offsetVertices++] = y + cutout[CUTOUT_HEIGHT];
vertices[offsetVertices++] = z;
vertices[offsetVertices++] = 1;
vertices[offsetVertices++] = cutout[CUTOUT_X] + cutout[CUTOUT_WIDTH];
vertices[offsetVertices++] = cutout[CUTOUT_Y] + cutout[CUTOUT_HEIGHT];
vertices[offsetVertices++] = 0;
vertices[offsetVertices++] = 1;
// Upper right corner
vertices[offsetVertices++] = x + cutout[CUTOUT_WIDTH];
vertices[offsetVertices++] = y;
vertices[offsetVertices++] = z;
vertices[offsetVertices++] = 1;
vertices[offsetVertices++] = cutout[CUTOUT_X] + cutout[CUTOUT_WIDTH];
vertices[offsetVertices++] = cutout[CUTOUT_Y];
vertices[offsetVertices++] = 0;
vertices[offsetVertices++] = 1;
// We make sure that the next letter will be drawn to the right of this letter.
x += cutout[CUTOUT_WIDTH];
// Indices
indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 0;
indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 1;
indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 2;
indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 2;
indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 3;
indices[offsetIndices++] = letterID * VERTICES_PER_QUAD + 0;
}
assert offsetVertices == vertices.length;
bind();
vertexData(vertices);
indexData(indices);
}
public void bind() {
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboID);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, iboID);
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GL11.glVertexPointer(VERTEX_COUNT, GL11.GL_FLOAT, stride, 0);
GL11.glTexCoordPointer(TEXCOORD_COUNT, GL11.GL_FLOAT, stride, VERTEX_COUNT * FLOAT_BYTE_SIZE);
}
public void dispose() {
GL15.glDeleteBuffers(vboID);
GL15.glDeleteBuffers(iboID);
}
public void vertexData(float[] data) {
FloatBuffer buffer = ByteBuffer.allocateDirect(data.length * FLOAT_BYTE_SIZE)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
buffer.put(data);
buffer.flip();
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
}
public void indexData(int[] data) {
IntBuffer buffer = ByteBuffer.allocateDirect(data.length * INT_BYTE_SIZE)
.order(ByteOrder.nativeOrder()).asIntBuffer();
buffer.put(data);
buffer.flip();
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
}
public void render() {
bind();
GL11.glDrawElements(GL11.GL_TRIANGLES, size, GL11.GL_UNSIGNED_INT, 0);
}
}
You need some kind of implementation for textures too.
This is not a very efficient implementation. Many things can be done much better then here, but it should be enough to understand the concept behind it.