neoLegacy/Minecraft.Client/Font.h
Revela d7822ac81e 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
2026-03-16 23:08:05 -05:00

105 lines
3.4 KiB
C++

#pragma once
class IntBuffer;
class Options;
class Textures;
class ResourceLocation;
class Font
{
private:
int *charWidths;
public:
int fontTexture;
Random *random;
private:
int colors[32]; // RGB colors for formatting
Textures *textures;
int unicodeTexID[256];
unsigned char unicodeWidth[65536];
int lastBoundTexture;
float xPos;
float yPos;
// § format state (bold, italic, underline, strikethrough); set during draw(), reset by §r
bool m_bold;
bool m_italic;
bool m_underline;
bool m_strikethrough;
bool enforceUnicodeSheet; // use unicode sheet for ascii
bool bidirectional; // use bidi to flip strings
int m_cols; // Number of columns in font sheet
int m_rows; // Number of rows in font sheet
int m_charWidth; // Maximum character width
int m_charHeight; // Maximum character height
ResourceLocation *m_textureLocation; // Texture
std::map<int, int> m_charMap;
public:
Font(Options *options, const wstring& name, Textures* textures, bool enforceUnicode, ResourceLocation *textureLocation, int cols, int rows, int charWidth, int charHeight, unsigned short charMap[] = nullptr);
#ifndef _XBOX
// 4J Stu - This dtor clashes with one in xui! We never delete these anyway so take it out for now. Can go back when we have got rid of XUI
~Font();
#endif
void renderFakeCB(IntBuffer *cb); // 4J added
private:
void renderCharacter(wchar_t c); // 4J added
void addCharacterQuad(wchar_t c);
void renderStyleLine(float x0, float y0, float x1, float y1); // solid line for underline/strikethrough
public:
void drawShadow(const wstring& str, int x, int y, int color);
void drawShadowLiteral(const wstring& str, int x, int y, int color); // draw without interpreting § codes
void drawShadowWordWrap(const wstring &str, int x, int y, int w, int color, int h); // 4J Added h param
void draw(const wstring &str, int x, int y, int color);
/**
* Reorders the string according to bidirectional levels. A bit expensive at
* the moment.
*
* @param str
* @return
*/
private:
wstring reorderBidi(const wstring &str);
void draw(const wstring &str, bool dropShadow);
void draw(const wstring& str, int x, int y, int color, bool dropShadow);
void drawLiteral(const wstring& str, int x, int y, int color); // no § parsing
int MapCharacter(wchar_t c); // 4J added
bool CharacterExists(wchar_t c); // 4J added
void loadUnicodePage(int page);
void renderUnicodeCharacter(wchar_t c);
float unicodeCharWidth(wchar_t c);
bool isUnicodeGlyphChar(wchar_t c);
public:
int width(const wstring& str);
int widthLiteral(const wstring& str); // width without skipping § codes (for chat input)
wstring sanitize(const wstring& str);
void drawWordWrap(const wstring &string, int x, int y, int w, int col, int h); // 4J Added h param
private:
void drawWordWrapInternal(const wstring &string, int x, int y, int w, int col, int h); // 4J Added h param
public:
void drawWordWrap(const wstring &string, int x, int y, int w, int col, bool darken, int h); // 4J Added h param
private:
void drawWordWrapInternal(const wstring& string, int x, int y, int w, int col, bool darken, int h); // 4J Added h param
public:
int wordWrapHeight(const wstring& string, int w);
void setEnforceUnicodeSheet(bool enforceUnicodeSheet);
void setBidirectional(bool bidirectional);
// 4J-PB - check for invalid player name - Japanese local name
bool AllCharactersValid(const wstring &str);
};