. 금칙 처리

GetLineSub 함수는 줄 끝에 닿았을 때 정렬방식에 맞게 무조건 줄을 자르며 잘리는 위치의 문자가 무엇인지는 상관하지 않는다. 이러다 보니 공백이 있는 위치에서도 줄이 잘릴 수 있게 되고 따라서 줄 처음에 공백이 올 수 있다. 다음 그림을 보자.

두 번째 줄을 보면 하느님이 다음에 공백 하나가 들어갈 자리가 없어 공백 위치에서 개행되었고 따라서 두 번째 줄의 첫 문자는 공백이다. 정해진 규칙에 따라 정렬을 하다보니 이렇게 되는 것이 당연하고 아주 자연스러운 현상이다. 하지만 문제는 보기가 싫다는 것이다. 문장의 왼쪽 끝이 가지런해야지 공백으로 인해 들쭉날쭉하면 문서의 모양이 보기에 썩 좋지 못하다.

그래서 줄 처음의 공백 문자에 대해서만 금칙 처리를 해보도록 하자. 금칙 처리란 특정한 문자가 특정 위치에 오지 못하도록 하는 것인데 앞금칙과 뒷금칙이 있다. 앞금칙 문자는 줄 처음에 못 오는 문자이며 공백, 닫는 괄호, 쉼표, 마침표 등의 대표적인 예이다. 뒷금칙 문자는 줄 뒤에 올 수 없는 문자이며 여는 괄호, 따옴표, 화폐단위 등이 있다. 텍스트 편집기가 이런 복잡한 금칙 처리를 다 할 필요는 없을 것 같고 공백에 대한 앞금칙 처리만 해보도록 하자.

금칙 처리를 하는 가장 큰 이유는 보기 싫기 때문이다. 하지만 자연스러운 정렬 규칙을 어긴다는 점에 있어서 금칙 처리가 문서편집에 방해가 될 수도 있으므로 무조건 금칙 처리를 하는 것 보다는 옵션을 만들어 두고 원하는 사람만 이 처리를 하도록 만드는 것이 좋다. 다음 전역변수를 추가하고 OnCreate에서 TRUE로 초기화한다.

 

BOOL bNoFirstSpace;

 

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     ....

     bNoFirstSpace=TRUE;

     return TRUE;

}

 

그렇다면 어떻게 줄 처음에 공백이 오지 못하도록 할 수 있을까? 이 문제를 해결하는 방법은 적어도 수백 가지는 있다. 여러분이 직접 이 문제에 부닥쳤다면 어떻게 공백을 금지할 것인지 먼저 생각해보도록 하자. 문서상에 엄연히 존재하는 공백을 없애 버릴 수는 없는 노릇이고 그렇다고 앞 줄에 공백을 넣을 공간도 없는데 억지로 끼워넣을 수도 없다. 공백은 어차피 안 보이는데 뒷줄 끝에 살짝 붙여 놓아도 되지 않을까 하는 생각이 들기도 하는데 실제로 해보면 문제가 아주 많다. 캐럿이 공백 뒤에 숨어서 안 보이는 문제도 있고 불필요한 수평스크롤이 발생하기도 한다. 또 제어코드 보기 기능을 만들면 뒤쪽에 숨어 있는 공백이 보일 수도 있다.

줄 앞에 오는 공백의 문자폭을 임시로 0으로 만드는 방법을 생각해 볼 수도 있는데 이렇게 되면 캐럿이동이 골치아파진다. 안 보이는 공백을 건너 뛰기 위해 추가 코드 작성이 필요하며 정렬 상태가 바뀌면 없던 공백이 갑자기 생겨난 것처럼 보일 우려도 있다. 이외에도 아주 다양한 방법을 생각해 볼 수 있는데 여기서 쓰는 방법은 이른바 물귀신 작전이다. 줄 앞에 공백이 올 것 같으면 그 앞에 있는 비공백 문자(또는 단어)를 하나 더 가지고 내려 오는 것이다. 이 방식대로라면 앞 줄의 끝이 조금 비는 듯한 느낌이 있지만 공백 문자의 폭이 작기 때문에 쉽게 눈치채기 어렵다.

