com.onkiup.minedroid.gui.betterfonts.StringCache.java Source code

Java tutorial

Introduction

Here is the source code for com.onkiup.minedroid.gui.betterfonts.StringCache.java

Source

/*
 * Minecraft OpenType Font Support Mod
 *
 * Copyright (C) 2012 Wojciech Stryjewski <thvortex@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>..
 */

package com.onkiup.minedroid.gui.betterfonts;

import java.lang.ref.WeakReference;
import java.util.*;
import java.awt.font.GlyphVector;
import java.awt.Font;
import java.awt.Point;
import java.text.Bidi;

import com.onkiup.minedroid.gui.GuiManager;
import com.onkiup.minedroid.gui.views.View;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import org.lwjgl.opengl.GL11;

/**
 * The StringCache is the public interface for rendering of all Unicode strings using OpenType fonts. It caches the glyph layout
 * of individual strings, and it uses a GlyphCache instance to cache the pre-rendered images for individual glyphs. Once a string
 * and its glyph images are cached, the critical path in renderString() will draw the glyphs as fast as if using a bitmap font.
 * Strings are cached using weak references through a two layer string cache. Strings that are no longer in use by Minecraft will
 * be evicted from the cache, while the pre-rendered images of individual glyphs remains cached forever. The following diagram
 * illustrates how this works:
 * <p/>
 * <pre>
 * String passed to            Key object considers      Entry object holds       Each Glyph object      GlyphCache.Entry stores
 * renderString();             all ASCII digits equal    an array of Glyph        belongs to only one    the texture ID, image
 * mapped by weak              to zero ('0');            objects which may        Entry object; it has   width/height and
 * weakRefCache                mapped by weak            not directly             the glyph x/y pos      normalized texture
 * to Key object               stringCahe to Entry       correspond to Unicode    within the string      coordinates.
 *                                                       chars in string
 * String("Fi1") ------------\                                               ---> Glyph("F") ----------> GlyphCache.Entry("F")
 *                    N:1     \                1:1                     1:N  /                    N:1
 * String("Fi4") ------------> Key("Fi0") -------------> Entry("Fi0") -----+----> Glyph("i") ----------> GlyphCache.Entry("i")
 *                                                                          \                    N:1
 *                                                                           ---> Glyph("0") -----\
 *                                                                                                 ----> GlyphCache.Entry("0")
 *                                                                           ---> Glyph("0") -----/
 *                    N:1                      1:1                     1:N  /                    N:1
 * String("Be1") ------------> Key("Be0") -------------> Entry("Be0") -----+----> Glyph("e") ----------> GlyphCache.Entry("e")
 *                                                                          \                    N:1
 *                                                                           ---> Glyph("B") ----------> GlyphCache.Entry("B")
 * </pre>
 */
public class StringCache {
    /**
     * Vertical adjustment (in pixels * 2) to string position because Minecraft uses top of string instead of baseline
     */
    private static final int BASELINE_OFFSET = 7;

    /**
     * Offset from the string's baseline as which to draw the underline (in pixels)
     */
    private static final int UNDERLINE_OFFSET = 1;

    /**
     * Thickness of the underline (in pixels)
     */
    private static final int UNDERLINE_THICKNESS = 2;

    /**
     * Offset from the string's baseline as which to draw the strikethrough line (in pixels)
     */
    private static final int STRIKETHROUGH_OFFSET = -6;

    /**
     * Thickness of the strikethrough line (in pixels)
     */
    private static final int STRIKETHROUGH_THICKNESS = 2;

    private Font currentFont;

    /**
     * Reference to the unicode.FontRenderer class. Needed for creating GlyphVectors and retrieving glyph texture coordinates.
     */
    private GlyphCache glyphCache;

    /**
     * Color codes from original FontRender class. First 16 entries are the primary chat colors; second 16 are darker versions
     * used for drop shadows.
     */
    private int colorTable[];

    /**
     * A cache of recently seen strings to their fully layed-out state, complete with color changes and texture coordinates of
     * all pre-rendered glyph images needed to display this string. The weakRefCache holds strong references to the Key
     * objects used in this map.
     */
    private WeakHashMap<Key<String>, WeakHashMap<Font, Entry>> stringCache = new WeakHashMap<Key<String>, WeakHashMap<Font, Entry>>();

    /**
     * Every String passed to the public renderString() function is added to this WeakHashMap. As long as As long as Minecraft
     * continues to hold a strong reference to the String object (i.e. from TileEntitySign and ChatLine) passed here, the
     * weakRefCache map will continue to hold a strong reference to the Key object that said strings all map to (multiple strings
     * in weakRefCache can map to a single Key if those strings only differ by their ASCII digits).
     */
    private WeakHashMap<String, Key<String>> weakRefCache = new WeakHashMap();

    /**
     * Temporary Key object re-used for lookups with stringCache.get(). Using a temporary object like this avoids the overhead
     * of allocating new objects in the critical rendering path. Of course, new Key objects are always created when adding
     * a mapping to stringCache.
     */
    private Key stringKey = new Key<String>();

    private Key fontKey = new Key<Font>();

    /**
     * Pre-cached glyphs for the ASCII digits 0-9 (in that order). Used by renderString() to substiture digit glyphs on the fly
     * as a performance boost. The speed up is most noticable on the F3 screen which rapidly displays lots of changing numbers.
     * The 4 element array is index by the font style (combination of Font.PLAIN, Font.BOLD, and Font.ITALIC), and each of the
     * nested elements is index by the digit value 0-9.
     */
    private Glyph[][] digitGlyphs = new Glyph[4][];

    /**
     * True if digitGlyphs[] has been assigned and cacheString() can begin replacing all digits with '0' in the string.
     */
    private boolean digitGlyphsReady = false;

    /**
     * If true, then enble GL_BLEND in renderString() so anti-aliasing font glyphs show up properly.
     */
    private boolean antiAliasEnabled = false;

    protected String currentFontName = "";

