net.minecraft.client.gui.fonts.GlyphCache.java Source code

Java tutorial

Introduction

Here is the source code for net.minecraft.client.gui.fonts.GlyphCache.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 net.minecraft.client.gui.fonts;

import net.minecraft.src.GLAllocation;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.nio.IntBuffer;
import java.nio.ByteOrder;
import java.nio.ByteBuffer;
import java.awt.image.BufferedImage;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Point2D;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Font;
import java.awt.RenderingHints;
import java.awt.Rectangle;
import java.awt.AlphaComposite;
import java.awt.GraphicsEnvironment;
import org.lwjgl.opengl.GL11;

/**
 * The GlyphCache class is responsible for caching pre-rendered images of every
 * glyph using OpenGL textures. This class is also responsible for selecting the
 * proper fonts to render each glyph, since Java's own "SansSerif" logical font
 * does not always select the proper physical font to use (especially on less
 * common Linux distributions). Once a pre-rendered glyph image is cached, it
 * will remain stored in an OpenGL texture for the entire lifetime of the
 * application (StringCache depends on this behavior).
 *
 * @todo Should have a separate glyph cache and a separate smaller point size
 * font for rendering the GUI at its smallest size and for use in the F3 debug
 * screen; may need some explicit argument in StringCache.renderString() to
 * select the size
 * @todo Need to have a config file that allows overring the font search order
 * by locale to properly support Traditional Chinese hanzi, Simplified Chinese
 * hanzi, Japanese kanji, and Korean hanja
 */
public class GlyphCache {

    /**
     * The width in pixels of every texture used for caching pre-rendered glyph
     * images. Used by GlyphCache when calculating floating point 0.0-1.0
     * texture coordinates. Must be a power of two for mip-mapping to work.
     */
    private static final int TEXTURE_WIDTH = 256;

    /**
     * The height in pixels of every texture used for caching pre-rendered glyph
     * images. Used by GlyphCache when calculating floating point 0.0-1.0
     * texture coordinates. Must be a power of two for mip-mapping to work.
     */
    private static final int TEXTURE_HEIGHT = 256;

    /**
     * Initial width in pixels of the stringImage buffer used to extract
     * individual glyph images.
     */
    private static final int STRING_WIDTH = 256;

    /**
     * Initial height in pixels of the stringImage buffer used to extract
     * individual glyph images.
     */
    private static final int STRING_HEIGHT = 64;

    /**
     * The width in pixels of a transparent border between individual glyphs in
     * the cache texture. This border keeps neighboring glyphs from "bleeding
     * through" when the scaled GUI resolution is not pixel aligned and
     * sometimes results in off-by-one sampling of the glyph cache textures.
     */
    private static final int GLYPH_BORDER = 1;

    /**
     * Transparent (alpha zero) white background color for use with
     * BufferedImage.clearRect().
     */
    private static Color BACK_COLOR = new Color(255, 255, 255, 0);

    /**
     * The point size at which every OpenType font is rendered.
     */
    private int fontSize = 18;

    /**
     * If true, then enble anti-aliasing when rendering the font glyph
     */
    private boolean antiAliasEnabled = false;

    /**
     * Temporary image for rendering a string to and then extracting the glyph
     * images from.
     */
    private BufferedImage stringImage;

    /**
     * The Graphics2D associated with stringImage and used for string drawing to
     * extract the individual glyph shapes.
     */
    private Graphics2D stringGraphics;

    /**
     * All font glyphs are packed inside this image and are then loaded from
     * here into an OpenGL texture.
     */
    private BufferedImage glyphCacheImage = new BufferedImage(TEXTURE_WIDTH, TEXTURE_HEIGHT,
            BufferedImage.TYPE_INT_ARGB);

    /**
     * The Graphics2D associated with glyphCacheImage and used for bit blitting
     * between stringImage.
     */
    private Graphics2D glyphCacheGraphics = glyphCacheImage.createGraphics();

    /**
     * Needed for all text layout operations that create GlyphVectors (maps
     * point size to pixel size).
     */
    private FontRenderContext fontRenderContext = glyphCacheGraphics.getFontRenderContext();

    /**
     * Intermediate data array for use with textureImage.getRgb().
     */
    private int imageData[] = new int[TEXTURE_WIDTH * TEXTURE_HEIGHT];

