본문 바로가기
대동단결 Python

pygame에서 한글입력 컴포넌트 만들기

by 즐거운 지니 2022. 9. 8.
반응형

pygame 실행환경에서는 한글을 자유롭게 출력할 수 있다. 그러나 한글을 입력하기는 어렵다. 물론 소스코드 내부에서 한글을 자유롭게 사용할 수 있지만, pygame실행중 직접 한글입력 구현은 어렵다. 그 이유는 다음과 같이 생각해 볼 수 있다.

  • python(적어도 pygame에서) 에서 한/영키를 인지 할 수 없다.
  • python에서 한글 텍스트의 입력은 외부 시스템(예를들어 윈도우즈 같은 OS system)에 의존한다.

즉, python에서 한글을 입력할 때는 OS에서 제공하는 오토마타 부터 유니코드를 받아올 뿐이고 그것을 제어하는 어떤 함수가 없다. 그렇다고 해서 일반적인 파이썬 애플리케이션에서 한글을 입력하는것이 문제되지는 않다. 다만 pygame 내부에서 한글을 입력받기 위해서는 여러 키로부터 한글을 합성하는 오토마타를 직접 제작해야 한다. 이 글은 그 고민을 해결하기 위한 기록이며 그 방법을 공유하고자 한다.

한글입력되는 pygame

한글 오토마타

한글 자모합성 기능은 https://mizykk.tistory.com/115 에서 찾았다. 이 글에서는 OS에서 한글입력이 지원되지 않은 경우 영자텍스트를 한글텍스트로 변환하는 방법을 제공한다. 그 방법에 관해서는 해당 사이트를 참조바란다. 나의 코드 역시 해당 사이트를 참조하였으며 그로부터 hangulInputBox.py를 만들어 pygame에서 한글을 입력할 수 있게 되었다.

hangulInputBox

hangulInputBox.py는 pygame에서 한글을 입력하는 박스 콤포넌트를 제공한다. 이 모듈의 HangulInputBox() 클래스가 그렇다. 이를 이용해 파이게임에서 한글을 입력해 보는 예제파일을 먼저 제시해 보겠다. 아래의 스크립트 main.py 을 보라.

main.py

import pygame
from hangulInputBox import *

SIZE = width, height = 640, 240
screen = pygame.display.set_mode(SIZE)
screen.fill('blue')
pygame.init()
box = HangulInputBox('a전자시계.ttf', 16, 20,'white', 'blue')
box.rect.center = (640/2, 240/2)
running=True
while running:
    keyEvent = None
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running=False
        if event.type == pygame.KEYDOWN:
            keyEvent = event
        if event.type == pygame.USEREVENT:
            if event.name == 'enterEvent': print(event.text)
    screen.fill('black')
    box.update(keyEvent)
    screen.blit(box.image, box.rect)
    pygame.display.update()

