Unicode improvements and version bump.

- Added full UTF-8 support for terminal rendering, including multi-width character handling.
- Improved font handling in ImGui with expanded glyph support (Greek, Mathematical Operators).
- Updated locale initialization to enable proper character rendering.
- Bumped version to 1.5.8.
This commit is contained in:
2026-01-11 11:39:08 -08:00
parent 7347556aa2
commit a8abda4b87
5 changed files with 178 additions and 67 deletions

View File

@@ -4,7 +4,7 @@ project(kte)
include(GNUInstallDirs) include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(KTE_VERSION "1.5.7") set(KTE_VERSION "1.5.8")
# Default to terminal-only build to avoid SDL/OpenGL dependency by default. # Default to terminal-only build to avoid SDL/OpenGL dependency by default.
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available. # Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.

View File

@@ -357,14 +357,43 @@ GUIFrontend::LoadGuiFont_(const char * /*path*/, const float size_px)
{ {
const ImGuiIO &io = ImGui::GetIO(); const ImGuiIO &io = ImGui::GetIO();
io.Fonts->Clear(); io.Fonts->Clear();
const ImFont *font = io.Fonts->AddFontFromMemoryCompressedTTF(
ImFontConfig config;
config.MergeMode = false;
// Load Basic Latin + Latin Supplement
io.Fonts->AddFontFromMemoryCompressedTTF(
kte::Fonts::DefaultFontData, kte::Fonts::DefaultFontData,
kte::Fonts::DefaultFontSize, kte::Fonts::DefaultFontSize,
size_px); size_px,
if (!font) { &config,
font = io.Fonts->AddFontDefault(); io.Fonts->GetGlyphRangesDefault());
}
(void) font; // Merge Greek and Coptic
config.MergeMode = true;
static const ImWchar greek_ranges[] = {
0x0370, 0x03FF, // Greek and Coptic
0,
};
io.Fonts->AddFontFromMemoryCompressedTTF(
kte::Fonts::DefaultFontData,
kte::Fonts::DefaultFontSize,
size_px,
&config,
greek_ranges);
// Merge Mathematical Operators
static const ImWchar math_ranges[] = {
0x2200, 0x22FF, // Mathematical Operators
0,
};
io.Fonts->AddFontFromMemoryCompressedTTF(
kte::Fonts::DefaultFontData,
kte::Fonts::DefaultFontSize,
size_px,
&config,
math_ranges);
io.Fonts->Build(); io.Fonts->Build();
return true; return true;
} }

View File

@@ -1,3 +1,6 @@
#include <clocale>
#define _XOPEN_SOURCE_EXTENDED 1
#include <cwchar>
#include <algorithm> #include <algorithm>
#include <cstdio> #include <cstdio>
#include <filesystem> #include <filesystem>
@@ -177,15 +180,32 @@ TerminalRenderer::Draw(Editor &ed)
} }
}; };
while (written < cols) { while (written < cols) {
char ch = ' ';
bool from_src = false; bool from_src = false;
wchar_t wch = L' ';
int wch_len = 1;
int disp_w = 1;
if (src_i < line.size()) { if (src_i < line.size()) {
unsigned char c = static_cast<unsigned char>(line[src_i]); // Decode UTF-8
if (c == '\t') { std::mbstate_t state = std::mbstate_t();
size_t res = std::mbrtowc(
&wch, &line[src_i], line.size() - src_i, &state);
if (res == (size_t) -1 || res == (size_t) -2) {
// Invalid or incomplete; treat as single byte
wch = static_cast<unsigned char>(line[src_i]);
wch_len = 1;
} else if (res == 0) {
wch = L'\0';
wch_len = 1;
} else {
wch_len = static_cast<int>(res);
}
if (wch == L'\t') {
std::size_t next_tab = tabw - (render_col % tabw); std::size_t next_tab = tabw - (render_col % tabw);
if (render_col + next_tab <= coloffs) { if (render_col + next_tab <= coloffs) {
render_col += next_tab; render_col += next_tab;
++src_i; src_i += wch_len;
continue; continue;
} }
// Emit spaces for tab // Emit spaces for tab
@@ -233,23 +253,34 @@ TerminalRenderer::Draw(Editor &ed)
++render_col; ++render_col;
--next_tab; --next_tab;
} }
++src_i; src_i += wch_len;
continue; continue;
} else { } else {
// normal char // normal char
disp_w = wcwidth(wch);
if (disp_w < 0)
disp_w = 1; // non-printable or similar
if (render_col < coloffs) { if (render_col < coloffs) {
++render_col; render_col += disp_w;
++src_i; src_i += wch_len;
continue; continue;
} }
ch = static_cast<char>(c);
from_src = true; from_src = true;
} }
} else { } else {
// beyond EOL, fill spaces // beyond EOL, fill spaces
ch = ' '; wch = L' ';
wch_len = 1;
disp_w = 1;
from_src = false; from_src = false;
} }
if (written + disp_w > cols) {
// would overflow, just break
break;
}
bool in_hl = search_mode && from_src && is_src_in_hl(src_i); bool in_hl = search_mode && from_src && is_src_in_hl(src_i);
bool in_cur = bool in_cur =
has_current && li == cur_my && from_src && src_i >= cur_mx && src_i < has_current && li == cur_my && from_src && src_i >= cur_mx && src_i <
@@ -273,11 +304,20 @@ TerminalRenderer::Draw(Editor &ed)
if (!in_hl && from_src) { if (!in_hl && from_src) {
apply_token_attr(token_at(src_i)); apply_token_attr(token_at(src_i));
} }
addch(static_cast<unsigned char>(ch));
++written; if (from_src) {
++render_col; cchar_t cch;
wchar_t warr[2] = {wch, L'\0'};
setcchar(&cch, warr, A_NORMAL, 0, nullptr);
add_wch(&cch);
} else {
addch(' ');
}
written += disp_w;
render_col += disp_w;
if (from_src) if (from_src)
++src_i; src_i += wch_len;
if (src_i >= line.size() && written >= cols) if (src_i >= line.size() && written >= cols)
break; break;
} }
@@ -306,14 +346,26 @@ TerminalRenderer::Draw(Editor &ed)
std::size_t src_i_cur = 0; std::size_t src_i_cur = 0;
std::size_t render_col_cur = 0; std::size_t render_col_cur = 0;
while (src_i_cur < line_for_cursor.size() && src_i_cur < cx) { while (src_i_cur < line_for_cursor.size() && src_i_cur < cx) {
unsigned char ccur = static_cast<unsigned char>(line_for_cursor[src_i_cur]); std::mbstate_t state = std::mbstate_t();
if (ccur == '\t') { wchar_t wch;
size_t res = std::mbrtowc(
&wch, &line_for_cursor[src_i_cur], line_for_cursor.size() - src_i_cur,
&state);
if (res == (size_t) -1 || res == (size_t) -2) {
render_col_cur += 1;
src_i_cur += 1;
} else if (res == 0) {
src_i_cur += 1;
} else {
if (wch == L'\t') {
std::size_t next_tab = tabw - (render_col_cur % tabw); std::size_t next_tab = tabw - (render_col_cur % tabw);
render_col_cur += next_tab; render_col_cur += next_tab;
++src_i_cur;
} else { } else {
++render_col_cur; int dw = wcwidth(wch);
++src_i_cur; render_col_cur += (dw < 0) ? 1 : dw;
}
src_i_cur += res;
} }
} }
rx_recomputed = render_col_cur; rx_recomputed = render_col_cur;

View File

@@ -8,16 +8,43 @@ Font::Load(const float size) const
{ {
const ImGuiIO &io = ImGui::GetIO(); const ImGuiIO &io = ImGui::GetIO();
io.Fonts->Clear(); io.Fonts->Clear();
const ImFont *font = io.Fonts->AddFontFromMemoryCompressedTTF(
ImFontConfig config;
config.MergeMode = false;
// Load Basic Latin + Latin Supplement
io.Fonts->AddFontFromMemoryCompressedTTF(
this->data_, this->data_,
this->size_, this->size_,
size); size,
&config,
io.Fonts->GetGlyphRangesDefault());
if (!font) { // Merge Greek and Coptic
font = io.Fonts->AddFontDefault(); config.MergeMode = true;
} static const ImWchar greek_ranges[] = {
0x0370, 0x03FF, // Greek and Coptic
0,
};
io.Fonts->AddFontFromMemoryCompressedTTF(
this->data_,
this->size_,
size,
&config,
greek_ranges);
// Merge Mathematical Operators
static const ImWchar math_ranges[] = {
0x2200, 0x22FF, // Mathematical Operators
0,
};
io.Fonts->AddFontFromMemoryCompressedTTF(
this->data_,
this->size_,
size,
&config,
math_ranges);
(void) font;
io.Fonts->Build(); io.Fonts->Build();
} }
} // namespace kte::Fonts } // namespace kte::Fonts

View File

@@ -1,3 +1,4 @@
#include <clocale>
#include <cctype> #include <cctype>
#include <cerrno> #include <cerrno>
#include <cstdio> #include <cstdio>
@@ -113,6 +114,8 @@ RunStressHighlighter(unsigned seconds)
int int
main(int argc, const char *argv[]) main(int argc, const char *argv[])
{ {
std::setlocale(LC_ALL, "");
Editor editor; Editor editor;
// CLI parsing using getopt_long // CLI parsing using getopt_long