gdx-studio
git clone https://git.pyrossh.dev/gdx-studio
An IDE for creating Games using libgdx and Java supported on all platforms Android, iOS, Desktop
src_libs/com/badlogic/gdx/tools/hiero/unicodefont/GlyphPage.java
| a62d533 | 1 | /******************************************************************************* |
| a62d533 | 2 | * Copyright 2011 See AUTHORS file. |
| a62d533 | 3 | * |
| a62d533 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| a62d533 | 5 | * you may not use this file except in compliance with the License. |
| a62d533 | 6 | * You may obtain a copy of the License at |
| a62d533 | 7 | * |
| a62d533 | 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| a62d533 | 9 | * |
| a62d533 | 10 | * Unless required by applicable law or agreed to in writing, software |
| a62d533 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| a62d533 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| a62d533 | 13 | * See the License for the specific language governing permissions and |
| a62d533 | 14 | * limitations under the License. |
| a62d533 | 15 | ******************************************************************************/ |
| a62d533 | 16 | |
| a62d533 | 17 | package com.badlogic.gdx.tools.hiero.unicodefont; |
| a62d533 | 18 | |
| a62d533 | 19 | import java.awt.AlphaComposite; |
| a62d533 | 20 | import java.awt.Graphics2D; |
| a62d533 | 21 | import java.awt.RenderingHints; |
| a62d533 | 22 | import java.awt.font.FontRenderContext; |
| a62d533 | 23 | import java.awt.image.BufferedImage; |
| a62d533 | 24 | import java.awt.image.WritableRaster; |
| a62d533 | 25 | import java.nio.ByteBuffer; |
| a62d533 | 26 | import java.nio.ByteOrder; |
| a62d533 | 27 | import java.nio.IntBuffer; |
| a62d533 | 28 | import java.util.ArrayList; |
| a62d533 | 29 | import java.util.Iterator; |
| a62d533 | 30 | import java.util.List; |
| a62d533 | 31 | import java.util.ListIterator; |
| a62d533 | 32 | |
| a62d533 | 33 | import org.lwjgl.opengl.GL11; |
| a62d533 | 34 | import org.lwjgl.opengl.GL12; |
| a62d533 | 35 | |
| a62d533 | 36 | import com.badlogic.gdx.graphics.Pixmap.Format; |
| a62d533 | 37 | import com.badlogic.gdx.graphics.Texture; |
| a62d533 | 38 | import com.badlogic.gdx.tools.hiero.unicodefont.effects.ColorEffect; |
| a62d533 | 39 | import com.badlogic.gdx.tools.hiero.unicodefont.effects.Effect; |
| a62d533 | 40 | |
| a62d533 | 41 | /** Stores a number of glyphs on a single texture. |
| a62d533 | 42 | * @author Nathan Sweet */ |
| a62d533 | 43 | public class GlyphPage {
|
| a62d533 | 44 | private final UnicodeFont unicodeFont; |
| a62d533 | 45 | private final int pageWidth, pageHeight; |
| a62d533 | 46 | private final Texture texture; |
| a62d533 | 47 | private int pageX, pageY, rowHeight; |
| a62d533 | 48 | private boolean orderAscending; |
| a62d533 | 49 | private final List pageGlyphs = new ArrayList(32); |
| a62d533 | 50 | |
| a62d533 | 51 | /** @param pageWidth The width of the backing texture. |
| a62d533 | 52 | * @param pageHeight The height of the backing texture. */ |
| a62d533 | 53 | GlyphPage (UnicodeFont unicodeFont, int pageWidth, int pageHeight) {
|
| a62d533 | 54 | this.unicodeFont = unicodeFont; |
| a62d533 | 55 | this.pageWidth = pageWidth; |
| a62d533 | 56 | this.pageHeight = pageHeight; |
| a62d533 | 57 | |
| a62d533 | 58 | texture = new Texture(pageWidth, pageHeight, Format.RGBA8888); |
| a62d533 | 59 | } |
| a62d533 | 60 | |
| a62d533 | 61 | /** Loads glyphs to the backing texture and sets the image on each loaded glyph. Loaded glyphs are removed from the list. |
| a62d533 | 62 | * |
| a62d533 | 63 | * If this page already has glyphs and maxGlyphsToLoad is -1, then this method will return 0 if all the new glyphs don't fit. |
| a62d533 | 64 | * This reduces texture binds when drawing since glyphs loaded at once are typically displayed together. |
| a62d533 | 65 | * @param glyphs The glyphs to load. |
| a62d533 | 66 | * @param maxGlyphsToLoad This is the maximum number of glyphs to load from the list. Set to -1 to attempt to load all the |
| a62d533 | 67 | * glyphs. |
| a62d533 | 68 | * @return The number of glyphs that were actually loaded. */ |
| a62d533 | 69 | int loadGlyphs (List glyphs, int maxGlyphsToLoad) {
|
| a62d533 | 70 | if (rowHeight != 0 && maxGlyphsToLoad == -1) {
|
| a62d533 | 71 | // If this page has glyphs and we are not loading incrementally, return zero if any of the glyphs don't fit. |
| a62d533 | 72 | int testX = pageX; |
| a62d533 | 73 | int testY = pageY; |
| a62d533 | 74 | int testRowHeight = rowHeight; |
| a62d533 | 75 | for (Iterator iter = getIterator(glyphs); iter.hasNext();) {
|
| a62d533 | 76 | Glyph glyph = (Glyph)iter.next(); |
| a62d533 | 77 | int width = glyph.getWidth(); |
| a62d533 | 78 | int height = glyph.getHeight(); |
| a62d533 | 79 | if (testX + width >= pageWidth) {
|
| a62d533 | 80 | testX = 0; |
| a62d533 | 81 | testY += testRowHeight; |
| a62d533 | 82 | testRowHeight = height; |
| a62d533 | 83 | } else if (height > testRowHeight) {
|
| a62d533 | 84 | testRowHeight = height; |
| a62d533 | 85 | } |
| a62d533 | 86 | if (testY + testRowHeight >= pageWidth) return 0; |
| a62d533 | 87 | testX += width; |
| a62d533 | 88 | } |
| a62d533 | 89 | } |
| a62d533 | 90 | |
| a62d533 | 91 | GL11.glColor4f(1, 1, 1, 1); |
| a62d533 | 92 | texture.bind(); |
| a62d533 | 93 | |
| a62d533 | 94 | int i = 0; |
| a62d533 | 95 | for (Iterator iter = getIterator(glyphs); iter.hasNext();) {
|
| a62d533 | 96 | Glyph glyph = (Glyph)iter.next(); |
| a62d533 | 97 | int width = Math.min(MAX_GLYPH_SIZE, glyph.getWidth()); |
| a62d533 | 98 | int height = Math.min(MAX_GLYPH_SIZE, glyph.getHeight()); |
| a62d533 | 99 | |
| a62d533 | 100 | if (rowHeight == 0) {
|
| a62d533 | 101 | // The first glyph always fits. |
| a62d533 | 102 | rowHeight = height; |
| a62d533 | 103 | } else {
|
| a62d533 | 104 | // Wrap to the next line if needed, or break if no more fit. |
| a62d533 | 105 | if (pageX + width >= pageWidth) {
|
| a62d533 | 106 | if (pageY + rowHeight + height >= pageHeight) break; |
| a62d533 | 107 | pageX = 0; |
| a62d533 | 108 | pageY += rowHeight; |
| a62d533 | 109 | rowHeight = height; |
| a62d533 | 110 | } else if (height > rowHeight) {
|
| a62d533 | 111 | if (pageY + height >= pageHeight) break; |
| a62d533 | 112 | rowHeight = height; |
| a62d533 | 113 | } |
| a62d533 | 114 | } |
| a62d533 | 115 | |
| a62d533 | 116 | renderGlyph(glyph, width, height); |
| a62d533 | 117 | pageGlyphs.add(glyph); |
| a62d533 | 118 | |
| a62d533 | 119 | pageX += width; |
| a62d533 | 120 | |
| a62d533 | 121 | iter.remove(); |
| a62d533 | 122 | i++; |
| a62d533 | 123 | if (i == maxGlyphsToLoad) {
|
| a62d533 | 124 | // If loading incrementally, flip orderAscending so it won't change, since we'll probably load the rest next time. |
| a62d533 | 125 | orderAscending = !orderAscending; |
| a62d533 | 126 | break; |
| a62d533 | 127 | } |
| a62d533 | 128 | } |
| a62d533 | 129 | |
| a62d533 | 130 | // Every other batch of glyphs added to a page are sorted the opposite way to attempt to keep same size glyps together. |
| a62d533 | 131 | orderAscending = !orderAscending; |
| a62d533 | 132 | |
| a62d533 | 133 | return i; |
| a62d533 | 134 | } |
| a62d533 | 135 | |
| a62d533 | 136 | /** Loads a single glyph to the backing texture, if it fits. */ |
| a62d533 | 137 | private void renderGlyph (Glyph glyph, int width, int height) {
|
| a62d533 | 138 | // Draw the glyph to the scratch image using Java2D. |
| a62d533 | 139 | scratchGraphics.setComposite(AlphaComposite.Clear); |
| a62d533 | 140 | scratchGraphics.fillRect(0, 0, MAX_GLYPH_SIZE, MAX_GLYPH_SIZE); |
| a62d533 | 141 | scratchGraphics.setComposite(AlphaComposite.SrcOver); |
| a62d533 | 142 | if (unicodeFont.getNativeRendering()) {
|
| a62d533 | 143 | for (Iterator iter = unicodeFont.getEffects().iterator(); iter.hasNext();) {
|
| a62d533 | 144 | Effect effect = (Effect)iter.next(); |
| a62d533 | 145 | if (effect instanceof ColorEffect) scratchGraphics.setColor(((ColorEffect)effect).getColor()); |
| a62d533 | 146 | } |
| a62d533 | 147 | scratchGraphics.setColor(java.awt.Color.white); |
| a62d533 | 148 | scratchGraphics.setFont(unicodeFont.getFont()); |
| a62d533 | 149 | scratchGraphics.drawString("" + (char)glyph.getCodePoint(), 0, unicodeFont.getAscent());
|
| a62d533 | 150 | } else {
|
| a62d533 | 151 | scratchGraphics.setColor(java.awt.Color.white); |
| a62d533 | 152 | for (Iterator iter = unicodeFont.getEffects().iterator(); iter.hasNext();) |
| a62d533 | 153 | ((Effect)iter.next()).draw(scratchImage, scratchGraphics, unicodeFont, glyph); |
| a62d533 | 154 | glyph.setShape(null); // The shape will never be needed again. |
| a62d533 | 155 | } |
| a62d533 | 156 | |
| a62d533 | 157 | width = Math.min(width, texture.getWidth()); |
| a62d533 | 158 | height = Math.min(height, texture.getHeight()); |
| a62d533 | 159 | |
| a62d533 | 160 | WritableRaster raster = scratchImage.getRaster(); |
| a62d533 | 161 | int[] row = new int[width]; |
| a62d533 | 162 | for (int y = 0; y < height; y++) {
|
| a62d533 | 163 | raster.getDataElements(0, y, width, 1, row); |
| a62d533 | 164 | scratchIntBuffer.put(row); |
| a62d533 | 165 | } |
| a62d533 | 166 | GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, pageX, pageY, width, height, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, |
| a62d533 | 167 | scratchByteBuffer); |
| a62d533 | 168 | scratchIntBuffer.clear(); |
| a62d533 | 169 | |
| a62d533 | 170 | float u = pageX / (float)texture.getWidth(); |
| a62d533 | 171 | float v = pageY / (float)texture.getHeight(); |
| a62d533 | 172 | float u2 = (pageX + width) / (float)texture.getWidth(); |
| a62d533 | 173 | float v2 = (pageY + height) / (float)texture.getHeight(); |
| a62d533 | 174 | glyph.setTexture(texture, u, v, u2, v2); |
| a62d533 | 175 | } |
| a62d533 | 176 | |
| a62d533 | 177 | /** Returns an iterator for the specified glyphs, sorted either ascending or descending. */ |
| a62d533 | 178 | private Iterator getIterator (List glyphs) {
|
| a62d533 | 179 | if (orderAscending) return glyphs.iterator(); |
| a62d533 | 180 | final ListIterator iter = glyphs.listIterator(glyphs.size()); |
| a62d533 | 181 | return new Iterator() {
|
| a62d533 | 182 | public boolean hasNext () {
|
| a62d533 | 183 | return iter.hasPrevious(); |
| a62d533 | 184 | } |
| a62d533 | 185 | |
| a62d533 | 186 | public Object next () {
|
| a62d533 | 187 | return iter.previous(); |
| a62d533 | 188 | } |
| a62d533 | 189 | |
| a62d533 | 190 | public void remove () {
|
| a62d533 | 191 | iter.remove(); |
| a62d533 | 192 | } |
| a62d533 | 193 | }; |
| a62d533 | 194 | } |
| a62d533 | 195 | |
| a62d533 | 196 | /** Returns the glyphs stored on this page. */ |
| a62d533 | 197 | public List getGlyphs () {
|
| a62d533 | 198 | return pageGlyphs; |
| a62d533 | 199 | } |
| a62d533 | 200 | |
| a62d533 | 201 | /** Returns the backing texture for this page. */ |
| a62d533 | 202 | public Texture getTexture () {
|
| a62d533 | 203 | return texture; |
| a62d533 | 204 | } |
| a62d533 | 205 | |
| a62d533 | 206 | static public final int MAX_GLYPH_SIZE = 256; |
| a62d533 | 207 | |
| a62d533 | 208 | static private ByteBuffer scratchByteBuffer = ByteBuffer.allocateDirect(MAX_GLYPH_SIZE * MAX_GLYPH_SIZE * 4); |
| a62d533 | 209 | static {
|
| a62d533 | 210 | scratchByteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
| a62d533 | 211 | } |
| a62d533 | 212 | static private IntBuffer scratchIntBuffer = scratchByteBuffer.asIntBuffer(); |
| a62d533 | 213 | |
| a62d533 | 214 | static private BufferedImage scratchImage = new BufferedImage(MAX_GLYPH_SIZE, MAX_GLYPH_SIZE, BufferedImage.TYPE_INT_ARGB); |
| a62d533 | 215 | static Graphics2D scratchGraphics = (Graphics2D)scratchImage.getGraphics(); |
| a62d533 | 216 | static {
|
| a62d533 | 217 | scratchGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| a62d533 | 218 | scratchGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); |
| a62d533 | 219 | } |
| a62d533 | 220 | static public FontRenderContext renderContext = scratchGraphics.getFontRenderContext(); |
| a62d533 | 221 | } |