pygame.quit()
  • from hangulInputBox import * : hangulInputBox 임포트 한다.

  • box = HangulInputBox('a전자시계.ttf', 16, 20,'white', 'blue') : 한글입력 박스를 만든다. 각각의 파라미터는 순서대로,

    • 한글폰트 파일
    • 폰트 크기
    • 박스 가로 사이즈 (단위는 글자 갯수)
    • 폰트 컬러
    • 배경 컬러
  • box.rect.center = (640/2, 240/2) : 박스를 화면 가운데 배치

  • if event.type == pygame.KEYDOWN:
        keyEvent = event

    키가 눌려지면 keyEvent에 그 정보(event)를 저장한다.

  • if event.type == pygame.USEREVENT:
        if event.name == 'enterEvent': print(event.text)

    엔터키를 눌러 글입력을 마치면 완성된 택스트를 출력한다.

  • box.update(keyEvent) : 키보드 입력 과정을 실시간으로 업데이트 한다.

  • screen.blit(box.image, box.rect) : 스트린에 박스를 블릿한다. (파이게임 스크린에 촐력한다.

hangulInputBox.py

스크립트 파일은 다음과 같다.

import pygame
import time
from hangul_utils import join_jamos

class HangulInputBox(pygame.sprite.Sprite):
    '''
    pygame 내부에 사용되는 비쥬얼 컴포넌트로
    한글을 입력할 수 있는 박스이다.
    한글/영문 전환 토클키는 Left-Shift + Space 이다.
    한글 모드에서는 굵은 사각형 커서가 나타나고
    영문 모드에서는 가는 사각형 커서가 보인다.
    '''
    # 자음-초성/종성
    cons = {'r': 'ㄱ', 'R': 'ㄲ', 's': 'ㄴ', 'e': 'ㄷ', 'E': 'ㄸ', 'f': 'ㄹ', 'a': 'ㅁ', 'q': 'ㅂ', 'Q': 'ㅃ', 't': 'ㅅ',
            'T': 'ㅆ',
            'd': 'ㅇ', 'w': 'ㅈ', 'W': 'ㅉ', 'c': 'ㅊ', 'z': 'ㅋ', 'x': 'ㅌ', 'v': 'ㅍ', 'g': 'ㅎ'}
    # 모음-중성
    vowels = {'k': 'ㅏ', 'o': 'ㅐ', 'i': 'ㅑ', 'O': 'ㅒ', 'j': 'ㅓ', 'p': 'ㅔ', 'u': 'ㅕ', 'P': 'ㅖ', 'h': 'ㅗ', 'hk': 'ㅘ',
              'ho': 'ㅙ', 'hl': 'ㅚ',
              'y': 'ㅛ', 'n': 'ㅜ', 'nj': 'ㅝ', 'np': 'ㅞ', 'nl': 'ㅟ', 'b': 'ㅠ', 'm': 'ㅡ', 'ml': 'ㅢ', 'l': 'ㅣ'}
    # 자음-종성
    cons_double = {'rt': 'ㄳ', 'sw': 'ㄵ', 'sg': 'ㄶ', 'fr': 'ㄺ', 'fa': 'ㄻ', 'fq': 'ㄼ', 'ft': 'ㄽ', 'fx': 'ㄾ', 'fv': 'ㄿ',
                   'fg': 'ㅀ', 'qt': 'ㅄ'}

    def __init__(self, font:str, fontSize:int, width:int, fColor:str, bColor:str):
        pygame.sprite.Sprite.__init__(self)
        self.font = pygame.font.Font(font, fontSize)
        self.fontSize:int = fontSize
        self.width = width * self.fontSize
        self.height = fontSize
        self.fColor = pygame.Color(fColor)
        self.bColor = pygame.Color(bColor)

        self.image = pygame.Surface([self.width, self.height])
        self.rect = self.image.get_rect()

        self.text = '첫 글자'
        self.textImage = self.font.render(self.text, True, self.fColor)
        self.textRect = self.textImage.get_rect()
        self.hanText = ''
        self.hanMode = False

        self.cursor = pygame.Rect(self.textRect.topright, (3, self.fontSize))

        pygame.key.set_repeat(500, 50)

        self.enterEvent = pygame.event.Event(pygame.USEREVENT, {'name':'enterEvent', 'text':''})

    def update(self, event) -> None:
        if event == None: event = pygame.event.Event(pygame.USEREVENT,{})
        if event.type == pygame.KEYDOWN:  # 키다운 이벤트라면, key, mod, ucicode, scancode 속성을 가진다.
            if event.key == pygame.K_RETURN or event.key == pygame.K_CARET:
                text2 = self.text + HangulInputBox.engkor(self.hanText)
                self.enterEvent.text = text2
                pygame.event.post(self.enterEvent)
                self.text = ''
                self.hanText = ''
            elif event.key == pygame.K_BACKSPACE:
                if self.hanMode and len(self.hanText) > 0:
                    self.hanText = self.hanText[:-1]
                elif len(self.text) > 0:
                    self.text = self.text[:-1]
            elif event.mod & pygame.KMOD_LSHIFT and event.key == pygame.K_SPACE:  # 한영 변환 인식 Left Shift + space
                if self.hanMode:  # 영문모드로 토글
                    self.text += HangulInputBox.engkor(self.hanText)
                    self.cursor = pygame.Rect(self.textRect.topright, (3, self.fontSize))
                    self.hanMode = False
                else:  # 한글모드로 토클
                    self.cursor = pygame.Rect(self.textRect.topright, (16, self.fontSize))
                    self.hanMode = True
                self.hanText = ''
            else:
                if self.hanMode:
                    self.hanText += event.unicode
                else:
                    self.text += event.unicode
            #----------
            text2 = self.text + HangulInputBox.engkor(self.hanText)
            self.textImage = self.font.render(text2, True, 'white')
            self.textRect = self.textImage.get_rect()
            if self.textRect.width > self.rect.width:
                self.textRect.topright = (self.rect.width - self.fontSize, 0)
            else:
                self.textRect.topleft = (0,0)
            self.cursor.topleft = self.textRect.topright

        self.image.fill(self.bColor)
        self.image.blit(self.textImage, self.textRect)
        if time.time() % 1 > 0.5:
            pygame.draw.rect(self.image, 'red', self.cursor)

    @classmethod
    def engkor(cls, text):
        result = ''  # 영 > 한 변환 결과

        # 1. 해당 글자가 자음인지 모음인지 확인
        vc = ''
        for t in text:
            if t in cls.cons:
                vc += 'c'
            elif t in cls.vowels:
                vc += 'v'
            else:
                vc += '!'

        # cvv → fVV / cv → fv / cc → dd
        vc = vc.replace('cvv', 'fVV').replace('cv', 'fv').replace('cc', 'dd')

        # 2. 자음 / 모음 / 두글자 자음 에서 검색
        i = 0
        while i < len(text):
            v = vc[i]
            t = text[i]

            j = 1
            # 한글일 경우
            try:
                if v == 'f' or v == 'c':  # 초성(f) & 자음(c) = 자음
                    result += cls.cons[t]

                elif v == 'V':  # 더블 모음
                    result += cls.vowels[text[i:i + 2]]
                    j += 1

                elif v == 'v':  # 모음
                    result += cls.vowels[t]

                elif v == 'd':  # 더블 자음
                    result += cls.cons_double[text[i:i + 2]]
                    j += 1
                else:
                    result += t

            # 한글이 아닐 경우
            except:
                if v in cls.cons:
                    result += cls.cons[t]
                elif v in cls.vowels:
                    result += cls.vowels[t]
                else:
                    result += t

            i += j

        return join_jamos(result)
반응형

댓글