. 제어문자 보기

제어문자란 개행코드나 탭같이 문자가 아닌 것을 말하는데 이런 코드는 다른 문자열의 출력 방식에 영향을 주기는 하지만 그 자체가 화면에 보이지는 않는다. 이런 제어코드를 명시적으로 화면에 보여주면 복잡한 문서를 편집할 때 많은 도움이 된다. 그렇다고 해서 항상 제어코드를 보여주면 오히려 지저분해보일 수 있으므로 옵션을 두어 원할 때만 볼 수 있도록 만들어보자. 다음 변수를 선언하고 초기화한다.

 

BOOL bShowTab;

BOOL bShowEnter;

BOOL bShowSpace;

int ShowTabType;

int ShowEnterType;

int ShowSpaceType;

COLORREF CodeColor;

 

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     bShowTab=TRUE;

     bShowEnter=TRUE;

     bShowSpace=TRUE;

     ShowTabType=1;

     ShowEnterType=2;

     ShowSpaceType=0;

     CodeColor=RGB(128,128,128);

 

     return TRUE;

}

 

각 변수들은 탭, 개행코드, 공백을 보여줄 것인가 아닌가와 보여준다면 어떤 식으로 보여줄 것인가를 지정하는데 디폴트값은 FALSE로 두고 사용자가 원할 때만 TRUE로 바꾸면 된다. , 지금은 제어코드가 화면에 잘 보이는지 테스트해야 하므로 TRUE로 초기화하도록 하자. CodeColor는 제어코드의 색상값이며 본문과 구분하기 쉽도록 회색으로 초기화하였다. 제어코드를 출력해야 할 시점은 DrawSegment가 조각을 출력할 때이다. 조각안에 탭이나 개행코드가 있다면 같이 출력하도록 하면 된다. 다음 코드를 DrawSegment 함수에 작성한다.

 

void DrawSegment(HDC hdc, int &x, int y, int SegOff, int len, BOOL ignoreX,

     COLORREF fore, COLORREF back)

{

     int docx;

     int oldx;

     RECT rt;

     HBRUSH Brush;

 

     if (buf[SegOff] == ‘\t’) {

          oldx=x;

          docx=x+xPos;

          docx=((docx-MarginWidth)/TabSize+1)*TabSize+MarginWidth;

          x=docx-xPos;

          SetRect(&rt,oldx,y,x+1,y+FontHeight);

          Brush=CreateSolidBrush(back);

          FillRect(hdc,&rt,Brush);

          DeleteObject(Brush);

        if (bShowTab) {

           DisplayTab(hdc,oldx,x,y,back);

        }

     } else {

          SetTextColor(hdc,fore);

          SetBkColor(hdc,back);

          TextOut(hdc,x,y,buf+SegOff,len);

        if (bShowSpace) {

           DisplaySpace(hdc,x,y,buf+SegOff,len);

        }

          if (bShowEnter || ignoreX == FALSE) {

              x+=MyGetTextExtent(buf+SegOff,len);

          }

     }

    if (bShowEnter) {

        if (buf[SegOff+len] == ‘\r’) {

           DisplayEnter(hdc,x,y,back);

        }

    }

}

 

제어코드 보기 변수의 상태에 따라 적절한 제어코드 출력함수를 호출하는데 예를 들어 bShowTab TRUE이면 DisplayTab 함수를 호출한다. 개행코드의 경우는 조각 문자열에 같이 포함되어 전달되지 않으므로 조각을 다 출력한 후 뒤쪽 문자가 개행코드인지 따로 점검해보고 조각 끝에서 개행문자를 보여주도록 해야 한다. 개행코드가 출력되어야 할 위치는 조각의 마지막 좌표이므로 bShowEnter TRUE이면 ignoreX에 상관없이 x의 끝 좌표를 계산하도록 하였다. 그러면 각각의 제어코드 출력함수들을 작성해보자. 전체 코드는 다음과 같다.

 

 

void DisplayTab(HDC hdc, int x1, int x2, int y,COLORREF back)