    /**
     * Reference to the main Minecraft thread that created this GlyphCache object. Starting with Minecraft 1.3.1, it is possible
     * for GlyphCache.cacheGlyphs() to be invoked from the TcpReaderThread while processing a chat packet and computing the width
     * of the incoming chat text. Unfortunately, if cacheGlyphs() makes any OpenGL calls from any thread except the main one,
     * it will crash LWJGL with a NullPointerException. By remembering the initial thread and comparing it later against
     * Thread.currentThread(), the StringCache code can avoid calling cacheGlyphs() when it's not safe to do so.
     */
    private Thread mainThread;

    /**
     * Wraps a String and acts as the key into stringCache. The hashCode() and equals() methods consider all ASCII digits
     * to be equal when hashing and comparing Key objects together. Therefore, Strings which only differ in their digits will
     * be all hashed together into the same entry. The renderString() method will then substitute the correct digit glyph on
     * the fly. This special digit handling gives a significant speedup on the F3 debug screen.
     */
    static private class Key<Type> {
        /**
         * A copy of the String which this Key is indexing. A copy is used to avoid creating a strong reference to the original
         * passed into renderString(). When the original String is no longer needed by Minecraft, it will be garbage collected
         * and the WeakHashMaps in StringCache will allow this Key object and its associated Entry object to be garbage
         * collected as well.
         */
        public Type key;

        /**
         * Computes a hash code on str in the same manner as the String class, except all ASCII digits hash as '0'
         *
         * @return the augmented hash code on str
         */
        @Override
        public int hashCode() {
            int code = 0;
            if (key instanceof String) {
                String str = (String) key;
                int length = str.length();

                /*
                 * True if a section mark character was last seen. In this case, if the next character is a digit, it must
                 * not be considered equal to any other digit. This forces any string that differs in color codes only to
                 * have a separate entry in the StringCache.
                 */
                boolean colorCode = false;

                for (int index = 0; index < length; index++) {
                    char c = str.charAt(index);
                    if (c >= '0' && c <= '9' && !colorCode) {
                        c = '0';
                    }
                    code = (code * 31) + c;
                    colorCode = (c == '\u00A7');
                }
            } else {
                code = key.hashCode();
            }

            return code;
        }

        /**
         * Compare str against another object (specifically, the object's string representation as returned by toString).
         * All ASCII digits are considered equal by this method, as long as they are at the same index within the string.
         *
         * @return true if the strings are the identical, or only differ in their ASCII digits
         */
        @Override
        public boolean equals(Object o) {
            /*
             * There seems to be a timing window inside WeakHashMap itself where a null object can be passed to this
             * equals() method. Presumably it happens between computing a hash code for the weakly referenced Key object
             * while it still exists and calling its equals() method after it was garbage collected.
             */
            if (o == null) {
                return false;
            }

            if (!(key instanceof String))
                return key.equals(o);

            /* Calling toString on a String object simply returns itself so no new object allocation is performed */
            String str = (String) key;
            String other = o.toString();
            int length = str.length();

            if (length != other.length()) {
                return false;
            }

            /*
             * True if a section mark character was last seen. In this case, if the next character is a digit, it must
             * not be considered equal to any other digit. This forces any string that differs in color codes only to
             * have a separate entry in the StringCache.
             */
            boolean colorCode = false;

            for (int index = 0; index < length; index++) {
                char c1 = str.charAt(index);
                char c2 = other.charAt(index);

                if (c1 != c2 && (c1 < '0' || c1 > '9' || c2 < '0' || c2 > '9' || colorCode)) {
                    return false;
                }
                colorCode = (c1 == '\u00A7');
            }

            return true;
        }

        /**
         * Returns the contained String object within this Key.
         *
         * @return the str object
         */
        @Override
        public String toString() {
            return key.toString();
        }
    }

    /**
     * This entry holds the layed out glyph positions for the cached string along with some relevant metadata.
     */
    static public class Entry {
        /**
         * A weak reference back to the Key object in stringCache that maps to this Entry.
         */
        public WeakReference<Key> keyRef;

        /**
         * The total horizontal advance (i.e. width) for this string in pixels.
         */
        public int advance;

        /**
         * Array of fully layed out glyphs for the string. Sorted by logical order of characters (i.e. glyph.stringIndex)
         */
        public Glyph glyphs[];

        /**
         * Array of color code locations from the original string
         */
        public ColorCode colors[];

        /**
         * True if the string uses strikethrough or underlines anywhere and needs an extra pass in renderString()
         */
        public boolean specialRender;

        public double height = 0;
        public double baseline = 0;

        public int getScaledAdvance() {
            int f = GuiManager.getScale().getScaleFactor();
            return (int) Math.round((advance) / (float) f);
        }

        public double getScaledHeight() {
            int f = GuiManager.getScale().getScaleFactor();
            return Math.ceil((height + baseline) / (float) f);
        }

        public double getScaledBase() {
            return Math.ceil(baseline / (float) GuiManager.getScale().getScaleFactor());
        }
    }

    /**
     * Identifies the location and value of a single color code in the original string
     */
    static private class ColorCode implements Comparable<Integer> {
        /**
         * Bit flag used with renderStyle to request the underline style
         */
        public static final byte UNDERLINE = 1;

        /**
         * Bit flag used with renderStyle to request the strikethrough style
         */
        public static final byte STRIKETHROUGH = 2;

        /**
         * The index into the original string (i.e. with color codes) for the location of this color code.
         */
        public int stringIndex;

        /**
         * The index into the stripped string (i.e. with no color codes) of where this color code would have appeared
         */
        public int stripIndex;

        /**
         * The numeric color code (i.e. index into the colorCode[] array); -1 to reset default color
         */
        public byte colorCode;

        /**
         * Combination of Font.PLAIN, Font.BOLD, and Font.ITALIC specifying font specific syles
         */
        public byte fontStyle;

        /**
         * Combination of UNDERLINE and STRIKETHROUGH flags specifying effects performed by renderString()
         */
        public byte renderStyle;

        /**
         * Performs numeric comparison on stripIndex. Allows binary search on ColorCode arrays in layoutStyle.
         *
         * @param i the Integer object being compared
         * @return either -1, 0, or 1 if this < other, this == other, or this > other
         */
        @Override
        public int compareTo(Integer i) {
            return (stringIndex == i.intValue()) ? 0 : (stringIndex < i.intValue()) ? -1 : 1;
        }
    }

