한용파 위키

test

2026-04-04 12:18:07 hanyongpa

# 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`는 모두 절대 좌표 단위입니다.