cn.lambdalib.util.client.font.Fragmentor.java Source code

Java tutorial

Introduction

Here is the source code for cn.lambdalib.util.client.font.Fragmentor.java

Source

/**
* Copyright (c) Lambda Innovation, 2013-2016
* This file is part of LambdaLib modding library.
* https://github.com/LambdaInnovation/LambdaLib
* Licensed under MIT, see project root for more information.
*/
package cn.lambdalib.util.client.font;

import org.apache.commons.lang3.tuple.Pair;

import java.util.ArrayList;
import java.util.List;

/**
 * A helper to 'fragmentize' string to draw in multi-line in a sane way.
 * Currently isn't perfecct, but is viable.
 * @author WeAthFolD
 */
public class Fragmentor {

    private static final String puncts = ",.<>?;:'\"[]{}?";

    public interface IFontSizeProvider {
        /**
         * Get the width of given character when drawed.
         */
        double getCharWidth(int chr);

        /**
         * Get the text width that will be drawn.
         */
        double getTextWidth(String str);
    }

    public static List<String> toMultiline(String str, IFontSizeProvider font, double local_x, double limit) {
        Fragmentor frag = new Fragmentor(str);
        List<String> ret = new ArrayList<>();

        StringBuilder builder = new StringBuilder();
        while (frag.hasNext()) {
            Pair<TokenType, String> next = frag.next();
            TokenType type = next.getLeft();
            String content = next.getRight();
            double len = font.getTextWidth(content);
            if (local_x + len > limit) {
                if (!type.canSplit) { // Draws as whole in next line
                    if (builder.length() > 0) {
                        ret.add(builder.toString());
                    }
                    builder.setLength(0);
                    builder.append(content);
                    local_x = len;
                } else { // Seperate to this line and next line
                    while (!content.isEmpty()) {
                        double acc = 0.0;
                        int i = 0;
                        for (; i < content.length() && local_x + acc <= limit; ++i) {
                            acc += font.getCharWidth(content.charAt(i));
                        }

                        if (i < content.length() && isPunct(content.charAt(i))) {
                            ++i;
                        }

                        builder.append(content.substring(0, i));
                        String add = builder.toString();
                        ret.add(add);

                        builder.setLength(0);
                        local_x = 0;

                        content = content.substring(i);
                    }
                }
            } else {
                builder.append(content);
                local_x += len;
            }
        }

        if (builder.length() > 0) {
            ret.add(builder.toString());
        }

        return ret;
    }

    /**
     * Converts a string with linesep to a list of string, based on the display property of the given font.
     */
    public static List<String> toMultiline(String str, IFontSizeProvider font, double limit) {
        return toMultiline(str, font, 0, limit);
    }

    public static boolean isPunct(char ch) {
        for (int i = 0; i < puncts.length(); ++i) {
            if (puncts.charAt(i) == ch)
                return true;
        }
        return false;
    }

    public enum TokenType {
        NULL(true), WORD(false), SPACE(true), CJKV(true), PUNCT(true);
        public final boolean canSplit;

        TokenType(boolean _canSplit) {
            canSplit = _canSplit;
        }
    }

    final String str;
    int index = 0;

    public Fragmentor(String _str) {
        str = _str;
    }

    public Pair<TokenType, String> next() {
        if (index == str.length()) {
            return Pair.of(TokenType.NULL, null);
        } else {
            TokenType init = getType(str.charAt(index));
            int lindex = index;
            for (++index; index < str.length(); ++index) {
                TokenType type = getType(str.charAt(index));
                if (init != TokenType.CJKV && init != type && type != TokenType.PUNCT)
                    break;
            }
            return Pair.of(init, str.substring(lindex, index));
        }
    }

    public boolean hasNext() {
        return index < str.length();
    }

    TokenType getType(char ch) {
        if (isPunct(ch)) {
            return TokenType.PUNCT;
        } else if (Character.isWhitespace(ch)) {
            return TokenType.SPACE;
        } else if (Character.isIdeographic(ch)) {
            return TokenType.CJKV;
        } else {
            return TokenType.WORD;
        }
    }

}