/* * Copyright (c) 2003-onwards Shaven Puppy Ltd * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions 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 'Shaven Puppy' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.shavenpuppy.jglib.opengl; import org.lwjgl.opengl.OpenGLException; import org.lwjgl.util.Point; import org.lwjgl.util.ReadableColor; import org.lwjgl.util.Rectangle; import com.shavenpuppy.jglib.sprites.SimpleRenderable; import com.shavenpuppy.jglib.sprites.SimpleRenderer; import static org.lwjgl.opengl.GL11.*; /** * A GLString is a renderable string of characters * * @author: cas */ public class GLString implements SimpleRenderable { private GLFont font; private char[] text; private int[] x, ox; private int[] y, oy; private GLGlyphBuffer buffer; private int xpos, ypos; private int length; private boolean changed; // Used by getSize() and getGlyphIndex(): private static final Rectangle temp = new Rectangle(); // The entire size of the string, after a layout private final Rectangle size = new Rectangle(); private boolean resized = false; private boolean coloured; private ReadableColor topColour, bottomColour; private int alpha = 255; /** * GLString constructor comment. */ public GLString(int length) { super(); text = new char[length]; x = new int[length]; y = new int[length]; ox = new int[length]; oy = new int[length]; buffer = new GLGlyphBuffer(length); this.length = 0; } /** * @param coloured the coloured to set */ public void setColoured(boolean coloured) { this.coloured = coloured; } /** * @return the coloured */ public boolean isColoured() { return coloured; } public GLString(String text, GLFont font) { this(text == null ? 0 : text.length()); setFont(font); setText(text); } /** * Returns the capacity */ public int capacity() { return text.length; } /** * Returns the index of the glyph at x (relative to origin 0, 0 of this glstring). * Don't forget this isn't necesarily the character position in the string! Some characters * get turned into more than one glyph which screws everything up. Nor will it work if you * manually start offsetting or positioning glyphs. * Returns -1 if there's no hit. */ public int getCharIndexAt(float xx, float yy) { // Layout and resize if necessary layout(); // Recalc size getBounds(size); for (int i = 0; i < buffer.length; i++) { if (buffer.glyph[i] != null) { temp.setBounds((x[i] + xpos), size.getY(), (buffer.glyph[i].getWidth()), size.getHeight()); if (temp.contains((int)xx, (int)yy)) { return i; } } } // Look to see if we're off the end if (yy >= size.getY() && yy <= size.getY() + size.getHeight() && xx >= size.getX() + size.getWidth()) { return buffer.length; } else if (yy >= size.getY() && yy <= size.getY() + size.getHeight() && xx < size.getX()) { return 0; } else { return -1; } } /** * Returns the glyph at the specified location in the string */ public GLGlyph getGlyph(int index) { return buffer.glyph[index]; } /** * Returns the index of the glyph at x,y (relative to origin 0, 0 of this glstring). * Don't forget this isn't necessarily the character position in the string! Some characters * get turned into more than one glyph which screws everything up. * Returns -1 if there's no hit. */ public int getGlyphIndexAt(float xx, float yy) { // Layout and resize if necessary layout(); for (int i = 0; i < buffer.glyph.length; i++) { if (buffer.glyph[i] != null) { temp.setBounds( (x[i] + ox[i] + xpos), (y[i] + oy[i] + ypos), (x[i] + ox[i] + xpos + buffer.glyph[i].getWidth()), (y[i] + oy[i] + ypos + buffer.glyph[i].getHeight())); if (temp.contains((int)xx, (int)yy)) { return i; } } } return -1; } /** * Returns the size of this String and stashes it in the incoming WritableRectangle. * @param ret The destination rectangle */ public Rectangle getBounds(Rectangle ret) { // Layout if necessary layout(); if (ret == null) { ret = new Rectangle(); } // If no change in size since last time then just return the previously calculated size if (!resized) { ret.setBounds(size); return ret; } boolean doneOne = false; for (int i = 0; i < length; i++) { GLGlyph g = buffer.glyph[i]; if (g != null) { if (!doneOne) { ret.setBounds ( (x[i] + ox[i] + xpos), (y[i] + oy[i] + ypos), buffer.glyph[i].getWidth(), buffer.glyph[i].getHeight() ); doneOne = true; } else { temp.setBounds ( (x[i] + ox[i] + xpos), (y[i] + oy[i] + ypos), buffer.glyph[i].getWidth(), buffer.glyph[i].getHeight() ); ret.union(temp, ret); } } } // Remember the size size.setBounds(ret); // No longer need to recalculate size resized = false; return ret; } /** * Returns the contents as a string. */ public String getText() { return new String(text, 0, length); } /** * Returns the contents as a string. */ public String getText(int start, int count) { return new String(text, start, count); } /** * Does this string need laying out? */ protected boolean isChanged() { return changed; } /** * Layout the text. Also computes the bounds of the string. */ public void layout() { if (font == null || !isChanged()) { return; } buffer = font.getGlyphBuffer(text, 0, length, buffer); for (int i = 0; i < buffer.length; i++) { if (buffer.glyph[i] != null) { x[i] = buffer.glyph[i].getXpos(); y[i] = buffer.glyph[i].getYpos(); } } // No longer need to layout setChanged(false); // Recalculate the size if asked later resized = true; } /** * Returns the current length of the string */ public int length() { return length; } @Override public void render(SimpleRenderer renderer) { if (font == null) { throw new OpenGLException("Null font in "+this); } layout(); renderer.glRender(new GLRenderable() { @Override public void render() { // Bind the font's texture first font.getTexture().render(); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); } }); // Render all the glyphs for (int i = 0; i < buffer.length; i++) { GLGlyph g = buffer.glyph[i]; if (g != null) { g.setLocation(x[i] + ox[i] + xpos, y[i] + oy[i] + ypos); if (coloured) { g.render(topColour, bottomColour, alpha, renderer); } else { g.render(renderer); } } } } public void setAlpha(int alpha) { this.alpha = alpha; } /** * Mark this string as needing layout or not. */ protected void setChanged(boolean changed) { this.changed = changed; } /** * Replaces the font */ public void setFont(GLFont font) { if (this.font == font) { return; } if (font == null) { throw new NullPointerException("Can't set font to null"); } setChanged(true); this.font = font; resized = true; } /** * @return Returns the font. */ public GLFont getFont() { return font; } /** * @return Returns the final coordinates of the specified glyph */ public Rectangle getGlyphBounds(int index, Rectangle dest) { if (isChanged()) { layout(); } if (dest == null) { dest = new Rectangle(); } if (index < 0) { dest.setBounds(xpos, ypos, 0, font.getAscent() + font.getDescent()); } else { GLGlyph g = buffer.glyph[index]; if (g != null) { dest.setBounds(x[index] + ox[index] + xpos, y[index] + oy[index] + ypos, g.getAdvance(), font.getAscent() + font.getDescent()); } } return dest; } /** * Sets a glyph's location */ public void setGlyphLocation(int index, int xx, int yy) { x[index] = xx; y[index] = yy; resized = true; } /** * Sets a glyph's location relative to its original location at 0,0 */ public void setGlyphOffset(int index, int xx, int yy) { ox[index] = xx; oy[index] = yy; resized = true; } /** * Sets the length of the string; must be less than the capacity or it gets capped. */ public void setLength(int length) { this.length = Math.min(capacity(), length); setChanged(true); resized = true; } /** * Sets the location of the whole string. */ public void setLocation(int xp, int yp) { if (this.xpos != xp || this.ypos != yp) { this.xpos = xp; this.ypos = yp; resized = true; } } public int getX() { return xpos; } public int getY() { return ypos; } /** * @return the location of the string */ public Point getLocation(Point ret) { if (ret == null) { ret = new Point(); } ret.setLocation(xpos, ypos); return ret; } /** * Set the text. */ public void setText(String s) { if (s == null) { s = ""; } length = Math.min(text.length, s.length()); s.getChars(0, length, text, 0); setChanged(true); resized = true; } /** * Set a portion of the text. */ public void setText(String s, int start, int end, int destpos) { if (s == null) { s = ""; } length = Math.max(destpos + (end - start), length); s.getChars(start, end, text, destpos); setChanged(true); resized = true; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "GLString["+(new String(text))+","+x+", "+y+"]"; } public void setColour(ReadableColor c) { setColoured(true); bottomColour = c; topColour = c; } public void setBottomColour(ReadableColor c) { setColoured(true); bottomColour = c; } public void setTopColour(ReadableColor c) { setColoured(true); topColour = c; } public ReadableColor getTopColour() { return topColour; } public ReadableColor getBottomColour() { return bottomColour; } public int getAlpha() { return alpha; } }