Support caching generated fonts for quicker load on subsequent attempts

This commit is contained in:
drdev
2014-05-26 17:34:36 +00:00
parent 20924826df
commit a90293c264
6 changed files with 492 additions and 17 deletions

1
.gitattributes vendored
View File

@@ -1090,6 +1090,7 @@ forge-gui-mobile/libs/gdx.jar -text
forge-gui-mobile/pom.xml -text
forge-gui-mobile/src/forge/Forge.java -text
forge-gui-mobile/src/forge/GuiMobile.java -text
forge-gui-mobile/src/forge/assets/BitmapFontWriter.java -text
forge-gui-mobile/src/forge/assets/FImage.java -text
forge-gui-mobile/src/forge/assets/FSkin.java -text
forge-gui-mobile/src/forge/assets/FSkinBorder.java -text

View File

@@ -66,6 +66,20 @@ public final class FileUtil {
return f.exists();
}
/**
* <p>
* ensureDirectoryExists.
* </p>
*
* @param dir
* a {@link java.lang.String} object.
* @return a boolean.
*/
public static boolean ensureDirectoryExists(final String path) {
File dir = new File(path);
return (dir.exists() && dir.isDirectory()) || dir.mkdirs();
}
/**
* <p>
* writeFile.

View File

@@ -1,7 +1,5 @@
package forge.app;
import java.io.File;
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.Bundle;
@@ -9,7 +7,9 @@ import android.os.Environment;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.android.AndroidApplication;
import forge.Forge;
import forge.util.FileUtil;
public class Main extends AndroidApplication {
@Override
@@ -28,13 +28,13 @@ public class Main extends AndroidApplication {
Gdx.app.exit();
return;
}
File assetsDir = new File(Environment.getExternalStorageDirectory() + "/Forge");
if (!assetsDir.exists() && !assetsDir.mkdirs()) {
String assetsDir = Environment.getExternalStorageDirectory() + "/Forge/";
if (!FileUtil.ensureDirectoryExists(assetsDir)) {
Gdx.app.error("Forge", "Can't access external storage");
Gdx.app.exit();
return;
}
initialize(new Forge(getClipboard(), assetsDir.getAbsolutePath() + "/"), true);
initialize(new Forge(getClipboard(), assetsDir), true);
}
}

View File

@@ -0,0 +1,391 @@
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package forge.assets;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.PixmapIO;
import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData;
import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph;
import com.badlogic.gdx.graphics.g2d.PixmapPacker.Page;
import com.badlogic.gdx.utils.Array;
/** A utility to output BitmapFontData to a FNT file. This can be useful for caching the result from TrueTypeFont, for faster load
* times.
*
* The font format is from the AngelCodeFont BMFont tool.
*
* @author mattdesl AKA davedes */
/**
* This file is 'borrowed' from gdx-tools in the libgdx source
*/
public class BitmapFontWriter {
/** The output format. */
public static enum OutputFormat {
/** AngelCodeFont text format */
Text,
/** AngelCodeFont XML format */
XML;
}
/** The output format */
private static OutputFormat format = OutputFormat.Text;
/** Sets the AngelCodeFont output format for subsequent writes; can be text (for LibGDX) or XML (for other engines, like
* Pixi.js).
*
* @param fmt the output format to use */
public static void setOutputFormat(OutputFormat fmt) {
if (fmt==null)
throw new NullPointerException("format cannot be null");
format = fmt;
}
/** Returns the currently used output format.
* @return the output format */
public static OutputFormat getOutputFormat() {
return format;
}
/** The Padding parameter for FontInfo. */
public static class Padding {
public int up, down, left, right;
public Padding() {
}
public Padding(int up, int down, int left, int right) {
this.up = up;
this.down = down;
this.left = left;
this.right = right;
}
}
/** The spacing parameter for FontInfo. */
public static class Spacing {
public int horizontal, vertical;
}
/** The font "info" line; this will be ignored by LibGDX's BitmapFont reader,
* but useful for clean and organized output. */
public static class FontInfo {
/** Face name */
public String face;
/** Font size (pt) */
public int size = 12;
/** Whether the font is bold */
public boolean bold;
/** Whether the font is italic */
public boolean italic;
/** The charset; or null/empty for default */
public String charset;
/** Whether the font uses unicode glyphs */
public boolean unicode = true;
/** Stretch for height; default to 100% */
public int stretchH = 100;
/** Whether smoothing is applied */
public boolean smooth = true;
/** Amount of anti-aliasing that was applied to the font */
public int aa = 2;
/** Padding that was applied to the font */
public Padding padding = new Padding();
/** Horizontal/vertical spacing that was applied to font */
public Spacing spacing = new Spacing();
public int outline = 0;
public FontInfo() {
}
public FontInfo(String face, int size) {
this.face = face;
this.size = size;
}
}
private static String quote(Object params) {
return quote(params, false);
}
private static String quote(Object params, boolean spaceAfter) {
if (BitmapFontWriter.getOutputFormat() == OutputFormat.XML)
return "\"" + params.toString().trim() + "\"" + (spaceAfter ? " " : "");
else
return params.toString();
}
/** Writes the given BitmapFontData to a file, using the specified <tt>pageRefs</tt> strings as the image paths for each texture
* page. The glyphs in BitmapFontData have a "page" id, which references the index of the pageRef you specify here.
*
* The FontInfo parameter is useful for cleaner output; such as including a size and font face name hint. However, it can be
* null to use default values. Ultimately, LibGDX ignores the "info" line when reading back fonts.
*
* Likewise, the scaleW and scaleH are only for cleaner output. They are currently ignored by LibGDX's reader. For maximum
* compatibility with other BMFont tools, you should use the width and height of your texture pages (each page should be the
* same size).
*
* @param fontData the bitmap font
* @param pageRefs the references to each texture page image file, generally in the same folder as outFntFile
* @param outFntFile the font file to save to (typically ends with '.fnt')
* @param info the optional info for the file header; can be null
* @param scaleW the width of your texture pages
* @param scaleH the height of your texture pages */
public static void writeFont (BitmapFontData fontData, String[] pageRefs, FileHandle outFntFile, FontInfo info, int scaleW, int scaleH) {
if (info==null) {
info = new FontInfo();
info.face = outFntFile.nameWithoutExtension();
}
int lineHeight = (int)fontData.lineHeight;
int pages = pageRefs.length;
int packed = 0;
int base = (int)((fontData.capHeight) + (fontData.flipped ? -fontData.ascent : fontData.ascent));
OutputFormat fmt = BitmapFontWriter.getOutputFormat();
boolean xml = fmt == OutputFormat.XML;
StringBuilder buf = new StringBuilder();
if (xml) {
buf.append("<font>\n");
}
String xmlOpen = xml ? "\t<" : "";
String xmlCloseSelf = xml ? "/>" : "";
String xmlTab = xml ? "\t" : "";
String xmlClose = xml ? ">" : "";
String xmlQuote = xml ? "\"" : "";
String alphaChnlParams =
xml ? " alphaChnl=\"0\" redChnl=\"0\" greenChnl=\"0\" blueChnl=\"0\""
: " alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0";
//INFO LINE
buf.append(xmlOpen)
.append("info face=\"")
.append(info.face==null ? "" : info.face.replaceAll("\"", "'"))
.append("\" size=").append( quote(info.size) )
.append(" bold=").append( quote(info.bold ? 1 : 0) )
.append(" italic=").append( quote(info.italic ? 1 : 0) )
.append(" charset=\"").append(info.charset==null ? "" : info.charset)
.append("\" unicode=").append( quote(info.unicode ? 1 : 0) )
.append(" stretchH=").append( quote(info.stretchH) )
.append(" smooth=").append( quote(info.smooth ? 1 : 0) )
.append(" aa=").append( quote(info.aa) )
.append(" padding=")
.append(xmlQuote)
.append(info.padding.up).append(",")
.append(info.padding.down).append(",")
.append(info.padding.left).append(",")
.append(info.padding.right)
.append(xmlQuote)
.append(" spacing=")
.append(xmlQuote)
.append(info.spacing.horizontal).append(",")
.append(info.spacing.vertical)
.append(xmlQuote)
.append(xmlCloseSelf)
.append("\n");
//COMMON line
buf.append(xmlOpen)
.append("common lineHeight=").append( quote(lineHeight) )
.append(" base=").append( quote(base) )
.append(" scaleW=").append( quote(scaleW) )
.append(" scaleH=").append( quote(scaleH) )
.append(" pages=").append( quote(pages) )
.append(" packed=").append( quote(packed) )
.append(alphaChnlParams)
.append(xmlCloseSelf)
.append("\n");
if (xml)
buf.append("\t<pages>\n");
//PAGES
for (int i=0; i<pageRefs.length; i++) {
buf.append(xmlTab)
.append(xmlOpen)
.append("page id=")
.append( quote(i) )
.append(" file=\"")
.append(pageRefs[i])
.append("\"")
.append(xmlCloseSelf)
.append("\n");
}
if (xml)
buf.append("\t</pages>\n");
//CHARS
Array<Glyph> glyphs = new Array<Glyph>(256);
for (int i=0; i<fontData.glyphs.length; i++) {
if (fontData.glyphs[i]==null)
continue;
for (int j=0; j<fontData.glyphs[i].length; j++) {
if (fontData.glyphs[i][j]!=null) {
glyphs.add(fontData.glyphs[i][j]);
}
}
}
buf.append(xmlOpen)
.append("chars count=").append(quote(glyphs.size))
.append(xmlClose)
.append("\n");
//CHAR definitions
for (int i=0; i<glyphs.size; i++) {
Glyph g = glyphs.get(i);
buf.append(xmlTab)
.append(xmlOpen)
.append("char id=")
.append(quote( String.format("%-5s", g.id), true ))
.append("x=").append(quote( String.format("%-5s", g.srcX), true ) )
.append("y=").append(quote( String.format("%-5s", g.srcY), true ) )
.append("width=").append(quote( String.format("%-5s", g.width), true ) )
.append("height=").append(quote( String.format("%-5s", g.height), true ) )
.append("xoffset=").append(quote( String.format("%-5s", g.xoffset), true ) )
.append("yoffset=").append(quote( String.format("%-5s", fontData.flipped ? g.yoffset : -(g.height + g.yoffset) ), true ) )
.append("xadvance=").append(quote( String.format("%-5s", g.xadvance), true ) )
.append("page=").append(quote( String.format("%-5s", g.page), true ) )
.append("chnl=").append(quote(0, true))
.append(xmlCloseSelf)
.append("\n");
}
if (xml)
buf.append("\t</chars>\n");
//KERNINGS
int kernCount = 0;
StringBuilder kernBuf = new StringBuilder();
for (int i = 0; i < glyphs.size; i++) {
for (int j = 0; j < glyphs.size; j++) {
Glyph first = glyphs.get(i);
Glyph second = glyphs.get(j);
int kern = first.getKerning((char)second.id);
if (kern!=0) {
kernCount++;
kernBuf.append(xmlTab)
.append(xmlOpen)
.append("kerning first=").append(quote(first.id))
.append(" second=").append(quote(second.id))
.append(" amount=").append(quote(kern, true))
.append(xmlCloseSelf)
.append("\n");
}
}
}
//KERN info
buf.append(xmlOpen)
.append("kernings count=").append(quote(kernCount))
.append(xmlClose)
.append("\n");
buf.append(kernBuf);
if (xml) {
buf.append("\t</kernings>\n");
buf.append("</font>");
}
String charset = info.charset;
if (charset!=null&&charset.length()==0)
charset = null;
outFntFile.writeString(buf.toString(), false, charset);
}
/** A utility method which writes the given font data to a file.
*
* The specified pixmaps are written to the parent directory of <tt>outFntFile</tt>, using that file's name without an
* extension for the PNG file name(s).
*
* The specified FontInfo is optional, and can be null.
*
* Typical usage looks like this:
*
* <pre>
* BitmapFontWriter.writeFont(myFontData, myFontPixmaps, Gdx.files.external(&quot;fonts/output.fnt&quot;), new FontInfo(&quot;Arial&quot;, 16));
* </pre>
*
* @param fontData the font data
* @param pages the pixmaps to write as PNGs
* @param outFntFile the output file for the font definition
* @param info the optional font info for the header file, can be null */
public static void writeFont (BitmapFontData fontData, Pixmap[] pages, FileHandle outFntFile, FontInfo info) {
String[] pageRefs = writePixmaps(pages, outFntFile.parent(), outFntFile.nameWithoutExtension());
//write the font data
writeFont(fontData, pageRefs, outFntFile, info, pages[0].getWidth(), pages[0].getHeight());
}
/** A utility method to write the given array of pixmaps to the given output directory, with the specified file name. If the
* pages array is of length 1, then the resulting file ref will look like: "fileName.png".
*
* If the pages array is greater than length 1, the resulting file refs will be appended with "_N", such as "fileName_0.png",
* "fileName_1.png", "fileName_2.png" etc.
*
* The returned string array can then be passed to the <tt>writeFont</tt> method.
*
* Note: None of the pixmaps will be disposed.
*
* @param pages the pages of pixmap data to write
* @param outputDir the output directory
* @param fileName the file names for the output images
* @return the array of string references to be used with <tt>writeFont</tt> */
public static String[] writePixmaps (Pixmap[] pages, FileHandle outputDir, String fileName) {
if (pages==null || pages.length==0)
throw new IllegalArgumentException("no pixmaps supplied to BitmapFontWriter.write");
String[] pageRefs = new String[pages.length];
for (int i=0; i<pages.length; i++) {
String ref = pages.length==1 ? (fileName+".png") : (fileName+"_"+i+".png");
//the ref for this image
pageRefs[i] = ref;
//write the PNG in that directory
PixmapIO.writePNG(outputDir.child(ref), pages[i]);
}
return pageRefs;
}
/** A convenience method to write pixmaps by page; typically returned from a PixmapPacker when used alongside
* FreeTypeFontGenerator.
*
* @param pages the pages containing the Pixmaps
* @param outputDir the output directory
* @param fileName the file name
* @return the file refs */
public static String[] writePixmaps (Array<Page> pages, FileHandle outputDir, String fileName) {
Pixmap[] pix = new Pixmap[pages.size];
for (int i=0; i<pages.size; i++) {
pix[i] = pages.get(i).getPixmap();
}
return writePixmaps(pix, outputDir, fileName);
}
}

