All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mathiewz.slick.font.GlyphPage Maven / Gradle / Ivy

Go to download

The main purpose of this libraryis to modernize and maintain the slick2D library.

The newest version!

package com.github.mathiewz.slick.font;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;

import com.github.mathiewz.slick.Color;
import com.github.mathiewz.slick.Image;
import com.github.mathiewz.slick.UnicodeFont;
import com.github.mathiewz.slick.opengl.TextureImpl;
import com.github.mathiewz.slick.opengl.renderer.Renderer;
import com.github.mathiewz.slick.opengl.renderer.SGL;

/**
 * Stores a number of glyphs on a single texture.
 *
 * @author Nathan Sweet
 */
public class GlyphPage {
    /** The interface to OpenGL */
    private static final SGL GL = Renderer.get();
    
    /** The maxium size of an individual glyph */
    public static final int MAX_GLYPH_SIZE = 256;
    
    /** A temporary working buffer */
    private static ByteBuffer scratchByteBuffer = ByteBuffer.allocateDirect(MAX_GLYPH_SIZE * MAX_GLYPH_SIZE * 4);
    
    static {
        scratchByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
    }
    
    /** A temporary working buffer */
    private static IntBuffer scratchIntBuffer = scratchByteBuffer.asIntBuffer();
    
    /** A temporary image used to generate the glyph page */
    private static BufferedImage scratchImage = new BufferedImage(MAX_GLYPH_SIZE, MAX_GLYPH_SIZE, BufferedImage.TYPE_INT_ARGB);
    /** The graphics context form the temporary image */
    private static Graphics2D scratchGraphics = (Graphics2D) scratchImage.getGraphics();
    
    static {
        scratchGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        scratchGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        scratchGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    }
    
    /** The render context in which the glyphs will be generated */
    public static final FontRenderContext RENDER_CONTEXT = scratchGraphics.getFontRenderContext();
    
    /**
     * Get the scratch graphics used to generate the page of glyphs
     *
     * @return The scratch graphics used to build the page
     */
    public static Graphics2D getScratchGraphics() {
        return scratchGraphics;
    }
    
    /** The font this page is part of */
    private final UnicodeFont unicodeFont;
    /** The width of this page's image */
    private final int pageWidth;
    /** The height of this page's image */
    private final int pageHeight;
    /** The image containing the glyphs */
    private final Image pageImage;
    /** The x position of the page */
    private int pageX;
    /** The y position of the page */
    private int pageY;
    /** The height of the last row on the page */
    private int rowHeight;
    /** True if the glyphs are ordered */
    private boolean orderAscending;
    /** The list of glyphs on this page */
    private final List pageGlyphs = new ArrayList<>(32);
    
    /**
     * Create a new page of glyphs
     *
     * @param unicodeFont
     *            The font this page forms part of
     * @param pageWidth
     *            The width of the backing texture.
     * @param pageHeight
     *            The height of the backing texture.
     */
    public GlyphPage(UnicodeFont unicodeFont, int pageWidth, int pageHeight) {
        this.unicodeFont = unicodeFont;
        this.pageWidth = pageWidth;
        this.pageHeight = pageHeight;
        
        pageImage = new Image(pageWidth, pageHeight);
    }
    
