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