LWJGL
February 07, 2012, 06:17:18 *
Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length
News: LWJGL 2.8.3 released!
 
   Home   Help Search Login Register  



Pages: [1]
  Print  
Author Topic: Render True Type Font through Bezier curves  (Read 4116 times)
broumbroum
Prolific Timewaster
****
Posts: 284



WWW
« on: August 20, 2009, 13:28:16 »

Something requested as an alternative to TrueTypeFont class was a way to render cyrillic characters.
The GLText class can (hopefully) do this by using bezier-curves.
It's basically using AWTGLCanvas or any other component where we could get a Font :
Code:
final Font font = gld.getFont();
        final GlyphVector gv = gld.getFont().createGlyphVector(new FontRenderContext(font.getTransform(), true, false), text);
        
Then comes the tricky bezier computation that will be stored in a GL list using a Shape PathIterator :
Code:
GLList gllist = new GLGlyph(glyphChar, font.getSize()) {

                @Override
                public Runnable getList() {
                    return new Runnable() {

                        public void run() {
                            Shape glyph = gv.getGlyphOutline(indexChar, -(float) glyphBounds.getX(), -(float) glyphBounds.getY());
                            PathIterator pi = glyph.getPathIterator(null);
                            Point2D.Float current = new Point2D.Float();
                            while (!pi.isDone()) {
                                float[] coords = new float[6];
                                int path = pi.currentSegment(coords);
                                switch (path) {
                                    case PathIterator.SEG_MOVETO:
                                        /*System.err.println("PaIt begin move"); */
                                        GL11.glBegin(GL11.GL_LINE_LOOP);
                                        GL11.glVertex2f(coords[0], coords[1]);
                                        current.x = coords[0];
                                        current.y = coords[1];
                                        break;
                                    case PathIterator.SEG_CLOSE:
                                        /*System.err.println("PaIt close");*/
                                        GL11.glEnd();
                                        break;
                                    case PathIterator.SEG_LINETO:
                                        /*System.err.println("PaIt line");*/
                                        GL11.glVertex2f(coords[0], coords[1]);
                                        current.x = coords[0];
                                        current.y = coords[1];
                                        break;
                                    case PathIterator.SEG_CUBICTO:
                                        /*System.err.println("PaIt cubic");*/
                                        for (Point2D.Float p : _computeBezierCurve(new Point2D.Float[]{current, new Point2D.Float(coords[0], coords[1]), new Point.Float(coords[2], coords[3]), new Point.Float(coords[4], coords[5])}, font.getSize())) {
                                            GL11.glVertex2f(p.x, p.y);
                                        }
                                        current.x = coords[4];
                                        current.y = coords[5];
                                        break;
                                    case PathIterator.SEG_QUADTO:
                                        /*System.err.println("PaIt quad");*/
                                        for (Point2D.Float p : _computeBezierCurve(new Point2D.Float[]{current, new Point2D.Float(coords[0], coords[1]), new Point.Float(coords[2], coords[3])}, font.getSize())) {
                                            GL11.glVertex2f(p.x, p.y);
                                        }
                                        current.x = coords[2];
                                        current.y = coords[3];
                                        break;
                                    default:
                                        break;
                                }
                                pi.next();
                            }
                        }
                    };
                }
            };
This way seems faster on rendering and is totally dependent of the current Font used when the method is invoked. That's if Font.size changes, then the renderer will increase the size too; as well as the Font.name changes then everything appear in the new font character (that case hasn't been tested yet).
 Cheesy

EDIT : GLList added, and LICENSE + NOTICE file (Apache 2 License + BSD LWJGL notice)
Logged

Sf3JSwing project (Apache License) -  http://sf.net/projects/sf3jswing
Sf3jswing Project Forum - http://apps.sourceforge.net/phpbb/sf3jswing/ (recent updates)
Mercurial Repository - http://sf3jswing.hg.sourceforge.net/hgweb/sf3jswing/sf3jswing/summary
broumbroum
Prolific Timewaster
****
Posts: 284



WWW
« Reply #1 on: December 26, 2009, 15:45:05 »

