프로젝트 소개

Pretext는 여러 줄 텍스트를 측정하고 줄 레이아웃을 계산하기 위한 순수 JavaScript/TypeScript 라이브러리입니다. `getBoundingClientRect`나 `offsetHeight`처럼 레이아웃 리플로우를 강제하는 DOM 측정 API를 피하고, 대신 브라우저의 canvas `measureText`를 기준값으로 삼아 자체 측정을 수행합니다. 따라서 가상화, 사용자 정의 레이아웃 엔진, canvas/SVG 렌더링, 개발 시점의 오버플로 검사에 유용합니다. ## 설치 ```sh npm install @chenglou/pretext ``` ## 주요 사용 사례 ### 1. DOM을 건드리지 않고 문단 높이 측정 ```ts import { prepare, layout } from '@chenglou/pretext' const prepared = prepare('AGI 春天到了. بدأت الرحلة 🚀', '16px Inter') const { height, lineCount } = layout(prepared, 320, 20) ``` `prepare()`는 일회성 작업을 수행합니다: 공백 정규화, 텍스트 분할, glue 규칙 적용, canvas 기반 측정입니다. 그런 다음 `layout()`은 캐시된 너비에 대한 저렴한 순수 산술 연산이므로, 다시 prepare하지 않고도 리사이즈 시 재실행할 수 있습니다. `prepare()`의 옵션에는 textarea와 유사한 동작을 위한 `whiteSpace: 'pre-wrap'`, CSS의 `word-break: keep-all`과 유사한 `wordBreak: 'keep-all'`, CSS `letter-spacing`을 위한 `letterSpacing`이 있습니다. ### 2. 수동 줄 레이아웃 `prepareWithSegments()`는 사용자 정의 레이아웃을 위한 더 풍부한 구조를 반환합니다. 다음 API를 사용할 수 있습니다: - `layoutWithLines()` — 고정된 최대 너비에서 모든 줄을 반환하며, 텍스트와 측정된 너비를 포함합니다. - `walkLineRanges()` — 줄 문자열을 만들지 않고, 너비와 시작/끝 커서를 사용해 줄마다 콜백을 호출합니다. - `measureLineStats()` — 할당 없이 줄 수와 가장 넓은 줄 너비를 반환합니다. - `measureNaturalWidth()` — 너비가 줄바꿈의 원인이 아닐 때 가장 넓은 강제 줄을 반환합니다. - `layoutNextLine()` / `layoutNextLineRange()` — 잠재적으로 서로 다른 너비로 한 번에 한 줄씩 레이아웃하기 위한 iterator 스타일 API로, float 주변으로 텍스트를 흘리거나 동적 컨테이너에 유용합니다. - `materializeLineRange()` — 레이아웃 범위를 다시 전체 줄 문자열로 변환합니다. 이를 통해 Canvas, SVG, WebGL, 그리고 향후 서버 측 환경으로 렌더링할 수 있습니다. 데모는 저장소와 chenglou.me/pretext에 포함되어 있습니다. ## 리치 인라인 헬퍼 `@chenglou/pretext/rich-inline`의 별도 헬퍼는 혼합 폰트, 원자적 항목(예: 칩과 멘션), pill chrome을 위한 호출자 소유의 추가 너비를 갖는 기본 리치 텍스트 인라인 흐름을 지원합니다. 의도적으로 범위가 좁습니다: 인라인 전용, `white-space: normal`만 지원하며, 일반적인 CSS 인라인 포매팅 엔진이 아닙니다. ## API 용어집 하이라이트 - `PreparedText`는 불투명한 빠른 경로 핸들이고, `PreparedTextWithSegments`는 더 풍부한 수동 레이아웃 핸들입니다. - `LayoutCursor`는 원시 문자열 오프셋이 아니라 세그먼트/그래핌 인덱스를 사용합니다. - 빈 문자열에 대한 `layout()`은 `{ lineCount: 0, height: 0 }`을 반환합니다. 브라우저는 빈 블록을 하나의 `line-height`로 크기 지정하므로, 호출자가 clamp하고 싶을 수 있습니다. - 소프트 하이픈이 지원됩니다: 선택적 줄바꿈 지점으로 작동하며, 선택되면 후행 `-`로 materialize됩니다. - `clearCache()`는 공유 내부 캐시를 지우고, `setLocale()`은 향후 prepare 호출의 로케일을 설정합니다. - 더 풍부한 핸들은 사용자 정의 bidi 인식 렌더링을 위한 대략적인 `segLevels`를 포함하지만, Pretext는 전체 Unicode Bidirectional Algorithm을 구현하지 않습니다. ## 주의사항 Pretext는 완전한 폰트 렌더링 엔진이 아닙니다. 일반적인 CSS 텍스트 설정을 대상으로 합니다: - `white-space: normal` 및 `pre-wrap` - `word-break: normal` 및 `keep-all` - `overflow-wrap: break-word` - `line-break: auto` - 숫자 픽셀 값으로서의 `letter-spacing` - 탭은 기본 `tab-size: 8`을 따릅니다 주목할 만한 제한사항: - `system-ui`와 `-apple-system`은 macOS에서 `layout()` 정확도에 안전하지 않습니다. 명명된 폰트를 권장합니다. - 구두점 옆의 이모지는 브라우저와 다르게 줄바꿈될 수 있습니다. - Shantell Sans 같은 일부 폰트는 긴 단어에서 다른 줄바꿈을 만들 수 있습니다. - 런타임에는 `Intl.Segmenter`, Canvas 2D 텍스트 측정, Unicode property escapes가 필요합니다. - canvas `font` 단축 속성 밖의 CSS 폰트 기능은 별도로 모델링되지 않습니다. 전체적으로 Pretext는 여러 줄 텍스트 측정을 위한 집중된 라이브러리로 자리매김하며, 웹 개발자에게 브라우저 레이아웃 쿼리에 대한 빠르고 결정론적인 대안을 제공합니다.