/* * 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 java.io.BufferedInputStream; import java.net.URL; import org.lwjgl.util.Point; import org.w3c.dom.Element; import com.shavenpuppy.jglib.Font; import com.shavenpuppy.jglib.Glyph; import com.shavenpuppy.jglib.Resource; import com.shavenpuppy.jglib.Resources; import com.shavenpuppy.jglib.resources.FontResource; import com.shavenpuppy.jglib.util.XMLUtil; import static org.lwjgl.opengl.GL11.*; /** * A font suitable for rendering with GL. */ public class GLFont extends Resource implements GLRenderable { private static final long serialVersionUID = 1L; /** Handy point */ private static final Point tempPoint = new Point(); /* * Resource data */ /** The URL to load the font from */ protected String url; /** Whether this is an ascii font or not */ protected boolean ascii; /** OR... use minMode/magMode instead of linear/fullscreen */ private int minMode, magMode; /** Font scale */ private float scale = 1.0f; /* * Transient data */ /** The font resource */ protected transient FontResource fontResource; /** The font */ protected transient Font font; /** The texture holding the font's image */ protected transient GLTexture texture; /** Whether to discard the font image afterwards */ protected transient boolean discardFontImage; /** Whether to discard the fontResource afterwards */ protected transient boolean discardFontResource; /** * Resource constructor * @param name */ public GLFont(String name) { super(name); } /** * GLFont constructor comment. * @param name java.lang.String */ public GLFont(String name, String url, boolean ascii) { super(name); this.url = url; this.ascii = ascii; discardFontImage = true; discardFontResource = true; } /** * GLFont constructor comment. * @param name java.lang.String */ public GLFont(String name, Font font, boolean ascii) { super(name); if (font == null) { throw new NullPointerException("Null 'Font' parameter for GLFont "+name); } this.font = font; this.ascii = ascii; this.minMode = GL_LINEAR_MIPMAP_LINEAR; this.magMode = GL_LINEAR; discardFontResource = true; } /** * GLFont constructor comment. * @param name java.lang.String */ public GLFont(String name, FontResource fontResource, boolean ascii) { super(name); this.fontResource = fontResource; this.ascii = ascii; } @Override protected void doCreate() { try { // First get the image if necessary if (url != null) { if (url.startsWith("classpath:")) { // Load directly from a serialised Font in the classpath BufferedInputStream bis = new BufferedInputStream(getClass().getClassLoader().getResourceAsStream(url.substring(10))); font = new Font(); font.readExternal(bis); bis.close(); } else if (url.startsWith("resource:")) { // Load directly from Resources fontResource = (FontResource) Resources.get(url.substring(9)); } else { // Load from a URL BufferedInputStream bis = new BufferedInputStream(new URL(url).openStream()); font = new Font(); font.readExternal(bis); bis.close(); } } if (fontResource != null) { font = fontResource.getFont(); } if (font == null) { throw new Exception("No font specified for GLFont "+this); } // Create a texture for the font texture = new GLTexture(getName(), font.getImage(), GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, minMode, magMode, false); texture.create(); if (discardFontImage) { font.getImage().dispose(); } if (discardFontResource) { fontResource = null; } } catch (Exception e) { throw new RuntimeException("Failed to create font "+this+" font:"+font+" minMode:"+GLUtil.recode(minMode)+" magMode:"+GLUtil.recode(magMode)); } } @Override protected void doDestroy() { texture.destroy(); texture = null; } /** * Convenience method * @return the font's ascent */ public int getAscent() { return (int) (font.getAscent() * scale); } /** * Convenience method * @return the font's descent */ public int getDescent() { return (int) (font.getDescent() * scale); } /** * Access to the underlying font * @return the font used */ public Font getFont() { return font; } /** * @return the scale */ public float getScale() { return scale; } /** * Convenience method * @return the font's height (already scaled) */ public int getHeight() { return (int) ((font.getAscent() + font.getDescent()) * scale); } /** * Convenience method * @return the font's leading (already scaled) */ public int getLeading() { return (int) (font.getLeading() * scale); } /** * @return the font's underlying texture */ public GLBaseTexture getTexture() { assert isCreated() : this + " is not created yet"; return texture; } /** * Binds the font's texture */ @Override public void render() { assert isCreated() : this + " is not created yet"; texture.render(); } /** * Initialize a GLGlyphBuffer from an array of characters. The characters are laid * out nicely in the correct positions. * @param text The array of characters * @param start The position in the text array at which to start * @param end The position in the text array before which to finish * @param buffer A pre-existing GLGlyphBuffer, or null, if a new buffer is to be created * @return a GLGlyphBuffer with glyphs in for the specified text */ public GLGlyphBuffer getGlyphBuffer(char[] text, int start, int end, GLGlyphBuffer buffer) { // Create a glyph buffer big enough if (buffer == null || buffer.capacity() < end - start) { buffer = new GLGlyphBuffer(end - start); } int count = 0; Glyph last = null; int penX = 0; for (int i = start; i < end; i ++) { Glyph next = font.map(text[i]); if (buffer.glyph[count] == null) { buffer.glyph[count] = new GLGlyph(texture, next, scale); } else { buffer.glyph[count].init(texture, next); } next.getBearing(tempPoint); // Scale bearing tempPoint.setLocation((int)(tempPoint.getX() * scale), (int)(tempPoint.getY() * scale)); // Scale kerning int kerning = (int) (next.getKerningAfter(last) * scale); buffer.glyph[count++].setLocation(tempPoint.getX() + penX - kerning, tempPoint.getY()); penX += (int) (next.getAdvance() * scale) + kerning; last = next; } buffer.length = count; return buffer; } /** * Map a single char to a new GLGlyph * @param c The character * @return a GLGlyph */ public GLGlyph map(char c) { return new GLGlyph(texture, font.map(c), scale); } @Override public void archive() { url = null; } @Override public void load(Element element, Resource.Loader loader) throws Exception { super.load(element, loader); url = XMLUtil.getString(element, "url"); ascii = XMLUtil.getBoolean(element, "ascii", true); scale = XMLUtil.getFloat(element, "scale", 1.0f); String minModeS = XMLUtil.getString(element, "minmode", null); String magModeS = XMLUtil.getString(element, "magmode", null); if (minModeS != null) { minMode = GLUtil.decode(minModeS); } else { minMode = GL_LINEAR_MIPMAP_LINEAR; } if (magModeS != null) { magMode = GLUtil.decode(magModeS); } else { magMode = GL_LINEAR; } } }