    001package net.minecraft.client.gui;
    002
    003import cpw.mods.fml.relauncher.Side;
    004import cpw.mods.fml.relauncher.SideOnly;
    005import java.awt.image.BufferedImage;
    006import java.io.IOException;
    007import java.io.InputStream;
    008import java.text.Bidi;
    009import java.util.Arrays;
    010import java.util.Iterator;
    011import java.util.List;
    012import java.util.Random;
    013import javax.imageio.ImageIO;
    014import net.minecraft.client.Minecraft;
    015import net.minecraft.client.renderer.RenderEngine;
    016import net.minecraft.client.renderer.Tessellator;
    017import net.minecraft.client.settings.GameSettings;
    018import net.minecraft.util.ChatAllowedCharacters;
    019import org.lwjgl.opengl.GL11;
    020
    021@SideOnly(Side.CLIENT)
    022public class FontRenderer
    023{
    024    /** Array of width of all the characters in default.png */
    025    private int[] charWidth = new int[256];
    026
    027    /** the height in pixels of default text */
    028    public int FONT_HEIGHT = 9;
    029    public Random fontRandom = new Random();
    030
    031    /**
    032     * Array of the start/end column (in upper/lower nibble) for every glyph in the /font directory.
    033     */
    034    private byte[] glyphWidth = new byte[65536];
    035
    036    /**
    037     * Array of RGB triplets defining the 16 standard chat colors followed by 16 darker version of the same colors for
    038     * drop shadows.
    039     */
    040    private int[] colorCode = new int[32];
    041    private final String fontTextureName;
    042
    043    /** The RenderEngine used to load and setup glyph textures. */
    044    private final RenderEngine renderEngine;
    045
    046    /** Current X coordinate at which to draw the next character. */
    047    private float posX;
    048
    049    /** Current Y coordinate at which to draw the next character. */
    050    private float posY;
    051
    052    /**
    053     * If true, strings should be rendered with Unicode fonts instead of the default.png font
    054     */
    055    private boolean unicodeFlag;
    056
    057    /**
    058     * If true, the Unicode Bidirectional Algorithm should be run before rendering any string.
    059     */
    060    private boolean bidiFlag;
    061
    062    /** Used to specify new red value for the current color. */
    063    private float red;
    064
    065    /** Used to specify new blue value for the current color. */
    066    private float blue;
    067
    068    /** Used to specify new green value for the current color. */
    069    private float green;
    070
    071    /** Used to speify new alpha value for the current color. */
    072    private float alpha;
    073
    074    /** Text color of the currently rendering string. */
    075    private int textColor;
    076
    077    /** Set if the "k" style (random) is active in currently rendering string */
    078    private boolean randomStyle = false;
    079
    080    /** Set if the "l" style (bold) is active in currently rendering string */
    081    private boolean boldStyle = false;
    082
    083    /** Set if the "o" style (italic) is active in currently rendering string */
    084    private boolean italicStyle = false;
    085
    086    /**
    087     * Set if the "n" style (underlined) is active in currently rendering string
    088     */
    089    private boolean underlineStyle = false;
    090
    091    /**
    092     * Set if the "m" style (strikethrough) is active in currently rendering string
    093     */
    094    private boolean strikethroughStyle = false;
    095
    096    FontRenderer()
    097    {
    098        this.renderEngine = null;
    099        this.fontTextureName = null;
    100    }
    101
    102    public FontRenderer(GameSettings par1GameSettings, String par2Str, RenderEngine par3RenderEngine, boolean par4)
    103    {
    104        this.fontTextureName = par2Str;
    105        this.renderEngine = par3RenderEngine;
    106        this.unicodeFlag = par4;
    107        this.readFontData();
    108        par3RenderEngine.bindTexture(par2Str);
    109
    110        for (int i = 0; i < 32; ++i)
    111        {
    112            int j = (i >> 3 & 1) * 85;
    113            int k = (i >> 2 & 1) * 170 + j;
    114            int l = (i >> 1 & 1) * 170 + j;
    115            int i1 = (i >> 0 & 1) * 170 + j;
    116
    117            if (i == 6)
    118            {
    119                k += 85;
    120            }
    121
    122            if (par1GameSettings.anaglyph)
    123            {
    124                int j1 = (k * 30 + l * 59 + i1 * 11) / 100;
    125                int k1 = (k * 30 + l * 70) / 100;
    126                int l1 = (k * 30 + i1 * 70) / 100;
    127                k = j1;
    128                l = k1;
    129                i1 = l1;
    130            }
    131
    132            if (i >= 16)
    133            {
    134                k /= 4;
    135                l /= 4;
    136                i1 /= 4;
    137            }
    138
    139            this.colorCode[i] = (k & 255) << 16 | (l & 255) << 8 | i1 & 255;
    140        }
    141    }
    142
    143    public void readFontData()
    144    {
    145        this.readGlyphSizes();
    146        this.readFontTexture(this.fontTextureName);
    147    }
    148
    149    private void readFontTexture(String par1Str)
    150    {
    151        BufferedImage bufferedimage;
    152
    153        try
    154        {
    155            bufferedimage = ImageIO.read(RenderEngine.class.getResourceAsStream(par1Str));
    156        }
    157        catch (IOException ioexception)
    158        {
    159            throw new RuntimeException(ioexception);
    160        }
    161
    162        int i = bufferedimage.getWidth();
    163        int j = bufferedimage.getHeight();
    164        int[] aint = new int[i * j];
    165        bufferedimage.getRGB(0, 0, i, j, aint, 0, i);
    166        int k = 0;
    167
    168        while (k < 256)
    169        {
    170            int l = k % 16;
    171            int i1 = k / 16;
    172            int j1 = 7;
    173
    174            while (true)
    175            {
    176                if (j1 >= 0)
    177                {
    178                    int k1 = l * 8 + j1;
    179                    boolean flag = true;
    180
    181                    for (int l1 = 0; l1 < 8 && flag; ++l1)
    182                    {
    183                        int i2 = (i1 * 8 + l1) * i;
    184                        int j2 = aint[k1 + i2] & 255;
    185
    186                        if (j2 > 0)
    187                        {
    188                            flag = false;
    189                        }
    190                    }
    191
    192                    if (flag)
    193                    {
    194                        --j1;
    195                        continue;
    196                    }
    197                }
    198
    199                if (k == 32)
    200                {
    201                    j1 = 2;
    202                }
    203
    204                this.charWidth[k] = j1 + 2;
    205                ++k;
    206                break;
    207            }
    208        }
    209    }
    210
    211    private void readGlyphSizes()
    212    {
    213        try
    214        {
    215            InputStream inputstream = Minecraft.getMinecraft().texturePackList.getSelectedTexturePack().getResourceAsStream("/font/glyph_sizes.bin");
    216            inputstream.read(this.glyphWidth);
    217        }
    218        catch (IOException ioexception)
    219        {
    220            throw new RuntimeException(ioexception);
    221        }
    222    }
    223
    224    /**
    225     * Pick how to render a single character and return the width used.
    226     */
    227    private float renderCharAtPos(int par1, char par2, boolean par3)
    228    {
    229        return par2 == 32 ? 4.0F : (par1 > 0 && !this.unicodeFlag ? this.renderDefaultChar(par1 + 32, par3) : this.renderUnicodeChar(par2, par3));
    230    }
    231
    232    /**
    233     * Render a single character with the default.png font at current (posX,posY) location...
    234     */
    235    private float renderDefaultChar(int par1, boolean par2)
    236    {
    237        float f = (float)(par1 % 16 * 8);
    238        float f1 = (float)(par1 / 16 * 8);
    239        float f2 = par2 ? 1.0F : 0.0F;
    240        this.renderEngine.bindTexture(this.fontTextureName);
    241        float f3 = (float)this.charWidth[par1] - 0.01F;
    242        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
    243        GL11.glTexCoord2f(f / 128.0F, f1 / 128.0F);
    244        GL11.glVertex3f(this.posX + f2, this.posY, 0.0F);
    245        GL11.glTexCoord2f(f / 128.0F, (f1 + 7.99F) / 128.0F);
    246        GL11.glVertex3f(this.posX - f2, this.posY + 7.99F, 0.0F);
    247        GL11.glTexCoord2f((f + f3) / 128.0F, f1 / 128.0F);
    248        GL11.glVertex3f(this.posX + f3 + f2, this.posY, 0.0F);
    249        GL11.glTexCoord2f((f + f3) / 128.0F, (f1 + 7.99F) / 128.0F);
    250        GL11.glVertex3f(this.posX + f3 - f2, this.posY + 7.99F, 0.0F);
    251        GL11.glEnd();
    252        return (float)this.charWidth[par1];
    253    }
    254
    255    /**
    256     * Load one of the /font/glyph_XX.png into a new GL texture and store the texture ID in glyphTextureName array.
    257     */
    258    private void loadGlyphTexture(int par1)
    259    {
    260        String s = String.format("/font/glyph_%02X.png", new Object[] {Integer.valueOf(par1)});
    261        this.renderEngine.bindTexture(s);
    262    }
    263
    264    /**
    265     * Render a single Unicode character at current (posX,posY) location using one of the /font/glyph_XX.png files...
    266     */
    267    private float renderUnicodeChar(char par1, boolean par2)
    268    {
    269        if (this.glyphWidth[par1] == 0)
    270        {
    271            return 0.0F;
    272        }
    273        else
    274        {
    275            int i = par1 / 256;
    276            this.loadGlyphTexture(i);
    277            int j = this.glyphWidth[par1] >>> 4;
    278            int k = this.glyphWidth[par1] & 15;
    279            float f = (float)j;
    280            float f1 = (float)(k + 1);
    281            float f2 = (float)(par1 % 16 * 16) + f;
    282            float f3 = (float)((par1 & 255) / 16 * 16);
    283            float f4 = f1 - f - 0.02F;
    284            float f5 = par2 ? 1.0F : 0.0F;
    285            GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
    286            GL11.glTexCoord2f(f2 / 256.0F, f3 / 256.0F);
    287            GL11.glVertex3f(this.posX + f5, this.posY, 0.0F);
    288            GL11.glTexCoord2f(f2 / 256.0F, (f3 + 15.98F) / 256.0F);
    289            GL11.glVertex3f(this.posX - f5, this.posY + 7.99F, 0.0F);
    290            GL11.glTexCoord2f((f2 + f4) / 256.0F, f3 / 256.0F);
    291            GL11.glVertex3f(this.posX + f4 / 2.0F + f5, this.posY, 0.0F);
    292            GL11.glTexCoord2f((f2 + f4) / 256.0F, (f3 + 15.98F) / 256.0F);
    293            GL11.glVertex3f(this.posX + f4 / 2.0F - f5, this.posY + 7.99F, 0.0F);
    294            GL11.glEnd();
    295            return (f1 - f) / 2.0F + 1.0F;
    296        }
    297    }
    298
    299    /**
    300     * Draws the specified string with a shadow.
    301     */
    302    public int drawStringWithShadow(String par1Str, int par2, int par3, int par4)
    303    {
    304        return this.drawString(par1Str, par2, par3, par4, true);
    305    }
    306
    307    /**
    308     * Draws the specified string.
    309     */
    310    public int drawString(String par1Str, int par2, int par3, int par4)
    311    {
    312        return this.drawString(par1Str, par2, par3, par4, false);
    313    }
    314
    315    /**
    316     * Draws the specified string. Args: string, x, y, color, dropShadow
    317     */
    318    public int drawString(String par1Str, int par2, int par3, int par4, boolean par5)
    319    {
    320        this.resetStyles();
    321
    322        if (this.bidiFlag)
    323        {
    324            par1Str = this.bidiReorder(par1Str);
    325        }
    326
    327        int l;
    328
    329        if (par5)
    330        {
    331            l = this.renderString(par1Str, par2 + 1, par3 + 1, par4, true);
    332            l = Math.max(l, this.renderString(par1Str, par2, par3, par4, false));
    333        }
    334        else
    335        {
    336            l = this.renderString(par1Str, par2, par3, par4, false);
    337        }
    338
    339        return l;
    340    }
    341
    342    /**
    343     * Apply Unicode Bidirectional Algorithm to string and return a new possibly reordered string for visual rendering.
    344     */
    345    private String bidiReorder(String par1Str)
    346    {
    347        if (par1Str != null && Bidi.requiresBidi(par1Str.toCharArray(), 0, par1Str.length()))
    348        {
    349            Bidi bidi = new Bidi(par1Str, -2);
    350            byte[] abyte = new byte[bidi.getRunCount()];
    351            String[] astring = new String[abyte.length];
    352            int i;
    353
    354            for (int j = 0; j < abyte.length; ++j)
    355            {
    356                int k = bidi.getRunStart(j);
    357                i = bidi.getRunLimit(j);
    358                int l = bidi.getRunLevel(j);
    359                String s1 = par1Str.substring(k, i);
    360                abyte[j] = (byte)l;
    361                astring[j] = s1;
    362            }
    363
    364            String[] astring1 = (String[])astring.clone();
    365            Bidi.reorderVisually(abyte, 0, astring, 0, abyte.length);
    366            StringBuilder stringbuilder = new StringBuilder();
    367            i = 0;
    368
    369            while (i < astring.length)
    370            {
    371                byte b0 = abyte[i];
    372                int i1 = 0;
    373
    374                while (true)
    375                {
    376                    if (i1 < astring1.length)
    377                    {
    378                        if (!astring1[i1].equals(astring[i]))
    379                        {
    380                            ++i1;
    381                            continue;
    382                        }
    383
    384                        b0 = abyte[i1];
    385                    }
    386
    387                    if ((b0 & 1) == 0)
    388                    {
    389                        stringbuilder.append(astring[i]);
    390                    }
    391                    else
    392                    {
    393                        for (i1 = astring[i].length() - 1; i1 >= 0; --i1)
    394                        {
    395                            char c0 = astring[i].charAt(i1);
    396
    397                            if (c0 == 40)
    398                            {
    399                                c0 = 41;
    400                            }
    401                            else if (c0 == 41)
    402                            {
    403                                c0 = 40;
    404                            }
    405
    406                            stringbuilder.append(c0);
    407                        }
    408                    }
    409
    410                    ++i;
    411                    break;
    412                }
    413            }
    414
    415            return stringbuilder.toString();
    416        }
    417        else
    418        {
    419            return par1Str;
    420        }
    421    }
    422
    423    /**
    424     * Reset all style flag fields in the class to false; called at the start of string rendering
    425     */
    426    private void resetStyles()
    427    {
    428        this.randomStyle = false;
    429        this.boldStyle = false;
    430        this.italicStyle = false;
    431        this.underlineStyle = false;
    432        this.strikethroughStyle = false;
    433    }
    434
    435    /**
    436     * Render a single line string at the current (posX,posY) and update posX
    437     */
    438    private void renderStringAtPos(String par1Str, boolean par2)
    439    {
    440        for (int i = 0; i < par1Str.length(); ++i)
    441        {
    442            char c0 = par1Str.charAt(i);
    443            int j;
    444            int k;
    445
    446            if (c0 == 167 && i + 1 < par1Str.length())
    447            {
    448                j = "0123456789abcdefklmnor".indexOf(par1Str.toLowerCase().charAt(i + 1));
    449
    450                if (j < 16)
    451                {
    452                    this.randomStyle = false;
    453                    this.boldStyle = false;
    454                    this.strikethroughStyle = false;
    455                    this.underlineStyle = false;
    456                    this.italicStyle = false;
    457
    458                    if (j < 0 || j > 15)
    459                    {
    460                        j = 15;
    461                    }
    462
    463                    if (par2)
    464                    {
    465                        j += 16;
    466                    }
    467
    468                    k = this.colorCode[j];
    469                    this.textColor = k;
    470                    GL11.glColor4f((float)(k >> 16) / 255.0F, (float)(k >> 8 & 255) / 255.0F, (float)(k & 255) / 255.0F, this.alpha);
    471                }
    472                else if (j == 16)
    473                {
    474                    this.randomStyle = true;
    475                }
    476                else if (j == 17)
    477                {
    478                    this.boldStyle = true;
    479                }
    480                else if (j == 18)
    481                {
    482                    this.strikethroughStyle = true;
    483                }
    484                else if (j == 19)
    485                {
    486                    this.underlineStyle = true;
    487                }
    488                else if (j == 20)
    489                {
    490                    this.italicStyle = true;
    491                }
    492                else if (j == 21)
    493                {
    494                    this.randomStyle = false;
    495                    this.boldStyle = false;
    496                    this.strikethroughStyle = false;
    497                    this.underlineStyle = false;
    498                    this.italicStyle = false;
    499                    GL11.glColor4f(this.red, this.blue, this.green, this.alpha);
    500                }
    501
    502                ++i;
    503            }
    504            else
    505            {
    506                j = ChatAllowedCharacters.allowedCharacters.indexOf(c0);
    507
    508                if (this.randomStyle && j > 0)
    509                {
    510                    do
    511                    {
    512                        k = this.fontRandom.nextInt(ChatAllowedCharacters.allowedCharacters.length());
    513                    }
    514                    while (this.charWidth[j + 32] != this.charWidth[k + 32]);
    515
    516                    j = k;
    517                }
    518
    519                float f = this.unicodeFlag ? 0.5F : 1.0F;
    520                boolean flag1 = (j <= 0 || this.unicodeFlag) && par2;
    521
    522                if (flag1)
    523                {
    524                    this.posX -= f;
    525                    this.posY -= f;
    526                }
    527
    528                float f1 = this.renderCharAtPos(j, c0, this.italicStyle);
    529
    530                if (flag1)
    531                {
    532                    this.posX += f;
    533                    this.posY += f;
    534                }
    535
    536                if (this.boldStyle)
    537                {
    538                    this.posX += f;
    539
    540                    if (flag1)
    541                    {
    542                        this.posX -= f;
    543                        this.posY -= f;
    544                    }
    545
    546                    this.renderCharAtPos(j, c0, this.italicStyle);
    547                    this.posX -= f;
    548
    549                    if (flag1)
    550                    {
    551                        this.posX += f;
    552                        this.posY += f;
    553                    }
    554
    555                    ++f1;
    556                }
    557
    558                Tessellator tessellator;
    559
    560                if (this.strikethroughStyle)
    561                {
    562                    tessellator = Tessellator.instance;
    563                    GL11.glDisable(GL11.GL_TEXTURE_2D);
    564                    tessellator.startDrawingQuads();
    565                    tessellator.addVertex((double)this.posX, (double)(this.posY + (float)(this.FONT_HEIGHT / 2)), 0.0D);
    566                    tessellator.addVertex((double)(this.posX + f1), (double)(this.posY + (float)(this.FONT_HEIGHT / 2)), 0.0D);
    567                    tessellator.addVertex((double)(this.posX + f1), (double)(this.posY + (float)(this.FONT_HEIGHT / 2) - 1.0F), 0.0D);
    568                    tessellator.addVertex((double)this.posX, (double)(this.posY + (float)(this.FONT_HEIGHT / 2) - 1.0F), 0.0D);
    569                    tessellator.draw();
    570                    GL11.glEnable(GL11.GL_TEXTURE_2D);
    571                }
    572
    573                if (this.underlineStyle)
    574                {
    575                    tessellator = Tessellator.instance;
    576                    GL11.glDisable(GL11.GL_TEXTURE_2D);
    577                    tessellator.startDrawingQuads();
    578                    int l = this.underlineStyle ? -1 : 0;
    579                    tessellator.addVertex((double)(this.posX + (float)l), (double)(this.posY + (float)this.FONT_HEIGHT), 0.0D);
    580                    tessellator.addVertex((double)(this.posX + f1), (double)(this.posY + (float)this.FONT_HEIGHT), 0.0D);
    581                    tessellator.addVertex((double)(this.posX + f1), (double)(this.posY + (float)this.FONT_HEIGHT - 1.0F), 0.0D);
    582                    tessellator.addVertex((double)(this.posX + (float)l), (double)(this.posY + (float)this.FONT_HEIGHT - 1.0F), 0.0D);
    583                    tessellator.draw();
    584                    GL11.glEnable(GL11.GL_TEXTURE_2D);
    585                }
    586
    587                this.posX += (float)((int)f1);
    588            }
    589        }
    590    }
    591
    592    /**
    593     * Render string either left or right aligned depending on bidiFlag
    594     */
    595    private int renderStringAligned(String par1Str, int par2, int par3, int par4, int par5, boolean par6)
    596    {
    597        if (this.bidiFlag)
    598        {
    599            par1Str = this.bidiReorder(par1Str);
    600            int i1 = this.getStringWidth(par1Str);
    601            par2 = par2 + par4 - i1;
    602        }
    603
    604        return this.renderString(par1Str, par2, par3, par5, par6);
    605    }
    606
    607    /**
    608     * Render single line string by setting GL color, current (posX,posY), and calling renderStringAtPos()
    609     */
    610    private int renderString(String par1Str, int par2, int par3, int par4, boolean par5)
    611    {
    612        if (par1Str == null)
    613        {
    614            return 0;
    615        }
    616        else
    617        {
    618            if ((par4 & -67108864) == 0)
    619            {
    620                par4 |= -16777216;
    621            }
    622
    623            if (par5)
    624            {
    625                par4 = (par4 & 16579836) >> 2 | par4 & -16777216;
    626            }
    627
    628            this.red = (float)(par4 >> 16 & 255) / 255.0F;
    629            this.blue = (float)(par4 >> 8 & 255) / 255.0F;
    630            this.green = (float)(par4 & 255) / 255.0F;
    631            this.alpha = (float)(par4 >> 24 & 255) / 255.0F;
    632            GL11.glColor4f(this.red, this.blue, this.green, this.alpha);
    633            this.posX = (float)par2;
    634            this.posY = (float)par3;
    635            this.renderStringAtPos(par1Str, par5);
    636            return (int)this.posX;
    637        }
    638    }
    639
    640    /**
    641     * Returns the width of this string. Equivalent of FontMetrics.stringWidth(String s).
    642     */
    643    public int getStringWidth(String par1Str)
    644    {
    645        if (par1Str == null)
    646        {
    647            return 0;
    648        }
    649        else
    650        {
    651            int i = 0;
    652            boolean flag = false;
    653
    654            for (int j = 0; j < par1Str.length(); ++j)
    655            {
    656                char c0 = par1Str.charAt(j);
    657                int k = this.getCharWidth(c0);
    658
    659                if (k < 0 && j < par1Str.length() - 1)
    660                {
    661                    ++j;
    662                    c0 = par1Str.charAt(j);
    663
    664                    if (c0 != 108 && c0 != 76)
    665                    {
    666                        if (c0 == 114 || c0 == 82)
    667                        {
    668                            flag = false;
    669                        }
    670                    }
    671                    else
    672                    {
    673                        flag = true;
    674                    }
    675
    676                    k = 0;
    677                }
    678
    679                i += k;
    680
    681                if (flag)
    682                {
    683                    ++i;
    684                }
    685            }
    686
    687            return i;
    688        }
    689    }
    690
    691    /**
    692     * Returns the width of this character as rendered.
    693     */
    694    public int getCharWidth(char par1)
    695    {
    696        if (par1 == 167)
    697        {
    698            return -1;
    699        }
    700        else if (par1 == 32)
    701        {
    702            return 4;
    703        }
    704        else
    705        {
    706            int i = ChatAllowedCharacters.allowedCharacters.indexOf(par1);
    707
    708            if (i >= 0 && !this.unicodeFlag)
    709            {
    710                return this.charWidth[i + 32];
    711            }
    712            else if (this.glyphWidth[par1] != 0)
    713            {
    714                int j = this.glyphWidth[par1] >>> 4;
    715                int k = this.glyphWidth[par1] & 15;
    716
    717                if (k > 7)
    718                {
    719                    k = 15;
    720                    j = 0;
    721                }
    722
    723                ++k;
    724                return (k - j) / 2 + 1;
    725            }
    726            else
    727            {
    728                return 0;
    729            }
    730        }
    731    }
    732
    733    /**
    734     * Trims a string to fit a specified Width.
    735     */
    736    public String trimStringToWidth(String par1Str, int par2)
    737    {
    738        return this.trimStringToWidth(par1Str, par2, false);
    739    }
    740
    741    /**
    742     * Trims a string to a specified width, and will reverse it if par3 is set.
    743     */
    744    public String trimStringToWidth(String par1Str, int par2, boolean par3)
    745    {
    746        StringBuilder stringbuilder = new StringBuilder();
    747        int j = 0;
    748        int k = par3 ? par1Str.length() - 1 : 0;
    749        int l = par3 ? -1 : 1;
    750        boolean flag1 = false;
    751        boolean flag2 = false;
    752
    753        for (int i1 = k; i1 >= 0 && i1 < par1Str.length() && j < par2; i1 += l)
    754        {
    755            char c0 = par1Str.charAt(i1);
    756            int j1 = this.getCharWidth(c0);
    757
    758            if (flag1)
    759            {
    760                flag1 = false;
    761
    762                if (c0 != 108 && c0 != 76)
    763                {
    764                    if (c0 == 114 || c0 == 82)
    765                    {
    766                        flag2 = false;
    767                    }
    768                }
    769                else
    770                {
    771                    flag2 = true;
    772                }
    773            }
    774            else if (j1 < 0)
    775            {
    776                flag1 = true;
    777            }
    778            else
    779            {
    780                j += j1;
    781
    782                if (flag2)
    783                {
    784                    ++j;
    785                }
    786            }
    787
    788            if (j > par2)
    789            {
    790                break;
    791            }
    792
    793            if (par3)
    794            {
    795                stringbuilder.insert(0, c0);
    796            }
    797            else
    798            {
    799                stringbuilder.append(c0);
    800            }
    801        }
    802
    803        return stringbuilder.toString();
    804    }
    805
    806    /**
    807     * Remove all newline characters from the end of the string
    808     */
    809    private String trimStringNewline(String par1Str)
    810    {
    811        while (par1Str != null && par1Str.endsWith("\n"))
    812        {
    813            par1Str = par1Str.substring(0, par1Str.length() - 1);
    814        }
    815
    816        return par1Str;
    817    }
    818
    819    /**
    820     * Splits and draws a String with wordwrap (maximum length is parameter k)
    821     */
    822    public void drawSplitString(String par1Str, int par2, int par3, int par4, int par5)
    823    {
    824        this.resetStyles();
    825        this.textColor = par5;
    826        par1Str = this.trimStringNewline(par1Str);
    827        this.renderSplitString(par1Str, par2, par3, par4, false);
    828    }
    829
    830    /**
    831     * Perform actual work of rendering a multi-line string with wordwrap and with darker drop shadow color if flag is
    832     * set
    833     */
    834    private void renderSplitString(String par1Str, int par2, int par3, int par4, boolean par5)
    835    {
    836        List list = this.listFormattedStringToWidth(par1Str, par4);
    837
    838        for (Iterator iterator = list.iterator(); iterator.hasNext(); par3 += this.FONT_HEIGHT)
    839        {
    840            String s1 = (String)iterator.next();
    841            this.renderStringAligned(s1, par2, par3, par4, this.textColor, par5);
    842        }
    843    }
    844
    845    /**
    846     * Returns the width of the wordwrapped String (maximum length is parameter k)
    847     */
    848    public int splitStringWidth(String par1Str, int par2)
    849    {
    850        return this.FONT_HEIGHT * this.listFormattedStringToWidth(par1Str, par2).size();
    851    }
    852
    853    /**
    854     * Set unicodeFlag controlling whether strings should be rendered with Unicode fonts instead of the default.png
    855     * font.
    856     */
    857    public void setUnicodeFlag(boolean par1)
    858    {
    859        this.unicodeFlag = par1;
    860    }
    861
    862    /**
    863     * Get unicodeFlag controlling whether strings should be rendered with Unicode fonts instead of the default.png
    864     * font.
    865     */
    866    public boolean getUnicodeFlag()
    867    {
    868        return this.unicodeFlag;
    869    }
    870
    871    /**
    872     * Set bidiFlag to control if the Unicode Bidirectional Algorithm should be run before rendering any string.
    873     */
    874    public void setBidiFlag(boolean par1)
    875    {
    876        this.bidiFlag = par1;
    877    }
    878
    879    /**
    880     * Breaks a string into a list of pieces that will fit a specified width.
    881     */
    882    public List listFormattedStringToWidth(String par1Str, int par2)
    883    {
    884        return Arrays.asList(this.wrapFormattedStringToWidth(par1Str, par2).split("\n"));
    885    }
    886
    887    /**
    888     * Inserts newline and formatting into a string to wrap it within the specified width.
    889     */
    890    String wrapFormattedStringToWidth(String par1Str, int par2)
    891    {
    892        int j = this.sizeStringToWidth(par1Str, par2);
    893
    894        if (par1Str.length() <= j)
    895        {
    896            return par1Str;
    897        }
    898        else
    899        {
    900            String s1 = par1Str.substring(0, j);
    901            char c0 = par1Str.charAt(j);
    902            boolean flag = c0 == 32 || c0 == 10;
    903            String s2 = getFormatFromString(s1) + par1Str.substring(j + (flag ? 1 : 0));
    904            return s1 + "\n" + this.wrapFormattedStringToWidth(s2, par2);
    905        }
    906    }
    907
    908    /**
    909     * Determines how many characters from the string will fit into the specified width.
    910     */
    911    private int sizeStringToWidth(String par1Str, int par2)
    912    {
    913        int j = par1Str.length();
    914        int k = 0;
    915        int l = 0;
    916        int i1 = -1;
    917
    918        for (boolean flag = false; l < j; ++l)
    919        {
    920            char c0 = par1Str.charAt(l);
    921
    922            switch (c0)
    923            {
    924                case 10:
    925                    --l;
    926                    break;
    927                case 167:
    928                    if (l < j - 1)
    929                    {
    930                        ++l;
    931                        char c1 = par1Str.charAt(l);
    932
    933                        if (c1 != 108 && c1 != 76)
    934                        {
    935                            if (c1 == 114 || c1 == 82 || isFormatColor(c1))
    936                            {
    937                                flag = false;
    938                            }
    939                        }
    940                        else
    941                        {
    942                            flag = true;
    943                        }
    944                    }
    945
    946                    break;
    947                case 32:
    948                    i1 = l;
    949                default:
    950                    k += this.getCharWidth(c0);
    951
    952                    if (flag)
    953                    {
    954                        ++k;
    955                    }
    956            }
    957
    958            if (c0 == 10)
    959            {
    960                ++l;
    961                i1 = l;
    962                break;
    963            }
    964
    965            if (k > par2)
    966            {
    967                break;
    968            }
    969        }
    970
    971        return l != j && i1 != -1 && i1 < l ? i1 : l;
    972    }
    973
    974    /**
    975     * Checks if the char code is a hexadecimal character, used to set colour.
    976     */
    977    private static boolean isFormatColor(char par0)
    978    {
    979        return par0 >= 48 && par0 <= 57 || par0 >= 97 && par0 <= 102 || par0 >= 65 && par0 <= 70;
    980    }
    981
    982    /**
    983     * Checks if the char code is O-K...lLrRk-o... used to set special formatting.
    984     */
    985    private static boolean isFormatSpecial(char par0)
    986    {
    987        return par0 >= 107 && par0 <= 111 || par0 >= 75 && par0 <= 79 || par0 == 114 || par0 == 82;
    988    }
    989
    990    /**
    991     * Digests a string for nonprinting formatting characters then returns a string containing only that formatting.
    992     */
    993    private static String getFormatFromString(String par0Str)
    994    {
    995        String s1 = "";
    996        int i = -1;
    997        int j = par0Str.length();
    998
    999        while ((i = par0Str.indexOf(167, i + 1)) != -1)
    1000        {
    1001            if (i < j - 1)
    1002            {
    1003                char c0 = par0Str.charAt(i + 1);
    1004
    1005                if (isFormatColor(c0))
    1006                {
    1007                    s1 = "\u00a7" + c0;
    1008                }
    1009                else if (isFormatSpecial(c0))
    1010                {
    1011                    s1 = s1 + "\u00a7" + c0;
    1012                }
    1013            }
    1014        }
    1015
    1016        return s1;
    1017    }
    1018
    1019    /**
    1020     * Get bidiFlag that controls if the Unicode Bidirectional Algorithm should be run before rendering any string
    1021     */
    1022    public boolean getBidiFlag()
    1023    {
    1024        return this.bidiFlag;
    1025    }
    1026}