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