From 68b8d1518163d09f463ea5d784aba2344f9cf535 Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Wed, 18 Mar 2026 21:36:46 -0700 Subject: [PATCH] Add emoji normalization utility and integrate into message rendering --- contact/ui/contact_ui.py | 3 +- contact/utilities/emoji_utils.py | 54 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 contact/utilities/emoji_utils.py diff --git a/contact/ui/contact_ui.py b/contact/ui/contact_ui.py index bdf10dd..f4d8a3a 100644 --- a/contact/ui/contact_ui.py +++ b/contact/ui/contact_ui.py @@ -12,6 +12,7 @@ from contact.ui.colors import get_color from contact.utilities.db_handler import get_name_from_database, update_node_info_in_db, is_chat_archived from contact.utilities.input_handlers import get_list_input from contact.utilities.i18n import t +from contact.utilities.emoji_utils import normalize_message_text import contact.ui.default_config as config import contact.ui.dialog from contact.ui.nav_utils import move_main_highlight, draw_main_arrows, get_msg_window_lines, wrap_text @@ -865,7 +866,7 @@ def draw_messages_window(scroll_to_bottom: bool = False) -> None: row = 0 for prefix, message in messages: - full_message = f"{prefix}{message}" + full_message = normalize_message_text(f"{prefix}{message}") wrapped_lines = wrap_text(full_message, messages_win.getmaxyx()[1] - 2) msg_line_count += len(wrapped_lines) messages_pad.resize(msg_line_count, messages_win.getmaxyx()[1]) diff --git a/contact/utilities/emoji_utils.py b/contact/utilities/emoji_utils.py new file mode 100644 index 0000000..84e7ce0 --- /dev/null +++ b/contact/utilities/emoji_utils.py @@ -0,0 +1,54 @@ +"""Helpers for normalizing emoji sequences in width-sensitive message rendering.""" + +# Strip zero-width and presentation modifiers that make terminal cell width inconsistent. +EMOJI_MODIFIER_REPLACEMENTS = { + "\u200d": "", + "\u20e3": "", + "\ufe0e": "", + "\ufe0f": "", + "\U0001F3FB": "", + "\U0001F3FC": "", + "\U0001F3FD": "", + "\U0001F3FE": "", + "\U0001F3FF": "", +} + +_EMOJI_MODIFIER_TRANSLATION = str.maketrans(EMOJI_MODIFIER_REPLACEMENTS) +_REGIONAL_INDICATOR_START = ord("\U0001F1E6") +_REGIONAL_INDICATOR_END = ord("\U0001F1FF") + + +def _regional_indicator_to_letter(char: str) -> str: + return chr(ord("A") + ord(char) - _REGIONAL_INDICATOR_START) + + +def _normalize_flag_emoji(text: str) -> str: + """Convert flag emoji built from regional indicators into ASCII country codes.""" + normalized = [] + index = 0 + + while index < len(text): + current = text[index] + current_ord = ord(current) + + if _REGIONAL_INDICATOR_START <= current_ord <= _REGIONAL_INDICATOR_END and index + 1 < len(text): + next_char = text[index + 1] + next_ord = ord(next_char) + if _REGIONAL_INDICATOR_START <= next_ord <= _REGIONAL_INDICATOR_END: + normalized.append(_regional_indicator_to_letter(current)) + normalized.append(_regional_indicator_to_letter(next_char)) + index += 2 + continue + + normalized.append(current) + index += 1 + + return "".join(normalized) + + +def normalize_message_text(text: str) -> str: + """Strip modifiers and rewrite flag emoji into stable terminal-friendly text.""" + if not text: + return text + + return _normalize_flag_emoji(text.translate(_EMOJI_MODIFIER_TRANSLATION))