    /**
     * Identifies a single glyph in the layed-out string. Includes a reference to a GlyphCache.Entry with the OpenGL texture ID
     * and position of the pre-rendered glyph image, and includes the x/y pixel coordinates of where this glyph occurs within
     * the string to which this Glyph object belongs.
     */
    static public class Glyph implements Comparable<Glyph> {
        /**
         * The index into the original string (i.e. with color codes) for the character that generated this glyph.
         */
        public int stringIndex;

        /**
         * Texture ID and position/size of the glyph's pre-rendered image within the cache texture.
         */
        public GlyphCache.Entry texture;

        /**
         * Glyph's horizontal position (in pixels) relative to the entire string's baseline
         */
        public int x;

        /**
         * Glyph's vertical position (in pixels) relative to the entire string's baseline
         */
        public int y;

        /**
         * Glyph's horizontal advance (in pixels) used for strikethrough and underline effects
         */
        public int advance;

        /**
         * Allows arrays of Glyph objects to be sorted. Performs numeric comparison on stringIndex.
         *
         * @param o the other Glyph object being compared with this one
         * @return either -1, 0, or 1 if this < other, this == other, or this > other
         */
        @Override
        public int compareTo(Glyph o) {
            return (stringIndex == o.stringIndex) ? 0 : (stringIndex < o.stringIndex) ? -1 : 1;
        }

        public int getScaledAdvance() {
            return (int) Math.ceil(advance / (float) GuiManager.getScale().getScaleFactor());
        }

        public int getScaledWidth() {
            return (int) Math.ceil(texture.width / (float) GuiManager.getScale().getScaleFactor());
        }
    }

    public StringCache() {
        colorTable = new int[32];
        for (int var5 = 0; var5 < 32; ++var5) {
            int var6 = (var5 >> 3 & 1) * 85;
            int var7 = (var5 >> 2 & 1) * 170 + var6;
            int var8 = (var5 >> 1 & 1) * 170 + var6;
            int var9 = (var5 >> 0 & 1) * 170 + var6;
            if (var5 == 6) {
                var7 += 85;
            }

            if (var5 >= 16) {
                var7 /= 4;
                var8 /= 4;
                var9 /= 4;
            }

            colorTable[var5] = (var7 & 255) << 16 | (var8 & 255) << 8 | var9 & 255;
        }

        /* StringCache is created by the main game thread; remember it for later thread safety checks */
        mainThread = Thread.currentThread();

        glyphCache = new GlyphCache();

        /* Pre-cache the ASCII digits to allow for fast glyph substitution */
        //        cacheDightGlyphs();
    }

    /**
     * A single StringCache object is allocated by Minecraft's FontRenderer which forwards all string drawing and requests for
     * string width to this class.
     *
     * @param colors 32 element array of RGBA colors corresponding to the 16 text color codes followed by 16 darker version of the
     *               color codes for use as drop shadows
     */
    public StringCache(int colors[]) {
        /* StringCache is created by the main game thread; remember it for later thread safety checks */
        mainThread = Thread.currentThread();

        glyphCache = new GlyphCache();
        colorTable = colors;

        /* Pre-cache the ASCII digits to allow for fast glyph substitution */
        //        cacheDightGlyphs();
    }

    /**
     * Change the default font used to pre-render glyph images. If this method is called at runtime, the string cache is flushed so that
     * all visible strings will be immediately re-layed out using the new font selection.
     *
     * @param name the new font name
     * @param size the new point size
     */
    public Font setDefaultFont(String name, int size, boolean antiAlias) {
        // do not change the font if it's already set
        if (currentFont != null && currentFontName.equals(name) && currentFont.getSize() == size
                && antiAlias == antiAliasEnabled)
            return currentFont;
        /* Change the font in the glyph cache and clear the string cache so all strings have to be re-layed out and re-rendered */
        currentFont = glyphCache.setDefaultFont(name, size, antiAlias);
        antiAliasEnabled = antiAlias;
        //        weakRefCache.clear();
        //        stringCache.clear();

        /* Pre-cache the ASCII digits to allow for fast glyph substitution */
        cacheDightGlyphs();
        return currentFont;
    }

    public void setDefaultFont(Font f, boolean antiAlias) {
        if (f.equals(currentFont))
            return;
        glyphCache.setDefaultFont(f, true);
        antiAliasEnabled = antiAlias;
        cacheDightGlyphs();
    }

    /**
     * Pre-cache the ASCII digits to allow for fast glyph substitution. Called once from the constructor and called any time the font selection
     * changes at runtime via setDefaultFont().
     */
    private void cacheDightGlyphs() {
        /* Need to cache each font style combination; the digitGlyphsReady = false disabled the normal glyph substitution mechanism */
        digitGlyphsReady = false;
        digitGlyphs[Font.PLAIN] = cacheString("0123456789").glyphs;
        digitGlyphs[Font.BOLD] = cacheString("l0123456789").glyphs;
        digitGlyphs[Font.ITALIC] = cacheString("o0123456789").glyphs;
        digitGlyphs[Font.BOLD | Font.ITALIC] = cacheString("lo0123456789").glyphs;
        digitGlyphsReady = true;
    }

