I think, that bitmap-based rendering is more appropriate for your task than converting strings to polygons (although converting fonts on a per-character basis does not work for some languages, like arabic), but I will nevertheless post, how it can be done. You might give it a try.
We start with a font and a string.
String s = “Hello”;
Font f = new Font(“Helvetica”, Font.PLAIN, 12);
First, we need a FontRenderContext. As we are not rendering to any device at all, we can just create a new one.
FontRenderContext frc = new FontRenderContext(null, false, false);
The first parameter is an optional transformation, the second and third parameter are antialiasing and fractional metrics. I could not see any effect of the last two. They probably only affect rasterization, which we won’t do.
Next we will instruct the font to layout our text and create a geometrical shape representing it:
GlyphVector g = font.layoutGlyphVector(frc, string.toCharArrray(), 0, string.length());
Shape shape = g.getOutline();
The resulting shape mainly consist of arcs. To render the shape as polygon, we have to approximate the arcs by straight lines and create a triangulation of them.
OpenGL provides a tesselator to calculate a triangulation. The tesselator takes the line segments and reports the triangles via callbacks. To use it, we have to register a callbackAdapter for some callbacks first.
GLUTessCallbackAdapter adapter = new MyTessAdapter();
GLUTEsselator tesselator = glu.gluNewTess();
glu.gluTessCallback(tesselator, GLU.GLU_TESS_BEGIN, adapter);
glu.gluTessCallback(tesselator, GLU.GLU_TESS_VERTEX, adapter);
glu.gluTessCallback(tesselator, GLU.GLU_TESS_END, adapter);
We tell the tesselator, that we will start producing points right away.
glu.gluBeginPolygon(tesselator);
Then we will iterate over the shape representing our text, line by line, and forward the points to the tesselator.
PathIterator i = shape.getPathIterator(null, 0.05);
The first parameter is again an optional transformation, the second parameter specifies, how far lines between the points produced by the iterator may deviate from the actual arcs of the shape. Reducing it increases the rendering quality but produces more lines and takes more time.
Iterating over a path is slightly different than iterating over a collection. We need a temporary array of coordinates.
double[] segment = new double[6];
while(!i.isDone()) {
switch (i.currentSegment(segment)) {
i.next();
The PathIterator puts the coordinates of the next point to the array and tells us, which type of point we have. There are three possibilitys:
MOVETO – The point starts a new line strip
LINETO – The point is the next point on the current line strip
CLOSE – Close the line strip to become a loop. We got no point this time.
case PathIterator.SEG_MOVE_TO {
double[] coords = new double[3];
coords[0] = segment[0];
coords[1] = segment[1];
// z value stays 0
glu.gluTessBeginContour(tesselator);
glu.gluTessVertex(tesselator, coords, coords);
break;
}
The tesselator is told to start a new line strip by gluTessBeginCountour. The gluTessVertex call takes two double arrays. The first one contains the coordinates of the point. The second one is the reference that the tesselator will report to us later as part of the triangulation. We therefore need to store the coordinates in a new array for every single point.
case PathIterator.SEG_LINE_TO {
double[] coords = new double[3];
coords[0] = segment[0];
coords[1] = segment[1];
glu.gluTessVertex(tesselator, coords, coords);
break;
}
case PathIterator.SEG_CLOSE {
glu.gluTessEndContour(tesselator);
break;
}
}
}
The tesselator expects all lines to be loops. Fortunately they are in all fonts I have seen so far. Now we are done parsing and tell the tesselator to start working.
glu.gluTessEndPolygon(tesselator);
Callbacks will come in to our adapter (sorry, stupid use of the word adapter):
class MyCallbackAdapter implements GLUTesselatorCallbackAdapter {
public void begin(int primitiveType) {
gl.glBegin(primitiveType);
}
public void vertex(Object data) {
gl.glVertex3dv((double[] data));
}
public void end() {
gl.glEnd();
}
}
MyCallbackAdapter just renders the triangles immidiately. It might sometimes be usefull, to store them in a display list instead.
After this process, we should finally see some text. It will probably look poor, because it is not properly antialiased. Basically, we want this:
gl.glEnable(GL.GL_POLYGON_SMOOTH);
To do the antialiasing properly, we have to use the correct blend function and to render all objects front to back without using the z buffer.
gl.glEnable(GL.GL_BLEND);
gl.glBlendFunc(GL.GL_SRC_ALPHA_SATURATE, GL.GL_ONE);
I have not really understood what this does, but it works. If you have a background other than black, you need to change the clear color to black and draw a quad of the desired color covering the complete view as last element.
As I said, it is a rather complex procedure. But you will get a true polygonal text in an arbitrary font, which can be scaled, rotated or textured. It is fast, if a display list can be reused, but slow ohterwise. Not really slow, off course, but compared to bitmap fonts.
The antialiasing is problematic, if you want to use the z-Buffer. The quality is decent, but not as good as fonts rendered to a buffered image by java, because it uses special antialiasing for font rendering.