Skip to content

Pygame:HangulInputText

Pygame에서 한글 입력에 관련된 내용 정리.

Example

WARNING

claude 답변 결과임

질문 내용은:

  • Pygame-ce 으로 Input Text Box GUI 구성.
  • Python 기본 라이브러리와 Pygame-ce를 제외한 다른 라이브러리 설치 금지.
  • 영문 keyboard 입력으로 한급 입력 구현.
  • 한글 자모 조합 방법 등에 대하여 숙지한 뒤 코드로 구현해야 함.
import pygame
import sys

# Initialize pygame
pygame.init()

# Screen setup
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("한글 입력 (Korean Input)")

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
LIGHT_BLUE = (200, 220, 255)

# Fonts
font = pygame.font.SysFont('nanumgothic', 32)  # Font that supports Korean

# Korean composition related constants
# Initial consonants (초성)
CHOSUNG = ['ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ']
# Vowels (중성)
JUNGSUNG = ['ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ']
# Final consonants (종성) - includes empty consonant
JONGSUNG = [' ', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ']

# English to Korean mapping
ENG_TO_KOR = {
    'q': 'ㅂ', 'w': 'ㅈ', 'e': 'ㄷ', 'r': 'ㄱ', 't': 'ㅅ', 'y': 'ㅛ', 'u': 'ㅕ', 'i': 'ㅑ', 'o': 'ㅐ', 'p': 'ㅔ',
    'a': 'ㅁ', 's': 'ㄴ', 'd': 'ㅇ', 'f': 'ㄹ', 'g': 'ㅎ', 'h': 'ㅗ', 'j': 'ㅓ', 'k': 'ㅏ', 'l': 'ㅣ',
    'z': 'ㅋ', 'x': 'ㅌ', 'c': 'ㅊ', 'v': 'ㅍ', 'b': 'ㅠ', 'n': 'ㅜ', 'm': 'ㅡ',
    # Shifted keys for double consonants and complex vowels
    'Q': 'ㅃ', 'W': 'ㅉ', 'E': 'ㄸ', 'R': 'ㄲ', 'T': 'ㅆ',
    # Other complex consonants and vowels can be added here
}

# Complex vowel compositions
COMPLEX_VOWELS = {
    ('ㅗ', 'ㅏ'): 'ㅘ', ('ㅗ', 'ㅐ'): 'ㅙ', ('ㅗ', 'ㅣ'): 'ㅚ',
    ('ㅜ', 'ㅓ'): 'ㅝ', ('ㅜ', 'ㅔ'): 'ㅞ', ('ㅜ', 'ㅣ'): 'ㅟ',
    ('ㅡ', 'ㅣ'): 'ㅢ'
}

# Complex consonant compositions
COMPLEX_CONSONANTS = {
    ('ㄱ', 'ㅅ'): 'ㄳ', ('ㄴ', 'ㅈ'): 'ㄵ', ('ㄴ', 'ㅎ'): 'ㄶ',
    ('ㄹ', 'ㄱ'): 'ㄺ', ('ㄹ', 'ㅁ'): 'ㄻ', ('ㄹ', 'ㅂ'): 'ㄼ',
    ('ㄹ', 'ㅅ'): 'ㄽ', ('ㄹ', 'ㅌ'): 'ㄾ', ('ㄹ', 'ㅍ'): 'ㄿ',
    ('ㄹ', 'ㅎ'): 'ㅀ', ('ㅂ', 'ㅅ'): 'ㅄ'
}

class KoreanInputHandler:
    def __init__(self):
        self.reset()

    def reset(self):
        self.chosung = None  # Initial consonant
        self.jungsung = None  # Vowel
        self.jongsung = None  # Final consonant

    def is_composing(self):
        return self.chosung is not None

    def get_current_syllable(self):
        if not self.is_composing():
            return ""

        # If we have at least chosung and jungsung, we can compose a syllable
        if self.chosung is not None and self.jungsung is not None:
            chosung_index = CHOSUNG.index(self.chosung)
            jungsung_index = JUNGSUNG.index(self.jungsung)
            jongsung_index = 0  # Default to no final consonant

            if self.jongsung is not None:
                jongsung_index = JONGSUNG.index(self.jongsung)

            # Formula to compose a complete Korean syllable
            # ((Chosung_index * 21) + Jungsung_index) * 28 + Jongsung_index + 0xAC00
            unicode_val = ((chosung_index * 21) + jungsung_index) * 28 + jongsung_index + 0xAC00
            return chr(unicode_val)
        elif self.chosung is not None:
            return self.chosung
        return ""

    def process_char(self, char, text):
        # Check if it's a non-mapped character (like semicolon)
        if char not in ENG_TO_KOR:
            # Complete any composing syllable first
            if self.is_composing():
                composed = self.get_current_syllable()
                self.reset()
                return text + composed + char
            else:
                # Just add the character directly
                return text + char

        kor_char = ENG_TO_KOR[char]

        # Check if the character is a consonant
        if kor_char in CHOSUNG:
            if self.chosung is None:
                # Start a new syllable
                self.chosung = kor_char
                return text
            elif self.jungsung is None:
                # Replace the current initial consonant
                composed = self.get_current_syllable()
                self.reset()
                self.chosung = kor_char
                return text + composed
            elif self.jongsung is None:
                # Add as final consonant
                self.jongsung = kor_char
                return text
            else:
                # Check if we can combine with existing final consonant
                if (self.jongsung, kor_char) in COMPLEX_CONSONANTS:
                    self.jongsung = COMPLEX_CONSONANTS[(self.jongsung, kor_char)]
                    return text
                else:
                    # Complete the current syllable and start a new one
                    composed = self.get_current_syllable()
                    self.reset()
                    self.chosung = kor_char
                    return text + composed

        # Check if the character is a vowel
        elif kor_char in JUNGSUNG:
            if self.chosung is None:
                # Vowel without initial consonant
                return text + kor_char
            elif self.jungsung is None:
                # Add vowel to current syllable
                self.jungsung = kor_char
                return text
            elif self.jongsung is None:
                # Check if we can combine with existing vowel
                if (self.jungsung, kor_char) in COMPLEX_VOWELS:
                    self.jungsung = COMPLEX_VOWELS[(self.jungsung, kor_char)]
                    return text
                else:
                    # Complete the current syllable and start a new one with the consonant
                    composed = self.get_current_syllable()
                    last_char = self.chosung
                    self.reset()
                    return text + composed
            else:
                # Final consonant already exists, check if it should be moved to the next syllable
                next_chosung = self.jongsung
                self.jongsung = None
                composed = self.get_current_syllable()
                self.reset()
                self.chosung = next_chosung
                self.jungsung = kor_char
                return text + composed

        return text

    def delete_char(self, text):
        if self.jongsung is not None:
            self.jongsung = None
            return text
        elif self.jungsung is not None:
            self.jungsung = None
            return text
        elif self.chosung is not None:
            self.reset()
            return text
        elif text:
            # Delete the last complete character
            text = text[:-1]
            return text
        return text

class TextInputBox:
    def __init__(self, x, y, width, height):
        self.rect = pygame.Rect(x, y, width, height)
        self.color = GRAY
        self.text = ""
        self.active = False
        self.korean_handler = KoreanInputHandler()
        self.english_mode = False  # Toggle for English/Korean input

    def handle_event(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            if self.rect.collidepoint(event.pos):
                self.active = True
                self.color = LIGHT_BLUE
            else:
                self.active = False
                self.color = GRAY

        if self.active and event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RETURN:
                print(self.text)  # Process the input text
                self.text = ""
                self.korean_handler.reset()
            elif event.key == pygame.K_BACKSPACE:
                self.text = self.korean_handler.delete_char(self.text)
            elif event.key == pygame.K_SPACE:
                # Complete any composing syllable
                if self.korean_handler.is_composing():
                    composed = self.korean_handler.get_current_syllable()
                    self.korean_handler.reset()
                    self.text += composed
                self.text += " "
            elif event.key == pygame.K_F1:  # Toggle between Korean and English
                self.english_mode = not self.english_mode
                if self.korean_handler.is_composing():
                    composed = self.korean_handler.get_current_syllable()
                    self.korean_handler.reset()
                    self.text += composed
            else:
                # Convert the key pressed to a character
                if event.unicode:
                    if self.english_mode:
                        self.text += event.unicode
                    else:
                        self.text = self.korean_handler.process_char(event.unicode, self.text)

    def update(self):
        # Get width of the text to ensure it stays within the box
        width = max(200, font.size(self.text + self.korean_handler.get_current_syllable())[0] + 10)
        self.rect.w = width

    def draw(self, screen):
        pygame.draw.rect(screen, self.color, self.rect, 2)

        # Combine the committed text and any currently composing syllable
        display_text = self.text + self.korean_handler.get_current_syllable()

        # Render the input box text
        if display_text:
            text_surface = font.render(display_text, True, BLACK)
            screen.blit(text_surface, (self.rect.x + 5, self.rect.y + 5))

        # Draw mode indicator
        mode_text = "한글" if not self.english_mode else "ENG"
        mode_surface = pygame.font.SysFont('Arial', 16).render(mode_text, True, BLACK)
        screen.blit(mode_surface, (self.rect.x, self.rect.y + self.rect.h + 5))

def main():
    clock = pygame.time.Clock()

    # Create a text input box
    input_box = TextInputBox(100, 250, 200, 50)

    # Instructions
    instructions = [
        "Press F1 to toggle between Korean and English input",
        "Type using Korean keyboard layout (QWERTY mapped to 한글)",
        "Press ENTER to submit"
    ]
    instruction_font = pygame.font.SysFont('Arial', 20)

    running = True
    while running:
        screen.fill(WHITE)

        # Draw instructions
        for i, text in enumerate(instructions):
            instr_surface = instruction_font.render(text, True, BLACK)
            screen.blit(instr_surface, (100, 100 + i * 30))

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

            input_box.handle_event(event)

        input_box.update()
        input_box.draw(screen)

        pygame.display.flip()
        clock.tick(30)

    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()

See also

Favorite site