refactor: shared Unicode display-width utility with grapheme cluster support

Consolidate two inconsistent codepoint_width implementations from
Screen.cpp and EventEditor.cpp into nocal/tui/unicode.hpp. The unified
table covers all CJK, Hangul, emoji, symbol, and combining mark ranges.
display_width and fit_text handle grapheme clusters (ZWJ sequences,
skin-tone/hair modifiers, regional indicator pairs, variation selectors)
so calendar text with emoji or East Asian characters renders correctly.
This commit is contained in:
2026-07-23 18:03:33 +01:00
parent 673a2c66c9
commit a159933baa
6 changed files with 571 additions and 182 deletions

View File

@@ -0,0 +1,35 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
namespace nocal::tui {
struct DecodedCodepoint {
std::uint32_t value;
std::size_t length;
};
// Decode one UTF-8 codepoint at position `at` in `text`.
// Returns {0xfffd, 1} on error. Caller must ensure at < text.size().
[[nodiscard]] DecodedCodepoint decode_codepoint(std::string_view text, std::size_t at) noexcept;
// Terminal display width of a single codepoint: 0 (zero-width/combining), 1, or 2.
// Uses Unicode East Asian Width property with complete range tables.
[[nodiscard]] int codepoint_width(std::uint32_t value) noexcept;
// Display width of a string. Handles grapheme clusters:
// - Emoji ZWJ sequences → width 2
// - Emoji + skin tone / hair modifiers → width 2
// - Regional indicator pairs (flags) → width 2
// - Combining marks → absorbed by base
[[nodiscard]] int display_width(std::string_view text);
// Truncate to `width` display cells. If `ellipsis` is true and the text is
// truncated, appends U+2026. Right-pads with spaces to reach `width`.
// Never breaks a grapheme cluster.
[[nodiscard]] std::string fit_text(std::string_view text, int width, bool ellipsis = true);
} // namespace nocal::tui