com.badlogic.gdx.graphics.g2d.TextOnPath.java Source code

Java tutorial

Introduction

Here is the source code for com.badlogic.gdx.graphics.g2d.TextOnPath.java

Source

/* 
 * Copyright (C) 2014 team-cachebox.de
 *
 * Licensed under the : GNU General Public License (GPL);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.gnu.org/licenses/gpl.html
 *
 * 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.badlogic.gdx.graphics.g2d;

import CB_UI_Base.graphics.FontCache;
import CB_UI_Base.graphics.GL_FontFamily;
import CB_UI_Base.graphics.GL_FontStyle;
import CB_UI_Base.graphics.GL_Matrix;
import CB_UI_Base.graphics.GL_Path;
import CB_UI_Base.graphics.Geometry.Circle;
import CB_UI_Base.graphics.extendedIntrefaces.ext_Paint;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData;
import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.IntArray;

/**
 * @author Longri
 */
public class TextOnPath implements Disposable {
    private static final float STROKE_WITH_SCALE_FACTOR = 1.2f;

    private final boolean PathToClose;
    private BitmapFont font;
    private float[][] vertexData;
    private float[][] StrokeVertexData;
    // private float[][] TransVertexData;
    // private float[][] TransStrokeVertexData;

    private int[] idx;
    private int[] tmpGlyphCount;
    private float color = Color.WHITE.toFloatBits();
    private int glyphCount = 0;
    private IntArray[] glyphIndices;
    private float[] centerPoints;
    private float[] PathMapedPoints;
    private final float PathOffset;

    private final float textWidth;
    private final float[] centerpoint = new float[2];
    private boolean centerpointCalculated = false;
    private final Matrix4 tmpMatrix4 = new Matrix4();
    private boolean isDisposed;

    public TextOnPath(String Text, GL_Path path, ext_Paint fill2, ext_Paint stroke2, boolean center) {

        if (fill2 == null) {
            textWidth = 0;
            PathToClose = true;
            PathOffset = 0;
            return;
        }

        // Convert Paint values to used GL_PaintValues
        GL_FontFamily fontFamily = fill2.getGLFontFamily();
        GL_FontStyle fontStyle = fill2.getGLFontStyle();
        float fontsize = fill2.getTextSize();

        if (fontsize < 3) {
            textWidth = 0;
            PathToClose = true;
            PathOffset = 0;
            return;
        }

        color = fill2.getHSV_Color().toFloatBits();
        this.font = FontCache.get(fontFamily, fontStyle, fontsize);

        if (this.font == null) {
            textWidth = 0;
            PathToClose = true;
            PathOffset = 0;
            return;
        }

        int regionsLength = font.regions.size;
        if (regionsLength == 0)
            throw new IllegalArgumentException("The specified font must contain at least 1 texture page");

        this.vertexData = new float[regionsLength][];

        this.idx = new int[regionsLength];
        int vertexDataLength = vertexData.length;
        if (vertexDataLength > 1) {
            glyphIndices = new IntArray[vertexDataLength];
            for (int i = 0, n = glyphIndices.length; i < n; i++) {
                glyphIndices[i] = new IntArray();
            }

            tmpGlyphCount = new int[vertexDataLength];
        }

        requireSequence(Text, 0, Text.length());
        textWidth = addToCache(Text, 0, 0, 0, Text.length());

        if (center) {
            PathOffset = (path.getLength() - textWidth) / 2;
        } else {
            PathOffset = 0;
        }

        PathToClose = !mapPath(path);

        if (!PathToClose) {
            if (stroke2 != null && stroke2.getStrokeWidth() > 1) {
                createStroke(stroke2.getStrokeWidth() * STROKE_WITH_SCALE_FACTOR, stroke2.getHSV_Color());
            }

        } else {
            dispose();
        }

    }

