Previous Next Table of Contents

8. X 프로그래밍, X 로케일 이용 출력

아직 적은 수이기는 하지만 리눅스의 전파와 더불어 X 윈도우에 대한 관심도 점점 더 해가는 듯 한 느낌을 받습니다.

우리가 한국인인 이상 피할 수 없는 문제는 우리의 글의 올바른 표현이라고 할 수 있습니다. 자기 나라 말을 사랑하지 않거나 관심을 갖고 발전시키지 않는 민족은 이 세상에서 사라지는 것 아니겠습니까? 그리고 자기 말을 갖지 않는 민족이 도대체 어떤 자부심을 가질 수 있을까요?

컴퓨터 정보 문화의 시대에 살고 있는 우리에게 한글은 선조들의 노력을 이어받아 더욱 더 발전시키고 현대화시켜야 할 살아있는 언어라는 생각이 듭니다. 최소한 발전은 안시키더라도 애착을 갖고 사용은 해야 할 것 아니겠습니까?

윈도그 사용자 시절 무지하게도 항상 이런 말을 자신에게 하면서 컴퓨터를 사용해 왔던 것 같습니다.

"한글 윈도우는 안돼... 역시... 한글화만 하면 영 아니야... 한글 안돼..."

웃기는 사실은 엉터리로 한글화를 시킨 M$ 측이나 미국 가서 나라의 기술을 팔아먹고 있는 한국의 컴퓨터 기술자들에게 비난을 보내는 것이 아니라 "한글" 자체를 들먹이고 있었다는 것입니다.

사실 1 바이트 문자권에서 탄생한 컴퓨터라는 환경 속에서 힘든 싸움을 하고 있는 것이 사실입니다. 힘든 일이지만 누구나 할 수 있는 일이라면 벌써 다른 사람들이 다 해놓을 겁니다. 힘든 일을 잘 하라고 비싼 돈 들여가며 귀중한 시간을 쏟아부어가며 프로그래머가 되려는 것 아닐까요?

1 바이트 문자보다는 약간 어려운 과정을 거치지만 그렇게 어려운 것도 아닙니다. 예전에 도그 시절에는 한글 입출력 모두를 매번 만들어서 사용했던 기억도 납니다. 아주 간단히 한글 기능 자체를 내장해버리니 속 편할 수 있었지만 지금의 소프트웨어 개발 상황에는 전혀 맞지 않는 구닥다리 방식으로 변화하고 말았습니다.

X 윈도우가 제공하고 있는 많은 기능들을 충분히 활용하면서 한글을 쉽게 표현할 수 있는 방법에 대해서 일단은 출력에 관한 부분을 다시 한 번 소개해드립니다.

8.1 X 윈도우 자체의 로케일 기능을 사용한 한글 출력

X 윈도우에서의 한글 출력 방식에 대하여

우선 저수준의 방법은 한텀, 그리고 한글화된 윈도우 관리자들에서 사용하고 있는 방식으로서 고수준의 방법과 구별되는 기준은 바로 X 윈도우 시스템이 제공하고 있는 국제화(I18N, Internationalization)와 지역화(L10N, Localization) 기술을 사용하는가 여부에 달려있습니다. XDrawString16 함수를 사용하는 방법을 소개해 드린 적이 있는 것으로 압니다. 그 글을 읽지 않으셨다면 그 글을 먼저 읽어보시기 바랍니다.

저수준의 방법은 한글 문자열( char 형 )을 한글과 영문으로 나누어서( 바이트의 최상위 비트를 보고 판단 ) 한글 부분은 XChar2b 형 배열에 넣어서 2 바이트용 함수인 XDrawString16 함수로 출력하고 영문 부분은 따로 1 바이트 전용 함수인 XDrawString 함수로 출력하는 방식을 사용합니다.

