001    package net.minecraft.client.gui;
002    
003    import cpw.mods.fml.relauncher.Side;
004    import cpw.mods.fml.relauncher.SideOnly;
005    import java.awt.image.BufferedImage;
006    import java.io.IOException;
007    import java.io.InputStream;
008    import java.text.Bidi;
009    import java.util.Arrays;
010    import java.util.Iterator;
011    import java.util.List;
012    import java.util.Random;
013    import javax.imageio.ImageIO;
014    import net.minecraft.client.renderer.RenderEngine;
015    import net.minecraft.client.renderer.Tessellator;
016    import net.minecraft.client.settings.GameSettings;
017    import net.minecraft.util.ChatAllowedCharacters;
018    import org.lwjgl.opengl.GL11;
019    
020    @SideOnly(Side.CLIENT)
021    public 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    }