{

     static BYTE arTab[]={0,0,1,0,2,0,1,1,1,2,1,3,1,4,

          4,0,3,1,3,2,3,3,3,4,5,1,5,2,5,3,5,4,4,2,

          6,0,6,1,6,2,6,3,6,4,7,0,7,2,7,4,8,1,8,3};

     HPEN hPen,OldPen;

     int i;

 

     hPen=CreatePen(PS_SOLID,1,CodeColor);

     OldPen=(HPEN)SelectObject(hdc,hPen);

 

     switch (ShowTabType) {

     case 0:

          MoveToEx(hdc,x1+1,FontHeight/2,NULL);

          LineTo(hdc,x2-1,FontHeight/2);

          LineTo(hdc,x2-3,FontHeight/2-2);

          LineTo(hdc,x2-3,FontHeight/2+2);

          LineTo(hdc,x2-1,FontHeight/2);

          break;

     case 1:

          MoveToEx(hdc,x1+1,FontHeight*3/4,NULL);

          LineTo(hdc,x2-1,FontHeight*3/4);

          LineTo(hdc,x2-3,FontHeight*3/4-2);

          LineTo(hdc,x2-3,FontHeight*3/4+2);

          LineTo(hdc,x2-1,FontHeight*3/4);

          for (i=0;i<sizeof(arTab)/sizeof(arTab[0]);i+=2) {

              SetPixel(hdc,arTab[i]+x1+2,arTab[i+1]+FontHeight*3/4-6,CodeColor);

          }

          break;

     }

 

     SelectObject(hdc,OldPen);

     DeleteObject(hPen);

}

 

void DisplayEnter(HDC hdc, int x, int y,COLORREF back)

{

     HPEN hPen,OldPen;

     HBRUSH Brush,OldBrush;

     RECT rt;

     int width;

 

     hPen=CreatePen(PS_SOLID,1,CodeColor);

     OldPen=(HPEN)SelectObject(hdc,hPen);

     Brush=CreateSolidBrush(back);

 

     switch (ShowEnterType) {

     case 0:

          width=arChWidth[0x20]*2;

          break;

     case 1:

          width=arChWidth[0x20]*3;

          break;

     case 2:

          width=arChWidth[0x20]*3;

          break;

     }

 

     SetRect(&rt,x,y,x+width,y+FontHeight);

     FillRect(hdc,&rt,Brush);

 

     if (ShowEnterType <= 1) {

          MoveToEx(hdc,x+width-2,FontHeight/10,NULL);

          LineTo(hdc,x+width-2,FontHeight*3/4);

          LineTo(hdc,x+2,FontHeight*3/4);

          LineTo(hdc,x+4,FontHeight*3/4-2);

          LineTo(hdc,x+4,FontHeight*3/4+2);

          LineTo(hdc,x+2,FontHeight*3/4);

     } else {

          OldBrush=(HBRUSH)SelectObject(hdc,GetStockObject(NULL_BRUSH));

          Rectangle(hdc,rt.left+1,rt.top+1,rt.right-1,rt.bottom-1);

          SelectObject(hdc,OldBrush);

          MoveToEx(hdc,x+width-4,FontHeight/5,NULL);

          LineTo(hdc,x+width-4,FontHeight*5/8);

          LineTo(hdc,x+3,FontHeight*5/8);

          LineTo(hdc,x+5,FontHeight*5/8-2);

          LineTo(hdc,x+5,FontHeight*5/8+2);

          LineTo(hdc,x+3,FontHeight*5/8);

     }

     SelectObject(hdc,OldPen);

     DeleteObject(hPen);

     DeleteObject(Brush);

}

 

void DisplaySpace(HDC hdc, int x, int y, TCHAR *text, int len)