저수준이므로 상당히 번거롭다고 하지 않을 수 없습니다. 한글과 영문을 섞어서 쓰는 경우에는 정말 신경쓸 일이 많습니다. 문자열을 읽어서 일일이 한글이 출력되는 화면 폭을 계산하고 그 다음에 영문을 출력하고 그 폭을 계산한 후 다시 한글을 출력하고...

고수준의 방법은 바로 X 윈도우 시스템이 R5 부터 지원하기 시작한 국제화/지역화 기법을 사용하는 것입니다. 같은 1 바이트 문자권이라 할 지라도 영어에 맞춰진 ASCII 문자로는 불가능한 유럽언어( 프랑스어, 독일어 등... )와 애초부터 ASCII로는 택도 없는 2 바이트 이상의 문자권인 한국어, 중국어, 일본어 등에 대한 시스템 차원의 지원이 논의되면서 프로그래머들이 쉽게 국제적인 프로그램을 만들 수있도록 고민한 결과입니다. 국제화란 프로그램이 어떤 언어 환경에서든 사용될 수 있도록 유연하게 프로그래밍하는 것을 말하며 그렇게 국제화된 프로그램을 특정 언어나 문화에서 사용할 수 있도록 적응시키는 것을 지역화라고 부릅니다.

이미 솔라리스나 HP-UX 등에서 상용 X 윈도우를 보신 분들은 이것이 무엇을 뜻하는지 아실 수 있을 겁니다. 윈도그95 같은 것을 프랑스판, 독일어판, 한국어판, 중국어판, 일본어판 이렇게 닭질을 해가면서 따로따로 만드는 것과는 차원이 다릅니다. 간단한 설정 하나로 같은 X 윈도우 시스템이 여러 개의 언어로 동작하도록 설계되어 있습니다. 로그인 화면부터가 아예 달라지지요.

X 윈도우 시스템이 지원하는 I18N, L10N 이용하기

이제 알아볼 내용은 바로 고수준의 한글 출력에 대한 것입니다.

어떤 절차를 거쳐야 하는지에 대해서 알아보도록 합시다.

LOCALE 데이터베이스 지원을 누가 하는가?

유닉스 시스템의 로케일 지원은 일단 X 윈도우 시스템이 아닌 ANSI/POSIX 규격 차원의 지원이 있습니다. 대부분의 상용 유닉스나 아는 바 없지만 FreeBSD 와 같은 운영체제에서 지원하고 있는 것으로 알고 있습니다. 지금 현재 GNU NLS 팀에서 열심히 프로그램 메세지 지역화를 하고 계신 분들의 결과가 조만간 우리 곁에 오게 되겠지요? 그 분들에게 격려를 보냅니다.

리눅스는 아직 C 라이브러리에 대해서 제대로 모르기 때문에 확실하게 말씀드릴 수 없습니다. C 라이브러리 5.4.X 에서 로케일에 대한 지원이 있다고는 하는 것 같은데 제대로 확인이 되질 않는데다가 저의 관심 영역을 로우레벨 프로그래밍이 아니라서...

두번째로는 운영체제 자체의 지원과는 완전 별도로 X 윈도우 자체적으로 해결해주는 방식이 있습니다. 바로 오늘 X 윈도우 자체가 지원하는 로케일 지원을 사용하여 해결할 것입니다. 전자나 후자 모두 중요한데 일단 X 윈도우 자체적으로도 지원을 해준다는 것은 어떤 플랫포옴인가에 상관없이 같은 X 윈도우만 돌아간다면 어디서든 여러분의 프로그램이 제대로 동작하리란 보장을 받을 수 있다는 장점이 있습니다.

리눅스 C 라이브러리에 대한 연구는 시간을 내서 좀 더 해봐야 할 것 같습니다. 많은 사람들이 뛰어들어주기를 바랄 뿐입니다. 리눅스는 개방 체제이므로 자원자가 많은 곳의 분위기를 따르지 않겠어요? 아시아의 젊은이들이 많이 뛰어들 수록 리눅스 자체는 국제적인 운영체제로 다시 태어날 수 있으리라 봅니다.

