# 터미널용 Glyph Protocol 소개

> Clean Markdown view of GeekNews topic #28934. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=28934](https://news.hada.io/topic?id=28934)
- GeekNews Markdown: [https://news.hada.io/topic/28934.md](https://news.hada.io/topic/28934.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-04-27T10:44:02+09:00
- Updated: 2026-04-27T10:44:02+09:00
- Original source: [rapha.land](https://rapha.land/introducing-glyph-protocol-for-terminals/)
- Points: 1
- Comments: 0

## Topic Body

- 터미널 애플리케이션에서 커스텀 아이콘을 렌더링하려면 **패치된 폰트(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 ; &lt;verb&gt; [ ; key=value ]* [ ; &lt;payload&gt; ] 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=&lt;nonzero&gt;; reason=&lt;code&gt;`로 응답  
  
### `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 입력기가 미커버 한자를 위한 글리프를 전송하거나 언어별 도구가 글리프를 오버라이드하는 사용 사례는 차단됨 → 명시적 사용자 수준 옵트인, 서명된 기능, 신뢰 출처 플래그 등으로 **피싱을 재개하지 않으면서 이 사례를 열 수 있는 형태**에 대한 의견 요청

## Comments



_No public comments on this page._