    /**
     * A big-endian direct int buffer used with glTexSubImage2D() and
     * glTexImage2D(). Used for loading the pre-rendered glyph images from the
     * glyphCacheImage BufferedImage into OpenGL textures. This buffer uses
     * big-endian byte ordering to ensure that the integers holding packed RGBA
     * colors are stored into memory in a predictable order.
     */
    private IntBuffer imageBuffer = ByteBuffer.allocateDirect(4 * TEXTURE_WIDTH * TEXTURE_HEIGHT)
            .order(ByteOrder.BIG_ENDIAN).asIntBuffer();

    /**
     * A single integer direct buffer with native byte ordering used for
     * returning values from glGenTextures().
     */
    private IntBuffer singleIntBuffer = GLAllocation.createDirectIntBuffer(1);

    /**
     * List of all available physical fonts on the system. Used by lookupFont()
     * to find alternate fonts.
     */
    private List<Font> allFonts = Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts());

    /**
     * A list of all fonts that have been returned so far by lookupFont(), and
     * that will always be searched first for a usable font before searching
     * through allFonts[]. This list will only have plain variation of a font at
     * a dummy point size, unlike fontCache which could have multiple entries
     * for the various styles (i.e. bold, italic, etc.) of a font. This list
     * starts with Java's "SansSerif" logical font.
     */
    private List<Font> usedFonts = new ArrayList();

    /**
     * ID of current OpenGL cache texture being used by cacheGlyphs() to store
     * pre-rendered glyph images.
     */
    private int textureName;

    /**
     * A cache of all fonts that have at least one glyph pre-rendered in a
     * texture. Each font maps to an integer (monotonically increasing) which
     * forms the upper 32 bits of the key into the glyphCache map. This font
     * cache can include different styles of the same font family like bold or
     * italic.
     */
    private LinkedHashMap<Font, Integer> fontCache = new LinkedHashMap();

    /**
     * A cache of pre-rendered glyphs mapping each glyph by its glyphcode to the
     * position of its pre-rendered image within the cache texture. The key is a
     * 64 bit number such that the lower 32 bits are the glyphcode and the upper
     * 32 are the index of the font in the fontCache. This makes for a single
     * globally unique number to identify any glyph from any font.
     */
    private LinkedHashMap<Long, Entry> glyphCache = new LinkedHashMap();

    /**
     * The X coordinate of the upper=left corner in glyphCacheImage where the
     * next glyph image should be stored. Glyphs are always added left-to-right
     * on the curren tline until it fills up, at which point they continue
     * filling the texture on the next line.
     */
    private int cachePosX = GLYPH_BORDER;

    /**
     * The Y coordinate of the upper-left corner in glyphCacheImage where the
     * next glyph image should be stored. Glyphs are stored left-to-right in
     * horizontal lines, and top-to-bottom until the entire texture fills up. At
     * that point, a new texture is allocated to keep storing additional glyphs,
     * and the original texture remains allocated for the lifetime of the
     * application.
     */
    private int cachePosY = GLYPH_BORDER;

    /**
     * The height in pixels of the current line of glyphs getting written into
     * the texture. This value determines by how much cachePosY will get
     * incremented when the current horizontal line in the texture fills up.
     */
    private int cacheLineHeight = 0;

    /**
     * This class holds information for a glyph about its pre-rendered image in
     * an OpenGL texture. The texture coordinates in this class are normalized
     * in the standard 0.0 - 1.0 OpenGL range.
     */
    static class Entry {

        /**
         * The OpenGL texture ID that contains this glyph image.
         */
        public int textureName;

        /**
         * The width in pixels of the glyph image.
         */
        public int width;

        /**
         * The height in pixels of the glyph image.
         */
        public int height;

        /**
         * The horizontal texture coordinate of the upper-left corner.
         */
        public float u1;

        /**
         * The vertical texture coordinate of the upper-left corner.
         */
        public float v1;

        /**
         * The horizontal texture coordinate of the lower-right corner.
         */
        public float u2;

        /**
         * The vertical texture coordinate of the lower-right corner.
         */
        public float v2;
    }

    /**
     * A single instance of GlyphCache is allocated for internal use by the
     * StringCache class.
     *
     * @param renderEngine needed to allocate OpenGL textures
     */
    public GlyphCache() {
        /* Set background color for use with clearRect() */
        glyphCacheGraphics.setBackground(BACK_COLOR);

        /* The drawImage() to this buffer will copy all source pixels instead of alpha blending them into the current image */
        glyphCacheGraphics.setComposite(AlphaComposite.Src);

        allocateGlyphCacheTexture();
        allocateStringImage(STRING_WIDTH, STRING_HEIGHT);

        /* Use Java's logical font as the default initial font if user does not override it in some configuration file */
        java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().preferLocaleFonts();
        usedFonts.add(new Font(Font.SANS_SERIF, Font.PLAIN, 1));
    }

    /**
     * Change the default font used to pre-render glyph images. If this method
     * is called at runtime, the existing glyph images will remain cached in
     * their respective textures and will remain accessible to StringCache. This
     * method is normally called by StringCache.setDefaultFont() since the
     * StringCache must also invalidate itself so it can re-layout and re-cache
     * the glyphs for all strings using the new font.
     *
     * @param name the new font name
     * @param size the new point size
     */
    void setDefaultFont(String name, int size, boolean antiAlias) {
        System.out.println("BetterFonts loading font \"" + name + "\"");
        usedFonts.clear();
        usedFonts.add(new Font(name, Font.PLAIN, 1));

        fontSize = size;
        antiAliasEnabled = antiAlias;
        setRenderingHints();
    }

    /**
     * Given a single OpenType font, perform full text layout and create a new
     * GlyphVector for a string.
     *
     * @param font the Font used to layout a GlyphVector for the string
     * @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
     * @return the newly created GlyphVector
     */
    GlyphVector layoutGlyphVector(Font font, char text[], int start, int limit, int layoutFlags) {
        /* Ensure this font is already in fontCache so it can be referenced by cacheGlyphs() later on */
        if (!fontCache.containsKey(font)) {
            fontCache.put(font, fontCache.size());
        }
        return font.layoutGlyphVector(fontRenderContext, text, start, limit, layoutFlags);
    }

    /**
     * Find the first font in the system able to render at least one character
     * from a given string. The function always tries searching first in the
     * fontCache (based on the request style). Failing that, it searches the
     * usedFonts list followed by the allFonts[] array.
     *
     * @param text the string to check against the font
     * @param start the offset into text at which to start checking characters
     * for being supported by a font
     * @param limit the (offset + length) at which to stop checking characters
     * @param a combination of the Font.PLAIN, Font.BOLD, and Font.ITALIC to
     * request a particular font style
     * @return an OpenType font capable of displaying at least the first
     * character at the start position in text
     */
    Font lookupFont(char text[], int start, int limit, int style) {
        /* Try using an already known base font; the first font in usedFonts list is the one set with setDefaultFont() */
        Iterator<Font> iterator = usedFonts.iterator();
        while (iterator.hasNext()) {
            /* Only use the font if it can layout at least the first character of the requested string range */
            Font font = iterator.next();
            if (font.canDisplayUpTo(text, start, limit) != start) {
                /* Return a font instance of the proper point size and style; usedFonts has only 1pt sized plain style fonts */
                return font.deriveFont(style, fontSize);
            }
        }

        /* If still not found, try searching through all fonts installed on the system for the first that can layout this string */
        iterator = allFonts.iterator();
        while (iterator.hasNext()) {
            /* Only use the font if it can layout at least the first character of the requested string range */
            Font font = iterator.next();
            if (font.canDisplayUpTo(text, start, limit) != start) {
                /* If found, add this font to the usedFonts list so it can be looked up faster next time */
                System.out.println("BetterFonts loading font \"" + font.getFontName() + "\"");
                usedFonts.add(font);

                /* Return a font instance of the proper point size and style; allFonts has only 1pt sized plain style fonts */
                return font.deriveFont(style, fontSize);
            }
        }

        /* If no supported fonts found, use the default one (first in usedFonts) so it can draw its unknown character glyphs */
        Font font = usedFonts.get(0);

        /* Return a font instance of the proper point size and style; usedFonts only 1pt sized plain style fonts */
        return font.deriveFont(style, fontSize);
    }

    /**
     * Given an OpenType font and a glyph code within that font, locate the
     * glyph's pre-rendered image in the glyph cache and return its cache
     * entry,. The entry stores the texture ID with the pre-rendered glyph
     * image, as well as the position and size of that image within the texture.
     * This function assumes that any glyph lookup requests passed to it have
     * been already cached by an earlier call to cacheGlyphs().
     *
     * @param font the font to which this glyphCode belongs and which was used
     * to pre-render the glyph image in cacheGlyphs()
     * @param glyphCode the font specific flyph code to lookup in the cache
     * @return the cache entry for this font/glyphCode pair
     */
    Entry lookupGlyph(Font font, int glyphCode) {
        long fontKey = (long) fontCache.get(font) << 32;
        return glyphCache.get(fontKey | glyphCode);
    }

    /**
     * Given an OpenType font and a string, make sure that every glyph used by
     * that string is pre-rendered into an OpenGL texture and cached in the
     * glyphCache map for later retrieval by lookupGlyph()
     *
     * @param font the font used to create a GlyphVector for the string and to
     * actually draw the individual glyphs
     * @param text the string from which to cache glyph images
     * @param start the offset into text at which to start caching glyphs
     * @param limit the (offset + length) at which to stop caching glyphs
     * @param layoutFlags either Font.LAYOUT_RIGHT_TO_LEFT or
     * Font.LAYOUT_LEFT_TO_RIGHT; needed for weak bidirectional characters like
     * parenthesis which are mapped to two different glyph codes depending on
     * the surrounding text direction
     *
     * @todo May need a blank border of pixels around everything for
     * mip-map/tri-linear filtering with Optifine
     */
    void cacheGlyphs(Font font, char text[], int start, int limit, int layoutFlags) {
        /* Create new GlyphVector so glyphs can be moved around (kerning workaround; see below) without affecting caller */
        GlyphVector vector = layoutGlyphVector(font, text, start, limit, layoutFlags);

        /* Pixel aligned bounding box for the entire vector; only set if the vector has to be drawn to cache a glyph image */
        Rectangle vectorBounds = null;

        /* This forms the upper 32 bits of the fontCache key to make every font/glyph code point unique */
        long fontKey = (long) fontCache.get(font) << 32;

        int numGlyphs = vector.getNumGlyphs(); /* Length of the GlyphVector */

        Rectangle dirty = null; /* Total area within texture that needs to be updated with glTexSubImage2D() */

        boolean vectorRendered = false; /* True if entire GlyphVector was rendered into stringImage */

        for (int index = 0; index < numGlyphs; index++) {
            /* If this glyph code is already in glyphCache, then there is no reason to pre-render it again */
            int glyphCode = vector.getGlyphCode(index);
            if (glyphCache.containsKey(fontKey | glyphCode)) {
                continue;
            }

            /*
             * The only way to get glyph shapes with font hinting is to draw the entire glyph vector into a
             * temporary BufferedImage, and then bit blit the individual glyphs based on their bounding boxes
             * returned by the glyph vector. Although it is possible to call font.createGlyphVector() with an
             * array of glyphcodes (and therefore render only a few glyphs at a time), this produces corrupted
             * Davengari glyphs under Windows 7. The vectorRendered flag will draw the string at most one time.
             */
            if (!vectorRendered) {
                vectorRendered = true;

                /*
                 * Kerning can make it impossible to cleanly separate adjacent glyphs. To work around this,
                 * each glyph is manually advanced by 2 pixels to the right of its neighbor before rendering
                 * the entire string. The getGlyphPixelBounds() later on will return the new adjusted bounds
                 * for the glyph.
                 */
                for (int i = 0; i < numGlyphs; i++) {
                    Point2D pos = vector.getGlyphPosition(i);
                    pos.setLocation(pos.getX() + 2 * i, pos.getY());
                    vector.setGlyphPosition(i, pos);
                }

                /*
                 * Compute the exact area that the rendered string will take up in the image buffer. Note that
                 * the string will actually be drawn at a positive (x,y) offset from (0,0) to leave enough room
                 * for the ascent above the baseline and to correct for a few glyphs that appear to have negative
                 * horizontal bearing (e.g. U+0423 Cyrillic uppercase letter U on Windows 7).
                 */
                vectorBounds = vector.getPixelBounds(fontRenderContext, 0, 0);

                /* Enlage the stringImage if it is too small to store the entire rendered string */
                if (stringImage == null || vectorBounds.width > stringImage.getWidth()
                        || vectorBounds.height > stringImage.getHeight()) {
                    int width = Math.max(vectorBounds.width, stringImage.getWidth());
                    int height = Math.max(vectorBounds.height, stringImage.getHeight());
                    allocateStringImage(width, height);
                }

                /* Erase the upper-left corner where the string will get drawn*/
                stringGraphics.clearRect(0, 0, vectorBounds.width, vectorBounds.height);

                /* Draw string with opaque white color and baseline adjustment so the upper-left corner of the image is at (0,0) */
                stringGraphics.drawGlyphVector(vector, -vectorBounds.x, -vectorBounds.y);
            }

            /*
             * Get the glyph's pixel-aligned bounding box. The JavaDoc claims that the "The outline returned
             * by this method is positioned around the origin of each individual glyph." However, the actual
             * bounds are all relative to the start of the entire GlyphVector, which is actually more useful
             * for extracting the glyph's image from the rendered string.
             */
            Rectangle rect = vector.getGlyphPixelBounds(index, null, -vectorBounds.x, -vectorBounds.y);

            /* If the current line in cache image is full, then advance to the next line */
            if (cachePosX + rect.width + GLYPH_BORDER > TEXTURE_WIDTH) {
                cachePosX = GLYPH_BORDER;
                cachePosY += cacheLineHeight + GLYPH_BORDER;
                cacheLineHeight = 0;
            }

            /*
             * If the entire image is full, update the current OpenGL texture with everything changed so far in the image
             * (i.e. the dirty rectangle), allocate a new cache texture, and then continue storing glyph images to the
             * upper-left corner of the new texture.
             */
            if (cachePosY + rect.height + GLYPH_BORDER > TEXTURE_HEIGHT) {
                updateTexture(dirty);
                dirty = null;

                /* Note that allocateAndSetupTexture() will leave the GL texture already bound */
                allocateGlyphCacheTexture();
                cachePosY = cachePosX = GLYPH_BORDER;
                cacheLineHeight = 0;
            }

            /* The tallest glyph on this line determines the total vertical advance in the texture */
            if (rect.height > cacheLineHeight) {
                cacheLineHeight = rect.height;
            }

            /*
             * Blit the individual glyph from it's position in the temporary string buffer to its (cachePosX,
             * cachePosY) position in the texture. NOTE: We don't have to erase the area in the texture image
             * first because the composite method in the Graphics object is always set to AlphaComposite.Src.
             */
            glyphCacheGraphics.drawImage(stringImage, cachePosX, cachePosY, cachePosX + rect.width,
                    cachePosY + rect.height, rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, null);

            /*
             * Store this glyph's position in texture and its origin offset. Note that "rect" will not be modified after
             * this point, and getGlyphPixelBounds() always returns a new Rectangle.
             */
            rect.setLocation(cachePosX, cachePosY);

            /*
             * Create new cache entry to record both the texture used by the glyph and its position within that texture.
             * Texture coordinates are normalized to 0.0-1.0 by dividing with TEXTURE_WIDTH and TEXTURE_HEIGHT.
             */
            Entry entry = new Entry();
            entry.textureName = textureName;
            entry.width = rect.width;
            entry.height = rect.height;
            entry.u1 = ((float) rect.x) / TEXTURE_WIDTH;
            entry.v1 = ((float) rect.y) / TEXTURE_HEIGHT;
            entry.u2 = ((float) (rect.x + rect.width)) / TEXTURE_WIDTH;
            entry.v2 = ((float) (rect.y + rect.height)) / TEXTURE_HEIGHT;

            /*
             * The lower 32 bits of the glyphCache key are the glyph codepoint. The upper 64 bits are the font number
             * stored in the fontCache. This creates a unique numerical id for every font/glyph combination.
             */
            glyphCache.put(fontKey | glyphCode, entry);

            /*
             * Track the overall modified region in the texture by performing a union of this glyph's texture position
             * with the update region created so far. Reusing "rect" here makes it easier to extend the dirty rectangle
             * region than using the add(x, y) method to extend by a single point. Also note that creating the first
             * dirty rectangle here avoids having to deal with the special rules for empty/non-existent rectangles.
             */
            if (dirty == null) {
                dirty = new Rectangle(cachePosX, cachePosY, rect.width, rect.height);
            } else {
                dirty.add(rect);
            }

            /* Advance cachePosX so the next glyph can be stored immediately to the right of this one */
            cachePosX += rect.width + GLYPH_BORDER;
        }

        /* Update OpenGL texture if any part of the glyphCacheImage has changed */
        updateTexture(dirty);
    }

    /**
     * Update a portion of the current glyph cache texture using the contents of
     * the glyphCacheImage with glTexSubImage2D().
     *
     * @param dirty The rectangular region in glyphCacheImage that has changed
     * and needs to be copied into the texture
     *
     * @todo Add mip-mapping support here
     * @todo Test with bilinear texture interpolation and possibly add a 1 pixel
     * transparent border around each glyph to avoid bleedover when
     * interpolation is active or add a small "fudge factor" to the UV
     * coordinates like already n FontRenderer
     */
    private void updateTexture(Rectangle dirty) {
        /* Only update OpenGL texture if changes were made to the texture */
        if (dirty != null) {
            /* Load imageBuffer with pixel data ready for transfer to OpenGL texture */
            updateImageBuffer(dirty.x, dirty.y, dirty.width, dirty.height);

            GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureName);
            GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, dirty.x, dirty.y, dirty.width, dirty.height, GL11.GL_RGBA,
                    GL11.GL_UNSIGNED_BYTE, imageBuffer);
        }
    }

    /**
     * Allocte and initialize a new BufferedImage and Graphics2D context for
     * rendering strings into. May need to be called at runtime to re-allocate a
     * bigger BufferedImage if cacheGlyphs() is called with a very long string.
     */
    private void allocateStringImage(int width, int height) {
        stringImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        stringGraphics = stringImage.createGraphics();
        setRenderingHints();

        /* Set background color for use with clearRect() */
        stringGraphics.setBackground(BACK_COLOR);

        /*
         * Full white (1.0, 1.0, 1.0, 1.0) can be modulated by vertex color to produce a full gamut of text colors, although with
         * a GL_ALPHA8 texture, only the alpha component of the color will actually get loaded into the texture.
         */
        stringGraphics.setPaint(Color.WHITE);
    }

    /**
     * Set rendering hints on stringGraphics object. Uses current
     * antiAliasEnabled settings and is therefore called both from
     * allocateStringImage() when expanding the size of the BufferedImage and
     * from setDefaultFont() when changing current configuration.
     */
    private void setRenderingHints() {
        stringGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                antiAliasEnabled ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
        stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                antiAliasEnabled ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON
                        : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
        stringGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
    }

    /**
     * Allocate a new OpenGL texture for caching pre-rendered glyph images. The
     * new texture is initialized to fully transparent white so the individual
     * glyphs images within can have a transparent border between them. The new
     * texture remains bound after returning from the function.
     *
     * @todo use GL_ALPHA4 if anti-alias is turned off for even smaller textures
     */
    private void allocateGlyphCacheTexture() {
        /* Initialize the background to all white but fully transparent. */
        glyphCacheGraphics.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);

        /* Allocate new OpenGL texure */
        singleIntBuffer.clear();
        GLAllocation.generateTextureNames(singleIntBuffer);
        textureName = singleIntBuffer.get(0);

        /* Load imageBuffer with pixel data ready for transfer to OpenGL texture */
        updateImageBuffer(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);

        /*
         * Initialize texture with the now cleared BufferedImage. Using a texture with GL_ALPHA8 internal format may result in
         * faster rendering since the GPU has to only fetch 1 byte per texel instead of 4 with a regular RGBA texture.
         */
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureName);
        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_ALPHA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL11.GL_RGBA,
                GL11.GL_UNSIGNED_BYTE, imageBuffer);

        /* Explicitely disable mipmap support becuase updateTexture() will only update the base level 0 */
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
    }

    /**
     * Copy pixel data from a region in glyphCacheImage into imageBuffer and
     * prepare it for use with glText(Sub)Image2D(). This function takes care of
     * converting the ARGB format used with BufferedImage into the RGBA format
     * used by OpenGL.
     *
     * @param x the horizontal coordinate of the region's upper-left corner
     * @param y the vertical coordinate of the region's upper-left corner
     * @param width the width of the pixel region that will be copied into the
     * buffer
     * @param height the height of the pixel region that will be copied into the
     * buffer
     */
    private void updateImageBuffer(int x, int y, int width, int height) {
        /* Copy raw pixel data from BufferedImage to imageData array with one integer per pixel in 0xAARRGGBB form */
        glyphCacheImage.getRGB(x, y, width, height, imageData, 0, width);

        /* Swizzle each color integer from Java's ARGB format to OpenGL's RGBA */
        for (int i = 0; i < width * height; i++) {
            int color = imageData[i];
            imageData[i] = (color << 8) | (color >>> 24);
        }

        /* Copy int array to direct buffer; big-endian order ensures a 0xRR, 0xGG, 0xBB, 0xAA byte layout */
        imageBuffer.clear();
        imageBuffer.put(imageData);
        imageBuffer.flip();
    }
}