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