diff --git a/core/PConstants.java b/core/PConstants.java index 554129275..84dbf4965 100644 --- a/core/PConstants.java +++ b/core/PConstants.java @@ -135,7 +135,6 @@ public interface PConstants { public final static int SOFT_LIGHT = 1 << 10; - // colour component bitmasks public static final int ALPHA_MASK = 0xff000000; @@ -202,8 +201,24 @@ public interface PConstants { // text placement modes - //static final int SCREEN = 4; // var SCREEN exists elsewhere - static final int MODEL = 3; + //static final int SCREEN = 1 << 7; // exists elsewhere + /** + * textMode(MODEL) is the default, meaning that characters + * will be drawn in model space. + */ + static final int MODEL = 3; + + /** + * textMode(SHAPE) draws text using the the glyph outlines of + * individual characters rather than as textures. If the outlines are + * not available, then textMode(SHAPE) will be ignored and textMode(MODEL) + * will be used instead. For this reason, be sure to call textMode() + * after calling textFont(). + *

+ * Currently, textMode(SHAPE) is only supported by OPENGL mode. + * It also requires Java 1.3 or higher. + */ + static final int SHAPE = 4; // text alignment modes diff --git a/core/PGraphics.java b/core/PGraphics.java index 9847cdb3e..0993d824e 100644 --- a/core/PGraphics.java +++ b/core/PGraphics.java @@ -1726,9 +1726,12 @@ public class PGraphics extends PImage implements PConstants { * textMode(SCREEN), because it uses the pixels directly from the font. */ public void textMode(int mode) { + if ((mode == LEFT) || (mode == CENTER) || (mode == RIGHT)) { + throw new RuntimeException("textMode() is now textAlign() in Processing beta"); + } if ((mode != SCREEN) && (mode != MODEL)) { throw new RuntimeException("Only textMode(SCREEN) or textMode(MODEL) " + - "can be used. Maybe you meant textAlign()?"); + "are available with this renderer."); } if (textFont != null) { diff --git a/core/PGraphics3.java b/core/PGraphics3.java index 3bffa4711..066ab9e35 100644 --- a/core/PGraphics3.java +++ b/core/PGraphics3.java @@ -1482,9 +1482,12 @@ public class PGraphics3 extends PGraphics { /** - * triangulate the current polygon. - * simple ear clipping polygon triangulation adapted - * from code by john w. ratcliff (jratcliff at verant.com) + * Triangulate the current polygon. + *

+ * Simple ear clipping polygon triangulation adapted from code by + * John W. Ratcliff (jratcliff at verant.com). Presumably + * this + * bit of code from the web. */ private void triangulate_polygon() { // first we check if the polygon goes clockwise or counterclockwise diff --git a/opengl/PGraphicsGL.java b/opengl/PGraphicsGL.java index 8a462927e..e9265d8a3 100644 --- a/opengl/PGraphicsGL.java +++ b/opengl/PGraphicsGL.java @@ -25,14 +25,19 @@ package processing.opengl; import processing.core.*; +import java.awt.*; +import java.awt.font.*; +import java.awt.geom.*; import java.lang.reflect.*; import net.java.games.jogl.*; /** - * Implementation of the PGraphics API that - * employs OpenGL rendering via JOGL. - *

+ * Implementation of the PGraphics API that employs OpenGL rendering via JOGL. + *

+ * JOGL requires Java 1.4 or higher, so there are no restrictions on this + * code to be compatible with Java 1.1 or Java 1.3. + *

* Lighting and camera implementation by Simon Greenwold. */ public class PGraphicsGL extends PGraphics3 { @@ -42,9 +47,12 @@ public class PGraphicsGL extends PGraphics3 { protected float[] projectionFloats; + GLUtesselator tobj; + TessCallback tessCallback; + /** - * true if the host system is big endian (mac, irix, sun), - * false if little endian (intel). + * true if the host system is big endian (PowerPC, MIPS, SPARC), + * false if little endian (x86 Intel). */ static public boolean BIG_ENDIAN = System.getProperty("sun.cpu.endian").equals("big"); @@ -111,6 +119,15 @@ public class PGraphicsGL extends PGraphics3 { resize(width, height); //defaults(); // call this just before setup instead + tobj = glu.gluNewTess(); + // unfortunately glu.gluDeleteTess(tobj); is never called + tessCallback = new TessCallback(gl, glu); + + glu.gluTessCallback(tobj, GLU.GLU_TESS_VERTEX, tessCallback); + glu.gluTessCallback(tobj, GLU.GLU_TESS_BEGIN, tessCallback); + glu.gluTessCallback(tobj, GLU.GLU_TESS_END, tessCallback); + glu.gluTessCallback(tobj, GLU.GLU_TESS_ERROR, tessCallback); + //System.out.println("done creating gl"); } @@ -767,25 +784,220 @@ public class PGraphicsGL extends PGraphics3 { ////////////////////////////////////////////////////////////// - /* - public void textMode(int mode) { - // TODO get this guy straightened out - if (mode != OBJECT) { - throw new RuntimeException("only textMode(OBJECT) is " + - "currently supported for OpenGL"); + public float textAscent() { + if ((textMode != SHAPE) || (textFontNative == null)) { + return super.textAscent(); } - super.textMode(mode); + return textFontNativeMetrics.getAscent(); } - */ + + + public float textDescent() { + if ((textMode != SHAPE) || (textFontNative == null)) { + return super.textDescent(); + } + return textFontNativeMetrics.getDescent(); + } + + + /** + * Same as parent, but override for native version of the font. + *

+ * Also gets called by textFont, so the metrics + * will get recorded properly. + */ + public void textSize(float size) { + if ((textMode != SHAPE) || (textFontNative == null)) { + super.textSize(size); + return; + } + textFontNative = textFontNative.deriveFont(size); + Graphics2D graphics = (Graphics2D) canvas.getGraphics(); + //g2.setFont(textFontNative); + graphics.setFont(textFontNative); + + // get the metrics info + //textFontNativeMetrics = g2.getFontMetrics(textFontNative); + textFontNativeMetrics = graphics.getFontMetrics(textFontNative); + } + + + protected float textWidthImpl(char buffer[], int start, int stop) { + if ((textMode != SHAPE) || (textFontNative == null)) { + return super.textWidthImpl(buffer, start, stop); + } + // maybe should use one of the newer/fancier functions for this? + int length = stop - start; + return textFontNativeMetrics.charsWidth(buffer, start, length); + } + + + // six element array received from the Java2D path iterator + float textPoints[] = new float[6]; + // three element array passed to gluTessVertex + double textVertex[] = new double[3]; + // array passed to createGylphVector, later should just be the whole string + char textArray[] = new char[1]; + + + // should instead override textPlacedImpl() because createGlyphVector can handle + // whole strings. or better yet, cache the font on a per-char basis + protected void textCharImpl(char ch, float x, float y) { + if (textMode != SHAPE) { + super.textCharImpl(ch, x, y); + return; + } + + textArray[0] = ch; + + Graphics2D graphics = (Graphics2D) canvas.getGraphics(); + //Graphics graphics = canvas.getGraphics(); + FontRenderContext frc = graphics.getFontRenderContext(); + GlyphVector gv = textFontNative.createGlyphVector(frc, textArray); + Shape shp = gv.getOutline(); + PathIterator iter = shp.getPathIterator(null); + + glu.gluTessBeginPolygon(tobj, null); + + float lastX, lastY; + // second param to gluTessVertex is for a user defined object that contains + // additional info about this point, but that's not needed for anything + + while (!iter.isDone()) { + int type = iter.currentSegment(textPoints); + switch (type) { + case PathIterator.SEG_MOVETO: // 1 point + //println("moveto\t" + vals[0] + "\t" + vals[1]); + glu.gluTessBeginContour(tobj); + textVertex[0] = textPoints[0]; + textVertex[1] = textPoints[1]; + glu.gluTessVertex(tobj, textVertex, textVertex); + lastX = textPoints[0]; + lastY = textPoints[1]; + break; + + case PathIterator.SEG_LINETO: // 1 point + //println("lineto\t" + vals[0] + "\t" + vals[1]); + textVertex[0] = textPoints[0]; + textVertex[1] = textPoints[1]; + glu.gluTessVertex(tobj, textVertex, textVertex); + lastX = textPoints[0]; + lastY = textPoints[1]; + break; + + case PathIterator.SEG_QUADTO: // 2 points + //println("quadto\t" + vals[0] + "\t" + vals[1] + "\t" + vals[2] + "\t" + vals[3]); + for (int i = 1; i < bezierDetail; i++) { + float t = (float)i / (float)bezierDetail; + textVertex[0] = + bezierPoint(lastX, textPoints[0], textPoints[0], textPoints[2], t); + textVertex[1] = + bezierPoint(lastY, textPoints[1], textPoints[1], textPoints[3], t); + glu.gluTessVertex(tobj, textVertex, textVertex); + lastX = textPoints[2]; + lastY = textPoints[3]; + } + break; + + case PathIterator.SEG_CUBICTO: // 3 points + //println("cubicto\t" + join(nf(vals, 0, 4), "\t")); + for (int i = 1; i < bezierDetail; i++) { + float t = (float)i / (float)bezierDetail; + textVertex[0] = + bezierPoint(lastX, textPoints[0], textPoints[2], textPoints[4], t); + textVertex[1] = + bezierPoint(lastY, textPoints[1], textPoints[3], textPoints[5], t); + glu.gluTessVertex(tobj, textVertex, textVertex); + lastX = textPoints[4]; + lastY = textPoints[5]; + } + break; + + case PathIterator.SEG_CLOSE: + //println("close"); println(); + glu.gluTessEndContour(tobj); + break; + } + iter.next(); + } + glu.gluTessEndPolygon(tobj); + } + + + public void textMode(int mode) { + if (mode == SHAPE) { + textMode = SHAPE; + + } else { + // if not SHAPE mode, then pass off to the PGraphics.textMode() + // which is built for error handling (but objects to SHAPE). + super.textMode(mode); + } + } + + + public static class TessCallback extends GLUtesselatorCallbackAdapter { + GL gl; + GLU glu; + + public TessCallback(GL gl, GLU glu) { + this.gl = gl; + this.glu = glu; + } + + public void begin(int type) { + gl.glBegin(type); + } + + public void end() { + gl.glEnd(); + } + + public void vertex(Object data) { + if (data instanceof double[]) { + double[] d = (double[]) data; + if (d.length == 6) { + double[] d2 = {d[0], d[1], d[2]}; + gl.glVertex3dv(d2); + d2 = new double[]{d[3], d[4], d[5]}; + gl.glColor3dv(d2); + } else if (d.length == 3) { + gl.glVertex3dv(d); + } + } + } + + public void error(int errnum) { + String estring; + estring = glu.gluErrorString(errnum); + System.out.println("Tessellation Error: " + estring); + throw new RuntimeException(); + } + + public void combine(double[] coords, Object[] data, + float[] weight, Object[] outData) { + double[] vertex = new double[6]; + vertex[0] = coords[0]; + vertex[1] = coords[1]; + vertex[2] = coords[2]; + for (int i = 3; i < 6; i++) + vertex[i] = weight[0] * ((double[]) data[0])[i] + + weight[1] * ((double[]) data[1])[i] + + weight[2] * ((double[]) data[2])[i] + + weight[3] * ((double[]) data[3])[i]; + outData[0] = vertex; + } + } + + + ////////////////////////////////////////////////////////////// + //public void cameraMode(int mode) { //super.cameraMode(mode); //syncMatrices(); //} - - ////////////////////////////////////////////////////////////// - /* public void endCamera() { //System.out.println("PGraphicsGL.endCamera() 1"); @@ -1834,7 +2046,7 @@ public class PGraphicsGL extends PGraphics3 { return; //System.out.println("no error"); } else { - System.out.print(where + ": "); + System.out.print("GL_ERROR at " + where + ": "); System.out.print(PApplet.hex(err, 4) + " "); switch (err) { case 0x0500: System.out.print("GL_INVALID_ENUM"); break;