3-9. Rust 를 접고 Zig 로 다시 쓰다

다섯 달 반의 공백, 그리고 archive

rc.10 을 3월 15일에 낸 뒤 3월 20일까지 HWPX 데모 페이지와 clippy 정리를 손보고, hwpjs 는 멈춰 있었다. 그 사이 zntc·suji·maru·@ohah/pdf가 전부 Zig 로 만들어졌고, 9월 5일 hwpjs 도 같은 길로 갔다.

첫 커밋이 chore: archive Rust and initialize Zig workspace 다. Rust crates/, Node/RN packages/, 예제, Cargo·Bun manifest, 문서, 벤치마크, CI 워크플로까지 통째로 legacy/rust/ 로 옮겼다. 지우지 않고 옮긴 것이라, 이동 직후 추적 파일 1,652개의 경로 정규화 내용 해시가 이동 전과 같음을 확인했다. 레거시는 지금도 cd legacy/rust && cargo test -p hwp-core 로 돈다.

docs/migration.md 에 못박아 둔 문장이 이 편의 규칙이다.

기존 Rust 기능을 신규 Zig 기능으로 표시하지 않습니다.

이동 시점에 Zig 쪽은 빌드, 경계 검사 바이트 리더, WASM ABI 버전 함수뿐이었다. 그 뒤 열흘 동안 1,325개의 .zig 파일이 생겼고, 둘째 주가 끝나자 1,554개가 됐다.

열흘 (9/5 ~ 9/14) 424커밋

커밋 수는 오늘의 커밋과 같은 gh search commits 기준이다.

날짜커밋들어간 것
9/515CFB 모듈러 리더, 검색·검증 계약 통일, hwp5 DocInfo 속성과 버전별 ID 매핑, 바이너리·폰트 리소스
9/6117hwp5 문서 구조 전수 검증 — ViewText·revision·connector·group·memo·drawing·arc·picture·history·인덱스 계층·표 격자
9/759XML 입력·zlib 경계, PNG(iTXt·sPLT·IDAT·Adam7·gAMA·cHRM·sRGB), ICC(curves·XYZ·parametric), form controls·DocHistory
9/868ICC 역함수 — rational XYZ 행렬 역, parametric preimage, 정확한 root 비교; JPEG frame/scan·양자화·Huffman 테이블
9/960자체 JPEG 디코더 — 엔트로피·Huffman·프로그레시브·IDCT·샘플 복원; BMP RLE, V5 ICC
9/1215hwp5 차트 파싱 — cell slot·type 선언·dual string·series v2·axis
9/1383차트 편집 — grid string·series label 일괄 typed edit, BinData 원자적 교체, raw deflate 인코딩; WMF 검증
9/1435EMF 레코드 — path bracket·palette·mode state·pixel format·object table

구조: 책임을 파일로 가른다

Rust 판은 hwp-core 크레이트가 파싱과 뷰어(src/viewer/)를 함께 들고 있었다. Zig 판은 처음부터 층을 가른다.

src/
  binary/      경계 검사·정수 읽기
  cfb/         컨테이너 읽기·검증·새 컨테이너 쓰기
  compression/ bounded raw DEFLATE
  hwp5/        FileHeader·레코드 경계·DocInfo·본문·차트·BinData
  image/       PNG·JPEG·BMP·ICC·EMF/WMF
  text/        BCP 47·IANA 언어 태그
  xml/         공통 문자 입력
  wasm/        메모리·CFB 수명·엔트리·ABI
js/            읽기·쓰기 API·편집 모델 변환·검색

원칙 몇 가지가 docs/architecture.md 에 적혀 있다.

  • CFB 에는 HWP 문단·표·글꼴 로직을 넣지 않는다. 컨테이너는 컨테이너만 안다.
  • 파일·시계·브라우저 API 에 직접 의존하지 않는 메모리 기반 읽기·쓰기를 우선한다. 그래서 같은 코드가 Node 와 브라우저에서 wasm 하나로 돈다.
  • 원값을 보정하지 않는다. PNG 감마·색도는 고정소수점 원값을 그대로 들고, 검사와 변환을 분리한다. "손실 기본값 없이", "추론 없이", "예약 바이트는 정규화하지 않는다" 같은 커밋 제목이 이 원칙의 흔적이다.
  • 관찰과 규칙을 구분한다. 차트처럼 명세가 없는 레코드는 "Contents 관찰" 과 "포맷 규칙" 을 나눠 적는다.