여러분의 X 윈도우 시스템을 점검합니다.

ANSI 규격에서 정의한 바와 같습니다. C 셸을 쓰는 분들은 setenv LANG ko 와 같은 표현을 써보신 적이 있을 겁니다. 이 글은 ANSI 규격의 setlocale 함수에 관련된 모든 사항을 자세히 설명하기 위한 것은 아니므로 따로 공부해보시기 바랍니다. 아주 중요한 이슈 중의 하나인 것은 분명한 것 같습니다.

일단은 /usr/X11R6/lib/X11/locale 디렉토리와 내용이 있는지를 확인해보십시요. 리눅스 배포판에서 X 윈도우 시리즈를 제대로 설치했다면 있어야 할 내용입니다.

 C/             iso8859-3/     iso8859-8/     ko/            th_TH.TACTIS/
 compose.dir    iso8859-4/     iso8859-9/     koi8-r/        zh/
 en_US.utf/     iso8859-5/     ja/            locale.alias   zh_TW/
 iso8859-1/     iso8859-6/     ja.JIS/        locale.dir
 iso8859-2/     iso8859-7/     ja.SJIS/       tbl_data/

ko 라는 디렉토리가 있어야 합니다. 그 안에는 XLC_LOCALE 이라는 화일이 들어있습니다. 그 화일 안에 한국어의 출력에 대한 관련 정보가 들어있습니다.

보통은 LANG 세팅이 없는 경우 기본적으로 C 디렉토리에 있는 내용을 사용합니다.

위 화일들이 제대로 있는 것을 확인한 후에 꼭 다음 세팅을 해주어야 합니다.

본셸 계열이라면...

        export LANG=ko

C 셸 계열이라면...

        setenv LANG ko

나중에 같은 프로그램에 대해서 그냥 LANG 세팅을 재미삼아 ja 라고 바꿔보십시요. 그러면 재미있는 일이 벌어질 것입니다. 여러분이 네스케이프에서 옵션 메뉴에서 코딩 옵션만 바꿔주면 텍스트의 내용이 한글로 보였다가 일본어로 보였다 하는 원리를 쉽게 이해하실 수 있으리라 봅니다.

이제부터 프로그래밍을 해봅시다.

프로그래밍이란 전형적인 패턴이 있게 마련이지요. 그리고 확실한 패턴( 물론 좋은 패턴 :> )을 익혀두는 것이 좋습니다.

예제를 손수 타이핑해 보시는 것이 좋을 겁니다. 일단 컴파일을 해보고 제대로 된다는 것을 확인하시기 바랍니다.

                        X Locale 을 이용한 한글 출력


*/
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
/*
#include <locale.h>
*/
#include <X11/Xlocale.h>

void DrawCenteredMbString ( Display *, Window, XFontSet, GC, char*, int, int,
                                int, int, int );