{

     int nPos;

     int nx=x;

     int width;

     HPEN hPen,OldPen;

     HBRUSH OldBrush;

 

     width=arChWidth[0x20];

     if (ShowSpaceType!=0) {

          hPen=CreatePen(PS_SOLID,1,CodeColor);

          OldPen=(HPEN)SelectObject(hdc,hPen);

          OldBrush=(HBRUSH)SelectObject(hdc,GetStockObject(NULL_BRUSH));

     }

     for (nPos=0;nPos!=len;nPos++) {

          if (text[nPos] == 0x20) {

              switch (ShowSpaceType) {

              default:

              case 0:

                   SetPixel(hdc,nx+width/2,y+FontHeight/2,CodeColor);

                   SetPixel(hdc,nx+width/2,y+FontHeight/2+1,CodeColor);

                   break;

              case 1:

                   MoveToEx(hdc,nx,FontHeight*3/4+1,NULL);

                   LineTo(hdc,nx,FontHeight-1);

                   LineTo(hdc,nx+width-1,FontHeight-1);

                   LineTo(hdc,nx+width-1,FontHeight*3/4);

                   break;

              case 2:

                   Rectangle(hdc,nx,0,nx+width,FontHeight);

                   break;

              }

              nx+=width;

          } else {

              if (IsDBCS(text-buf+nPos)) {

                   nx += GetCharWidth(text+nPos,2);

                   nPos++;

              } else {

                   nx += GetCharWidth(text+nPos,1);

              }

          }

     }

     if (ShowSpaceType!=0) {

          SelectObject(hdc,OldPen);

          DeleteObject(hPen);

          SelectObject(hdc,OldBrush);

     }

}

 

각 함수는 제어코드를 보여주는 방식에 따라 각각 다른 모양으로 제어코드를 표현한다. 공백의 경우 두 개의 점으로 표현하기도 하고 사각형이나 열린 사각형으로 공백 위치를 표현할 수도 있다. 엔터코드는 세 가지 방식으로 그리는데 0,1번의 두 타입은 동일한 모양이되 폭만 다르며 2번은 사각형안에 개행코드를 그리도록 하였다. 탭은 중앙에 화살표가 있는 모양 또는 아래쪽에 화살표가 있고 TAB 문자가 있는 모양 두 가지가 있다. 코드 자체는 단순한 출력문들이므로 어렵지 않게 분석이 될 것이다.

그런데 왜 제어코드를 힘들게 그래픽으로 그리는지 의아할 것이다. 적당한 모양의 그래픽문자를 출력하면 훨씬 더 쉬운데 코드량도 많고 속도도 느린 작도 함수를 쓰는 것일까? 실제로 탭은 0x1a문자(®)를 출력하면 되고 개행코드는 0x14번 문자를 출력하면 보기 좋게 출력된다. 더구나 이렇게 문자코드를 사용하면 폰트의 크기가 바뀔 때 제어코드 크기도 같이 바뀌어 출력되므로 보기에도 좋다.

그러나 문자코드를 쓰는 방식에는 결정적인 단점이 있는데 호환성이 좋지 못하다는 점이다. 그래픽문자는 선택되어 있는 폰트에 따라 달라질 수가 있으며 같은 폰트라도 운영체제에 따라 그 모양이 달라진다. 0x1a 문자는 2000에서는 탭표식이 나오지만 XP에서는 엉뚱한 문자가 나온다. 항상 일정한 모양의 문자가 나오려면 제어코드 출력용으로 별도의 폰트를 하나 더 생성해야 하는데 이것도 약간 부담스러운 일이다.

그래픽을 직접 그리면 표현에 있어서는 완전한 자유를 누릴 수 있다. 탭표식을 탭의 길이만큼 길게 늘려줄 수 있으므로 실제 탭의 폭을 볼 수 있어서 아주 편리하다. 물론 실행속도가 느려지기는 하지만 자주 쓰는 기능이 아니므로 감수할만하다. 탭표식의 TAB 문자는 9*5의 비트맵을 만들고 점 좌표로 바꾼 후 일일이 찍었다. 비트맵을 쓰는 것이 더 빠를 것 같지만 비트맵은 투명 출력이 어려워 그냥 점을 찍는 다소 무식한 방법을 선택했다. 제어코드가 제대로 보이는지 실행해보자. 물론 제대로 보일 것이다.

Show*Type 변수의 초기값을 바꿔 가면서 실행해보면 제어코드의 모양을 다르게 선택할 수도 있다. 이 타입은 코드의 다른 부분과 전혀 연관되어 있지 않으므로 시간만 허락한다면 이런 출력타입은 얼마든지 늘릴 수 있을 것이다.