터미널 애플리케이션에서 커스텀 아이콘을 렌더링하려면 패치된 폰트(Nerd Font 등)를 설치 해야 하는 기존 문제를 해결하기 위해 새로운 터미널 프로토콜이 등장
Glyph Protocol은 애플리케이션이 런타임에 벡터 글리프를 터미널에 직접 등록 하고, 특정 코드포인트의 렌더링 가능 여부를 질의할 수 있는 구조
글리프 데이터는 TrueType의 glyf 포맷 을 사용하며, 터미널이 이미 보유한 래스터라이저를 그대로 활용해 새로운 의존성 없이 구현 가능
등록 대상 코드포인트를 Unicode Private Use Area(PUA) 로 제한해 피싱·시각 위조 공격을 원천 차단
Rio 터미널 에서 첫 구현이 진행 중이며, Bubble Tea·Ratatui·Ink 등 주요 TUI 프레임워크용 예제 코드가 공개된 상태
기존 문제: 패치 폰트 의존성
터미널 에디터, 프롬프트, TUI가 아이콘을 제대로 표시하려면 사용자가 Nerd Font이나 Powerline 같은 패치 폰트를 직접 설치 해야 함
폰트를 설치하지 않으면 아이콘 자리에 tofu(□) 가 표시되며, 패치 폰트는 개당 6~12MB 수준의 대용량
JetBrainsMono Nerd Font Regular 약 7.8MB, FiraCode Nerd Font Regular 약 10.4MB, 전체 심볼 아카이브는 약 60MB
애플리케이션 개발자는 원하는 글리프를 직접 배포할 방법이 없고, 사용자가 올바른 폰트·버전·코드포인트 매핑 을 갖추고 있기를 기대할 수밖에 없는 구조
Glyph Protocol 핵심 기능
두 가지 핵심 동작 지원
커스텀 글리프 등록 : 애플리케이션이 Unicode PUA 코드포인트를 선택하고, 벡터 아웃라인을 터미널에 직접 전송해 런타임에 등록
코드포인트 질의 : 특정 코드포인트가 시스템 폰트에 의해 커버되는지, 세션 내 등록에 의해 커버되는지, 둘 다인지, 아무것도 아닌지를 질의
사용자가 Nerd Font을 이미 설치했으면 질의를 통해 글리프 전송을 생략 하고, 미설치 상태에서도 애플리케이션이 아웃라인을 직접 전송해 아이콘이 정상 표시
프로토콜 구조
전송 방식 (Transport)
OSC 대신 APC(Application Program Command) 를 사용
APC는 애플리케이션 정의 명령을 위해 설계되었으며, 미구현 터미널은 해당 시퀀스를 안전하게 무시 가능
OSC는 단일 10진수 정수를 명령 식별자로 사용하는 전역 네임스페이스 를 공유하므로 충돌 위험이 있으나, APC는 자체 식별 구조로 이 문제가 없음
식별자 (Identifier)
모든 Glyph Protocol 메시지에 25a1(U+25A1, WHITE SQUARE) 코드포인트가 접두사로 붙음
이 문자는 글리프가 없을 때 터미널이 그리는 tofu의 표준 심볼
프레이밍 형식: ESC _ 25a1 ; <verb> [ ; key=value ]* [ ; <payload> ] ESC \
4개의 verb: s(support), q(query), r(register), c(clear)
Support (s): 터미널 지원 여부 확인
터미널이 어떤 페이로드 포맷과 프로토콜 버전을 지원하는지 확인하는 용도
Glyph Protocol 존재 자체를 감지 하는 표준적 방법이기도 함
응답의 fmt는 비트필드 로, 각 비트가 하나의 페이로드 포맷을 의미
1 = glyf : TrueType 단순 글리프, v1에서 필수
2 = colrv0 : 레이어드 플랫 컬러 글리프(OpenType COLR v0), v1.2에서 추가
4 = colrv1 : 그래디언트·트랜스폼이 포함된 전체 페인트 그래프(OpenType COLR v1), v1.2에서 추가
응답이 오면 프로토콜 지원 확인, 타임아웃이면 미지원, fmt=0이면 프로토콜은 구현했으나 포맷 미지원(완전성을 위한 정의)
Query (q): 코드포인트 렌더링 가능 여부 질의
특정 코드포인트가 렌더링 가능한지 질의하고 status 값으로 응답
0(free ): 아무것도 렌더링하지 않음, tofu 표시
1(system ): 시스템 폰트가 커버
2(glossary ): 세션 내 등록이 커버
3(both ): 양쪽 다 커버, 등록이 시스템 폰트를 렌더링 시 덮어씀
TUI가 시스템에 이미 해당 아이콘이 있으면 등록을 건너뛰고 , 없으면 커스텀 코드포인트를 등록해 우아하게 폴백 가능
Register (r): 글리프 등록
애플리케이션이 PUA 코드포인트를 선택하고 base64 인코딩된 glyf 아웃라인 을 전송해 등록
주요 파라미터
cp: 대상 코드포인트(hex), 반드시 3개의 Unicode PUA 범위 내여야 함(U+E000–U+F8FF, U+F0000–U+FFFFD, U+100000–U+10FFFD), 범위 밖이면 reason=out_of_namespace로 거부
fmt: 페이로드 포맷, v1에서는 glyf만 정의되며 기본값이므로 대부분 생략 가능
upm: units per em, 아웃라인 좌표 공간 정의, 기본값 1000
같은 cp에 두 번째 r을 보내면 이전 등록을 덮어씀
오류 시(비PUA 코드포인트, 잘못된 페이로드, 컴포지트 글리프 등) status=<nonzero>; reason=<code>로 응답
glyf 포맷 선택 이유
벡터인 이유
글리프는 사진이 아니므로 고정 해상도가 없음 : 같은 아이콘이 12px 밀도 높은 TUI와 24px HiDPI 디스플레이 모두에서 렌더링 필요
래스터 글리프는 특정 해상도에 고정되어 HiDPI에서 흐릿하거나 소형 셀에서 판독 불가
glyf 구체적 선택 이유
텍스트를 렌더링하는 모든 터미널에 이미 glyf 래스터라이저 가 링크되어 있음(FreeType, swash, ttf-parser, fontdue, allsorts 등)
Glyph Protocol 채택 시 터미널 측에 새로운 의존성이 전혀 추가되지 않음
SVG를 채택하면 resvg를 끌어오거나 새 XML+path 파서를 작성해야 함
와이어 크기도 작음 : 일반 아이콘이 150~400바이트의 glyf 데이터로, 동등한 SVG 대비 2~3배 작음 (base64 오버헤드 포함)
50개 아이콘 등록 시 약 13KB vs 35KB 차이, tmux 파이프나 모바일 SSH 링크에서 체감 가능
glyf 간략 설명
glyf 레코드는 글리프를 닫힌 윤곽선(contour)의 집합 으로 저장
각 점은 on-curve 또는 off-curve 메타데이터 1비트를 가짐
on-curve 두 점 연속 → 직선
on-curve 사이에 off-curve 점 → 2차 베지어 곡선
off-curve 두 점 연속 → 중간점에 암묵적 on-curve 점 존재(압축 트릭)
좌표는 EM 스퀘어 내 정수 그리드 위치, upm=1000에서 (500, 900)은 절반 너비·90% 높이
닫힌 삼각형 약 30바이트, 30개 점 아이콘 약 200바이트
프로토콜이 정의하는 glyf 서브셋
단순 글리프만 허용 : 컴포지트 글리프, 다른 글리프 참조, 폰트 수준 컨텍스트 불가
OpenType 스펙에 정의된 표준 플래그 인코딩 사용
힌팅 명령 없음 : 힌팅은 폰트 전체 제어값 세트를 전제로 하며 여기에는 해당 없음
좌표 공간은 upm으로 정의, 기본값 1000, 등록별 오버라이드 가능
컬러·스케일링·저작
glyf 아웃라인은 색상 정보가 없으며 현재 전경색 으로 렌더링 → Nerd Font 상속 케이스와 동일
컬러 글리프는 별도 페이로드 포맷 fmt=colrv0 / fmt=colrv1로 지원
upm 값이 글리프 좌표 공간을 정의하고, 터미널이 렌더 시 셀에 매핑 → 폰트 크기 변경 시 재등록 불필요
대부분의 개발자는 glyf 바이트를 직접 작성하지 않고 SVG에서 빌드 타임에 변환 : fonttools의 ttx/pens 인터페이스 활용 가능, svg2glyf 헬퍼도 Rio 레퍼런스 구현과 함께 배포 예정
수명 및 용량
각 터미널 세션은 3개 PUA 범위 내 코드포인트로 키잉된 최대 1024개 동시 등록 을 담는 glossary 보유
등록은 세션 지속 기간 동안 유효
1025번째 글리프 등록 시 FIFO 순서로 가장 오래된 등록을 축출 → "glossary full" 오류 없음
무음 축출을 허용할 수 없는 애플리케이션은 출력 전 해당 코드포인트를 질의해야 함
실제 예제: 빈 PUA에 아이콘 등록
U+100000(Supplementary PUA-B의 첫 코드포인트)에 스타일라이즈드 아웃라인을 등록하는 전체 파이프라인 예시
fontTools 를 SVG→glyf 변환기로 사용
TTGlyphPen으로 아웃라인을 그린 후 base64 인코딩하여 APC 시퀀스로 전송, 이후 해당 코드포인트 출력
일반 20포인트 아이콘의 glyf 페이로드 약 150바이트 , APC 래핑·base64 포함 약 250바이트
SVG 자산이 있는 개발자를 위해 svg2glyf 헬퍼 제공 예정 → 2줄로 등록 완료
대량 등록용 옵션: reply=
기본적으로 터미널은 모든 r에 대해 ACK 응답을 보내지만, 100개 글리프를 등록하는 시작 훅에서는 100개의 큐잉된 ACK가 PTY로 흘러나와 셸에 쓰레기로 표시 되는 문제 발생
3단계 제어
reply=1(기본): 성공·실패 모두 응답, 대화형 단건 등록용
reply=2: 실패만 응답, 성공은 무음, 대량 등록에서 오류만 감지 할 때 사용
reply=0: 아무 응답 없음, fire-and-forget, 시작 훅처럼 응답을 읽을 주체가 없을 때 사용
알 수 없는 값은 reply=1로 자동 폴백되므로 향후 확장 시 하위 호환 유지
Clear (c): 등록 해제
에디터 종료 시 터미널 기본값 복원, TUI 테마 전환, 디버깅 시 사용
단일 슬롯 해제: cp 파라미터로 특정 코드포인트 지정
전체 glossary 해제 : cp 생략
빈 슬롯 해제는 오류가 아닌 no-op , status=0 응답
cp는 PUA 범위 내여야 하며, 범위 밖이면 reason=out_of_namespace 반환
v1에 의도적으로 포함하지 않은 기능
비PUA 코드포인트 등록 불가 : 3개의 Unicode PUA 범위로 제한
리거처 없음 : 단일 코드포인트에만 등록 적용, 시퀀스 키 치환은 v1 범위 밖, 프로그래밍 리거처(-> → ⟶)는 이미 OpenType 폰트가 처리
세션 간 지속성 없음 : 매 실행마다 글리프를 새로 전송, 터미널을 폰트 캐시로 전환하는 것을 방지
크로스 애플리케이션 공유 없음 : 각 터미널 세션이 자체 glossary 소유, IPC나 데몬 없음
v1 glyf 페이로드에 컬러 글리프 없음 : 전경색으로 렌더링, 컬러는 v1.2의 colrv0/colrv1로 분리
이 기능들은 필요 시 나중에 추가 가능하나, 한번 추가되면 쉽게 제거할 수 없으므로 의도적으로 제외
PUA 제한의 보안적 근거
PUA 제한은 API 미학이 아니라 프로토콜을 기본 활성화해도 안전하게 만드는 속성
임의 코드포인트 등록을 허용하면: U+0061(a)에 o 모양 글리프를 등록해 bad.com이 bod.com으로 보이게 할 수 있음
셀 버퍼는 여전히 bad.com이므로 복사·붙여넣기 시 바이트는 정직하지만, 사용자가 읽는 것은 거짓
모든 터미널 프로그램에 피싱 프리미티브 가 생기며, 같은 세션에서 이후 실행되는 프로그램에도 영향 지속
PUA로 제한하면 이 공격 유형이 기계적으로 불가능 : 사용자는 PUA 코드포인트를 타이핑하지 않으며, 파일명·URL·명령·변수명·로그에 PUA 코드포인트가 포함되지 않음
Nerd Font이 관례로 확립한 신뢰 모델 (커스텀 글리프는 예약 범위에만 존재, 실제 텍스트 위에는 불가)을 프로토콜 수준에서 강제
추가 보안 속성
셀 버퍼가 권위적 : 선택·복사·검색·하이퍼링크 감지·셸 히스토리 등은 애플리케이션이 출력한 코드포인트를 반환해야 하며, "보는 것과 복사하는 것이 다른" 함정 생성 불가
세션 격리 : 두 탭이 U+E0A0에 서로 다른 branch 아이콘을 독립적으로 등록 가능, 한 탭의 등록이 다른 탭의 렌더링에 영향 불가
기존 방식과의 비교
Kitty Image Protocol (KIP) + Unicode Placeholders
KIP의 Unicode placeholder를 통해 Glyph Protocol을 근사적으로 구현 가능하나, 통합이 까다롭고 placeholder를 구현하는 터미널이 Kitty, Ghostty, Rio뿐
KIP는 이미지 프로토콜 이며 글리프는 이미지가 아님
사용별 비용 : 화면에 200번 재사용되는 글리프(테이블 테두리, 불릿 마커 등)마다 200개의 이미지 참조를 배치해야 하며 레이아웃·합성 비용 발생. Glyph Protocol은 코드포인트 등록 후 폰트 속도로 렌더링
네이티브 해상도 없음 : glyf 아웃라인은 픽셀 크기가 없어 폰트 크기 변경 시 자동 적용. KIP는 특정 크기의 비트맵을 전송하므로 크기 변경 시 흐릿해지거나 재업로드 필요 , 폰트 크기 변경 감지 수단도 부재
전경색 상속 : 단색 glyf 아웃라인은 셀의 현재 전경색으로 렌더링되어 테마 자동 적용 . 이미지는 자체 픽셀이므로 텍스트 컬러링에 참여하지 않음
DEC DECDLD / DRCS
VT220이 1983년에 도입한 Dynamically Redefinable Character Sets 로, 형태상 Glyph Protocol과 유사
두 가지 핵심 문제
비트맵 방식 : 터미널의 현재 셀 크기에 맞춘 픽셀 그리드를 업로드하므로, 폰트 크기 변경·HiDPI·4K 모니터 전환 시 블록 픽셀이 확대되거나 축소됨 . 고정 10×20 CRT 시대의 방식으로 현대의 다양한 셀 크기에 부적합
네임스페이스 제한 없음 : DECDLD는 GL 범위(a, b, c가 있는 영역)에 매핑될 수 있는 문자셋을 덮어쓸 수 있어, 신뢰할 수 없는 프로그램이 a의 렌더링을 재정의 가능 → 현대 터미널이 DECDLD 활성화를 꺼리는 가장 큰 이유
Rio 터미널에서의 구현 현황
Glyph Protocol은 Rio 터미널 의 main 브랜치에서 이미 사용 가능하며, 5월 중 정식 랜딩 예정 → 최초 구현
전체 스펙이 릴리스와 함께 공개되며, 글리프 등록·터미널 질의를 위한 예제 코드 포함
작동 예제는 raphamorim/glyph-protocol-examples 저장소에서 확인 가능: Bubble Tea, Ratatui, Ink용 샘플 통합 포함
프로토콜은 아직 업데이트될 가능성이 있으며, 더 많은 애플리케이션과 터미널이 참여하면서 메시지 형태·질의 응답·에지 케이스가 변경될 수 있음 → 현재 빌드 시 이동 대상으로 취급하고 구현 버전 고정 권장
다른 터미널 에뮬레이터들의 채택을 기대하며, 생태계 전체의 이점이 크고 구현 범위는 의도적으로 작게 유지
커뮤니티 오픈 질문
폰트 크기 변경 알림이 프로토콜 범위에 포함되어야 하는가? : Glyph Protocol 자체는 아웃라인이 해상도 독립적이므로 이 문제를 회피하나, 이미지와 글리프를 함께 구성하는 TUI는 셀 메트릭 변경을 알 방법이 폴링 외에 없음 → resize 또는 metrics-changed 알림이 범위 내인지 범위 초과인지에 대한 논의
비PUA 등록을 허용하는 책임 있는 방법이 있는가? : PUA 전용 규칙이 기본 안전성을 보장하지만, CJK 입력기가 미커버 한자를 위한 글리프를 전송하거나 언어별 도구가 글리프를 오버라이드하는 사용 사례는 차단됨 → 명시적 사용자 수준 옵트인, 서명된 기능, 신뢰 출처 플래그 등으로 피싱을 재개하지 않으면서 이 사례를 열 수 있는 형태 에 대한 의견 요청