    /**
     * Render a single-line string to the screen using the current OpenGL color. The (x,y) coordinates are of the uppet-left
     * corner of the string's bounding box, rather than the baseline position as is typical with fonts. This function will also
     * add the string to the cache so the next renderString() call with the same string is faster.
     *
     * @param str          the string being rendered; it can contain color codes
     * @param startX       the x coordinate to draw at
     * @param startY       the y coordinate to draw at
     * @param initialColor the initial RGBA color to use when drawing the string; embedded color codes can override the RGB component
     * @param shadowFlag   if true, color codes are replaces by a darker version used for drop shadows
     * @return the total advance (horizontal distance) of this string
     * @todo Add optional NumericShaper to replace ASCII digits with locale specific ones
     * @todo Add support for the "k" code which randomly replaces letters on each render (used only by splash screen)
     * @todo Pre-sort by texture to minimize binds; can store colors per glyph in string cache
     * @todo Optimize the underline/strikethrough drawing to draw a single line for each run
     */
    public Entry renderString(String str, int startX, int startY, int initialColor, boolean shadowFlag) {
        /* Check for invalid arguments */
        if (str == null || str.isEmpty()) {
            return null;
        }
        View.resetBlending();
        GlStateManager.enableTexture2D();
        float scale = GuiManager.getDisplayScale();
        float f = GuiManager.getScale().getScaleFactor();

        /* Make sure the entire string is cached before rendering and return its glyph representation */
        Entry entry = cacheString(str);

        /* Adjust the baseline of the string because the startY coordinate in Minecraft is for the top of the string */
        startY += entry.getScaledBase();

        /* Color currently selected by color code; reapplied to Tessellator instance after glBindTexture() */
        int color = initialColor;

        /* Track which texture is currently bound to minimize the number of glBindTexture() and Tessellator.draw() calls needed */
        int boundTextureName = 0;

        /*
         * This color change will have no effect on the actual text (since colors are included in the Tessellator vertex
         * array), however GuiEditSign of all things depends on having the current color set to white when it renders its
         * "Edit sign message:" text. Otherwise, the sign which is rendered underneath would look too dark.
         */
        GL11.glColor3f(color >> 16 & 0xff, color >> 8 & 0xff, color & 0xff);

        /*
         * Enable GL_BLEND in case the font is drawn anti-aliased because Minecraft itself only enables blending for chat text
         * (so it can fade out), but not GUI text or signs. Minecraft uses multiple blend functions so it has to be specified here
         * as well for consistent blending. To reduce the overhead of OpenGL state changes and making native LWJGL calls, this
         * function doesn't try to save/restore the blending state. Hopefully everything else that depends on blending in Minecraft
         * will set its own state as needed.
         */
        if (antiAliasEnabled) {
            GL11.glEnable(GL11.GL_BLEND);
            GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        }

        /* Using the Tessellator to queue up data in a vertex array and then draw all at once should be faster than immediate mode */
        Tessellator tessellator = Tessellator.getInstance();
        tessellator.getWorldRenderer().startDrawingQuads();
        tessellator.getWorldRenderer().setColorRGBA(color >> 16 & 0xff, color >> 8 & 0xff, color & 0xff,
                color >> 24 & 0xff);

        /* The currently active font syle is needed to select the proper ASCII digit style for fast replacement */
        int fontStyle = Font.PLAIN;

        for (int glyphIndex = 0, colorIndex = 0; glyphIndex < entry.glyphs.length; glyphIndex++) {
            /*
             * If the original string had a color code at this glyph's position, then change the current GL color that gets added
             * to the vertex array. Note that only the RGB component of the color is replaced by a color code; the alpha component
             * of the original color passed into this function will remain. The while loop handles multiple consecutive color codes,
             * in which case only the last such color code takes effect.
             */
            while (colorIndex < entry.colors.length
                    && entry.glyphs[glyphIndex].stringIndex >= entry.colors[colorIndex].stringIndex) {
                color = applyColorCode(entry.colors[colorIndex].colorCode, initialColor, shadowFlag);
                fontStyle = entry.colors[colorIndex].fontStyle;
                colorIndex++;
            }

            /* Select the current glyph's texture information and horizontal layout position within this string */
            Glyph glyph = entry.glyphs[glyphIndex];
            GlyphCache.Entry texture = glyph.texture;
            int glyphX = glyph.x;

            /*
             * Replace ASCII digits in the string with their respective glyphs; strings differing by digits are only cached once.
             * If the new replacement glyph has a different width than the original placeholder glyph (e.g. the '1' glyph is often
             * narrower than other digits), re-center the new glyph over the placeholder's position to minimize the visual impact
             * of the width mismatch.
             */
            char c = str.charAt(glyph.stringIndex);
            if (c >= '0' && c <= '9') {
                int oldWidth = texture.width;
                texture = digitGlyphs[fontStyle][c - '0'].texture;
                int newWidth = texture.width;
                glyphX += (oldWidth - newWidth) >> 1;
            }

            /*
             * Make sure the OpenGL texture storing this glyph's image is bound (if not already bound). All pending glyphs in the
             * Tessellator's vertex array must be drawn before switching textures, otherwise they would erroneously use the new
             * texture as well.
             */
            if (boundTextureName != texture.textureName) {
                tessellator.draw();
                tessellator.getWorldRenderer().startDrawingQuads();
                tessellator.getWorldRenderer().setColorRGBA(color >> 16 & 0xff, color >> 8 & 0xff, color & 0xff,
                        color >> 24 & 0xff);

                GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture.textureName);
                GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
                GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
                boundTextureName = texture.textureName;
            }

            /* The divide by 2.0F is needed to align with the scaled GUI coordinate system; startX/startY are already scaled */
            float x1 = startX + (glyphX) / f;
            float x2 = startX + (glyphX + texture.width) / f;
            float y1 = startY + (glyph.y) / f;
            float y2 = startY + (glyph.y + texture.height) / f;

            tessellator.getWorldRenderer().addVertexWithUV(x1, y1, 0, texture.u1, texture.v1);
            tessellator.getWorldRenderer().addVertexWithUV(x1, y2, 0, texture.u1, texture.v2);
            tessellator.getWorldRenderer().addVertexWithUV(x2, y2, 0, texture.u2, texture.v2);
            tessellator.getWorldRenderer().addVertexWithUV(x2, y1, 0, texture.u2, texture.v1);
        }

        /* Draw any remaining glyphs in the Tessellator's vertex array (there should be at least one glyph pending) */
        tessellator.draw();

