diff --git a/.gitattributes b/.gitattributes index 4657f443da9..9312c9f0e6b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/forge-core/src/main/java/forge/util/FileUtil.java b/forge-core/src/main/java/forge/util/FileUtil.java index a25b59f2bec..83ae71f9d48 100644 --- a/forge-core/src/main/java/forge/util/FileUtil.java +++ b/forge-core/src/main/java/forge/util/FileUtil.java @@ -66,6 +66,20 @@ public final class FileUtil { return f.exists(); } + /** + *

+ * ensureDirectoryExists. + *

+ * + * @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(); + } + /** *

* writeFile. diff --git a/forge-gui-android/src/forge/app/Main.java b/forge-gui-android/src/forge/app/Main.java index 60c07e356a2..0771593e487 100644 --- a/forge-gui-android/src/forge/app/Main.java +++ b/forge-gui-android/src/forge/app/Main.java @@ -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); } } diff --git a/forge-gui-mobile/src/forge/assets/BitmapFontWriter.java b/forge-gui-mobile/src/forge/assets/BitmapFontWriter.java new file mode 100644 index 00000000000..df9778df5ce --- /dev/null +++ b/forge-gui-mobile/src/forge/assets/BitmapFontWriter.java @@ -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 pageRefs 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("\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\n"); + + //PAGES + for (int i=0; i\n"); + + //CHARS + Array glyphs = new Array(256); + for (int i=0; i\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\n"); + buf.append(""); + } + + 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 outFntFile, 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: + * + *

+     * BitmapFontWriter.writeFont(myFontData, myFontPixmaps, Gdx.files.external("fonts/output.fnt"), new FontInfo("Arial", 16));
+     * 
+ * + * @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 writeFont 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 writeFont */ + 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, FileHandle outputDir, String fileName) { + Pixmap[] pix = new Pixmap[pages.size]; + for (int i=0; i images = new HashMap(); private static final Map avatars = new HashMap(); private static ArrayList 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. * diff --git a/forge-gui-mobile/src/forge/assets/FSkinFont.java b/forge-gui-mobile/src/forge/assets/FSkinFont.java index 4188a41594c..878c8686f69 100644 --- a/forge-gui-mobile/src/forge/assets/FSkinFont.java +++ b/forge-gui-mobile/src/forge/assets/FSkinFont.java @@ -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 fonts = new HashMap(); + 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 pages = packer.getPages(); + TextureRegion[] texRegions = new TextureRegion[pages.size]; + for (int i=0; i