    /**
     * Loads glyphs to the backing texture and sets the image on each loaded glyph. Loaded glyphs are removed from the list.
     *
     * If this page already has glyphs and maxGlyphsToLoad is -1, then this method will return 0 if all the new glyphs don't fit.
     * This reduces texture binds when drawing since glyphs loaded at once are typically displayed together.
     *
     * @param glyphs
     *            The glyphs to load.
     * @param maxGlyphsToLoad
     *            This is the maximum number of glyphs to load from the list. Set to -1 to attempt to load all the
     *            glyphs.
     * @return The number of glyphs that were actually loaded.
     */
    public int loadGlyphs(List glyphs, int maxGlyphsToLoad) {
        if (rowHeight != 0 && maxGlyphsToLoad == -1) {
            // If this page has glyphs and we are not loading incrementally, return zero if any of the glyphs don't fit.
            int testX = pageX;
            int testY = pageY;
            int testRowHeight = rowHeight;
            for (Iterator iter = getIterator(glyphs); iter.hasNext();) {
                Glyph glyph = iter.next();
                int width = glyph.getWidth();
                int height = glyph.getHeight();
                if (testX + width >= pageWidth) {
                    testX = 0;
                    testY += testRowHeight;
                    testRowHeight = height;
                } else if (height > testRowHeight) {
                    testRowHeight = height;
                }
                if (testY + testRowHeight >= pageWidth) {
                    return 0;
                }
                testX += width;
            }
        }
        
        Color.white.bind();
        pageImage.bind();
        
        int i = 0;
        for (Iterator iter = getIterator(glyphs); iter.hasNext();) {
            Glyph glyph = iter.next();
            int width = Math.min(MAX_GLYPH_SIZE, glyph.getWidth());
            int height = Math.min(MAX_GLYPH_SIZE, glyph.getHeight());
            
            if (rowHeight == 0) {
                // The first glyph always fits.
                rowHeight = height;
            } else if (pageX + width >= pageWidth) {
                if (pageY + rowHeight + height >= pageHeight) {
                    break;
                }
                pageX = 0;
                pageY += rowHeight;
                rowHeight = height;
            } else if (height > rowHeight) {
                if (pageY + height >= pageHeight) {
                    break;
                }
                rowHeight = height;
            }
            
            renderGlyph(glyph, width, height);
            pageGlyphs.add(glyph);
            
            pageX += width;
            
            iter.remove();
            i++;
            if (i == maxGlyphsToLoad) {
                // If loading incrementally, flip orderAscending so it won't change, since we'll probably load the rest next time.
                orderAscending = !orderAscending;
                break;
            }
        }
        
        TextureImpl.bindNone();
        
        // Every other batch of glyphs added to a page are sorted the opposite way to attempt to keep same size glyps together.
        orderAscending = !orderAscending;
        
        return i;
    }
    
    /**
     * Loads a single glyph to the backing texture, if it fits.
     *
     * @param glyph
     *            The glyph to be rendered
     * @param width
     *            The expected width of the glyph
     * @param height
     *            The expected height of the glyph
     */
    private void renderGlyph(Glyph glyph, int width, int height) {
        // Draw the glyph to the scratch image using Java2D.
        scratchGraphics.setComposite(AlphaComposite.Clear);
        scratchGraphics.fillRect(0, 0, MAX_GLYPH_SIZE, MAX_GLYPH_SIZE);
        scratchGraphics.setComposite(AlphaComposite.SrcOver);
        scratchGraphics.setColor(java.awt.Color.white);
        unicodeFont.getEffects().forEach(effect -> effect.draw(scratchImage, scratchGraphics, unicodeFont, glyph));
        glyph.setShape(null); // The shape will never be needed again.
        
        WritableRaster raster = scratchImage.getRaster();
        int[] row = new int[width];
        for (int y = 0; y < height; y++) {
            raster.getDataElements(0, y, width, 1, row);
            scratchIntBuffer.put(row);
        }
        GL.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, pageX, pageY, width, height, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, scratchByteBuffer);
        scratchIntBuffer.clear();
        
        glyph.setImage(pageImage.getSubImage(pageX, pageY, width, height));
    }
    
    /**
     * Returns an iterator for the specified glyphs, sorted either ascending or descending.
     *
     * @param glyphs
     *            The glyphs to return if present
     * @return An iterator of the sorted list of glyphs
     */
    private Iterator getIterator(List glyphs) {
        if (orderAscending) {
            return glyphs.iterator();
        }
        final ListIterator iter = glyphs.listIterator(glyphs.size());
        
        return new Iterator() {
            @Override
            public boolean hasNext() {
                return iter.hasPrevious();
            }
            
            @Override
            public Glyph next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                return iter.previous();
            }
            
            @Override
            public void remove() {
                iter.remove();
            }
        };
    }
    
    /**
     * Returns the glyphs stored on this page.
     *
     * @return A list of {@link Glyph} elements on this page
     */
    public List getGlyphs() {
        return pageGlyphs;
    }
    
    /**
     * Returns the backing texture for this page.
     *
     * @return The image of this page of glyphs
     */
    public Image getImage() {
        return pageImage;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy