Skip to content

Commit

Permalink
Merge pull request #72579 from katemonster33/imgui_cjk_fix
Browse files Browse the repository at this point in the history
Implemented drawing of CJK characters with ImGui screens
  • Loading branch information
Maleclypse authored Mar 29, 2024
2 parents 7b13a60 + 84b2a8d commit cd3792c
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 43 deletions.
69 changes: 64 additions & 5 deletions src/cata_imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,41 @@ RGBTuple color_loader<RGBTuple>::from_rgb( const int r, const int g, const int b
}
#else
#include "sdl_utils.h"
#include "sdl_font.h"
#include "font_loader.h"
#include "wcwidth.h"
#include <imgui/imgui_impl_sdl2.h>
#include <imgui/imgui_impl_sdlrenderer2.h>

SDL_Renderer *cataimgui::client::sdl_renderer = nullptr;
SDL_Window *cataimgui::client::sdl_window = nullptr;
struct CataImFont : public ImFont {
std::unordered_map<ImU32, unsigned char> sdlColorsToCata;
const cataimgui::client &imclient;
const std::unique_ptr<Font> &cata_font;
CataImFont( const cataimgui::client &imclient, const std::unique_ptr<Font> &cata_font ) :
imclient( imclient ), cata_font( cata_font ) {
}

cataimgui::client::client()
// this function QUEUES a character to be drawn
bool CanRenderFallbackChar( const char *s_begin, const char *s_end ) const override {
return s_begin != nullptr && s_end != nullptr;
}

int GetFallbackCharWidth( const char *s_begin, const char *s_end,
const float scale ) const override {
return cata_font->width * utf8_width( std::string( s_begin, s_end ) ) * int( scale );
}

int GetFallbackCharWidth( ImWchar c, const float scale ) const override {
return cata_font->width * mk_wcwidth( c ) * scale;
}
};
static CataImFont *activeFont;

cataimgui::client::client( const SDL_Renderer_Ptr &sdl_renderer, const SDL_Window_Ptr &sdl_window,
const GeometryRenderer_Ptr &sdl_geometry ) :
sdl_renderer( sdl_renderer ),
sdl_window( sdl_window ),
sdl_geometry( sdl_geometry )
{
IMGUI_CHECKVERSION();
ImGui::CreateContext();
Expand All @@ -177,8 +205,39 @@ cataimgui::client::client()

// Setup Dear ImGui style
ImGui::StyleColorsDark();
ImGui_ImplSDL2_InitForSDLRenderer( sdl_window, sdl_renderer );
ImGui_ImplSDLRenderer2_Init( sdl_renderer );
ImGui_ImplSDL2_InitForSDLRenderer( sdl_window.get(), sdl_renderer.get() );
ImGui_ImplSDLRenderer2_Init( sdl_renderer.get() );
}

void cataimgui::client::load_fonts( const std::unique_ptr<Font> &cata_font,
const std::array<SDL_Color, color_loader<SDL_Color>::COLOR_NAMES_COUNT> &windowsPalette )
{
ImGuiIO &io = ImGui::GetIO();
if( ImGui::GetIO().FontDefault == nullptr ) {
std::vector<std::string> typefaces;
ensure_unifont_loaded( typefaces );

ImFontConfig cfg;
cfg.DstFont = activeFont = new CataImFont( *this, cata_font );
for( size_t index = 0; index < color_loader<SDL_Color>::COLOR_NAMES_COUNT; index++ ) {
SDL_Color sdlCol = windowsPalette[index];
ImU32 rgb = sdlCol.b << 16 | sdlCol.g << 8 | sdlCol.r;
activeFont->sdlColorsToCata[rgb] = index;
}
io.FontDefault = io.Fonts->AddFontFromFileTTF( typefaces[0].c_str(), cata_font->height, &cfg,
io.Fonts->GetGlyphRangesDefault() );
io.Fonts->Fonts[0] = cfg.DstFont;
ImGui_ImplSDLRenderer2_SetFallbackGlyphDrawCallback( [&]( const ImFontGlyphToDraw & glyph ) {
std::string uni_string = std::string( glyph.uni_str );
point p( int( glyph.pos.x ), int( glyph.pos.y - 5 ) );
unsigned char col = 0;
auto it = activeFont->sdlColorsToCata.find( glyph.col & 0xFFFFFF );
if( it != activeFont->sdlColorsToCata.end() ) {
col = it->second;
}
cata_font->OutputChar( sdl_renderer, sdl_geometry, glyph.uni_str, p, col );
} );
}
}

