mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/LCE-Revelations.git
synced 2026-07-09 07:48:17 +00:00
Enable multi-language font rendering and Unicode text input
Goal: Allow players to type and display text in any language supported by Unicode, including Chinese, Japanese, Korean, Thai, Arabic, Korean, Hindi, and more. This covers all text surfaces: chat editor, chat messages, signs (in-world and editor), world name/seed, server address/port fields, and all Iggy Flash UI text fields. Multi-language support: Two complementary rendering systems were added to handle Unicode text across the entire client: 1. Iggy UI (Flash-based text fields): A new UIUnicodeBitmapFont class serves Java Minecraft's glyph page PNGs (glyph_00.png-glyph_FF.png) through Iggy's bitmap font provider API. Registered as the global fallback font with metrics matching the Mojangles bitmap font for correct baseline alignment. When the primary bitmap font lacks a glyph, it returns IGGY_GLYPH_INVALID and Iggy seamlessly falls back to the unicode bitmap font. 2. Legacy C++ Font renderer (chat editor, in-world signs): Revived the commented-out unicode glyph page system in Font.cpp. Characters not in the bitmap font texture are rendered from glyph page PNGs loaded on demand, with proper texture switching mid-string. 3. ChatScreen input: Removed the restrictive acceptableLetters filter so all printable Unicode characters are accepted in chat. Languages now supported for text input and rendering: - Japanese (Hiragana, Katakana, Kanji) - Chinese (Simplified and Traditional) - Korean (Hangul) - Thai - Arabic - Hindi (Devanagari) - Russian (Cyrillic) - already worked via bitmap font - Greek - already worked via bitmap font - Polish, Czech, Turkish (Extended Latin) - already worked via bitmap font - Armenian, Georgian, and other scripts covered by glyph pages Security fixes: - Fixed memset under-initialization of Font::charWidths (zeroed 460 bytes instead of 460*sizeof(int)=1840 bytes, leaving entries 115+ uninitialized) - pre-existing bug - Added bounds checks to all UIUnicodeBitmapFont callbacks to reject glyph IDs outside [0, 65535], preventing OOB array access - Added bounds check in Font::width() section-sign fallback path to prevent OOB read on charWidths[] with high codepoints - Blocked Unicode bidirectional override characters (U+202A-202E, U+2066-2069) in chat input to prevent message spoofing Memory leak fix: - Fixed SignTileEntity::load allocating wchar_t[256] with new[] on every sign load without freeing. Replaced with stack allocation. Debug logging: - Added [SIGN] prefixed logging for sign save/update operations - Added [CHAT] prefixed logging for chat send/receive operations Files changed: - UIUnicodeBitmapFont.h/.cpp (new) - Iggy bitmap font for glyph pages - UIBitmapFont.cpp - Return IGGY_GLYPH_INVALID for unknown chars - UIFontData.h/.cpp - Added hasGlyph() method - UIController.h/.cpp - Load and register unicode bitmap fallback font - UITTFFont.h/.cpp - Added registerAsDefaultFonts parameter - Font.h/.cpp - Revived unicode glyph page rendering system - ChatScreen.cpp - Accept all Unicode input, block bidi overrides - Gui.cpp - Chat display debug logging - ClientConnection.cpp - Sign update debug logging - SignTileEntity.cpp - Sign save logging, memory leak fix
This commit is contained in:
@@ -16,7 +16,7 @@ Font::Font(Options *options, const wstring& name, Textures* textures, bool enfor
|
||||
charWidths = new int[charC];
|
||||
|
||||
// 4J - added initialisers
|
||||
memset(charWidths, 0, charC);
|
||||
memset(charWidths, 0, charC * sizeof(int));
|
||||
|
||||
enforceUnicodeSheet = false;
|
||||
bidirectional = false;
|
||||
@@ -26,6 +26,19 @@ Font::Font(Options *options, const wstring& name, Textures* textures, bool enfor
|
||||
m_underline = false;
|
||||
m_strikethrough = false;
|
||||
|
||||
memset(unicodeTexID, 0, sizeof(unicodeTexID));
|
||||
memset(unicodeWidth, 0, sizeof(unicodeWidth));
|
||||
lastBoundTexture = 0;
|
||||
|
||||
// Load unicode glyph sizes
|
||||
FILE *glyphFile = nullptr;
|
||||
fopen_s(&glyphFile, "Common/res/1_2_2/font/glyph_sizes.bin", "rb");
|
||||
if (glyphFile)
|
||||
{
|
||||
fread(unicodeWidth, 1, 65536, glyphFile);
|
||||
fclose(glyphFile);
|
||||
}
|
||||
|
||||
// Set up member variables
|
||||
m_cols = cols;
|
||||
m_rows = rows;
|
||||
@@ -261,7 +274,19 @@ void Font::drawLiteral(const wstring& str, int x, int y, int color)
|
||||
yPos = static_cast<float>(y);
|
||||
wstring cleanStr = sanitize(str);
|
||||
for (size_t i = 0; i < cleanStr.length(); ++i)
|
||||
renderCharacter(cleanStr.at(i));
|
||||
{
|
||||
wchar_t c = cleanStr.at(i);
|
||||
if (isUnicodeGlyphChar(c))
|
||||
{
|
||||
renderUnicodeCharacter(c);
|
||||
textures->bindTexture(m_textureLocation);
|
||||
lastBoundTexture = fontTexture;
|
||||
}
|
||||
else
|
||||
{
|
||||
renderCharacter(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Font::drawShadowWordWrap(const wstring &str, int x, int y, int w, int color, int h)
|
||||
@@ -345,7 +370,7 @@ void Font::draw(const wstring &str, bool dropShadow)
|
||||
}
|
||||
|
||||
// "noise" for crazy splash screen message
|
||||
if (noise)
|
||||
if (noise && !isUnicodeGlyphChar(c))
|
||||
{
|
||||
int newc;
|
||||
do
|
||||
@@ -355,7 +380,18 @@ void Font::draw(const wstring &str, bool dropShadow)
|
||||
c = newc;
|
||||
}
|
||||
|
||||
addCharacterQuad(c);
|
||||
if (isUnicodeGlyphChar(c))
|
||||
{
|
||||
t->end();
|
||||
renderUnicodeCharacter(c);
|
||||
textures->bindTexture(m_textureLocation);
|
||||
lastBoundTexture = fontTexture;
|
||||
t->begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
addCharacterQuad(c);
|
||||
}
|
||||
}
|
||||
|
||||
t->end();
|
||||
@@ -396,11 +432,22 @@ int Font::width(const wstring& str)
|
||||
++i;
|
||||
else
|
||||
{
|
||||
len += charWidths[167];
|
||||
if (isUnicodeGlyphChar(167))
|
||||
len += (int)unicodeCharWidth(167);
|
||||
else
|
||||
len += charWidths[167];
|
||||
if (i + 1 < cleanStr.length())
|
||||
len += charWidths[static_cast<unsigned>(cleanStr[++i])];
|
||||
{
|
||||
wchar_t nextC = cleanStr[++i];
|
||||
if (isUnicodeGlyphChar(nextC))
|
||||
len += (int)unicodeCharWidth(nextC);
|
||||
else if (static_cast<unsigned>(nextC) < static_cast<unsigned>(m_cols * m_rows))
|
||||
len += charWidths[static_cast<unsigned>(nextC)];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isUnicodeGlyphChar(c))
|
||||
len += (int)unicodeCharWidth(c);
|
||||
else
|
||||
len += charWidths[c];
|
||||
}
|
||||
@@ -414,7 +461,13 @@ int Font::widthLiteral(const wstring& str)
|
||||
if (cleanStr == L"") return 0;
|
||||
int len = 0;
|
||||
for (size_t i = 0; i < cleanStr.length(); ++i)
|
||||
len += charWidths[static_cast<unsigned>(cleanStr.at(i))];
|
||||
{
|
||||
wchar_t wc = cleanStr.at(i);
|
||||
if (isUnicodeGlyphChar(wc))
|
||||
len += (int)unicodeCharWidth(wc);
|
||||
else
|
||||
len += charWidths[static_cast<unsigned>(wc)];
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -428,6 +481,10 @@ wstring Font::sanitize(const wstring& str)
|
||||
{
|
||||
sb[i] = MapCharacter(sb[i]);
|
||||
}
|
||||
else if (unicodeWidth[sb[i]] != 0)
|
||||
{
|
||||
// Leave as-is: raw codepoint for glyph page rendering
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this character isn't supported, just show the first character (empty square box character)
|
||||
@@ -671,33 +728,22 @@ void Font::renderFakeCB(IntBuffer *ib)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void Font::loadUnicodePage(int page)
|
||||
{
|
||||
wchar_t fileName[25];
|
||||
//String fileName = String.format("/1_2_2/font/glyph_%02X.png", page);
|
||||
swprintf(fileName,25,L"/1_2_2/font/glyph_%02X.png",page);
|
||||
wchar_t fileName[40];
|
||||
swprintf(fileName, 40, L"/1_2_2/font/glyph_%02X.png", page);
|
||||
BufferedImage *image = new BufferedImage(fileName);
|
||||
//try
|
||||
//{
|
||||
// image = ImageIO.read(Textures.class.getResourceAsStream(fileName.toString()));
|
||||
//}
|
||||
//catch (IOException e)
|
||||
//{
|
||||
// throw new RuntimeException(e);
|
||||
//}
|
||||
|
||||
unicodeTexID[page] = textures->getTexture(image);
|
||||
lastBoundTexture = unicodeTexID[page];
|
||||
delete image;
|
||||
}
|
||||
|
||||
void Font::renderUnicodeCharacter(wchar_t c)
|
||||
{
|
||||
if (unicodeWidth[c] == 0)
|
||||
{
|
||||
// System.out.println("no-width char " + c);
|
||||
return;
|
||||
}
|
||||
|
||||
int page = c / 256;
|
||||
|
||||
@@ -709,19 +755,17 @@ void Font::renderUnicodeCharacter(wchar_t c)
|
||||
lastBoundTexture = unicodeTexID[page];
|
||||
}
|
||||
|
||||
// first column with non-trans pixels
|
||||
int firstLeft = unicodeWidth[c] >> 4;
|
||||
// last column with non-trans pixels
|
||||
int firstRight = unicodeWidth[c] & 0xF;
|
||||
|
||||
float left = firstLeft;
|
||||
float right = firstRight + 1;
|
||||
float left = (float)firstLeft;
|
||||
float right = (float)(firstRight + 1);
|
||||
|
||||
float xOff = c % 16 * 16 + left;
|
||||
float yOff = (c & 0xFF) / 16 * 16;
|
||||
float xOff = (c % 16) * 16 + left;
|
||||
float yOff = ((c & 0xFF) / 16) * 16;
|
||||
float width = right - left - .02f;
|
||||
|
||||
Tesselator *t = Tesselator::getInstance();
|
||||
Tesselator *t = Tesselator::getInstance();
|
||||
t->begin(GL_TRIANGLE_STRIP);
|
||||
t->tex(xOff / 256.0F, yOff / 256.0F);
|
||||
t->vertex(xPos, yPos, 0.0f);
|
||||
@@ -735,5 +779,17 @@ void Font::renderUnicodeCharacter(wchar_t c)
|
||||
|
||||
xPos += (right - left) / 2 + 1;
|
||||
}
|
||||
*/
|
||||
|
||||
float Font::unicodeCharWidth(wchar_t c)
|
||||
{
|
||||
if (unicodeWidth[c] == 0) return 0;
|
||||
int firstLeft = unicodeWidth[c] >> 4;
|
||||
int firstRight = unicodeWidth[c] & 0xF;
|
||||
return (firstRight + 1 - firstLeft) / 2.0f + 1;
|
||||
}
|
||||
|
||||
bool Font::isUnicodeGlyphChar(wchar_t c)
|
||||
{
|
||||
return c >= m_cols * m_rows && unicodeWidth[c] != 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user