|
|
|
|
@@ -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<String, CachedText> 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<Map.Entry<String, CachedText>> 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<Integer, FloatBuffer> 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<RenderedText> 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() { }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|