|
# paFont Usage |
| − |
|
| − |
`paFont`는 폰트를 텍스트 렌더러가 아니라 도형 생성기로 다루는 유틸리티입니다. |
| − |
|
| − |
현재 public entry: |
| − |
|
| − |
- `src/paFont.js` |
| − |
- 내부 구현: |
| − |
- `src/paFont/paFont.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(...)` 중 필요한 결과를 꺼냅니다. |
| − |
|
| − |
문단이 필요할 때는: |
| − |
|
| − |
1. `font.paragraph(...)`로 `paParagraph`를 만듭니다. |
| − |
2. `paragraph.drawText(ctx)`로 canvas 텍스트를 그립니다. |
| − |
3. 필요하면 `paragraph.toShape()`, `paragraph.toRegions()`, `paragraph.toPoints()`로 같은 레이아웃을 geometry로 바꿉니다. |
| − |
|
| − |
모든 변환 옵션은 절대 좌표 단위입니다. |
| − |
|
| − |
- `step`: 재샘플링 또는 점 간격 |
| − |
- `openWidth`: hole을 slit로 열 때의 폭 |
| − |
|
| − |
비례형 `sample/open` 개념은 더 이상 사용하지 않습니다. |
| − |
|
| − |
## Breaking Changes |
| − |
|
| − |
이번 버전은 클린 브레이크입니다. |
| − |
- `font.textToPoints()` 제거 |
| − |
- `shape.outline()` 제거 |
| − |
- `shape.regions()` 제거 |
| − |
- `shape.points()` 제거 |
| − |
- `shape.open()` 제거 |
| − |
- `shape.openHoles()` 추가 |
| − |
- 고수준 변환은 object-only 옵션으로 통일 |
| − |
- `sample`, `open` 같은 비례형 옵션 제거 |
| + |
`paFont`는 OpenType 폰트를 읽어: |
| − |
## 폰트 로드 |
| + |
- canvas 문단 텍스트로 그리거나 |
| + |
- polygon / region / point geometry로 변환하는 라이브러리입니다. |
| − |
### 1. 공개 URL 로드 |
| + |
## 기본 흐름 |
| − |
브라우저에서 가장 단순한 방법입니다. |
| + |
### 1. 폰트 로드 |
|
```js |
|
const font = await paFont.load("/assets/font.otf"); |
|
``` |
| − |
|
| − |
### 2. 모듈 상대 경로 로드 |
| − |
모듈 기준 상대 경로가 필요하면 `base`를 같이 넘깁니다. |
| + |
모듈 상대 경로를 쓰고 싶으면: |
|
```js |
| − |
import paFont from "./src/paFont.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.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`로 만듭니다. |
| + |
### 1. 텍스트를 geometry로 만들기 |
|
```js |
| − |
const glyph = font.glyph("영", { |
| + |
const shape = font.text("안녕", { |
|
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); |
| + |
const regions = shape.toRegions(); |
| + |
const points = shape.toPoints({ step: 8 }); |
|
``` |
| − |
|
| − |
### `font.paragraph(value, options)` |
| − |
`pretext.js` 기반 문단 레이아웃 객체를 만듭니다. |
| + |
### 2. 문단을 canvas에 그리고, 필요하면 geometry로 바꾸기 |
|
```js |
| − |
const paragraph = font.paragraph("문단입니다.", { |
| + |
const paragraph = font.paragraph("캔버스에서 자동 줄바꿈되는 문단입니다.", { |
|
x: 40, |
|
y: 80, |
|
size: 32, |
|
fontFamily: "MyFont", |
| + |
fontWeight: 400, |
|
lineHeight: 1.4, |
| + |
align: "left", |
| + |
engine: "pretext", |
|
}); |
| − |
paragraph.drawText(ctx); |
| + |
paragraph.drawText(ctx, { |
| + |
fillStyle: "#111", |
| + |
}); |
|
const shape = paragraph.toShape(); |
|
``` |
| − |
옵션 요약: |
| + |
## Font API |
| − |
- `width`: 필수 문단 폭 |
| − |
- `lineHeight`: `1.4` 같은 배수 또는 `44` 같은 절대 px |
| − |
- `fontFamily`, `fontWeight`, `fontStyle`, `font`: canvas 측정/렌더링용 폰트 설정 |
| − |
- `align`: `left`, `center`, `right`, `justify` |
| − |
- `whiteSpace`: `normal`, `pre-wrap`, `nowrap` |
| − |
- `engine`: `pretext`, `native` |
| + |
### `paFont.load(source, options?)` |
| − |
메서드: |
| + |
폰트를 로드합니다. |
| − |
- `paragraph.relayout({ width })` |
| − |
- `paragraph.drawText(ctx, { fillStyle, strokeStyle, fill, stroke })` |
| − |
- `paragraph.toShape({ layout, step, openWidth })` |
| − |
- `paragraph.toRegions({ layout, step, openWidth })` |
| − |
- `paragraph.toPoints({ layout, step, openWidth, includeHoles })` |
| + |
- `source`: `string | URL | ArrayBuffer | ArrayBufferView` |
| + |
- `options.base`: 모듈 기준 상대 경로 해석용 |
| − |
## PAShape API |
| + |
### `font.text(value, options?)` |
| − |
`font.text()`와 `font.glyph()`는 모두 `PAShape`를 반환합니다. |
| + |
문자열 전체를 `PAShape`로 만듭니다. |
| − |
`PAShape`에서 가장 자주 쓰는 메서드는 아래 3개입니다. |
| + |
주요 옵션: |
| − |
- `toShape({ step, openWidth })` |
| − |
- `toRegions({ step, openWidth })` |
| − |
- `toPoints({ step, openWidth, includeHoles })` |
| + |
- `x`, `y`: baseline 시작 위치 |
| + |
- `size`: 글자 크기 |
| + |
- `flatten`: 곡선 평탄화 허용 오차 |
| + |
- `kerning`, `letterSpacing`, `tracking` |
| + |
- `script`, `language`, `features` |
| − |
### `shape.glyphs()` |
| + |
### `font.glyph(value, options?)` |
| − |
문장 shape를 글자별 shape 배열로 분리합니다. |
| + |
한 글자만 `PAShape`로 만듭니다. |
| − |
```js |
| − |
const glyphs = font.text("가나다", { size: 160 }).glyphs(); |
| + |
### `font.metrics(value, options?)` |
| − |
console.log(glyphs.length); // 3 |
| + |
텍스트 폭과 bounding box만 빠르게 계산합니다. |
| + |
|
| + |
```js |
| + |
const metrics = font.metrics("안녕", { size: 120 }); |
| + |
console.log(metrics.width, metrics.bbox); |
|
``` |
| − |
### `shape.toShape({ step = 0, openWidth = 0 })` |
| + |
### `font.paragraph(value, options)` |
| − |
shape 자체를 변환한 뒤 다시 `PAShape`로 돌려줍니다. |
| + |
문단 레이아웃 객체 `paParagraph`를 만듭니다. |
| − |
```js |
| − |
const shape = font.glyph("영", { size: 160 }).toShape({ |
| − |
step: 8, |
| − |
openWidth: 1, |
| − |
}); |
| + |
주요 옵션: |
| − |
shape.hit(10, 10); |
| − |
shape.contains(10, 10); |
| − |
shape.toRegions(); |
| − |
``` |
| + |
- `width`: 필수, 문단 폭 |
| + |
- `x`, `y`, `size` |
| + |
- `lineHeight`: `1.4` 같은 배수 또는 절대 px |
| + |
- `align`: `left | center | right | justify` |
| + |
- `whiteSpace`: `normal | pre-wrap | nowrap` |
| + |
- `overflowWrap`: `normal | break-word | anywhere` |
| + |
- `fontFamily`, `fontWeight`, `fontStyle`, `font` |
| + |
- `engine`: `pretext | native` |
| + |
- `maxLines`, `ellipsis` |
| − |
설명: |
| + |
## paParagraph API |
| − |
- `openWidth > 0`이면 hole을 slit로 엽니다. |
| − |
- `step > 0`이면 polygon을 재샘플링합니다. |
| − |
- 반환값이 `PAShape`이므로 `hit()`, `contains()`, `glyphs()` 등을 계속 쓸 수 있습니다. |
| + |
`font.paragraph()`는 `paParagraph`를 반환합니다. |
| − |
### `shape.toRegions({ step = 0, openWidth = 0 })` |
| + |
주요 속성: |
| − |
최종 도형을 plain region data로 반환합니다. |
| + |
- `paragraph.text` |
| + |
- `paragraph.lines` |
| + |
- `paragraph.metrics` |
| + |
- `paragraph.options` |
| − |
```js |
| − |
const regions = font.glyph("가", { size: 160 }).toRegions(); |
| − |
``` |
| + |
주요 메서드: |
| − |
반환 구조: |
| + |
### `paragraph.relayout(next?)` |
| + |
|
| + |
폭이나 정렬이 바뀌었을 때 문단을 다시 레이아웃합니다. |
|
```js |
| − |
[ |
| − |
{ |
| − |
outer: [[x, y], ...], |
| − |
holes: [ |
| − |
[[x, y], ...], |
| − |
], |
| − |
bbox: { x, y, w, h }, |
| − |
}, |
| − |
]; |
| + |
const mobile = paragraph.relayout({ width: 280 }); |
|
``` |
| − |
예시: |
| + |
### `paragraph.drawText(ctx, options?)` |
| + |
|
| + |
현재 문단 레이아웃을 canvas 텍스트로 그립니다. |
|
```js |
| − |
const lowpoly = font.glyph("영", { size: 160 }).toRegions({ |
| − |
step: 8, |
| − |
openWidth: 1, |
| + |
paragraph.drawText(ctx, { |
| + |
fillStyle: "#111", |
|
}); |
|
``` |
| − |
|
| − |
권장 기준: |
| − |
- 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 })` |
| + |
- `fillStyle` |
| + |
- `strokeStyle` |
| + |
- `fill` |
| + |
- `stroke` |
| − |
경계를 점으로 샘플링합니다. |
| + |
### `paragraph.toShape(options?)` |
| − |
```js |
| − |
const points = font.glyph("가", { size: 160 }).toPoints({ |
| − |
step: 8, |
| − |
}); |
| − |
``` |
| + |
문단 전체를 하나의 `PAShape`로 변환합니다. |
|
```js |
| − |
const points = font.glyph("영", { size: 160 }).toPoints({ |
| − |
step: 8, |
| + |
const shape = paragraph.toShape({ |
| + |
layout: "current", |
| + |
step: 6, |
|
openWidth: 1, |
| − |
includeHoles: false, |
|
}); |
| − |
``` |
| − |
|
| − |
반환 구조: |
| − |
|
| − |
```js |
| − |
[ |
| − |
{ |
| − |
x: 10, |
| − |
y: 20, |
| − |
partIndex: 0, |
| − |
hole: false, |
| − |
}, |
| − |
]; |
|
``` |
| − |
설명: |
| + |
### `paragraph.toRegions(options?)` |
| − |
- `step`이 작을수록 더 촘촘합니다. |
| − |
- `openWidth`가 있으면 점 샘플링 전에 slit를 적용합니다. |
| − |
- `includeHoles: false`면 hole 경계 점을 제외합니다. |
| + |
문단 전체를 plain region 데이터로 반환합니다. |
| − |
## Low-level Shape Transforms |
| + |
### `paragraph.toPoints(options?)` |
| − |
고수준에서는 `toShape()`를 쓰면 충분하지만, 변환 단계를 직접 나누고 싶다면 low-level 메서드를 쓸 수 있습니다. |
| + |
문단 전체를 점 샘플로 반환합니다. |
| − |
### `shape.openHoles(width)` |
| + |
공통 옵션: |
| − |
hole을 slit 방식으로 열어 hole 없는 경계에 가깝게 바꾼 새 `PAShape`를 반환합니다. |
| + |
- `layout`: `current | pretext | native` |
| + |
- `step` |
| + |
- `openWidth` |
| + |
- `includeHoles` (`toPoints()` 전용) |
| − |
```js |
| − |
const opened = font.glyph("O", { size: 160 }).openHoles(1); |
| − |
const regions = opened.toRegions(); |
| − |
``` |
| + |
`layout` 의미: |
| − |
### `shape.resample(step)` |
| + |
- `current`: 현재 문단 줄바꿈 그대로 사용 |
| + |
- `pretext`: `pretext` 기준으로 다시 줄 계산 |
| + |
- `native`: `opentype.js` 측정 기준으로 다시 줄 계산 |
| − |
shape를 재샘플링한 새 `PAShape`를 반환합니다. |
| + |
## PAShape API |
| − |
```js |
| − |
const sparse = font.glyph("영", { size: 160 }).resample(8); |
| − |
const regions = sparse.toRegions(); |
| − |
``` |
| + |
`font.text()`와 `font.glyph()`는 `PAShape`를 반환합니다. |
| − |
### `shape.hit(x, y)` / `shape.contains(x, y)` |
| + |
주요 속성: |
| − |
```js |
| − |
const glyph = font.glyph("영", { size: 160 }); |
| + |
- `shape.text` |
| + |
- `shape.bbox` |
| + |
- `shape.metrics` |
| + |
- `shape.polygons` |
| − |
console.log(glyph.hit(30, -30)); |
| − |
console.log(glyph.contains(30, -30)); |
| − |
``` |
| + |
주요 메서드: |
| − |
`hit()` 결과: |
| + |
### `shape.glyphs()` |
| − |
- `"fill"` |
| − |
- `"hole"` |
| − |
- `"edge"` |
| − |
- `"outside"` |
| + |
문장을 글자별 `PAShape[]`로 나눕니다. |
| − |
## 자주 쓰는 패턴 |
| + |
### `shape.toShape({ step, openWidth })` |
| − |
### 문장을 글자별로 처리 |
| + |
변형된 새 `PAShape`를 반환합니다. |
| − |
```js |
| − |
for (const glyph of font.text("영가", { size: 160 }).glyphs()) { |
| − |
const regions = glyph.toRegions(); |
| − |
console.log(regions.length); |
| − |
} |
| − |
``` |
| + |
- `step`: polygon 재샘플링 간격 |
| + |
- `openWidth`: hole을 slit처럼 열기 위한 폭 |
| − |
### slit + lowpoly region |
| + |
### `shape.toRegions({ step, openWidth })` |
| − |
```js |
| − |
const regions = font.glyph("영", { size: 160 }).toRegions({ |
| − |
step: 8, |
| − |
openWidth: 1, |
| − |
}); |
| − |
``` |
| + |
최종 polygon 데이터를 반환합니다. |
| − |
### 충돌 판정 가능한 shape |
| + |
반환 형태: |
|
```js |
| − |
const shape = font.glyph("영", { size: 160 }).toShape({ |
| − |
step: 8, |
| − |
openWidth: 1, |
| − |
}); |
| − |
|
| − |
if (shape.contains(20, -20)) { |
| − |
console.log("inside"); |
| − |
} |
| + |
[ |
| + |
{ |
| + |
outer: [[x, y], ...], |
| + |
holes: [ |
| + |
[[x, y], ...], |
| + |
], |
| + |
bbox: { x, y, w, h }, |
| + |
}, |
| + |
]; |
|
``` |
| − |
|
| − |
### 경계 점 추출 |
| − |
```js |
| − |
const points = font.glyph("가", { size: 160 }).toPoints({ |
| − |
step: 8, |
| − |
}); |
| − |
``` |
| + |
### `shape.toPoints({ step, openWidth, includeHoles })` |
| − |
## 짧은 요약 |
| + |
경계를 일정 간격의 점들로 샘플링합니다. |
| − |
제일 자주 쓰는 흐름은 이 셋입니다. |
| + |
### `shape.hit(x, y)` / `shape.contains(x, y)` |
| − |
```js |
| − |
font.text(...) |
| − |
shape.glyphs() |
| − |
shape.toRegions(...) |
| − |
``` |
| + |
도형 hit test를 합니다. |
| − |
shape 자체를 유지한 채 후속 계산까지 이어가고 싶으면: |
| + |
## 언제 무엇을 쓰나 |
| − |
```js |
| − |
shape.toShape({ step, openWidth }) |
| − |
``` |
| + |
- 편집/후처리를 계속할 거면 `toShape()` |
| + |
- 정확한 polygon 데이터가 필요하면 `toRegions()` |
| + |
- 점 기반 효과나 입자용이면 `toPoints()` |
| + |
- 문단을 화면에 바로 그리고 싶으면 `paragraph.drawText(ctx)` |
| + |
- 같은 문단을 geometry로 쓰고 싶으면 `paragraph.toShape()` |
| − |
점만 필요하면: |
| + |
## 주의 |
| − |
```js |
| − |
shape.toPoints({ step, openWidth, includeHoles }) |
| − |
``` |
| + |
- `paragraph.drawText()`는 canvas 텍스트 렌더링입니다. |
| + |
- `fontFamily` 또는 `font`는 브라우저에 실제 등록된 폰트와 맞아야 합니다. |
| + |
- `step`, `openWidth`는 모두 절대 좌표 단위입니다. |