cataimgui::client::~client()
Expand Down
18 changes: 14 additions & 4 deletions src/cata_imgui.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ struct input_event;
struct item_info_data;

#if defined(WIN32) || defined(TILES)
struct SDL_Renderer;
struct SDL_Window;
#include "sdl_geometry.h"
#include "sdl_wrappers.h"
#include "color_loader.h"
#endif

struct point;
class ImVec2;
class Font;

namespace cataimgui
{
Expand Down Expand Up @@ -44,7 +46,14 @@ enum class dialog_result {
class client
{
public:
#if !(defined(TILES) || defined(WIN32))
client();
#else
client( const SDL_Renderer_Ptr &sdl_renderer, const SDL_Window_Ptr &sdl_window,
const GeometryRenderer_Ptr &sdl_geometry );
void load_fonts( const std::unique_ptr<Font> &cata_font,
const std::array<SDL_Color, color_loader<SDL_Color>::COLOR_NAMES_COUNT> &windowsPalette );
#endif
~client();

void new_frame();
Expand All @@ -55,8 +64,9 @@ class client
void upload_color_pair( int p, int f, int b );
void set_alloced_pair_count( short count );
#else
static struct SDL_Renderer *sdl_renderer;
static struct SDL_Window *sdl_window;
const SDL_Renderer_Ptr &sdl_renderer;
const SDL_Window_Ptr &sdl_window;
const GeometryRenderer_Ptr &sdl_geometry;
#endif
};

Expand Down
30 changes: 7 additions & 23 deletions src/sdl_font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

#include "font_loader.h"
#include "output.h"
#include "imgui/imgui.h"
#include "sdl_utils.h"
#include "ui_manager.h"

#if defined(_WIN32)
# if 1 // HACK: Hack to prevent reordering of #include "platform_win.h" by IWYU
Expand Down Expand Up @@ -299,26 +299,11 @@ CachedTTFFont::CachedTTFFont(
throw std::runtime_error( TTF_GetError() );
}
TTF_SetFontStyle( font.get(), TTF_STYLE_NORMAL );

ImGuiIO &io = ImGui::GetIO();
if( io.FontDefault == nullptr && typeface.find( "unifont" ) != std::string::npos ) {
static const std::array<ImWchar, 17> ranges = {
0x0020, 0x052F,
0x1D00, 0x1DFF,
0x2000, 0x20CF,
0x2100, 0x214F,
0x2190, 0x22FF,
0x2500, 0x27BF,
0xC900, 0xC9FF,
//0x0020, 0xCFFF,
0
};
io.FontDefault = io.Fonts->AddFontFromFileTTF( typeface.c_str(), fontsize, nullptr, ranges.data() );
}
}

SDL_Texture_Ptr CachedTTFFont::create_glyph( const SDL_Renderer_Ptr &renderer,
const std::string &ch,
int &ch_width,
const int color )
{
const auto function = fontblending ? TTF_RenderUTF8_Blended : TTF_RenderUTF8_Solid;
Expand All @@ -328,11 +313,12 @@ SDL_Texture_Ptr CachedTTFFont::create_glyph( const SDL_Renderer_Ptr &renderer,
return nullptr;
}
const int wf = utf8_width( ch );
ch_width = width * wf;
// Note: bits per pixel must be 8 to be synchronized with the surface
// that TTF_RenderGlyph above returns. This is important for SDL_BlitScaled
SDL_Surface_Ptr surface = create_surface_32( width * wf, height );
SDL_Surface_Ptr surface = create_surface_32( ch_width, height );
SDL_Rect src_rect = { 0, 0, sglyph->w, sglyph->h };
SDL_Rect dst_rect = { 0, 0, width * wf, height };
SDL_Rect dst_rect = { 0, 0, ch_width, height };
if( src_rect.w < dst_rect.w ) {
dst_rect.x = ( dst_rect.w - src_rect.w ) / 2;
dst_rect.w = src_rect.w;
Expand Down Expand Up @@ -380,10 +366,8 @@ void CachedTTFFont::OutputChar( const SDL_Renderer_Ptr &renderer, const Geometry

auto it = glyph_cache_map.find( key );
if( it == std::end( glyph_cache_map ) ) {
cached_t new_entry {
create_glyph( renderer, key.codepoints, key.color ),
static_cast<int>( width * utf8_width( key.codepoints ) )
};
cached_t new_entry;
new_entry.texture = create_glyph( renderer, key.codepoints, new_entry.width, key.color );
it = glyph_cache_map.insert( std::make_pair( std::move( key ), std::move( new_entry ) ) ).first;
}
const cached_t &value = it->second;
Expand Down
3 changes: 2 additions & 1 deletion src/sdl_font.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ class CachedTTFFont : public Font
unsigned char color, float opacity = 1.0f ) override;

protected:
SDL_Texture_Ptr create_glyph( const SDL_Renderer_Ptr &renderer, const std::string &ch, int color );
SDL_Texture_Ptr create_glyph( const SDL_Renderer_Ptr &renderer, const std::string &ch,
int &ch_width, int color );

TTF_Font_Ptr font;
// Maps (character code, color) to SDL_Texture*
Expand Down
9 changes: 2 additions & 7 deletions src/sdltiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,12 +425,7 @@ static void WinCreate()
geometry = std::make_unique<DefaultGeometryRenderer>();
}

cataimgui::client::sdl_renderer = renderer.get();
cataimgui::client::sdl_window = window.get();
imclient = std::make_unique<cataimgui::client>();

//io.Fonts->AddFontDefault();
//io.Fonts->Build();
imclient = std::make_unique<cataimgui::client>( renderer, window, geometry );
}

static void WinDestroy()
Expand Down Expand Up @@ -3688,7 +3683,7 @@ void catacurses::init_interface()
windowsPalette, fl.overmap_typeface, fl.overmap_fontsize, fl.fontblending );
stdscr = newwin( get_terminal_height(), get_terminal_width(), point_zero );
//newwin calls `new WINDOW`, and that will throw, but not return nullptr.

