/*
* Copyright 2014 BECKERS romain.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
todo:
fragment shader -> replace white color with font color
vertex shader -> render a texture
ortho view matrix ?
pass width and height to the vertex shader ?
*/
package romcgb.glfontrenderer;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.nio.IntBuffer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.BufferUtils;
class Glyph {
public final int textureId;
public final int advance;
public final int height;
public final int width;
Glyph (int textureId, int advance, int width, int height) {
this.textureId = textureId;
this.advance = advance;
this.width = width;
this.height = height;
}
/*
@Override
protected void finalize() throws Throwable {
GL11.glDeleteTextures(this.textureId);
}
*/
}
public class FontRenderer {
private Color currentColor;
/*
private static final String fragmentShaderSource = "";
private static final String vertexShaderSource = "";
private static int programId;
*/
private static boolean initialized = false;
private boolean disposed;
private final Glyph[] glyphs;
private static final int MAX_GLYPH = 127;
FontRenderer (Font font) {
this.currentColor = java.awt.Color.WHITE;
if (!FontRenderer.initialized) {
FontRenderer.initialize();
}
this.disposed = false;
this.glyphs = new Glyph[MAX_GLYPH];
buildGlyphs(font);
}
private static void initialize () {
/*
int vertexShaderId = loadShader(
GL20.GL_VERTEX_SHADER,
FontRenderer.vertexShaderSource);
int fragmentShaderId = loadShader(
GL20.GL_FRAGMENT_SHADER,
FontRenderer.fragmentShaderSource);
FontRenderer.programId = loadProgram(vertexShaderId, fragmentShaderId);
GL20.glDeleteShader(vertexShaderId);
GL20.glDeleteShader(fragmentShaderId);
*/
FontRenderer.initialized = true;
}
/*
private static int loadShader (int type, String source) {
int shaderId = GL20.glCreateShader(type);
if (shaderId == 0) {
return 0;
}
GL20.glShaderSource(shaderId, source);
GL20.glCompileShader(shaderId);
if (GL20.glGetShaderi(shaderId, GL20.GL_COMPILE_STATUS) == 0) {
GL20.glDeleteShader(shaderId);
return 0;
}
return shaderId;
}
private static int loadProgram (int vertexShaderId, int fragmentShaderId) {
int programId = GL20.glCreateProgram();
GL20.glAttachShader(programId, vertexShaderId);
GL20.glAttachShader(programId, fragmentShaderId);
GL20.glLinkProgram(programId);
if (GL20.glGetProgrami(programId, GL20.GL_LINK_STATUS)==0) {
GL20.glDeleteProgram(programId);
return 0;
}
return programId;
}
*/
private void buildGlyphs (Font font) {
int textureId;
int advance;
int width;
int height;
FontRenderContext frc = new FontRenderContext(font.getTransform(),false,true);
TextLayout tl;
BufferedImage image;
Graphics2D g;
for (int i = 32; i<MAX_GLYPH; ++i) {
tl = new TextLayout(String.valueOf((char)i), font, frc);
width = (int)Math.ceil(tl.getAdvance());
advance = (int)tl.getAdvance();
height = (int)Math.ceil(tl.getAscent()+tl.getDescent());
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
g = image.createGraphics();
tl.draw(g, 0, tl.getAscent());
g.dispose();
textureId = bufferedImageToTexture(image);
this.glyphs[i] = new Glyph(textureId, advance, width, height);
}
}
private static int bufferedImageToTexture (BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
int[] texels = image.getRGB(0, 0, width, height, null, 0, width);
IntBuffer buffer = BufferUtils.createIntBuffer(texels.length);
buffer.put(texels);
buffer.flip();
int textureID = GL11.glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureID);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D,
0,
GL11.GL_RGBA,
width,
height,
0,
GL12.GL_BGRA,
GL12.GL_UNSIGNED_INT_8_8_8_8,
buffer);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
GL11.GL_TEXTURE_WRAP_S,
GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
GL11.GL_TEXTURE_WRAP_T,
GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
GL11.GL_TEXTURE_MIN_FILTER,
GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D,
GL11.GL_TEXTURE_MAG_FILTER,
GL11.GL_LINEAR);
return textureID;
}
public void debug () {
for (Glyph glyph : glyphs) {
if (glyph==null)
continue;
System.out.println(glyph.textureId+" "+glyph.advance);
}
}
public void render (int x, int y, String caption) {
for (char ch : caption.toCharArray()) {
Glyph glyph = this.glyphs[(int)ch];
renderGlyph(x, y, glyph);
x = x + glyph.advance;
}
}
public void render (int x, int y, int number) {
int remainder = 1;
int num;
Glyph glyph;
while (remainder != 0) {
num = number % 10;
remainder = num / 10;
glyph = this.glyphs[48+num];
renderGlyph(x, y, glyph);
x = x + glyph.advance;
}
}
public void dispose() {
if (this.disposed)
return;
for (Glyph glyph : this.glyphs) {
if (glyph==null)
continue;
GL11.glDeleteTextures(glyph.textureId);
}
this.disposed = true;
}
public boolean isDisposed () {
return this.disposed;
}
public void getPixelWidth (String caption) {
// TODO: not implemented yet
}
public void setColor (Color color) {
this.currentColor = color;
}
public Color getColor () {
return this.currentColor;
}
//OpenGL 1.1
private void renderGlyph (int x, int y, Glyph glyph) {
int right = x+glyph.width;
int bottom = y+glyph.height;
GL11.glBindTexture(GL11.GL_TEXTURE_2D, glyph.textureId);
GL11.glBegin(GL11.GL_QUADS);
GL11.glTexCoord2f(0.0f, 1.0f);
GL11.glVertex3i(x, y, 0);
GL11.glTexCoord2f(1.0f, 1.0f);
GL11.glVertex3i(right, y, 0);
GL11.glTexCoord2f(1.0f, 0.0f);
GL11.glVertex3i(right, bottom, 0);
GL11.glTexCoord2f(0.0f, 0.0f);
GL11.glVertex3i(x, bottom, 0);
GL11.glEnd();
}
/*
@Override
protected void finalize() throws Throwable {
if (!this.disposed) {
dispose();
}
super.finalize();
}
*/
}