    private void createStroke(float width, Color color) {

        int seg = Math.max(5, (int) width);

        Circle circ = new Circle(0, 0, width / 2, seg);
        float[] vertices = circ.getVertices();

        StrokeVertexData = new float[vertexData.length][vertexData[0].length * ((vertices.length / 2) - 1)];

        for (int j = 0, n = vertexData.length; j < n; j++) {
            int index = 0;
            for (int i = 2; i < vertices.length; i += 2) {
                float[][] tmp = createDataCopy(color, vertices[i], vertices[i + 1]);
                System.arraycopy(tmp[j], 0, StrokeVertexData[j], index, tmp[0].length);
                index += tmp[j].length;
            }
        }
        circ.dispose();
        circ = null;
        vertices = null;
    }

    private float[][] createDataCopy(Color color, float xOffset, float yOffset) {
        float[][] data = new float[vertexData.length][];

        for (int j = 0, n = vertexData.length; j < n; j++) {
            if (idx[j] >= 0) { // ignore if this texture has no glyphs
                float[] vertices = vertexData[j];
                data[j] = new float[vertices.length];
                System.arraycopy(vertices, 0, data[j], 0, vertices.length);
            }
        }

        final float c = color.toFloatBits();

        final float[] p0 = new float[2];
        final float[] p1 = new float[2];
        final float[] p2 = new float[2];
        final float[] p3 = new float[2];

        for (int j = 0, length = data.length; j < length; j++) {
            float[] vertices = data[j];
            for (int i = 2, n = idx[j]; i < n; i += 5)
                vertices[i] = c;

            for (int i = 0, n = idx[0]; i < n; i += 20) {

                tmpMatrix4.setToTranslation(xOffset, yOffset, 0);

                GL_Matrix.MapPoint(vertices[i], vertices[i + 1], tmpMatrix4, p0);
                GL_Matrix.MapPoint(vertices[i + 5], vertices[i + 6], tmpMatrix4, p1);
                GL_Matrix.MapPoint(vertices[i + 10], vertices[i + 11], tmpMatrix4, p2);
                GL_Matrix.MapPoint(vertices[i + 15], vertices[i + 16], tmpMatrix4, p3);

                vertices[i] = p0[0];
                vertices[i + 1] = p0[1];

                vertices[i + 5] = p1[0];
                vertices[i + 6] = p1[1];

                vertices[i + 10] = p2[0];
                vertices[i + 11] = p2[1];

                vertices[i + 15] = p3[0];
                vertices[i + 16] = p3[1];

            }

        }
        return data;
    }

    /**
     * Returns True, if the Path to close for drawing Text
     * 
     * @return
     */
    public boolean PathToClose() {
        return PathToClose;
    }

    private final Matrix4 lastTransform = new Matrix4();
    private float[][] TransStrokeVertexData;
    private float[][] TransVertexData;

    private boolean transformChanged(Matrix4 transform) {
        if (lastTransform == null)
            return true;

        for (int i = 0; i < 16; i++) {
            if (transform.val[i] != lastTransform.val[i])
                return true;
        }

        return false;
    }

    private void MapTransform(float[][] srcData, float[][] tarData, Matrix4 matrix4) {
        // Copy Data
        for (int j = 0, n = srcData.length; j < n; j++) {
            if (idx[j] >= 0) {
                float[] vertices = srcData[j];
                System.arraycopy(vertices, 0, tarData[j], 0, vertices.length);
            }
        }

        // Map Data
        final float[] p0 = new float[2];
        for (int j = 0, length = tarData.length; j < length; j++) {
            float[] vertices = tarData[j];
            for (int i = 0; i < vertices.length; i += 5) {
                GL_Matrix.MapPoint(vertices[i], vertices[i + 1], matrix4, p0);

                vertices[i] = p0[0];
                vertices[i + 1] = p0[1];
            }
        }
    }

