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 = 0;
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 * Get unicodeFlag controlling whether strings should be rendered with Unicode fonts instead of the default.png
1000 * font.
1001 */
1002 public boolean getUnicodeFlag()
1003 {
1004 return this.unicodeFlag;
1005 }
1006
1007 /**
1008 * Set bidiFlag to control if the Unicode Bidirectional Algorithm should be run before rendering any string.
1009 */
1010 public void setBidiFlag(boolean par1)
1011 {
1012 this.bidiFlag = par1;
1013 }
1014
1015 /**
1016 * Breaks a string into a list of pieces that will fit a specified width.
1017 */
1018 public List listFormattedStringToWidth(String par1Str, int par2)
1019 {
1020 return Arrays.asList(this.wrapFormattedStringToWidth(par1Str, par2).split("\n"));
1021 }
1022
1023 /**
1024 * Inserts newline and formatting into a string to wrap it within the specified width.
1025 */
1026 String wrapFormattedStringToWidth(String par1Str, int par2)
1027 {
1028 int var3 = this.sizeStringToWidth(par1Str, par2);
1029
1030 if (par1Str.length() <= var3)
1031 {
1032 return par1Str;
1033 }
1034 else
1035 {
1036 String var4 = par1Str.substring(0, var3);
1037 String var5 = getFormatFromString(var4) + par1Str.substring(var3 + (par1Str.charAt(var3) == 32 ? 1 : 0));
1038 return var4 + "\n" + this.wrapFormattedStringToWidth(var5, par2);
1039 }
1040 }
1041
1042 /**
1043 * Determines how many characters from the string will fit into the specified width.
1044 */
1045 private int sizeStringToWidth(String par1Str, int par2)
1046 {
1047 int var3 = par1Str.length();
1048 int var4 = 0;
1049 int var5 = 0;
1050 int var6 = -1;
1051
1052 for (boolean var7 = false; var5 < var3; ++var5)
1053 {
1054 char var8 = par1Str.charAt(var5);
1055
1056 switch (var8)
1057 {
1058 case 167:
1059 if (var5 < var3 - 1)
1060 {
1061 ++var5;
1062 char var9 = par1Str.charAt(var5);
1063
1064 if (var9 != 108 && var9 != 76)
1065 {
1066 if (var9 == 114 || var9 == 82)
1067 {
1068 var7 = false;
1069 }
1070 }
1071 else
1072 {
1073 var7 = true;
1074 }
1075 }
1076
1077 break;
1078 case 32:
1079 var6 = var5;
1080 default:
1081 var4 += this.getCharWidth(var8);
1082
1083 if (var7)
1084 {
1085 ++var4;
1086 }
1087 }
1088
1089 if (var8 == 10)
1090 {
1091 ++var5;
1092 var6 = var5;
1093 break;
1094 }
1095
1096 if (var4 > par2)
1097 {
1098 break;
1099 }
1100 }
1101
1102 return var5 != var3 && var6 != -1 && var6 < var5 ? var6 : var5;
1103 }
1104
1105 /**
1106 * Checks if the char code is a hexadecimal character, used to set colour.
1107 */
1108 private static boolean isFormatColor(char par0)
1109 {
1110 return par0 >= 48 && par0 <= 57 || par0 >= 97 && par0 <= 102 || par0 >= 65 && par0 <= 70;
1111 }
1112
1113 /**
1114 * Checks if the char code is O-K...lLrRk-o... used to set special formatting.
1115 */
1116 private static boolean isFormatSpecial(char par0)
1117 {
1118 return par0 >= 107 && par0 <= 111 || par0 >= 75 && par0 <= 79 || par0 == 114 || par0 == 82;
1119 }
1120
1121 /**
1122 * Digests a string for nonprinting formatting characters then returns a string containing only that formatting.
1123 */
1124 private static String getFormatFromString(String par0Str)
1125 {
1126 String var1 = "";
1127 int var2 = -1;
1128 int var3 = par0Str.length();
1129
1130 while ((var2 = par0Str.indexOf(167, var2 + 1)) != -1)
1131 {
1132 if (var2 < var3 - 1)
1133 {
1134 char var4 = par0Str.charAt(var2 + 1);
1135
1136 if (isFormatColor(var4))
1137 {
1138 var1 = "\u00a7" + var4;
1139 }
1140 else if (isFormatSpecial(var4))
1141 {
1142 var1 = var1 + "\u00a7" + var4;
1143 }
1144 }
1145 }
1146
1147 return var1;
1148 }
1149
1150 /**
1151 * Get bidiFlag that controls if the Unicode Bidirectional Algorithm should be run before rendering any string
1152 */
1153 public boolean getBidiFlag()
1154 {
1155 return this.bidiFlag;
1156 }
1157 }