Path: blob/master/SLICK_HOME/src/org/newdawn/slick/font/GlyphPage.java
1460 views
1package org.newdawn.slick.font;23import java.awt.AlphaComposite;4import java.awt.Graphics2D;5import java.awt.RenderingHints;6import java.awt.font.FontRenderContext;7import java.awt.image.BufferedImage;8import java.awt.image.WritableRaster;9import java.nio.ByteBuffer;10import java.nio.ByteOrder;11import java.nio.IntBuffer;12import java.util.ArrayList;13import java.util.Iterator;14import java.util.List;15import java.util.ListIterator;1617import org.lwjgl.opengl.GL11;18import org.lwjgl.opengl.GL12;19import org.newdawn.slick.Color;20import org.newdawn.slick.Image;21import org.newdawn.slick.SlickException;22import org.newdawn.slick.UnicodeFont;23import org.newdawn.slick.font.effects.Effect;24import org.newdawn.slick.opengl.TextureImpl;25import org.newdawn.slick.opengl.renderer.Renderer;26import org.newdawn.slick.opengl.renderer.SGL;2728/**29* Stores a number of glyphs on a single texture.30*31* @author Nathan Sweet <[email protected]>32*/33public class GlyphPage {34/** The interface to OpenGL */35private static final SGL GL = Renderer.get();3637/** The maxium size of an individual glyph */38public static final int MAX_GLYPH_SIZE = 256;3940/** A temporary working buffer */41private static ByteBuffer scratchByteBuffer = ByteBuffer.allocateDirect(MAX_GLYPH_SIZE * MAX_GLYPH_SIZE * 4);4243static {44scratchByteBuffer.order(ByteOrder.LITTLE_ENDIAN);45}4647/** A temporary working buffer */48private static IntBuffer scratchIntBuffer = scratchByteBuffer.asIntBuffer();495051/** A temporary image used to generate the glyph page */52private static BufferedImage scratchImage = new BufferedImage(MAX_GLYPH_SIZE, MAX_GLYPH_SIZE, BufferedImage.TYPE_INT_ARGB);53/** The graphics context form the temporary image */54private static Graphics2D scratchGraphics = (Graphics2D)scratchImage.getGraphics();5556static {57scratchGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);58scratchGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);59scratchGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);60}6162/** The render context in which the glyphs will be generated */63public static FontRenderContext renderContext = scratchGraphics.getFontRenderContext();6465/**66* Get the scratch graphics used to generate the page of glyphs67*68* @return The scratch graphics used to build the page69*/70public static Graphics2D getScratchGraphics() {71return scratchGraphics;72}7374/** The font this page is part of */75private final UnicodeFont unicodeFont;76/** The width of this page's image */77private final int pageWidth;78/** The height of this page's image */79private final int pageHeight;80/** The image containing the glyphs */81private final Image pageImage;82/** The x position of the page */83private int pageX;84/** The y position of the page */85private int pageY;86/** The height of the last row on the page */87private int rowHeight;88/** True if the glyphs are ordered */89private boolean orderAscending;90/** The list of glyphs on this page */91private final List pageGlyphs = new ArrayList(32);9293/**94* Create a new page of glyphs95*96* @param unicodeFont The font this page forms part of97* @param pageWidth The width of the backing texture.98* @param pageHeight The height of the backing texture.99* @throws SlickException if the backing texture could not be created.100*/101public GlyphPage(UnicodeFont unicodeFont, int pageWidth, int pageHeight) throws SlickException {102this.unicodeFont = unicodeFont;103this.pageWidth = pageWidth;104this.pageHeight = pageHeight;105106pageImage = new Image(pageWidth, pageHeight);107}108109/**110* Loads glyphs to the backing texture and sets the image on each loaded glyph. Loaded glyphs are removed from the list.111*112* If this page already has glyphs and maxGlyphsToLoad is -1, then this method will return 0 if all the new glyphs don't fit.113* This reduces texture binds when drawing since glyphs loaded at once are typically displayed together.114* @param glyphs The glyphs to load.115* @param maxGlyphsToLoad This is the maximum number of glyphs to load from the list. Set to -1 to attempt to load all the116* glyphs.117* @return The number of glyphs that were actually loaded.118* @throws SlickException if the glyph could not be rendered.119*/120public int loadGlyphs (List glyphs, int maxGlyphsToLoad) throws SlickException {121if (rowHeight != 0 && maxGlyphsToLoad == -1) {122// If this page has glyphs and we are not loading incrementally, return zero if any of the glyphs don't fit.123int testX = pageX;124int testY = pageY;125int testRowHeight = rowHeight;126for (Iterator iter = getIterator(glyphs); iter.hasNext();) {127Glyph glyph = (Glyph)iter.next();128int width = glyph.getWidth();129int height = glyph.getHeight();130if (testX + width >= pageWidth) {131testX = 0;132testY += testRowHeight;133testRowHeight = height;134} else if (height > testRowHeight) {135testRowHeight = height;136}137if (testY + testRowHeight >= pageWidth) return 0;138testX += width;139}140}141142Color.white.bind();143pageImage.bind();144145int i = 0;146for (Iterator iter = getIterator(glyphs); iter.hasNext();) {147Glyph glyph = (Glyph)iter.next();148int width = Math.min(MAX_GLYPH_SIZE, glyph.getWidth());149int height = Math.min(MAX_GLYPH_SIZE, glyph.getHeight());150151if (rowHeight == 0) {152// The first glyph always fits.153rowHeight = height;154} else {155// Wrap to the next line if needed, or break if no more fit.156if (pageX + width >= pageWidth) {157if (pageY + rowHeight + height >= pageHeight) break;158pageX = 0;159pageY += rowHeight;160rowHeight = height;161} else if (height > rowHeight) {162if (pageY + height >= pageHeight) break;163rowHeight = height;164}165}166167renderGlyph(glyph, width, height);168pageGlyphs.add(glyph);169170pageX += width;171172iter.remove();173i++;174if (i == maxGlyphsToLoad) {175// If loading incrementally, flip orderAscending so it won't change, since we'll probably load the rest next time.176orderAscending = !orderAscending;177break;178}179}180181TextureImpl.bindNone();182183// Every other batch of glyphs added to a page are sorted the opposite way to attempt to keep same size glyps together.184orderAscending = !orderAscending;185186return i;187}188189/**190* Loads a single glyph to the backing texture, if it fits.191*192* @param glyph The glyph to be rendered193* @param width The expected width of the glyph194* @param height The expected height of the glyph195* @throws SlickException if the glyph could not be rendered.196*/197private void renderGlyph(Glyph glyph, int width, int height) throws SlickException {198// Draw the glyph to the scratch image using Java2D.199scratchGraphics.setComposite(AlphaComposite.Clear);200scratchGraphics.fillRect(0, 0, MAX_GLYPH_SIZE, MAX_GLYPH_SIZE);201scratchGraphics.setComposite(AlphaComposite.SrcOver);202scratchGraphics.setColor(java.awt.Color.white);203for (Iterator iter = unicodeFont.getEffects().iterator(); iter.hasNext();)204((Effect)iter.next()).draw(scratchImage, scratchGraphics, unicodeFont, glyph);205glyph.setShape(null); // The shape will never be needed again.206207WritableRaster raster = scratchImage.getRaster();208int[] row = new int[width];209for (int y = 0; y < height; y++) {210raster.getDataElements(0, y, width, 1, row);211scratchIntBuffer.put(row);212}213GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, pageX, pageY, width, height, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE,214scratchByteBuffer);215scratchIntBuffer.clear();216217glyph.setImage(pageImage.getSubImage(pageX, pageY, width, height));218}219220/**221* Returns an iterator for the specified glyphs, sorted either ascending or descending.222*223* @param glyphs The glyphs to return if present224* @return An iterator of the sorted list of glyphs225*/226private Iterator getIterator(List glyphs) {227if (orderAscending) return glyphs.iterator();228final ListIterator iter = glyphs.listIterator(glyphs.size());229return new Iterator() {230public boolean hasNext () {231return iter.hasPrevious();232}233234public Object next () {235return iter.previous();236}237238public void remove () {239iter.remove();240}241};242}243244/**245* Returns the glyphs stored on this page.246*247* @return A list of {@link Glyph} elements on this page248*/249public List getGlyphs () {250return pageGlyphs;251}252253/**254* Returns the backing texture for this page.255*256* @return The image of this page of glyphs257*/258public Image getImage () {259return pageImage;260}261}262263264