    public void draw(Batch batch, Matrix4 transform) {

        if (vertexData == null)
            return;

        if (transformChanged(transform)) {
            // FIXME Map only difference reduce copy VertexData

            lastTransform.set(transform);
            if (StrokeVertexData != null) {
                if (TransStrokeVertexData == null) {
                    TransStrokeVertexData = new float[StrokeVertexData.length][];
                    for (int j = 0, n = StrokeVertexData.length; j < n; j++) {
                        if (idx[j] >= 0) {
                            TransStrokeVertexData[j] = new float[StrokeVertexData[j].length];
                        }
                    }
                }

                MapTransform(StrokeVertexData, TransStrokeVertexData, lastTransform);
            }

            if (TransVertexData == null) {
                TransVertexData = new float[vertexData.length][];
                for (int j = 0, n = vertexData.length; j < n; j++) {
                    if (idx[j] >= 0) {
                        TransVertexData[j] = new float[vertexData[j].length];
                    }
                }
            }

            MapTransform(vertexData, TransVertexData, lastTransform);
        }

        if (PathToClose || isDisposed)
            return;
        Array<TextureRegion> regions = font.getRegions();

        if (StrokeVertexData != null) {
            drawVertexData(batch, regions, TransStrokeVertexData);
        }
        drawVertexData(batch, regions, TransVertexData);

    }

    private void drawVertexData(Batch batch, Array<TextureRegion> regions, float[][] data) {
        if (data == null)
            return;
        for (int j = 0, n = data.length; j < n; j++) {
            if (idx[j] >= 0) { // ignore if this texture has no glyphs
                float[] vertices = data[j];
                batch.draw(regions.get(j).getTexture(), vertices, 0, vertices.length);

            }
        }
    }

    private void requireSequence(CharSequence seq, int start, int end) {
        int newGlyphCount = end - start;
        if (vertexData.length == 1) {
            require(0, newGlyphCount); // don't scan sequence if we just have one page
        } else {
            for (int i = 0, n = tmpGlyphCount.length; i < n; i++)
                tmpGlyphCount[i] = 0;

            // determine # of glyphs in each page
            while (start < end) {
                Glyph g = font.data.getGlyph(seq.charAt(start++));
                if (g == null)
                    continue;
                tmpGlyphCount[g.page]++;
            }
            // require that many for each page
            for (int i = 0, n = tmpGlyphCount.length; i < n; i++)
                require(i, tmpGlyphCount[i]);
        }
    }

    private void require(int page, int glyphCount) {
        if (glyphIndices != null) {
            if (glyphCount > glyphIndices[page].items.length)
                glyphIndices[page].ensureCapacity(glyphCount - glyphIndices[page].items.length);
        }

        int vertexCount = idx[page] + glyphCount * 20;
        float[] vertices = vertexData[page];
        if (vertices == null) {
            vertexData[page] = new float[vertexCount];
        } else if (vertices.length < vertexCount) {
            float[] newVertices = new float[vertexCount];
            System.arraycopy(vertices, 0, newVertices, 0, idx[page]);
            vertexData[page] = newVertices;
        }
    }

    private float addToCache(CharSequence str, float x, float y, int start, int end) {
        float startX = x;
        BitmapFont font = this.font;
        Glyph lastGlyph = null;
        BitmapFontData data = font.data;
        if (data.scaleX == 1 && data.scaleY == 1) {
            while (start < end) {
                lastGlyph = data.getGlyph(str.charAt(start++));
                if (lastGlyph != null) {
                    addGlyph(lastGlyph, x + lastGlyph.xoffset, y + lastGlyph.yoffset, lastGlyph.width,
                            lastGlyph.height);
                    x += lastGlyph.xadvance;
                    break;
                }
            }
            while (start < end) {
                char ch = str.charAt(start++);
                Glyph g = data.getGlyph(ch);
                if (g != null) {
                    x += lastGlyph.getKerning(ch);
                    lastGlyph = g;
                    addGlyph(lastGlyph, x + g.xoffset, y + g.yoffset, g.width, g.height);
                    x += g.xadvance;
                }
            }
        } else {
            float scaleX = data.scaleX, scaleY = data.scaleY;
            while (start < end) {
                lastGlyph = data.getGlyph(str.charAt(start++));
                if (lastGlyph != null) {
                    addGlyph(lastGlyph, //
                            x + lastGlyph.xoffset * scaleX, //
                            y + lastGlyph.yoffset * scaleY, //
                            lastGlyph.width * scaleX, //
                            lastGlyph.height * scaleY);
                    x += lastGlyph.xadvance * scaleX;
                    break;
                }
            }
            while (start < end) {
                char ch = str.charAt(start++);
                Glyph g = data.getGlyph(ch);
                if (g != null) {
                    x += lastGlyph.getKerning(ch) * scaleX;
                    lastGlyph = g;
                    addGlyph(lastGlyph, //
                            x + g.xoffset * scaleX, //
                            y + g.yoffset * scaleY, //
                            g.width * scaleX, //
                            g.height * scaleY);
                    x += g.xadvance * scaleX;
                }
            }
        }
        return x - startX;
    }

