수동 육안이 아니라 자동 게이트

한글이 계산한 답과 대조합니다

“원본과 비슷해 보인다”는 검증이 아닙니다. 한글(HWP)은 문서를 저장할 때 자기가 계산한 줄바꿈 결과(lineseg)를 파일 안에 함께 적어 둡니다. 오토한글은 같은 문서를 자체 엔진으로 조판한 뒤 그 저장값과 문단 단위로 대조합니다 — 몇 쪽이 나왔는지, 어느 문단이 몇 줄로 끊겼는지, 표 안 셀까지 재귀로. 사람 눈이 개입하지 않고, 점수가 떨어지면 커밋이 막힙니다.

아래 수치는 3cb5a7a 시점에 재현 커맨드를 그대로 실행해 얻은 출력입니다 (측정 2026-07-30). 각 항목마다 커맨드를 함께 답니다 — 직접 돌려 확인하십시오.

오라클 ① 저장된 lineseg

한컴이 저장한 문단별 줄 정보. 우리가 만든 정답이 아니라 한글이 실제로 계산한 결과라 해석의 여지가 없습니다.

오라클 ② rhwp 파싱

바이너리 .hwp를 읽는 상류 파서(MIT). HWP5/HWP3 decode와 명시적 read-only 원본/오라클·수식/차트 보강에만 쓰며, live 렌더·PDF·편집은 항상 우리 IR에서 나옵니다.

게이트가 잠근다

scripts/verify-local.sh가 쪽수 불일치 하나만 나와도 실패로 종료합니다. 수치는 보고용이 아니라 회귀 차단선입니다.

쪽수 게이트

하드 게이트 · 불일치 시 빌드 실패

같은 문서를 우리 조판기로 흘렸을 때 나오는 쪽수가 한컴 쪽수와 정확히 같아야 합니다. ±1 허용 같은 완충은 없습니다. 두 조판 경로(place_doc / NaiveLayout)의 쪽수도 항상 서로 일치해야 합니다(LOCKSTEP).

문서우리한컴(rhwp)판정
benchmarks/benchmark.hwp88일치
benchmarks/benchmark1.hwp1818일치
benchmarks/benchmark2.hwp2424일치
modu-startup.hwp (로컬 전용)66일치

modu-startup은 실사용자 양식 실물이라 재배포할 수 없어 corpus/private/(git 제외)에만 둡니다. 게이트는 파일이 있으면 6==6 을 강제하고, 없으면 점수를 꾸며내지 않고 skip 을 명시합니다.

재현
git clone --recurse-submodules https://github.com/kwakseongjae/auto-hwp
cd auto-hwp

# 4개 문서 쪽수 게이트를 한 번에 (verify-local.sh 가 도는 것과 같은 루프)
for b in benchmark benchmark1 benchmark2; do
  cargo run -q -p auto-hwp-cli --features "shaper rhwp" -- \
    layout-check "benchmarks/${b}.hwp" | grep 쪽수
done

본문 문단 줄바꿈 정확 일치율

98.9%+ 유지 게이트

본문 문단 하나하나에 대해 우리가 낸 줄 수한컴이 저장한 줄 수를 비교합니다. “정확 일치”는 줄 수가 완전히 같은 문단의 비율, “±1 이내”는 한 줄 차이까지 허용한 비율입니다. 실제 글꼴 폭(rustybuzz)으로 재는 shaper 경로 기준입니다.

문서대조 문단줄 수 정확 일치±1 이내총 줄 수 (우리 / 한컴)
benchmark.hwp9190 (98.9%)91 (100.0%)92 / 93
benchmark1.hwp257255 (99.2%)257 (100.0%)297 / 299
benchmark2.hwp365364 (99.7%)365 (100.0%)376 / 377

세 문서 모두 ±1 이내 100% — 두 줄 이상 어긋나는 문단이 하나도 없습니다.