View File

@@ -22,13 +22,16 @@ import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref;
import forge.screens.SplashScreen;
import forge.toolbox.FProgressBar;
import forge.util.FileUtil;
public class FSkin {
private static final String FONT_CACHE_DIR = ForgeConstants.CACHE_DIR + "fonts/";
private static final Map<FSkinProp, FSkinImage> images = new HashMap<FSkinProp, FSkinImage>();
private static final Map<Integer, TextureRegion> avatars = new HashMap<Integer, TextureRegion>();
private static ArrayList<String> allSkins;
private static String preferredDir;
private static String preferredFontDir;
private static String preferredName;
private static boolean loaded = false;
@@ -69,6 +72,9 @@ public class FSkin {
// Non-default (preferred) skin name and dir.
preferredName = skinName.toLowerCase().replace(' ', '_');
preferredDir = ForgeConstants.SKINS_DIR + preferredName + "/";
preferredFontDir = FONT_CACHE_DIR + preferredName + "/";
FileUtil.ensureDirectoryExists(preferredFontDir);
FSkinTexture.BG_TEXTURE.load(preferredDir, ForgeConstants.DEFAULT_SKINS_DIR); //load background texture early for splash screen
@@ -291,6 +297,15 @@ public class FSkin {
return FSkin.preferredDir;
}
/**
* Gets the directory where fonts should be cached
*
* @return Path of font cache directory for the current skin.
*/
public static String getFontDir() {
return preferredFontDir;
}
/**
* Gets the skins.
*

View File

@@ -5,8 +5,14 @@ import java.util.Map;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.PixmapPacker;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.graphics.glutils.PixmapTextureData;
import com.badlogic.gdx.utils.Array;
import forge.util.Utils;
@@ -15,6 +21,7 @@ public class FSkinFont {
private static final String TTF_FILE = "font1.ttf";
private static final Map<Integer, FSkinFont> fonts = new HashMap<Integer, FSkinFont>();
private static final int FONT_PAGE_SIZE = 256;
public static FSkinFont get(final int size0) {
FSkinFont skinFont = fonts.get(size0);
@@ -35,6 +42,12 @@ public class FSkinFont {
}
}
public static void updateAll() {
for (FSkinFont skinFont : fonts.values()) {
skinFont.updateFont();
}
}
private final int size;
private BitmapFont font;
@@ -52,21 +65,62 @@ public class FSkinFont {
}
private void updateFont() {
String dir = FSkin.getDir();
//generate .fnt and .png files from .ttf if needed
FileHandle ttfFile = Gdx.files.absolute(dir + TTF_FILE);
if (ttfFile.exists()) {
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(ttfFile);
font = generator.generateFont((int)Utils.scaleMax(size)); //scale font based on size
font.setUseIntegerPositions(true); //prevent parts of text getting cut off at times
generator.dispose();
try {
int fontSize = (int)Utils.scaleMax(size);
String fontName = "f" + fontSize;
FileHandle fontFile = Gdx.files.absolute(FSkin.getFontDir() + fontName + ".fnt");
if (fontFile.exists()) {
font = new BitmapFont(fontFile);
}
else {
FileHandle ttfFile = Gdx.files.absolute(FSkin.getDir() + TTF_FILE);
font = generateFont(ttfFile, fontName, fontSize);
}
}
catch (Exception e) {
e.printStackTrace();
}
if (font == null) {
font = new BitmapFont(); //use scaled default font as fallback
font.setScale(Utils.scaleMax(size) / font.getLineHeight());
}
font.setUseIntegerPositions(true); //prevent parts of text getting cut off at times
}
public static void updateAll() {
for (FSkinFont skinFont : fonts.values()) {
skinFont.updateFont();
private BitmapFont generateFont(FileHandle ttfFile, String fontName, int fontSize) {
if (!ttfFile.exists()) { return null; }
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(ttfFile);
PixmapPacker packer = new PixmapPacker(FONT_PAGE_SIZE, FONT_PAGE_SIZE, Pixmap.Format.RGBA8888, 2, false);
FreeTypeFontGenerator.FreeTypeBitmapFontData fontData = generator.generateData(fontSize, FreeTypeFontGenerator.DEFAULT_CHARS, false, packer);
Array<PixmapPacker.Page> pages = packer.getPages();
TextureRegion[] texRegions = new TextureRegion[pages.size];
for (int i=0; i<pages.size; i++) {
PixmapPacker.Page p = pages.get(i);
Texture texture = new Texture(new PixmapTextureData(p.getPixmap(), p.getPixmap().getFormat(), false, false)) {
@Override
public void dispose() {
super.dispose();
getTextureData().consumePixmap().dispose();
}
};
texture.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);
texRegions[i] = new TextureRegion(texture);
}
BitmapFont font = new BitmapFont(fontData, texRegions, false);
//create .fnt and .png files for font
FileHandle fontFile = Gdx.files.absolute(FSkin.getFontDir() + fontName + ".fnt");
FileHandle pixmapDir = Gdx.files.absolute(FSkin.getFontDir());
BitmapFontWriter.setOutputFormat(BitmapFontWriter.OutputFormat.Text);
String[] pageRefs = BitmapFontWriter.writePixmaps(packer.getPages(), pixmapDir, fontName);
BitmapFontWriter.writeFont(font.getData(), pageRefs, fontFile, new BitmapFontWriter.FontInfo(fontName, fontSize), 1, 1);
generator.dispose();
packer.dispose();
return font;
}
}