int
main (argc, argv)
        int argc;
        char *argv[];
{
        Display *dpy;
        Window  w;
        XFontSet fontset;
        char **missing_charsets;
        int num_missing_charsets;
        char *default_string;
        int i;
        GC      gc;
        XEvent  xe;

        char *program_name = argv[0];

        if ( setlocale (LC_ALL,"") == NULL )       {
                (void) fprintf (stderr, "%s: cannot set locale.\n",
                program_name );
                exit (1);
        }

        if ( !XSupportsLocale () ) {
                (void) fprintf (stderr,"%s: X does not support locale %s.\n",
                program_name, setlocale( LC_ALL, NULL ) );
                exit (1);
        }

        if ( XSetLocaleModifiers("") == NULL )    {
                (void) fprintf (stderr,
                        "%s: Warning: cannot set locale modifiers.\n",
                        program_name);
        }

        dpy = XOpenDisplay ( NULL);
        w   = XCreateSimpleWindow ( dpy, RootWindow( dpy, 0 ), 50, 50,
                                        100, 50, 5, BlackPixel( dpy, 0 ),
                                        WhitePixel( dpy, 0 ) );
        gc = XCreateGC ( dpy, w, 0L, ( XGCValues * ) NULL );

        fontset = XCreateFontSet (dpy, "-*-*-*-*-*-*-16-*-*-*-*-*-*-*",
                        &missing_charsets, &num_missing_charsets,
                        &default_string);
        if ( num_missing_charsets > 0 ) {
                (void) fprintf (stderr,
                        "%s: The following charsets are missing:\n",
                        program_name);
                for ( i=0; i < num_missing_charsets; i++ )
                        (void)fprintf (stderr, "%s:   %s\n", program_name,
                                missing_charsets[i]);
                (void) fprintf (stderr, "%s: The string is %s", program_name,
                                default_string);
                (void) fprintf (stderr, 
                                "%s: of any characters from those sets.\n",
                                program_name);
                XFreeStringList (missing_charsets);
        }

        XSetLineAttributes ( dpy, gc, 5, LineSolid, CapRound, JoinRound );

        XSelectInput ( dpy, w, ExposureMask | ButtonPressMask 
                        | EnterWindowMask | LeaveWindowMask );

        XMapWindow ( dpy, w );

        XFlush ( dpy );

        while ( True )
        {
                XNextEvent ( dpy, &xe );

                switch ( xe.type )      {

                case Expose:
                        printf ("Expose 이벤트가 발생했습니다.\n");
                        XSetForeground ( dpy, gc, BlackPixel( dpy, 0 ) );
                        DrawCenteredMbString ( dpy, w, fontset, gc, 
                                        "안녕 Hello",
                                        10, 0, 0, 100, 50 );
                        break;
                case ButtonPress:
                        printf ("버튼을 누르셨습니다!\n");
                        exit (1);
                        break;
                case EnterNotify:
                        printf ("마우스가 창 안으로 들어왔습니다.\n");
                        XSetForeground ( dpy, gc, BlackPixel( dpy, 0 ) );
                        XDrawRectangle ( dpy, w, gc, 0, 0, 100, 50 );
                        break;
                case LeaveNotify:
                        printf ("마우스가 창 밖으로 나갔습니다.\n");
                        XSetForeground ( dpy, gc, WhitePixel( dpy, 0 ) );
                        XDrawRectangle ( dpy, w, gc, 0, 0, 100, 50 );
                        break;
                default:
                        printf ("모르는 이벤트입니다. 무시합니다.\n");
                        break;
                }
        }
        return 0;
}

void
DrawCenteredMbString (dpy, w, fontset, gc, str, num_bytes, x, y, width, height )
        Display *dpy;
        Window w;
        XFontSet fontset;
        GC gc;
        char *str;
        int num_bytes;
        int x,y, width, height;
{
        XRectangle boundingbox;
        XRectangle dummy;
        int originx, originy;

        (void) XmbTextExtents (fontset, str, num_bytes, &dummy, &boundingbox);

        originx = x + ( width - boundingbox.width )/2 - boundingbox.x;
        originy = y + ( height - boundingbox.height)/2 - boundingbox.y;

        XmbDrawString (dpy, w, fontset, gc, originx, originy, str, num_bytes);
}

자, 컴파일을 해보도록 합시다. 약간의 주의가 필요합니다.

이 프로그램은 꼭 리눅스에서만 컴파일하란 법은 없습니다. ( 확인은 안했지만... )

시스템 수준에서 로케일을 지원하는 경우에는 다음과 같이 컴파일해야 합니다.

        gcc -o test_locale test.locale.c -lX11 -L/usr/X11/lib

리눅스 시스템인 경우에는 X 윈도우 자체의 로케일을 사용할 것이므로 다음과 같이 해주어야만 합니다.

        gcc -o test_locale test.locale.c -DX_LOCALE -lX11 -L/usr/X11/lib