이미지 디코더를 직접 짠 이유

Rust 판의 hwp-core 는 CFB·DEFLATE·AES 를 크레이트(cfb·flate2·aes)에 맡겼고 이미지 디코더는 갖고 있지 않았다. Zig 판은 CFB 와 DEFLATE 부터 직접 짜고, PNG·JPEG·BMP 디코더와 ICC 프로파일 읽기까지 안에 뒀다. 9월 8일 하루가 ICC 역함수였다 — matrix TRC 를 정확·근사 두 경로로 forward 평가하고, full-width rational XYZ 행렬을 뒤집고, parametric curve 의 preimage 를 정확한 분수로 보존한다. 왜 직접 짰는지를 적은 대화 기록은 없다. 결과물로 보이는 것은 둘이다 — 의존성 없는 wasm 하나로 나가고, 색 프로파일까지 읽어 원값을 보존한다.

JPEG 은 9월 9일에 프로그레시브까지 풀었다. 경계 계층(마커 분류·세그먼트·바이트 스터핑)과 프레임/스캔 상태, 픽셀 복호화를 문서에서부터 분리해 두고, "HWP 이미지 지원 완료를 대신하지 않는다" 고 적었다.

읽기에서 쓰기로 2026-09-13

9월 13일에 방향이 하나 더 열렸다. 차트의 grid string cell 과 series label 을 편집하고, 여러 차트에 걸친 typed edit 를 일괄 적용하고, BinData 스트림을 원자적으로 교체한다. 이를 위해 raw deflate stored block 인코딩과 CFB 새 컨테이너 쓰기가 필요했다. Rust 판이 끝까지 못 간 자리 — HWP 를 고쳐서 다시 저장하는 것 — 에 Zig 판은 열흘 만에 닿았다.

둘째 주: EMF 를 끝까지 (9/15 ~ 9/21) 116커밋

첫 열흘이 HWP 본체와 이미지였다면 둘째 주는 EMF 하나에 들어갔다. 한글 문서에 박힌 그림 상당수가 EMF/EMF+ 메타파일이라, 이걸 읽지 못하면 그림이 빈 칸이 된다.

날짜들어간 것
9/15image color management·extended flood fill·bit block transfer·alpha blend·parallelogram transfer·comment envelope, EMF+ object·comment 검증
9/16EMF+ path·brush 객체
9/19EMF+ image attributes·clear·string format·region·draw curve·beziers
9/20world transform 셋(set·multiply·rotate·scale)·clip·fill·rendering origin·interpolation·compositing, EMF+ rectangle·lines·draw string·images·driver strings·container (39커밋)
9/21레코드 wire 지원 분류, polyline·Path 기하·closure 조립, fill path/ellipse, terminal server graphics, hwp5 가 EMF+ world transform 을 replay

9월 21일 시점에서 Zig 파일은 1,554개, 9월 5일 이후 커밋은 557개다. 레코드 커버리지 표(docs/emf/)가 어디까지 되는지를 적는다.

9월 21일 시점에 없는 것

README 가 스스로 적어 둔 한 줄이 정확하다.

반환값은 CFB 스트림의 원시 바이트입니다. HWP/HWPX 본문 파싱·편집 API는 아직 제공하지 않습니다.

레코드 경계·DocInfo·문단 헤더·텍스트 토큰·차트까지는 읽지만, 문서 모델과 렌더링 문자열은 아직 만들지 않는다. HWPX(ZIP/XML)도 예정이다. rc.10 의 HTML 뷰어와 페이지네이션은 legacy/rust/ 에 그대로 있고, 새 판이 그 자리에 닿으면 이 편의 다음 편이 될 것이다.

9월 23일 이후 HWPX의 ZIP/XML 읽기와 Zig 코어 검사 API가 들어갔다. 그 진행과 아직 남은 문서 모델·렌더링·편집 범위는 다음 편에 이어 적었다.

Tip

이 편은 hwpjs 저장소의 커밋 이력과 docs/migration.md·docs/architecture.md 를 근거로 정리했다. 재작성 결정 자체의 대화 기록은 남아 있지 않다.