aboutsummaryrefslogtreecommitdiffstats
path: root/src/classes/com/sun/opengl/util/j2d/TextRenderer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/classes/com/sun/opengl/util/j2d/TextRenderer.java')
-rwxr-xr-xsrc/classes/com/sun/opengl/util/j2d/TextRenderer.java1931
1 files changed, 0 insertions, 1931 deletions
diff --git a/src/classes/com/sun/opengl/util/j2d/TextRenderer.java b/src/classes/com/sun/opengl/util/j2d/TextRenderer.java
deleted file mode 100755
index c8493affa..000000000
--- a/src/classes/com/sun/opengl/util/j2d/TextRenderer.java
+++ /dev/null
@@ -1,1931 +0,0 @@
-/*
- * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * - Redistribution of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistribution in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * Neither the name of Sun Microsystems, Inc. or the names of
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * This software is provided "AS IS," without a warranty of any kind. ALL
- * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
- * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
- * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
- * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
- * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
- * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
- * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
- * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
- * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
- * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
- * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
- *
- * You acknowledge that this software is not designed or intended for use
- * in the design, construction, operation or maintenance of any nuclear
- * facility.
- *
- * Sun gratefully acknowledges that this software was originally authored
- * and developed by Kenneth Bradley Russell and Christopher John Kline.
- */
-package com.sun.opengl.util.j2d;
-
-import com.sun.opengl.impl.*;
-import com.sun.opengl.impl.packrect.*;
-import com.sun.opengl.util.*;
-import com.sun.opengl.util.texture.*;
-
-import java.awt.AlphaComposite;
-import java.awt.Color;
-import java.awt.Composite;
-
-// For debugging purposes
-import java.awt.EventQueue;
-import java.awt.Font;
-import java.awt.Frame;
-import java.awt.Graphics2D;
-import java.awt.Image;
-import java.awt.Point;
-import java.awt.RenderingHints;
-import java.awt.event.*;
-import java.awt.font.*;
-import java.awt.geom.*;
-
-import java.nio.*;
-
-import java.text.*;
-
-import java.util.*;
-
-import javax.media.opengl.*;
-import javax.media.opengl.glu.*;
-
-
-/** Renders bitmapped Java 2D text into an OpenGL window with high
- performance, full Unicode support, and a simple API. Performs
- appropriate caching of text rendering results in an OpenGL texture
- internally to avoid repeated font rasterization. The caching is
- completely automatic, does not require any user intervention, and
- has no visible controls in the public API. <P>
-
- Using the {@link TextRenderer TextRenderer} is simple. Add a
- "<code>TextRenderer renderer;</code>" field to your {@link
- javax.media.opengl.GLEventListener GLEventListener}. In your {@link
- javax.media.opengl.GLEventListener#init init} method, add:
-
- <PRE>
- renderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 36));
- </PRE>
-
- <P> In the {@link javax.media.opengl.GLEventListener#display display} method of your
- {@link javax.media.opengl.GLEventListener GLEventListener}, add:
- <PRE>
- renderer.beginRendering(drawable.getWidth(), drawable.getHeight());
- // optionally set the color
- renderer.setColor(1.0f, 0.2f, 0.2f, 0.8f);
- renderer.draw("Text to draw", xPosition, yPosition);
- // ... more draw commands, color changes, etc.
- renderer.endRendering();
- </PRE>
-
- Unless you are sharing textures and display lists between OpenGL
- contexts, you do not need to call the {@link #dispose dispose}
- method of the TextRenderer; the OpenGL resources it uses
- internally will be cleaned up automatically when the OpenGL
- context is destroyed. <P>
-
- <b>Note</b> that the TextRenderer may cause the vertex and texture
- coordinate array buffer bindings to change, or to be unbound. This
- is important to note if you are using Vertex Buffer Objects (VBOs)
- in your application. <P>
-
- Internally, the renderer uses a rectangle packing algorithm to
- pack both glyphs and full Strings' rendering results (which are
- variable size) onto a larger OpenGL texture. The internal backing
- store is maintained using a {@link
- com.sun.opengl.util.j2d.TextureRenderer TextureRenderer}. A least
- recently used (LRU) algorithm is used to discard previously
- rendered strings; the specific algorithm is undefined, but is
- currently implemented by flushing unused Strings' rendering
- results every few hundred rendering cycles, where a rendering
- cycle is defined as a pair of calls to {@link #beginRendering
- beginRendering} / {@link #endRendering endRendering}.
-
- @author John Burkey
- @author Kenneth Russell
-*/
-public class TextRenderer {
- private static final boolean DEBUG = Debug.debug("TextRenderer");
-
- // These are occasionally useful for more in-depth debugging
- private static final boolean DISABLE_GLYPH_CACHE = false;
- private static final boolean DRAW_BBOXES = false;
-
- static final int kSize = 256;
-
- // Every certain number of render cycles, flush the strings which
- // haven't been used recently
- private static final int CYCLES_PER_FLUSH = 100;
-
- // The amount of vertical dead space on the backing store before we
- // force a compaction
- private static final float MAX_VERTICAL_FRAGMENTATION = 0.7f;
- static final int kQuadsPerBuffer = 100;
- static final int kCoordsPerVertVerts = 3;
- static final int kCoordsPerVertTex = 2;
- static final int kVertsPerQuad = 4;
- static final int kTotalBufferSizeVerts = kQuadsPerBuffer * kVertsPerQuad;
- static final int kTotalBufferSizeCoordsVerts = kQuadsPerBuffer * kVertsPerQuad * kCoordsPerVertVerts;
- static final int kTotalBufferSizeCoordsTex = kQuadsPerBuffer * kVertsPerQuad * kCoordsPerVertTex;
- static final int kTotalBufferSizeBytesVerts = kTotalBufferSizeCoordsVerts * 4;
- static final int kTotalBufferSizeBytesTex = kTotalBufferSizeCoordsTex * 4;
- static final int kSizeInBytes_OneVertices_VertexData = kCoordsPerVertVerts * 4;
- static final int kSizeInBytes_OneVertices_TexData = kCoordsPerVertTex * 4;
- private Font font;
- private boolean antialiased;
- private boolean useFractionalMetrics;
-
- // Whether we're attempting to use automatic mipmap generation support
- private boolean mipmap;
- private RectanglePacker packer;
- private boolean haveMaxSize;
- private RenderDelegate renderDelegate;
- private TextureRenderer cachedBackingStore;
- private Graphics2D cachedGraphics;
- private FontRenderContext cachedFontRenderContext;
- private Map /*<String,Rect>*/ stringLocations = new HashMap /*<String,Rect>*/();
- private GlyphProducer mGlyphProducer;
-
- private int numRenderCycles;
-
- // Need to keep track of whether we're in a beginRendering() /
- // endRendering() cycle so we can re-enter the exact same state if
- // we have to reallocate the backing store
- private boolean inBeginEndPair;
- private boolean isOrthoMode;
- private int beginRenderingWidth;
- private int beginRenderingHeight;
- private boolean beginRenderingDepthTestDisabled;
-
- // For resetting the color after disposal of the old backing store
- private boolean haveCachedColor;
- private float cachedR;
- private float cachedG;
- private float cachedB;
- private float cachedA;
- private Color cachedColor;
- private boolean needToResetColor;
-
- // For debugging only
- private Frame dbgFrame;
-
- // Debugging purposes only
- private boolean debugged;
- Pipelined_QuadRenderer mPipelinedQuadRenderer;
-
- //emzic: added boolean flag
- private boolean useVertexArrays = true;
-
- //emzic: added boolean flag
- private boolean isExtensionAvailable_GL_VERSION_1_5;
- private boolean checkFor_isExtensionAvailable_GL_VERSION_1_5;
-
- // Whether GL_LINEAR filtering is enabled for the backing store
- private boolean smoothing = true;
-
- /** Creates a new TextRenderer with the given font, using no
- antialiasing or fractional metrics, and the default
- RenderDelegate. Equivalent to <code>TextRenderer(font, false,
- false)</code>.
-
- @param font the font to render with
- */
- public TextRenderer(Font font) {
- this(font, false, false, null, false);
- }
-
- /** Creates a new TextRenderer with the given font, using no
- antialiasing or fractional metrics, and the default
- RenderDelegate. If <CODE>mipmap</CODE> is true, attempts to use
- OpenGL's automatic mipmap generation for better smoothing when
- rendering the TextureRenderer's contents at a distance.
- Equivalent to <code>TextRenderer(font, false, false)</code>.
-
- @param font the font to render with
- @param mipmap whether to attempt use of automatic mipmap generation
- */
- public TextRenderer(Font font, boolean mipmap) {
- this(font, false, false, null, mipmap);
- }
-
- /** Creates a new TextRenderer with the given Font, specified font
- properties, and default RenderDelegate. The
- <code>antialiased</code> and <code>useFractionalMetrics</code>
- flags provide control over the same properties at the Java 2D
- level. No mipmap support is requested. Equivalent to
- <code>TextRenderer(font, antialiased, useFractionalMetrics,
- null)</code>.
-
- @param font the font to render with
- @param antialiased whether to use antialiased fonts
- @param useFractionalMetrics whether to use fractional font
- metrics at the Java 2D level
- */
- public TextRenderer(Font font, boolean antialiased,
- boolean useFractionalMetrics) {
- this(font, antialiased, useFractionalMetrics, null, false);
- }
-
- /** Creates a new TextRenderer with the given Font, specified font
- properties, and given RenderDelegate. The
- <code>antialiased</code> and <code>useFractionalMetrics</code>
- flags provide control over the same properties at the Java 2D
- level. The <code>renderDelegate</code> provides more control
- over the text rendered. No mipmap support is requested.
-
- @param font the font to render with
- @param antialiased whether to use antialiased fonts
- @param useFractionalMetrics whether to use fractional font
- metrics at the Java 2D level
- @param renderDelegate the render delegate to use to draw the
- text's bitmap, or null to use the default one
- */
- public TextRenderer(Font font, boolean antialiased,
- boolean useFractionalMetrics, RenderDelegate renderDelegate) {
- this(font, antialiased, useFractionalMetrics, renderDelegate, false);
- }
-
- /** Creates a new TextRenderer with the given Font, specified font
- properties, and given RenderDelegate. The
- <code>antialiased</code> and <code>useFractionalMetrics</code>
- flags provide control over the same properties at the Java 2D
- level. The <code>renderDelegate</code> provides more control
- over the text rendered. If <CODE>mipmap</CODE> is true, attempts
- to use OpenGL's automatic mipmap generation for better smoothing
- when rendering the TextureRenderer's contents at a distance.
-
- @param font the font to render with
- @param antialiased whether to use antialiased fonts
- @param useFractionalMetrics whether to use fractional font
- metrics at the Java 2D level
- @param renderDelegate the render delegate to use to draw the
- text's bitmap, or null to use the default one
- @param mipmap whether to attempt use of automatic mipmap generation
- */
- public TextRenderer(Font font, boolean antialiased,
- boolean useFractionalMetrics, RenderDelegate renderDelegate,
- boolean mipmap) {
- this.font = font;
- this.antialiased = antialiased;
- this.useFractionalMetrics = useFractionalMetrics;
- this.mipmap = mipmap;
-
- // FIXME: consider adjusting the size based on font size
- // (it will already automatically resize if necessary)
- packer = new RectanglePacker(new Manager(), kSize, kSize);
-
- if (renderDelegate == null) {
- renderDelegate = new DefaultRenderDelegate();
- }
-
- this.renderDelegate = renderDelegate;
-
- mGlyphProducer = new GlyphProducer(font.getNumGlyphs());
- }
-
- /** Returns the bounding rectangle of the given String, assuming it
- was rendered at the origin. See {@link #getBounds(CharSequence)
- getBounds(CharSequence)}. */
- public Rectangle2D getBounds(String str) {
- return getBounds((CharSequence) str);
- }
-
- /** Returns the bounding rectangle of the given CharSequence,
- assuming it was rendered at the origin. The coordinate system of
- the returned rectangle is Java 2D's, with increasing Y
- coordinates in the downward direction. The relative coordinate
- (0, 0) in the returned rectangle corresponds to the baseline of
- the leftmost character of the rendered string, in similar
- fashion to the results returned by, for example, {@link
- java.awt.font.GlyphVector#getVisualBounds}. Most applications
- will use only the width and height of the returned Rectangle for
- the purposes of centering or justifying the String. It is not
- specified which Java 2D bounds ({@link
- java.awt.font.GlyphVector#getVisualBounds getVisualBounds},
- {@link java.awt.font.GlyphVector#getPixelBounds getPixelBounds},
- etc.) the returned bounds correspond to, although every effort
- is made to ensure an accurate bound. */
- public Rectangle2D getBounds(CharSequence str) {
- // FIXME: this should be more optimized and use the glyph cache
- Rect r = null;
-
- if ((r = (Rect) stringLocations.get(str)) != null) {
- TextData data = (TextData) r.getUserData();
-
- // Reconstitute the Java 2D results based on the cached values
- return new Rectangle2D.Double(-data.origin().x, -data.origin().y,
- r.w(), r.h());
- }
-
- // Must return a Rectangle compatible with the layout algorithm --
- // must be idempotent
- return normalize(renderDelegate.getBounds(str, font,
- getFontRenderContext()));
- }
-
- /** Returns the Font this renderer is using. */
- public Font getFont() {
- return font;
- }
-
- /** Returns a FontRenderContext which can be used for external
- text-related size computations. This object should be considered
- transient and may become invalidated between {@link
- #beginRendering beginRendering} / {@link #endRendering
- endRendering} pairs. */
- public FontRenderContext getFontRenderContext() {
- if (cachedFontRenderContext == null) {
- cachedFontRenderContext = getGraphics2D().getFontRenderContext();
- }
-
- return cachedFontRenderContext;
- }
-
- /** Begins rendering with this {@link TextRenderer TextRenderer}
- into the current OpenGL drawable, pushing the projection and
- modelview matrices and some state bits and setting up a
- two-dimensional orthographic projection with (0, 0) as the
- lower-left coordinate and (width, height) as the upper-right
- coordinate. Binds and enables the internal OpenGL texture
- object, sets the texture environment mode to GL_MODULATE, and
- changes the current color to the last color set with this
- TextRenderer via {@link #setColor setColor}. This method
- disables the depth test and is equivalent to
- beginRendering(width, height, true).
-
- @param width the width of the current on-screen OpenGL drawable
- @param height the height of the current on-screen OpenGL drawable
- @throws javax.media.opengl.GLException If an OpenGL context is not current when this method is called
- */
- public void beginRendering(int width, int height) throws GLException {
- beginRendering(width, height, true);
- }
-
- /** Begins rendering with this {@link TextRenderer TextRenderer}
- into the current OpenGL drawable, pushing the projection and
- modelview matrices and some state bits and setting up a
- two-dimensional orthographic projection with (0, 0) as the
- lower-left coordinate and (width, height) as the upper-right
- coordinate. Binds and enables the internal OpenGL texture
- object, sets the texture environment mode to GL_MODULATE, and
- changes the current color to the last color set with this
- TextRenderer via {@link #setColor setColor}. Disables the depth
- test if the disableDepthTest argument is true.
-
- @param width the width of the current on-screen OpenGL drawable
- @param height the height of the current on-screen OpenGL drawable
- @param disableDepthTest whether to disable the depth test
- @throws GLException If an OpenGL context is not current when this method is called
- */
- public void beginRendering(int width, int height, boolean disableDepthTest)
- throws GLException {
- beginRendering(true, width, height, disableDepthTest);
- }
-
- /** Begins rendering of 2D text in 3D with this {@link TextRenderer
- TextRenderer} into the current OpenGL drawable. Assumes the end
- user is responsible for setting up the modelview and projection
- matrices, and will render text using the {@link #draw3D draw3D}
- method. This method pushes some OpenGL state bits, binds and
- enables the internal OpenGL texture object, sets the texture
- environment mode to GL_MODULATE, and changes the current color
- to the last color set with this TextRenderer via {@link
- #setColor setColor}.
-
- @throws GLException If an OpenGL context is not current when this method is called
- */
- public void begin3DRendering() throws GLException {
- beginRendering(false, 0, 0, false);
- }
-
- /** Changes the current color of this TextRenderer to the supplied
- one. The default color is opaque white.
-
- @param color the new color to use for rendering text
- @throws GLException If an OpenGL context is not current when this method is called
- */
- public void setColor(Color color) throws GLException {
- boolean noNeedForFlush = (haveCachedColor && (cachedColor != null) &&
- color.equals(cachedColor));
-
- if (!noNeedForFlush) {
- flushGlyphPipeline();
- }
-
- getBackingStore().setColor(color);
- haveCachedColor = true;
- cachedColor = color;
- }
-
- /** Changes the current color of this TextRenderer to the supplied
- one, where each component ranges from 0.0f - 1.0f. The alpha
- component, if used, does not need to be premultiplied into the
- color channels as described in the documentation for {@link
- com.sun.opengl.util.texture.Texture Texture}, although
- premultiplied colors are used internally. The default color is
- opaque white.
-
- @param r the red component of the new color
- @param g the green component of the new color
- @param b the blue component of the new color
- @param a the alpha component of the new color, 0.0f = completely
- transparent, 1.0f = completely opaque
- @throws GLException If an OpenGL context is not current when this method is called
- */
- public void setColor(float r, float g, float b, float a)
- throws GLException {
- boolean noNeedForFlush = (haveCachedColor && (cachedColor == null) &&
- (r == cachedR) && (g == cachedG) && (b == cachedB) &&
- (a == cachedA));
-
- if (!noNeedForFlush) {
- flushGlyphPipeline();
- }
-
- getBackingStore().setColor(r, g, b, a);
- haveCachedColor = true;
- cachedR = r;
- cachedG = g;
- cachedB = b;
- cachedA = a;
- cachedColor = null;
- }
-
- /** Draws the supplied CharSequence at the desired location using
- the renderer's current color. The baseline of the leftmost
- character is at position (x, y) specified in OpenGL coordinates,
- where the origin is at the lower-left of the drawable and the Y
- coordinate increases in the upward direction.
-
- @param str the string to draw
- @param x the x coordinate at which to draw
- @param y the y coordinate at which to draw
- @throws GLException If an OpenGL context is not current when this method is called
- */
- public void draw(CharSequence str, int x, int y) throws GLException {
- draw3D(str, x, y, 0, 1);
- }
-
- /** Draws the supplied String at the desired location using the
- renderer's current color. See {@link #draw(CharSequence, int,
- int) draw(CharSequence, int, int)}. */
- public void draw(String str, int x, int y) throws GLException {
- draw3D(str, x, y, 0, 1);
- }
-
- /** Draws the supplied CharSequence at the desired 3D location using
- the renderer's current color. The baseline of the leftmost
- character is placed at position (x, y, z) in the current
- coordinate system.
-
- @param str the string to draw
- @param x the x coordinate at which to draw
- @param y the y coordinate at which to draw
- @param z the z coordinate at which to draw
- @param scaleFactor a uniform scale factor applied to the width and height of the drawn rectangle
- @throws GLException If an OpenGL context is not current when this method is called
- */
- public void draw3D(CharSequence str, float x, float y, float z,
- float scaleFactor) {
- internal_draw3D(str, x, y, z, scaleFactor);
- }
-
- /** Draws the supplied String at the desired 3D location using the
- renderer's current color. See {@link #draw3D(CharSequence,
- float, float, float, float) draw3D(CharSequence, float, float,
- float, float)}. */
- public void draw3D(String str, float x, float y, float z, float scaleFactor) {
- internal_draw3D(str, x, y, z, scaleFactor);
- }
-
- /** Returns the pixel width of the given character. */
- public float getCharWidth(char inChar) {
- return mGlyphProducer.getGlyphPixelWidth(inChar);
- }
-
- /** Causes the TextRenderer to flush any internal caches it may be
- maintaining and draw its rendering results to the screen. This
- should be called after each call to draw() if you are setting
- OpenGL state such as the modelview matrix between calls to
- draw(). */
- public void flush() {
- flushGlyphPipeline();
- }
-
- /** Ends a render cycle with this {@link TextRenderer TextRenderer}.
- Restores the projection and modelview matrices as well as
- several OpenGL state bits. Should be paired with {@link
- #beginRendering beginRendering}.
-
- @throws GLException If an OpenGL context is not current when this method is called
- */
- public void endRendering() throws GLException {
- endRendering(true);
- }
-
- /** Ends a 3D render cycle with this {@link TextRenderer TextRenderer}.
- Restores several OpenGL state bits. Should be paired with {@link
- #begin3DRendering begin3DRendering}.
-
- @throws GLException If an OpenGL context is not current when this method is called
- */
- public void end3DRendering() throws GLException {
- endRendering(false);
- }
-
- /** Disposes of all resources this TextRenderer is using. It is not
- valid to use the TextRenderer after this method is called.
-
- @throws GLException If an OpenGL context is not current when this method is called
- */
- public void dispose() throws GLException {
- packer.dispose();
- packer = null;
- cachedBackingStore = null;
- cachedGraphics = null;
- cachedFontRenderContext = null;
-
- if (dbgFrame != null) {
- dbgFrame.dispose();
- }
- }
-
- //----------------------------------------------------------------------
- // Internals only below this point
- //
-
- private static Rectangle2D preNormalize(Rectangle2D src) {
- // Need to round to integer coordinates
- // Also give ourselves a little slop around the reported
- // bounds of glyphs because it looks like neither the visual
- // nor the pixel bounds works perfectly well
- int minX = (int) Math.floor(src.getMinX()) - 1;
- int minY = (int) Math.floor(src.getMinY()) - 1;
- int maxX = (int) Math.ceil(src.getMaxX()) + 1;
- int maxY = (int) Math.ceil(src.getMaxY()) + 1;
- return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
- }
-
-
- private Rectangle2D normalize(Rectangle2D src) {
- // Give ourselves a boundary around each entity on the backing
- // store in order to prevent bleeding of nearby Strings due to
- // the fact that we use linear filtering
-
- // NOTE that this boundary is quite heuristic and is related
- // to how far away in 3D we may view the text --
- // heuristically, 1.5% of the font's height
- int boundary = (int) Math.max(1, 0.015 * font.getSize());
-
- return new Rectangle2D.Double((int) Math.floor(src.getMinX() - boundary),
- (int) Math.floor(src.getMinY() - boundary),
- (int) Math.ceil(src.getWidth() + 2 * boundary),
- (int) Math.ceil(src.getHeight()) + 2 * boundary);
- }
-
- private TextureRenderer getBackingStore() {
- TextureRenderer renderer = (TextureRenderer) packer.getBackingStore();
-
- if (renderer != cachedBackingStore) {
- // Backing store changed since last time; discard any cached Graphics2D
- if (cachedGraphics != null) {
- cachedGraphics.dispose();
- cachedGraphics = null;
- cachedFontRenderContext = null;
- }
-
- cachedBackingStore = renderer;
- }
-
- return cachedBackingStore;
- }
-
- private Graphics2D getGraphics2D() {
- TextureRenderer renderer = getBackingStore();
-
- if (cachedGraphics == null) {
- cachedGraphics = renderer.createGraphics();
-
- // Set up composite, font and rendering hints
- cachedGraphics.setComposite(AlphaComposite.Src);
- cachedGraphics.setColor(Color.WHITE);
- cachedGraphics.setFont(font);
- cachedGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
- (antialiased ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON
- : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
- cachedGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
- (useFractionalMetrics
- ? RenderingHints.VALUE_FRACTIONALMETRICS_ON
- : RenderingHints.VALUE_FRACTIONALMETRICS_OFF));
- }
-
- return cachedGraphics;
- }
-
- private void beginRendering(boolean ortho, int width, int height,
- boolean disableDepthTestForOrtho) {
- if (DEBUG && !debugged) {
- debug();
- }
-
- inBeginEndPair = true;
- isOrthoMode = ortho;
- beginRenderingWidth = width;
- beginRenderingHeight = height;
- beginRenderingDepthTestDisabled = disableDepthTestForOrtho;
-
- if (ortho) {
- getBackingStore().beginOrthoRendering(width, height,
- disableDepthTestForOrtho);
- } else {
- getBackingStore().begin3DRendering();
- }
-
- GL gl = GLU.getCurrentGL();
-
- // Push client attrib bits used by the pipelined quad renderer
- gl.glPushClientAttrib((int) GL.GL_ALL_CLIENT_ATTRIB_BITS);
-
- if (!haveMaxSize) {
- // Query OpenGL for the maximum texture size and set it in the
- // RectanglePacker to keep it from expanding too large
- int[] sz = new int[1];
- gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, sz, 0);
- packer.setMaxSize(sz[0], sz[0]);
- haveMaxSize = true;
- }
-
- if (needToResetColor && haveCachedColor) {
- if (cachedColor == null) {
- getBackingStore().setColor(cachedR, cachedG, cachedB, cachedA);
- } else {
- getBackingStore().setColor(cachedColor);
- }
-
- needToResetColor = false;
- }
-
- // Disable future attempts to use mipmapping if TextureRenderer
- // doesn't support it
- if (mipmap && !getBackingStore().isUsingAutoMipmapGeneration()) {
- if (DEBUG) {
- System.err.println("Disabled mipmapping in TextRenderer");
- }
-
- mipmap = false;
- }
- }
-
- /**
- * emzic: here the call to glBindBuffer crashes on certain graphicscard/driver combinations
- * this is why the ugly try-catch block has been added, which falls back to the old textrenderer
- *
- * @param ortho
- * @throws GLException
- */
- private void endRendering(boolean ortho) throws GLException {
- flushGlyphPipeline();
-
- inBeginEndPair = false;
-
- GL gl = GLU.getCurrentGL();
-
- // Pop client attrib bits used by the pipelined quad renderer
- gl.glPopClientAttrib();
-
- // The OpenGL spec is unclear about whether this changes the
- // buffer bindings, so preemptively zero out the GL_ARRAY_BUFFER
- // binding
- if (is15Available(gl)) {
- try {
- gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
- } catch (Exception e) {
- isExtensionAvailable_GL_VERSION_1_5 = false;
- }
- }
-
- if (ortho) {
- getBackingStore().endOrthoRendering();
- } else {
- getBackingStore().end3DRendering();
- }
-
- if (++numRenderCycles >= CYCLES_PER_FLUSH) {
- numRenderCycles = 0;
-
- if (DEBUG) {
- System.err.println("Clearing unused entries in endRendering()");
- }
-
- clearUnusedEntries();
- }
- }
-
- private void clearUnusedEntries() {
- final java.util.List deadRects = new ArrayList /*<Rect>*/();
-
- // Iterate through the contents of the backing store, removing
- // text strings that haven't been used recently
- packer.visit(new RectVisitor() {
- public void visit(Rect rect) {
- TextData data = (TextData) rect.getUserData();
-
- if (data.used()) {
- data.clearUsed();
- } else {
- deadRects.add(rect);
- }
- }
- });
-
- for (Iterator iter = deadRects.iterator(); iter.hasNext();) {
- Rect r = (Rect) iter.next();
- packer.remove(r);
- stringLocations.remove(((TextData) r.getUserData()).string());
-
- int unicodeToClearFromCache = ((TextData) r.getUserData()).unicodeID;
-
- if (unicodeToClearFromCache > 0) {
- mGlyphProducer.clearCacheEntry(unicodeToClearFromCache);
- }
-
- // if (DEBUG) {
- // Graphics2D g = getGraphics2D();
- // g.setComposite(AlphaComposite.Clear);
- // g.fillRect(r.x(), r.y(), r.w(), r.h());
- // g.setComposite(AlphaComposite.Src);
- // }
- }
-
- // If we removed dead rectangles this cycle, try to do a compaction
- float frag = packer.verticalFragmentationRatio();
-
- if (!deadRects.isEmpty() && (frag > MAX_VERTICAL_FRAGMENTATION)) {
- if (DEBUG) {
- System.err.println(
- "Compacting TextRenderer backing store due to vertical fragmentation " +
- frag);
- }
-
- packer.compact();
- }
-
- if (DEBUG) {
- getBackingStore().markDirty(0, 0, getBackingStore().getWidth(),
- getBackingStore().getHeight());
- }
- }
-
- private void internal_draw3D(CharSequence str, float x, float y, float z,
- float scaleFactor) {
- List/*<Glyph>*/ glyphs = mGlyphProducer.getGlyphs(str);
- for (Iterator iter = glyphs.iterator(); iter.hasNext(); ) {
- Glyph glyph = (Glyph) iter.next();
- float advance = glyph.draw3D(x, y, z, scaleFactor);
- x += advance * scaleFactor;
- }
- }
-
- private void flushGlyphPipeline() {
- if (mPipelinedQuadRenderer != null) {
- mPipelinedQuadRenderer.draw();
- }
- }
-
- private void draw3D_ROBUST(CharSequence str, float x, float y, float z,
- float scaleFactor) {
- String curStr;
- if (str instanceof String) {
- curStr = (String) str;
- } else {
- curStr = str.toString();
- }
-
- // Look up the string on the backing store
- Rect rect = (Rect) stringLocations.get(curStr);
-
- if (rect == null) {
- // Rasterize this string and place it on the backing store
- Graphics2D g = getGraphics2D();
- Rectangle2D origBBox = preNormalize(renderDelegate.getBounds(curStr, font, getFontRenderContext()));
- Rectangle2D bbox = normalize(origBBox);
- Point origin = new Point((int) -bbox.getMinX(),
- (int) -bbox.getMinY());
- rect = new Rect(0, 0, (int) bbox.getWidth(),
- (int) bbox.getHeight(),
- new TextData(curStr, origin, origBBox, -1));
-
- packer.add(rect);
- stringLocations.put(curStr, rect);
-
- // Re-fetch the Graphics2D in case the addition of the rectangle
- // caused the old backing store to be thrown away
- g = getGraphics2D();
-
- // OK, should now have an (x, y) for this rectangle; rasterize
- // the String
- int strx = rect.x() + origin.x;
- int stry = rect.y() + origin.y;
-
- // Clear out the area we're going to draw into
- g.setComposite(AlphaComposite.Clear);
- g.fillRect(rect.x(), rect.y(), rect.w(), rect.h());
- g.setComposite(AlphaComposite.Src);
-
- // Draw the string
- renderDelegate.draw(g, curStr, strx, stry);
-
- if (DRAW_BBOXES) {
- TextData data = (TextData) rect.getUserData();
- // Draw a bounding box on the backing store
- g.drawRect(strx - data.origOriginX(),
- stry - data.origOriginY(),
- (int) data.origRect().getWidth(),
- (int) data.origRect().getHeight());
- g.drawRect(strx - data.origin().x,
- stry - data.origin().y,
- rect.w(),
- rect.h());
- }
-
- // Mark this region of the TextureRenderer as dirty
- getBackingStore().markDirty(rect.x(), rect.y(), rect.w(),
- rect.h());
- }
-
- // OK, now draw the portion of the backing store to the screen
- TextureRenderer renderer = getBackingStore();
-
- // NOTE that the rectangles managed by the packer have their
- // origin at the upper-left but the TextureRenderer's origin is
- // at its lower left!!!
- TextData data = (TextData) rect.getUserData();
- data.markUsed();
-
- Rectangle2D origRect = data.origRect();
-
- // Align the leftmost point of the baseline to the (x, y, z) coordinate requested
- renderer.draw3DRect(x - (scaleFactor * data.origOriginX()),
- y - (scaleFactor * ((float) origRect.getHeight() - data.origOriginY())), z,
- rect.x() + (data.origin().x - data.origOriginX()),
- renderer.getHeight() - rect.y() - (int) origRect.getHeight() -
- (data.origin().y - data.origOriginY()),
- (int) origRect.getWidth(), (int) origRect.getHeight(), scaleFactor);
- }
-
- //----------------------------------------------------------------------
- // Debugging functionality
- //
- private void debug() {
- dbgFrame = new Frame("TextRenderer Debug Output");
-
- GLCanvas dbgCanvas = new GLCanvas(new GLCapabilities(), null,
- GLContext.getCurrent(), null);
- dbgCanvas.addGLEventListener(new DebugListener(dbgFrame));
- dbgFrame.add(dbgCanvas);
-
- final FPSAnimator anim = new FPSAnimator(dbgCanvas, 10);
- dbgFrame.addWindowListener(new WindowAdapter() {
- public void windowClosing(WindowEvent e) {
- // Run this on another thread than the AWT event queue to
- // make sure the call to Animator.stop() completes before
- // exiting
- new Thread(new Runnable() {
- public void run() {
- anim.stop();
- }
- }).start();
- }
- });
- dbgFrame.setSize(kSize, kSize);
- dbgFrame.setVisible(true);
- anim.start();
- debugged = true;
- }
-
- /** Class supporting more full control over the process of rendering
- the bitmapped text. Allows customization of whether the backing
- store text bitmap is full-color or intensity only, the size of
- each individual rendered text rectangle, and the contents of
- each individual rendered text string. The default implementation
- of this interface uses an intensity-only texture, a
- closely-cropped rectangle around the text, and renders text
- using the color white, which is modulated by the set color
- during the rendering process. */
- public static interface RenderDelegate {
- /** Indicates whether the backing store of this TextRenderer
- should be intensity-only (the default) or full-color. */
- public boolean intensityOnly();
-
- /** Computes the bounds of the given String relative to the
- origin. */
- public Rectangle2D getBounds(String str, Font font,
- FontRenderContext frc);
-
- /** Computes the bounds of the given character sequence relative
- to the origin. */
- public Rectangle2D getBounds(CharSequence str, Font font,
- FontRenderContext frc);
-
- /** Computes the bounds of the given GlyphVector, already
- assumed to have been created for a particular Font,
- relative to the origin. */
- public Rectangle2D getBounds(GlyphVector gv, FontRenderContext frc);
-
- /** Render the passed character sequence at the designated
- location using the supplied Graphics2D instance. The
- surrounding region will already have been cleared to the RGB
- color (0, 0, 0) with zero alpha. The initial drawing context
- of the passed Graphics2D will be set to use
- AlphaComposite.Src, the color white, the Font specified in the
- TextRenderer's constructor, and the rendering hints specified
- in the TextRenderer constructor. Changes made by the end user
- may be visible in successive calls to this method, but are not
- guaranteed to be preserved. Implementors of this method
- should reset the Graphics2D's state to that desired each time
- this method is called, in particular those states which are
- not the defaults. */
- public void draw(Graphics2D graphics, String str, int x, int y);
-
- /** Render the passed GlyphVector at the designated location using
- the supplied Graphics2D instance. The surrounding region will
- already have been cleared to the RGB color (0, 0, 0) with zero
- alpha. The initial drawing context of the passed Graphics2D
- will be set to use AlphaComposite.Src, the color white, the
- Font specified in the TextRenderer's constructor, and the
- rendering hints specified in the TextRenderer constructor.
- Changes made by the end user may be visible in successive
- calls to this method, but are not guaranteed to be preserved.
- Implementors of this method should reset the Graphics2D's
- state to that desired each time this method is called, in
- particular those states which are not the defaults. */
- public void drawGlyphVector(Graphics2D graphics, GlyphVector str,
- int x, int y);
- }
-
- private static class CharSequenceIterator implements CharacterIterator {
- CharSequence mSequence;
- int mLength;
- int mCurrentIndex;
-
- CharSequenceIterator() {
- }
-
- CharSequenceIterator(CharSequence sequence) {
- initFromCharSequence(sequence);
- }
-
- public void initFromCharSequence(CharSequence sequence) {
- mSequence = sequence;
- mLength = mSequence.length();
- mCurrentIndex = 0;
- }
-
- public char last() {
- mCurrentIndex = Math.max(0, mLength - 1);
-
- return current();
- }
-
- public char current() {
- if ((mLength == 0) || (mCurrentIndex >= mLength)) {
- return CharacterIterator.DONE;
- }
-
- return mSequence.charAt(mCurrentIndex);
- }
-
- public char next() {
- mCurrentIndex++;
-
- return current();
- }
-
- public char previous() {
- mCurrentIndex = Math.max(mCurrentIndex - 1, 0);
-
- return current();
- }
-
- public char setIndex(int position) {
- mCurrentIndex = position;
-
- return current();
- }
-
- public int getBeginIndex() {
- return 0;
- }
-
- public int getEndIndex() {
- return mLength;
- }
-
- public int getIndex() {
- return mCurrentIndex;
- }
-
- public Object clone() {
- CharSequenceIterator iter = new CharSequenceIterator(mSequence);
- iter.mCurrentIndex = mCurrentIndex;
-
- return iter;
- }
-
- public char first() {
- if (mLength == 0) {
- return CharacterIterator.DONE;
- }
-
- mCurrentIndex = 0;
-
- return current();
- }
- }
-
- // Data associated with each rectangle of text
- static class TextData {
- // Back-pointer to String this TextData describes, if it
- // represents a String rather than a single glyph
- private String str;
-
- // If this TextData represents a single glyph, this is its
- // unicode ID
- int unicodeID;
-
- // The following must be defined and used VERY precisely. This is
- // the offset from the upper-left corner of this rectangle (Java
- // 2D coordinate system) at which the string must be rasterized in
- // order to fit within the rectangle -- the leftmost point of the
- // baseline.
- private Point origin;
-
- // This represents the pre-normalized rectangle, which fits
- // within the rectangle on the backing store. We keep a
- // one-pixel border around entries on the backing store to
- // prevent bleeding of adjacent letters when using GL_LINEAR
- // filtering for rendering. The origin of this rectangle is
- // equivalent to the origin above.
- private Rectangle2D origRect;
-
- private boolean used; // Whether this text was used recently
-
- TextData(String str, Point origin, Rectangle2D origRect, int unicodeID) {
- this.str = str;
- this.origin = origin;
- this.origRect = origRect;
- this.unicodeID = unicodeID;
- }
-
- String string() {
- return str;
- }
-
- Point origin() {
- return origin;
- }
-
- // The following three methods are used to locate the glyph
- // within the expanded rectangle coming from normalize()
- int origOriginX() {
- return (int) -origRect.getMinX();
- }
-
- int origOriginY() {
- return (int) -origRect.getMinY();
- }
-
- Rectangle2D origRect() {
- return origRect;
- }
-
- boolean used() {
- return used;
- }
-
- void markUsed() {
- used = true;
- }
-
- void clearUsed() {
- used = false;
- }
- }
-
- class Manager implements BackingStoreManager {
- private Graphics2D g;
-
- public Object allocateBackingStore(int w, int h) {
- // FIXME: should consider checking Font's attributes to see
- // whether we're likely to need to support a full RGBA backing
- // store (i.e., non-default Paint, foreground color, etc.), but
- // for now, let's just be more efficient
- TextureRenderer renderer;
-
- if (renderDelegate.intensityOnly()) {
- renderer = TextureRenderer.createAlphaOnlyRenderer(w, h, mipmap);
- } else {
- renderer = new TextureRenderer(w, h, true, mipmap);
- }
- renderer.setSmoothing(smoothing);
-
- if (DEBUG) {
- System.err.println(" TextRenderer allocating backing store " +
- w + " x " + h);
- }
-
- return renderer;
- }
-
- public void deleteBackingStore(Object backingStore) {
- ((TextureRenderer) backingStore).dispose();
- }
-
- public boolean preExpand(Rect cause, int attemptNumber) {
- // Only try this one time; clear out potentially obsolete entries
- // NOTE: this heuristic and the fact that it clears the used bit
- // of all entries seems to cause cycling of entries in some
- // situations, where the backing store becomes small compared to
- // the amount of text on the screen (see the TextFlow demo) and
- // the entries continually cycle in and out of the backing
- // store, decreasing performance. If we added a little age
- // information to the entries, and only cleared out entries
- // above a certain age, this behavior would be eliminated.
- // However, it seems the system usually stabilizes itself, so
- // for now we'll just keep things simple. Note that if we don't
- // clear the used bit here, the backing store tends to increase
- // very quickly to its maximum size, at least with the TextFlow
- // demo when the text is being continually re-laid out.
- if (attemptNumber == 0) {
- if (DEBUG) {
- System.err.println(
- "Clearing unused entries in preExpand(): attempt number " +
- attemptNumber);
- }
-
- if (inBeginEndPair) {
- // Draw any outstanding glyphs
- flush();
- }
-
- clearUnusedEntries();
-
- return true;
- }
-
- return false;
- }
-
- public void additionFailed(Rect cause, int attemptNumber) {
- // Heavy hammer -- might consider doing something different
- packer.clear();
- stringLocations.clear();
- mGlyphProducer.clearAllCacheEntries();
-
- if (DEBUG) {
- System.err.println(
- " *** Cleared all text because addition failed ***");
- }
- }
-
- public void beginMovement(Object oldBackingStore, Object newBackingStore) {
- // Exit the begin / end pair if necessary
- if (inBeginEndPair) {
- // Draw any outstanding glyphs
- flush();
-
- GL gl = GLU.getCurrentGL();
-
- // Pop client attrib bits used by the pipelined quad renderer
- gl.glPopClientAttrib();
-
- // The OpenGL spec is unclear about whether this changes the
- // buffer bindings, so preemptively zero out the GL_ARRAY_BUFFER
- // binding
- if (is15Available(gl)) {
- try {
- gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
- } catch (Exception e) {
- isExtensionAvailable_GL_VERSION_1_5 = false;
- }
- }
-
- if (isOrthoMode) {
- ((TextureRenderer) oldBackingStore).endOrthoRendering();
- } else {
- ((TextureRenderer) oldBackingStore).end3DRendering();
- }
- }
-
- TextureRenderer newRenderer = (TextureRenderer) newBackingStore;
- g = newRenderer.createGraphics();
- }
-
- public void move(Object oldBackingStore, Rect oldLocation,
- Object newBackingStore, Rect newLocation) {
- TextureRenderer oldRenderer = (TextureRenderer) oldBackingStore;
- TextureRenderer newRenderer = (TextureRenderer) newBackingStore;
-
- if (oldRenderer == newRenderer) {
- // Movement on the same backing store -- easy case
- g.copyArea(oldLocation.x(), oldLocation.y(), oldLocation.w(),
- oldLocation.h(), newLocation.x() - oldLocation.x(),
- newLocation.y() - oldLocation.y());
- } else {
- // Need to draw from the old renderer's image into the new one
- Image img = oldRenderer.getImage();
- g.drawImage(img, newLocation.x(), newLocation.y(),
- newLocation.x() + newLocation.w(),
- newLocation.y() + newLocation.h(), oldLocation.x(),
- oldLocation.y(), oldLocation.x() + oldLocation.w(),
- oldLocation.y() + oldLocation.h(), null);
- }
- }
-
- public void endMovement(Object oldBackingStore, Object newBackingStore) {
- g.dispose();
-
- // Sync the whole surface
- TextureRenderer newRenderer = (TextureRenderer) newBackingStore;
- newRenderer.markDirty(0, 0, newRenderer.getWidth(),
- newRenderer.getHeight());
-
- // Re-enter the begin / end pair if necessary
- if (inBeginEndPair) {
- if (isOrthoMode) {
- ((TextureRenderer) newBackingStore).beginOrthoRendering(beginRenderingWidth,
- beginRenderingHeight, beginRenderingDepthTestDisabled);
- } else {
- ((TextureRenderer) newBackingStore).begin3DRendering();
- }
-
- // Push client attrib bits used by the pipelined quad renderer
- GL gl = GLU.getCurrentGL();
- gl.glPushClientAttrib((int) GL.GL_ALL_CLIENT_ATTRIB_BITS);
-
- if (haveCachedColor) {
- if (cachedColor == null) {
- ((TextureRenderer) newBackingStore).setColor(cachedR,
- cachedG, cachedB, cachedA);
- } else {
- ((TextureRenderer) newBackingStore).setColor(cachedColor);
- }
- }
- } else {
- needToResetColor = true;
- }
- }
- }
-
- public static class DefaultRenderDelegate implements RenderDelegate {
- public boolean intensityOnly() {
- return true;
- }
-
- public Rectangle2D getBounds(CharSequence str, Font font,
- FontRenderContext frc) {
- return getBounds(font.createGlyphVector(frc,
- new CharSequenceIterator(str)),
- frc);
- }
-
- public Rectangle2D getBounds(String str, Font font,
- FontRenderContext frc) {
- return getBounds(font.createGlyphVector(frc, str), frc);
- }
-
- public Rectangle2D getBounds(GlyphVector gv, FontRenderContext frc) {
- return gv.getVisualBounds();
- }
-
- public void drawGlyphVector(Graphics2D graphics, GlyphVector str,
- int x, int y) {
- graphics.drawGlyphVector(str, x, y);
- }
-
- public void draw(Graphics2D graphics, String str, int x, int y) {
- graphics.drawString(str, x, y);
- }
- }
-
- //----------------------------------------------------------------------
- // Glyph-by-glyph rendering support
- //
-
- // A temporary to prevent excessive garbage creation
- private char[] singleUnicode = new char[1];
-
- /** A Glyph represents either a single unicode glyph or a
- substring of characters to be drawn. The reason for the dual
- behavior is so that we can take in a sequence of unicode
- characters and partition them into runs of individual glyphs,
- but if we encounter complex text and/or unicode sequences we
- don't understand, we can render them using the
- string-by-string method. <P>
-
- Glyphs need to be able to re-upload themselves to the backing
- store on demand as we go along in the render sequence.
- */
-
- class Glyph {
- // If this Glyph represents an individual unicode glyph, this
- // is its unicode ID. If it represents a String, this is -1.
- private int unicodeID;
- // If the above field isn't -1, then these fields are used.
- // The glyph code in the font
- private int glyphCode;
- // The GlyphProducer which created us
- private GlyphProducer producer;
- // The advance of this glyph
- private float advance;
- // The GlyphVector for this single character; this is passed
- // in during construction but cleared during the upload
- // process
- private GlyphVector singleUnicodeGlyphVector;
- // The rectangle of this glyph on the backing store, or null
- // if it has been cleared due to space pressure
- private Rect glyphRectForTextureMapping;
- // If this Glyph represents a String, this is the sequence of
- // characters
- private String str;
- // Whether we need a valid advance when rendering this string
- // (i.e., whether it has other single glyphs coming after it)
- private boolean needAdvance;
-
- // Creates a Glyph representing an individual Unicode character
- public Glyph(int unicodeID,
- int glyphCode,
- float advance,
- GlyphVector singleUnicodeGlyphVector,
- GlyphProducer producer) {
- this.unicodeID = unicodeID;
- this.glyphCode = glyphCode;
- this.advance = advance;
- this.singleUnicodeGlyphVector = singleUnicodeGlyphVector;
- this.producer = producer;
- }
-
- // Creates a Glyph representing a sequence of characters, with
- // an indication of whether additional single glyphs are being
- // rendered after it
- public Glyph(String str, boolean needAdvance) {
- this.str = str;
- this.needAdvance = needAdvance;
- }
-
- /** Returns this glyph's unicode ID */
- public int getUnicodeID() {
- return unicodeID;
- }
-
- /** Returns this glyph's (font-specific) glyph code */
- public int getGlyphCode() {
- return glyphCode;
- }
-
- /** Returns the advance for this glyph */
- public float getAdvance() {
- return advance;
- }
-
- /** Draws this glyph and returns the (x) advance for this glyph */
- public float draw3D(float inX, float inY, float z, float scaleFactor) {
- if (str != null) {
- draw3D_ROBUST(str, inX, inY, z, scaleFactor);
- if (!needAdvance) {
- return 0;
- }
- // Compute and return the advance for this string
- GlyphVector gv = font.createGlyphVector(getFontRenderContext(), str);
- float totalAdvance = 0;
- for (int i = 0; i < gv.getNumGlyphs(); i++) {
- totalAdvance += gv.getGlyphMetrics(i).getAdvance();
- }
- return totalAdvance;
- }
-
- // This is the code path taken for individual glyphs
- if (glyphRectForTextureMapping == null) {
- upload();
- }
-
- try {
- if (mPipelinedQuadRenderer == null) {
- mPipelinedQuadRenderer = new Pipelined_QuadRenderer();
- }
-
- TextureRenderer renderer = getBackingStore();
- // Handles case where NPOT texture is used for backing store
- TextureCoords wholeImageTexCoords = renderer.getTexture().getImageTexCoords();
- float xScale = wholeImageTexCoords.right();
- float yScale = wholeImageTexCoords.bottom();
-
- Rect rect = glyphRectForTextureMapping;
- TextData data = (TextData) rect.getUserData();
- data.markUsed();
-
- Rectangle2D origRect = data.origRect();
-
- float x = inX - (scaleFactor * data.origOriginX());
- float y = inY - (scaleFactor * ((float) origRect.getHeight() - data.origOriginY()));
-
- int texturex = rect.x() + (data.origin().x - data.origOriginX());
- int texturey = renderer.getHeight() - rect.y() - (int) origRect.getHeight() -
- (data.origin().y - data.origOriginY());
- int width = (int) origRect.getWidth();
- int height = (int) origRect.getHeight();
-
- float tx1 = xScale * (float) texturex / (float) renderer.getWidth();
- float ty1 = yScale * (1.0f -
- ((float) texturey / (float) renderer.getHeight()));
- float tx2 = xScale * (float) (texturex + width) / (float) renderer.getWidth();
- float ty2 = yScale * (1.0f -
- ((float) (texturey + height) / (float) renderer.getHeight()));
-
- mPipelinedQuadRenderer.glTexCoord2f(tx1, ty1);
- mPipelinedQuadRenderer.glVertex3f(x, y, z);
- mPipelinedQuadRenderer.glTexCoord2f(tx2, ty1);
- mPipelinedQuadRenderer.glVertex3f(x + (width * scaleFactor), y,
- z);
- mPipelinedQuadRenderer.glTexCoord2f(tx2, ty2);
- mPipelinedQuadRenderer.glVertex3f(x + (width * scaleFactor),
- y + (height * scaleFactor), z);
- mPipelinedQuadRenderer.glTexCoord2f(tx1, ty2);
- mPipelinedQuadRenderer.glVertex3f(x,
- y + (height * scaleFactor), z);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return advance;
- }
-
- /** Notifies this glyph that it's been cleared out of the cache */
- public void clear() {
- glyphRectForTextureMapping = null;
- }
-
- private void upload() {
- GlyphVector gv = getGlyphVector();
- Rectangle2D origBBox = preNormalize(renderDelegate.getBounds(gv, getFontRenderContext()));
- Rectangle2D bbox = normalize(origBBox);
- Point origin = new Point((int) -bbox.getMinX(),
- (int) -bbox.getMinY());
- Rect rect = new Rect(0, 0, (int) bbox.getWidth(),
- (int) bbox.getHeight(),
- new TextData(null, origin, origBBox, unicodeID));
- packer.add(rect);
- glyphRectForTextureMapping = rect;
- Graphics2D g = getGraphics2D();
- // OK, should now have an (x, y) for this rectangle; rasterize
- // the glyph
- int strx = rect.x() + origin.x;
- int stry = rect.y() + origin.y;
-
- // Clear out the area we're going to draw into
- g.setComposite(AlphaComposite.Clear);
- g.fillRect(rect.x(), rect.y(), rect.w(), rect.h());
- g.setComposite(AlphaComposite.Src);
-
- // Draw the string
- renderDelegate.drawGlyphVector(g, gv, strx, stry);
-
- if (DRAW_BBOXES) {
- TextData data = (TextData) rect.getUserData();
- // Draw a bounding box on the backing store
- g.drawRect(strx - data.origOriginX(),
- stry - data.origOriginY(),
- (int) data.origRect().getWidth(),
- (int) data.origRect().getHeight());
- g.drawRect(strx - data.origin().x,
- stry - data.origin().y,
- rect.w(),
- rect.h());
- }
-
- // Mark this region of the TextureRenderer as dirty
- getBackingStore().markDirty(rect.x(), rect.y(), rect.w(),
- rect.h());
- // Re-register ourselves with our producer
- producer.register(this);
- }
-
- private GlyphVector getGlyphVector() {
- GlyphVector gv = singleUnicodeGlyphVector;
- if (gv != null) {
- singleUnicodeGlyphVector = null; // Don't need this anymore
- return gv;
- }
- singleUnicode[0] = (char) unicodeID;
- return font.createGlyphVector(getFontRenderContext(), singleUnicode);
- }
- }
-
- class GlyphProducer {
- final int undefined = -2;
- FontRenderContext fontRenderContext;
- List/*<Glyph>*/ glyphsOutput = new ArrayList/*<Glyph>*/();
- // The mapping from unicode character to font-specific glyph ID
- int[] unicodes2Glyphs;
- // The mapping from glyph ID to Glyph
- Glyph[] glyphCache;
- // We re-use this for each incoming string
- CharSequenceIterator iter = new CharSequenceIterator();
-
- GlyphProducer(int fontLengthInGlyphs) {
- unicodes2Glyphs = new int[512];
- glyphCache = new Glyph[fontLengthInGlyphs];
- clearAllCacheEntries();
- }
-
- public List/*<Glyph>*/ getGlyphs(CharSequence inString) {
- glyphsOutput.clear();
- iter.initFromCharSequence(inString);
- GlyphVector fullRunGlyphVector = font.createGlyphVector(getFontRenderContext(),
- iter);
- boolean complex = (fullRunGlyphVector.getLayoutFlags() != 0);
- if (complex || DISABLE_GLYPH_CACHE) {
- // Punt to the robust version of the renderer
- glyphsOutput.add(new Glyph(inString.toString(), false));
- return glyphsOutput;
- }
-
- int lengthInGlyphs = fullRunGlyphVector.getNumGlyphs();
- int i = 0;
- while (i < lengthInGlyphs) {
- Glyph glyph = getGlyph(inString, fullRunGlyphVector, i);
- if (glyph != null) {
- glyphsOutput.add(glyph);
- i++;
- } else {
- // Assemble a run of characters that don't fit in
- // the cache
- StringBuffer buf = new StringBuffer();
- while (i < lengthInGlyphs &&
- getGlyph(inString, fullRunGlyphVector, i) == null) {
- buf.append(inString.charAt(i++));
- }
- glyphsOutput.add(new Glyph(buf.toString(),
- // Any more glyphs after this run?
- i < lengthInGlyphs));
- }
- }
- return glyphsOutput;
- }
-
- public void clearCacheEntry(int unicodeID) {
- int glyphID = unicodes2Glyphs[unicodeID];
- if (glyphID != undefined) {
- Glyph glyph = glyphCache[glyphID];
- if (glyph != null) {
- glyph.clear();
- }
- glyphCache[glyphID] = null;
- }
- unicodes2Glyphs[unicodeID] = undefined;
- }
-
- public void clearAllCacheEntries() {
- for (int i = 0; i < unicodes2Glyphs.length; i++) {
- clearCacheEntry(i);
- }
- }
-
- public void register(Glyph glyph) {
- unicodes2Glyphs[glyph.getUnicodeID()] = glyph.getGlyphCode();
- glyphCache[glyph.getGlyphCode()] = glyph;
- }
-
- public float getGlyphPixelWidth(char unicodeID) {
- Glyph glyph = getGlyph(unicodeID);
- if (glyph != null) {
- return glyph.getAdvance();
- }
-
- // Have to do this the hard / uncached way
- singleUnicode[0] = unicodeID;
- GlyphVector gv = font.createGlyphVector(fontRenderContext,
- singleUnicode);
- return gv.getGlyphMetrics(0).getAdvance();
- }
-
- // Returns a glyph object for this single glyph. Returns null
- // if the unicode or glyph ID would be out of bounds of the
- // glyph cache.
- private Glyph getGlyph(CharSequence inString,
- GlyphVector fullRunGlyphVector,
- int index) {
- char unicodeID = inString.charAt(index);
-
- if (unicodeID >= unicodes2Glyphs.length) {
- return null;
- }
-
- int glyphID = unicodes2Glyphs[unicodeID];
- if (glyphID != undefined) {
- return glyphCache[glyphID];
- }
-
- // Must fabricate the glyph
- singleUnicode[0] = unicodeID;
- GlyphVector gv = font.createGlyphVector(getFontRenderContext(), singleUnicode);
- return getGlyph(unicodeID, gv, fullRunGlyphVector.getGlyphMetrics(index));
- }
-
- // It's unclear whether this variant might produce less
- // optimal results than if we can see the entire GlyphVector
- // for the incoming string
- private Glyph getGlyph(int unicodeID) {
- if (unicodeID >= unicodes2Glyphs.length) {
- return null;
- }
-
- int glyphID = unicodes2Glyphs[unicodeID];
- if (glyphID != undefined) {
- return glyphCache[glyphID];
- }
- singleUnicode[0] = (char) unicodeID;
- GlyphVector gv = font.createGlyphVector(getFontRenderContext(), singleUnicode);
- return getGlyph(unicodeID, gv, gv.getGlyphMetrics(0));
- }
-
- private Glyph getGlyph(int unicodeID,
- GlyphVector singleUnicodeGlyphVector,
- GlyphMetrics metrics) {
- int glyphCode = singleUnicodeGlyphVector.getGlyphCode(0);
- // Have seen huge glyph codes (65536) coming out of some fonts in some Unicode situations
- if (glyphCode >= glyphCache.length) {
- return null;
- }
- Glyph glyph = new Glyph(unicodeID,
- glyphCode,
- metrics.getAdvance(),
- singleUnicodeGlyphVector,
- this);
- register(glyph);
- return glyph;
- }
- }
-
- class Pipelined_QuadRenderer {
- int mOutstandingGlyphsVerticesPipeline = 0;
- FloatBuffer mTexCoords;
- FloatBuffer mVertCoords;
- boolean usingVBOs;
- int mVBO_For_ResuableTileVertices;
- int mVBO_For_ResuableTileTexCoords;
-
- Pipelined_QuadRenderer() {
- GL gl = GLU.getCurrentGL();
- mVertCoords = BufferUtil.newFloatBuffer(kTotalBufferSizeCoordsVerts);
- mTexCoords = BufferUtil.newFloatBuffer(kTotalBufferSizeCoordsTex);
-
- usingVBOs = is15Available(gl);
-
- if (usingVBOs) {
- try {
- int[] vbos = new int[2];
- gl.glGenBuffers(2, IntBuffer.wrap(vbos));
-
- mVBO_For_ResuableTileVertices = vbos[0];
- mVBO_For_ResuableTileTexCoords = vbos[1];
-
- gl.glBindBuffer(GL.GL_ARRAY_BUFFER,
- mVBO_For_ResuableTileVertices);
- gl.glBufferData(GL.GL_ARRAY_BUFFER, kTotalBufferSizeBytesVerts,
- null, GL.GL_STREAM_DRAW); // stream draw because this is a single quad use pipeline
-
- gl.glBindBuffer(GL.GL_ARRAY_BUFFER,
- mVBO_For_ResuableTileTexCoords);
- gl.glBufferData(GL.GL_ARRAY_BUFFER, kTotalBufferSizeBytesTex,
- null, GL.GL_STREAM_DRAW); // stream draw because this is a single quad use pipeline
- } catch (Exception e) {
- isExtensionAvailable_GL_VERSION_1_5 = false;
- usingVBOs = false;
- }
- }
- }
-
- public void glTexCoord2f(float v, float v1) {
- mTexCoords.put(v);
- mTexCoords.put(v1);
- }
-
- public void glVertex3f(float inX, float inY, float inZ) {
- mVertCoords.put(inX);
- mVertCoords.put(inY);
- mVertCoords.put(inZ);
-
- mOutstandingGlyphsVerticesPipeline++;
-
- if (mOutstandingGlyphsVerticesPipeline >= kTotalBufferSizeVerts) {
- this.draw();
- }
- }
-
- private void draw() {
- if (useVertexArrays) {
- drawVertexArrays();
- } else {
- drawIMMEDIATE();
- }
- }
-
- private void drawVertexArrays() {
- if (mOutstandingGlyphsVerticesPipeline > 0) {
- GL gl = GLU.getCurrentGL();
-
- TextureRenderer renderer = getBackingStore();
- Texture texture = renderer.getTexture(); // triggers texture uploads. Maybe this should be more obvious?
-
- mVertCoords.rewind();
- mTexCoords.rewind();
-
- gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
-
- if (usingVBOs) {
- gl.glBindBuffer(GL.GL_ARRAY_BUFFER,
- mVBO_For_ResuableTileVertices);
- gl.glBufferSubData(GL.GL_ARRAY_BUFFER, 0,
- mOutstandingGlyphsVerticesPipeline * kSizeInBytes_OneVertices_VertexData,
- mVertCoords); // upload only the new stuff
- gl.glVertexPointer(3, GL.GL_FLOAT, 0, 0);
- } else {
- gl.glVertexPointer(3, GL.GL_FLOAT, 0, mVertCoords);
- }
-
- gl.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY);
-
- if (usingVBOs) {
- gl.glBindBuffer(GL.GL_ARRAY_BUFFER,
- mVBO_For_ResuableTileTexCoords);
- gl.glBufferSubData(GL.GL_ARRAY_BUFFER, 0,
- mOutstandingGlyphsVerticesPipeline * kSizeInBytes_OneVertices_TexData,
- mTexCoords); // upload only the new stuff
- gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, 0);
- } else {
- gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, mTexCoords);
- }
-
- gl.glDrawArrays(GL.GL_QUADS, 0,
- mOutstandingGlyphsVerticesPipeline);
-
- mVertCoords.rewind();
- mTexCoords.rewind();
- mOutstandingGlyphsVerticesPipeline = 0;
- }
- }
-
- private void drawIMMEDIATE() {
- if (mOutstandingGlyphsVerticesPipeline > 0) {
- TextureRenderer renderer = getBackingStore();
- Texture texture = renderer.getTexture(); // triggers texture uploads. Maybe this should be more obvious?
-
- GL gl = GLU.getCurrentGL();
- gl.glBegin(GL.GL_QUADS);
-
- try {
- int numberOfQuads = mOutstandingGlyphsVerticesPipeline / 4;
- mVertCoords.rewind();
- mTexCoords.rewind();
-
- for (int i = 0; i < numberOfQuads; i++) {
- gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get());
- gl.glVertex3f(mVertCoords.get(), mVertCoords.get(),
- mVertCoords.get());
-
- gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get());
- gl.glVertex3f(mVertCoords.get(), mVertCoords.get(),
- mVertCoords.get());
-
- gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get());
- gl.glVertex3f(mVertCoords.get(), mVertCoords.get(),
- mVertCoords.get());
-
- gl.glTexCoord2f(mTexCoords.get(), mTexCoords.get());
- gl.glVertex3f(mVertCoords.get(), mVertCoords.get(),
- mVertCoords.get());
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- gl.glEnd();
- mVertCoords.rewind();
- mTexCoords.rewind();
- mOutstandingGlyphsVerticesPipeline = 0;
- }
- }
- }
- }
-
- class DebugListener implements GLEventListener {
- private GLU glu = new GLU();
- private Frame frame;
-
- DebugListener(Frame frame) {
- this.frame = frame;
- }
-
- public void display(GLAutoDrawable drawable) {
- GL gl = drawable.getGL();
- gl.glClear(GL.GL_DEPTH_BUFFER_BIT | GL.GL_COLOR_BUFFER_BIT);
-
- if (packer == null) {
- return;
- }
-
- TextureRenderer rend = getBackingStore();
- final int w = rend.getWidth();
- final int h = rend.getHeight();
- rend.beginOrthoRendering(w, h);
- rend.drawOrthoRect(0, 0);
- rend.endOrthoRendering();
-
- if ((frame.getWidth() != w) || (frame.getHeight() != h)) {
- EventQueue.invokeLater(new Runnable() {
- public void run() {
- frame.setSize(w, h);
- }
- });
- }
- }
-
- // Unused methods
- public void init(GLAutoDrawable drawable) {
- }
-
- public void reshape(GLAutoDrawable drawable, int x, int y, int width,
- int height) {
- }
-
- public void displayChanged(GLAutoDrawable drawable,
- boolean modeChanged, boolean deviceChanged) {
- }
- }
-
- /**
- * Sets whether vertex arrays are being used internally for
- * rendering, or whether text is rendered using the OpenGL
- * immediate mode commands. This is provided as a concession for
- * certain graphics cards which have poor vertex array
- * performance. Defaults to true.
- */
- public void setUseVertexArrays(boolean useVertexArrays) {
- this.useVertexArrays = useVertexArrays;
- }
-
- /**
- * Indicates whether vertex arrays are being used internally for
- * rendering, or whether text is rendered using the OpenGL
- * immediate mode commands. Defaults to true.
- */
- public boolean getUseVertexArrays() {
- return useVertexArrays;
- }
-
- /**
- * Sets whether smoothing (i.e., GL_LINEAR filtering) is enabled
- * in the backing TextureRenderer of this TextRenderer. A few
- * graphics cards do not behave well when this is enabled,
- * resulting in fuzzy text. Defaults to true.
- */
- public void setSmoothing(boolean smoothing) {
- this.smoothing = smoothing;
- getBackingStore().setSmoothing(smoothing);
- }
-
- /**
- * Indicates whether smoothing is enabled in the backing
- * TextureRenderer of this TextRenderer. A few graphics cards do
- * not behave well when this is enabled, resulting in fuzzy text.
- * Defaults to true.
- */
- public boolean getSmoothing() {
- return smoothing;
- }
-
- private boolean is15Available(GL gl) {
- if (!checkFor_isExtensionAvailable_GL_VERSION_1_5) {
- isExtensionAvailable_GL_VERSION_1_5 = gl.isExtensionAvailable("GL_VERSION_1_5");
- checkFor_isExtensionAvailable_GL_VERSION_1_5 = true;
- }
- return isExtensionAvailable_GL_VERSION_1_5;
- }
-}