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