diff --git a/blade-engine/src/com/bladecoder/engine/ui/BladeSkin.java b/blade-engine/src/com/bladecoder/engine/ui/BladeSkin.java index 612ec56d..a269b71f 100644 --- a/blade-engine/src/com/bladecoder/engine/ui/BladeSkin.java +++ b/blade-engine/src/com/bladecoder/engine/ui/BladeSkin.java @@ -24,211 +24,252 @@ import com.bladecoder.engine.util.DPIUtils; import com.bladecoder.engine.util.EngineLogger; import com.bladecoder.engine.util.FileUtils; +import com.bladecoder.engine.util.MultiFontBitmapFontData; + +import java.util.ArrayList; +import java.util.List; /** * Custom Skin class to add TTF font support - * + * * @author rgarcia */ public class BladeSkin extends Skin { - public BladeSkin(FileHandle skinFile) { - super(skinFile); - } - - public BladeSkin(FileHandle skinFile, TextureAtlas atlas) { - super(skinFile, atlas); - } - - public BladeSkin(TextureAtlas atlas) { - super(atlas); - } - - /** - * Override BitmapFont.class serializer to support TTF fonts - * - * Also add the size parameter to support bitmaps font size in pt - */ - @Override - protected Json getJsonLoader(final FileHandle skinFile) { - Json json = super.getJsonLoader(skinFile); - - final Skin skin = this; - - json.setSerializer(Skin.class, new ReadOnlySerializer() { - @Override - public Skin read(Json json, JsonValue typeToValueMap, @SuppressWarnings("rawtypes") Class ignored) { - for (JsonValue valueMap = typeToValueMap.child; valueMap != null; valueMap = valueMap.next) { - try { - Class type = json.getClass(valueMap.name()); - if (type == null) - type = ClassReflection.forName(valueMap.name()); - readNamedObjects(json, type, valueMap); - } catch (ReflectionException ex) { - throw new SerializationException(ex); - } - } - return skin; - } - - private void readNamedObjects(Json json, Class type, JsonValue valueMap) { - Class addType = type == TintedDrawable.class ? Drawable.class : type; - for (JsonValue valueEntry = valueMap.child; valueEntry != null; valueEntry = valueEntry.next) { - Object object = json.readValue(type, valueEntry); - if (object == null) - continue; - try { - add(valueEntry.name, object, addType); - if (addType != Drawable.class && ClassReflection.isAssignableFrom(Drawable.class, addType)) - add(valueEntry.name, object, Drawable.class); - } catch (Exception ex) { - throw new SerializationException( - "Error reading " + ClassReflection.getSimpleName(type) + ": " + valueEntry.name, ex); - } - } - } - }); - - json.setSerializer(BitmapFont.class, new ReadOnlySerializer() { - @Override - public BitmapFont read(Json json, JsonValue jsonData, @SuppressWarnings("rawtypes") Class type) { - String path = json.readValue("file", String.class, jsonData); - int scaledSize = json.readValue("scaledSize", int.class, -1, jsonData); - Boolean flip = json.readValue("flip", Boolean.class, false, jsonData); - int size = json.readValue("size", int.class, -1, jsonData); - - FileHandle fontFile = skinFile.parent().child(path); - if (!FileUtils.exists(fontFile)) - fontFile = Gdx.files.internal(path); - - if (!FileUtils.exists(fontFile)) - throw new SerializationException("Font file not found: " + fontFile); - - BitmapFont font; - - if (fontFile.extension().equalsIgnoreCase("ttf")) { - - if (size == -1) - throw new SerializationException("'size' mandatory parameter for .ttf fonts"); - - long initTime = System.currentTimeMillis(); - - FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile); - FreeTypeFontParameter parameter = new FreeTypeFontParameter(); - parameter.size = (int) (DPIUtils.dpToPixels(size) * DPIUtils.getSizeMultiplier()); - parameter.color = json.readValue("color", Color.class, Color.WHITE, jsonData); - parameter.incremental = json.readValue("incremental", boolean.class, true, jsonData); - parameter.borderWidth = json.readValue("borderWidth", int.class, 0, jsonData); - parameter.borderColor = json.readValue("borderColor", Color.class, Color.BLACK, jsonData); - parameter.borderStraight = json.readValue("borderStraight", boolean.class, false, jsonData); - parameter.shadowOffsetX = json.readValue("shadowOffsetX", int.class, 0, jsonData); - parameter.shadowOffsetY = json.readValue("shadowOffsetY", int.class, 0, jsonData); - parameter.shadowColor = json.readValue("shadowColor", Color.class, Color.BLACK, jsonData); - if (parameter.incremental) - parameter.characters = ""; - - // parameter.hinting = Hinting.Medium; - - // parameter.mono = false; - - font = generator.generateFont(parameter); - - EngineLogger.debug(path + " TIME (ms): " + (System.currentTimeMillis() - initTime)); - - // TODO Dispose all generators. - - } else { - - // Use a region with the same name as the font, else use a - // PNG file in the same directory as the FNT file. - String regionName = fontFile.nameWithoutExtension(); - try { - TextureRegion region = skin.optional(regionName, TextureRegion.class); - if (region != null) - font = new BitmapFont(fontFile, region, flip); - else { - FileHandle imageFile = fontFile.parent().child(regionName + ".png"); - if (FileUtils.exists(imageFile)) - font = new BitmapFont(fontFile, imageFile, flip); - else - font = new BitmapFont(fontFile, flip); - } - // Scaled size is the desired cap height to scale the - // font to. - if (scaledSize != -1) - font.getData().setScale(scaledSize / font.getCapHeight()); - else if (size != -1) // TODO set size in points (dpi - // independent) - font.getData().setScale( - (DPIUtils.dpToPixels(size) * DPIUtils.getSizeMultiplier()) / font.getCapHeight()); - } catch (RuntimeException ex) { - throw new SerializationException("Error loading bitmap font: " + fontFile, ex); - } - } - - font.getData().markupEnabled = true; - - return font; - } - }); - - json.setSerializer(AnimationDrawable.class, new ReadOnlySerializer() { - @Override - public AnimationDrawable read(Json json, JsonValue jsonData, @SuppressWarnings("rawtypes") Class type) { - String name = json.readValue("name", String.class, jsonData); - float duration = json.readValue("duration", Float.class, 1f, jsonData); - PlayMode playMode = json.readValue("play_mode", PlayMode.class, PlayMode.LOOP, jsonData); - - Array regions = getAtlas().findRegions(name); - - if (regions.size == 0) - throw new SerializationException("AnimationDrawable not found: " + name); - - Animation a = new Animation<>(duration / regions.size, regions, playMode); - AnimationDrawable drawable = new AnimationDrawable(a); - - if (drawable instanceof BaseDrawable) { - BaseDrawable named = drawable; - named.setName(jsonData.name + " (" + name + ", " + duration + ")"); - } - - return drawable; - } - }); - - json.addClassTag("AnimationDrawable", AnimationDrawable.class); - - return json; - } - - public void addStyleTag(Class tag) { - getJsonClassTags().put(tag.getSimpleName(), tag); - } - - @Override - public Drawable newDrawable(Drawable drawable) { - if (drawable instanceof AnimationDrawable) - return new AnimationDrawable((AnimationDrawable) drawable); - return super.newDrawable(drawable); - } - - @Override - public Drawable newDrawable(Drawable drawable, Color tint) { - Drawable newDrawable; - if (drawable instanceof AnimationDrawable) { - newDrawable = ((AnimationDrawable) drawable).tint(tint); - - if (newDrawable instanceof BaseDrawable) { - BaseDrawable named = (BaseDrawable) newDrawable; - if (drawable instanceof BaseDrawable) - named.setName(((BaseDrawable) drawable).getName() + " (" + tint + ")"); - else - named.setName(" (" + tint + ")"); - } - - return newDrawable; - } - - return super.newDrawable(drawable, tint); - } + private final List fontGenerators = new ArrayList<>(); + + public BladeSkin(FileHandle skinFile) { + super(skinFile); + } + + public BladeSkin(FileHandle skinFile, TextureAtlas atlas) { + super(skinFile, atlas); + } + + public BladeSkin(TextureAtlas atlas) { + super(atlas); + } + + /** + * Override BitmapFont.class serializer to support TTF fonts + *

+ * Also add the size parameter to support bitmaps font size in pt + */ + @Override + protected Json getJsonLoader(final FileHandle skinFile) { + Json json = super.getJsonLoader(skinFile); + + final Skin skin = this; + + json.setSerializer(Skin.class, new ReadOnlySerializer() { + @Override + public Skin read(Json json, JsonValue typeToValueMap, @SuppressWarnings("rawtypes") Class ignored) { + for (JsonValue valueMap = typeToValueMap.child; valueMap != null; valueMap = valueMap.next) { + try { + Class type = json.getClass(valueMap.name()); + if (type == null) + type = ClassReflection.forName(valueMap.name()); + readNamedObjects(json, type, valueMap); + } catch (ReflectionException ex) { + throw new SerializationException(ex); + } + } + return skin; + } + + private void readNamedObjects(Json json, Class type, JsonValue valueMap) { + Class addType = type == TintedDrawable.class ? Drawable.class : type; + for (JsonValue valueEntry = valueMap.child; valueEntry != null; valueEntry = valueEntry.next) { + Object object = json.readValue(type, valueEntry); + if (object == null) + continue; + try { + add(valueEntry.name, object, addType); + if (addType != Drawable.class && ClassReflection.isAssignableFrom(Drawable.class, addType)) + add(valueEntry.name, object, Drawable.class); + } catch (Exception ex) { + throw new SerializationException( + "Error reading " + ClassReflection.getSimpleName(type) + ": " + valueEntry.name, ex); + } + } + } + }); + + json.setSerializer(BitmapFont.class, new ReadOnlySerializer() { + @Override + public BitmapFont read(Json json, JsonValue jsonData, @SuppressWarnings("rawtypes") Class type) { + String path = json.readValue("file", String.class, jsonData); + int scaledSize = json.readValue("scaledSize", int.class, -1, jsonData); + Boolean flip = json.readValue("flip", Boolean.class, false, jsonData); + int size = json.readValue("size", int.class, -1, jsonData); + + FileHandle fontFile = skinFile.parent().child(path); + if (!FileUtils.exists(fontFile)) + fontFile = Gdx.files.internal(path); + + if (!FileUtils.exists(fontFile)) + throw new SerializationException("Font file not found: " + fontFile); + + BitmapFont font; + + if (fontFile.extension().equalsIgnoreCase("ttf")) { + + if (size == -1) + throw new SerializationException("'size' mandatory parameter for .ttf fonts"); + + long initTime = System.currentTimeMillis(); + + FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile); + FreeTypeFontParameter parameter = new FreeTypeFontParameter(); + parameter.size = (int) (DPIUtils.dpToPixels(size) * DPIUtils.getSizeMultiplier()); + parameter.color = json.readValue("color", Color.class, Color.WHITE, jsonData); + parameter.incremental = json.readValue("incremental", boolean.class, true, jsonData); + parameter.borderWidth = json.readValue("borderWidth", int.class, 0, jsonData); + parameter.borderColor = json.readValue("borderColor", Color.class, Color.BLACK, jsonData); + parameter.borderStraight = json.readValue("borderStraight", boolean.class, false, jsonData); + parameter.shadowOffsetX = json.readValue("shadowOffsetX", int.class, 0, jsonData); + parameter.shadowOffsetY = json.readValue("shadowOffsetY", int.class, 0, jsonData); + parameter.shadowColor = json.readValue("shadowColor", Color.class, Color.BLACK, jsonData); + if (parameter.incremental) + parameter.characters = ""; + + ArrayList fallbacksFonts = json.readValue("fallbacks", ArrayList.class, String.class, jsonData); + + // parameter.hinting = Hinting.Medium; + // parameter.mono = false; + + if (fallbacksFonts == null) { + font = generator.generateFont(parameter); + } else { + MultiFontBitmapFontData data = new MultiFontBitmapFontData(); + data.createPacker(parameter); + font = generator.generateFont(parameter, data); + + FreeTypeFontParameter parameterFB = new FreeTypeFontParameter(); + parameterFB.size = parameter.size; + parameterFB.color = parameter.color; + parameterFB.incremental = true; + parameterFB.borderWidth = parameter.borderWidth; + parameterFB.borderColor = parameter.borderColor; + parameterFB.borderStraight = parameter.borderStraight; + parameterFB.shadowOffsetX = parameter.shadowOffsetX; + parameterFB.shadowOffsetY = parameter.shadowOffsetY; + parameterFB.shadowColor = parameter.shadowColor; + parameterFB.characters = ""; + + for (String filename : fallbacksFonts) { + FileHandle file = skinFile.parent().child(filename); + + if (!FileUtils.exists(file)) + file = Gdx.files.internal(path); + + if (!FileUtils.exists(file)) + throw new SerializationException("Font file not found: " + file); + + data.addFallBackFont(file, parameterFB); + } + } + + EngineLogger.debug(path + " TIME (ms): " + (System.currentTimeMillis() - initTime)); + + fontGenerators.add(generator); + + } else { + + // Use a region with the same name as the font, else use a + // PNG file in the same directory as the FNT file. + String regionName = fontFile.nameWithoutExtension(); + try { + TextureRegion region = skin.optional(regionName, TextureRegion.class); + if (region != null) + font = new BitmapFont(fontFile, region, flip); + else { + FileHandle imageFile = fontFile.parent().child(regionName + ".png"); + if (FileUtils.exists(imageFile)) + font = new BitmapFont(fontFile, imageFile, flip); + else + font = new BitmapFont(fontFile, flip); + } + // Scaled size is the desired cap height to scale the + // font to. + if (scaledSize != -1) + font.getData().setScale(scaledSize / font.getCapHeight()); + else if (size != -1) // TODO set size in points (dpi + // independent) + font.getData().setScale( + (DPIUtils.dpToPixels(size) * DPIUtils.getSizeMultiplier()) / font.getCapHeight()); + } catch (RuntimeException ex) { + throw new SerializationException("Error loading bitmap font: " + fontFile, ex); + } + } + + font.getData().markupEnabled = true; + + return font; + } + }); + + json.setSerializer(AnimationDrawable.class, new ReadOnlySerializer() { + @Override + public AnimationDrawable read(Json json, JsonValue jsonData, @SuppressWarnings("rawtypes") Class type) { + String name = json.readValue("name", String.class, jsonData); + float duration = json.readValue("duration", Float.class, 1f, jsonData); + PlayMode playMode = json.readValue("play_mode", PlayMode.class, PlayMode.LOOP, jsonData); + + Array regions = getAtlas().findRegions(name); + + if (regions.size == 0) + throw new SerializationException("AnimationDrawable not found: " + name); + + Animation a = new Animation<>(duration / regions.size, regions, playMode); + AnimationDrawable drawable = new AnimationDrawable(a); + + if (drawable instanceof BaseDrawable) { + BaseDrawable named = drawable; + named.setName(jsonData.name + " (" + name + ", " + duration + ")"); + } + + return drawable; + } + }); + + json.addClassTag("AnimationDrawable", AnimationDrawable.class); + + return json; + } + + public void addStyleTag(Class tag) { + getJsonClassTags().put(tag.getSimpleName(), tag); + } + + @Override + public Drawable newDrawable(Drawable drawable) { + if (drawable instanceof AnimationDrawable) + return new AnimationDrawable((AnimationDrawable) drawable); + return super.newDrawable(drawable); + } + + @Override + public Drawable newDrawable(Drawable drawable, Color tint) { + Drawable newDrawable; + if (drawable instanceof AnimationDrawable) { + newDrawable = ((AnimationDrawable) drawable).tint(tint); + ((BaseDrawable) newDrawable).setName(((BaseDrawable) drawable).getName() + " (" + tint + ")"); + + return newDrawable; + } + + return super.newDrawable(drawable, tint); + } + + @Override + public void dispose() { + super.dispose(); + + for (FreeTypeFontGenerator generator : fontGenerators) { + generator.dispose(); + } + + fontGenerators.clear(); + } } diff --git a/blade-engine/src/com/bladecoder/engine/util/MultiFontBitmapFontData.java b/blade-engine/src/com/bladecoder/engine/util/MultiFontBitmapFontData.java new file mode 100644 index 00000000..4ba9865c --- /dev/null +++ b/blade-engine/src/com/bladecoder/engine/util/MultiFontBitmapFontData.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright 2014 Rafael Garcia Moreno. + * + * 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 com.bladecoder.engine.util; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.PixmapPacker; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; + +import java.util.ArrayList; +import java.util.List; + +public final class MultiFontBitmapFontData extends FreeTypeFontGenerator.FreeTypeBitmapFontData { + + private final List fallBackBitmapFontData = new ArrayList<>(); + private final List fallbackFontGenerators = new ArrayList<>(); + private PixmapPacker packer; + + public void addFallBackFont(FileHandle fontFile, FreeTypeFontGenerator.FreeTypeFontParameter parameter) { + FreeTypeFontGenerator.FreeTypeBitmapFontData fallbackData = new FreeTypeFontGenerator.FreeTypeBitmapFontData(); + fallbackData.regions = regions; + parameter.packer = packer; + + FreeTypeFontGenerator fallbackGen = new FreeTypeFontGenerator(fontFile); + fallbackFontGenerators.add(fallbackGen); + fallBackBitmapFontData.add(fallbackGen.generateData(parameter, fallbackData)); + } + + public void createPacker(FreeTypeFontGenerator.FreeTypeFontParameter parameter) { + int maxTextureSize = 1024; + + int size; + PixmapPacker.PackStrategy packStrategy; + + size = maxTextureSize; + packStrategy = new PixmapPacker.GuillotineStrategy(); + + PixmapPacker packer = new PixmapPacker(size, size, Pixmap.Format.RGBA8888, 1, false, packStrategy); + packer.setTransparentColor(parameter.color); + packer.getTransparentColor().a = 0; + if (parameter.borderWidth > 0) { + packer.setTransparentColor(parameter.borderColor); + packer.getTransparentColor().a = 0; + } + + this.packer = packer; + parameter.packer = packer; + } + + @Override + public BitmapFont.Glyph getGlyph(char ch) { + BitmapFont.Glyph glyph = super.getGlyph(ch); + + if (glyph == null && fallBackBitmapFontData != null) { + for (FreeTypeFontGenerator.FreeTypeBitmapFontData data : fallBackBitmapFontData) { + glyph = data.getGlyph(ch); + if (glyph != null) { + return glyph; + } + } + } + + return glyph; + } + + @Override + public void dispose() { + super.dispose(); + packer.dispose(); + + for (FreeTypeFontGenerator.FreeTypeBitmapFontData data : fallBackBitmapFontData) { + data.dispose(); + } + + for (FreeTypeFontGenerator gen : fallbackFontGenerators) { + gen.dispose(); + } + } +}