glutStrokeString width of the font somewhat newbie

Hi,
I’m using JOGL and very pleased with it… I have something rather unique that I hope to post here soon. Anyway while I did get most things working (more or less) I still have a couple of problems, one of them is the size of the text I draw using glutStrokeString.
Bitmaped fonts didn’t really fit my need (don’t scale well) and I don’t need anything too fancy, but the glutStrokeString font ends up looking very thin and barely readable. I use glScalef() to make the font larger and while this works on all axises it dosen’t effect the thikness of the characters which causes the font to look ridiculously large and thin.

I’m not set on using glutStrokeString() but I need something I can scale easily for my purposes, I thought about using AWT to draw the text onto a surface and stretch it but I’m not sure whether this will be a good idea (and I don’t want to waste texture space since its in short supply for the things I’m trying to achive).

Thanks in advance :wink:

Hi,

I had the same problem today and found two different solutions.

The first one is to set the line thickness with gl.glLineWidth() to some value greater one and then render the fonts with glutStrokeString().

The solution is attractive, because it is easy to implement and because it needs no additional memory and renders fast.

However, there are two problems:

  • The line width must be set explicitly and is not automatically derived from the scaling of the current transformation.
  • The connections of different line strips in the glyphs are not rendered correctly. The text looks damaged, especially when it is really large.

The second solution creates an actual polygon from the string to render using a java font and bypasses both problems of the outline fonts. It is rather complex, but I can post it, if you are still interested.

I’m definetly interested in decent Java2D font rendering…
I need it for the JogLF implementation discussed here http://www.JavaGaming.org/cgi-bin/JGNetForums/YaBB.cgi?board=jogl;action=display;num=1068893837.
So if you have something I can use it might be very usefull if I choose
to implement the whole thing.
Right now however I am rather busy and don’t see all that much interest in JogLF so I don’t know whether I’ll implement it soon but
I probably will do something when I get some time.

I’ve written a couple of classes that converts the characters in a Font into (j)ogl bitmaps and assigns them a display list on demand.

It works fairly well, but if you expect to use more than 2-3 fonts, it’s probably not the best option.

Click for screenshot.

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.