재현 (위 표 전체가 한 커맨드의 출력)
cargo run -p auto-hwp-cli --features "shaper rhwp" -- \
  layout-check benchmarks/benchmark1.hwp

표 셀 안 줄바꿈 (재귀)

baseline 고정 테스트

양식 문서는 내용의 대부분이 표 안에 있습니다 — benchmark1.hwp만 해도 표 밖 문단이 257개인데 표 안 문단은 839개입니다. 본문 줄바꿈 지표는 표 밖 문단만 재기 때문에, 중첩 표까지 재귀로 내려가 셀 안 문단의 lineseg 를 따로 대조하는 게이트를 둡니다. 숫자는 테스트에 정확한 기대값으로 박혀 있어 한 문단만 어긋나도 실패합니다.

문서대조 셀 문단정확 일치±1 이내셀 총 줄 수 (우리 / 한컴)구조 불일치
benchmark.hwp261261 (100.0%)261 (100.0%)291 / 2910
benchmark1.hwp839826 (98.5%)839 (100.0%)969 / 9780
benchmark2.hwp1,0591,042 (98.4%)1,059 (100.0%)1,185 / 1,1900

구조 불일치 0은 두 쪽이 같은 표·같은 셀·같은 문단을 세고 있다는 뜻입니다(세는 대상이 어긋나면 점수 자체가 무의미해집니다). oracle 없음 0도 함께 강제해, 한컴 저장값이 빠진 문단을 조용히 건너뛰고 만점을 받는 일이 없게 합니다.

재현
# ① 게이트 테스트 (기대값이 코드에 박혀 있다)
cargo test -p hwp-rhwp --features "rhwp shaper" cell_lineseg

# ② 어느 셀이 왜 어긋났는지 감사 — 셀 폭·좌우 패딩·텍스트까지 출력
cargo run -p auto-hwp-cli --features "shaper rhwp" -- \
  layout-check benchmarks/benchmark1.hwp --cells all

본문 캐럿 좌표 교차검증

8+18+24쪽 · 563 밴드

조판이 맞아도 클릭한 자리에 커서가 서지 않으면 편집기로서는 실패입니다. 세 벤치마크 문서의 모든 줄 밴드를 훑으면서, 엔진이 돌려준 캐럿 사각형과 히트테스트 결과가 서로 왕복하는지 확인합니다.

검사 항목결과
히트 결과가 질의한 쪽 안에 캐럿을 담는가 (page-local)1,825 / 1,825
캐럿 사각형 → 히트테스트 왕복 (주소·오프셋 복원)431 / 431
글리프 baseline 이 엔진 LineSeg 캐럿 상자 안에 있는가431 / 431
보이는 글리프의 x 좌표 일치431 / 431
질의 3,227건 p95 응답0.123 ms (기기 의존)

응답 시간은 측정 기기에 따라 달라집니다 — 레포 기록상 같은 스크립트의 p95 는 0.081~0.155ms 범위에서 움직였습니다. 반면 위 네 개의 일치 카운트는 결정론적이라 기기와 무관합니다.

재현 (wasm 을 먼저 빌드해야 한다)
cargo build -p hwp-wasm --profile wasm-size --target wasm32-unknown-unknown
wasm-bindgen --target web --out-dir packages/engine/pkg \
  target/wasm32-unknown-unknown/wasm-size/hwp_wasm.wasm
node packages/engine/bench/body-caret-crosscheck.mjs

HWPX 파서 파리티

입력을 잠그는 오라클

.hwp는 rhwp 로, .hwpx는 우리 자체 파서로 읽습니다. 두 경로가 같은 문서에서 다른 IR 을 만들면 조판은 조용히 갈라집니다. 그래서 같은 benchmark1.hwpx를 두 파서로 각각 읽고, 엔진을 고정한 채 결과를 대조합니다. 쪽수 게이트가 결과를 잠근다면 이 테스트는 입력을 잠급니다.