순서대로 설명을 해보도록 하겠습니다.

시스템이 로케일을 지원하는가?

setlocale 함수를 써서 시스템 차원에서 또는 X 윈도우 시스템 차원에서 로케일을 지원하는지 여부에 대해서 알아봅니다. 실행되는 플랫포옴에서 로케일을 지원하지 않는다면 다른 조치를 취하든지 아니면 실행을 중지해야 할 것입니다. 제대로 된 결과가 나오질 않을테니까요.

  setlocale (LC_ALL,"") == NULL 이 문장으로 확인을 하고 있습니다.

X 윈도우 차원에서의 로케일 지원 여부 확인

XSupportsLocale 함수와 XSetLocaleModifiers 함수를 사용하여 로케일 지원여부를 물어봅니다.

후자의 함수는 "X 입력 서버"에 관계된 것이므로 지금으로서는 설명드릴 수가 없고 상당히 전문적인 얘기로 빠질 가능성이 많습니다. 조만간에 "X 입력 서버" 제작에 대해서 따로 소개를 해드리기로 하겠습니다. 궁금증을 참기 어려운 분은 그냥 오렐리 출판사의 X 윈도우 시리즈 Volume One Xlib 프로그래밍 매뉴얼을 보시면 됩니다.

폰트셋(FontSet)이라는 개념....

폰트셋이라는 개념은 글자 그대로 폰트의 한 세트를 말합니다. 어떤 특정 언어의 코드에 맞는( 예를 들어 ASCII, ISO8859-1, EUC-KR, ISO-2022-KR 등등 ) 문자 폰트들이 이미 마련되어 있어야 출력을 할 수 있겠지요? 즉 환경 변수 LANG 이 지시하는 코딩 스타일에 맞는 폰트들의 한 세트를 지정해주어야 합니다.

모티프 프로그래밍을 해보신 분들이라면 폰트셋이라는 개념에 대해서 어느 정도 아실 겁니다. 일단은 X 윈도우 로케일을 이용하는 방식은 폰트셋을 사용한다는 사실을 정확히 아셔야 합니다. 생각보다 상당히 간편하게 만듭니다.

XCreateFontSet 함수를 이용하지요.

두번째 인수로 그냥 -*-*-*-*-*-*-16-*-*-*-*-*-*-* 이라고 XLFD 방식의 폰트명을 지정해주었으므로 16 포인트 폰트들을 찾아내서 폰트셋에 등록하게 됩니다. 만약 16 포인트 폰트가 없다면 가장 가까운 폰트를 찾아서 X 윈도우 자체가 스케일링을 해줍니다. 이 때가 X 윈도우의 가장 취약한 부분인데... 2 바이트권의 엄청난 양의 폰트를 스케일링하게 될 때는 마치 X 서버가 멈춰버리는 듯 한 현상이 일어납니다. 폰트를 모두 변환하느라 엄청난 시간을 소비하고 있기 때문이지요. 위 숫자를 15나 다른 숫자로 바꾸어서 한 번 해보시기 바랍니다. 그 차이을 느끼실 수도 있을 겁니다.

문자열의 출력은 간단히 XmbDrawString 함수 이용

위에서는 DrawCenteredMbString 라는 사용자 정의 함수 내에서 멀티 바이트 코딩에 사용하는 출력 함수 XmbDrawString 를 사용하였습니다. 위에서 보시는 바와 같이 옛날에 저수준 출력함수를 사용하여 한글을 한글대로 영문은 영문대로 따로 닭질을 할 필요가 없습니다. -_-

한글 코딩된 2 바이트 문자를 그냥 쓰면 됩니다. 단지 위에서는 문자 출력 위치를 조정하기 위하여 XmbTextExtents 함수와 같은 것을 사용하였습니다.


Previous Next Table of Contents