Text Height Calculation

WPF FormattedText 기반의 정확한 텍스트 높이 계산 기능 가이드입니다. 레거시 .NET 4.5 편집기와의 완벽한 호환성을 유지하며 다양한 폰트, 크기, 스타일을 지원합니다.

개요

특징설명
레거시 호환성.NET 4.5 편집기와 동일한 측정 규칙 적용
WPF 기반Windows 렌더링 엔진을 사용한 정확한 측정
다국어 지원한글, 영문 등 모든 폰트 지원
고성능STA 스레드 풀을 통한 동시 처리 (기본 4개 스레드)
안정성타임아웃 및 과부하 방지 메커니즘

레거시 측정 규칙

규칙설명
폭 인셋width - 8텍스트 프레임 폭에서 8px 제외
라인 높이fontSize × 1.6984고정 배수 적용 (px 단위)
정렬 방식justify양쪽 정렬 강제
최종 높이Max(oneLine, total + 10)최소 1줄 높이 보장, 전체 높이에 10px 패딩
폰트 크기fontSize × 1.1WPF 내부 처리용

TextUtil API

GetHeight()

텍스트의 계산된 높이를 반환합니다.

csharp
double height = TextUtil.GetHeight(
    text: "안녕하세요\n여러 줄의\n텍스트입니다",
    fontSize: 20,
    width: 300,
    fontFamily: "Malgun Gothic",
    lineHeight: "normal",
    textAlign: "justify",
    bold: false,
    italic: false
);

GetLineCount()

텍스트가 차지하는 줄 수를 반환합니다.

csharp
int lines = TextUtil.GetLineCount(
    text: "긴 텍스트가 여기에 들어갑니다",
    fontSize: 16,
    width: 250,
    fontFamily: "카페24 당당해"
);

GetOneLineHeight()

한 줄의 높이를 반환합니다 (텍스트 내용과 무관).

GetDispartText()

주어진 높이 제한 내에서 텍스트를 분할합니다.

csharp
List<string> parts = TextUtil.GetDispartText(
    text: "매우 긴 텍스트 내용...",
    fontSize: 14,
    height: 100,
    width: 200,
    fontFamily: "맑은 고딕"
);
// parts[0]: 100px 높이 내에 들어가는 텍스트
// parts[1]: 나머지 텍스트 (있는 경우)

BadTextRemover()

HTML 엔티티 및 특수문자를 제거합니다.

csharp
string cleaned = TextUtil.BadTextRemover("&lt;div&gt;텍스트&nbsp;&amp;&nbsp;내용&lt;/div&gt;");
// 결과: "텍스트 & 내용"

Test API

POST /books/{bookUid}/text-height

텍스트 높이 계산을 테스트하고 결과를 페이지로 추가하는 API입니다. 개발/디버깅 용도로 사용합니다.

json
// 요청 본문
{
  "templateUid": "template-blank-page",
  "textJson": [
    {
      "type": "text",
      "position": {"x": 100, "y": 200},
      "width": 300,
      "height": 0,
      "text": "측정할 텍스트\n여러 줄 가능",
      "fontFamily": "Malgun Gothic",
      "fontSize": 20,
      "bold": false,
      "italic": false
    }
  ]
}

// 응답
{
  "success": true,
  "data": {
    "pageNumber": 5,
    "calculatedHeights": [85.5, 62.3]
  },
  "message": "Text height page added"
}

폰트 처리

텍스트 스타일(굵기, 기울임)은 템플릿 JSON의 textBoldtextItalic 프로퍼티를 통해 적용됩니다.

폰트 파일을 /fonts/ 디렉토리에 넣는 것만으로는 충분하지 않습니다. 반드시 시스템에 설치해야 합니다 (Windows: C:\Windows\Fonts\).

프로덕션 환경 고려사항

스레드 풀 설정

bash
# Windows
set WPF_TEXT_MEASURER_THREADS=8

# Linux (Docker)
ENV WPF_TEXT_MEASURER_THREADS=8

# 권장 설정:
# 낮은 부하: 2~4 스레드
# 중간 부하: 4~8 스레드
# 높은 부하: 8~16 스레드

타임아웃 설정

항목
큐 추가 타임아웃2초
측정 타임아웃10초
대기열 용량1,000개

성능 벤치마크

작업평균 시간동시 처리
단일 텍스트 측정 (1줄)5ms4 스레드
단일 텍스트 측정 (10줄)8ms4 스레드
배치 측정 (10개)45ms4 스레드
최대 처리량~800 req/s8 스레드

제약사항

Windows 전용: WPF 의존으로 Linux에서 동작하지 않습니다. Docker 사용 시 Windows 컨테이너가 필요합니다. 크로스 플랫폼 지원이 필요한 경우 SkiaSharp 또는 SixLabors.Fonts로 전환을 고려하세요.

정확도 관련: WPF와 브라우저의 렌더링 엔진이 다르므로 약간의 차이가 발생할 수 있습니다. 정확한 측정을 위해 동일한 폰트가 시스템에 설치되어 있어야 합니다. DPI는 96으로 고정됩니다.

텍스트 분할 구현

이진 탐색 알고리즘

원본 LinkedBooks의 선형 탐색(O(n)) 대신 이진 탐색(O(log n))을 사용하여 분할 지점을 찾습니다.

텍스트 길이이진 탐색선형 탐색성능 향상
100자7번최대 100번14배
500자9번최대 500번55배
1000자10번최대 1000번100배
10000자14번최대 10000번714배

단어 경계 조정 우선순위

이진 탐색으로 찾은 분할 지점을 자연스러운 위치로 조정합니다.

  1. 공백 문자 (` `, `\t`) — 단어 중간에서 자르지 않음. 개행(`\n`)은 제외 (문단 간격 보존)
  2. 문장 부호 (`.`, `!`, `?`, `,`, `。`) — 문장 단위로 자름
  3. 최소 95% 유지 — 너무 적은 텍스트가 배치되지 않도록
  4. 원본 지점 — 좋은 지점을 못 찾으면 이진 탐색 결과 사용

개행 처리 방식

text
원본: "상상력을 발휘했어요.\n\n두 번째 전시실에는..."

분할 후:
  첫 번째: "상상력을 발휘했어요."      (TrimEnd)
  두 번째: "\n두 번째 전시실에는..."   (앞 개행 1개만 제거, 문단 간격 유지)

// "\n\n두 번째..." → "\n두 번째..." (문단 간격 유지)
// "\n다음 문장"    → "다음 문장"     (바로 시작)

원본(LinkedBooks)과의 차이점 요약

항목원본 (LinkedBooks)현재 (PhotobookAPI)
분할 알고리즘선형 탐색 O(n)이진 탐색 O(log n)
폰트 지원고정 (Malgun Gothic)다양한 폰트/스타일
공백 처리항상 Trim() 제거trimWhitespace 옵션으로 제어
분할 정확도단어 경계만문장 > 단어 > 문자
500자 처리 시간~2,500-4,000ms~50-80ms

관련 문서