항목우리 HWPX 파서rhwp 경로판정
문단 수325325일치
표 수6767일치
조판 쪽수2222일치
조판 총 줄 수301301일치
하이퍼링크 필드쌍 회수1 / 1보존

총 줄 수는 구조 회귀의 가장 예민한 탐지기입니다 — 표 앵커 문단을 잘못 세면 표 개수만큼 빈 줄이 초과 예약돼(실측 368 vs 301) 바로 드러납니다. 이 표는 “두 파서가 같다”는 뜻이지 “22쪽이 참값”이라는 뜻이 아닙니다 — 아래 한계를 보십시오.

재현
cargo test -p hwp-core --features rhwp --test hwpx_rhwp_parity -- --nocapture

이 수치가 말하지 않는 것

정직 고지
  • 한컴 네이티브 참값 오라클은 아직 없습니다. 지금의 정답지는 “한글이 파일에 저장해 둔 값”과 “rhwp 파싱”입니다. 한글을 실제로 실행시켜(Windows COM) 그 결과를 참값으로 쓰는 nightly 레인은 미완이며 이슈 075 로 열려 있습니다.
  • HWPX 입력의 절대 쪽수는 아직 정답이 아닙니다. 같은 문서라도 benchmark1.hwpx를 우리 HWPX 파서로 읽으면 22쪽, rhwp 참조로는 25쪽이 나옵니다 (layout-check benchmarks/benchmark1.hwpx로 직접 확인할 수 있습니다). 위 파리티 표와 HWPX 관련 게이트는 회귀를 잠그는 장치이지 참값 주장이 아닙니다. 이슈 074 에서 네 갈래(본문 상자·세로 병합 셀·ragged 표·행 높이 바닥)를, 080 에서 두 갈래(명시적 쪽 나누기 누락·noAdjust="1" 표 행 높이 과다 예약)를 해소했습니다. 그 뒤에도 남는 22 vs 25 는 줄/행 높이 참값 축이라 075 로 넘어갑니다.
  • 레포 기록(재현 불가 표본). 한컴이 저작한 HWPX 5종을 production 파서로 재측정했을 때 4종은 한컴 쪽수와 정확히 같았고 bizinfo-mss__붙임1 하나만 우리 24 vs 한컴 25 였습니다(이슈 080). 080 해소 후 2026-07-30 재실측에서 25 == 25 로 일치합니다. 해당 문서들은 공개 코퍼스가 아니라 이 수치는 직접 재현할 수 없습니다 — 그래서 위 표에 섞지 않고 여기 따로 적습니다.
  • modu-startup 6==6 은 로컬 전용 벤치입니다. 실사용자 양식 실물이라 공개 재배포가 불가능해 corpus/private/에만 존재합니다. 클론한 레포에서는 이 게이트가 skip 으로 표시됩니다.
  • 줄바꿈 지표는 “줄 개수” 충실도입니다. 어느 글자에서 줄이 끊겼는지가 아니라 몇 줄로 끊겼는지를 셉니다. 글자 x 좌표는 이 지표가 아니라 위 캐럿 교차검증이 따로 잽니다.
  • 벤치마크 밖 문서의 완전 일치는 보증하지 않습니다. 게이트가 도는 문서는 위 4종이 전부입니다. 다단·세로쓰기·개체 어울림(float/wrap)은 미구현이고, 함초롬 등 상용 서체는 번들하지 않아 OFL 대체(나눔 계열)로 렌더되므로 글리프 메트릭의 미세 차이가 남습니다.

전부 한 번에 돌리기

위 게이트는 전부 하나의 스크립트 안에 있습니다. 이 스크립트가 그린이 아니면 머지하지 않는다는 것이 프로젝트 규율입니다.

검증 정본
scripts/verify-local.sh          # fmt · clippy · 전체 테스트 · 쪽수 게이트 · wasm 위생
scripts/verify-local.sh --full   # + wasm 재빌드 · 캐럿 교차검증 · JS 빌드/유닛 · E2E