gdx-studio

#libgdx#java#desktop

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
}