It now uses GLUTesselator from 2.2.1 LWJGL to improve speed :
Code:
           final char glyphChar = text.charAt(i);
            final Rectangle2D glyphBounds = gv.getGlyphOutline(i).getBounds2D();
            final int indexChar = i;
            GL11.glPushMatrix();
            GL11.glTranslated(glyphBounds.getX(), glyphBounds.getY(), 0);
            GLList gllist = new GLGlyph(glyphChar, font.getSize()) {

                @Override
                public Runnable getList() {
                    return new Runnable() {

                        public void run() {
                            Shape glyph = gv.getGlyphOutline(indexChar, -(float) glyphBounds.getX(), -(float) glyphBounds.getY());
                            PathIterator pi = glyph.getPathIterator(null);
                            GLUtessellator tess = GLUtessellatorImpl.gluNewTess();
                            GLUtessellatorCallbackAdapter tessCb = new GLUtessellatorCallbackAdapter() {

                                @Override
                                public void begin(int type) {
                                    GL11.glBegin(type);
                                }

                                @Override
                                public void vertex(Object vertexData) {
                                    float[] vert = (float[]) vertexData;
                                    GL11.glVertex2f(vert[0], vert[1]);
                                }

                                public void combine(double[] coords, Object[] data, float[] weight, Object[] outData) {
                                    for (int i = 0; i < outData.length; i++) {
                                        float[] combined = new float[6];
                                        combined[0] = (float) coords[0];
                                        combined[1] = (float) coords[1];
                                        /*combined[2] = (float) coords[2];
                                        for (int j = 3; j < 6; j++) {
                                        for(int d = 0; d < data.length; d++)
                                        combined[j] = weight[d] * data[d][j];
                                        }*/
                                        outData[i] = combined;
                                    }
                                }

                                @Override
                                public void end() {
                                    GL11.glEnd();
                                }
                            };
                            tess.gluTessCallback(GLU.GLU_TESS_BEGIN, tessCb);
                            tess.gluTessCallback(GLU.GLU_TESS_VERTEX, tessCb);
                            tess.gluTessCallback(GLU.GLU_TESS_COMBINE, tessCb);
                            tess.gluTessCallback(GLU.GLU_TESS_END, tessCb);
                            Point2D.Float current = new Point2D.Float();
                            tess.gluTessBeginPolygon(null);
                            while (!pi.isDone()) {
                                float[] coords = new float[6];
                                int path = pi.currentSegment(coords);
                                switch (pi.getWindingRule()) {
                                    case PathIterator.WIND_EVEN_ODD:
                                        tess.gluTessProperty(GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_ODD);
                                        break;
                                    case PathIterator.WIND_NON_ZERO:
                                        tess.gluTessProperty(GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_NONZERO);
                                        break;
                                    default:
                                        if (JXAenvUtils._debug) {
                                            System.err.println(JXAenvUtils._JXAEnvOutput("no winding rule", JXAenvUtils.APP_WARNING));
                                        }
                                        break;
                                }

                                switch (path) {
                                    case PathIterator.SEG_MOVETO:
                                        /*System.err.println("PaIt begin move"); */
                                        /*GL11.glBegin(GL11.GL_LINE_LOOP);*/
                                        tess.gluTessBeginContour();
                                        /*GL11.glVertex2f(coords[0], coords[1]);*/
                                        tess.gluTessVertex(new double[]{coords[0], coords[1], 0.}, 0, new float[]{coords[0], coords[1], 0f});
                                        current.x = coords[0];
                                        current.y = coords[1];
                                        break;
                                    case PathIterator.SEG_CLOSE:
                                        /*System.err.println("PaIt close");*/
                                        /*GL11.glEnd();*/
                                        tess.gluTessEndContour();
                                        break;
                                    case PathIterator.SEG_LINETO:
                                        /*System.err.println("PaIt line");*/
                                        /*GL11.glVertex2f(coords[0], coords[1]);*/
                                        tess.gluTessVertex(new double[]{coords[0], coords[1], 0.}, 0, new float[]{coords[0], coords[1], 0f});
                                        current.x = coords[0];
                                        current.y = coords[1];
                                        break;
                                    case PathIterator.SEG_CUBICTO:
                                        /*System.err.println("PaIt cubic");*/
                                        for (Point2D.Float p : GLGeom._computeBezierCurve(new Point2D.Float[]{current, new Point2D.Float(coords[0], coords[1]), new Point.Float(coords[2], coords[3]), new Point.Float(coords[4], coords[5])}, font.getSize())) {
                                            /*GL11.glVertex2f(p.x, p.y);*/
                                            tess.gluTessVertex(new double[]{p.x, p.y, 0.}, 0, new float[]{p.x, p.y, 0f});
                                        }
                                        current.x = coords[4];
                                        current.y = coords[5];
                                        break;
                                    case PathIterator.SEG_QUADTO:
                                        /*System.err.println("PaIt quad");*/
                                        for (Point2D.Float p : GLGeom._computeBezierCurve(new Point2D.Float[]{current, new Point2D.Float(coords[0], coords[1]), new Point.Float(coords[2], coords[3])}, font.getSize())) {
                                            /*GL11.glVertex2f(p.x, p.y);*/
                                            tess.gluTessVertex(new double[]{p.x, p.y, 0.}, 0, new float[]{p.x, p.y, 0f});
                                        }
                                        current.x = coords[2];
                                        current.y = coords[3];
                                        break;
                                    default:
                                        break;
                                }
                                pi.next();
                            }
                            tess.gluTessEndPolygon();
                            tess.gluDeleteTess();
                        }
                    };
                }
            };
Logged

Sf3JSwing project (Apache License) -  http://sf.net/projects/sf3jswing
Sf3jswing Project Forum - http://apps.sourceforge.net/phpbb/sf3jswing/ (recent updates)
Mercurial Repository - http://sf3jswing.hg.sourceforge.net/hgweb/sf3jswing/sf3jswing/summary
pd_
Newbie
*
Posts: 6


