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 }