2026.07 – 현재 · DigitalPresso

HWPX 문서 렌더러

한컴오피스 없이 브라우저에서 HWPX 원본 레이아웃을 재현하는 렌더러

FrontendBackendPythonlxmlTypeScriptNext.js 15React 19PlaywrightvitestpytestaiohttpMCPturborepo
레이아웃 정확도
미측정→한컴 캐시 대비 0.5mm 이내
최악 페이지 넘침
+1,969mm→+10.6mm
그림 옆 본문 폭
40mm→117.7mm
쪽번호 렌더
0개→26/26
웹폰트 전송량
7.6MB→0.72MB
IR API 응답
콜드 10.0초→웜 0.001초
테스트
백엔드 476 · 프런트 281

문제

  • HWPX: KS X 6101(OWPML) 표준, ZIP 안에 XML이 든 한글 문서 포맷
  • 목표: 브라우저에서 원본과 같은 레이아웃 재현
  • 제약
    • 참조 구현(한컴오피스)이 블랙박스 → 정답 판정 기준 없음
    • 상용 폰트(함초롬·휴먼명조) 부재 → 글자 폭·줄바꿈·페이지 수 변동
    • 표준 문서가 기계가독 불가 (HTML 스크레이프)

접근 — 문서 안의 정답지

  • 한컴이 저장 시 줄별 레이아웃 좌표를 hp:linesegarray에 기록 → 정답지로 사용
  • 세 출처 삼각측량, 어긋나는 지점 = 결함
출처단위 변환얻는 것
브라우저 실측 (Playwright)CSS px × 25.4/96실제로 그려진 위치
원본 XMLHWPUNIT × 25.4/7200문서가 선언한 값
KS X 6101 표준—속성의 의미와 적용 조건

사례 1 — 용지 여백 해석 오류

  • 표준 10.6.6.2/10.6.6.3: 본문·머리말·꼬리말 여백이 쌓이는 4-밴드 구조
  • 기존 렌더러: 머리말·꼬리말을 용지 끝 기준으로 해석 → 본문 267mm (20mm 과다)
  • 검증: 한컴 캐시의 최심 줄이 4-밴드 한계(247mm)에 0.3~1.2mm까지 근접, 26쪽 중 2-밴드 여유에 닿는 줄 없음
문서최심 줄4-밴드 247mm2-밴드 267mm
A246.7mm여유 0.3mm여유 20.3mm
B245.8mm여유 1.2mm여유 21.2mm
  • 가로축 교차검증: 최대 51026 HWPUNIT = 180.0mm = 210 − 15 − 15
  • 부수 효과: 쪽번호가 비인쇄 여백 → 꼬리말 밴드 안으로 이동

사례 2 — CSS float 예약 위치 어긋남

  • 증상: 그림 옆 제목이 단어 중간에서 끊기고 문단이 좁은 리본에 눌림
  • 실측: 그림 x 132.7–191.6mm, 배제 영역 x 55.8–114.7mm (76.9mm 왼쪽)
  • 원인: 같은 변의 두 float 예약이 세로로 겹치면 CSS가 나란히 배치
  • 해결: clear로 세로 스택 강제 → 본문 컬럼 40mm → 117.7mm
  • 반증된 가설: 밴드 겹침 원인설, post-layout 보정 패스(예약·객체 4.6mm 어긋남 유발)
  • 습관: 수정 전 "무엇이 얼마나 바뀌어야 하는가"를 수치로 기록

기타 개선

  • CJK 웹폰트 unicode-range 분할: 원본 112MB, 문서당 7.6MB → 0.72MB, 페이지 27 → 26쪽
  • 캐시: (mtime_ns, size) 키 + 기동 시 워밍 → 콜드 10.0초 → 웜 0.001초, 손상 ZIP은 503·실패 미캐시
  • 계측기: min-height 착시 제거(actual − minHeight 측정) → 최악 +1,969mm 넘침 발견
  • 조용한 폴백 제거: 데이터 소스 배지로 낡은 픽스처 사용 발견

배운 것

  • "됐다"를 판정할 방법을 먼저 만들고 고친다
  • 참조 구현이 블랙박스여도 산출물 안에 정답이 남아 있을 수 있다
  • 표준의 조건절이 곧 테스트 케이스다
  • 스펙과 실제 파일이 다르면 파일이 이긴다
  • 드러난 결함은 xfail(strict=True)로 고정해 숨기지 않는다

Posts

Portfolio | jwsong