한용파 위키

test

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 로드

브라우저에서 가장 단순한 방법입니다.

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 })