    private void addGlyph(Glyph glyph, float x, float y, float width, float height) {
        float x2 = x + width;
        float y2 = y + height;
        final float u = glyph.u;
        final float u2 = glyph.u2;
        final float v = glyph.v;
        final float v2 = glyph.v2;

        final int page = glyph.page;

        if (glyphIndices != null) {
            glyphIndices[page].add(glyphCount++);
        }

        final float[] vertices = vertexData[page];

        int idx = this.idx[page];
        this.idx[page] += 20;

        vertices[idx++] = x;
        vertices[idx++] = y;
        vertices[idx++] = color;
        vertices[idx++] = u;
        vertices[idx++] = v;

        vertices[idx++] = x;
        vertices[idx++] = y2;
        vertices[idx++] = color;
        vertices[idx++] = u;
        vertices[idx++] = v2;

        vertices[idx++] = x2;
        vertices[idx++] = y2;
        vertices[idx++] = color;
        vertices[idx++] = u2;
        vertices[idx++] = v2;

        vertices[idx++] = x2;
        vertices[idx++] = y;
        vertices[idx++] = color;
        vertices[idx++] = u2;
        vertices[idx] = v;
    }

    /**
     * Set the center points of all Glyphs on the given Path.<br>
     * Returns False, if the Path to close for the Text
     * 
     * @param path
     * @return
     */
    private boolean mapPath(GL_Path path) {
        if (vertexData == null)
            return false;
        int index = 0;
        float[] vertices = vertexData[0];

        // calculate center points of anny Glyph

        centerPoints = new float[idx[0] / 10];
        PathMapedPoints = new float[idx[0] / 10];
        float[] GlyphRotation = new float[(idx[0] / 20) + 1];

        int indexCenterPoints = 0;
        int indexPathMapedPoints = 0;
        int indexGlyphRotation = 0;
        float firstCenter = (vertices[1] + vertices[11]) / 2;
        for (int i = 0, n = idx[0]; i <= n - 20; i += 20) {
            float centerX = (vertices[i] + vertices[i + 10]) / 2;
            float centerY = (vertices[i + 1] + vertices[i + 11]) / 2;

            centerY = firstCenter;

            centerPoints[indexCenterPoints++] = centerX;
            centerPoints[indexCenterPoints++] = centerY;

            if (i == 0) {
                float[] res = path.getPointOnPathAfter(PathOffset);
                if (res == null)
                    return false;
                PathMapedPoints[indexPathMapedPoints++] = res[0];
                PathMapedPoints[indexPathMapedPoints++] = res[1];
                GlyphRotation[indexGlyphRotation++] = res[2];
                res = null;
            } else {
                float distanceFromFirst = centerPoints[indexCenterPoints - 2] - centerPoints[0];
                float[] res = path.getPointOnPathAfter(PathOffset + distanceFromFirst);
                if (res == null)
                    return false;
                PathMapedPoints[indexPathMapedPoints++] = res[0];
                PathMapedPoints[indexPathMapedPoints++] = res[1];
                GlyphRotation[indexGlyphRotation++] = res[2];
                res = null;
            }

        }

        index = 0;
        indexGlyphRotation = 0;
        final float[] p0 = new float[2];
        final float[] p1 = new float[2];
        final float[] p2 = new float[2];
        final float[] p3 = new float[2];
        for (int i = 0, n = idx[0]; i < n; i += 20) {
            float GlyphCenterX = centerPoints[index];
            float GlyphCenterY = centerPoints[index + 1];

            float MapX = PathMapedPoints[index] - GlyphCenterX;
            float MapY = PathMapedPoints[index + 1] - GlyphCenterY;

            tmpMatrix4.setToTranslation(MapX + GlyphCenterX, MapY + GlyphCenterY, 0);
            // tmpMatrix4.translate(GlyphCenterX, GlyphCenterY, 0);
            tmpMatrix4.rotate(0, 0, 1, GlyphRotation[indexGlyphRotation++]);
            tmpMatrix4.translate(-GlyphCenterX, -GlyphCenterY, 0);
            GL_Matrix.MapPoint(vertices[i], vertices[i + 1], tmpMatrix4, p0);
            GL_Matrix.MapPoint(vertices[i + 5], vertices[i + 6], tmpMatrix4, p1);
            GL_Matrix.MapPoint(vertices[i + 10], vertices[i + 11], tmpMatrix4, p2);
            GL_Matrix.MapPoint(vertices[i + 15], vertices[i + 16], tmpMatrix4, p3);

            vertices[i] = p0[0];
            vertices[i + 1] = p0[1];

            vertices[i + 5] = p1[0];
            vertices[i + 6] = p1[1];

            vertices[i + 10] = p2[0];
            vertices[i + 11] = p2[1];

            vertices[i + 15] = p3[0];
            vertices[i + 16] = p3[1];

            index += 2;
        }
        GlyphRotation = null;
        return true;
    }

