이모지
이모지의 유니코드 표현, 서로게이트와의 관계, 에디터·IME에서의 처리
이모지는 사용자 눈에는 “한 글자”지만, 유니코드에서는 단일 코드 포인트일 수도 있고 여러 코드 포인트의 조합일 수도 있다. 에디터와 IME에서 커서·선택·삭제를 올바르게 하려면 이 구조를 알아 두는 것이 좋다.
1. 이모지의 유니코드 표현
이모지는 다음처럼 표현된다.
- 단일 코드 포인트: 예) U+1F600 (😀), U+1F4A9 (💩). 대부분 U+10000 이상이라 BMP 밖에 있다.
- 기본 문자 + Variation Selector: 예) U+2600 (☀) + U+FE0F → 이모지 스타일로 렌더링. 코드 포인트 2개가 한 “글자”를 이룬다.
- ZWJ 시퀀스(Zero Width Joiner): U+200D로 여러 코드 포인트를 이어 한 이모지로 만든다. 예) 👨(U+1F468) + U+200D + 👩(U+1F469) + U+200D + 👧(U+1F467) + U+200D + 👦(U+1F466) → 👨👩👧👦. 코드 포인트가 여러 개이므로 UTF-16에서는 서로게이트 쌍도 여러 개다.
2. 서로게이트와의 관계
대부분의 이모지 코드 포인트는 U+10000 ~ U+10FFFF에 있으므로, UTF-16에서는 서로게이트 쌍 1개(코드 유닛 2개)로 인코딩된다. ZWJ 시퀀스나 variation selector가 붙으면 코드 포인트 수만큼 서로게이트 쌍(또는 BMP 문자)이 이어진다.
JavaScript에서 "😀".length === 2인 이유는 UTF-16 코드 유닛이 2개이기 때문이다. 서로게이트 쌍의 정의·계산·에디터에서의 주의사항은 유니코드 기본의 “서로게이트 쌍”·“IME·에디터 관점” 섹션을 참고하면 된다.
3. 에디터·IME에서의 처리
사용자 입장에서는 이모지 하나를 “한 글자”로 본다. 하지만 내부적으로는 여러 코드 포인트·여러 코드 유닛일 수 있으므로, 다음을 지키는 것이 좋다.
- 커서 이동: “한 글자” 단위로 옮길 때 그래핀 클러스터(또는 확장 그래핀 클러스터) 경계를 사용한다. 코드 유닛 단위로만 이동하면 이모지 한가운데에 커서가 서거나, ZWJ 이모지가 쪼개진다.
- 한 글자 삭제: Backspace/Delete 시 삭제할 범위를 그래핀(또는 코드 포인트) 단위로 정한다. 코드 유닛 1개만 지우면 서로게이트가 깨져 같은 대체 문자만 남을 수 있다.
- 선택: 드래그로 “한 글자” 선택 시 같은 단위(그래핀)로 선택 범위를 맞추면, 이모지가 반만 선택되는 일을 줄일 수 있다.
웹에서는 Intl.Segmenter를 사용해 그래핀 단위로 나눌 수 있다. Intl.Segmenter('ko', { granularity: 'grapheme' }) 등으로 세그먼터를 만든 뒤 segmenter.segment(str)로 반복하면 “사용자 인식 문자” 경계를 얻을 수 있다.
4. 참고
- 이모지가 할당된 유니코드 블록 예: Miscellaneous Symbols and Pictographs (U+1F300
U+1F5FF), Emoticons (U+1F600U+1F64F), Supplemental Symbols and Pictographs (U+1F900~U+1F9FF) 등. - Variation Selector: U+FE0F (EMOJI presentation), U+FE0E (text presentation) 등. 기본 문자가 이모지/텍스트 두 스타일을 가질 때 선택한다.
- ZWJ: U+200D (Zero Width Joiner). 인접한 문자를 하나의 글자처럼 묶어 렌더링할 때 사용한다.
- 유니코드 이모지 관련 명세: Unicode Technical Report #51 (Unicode Emoji), UAX #29 (Unicode Text Segmentation).