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/BMFontUtil.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; |
| a62d533 | 18 | |
| a62d533 | 19 | import java.awt.Font; |
| a62d533 | 20 | import java.awt.font.GlyphMetrics; |
| a62d533 | 21 | import java.awt.font.GlyphVector; |
| a62d533 | 22 | import java.awt.image.BufferedImage; |
| a62d533 | 23 | import java.awt.image.WritableRaster; |
| a62d533 | 24 | import java.io.File; |
| a62d533 | 25 | import java.io.FileOutputStream; |
| a62d533 | 26 | import java.io.IOException; |
| a62d533 | 27 | import java.io.PrintStream; |
| a62d533 | 28 | import java.nio.IntBuffer; |
| a62d533 | 29 | import java.util.ArrayList; |
| a62d533 | 30 | import java.util.HashMap; |
| a62d533 | 31 | import java.util.Iterator; |
| a62d533 | 32 | import java.util.List; |
| a62d533 | 33 | import java.util.Map; |
| a62d533 | 34 | |
| a62d533 | 35 | import javax.imageio.ImageIO; |
| a62d533 | 36 | |
| a62d533 | 37 | import org.lwjgl.BufferUtils; |
| a62d533 | 38 | import org.lwjgl.opengl.GL11; |
| a62d533 | 39 | import org.lwjgl.opengl.GL12; |
| a62d533 | 40 | |
| a62d533 | 41 | import com.badlogic.gdx.Gdx; |
| a62d533 | 42 | import com.badlogic.gdx.tools.hiero.unicodefont.Glyph; |
| a62d533 | 43 | import com.badlogic.gdx.tools.hiero.unicodefont.GlyphPage; |
| a62d533 | 44 | import com.badlogic.gdx.tools.hiero.unicodefont.UnicodeFont; |
| a62d533 | 45 | |
| a62d533 | 46 | /** @author Nathan Sweet */ |
| a62d533 | 47 | public class BMFontUtil {
|
| a62d533 | 48 | private final UnicodeFont unicodeFont; |
| a62d533 | 49 | |
| a62d533 | 50 | public BMFontUtil (UnicodeFont unicodeFont) {
|
| a62d533 | 51 | this.unicodeFont = unicodeFont; |
| a62d533 | 52 | } |
| a62d533 | 53 | |
| a62d533 | 54 | public void save (File outputBMFontFile) throws IOException {
|
| a62d533 | 55 | File outputDir = outputBMFontFile.getParentFile(); |
| a62d533 | 56 | String outputName = outputBMFontFile.getName(); |
| a62d533 | 57 | if (outputName.endsWith(".fnt")) outputName = outputName.substring(0, outputName.length() - 4);
|
| a62d533 | 58 | |
| a62d533 | 59 | unicodeFont.loadGlyphs(); |
| a62d533 | 60 | |
| a62d533 | 61 | PrintStream out = new PrintStream(new FileOutputStream(new File(outputDir, outputName + ".fnt"))); |
| a62d533 | 62 | Font font = unicodeFont.getFont(); |
| a62d533 | 63 | int pageWidth = unicodeFont.getGlyphPageWidth(); |
| a62d533 | 64 | int pageHeight = unicodeFont.getGlyphPageHeight(); |
| a62d533 | 65 | out.println("info face=\"" + font.getFontName() + "\" size=" + font.getSize() + " bold=" + (font.isBold() ? 1 : 0)
|
| a62d533 | 66 | + " italic=" + (font.isItalic() ? 1 : 0) + " charset=\"\" unicode=0 stretchH=100 smooth=1 aa=1 padding=" |
| a62d533 | 67 | + unicodeFont.getPaddingTop() + "," + unicodeFont.getPaddingLeft() + "," + unicodeFont.getPaddingBottom() + "," |
| a62d533 | 68 | + unicodeFont.getPaddingRight() + " spacing=" + unicodeFont.getPaddingAdvanceX() + "," |
| a62d533 | 69 | + unicodeFont.getPaddingAdvanceY()); |
| a62d533 | 70 | out.println("common lineHeight=" + unicodeFont.getLineHeight() + " base=" + unicodeFont.getAscent() + " scaleW="
|
| a62d533 | 71 | + pageWidth + " scaleH=" + pageHeight + " pages=" + unicodeFont.getGlyphPages().size() + " packed=0"); |
| a62d533 | 72 | |
| a62d533 | 73 | int pageIndex = 0, glyphCount = 0; |
| a62d533 | 74 | for (Iterator pageIter = unicodeFont.getGlyphPages().iterator(); pageIter.hasNext();) {
|
| a62d533 | 75 | GlyphPage page = (GlyphPage)pageIter.next(); |
| a62d533 | 76 | String fileName; |
| a62d533 | 77 | if (pageIndex == 0 && !pageIter.hasNext()) |
| a62d533 | 78 | fileName = outputName + ".png"; |
| a62d533 | 79 | else |
| a62d533 | 80 | fileName = outputName + (pageIndex + 1) + ".png"; |
| a62d533 | 81 | out.println("page id=" + pageIndex + " file=\"" + fileName + "\"");
|
| a62d533 | 82 | glyphCount += page.getGlyphs().size(); |
| a62d533 | 83 | pageIndex++; |
| a62d533 | 84 | } |
| a62d533 | 85 | |
| a62d533 | 86 | out.println("chars count=" + glyphCount);
|
| a62d533 | 87 | |
| a62d533 | 88 | // Always output space entry (codepoint 32). |
| a62d533 | 89 | int[] glyphMetrics = getGlyphMetrics(font, 32); |
| a62d533 | 90 | int xAdvance = glyphMetrics[1]; |
| a62d533 | 91 | out.println("char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=" + unicodeFont.getAscent()
|
| a62d533 | 92 | + " xadvance=" + xAdvance + " page=0 chnl=0 "); |
| a62d533 | 93 | |
| a62d533 | 94 | pageIndex = 0; |
| a62d533 | 95 | List allGlyphs = new ArrayList(512); |
| a62d533 | 96 | for (Iterator pageIter = unicodeFont.getGlyphPages().iterator(); pageIter.hasNext();) {
|
| a62d533 | 97 | GlyphPage page = (GlyphPage)pageIter.next(); |
| a62d533 | 98 | for (Iterator glyphIter = page.getGlyphs().iterator(); glyphIter.hasNext();) {
|
| a62d533 | 99 | Glyph glyph = (Glyph)glyphIter.next(); |
| a62d533 | 100 | |
| a62d533 | 101 | glyphMetrics = getGlyphMetrics(font, glyph.getCodePoint()); |
| a62d533 | 102 | int xOffset = glyphMetrics[0]; |
| a62d533 | 103 | xAdvance = glyphMetrics[1]; |
| a62d533 | 104 | |
| a62d533 | 105 | out.println("char id=" + glyph.getCodePoint() + " " + "x=" + (int)(glyph.getU() * pageWidth) + " y="
|
| a62d533 | 106 | + (int)(glyph.getV() * pageHeight) + " width=" + glyph.getWidth() + " height=" + glyph.getHeight() |
| a62d533 | 107 | + " xoffset=" + xOffset + " yoffset=" + glyph.getYOffset() + " xadvance=" + xAdvance + " page=" |
| a62d533 | 108 | + pageIndex + " chnl=0 "); |
| a62d533 | 109 | } |
| a62d533 | 110 | allGlyphs.addAll(page.getGlyphs()); |
| a62d533 | 111 | pageIndex++; |
| a62d533 | 112 | } |
| a62d533 | 113 | |
| a62d533 | 114 | String ttfFileRef = unicodeFont.getFontFile(); |
| a62d533 | 115 | if (ttfFileRef == null) |
| a62d533 | 116 | System.out.println("Kerning information could not be output because a TTF font file was not specified.");
|
| a62d533 | 117 | else {
|
| a62d533 | 118 | Kerning kerning = new Kerning(); |
| a62d533 | 119 | try {
|
| a62d533 | 120 | kerning.load(Gdx.files.internal(ttfFileRef).read(), font.getSize()); |
| a62d533 | 121 | } catch (IOException ex) {
|
| a62d533 | 122 | System.out.println("Unable to read kerning information from font: " + ttfFileRef);
|
| a62d533 | 123 | } |
| a62d533 | 124 | |
| a62d533 | 125 | Map glyphCodeToCodePoint = new HashMap(); |
| a62d533 | 126 | for (Iterator iter = allGlyphs.iterator(); iter.hasNext();) {
|
| a62d533 | 127 | Glyph glyph = (Glyph)iter.next(); |
| a62d533 | 128 | glyphCodeToCodePoint.put(new Integer(getGlyphCode(font, glyph.getCodePoint())), new Integer(glyph.getCodePoint())); |
| a62d533 | 129 | } |
| a62d533 | 130 | |
| a62d533 | 131 | List kernings = new ArrayList(256); |
| a62d533 | 132 | class KerningPair {
|
| a62d533 | 133 | public int firstCodePoint, secondCodePoint, offset; |
| a62d533 | 134 | } |
| a62d533 | 135 | for (Iterator iter1 = allGlyphs.iterator(); iter1.hasNext();) {
|
| a62d533 | 136 | Glyph firstGlyph = (Glyph)iter1.next(); |
| a62d533 | 137 | int firstGlyphCode = getGlyphCode(font, firstGlyph.getCodePoint()); |
| a62d533 | 138 | int[] values = kerning.getValues(firstGlyphCode); |
| a62d533 | 139 | if (values == null) continue; |
| a62d533 | 140 | for (int i = 0; i < values.length; i++) {
|
| a62d533 | 141 | Integer secondCodePoint = (Integer)glyphCodeToCodePoint.get(new Integer(values[i] & 0xffff)); |
| a62d533 | 142 | if (secondCodePoint == null) continue; // We may not be outputting the second character. |
| a62d533 | 143 | int offset = values[i] >> 16; |
| a62d533 | 144 | KerningPair pair = new KerningPair(); |
| a62d533 | 145 | pair.firstCodePoint = firstGlyph.getCodePoint(); |
| a62d533 | 146 | pair.secondCodePoint = secondCodePoint.intValue(); |
| a62d533 | 147 | pair.offset = offset; |
| a62d533 | 148 | kernings.add(pair); |
| a62d533 | 149 | } |
| a62d533 | 150 | } |
| a62d533 | 151 | out.println("kernings count=" + kerning.getCount());
|
| a62d533 | 152 | for (Iterator iter = kernings.iterator(); iter.hasNext();) {
|
| a62d533 | 153 | KerningPair pair = (KerningPair)iter.next(); |
| a62d533 | 154 | out.println("kerning first=" + pair.firstCodePoint + " second=" + pair.secondCodePoint + " amount=" + pair.offset);
|
| a62d533 | 155 | } |
| a62d533 | 156 | } |
| a62d533 | 157 | out.close(); |
| a62d533 | 158 | |
| a62d533 | 159 | int width = unicodeFont.getGlyphPageWidth(); |
| a62d533 | 160 | int height = unicodeFont.getGlyphPageHeight(); |
| a62d533 | 161 | IntBuffer buffer = BufferUtils.createIntBuffer(width * height); |
| a62d533 | 162 | BufferedImage pageImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); |
| a62d533 | 163 | int[] row = new int[width]; |
| a62d533 | 164 | |
| a62d533 | 165 | pageIndex = 0; |
| a62d533 | 166 | for (Iterator pageIter = unicodeFont.getGlyphPages().iterator(); pageIter.hasNext();) {
|
| a62d533 | 167 | GlyphPage page = (GlyphPage)pageIter.next(); |
| a62d533 | 168 | String fileName; |
| a62d533 | 169 | if (pageIndex == 0 && !pageIter.hasNext()) |
| a62d533 | 170 | fileName = outputName + ".png"; |
| a62d533 | 171 | else |
| a62d533 | 172 | fileName = outputName + (pageIndex + 1) + ".png"; |
| a62d533 | 173 | |
| a62d533 | 174 | page.getTexture().bind(); |
| a62d533 | 175 | buffer.clear(); |
| a62d533 | 176 | GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, buffer); |
| a62d533 | 177 | WritableRaster raster = pageImage.getRaster(); |
| a62d533 | 178 | for (int y = 0; y < height; y++) {
|
| a62d533 | 179 | buffer.get(row); |
| a62d533 | 180 | raster.setDataElements(0, y, width, 1, row); |
| a62d533 | 181 | } |
| a62d533 | 182 | File imageOutputFile = new File(outputDir, fileName); |
| a62d533 | 183 | ImageIO.write(pageImage, "png", imageOutputFile); |
| a62d533 | 184 | |
| a62d533 | 185 | pageIndex++; |
| a62d533 | 186 | } |
| a62d533 | 187 | } |
| a62d533 | 188 | |
| a62d533 | 189 | private int getGlyphCode (Font font, int codePoint) {
|
| a62d533 | 190 | char[] chars = Character.toChars(codePoint); |
| a62d533 | 191 | GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT); |
| a62d533 | 192 | return vector.getGlyphCode(0); |
| a62d533 | 193 | } |
| a62d533 | 194 | |
| a62d533 | 195 | private int[] getGlyphMetrics (Font font, int codePoint) {
|
| a62d533 | 196 | // xOffset and xAdvance will be incorrect for unicode characters such as combining marks or non-spacing characters |
| a62d533 | 197 | // (eg Pnujabi's "\u0A1C\u0A47") that require the context of surrounding glyphs to determine spacing, but thisis the |
| a62d533 | 198 | // best we can do with the BMFont format. |
| a62d533 | 199 | char[] chars = Character.toChars(codePoint); |
| a62d533 | 200 | GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT); |
| a62d533 | 201 | GlyphMetrics metrics = vector.getGlyphMetrics(0); |
| a62d533 | 202 | int xOffset = vector.getGlyphPixelBounds(0, GlyphPage.renderContext, 0.5f, 0).x - unicodeFont.getPaddingLeft(); |
| a62d533 | 203 | int xAdvance = (int)(metrics.getAdvanceX() + unicodeFont.getPaddingAdvanceX() + unicodeFont.getPaddingLeft() + unicodeFont |
| a62d533 | 204 | .getPaddingRight()); |
| a62d533 | 205 | return new int[] {xOffset, xAdvance};
|
| a62d533 | 206 | } |
| a62d533 | 207 | } |