유니코드 기본
코드 포인트, BMP·보조 평면, UTF-16 서로게이트 쌍, JavaScript·에디터에서의 처리
에디터와 IME가 다루는 텍스트는 유니코드로 표현된다. 코드 포인트·인코딩·서로게이트 쌍을 구분해야 문자열 길이·커서·선택을 올바르게 처리할 수 있다.
1. 코드 포인트와 스칼라 값
**코드 포인트(code point)**는 유니코드에서 한 문자에 부여한 번호다. 범위는 U+0000 ~ U+10FFFF(십진 0 ~ 1,114,111). 표기할 때는 U+ 뒤에 16진수 4~6자리를 쓴다(예: U+0041 = ‘A’, U+AC00 = ‘가’, U+1F600 = ’😀’).
**스칼라 값(scalar value)**은 유니코드 명세에서 “유효한” 코드 포인트를 이르는 말이다. U+D800U+DFFF 구간은 서로게이트 전용으로 예약되어 있어 문자에 할당되지 않으며, 이 구간을 제외한 나머지가 스칼라 값이다. 실무에서는 “한 코드 포인트 = 한 스칼라 값”으로 보면 된다(U+D800U+DFFF는 단독으로 문자를 나타내지 않음).
2. BMP와 보조 평면
**기본 다국어 평면(Basic Multilingual Plane, BMP)**은 U+0000 ~ U+FFFF 구간이다. 한 코드 포인트가 16비트 하나로 표현 가능하다.
**보조 평면(Supplementary Planes)**은 U+10000 ~ U+10FFFF 구간이다. 한글 완성형(U+AC00~U+D7A3), 라틴·히라가나·한자 대부분은 BMP 안에 있고, 많은 이모지·일부 한자·기호는 보조 평면에 있다. 보조 평면 문자는 UTF-16에서 16비트 코드 유닛 두 개(서로게이트 쌍)로 인코딩된다.
3. 서로게이트 쌍 (Surrogate pairs)
UTF-16은 16비트 단위(코드 유닛)로 텍스트를 저장한다. BMP(U+0000~U+FFFF)는 코드 유닛 1개로 표현하고, U+10000 ~ U+10FFFF는 코드 유닛 2개로 표현한다. 이 두 개의 코드 유닛을 서로게이트 쌍이라 한다.
- High surrogate: U+D800 ~ U+DBFF (1,024개)
- Low surrogate: U+DC00 ~ U+DFFF (1,024개)
코드 포인트 U( U+10000 ≤ U ≤ U+10FFFF )를 UTF-16으로 바꾸는 공식은 다음과 같다.
U' = U - 0x10000(0 ~ 0xFFFFF)- High surrogate =
0xD800 + (U' >> 10) - Low surrogate =
0xDC00 + (U' & 0x3FF)
역으로, high surrogate H, low surrogate L이 연달아 오면 코드 포인트는
0x10000 + ((H - 0xD800) << 10) + (L - 0xDC00) 이다.
서로게이트 구간(U+D800~U+DFFF)은 단독으로는 유니코드 문자를 나타내지 않는다. High만 있거나 low만 있거나, 순서가 바뀌면 잘못된 바이트 시퀀스로 간주된다.
4. JavaScript/웹에서의 영향
ECMAScript의 문자열은 UTF-16 코드 유닛 단위다. 따라서 보조 평면 문자(이모지 등)는 길이 2로 잡힌다.
"😀".length === 2; // true
"가".length === 1; // true (U+AC00은 BMP)
str[i]처럼 인덱스로 접근하면 코드 유닛 한 개만 반환한다. 서로게이트 쌍 한 글자 중간 인덱스면 high 또는 low surrogate만 나오므로, 그대로 출력하면 깨진 문자()가 나올 수 있다.
코드 포인트 단위로 다루려면 다음을 사용한다.
for...of: 문자열을 코드 포인트(확장 그래핀은 아님) 단위로 순회한다.Array.from(str): 코드 포인트 단위로 분할한 배열을 만든다.String.prototype.codePointAt(i): 인덱스i에서 시작하는 코드 포인트를 반환한다(서로게이트 쌍이면 21비트 값).String.fromCodePoint(cp): 코드 포인트cp를 문자열로 만든다.
5. IME·에디터 관점
커서 위치·선택·한 글자 삭제·substring을 구현할 때 “한 글자”를 무엇으로 볼지 정해야 한다.
- 코드 유닛:
length,str[i],substring(start, end)가 기준으로 하는 단위. 서로게이트 쌍을 반으로 쪼개면 잘못된 문자가 나온다. - 코드 포인트: 이모지(단일 코드 포인트)나 BMP 문자를 “한 단위”로 셀 수 있다. 서로게이트 쌍을 쪼개지 않게 할 수 있다.
- 그래핀 클러스터(grapheme cluster): 사용자가 “한 글자”로 인식하는 단위. 예:
é(e + combining acute), 이모지 + variation selector, ZWJ 시퀀스(👨👩👧👦 등). 여러 코드 포인트가 묶여 한 개로 보일 수 있다.
에디터에서는 최소한 서로게이트 쌍을 쪼개지 않도록 해야 한다. Backspace/Delete로 “한 글자” 지울 때 코드 유닛 1개만 지우면 이모지가 반만 지워져 깨진 문자가 남는다. 커서를 “한 글자” 단위로 이동하려면 코드 포인트 단위(또는 그래핀 단위)로 경계를 계산해야 한다. 그래핀 단위 분할이 필요하면 Intl.Segmenter (granularity: 'grapheme')를 사용할 수 있다. 여러 코드 포인트로 이루어진 사용자 인식 문자(이모지 조합 등)는 이모지 문서를 참고하면 된다.
6. 인코딩 요약
| 인코딩 | 단위 | BMP 문자 | 보조 평면 문자(U+10000~) |
|---|---|---|---|
| UTF-8 | 1~4바이트 | 1~3바이트 | 4바이트 |
| UTF-16 | 16비트 코드 유닛 | 1개 | 2개(서로게이트 쌍) |
유니코드 정규화(NFC, NFD)는 같은 문자를 다른 바이트/코드 포인트 시퀀스로 표현할 수 있게 한다. 한글·유럽어에서의 NFC/NFD 동작은 한글 조합 원리, 유럽권 조합을 참고하면 된다.