한용파 위키

test

2026-04-02 11:17:49 hanyongpa

+ # 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 })
+ ```