왕밤빵

글자를 다루는 일은 늘 골치아프고, 웹 환경에서도 예외는 아니다. 그렇기에 폰트 파일만으로 글자 세부 영역까지 다룰 수 있는 도구의 제작을 목표로 한다.

***

웹에서, 특히 HTML Canvas 위에서 글자를 활용해 무언가를 구현하려 할 때 가장 자주 부딪히는 문제 중 하나는 글자 내부의 빈 공간, 즉 속공간이다. 일반적으로는 글자의 외곽을 그린 뒤 내부 공간을 도려내는 방식을 사용하기에 원하는 형태를 세밀하게 제어하기 어렵다. 같은 맥락에서 이를테면 ‘왕밤빵’은 사상 최악의 악질 단어인 셈이다.

반대로 속공간을 별도의 예외 처리 없이, 글자의 구조 안에서 하나의 구성 요소로 함께 다룰 수 있다면 웹 환경에서도 글자 형태를 훨씬 더 유연하게 활용할 수 있다. 이로써 내 목표는 ‘왕밤빵’을 악질 단어에서 귀엽고 맛난 단어로 탈바꿈시켜줄 도구를 제작하는 것으로 정해졌다.

우선 필요 기능의 우선순위부터 정리했다. 가장 필요한 기능은 글자의 내부와 외부, 그리고 글자 외곽이 형성하는 각 구성 요소를 구분해 반환하는 것이다.

구체적으로는 다음과 같다.

  • 폰트 로드
  • 영역 추출
  • 글리프 변환
  • 포인트 샘플링
  • 단일 폴리곤화
  • 히트 체크

***

폰트 파일은 opentype.js로 다루기로 했다. opentype.js는 글자의 외곽 정보를 Path 명령인 M(move), L(line), C(cubic Bézier), Q(quadratic Bézier), Z(close) 형태로 파싱해 Canvas 2D API 등으로 그릴 수 있게 해준다. 여기서 문제는 폰트 포맷에 따라 2차 베지어 곡선과 3차 베지어 곡선이 구분되어 사용된다는 것이다. 앞으로 구현할 기능을 위해 이를 라이브러리 내부에서 자동으로 처리되도록 만들 필요가 있다.

우선 필요한 것은 p5.js의 textToPoints처럼 글자 외곽을 low-poly 느낌으로 보간하는 기능이다. 이를 위해 드 카스텔조 알고리즘(De Casteljau’s algorithm) 을 사용해 곡선을 짧은 직선으로 분할한다. 이렇게 하면 글자의 외곽은 결국 여러 개의 Segment 집합이 되고, 이를 다시 보간하여 글자 외곽을 이루는 점들을 추출할 수 있다.

드 카스텔조 알고리즘은 베지어 곡선의 제어점과 점들 사이의 중간점을 재귀적으로 계산해 곡선을 분해하는 규칙이다. 일반적으로 n차 베지어 곡선은 n-1개의 핸들을 가지는데, 폰트 파일은 형식에 따라 2차 혹은 3차 베지어 곡선을 사용하기 때문에 라이브러리 내부에서 자동으로 처리되게 설계할 필요가 있었던 것이다.

***

글자의 외곽을 이루는 정점을 추출했다면 글자의 속공간을 구분할 필요가 있다. 본래 opentype.js의 path 명령 규칙은 속공간을 이루는 선을 별도의 내부 contour 로 다루며, 바깥 외곽선과 안쪽 윤곽선을 함께 이용해 글자를 그려낸다. 안쪽 윤곽선은 바깥 외곽선과 반대 방향을 가지며, 보통 Canvas 2D API의 fill rule(nonzero)를 이용해 처리된다.

Nonzero-rule은 어디가 형태의 안쪽이고 바깥쪽인지 판단하기 위한 규칙이다. 임의의 지점에서 무한대로 뻗어가는 가상의 직선(ray)를 그린다. 이 직선이 도형의 윤곽선과 만날 때마다 해당 윤곽선이 시계 방향인지, 반시계 방향인지 판단하고, 시계 방향이라면 +1, 아니라면 -1 한다. 결과가 0이면 해당 지점은 밖, 0이 아니면 안이다.

참고로 Canvas 2D API와 일반적인 벡터 그래픽(SVG 등)에서 지원하는 다른 채우기 규칙도 존재한다. Even–odd rule은 선의 방향(시계/반시계)을 완전히 무시하고, 오직 ‘선을 몇 번 가로질렀는가'만 따져 홀수라면 안쪽, 짝수라면 바깥쪽으로 판단하는 규칙이다. 이는 단순하고 직관적이지만 정교하지 않기에, 보통 Nonzero-rule이 사용된다.

속공간과 밖을 잇는 방식

하지만 외곽선과 내부 윤곽선을 구분한 뒤, 그리기 규칙을 이용하는 방법들로는 글자 형태를 다루고 그려내는 데에 제약이 따른다. 더 나은 방법이 있을지 모르겠지만, 일단 나는 글자 형태 구성 요소를 ‘한붓 그리기’로 그려낼 수 있게 하는 방식을 적용하였다. 그럼 ‘ㅇ’, ‘ㅍ’같은 글자의 속공간과 외부가 아주 얇게 연결되고, canvas의 채우기 규칙 없이도 일종의 ‘도넛 글자’들을 안정적으로 다룰 수 있게 된다.

구체적으로는 외곽선과 내부 윤곽선 중 연결 가능한 선 중 서로 가장 가까운 점(외곽선 하나, 내부 윤곽선 하나)을 찾는다. 여기서 기준은 곧 속공간과 외부의 연결 가능성을 의미하며, 구체적으로는 아래와 같다.

  • 두 점이 사실상 같은 점이면 제외
  • 두 점의 중점이 외곽선 외부에 있으면 제외
  • 그 중점이 해당 속공간 내부에 있으면 제외
  • 그 중점이 다른 속공간 내부에 있으면 제외
  • 두 점을 잇는 선분이 외관선 경계와 교차하면 제외
  • 두 점을 잇는 선분이 해당 속공간 경계와 교차하면 제외
  • 그 선분이 다른 속공간들과 교차하면 제외

그렇게 선택된 각 점에서 윤곽선을 따라 양방향으로 소폭 이동한 컷 포인트를 만들고, 이를 이용해 내곽과 외곽을 연결한다. 이와 유사한 방식을 이전부터 사용해 왔으나, 이것이 최선의 방법인지는 확신할 수 없다.

***

아래와 같은 요구를 시작으로 세부 API를 확정해 나간다.

  • PAFont 클래스를 import하여 사용
  • load 메서드로 경로를 입력받아 폰트파일을 opentype.js로 파싱
  • glyph 메서드로 문자열과 위치, 크기 등의 정보를 입력받아 서체의 형태 정보들을 반환

***

계속…

prev next