    @Override
    public void dispose() {
        if (isDisposed)
            return;
        PathMapedPoints = null;
        centerPoints = null;
        glyphIndices = null;

        if (vertexData != null) {
            for (int i = 0; i < vertexData.length; i++) {
                vertexData[i] = null;
            }
        }
        vertexData = null;

        if (StrokeVertexData != null) {
            for (int i = 0; i < StrokeVertexData.length; i++) {
                StrokeVertexData[i] = null;
            }
        }
        StrokeVertexData = null;
        idx = null;
        font = null;
        isDisposed = true;
    }

    public int getWidth() {
        // TODO Join all Glyph recs and return the width
        return 0;
    }

    public int getHeight() {
        // TODO Join all Glyph recs and return the hight
        return 0;
    }

    public float[] getCenterPoint() {
        if (centerpointCalculated)
            return centerpoint;

        if (vertexData == null) {
            centerpoint[0] = 0;
            centerpoint[1] = 0;
            centerpointCalculated = true;
            return centerpoint;
        }

        try {
            float x = Float.MAX_VALUE;
            float y = Float.MAX_VALUE;
            float u = Float.MIN_VALUE;
            float v = Float.MIN_VALUE;

            for (int j = 0, length = vertexData.length; j < length; j++) {
                float[] vertices = vertexData[j];
                for (int i = 0, n = idx[0]; i < n; i += 5) {
                    x = Math.min(x, vertices[i]);
                    y = Math.min(y, vertices[i + 1]);

                    u = Math.max(u, vertices[i]);
                    v = Math.max(v, vertices[i + 1]);

                }
            }

            centerpoint[0] = x + ((u - x) / 2);
            centerpoint[1] = y + ((v - y) / 2);
            centerpointCalculated = true;
            return centerpoint;
        } catch (Exception e) {
            centerpoint[0] = 0;
            centerpoint[1] = 0;
            centerpointCalculated = true;
            return centerpoint;
        }

    }

}