« Reply #2 on: February 23, 2010, 04:50:04 »

Thanks for this, it works pretty well! Smiley

One question, though: how do I get it to render smoothly?
I have GL_LINE_SMOOTH and blending enabled (and I tried it with lines), but it seems that tesselated polygons are not affected by that, so texts rendered in smaller sizes are practially unreadable.
Logged
broumbroum
Prolific Timewaster
****
Posts: 284



WWW
« Reply #3 on: February 24, 2010, 12:44:34 »

...
 so texts rendered in smaller sizes are practially unreadable.

the GLGeom compute bezier curve has its last parameter as a "fine tuning". It's set on the font size, that is if the font is small, near 1, the bezier curves would  be computed as with 1 control point (see bezier curves on wikipedia) and therefore it may be unsufficient for high-definition overlays.
Code:
for (Point2D.Float p :
GLGeom._computeBezierCurve(new Point2D.Float[]{current, new Point2D.Float(coords[0], coords[1]), new Point.Float(coords[2], coords[3])},
font.getSize())) {} // in cubic and quadratic cases
I suggest you to get the last parameter throsholded to a value of ~3-5 control points when font is smaller than 5. adjust it to your requirements/tests ! Cool
Logged

Sf3JSwing project (Apache License) -  http://sf.net/projects/sf3jswing
Sf3jswing Project Forum - http://apps.sourceforge.net/phpbb/sf3jswing/ (recent updates)
Mercurial Repository - http://sf3jswing.hg.sourceforge.net/hgweb/sf3jswing/sf3jswing/summary
pd_
Newbie
*
Posts: 6


« Reply #4 on: February 25, 2010, 00:23:07 »

Sorry, I guess I put it a little misunderstandably.
I didn't really refer to the smoothness of the bezier curve (thanks for that hint though, it might be a nice toy!) but to the smoothness of the rendering as in "anti-aliasing".

I believe that due to aliasing, at some places, some pixels go missing (e.g. on the "e") and some are off (e.g. on the "C"). See the attachment for an example (that's "Lucida Console", size 11 and bold, regular has similar problems).
The question is how can I enable anti-aliasing for tesselated polygons? GL_BLEND is enabled, but that doesn't seem to do it.

Thanks for your help!
Logged
broumbroum
Prolific Timewaster
****
Posts: 284



WWW
« Reply #5 on: November 25, 2010, 17:32:07 »

updated GLText.zip in the first post (it was no longer available, since the forum crashed)
Wink
Logged

Sf3JSwing project (Apache License) -  http://sf.net/projects/sf3jswing
Sf3jswing Project Forum - http://apps.sourceforge.net/phpbb/sf3jswing/ (recent updates)
Mercurial Repository - http://sf3jswing.hg.sourceforge.net/hgweb/sf3jswing/sf3jswing/summary
kappa
Administrator
Nerdus Imperius
*****
Posts: 889



« Reply #6 on: November 26, 2010, 01:35:18 »

ah nice, thx for that, just curious what the licence for the code?
Logged
broumbroum
Prolific Timewaster
****
Posts: 284



WWW
« Reply #7 on: November 26, 2010, 05:49:37 »

Apache
Logged

Sf3JSwing project (Apache License) -  http://sf.net/projects/sf3jswing
Sf3jswing Project Forum - http://apps.sourceforge.net/phpbb/sf3jswing/ (recent updates)
Mercurial Repository - http://sf3jswing.hg.sourceforge.net/hgweb/sf3jswing/sf3jswing/summary
broumbroum
Prolific Timewaster
****
Posts: 284



WWW
« Reply #8 on: January 12, 2012, 02:22:09 »

It's a bit slower than bitmap rendering, though PBO can solve part of this issue (work in progress for HUD), but a lot easier to change font size and face, to change the default Font.
GLText.java (full) http://sf3jswing.hg.sourceforge.net/hgweb/sf3jswing/sf3jswing/file/4bdc82814598/sf3jswing-jigaxtended/src/all/net/sf/jiga/xtended/impl/game/gl/GLText.java
GLGeom.java (tess and renderShape) http://sf3jswing.hg.sourceforge.net/hgweb/sf3jswing/sf3jswing/file/4bdc82814598/sf3jswing-jigaxtended/src/all/net/sf/jiga/xtended/impl/game/gl/GLGeom.java
Logged

Sf3JSwing project (Apache License) -  http://sf.net/projects/sf3jswing
Sf3jswing Project Forum - http://apps.sourceforge.net/phpbb/sf3jswing/ (recent updates)
Mercurial Repository - http://sf3jswing.hg.sourceforge.net/hgweb/sf3jswing/sf3jswing/summary
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.16 | SMF © 2011, Simple Machines
SMFAds for Free Forums
Valid XHTML 1.0! Valid CSS!