| + |
# PAFont Usage |
| + |
|
| + |
`PAFont`는 폰트를 텍스트 렌더러가 아니라 도형 생성기로 다루는 유틸리티입니다. |
| + |
|
| + |
현재 public entry: |
| + |
|
| + |
- `src/paFont/index.js` |
| + |
- 내부 구현: |
| + |
- `src/paFont/core.js` |
| + |
- `src/paFont/shape.js` |
| + |
- `src/paFont/geometry.js` |
| + |
|
| + |
## 핵심 흐름 |
| + |
|
| + |
새 API는 shape 중심으로 동작합니다. |
| + |
|
| + |
1. `PAFont.load(...)`로 폰트를 로드합니다. |
| + |
2. `font.text(...)` 또는 `font.glyph(...)`로 `PAShape`를 만듭니다. |
| + |
3. `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 로드 |
| + |
|
| + |
브라우저에서 가장 단순한 방법입니다. |
| + |
|
| + |
```js |
| + |
import PAFont from "./src/paFont/index.js"; |
| + |
|
| + |
const font = await PAFont.load("/assets/font.otf"); |
| + |
``` |
| + |
|
| + |
### 2. 모듈 상대 경로 로드 |
| + |
|
| + |
모듈 기준 상대 경로가 필요하면 `base`를 같이 넘깁니다. |
| + |
|
| + |
```js |
| + |
import PAFont from "./src/paFont/index.js"; |
| + |
|
| + |
const font = await PAFont.load("./font.otf", { |
| + |
base: import.meta.url, |
| + |
}); |
| + |
``` |
| + |
|
| + |
### 3. URL 객체로 로드 |
| + |
|
| + |
```js |
| + |
const url = new URL("./font.otf", import.meta.url); |
| + |
const font = await PAFont.load(url); |
| + |
``` |
| + |
|
| + |
### 4. ArrayBuffer / TypedArray로 로드 |
| + |
|
| + |
Node나 커스텀 로더와 함께 쓸 때 적합합니다. |
| + |
|
| + |
```js |
| + |
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`로 만듭니다. |
| + |
|
| + |
```js |
| + |
const text = font.text("가나다", { |
| + |
x: 0, |
| + |
y: 200, |
| + |
size: 160, |
| + |
flatten: 1, |
| + |
}); |
| + |
``` |
| + |
|
| + |
### `font.glyph(value, options)` |
| + |
|
| + |
글자 하나를 `PAShape`로 만듭니다. |
| + |
|
| + |
```js |
| + |
const glyph = font.glyph("영", { |
| + |
x: 0, |
| + |
y: 200, |
| + |
size: 160, |
| + |
flatten: 1, |
| + |
}); |
| + |
``` |
| + |
|
| + |
### `font.metrics(value, options)` |
| + |
|
| + |
텍스트의 폭과 bounding box만 빠르게 측정합니다. |
| + |
|
| + |
```js |
| + |
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 배열로 분리합니다. |
| + |
|
| + |
```js |
| + |
const glyphs = font.text("가나다", { size: 160 }).glyphs(); |
| + |
|
| + |
console.log(glyphs.length); // 3 |
| + |
``` |
| + |
|
| + |
### `shape.toShape({ step = 0, openWidth = 0 })` |
| + |
|
| + |
shape 자체를 변환한 뒤 다시 `PAShape`로 돌려줍니다. |
| + |
|
| + |
```js |
| + |
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로 반환합니다. |
| + |
|
| + |
```js |
| + |
const regions = font.glyph("가", { size: 160 }).toRegions(); |
| + |
``` |
| + |
|
| + |
반환 구조: |
| + |
|
| + |
```js |
| + |
[ |
| + |
{ |
| + |
outer: [[x, y], ...], |
| + |
holes: [ |
| + |
[[x, y], ...], |
| + |
], |
| + |
bbox: { x, y, w, h }, |
| + |
}, |
| + |
]; |
| + |
``` |
| + |
|
| + |
예시: |
| + |
|
| + |
```js |
| + |
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 })` |
| + |
|
| + |
경계를 점으로 샘플링합니다. |
| + |
|
| + |
```js |
| + |
const points = font.glyph("가", { size: 160 }).toPoints({ |
| + |
step: 8, |
| + |
}); |
| + |
``` |
| + |
|
| + |
```js |
| + |
const points = font.glyph("영", { size: 160 }).toPoints({ |
| + |
step: 8, |
| + |
openWidth: 1, |
| + |
includeHoles: false, |
| + |
}); |
| + |
``` |
| + |
|
| + |
반환 구조: |
| + |
|
| + |
```js |
| + |
[ |
| + |
{ |
| + |
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`를 반환합니다. |
| + |
|
| + |
```js |
| + |
const opened = font.glyph("O", { size: 160 }).openHoles(1); |
| + |
const regions = opened.toRegions(); |
| + |
``` |
| + |
|
| + |
### `shape.resample(step)` |
| + |
|
| + |
shape를 재샘플링한 새 `PAShape`를 반환합니다. |
| + |
|
| + |
```js |
| + |
const sparse = font.glyph("영", { size: 160 }).resample(8); |
| + |
const regions = sparse.toRegions(); |
| + |
``` |
| + |
|
| + |
### `shape.hit(x, y)` / `shape.contains(x, y)` |
| + |
|
| + |
```js |
| + |
const glyph = font.glyph("영", { size: 160 }); |
| + |
|
| + |
console.log(glyph.hit(30, -30)); |
| + |
console.log(glyph.contains(30, -30)); |
| + |
``` |
| + |
|
| + |
`hit()` 결과: |
| + |
|
| + |
- `"fill"` |
| + |
- `"hole"` |
| + |
- `"edge"` |
| + |
- `"outside"` |
| + |
|
| + |
## 자주 쓰는 패턴 |
| + |
|
| + |
### 문장을 글자별로 처리 |
| + |
|
| + |
```js |
| + |
for (const glyph of font.text("영가", { size: 160 }).glyphs()) { |
| + |
const regions = glyph.toRegions(); |
| + |
console.log(regions.length); |
| + |
} |
| + |
``` |
| + |
|
| + |
### slit + lowpoly region |
| + |
|
| + |
```js |
| + |
const regions = font.glyph("영", { size: 160 }).toRegions({ |
| + |
step: 8, |
| + |
openWidth: 1, |
| + |
}); |
| + |
``` |
| + |
|
| + |
### 충돌 판정 가능한 shape |
| + |
|
| + |
```js |
| + |
const shape = font.glyph("영", { size: 160 }).toShape({ |
| + |
step: 8, |
| + |
openWidth: 1, |
| + |
}); |
| + |
|
| + |
if (shape.contains(20, -20)) { |
| + |
console.log("inside"); |
| + |
} |
| + |
``` |
| + |
|
| + |
### 경계 점 추출 |
| + |
|
| + |
```js |
| + |
const points = font.glyph("가", { size: 160 }).toPoints({ |
| + |
step: 8, |
| + |
}); |
| + |
``` |
| + |
|
| + |
## 짧은 요약 |
| + |
|
| + |
제일 자주 쓰는 흐름은 이 셋입니다. |
| + |
|
| + |
```js |
| + |
font.text(...) |
| + |
shape.glyphs() |
| + |
shape.toRegions(...) |
| + |
``` |
| + |
|
| + |
shape 자체를 유지한 채 후속 계산까지 이어가고 싶으면: |
| + |
|
| + |
```js |
| + |
shape.toShape({ step, openWidth }) |
| + |
``` |
| + |
|
| + |
점만 필요하면: |
| + |
|
| + |
```js |
| + |
shape.toPoints({ step, openWidth, includeHoles }) |
| + |
``` |