        /* Draw strikethrough and underlines if the string uses them anywhere */
        if (entry.specialRender) {
            int renderStyle = 0;

            /* Use initial color passed to renderString(); disable texturing to draw solid color lines */
            color = initialColor;
            GL11.glDisable(GL11.GL_TEXTURE_2D);
            tessellator.getWorldRenderer().startDrawingQuads();
            tessellator.getWorldRenderer().setColorRGBA(color >> 16 & 0xff, color >> 8 & 0xff, color & 0xff,
                    color >> 24 & 0xff);

            for (int glyphIndex = 0, colorIndex = 0; glyphIndex < entry.glyphs.length; glyphIndex++) {
                /*
                 * If the original string had a color code at this glyph's position, then change the current GL color that gets added
                 * to the vertex array. The while loop handles multiple consecutive color codes, in which case only the last such
                 * color code takes effect.
                 */
                while (colorIndex < entry.colors.length
                        && entry.glyphs[glyphIndex].stringIndex >= entry.colors[colorIndex].stringIndex) {
                    color = applyColorCode(entry.colors[colorIndex].colorCode, initialColor, shadowFlag);
                    renderStyle = entry.colors[colorIndex].renderStyle;
                    colorIndex++;
                }

                /* Select the current glyph within this string for its layout position */
                Glyph glyph = entry.glyphs[glyphIndex];

                /* The strike/underlines are drawn beyond the glyph's width to include the extra space between glyphs */
                int glyphSpace = glyph.advance - glyph.texture.width;

                /* Draw underline under glyph if the style is enabled */
                if ((renderStyle & ColorCode.UNDERLINE) != 0) {
                    /* The divide by 2.0F is needed to align with the scaled GUI coordinate system; startX/startY are already scaled */
                    float x1 = startX + (glyph.x - glyphSpace) / f;
                    float x2 = startX + (glyph.x + glyph.advance) / f;
                    float y1 = startY + (UNDERLINE_OFFSET) / f;
                    float y2 = startY + (UNDERLINE_OFFSET + UNDERLINE_THICKNESS) / f;

                    tessellator.getWorldRenderer().addVertex(x1, y1, 0);
                    tessellator.getWorldRenderer().addVertex(x1, y2, 0);
                    tessellator.getWorldRenderer().addVertex(x2, y2, 0);
                    tessellator.getWorldRenderer().addVertex(x2, y1, 0);
                }

                /* Draw strikethrough in the middle of glyph if the style is enabled */
                if ((renderStyle & ColorCode.STRIKETHROUGH) != 0) {
                    /* The divide by 2.0F is needed to align with the scaled GUI coordinate system; startX/startY are already scaled */
                    float x1 = startX + (glyph.x - glyphSpace) / f;
                    float x2 = startX + (glyph.x + glyph.advance) / f;
                    float y1 = startY + (STRIKETHROUGH_OFFSET) / f;
                    float y2 = startY + (STRIKETHROUGH_OFFSET + STRIKETHROUGH_THICKNESS) / f;

                    tessellator.getWorldRenderer().addVertex(x1, y1, 0);
                    tessellator.getWorldRenderer().addVertex(x1, y2, 0);
                    tessellator.getWorldRenderer().addVertex(x2, y2, 0);
                    tessellator.getWorldRenderer().addVertex(x2, y1, 0);
                }
            }

            /* Finish drawing the last strikethrough/underline segments */
            tessellator.draw();
            GL11.glEnable(GL11.GL_TEXTURE_2D);
        }

