notepad2 컴파일 삽질기 부록#12 : NBSP 관련 수정

SMI 포맷의 자막을 만들거나, HTML을 직접 작성해본 경험이 있는 사람은  라는 표현을 자주 봤을 것이다.
그런데, NBSP가 뭔지 정확하게 아는 사람은 그리 많지 않은 것 같다.

NBSP는 Non-break Space의 약자로 공백의 일종이지만, 사실 일반적인 공백(0x20)과는 다르다.
줄바꿈을 하지 않는 공백을 의미하며, 유니코드에서는 0xa0이라는 값에 할당되어 있고, ASCII에는 이 개념이 없다.

유니코드를 ASCII 코드로 변환할 때 ASCII 코드에 없는 문자를 변환하는 방식은 정의되어있지 않다.
따라서 표준에 따르면 NBSP를 ASCII로 변환하면 공백으로 만들던, 물음표로 만들던 문제가 되지 않는다.

하지만, 실세계에서는 NBSP는 알아서 공백(0x20)으로 변환해주는 것이 예의다.

notepad2는 WideCharToMultiByte() 함수를 이용해서 유니코드를 ASCII 등으로 변환한다.
그런데, 이 함수에선 NBSP의 변환을 별도로 처리하지 않는다.

따라서, NBSP를 ASCII 모드에서 붙여넣으면 아래와 같은 결과를 보여준다.

구글 크롬에서 Syntax Highlighter 3.0으로 처리한 소스를 복사-붙이기 한 화면


웹 브라우저 중에서 IE나 FireFox에서 복사(copy)를 하면 NBSP를 공백으로 변환한 뒤 클립보드로 보내지만, 구글 크롬은 NBSP를 그대로 내보낸다.
Syntax Highlighter에서 코드를 복사한 뒤에 메모장2에 붙여넣으면 이 현상을 경험할 수 있다.

전술했듯이, 이 현상은 결코 버그가 아니며, 단지 현상일 뿐이다.
하지만, 사용자 입장에서 불편하긴 마찬가지다.

그래서 메모장2를 패치하기로 했다.


1. ScintillaWin.cxx 수정


이 현상을 수정하려면 일단 붙여넣기 부분을 수정해야 된다.
ScintillaWin.cxx 파일에서 void ScintillaWin::Paste() 함수를 찾은 뒤 아래 부분의 위치를 확인한다.

  // Convert from Unicode to current Scintilla code page
  UINT cpDest = CodePageOfDocument();
  len = ::WideCharToMultiByte(cpDest, 0, uptr, -1,
      NULL, 0, NULL, NULL) - 1; // subtract 0 terminator

이 부분을 아래와 같이 수정한다. 몇 줄의 코드를 삽입하는 것이며, 기존 코드의 수정은 없다.

  // Convert from Unicode to current Scintilla code page
  UINT cpDest = CodePageOfDocument();

  // 여기부터 삽입
  int wlen = wcslen(uptr);
  for (int i=0; i<wlen; i++)
  {
      if (uptr[i] == 0xa0) uptr[i] = 0x20;
  }
  // 여기까지 삽입

  len = ::WideCharToMultiByte(cpDest, 0, uptr, -1,
      NULL, 0, NULL, NULL) - 1; // subtract 0 terminator

이것으로 붙여넣기에서 문제는 해결되었다.


2. Edit.c

사실, 이 현상을 경험할 수 있는 것은 붙이기 명령 외에 하나가 더 있다.

유니코드에서 ASCII로 코드 페이지를 바꾸면 이 현상이 발생할 수 있다.
그래서, 코드 페이지를 바꿀 때도 이 문제가 발생하지 않도록 수정한다.

BOOL EditConvertText(...) 함수를 찾아 아래 내용의 위치를 확인한다.

  cbwText  = MultiByteToWideChar(cpSource,0,pchText,length,pwchText,length*3+2);
  cbText   = WideCharToMultiByte(cpDest,0,pwchText,cbwText,pchText,length*5+2,NULL,NULL);


이 내용을 아래처럼 수정한다. 역시 몇 줄의 코드를 삽입하는 것이며, 기존 코드의 수정은 없다.

  cbwText  = MultiByteToWideChar(cpSource,0,pchText,length,pwchText,length*3+2);
  // 여기부터 삽입
  {
    int i;
    for (i=0; i<length; i++)
        if (pwchText[i] == 0xa0) pwchText[i] = 0x20;
  }
  // 여기까지 삽입
  cbText   = WideCharToMultiByte(cpDest,0,pwchText,cbwText,pchText,length*5+2,NULL,NULL);

이렇게 수정한 뒤 구글 크롬 메모장2로 복사-붙이기를 실행하면 아래와 같이 동작하는 모습을 볼 수 있다.

구글 크롬 따위는 겁나지 않는다능!