문서
폰꾸는 한글 음절을 초성·중성·종성으로 나눠 다시 조립해 그리는 브라우저 런타임입니다. 이 문서는 스크립트 한 줄로 붙이는 법부터 글꼴 고르기, 페이지 CSS로 꾸미기, 움직임, 평문 모드, 글꼴 만들기, JavaScript API까지 한 페이지에 모았습니다. 이 페이지의 제목도 모두 폰꾸로 그렸습니다.
시작하기
설치
폰꾸는 세 파일로 동작합니다. 런타임 font-kku.js, 바탕 CSS font-kku.css, 그리고 쓸 글꼴의
CSS profiles/<글꼴>.css입니다. 빌드 단계는 없습니다. 어떤 방법으로 붙이든 이 세 가지가 페이지에
들어오면 런타임이 전용 태그를 등록하고 바탕 요소를 모두 그립니다.
한 줄 설치
data-fonts 속성을 단 <script> 하나로 런타임을 불러오면, 런타임이 스크립트 주소를
기준으로 font-kku.css와 나열한 글꼴의 profiles/<이름>.css를 함께 붙입니다.
<script src="https://font.tty.link/lib/font-kku.js" data-fonts="sebul neon"></script>
<font-kku font="neon" size="64">을지로 네온</font-kku>
- 글꼴 이름은 공백이나 쉼표로 나눕니다. 비워 두면(
data-fonts="") 바탕 CSS만 붙습니다. - 여기 적은 글꼴은 처음부터 받습니다(미리 받기). 적지 않은 글꼴은 필요할 때 받습니다 — 그 글꼴을 쓰는 요소가 화면 가까이 오면 런타임이 CSS 한 장(그림 자소면 자소판도)을 그때 받습니다. 첫 화면에 보이는 글꼴만 적으면 됩니다. 끄려면
data-autoload="false". - 칠만 다른 글꼴(네온·금박 …)은
font="sebul" class="kku-look-neon"처럼 꾸밈 클래스로 쓰면 CSS를 덜 받습니다. - jsDelivr에서
font-kku.min.js로 불러오면 CSS·확장도.min압축본으로 받습니다. - CSS가 도착할 때까지(늦어도 3초) 조립 전 자소가 보이지 않게 가려 둡니다.
- 파일 이름이 없는 CDN 짧은 주소(
https://cdn.jsdelivr.net/npm/font-kku@0.4.0)도 패키지 폴더로 보고 같은 방식으로 풉니다. data-addons="looks motion fx"를 더하면 조합 확장(꾸밈kku-look-*·변주kku-vary-*, 자모 움직임, 상호작용)도 함께 붙습니다 — 확장 붙이기.
npm 공개 전입니다. 패키지를 아직 npm registry에 올리지 않아서 위 CDN 주소는 공개 뒤에 동작합니다.
그 전에는 저장소의 font-kku.js, font-kku.css, profiles/ 폴더를 같은 폴더 구조로
복사해 <script src="./font-kku.js" data-fonts="sebul neon">처럼 상대 주소로 씁니다.
npm · 번들러
패키지는 CommonJS require(), 빌드 없는 ES module 진입점(font-kku.mjs), TypeScript 선언
(font-kku.d.ts, 별도 @types 없음), CSS·글꼴·spec 서브패스를 제공합니다. registry 공개 전에는
저장소에서 tarball을 만들어 같은 경로로 설치합니다.
# registry 공개 전: 저장소에서 tarball을 만들어 설치
npm pack
npm install /path/to/font-kku-0.4.0.tgz
# registry 공개 후
npm install font-kku
번들러에는 스크립트 태그가 없으므로 한 줄 설치(data-fonts)가 동작하지 않습니다. 바탕 CSS와 글꼴 CSS를 직접
import합니다. render·renderText 같은 이름 import도 됩니다.
import FontKku from "font-kku";
import "font-kku/font-kku.css";
import "font-kku/profiles/sebul.css";
import "font-kku/profiles/neon.css";
FontKku.renderText("안녕하세요").forEach((line) => console.log(line));
CSS를 직접 연결하기
한 줄 설치 대신 태그를 따로 쓸 때는 바탕 CSS → 글꼴 CSS → 런타임 순서로 둡니다. 글꼴 CSS가 첫 그리기
뒤에 도착해도(비동기 <link>, 프레임워크의 동적 주입) 런타임이 스타일시트 load 이벤트에서
다시 맞춥니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="./font-kku.css" />
<link rel="stylesheet" href="./profiles/sebul.css" />
<script src="./font-kku.js" defer></script>
</head>
<body>
<font-kku font="sebul" size="40">광화문</font-kku>
<h1 class="font-kku" data-font="sebul" data-size="32">안녕하세요</h1>
</body>
</html>
ES module로 쓰기
번들러 없이 module로 쓰려면 font-kku.mjs를 import합니다. module script는 file://에서 막히므로
정적 서버로 엽니다. classic 스크립트와 ES module을 한 페이지에서 섞어도 런타임은 하나만 돕니다.
<link rel="stylesheet" href="./font-kku.css" />
<link rel="stylesheet" href="./profiles/sebul.css" />
<script type="module">
import FontKku, { renderText } from "./font-kku.mjs";
</script>
오프라인 · 사내망
-
인터넷이 막힌 곳에서는
font-kku.js,font-kku.css,profiles/를 같은 폴더 구조로 복사해 상대 주소로 씁니다. 한 줄 설치는 스크립트 주소를 기준으로 CSS를 찾으므로 그대로 동작합니다. -
폰꾸는 원격 웹 글꼴을 부르지 않습니다.
sebul·gothic과 그 파생 글꼴은 방문자 기기에 설치된 한글 글꼴을 쓰고, 그림 자소 글꼴은 패키지에 든 상대 경로 자소판(SVG)만 읽습니다. 글꼴 CDN으로 방문자 요청이 나가지 않습니다. - 일부 브라우저는
file://에서 자소판 그림을 제한합니다. 파일을 더블클릭해 여는 대신 정적 서버로 엽니다.
python3 -m http.server 8000
# http://127.0.0.1:8000/ 에서 페이지를 엽니다
시작하기
바탕 요소와 속성
폰꾸가 그리는 요소를 바탕 요소라고 합니다. 새로 쓰는 장식 문구에는 전용 태그
<font-kku>를, 기존 의미 태그(<h1>, <span> …)를 그대로 둬야 할 때는
클래스 방식 class="font-kku"와 data-* 속성을 씁니다.
<!-- 전용 태그: 일반 속성 -->
<font-kku font="sebul" size="48">도시의 밤</font-kku>
<!-- 클래스 방식: class="font-kku" + data-* (h2 의미는 그대로) -->
<h2 class="font-kku" data-font="gothic" data-size="36">서울</h2>
| 상황 | 쓸 것 |
|---|---|
| 짧은 강조 문구, 포스터, 배너처럼 폰꾸로만 보이는 글 | 전용 태그 <font-kku> |
| 제목처럼 의미 태그를 유지해야 하는 글 (검색·스크린리더) | 클래스 방식 class="font-kku" |
바탕 요소에 넣는 글
- 바탕 요소 안에는 글자만 넣습니다. 런타임이 안의 글자를 자소 노드로 바꾸므로 다른 태그나 아이콘을 섞지 않습니다.
-
줄바꿈과 공백도 글로 읽습니다. 태그 안쪽에서 줄을 바꾸거나 들여 쓰면 빈 줄과 공백이 생기므로
<font-kku>글자</font-kku>처럼 붙여 씁니다. 줄을 나누고 싶을 때만 글 안에서 줄을 바꿉니다 (줄바꿈). - 영문·숫자·문장부호는 조립하지 않고 기기 글꼴로, 한글과 같은 기준선에 그립니다.
속성
전용 태그는 일반 속성을, 클래스 방식은 같은 이름의 data-* 속성을 읽습니다.
| 전용 태그 | 클래스 방식 | 기본값 | 값 |
|---|---|---|---|
font | data-font | sebul | 글꼴 이름(글꼴 고르기). 앞뒤 공백과 대소문자를 무시하고, 옛 별칭 default·classic은 sebul, gothic2는 gothic으로 읽습니다. |
size | data-size | 24px | 숫자(px) 또는 CSS 길이 — 48, 3rem, clamp(28px, 4vw, 48px) |
spacing | data-spacing | 없음 | CSS 길이. 공백 폭(--fy-space-width)과 낱말 간격(--fy-word-gap)을 함께 정합니다. |
wrap-unit | data-wrap-unit | word | glyph · word · line (줄바꿈) |
animate | data-animate | 없음 | rise fade stagger bounce wiggle drift, motion 확장의 assemble drop wave flicker cycle shine scroll-assemble draw 또는 직접 만든 이름 (움직임, 자모 움직임) |
seed | data-seed | 없음 | auto·random(그릴 때마다 새 값), 고정 숫자(0.42), 고정 문자열(poster-v1, 숫자로 해시) |
jamo-set | data-jamo-set | 없음 | 벡터 자소 세트 이름. 그림 자소 배치 글꼴을 자소판 대신 자모마다 SVG로 그립니다(SVG로 자모 그리기) |
stroke-depth | data-stroke-depth | jamo | stroke면 벡터 자소를 획 단위로 쪼갭니다(획 깊이) |
jamo-seeds | data-jamo-seeds | 꺼짐 | 값 없이 쓰는 불리언 속성. "false"·"off"·"none"·"0"이면 꺼집니다. 브라우저 전용 |
profile | data-profile | — | font의 하위 호환 별칭. 새 코드는 font를 씁니다. |
속성은 계속 남는 옵션
속성은 지속 옵션입니다. 값을 바꾸면 런타임이 알아서 다시 그립니다 — 전용 태그는 속성 변경을, 클래스 방식은 자기
data-*와 글 변경을 감시하고, 값이 그대로인 쓰기는 무시합니다. 반면 FontKku.render()·refresh()에
넘긴 JS 옵션은 그 호출에만 적용되므로, 계속 유지할 값은 속성으로 기록합니다. 같은 호출 안에서는
JS 옵션 → 속성 → 기본값 순으로 이깁니다.
size·spacing·animate의 자동 갱신은 원문·글꼴·자모 깊이·해석된 씨앗값과
재료·자모 세트·획 깊이·자모 씨앗 설정이 같으면 기존 자모 트리와 그 노드에 붙은 리스너를 유지합니다.
seed="auto"는 갱신마다 씨앗값이 새로 정해져 이 조건에 해당하지 않을 수 있습니다.
명시적인 render()·refresh() 호출은 관리 DOM을 다시 만듭니다.
// 전용 태그: 속성을 바꾸면 다시 그린다
document.querySelector("font-kku").setAttribute("font", "gothic");
// 클래스 방식: data-*를 바꾸면 다시 그린다
document.querySelector("h2.font-kku").dataset.size = "48";
런타임은 해석한 값을 바탕 요소의 data-fy-* 속성(data-fy-font, data-fy-size …)에
기록하고, 작성자가 쓴 속성은 건드리지 않습니다. CSS에서 바탕 요소를 고를 때는 이 정본 속성을 씁니다
(레퍼런스).
시작하기
글꼴 고르기
font에 이름을 주고, 한 줄 설치의 data-fonts에도 같은 이름을 적습니다. 몇몇 글꼴은 바탕
조건을 지키지 않으면 거의 보이지 않습니다. 같은 문장으로 비교하고 직접 입력해 보려면
글꼴 견본과 시험대를 엽니다.
| 견본 | font | 기반 | 모양 | 바탕 |
|---|---|---|---|---|
sebul | 기본 제공 | 둥근 탈네모꼴 세벌식 조합. 기본값 | 아무 바탕 | |
gothic | 기본 제공 | 음절 높이를 묶는 단정한 네모꼴 | 아무 바탕 | |
neon | sebul | 속을 비운 획과 겹 그림자 빛번짐 | 어두운 바탕 필수 | |
sticker | sebul | 역할별 색, 흰 테두리, 뜬 그림자 | 색 바탕 (흰 바탕이면 테두리가 안 보임) | |
malang | sebul | 자리·자모마다 살짝 다른 기울기 | 아무 바탕 | |
geumbak | sebul | 역할마다 다른 금빛, 짙은 테두리, 빛·눌림 그림자 겹 | 어두운 바탕 권장 | |
glitch | sebul | 초성·중성·받침이 저마다 다른 방향으로 청록·자홍이 어긋남 | 어두운 바탕 필수 | |
retro | gothic | 노란 면, 남색 외곽선, 두 단 입체 그림자 | 밝은 바탕 | |
saekdong | gothic | 초성·중성·받침마다 다른 색동 색 | 밝은 바탕 | |
riso | gothic | 분홍·파랑 반투명 잉크 두 판과 판 어긋남 자국 | 종이색 밝은 바탕 | |
blueprint | gothic | 속 빈 선 글자, 역할마다 선 색(흰색·하늘색·노랑) | 파란 도면 바탕 필수 | |
pilgi | sprite-system | 획마다 살짝 흔들리고 오른쪽으로 기운 손글씨 획. color가 먹색 | 밝은 바탕 | |
dunggeun | sprite-system | 굵고 둥근 끝, 둥글린 모서리. color가 먹색 | 밝은·분홍 바탕 | |
butgeul | sprite-system | 누르며 들어가 빼며 나가는 붓 획. color가 먹색 | 종이색 밝은 바탕 | |
pixel | sprite-system | 같은 획을 칸당 20×20 도트로 찍은 자소판, 어느 크기에서나 모서리가 날카로움 | 밝은 바탕 | |
dojang | sprite-system | 붉은 인주로 다시 칠한 자소판 | 종이색 밝은 바탕 | |
chalk | sprite-system | 흔들린 가장자리와 가루 결의 흰 자소판 | 어두운 녹색·검정 바탕 필수 | |
crayon | sprite-system | 밀랍 질감 자소판을 마스크로 써서 자모마다 다른 크레용 색 | 밝은 바탕 | |
jasu | sprite-system | 둥근 실땀으로 수놓은 자소판, 실 빛과 그림자 | 천(리넨)·밝은 바탕 |
neon·sticker·geumbak·glitch·retro·saekdong·riso·blueprint는
칠만 다른 꾸러미 글꼴입니다. font="neon"은 font="sebul" class="kku-look-neon"과 칠이 같고,
같은 칠을 꾸밈(class="kku-look-neon" 등)으로 다른 글꼴에도 걸 수 있습니다(조합).
두 가지 재료
조립 구조는 하나이고 자소를 무엇으로 그리느냐만 다릅니다. 한 페이지에서 섞어 써도 됩니다.
| 기기 글꼴 | 그림 자소 | 벡터 자소 | |
|---|---|---|---|
| 글꼴 | sebul gothic과 파생 neon sticker malang geumbak glitch retro saekdong riso blueprint | 파생 pilgi dunggeun butgeul pixel dojang chalk crayon jasu, 기본 sprite-example sprite-system | 그림 자소 배치 글꼴 + jamo-set (기본 세트 sprite-system sprite-example pixel) |
| 자소 출처 | 방문자 기기에 이미 있는 한글 글꼴 | 자음 19 + 모음 21칸을 그린 자소판(SVG·PNG) | 자모마다 코드로 등록한 SVG 경로 (FontKku.registerJamo) |
| 추가로 받는 것 | 없음 | 자소판 1장 (기본 제공 SVG는 gzip 약 4KB, 어느 크기에서나 선명) | 자모 세트 모듈 하나(gzip 약 3KB) 또는 페이지 안의 코드 |
| 기기마다 | 기기 한글 글꼴에 따라 획 모양이 조금 다름 | 한글 글꼴이 없는 기기에서도 같은 모양 | 같은 모양, 자모마다 색·굵기·끝 모양을 CSS로. 눌린 자리도 획 굵기 그대로 |
sprite-example(2행 자소판, 출발용 견본)과 sprite-system(6행 자소판, 실측판)은 그림
자소 기본 글꼴입니다. 제목에 바로 쓰기보다 자기 자소판을 그릴 때 출발점으로 씁니다(글꼴 만들기).
고를 때
- 한 페이지에는 2~3종까지만 씁니다.
- 큰 제목은 화면 폭을 넘지 않게 글자 수와 크기를 맞춥니다. 모바일 폭 360px에서 40~48px, 데스크톱에서 56~96px 정도가 알맞고,
size="clamp(40px, 8vw, 96px)"처럼 화면 폭을 따라가게 해도 됩니다. neon·chalk·glitch를 밝은 바탕에,blueprint를 파랑이 아닌 바탕에 두면 거의 보이지 않습니다.sticker는 흰 테두리가 보이는 색 바탕에 둡니다.
꾸미기
줄바꿈
줄을 어디서 바꿀지는 wrap-unit(클래스 방식은 data-wrap-unit)이 정합니다. 기본값은 word입니다.
| 값 | 줄이 바뀌는 곳 | 어울리는 곳 |
|---|---|---|
glyph | 낱말 사이에서 바뀌고, 한 줄보다 긴 낱말은 글자 사이에서도 바뀜. 가장 유연함 | 긴 낱말이 있는 좁은 칸 |
word | 낱말(공백) 사이에서만 바뀜. 한 줄보다 긴 낱말은 넘침 | 제목과 문구 대부분 |
line | 글 안에서 줄을 바꾼 곳에서만 바뀜. 각 줄은 꺾지 않고, 바탕 요소가 블록이 됨 | 줄을 직접 나누는 포스터·배너 제목 |
glyph긴 낱말은 글자 사이에서도word낱말 사이에서만line직접 바꾼 줄만<font-kku font="sebul" size="28" wrap-unit="glyph">서울 국립중앙박물관 특별전</font-kku>
<font-kku font="sebul" size="28" wrap-unit="word">서울 국립중앙박물관 특별전</font-kku>
<font-kku font="sebul" size="28" wrap-unit="line">서울 국립중앙박물관
특별전</font-kku>
좁은 상자에서 word는 긴 낱말을 꺾지 않아 상자 밖으로 넘치고, line은 직접 바꾼 줄만 지키므로
한 줄이 상자보다 길면 넘칩니다. 큰 제목은 줄마다 글자 수를 맞추고, 크기를 clamp()로 주면 좁은 화면에서도
넘치지 않습니다.
간격
spacing 속성은 공백 폭과 낱말 간격을 한꺼번에 넓힙니다. 따로 조절하려면 바탕 요소에 CSS 변수를 줍니다.
페이지 CSS가 글꼴 값보다 먼저 적용되므로(페이지 CSS로 꾸미기) 바탕 요소에 쓰면 됩니다.
<font-kku font="gothic" size="40" spacing="0.6em">넓게 띄운 제목</font-kku>
<style>
.roomy {
--fy-space-width: 0.8em; /* 공백 한 칸의 폭 */
--fy-word-gap: 0.1em; /* 낱말 뒤 간격 */
--fy-glyph-gap: 0.04em; /* 글자 사이 간격 */
--fy-line-gap: 0.2em; /* 줄 사이 간격 */
}
</style>
꾸미기
페이지 CSS로 꾸미기
글꼴 CSS는 모두 @layer font-kku.font, font-kku.jamo, font-kku.context 레이어 안에 있습니다. 그래서
레이어 밖에 쓴 페이지 CSS가 특이도와 상관없이 언제나 이깁니다. 페이지가 자기 레이어 체계를 쓴다면
@layer reset, font-kku, app;처럼 font-kku 전체의 자리를 정합니다.
자소를 part로 고르기
런타임은 그린 노드마다 part 속성을 답니다. 한 노드가 여러 토큰을 가질 수 있으므로(part="jamo jungseong
jungseong-tail") 선택자는 [part~="…"]로 씁니다. 자주 쓰는 토큰은 jamo(모든 자소),
choseong, jungseong, jongseong, glyph(글자 한 칸)이고, 전체 목록은
레퍼런스에 있습니다.
<font-kku font="sebul" size="56" class="paint">과일 화채</font-kku>
<style>
.paint :is([part~="choseong"], [part~="head"]) { color: #d62839; }
.paint [part~="jungseong"] { color: #1f4fd1; }
.paint [part~="jongseong"] { color: #12965f; }
</style>
sebul은 과·화 같은 섞임모음 음절에서 초성과 앞 모음을 완성형 한 덩어리(고+ㅏ)로
그립니다. 이 덩어리가 part="jamo head"라서 초성 색을 줄 때 head도 함께 고릅니다. 이 문서 맨
위의 제목도 같은 방법으로 초성만 적색으로 칠했습니다.
페이지 전용 변수
자소의 굵기·외곽선·기울기·배율은 font-size나 transform을 직접 쓰지 말고 페이지 전용 변수로
줍니다. 직접 쓰면 코어의 배치식을 통째로 덮어 조립이 무너집니다. 글꼴은 이 변수를 쓰지 않으므로 페이지 값이 언제나
마지막에 적용됩니다.
| 변수 | 하는 일 |
|---|---|
--fy-weight | 굵기 (300~900) |
--fy-stroke | 획 외곽선 두께. 색은 -webkit-text-stroke-color |
--fy-rotate | 자소 회전. 글꼴의 회전에 더해집니다 |
--fy-scale-x, --fy-scale-y | 자소 가로·세로 배율. 글꼴의 배율에 곱해집니다 |
--fy-top, --fy-left, --fy-font-size | 자소 위치·크기를 대신 정합니다. 조립이 쉽게 무너지므로 꾸밈에는 쓰지 않습니다 |
--fy-clip | 자소 잘라내기(clip-path 값) |
.custom {
--fy-weight: 800;
--fy-stroke: 0em;
--fy-scale-x: 1.15;
--fy-rotate: -6deg;
}
한 자리만 바꾸려면 그 자리의 노드에 줍니다. 예를 들어 .hero [part~="jongseong"] { --fy-weight: 800; }는
받침만 굵게 합니다. 기기 글꼴 재료의 바탕 글꼴 자체를 바꾸려면 바탕 요소에 --fy-font-family를 줍니다.
나만의 꾸밈 글꼴
자모 배치는 교정이 필요하므로 새로 만들지 않고, 기반 글꼴(sebul 또는 gothic) 위에 꾸밈
CSS를 얹어 클래스 하나로 다시 쓰는 글꼴을 만듭니다. 파일 하나(my-font.css)로 저장해 여러 페이지에서
씁니다.
/* 나만의 글꼴: 딸기 — 기반 sebul. 쓰는 법: <font-kku font="sebul" class="kku-berry">…</font-kku> */
.kku-berry { --fy-weight: 800; }
.kku-berry :is([part~="choseong"], [part~="head"]) { color: #c2185b; }
.kku-berry [part~="jungseong"] { color: #7b1fa2; }
.kku-berry [part~="jongseong"] { color: #f06292; }
.kku-berry [part~="jamo"] { text-shadow: 0.04em 0.04em 0 #ffd1e3; }
| 하고 싶은 것 | 쓰는 것 |
|---|---|
| 초성·중성·받침 색 | color ([part~="choseong"], [part~="head"], [part~="jungseong"], [part~="jongseong"]) |
| 굵기 | 바탕 요소에 --fy-weight: 300~900 |
| 외곽선 | --fy-stroke: 0.03em + -webkit-text-stroke-color |
| 속 빈 글자(네온) | -webkit-text-fill-color: transparent + 외곽선 + text-shadow 빛번짐 |
| 스티커 테두리 | 8방향 흰 text-shadow (0.05em 0 #fff, -0.05em 0 #fff, 0 0.05em #fff, …) |
| 입체 그림자 | 같은 방향으로 겹친 text-shadow (0.02em 0.02em 0 색, 0.04em 0.04em 0 색, …) |
| 손맛 흔들림 | seed + jamo-seeds 속성 + 변주 class="kku-vary-tilt kku-vary-bob"(꾸밈 확장, 겹침 감사를 거친 폭). 굵기·너비·색조·농담은 kku-vary-weight·kku-vary-width·kku-vary-hue·kku-vary-ink (변주 클래스) |
| 움직임 | animate 속성 (움직임과 변주) |
| 이미 있는 칠 그대로 | 꾸밈 class="kku-look-neon" 등 (조합) |
- 자모 위치(
--fy-top,--fy-left)와 크기는 바꾸지 않습니다. 조립이 무너집니다. - 기울기는 변주 클래스에 맡깁니다. 직접 기울일 때는 ±3° 이하, 세로 모음 옆 초성부터 줍니다. 자모가 이웃 자모에 쉽게 닿기 때문입니다(겹침 감사에서 ±3°로도 '꽤'·'셊'의 자모가 15% 넘게 포개졌습니다). 굵기·외곽선은 작은 크기(24px)에서도 획이 뭉치지 않는지 봅니다.
- AI에게 맡기려면 AI와 쓰기의 "나만의 글꼴" 프롬프트를 씁니다.
꾸미기
조합 — 여섯 축과 테마
글자 모양은 여섯 가지를 따로 고른 조합입니다. 글꼴 하나를 고르고, 그 위에 꾸밈·변주·움직임·상호작용을 클래스와 속성으로 얹고, 어울리는 바탕에 둡니다. 자주 쓰는 조합에는 이름이 있는데 이것이 테마입니다. 테마 전체와 축을 하나씩 고르는 조합기는 글꼴 페이지에 있습니다 (ADR-017).
| 축 | 정하는 것 | 쓰는 법 | 고를 수 있는 것 |
|---|---|---|---|
| 글꼴 | 뼈대 — 짜임(자모 배치) × 획 | font="gothic" | 기기 글꼴 sebul gothic malang · 그림 자소 sprite-system pilgi dunggeun butgeul · 획 표현 pixel dojang chalk crayon jasu |
| 꾸밈 | 칠 — 색·외곽선·그림자, 자모 역할별 | class="kku-look-neon" | neon sticker geumbak glitch retro saekdong riso blueprint outline pastel shadow inju |
| 변주 | 씨앗만큼 자모마다 조금씩 | seed="auto" jamo-seeds class="kku-vary-tilt" | tilt 기울기 · bob 들썩임 · hue 색조 · ink 농담 |
| 움직임 | 글자 또는 자모 단위 애니메이션 | animate="wave" | 글자 rise fade stagger bounce wiggle drift · 자모 assemble drop wave flicker cycle shine scroll-assemble · 획 draw |
| 상호작용 | 입력에 반응 | data-kku-fx="magnet" | type 타자 · scatter 흩어졌다 모이기 · magnet 커서 자석 · shuffle 씨앗 섞기 |
| 바탕 | 꾸밈이 사는 배경 | 페이지 배경 | 밝은 · 어두운 · 분홍 · 종이 · 칠판 · 도면 · 천 |
확장 붙이기
꾸밈·변주·자모 움직임·상호작용은 런타임 옆의 확장 파일입니다. 한 줄 설치에 data-addons를 더하면 함께 붙습니다.
<script src="https://font.tty.link/lib/font-kku.js" data-fonts="sebul gothic chalk" data-addons="looks motion fx"></script>
<!-- 고장 난 네온: 세벌 + 네온 꾸밈 + 깜빡임 (어두운 바탕) -->
<font-kku font="sebul" class="kku-look-neon" animate="flicker">을지로 네온</font-kku>
<!-- 칠판 손글씨: 분필 + 기울기·들썩임 변주, 그릴 때마다 새로 -->
<font-kku font="chalk" seed="auto" jamo-seeds class="kku-vary-tilt kku-vary-bob kku-vary-width">가나다 시간표</font-kku>
<!-- 색동 자석: 고딕 + 색동 꾸밈 + 커서 자석 -->
<font-kku font="gothic" class="kku-look-saekdong" data-kku-fx="magnet">한글 자석</font-kku>
| 이름 | 붙는 파일 | 하는 일 |
|---|---|---|
looks | font-kku-looks.css | 꾸밈 kku-look-*와 변주 kku-vary-* 클래스 |
motion | font-kku-motion.css | 자모 단위 움직임 일곱 가지 |
fx | font-kku-fx.js | 상호작용 FontKkuFx와 선언형 data-kku-fx |
data-fonts없이data-addons만 써도 바탕 CSS와 확장을 붙입니다. 모르는 이름은 콘솔에 경고하고 건너뜁니다.fx스크립트는 런타임 뒤에 순서대로 실행되지만 파서를 막지 않습니다. 인라인 코드에서FontKkuFx를 부를 때는load뒤에 부르거나font-kku-fx.js를 직접<script>로 싣습니다. 선언형data-kku-fx는 저절로 붙습니다.- 번들러:
import "font-kku/font-kku-looks.css",import "font-kku/font-kku-motion.css",import FontKkuFx from "font-kku/fx". - 레이어 순서는 글꼴 < 꾸밈(
font-kku.look) < 움직임(font-kku.motion) < 레이어 밖 페이지 CSS입니다. 페이지 CSS가 언제나 마지막에 이깁니다.
꾸밈 — 같은 칠, 다른 글꼴
꾸밈(kku-look-*)은 글꼴의 칠을 섞지 않고 바꿉니다. 기기 글꼴에는 색·외곽선·그림자를 모두 칠하고, 그림 자소에는 마스크 칠로
색만 칠하며 그림자는 글자 단위로 옮깁니다 — 자소판의 획 질감(도트·손글씨·붓)은 그대로 남습니다.
sebulgothicpixelpilgigothicdunggeunbutgeulbutgeul + inju- 칠만 다른 꾸러미 글꼴(
font="neon"등 여덟)은 기반 글꼴 + 같은 이름의 꾸밈과 칠이 같습니다. 확장 없이 CSS 한 장으로 끝나는 지름길입니다. - 그림 자소 글꼴의
kku-look-*클래스를 붙이거나 떼면 런타임이 마스크 칠 메타데이터를 다시 해석합니다.<style>내용이나 CSS 변수를 직접 바꿨다면FontKku.refreshAll(host)를 부릅니다. - 새 칠은 새 글꼴이 아니라
spec/themes.json의 꾸밈으로 더합니다(글꼴 만들기).
변주 클래스
변주 클래스는 seed와 jamo-seeds를 켠 바탕 요소에서만 움직이고, 글꼴의 보정에 더해집니다. 기울기·들썩임·굵기·너비의
폭은 조합 글꼴 전부에 함께 걸어 잰 겹침 감사를 통과한 범위라, 무작위여도 자모가 서로 부딪치지 않습니다.
| 클래스 | 하는 일 |
|---|---|
kku-vary-tilt | 세로 모음 옆 초성이 씨앗만큼 기웁니다 |
kku-vary-bob | 초성은 위로, 받침은 아래로, 세로 모음은 위아래로 — 자모가 서로 멀어지는 쪽으로만 들썩입니다 |
kku-vary-weight | 자모마다 획 굵기가 다릅니다(기기 글꼴 — 그림 자소는 자소판 그림이 획을 정합니다) |
kku-vary-width | 초성과 받침이 씨앗만큼 좁아집니다 |
kku-vary-hue | 자모마다 색조가 돕니다(색 있는 꾸밈·글꼴과 함께) |
kku-vary-ink | 자모마다 진하기가 달라 먹·인주가 덜 묻은 듯합니다 |
자모 움직임
motion 확장의 움직임은 글자가 아니라 자모를 하나씩 움직입니다. 고르는 법은 코어 움직임과 같은
animate이고, 동작 줄이기를 켜면 멈추며, 바탕 요소나 조상에 data-kku-paused를 두면 그 자리에 섭니다.
assemble날아와 맞물림 (한 번)drop받침이 떨어져 튐 (한 번)wave역할마다 다른 박자flicker받침 하나가 깜빡임cycle오방색이 자모로 돎shine빛 띠가 지나감scroll-assemble내릴수록 모임pilgi + wave그림 자소도 같은 훅dunggeun + cycle마스크 칠로 색이 돎- 한 번 도는 움직임(
assemble·drop)은 다시 그리면 다시 돕니다 —FontKku.refresh(host)또는FontKkuFx.replay(host). - 조절: 바탕 요소에
--kku-motion-duration·--kku-motion-stagger, 색 순환은--kku-cycle-1…4, 반짝은--kku-shine-color. - 모든 카드와 가져가는 코드는 움직임 페이지에 있습니다.
획 깊이 — 자모를 획으로
기본은 자모 단위입니다(하 = ㅎ + ㅏ). stroke-depth="stroke"를 걸면 벡터 자소(jamo-set)를 한 단계 더 쪼개
획마다 노드가 됩니다(하 = 꼭지·가로·동그라미 + 세로·점). 획에는 획순 차례와 종류, 그 획을 들여온 글자,
제자원리 역할이 붙습니다 — 자음은 기본자(base)에 가획(added)을 더하고 ㄹ은 이체(variant),
모음은 천(heaven)·지(earth)·인(person)입니다. 기기 글꼴과 자소판은 그림을 쪼갤 수 없어 자모 단위로 그립니다.
data-hoek-rolekku-vary-hoekanimate="draw"<style>
.origin [data-hoek-role="added"] { color: #e63946; } /* 가획 */
.origin [data-hoek-kind="dot"] { --fy-hoek-dy: -4; } /* 꼭지·점만 칸의 4% 위로 */
</style>
<font-kku class="origin" font="sprite-system" jamo-set="sprite-system" stroke-depth="stroke">한글</font-kku>
- 획 노드:
[part="hoek"], 차례data-hoek·--fy-hoek-index(자모 안)·--fy-hoek-order(읽는 차례 전체), 종류data-hoek-kind(hvhvvhlfrfdotring), 들여온 글자data-hoek-from, 속한 자모data-hoek-letter, 역할data-hoek-role. - 획만 옮기기:
--fy-hoek-dx·--fy-hoek-dy(칸 0–100 단위),--fy-hoek-rotate,--fy-hoek-scale-x·--fy-hoek-scale-y. - 페이지 CSS
--fy-stroke-depth: stroke로도 켭니다 —animate="draw"와kku-vary-hoek가 이렇게 켭니다. 자모 하나가 획 1–7개가 되므로 긴 글에는 쓰지 않습니다.
상호작용
fx 확장(FontKkuFx)은 입력에 반응합니다. 선언형 data-kku-fx로 붙이거나 JS로 부르고,
JS 호출은 모두 stop()이 있는 controller를 돌려줍니다.
magnetscattershuffletypewindow.addEventListener("load", function () {
var title = document.querySelector("#title");
var typing = FontKkuFx.type(title, ["첫 문장", "둘째 문장"], { start: "random" });
var shuffle = FontKkuFx.shuffle(title, { every: 3000 }); // 3초마다 씨앗 새로
// typing.stop(); shuffle.stop();
});
| API | 하는 일 |
|---|---|
type(target, phrases, options) | 두벌식으로 치듯 자모 단위로 지웠다 다시 칩니다. 숨은 탭·화면 밖에서 쉽니다. 선언형은 data-kku-phrases="a|b" |
scatter(target, { on }) | 올리기·누르기에 자모가 흩어졌다 모입니다("auto"·"hover"·"click") |
magnet(target, options) | 커서 가까운 자모를 끌어당깁니다 |
shuffle(target, { on, every }) | 누르기·올리기·주기마다 씨앗을 새로 뽑아 다시 그립니다. 선언형은 data-kku-shuffle="click | hover | 2000" |
replay · bind · unbind | 한 번 도는 움직임 다시 · 선언형 연결·해제 |
scatter와 magnet을 함께 쓰면 실제로 자모를 옮기는 자석이 우선합니다.
같은 효과의 controller가 여러 개면 나중에 만든 활성 controller가 우선하고, 이동값을 합산하지 않습니다.
자석이 제자리로 돌아오거나 stop()하면 여전히 열린 scatter가 다시 적용됩니다.
각 controller는 자기 이동·회전·애니메이션·transition 상태만 반환하며, 마지막 소유자가 해제되면
작성자의 원래 인라인 값과 !important가 복원됩니다. 인라인 값이 없었다면 CSS 변주와 움직임이 다시 적용됩니다.
멈춘 효과를 다시 쓰려면 새 controller를 만듭니다.
테마
테마는 런타임 옵션이 아니라 이름 붙은 조합법입니다. 글꼴 페이지의 테마 카드에서 코드를 복사하면 위처럼 속성과 클래스로 풀린 HTML이 나옵니다. 몇 가지 예:
| 테마 | 조합 |
|---|---|
고장 난 네온 neon-sign | 세벌 + 꾸밈 네온 + 움직임 깜빡임 + 어두운 바탕 |
스티커 한 장 sticker-pack | 세벌 + 꾸밈 스티커 + 변주 기울기(매번 새로) + 씨앗 섞기 + 분홍 바탕 |
도면 조립 blueprint-assemble | 고딕 + 꾸밈 도면 + 자모 조립 + 도면 바탕 |
칠판 손글씨 chalk-hand | 분필 + 변주 기울기·들썩임(매번 새로) + 칠판 바탕 |
도트 오락실 pixel-retro | 도트 + 꾸밈 레트로 + 통통 + 밝은 바탕 |
크레용 일기 crayon-diary | 크레용 + 변주 기울기·들썩임·색조(매번 새로) |
전체 목록과 조합법의 정본은 패키지의 spec/themes.json입니다(import themes from "font-kku/spec/themes.json" with { type: "json" } — Node ESM은 with가 필요하고, 번들러는 대개 없어도 됩니다).
꾸미기
움직임과 변주
animate 속성(클래스 방식은 data-animate)에 이름을 주면 글자 단위로 움직입니다. 내장 움직임은
여섯 가지이고, 모두 글자 칸([part="glyph"])을 움직이며 사용자가 동작 줄이기를 켜면
저절로 멈춥니다.
rise아래에서 올라오며 나타남fade서서히 나타남stagger한 글자씩 차례로bounce위아래로 반복wiggle좌우로 기울며 반복drift미세하게 떠다님| 값 | 성격 | 타이밍 |
|---|---|---|
rise | 아래에서 위로 + 서서히 나타남 | 360ms, 글자마다 20ms 지연 |
fade | 서서히 나타남 | 400ms, 지연 없음 |
stagger | 서서히 나타남 + 차례로 등장 | 400ms, 글자마다 40ms 지연 |
bounce | 위아래 반복 | 1.2s 무한 반복, 글자마다 80ms 지연 |
wiggle | 좌우 회전 반복 | 1.6s 무한 반복, 글자마다 60ms 지연 |
drift | 미세한 불규칙 움직임 | 4s 무한 반복, 글자마다 시작 시점을 250ms씩 당김 |
<font-kku font="sebul" animate="rise">안녕하세요</font-kku>
<h1 class="font-kku" data-font="sebul" data-animate="bounce">신나는 여름</h1>
자모를 하나씩 움직이는 일곱 가지(assemble·drop·wave·flicker·cycle·shine·scroll-assemble)와
획을 획순대로 긋는 draw는 motion 확장에 있습니다 — 자모 움직임, 획 깊이.
한 번만 재생되는 움직임(rise·fade·stagger)은 바탕 요소를 다시 그리면 처음부터
다시 재생됩니다. 위 견본의 다시 재생 단추는 FontKku.refresh(host)를 부릅니다.
직접 만든 움직임
새 이름을 animate에 주고, 런타임이 기록하는 정본 속성 [data-fy-animate="<이름>"]으로
규칙을 씁니다. 지연에는 런타임이 노드마다 넣는 순번 변수 --fy-line-index, --fy-word-index,
--fy-glyph-index, --fy-jamo-index를 씁니다. 직접 만든 움직임에는 동작 줄이기 가드를
반드시 직접 넣습니다.
:where(font-kku, .font-kku)[data-fy-animate="shake"] [part="glyph"] {
animation: my-shake 0.4s ease-in-out infinite;
animation-delay: calc(var(--fy-glyph-index, 0) * 50ms);
}
@keyframes my-shake {
0%, 100% { transform: translateX(0); }
50% { transform: translateX(0.08em); }
}
/* 동작 줄이기 가드는 의무 */
@media (prefers-reduced-motion: reduce) {
:where(font-kku, .font-kku)[data-fy-animate="shake"] [part="glyph"] {
animation: none !important;
}
}
자소 노드([part~="jamo"])에 transform 움직임을 주면 런타임이 계산한 자소 배율·회전을 덮습니다.
자소 단위로는 opacity처럼 transform과 무관한 속성을 움직이고, 이동·회전은 글자
([part="glyph"])나 묶음 단위에서 줍니다.
씨앗값과 변주
seed를 주면 런타임이 바탕 요소에 0~1 사이 값 --fy-seed를 넣습니다. 고정 숫자(0.42)나
고정 문자열(poster-v1)이면 언제나 같은 값이고, auto·random이면 그릴 때마다 새
값입니다. 자소마다 다른 값이 필요하면 jamo-seeds를 켭니다. 그릴 때마다 자소 노드에 --fy-jamo-seed를
넣고, 바탕 요소의 씨앗값이 계산에 들어가므로 씨앗값이 다르면 자소 값도 달라집니다.
<font-kku font="sebul" size="48" seed="poster" jamo-seeds class="wobble">말랑 젤리</font-kku>
<font-kku font="sebul" size="48" seed="auto" jamo-seeds class="wobble">말랑 젤리</font-kku>
<style>
.wobble [part~="jamo"] { --fy-rotate: calc((var(--fy-jamo-seed, 0.5) - 0.5) * 20deg); }
</style>
jamo-seeds 없이 FontKku.applyJamoSeeds(host)를 직접 불러도 됩니다. 관리 DOM을 다시 만들면 값이
지워지므로 font-kku:render 이벤트에서 다시 적용합니다. 기존 노드가 유지되는 이벤트에서도 같은 값을 덮어써도 됩니다. salt를 바꾸면 같은 글도 새 표정이 됩니다.
FontKku.applyJamoSeeds(host); // 각 [data-jamo]에 --fy-jamo-seed
FontKku.applyJamoSeeds(host, { salt: Date.now() }); // 새 씨앗값 뿌리기
// 다시 그린 뒤에도 유지: 모든 그리기 뒤에 font-kku:render가 발생한다
document.addEventListener("font-kku:render", (event) => {
FontKku.applyJamoSeeds(event.target, { salt: "v2" });
});
씨앗 반응 글꼴
malang·crayon·chalk·jasu·sticker·glitch는 글꼴이 직접
씨앗을 읽습니다. 페이지 CSS 없이 seed와 jamo-seeds만 주면 자모마다 기울기·위치·색조가 조금씩 달라지고,
켜지 않으면 견본의 모양 그대로입니다. 흔들림 폭은 씨앗을 여러 개 넣어 잰 겹침 감사를 통과한 범위라, 무작위여도 자모가
서로 부딪치지 않습니다(ADR-016).
<font-kku font="crayon" size="48" seed="auto" jamo-seeds>하하하하</font-kku> <!-- 그릴 때마다 새로 -->
<font-kku font="sticker" size="48" seed="민지" jamo-seeds>민지</font-kku> <!-- 이름마다 다르고 언제나 같게 -->
마음에 드는 모양을 고정하려면 그린 뒤 바탕 요소의 data-fy-seed(해석된 숫자)를 seed에 적습니다.
어느 글꼴에나 걸리는 공용 변주는 변주 클래스(kku-vary-*)입니다.
글꼴 페이지의 시험대 '변주'로 견본을 한꺼번에 흔들어 보고, 꾸미기 창의 '변주' 묶음으로 어느 글꼴에나
흔들림을 얹은 페이지 CSS를 만들며, '글꼴 뽑기'로 뽑기 번호 하나에 재현되는 무작위 꾸밈 글꼴을 얻습니다.
상태에 따라 바꾸기
별도 규칙 엔진은 없습니다. 바탕 요소의 class·data-* 상태와 CSS 선택자, 위 변수와 도우미 함수를 조합합니다.
:where(font-kku, .font-kku)[data-state="active"] [part~="jamo"] {
--fy-rotate: calc((var(--fy-seed, 0.5) - 0.5) * 8deg);
}
host.dataset.state = "active";
FontKku.refresh(host, { seed: "poster-v1" });
동작 줄이기
내장 움직임은 prefers-reduced-motion: reduce에서 모두 꺼집니다. 기기에서 동작 줄이기를 켜 두었다면 위
견본도 멈춰 보입니다. 직접 만든 움직임에는 같은 가드를 넣어야 하며, 빠뜨리면 접근성 결함입니다. 움직임과 씨앗값은
바탕 요소의 원문(aria-label, data-source)을 바꾸지 않습니다.
다른 환경
평문 모드와 봇
브라우저 CSS 없이 한글을 2줄 평문으로 풀어 씁니다. 초성·중성을 윗줄에, 받침과 가로모음을 아랫줄에 둡니다. 메신저 알림, 배포 로그, 터미널 배너에서 한글을 크게 강조할 때 씁니다. 글꼴·크기·색·움직임은 적용되지 않고, 브라우저에서 그리는 것과 같은 분해 결과를 씁니다.
renderText(text)
문자열 배열을 돌려줍니다. 입력이 한 줄이면 두 줄(윗줄, 아랫줄), N줄이면 2N줄입니다. 출력은 호환 자모와 전각 공백 (U+3000)으로 칸을 맞추므로 고정폭 글꼴(코드 블록, 터미널)에서 줄이 가장 잘 맞습니다. 브라우저 콘솔과 Node.js에서 모두 동작합니다.
const FontKku = require("font-kku"); // 저장소에서는 require("./font-kku.js")
FontKku.renderText("안녕하세요").forEach((line) => console.log(line));
// ㅇㅏ ㄴㅕ ㅎㅏ ㅅㅔ ㅇ
// ㄴ ㅇ ㅛ
터미널 명령 font-kku-text
패키지에 터미널 명령이 들어 있습니다. 인자, 파이프로 넘긴 표준 입력, 명시적 표준 입력 표시 -를 받습니다.
# 패키지를 설치한 프로젝트에서
npx font-kku-text "배포 완료"
# 저장소에서
node ./bin/font-kku-text.js "안녕하세요 반갑습니다"
# 표준 입력: 파이프는 저절로 읽고, -는 명시적으로 읽는다
printf "한글\n테스트\n" | node ./bin/font-kku-text.js
cat input.txt | node ./bin/font-kku-text.js -
# -로 시작하는 글은 -- 뒤에 둔다
node ./bin/font-kku-text.js -- "-5도 한파"
node ./bin/font-kku-text.js --version
| 입력·옵션 | 동작 |
|---|---|
| 여러 인자 | 공백 한 칸으로 이어 붙입니다. 공백 여러 칸을 지키려면 따옴표로 묶어 한 인자로 넘깁니다. |
| 표준 입력 | 셸이 붙이는 마지막 줄바꿈 하나를 떼고, 입력 줄마다 2줄씩 출력합니다. |
-h, --help | 사용법 |
-v, --version | 설치된 font-kku 버전 |
- | 표준 입력에서 읽기 |
-- | 옵션 해석을 끝냅니다. 뒤의 인자는 -로 시작해도 글로 그립니다. |
그 밖의 -·-- 옵션 | 오류: 표준 오류에 unknown option과 사용법, 종료 코드 2 |
| 빈 입력 | 표준 오류에 사용법, 종료 코드 1 |
메신저 봇
슬랙·디스코드에서는 고정폭으로 보이도록 출력을 코드 블록(```)으로 감쌉니다. 한 줄에 10~15자 안쪽이 읽기 좋습니다.
const { renderText } = require("font-kku");
const big = renderText("배포 완료").join("\n");
await say("```\n" + big + "\n```"); // 슬랙 Bolt의 say 예
from font_kku import render_text
message = "```\n" + "\n".join(render_text("배포 완료")) + "\n```"
다른 언어 보조 렌더러
Python·Go·Rust·Ruby 구현은 JavaScript를 감싼 것이 아니라 같은 명세(spec/)를 따로 구현한 보조 렌더러입니다.
모두 같은 2줄 평문과, 서버에서 쓰는 정적 HTML을 냅니다.
| 언어 | 평문 | 정적 HTML |
|---|---|---|
| Python | font_kku.render_text(text) | font_kku.render_html(text, font=, size=, mode=, jamo_depth=, …) |
| Go | fontkku.RenderText(text) | fontkku.RenderHTML(text, opts...) — WithFont, WithSize, WithMode … |
| Rust | font_kku::render_text(text) | font_kku::render_html(text, font, size, mode) 외 |
| Ruby | FontKku.render_text(text) | FontKku.render_html(text, font:, size:, mode:, …) |
- 기본 포함 글꼴은
sebul·gothic입니다. 호출별FontDefinition으로 이름·CSS·표현 모드·기본 자모 깊이를 전달하면 사용자 기기 글꼴(material="system")도 정적 HTML에 적용할 수 있습니다. 정의 없이 모르는 이름을 넘기거나 sprite/vector 재료를 요청하면 오류입니다. FontDefinition은 해당 호출의font보다 우선하며 전역 목록을 바꾸지 않습니다. Python·Ruby는font_definition, Go는WithFontDefinition, Rust는HtmlOptions.font_definition으로 전달합니다. 필드·예제와 Unicode 지원 범위는 언어 공통 렌더 계약과 사용자 글꼴을 봅니다.render_html은 서버에서 정적 HTML을 만드는 공식 경로입니다. 이렇게 만든 바탕 요소 위에 브라우저 런타임을 다시 얹지 않습니다.- 현재는 저장소의
renderers/폴더를 로컬 패키지·경로 의존성으로 씁니다. 설치와 언어별 한계는 Multi-language Renderers를 봅니다.
다른 환경
글꼴·입력기 없는 기기
한글 글꼴도 한글 입력기도 없는 기기(키오스크, 최소 설치 리눅스, 외국에서 빌린 컴퓨터)에서도 한글을 보이고 쓰게 할 수
있습니다. 그림 자소 글꼴은 자소판 그림으로 자소를 그려 기기 글꼴이 필요 없고, 입력기 모듈
font-kku-ime.js는 영문 자판의 키를 두벌식·세벌식 배열대로 받아 한글을 직접 조합합니다. 완성된 예가
전광판과 메모장입니다.
글자: 그림 자소 글꼴로 그리기
- 기기 글꼴 재료 글꼴(
sebul·gothic과 파생)은 기기의 한글 글꼴로 자소를 그리므로, 한글 글꼴이 없으면 네모(두부)로 보입니다. - 그림 자소 글꼴(
pilgi·dunggeun·butgeul·pixel·dojang·chalk·crayon·jasu·sprite-system)은 기본 자모 깊이에서 모든 자소 노드를 자소판 칸으로 칠하고 글자는 투명하게 둡니다. 낱자(ㅋㅋ, 조합 중인ㄱ)도 자소판 칸으로 그립니다. - 영문·숫자·문장 부호는 어느 글꼴에서나 기기 글꼴로 그립니다(어느 기기에나 있습니다). 그림 자소는 기본적으로
color를 따르지 않습니다.--fy-sprite-paint: mask(마스크 칠)나 벡터 자소(jamo-set)로 그리면color를 그대로 씁니다. - 그림 자소 글꼴에서 페이지가
--fy-jamo-depth: syllable을 걸면 합자 모음 음절의 완성형 머리 노드는 자소판 칸이 없어 기기 글꼴로 돌아갑니다. 글꼴 없는 기기용 화면에서는 깊이를 바꾸지 않습니다.
페이지 글꼴을 기기에 맞춰 고르려면 FontKku.hasHangulFont()로 기기 글꼴이 한글(완성형 음절과 첫가끝 자모)을
그릴 수 있는지 확인합니다. 캔버스 글자 상자만 재고(픽셀을 읽지 않습니다) 결과를 캐시하며, 부르기 전에는 돌지 않습니다.
판단할 수 없는 환경(Node 등)에서는 null입니다.
<script src="https://font.tty.link/lib/font-kku.js" data-fonts="sebul pixel"></script>
<script>
if (FontKku.hasHangulFont() === false) {
document.querySelectorAll("font-kku[font='sebul']").forEach(function (host) {
host.setAttribute("font", "pixel"); // 속성이 바뀌면 다시 그린다
});
}
</script>
입력: font-kku-ime.js
폰꾸 런타임과 따로 쓰는 선택 모듈입니다(npm font-kku/ime, 형식은 font-kku-ime.d.ts).
배열은 두벌식 표준(KS X 5002)·세벌식 390·세벌식 최종(3-91)이고, 키는 물리 자리(QWERTY) 기준이라 Caps Lock이나 OS 자판
배열에 흔들리지 않습니다. 합자 모음, 겹받침, 두벌식 받침 넘김(갑+ㅏ → 가바, 값+ㅏ → 갑사), 자모 단위 Backspace를 처리하고,
한/영 전환은 Shift+Space와 한/영 키입니다. OS 입력기가 조합 중인 키는 건드리지 않습니다.
<script src="https://font.tty.link/lib/font-kku-ime.js"></script>
<script>
FontKkuIme.convert("dkssudgktpdy"); // "안녕하세요"
FontKkuIme.convert("jfsheamfncj4", "sebeol-final"); // "안녕하세요"
// 입력 칸에 붙이기: 키를 가로채 조합 중인 음절을 칸에 바로 쓴다
const ime = FontKkuIme.attach(document.querySelector("textarea"), {
layout: "dubeol",
onChange(state) { console.log(state.value, state.composing); },
});
ime.toggleMode(); // 한/영
ime.feed("r"); // 화면 키보드에서 키 하나
</script>
| 이름 | 돌려주는 값 | 하는 일 |
|---|---|---|
convert(keys, layout?) | string | 키 문자열을 한 번에 한글로. "\b"는 Backspace. |
createComposer(layout?) | Composer | 키 하나씩 넣는 조합기. key(ch)는 { commit, composing }, backspace()·flush()·peek(). |
attach(field, options?) | Controller | <input>·<textarea>에 입력기를 붙입니다. onChange(state, reason)의 state.composing이 조합 구간입니다. |
keyMap(layout?) · lookup(layout, key) | KeyCap[][] | 화면 자판용 표 — 키마다 내는 자모와 자리(초성·중성·종성). |
layouts · normalizeLayout(name) | LayoutInfo[] | dubeol · sebeol-390 · sebeol-final. 별칭 2 · 390 · 3-91. |
한글 글꼴 없는 화면 만들기
- 보이는 한글은 모두 그림 자소 글꼴로 그립니다.
<textarea>에 친 한글은 기기 글꼴로 그려지므로, 메모장처럼 숨은 입력 칸이 키를 받고 폰꾸가 그린 면 위에 캐럿과 조합 중인 음절을 겹쳐 그립니다. - 단추·안내 같은 화면 글은 영어로 바꿀 수 있게 둡니다. 전광판·메모장은
?hangul-font=none을 붙이면 글꼴 없는 기기처럼 보입니다. - 복사·내려받기에는 보통 유니코드 글을 넘깁니다. 받는 쪽 기기에 한글 글꼴이 있으면 그대로 읽힙니다.
다른 환경
AI와 쓰기
폰꾸에는 AI가 읽는 사용 설명서 skills/font-kku-use/SKILL.md가
있습니다(npm 패키지에 포함). ChatGPT·Claude 같은 AI에게 이 설명서 링크와 함께 부탁하면 AI가 만드는 HTML의 제목과 강조
문구를 폰꾸로 꾸미고, 브랜드에 맞는 나만의 꾸밈 글꼴을 만들고, 봇·터미널 출력을 크게 풀어 줍니다.
- 복사해서 붙여 넣기아래 프롬프트를 AI 대화창에 붙여 넣고 만들 내용만 바꿉니다.
- 미리보기로 확인만들어진 HTML을 대화창 미리보기로 보거나
.html파일로 저장해 브라우저로 엽니다. - 말로 고치기"제목은 네온 말고 분필로, 바탕은 칠판색으로", "고딕에 네온 칠, 깜빡이게"처럼 이어서 부탁합니다. 설명서는 글꼴·꾸밈·움직임을 따로 고르는 조합을 알려 주므로 축마다 따로 말해도 됩니다.
이 설명서를 읽고 따라 해 줘: https://font.tty.link/lib/skill/SKILL.md
카페 가을 신메뉴를 알리는 HTML 페이지를 한 장으로 만들어 줘.
주요 제목과 강조하고 싶은 문구는 폰꾸로 꾸며 줘.
이 설명서를 읽고 따라 해 줘: https://font.tty.link/lib/skill/SKILL.md
우리 딸기 디저트 가게(분홍·보라, 귀엽고 말랑한 느낌)에 맞는
나만의 폰꾸 꾸밈 글꼴을 CSS 파일 하나로 만들어 줘.
가게 이름 '딸기 공방'으로 기반 글꼴과 나란히 비교하는 미리보기 HTML도 함께.
AI가 링크를 열지 못할 때
웹 검색이 꺼져 있거나 npm 공개 전이라 CDN 주소가 열리지 않으면, 설명서 내용을 통째로 대화창에 붙여 넣습니다.
Claude Code
skills/font-kku-use 폴더를 ~/.claude/skills/에 넣어 두면 링크 없이 "폰꾸로 꾸며 줘"만으로 씁니다.
mkdir -p ~/.claude/skills
cp -R skills/font-kku-use ~/.claude/skills/
결과 확인
- 제목이 자모가 흩어진 채(ㄱㅏ ㅇ…) 보이면 글꼴 CSS가 붙지 않은 것입니다.
data-fonts에 그 글꼴 이름이 있는지, 스크립트 주소가 맞는지 봅니다. neon·chalk·glitch가 밝은 바탕에,blueprint가 파랑이 아닌 바탕에,sticker가 흰 바탕에 있으면 바탕을 바꿔 달라고 합니다.- 본문 문단, 표, 버튼 라벨까지 꾸몄다면 제목과 짧은 강조에만 쓰도록 다시 부탁합니다.
만들기
글꼴 만들기
폰꾸 글꼴은 CSS 파일 하나(+ 그림 자소 글꼴이면 자소판 SVG·PNG)입니다. 얼마나 깊이 만들지에 따라 길이 넷입니다.
-
가장 쉬움
나만의 꾸밈 글꼴
sebul·gothic위에 색·외곽선·그림자·기울기를 얹은 페이지 CSS 한 파일. 배치 교정이 필요 없습니다. 페이지 CSS - CSS 글꼴 글꼴 작업실 자소를 눌러 값이 어디서 오는지(자리·자모·문맥)를 보고, 범위를 정해 끌기·방향키로 고칩니다. 기존 글꼴에서 새 글꼴을 시작하고 CSS로 내보냅니다. font-studio.html
- 그림 자소 글꼴 자소판 그리기 자음 19 + 모음 21 = 40칸을 그리면 한글 11,172자가 만들어집니다. PNG(1x·2x·3x)와 연결 CSS를 내보냅니다. font-studio.html?font=sprite-example
- 사람 · AI 에이전트 SVG로 직접 그리기 자모마다 SVG 경로를 코드로 등록하거나 SVG 자소판 한 장을 그리면 그림 자소 글꼴입니다. 배치는 이미 맞춰져 있고, 어느 크기에서나 선명합니다. spec/sprite-layout.json
글꼴 작업실
- 기본 제공 글꼴을 열거나 CSS 파일을 불러와, 미리보기에서 자소를 고르면 그 자소에 닿는 값의 출처를 보여 줍니다.
- 편집 범위(자리 가족, 자리 전체, 이 자리의 이 자모, 이 자모, 문맥)를 골라 원문의 그 선언만 고칩니다. 되돌리기와 브라우저 초안이 있습니다.
- "이 글꼴로 새 글꼴"은 지금 글꼴을 새 이름으로 옮겨 교정된 배치에서 출발하게 합니다. 고칠 때마다 글꼴 계약 검사 결과를 줄 번호와 함께 보여 줍니다.
- 재료 탭에서는 펜·지우개·채우기·직선·사각형으로 자소판 칸을 그리고, PNG를 가져와 칸에 맞춥니다. SVG 자소판을 열어 픽셀로 고치면 옆의 PNG로 저장합니다(획 자체는 SVG를 직접 고칩니다).
- CSS 내려받기·복사, 프로젝트 JSON 저장을 지원하고, 지원하는 브라우저에서는 파일에 바로 저장합니다.
SVG로 자모 그리기 — 사람과 AI 에이전트
기본 제공 그림 자소 글꼴(sprite-system·pilgi·dunggeun·butgeul·pixel·dojang·chalk·crayon·jasu·sprite-example)의
그림은 획으로 직접 그린 SVG입니다. 저장소의 생성기는 같은 획을 획 뼈대(곧은·필기·둥근·붓)와 획 표현(민획·도트·인주·분필·크레용·실땀)의 조합으로 그립니다. 같은 칸에 그리면 누구나 — AI 에이전트도 — 새 그림 자소 글꼴을 만듭니다. 길은 둘입니다.
- 코드로 그리기(권장) — 자모마다 SVG 경로를
FontKku.registerJamo로 등록하고jamo-set으로 씁니다(벡터 자소). HTML 한 장 안에서 끝나고, 자모마다 색·굵기를 CSS로 바꾸며, 자리가 눌려도 획 굵기가 그대로입니다. - SVG 자소판 파일 — 자모 전체를 SVG 한 장(21열 × 6행)에 그려
--fy-sprite-url로 끼웁니다. 인주·분필 같은 질감 필터를 쓰려면 이 길입니다.
- 칸 키는
r<행>-<자모>, 칸 하나는 한 변 100입니다. 행은 0 초성(세로 모음 옆) · 1 중성 · 2 받침 · 3 겹받침 · 4 초성(가로 모음 위) · 5 합자 모음(키 큰 초성 옆)입니다. - 칸은 음절 상자를 그대로 줄인 것이라, 자모를 칸 가운데가 아니라 음절 안 제자리에 그립니다. 자리는
spec/sprite-layout.json의 자리 상자(box, 두 짝 자음·겹받침·합자 모음은parts)가 알려 줍니다. 상자 안에 그리면sprite-system의 보정된 배치를 그대로 씁니다. - 칸 경계에서 2단위 안쪽에만 그립니다. 본보기는
profiles/sprite-system.jamo.js(칸 하나가 한 줄)와profiles/sprite-system.svg입니다. - 일부 칸만 등록해도 됩니다. 등록된 행·자모 칸만 벡터로 그리고, 빠진 칸은 기반 글꼴의 자소판·마스크 칠로 돌아갑니다. 자소판이 지원하지 않는 자모 종류는 원래 기기 글꼴 표시를 유지합니다. 빈 세트
{}와 빈 도형 배열[]은 오류입니다.
<script src="https://font.tty.link/lib/font-kku.js" data-fonts="sprite-system"></script>
<script>
FontKku.registerJamo("brush", {
"r0-ㄱ": "M5 12 H45 V28 Q45 54 11 65", // 획 중심선(칸 0–100)
"r1-ㅏ": ["M67 2 V86", "M67 39 H85"],
"r2-ㅇ": [{ ellipse: [43, 70, 27, 12] }] // { d, fill: true }는 채운 모양
}, { stroke: 8, cap: "round" });
</script>
<font-kku font="sprite-system" jamo-set="brush" size="72">붓글씨</font-kku>
폰꾸(font-kku) 그림 자소 글꼴을 그려줘(굵은 붓글씨 느낌, 먹색).
자모마다 SVG 경로를 FontKku.registerJamo로 등록하는 HTML 한 장으로 —
칸 키는 r행-자모(예: r0-ㄱ), 좌표는 칸 0–100이고 spec/sprite-layout.json의
자리 상자 안에 그려. profiles/sprite-system.jamo.js가 본보기야.
<font-kku font="sprite-system" jamo-set="이름">으로 미리보기 문장도 함께.
저장소에서는 scripts/build_jamo_svg.js가 획 정의 하나로 자소판 다섯 장과 자모 세트 모듈 세 개를 그립니다.
node scripts/build_jamo_svg.js --guide system guide.svg는 자리 상자를 그린 안내 SVG를 냅니다.
결정 근거는 ADR-011(SVG 자소판)과
ADR-013(벡터 자소)에 있습니다.
글꼴 파일로 내보내기
그림 자소 글꼴과 벡터 자소는 보통 글꼴 파일(TTF·WOFF)로 내보낼 수 있습니다. 폰꾸가 조립한 자리와 칸 그림으로 한글 11,172자를 브라우저에서 몇 초 만에 만들고, 손으로 그린 PNG 자소판도 외곽선을 따서 같은 길로 갑니다. 런타임 없이 워드·디자인 도구·다른 사이트에서 씁니다.
- 글꼴 시험대의 그림 자소 글꼴마다 "글꼴 파일 받기 (TTF)"가 있습니다.
- 글꼴 작업실 재료 탭의 "글꼴 파일(TTF)로 내보내기"는 지금 고치고 있는 자소판으로 만듭니다.
- 자소판은 페이지와 같은 출처여야 픽셀을 읽을 수 있습니다. 파일을 더블클릭해 연 페이지(
file://)에서는 안 됩니다. - 글꼴 파일은 단색입니다. 재료 색·질감 필터의 색은 빠지고 모양만 남으며, 영문·숫자는 기기 글꼴로 대체됩니다.
<script src="https://font.tty.link/lib/font-kku.js" data-fonts="sprite-system"></script>
<script src="https://font.tty.link/lib/font-kku-export.js"></script>
<script>
const { ttf, woff, report } = await FontKkuExport.exportFont({
font: "sprite-system", // 그림 자소 배치 글꼴
jamoSet: "brush", // 벡터 자소면 세트 이름(없으면 자소판)
familyName: "내 손글씨",
onProgress: (f) => console.log(Math.round(f * 100) + "%")
});
FontKkuExport.download(ttf, "my-hangul.ttf");
</script>
저장소에서는 python scripts/export_font_file.py --font pixel --out tmp/pixel.ttf --woff2 tmp/pixel.woff2 --compare가
같은 일을 헤드리스 브라우저에서 하고 fontTools로 검증합니다(WOFF2 약 20KB). 결정 근거는 ADR-015에 있습니다.
만든 글꼴 붙이기
글꼴 CSS를 링크하거나 FontKku.loadFont()로 불러온 뒤 font에 그 이름을 줍니다. 글꼴 파일을
런타임 옆 profiles/ 폴더에 두면 한 줄 설치의 data-fonts에 이름만 적어도 됩니다(런타임은
profiles/<이름>.css를 찾습니다). 런타임은 글꼴 이름을 소문자로 바꿔 기록하므로 글꼴 이름은 소문자로
짓습니다.
<link rel="stylesheet" href="./my-font.css" />
<font-kku font="my-font" size="48">나만의 글꼴</font-kku>
<script>
// 또는 JS에서: 불러온 뒤 바탕 요소를 다시 해석한다
FontKku.loadFont("./my-font.css");
</script>
글꼴 계약 한눈에
- 첫 규칙은 레이어 순서 선언
@layer font-kku.font, font-kku.jamo, font-kku.context;이고, 나머지 규칙은 모두 세 레이어 안에 둡니다. - 모든 선택자는
:where([data-fy-font="<이름>"])로 시작합니다. - 바탕 요소 규칙에 메타데이터
--fy-font-id: <이름>,--fy-contract: 2,--fy-material: system | sprite를 둡니다. - 자소에는 자리·자모·문맥 레벨 변수로만 닿습니다. 페이지 전용 변수(
--fy-top,--fy-weight…)와transform·font-size같은 코어 속성은 선언하지 않습니다.
자세한 문법은 ADR-008 글꼴 계약 v2, 교정 절차는 글꼴 제작·교정 안내, 그림 자소는 그림 자소 글꼴을 봅니다.
파생 글꼴 (기여자용)
저장소에 글꼴을 더할 때는 파생 글꼴로 만듭니다. spec/fonts.json의 글꼴 항목에 derivedFrom으로
기반 글꼴을 적고, 생성기가 기반 글꼴의 배치를 파일의 GENERATED 구간에 복사합니다. 꾸밈은 그 아래에 손으로
씁니다. 기반 글꼴이 다시 교정되면 같은 명령으로 따라가며, npm test가 어긋난 파생 글꼴과 자소판을 거부합니다.
node scripts/build_derived_fonts.js --write # 기반 배치를 GENERATED 구간에 복사
node scripts/build_jamo_svg.js --write # 그림 자소 자소판(SVG) 다시 그리기
npm test
결정 근거와 규칙은 ADR-010에 있습니다.
레퍼런스
JavaScript API
대부분의 정적 페이지는 API를 부르지 않아도 됩니다. 동적 글이나 나중에 넣은 DOM을 다룰 때 window.FontKku를
씁니다(ES module은 default와 같은 이름의 named export). 형식은 font-kku.d.ts에 있습니다.
| 이름과 인자 | 돌려주는 값·형식 | 하는 일 |
|---|---|---|
render(target, options?) | HTMLElement | 관리 DOM을 새로 만들어 바탕 요소를 바로 그립니다. 옵션은 이 호출에만 적용됩니다. |
refresh(target, options?) | HTMLElement | render와 같습니다. options.text로 원문을 바꿉니다. |
upgrade(target) | HTMLElement | null | 아직 관리하지 않는 요소 하나를 그립니다(중복 안전). 요소가 아니면 null. |
upgradeAll(root?) | HTMLElement[] | root 자손의 font-kku, .font-kku를 한꺼번에 그립니다. root 자신은 빼므로 root가 바탕 요소면 upgrade(root). |
bootstrap(root?) | HTMLElement[] | define() + upgradeAll(root || document). 자동으로 돌므로 자동 시작을 끈 경우에만 부릅니다. |
define() | void | 전용 태그 font-kku를 등록합니다. |
refreshAll(root?) | HTMLElement[] | 현재 스타일로 글꼴 메타데이터를 다시 해석합니다. 구조가 바뀌면 다시 만들고, 벡터 자소의 행만 바뀌면 해당 SVG를 교체하며 자모 트리는 유지합니다. shadow root 안도 포함하고, 메타데이터·구조·벡터 칠이 바뀐 바탕 요소를 돌려줍니다. |
retryFont(host) | HTMLElement | null | 현재 URL의 글꼴·자모 세트 자동 로딩을 다시 시도합니다. 연결된 진행 중 요청은 공유합니다. 관리 중인 바탕 요소와 자동 로딩 기준 주소가 있으면 그 요소, 없으면 null을 돌려줍니다. |
loadFont(href, { root }?) | Promise<HTMLElement[]> | 글꼴 CSS를 <link>로 넣고 도착하면 다시 해석합니다. |
fontInfo(host) | FontInfo | null | 직전 그리기의 { font, id, contract, material, loaded }. 그리기 전이면 null. |
decomposeGlyph(char, index?, depth?) | Glyph | 한 글자를 분해해 짜임·배치 키·자모를 돌려줍니다. |
createTextModel(text, depth?) | TextModel | 줄 → 낱말·공백 → 글자 트리. 모든 그리기 경로가 이 트리를 씁니다. |
isHangulSyllable(char?) | boolean | 완성형 한글 음절인지. |
renderText(text) | string[] | 평문 모드 2줄 출력(평문 모드). |
hashSeed(value) | number | 값을 0~1 사이 결정적 숫자로 해시합니다. |
applyJamoSeeds(target, options?) | number | 각 [data-jamo] 노드에 --fy-jamo-seed를 씁니다. 처리한 노드 수를 돌려줍니다. |
registerJamo(name, cells, options?) | { name, cells } | 벡터 자소 세트를 등록합니다. cells는 { "r0-ㄱ": "M8 12 H44 V30", … }(칸 0–100), options는 stroke·cap·join. 그 세트를 쓰는 바탕 요소를 다시 그립니다. 일부 칸만 등록할 수 있고 누락 칸은 기반 재료로 표시합니다. 빈 세트·빈 도형 배열은 오류입니다. |
jamoSets() | string[] | 등록된 벡터 자소 세트 이름. |
hasHangulFont(options?) | boolean | null | 기기 글꼴이 한글을 그릴 수 있는지(글꼴·입력기 없는 기기). 판단할 수 없으면 null. |
placementTables | Record<JamoDepth, PlacementTable> | 자모 깊이(atomic·composite·syllable)별 배치 표(짜임 키 9가지). 동결된 사본. |
placementTable | PlacementTable | atomic 배치 표. 예전 이름 호환용. |
version | string | 런타임 버전 (지금 "0.4.0"). |
그리기 — render · refresh
옵션은 text, font, size, spacing, wrapUnit, animate,
seed, jamoSeeds입니다(profile은 하위 호환). undefined·null·빈
문자열은 속성 값으로 돌아갑니다. 옵션은 그 호출에만 적용되므로, 나중에 자동으로 다시 그릴 때(속성·원문 변경, 늦게
도착한 글꼴 CSS)는 속성 값이 쓰입니다. 계속 유지할 값은 속성으로 씁니다.
const host = document.querySelector("#hero");
// 새 옵션으로 바로 그리기
FontKku.render(host, {
text: "광화문", font: "gothic", size: 64, wrapUnit: "line", animate: "rise"
});
// 글만 바꾸기
FontKku.refresh(host, { text: "새 문장" });
// 옵션을 계속 유지하려면 속성으로 — 저절로 다시 그린다
host.setAttribute("font", "sebul");
분리된 요소에 render()한 뒤 문서에 넣어도 됩니다. 분리 상태에서는 글꼴 스타일을 읽지 못해 기본 구조로
그리고, 연결될 때 같은 옵션으로 마저 해석합니다. 바탕 요소 안 DOM은 직접 고치지 않습니다 — 다음 그리기에서 덮입니다.
나중에 넣은 DOM과 프레임워크
자동 시작은 처음 문서만 훑습니다. innerHTML·insertAdjacentHTML로 넣은 조각은 직접 그립니다.
modal.innerHTML = '<h2 class="font-kku" data-font="sebul">알림</h2>';
FontKku.upgradeAll(modal); // 컨테이너를 넣었을 때
FontKku.upgrade(newHost); // 바탕 요소 하나를 넣었을 때
- React·Vue·Svelte에서는 마운트한 요소가 바탕 요소면
upgrade(el), 컨테이너면upgradeAll(el)을 한 번 부릅니다. - props로
data-font·data-size를 바꾸면 런타임이 감지해 다시 그립니다. 따로refresh()할 필요가 없습니다. - 언마운트 때 정리할 것이 없습니다. 런타임은 바탕 요소 상태를
WeakMap으로 들고 있습니다. - 가상 리스트처럼 분리 상태에서 그리고 나중에 붙여도 연결될 때 글꼴 구조를 해석합니다.
글꼴 CSS 불러오기 — loadFont · refreshAll · retryFont · fontInfo
loadFont(href, { root })는 <link rel="stylesheet">를 root(기본은 현재 문서의
<head>, ShadowRoot면 그 안)에 넣고, 도착하면 바탕 요소를 다시 해석합니다. 같은 URL·root면 같은 Promise를
돌려주고(이미 로드된 링크는 재사용), 실패하면 넣은 링크를 지우고 reject하므로 다음 호출이 다시 시도합니다.
<link>의 load는 런타임이 자동으로 처리하므로, refreshAll()은
adoptedStyleSheets나 <style> 내용 교체처럼 load 이벤트가 없는 스타일 변경 뒤에만
부릅니다. --fy-sprite-row를 바꾼 뒤에도 부르면 벡터 SVG를 현재 행으로 갱신하고,
해당 행의 그림이 없으면 기반 글꼴의 자소판·마스크 칠로 돌아갑니다.
자동 로딩 상태는 실제 URL과 로딩 root별로 관리합니다(CSS는 Document 또는 ShadowRoot, 자모 모듈은 Document).
실패한 같은 요청을 자동으로 반복하지 않으며, FontKku.retryFont(host)로 명시적으로 다시 시도합니다.
FontKkuConfig.fontBase를 바꾸거나 다른 root에서 요청하면 별도 요청으로 처리합니다.
진행 중인 요청은 공유하고, 요청 노드가 제거됐다면 다시 만들 수 있습니다.
자동 로딩 실패는 바탕 요소의 font-kku:loaderror 이벤트로 알립니다(bubbles: true,
composed: true). event.detail은 { kind, name, url, error }이고,
kind는 "font" 또는 "jamo-set", error는 Error입니다.
이 이벤트에서 오류를 표시하고 사용자 재시도 동작에 retryFont(host)를 연결할 수 있습니다.
직접 호출한 loadFont()의 실패는 반환 Promise의 reject로 처리합니다.
await FontKku.loadFont("./profiles/retro.css");
// 섀도 DOM 안이라면 그 shadow root에 넣는다
await FontKku.loadFont("./profiles/retro.css", { root: shadowRoot });
FontKku.fontInfo(host);
// { font: "retro", id: "retro", contract: 2, material: "system", loaded: true }
// adoptedStyleSheets·<style> 교체 뒤
FontKku.refreshAll();
fontInfo(host).loaded는 요청한 글꼴의 CSS(--fy-font-id)가 그 바탕 요소에 닿았는지 알려 줍니다.
글꼴이 제대로 붙었는지 확인할 때 씁니다.
그리기 이벤트 font-kku:render
모든 그리기(명시적 호출과 자동 다시 그리기) 뒤에 바탕 요소에서 font-kku:render 이벤트가 발생합니다
(bubbles: true, composed: true). 크기·간격·움직임 속성의 자동 갱신과 벡터 행의 재칠하기도
알리므로, 이벤트가 발생해도 관리 DOM이 새로 만들어졌다고 가정하지 않습니다. 후처리는 이 이벤트에 연결하되
같은 노드에 여러 번 실행해도 중복 리스너·장식 노드가 생기지 않도록 만듭니다.
document.addEventListener("font-kku:render", (event) => {
const { reason, font, source, resolved } = event.detail;
// event.target이 바탕 요소
});
detail.reason | 언제 |
|---|---|
render | FontKku.render() |
refresh | FontKku.refresh() |
upgrade | upgrade·upgradeAll·bootstrap의 첫 그리기 |
connect | 전용 태그 연결, 분리 상태에서 그린 요소의 연결 시 해석, 이동 뒤 재확인 |
attribute | 옵션 속성 변경(자모 트리를 유지하는 자동 갱신 포함) |
mutation | 원문 변경 |
restyle | 늦게 도착한 글꼴 CSS, refreshAll, loadFont에 따른 구조 변경·벡터 칠 갱신 |
font는 그리기에 쓴 정규화된 글꼴 이름, source는 원문입니다. resolved가
false면 분리 상태에서 그려 글꼴 스타일을 아직 해석하지 못한 것입니다. 글꼴 id처럼 DOM을 바꾸지 않는
메타데이터만 바뀐 재해석은 그리기가 아니므로 이벤트가 없습니다.
// 글을 고치면 FontKku.refresh(host, { text }), 글꼴을 누르면 host.setAttribute("font", …)
자동 시작 끄기
모델·평문 API만 쓰거나 DOM을 바꿀 시점을 직접 정하려면 런타임을 불러오기 전에 자동 시작을 끄고, 필요한
곳에만 bootstrap(root)을 부릅니다.
globalThis.FontKkuConfig = { bootstrap: false };
const { default: FontKku } = await import("font-kku");
FontKku.bootstrap(document.querySelector("#poster"));
분해 모델
decomposeGlyph와 createTextModel은 DOM 없이 Node에서도 동작합니다. 세 번째·두 번째 인자로
자모 깊이를 줄 수 있습니다 — 줄임형 문자열("atomic" · "composite" · "syllable")이나
축별 객체({ vowel: "syllable", coda: "split" }).
FontKku.decomposeGlyph("왕").topology;
// { jungLayout: "split-right", jongLayout: "single",
// placementKey: "split-right:single", layoutBucket: "split-closed" }
FontKku.createTextModel("과일 밟기", "composite");
FontKku.isHangulSyllable("한"); // true
FontKku.hashSeed("poster-v1"); // 0~1 사이의 같은 값
TypeScript는 font-kku.d.ts(ES module은 font-kku.d.mts)를 읽습니다.
font-kku:render와 font-kku:loaderror는 HTMLElementEventMap에 등록돼 있어
event.detail이 타입을 가집니다.
레퍼런스
속성·part·CSS 변수
그린 뒤의 DOM에서 페이지 CSS와 스크립트가 기댈 수 있는 표면입니다. 생성 DOM 전체 계약은
DOM 계약, 공개 CSS 변수 전체 목록은
spec/css-variables.json이 정본입니다.
바탕 요소의 정본 속성
런타임이 해석한 값을 바탕 요소에 기록합니다. 읽기 전용으로 쓰고, CSS에서 바탕 요소를 고를 때 씁니다.
| 속성 | 값 |
|---|---|
data-fy-upgraded | "true" — 런타임이 관리하는 바탕 요소 |
data-fy-host | custom-element(전용 태그) · decorator(클래스 방식) |
data-fy-font, data-fy-profile | 정규화한 글꼴 이름(소문자, 별칭을 푼 이름) |
data-fy-size | 해석한 크기 (24px, clamp(…) …) |
data-fy-spacing | spacing을 준 경우 |
data-fy-wrap-unit | glyph · word · line |
data-fy-animate | animate를 준 경우. 움직임 규칙이 이 속성을 읽습니다 |
data-fy-seed | seed를 준 경우, 해석한 숫자 |
data-fy-jamo-seeds | "true" — jamo-seeds를 켰을 때만 |
data-fy-expression | sebeol · general — 글꼴 CSS가 정한 표현 방식 |
data-fy-vowel-depth, data-fy-coda-depth | split·whole·syllable / split·whole — 해석한 자모 깊이 |
data-fy-material, data-fy-sprite-kinds | sprite · vector · bundled / consonant vowel [coda-cluster] — 재료가 system이 아닐 때만 |
data-fy-jamo-set | 벡터 자소로 그릴 때 자모 세트 이름. 자모 노드마다 <svg part="vector" data-cell="r0-ㄱ">이 붙습니다 |
data-fy-stroke-depth | 획 깊이를 요청했을 때만. 벡터 자소면 stroke(획마다 part="hoek"), 쪼갤 수 없는 재료면 jamo |
data-source, aria-label | 원문 (호환용 표면) |
클래스 방식의 data-font·data-size 같은 입력 속성은 작성자 것이라 런타임이 덮어쓰지 않습니다.
CSS는 바탕 요소 종류와 상관없이 data-fy-*를 읽습니다.
part 토큰
| 무리 | 토큰 |
|---|---|
| 컨테이너 | root line word space glyph glyph-body content(비한글 글자) |
| 묶음 | cluster jungseong-cluster jongseong-cluster |
| 자소 | jamo choseong jungseong jungseong-base jungseong-tail jongseong jongseong-left jongseong-right |
| 완성형 머리 | head — syllable 깊이에서만 (part="jamo head") |
| 접근성 | sr-source — 관리 root의 형제, 원문, 화면에서는 숨김 |
글자·자소 노드 속성
| 위치 | 속성 | 뜻 |
|---|---|---|
| 글자 | data-hangul, data-glyph-kind | 한글인지, hangul·latin·digit·punctuation·symbol |
| 글자 | data-jung-layout, data-jong-layout | 짜임: right·bottom·split-right / 받침: none·single·double |
| 글자 | data-layout-key | 위 둘을 합친 9가지 (right:none … split-right:double) |
| 자소 | data-jamo | 조합형 자모 값 (ᄀ, ᅡ, ᆼ) |
| 자소 | data-fy-role | choseong jung-main jung-base jung-tail jong-main jong-left jong-right (head) |
| 자소 | data-fy-pos | 배치 자리 토큰 (tlo, trc, bbrs …) |
| 줄·낱말·글자·자소 | data-line-index, data-word-index, data-glyph-index, data-jamo-index | 순번 (같은 값이 --fy-*-index 변수로도 들어감) |
CSS 변수
페이지 전용 — 페이지가 쓰고, 글꼴은 쓰지 않음
| 변수 | 뜻 |
|---|---|
--fy-top, --fy-left, --fy-font-size | 자소 위치·크기. 자리 값을 대신합니다(자모·문맥 보정은 그대로 더해짐) |
--fy-weight, --fy-stroke, --fy-clip | 굵기, 획 두께, 잘라내기. 글꼴 값을 대신합니다 |
--fy-scale-x, --fy-scale-y, --fy-rotate | 배율은 글꼴 값에 곱하고, 회전은 더합니다 |
--fy-jamo-depth, --fy-vowel-depth, --fy-coda-depth | 자모 깊이 (아래 표). 글꼴의 깊이를 이깁니다 |
--fy-sprite-filter, --fy-optical-sprite-zoom, --fy-optical-sprite-crop-x, --fy-optical-sprite-crop-y, --fy-optical-sprite-filter | 그림 자소 칠 필터와 작은 글씨용 보정 |
--fy-sprite-paint, --fy-sprite-fill | 그림 자소 칠 방식. mask면 자소판을 마스크로 쓰고 --fy-sprite-fill(기본 글자 색, 그라데이션·무늬 가능)로 칠합니다. 자모마다 color가 먹습니다 |
--fy-vector-stroke | 벡터 자소의 획 굵기(길이, 예: 0.06em). 자모·역할마다 줄 수 있습니다 |
글꼴이 정하고 페이지가 덮어도 되는 바탕 값
| 변수 | 코어 기본값 | 뜻 |
|---|---|---|
--fy-font-family | "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans CJK KR", sans-serif | 기기 글꼴 재료로 쓸 글꼴 |
--fy-space-width | 0.5em | 공백 한 칸 폭 |
--fy-word-gap | 0 | 낱말 뒤 간격 |
--fy-glyph-gap | 0 | 글자 뒤 간격 |
--fy-line-gap | 0 | 줄 사이 간격 |
런타임이 넣는 값 — 읽기 전용
| 변수 | 위치 | 뜻 |
|---|---|---|
--fy-size | 바탕 요소 | 해석한 size |
--fy-seed | 바탕 요소 | 해석한 씨앗값 0~1 (seed를 준 경우) |
--fy-line-index, --fy-word-index, --fy-glyph-index, --fy-jamo-index | 줄·낱말·글자·자소 | 순번 — 지연·파동 움직임에 |
--fy-space-count | 공백 | 이어진 공백 수 |
--fy-jamo-seed | 자소 | jamo-seeds·applyJamoSeeds()가 넣는 0~1 값 |
--fy-sprite-jamo-index | 자소 | 그림 자소의 자소판 열 번호 |
--fy-vector-stroke-cells | 벡터 칸 그림 | 자모 세트의 획 굵기(칸 100 기준) |
자모 깊이
섞임모음(ㅘ ㅙ ㅚ ㅝ ㅞ ㅟ ㅢ)과 겹받침을 얼마나 쪼갤지 정합니다. 글꼴마다 기본값이 있고(sebul은
syllable), 페이지가 바탕 요소에 변수로 덮을 수 있습니다. 축별 변수(--fy-vowel-depth,
--fy-coda-depth)가 줄임형(--fy-jamo-depth)을 이깁니다.
| 지정 | 과 | 쓰임새 |
|---|---|---|
--fy-jamo-depth: atomic | ㄱ+ㅗ+ㅏ 세 노드 | 자모별 색·움직임을 가장 잘게 |
--fy-jamo-depth: composite | ㄱ+ㅘ 두 노드 | 기기 글꼴의 합자 모양 그대로 |
--fy-jamo-depth: syllable | 고+ㅏ 두 노드 | 완성형 머리 — 합성 왜곡이 가장 적음 |
<font-kku font="sebul" style="--fy-jamo-depth: atomic">과일 밟기</font-kku>
<script>
// 인라인 style이나 load 이벤트 없는 스타일로 깊이를 바꾼 뒤에는 직접 다시 그린다
host.style.setProperty("--fy-vowel-depth", "whole");
FontKku.refresh(host);
</script>
레퍼런스
접근성과 지원 범위
스크린리더와 복사
- 런타임은 분해한 자소 DOM(
part="root")에aria-hidden="true"를 달고, 그 형제part="sr-source"노드에 원문을 둡니다. 이 노드가 스크린리더와 복사가 읽는 1차 표면이며 화면에서는 숨겨집니다. - 바탕 요소의
aria-label과data-source에도 원문을 기록합니다(호환 표면). - 제목처럼 의미가 있는 글은 클래스 방식으로 써서
<h1>·<h2>같은 의미 태그를 유지합니다. 이 문서의 제목도 모두<h2 class="font-kku">입니다.
검색과 서버 HTML
- 바탕 요소 안의 원래 텍스트 DOM은 관리 DOM으로 바뀝니다. 검색 노출이나 hydration을 보장한다고 기대하지 않습니다.
- 브라우저 런타임에는 서버 DOM·hydration API가 없습니다. 서버에서 정적 HTML이 필요하면 보조 렌더러의
render_html을 쓰거나, 서버는 원문만 내보내고 브라우저에서 그립니다. 둘을 겹쳐 쓰지 않습니다.
움직임과 대비
- 내장 움직임은
prefers-reduced-motion: reduce에서 꺼지고, 초당 3회 이상의 강한 밝기 점멸을 피합니다. 직접 만든 움직임에는 같은 가드를 직접 넣습니다. - 바탕 조건을 지킵니다.
neon·chalk·glitch는 어두운 바탕,blueprint는 파란 도면 바탕,sticker는 색 바탕,dojang·riso는 종이색 밝은 바탕입니다. - 제목과 짧은 강조(1~20자)에만 씁니다. 본문 문단, 표, 버튼 라벨, 입력창, 긴 목록에는 쓰지 않습니다.
지원 브라우저
- 기본 제공 글꼴이
:has()와@layer를 쓰므로 실제 기준선은:has()를 지원하는 브라우저(Chrome 105, Safari 15.4, Firefox 121 이상)입니다. - 자동 브라우저 회귀 검사는 Playwright Chromium을 기준으로 합니다. Firefox·WebKit은 아직 릴리스 검사에 들어 있지 않습니다.
- 기기 글꼴 재료는 방문자 기기의 한글 글꼴을 쓰므로 운영체제마다 획 모양이 조금 다릅니다. 어디서나 같은 모양이 필요하면 그림 자소 글꼴을 고릅니다.
레퍼런스
자주 묻는 질문
본문 글꼴로 써도 되나요?
아니요. 폰꾸는 글자마다 관리 DOM을 만들기 때문에 일반 웹 글꼴이나 긴 본문 렌더러를 대신하지 않습니다. 포스터, 제목,
배너, 짧은 강조 문구처럼 모양과 움직임이 중요한 글(1~20자 정도)에 씁니다. 본문은 평소처럼 CSS
font-family로 둡니다.
한글 글꼴이 없는 기기에서는 어떻게 보이나요?
sebul·gothic과 그 파생 글꼴은 기기 한글 글꼴을 재료로 씁니다(기본 목록:
"Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans CJK KR"). 한글 글꼴이 없는 기기에서도 같은 모양이 필요하면
자소판으로 그리는 그림 자소 글꼴(sprite-system·pixel·dojang·chalk·crayon·jasu, 또는 직접 그린 자소판)을 씁니다.
영문·숫자·이모지는 어떻게 되나요?
한 코드포인트짜리 완성형 한글 음절만 분해합니다. 영문·숫자·문장부호·이모지는 조립하지 않고 part="content"
안에 기기 글꼴로 그려 한글과 같은 기준선에 둡니다. 그래서 "오늘만 1+1" 같은 글도 그대로 씁니다.
글자 수가 많으면 느려지나요?
글자 하나마다 글자·자소 노드 여러 개를 만들므로 글자 수만큼 DOM이 늘어납니다. 제목과 강조에만 쓰고, 한 페이지에는
글꼴을 2~3종까지만 씁니다. 여러 바탕 요소를 한꺼번에 넣을 때는 upgradeAll(container)가 스타일 읽기를 모아
한 번에 처리합니다.
React·Vue 같은 프레임워크에서 쓸 수 있나요?
됩니다. 마운트한 요소에 FontKku.upgrade(el) 또는 upgradeAll(el)을 한 번 부르고, 옵션은
data-* 속성으로 바꿉니다. 런타임이 변경을 감지해 다시 그리고, 언마운트 때 정리할 것은 없습니다. 바탕 요소
안쪽 DOM은 프레임워크가 렌더하지 않도록 글만 넣어 둡니다(나중에 넣은 DOM과 프레임워크).
제목이 ㄱㅏ ㅇ… 처럼 흩어져 보여요.
글꼴 CSS가 붙지 않은 것입니다. 한 줄 설치의 data-fonts에 그 글꼴 이름이 있는지, 스크립트 주소가 맞는지
확인합니다. 콘솔에서 FontKku.fontInfo(host).loaded가 false면 CSS가 바탕 요소에 닿지 않은
것입니다.
라이선스는 무엇인가요?
코드(JavaScript, CSS, 도구, 보조 렌더러)와 기본 제공 자소판(profiles/*.svg, 직접 그린 그림)은 모두 MIT입니다.
개발 도구가 명시적으로 받는 외부 글꼴의 경계는 Third-party notices를 봅니다.
더 보기
개발 도구
짜임과 그리기를 검증하고 실험하는 페이지입니다. 공개 메뉴에는 없고, 브라우저 회귀 검사가 이 페이지들을 씁니다. 화면이 거칠 수 있지만 런타임의 실제 동작을 가장 자세히 볼 수 있습니다.
- 기본 글sebul·gothic을 같은 문장·혼합 콘텐츠·구조 스트레스 문장으로 비교basic-text.html
- 줄바꿈글자·낱말·줄 단위 줄바꿈과 포스터형 대형 배치layout-modes.html
- 짜임 비교네모꼴·탈네모꼴 기준으로 짜임과 문제 글자를 점검shape-comparison.html
- 놀이터render·renderText·createTextModel을 조작하며 DOM·평문·구조 모델을 나란히 확인api-studio.html
- 그림 자소 보기자소판에서 자소를 잘라 조합하는 계약과 작은 크기 비교sprite-demo.html
- 움직임글자·자소 단위 움직임 훅과 씨앗값 변주animation-catalog.html
저장소 문서: README · 용어집 · Framework Guide · Browser Runtime Design · TESTING.md