        /* Return total horizontal advance (slightly wider than the bounding box, but close enough for centering strings) */
        return entry;
    }

    /**
     * Return the number of characters in a string that will completly fit inside the specified width when rendered, with
     * or without prefering to break the line at whitespace instead of breaking in the middle of a word. This private provides
     * the real implementation of both sizeStringToWidth() and trimStringToWidth().
     *
     * @param str           the String to analyze
     * @param width         the desired string width (in GUI coordinate system)
     * @param breakAtSpaces set to prefer breaking line at spaces than in the middle of a word
     * @return the number of characters from str that will fit inside width
     */
    private int sizeString(String str, int width, boolean breakAtSpaces) {
        /* Check for invalid arguments */
        if (str == null || str.isEmpty()) {
            return 0;
        }

        /* Convert the width from GUI coordinate system to pixels */
        width += width;

        /* The glyph array for a string is sorted by the string's logical character position */
        Glyph glyphs[] = cacheString(str).glyphs;

        /* Index of the last whitespace found in the string; used if breakAtSpaces is true */
        int wsIndex = -1;

        /* Add up the individual advance of each glyph until it exceeds the specified width */
        int advance = 0, index = 0;
        while (index < glyphs.length && advance <= width) {
            /* Keep track of spaces if breakAtSpaces it set */
            if (breakAtSpaces) {
                char c = str.charAt(glyphs[index].stringIndex);
                if (c == ' ') {
                    wsIndex = index;
                } else if (c == '\n') {
                    wsIndex = index;
                    break;
                }
            }

            advance += glyphs[index].advance;
            index++;
        }

        /* Avoid splitting individual words if breakAtSpaces set; same test condition as in Minecraft's FontRenderer */
        if (index < glyphs.length && wsIndex != -1 && wsIndex < index) {
            index = wsIndex;
        }

        /* The string index of the last glyph that wouldn't fit gives the total desired length of the string in characters */
        return index < glyphs.length ? glyphs[index].stringIndex : str.length();
    }

    /**
     * Return the number of characters in a string that will completly fit inside the specified width when rendered.
     *
     * @param str   the String to analyze
     * @param width the desired string width (in GUI coordinate system)
     * @return the number of characters from str that will fit inside width
     */
    public int sizeStringToWidth(String str, int width) {
        return sizeString(str, width, true);
    }

    /**
     * Trim a string so that it fits in the specified width when rendered, optionally reversing the string
     *
     * @param str     the String to trim
     * @param width   the desired string width (in GUI coordinate system)
     * @param reverse if true, the returned string will also be reversed
     * @return the trimmed and optionally reversed string
     */
    public String trimStringToWidth(String str, int width, boolean reverse) {
        int length = sizeString(str, width, false);
        str = str.substring(0, length);

        if (reverse) {
            str = (new StringBuilder(str)).reverse().toString();
        }

        return str;
    }

    /**
     * Apply a new vertex color to the Tessellator instance based on the numeric chat color code. Only the RGB component of the
     * color is replaced by a color code; the alpha component of the original default color will remain.
     *
     * @param colorCode  the chat color code as a number 0-15 or -1 to reset the default color
     * @param color      the default color used when the colorCode is -1
     * @param shadowFlag ir true, the color code will select a darker version of the color suitable for drop shadows
     * @return the new RGBA color set by this function
     */
    private int applyColorCode(int colorCode, int color, boolean shadowFlag) {
        /* A -1 color code indicates a reset to the initial color passed into renderString() */
        if (colorCode != -1) {
            colorCode = shadowFlag ? colorCode + 16 : colorCode;
            color = colorTable[colorCode] & 0xffffff | color & 0xff000000;
        }

        Tessellator.getInstance().getWorldRenderer().setColorRGBA(color >> 16 & 0xff, color >> 8 & 0xff,
                color & 0xff, color >> 24 & 0xff);
        return color;
    }

    /**
     * Add a string to the string cache by perform full layout on it, remembering its glyph positions, and making sure that
     * every font glyph used by the string is pre-rendering. If this string has already been cached, then simply return its
     * existing Entry from the cahe. Note that for caching purposes, this method considers two strings to be identical if they
     * only differ in their ASCII digits; the renderString() method performs fast glyph substitution based on the actual digits
     * in the string at the time.
     *
     * @param str this String will be layed out and added to the cache (or looked up, if alraedy cached)
     * @return the string's cache entry containing all the glyph positions
     */
    public Entry cacheString(String str) {
        /*
         * New Key object allocated only if the string was not found in the StringCache using stringKey. This variable must
         * be outside the (entry == null) code block to have a temporary strong reference between the time when the Key is
         * added to stringCache and added to weakRefCache.
         */
        Key key;

        /* Either a newly created Entry object for the string, or the cached Entry if the string is already in the cache */
        Entry entry = null;

        /* Don't perform a cache lookup from other threads because the stringCache is not synchronized */
        if (mainThread == Thread.currentThread()) {
            /* If this string is already in the cache, simply return the cached Entry object */
            entry = getEntry(str);
        }

        /* If string is not cached (or not on main thread) then layout the string */
        if (entry == null) {
            /* layoutGlyphVector() requires a char[] so create it here and pass it around to avoid duplication later on */
            char text[] = str.toCharArray();

            /* Strip all color codes from the string */
            entry = new Entry();
            int length = stripColorCodes(entry, str, text);

            /* Layout the entire string, splitting it up by color codes and the Unicode bidirectional algorithm */
            List<Glyph> glyphList = new ArrayList();
            entry.advance = (int) layoutBidiString(glyphList, text, 0, length, entry.colors);

            /* Convert the accumulated Glyph list to an array for efficient storage */
            entry.glyphs = new Glyph[glyphList.size()];
            entry.glyphs = glyphList.toArray(entry.glyphs);

            /*
             * Sort Glyph array by stringIndex so it can be compared during rendering to the already sorted ColorCode array.
             * This will apply color codes in the string's logical character order and not the visual order on screen.
             */
            Arrays.sort(entry.glyphs);

            /* Do some post-processing on each Glyph object */
            int colorIndex = 0, shift = 0;
            for (int glyphIndex = 0; glyphIndex < entry.glyphs.length; glyphIndex++) {
                Glyph glyph = entry.glyphs[glyphIndex];

                /*
                 * Adjust the string index for each glyph to point into the original string with unstripped color codes. The while
                 * loop is necessary to handle multiple consecutive color codes with no visible glyphs between them. These new adjusted
                 * stringIndex can now be compared against the color stringIndex during rendering. It also allows lookups of ASCII
                 * digits in the original string for fast glyph replacement during rendering.
                 */
                while (colorIndex < entry.colors.length
                        && glyph.stringIndex + shift >= entry.colors[colorIndex].stringIndex) {
                    shift += 2;
                    colorIndex++;
                }
                glyph.stringIndex += shift;

                if (Math.abs(glyph.y) > entry.baseline)
                    entry.baseline = Math.abs(glyph.y);
                if (glyph.texture.height + glyph.y > entry.height) {
                    entry.height = glyph.texture.height + glyph.y;
                }
            }

            putEntry(str, entry);
        }

        /* Return either the existing or the newly created entry so it can be accessed immediately */
        return entry;
    }

    /**
     * Remove all color codes from the string by shifting data in the text[] array over so it overwrites them. The value of each
     * color code and its position (relative to the new stripped text[]) is also recorded in a separate array. The color codes must
     * be removed for a font's context sensitive glyph substitution to work (like Arabic letter middle form).
     *
     * @param str  the string from which color codes will be stripped
     * @param text on input it should be an identical copy of str; on output it will be string with all color codes removed
     * @return the length of the new stripped string in text[]; actual text.length will not change because the array is not reallocated
     */
    private int stripColorCodes(Entry cacheEntry, String str, char text[]) {
        List<ColorCode> colorList = new ArrayList();
        int start = 0, shift = 0, next;

        byte fontStyle = Font.PLAIN;
        byte renderStyle = 0;
        byte colorCode = -1;

        /* Search for section mark characters indicating the start of a color code (but only if followed by at least one character) */
        while ((next = str.indexOf('\u00A7', start)) != -1 && next + 1 < str.length()) {
            /*
             * Remove the two char color code from text[] by shifting the remaining data in the array over on top of it.
             * The "start" and "next" variables all contain offsets into the original unmodified "str" string. The "shift"
             * variable keeps track of how many characters have been sripped so far, and it's used to compute offsets into
             * the text[] array based on the start/next offsets in the original string.
             */
            System.arraycopy(text, next - shift + 2, text, next - shift, text.length - next - 2);

            /* Decode escape code used in the string and change current font style / color based on it */
            int code = "0123456789abcdefklmnor".indexOf(Character.toLowerCase(str.charAt(next + 1)));
            switch (code) {
            /* Random style; TODO: NOT IMPLEMENTED YET */
            case 16:
                break;

            /* Bold style */
            case 17:
                fontStyle |= Font.BOLD;
                break;

            /* Strikethrough style */
            case 18:
                renderStyle |= ColorCode.STRIKETHROUGH;
                cacheEntry.specialRender = true;
                break;

            /* Underline style */
            case 19:
                renderStyle |= ColorCode.UNDERLINE;
                cacheEntry.specialRender = true;
                break;

            /* Italic style */
            case 20:
                fontStyle |= Font.ITALIC;
                break;

            /* Plain style */
            case 21:
                fontStyle = Font.PLAIN;
                renderStyle = 0;
                colorCode = -1; // This may be a bug in Minecraft's original FontRenderer
                break;

            /* Otherwise, must be a color code or some other unsupported code */
            default:
                if (code >= 0 && code <= 15) {
                    colorCode = (byte) code;
                    fontStyle = Font.PLAIN; // This may be a bug in Minecraft's original FontRenderer
                    renderStyle = 0; // This may be a bug in Minecraft's original FontRenderer
                }
                break;
            }

            /* Create a new ColorCode object that tracks the position of the code in the original string */
            ColorCode entry = new ColorCode();
            entry.stringIndex = next;
            entry.stripIndex = next - shift;
            entry.colorCode = colorCode;
            entry.fontStyle = fontStyle;
            entry.renderStyle = renderStyle;
            colorList.add(entry);

            /* Resume search for section marks after skipping this one */
            start = next + 2;
            shift += 2;
        }

        /* Convert the accumulated ColorCode list to an array for efficient storage */
        cacheEntry.colors = new ColorCode[colorList.size()];
        cacheEntry.colors = colorList.toArray(cacheEntry.colors);

        /* Return the new length of the string after all color codes were removed */
        return text.length - shift;
    }

    /**
     * Split a string into contiguous LTR or RTL sections by applying the Unicode Bidirectional Algorithm. Calls layoutString()
     * for each contiguous run to perform further analysis.
     *
     * @param glyphList will hold all new Glyph objects allocated by layoutFont()
     * @param text      the string to lay out
     * @param start     the offset into text at which to start the layout
     * @param limit     the (offset + length) at which to stop performing the layout
     * @return the total advance (horizontal distance) of this string
     */
    private int layoutBidiString(List<Glyph> glyphList, char text[], int start, int limit, ColorCode colors[]) {
        int advance = 0;

        /* Avoid performing full bidirectional analysis if text has no "strong" right-to-left characters */
        if (Bidi.requiresBidi(text, start, limit)) {
            /* Note that while requiresBidi() uses start/limit the Bidi constructor uses start/length */
            Bidi bidi = new Bidi(text, start, null, 0, limit - start, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT);

            /* If text is entirely right-to-left, then insert an EntryText node for the entire string */
            if (bidi.isRightToLeft()) {
                return layoutStyle(glyphList, text, start, limit, Font.LAYOUT_RIGHT_TO_LEFT, advance, colors);
            }

            /* Otherwise text has a mixture of LTR and RLT, and it requires full bidirectional analysis */
            else {
                int runCount = bidi.getRunCount();
                byte levels[] = new byte[runCount];
                Integer ranges[] = new Integer[runCount];

                /* Reorder contiguous runs of text into their display order from left to right */
                for (int index = 0; index < runCount; index++) {
                    levels[index] = (byte) bidi.getRunLevel(index);
                    ranges[index] = new Integer(index);
                }
                Bidi.reorderVisually(levels, 0, ranges, 0, runCount);

                /*
                 * Every GlyphVector must be created on a contiguous run of left-to-right or right-to-left text. Keep track of
                 * the horizontal advance between each run of text, so that the glyphs in each run can be assigned a position relative
                 * to the start of the entire string and not just relative to that run.
                 */
                for (int visualIndex = 0; visualIndex < runCount; visualIndex++) {
                    int logicalIndex = ranges[visualIndex];

                    /* An odd numbered level indicates right-to-left ordering */
                    int layoutFlag = (bidi.getRunLevel(logicalIndex) & 1) == 1 ? Font.LAYOUT_RIGHT_TO_LEFT
                            : Font.LAYOUT_LEFT_TO_RIGHT;
                    advance = layoutStyle(glyphList, text, start + bidi.getRunStart(logicalIndex),
                            start + bidi.getRunLimit(logicalIndex), layoutFlag, advance, colors);
                }
            }

            return advance;
        }

        /* If text is entirely left-to-right, then insert an EntryText node for the entire string */
        else {
            return layoutStyle(glyphList, text, start, limit, Font.LAYOUT_LEFT_TO_RIGHT, advance, colors);
        }
    }

    private int layoutStyle(List<Glyph> glyphList, char text[], int start, int limit, int layoutFlags, int advance,
            ColorCode colors[]) {
        int currentFontStyle = Font.PLAIN;

        /* Find ColorCode object with stripIndex <= start; that will have the font style in effect at the beginning of this text run */
        int colorIndex = Arrays.binarySearch(colors, start);

        /*
         * If no exact match is found, Arrays.binarySearch() returns (-(insertion point) - 1) where the insertion point is the index
         * of the first ColorCode with a stripIndex > start. In that case, colorIndex is adjusted to select the immediately preceding
         * ColorCode whose stripIndex < start.
         */
        if (colorIndex < 0) {
            colorIndex = -colorIndex - 2;
        }

        /* Break up the string into segments, where each segment has the same font style in use */
        while (start < limit) {
            int next = limit;

            /* In case of multiple consecutive color codes with the same stripIndex, select the last one which will have active font style */
            while (colorIndex >= 0 && colorIndex < (colors.length - 1)
                    && colors[colorIndex].stripIndex == colors[colorIndex + 1].stripIndex) {
                colorIndex++;
            }

            /* If an actual ColorCode object was found (colorIndex within the array), use its fontStyle for layout and render */
            if (colorIndex >= 0 && colorIndex < colors.length) {
                currentFontStyle = colors[colorIndex].fontStyle;
            }

            /*
             * Search for the next ColorCode that uses a different fontStyle than the current one. If found, the stripIndex of that
             * new code is the split point where the string must be split into a separately styled segment.
             */
            while (++colorIndex < colors.length) {
                if (colors[colorIndex].fontStyle != currentFontStyle) {
                    next = colors[colorIndex].stripIndex;
                    break;
                }
            }

            /* Layout the string segment with the style currently selected by the last color code */
            advance = layoutString(glyphList, text, start, next, layoutFlags, advance, currentFontStyle);
            start = next;
        }

        return advance;
    }

    /**
     * Given a string that runs contiguously LTR or RTL, break it up into individual segments based on which fonts can render
     * which characters in the string. Calls layoutFont() for each portion of the string that can be layed out with a single
     * font.
     *
     * @param glyphList   will hold all new Glyph objects allocated by layoutFont()
     * @param text        the string to lay out
     * @param start       the offset into text at which to start the layout
     * @param limit       the (offset + length) at which to stop performing the layout
     * @param layoutFlags either Font.LAYOUT_RIGHT_TO_LEFT or Font.LAYOUT_LEFT_TO_RIGHT
     * @param advance     the horizontal advance (i.e. X position) returned by previous call to layoutString()
     * @param style       combination of Font.PLAIN, Font.BOLD, and Font.ITALIC to select a fonts with some specific style
     * @return the advance (horizontal distance) of this string plus the advance passed in as an argument
     * @todo Correctly handling RTL font selection requires scanning the sctring from RTL as well.
     * @todo Use bitmap fonts as a fallback if no OpenType font could be found
     */
    private int layoutString(List<Glyph> glyphList, char text[], int start, int limit, int layoutFlags, int advance,
            int style) {
        /*
         * Convert all digits in the string to a '0' before layout to ensure that any glyphs replaced on the fly will all have
         * the same positions. Under Windows, Java's "SansSerif" logical font uses the "Arial" font for digits, in which the "1"
         * digit is slightly narrower than all other digits. Checking the digitGlyphsReady flag prevents a chicken-and-egg
         * problem where the digit glyphs have to be initially cached and the digitGlyphs[] array initialized without replacing
         * every digit with '0'.
         */
        if (digitGlyphsReady) {
            for (int index = start; index < limit; index++) {
                if (text[index] >= '0' && text[index] <= '9') {
                    text[index] = '0';
                }
            }
        }

        /* Break the string up into segments, where each segment can be displayed using a single font */
        while (start < limit) {
            Font font = currentFont.deriveFont(style); // glyphCache.lookupFont(text, start, limit, style);
            int next = font.canDisplayUpTo(text, start, limit);

            /* canDisplayUpTo returns -1 if the entire string range is supported by this font */
            if (next == -1) {
                next = limit;
            }

            /*
             * canDisplayUpTo() returns start if the starting character is not supported at all. In that case, draw just the
             * one unsupported character (which will use the font's "missing glyph code"), then retry the lookup again at the
             * next character after that.
             */
            if (next == start) {
                next++;
            }

            advance = layoutFont(glyphList, text, start, next, layoutFlags, advance, font);
            start = next;
        }

        return advance;
    }

    /**
     * Allocate new Glyph objects and add them to the glyph list. This sequence of Glyphs represents a portion of the
     * string where all glyphs run contiguously in either LTR or RTL and come from the same physical/logical font.
     *
     * @param glyphList   all newly created Glyph objects are added to this list
     * @param text        the string to layout
     * @param start       the offset into text at which to start the layout
     * @param limit       the (offset + length) at which to stop performing the layout
     * @param layoutFlags either Font.LAYOUT_RIGHT_TO_LEFT or Font.LAYOUT_LEFT_TO_RIGHT
     * @param advance     the horizontal advance (i.e. X position) returned by previous call to layoutString()
     * @param font        the Font used to layout a GlyphVector for the string
     * @return the advance (horizontal distance) of this string plus the advance passed in as an argument
     * @todo need to ajust position of all glyphs if digits are present, by assuming every digit should be 0 in length
     */
    private int layoutFont(List<Glyph> glyphList, char text[], int start, int limit, int layoutFlags, int advance,
            Font font) {
        /*
         * Ensure that all glyphs used by the string are pre-rendered and cached in the texture. Only safe to do so from the
         * main thread because cacheGlyphs() can crash LWJGL if it makes OpenGL calls from any other thread. In this case,
         * cacheString() will also not insert the entry into the stringCache since it may be incomplete if lookupGlyph()
         * returns null for any glyphs not yet stored in the glyph cache.
         */
        if (mainThread == Thread.currentThread()) {
            glyphCache.cacheGlyphs(font, text, start, limit, layoutFlags);
        }

        /* Creating a GlyphVector takes care of all language specific OpenType glyph substitutions and positionings */
        GlyphVector vector = glyphCache.layoutGlyphVector(font, text, start, limit, layoutFlags);
        float scale = glyphCache.getScale();

        /*
         * Extract all needed information for each glyph from the GlyphVector so it won't be needed for actual rendering.
         * Note that initially, glyph.start holds the character index into the stripped text array. But after the entire
         * string is layed out, this field will be adjusted on every Glyph object to correctly index the original unstripped
         * string.
         */
        Glyph glyph = null;
        int numGlyphs = vector.getNumGlyphs();
        for (int index = 0; index < numGlyphs; index++) {
            Point position = vector.getGlyphPixelBounds(index, null, advance, 0).getLocation();

            /* Compute horizontal advance for the previous glyph based on this glyph's position */
            if (glyph != null) {
                glyph.advance = position.x - glyph.x;
            }

            /*
             * Allocate a new glyph object and add to the glyphList. The glyph.stringIndex here is really like stripIndex but
             * it will be corrected later to account for the color codes that have been stripped out.
             */
            glyph = new Glyph();
            glyph.stringIndex = start + vector.getGlyphCharIndex(index);
            glyph.texture = glyphCache.lookupGlyph(font, vector.getGlyphCode(index));
            glyph.x = position.x;
            glyph.y = position.y;
            glyphList.add(glyph);
        }

        /* Compute the advance position of the last glyph (or only glyph) since it can't be done by the above loop */
        advance += (int) vector.getGlyphPosition(numGlyphs).getX();
        if (glyph != null) {
            glyph.advance = advance - glyph.x;
        }

        /* Return the overall horizontal advance in pixels from the start of string */
        return advance;
    }

    protected Entry getEntry(String s) {
        stringKey.key = s;
        WeakHashMap<Font, Entry> entries = stringCache.get(stringKey);
        if (entries == null)
            return null;

        return entries.get(currentFont);
    }

    protected void putEntry(String s, Entry entry) {
        if (mainThread == Thread.currentThread()) {
            Key k = new Key();
            k.key = new String(s);
            entry.keyRef = new WeakReference<Key>(k);
            if (!stringCache.containsKey(stringKey)) {
                weakRefCache.put(s, k);
                stringCache.put(k, new WeakHashMap<Font, Entry>());
            }

            stringCache.get(k).put(currentFont, entry);
        }
    }
}