물귀신 작전을 쓰려면 물귀신에 희생될 후보 문자나 단어의 위치를 항상 기억하고 있어야 한다. 그래야 줄 처음에 공백을 만났을 때 자를 위치를 여분의 코드없이 선정할 수 있다. GetLineSub 함수에 비공백 문자의 후보 위치를 기억하는 변수 EndPosNoSpace를 추가하고 다음과 같이 코드를 수정한다.

 

int GetLineSub(TCHAR *&p)

{

    TCHAR *EndPosNoSpace=NULL;

 

     ....

          for (acwidth=0;;) {

               if (IsDBCS(p-buf)) {

                   len=2;

 

                   if (nWrap==2 || (nWrap==3 && IsPrevDBCS==FALSE)) {

                        EndPos=p;

                   if (*EndPos != ‘ ‘) {

                       EndPosNoSpace=EndPos;

                   }

                   }

                   IsPrevDBCS=TRUE;

               } else {

                   len=1;

 

                   if (IsPrevDBCS==TRUE) {

                        EndPos=p;

                   if (*EndPos != ‘ ‘) {

                       EndPosNoSpace=EndPos;

                   }

                   }

                   IsPrevDBCS=FALSE;

               }

               acwidth+=GetCharWidth(hdc,p,len);

 

               ....

               if (*p == ‘ ‘ || *p==‘\t’) {

                   EndPos=p+1;

               if (*EndPos != ‘ ‘) {

                   EndPosNoSpace=EndPos;

               }

               }

 

               p+=len;

          }

     }

 

     ReleaseDC(hWndMain,hdc);

     if (nWrap == 1 || EndPos == NULL) {

          p=p;

     } else {

          p=EndPos;

     }

 

    if (bNoFirstSpace && *p==‘ ‘ && EndPosNoSpace!=NULL) {

        p=EndPosNoSpace;

    }

     return ret;

}

 

EndPos에 후보 위치를 기억시킬 때마다 이 자리의 문자가 공백인지 보고 공백이 아니라면 EndPosNoSpace에도 위치를 같이 기억시켜 둔다. 루프의 중간에서는 비공백 문자의 위치만 기억하도록 하고 금칙 처리는 정렬이 완전히 끝난 후 따로 하는 것이 좋다. 그래서 GetLineSub는 리턴하기 직전에 금칙 처리를 한다.

bNoFirstSpace 옵션이 선택되어 있고 현재 개행될 위치가 공백이면 미리 기억해 둔 비공백 후보 위치를 줄 끝 위치로 취하면 된다. 이때도 예외 처리가 필요한데 만약 비공백 후보 위치가 없다면, 즉 줄 전체가 공백인 특수한 상황을 만나면 어쩔 수 없이 다음 줄 처음에도 공백이 올 수밖에 없다. 이렇게 코드를 수정한 후 테스트해보면 공백이 줄 처음에 오지 않을 것이다.

첫 줄의 끝에 자가 들어갈 충분한 공간이 있지만 그 뒤의 공백까지 같이 넣을 만큼의 여유가 없으므로 아예 같이 다음 줄로 내려와 버렸다. 공백이라는 물귀신이 자를 잡아 먹은 것이다. 공백이 처음에 있는 것보다는 이 방식이 훨씬 더 보기에는 좋다. GetLineSub의 공백 금지 코드는 복수 개의 공백에 대해서도 동작한다. 좀 더 정밀하게 작성한다면 하나의 공백만 금지하고 둘 이상일 때는 허용하는 방식을 취할 수도 있다.

공백 앞금칙 처리는 있으면 좋은 기능이지만 없어도 별 상관은 없고 텍스트 편집기에게는 다소 사치스러운 기능이다. 그래서 이 옵션의 디폴트값은 FALSE로 초기화하는 것이 좋을 것 같다. OnCreate를 다시 수정해서 이 변수의 초기값을 FALSE로 바꿔 놓도록 하자. ApiEdit는 공백 금지 기능을 가지고는 있지만 디폴트로 선택하지 않는다. 문서의 가독성보다는 자연스러움에 더 무게를 두겠다는 정책이다.