PAFont Usage
PAFont는 폰트를 텍스트 렌더러가 아니라 도형 생성기로 다루는 유틸리티입니다.
현재 public entry:
src/paFont/index.js- 내부 구현:
src/paFont/core.jssrc/paFont/shape.jssrc/paFont/geometry.js
핵심 흐름
새 API는 shape 중심으로 동작합니다.
PAFont.load(...)로 폰트를 로드합니다.font.text(...)또는font.glyph(...)로PAShape를 만듭니다.shape.toShape(...),shape.toRegions(...),shape.toPoints(...)중 필요한 결과를 꺼냅니다.
모든 변환 옵션은 절대 좌표 단위입니다.
step: 재샘플링 또는 점 간격openWidth: hole을 slit로 열 때의 폭
비례형 sample/open 개념은 더 이상 사용하지 않습니다.
Breaking Changes
이번 버전은 클린 브레이크입니다.
font.textToPoints()제거shape.outline()제거shape.regions()제거shape.points()제거shape.open()제거shape.openHoles()추가- 고수준 변환은 object-only 옵션으로 통일
sample,open같은 비례형 옵션 제거
폰트 로드
1. 공개 URL 로드
브라우저에서 가장 단순한 방법입니다.
import PAFont from "./src/paFont/index.js";
const font = await PAFont.load("/assets/font.otf");
2. 모듈 상대 경로 로드
모듈 기준 상대 경로가 필요하면 base를 같이 넘깁니다.
import PAFont from "./src/paFont/index.js";
const font = await PAFont.load("./font.otf", {
base: import.meta.url,
});
3. URL 객체로 로드
const url = new URL("./font.otf", import.meta.url);
const font = await PAFont.load(url);
4. ArrayBuffer / TypedArray로 로드
Node나 커스텀 로더와 함께 쓸 때 적합합니다.
import { readFile } from "node:fs/promises";
import PAFont from "./src/paFont/index.js";
const bytes = await readFile("./fonts/font.otf");
const font = await PAFont.load(bytes);
경로 관련 주의점
- 브라우저에서 plain string 경로는 현재 페이지 URL 기준으로 해석됩니다.
- 모듈 파일 기준 상대 경로를 쓰고 싶으면
{ base: import.meta.url }를 써야 합니다. - 폰트 URL이 HTML을 돌려주면
PAFont.load()는 원인과 해결책을 포함한 에러를 던집니다.
Font API
font.text(value, options)
문자열 전체를 하나의 PAShape로 만듭니다.
const text = font.text("가나다", {
x: 0,
y: 200,
size: 160,
flatten: 1,
});
font.glyph(value, options)
글자 하나를 PAShape로 만듭니다.
const glyph = font.glyph("영", {
x: 0,
y: 200,
size: 160,
flatten: 1,
});
font.metrics(value, options)
텍스트의 폭과 bounding box만 빠르게 측정합니다.
const metrics = font.metrics("가나다", {
x: 0,
y: 200,
size: 160,
});
console.log(metrics.width);
console.log(metrics.bbox);
PAShape API
font.text()와 font.glyph()는 모두 PAShape를 반환합니다.
PAShape에서 가장 자주 쓰는 메서드는 아래 3개입니다.
toShape({ step, openWidth })toRegions({ step, openWidth })toPoints({ step, openWidth, includeHoles })
shape.glyphs()
문장 shape를 글자별 shape 배열로 분리합니다.
const glyphs = font.text("가나다", { size: 160 }).glyphs();
console.log(glyphs.length); // 3
shape.toShape({ step = 0, openWidth = 0 })
shape 자체를 변환한 뒤 다시 PAShape로 돌려줍니다.
const shape = font.glyph("영", { size: 160 }).toShape({
step: 8,
openWidth: 1,
});
shape.hit(10, 10);
shape.contains(10, 10);
shape.toRegions();
설명:
openWidth > 0이면 hole을 slit로 엽니다.step > 0이면 polygon을 재샘플링합니다.- 반환값이
PAShape이므로hit(),contains(),glyphs()등을 계속 쓸 수 있습니다.
shape.toRegions({ step = 0, openWidth = 0 })
최종 도형을 plain region data로 반환합니다.
const regions = font.glyph("가", { size: 160 }).toRegions();
반환 구조:
[
{
outer: [[x, y], ...],
holes: [
[[x, y], ...],
],
bbox: { x, y, w, h },
},
];
예시:
const lowpoly = font.glyph("영", { size: 160 }).toRegions({
step: 8,
openWidth: 1,
});
권장 기준:
- exact polygon이 필요하면
toRegions() - slit 적용 shape data가 필요하면
toRegions({ openWidth }) - lowpoly polygon이 필요하면
toRegions({ step }) - slit + lowpoly가 같이 필요하면
toRegions({ step, openWidth })
shape.toPoints({ step = 8, openWidth = 0, includeHoles = true })
경계를 점으로 샘플링합니다.
const points = font.glyph("가", { size: 160 }).toPoints({
step: 8,
});
const points = font.glyph("영", { size: 160 }).toPoints({
step: 8,
openWidth: 1,
includeHoles: false,
});
반환 구조:
[
{
x: 10,
y: 20,
partIndex: 0,
hole: false,
},
];
설명:
step이 작을수록 더 촘촘합니다.openWidth가 있으면 점 샘플링 전에 slit를 적용합니다.includeHoles: false면 hole 경계 점을 제외합니다.
Low-level Shape Transforms
고수준에서는 toShape()를 쓰면 충분하지만, 변환 단계를 직접 나누고 싶다면 low-level 메서드를 쓸 수 있습니다.
shape.openHoles(width)
hole을 slit 방식으로 열어 hole 없는 경계에 가깝게 바꾼 새 PAShape를 반환합니다.
const opened = font.glyph("O", { size: 160 }).openHoles(1);
const regions = opened.toRegions();
shape.resample(step)
shape를 재샘플링한 새 PAShape를 반환합니다.
const sparse = font.glyph("영", { size: 160 }).resample(8);
const regions = sparse.toRegions();
shape.hit(x, y) / shape.contains(x, y)
const glyph = font.glyph("영", { size: 160 });
console.log(glyph.hit(30, -30));
console.log(glyph.contains(30, -30));
hit() 결과:
"fill""hole""edge""outside"
자주 쓰는 패턴
문장을 글자별로 처리
for (const glyph of font.text("영가", { size: 160 }).glyphs()) {
const regions = glyph.toRegions();
console.log(regions.length);
}
slit + lowpoly region
const regions = font.glyph("영", { size: 160 }).toRegions({
step: 8,
openWidth: 1,
});
충돌 판정 가능한 shape
const shape = font.glyph("영", { size: 160 }).toShape({
step: 8,
openWidth: 1,
});
if (shape.contains(20, -20)) {
console.log("inside");
}
경계 점 추출
const points = font.glyph("가", { size: 160 }).toPoints({
step: 8,
});
짧은 요약
제일 자주 쓰는 흐름은 이 셋입니다.
font.text(...)
shape.glyphs()
shape.toRegions(...)
shape 자체를 유지한 채 후속 계산까지 이어가고 싶으면:
shape.toShape({ step, openWidth })
점만 필요하면:
shape.toPoints({ step, openWidth, includeHoles })