imclient->load_fonts( font, windowsPalette );
#if defined(__ANDROID__)
// Make sure we initialize preview_terminal_width/height to sensible values
preview_terminal_width = TERMINAL_WIDTH * fontwidth;
Expand Down
16 changes: 14 additions & 2 deletions src/third-party/imgui/imgui.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ struct ImFontAtlas; // Runtime data for multiple fonts, bake mul
struct ImFontBuilderIO; // Opaque interface to a font builder (stb_truetype or FreeType).
struct ImFontConfig; // Configuration data when adding a font or merging fonts
struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset)
struct ImFontGlyphToDraw;
struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data
struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using)
struct ImGuiContext; // Dear ImGui context (opaque structure, unless including imgui_internal.h)
Expand Down Expand Up @@ -2569,6 +2570,7 @@ struct ImDrawList
ImVector<ImDrawCmd> CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback.
ImVector<ImDrawIdx> IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those
ImVector<ImDrawVert> VtxBuffer; // Vertex buffer.
ImVector<ImFontGlyphToDraw> FallbackGlyphs;
ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive.

// [Internal, used while building lists]
Expand Down Expand Up @@ -2751,6 +2753,13 @@ struct ImFontGlyph
float U0, V0, U1, V1; // Texture coordinates
};

struct ImFontGlyphToDraw
{
char uni_str[7];
ImVec2 pos;
ImU32 col;
};

// Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges().
// This is essentially a tightly packed of vector of 64k booleans = 8KB storage.
struct ImFontGlyphRangesBuilder
Expand Down Expand Up @@ -2939,17 +2948,20 @@ struct ImFont

