TrueType 글꼴은 대부분 단일 파일(.TTF 확장자)로 패키징된다. OpenType은 이와 유사한 구조를 따르며 .OTF 확장자를 사용한다. 여러 개의 OpenType 글꼴을 하나로 묶은 컬렉션은 .TTC 확장자로 저장된다.
TrueType은 'sfnt'라는 고유 시그니처로 식별되는 매킨토시 윤곽선 글꼴 포맷을 기반으로 한다. 글꼴 디렉터리에는 버전 정보와 여러 개의 테이블 엔트리가 포함되며, 각 엔트리는 리소스 태그, 체크섬, 오프셋, 크기를 담고 있다.
typedef struct {
char tag[4];
ULONG checksum;
ULONG offset;
ULONG length;
} FontTableEntry;
typedef struct {
Fixed sfntVersion; // 0x00010000 for version 1.0
USHORT tableCount;
USHORT searchRange;
USHORT entrySelector;
USHORT rangeShift;
FontTableEntry tables[1]; // 가변 길이 배열
} FontDirectory;
FontDirectory의 마지막 필드는 가변 길이 배열이며, 꼴 내 각 테이블에 대응한다. 필수 테이블과 선택적 테이블이 있으며, 주요 테이블은 다음과 같다.
| 태그 | 설명 |
|---|---|
| head | 글꼴 헤더, 전역 정보 |
| cmap | 문자 코드 → 글리프 인덱스 매핑 |
| glyf | 글리프 윤곽선 데이터 및 힌팅 명령 |
| maxp | 메모리 요구사항 최대값 |
| hmtx | 수평 메트릭 |
| loca | 글리프 데이터 오프셋 인덱스 |
| name | 저작권, 글꼴명, 패밀리명 등 |
| OS/2 | OS/2 및 Windows 전용 속성 |
| post | PostScript 관련 정보 |
| kern | 커닝(kerning) 조정값 |
테이블 엔트리는 태그 이름 기준으로 정렬되어 있어야 한다. 예를 들어 cmap은 head보다 먼저, head는 glyf보다 먼저 와야 한다. 하지만 실제 테이블 데이터는 파일 내 임의의 위치에 존재할 수 있다.
Win32 API를 통한 글꼴 데이터 접근
GetFontData 함수를 사용하면 장치 컨텍스트(DC)의 현재 논리 글꼴에 해당하는 TrueType 원시 데이터를 조회할 수 있다.
DWORD GetFontData(
HDC hdc,
DWORD tableTag, // 0이면 전체 파일
DWORD offset, // 테이블 내 시작 오프셋
LPVOID buffer, // 수신 버퍼
DWORD size // 버퍼 크기
);
버퍼와 크기를 0과 NULL로 전달하면 필요한 데이터 크기를 반환한다. 이를 활용한 전체 글꼴 데이터 추출 예시:
FontDirectory* ExtractFontData(HDC hdc, DWORD& outSize)
{
outSize = GetFontData(hdc, 0, 0, NULL, 0);
if (outSize == 0) return nullptr;
BYTE* raw = new BYTE[outSize];
if (!raw) return nullptr;
GetFontData(hdc, 0, 0, raw, outSize);
return reinterpret_cast<FontDirectory*>(raw);
}
이 메커니즘은 문서 내에 글꼴을 임베딩하여 다른 환경에서도 동일하게 렌더링되도록 보장하는 데 활용된다. Windows의 스풀러도 원격 프린팅 시 이 방식으로 글꼴을 포함시킨다.
핵심 테이블 상세 분석
글꼴 헤더(head)
head 테이블은 글꼴의 전역 속성을 담고 있다.
typedef struct {
Fixed version; // 0x00010000
Fixed fontRevision; // 제조사 설정 버전
ULONG checksumAdjust; // 체크섬 보정값
ULONG magicNumber; // 0x5F0F3CF5
USHORT flags;
USHORT unitsPerEm; // 16 ~ 16384, 일반적으로 2048
longDateTime created; // 1904년 1월 1일 기준 초
longDateTime modified; // 마지막 수정 시각
FWord xMin, yMin; // 전체 글리프 바운딩 박스
FWord xMax, yMax;
USHORT macStyle;
USHORT lowestRecPPEM; // 최소 가독 픽셀 크기
SHORT fontDirectionHint;
SHORT indexToLocFormat; // 0=short, 1=long 오프셋
SHORT glyphDataFormat; // 0=현재 포맷
} Table_head;
unitsPerEm은 EM 사각형의 그리드 단위 수를 정의하며, 이 값이 클수록 정밀도가 높아진다. timestamp는 1904년 기준 8바이트 값으로 사용하여 Y2K 문제를 회피했다.
최대값 테이블(maxp)
래스터라이저가 메모리를 사전 할당할 수 있도록 글꼴의 최대 복잡도를 기술한다.
typedef struct {
Fixed version;
USHORT glyphTotal; // 글리프 전체 개수
USHORT maxPointsSimple; // 비복합 글리프 최대 점 수
USHORT maxContoursSimple; // 비복합 글리프 최대 윤곽선 수
USHORT maxPointsComposite; // 복합 글리프 최대 점 수
USHORT maxContoursComposite; // 복합 글리프 최대 윤곽선 수
USHORT zoneCount; // 1 또는 2 (twilight zone 사용 시)
USHORT maxTwilightPoints; // Z0 영역 최대 점 수
USHORT maxStorage; // 스토리지 영역 슬롯 수
USHORT maxFunctionDefs; // FDEF 개수
USHORT maxStackDepth; // 스택 최대 깊이
USHORT maxInstructionSize; // 최대 명령어 바이트 수
USHORT maxComponentRefs; // 참조하는 최상위 컴포넌트 수
USHORT maxComponentDepth; // 재귀 최대 깊이
} Table_maxp;
문자 매핑(cmap)
cmap은 문자 코드에서 글리프 인덱스로의 변환을 정의하며, 여러 플랫폼과 인코딩 체계를 지원하기 위해 서브테이블을 포함한다.
typedef struct {
USHORT platformID;
USHORT encodingID;
ULONG subtableOffset;
} EncodingRecord;
// Unicode 범위 정보 조회
DWORD GetFontUnicodeRanges(HDC hdc, LPGLYPHSET glyphSet);
DWORD GetGlyphIndices(HDC hdc, LPCTSTR str, int len, LPWORD indices, DWORD flags);
GetFontUnicodeRanges는 지원하는 유니코드 범위를 반환하며, 일반적으로 두 단계 호출 패턴(크기 조회 → 실제 데이터 획득)이 필요하다.
GLYPHSET* FetchUnicodeCoverage(HDC hdc)
{
DWORD bytesNeeded = GetFontUnicodeRanges(hdc, NULL);
if (bytesNeeded == 0) return NULL;
GLYPHSET* result = (GLYPHSET*) new BYTE[bytesNeeded];
result->cbThis = bytesNeeded;
GetFontUnicodeRanges(hdc, result);
return result;
}
위치 인덱스(loca)
loca 테이블은 글리프 인덱스를 glyf 테이블 내 바이트 오프셋으로 변환한다. n개 글리프에 대해 n+1개의 엔트리를 저장하며, 마지막 엔트리는 데이터 길이 계산용이다. 오프셋은 short(×2) 또는 long 형식으로 저장될 수 있다.
글리프 데이터(glyf)
glyf 테이블은 각 글리프의 윤곽선 정보를 담는 핵심 테이블이다.
typedef struct {
SHORT contourCount; // 음수면 복합 글리프
FWord bboxXMin, bboxYMin;
FWord bboxXMax, bboxYMax;
} GlyphHeader;
단순 글리프의 데이터 레이아웃:
USHORT endPtsOfContours[contourCount]; // 각 윤곽선 종점 인덱스
USHORT instructionLength;
BYTE instructions[instructionLength];
BYTE flags[]; // 가변 길이, 각 점의 속성
BYTE xCoordinates[]; // 가변 길이, 부호화된 상대 좌표
BYTE yCoordinates[]; // 가변 길이
flags의 각 비트 의미:
enum GlyphFlags {
ON_CURVE = 0x01, // 1: 곡선 위, 0: 곡선 외(제어점)
REPEAT_FLAG = 0x08, // 다음 바이트가 반복 횟수
X_SHORT = 0x02, // 1: 1바이트, 0: 2바이트 또는 동일
X_SAME = 0x10, // X_SHORT와 조합하여 부호/동일 여부
Y_SHORT = 0x04,
Y_SAME = 0x20
};
좌표는 상대값으로 인코딩되어 있어 첫 점은 (0,0) 기준, 이후 점은 이전 점과의 차이로 표현된다. 플래그를 통해 각 좌표가 0바이트(동일), 1바이트(부호 포함), 2바이트 중 어떤 형식인지 판별한다.
TrueType은 2차 베지어 곡선을 사용하며, on-off-off-on 패턴은 암묵적으로 중간점을 추가하여 두 개의 2차 곡선으로 해석된다.
복합 글리프
복합 글리프는 여러 기본 글리프를 변환하여 조합한다. 각 컴포넌트는 플래그, 글리프 인덱스, 변환 행렬로 정의된다.
int KTrueType::DecodeComposite(const void* data, KCurve& output) const
{
KDataStream stream(data);
unsigned flags;
int total = 0;
do {
flags = stream.ReadU16();
unsigned glyphIdx = stream.ReadU16();
short arg1, arg2;
if (flags & ARGS_ARE_WORDS) {
arg1 = stream.ReadS16();
arg2 = stream.ReadS16();
} else {
arg1 = (signed char) stream.ReadU8();
arg2 = (signed char) stream.ReadU8();
}
float m11 = 1.0f, m12 = 0.0f, m21 = 0.0f, m22 = 1.0f;
float dx = 0.0f, dy = 0.0f;
if (flags & HAS_SCALE) {
m11 = m22 = stream.ReadF2Dot14();
} else if (flags & HAS_XY_SCALE) {
m11 = stream.ReadF2Dot14();
m22 = stream.ReadF2Dot14();
} else if (flags & HAS_2X2) {
m11 = stream.ReadF2Dot14();
m12 = stream.ReadF2Dot14();
m21 = stream.ReadF2Dot14();
m22 = stream.ReadF2Dot14();
}
if (flags & ARGS_ARE_XY_VALUES) {
dx = arg1;
dy = arg2;
XFORM xf = { m11, m12, m21, m22, dx, dy };
total += DecodeGlyph(glyphIdx, output, &xf);
}
} while (flags & MORE_COMPONENTS);
if (flags & HAS_INSTRUCTIONS) {
unsigned instrLen = stream.ReadU16();
stream.Skip(instrLen); // 복합 글리프 명령어 (미사용)
}
return total;
}
변환 행렬은 2.14 고정소수점 형식으로 저장되며, 평행이동은 정수 단위로 처리된다. USE_MY_METRICS 플래그는 특정 컴포넌트의 메트릭을 강제 적용하는 데 사용된다.
힌팅과 그리드 맞춤
단순 윤곽선 스캔 변환만으로는 저해상도에서 품질이 저하된다. TrueType은 그리드 맞춤(grid fitting) 기로 이를 해결하며, 핵심 요소는 다음과 같다.
- cvt(Control Value Table): 줄 간격, 기준선, 줄기 너비 등 설계자가 정의한 기준값. 포인트 크기에 따라 스케일링됨
- 글리프 명령어: 각 글리프에 첨부된 스택 기반 가상머신 프로그램. cvt 값을 참조하여 윤곽선 점들을 조정
명령어는 스택 머신에서 실행되며, 픽셀 경계에 맞춰 점을 이동시키고, 줄기 두께를 일정하게 유지하며, 대칭성과 세리프 등 디자인 의도를 보존한다. 이는 Forth나 Java VM과 유사한 실행 모델을 따른다.