From b985ed3bf5f256f643b4c53d054955a7996240e6 Mon Sep 17 00:00:00 2001 From: Evan Debenham Date: Thu, 10 Oct 2019 22:20:23 -0400 Subject: [PATCH] v0.7.5b: first major portion of rewriting text rendering some rough edges still need smoothing out --- SPD-classes/build.gradle | 3 +- .../main/java/com/watabou/glwrap/Texture.java | 4 + .../src/main/java/com/watabou/noosa/Game.java | 1 - .../java/com/watabou/noosa/RenderedText.java | 357 ++++++++---------- .../com/watabou/utils/PlatformSupport.java | 10 + android/.gitignore | 3 +- android/build.gradle | 6 + .../android/AndroidPlatformSupport.java | 151 ++++++++ .../android/windows/WndAndroidTextInput.java | 7 +- .../shatteredpixeldungeon/SPDSettings.java | 8 +- .../ShatteredPixelDungeon.java | 5 - .../scenes/PixelScene.java | 20 + .../ui/RenderedTextMultiline.java | 14 +- .../windows/WndLangs.java | 18 +- 14 files changed, 393 insertions(+), 214 deletions(-) diff --git a/SPD-classes/build.gradle b/SPD-classes/build.gradle index f1dbc5db3..523ab0edd 100644 --- a/SPD-classes/build.gradle +++ b/SPD-classes/build.gradle @@ -18,4 +18,5 @@ dependencies { api "com.badlogicgames.gdx:gdx:$gdxVersion" api "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" implementation "com.badlogicgames.gdx:gdx-controllers:$gdxVersion" -} \ No newline at end of file + implementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" +} diff --git a/SPD-classes/src/main/java/com/watabou/glwrap/Texture.java b/SPD-classes/src/main/java/com/watabou/glwrap/Texture.java index 20fd0a6c4..8d7be9080 100644 --- a/SPD-classes/src/main/java/com/watabou/glwrap/Texture.java +++ b/SPD-classes/src/main/java/com/watabou/glwrap/Texture.java @@ -60,6 +60,10 @@ public class Texture { } } + public static void clear(){ + bound_id = 0; + } + public void filter( int minMode, int maxMode ) { bind(); Gdx.gl.glTexParameterf( Gdx.gl.GL_TEXTURE_2D, Gdx.gl.GL_TEXTURE_MIN_FILTER, minMode ); diff --git a/SPD-classes/src/main/java/com/watabou/noosa/Game.java b/SPD-classes/src/main/java/com/watabou/noosa/Game.java index 88fd17d9d..772fc301b 100644 --- a/SPD-classes/src/main/java/com/watabou/noosa/Game.java +++ b/SPD-classes/src/main/java/com/watabou/noosa/Game.java @@ -105,7 +105,6 @@ public class Game implements ApplicationListener { //refreshes texture and vertex data stored on the gpu TextureCache.reload(); - RenderedText.reloadCache(); Vertexbuffer.refreshAllBuffers(); } diff --git a/SPD-classes/src/main/java/com/watabou/noosa/RenderedText.java b/SPD-classes/src/main/java/com/watabou/noosa/RenderedText.java index f761d36be..4d6458753 100644 --- a/SPD-classes/src/main/java/com/watabou/noosa/RenderedText.java +++ b/SPD-classes/src/main/java/com/watabou/noosa/RenderedText.java @@ -21,239 +21,212 @@ package com.watabou.noosa; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Typeface; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.backends.android.AndroidApplication; -import com.badlogic.gdx.graphics.Pixmap; -import com.watabou.gltextures.SmartTexture; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Affine2; +import com.badlogic.gdx.math.Matrix4; import com.watabou.glwrap.Matrix; -import com.watabou.glwrap.Texture; -import com.watabou.utils.RectF; +import com.watabou.glwrap.Quad; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; +import java.nio.FloatBuffer; +import java.util.HashMap; public class RenderedText extends Image { - - private static Canvas canvas = new Canvas(); - private static Paint painter = new Paint(); - - private static Typeface font; - //this is basically a LRU cache. capacity is determined by character count, not entry count. - //FIXME: Caching based on words is very inefficient for every language but chinese. - private static LinkedHashMap textCache = new LinkedHashMap<>(700, 0.75f, true); - - private static int cachedChars = 0; - - private static final int GC_TRIGGER = 1250; - private static final int GC_TARGET = 1000; - - private static void runGC(){ - Iterator> it = textCache.entrySet().iterator(); - while (cachedChars > GC_TARGET && it.hasNext()){ - CachedText cached = it.next().getValue(); - if (cached.activeTexts.isEmpty()) { - cachedChars -= cached.length; - cached.texture.delete(); - it.remove(); - } - } - } - + private BitmapFont font = null; private int size; private String text; - private CachedText cache; - - private boolean needsRender = false; - - public RenderedText( ){ + + public RenderedText( ) { text = null; } - + public RenderedText( int size ){ text = null; this.size = size; } - + public RenderedText(String text, int size){ this.text = text; this.size = size; - - needsRender = true; - measure(this); + + measure(); } - + public void text( String text ){ this.text = text; - - needsRender = true; - measure(this); + + measure(); } - + public String text(){ return text; } - + public void size( int size ){ this.size = size; - needsRender = true; - measure(this); + measure(); } - + public float baseLine(){ return size * scale.y; } - - private static synchronized void measure(RenderedText r){ - - if ( r.text == null || r.text.equals("") ) { - r.text = ""; - r.width=r.height=0; - r.visible = false; + + private synchronized void measure(){ + + if (Thread.currentThread().getName().equals("SHPD Actor Thread")){ + throw new RuntimeException("Text measured from the actor thread!"); + } + + if ( text == null || text.equals("") ) { + text = ""; + width=height=0; + visible = false; return; } else { - r.visible = true; + visible = true; } - - painter.setTextSize(r.size); - painter.setAntiAlias(true); - - if (font != null) { - painter.setTypeface(font); - } else { - painter.setTypeface(Typeface.DEFAULT); - } - - //paint outer strokes - painter.setARGB(0xff, 0, 0, 0); - painter.setStyle(Paint.Style.STROKE); - painter.setStrokeWidth(r.size / 5f); - - r.width = (painter.measureText(r.text)+ (r.size/5f)); - r.height = (-painter.ascent() + painter.descent()+ (r.size/5f)); + + font = Game.platform.getFont(size, text); + + GlyphLayout l = new GlyphLayout( font, text); + + width = l.width; + + //TODO this is almost the same as old height, but old height was clearly a bit off + height = size*1.375f; + //height = l.height - fonts.get(fontGenerator).get(size).getDescent() + size/5f; } - - private static synchronized void render(RenderedText r){ - r.needsRender = false; - - if (r.cache != null) - r.cache.activeTexts.remove(r); - - String key = "text:" + r.size + " " + r.text; - if (textCache.containsKey(key)){ - r.cache = textCache.get(key); - r.texture = r.cache.texture; - r.frame(r.cache.rect); - r.cache.activeTexts.add(r); - } else { - - measure(r); - - if (r.width == 0 || r.height == 0) - return; - - //bitmap has to be in a power of 2 for some devices (as we're using openGL methods to render to texture) - Bitmap bitmap = Bitmap.createBitmap(Integer.highestOneBit((int)r.width)*2, Integer.highestOneBit((int)r.height)*2, Bitmap.Config.ARGB_4444); - bitmap.eraseColor(0x00000000); - - canvas.setBitmap(bitmap); - canvas.drawText(r.text, (r.size/10f), r.size, painter); - - //paint inner text - painter.setARGB(0xff, 0xff, 0xff, 0xff); - painter.setStyle(Paint.Style.FILL); - - canvas.drawText(r.text, (r.size/10f), r.size, painter); - - //FIXME really ugly and slow conversion between android bitmap and gdx pixmap - int[] pixels = new int[bitmap.getWidth()*bitmap.getHeight()]; - bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); - // Convert from ARGB to RGBA - for (int i = 0; i< pixels.length; i++) { - int pixel = pixels[i]; - pixels[i] = (pixel << 8) | ((pixel >> 24) & 0xFF); - } - Pixmap pixmap = new Pixmap(bitmap.getWidth(), bitmap.getHeight(), Pixmap.Format.RGBA8888); - pixmap.getPixels().asIntBuffer().put(pixels); - r.texture = new SmartTexture(pixmap, Texture.NEAREST, Texture.CLAMP, true); - bitmap.recycle(); - - RectF rect = r.texture.uvRect(0, 0, r.width, r.height); - r.frame(rect); - - r.cache = new CachedText(); - r.cache.rect = rect; - r.cache.texture = r.texture; - r.cache.length = r.text.length(); - r.cache.activeTexts = new HashSet<>(); - r.cache.activeTexts.add(r); - - cachedChars += r.cache.length; - textCache.put("text:" + r.size + " " + r.text, r.cache); - - if (cachedChars >= GC_TRIGGER){ - runGC(); - } - } - } - + @Override protected void updateMatrix() { super.updateMatrix(); //the y value is set at the top of the character, not at the top of accents. - Matrix.translate( matrix, 0, -Math.round((baseLine()*0.15f)/scale.y) ); + //FIXME this doesn't work for .otf fonts on android 6.0 + Matrix.translate( matrix, 0, Math.round((baseLine()*0.1f)/scale.y) ); } - + + private static TextRenderBatch textRenderer = new TextRenderBatch(); + @Override - public void draw() { - if (needsRender) - render(this); - if (texture != null) - super.draw(); + public synchronized void draw() { + updateMatrix(); + TextRenderBatch.textBeingRendered = this; + font.draw(textRenderer, text, 0, 0); } - @Override - public void destroy() { - if (cache != null) - cache.activeTexts.remove(this); - super.destroy(); - } - - public static void clearCache(){ - for (CachedText cached : textCache.values()){ - cached.texture.delete(); + //implements regular PD rendering within a LibGDX batch so that our rendering logic + //can interface with the freetype font generator + private static class TextRenderBatch implements Batch { + + //this isn't as good as only updating once, like with bitmaptext + // but it skips almost all allocations, which is almost as good + private static RenderedText textBeingRendered = null; + private static float[] vertices = new float[16]; + private static HashMap buffers = new HashMap<>(); + + @Override + public void draw(Texture texture, float[] spriteVertices, int offset, int count) { + Visual v = textBeingRendered; + + FloatBuffer toOpenGL; + if (buffers.containsKey(count/20)){ + toOpenGL = buffers.get(count/20); + toOpenGL.position(0); + } else { + toOpenGL = Quad.createSet(count / 20); + buffers.put(count/20, toOpenGL); + } + + for (int i = 0; i < count; i += 20){ + + vertices[0] = spriteVertices[i+0]; + vertices[1] = spriteVertices[i+1]; + + vertices[2] = spriteVertices[i+3]; + vertices[3] = spriteVertices[i+4]; + + vertices[4] = spriteVertices[i+5]; + vertices[5] = spriteVertices[i+6]; + + vertices[6] = spriteVertices[i+8]; + vertices[7] = spriteVertices[i+9]; + + vertices[8] = spriteVertices[i+10]; + vertices[9] = spriteVertices[i+11]; + + vertices[10] = spriteVertices[i+13]; + vertices[11] = spriteVertices[i+14]; + + vertices[12] = spriteVertices[i+15]; + vertices[13] = spriteVertices[i+16]; + + vertices[14] = spriteVertices[i+18]; + vertices[15] = spriteVertices[i+19]; + + toOpenGL.put(vertices); + + } + + toOpenGL.position(0); + + NoosaScript script = NoosaScript.get(); + + texture.bind(); + com.watabou.glwrap.Texture.clear(); + + script.camera( v.camera() ); + + script.uModel.valueM4( v.matrix ); + script.lighting( + v.rm, v.gm, v.bm, v.am, + v.ra, v.ga, v.ba, v.aa ); + + script.drawQuadSet( toOpenGL, count/20 ); } - cachedChars = 0; - textCache.clear(); - } - - public static void reloadCache(){ - for (CachedText txt : textCache.values()){ - txt.texture.reload(); - } - } - - public static void setFont(String asset){ - if (asset == null) font = null; - else font = Typeface.createFromAsset(((AndroidApplication)Gdx.app).getAssets(), asset); - clearCache(); - } - - public static Typeface getFont(){ - return font; - } - - private static class CachedText{ - public SmartTexture texture; - public RectF rect; - public int length; - public HashSet activeTexts; + + //none of these functions are needed, so they are stubbed + @Override + public void begin() { } + public void end() { } + public void setColor(Color tint) { } + public void setColor(float r, float g, float b, float a) { } + public Color getColor() { return null; } + public void setPackedColor(float packedColor) { } + public float getPackedColor() { return 0; } + public void draw(Texture texture, float x, float y, float originX, float originY, float width, float height, float scaleX, float scaleY, float rotation, int srcX, int srcY, int srcWidth, int srcHeight, boolean flipX, boolean flipY) { } + public void draw(Texture texture, float x, float y, float width, float height, int srcX, int srcY, int srcWidth, int srcHeight, boolean flipX, boolean flipY) { } + public void draw(Texture texture, float x, float y, int srcX, int srcY, int srcWidth, int srcHeight) { } + public void draw(Texture texture, float x, float y, float width, float height, float u, float v, float u2, float v2) { } + public void draw(Texture texture, float x, float y) { } + public void draw(Texture texture, float x, float y, float width, float height) { } + public void draw(TextureRegion region, float x, float y) { } + public void draw(TextureRegion region, float x, float y, float width, float height) { } + public void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float scaleX, float scaleY, float rotation) { } + public void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float scaleX, float scaleY, float rotation, boolean clockwise) { } + public void draw(TextureRegion region, float width, float height, Affine2 transform) { } + public void flush() { } + public void disableBlending() { } + public void enableBlending() { } + public void setBlendFunction(int srcFunc, int dstFunc) { } + public void setBlendFunctionSeparate(int srcFuncColor, int dstFuncColor, int srcFuncAlpha, int dstFuncAlpha) { } + public int getBlendSrcFunc() { return 0; } + public int getBlendDstFunc() { return 0; } + public int getBlendSrcFuncAlpha() { return 0; } + public int getBlendDstFuncAlpha() { return 0; } + public Matrix4 getProjectionMatrix() { return null; } + public Matrix4 getTransformMatrix() { return null; } + public void setProjectionMatrix(Matrix4 projection) { } + public void setTransformMatrix(Matrix4 transform) { } + public void setShader(ShaderProgram shader) { } + public ShaderProgram getShader() { return null; } + public boolean isBlendingEnabled() { return false; } + public boolean isDrawing() { return false; } + public void dispose() { } } } diff --git a/SPD-classes/src/main/java/com/watabou/utils/PlatformSupport.java b/SPD-classes/src/main/java/com/watabou/utils/PlatformSupport.java index a351d8909..8bbf89479 100644 --- a/SPD-classes/src/main/java/com/watabou/utils/PlatformSupport.java +++ b/SPD-classes/src/main/java/com/watabou/utils/PlatformSupport.java @@ -21,6 +21,8 @@ package com.watabou.utils; +import com.badlogic.gdx.graphics.g2d.BitmapFont; + public abstract class PlatformSupport { public abstract void updateDisplaySize(); @@ -35,5 +37,13 @@ public abstract class PlatformSupport { public static abstract class TextCallback { public abstract void onSelect( boolean positive, String text ); } + + //TODO should consider spinning this into its own class, rather than platform support getting ever bigger + + public abstract void setupFontGenerators(int pageSize, boolean systemFont ); + + public abstract void resetGenerators(); + + public abstract BitmapFont getFont(int size, String text); } diff --git a/android/.gitignore b/android/.gitignore index 2166f7c0c..736683675 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,2 +1,3 @@ #LibGDX native dependancies -libgdx.so \ No newline at end of file +libgdx.so +libgdx-freetype.so \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index bd9809ad3..a94c5805e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -69,6 +69,7 @@ dependencies { implementation project(':core') implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" + implementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi" natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a" natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86" @@ -76,6 +77,11 @@ dependencies { natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64" implementation "com.badlogicgames.gdx:gdx-controllers:$gdxVersion" implementation "com.badlogicgames.gdx:gdx-controllers-android:$gdxVersion" + natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi" + natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi-v7a" + natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-arm64-v8a" + natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86" + natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86_64" } // called every time gradle gets executed, takes the native dependencies of diff --git a/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidPlatformSupport.java b/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidPlatformSupport.java index ed09a50f8..b59a2f188 100644 --- a/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidPlatformSupport.java +++ b/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidPlatformSupport.java @@ -27,12 +27,20 @@ import android.os.Build; import android.view.View; import android.view.WindowManager; +import com.badlogic.gdx.Gdx; +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 com.shatteredpixel.shatteredpixeldungeon.SPDSettings; import com.shatteredpixel.shatteredpixeldungeon.android.windows.WndAndroidTextInput; import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene; import com.watabou.noosa.Game; import com.watabou.utils.PlatformSupport; +import java.util.HashMap; +import java.util.regex.Pattern; + public class AndroidPlatformSupport extends PlatformSupport { public void updateDisplaySize(){ @@ -140,4 +148,147 @@ public class AndroidPlatformSupport extends PlatformSupport { } }); } + + /* FONT SUPPORT */ + + private int pageSize; + private PixmapPacker packer; + private boolean systemfont; + + //custom ttf or droid sans, for use with Latin and Cyrillic languages + private static FreeTypeFontGenerator latinAndCryllicFontGenerator; + private static HashMap pixelFonts = new HashMap<>(); + + //droid sans, for use with hangul languages (Korean) + private static FreeTypeFontGenerator hangulFontGenerator; + private static HashMap hangulFonts = new HashMap<>(); + + //droid sans, for use with han languages (Chinese, Japanese) + private static FreeTypeFontGenerator hanFontGenerator; + private static HashMap hanFonts = new HashMap<>(); + + private static HashMap> fonts; + + public static Pattern hanMatcher = Pattern.compile("\\p{InHiragana}|\\p{InKatakana}|\\p{InCJK_Unified_Ideographs}|\\p{InCJK_Symbols_and_Punctuation}"); + public static Pattern hangulMatcher = Pattern.compile("\\p{InHangul_Syllables}"); + + @Override + public void setupFontGenerators(int pageSize, boolean systemfont) { + //don't bother doing anything if nothing has changed + if (fonts != null && this.pageSize == pageSize && this.systemfont == systemfont){ + return; + } + this.pageSize = pageSize; + this.systemfont = systemfont; + + if (fonts != null){ + for (FreeTypeFontGenerator generator : fonts.keySet()){ + for (BitmapFont f : fonts.get(generator).values()){ + f.dispose(); + } + fonts.get(generator).clear(); + generator.dispose(); + } + fonts.clear(); + if (packer != null){ + for (PixmapPacker.Page p : packer.getPages()){ + p.getTexture().dispose(); + } + packer.dispose(); + } + } + fonts = new HashMap<>(); + + if (systemfont){ + latinAndCryllicFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/DroidSans.ttf")); + } else { + //FIXME need to add currency symbols + latinAndCryllicFontGenerator = new FreeTypeFontGenerator(Gdx.files.internal("pixelfont.ttf")); + } + + //android 7.0+. Finally back to normalcy, everything nicely in one .ttc + if (Gdx.files.absolute("/system/fonts/NotoSansCJK-Regular.ttc").exists()) { + //TODO why typeface #2 here? It seems to match DroidSansFallback.ttf the best, but why? + //might be that different languages prefer a different face, see about tweaking this? + hangulFontGenerator = hanFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/NotoSansCJK-Regular.ttc"), 2); + + //android 6.0. Fonts are split over multiple .otf files, very awkward + } else if (Gdx.files.absolute("/system/fonts/NotoSansKR-Regular.otf").exists()) { + //FIXME all fonts are messed up here currently, need to fix this + hangulFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/NotoSansKR-Regular.otf")); + hanFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/NotoSansSC-Regular.otf")); + + //android 4.4-5.1. Korean no longer broken with the addition of NanumGothic. + } else if (Gdx.files.absolute("/system/fonts/NanumGothic.ttf").exists()){ + hangulFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/NanumGothic.ttf")); + hanFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/DroidSansFallback.ttf")); + + //android 4.3-. Note that korean isn't in DroidSandFallback and is therefore unfixably broken on 4.2 and 4.3 + } else if (Gdx.files.absolute("/system/fonts/DroidSansFallback.ttf").exists()) { + hangulFontGenerator = hanFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/DroidSansFallback.ttf")); + + //shouldn't ever trigger, but just incase + } else { + hangulFontGenerator = hanFontGenerator = latinAndCryllicFontGenerator; + } + + fonts.put(latinAndCryllicFontGenerator, pixelFonts); + fonts.put(hangulFontGenerator, hangulFonts); + fonts.put(hanFontGenerator, hanFonts); + + //use RGBA4444 to save memory. Extra precision isn't needed here. + packer = new PixmapPacker(pageSize, pageSize, Pixmap.Format.RGBA4444, 1, false); + } + + @Override + public void resetGenerators() { + for (FreeTypeFontGenerator generator : fonts.keySet()){ + for (BitmapFont f : fonts.get(generator).values()){ + f.dispose(); + } + fonts.get(generator).clear(); + generator.dispose(); + } + fonts.clear(); + if (packer != null){ + for (PixmapPacker.Page p : packer.getPages()){ + p.getTexture().dispose(); + } + packer.dispose(); + } + fonts = null; + setupFontGenerators(pageSize, systemfont); + } + + private static FreeTypeFontGenerator getGeneratorForString( String input ){ + if (hanMatcher.matcher(input).find()){ + return hanFontGenerator; + } else if (hangulMatcher.matcher(input).find()){ + return hangulFontGenerator; + } else { + return latinAndCryllicFontGenerator; + } + } + + @Override + public BitmapFont getFont(int size, String text) { + FreeTypeFontGenerator generator = getGeneratorForString(text); + + if (!fonts.get(generator).containsKey(size)) { + FreeTypeFontGenerator.FreeTypeFontParameter parameters = new FreeTypeFontGenerator.FreeTypeFontParameter(); + parameters.size = size; + parameters.flip = true; + parameters.borderWidth = parameters.size / 10f; + parameters.renderCount = 3; + parameters.hinting = FreeTypeFontGenerator.Hinting.None; + parameters.spaceX = -(int) parameters.borderWidth; + parameters.incremental = true; + parameters.characters = ""; + parameters.packer = packer; + + fonts.get(generator).put(size, generator.generateFont(parameters)); + } + + return fonts.get(generator).get(size); + } } diff --git a/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/windows/WndAndroidTextInput.java b/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/windows/WndAndroidTextInput.java index 443fc9231..cf72cfad3 100644 --- a/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/windows/WndAndroidTextInput.java +++ b/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/windows/WndAndroidTextInput.java @@ -22,6 +22,7 @@ package com.shatteredpixel.shatteredpixeldungeon.android.windows; import android.app.Activity; +import android.graphics.Typeface; import android.text.InputFilter; import android.text.InputType; import android.util.TypedValue; @@ -39,12 +40,12 @@ import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidGraphics; import com.shatteredpixel.shatteredpixeldungeon.SPDSettings; import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon; +import com.shatteredpixel.shatteredpixeldungeon.android.AndroidLauncher; import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene; import com.shatteredpixel.shatteredpixeldungeon.ui.RedButton; import com.shatteredpixel.shatteredpixeldungeon.ui.RenderedTextMultiline; import com.shatteredpixel.shatteredpixeldungeon.ui.Window; import com.watabou.noosa.Game; -import com.watabou.noosa.RenderedText; //This class makes use of the android EditText component to handle text input //FIXME this window is currently android-specific, should generalize it @@ -96,7 +97,9 @@ public class WndAndroidTextInput extends Window { textInput = new EditText((AndroidApplication)Gdx.app); textInput.setText( initialValue ); - textInput.setTypeface( RenderedText.getFont() ); + if (!SPDSettings.systemFont()){ + textInput.setTypeface( Typeface.createFromAsset(AndroidLauncher.instance.getAssets(), "pixelfont.ttf") ); + } textInput.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)}); textInput.setInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES ); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java index 04b41e5d4..d0f564065 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/SPDSettings.java @@ -251,16 +251,12 @@ public class SPDSettings extends GameSettings { public static void systemFont(boolean value){ put(KEY_SYSTEMFONT, value); - if (!value) { - RenderedText.setFont("pixelfont.ttf"); - } else { - RenderedText.setFont( null ); - } + ShatteredPixelDungeon.seamlessResetScene(); } public static boolean systemFont(){ return getBoolean(KEY_SYSTEMFONT, - (language() == Languages.KOREAN || language() == Languages.CHINESE)); + (language() == Languages.KOREAN || language() == Languages.CHINESE || language() == Languages.JAPANESE)); } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ShatteredPixelDungeon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ShatteredPixelDungeon.java index 740fdbbc8..26c23199f 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ShatteredPixelDungeon.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ShatteredPixelDungeon.java @@ -173,11 +173,6 @@ public class ShatteredPixelDungeon extends Game { Assets.SND_DEGRADE, Assets.SND_MIMIC ); - if (!SPDSettings.systemFont()) { - RenderedText.setFont("pixelfont.ttf"); - } else { - RenderedText.setFont( null ); - } } diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/PixelScene.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/PixelScene.java index 75590678b..9eb765093 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/PixelScene.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/scenes/PixelScene.java @@ -25,6 +25,8 @@ import com.shatteredpixel.shatteredpixeldungeon.Assets; import com.shatteredpixel.shatteredpixeldungeon.Badges; import com.shatteredpixel.shatteredpixeldungeon.SPDSettings; import com.shatteredpixel.shatteredpixeldungeon.effects.BadgeBanner; +import com.shatteredpixel.shatteredpixeldungeon.messages.Languages; +import com.shatteredpixel.shatteredpixeldungeon.messages.Messages; import com.shatteredpixel.shatteredpixeldungeon.ui.RenderedTextMultiline; import com.shatteredpixel.shatteredpixeldungeon.ui.Window; import com.watabou.glwrap.Blending; @@ -133,6 +135,24 @@ public class PixelScene extends Scene { font2x.tracking = -4; font2x.texture.filter(Texture.LINEAR, Texture.NEAREST);*/ } + + //set up the texture size which rendered text will use for any new glyphs. + int renderedTextPageSize; + if (defaultZoom <= 3){ + renderedTextPageSize = 256; + } else if (defaultZoom <= 8){ + renderedTextPageSize = 512; + } else { + renderedTextPageSize = 1024; + } + //asian languages have many more unique characters, so increase texture size to anticipate that + if (Messages.lang() == Languages.KOREAN || + Messages.lang() == Languages.CHINESE || + Messages.lang() == Languages.JAPANESE){ + renderedTextPageSize *= 2; + } + Game.platform.setupFontGenerators(renderedTextPageSize, SPDSettings.systemFont()); + } //FIXME this system currently only works for a subset of windows diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/RenderedTextMultiline.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/RenderedTextMultiline.java index 06675fc1d..67f4452ed 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/RenderedTextMultiline.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ui/RenderedTextMultiline.java @@ -29,13 +29,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +//TODO gdx-freetype can manage multi-line layouts for us, and is more efficient. Should consider migrating to that. public class RenderedTextMultiline extends Component { private int maxWidth = Integer.MAX_VALUE; public int nLines; private String text; - private List tokens; + private String[] tokens; private ArrayList words = new ArrayList<>(); private int size; @@ -66,9 +67,9 @@ public class RenderedTextMultiline extends Component { chinese = text.replaceAll("\\p{Han}", "").length() != text.length(); if (chinese){ - tokens = Arrays.asList(text.split("")); + tokens = text.split(""); } else { - tokens = Arrays.asList(text.split("(?<= )|(?= )|(?<=\n)|(?=\n)")); + tokens = text.split("(?<= )|(?= )|(?<=\n)|(?=\n)"); } build(); } @@ -82,6 +83,10 @@ public class RenderedTextMultiline extends Component { public String text(){ return text; } + + public float baseLine(){ + return size * zoom; + } public void maxWidth(int maxWidth){ if (this.maxWidth != maxWidth){ @@ -189,8 +194,9 @@ public class RenderedTextMultiline extends Component { word.y = y; PixelScene.align(word); x += word.width(); + if (!chinese) x ++; - else x--; + else x -= 0.5f; if ((x - this.x) > width) width = (x - this.x); diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndLangs.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndLangs.java index 0605c403b..a76c41737 100644 --- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndLangs.java +++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/windows/WndLangs.java @@ -73,7 +73,6 @@ public class WndLangs extends Window { @Override public void beforeCreate() { SPDSettings.language(langs.get(langIndex)); - RenderedText.clearCache(); } @Override public void afterCreate() { @@ -237,5 +236,20 @@ public class WndLangs extends Window { } } - + + @Override + public void hide() { + super.hide(); + //resets generators because there's no need to retain chars for languages not selected + ShatteredPixelDungeon.seamlessResetScene(new Game.SceneChangeCallback() { + @Override + public void beforeCreate() { + Game.platform.resetGenerators(); + } + @Override + public void afterCreate() { + //do nothing + } + }); + } }