// Methods
IMGUI_API ImFont();
IMGUI_API ~ImFont();
virtual IMGUI_API ~ImFont();
IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const;
IMGUI_API const ImFontGlyph*FindGlyphNoFallback(ImWchar c) const;
float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; }
float GetCharAdvance(ImWchar c) const;
bool IsLoaded() const { return ContainerAtlas != NULL; }
const char* GetDebugName() const { return ConfigData ? ConfigData->Name : "<unknown>"; }

// 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable.
// 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable.
IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8
IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const;
virtual bool CanRenderFallbackChar(const char *s_begin, const char *s_end) const;
virtual int GetFallbackCharWidth( const char* s_begin, const char* s_end, const float scale ) const;
virtual int GetFallbackCharWidth(ImWchar c, const float scale) const;
IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) const;
IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const;

Expand Down
55 changes: 54 additions & 1 deletion src/third-party/imgui/imgui_draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ void ImDrawList::_ResetForNewFrame()
CmdBuffer.resize(0);
IdxBuffer.resize(0);
VtxBuffer.resize(0);
FallbackGlyphs.resize(0);
Flags = _Data->InitialFlags;
memset(&_CmdHeader, 0, sizeof(_CmdHeader));
_VtxCurrentIdx = 0;
Expand Down Expand Up @@ -3341,6 +3342,18 @@ const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const
return &Glyphs.Data[i];
}

float ImFont::GetCharAdvance(ImWchar c) const
{
if((int)c < IndexAdvanceX.Size)
{
return IndexAdvanceX[(int)c];
}
else
{
return GetFallbackCharWidth(c, 1.f);
}
}

const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const
{
if (c >= (size_t)IndexLookup.Size)
Expand Down Expand Up @@ -3518,7 +3531,12 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons
continue;
}

const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX) * scale;
float char_width = 0.f;
if( (int)c < IndexAdvanceX.Size ) {
char_width = IndexAdvanceX.Data[c] * scale;
} else {
char_width = GetFallbackCharWidth( prev_s, s, scale );
}
if (line_width + char_width >= max_width)
{
s = prev_s;
Expand Down Expand Up @@ -3555,6 +3573,21 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im
draw_list->PrimRectUV(ImVec2(x + glyph->X0 * scale, y + glyph->Y0 * scale), ImVec2(x + glyph->X1 * scale, y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col);
}

bool ImFont::CanRenderFallbackChar(const char *s_begin, const char *s_end) const
{
return false;
}

int ImFont::GetFallbackCharWidth( const char* s_begin, const char* s_end, const float scale ) const
{
return FallbackAdvanceX * scale;
}

int ImFont::GetFallbackCharWidth(ImWchar c, const float scale) const
{
return FallbackAdvanceX * scale;
}

// Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound.
void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const
{
Expand Down Expand Up @@ -3624,6 +3657,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im

while (s < text_end)
{
const char* s_orig = s;
if (word_wrap_enabled)
{
// Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.
Expand Down Expand Up @@ -3666,6 +3700,10 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
continue;

float char_width = glyph->AdvanceX * scale;
if( glyph == FallbackGlyph ) {
char_width = GetFallbackCharWidth( s_orig, s, scale );
}

if (glyph->Visible)
{
// We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w
Expand Down Expand Up @@ -3725,6 +3763,21 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
// Support for untinted glyphs
ImU32 glyph_col = glyph->Colored ? col_untinted : col;

if(glyph == FallbackGlyph)
{
if(CanRenderFallbackChar(s_orig, s))
{
ImFontGlyphToDraw glyphToDraw;
size_t len = s - s_orig;
memcpy(glyphToDraw.uni_str, s_orig, len);
glyphToDraw.uni_str[len] = 0;
glyphToDraw.pos = { x1, y1 };
glyphToDraw.col = glyph_col;
draw_list->FallbackGlyphs.push_back(glyphToDraw);
}
glyph = NULL;
}
else
// We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here:
{
vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = glyph_col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1;
Expand Down
Loading

0 comments on commit cd3792c

Please sign in to comment.