3. 개발 과정
2026년 8월 30일부터 9월 26일 확인 시점까지 163커밋의 기록이다. 마지막 반영은 9월 22일이다. 저장소의 커밋 이력을 근거로 시기별로 나눴다.
8/30~8/31 — 떼어내고 채우기
첫날 저녁에 저장소가 열렸다. 다음 날 하루에 25커밋이 들어갔는데, 대부분 "뷰어가 라이브러리가 되려면 있어야 하는 것" 이다.
- 문서 정보 넷 — 권한·쪽 라벨·열 때 설정·지문
- 주석을 종류 가리지 않고 걷고, 얹어서 다룰 수 있는 주석 층
- 이름 목적지·뷰어 설정·XMP
- 표준 14종 글꼴의 자간을 AFM 폭 표로 정확히
- 주소로 열기와 내려받기 진행률(
onProgress,signal로 그만두기) - 글자 덩이 정보와 구조 나무
- 뷰어에 아쉽던 넷 — 렌더 취소·뷰포트·글자층·렌더 옵션
- Node.js 에서도 돌게
- 여는 값을 34MB 기준 417ms → 120ms
같은 이틀에 zntc 쪽 소스맵 결함 둘을 찾아 고쳤고, 그 결과가 zntc v0.1.5·v0.1.6 이다. 코드 리뷰와 엔진 리뷰가 잡은 그리기·서명·양식 결함 열셋, 갱신이 얹힌 암호 문서를 안 잠긴 문서로 읽던 것, Node 에서 문서를 두 개 열면 앞 문서가 망가지던 것도 이때 닫혔다.
9/1~9/2 — 메모리, 색, 양식
9월 1일은 메모리다. 세는 상한을 걷어내자 malloc 이 엔진 메모리를 밟던 것이 드러났고, 고친 뒤 안 쓰는 자리를 안 잡게 해 바닥 메모리가 112MB 에서 38MB 로 내려갔다. 빽빽한 쪽이 통째로 백지로 나오던 것, 출력 자리가 꽉 차면 쪽 표를 뭉개던 것도 같은 뿌리였다. 레거시 전수조사에서 다섯이 나왔고 그중 둘은 파일을 망가뜨리고 있었다. 낮은 층 API(drawOps·checkSignature·Stencil)를 내보내고, 범위 요청·열 때 갈 자리·양식 계산식·XFA 읽기도 붙였다.
9월 2일은 색과 양식이다. ICC 색 프로파일을 읽어 53/255 가 0.8 이 됐고, 색차를 늘릴 때 네 이웃을 섞어 libjpeg 과 5 안으로 맞췄으며, Node 에서도 그림을 그리고 JPEG 은 엔진이 풀고 프로그레시브 JPEG 도 푼다. 문서가 준 JavaScript 를 자체 해석기로 돌려 관공서 양식이 살았고, 해석기를 15개 중 7개에서 15개로 넓혔으며, 동적 XFA 가 들어갔다.
9/3~9/8 — 다듬기
- 9/3 — JPEG 을 푸는 값을 122ms 에서 52ms 로. 세 군데를 고쳤고, 걸림돌은 실제 바이트 잦기로 골랐다.
- 9/4 — Type3 글리프 스트림을 쪽 단위로 들고 있는다.
- 9/5 — 가장 많은 날(38커밋). 문서 다시 쓰기(apply)를 떼어 12,525줄을 10,859줄로,
scanResources를 자원 갈래 여섯으로, 늘어나는 표를Table(T)하나로. pdf.js 와 그림을 맞대는 시험을 더해 타일 무늬 간격과 그림 키울 때 뭉개기를 잡았고, 문서를 잇달아 열면 앞 문서의 마스크 그림이 나오던 것, /CalRGB·16비트·무색 무늬 색, 내보낸 PDF 에 모듈 이름이 새던 것(/Subtype /core.Form)을 고쳤다. 아직 없는 기능을 미리 못 박는 빈틈 견본 6개도 이날이다. - 9/8 — 표준 글꼴을 밖에서 받아 실을 수 있게 했다. 브라우저마다 다르던 글자 자리가 같아진다.
9/14 — 포트폴리오
파일 묶음(PDF 포트폴리오)을 알려 주고, 거듭 열어도 메모리가 안 자라는지 보는 테스트를 붙였다. 미뤄 뒀던 셋은 까닭을 제대로 적어 가렸다.
9/20~9/21 — npm 에 나가고, 하루 뒤 LLM 용 출력
9월 20일 v0.1.0 이 npm 에 첫 출시됐다. Zig → wasm(321KB) 엔진, 읽기·그리기·글자층·검색·양식 채우기·주석·서명 확인·암호·병합·XFA, 화면 갈래 넷. 이 판에 라디오 묶음 배타와 겉모습 없는 주석 렌더(9/15 — /FreeText 와 규격 기본 모양)가 들어갔다.
다음 날 v0.2.0 은 방향이 하나 더 열린 판이다 — "PDF → Markdown/JSON, LLM 에 먹이는 꼴로".
pdf.markdown({ pages? })·pdf.blocks({ pages? })— 제목 계층·문단(하이픈 잇기)·목록·코드·표·머리말/꼬리말 버림·두 단 순서. blocks 는 같은 덩이에 쪽 번호와 자리(pt)가 붙은 JSON- 태그 PDF(Word·InDesign·PDF/UA)는 구조 나무를 그대로 따른다 — H1~H6·P·L/LI·Table·Code·Figure 대체 글,
/Artifact버림,/RoleMap해석 - 괘선 없는 표 — 같은 자리에서 끊기는 칸이 세 줄 넘게 이어지고 칸의 반이 숫자면 표로 본다
- 글자 뽑기를 pdf.js 수준으로 — 조각 폭·틈으로 띄어쓰기, 합자 풀기, ToUnicode 여러 글자 목적지, 내장 인코딩
고친 것도 실측에서 나왔다. /DescendantFonts 배열 객체(InDesign)·/W 참조(Word)를 못 따라가 모든 글자가 1000 폭이 되어 줄 뒤쪽 글자가 쪽 밖으로 밀려 사라지던 것, q…Q 안의 Tc 가 밖으로 새던 것, 큰 암호 문서 여는 데 56초 걸리던 것을 90ms 로. 무작위 27편(arXiv 22·한국 보고서 5)을 돌려 아홉을 잡았고, 한국어 실전 문서로 다듬어 docling 과 맞댔다.
릴리즈 노트가 적은 수치 — 정답 벤치 115/115(docling 95/102, pymupdf4llm 51/102), 정답 밖 27편에서 docling 과 제목 일치 278(docling 만 141, 우리만 91), 표 73(docling 54).
9/22 — 정답 견본 밖의 문서 100편
v0.2.0 다음 날에는 새 출력 형식보다 실문서에서 맞는지가 중심이 됐다. arXiv 논문·한국 보고서·IRS 양식·여러 언어의 공문서와 규격 등 100편에서 pymupdf·pdf.js와 글자와 좌표를 맞댔다. 기존 정답 115개로는 잡지 못한 결함 아홉을 고쳐 v0.2.1을 냈다.
큰 객체 스트림을 풀 자리가 모자라 문서를 열지 못하는 것, 쪽 내용 압축이 많이 풀리면 백지가 되는 것, 참조 객체인 MediaBox·CropBox를 놓치는 것, 폼과 쪽에 같은 이름의 글꼴이 있으면 다른 자원의 ToUnicode를 쓰는 것이 포함됐다. 자원을 어느 범위에서 찾고, 필요한 출력 공간을 어떻게 확보하는지가 실제 텍스트 정확성으로 이어졌다.
이어 같은 문서 100편의 752쪽을 72dpi로 그려 화소를 비교했다. 투명 그룹 참조, 폼과 이미지의 같은 이름, Type1 서브루틴 수, 소수 글자 폭, 브라우저가 거부하는 내장 글꼴, 패턴 좌표계와 저비트 이미지가 수정 대상이었다. 그리기 수정 커밋
저장소의 비교 기록은 이 표본에서 텍스트 재현율 0.951 → 0.992, 자리 일치 0.926 → 0.988을 보고한다. 그리기는 어느 색 채널이든 32 넘게 다른 화소의 비율을 쟀고, mupdf 대비 7.43% → 3.12%였다. 이 값은 해당 문서·해상도·비교 규칙의 결과이며 전체 PDF 호환성이나 처리 속도 수치는 아니다. JPEG 디코더 차이와 일부 글꼴·서브픽셀 배치 차이는 남아 있다.
마지막으로 퍼저가 찾은 둘을 고쳤다. 비정상적으로 큰 예측기 /Colors 값이 범위 밖을 읽는 경로와, 큰 물결 밑줄 QuadPoints가 끝없는 반복을 만드는 경로다. 입력 처리 수정까지 담아 버전을 v0.2.2로 올렸고, 9월 26일 확인한 npm 최신 배포는 0.2.2다. GitHub Release는 0.2.1까지만 등록돼 있어, 릴리즈 페이지와 npm의 최신 표시는 다르다.
이 프로젝트의 방식
- "된다" 는 시험이 근거다. 기능 단언과 실제 문서 대조를 함께 둔다. 9월 26일 확인한 검증 스크립트는 기능 단언을 최소 434개로 검사하며, 지원 문서의 397개 표기는 아직 갱신되지 않았다.
- 안 되는 것도 적는다. pdf.js 와 크게 다른 것은 까닭과 함께
tests/pdfjs-known.json에 남긴다. 9월 5일의 빈틈 견본 여섯은 이후 구현되어 일반 시험으로 옮겨졌고, 새 미지원 범위가 생기면 다시 견본으로 고정하는 구조다. - 커밋 제목이 한국어 문장이다.
빽빽한 쪽이 통째로 백지로 나오던 것 — 자리를 필요한 만큼 잡는다처럼, 증상과 조치를 한 줄에 쓴다. 이 개발기의 서술 대부분이 그 제목을 그대로 옮긴 것이다.