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/UnicodeFont.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.Font; |
| a62d533 | 20 | import java.awt.FontFormatException; |
| a62d533 | 21 | import java.awt.FontMetrics; |
| a62d533 | 22 | import java.awt.Rectangle; |
| a62d533 | 23 | import java.awt.font.GlyphVector; |
| a62d533 | 24 | import java.awt.font.TextAttribute; |
| a62d533 | 25 | import java.io.IOException; |
| a62d533 | 26 | import java.lang.reflect.Field; |
| a62d533 | 27 | import java.util.ArrayList; |
| a62d533 | 28 | import java.util.Collections; |
| a62d533 | 29 | import java.util.Comparator; |
| a62d533 | 30 | import java.util.Iterator; |
| a62d533 | 31 | import java.util.LinkedHashMap; |
| a62d533 | 32 | import java.util.List; |
| a62d533 | 33 | import java.util.Map; |
| a62d533 | 34 | import java.util.Map.Entry; |
| a62d533 | 35 | |
| a62d533 | 36 | import org.lwjgl.opengl.GL11; |
| a62d533 | 37 | |
| a62d533 | 38 | import com.badlogic.gdx.Gdx; |
| a62d533 | 39 | import com.badlogic.gdx.graphics.Color; |
| a62d533 | 40 | import com.badlogic.gdx.graphics.Texture; |
| a62d533 | 41 | import com.badlogic.gdx.tools.hiero.unicodefont.effects.Effect; |
| a62d533 | 42 | import com.badlogic.gdx.utils.GdxRuntimeException; |
| a62d533 | 43 | |
| a62d533 | 44 | // BOZO - Look at actual pixels to determine glyph size, current size sometimes selects blank pixels (eg Calibri, 45, 'o'). |
| a62d533 | 45 | |
| a62d533 | 46 | /** A bitmap font that can display unicode glyphs from a TrueTypeFont. |
| a62d533 | 47 | * |
| a62d533 | 48 | * For efficiency, glyphs are packed on to textures. Glyphs can be loaded to the textures on the fly, when they are first needed |
| a62d533 | 49 | * for display. However, it is best to load the glyphs that are known to be needed at startup. |
| a62d533 | 50 | * @author Nathan Sweet */ |
| a62d533 | 51 | public class UnicodeFont {
|
| a62d533 | 52 | static private final int DISPLAY_LIST_CACHE_SIZE = 200; |
| a62d533 | 53 | static private final int MAX_GLYPH_CODE = 0x10FFFF; |
| a62d533 | 54 | static private final int PAGE_SIZE = 512; |
| a62d533 | 55 | static private final int PAGES = MAX_GLYPH_CODE / PAGE_SIZE; |
| a62d533 | 56 | |
| a62d533 | 57 | private Font font; |
| a62d533 | 58 | private FontMetrics metrics; |
| a62d533 | 59 | private String ttfFileRef; |
| a62d533 | 60 | private int ascent, descent, leading, spaceWidth; |
| a62d533 | 61 | private final Glyph[][] glyphs = new Glyph[PAGES][]; |
| a62d533 | 62 | private final List glyphPages = new ArrayList(); |
| a62d533 | 63 | private final List queuedGlyphs = new ArrayList(256); |
| a62d533 | 64 | private final List effects = new ArrayList(); |
| a62d533 | 65 | private int paddingTop, paddingLeft, paddingBottom, paddingRight, paddingAdvanceX, paddingAdvanceY; |
| a62d533 | 66 | private Glyph missingGlyph; |
| a62d533 | 67 | private int glyphPageWidth = 512, glyphPageHeight = 512; |
| a62d533 | 68 | private final DisplayList emptyDisplayList = new DisplayList(); |
| a62d533 | 69 | private boolean nativeRendering; |
| a62d533 | 70 | |
| a62d533 | 71 | private int baseDisplayListID = -1; |
| a62d533 | 72 | int eldestDisplayListID; |
| a62d533 | 73 | private final LinkedHashMap displayLists = new LinkedHashMap(DISPLAY_LIST_CACHE_SIZE, 1, true) {
|
| a62d533 | 74 | protected boolean removeEldestEntry (Entry eldest) {
|
| a62d533 | 75 | DisplayList displayList = (DisplayList)eldest.getValue(); |
| a62d533 | 76 | if (displayList != null) eldestDisplayListID = displayList.id; |
| a62d533 | 77 | return size() > DISPLAY_LIST_CACHE_SIZE; |
| a62d533 | 78 | } |
| a62d533 | 79 | }; |
| a62d533 | 80 | |
| a62d533 | 81 | /** @param ttfFileRef The file system or classpath location of the TrueTypeFont file. |
| a62d533 | 82 | * @param hieroFileRef The file system or classpath location of the Hiero settings file. */ |
| a62d533 | 83 | public UnicodeFont (String ttfFileRef, String hieroFileRef) {
|
| a62d533 | 84 | this(ttfFileRef, new HieroSettings(hieroFileRef)); |
| a62d533 | 85 | } |
| a62d533 | 86 | |
| a62d533 | 87 | /** @param ttfFileRef The file system or classpath location of the TrueTypeFont file. */ |
| a62d533 | 88 | public UnicodeFont (String ttfFileRef, HieroSettings settings) {
|
| a62d533 | 89 | this.ttfFileRef = ttfFileRef; |
| a62d533 | 90 | Font font = createFont(ttfFileRef); |
| a62d533 | 91 | initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic()); |
| a62d533 | 92 | loadSettings(settings); |
| a62d533 | 93 | } |
| a62d533 | 94 | |
| a62d533 | 95 | /** @param ttfFileRef The file system or classpath location of the TrueTypeFont file. */ |
| a62d533 | 96 | public UnicodeFont (String ttfFileRef, int size, boolean bold, boolean italic) {
|
| a62d533 | 97 | this.ttfFileRef = ttfFileRef; |
| a62d533 | 98 | initializeFont(createFont(ttfFileRef), size, bold, italic); |
| a62d533 | 99 | } |
| a62d533 | 100 | |
| a62d533 | 101 | /** Creates a new UnicodeFont. |
| a62d533 | 102 | * @param hieroFileRef The file system or classpath location of the Hiero settings file. */ |
| a62d533 | 103 | public UnicodeFont (Font font, String hieroFileRef) {
|
| a62d533 | 104 | this(font, new HieroSettings(hieroFileRef)); |
| a62d533 | 105 | } |
| a62d533 | 106 | |
| a62d533 | 107 | /** Creates a new UnicodeFont. */ |
| a62d533 | 108 | public UnicodeFont (Font font, HieroSettings settings) {
|
| a62d533 | 109 | initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic()); |
| a62d533 | 110 | loadSettings(settings); |
| a62d533 | 111 | } |
| a62d533 | 112 | |
| a62d533 | 113 | /** Creates a new UnicodeFont. */ |
| a62d533 | 114 | public UnicodeFont (Font font) {
|
| a62d533 | 115 | initializeFont(font, font.getSize(), font.isBold(), font.isItalic()); |
| a62d533 | 116 | } |
| a62d533 | 117 | |
| a62d533 | 118 | /** Creates a new UnicodeFont. */ |
| a62d533 | 119 | public UnicodeFont (Font font, int size, boolean bold, boolean italic) {
|
| a62d533 | 120 | initializeFont(font, size, bold, italic); |
| a62d533 | 121 | } |
| a62d533 | 122 | |
| a62d533 | 123 | private void initializeFont (Font baseFont, int size, boolean bold, boolean italic) {
|
| a62d533 | 124 | Map attributes = baseFont.getAttributes(); |
| a62d533 | 125 | attributes.put(TextAttribute.SIZE, new Float(size)); |
| a62d533 | 126 | attributes.put(TextAttribute.WEIGHT, bold ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR); |
| a62d533 | 127 | attributes.put(TextAttribute.POSTURE, italic ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR); |
| a62d533 | 128 | try {
|
| a62d533 | 129 | attributes.put(TextAttribute.class.getDeclaredField("KERNING").get(null),
|
| a62d533 | 130 | TextAttribute.class.getDeclaredField("KERNING_ON").get(null));
|
| a62d533 | 131 | } catch (Throwable ignored) {
|
| a62d533 | 132 | } |
| a62d533 | 133 | font = baseFont.deriveFont(attributes); |
| a62d533 | 134 | |
| a62d533 | 135 | metrics = GlyphPage.scratchGraphics.getFontMetrics(font); |
| a62d533 | 136 | ascent = metrics.getAscent(); |
| a62d533 | 137 | descent = metrics.getDescent(); |
| a62d533 | 138 | leading = metrics.getLeading(); |
| a62d533 | 139 | |
| a62d533 | 140 | // Determine width of space glyph (getGlyphPixelBounds gives a width of zero). |
| a62d533 | 141 | char[] chars = " ".toCharArray(); |
| a62d533 | 142 | GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT); |
| a62d533 | 143 | spaceWidth = vector.getGlyphLogicalBounds(0).getBounds().width; |
| a62d533 | 144 | } |
| a62d533 | 145 | |
| a62d533 | 146 | private void loadSettings (HieroSettings settings) {
|
| a62d533 | 147 | paddingTop = settings.getPaddingTop(); |
| a62d533 | 148 | paddingLeft = settings.getPaddingLeft(); |
| a62d533 | 149 | paddingBottom = settings.getPaddingBottom(); |
| a62d533 | 150 | paddingRight = settings.getPaddingRight(); |
| a62d533 | 151 | paddingAdvanceX = settings.getPaddingAdvanceX(); |
| a62d533 | 152 | paddingAdvanceY = settings.getPaddingAdvanceY(); |
| a62d533 | 153 | glyphPageWidth = settings.getGlyphPageWidth(); |
| a62d533 | 154 | glyphPageHeight = settings.getGlyphPageHeight(); |
| a62d533 | 155 | effects.addAll(settings.getEffects()); |
| a62d533 | 156 | } |
| a62d533 | 157 | |
| a62d533 | 158 | /** Queues the glyphs in the specified codepoint range (inclusive) to be loaded. Note that the glyphs are not actually loaded |
| a62d533 | 159 | * until {@link #loadGlyphs()} is called.
|
| a62d533 | 160 | * |
| a62d533 | 161 | * Some characters like combining marks and non-spacing marks can only be rendered with the context of other glyphs. In this |
| a62d533 | 162 | * case, use {@link #addGlyphs(String)}. */
|
| a62d533 | 163 | public void addGlyphs (int startCodePoint, int endCodePoint) {
|
| a62d533 | 164 | for (int codePoint = startCodePoint; codePoint <= endCodePoint; codePoint++) |
| a62d533 | 165 | addGlyphs(new String(Character.toChars(codePoint))); |
| a62d533 | 166 | } |
| a62d533 | 167 | |
| a62d533 | 168 | /** Queues the glyphs in the specified text to be loaded. Note that the glyphs are not actually loaded until |
| a62d533 | 169 | * {@link #loadGlyphs()} is called. */
|
| a62d533 | 170 | public void addGlyphs (String text) {
|
| a62d533 | 171 | if (text == null) throw new IllegalArgumentException("text cannot be null.");
|
| a62d533 | 172 | |
| a62d533 | 173 | char[] chars = text.toCharArray(); |
| a62d533 | 174 | GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT); |
| a62d533 | 175 | for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {
|
| a62d533 | 176 | int codePoint = text.codePointAt(vector.getGlyphCharIndex(i)); |
| a62d533 | 177 | Rectangle bounds = getGlyphBounds(vector, i, codePoint); |
| a62d533 | 178 | getGlyph(vector.getGlyphCode(i), codePoint, bounds, vector, i); |
| a62d533 | 179 | } |
| a62d533 | 180 | } |
| a62d533 | 181 | |
| a62d533 | 182 | /** Queues the glyphs in the ASCII character set (codepoints 32 through 255) to be loaded. Note that the glyphs are not actually |
| a62d533 | 183 | * loaded until {@link #loadGlyphs()} is called. */
|
| a62d533 | 184 | public void addAsciiGlyphs () {
|
| a62d533 | 185 | addGlyphs(32, 255); |
| a62d533 | 186 | } |
| a62d533 | 187 | |
| a62d533 | 188 | /** Queues the glyphs in the NEHE character set (codepoints 32 through 128) to be loaded. Note that the glyphs are not actually |
| a62d533 | 189 | * loaded until {@link #loadGlyphs()} is called. */
|
| a62d533 | 190 | public void addNeheGlyphs () {
|
| a62d533 | 191 | addGlyphs(32, 32 + 96); |
| a62d533 | 192 | } |
| a62d533 | 193 | |
| a62d533 | 194 | /** Loads all queued glyphs to the backing textures. Glyphs that are typically displayed together should be added and loaded at |
| a62d533 | 195 | * the same time so that they are stored on the same backing texture. This reduces the number of backing texture binds required |
| a62d533 | 196 | * to draw glyphs. */ |
| a62d533 | 197 | public boolean loadGlyphs () {
|
| a62d533 | 198 | return loadGlyphs(-1); |
| a62d533 | 199 | } |
| a62d533 | 200 | |
| a62d533 | 201 | /** Loads up to the specified number of queued glyphs to the backing textures. This is typically called from the game loop to |
| a62d533 | 202 | * load glyphs on the fly that were requested for display but have not yet been loaded. */ |
| a62d533 | 203 | public boolean loadGlyphs (int maxGlyphsToLoad) {
|
| a62d533 | 204 | if (queuedGlyphs.isEmpty()) return false; |
| a62d533 | 205 | |
| a62d533 | 206 | if (effects.isEmpty()) |
| a62d533 | 207 | throw new IllegalStateException("The UnicodeFont must have at least one effect before any glyphs can be loaded.");
|
| a62d533 | 208 | |
| a62d533 | 209 | for (Iterator iter = queuedGlyphs.iterator(); iter.hasNext();) {
|
| a62d533 | 210 | Glyph glyph = (Glyph)iter.next(); |
| a62d533 | 211 | int codePoint = glyph.getCodePoint(); |
| a62d533 | 212 | |
| a62d533 | 213 | // Don't load an image for a glyph with nothing to display. |
| a62d533 | 214 | if (glyph.getWidth() == 0 || codePoint == ' ') {
|
| a62d533 | 215 | iter.remove(); |
| a62d533 | 216 | continue; |
| a62d533 | 217 | } |
| a62d533 | 218 | |
| a62d533 | 219 | // Only load the first missing glyph. |
| a62d533 | 220 | if (glyph.isMissing()) {
|
| a62d533 | 221 | if (missingGlyph != null) {
|
| a62d533 | 222 | if (glyph != missingGlyph) iter.remove(); |
| a62d533 | 223 | continue; |
| a62d533 | 224 | } |
| a62d533 | 225 | missingGlyph = glyph; |
| a62d533 | 226 | } |
| a62d533 | 227 | } |
| a62d533 | 228 | |
| a62d533 | 229 | Collections.sort(queuedGlyphs, heightComparator); |
| a62d533 | 230 | |
| a62d533 | 231 | // Add to existing pages. |
| a62d533 | 232 | for (Iterator iter = glyphPages.iterator(); iter.hasNext();) {
|
| a62d533 | 233 | GlyphPage glyphPage = (GlyphPage)iter.next(); |
| a62d533 | 234 | maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad); |
| a62d533 | 235 | if (maxGlyphsToLoad == 0 || queuedGlyphs.isEmpty()) return true; |
| a62d533 | 236 | } |
| a62d533 | 237 | |
| a62d533 | 238 | // Add to new pages. |
| a62d533 | 239 | while (!queuedGlyphs.isEmpty()) {
|
| a62d533 | 240 | GlyphPage glyphPage = new GlyphPage(this, glyphPageWidth, glyphPageHeight); |
| a62d533 | 241 | glyphPages.add(glyphPage); |
| a62d533 | 242 | maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad); |
| a62d533 | 243 | if (maxGlyphsToLoad == 0) return true; |
| a62d533 | 244 | } |
| a62d533 | 245 | |
| a62d533 | 246 | return true; |
| a62d533 | 247 | } |
| a62d533 | 248 | |
| a62d533 | 249 | /** Clears all loaded and queued glyphs. */ |
| a62d533 | 250 | public void clearGlyphs () {
|
| a62d533 | 251 | for (int i = 0; i < PAGES; i++) |
| a62d533 | 252 | glyphs[i] = null; |
| a62d533 | 253 | |
| a62d533 | 254 | for (Iterator iter = glyphPages.iterator(); iter.hasNext();) {
|
| a62d533 | 255 | GlyphPage page = (GlyphPage)iter.next(); |
| a62d533 | 256 | page.getTexture().dispose(); |
| a62d533 | 257 | } |
| a62d533 | 258 | glyphPages.clear(); |
| a62d533 | 259 | |
| a62d533 | 260 | if (baseDisplayListID != -1) {
|
| a62d533 | 261 | GL11.glDeleteLists(baseDisplayListID, displayLists.size()); |
| a62d533 | 262 | baseDisplayListID = -1; |
| a62d533 | 263 | } |
| a62d533 | 264 | |
| a62d533 | 265 | queuedGlyphs.clear(); |
| a62d533 | 266 | missingGlyph = null; |
| a62d533 | 267 | } |
| a62d533 | 268 | |
| a62d533 | 269 | /** Releases all resources used by this UnicodeFont. This method should be called when this UnicodeFont instance is no longer |
| a62d533 | 270 | * needed. */ |
| a62d533 | 271 | public void destroy () {
|
| a62d533 | 272 | // The destroy() method is just to provide a consistent API for releasing resources. |
| a62d533 | 273 | clearGlyphs(); |
| a62d533 | 274 | } |
| a62d533 | 275 | |
| a62d533 | 276 | /** Identical to {@link #drawString(float, float, String, Color, int, int)} but returns a DisplayList which provides access to
|
| a62d533 | 277 | * the width and height of the text drawn. */ |
| a62d533 | 278 | public void drawDisplayList (float x, float y, String text, Color color, int startIndex, int endIndex) {
|
| a62d533 | 279 | if (text == null) throw new IllegalArgumentException("text cannot be null.");
|
| a62d533 | 280 | if (text.length() == 0) return; |
| a62d533 | 281 | if (color == null) throw new IllegalArgumentException("color cannot be null.");
|
| a62d533 | 282 | |
| a62d533 | 283 | x -= paddingLeft; |
| a62d533 | 284 | y -= paddingTop; |
| a62d533 | 285 | |
| a62d533 | 286 | String displayListKey = text.substring(startIndex, endIndex); |
| a62d533 | 287 | |
| a62d533 | 288 | GL11.glColor4f(color.r, color.g, color.b, color.a); |
| a62d533 | 289 | |
| a62d533 | 290 | GL11.glTranslatef(x, y, 0); |
| a62d533 | 291 | |
| a62d533 | 292 | char[] chars = text.substring(0, endIndex).toCharArray(); |
| a62d533 | 293 | GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT); |
| a62d533 | 294 | |
| a62d533 | 295 | int maxWidth = 0, totalHeight = 0, lines = 0; |
| a62d533 | 296 | int extraX = 0, extraY = ascent; |
| a62d533 | 297 | boolean startNewLine = false; |
| a62d533 | 298 | Texture lastBind = null; |
| a62d533 | 299 | int offsetX = 0; |
| a62d533 | 300 | for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {
|
| a62d533 | 301 | int charIndex = vector.getGlyphCharIndex(glyphIndex); |
| a62d533 | 302 | if (charIndex < startIndex) continue; |
| a62d533 | 303 | if (charIndex > endIndex) break; |
| a62d533 | 304 | |
| a62d533 | 305 | int codePoint = text.codePointAt(charIndex); |
| a62d533 | 306 | |
| a62d533 | 307 | Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint); |
| a62d533 | 308 | bounds.x += offsetX; |
| a62d533 | 309 | Glyph glyph = getGlyph(vector.getGlyphCode(glyphIndex), codePoint, bounds, vector, glyphIndex); |
| a62d533 | 310 | |
| a62d533 | 311 | if (startNewLine && codePoint != '\n') {
|
| a62d533 | 312 | extraX = -bounds.x; |
| a62d533 | 313 | startNewLine = false; |
| a62d533 | 314 | } |
| a62d533 | 315 | |
| a62d533 | 316 | if (glyph.getTexture() == null && missingGlyph != null && glyph.isMissing()) glyph = missingGlyph; |
| a62d533 | 317 | if (glyph.getTexture() != null) {
|
| a62d533 | 318 | // Draw glyph, only binding a new glyph page texture when necessary. |
| a62d533 | 319 | Texture texture = glyph.getTexture(); |
| a62d533 | 320 | if (lastBind != null && lastBind != texture) {
|
| a62d533 | 321 | GL11.glEnd(); |
| a62d533 | 322 | lastBind = null; |
| a62d533 | 323 | } |
| a62d533 | 324 | if (lastBind == null) {
|
| a62d533 | 325 | texture.bind(); |
| a62d533 | 326 | GL11.glBegin(GL11.GL_QUADS); |
| a62d533 | 327 | lastBind = texture; |
| a62d533 | 328 | } |
| a62d533 | 329 | int glyphX = bounds.x + extraX; |
| a62d533 | 330 | int glyphY = bounds.y + extraY; |
| a62d533 | 331 | GL11.glTexCoord2f(glyph.getU(), glyph.getV()); |
| a62d533 | 332 | GL11.glVertex3f(glyphX, glyphY, 0); |
| a62d533 | 333 | GL11.glTexCoord2f(glyph.getU(), glyph.getV2()); |
| a62d533 | 334 | GL11.glVertex3f(glyphX, glyphY + glyph.getHeight(), 0); |
| a62d533 | 335 | GL11.glTexCoord2f(glyph.getU2(), glyph.getV2()); |
| a62d533 | 336 | GL11.glVertex3f(glyphX + glyph.getWidth(), glyphY + glyph.getHeight(), 0); |
| a62d533 | 337 | GL11.glTexCoord2f(glyph.getU2(), glyph.getV()); |
| a62d533 | 338 | GL11.glVertex3f(glyphX + glyph.getWidth(), glyphY, 0); |
| a62d533 | 339 | } |
| a62d533 | 340 | |
| a62d533 | 341 | if (glyphIndex > 0) extraX += paddingRight + paddingLeft + paddingAdvanceX; |
| a62d533 | 342 | maxWidth = Math.max(maxWidth, bounds.x + extraX + bounds.width); |
| a62d533 | 343 | totalHeight = Math.max(totalHeight, ascent + bounds.y + bounds.height); |
| a62d533 | 344 | |
| a62d533 | 345 | if (codePoint == '\n') {
|
| a62d533 | 346 | startNewLine = true; // Mac gives -1 for bounds.x of '\n', so use the bounds.x of the next glyph. |
| a62d533 | 347 | extraY += getLineHeight(); |
| a62d533 | 348 | lines++; |
| a62d533 | 349 | totalHeight = 0; |
| a62d533 | 350 | } else if (nativeRendering) offsetX += bounds.width; |
| a62d533 | 351 | } |
| a62d533 | 352 | if (lastBind != null) GL11.glEnd(); |
| a62d533 | 353 | |
| a62d533 | 354 | GL11.glTranslatef(-x, -y, 0); |
| a62d533 | 355 | } |
| a62d533 | 356 | |
| a62d533 | 357 | public void drawString (float x, float y, String text, Color color, int startIndex, int endIndex) {
|
| a62d533 | 358 | drawDisplayList(x, y, text, color, startIndex, endIndex); |
| a62d533 | 359 | } |
| a62d533 | 360 | |
| a62d533 | 361 | public void drawString (float x, float y, String text) {
|
| a62d533 | 362 | drawString(x, y, text, Color.WHITE); |
| a62d533 | 363 | } |
| a62d533 | 364 | |
| a62d533 | 365 | public void drawString (float x, float y, String text, Color col) {
|
| a62d533 | 366 | drawString(x, y, text, col, 0, text.length()); |
| a62d533 | 367 | } |
| a62d533 | 368 | |
| a62d533 | 369 | /** Returns the glyph for the specified codePoint. If the glyph does not exist yet, it is created and queued to be loaded. */ |
| a62d533 | 370 | private Glyph getGlyph (int glyphCode, int codePoint, Rectangle bounds, GlyphVector vector, int index) {
|
| a62d533 | 371 | if (glyphCode < 0 || glyphCode >= MAX_GLYPH_CODE) {
|
| a62d533 | 372 | // GlyphVector#getGlyphCode sometimes returns negative numbers on OS X!? |
| a62d533 | 373 | return new Glyph(codePoint, bounds, vector, index, this) {
|
| a62d533 | 374 | public boolean isMissing () {
|
| a62d533 | 375 | return true; |
| a62d533 | 376 | } |
| a62d533 | 377 | }; |
| a62d533 | 378 | } |
| a62d533 | 379 | int pageIndex = glyphCode / PAGE_SIZE; |
| a62d533 | 380 | int glyphIndex = glyphCode & (PAGE_SIZE - 1); |
| a62d533 | 381 | Glyph glyph = null; |
| a62d533 | 382 | Glyph[] page = glyphs[pageIndex]; |
| a62d533 | 383 | if (page != null) {
|
| a62d533 | 384 | glyph = page[glyphIndex]; |
| a62d533 | 385 | if (glyph != null) return glyph; |
| a62d533 | 386 | } else |
| a62d533 | 387 | page = glyphs[pageIndex] = new Glyph[PAGE_SIZE]; |
| a62d533 | 388 | // Add glyph so size information is available and queue it so its image can be loaded later. |
| a62d533 | 389 | glyph = page[glyphIndex] = new Glyph(codePoint, bounds, vector, index, this); |
| a62d533 | 390 | queuedGlyphs.add(glyph); |
| a62d533 | 391 | return glyph; |
| a62d533 | 392 | } |
| a62d533 | 393 | |
| a62d533 | 394 | private Rectangle getGlyphBounds (GlyphVector vector, int index, int codePoint) {
|
| a62d533 | 395 | Rectangle bounds; |
| a62d533 | 396 | bounds = vector.getGlyphPixelBounds(index, GlyphPage.renderContext, 0, 0); |
| a62d533 | 397 | if (nativeRendering) {
|
| a62d533 | 398 | if (bounds.width == 0 || bounds.height == 0) |
| a62d533 | 399 | bounds = new Rectangle(); |
| a62d533 | 400 | else |
| a62d533 | 401 | bounds = metrics.getStringBounds("" + (char)codePoint, GlyphPage.scratchGraphics).getBounds();
|
| a62d533 | 402 | } |
| a62d533 | 403 | if (codePoint == ' ') bounds.width = spaceWidth; |
| a62d533 | 404 | return bounds; |
| a62d533 | 405 | } |
| a62d533 | 406 | |
| a62d533 | 407 | public int getSpaceWidth () {
|
| a62d533 | 408 | return spaceWidth; |
| a62d533 | 409 | } |
| a62d533 | 410 | |
| a62d533 | 411 | public int getWidth (String text) {
|
| a62d533 | 412 | if (text == null) throw new IllegalArgumentException("text cannot be null.");
|
| a62d533 | 413 | if (text.length() == 0) return 0; |
| a62d533 | 414 | |
| a62d533 | 415 | char[] chars = text.toCharArray(); |
| a62d533 | 416 | GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT); |
| a62d533 | 417 | |
| a62d533 | 418 | int width = 0; |
| a62d533 | 419 | int extraX = 0; |
| a62d533 | 420 | boolean startNewLine = false; |
| a62d533 | 421 | for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {
|
| a62d533 | 422 | int charIndex = vector.getGlyphCharIndex(glyphIndex); |
| a62d533 | 423 | int codePoint = text.codePointAt(charIndex); |
| a62d533 | 424 | Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint); |
| a62d533 | 425 | |
| a62d533 | 426 | if (startNewLine && codePoint != '\n') extraX = -bounds.x; |
| a62d533 | 427 | |
| a62d533 | 428 | if (glyphIndex > 0) extraX += paddingLeft + paddingRight + paddingAdvanceX; |
| a62d533 | 429 | width = Math.max(width, bounds.x + extraX + bounds.width); |
| a62d533 | 430 | |
| a62d533 | 431 | if (codePoint == '\n') startNewLine = true; |
| a62d533 | 432 | } |
| a62d533 | 433 | |
| a62d533 | 434 | return width; |
| a62d533 | 435 | } |
| a62d533 | 436 | |
| a62d533 | 437 | public int getHeight (String text) {
|
| a62d533 | 438 | if (text == null) throw new IllegalArgumentException("text cannot be null.");
|
| a62d533 | 439 | if (text.length() == 0) return 0; |
| a62d533 | 440 | |
| a62d533 | 441 | char[] chars = text.toCharArray(); |
| a62d533 | 442 | GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT); |
| a62d533 | 443 | |
| a62d533 | 444 | int lines = 0, height = 0; |
| a62d533 | 445 | for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {
|
| a62d533 | 446 | int charIndex = vector.getGlyphCharIndex(i); |
| a62d533 | 447 | int codePoint = text.codePointAt(charIndex); |
| a62d533 | 448 | if (codePoint == ' ') continue; |
| a62d533 | 449 | Rectangle bounds = getGlyphBounds(vector, i, codePoint); |
| a62d533 | 450 | |
| a62d533 | 451 | height = Math.max(height, ascent + bounds.y + bounds.height); |
| a62d533 | 452 | |
| a62d533 | 453 | if (codePoint == '\n') {
|
| a62d533 | 454 | lines++; |
| a62d533 | 455 | height = 0; |
| a62d533 | 456 | } |
| a62d533 | 457 | } |
| a62d533 | 458 | return lines * getLineHeight() + height; |
| a62d533 | 459 | } |
| a62d533 | 460 | |
| a62d533 | 461 | /** Returns the distance from the y drawing location to the top most pixel of the specified text. */ |
| a62d533 | 462 | public int getYOffset (String text) {
|
| a62d533 | 463 | if (text == null) throw new IllegalArgumentException("text cannot be null.");
|
| a62d533 | 464 | |
| a62d533 | 465 | int index = text.indexOf('\n');
|
| a62d533 | 466 | if (index != -1) text = text.substring(0, index); |
| a62d533 | 467 | char[] chars = text.toCharArray(); |
| a62d533 | 468 | GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT); |
| a62d533 | 469 | int yOffset = ascent + vector.getPixelBounds(null, 0, 0).y; |
| a62d533 | 470 | |
| a62d533 | 471 | return yOffset; |
| a62d533 | 472 | } |
| a62d533 | 473 | |
| a62d533 | 474 | /** Returns the TrueTypeFont for this UnicodeFont. */ |
| a62d533 | 475 | public Font getFont () {
|
| a62d533 | 476 | return font; |
| a62d533 | 477 | } |
| a62d533 | 478 | |
| a62d533 | 479 | /** Returns the padding above a glyph on the GlyphPage to allow for effects to be drawn. */ |
| a62d533 | 480 | public int getPaddingTop () {
|
| a62d533 | 481 | return paddingTop; |
| a62d533 | 482 | } |
| a62d533 | 483 | |
| a62d533 | 484 | /** Sets the padding above a glyph on the GlyphPage to allow for effects to be drawn. */ |
| a62d533 | 485 | public void setPaddingTop (int paddingTop) {
|
| a62d533 | 486 | this.paddingTop = paddingTop; |
| a62d533 | 487 | } |
| a62d533 | 488 | |
| a62d533 | 489 | /** Returns the padding to the left of a glyph on the GlyphPage to allow for effects to be drawn. */ |
| a62d533 | 490 | public int getPaddingLeft () {
|
| a62d533 | 491 | return paddingLeft; |
| a62d533 | 492 | } |
| a62d533 | 493 | |
| a62d533 | 494 | /** Sets the padding to the left of a glyph on the GlyphPage to allow for effects to be drawn. */ |
| a62d533 | 495 | public void setPaddingLeft (int paddingLeft) {
|
| a62d533 | 496 | this.paddingLeft = paddingLeft; |
| a62d533 | 497 | } |
| a62d533 | 498 | |
| a62d533 | 499 | /** Returns the padding below a glyph on the GlyphPage to allow for effects to be drawn. */ |
| a62d533 | 500 | public int getPaddingBottom () {
|
| a62d533 | 501 | return paddingBottom; |
| a62d533 | 502 | } |
| a62d533 | 503 | |
| a62d533 | 504 | /** Sets the padding below a glyph on the GlyphPage to allow for effects to be drawn. */ |
| a62d533 | 505 | public void setPaddingBottom (int paddingBottom) {
|
| a62d533 | 506 | this.paddingBottom = paddingBottom; |
| a62d533 | 507 | } |
| a62d533 | 508 | |
| a62d533 | 509 | /** Returns the padding to the right of a glyph on the GlyphPage to allow for effects to be drawn. */ |
| a62d533 | 510 | public int getPaddingRight () {
|
| a62d533 | 511 | return paddingRight; |
| a62d533 | 512 | } |
| a62d533 | 513 | |
| a62d533 | 514 | /** Sets the padding to the right of a glyph on the GlyphPage to allow for effects to be drawn. */ |
| a62d533 | 515 | public void setPaddingRight (int paddingRight) {
|
| a62d533 | 516 | this.paddingRight = paddingRight; |
| a62d533 | 517 | } |
| a62d533 | 518 | |
| a62d533 | 519 | /** Gets the additional amount to offset glyphs on the x axis. */ |
| a62d533 | 520 | public int getPaddingAdvanceX () {
|
| a62d533 | 521 | return paddingAdvanceX; |
| a62d533 | 522 | } |
| a62d533 | 523 | |
| a62d533 | 524 | /** Sets the additional amount to offset glyphs on the x axis. This is typically set to a negative number when left or right |
| a62d533 | 525 | * padding is used so that glyphs are not spaced too far apart. */ |
| a62d533 | 526 | public void setPaddingAdvanceX (int paddingAdvanceX) {
|
| a62d533 | 527 | this.paddingAdvanceX = paddingAdvanceX; |
| a62d533 | 528 | } |
| a62d533 | 529 | |
| a62d533 | 530 | /** Gets the additional amount to offset a line of text on the y axis. */ |
| a62d533 | 531 | public int getPaddingAdvanceY () {
|
| a62d533 | 532 | return paddingAdvanceY; |
| a62d533 | 533 | } |
| a62d533 | 534 | |
| a62d533 | 535 | /** Sets the additional amount to offset a line of text on the y axis. This is typically set to a negative number when top or |
| a62d533 | 536 | * bottom padding is used so that lines of text are not spaced too far apart. */ |
| a62d533 | 537 | public void setPaddingAdvanceY (int paddingAdvanceY) {
|
| a62d533 | 538 | this.paddingAdvanceY = paddingAdvanceY; |
| a62d533 | 539 | } |
| a62d533 | 540 | |
| a62d533 | 541 | /** Returns the distance from one line of text to the next. This is the sum of the descent, ascent, leading, padding top, |
| a62d533 | 542 | * padding bottom, and padding advance y. To change the line height, use {@link #setPaddingAdvanceY(int)}. */
|
| a62d533 | 543 | public int getLineHeight () {
|
| a62d533 | 544 | return descent + ascent + leading + paddingTop + paddingBottom + paddingAdvanceY; |
| a62d533 | 545 | } |
| a62d533 | 546 | |
| a62d533 | 547 | /** Gets the distance from the baseline to the y drawing location. */ |
| a62d533 | 548 | public int getAscent () {
|
| a62d533 | 549 | return ascent; |
| a62d533 | 550 | } |
| a62d533 | 551 | |
| a62d533 | 552 | /** Gets the distance from the baseline to the bottom of most alphanumeric characters with descenders. */ |
| a62d533 | 553 | public int getDescent () {
|
| a62d533 | 554 | return descent; |
| a62d533 | 555 | } |
| a62d533 | 556 | |
| a62d533 | 557 | /** Gets the extra distance between the descent of one line of text to the ascent of the next. */ |
| a62d533 | 558 | public int getLeading () {
|
| a62d533 | 559 | return leading; |
| a62d533 | 560 | } |
| a62d533 | 561 | |
| a62d533 | 562 | /** Returns the width of the backing textures. */ |
| a62d533 | 563 | public int getGlyphPageWidth () {
|
| a62d533 | 564 | return glyphPageWidth; |
| a62d533 | 565 | } |
| a62d533 | 566 | |
| a62d533 | 567 | /** Sets the width of the backing textures. Default is 512. */ |
| a62d533 | 568 | public void setGlyphPageWidth (int glyphPageWidth) {
|
| a62d533 | 569 | this.glyphPageWidth = glyphPageWidth; |
| a62d533 | 570 | } |
| a62d533 | 571 | |
| a62d533 | 572 | /** Returns the height of the backing textures. */ |
| a62d533 | 573 | public int getGlyphPageHeight () {
|
| a62d533 | 574 | return glyphPageHeight; |
| a62d533 | 575 | } |
| a62d533 | 576 | |
| a62d533 | 577 | /** Sets the height of the backing textures. Default is 512. */ |
| a62d533 | 578 | public void setGlyphPageHeight (int glyphPageHeight) {
|
| a62d533 | 579 | this.glyphPageHeight = glyphPageHeight; |
| a62d533 | 580 | } |
| a62d533 | 581 | |
| a62d533 | 582 | /** Returns the GlyphPages for this UnicodeFont. */ |
| a62d533 | 583 | public List getGlyphPages () {
|
| a62d533 | 584 | return glyphPages; |
| a62d533 | 585 | } |
| a62d533 | 586 | |
| a62d533 | 587 | /** Returns a list of {@link Effect}s that will be applied to the glyphs. */
|
| a62d533 | 588 | public List getEffects () {
|
| a62d533 | 589 | return effects; |
| a62d533 | 590 | } |
| a62d533 | 591 | |
| a62d533 | 592 | public void setNativeRendering (boolean nativeRendering) {
|
| a62d533 | 593 | this.nativeRendering = nativeRendering; |
| a62d533 | 594 | } |
| a62d533 | 595 | |
| a62d533 | 596 | public boolean getNativeRendering () {
|
| a62d533 | 597 | return nativeRendering; |
| a62d533 | 598 | } |
| a62d533 | 599 | |
| a62d533 | 600 | /** Returns the path to the TTF file for this UnicodeFont, or null. If this UnicodeFont was created without specifying the TTF |
| a62d533 | 601 | * file, it will try to determine the path using Sun classes. If this fails, null is returned. */ |
| a62d533 | 602 | public String getFontFile () {
|
| a62d533 | 603 | if (ttfFileRef == null) {
|
| a62d533 | 604 | // Worst case if this UnicodeFont was loaded without a ttfFileRef, try to get the font file from Sun's classes. |
| a62d533 | 605 | try {
|
| a62d533 | 606 | Object font2D = Class.forName("sun.font.FontManager").getDeclaredMethod("getFont2D", new Class[] {Font.class})
|
| a62d533 | 607 | .invoke(null, new Object[] {font});
|
| a62d533 | 608 | Field platNameField = Class.forName("sun.font.PhysicalFont").getDeclaredField("platName");
|
| a62d533 | 609 | platNameField.setAccessible(true); |
| a62d533 | 610 | ttfFileRef = (String)platNameField.get(font2D); |
| a62d533 | 611 | } catch (Throwable ignored) {
|
| a62d533 | 612 | } |
| a62d533 | 613 | if (ttfFileRef == null) ttfFileRef = ""; |
| a62d533 | 614 | } |
| a62d533 | 615 | if (ttfFileRef.length() == 0) return null; |
| a62d533 | 616 | return ttfFileRef; |
| a62d533 | 617 | } |
| a62d533 | 618 | |
| a62d533 | 619 | /** @param ttfFileRef The file system or classpath location of the TrueTypeFont file. */ |
| a62d533 | 620 | static private Font createFont (String ttfFileRef) {
|
| a62d533 | 621 | try {
|
| a62d533 | 622 | return Font.createFont(Font.TRUETYPE_FONT, Gdx.files.absolute(ttfFileRef).read()); |
| a62d533 | 623 | } catch (FontFormatException ex) {
|
| a62d533 | 624 | throw new GdxRuntimeException("Invalid font: " + ttfFileRef, ex);
|
| a62d533 | 625 | } catch (IOException ex) {
|
| a62d533 | 626 | throw new GdxRuntimeException("Error reading font: " + ttfFileRef, ex);
|
| a62d533 | 627 | } |
| a62d533 | 628 | } |
| a62d533 | 629 | |
| a62d533 | 630 | /** Sorts glyphs by height, tallest first. */ |
| a62d533 | 631 | static private final Comparator heightComparator = new Comparator() {
|
| a62d533 | 632 | public int compare (Object o1, Object o2) {
|
| a62d533 | 633 | return ((Glyph)o1).getHeight() - ((Glyph)o2).getHeight(); |
| a62d533 | 634 | } |
| a62d533 | 635 | }; |
| a62d533 | 636 | |
| a62d533 | 637 | public class DisplayList {
|
| a62d533 | 638 | boolean invalid; |
| a62d533 | 639 | int id; |
| a62d533 | 640 | Short yOffset; |
| a62d533 | 641 | |
| a62d533 | 642 | public short width, height; |
| a62d533 | 643 | public Object userData; |
| a62d533 | 644 | |
| a62d533 | 645 | DisplayList () {
|
| a62d533 | 646 | } |
| a62d533 | 647 | } |
| a62d533 | 648 | } |