Pretext: 멀티라인 텍스트 측정과 레이아웃을 위한 TypeScript 라이브러리

3 days ago 4
  • Pretext는 DOM 접근 없이 멀티라인 텍스트의 높이와 줄 배치를 계산하는 순수 JavaScript/TypeScript 라이브러리로, 브라우저와 서버 환경 모두 지원
  • getBoundingClientRect 같은 DOM 측정 API를 사용하지 않아 레이아웃 리플로우 비용을 제거하고, 폰트 엔진 기반 자체 측정 로직으로 정확도를 확보
  • prepare() / layout() API를 통해 텍스트를 전처리하고, 캐시된 폭 데이터를 이용해 순수 산술 연산으로 빠른 높이 계산 수행
  • 이모지, 혼합 방향 텍스트(bidi), 다양한 언어를 지원하며, Canvas·SVG·WebGL·서버 렌더링에서도 동일한 결과 제공
  • 가상화 스크롤, 텍스트 오버플로 검증, 플로팅 텍스트 배치 등 정밀한 UI 레이아웃 구현에 활용 가능한 고성능 텍스트 엔진

개요

  • Pretext멀티라인 텍스트 측정과 레이아웃을 위한 순수 JavaScript/TypeScript 라이브러리로, DOM, Canvas, SVG, 그리고 서버 사이드 렌더링까지 지원
  • DOM 측정 API(getBoundingClientRect, offsetHeight 등)를 사용하지 않아 레이아웃 리플로우 비용을 제거
  • 브라우저의 폰트 엔진을 기준으로 한 자체 측정 로직을 통해 정확하고 빠른 성능을 제공
  • 모든 언어, 이모지, 혼합 방향 텍스트(bidi) 를 지원하며, 브라우저별 차이도 처리

설치 및 데모

주요 기능

  • Pretext는 두 가지 주요 사용 방식을 제공
  • 1. DOM 접근 없이 문단 높이 측정

    • prepare()는 텍스트를 전처리하고, 공백 정규화·세그먼트 분리·glue 규칙 적용·canvas 기반 측정을 수행해 불투명 핸들(opaque handle) 을 반환
    • layout()은 캐시된 폭 데이터를 이용해 순수 산술 연산으로 높이와 줄 수 계산
    • 동일한 텍스트와 설정에서는 prepare()를 반복 호출하지 않고, 리사이즈 시에는 layout()만 다시 실행
    • { whiteSpace: 'pre-wrap' } 옵션으로 공백, 탭(\t), 줄바꿈(\n)을 그대로 유지
    • 벤치마크 결과: prepare() 약 19ms (500개 텍스트 기준), layout() 약 0.09ms
    • 반환된 높이 값은 다음과 같은 UI 기능에 활용 가능
      • 가상화 및 오클루전 처리에서 정확한 높이 계산
      • JS 기반 레이아웃 시스템(예: masonry, flexbox 유사 구조)
      • AI 기반 텍스트 오버플로 검증
      • 텍스트 로드 시 스크롤 위치 유지
  • 2. 수동 문단 레이아웃 구성

    • prepareWithSegments()로 세그먼트 단위 데이터 생성
    • layoutWithLines()는 고정 폭에서 각 줄의 텍스트와 폭 정보를 반환
    • walkLineRanges()는 텍스트 문자열을 만들지 않고 각 줄의 폭과 커서 범위를 순회
      • 예: 여러 폭을 테스트해 적절한 줄 수와 높이를 찾는 이진 탐색형 레이아웃 조정 가능
    • layoutNextLine()은 줄마다 폭이 달라지는 경우 한 줄씩 순차적으로 레이아웃
      • 예: 이미지 주위로 텍스트를 흐르게 하는 플로팅 텍스트 배치
    • 이 방식은 Canvas, SVG, WebGL, 서버 사이드 렌더링에도 동일하게 적용 가능

API 요약

  • 기본 측정용 API

    • prepare(text, font, options?): 텍스트 분석 및 측정, layout()에 전달할 핸들 반환
    • layout(prepared, maxWidth, lineHeight): 주어진 폭과 줄 높이에 따른 텍스트 높이와 줄 수 계산
  • 수동 레이아웃용 API

    • prepareWithSegments(text, font, options?): 세그먼트 단위 데이터 반환
    • layoutWithLines(prepared, maxWidth, lineHeight): 각 줄의 텍스트, 폭, 커서 정보 포함
    • walkLineRanges(prepared, maxWidth, onLine): 각 줄의 폭과 커서 범위를 콜백으로 전달
    • layoutNextLine(prepared, start, maxWidth): 줄 단위 반복자 형태로 레이아웃 수행
    • LayoutLine, LayoutLineRange, LayoutCursor 타입 정의 포함
  • 기타 유틸리티

    • clearCache(): 내부 캐시 초기화
    • setLocale(locale?): 로케일 설정 및 캐시 초기화 (기존 상태에는 영향 없음)

제약 및 주의사항

  • Pretext는 완전한 폰트 렌더링 엔진은 아님
  • 기본 대상 CSS 속성
    • white-space: normal
    • word-break: normal
    • overflow-wrap: break-word
    • line-break: auto
  • { whiteSpace: 'pre-wrap' } 사용 시 공백, 탭, 줄바꿈 유지하며 tab-size: 8 적용
  • macOS에서 system-ui 폰트는 layout() 정확도에 부적합하므로 명시적 폰트명 사용 권장
  • overflow-wrap: break-word로 인해 매우 좁은 폭에서는 단어 내부에서도 줄바꿈 가능, 단 문자 단위(grapheme) 기준으로만 분리

개발 관련

  • 개발 환경 및 명령은 DEVELOPMENT.md 참고

기여 및 배경

  • Sebastian Markbagetext-layout 프로젝트에서 아이디어를 이어받음
  • canvas measureText 기반 셰이핑, pdf.js의 bidi 처리, 스트리밍 라인 브레이킹 설계를 계승해 발전시킨 구조
Read Entire Article