kordoc 4.16.0 → 4.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +345 -1135
  2. package/dist/{-VQNLLLPE.js → -3DURLXSO.js} +11 -11
  3. package/dist/{chunk-3JWMDL3S.js → chunk-24N4IJ5I.js} +2 -2
  4. package/dist/{chunk-H6KMTYYZ.cjs → chunk-2BZFKIRB.cjs} +3 -3
  5. package/dist/{chunk-H6KMTYYZ.cjs.map → chunk-2BZFKIRB.cjs.map} +1 -1
  6. package/dist/{chunk-RC4C7Q33.js → chunk-3VLVNKJV.js} +2 -2
  7. package/dist/{chunk-XHOZB2WP.js → chunk-3WIQSM3Z.js} +681 -22
  8. package/dist/chunk-3WIQSM3Z.js.map +1 -0
  9. package/dist/{chunk-DYENR3PX.js → chunk-63YGGAQW.js} +2 -2
  10. package/dist/{chunk-43FKGDDO.js → chunk-7FKPPIDZ.js} +50 -19
  11. package/dist/chunk-7FKPPIDZ.js.map +1 -0
  12. package/dist/{chunk-MH6MAL64.cjs → chunk-7IRHSRPU.cjs} +136 -105
  13. package/dist/chunk-7IRHSRPU.cjs.map +1 -0
  14. package/dist/{chunk-A3QDRMVD.js → chunk-7XAGIPOX.js} +2 -2
  15. package/dist/{chunk-NNYF2AGC.js → chunk-ANZTLTKU.js} +2 -2
  16. package/dist/{chunk-N6TPODYL.js → chunk-BIHS4VWR.js} +2 -2
  17. package/dist/{chunk-476OYHGZ.js → chunk-BTO7ZEE4.js} +6 -6
  18. package/dist/{chunk-46DZF3RK.js → chunk-CXWWX6BQ.js} +2 -2
  19. package/dist/{chunk-DRY3L5KQ.cjs → chunk-D2QCXK2X.cjs} +6 -6
  20. package/dist/{chunk-DRY3L5KQ.cjs.map → chunk-D2QCXK2X.cjs.map} +1 -1
  21. package/dist/{chunk-JB5X5NNL.js → chunk-DJ2BBQ5P.js} +680 -21
  22. package/dist/chunk-DJ2BBQ5P.js.map +1 -0
  23. package/dist/{chunk-ZY5K3LPM.cjs → chunk-DO74GYSM.cjs} +1 -1
  24. package/dist/chunk-DO74GYSM.cjs.map +1 -0
  25. package/dist/{chunk-6LMKQM7I.js → chunk-EEVAPRPD.js} +3 -3
  26. package/dist/{chunk-XWA625AN.js → chunk-FSDJLAOW.js} +3 -1
  27. package/dist/chunk-FSDJLAOW.js.map +1 -0
  28. package/dist/{chunk-QJERJZJ6.js → chunk-KBAQLAEL.js} +1 -1
  29. package/dist/chunk-KBAQLAEL.js.map +1 -0
  30. package/dist/{chunk-PQRP4NSF.js → chunk-N65ZXWNQ.js} +2 -2
  31. package/dist/{chunk-UDX7TSOC.js → chunk-NCXRSQNE.js} +2 -2
  32. package/dist/{chunk-GOQBON77.js → chunk-NUPXPKIQ.js} +2 -2
  33. package/dist/{chunk-B5NEGP65.cjs → chunk-NV37CQFN.cjs} +692 -33
  34. package/dist/chunk-NV37CQFN.cjs.map +1 -0
  35. package/dist/{chunk-CB5XDCSL.js → chunk-OBRPYCIS.js} +1377 -947
  36. package/dist/chunk-OBRPYCIS.js.map +1 -0
  37. package/dist/{chunk-HIVK5V27.js → chunk-PVMROO3A.js} +51 -20
  38. package/dist/chunk-PVMROO3A.js.map +1 -0
  39. package/dist/{chunk-UQ6TZDW6.js → chunk-T77M2QP7.js} +1 -1
  40. package/dist/chunk-T77M2QP7.js.map +1 -0
  41. package/dist/{chunk-TGD7UFHD.js → chunk-UZHT3LBF.js} +2 -2
  42. package/dist/{chunk-PIMB2YNO.js → chunk-VCRBR72A.js} +2 -2
  43. package/dist/{chunk-C75WE52C.js → chunk-VNOQTDTE.js} +2 -2
  44. package/dist/{chunk-NWODPYBD.cjs → chunk-YRRBA4HW.cjs} +2 -2
  45. package/dist/{chunk-NWODPYBD.cjs.map → chunk-YRRBA4HW.cjs.map} +1 -1
  46. package/dist/chunks-PYYQURNO.js +10 -0
  47. package/dist/cli.js +31 -29
  48. package/dist/cli.js.map +1 -1
  49. package/dist/{formula-FNQYHMIY.js → formula-IPLFHEBD.js} +3 -3
  50. package/dist/{formula-VEFKYXPB.cjs → formula-TRTONZMA.cjs} +9 -9
  51. package/dist/{formula-VEFKYXPB.cjs.map → formula-TRTONZMA.cjs.map} +1 -1
  52. package/dist/{formula-5775PTP2.js → formula-U4BTEZ6I.js} +4 -4
  53. package/dist/{image-ocr-PYZU4TO7.js → image-ocr-3IJ25PS4.js} +8 -8
  54. package/dist/{image-ocr-YA2MQBRP.js → image-ocr-GTDG4SAJ.js} +6 -6
  55. package/dist/{image-ocr-6JZR734G.cjs → image-ocr-THFLQ3DC.cjs} +15 -15
  56. package/dist/{image-ocr-6JZR734G.cjs.map → image-ocr-THFLQ3DC.cjs.map} +1 -1
  57. package/dist/index.cjs +1555 -1125
  58. package/dist/index.cjs.map +1 -1
  59. package/dist/index.d.cts +17 -5
  60. package/dist/index.d.ts +17 -5
  61. package/dist/index.js +1105 -675
  62. package/dist/index.js.map +1 -1
  63. package/dist/mcp.js +31 -27
  64. package/dist/mcp.js.map +1 -1
  65. package/dist/{model-bundle-O6J7ORVC.js → model-bundle-PVWIAVEX.js} +5 -5
  66. package/dist/{models-YMOKZGCI.js → models-VCRF3JMT.js} +5 -5
  67. package/dist/{parser-YAQV7FBO.cjs → parser-2CN2DIPV.cjs} +356 -218
  68. package/dist/parser-2CN2DIPV.cjs.map +1 -0
  69. package/dist/{parser-3B6I2XL7.js → parser-T4RITZDP.js} +221 -83
  70. package/dist/parser-T4RITZDP.js.map +1 -0
  71. package/dist/{parser-PXATL5DM.js → parser-UEROAPYZ.js} +218 -80
  72. package/dist/parser-UEROAPYZ.js.map +1 -0
  73. package/dist/pdf-ocr-NARV2AQI.cjs +16 -0
  74. package/dist/{pdf-ocr-T2JXQATA.cjs.map → pdf-ocr-NARV2AQI.cjs.map} +1 -1
  75. package/dist/pdf-ocr-SYLMM727.js +18 -0
  76. package/dist/pdf-ocr-YMQZDJBO.js +15 -0
  77. package/dist/{profile-io-ZTV2MKRT.js → profile-io-YD54CDXY.js} +3 -3
  78. package/dist/{rasterize-MMUZ537K.js → rasterize-3THYOOUN.js} +3 -3
  79. package/dist/{redact-doc-S42XGV5D.js → redact-doc-65L2YJN6.js} +11 -11
  80. package/dist/{render-WCWPZKJ7.js → render-YU7QJVZ7.js} +7 -7
  81. package/dist/seal-6CQGFTML.js +10 -0
  82. package/dist/{setup-D35LKZCH.js → setup-CFHKXDY4.js} +3 -3
  83. package/dist/visual-ED2H75Q5.js +20 -0
  84. package/dist/{watch-AX64CIMN.js → watch-D6FCAYKJ.js} +12 -12
  85. package/package.json +1 -1
  86. package/dist/chunk-43FKGDDO.js.map +0 -1
  87. package/dist/chunk-B5NEGP65.cjs.map +0 -1
  88. package/dist/chunk-CB5XDCSL.js.map +0 -1
  89. package/dist/chunk-HIVK5V27.js.map +0 -1
  90. package/dist/chunk-JB5X5NNL.js.map +0 -1
  91. package/dist/chunk-MH6MAL64.cjs.map +0 -1
  92. package/dist/chunk-QJERJZJ6.js.map +0 -1
  93. package/dist/chunk-UQ6TZDW6.js.map +0 -1
  94. package/dist/chunk-XHOZB2WP.js.map +0 -1
  95. package/dist/chunk-XWA625AN.js.map +0 -1
  96. package/dist/chunk-ZY5K3LPM.cjs.map +0 -1
  97. package/dist/chunks-JD25E5PM.js +0 -10
  98. package/dist/parser-3B6I2XL7.js.map +0 -1
  99. package/dist/parser-PXATL5DM.js.map +0 -1
  100. package/dist/parser-YAQV7FBO.cjs.map +0 -1
  101. package/dist/pdf-ocr-2TTO5PX2.js +0 -15
  102. package/dist/pdf-ocr-T2JXQATA.cjs +0 -16
  103. package/dist/pdf-ocr-WF2NQA6X.js +0 -18
  104. package/dist/seal-WHK4GOHR.js +0 -10
  105. package/dist/visual-WL4TDE5B.js +0 -20
  106. /package/dist/{-VQNLLLPE.js.map → -3DURLXSO.js.map} +0 -0
  107. /package/dist/{chunk-3JWMDL3S.js.map → chunk-24N4IJ5I.js.map} +0 -0
  108. /package/dist/{chunk-RC4C7Q33.js.map → chunk-3VLVNKJV.js.map} +0 -0
  109. /package/dist/{chunk-DYENR3PX.js.map → chunk-63YGGAQW.js.map} +0 -0
  110. /package/dist/{chunk-A3QDRMVD.js.map → chunk-7XAGIPOX.js.map} +0 -0
  111. /package/dist/{chunk-NNYF2AGC.js.map → chunk-ANZTLTKU.js.map} +0 -0
  112. /package/dist/{chunk-N6TPODYL.js.map → chunk-BIHS4VWR.js.map} +0 -0
  113. /package/dist/{chunk-476OYHGZ.js.map → chunk-BTO7ZEE4.js.map} +0 -0
  114. /package/dist/{chunk-46DZF3RK.js.map → chunk-CXWWX6BQ.js.map} +0 -0
  115. /package/dist/{chunk-6LMKQM7I.js.map → chunk-EEVAPRPD.js.map} +0 -0
  116. /package/dist/{chunk-PQRP4NSF.js.map → chunk-N65ZXWNQ.js.map} +0 -0
  117. /package/dist/{chunk-UDX7TSOC.js.map → chunk-NCXRSQNE.js.map} +0 -0
  118. /package/dist/{chunk-GOQBON77.js.map → chunk-NUPXPKIQ.js.map} +0 -0
  119. /package/dist/{chunk-TGD7UFHD.js.map → chunk-UZHT3LBF.js.map} +0 -0
  120. /package/dist/{chunk-PIMB2YNO.js.map → chunk-VCRBR72A.js.map} +0 -0
  121. /package/dist/{chunk-C75WE52C.js.map → chunk-VNOQTDTE.js.map} +0 -0
  122. /package/dist/{chunks-JD25E5PM.js.map → chunks-PYYQURNO.js.map} +0 -0
  123. /package/dist/{formula-FNQYHMIY.js.map → formula-IPLFHEBD.js.map} +0 -0
  124. /package/dist/{formula-5775PTP2.js.map → formula-U4BTEZ6I.js.map} +0 -0
  125. /package/dist/{image-ocr-PYZU4TO7.js.map → image-ocr-3IJ25PS4.js.map} +0 -0
  126. /package/dist/{image-ocr-YA2MQBRP.js.map → image-ocr-GTDG4SAJ.js.map} +0 -0
  127. /package/dist/{model-bundle-O6J7ORVC.js.map → model-bundle-PVWIAVEX.js.map} +0 -0
  128. /package/dist/{models-YMOKZGCI.js.map → models-VCRF3JMT.js.map} +0 -0
  129. /package/dist/{pdf-ocr-2TTO5PX2.js.map → pdf-ocr-SYLMM727.js.map} +0 -0
  130. /package/dist/{pdf-ocr-WF2NQA6X.js.map → pdf-ocr-YMQZDJBO.js.map} +0 -0
  131. /package/dist/{profile-io-ZTV2MKRT.js.map → profile-io-YD54CDXY.js.map} +0 -0
  132. /package/dist/{rasterize-MMUZ537K.js.map → rasterize-3THYOOUN.js.map} +0 -0
  133. /package/dist/{redact-doc-S42XGV5D.js.map → redact-doc-65L2YJN6.js.map} +0 -0
  134. /package/dist/{render-WCWPZKJ7.js.map → render-YU7QJVZ7.js.map} +0 -0
  135. /package/dist/{seal-WHK4GOHR.js.map → seal-6CQGFTML.js.map} +0 -0
  136. /package/dist/{setup-D35LKZCH.js.map → setup-CFHKXDY4.js.map} +0 -0
  137. /package/dist/{visual-WL4TDE5B.js.map → visual-ED2H75Q5.js.map} +0 -0
  138. /package/dist/{watch-AX64CIMN.js.map → watch-D6FCAYKJ.js.map} +0 -0
package/README.md CHANGED
@@ -7,110 +7,101 @@
7
7
 
8
8
  > *대한민국에서 둘째가라면 서러울 문서지옥. 거기서 7년 버틴 공무원이 만들었습니다.*
9
9
 
10
- HWP 3.x/5.x, HWPX, HWPML, PDF, XLS, XLSX, DOCX, 이미지(PNG/JPG/WebP) — 관공서에서 쏟아지는 모든 문서를 파싱하고, 비교하고, 분석하고, 생성합니다.
10
+ HWP 3.x/5.x, HWPX, HWPML, PDF, XLS, XLSX, DOCX, 이미지(PNG/JPG/WebP) — 관공서 문서를 파싱·비교·생성합니다. [English](./README-EN.md)
11
11
 
12
- [English](./README-EN.md)
13
-
14
- > 📊 **PDF 공개 벤치(opendataloader-bench, 200문서) 종합 0.940(OCR 0.960·OCR+plain 0.967) — 공개된 12개 PDF 파서(상용 포함) 모두보다 높고, 쪽당 0.03초.** 한국 공문서는 원본 HWPX를 정답으로 채점해 HWPX 표 13,041개 전부 칸까지 일치합니다. → [성능 한눈에 보기](#-성능-한눈에-보기)
12
+ - 📊 **PDF 공개 벤치 1위** — opendataloader-bench 200문서 종합 0.960, 공개 12개 파서(상용 포함)보다 높음 (OCR 끄면 0.937·쪽당 0.04초)
13
+ - 🇰🇷 **한국 공문서 표 무손실** — 원본 HWPX 를 정답으로 채점해 HWPX 표 13,041개가 칸까지 일치
15
14
 
16
15
  [![Kordoc 활용하기 — 영상 보기](./docs/video-demo.jpg)](https://youtu.be/Q13GmgDcIw0)
17
16
 
18
17
  <sub>▶ 클릭하면 유튜브에서 재생됩니다.</sub>
19
18
 
19
+ **목차** — [설치](#-설치) · [주요 기능](#-주요-기능) · [성능](#-성능) · [빠른 시작](#-빠른-시작) · [CLI](#-cli) · [MCP 서버](#-mcp-서버) · [API](#-api) · [지원 포맷](#-지원-포맷) · [보안](#-보안) · [최근 변경](#-최근-변경)
20
+
20
21
  ---
21
22
 
22
- ## ⚡ 30초 설치 (AI 에이전트 연동)
23
+ ## ⚡ 설치
24
+
25
+ Node.js 20+ 만 있으면 됩니다 (macOS / Linux / Windows).
23
26
 
24
- **macOS / Linux / Windows 공용**. Node.js 20+ 만 있으면 됩니다.
27
+ ### AI 에이전트 연동 (MCP) — 30초
25
28
 
26
29
  ```bash
27
30
  npx -y kordoc setup
28
31
  ```
29
32
 
30
- 대화형 마법사가:
31
- 1. 사용 중인 AI 클라이언트 번호 선택 (Claude Desktop / Cursor / Claude Code / Windsurf / VS Code / Gemini CLI / Zed / Antigravity / Codex — 설치된 건 `[감지됨]` 표시)
32
- 2. 설정 파일 자동 패치 → 클라이언트 재시작
33
+ 설치된 AI 클라이언트(Claude Desktop · Cursor · Claude Code · Windsurf · VS Code · Gemini CLI · Zed · Antigravity · Codex)를 골라 설정을 자동으로 넣습니다. 재시작하면 [17개 문서 도구](#-mcp-서버)가 켜집니다.
33
34
 
34
- Windows 도 자동으로 `cmd /c npx` 래핑. 수동 JSON 편집 불필요. 재시작하면 17개 문서 도구 (`parse_document`, `parse_table`, `fill_form`, `patch_document`, `generate_document` 등) 활성화.
35
+ ### Claude Code 플러그인
35
36
 
36
- > **CLI 로만 쓸 거면** 설치 없이 `npx kordoc <파일>` 바로 사용. 아래 [CLI](#cli) 섹션 참고.
37
+ ```
38
+ /plugin marketplace add chrisryugj/kordoc
39
+ /plugin install kordoc@kordoc
40
+ ```
37
41
 
38
- > **`MODULE_NOT_FOUND` / `Cannot find module ...\dist\cli.js` 가 뜨면**: 과거에 깨진 글로벌 설치가 남아있는 상태입니다. 아래로 해결:
39
- > ```powershell
40
- > npm uninstall -g kordoc
41
- > npx -y kordoc@latest setup
42
- > ```
42
+ `.hwp`/`.hwpx` 파일이나 공문서 작성 요청에 스킬이 자동으로 켜집니다(별도 설치 불필요).
43
43
 
44
- > **Windows PowerShell 에서 `npx.ps1 파일을 로드할 수 없습니다 · PSSecurityException` 이 뜨면**: PowerShell 기본 보안 정책이 서명 없는 `.ps1` 을 차단하는 표준 동작입니다 (kordoc 무관). 아래 중 하나 쓰시면 됩니다.
45
- >
46
- > **방법 1 — 명령 프롬프트(cmd) 창에서 실행** (가장 안전)
47
- > 윈도우 키 → `cmd` 검색 → Enter → 검은 창에서 그대로:
48
- > ```
49
- > npx -y kordoc setup
50
- > ```
51
- >
52
- > **방법 2 — PowerShell 실행 정책 한 번만 완화**
53
- > 관리자 권한 PowerShell:
54
- > ```powershell
55
- > Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
56
- > ```
57
- > 이후 PowerShell 재시작 → `npx -y kordoc setup` 그대로 됨.
44
+ ### 라이브러리 · CLI
58
45
 
59
- > **네트워크가 제한된 linux/x64 에서 PNG 렌더·이미지 OCR 만 `MISSING_DEPENDENCY`(sharp) 로 실패하면** (#99): 선택 의존 `onnxruntime-node` 의 설치 스크립트가 CUDA 바이너리를 `api.nuget.org` 에서 받다 실패하면 npm 이 `sharp`·`@huggingface/transformers` 까지 함께 뺍니다(kordoc 은 CPU 추론만 씁니다). CUDA 다운로드를 건너뛰고 설치하세요 — npx 캐시 실행은 `npm install sharp` 로 고쳐지지 않습니다.
60
- > ```bash
61
- > ONNXRUNTIME_NODE_INSTALL=skip npx -y kordoc@^4 <command> ...
62
- > ```
46
+ ```bash
47
+ npm install kordoc # CLI 만 쓸 거면 설치 없이 npx kordoc <파일>
48
+ ```
63
49
 
64
- ### Claude Code 플러그인으로 설치
50
+ PDF·OCR 의존성은 기본으로 설치됩니다(줄이려면 `--omit=optional` — 대신 PDF·OCR 이 빠짐). PDF 인쇄 렌더(`markdownToPdf`)만 `puppeteer-core` 를 따로 설치하세요.
65
51
 
66
- MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
52
+ <details>
53
+ <summary>설치 문제 해결</summary>
67
54
 
68
- ```
69
- /plugin marketplace add chrisryugj/kordoc
70
- /plugin install kordoc@kordoc
71
- ```
55
+ - **`MODULE_NOT_FOUND` / `Cannot find module ...\dist\cli.js`** — 깨진 글로벌 설치가 남은 상태입니다.
56
+ ```powershell
57
+ npm uninstall -g kordoc
58
+ npx -y kordoc@latest setup
59
+ ```
60
+ - **PowerShell `npx.ps1 … PSSecurityException`** — PowerShell 기본 정책 문제입니다. 명령 프롬프트(cmd)에서 `npx -y kordoc setup` 을 실행하거나, `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` 후 PowerShell 을 다시 여세요.
61
+ - **제한 네트워크 linux/x64 에서 PNG 렌더·이미지 OCR 만 `MISSING_DEPENDENCY`(sharp)** (#99) — onnxruntime 의 CUDA 다운로드가 실패하면서 sharp 까지 빠진 경우입니다.
62
+ ```bash
63
+ ONNXRUNTIME_NODE_INSTALL=skip npx -y kordoc@^4 <command> ...
64
+ ```
65
+ - **폐쇄망** — [보안](#-보안) 참고.
72
66
 
73
- `.hwp`/`.hwpx` 언급이나 공문서 생성·서식 채우기 요청 시 kordoc 스킬이 자동 활성화됩니다
74
- (내부에서 `npx -y kordoc@^4` CLI 호출 — 별도 설치 불필요).
67
+ </details>
75
68
 
76
69
  ---
77
70
 
78
- ## 💡 kordoc으로 무엇을 할 수 있나요?
79
-
80
- 단순한 텍스트 추출을 넘어, **공문서 처리를 위한 모든 과정**을 자동화합니다.
81
-
82
- * **📄 어떤 문서든 마크다운으로**: `HWP3` (구버전), `HWP`(5.x), `HWPX`, `HWPML`, `PDF`, `XLS`, `XLSX`, `DOCX` 파일은 물론 `PNG`/`JPG`/`WebP` 이미지(자동 OCR)까지 즉시 `Markdown`으로 변환합니다. AI(LLM)가 문서를 읽고 분석하기 가장 좋은 상태로 만들어줍니다.
83
- * **📊 복잡한 표(Table) 복원 — 숫자로 잠금**: 병합·중첩 표를 구조 그대로 살립니다. HWPX 코퍼스 2,286문서 **표 13,041/13,041 칸까지 무손실**(독립 추출기 대조 게이트 1.0), 같은 문서를 PDF로 내보내 원본 HWPX와 대조하면(716쌍 2,692표) **표 찾기 98.9%·칸까지 완전 일치 94.2%**. 선 없는 PDF 표도 감지해 복원하고, 법령 개정안 PDF의 신구조문대비표도 통째로 살립니다 (v3.16.2).
84
- * **🔍 신구대조표 자동 생성**: 두 문서의 차이점을 분석하여 무엇이 바뀌었는지 한눈에 보여줍니다. (HWP와 HWPX 간의 비교도 가능!)
85
- * **📝 마크다운을 다시 HWPX로**: AI가 작성한 내용을 다시 보고서 양식(`HWPX`)으로 되돌려줍니다. 이제 복사-붙여넣기 노가다에서 해방되세요.
86
- * **🏛️ 정부 표준 공문서 생성 (v4.0)**: 실제 정부 양식 16종 + 실결재 기안문 60건을 전수 디코드·대조해 만든 공문서 엔진. 개조식 보고서(표지·목차 배너·로마숫자 장헤더·쪽번호·결재란), 기안문(별지 제1호서식 두문·결문, "끝." 자동), 공고문·보도자료 프리셋, 항목부호 8단계(1. 가. 1) 가)…) 자동, 공문서 표기법 검수 19룰(`kordoc lint`)까지 — 한글 COM 실렌더로 조판까지 실측 검증했습니다.
87
- * **🔄 서식 보존 무손실 라운드트립 (v3.0)**: 변환된 마크다운을 편집해서 `patchHwpx`(HWPX) / `patchHwp`(HWP 5.x 바이너리)에 넘기면, **원본 서식을 1바이트도 건드리지 않고** 바뀐 문단/표 셀의 텍스트만 원본 안에서 교체합니다. v3.7부터는 **표에 행을 추가/삭제하는 편집**도 원본 서식을 승계하며 반영되고, v3.8부터는 HWP 5.x의 **빈 셀에 값 넣기**도 지원합니다.
88
- * **🖼️ 레이아웃 보존 렌더 (v3.10~3.15)**: 한컴이 저장한 조판 캐시 좌표로 원본 레이아웃을 SVG로 재현하고, 캐시가 없는 파일(AI가 만든 HWPX·편집본)은 **순수 TS reflow 엔진**이 직접 조판합니다. 다페이지·표·그리기 도형·검색어 형광펜까지. 서버에 한컴 없이 HWPX 미리보기를 만들 수 있습니다.
89
- * **📊 차트 생성 (v3.16)**: 마크다운의 ```chart 펜스(type/cat/계열 라인)가 한컴 네이티브 차트(OOXML chartSpace)로 생성됩니다 — 막대·선·원·도넛·영역·분산·방사형 등 20종, 계열/조각 색 지정 가능.
90
- * **🔴 도장/서명 자동 날인 (v3.16)**: "(인)"·"서명 또는 인" 같은 앵커 문구를 찾아 도장 PNG를 글 앞 부유로 배치합니다. 표/페이지를 키우지 않아 날인 후 서식이 밀리지 않습니다 (`kordoc seal`).
91
- * **✏️ 양식 자동 채우기**: 공문서 양식 템플릿(신청서, 보고서)에 값을 넣으면 자동으로 빈칸을 채웁니다. 원본 서식(글꼴, 크기, 정렬)을 100% 보존합니다.
92
- * **👓 내장 OCR — API 키 없이 (v4.2)**: 스캔본·이미지 PDF와 PNG/JPG/WebP 이미지를 **로컬 CPU 추론**(PP-OCRv5 korean)으로 읽습니다. 외부 서비스도 API 키도 없이, 텍스트층이 깨진 페이지만 골라 OCR 하고, 래스터 괘선까지 찾아 스캔본에서도 표를 복원합니다.
93
- * **📑 RAG·인용 대응 (v4.1~4.8)**: 헤딩·개조식 위계를 breadcrumb으로 보존한 구조 청크(`--format chunks`)와, 조판 캐시로 복원한 **실제 쪽 번호** 기준 페이지별 마크다운(`pages`)을 냅니다 — 답변에 "몇 쪽" 각주를 붙일 수 있습니다.
94
- * **🕶️ 개인정보 마스킹 (v4.1)**: 주민·외국인등록번호·전화·이메일·카드·계좌·사업자등록번호·여권·운전면허(v4.14.4 부터 opt-in 으로 이름·주소)를 탐지해 **원본 서식 그대로** 가린 HWPX/HWP를 냅니다 (`kordoc redact`). 본문·표·머리말/꼬리말·각주·글상자·필드·미리보기(텍스트·이미지)·문서 정보(제목·작성자)까지 가리고, 결과 파일을 다시 훑어 남은 개인정보가 있으면 실패(exit 2)로 알립니다. **PDF·DOCX·XLSX 등은 원본 파일을 고치지 않고 마스킹된 마크다운만** 냅니다 — PDF 자체의 가림(redaction)은 지원하지 않습니다. 이미지 속 글자와 문맥 없는 맨이름은 탐지하지 못하는 자동 검출 보조 도구이므로 공개 전 사람 확인은 필수입니다.
95
- * **🤖 AI 에이전트 연동 (MCP)**: `Claude Desktop`, `Cursor`, `Codex`와 같은 도구에서 직접 `kordoc`을 호출해 문서를 읽고 코딩할 수 있습니다.
71
+ ## 💡 주요 기능
72
+
73
+ | 기능 | 내용 |
74
+ | --- | --- |
75
+ | 📄 **문서 → Markdown** | HWP·HWPX·PDF·DOCX·XLS(X)·이미지를 Markdown + 구조 IR(`IRBlock[]`)로 |
76
+ | 📊 **표 복원** | 병합·중첩 표를 구조 그대로 — 선 없는 PDF 표, 신구조문대비표까지 |
77
+ | 🔍 **신구대조표** | 두 문서 차이를 블록·셀 단위로 (HWP ↔ HWPX 교차 비교) |
78
+ | 📝 **Markdown → HWPX** | AI 가 쓴 글을 표·수식·차트 포함 HWPX 로 |
79
+ | 🏛️ **공문서 생성** | 개조식 보고서·기안문·보도자료·서울 방침서 프리셋, 표기법 검수 19룰(`kordoc lint`) |
80
+ | 🔄 **서식 보존 편집** | 고친 Markdown 을 원본에 반영 — 바뀐 글만 교체 (`patchHwpx`/`patchHwp`) |
81
+ | ✏️ **양식 채우기** | 빈칸·누름틀을 서식 그대로 채움, 내장 표준 기안문 2종 |
82
+ | 🔴 **도장 날인** | "(인)" 자리에 도장 이미지를 띄움 (`kordoc seal`) |
83
+ | 🖼️ **렌더** | 한컴 없이 원본 모양 그대로 SVG/PNG/PDF 미리보기 |
84
+ | 📈 **차트** | Markdown 펜스 → 한컴 네이티브 차트 20종 |
85
+ | 👓 **내장 OCR** | 스캔 PDF·이미지를 로컬 CPU 로 — API 키 불필요 |
86
+ | 📑 **RAG · 인용** | 위계 breadcrumb 청크, 실제 쪽 번호로 인용 |
87
+ | 🕶️ **개인정보 마스킹** | 주민번호·전화·계좌 등을 찾아 HWPX/HWP 는 서식 그대로 가림 (공개 전 사람 확인 필수) |
88
+ | 🤖 **MCP** | Claude·Cursor·Codex 에서 문서 도구로 직접 호출 |
96
89
 
97
90
  ---
98
91
 
99
- ## 📊 성능 한눈에 보기
92
+ ## 📊 성능
93
+
94
+ 모든 수치는 `npm run bench:gate` 로 재현되고, 배포 때마다 이 게이트를 통과해야 합니다. 채점 기준·재현 방법·옵션별 수치는 [docs/benchmarks.md](docs/benchmarks.md).
100
95
 
101
- ### PDF → Markdown: 공개 벤치 12개 엔진보다 높습니다
96
+ ### PDF → Markdown (opendataloader-bench 200문서)
102
97
 
103
- [opendataloader-bench](https://github.com/opendataloader-project/opendataloader-bench)는 PDF 200쪽(논문·보고서·슬라이드·포스터·스캔)을 사람이 만든 정답과 비교해 **읽기 순서(NID)·표 구조(TEDS)·제목 위계(MHS)** 를 점수로 내는 공개 벤치입니다. 1.0이 정답과 똑같다는 뜻입니다.
98
+ 논문·보고서·슬라이드·스캔 PDF 200개를 사람이 만든 정답과 비교해 읽기 순서·표 구조·제목 위계를 잽니다(1.0 = 정답).
104
99
 
105
100
  | 순위 | 엔진 | 종합 | 읽기 순서 | 표 | 제목 | 쪽당 시간 |
106
101
  | ---: | --- | ---: | ---: | ---: | ---: | ---: |
107
- | **1** | **kordoc 기본값** (OCR 모델이 캐시에 있을 때) | **0.940** | **0.943** | **0.936** | **0.935** | **0.05초** |
108
- | 참고 | kordoc 기본값 (OCR 모델 없음) | 0.937 | 0.938 | 0.936 | 0.933 | 0.04초 |
109
- | 참고 | kordoc `plain: true` (그림 자리 표시·링크 URL 없이) | 0.946 | 0.947 | 0.937 | 0.940 | 0.03초 |
110
- | 참고 | kordoc `ocr: true` (그림 속 글까지) | 0.960 | 0.960 | 0.979 | 0.949 | 0.46초 |
111
- | 참고 | kordoc `ocr: true, plain: true` | 0.967 | 0.968 | 0.981 | 0.956 | 0.46초 |
112
- | 참고 | kordoc `plain: true, htmlTables: true` | 0.949 | 0.954 | 0.940 | 0.943 | 0.03초 |
113
- | 참고 | kordoc `ocr: true, plain: true, htmlTables: true` | 0.973 | 0.977 | 0.983 | 0.959 | 0.46초 |
102
+ | **1** | **kordoc 기본값** | **0.960** | **0.961** | **0.979** | **0.945** | **0.52초** |
103
+ | 참고 | kordoc `ocr: false` (가장 빠름) | 0.937 | 0.938 | 0.936 | 0.933 | 0.04초 |
104
+ | 참고 | kordoc `ocr: true, plain: true, htmlTables: true` | 0.973 | 0.977 | 0.983 | 0.959 | 0.58초 |
114
105
  | 2 | opendataloader-hybrid | 0.907 | 0.934 | 0.928 | 0.821 | 0.46초 |
115
106
  | 3 | nutrient (상용) | 0.885 | 0.925 | 0.708 | 0.819 | 0.01초 |
116
107
  | 4 | docling | 0.882 | 0.898 | 0.887 | 0.824 | 0.76초 |
@@ -124,33 +115,27 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
124
115
  | 12 | markitdown | 0.589 | 0.844 | 0.273 | 0.000 | 0.11초 |
125
116
  | 13 | liteparse | 0.576 | 0.866 | 0.000 | 0.000 | 1.06초 |
126
117
 
127
- - 기본값만으로 **종합·읽기 순서·표·제목 네 항목 모두 1위**입니다. GPU·클라우드 API·LLM 없이 Node.js 하나로, 쪽당 0.04초에 돕니다.
128
- - `ocr: true`를 켜면 차트·로고·스캔처럼 **그림 속 글자까지** 로컬에서 읽어 0.960이 됩니다. `plain: true`는 그림 자리 표시·링크 URL·밑줄/굵게 표기를 빼 색인·RAG 용 글만 냅니다(정답지가 이런 표기를 담지 않아 점수도 오릅니다). 기본값은 OCR 모델이 이미 캐시에 있으면(`kordoc models`·앞선 `ocr: true` 로 받은 경우) **텍스트층이 없는 쪽(스캔·곡선 글자)만** 자동으로 읽고, 모델이 없으면 다운로드하지 않고 `NEEDS_OCR` 경고로 알립니다. 글이 있는 쪽 안의 그림 속 글은 `SKIPPED_IMAGE` 경고로 알리고 `ocr: true` 로 읽습니다(`ocr: false` 는 자동 OCR 도 끔).
129
- - 다른 엔진 점수는 벤치 저장소가 공개한 결과(Apple M4 32GB)이고, kordoc은 같은 PDF·같은 정답·**수정하지 않은 원본 채점기**로 재었습니다(Apple M4 24GB, 200문서 한 프로세스 순차 8초). 벤치 저장소의 opendataloader-hybrid 예측을 같은 채점기로 다시 채점하면 0.9066으로 공개 점수와 같습니다. 재현: `node bench/odl-bench.mjs <벤치 클론>` → 벤치의 `src/evaluator.py`.
130
- - 벤치 저장소에 결과만 PR 로 올라온 상용 LM-Kit One(미병합 #34)은 OCR 없이 0.948·OCR 0.963입니다. 표 칸을 정답지와 같은 HTML 모양(`<td> 글 </td>`, 머리행도 `<td>`)으로 내는 차이가 OCR 없는 쪽 격차의 대부분이고, 같은 서식으로 맞춰 비교하면 kordoc `plain` 0.951·LM-Kit 0.948이고, 표를 들여쓴 HTML 로 내는 `htmlTables` 를 켜면 kordoc 이 OCR 없이 0.949·OCR 0.973 로 두 줄 모두 앞섭니다.
118
+ - 기본값만으로 네 항목 모두 1위 — GPU·클라우드 API·LLM 없이 Node.js 하나로. `ocr: false` 도 네 항목 모두 1위입니다.
119
+ - 기본값은 OCR 모델이 캐시에 있으면 스캔 쪽과 큰 그림 속 글을 자동으로 읽습니다. 그림이 많은 문서가 느려지니 속도가 중요하면 `ocr: false`.
131
120
 
132
- ### 한국 공문서: 원본 HWPX를 정답으로 채점합니다
121
+ ### 한국 공문서 — 원본 HWPX 가 정답
133
122
 
134
- 한글(HWPX)로 쓴 원본과 그 문서를 PDF로 내보낸 파일이 짝으로 있는 공문서를 모아, **원본 HWPX를 정답**으로 삼아 PDF에서 뽑은 글과 표를 잽니다. 보도자료·결재문서·법령 서식·예산서 등 실제 정부 문서입니다.
123
+ HWPX 원본과 그 PDF 가 짝으로 있는 실제 정부 문서(보도자료·결재문서·법령 서식·예산서 등)로 잽니다.
135
124
 
136
125
  | 분야 | 규모 | 결과 |
137
126
  | --- | --- | --- |
138
- | HWPX 본문·표 | 2,286문서, 표 13,041개 | 글 누락 0 · 표 13,041개 전부 칸까지 일치 · 읽기 순서 100% |
139
- | HWP 5.x | HWPX와 짝 1,120쌍 | HWPX 결과와 전부 일치 |
140
- | PDF 글 | 744쌍 (HWPX 정답) | 글자 재현율 99.8% · 정확도 99.5% · 읽기 순서 99.1% · 어절 F1 98.7% |
141
- | PDF 표 | 708쌍, 표 2,632개 (HWPX 정답) | 표 찾기 99.5% · 칸까지 완전 일치 96.5% · 칸 F1 0.983 |
142
- | PDF 전체 | 1,911문서 (텍스트층 채점 1,724) | 글 커버리지 99.8% |
143
- | 스캔 OCR (내장, 로컬 CPU) | 54문서 104쪽 (216dpi 렌더) | 글자 재현율 98.1% · 한글 재현율 99.3% · 정확도 97.5% · 쪽당 0.9초 |
127
+ | HWPX 본문·표 | 2,286문서, 표 13,041개 | 글 누락 0 · 표 전부 칸까지 일치 · 읽기 순서 100% |
128
+ | HWP 5.x | HWPX 와 짝 1,120쌍 | HWPX 결과와 전부 일치 |
129
+ | PDF 글 | 744쌍 | 글자 재현율 99.8% · 정확도 99.6% · 읽기 순서 99.1% · 어절 F1 98.8% |
130
+ | PDF 표 | 708쌍, 표 2,632개 | 표 찾기 99.5% · 칸까지 완전 일치 97.4% · 칸 F1 0.986 |
131
+ | PDF 전체 | 1,911문서 | 글 커버리지 99.8% |
132
+ | 스캔 OCR (로컬 CPU) | 53문서 102쪽 | 글자 재현율 99.0% · 정확도 99.4% · 쪽당 약 1초 |
144
133
  | DOCX·XLSX·XLS·HML | 88문서 | 글·숫자 누락 0 |
145
- | Markdown → HWPX → Markdown 왕복 | 83건 | 글·표·제목·수식 손실 0 |
134
+ | Markdown → HWPX → Markdown | 83건 | 글·표·제목·수식 손실 0 |
146
135
 
147
- 모든 수치는 `npm run bench:gate`로 재현되며, 배포(`npm publish`) 때마다 이 게이트를 통과해야만 올라갑니다.
136
+ ### HWP·HWPX → Markdown — HwpForge 비교
148
137
 
149
- > **채점 기준 변경 (2026-09-28)** — PDF 글 읽기 순서는 같은 글이 여러 번 나오는 줄(쪽마다 되풀이되는 서식 칸)을 순서가 맞는 등장 자리로 보고, 떠 있는 글상자(DOCX 글상자·HWPX 그리기 개체 글)와 글자·숫자 없는 줄(마스킹 "*****")은 순서 채점에서만 뺍니다(원본에 흐름 순서가 없음). 두 평문 모두 줄 머리 목록 표지 "- "와 각주 감싸개 "(주: …)" 괄호를 걷습니다. PDF 커버리지는 리더 점 런을 지우고, 표 정답은 HWPX 와 다른 판인 쌍(PDF 글이 3배 넘음)을 글 정답처럼 모수에서 빼며, OCR 정확도는 문서별 표본 쪽을 고정했습니다(`bench/ocr-pages.json`). v4.15.7 출력을 새 채점기로 재면 읽기 순서 0.98787, 이번 파서 수정으로 0.99094 입니다. PDF 글·표 정답은 PDF 텍스트층(pdftotext)이 정답 글자의 93%를 담지 못한 쌍(렌더 결함 재현본 등 PDF 에 글이 없는 쌍 7개)도 모수에서 뺍니다. 자세한 내용은 [CHANGELOG](CHANGELOG.md).
150
-
151
- ### HWP·HWPX → Markdown: HwpForge와 같은 채점기로
152
-
153
- 같은 코퍼스를 [HwpForge](https://github.com/ai-screams/HwpForge) 0.16.6(`to_md`, lossy)과 kordoc으로 각각 Markdown으로 바꾸고, **원본 HWPX XML을 정답**으로 같은 채점기에 넣었습니다(표는 두 출력 모두 같은 Markdown 표 파서로 격자화, 1열 표는 양쪽 모두 표 채점에서 제외).
138
+ 같은 코퍼스를 원본 HWPX XML 을 정답으로 같은 채점기에 넣었습니다(1열 표 제외).
154
139
 
155
140
  | | kordoc | HwpForge 0.16.6 |
156
141
  | --- | ---: | ---: |
@@ -163,740 +148,11 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
163
148
  | HWP — 표 완전 일치 (3,111표) | **100%** | 27.0% |
164
149
  | HWP — 칸 F1 | **1.000** | 0.349 |
165
150
 
166
- HwpForge는 생성·편집 중심 라이브러리이고, Markdown 표가 파이프 표뿐이라 병합 칸을 표현하지 못하는 점이 표 점수 차이의 대부분입니다. 1열 표(코퍼스 1,288개)는 제목·본문 상자 43%, 빈 여백 행 틀 28%, 목록형 3%인 꾸밈 틀이라 표로 낼지 줄 글로 낼지는 표현 선택이고, 그 글은 글 재현율이 채점합니다. 1열 표를 넣으면 HWPX 10,392표 kordoc 90.6%·HwpForge 36.0%, HWP 3,500표 92.8%·32.1%입니다. HWP 문서 수는 짝 HWPX가 배포용 암호라 정답이 없는 1건을 뺀 값입니다. 재현: `bench/hwpforge-bench.py` → `node bench/compare-md-parsers.mjs <출력 폴더>` (1열 표 포함은 `--include-single-col`).
167
-
168
- ---
169
-
170
- ## v4.16.0 변경사항
171
-
172
- PDF 글·표 복원을 채점 기준 정비와 함께 올렸습니다. 원본 HWPX·DOCX 를 정답으로 잰 PDF 글은 글자 재현율 99.8%·정확도 99.5%·읽기 순서 99.1%·어절 F1 98.7%, 한국 PDF 표는 칸까지 완전 일치 96.7% 입니다. 쪽 아래 각주와 문서 끝 미주를 참조 자리로 옮기고, 목차 점선·책 가장자리 색인 탭·서식 밑줄 빈칸·중·일문 줄 꺾임·보도자료 연락처 표를 바로잡았습니다. 텍스트층 없는 쪽(스캔·곡선 글자)은 OCR 모델이 이미 캐시에 있으면 자동으로 읽어 ODL 200 기본 0.940 이 됩니다(`ocr: false` 로 끔, 모델이 없으면 다운로드하지 않고 종전처럼 경고). DOCX 번호 목록은 "1." 대신 실제 번호("[3]"·"5.1"·"A.1")로 나옵니다. `-o`/`-d` 로 저장하는 그림은 문서마다 `images/<문서 이름>/` 에 저장되어 여러 문서를 같은 폴더로 변환해도 덮어쓰이지 않습니다(#98 — 그림 경로가 바뀌었습니다). `render --reflow` 호환(#97), 네트워크가 제한된 linux/x64 설치 안내(#99), webhook·MCP 출력·HTML 렌더 보안 보강(#100)도 담았습니다.
173
-
174
- ## v4.15.7 변경사항
175
-
176
- 표 안의 표와 PDF 글 복원을 넓혔습니다. HWP5 표는 같은 문서의 HWPX 와 같은 모양으로 나오고(여러 쪽 본문 상자만 문단으로 풂), 3~8단 중첩표가 HWPX·HWP5·PDF 모두 단 수 그대로 남습니다. HWP·HWPX → Markdown 표는 1열 표를 뺀 채점에서 HWP 3,111/3,111·HWPX 9,122/9,123 이 일치합니다. PDF 는 배경에 가려진 글 제외, 세로쓰기 글상자, 자간 벌린 영문, TeX 수식 글꼴 기호, 장 번호 제목, 칸에 줄마다 적은 숫자 목록을 바로잡아 ODL 200 기본 0.937·`ocr: true` 0.960 이 됐습니다. 새 옵션 `plain`(그림 자리 표시·링크 URL·강조 표기 없이 글 위주)과 `htmlTables`(모든 표를 들여쓴 HTML 로)를 켜면 OCR 없이 0.949·OCR 0.971 입니다. 이슈 #91~#95(희소 XLSX·누름틀 안내문·암호 HWPX Windows 멈춤·`-o` 그림 링크·`generate --image-dir` 한글 이름)를 고쳤습니다.
177
-
178
- ## v4.15.6 변경사항
179
-
180
- PDF 구조 복원을 한 단계 더 올려 ODL 공개 PDF 200문서 종합이 0.905513(4.15.5)에서 **0.934509**로 올랐습니다(읽기 순서 0.938·표 0.931·제목 0.926, 공개 12개 엔진 중 최고). 워드·슬라이드 표(음영 칸·투명 글상자·위아래 괘선만 있는 여러 줄 머리), 작은 대문자·옛 숫자 글리프, 여백의 세로 도장, 사이드바 소제목, 칸 안 영문 줄바꿈과 줄마다 쪼개진 링크를 바로잡았습니다. `ocr: true`는 차트·로고 같은 그림 속 글까지 영역마다 읽어 0.952이고, 기본값에서 OCR이 필요한 쪽은 경고에 다시 파싱하는 방법(`ocr: true` / `--ocr`)을 알려줍니다. 한국 공문서 게이트(표 exact 2,536/2,692·글 recall 99.49%)는 그대로 유지했습니다.
181
-
182
- ## v4.15.5 변경사항
183
-
184
- PDF 표 경계와 두 단 지면 읽기 순서를 복원했습니다. 세로선만 내려 그은 머리·합계 행, 머리행만 음영 상자로 두른 표, 가로 괘선만 있는 학술지 표를 한 표로 읽고, 두 단 지면에서는 그림 옆 캡션을 먼저, 각주를 두 단 본문 뒤에 읽습니다. ODL 공개 PDF 200문서 종합은 0.775354(4.15.4)에서 0.905513으로 올랐습니다. PDF 제목 오탐 두 유형(#89)과 없는 입력 파일의 실패 코드(`FILE_NOT_FOUND`, #88)도 바로잡았습니다. 세부 지표와 OCR 게이트 기준 재산정 사유는 [CHANGELOG](CHANGELOG.md)를 참고하세요.
185
-
186
- ## v4.15.4 변경사항
187
-
188
- 한국 PDF의 밀집 표 행과 클립 경계를 복원해 표 exact가 2,512/2,692에서 2,526/2,692로 늘었습니다. ODL 공개 PDF 200문서에서는 제목과 정렬된 수치표를 개선해 종합 점수가 0.724617에서 0.775354로 올랐습니다. 외부 하이브리드의 기존 예측을 재채점한 0.906572에는 아직 못 미칩니다. 남은 구조 오류와 세부 지표는 [CHANGELOG](CHANGELOG.md)를 참고하세요.
189
-
190
- ## v4.15.3 변경사항
191
-
192
- 쪽을 넘는 중첩표의 머리 행과 다음 쪽 본문이 감싸개 때문에 갈라지던 사례를 복원했습니다. PDF 중첩표 정답 176개 중 정확 일치는 157개에서 158개로 늘었고, 최상위 표 구조 정확 일치는 유지됐습니다. 셀 클립이 많은 PDF 쪽에서는 가까운 변끼리만 비교하도록 바꿔 클립 묶음 단계의 시간을 줄였습니다. 전체 문서 속도와 남은 정확도 한계는 [CHANGELOG](CHANGELOG.md)를 참고하세요.
193
-
194
- ## v4.15.2 변경사항
195
-
196
- PDF 표의 좁은 시각적 간격을 빈 행·열로 잘못 늘리던 경우를 줄였습니다. 여러 행에 반복되는 실제 빈 열은 유지해, 기존 PDF 표 정답 2,692개의 정확도는 v4.15.1과 같습니다. 밀집 괘선의 교차점 처리에 쓰는 메모리도 줄였습니다. 일부 한 행짜리 머리표와 법령 양식의 빈 칸 구조는 아직 정확히 복원하지 못합니다. 자세한 수치와 남은 문제는 [CHANGELOG](CHANGELOG.md)를 참고하세요.
197
-
198
- ## v4.15.1 변경사항
199
-
200
- PDF 표 복원과 대형 입력의 안정성을 보완했습니다.
201
-
202
- - **PDF 안정성과 속도**: 매우 긴 괘선 때문에 메모리가 폭증하던 문제를 고치고, 쪽 처리 후 자원 해제와 반복 그림 처리를 개선했습니다. 입력 ArrayBuffer를 재사용할 수 있으며, 쪽 수 상한으로 잘린 범위는 경고합니다.
203
- - **PDF 글·표**: 원점이 이동하거나 회전된 쪽과 Form XObject 안의 좌표 처리를 바로잡았습니다. 좁은 빈 열, 쪽을 넘는 중첩표·비고를 복원하고 표 아래 주석·출처가 꼬리말로 삭제되지 않게 했습니다. 쪽별 마크다운도 제공합니다.
204
- - **OCR**: JPEG의 EXIF 방향을 반영합니다. 큰 이미지는 최대 24 MP로 축소하고, 검출 상자는 3,000개까지 인식하며 축소·상한 초과는 경고합니다. 괘선 검출의 반복 계산도 줄였습니다.
205
- - **인명 마스킹**: 명시적인 속기록·회의록 문맥의 이름 4건을 추가 탐지했습니다(opt-in). 같은 외부 정답에서 361/411, 오탐 0건이며 미탐 50건은 남아 있습니다.
206
-
207
- 자세한 변경 내용은 [CHANGELOG](CHANGELOG.md)를 참고하세요.
208
-
209
- ## v4.15.0 변경사항
210
-
211
- 실문서 코퍼스를 보강하고 프로덕션 리뷰에서 확인한 파싱·보안·성능 문제를 고쳤습니다.
212
-
213
- - **긴 스프레드시트**: XLSX·XLS의 1만 행 무경고 절단을 수정하고, 칸 예산을 초과하면 경고합니다. XLSX는 XML을 행 묶음으로 읽어 대형 시트 메모리를 줄이고, XLS는 희소 격자를 사용합니다. 동아시아 날짜와 DOCX 머리글·바닥글도 보완했습니다.
214
- - **PDF 글**: 실제 낱말을 붙이던 균등배분 정리, 표 칸의 글자 조각, 날짜 빈칸, 넓은 줄간격의 문단 이음을 고쳤습니다.
215
- - **보안**: HTML 표 칸의 원문을 이스케이프하고, 인쇄·렌더 PDF는 JavaScript를 끄고 `data:`·`about:` 외 요청을 차단합니다. webhook의 IPv6 SSRF 방어와 MCP 쓰기 경로 제한도 보강했습니다.
216
- - **안정성과 속도**: CLI·MCP·parse-worker의 진단 메시지를 stderr로 보내 JSON 출력을 보호합니다. 확장자가 실제 형식과 다른 문서의 무손실 패치를 고치고, 문서 비교·여러 구간 치환의 반복 계산을 줄였습니다.
217
- - **인명·주소 마스킹**: 회의록 발언자·기관 직함 앞 이름·결재란과 도로명 변형을 보강하고 오탐을 줄였습니다(opt-in).
218
- - **디렉토리 감시**: `watch -d`가 하위 폴더 구조를 보존해 같은 파일명끼리 덮어쓰지 않습니다. [PR #82](https://github.com/chrisryugj/kordoc/pull/82), 기여: @ROTl24.
219
- - **검증 코퍼스**: 정책브리핑 HWPX↔PDF 300쌍, DOCX↔LibreOffice PDF 36쌍과 XLS 정답지 트랙을 추가했습니다. 자세한 실측은 [CHANGELOG](CHANGELOG.md)를 참고하세요.
220
-
221
- ## v4.14.4 변경사항
222
-
223
- 읽기 품질을 한 번 더 점검했습니다. 원본 HWPX 를 정답으로 PDF 글을 재는 벤치를 새로 세워 띄어쓰기·읽기 순서까지 보고, PDF 표·OCR·개인정보 마스킹을 고쳤습니다.
224
-
225
- - **📊 PDF 표**: 쪽을 넘는 표의 쪼개진 행과 반복 머리 행, 쪽을 넘는 한 칸, 표 위에 걸친 그림·글상자, 셀 간격 표를 바로잡았습니다. hwpx↔pdf 430쌍 1,784표 정확 일치 90.1% → 92.9%, 표 안의 표 63.7% → 72.0%. 텍스트층이 있는 417쌍만 보면 94.5%, 칸 글이 제자리에 있는 비율 0.768 → 0.903.
226
- - **✍️ PDF 띄어쓰기·줄 이음**: 본문을 시각 줄이 아니라 문단으로 복원합니다. 한글이 글자 단위로 줄바꿈된 문서에서 "재 일학도의용군인"·"또 는" 처럼 낱말 중간에 공백이 들어가던 것을 조사·어미·문서 안 어휘로 판정해 붙입니다. 어절 F1 0.925 → 0.971.
227
- - **🧮 한컴 밖에서 만든 PDF**: 예산서처럼 글자마다 떨어져 찍힌 숫자("2 0 , 7 7 5" → "20,775"), 행마다 끊어 그은 괘선, 빈칸 위에 따로 얹은 숫자를 바로잡았습니다. PDF 글자 커버리지 0.99569 → 0.99760.
228
- - **🔎 글자를 곡선으로 그린 PDF**: 한글을 글꼴 대신 곡선으로 그린 쪽은 텍스트층에 한글이 없어 종전엔 경고 없이 한글이 빠졌습니다. 이런 쪽을 찾아(`ocrReason: "vector_text"`) OCR 을 켜면 읽고, 끄면 경고합니다. 표는 그 쪽의 실제 괘선으로 복원합니다.
229
- - **👓 OCR**: 점선 괘선·색 칸 경계·목차 리더 점·숫자 앞 △ 를 바로잡았습니다. 글자 오류율 0.171 → 0.096(정답지인 텍스트층 수정분 포함), OCR 표 셀 F1 0.440 → 0.618.
230
- - **🕶️ 개인정보 마스킹 인명·주소** (opt-in, `--rules …,name,address`): 역할어·호칭·라벨·표 머리글 문맥으로 이름을, 도로명·지번 문법으로 주소를 찾아 가립니다(주소는 시·군·구까지 남김). 외부 정답(473문장) 인명 354/411·주소 141/141, 실문서 표본 오탐 0. 공무원 결재란 이름은 정보공개 관행상 공개 대상이라 기본값에는 넣지 않았습니다.
231
-
232
- ## v4.14.3 변경사항
233
-
234
- 읽기 품질을 전면 점검했습니다. 코퍼스를 넓히고(rhwp 표본 1,351파일·정책브리핑 hwpx+pdf 짝 200쌍) 포맷마다 정답지 벤치를 다시 세운 뒤 표·PDF·OCR·개인정보 마스킹을 고쳤고, 검색 색인처럼 글자만 필요한 호출자를 위한 두 가지를 더했습니다.
235
-
236
- - **📊 PDF 표 복원**: 쪽을 넘는 표를 잇고, 표 안의 표를 틀 칸 안에 넣습니다. hwpx↔pdf 430쌍 1,784표 정확 일치 64.1% → 90.1%, 셀 F1 0.790 → 0.945.
237
- - **🧾 HWP·HWP3·HWPX 표와 주석**: HWP 표를 HWPX 와 같은 규칙으로 배치해 hwp↔hwpx 쌍 3,593표가 모두 정확히 일치합니다(0.824 → 1). HWP3 표 격자를 복원하고, HWPX 각주·미주 번호와 문서 정보(제목·지은이 등)를 읽습니다.
238
- - **👓 OCR**: 기울기를 보정하고 세로로 이어진 글 상자를 나눠 읽으며 공문서 기호(○·△·따옴표)를 되살립니다. 글자 오류율 0.201 → 0.171, 3° 기울어진 스캔 57.6% → 22.0%.
239
- - **🕶️ 개인정보 마스킹**: 파일 안 모든 글자 저장소(머리말·각주·글상자·필드·미리보기·문서 정보 등)를 같은 길이로 가리고 결과를 다시 훑습니다. 5,081문서 오탐 387 → 0.
240
- - **💲 본문의 `$` 가 수식이 되지 않습니다**: 한글 문서(HWPX·HWP·HWP3)의 리터럴 `$` 는 `\$` 로 내고 `$…$` 는 수식에만 씁니다. "echo $HOME $PATH" 같은 글이 마크다운 렌더러에서 수식으로 바뀌던 것. 역슬래시 뒤 구두점("C:\.Pls")에서 역슬래시가 사라지던 것도 고쳤습니다.
241
- - **📝 미기입 누름틀 안내문을 빼고 읽습니다**: 한컴이 화면에만 흐리게 보이고 인쇄하지 않는 HWPX 안내문을 마크다운에서 뺍니다(블록 글에는 남김). 누름틀을 채우면 수정 표시를 켭니다.
242
- - **🖼️ 이미지 끄기** (`images: false` / `--no-images`): 결과에 이미지 바이트를 싣지 않습니다. 그림 자리 표시(`![image](…)`)와 본문 글자는 그대로라 그림 위치는 알 수 있습니다. PDF 는 PNG 인코딩을 건너뜁니다. 그림 많은 PDF 실측: JSON 출력 200MB → 9.5MB, CPU 시간 30% 감소.
243
- - **🧹 글 위주 출력** (`plain: true` / `--plain`): 그림 자리 표시·링크 URL·밑줄(`<u>`)·굵게(`**`) 표기를 빼고 제목·목록·표 구조와 글만 냅니다. 이미지 파일을 저장하지 않는 색인·RAG 용.
244
- - **🧾 HTML 표** (`htmlTables: true` / `--html-tables`): 파이프 표까지 모든 표를 HTML 로, 태그마다 한 줄씩 들여써 냅니다(첫 행 `<th>`). HTML 표만 다루는 소비자용.
245
- - **🚀 상주 파싱 워커** (`kordoc parse-worker`): 프로세스를 띄워 둔 채 stdin 요청 한 줄마다 한 줄 JSON 을 돌려줍니다. 파일마다 node 를 새로 띄우지 않아 대량 색인에 알맞습니다. 응답에 메모리 사용량(`rss`)을 실어 호스트가 워커 교체 시점을 정할 수 있습니다.
246
-
247
- ## v4.14.2 변경사항
248
-
249
- 공문서 출력을 한글 2024 실렌더 PDF로 다시 대조해 "진짜 공문처럼" 다듬었습니다.
250
-
251
- - **📐 항목 둘째 줄이 첫 줄 내용과 정확히 맞습니다**: 부호(□ ○ - ※ 1. 가.) 뒤를 공백이 아니라 탭으로 두고, 한글의 "내어쓰기용 자동 탭"을 씁니다. 양쪽 정렬이 부호 뒤 공백을 늘려 첫 줄 내용이 밀리던 것, 자간을 줄인 줄에서 내용이 당겨지던 것이 구조적으로 없어졌습니다. 기안문·보고서·계획서·통지·회의록·업무보고·개조식·보도자료 전부.
252
- - **✂️ 낱말이 줄 끝에서 쪼개지지 않습니다**: 본문을 어절 단위 줄바꿈으로 바꾸고("동대/문" → "동대문") 외톨이줄 보호를 켰습니다. 날짜·금액은 묶음 빈칸으로 묶어 "2026. 1. / 22." 처럼 갈라지지 않습니다. 곧은따옴표는 ‘ ’ “ ”로 바꿉니다.
253
- - **📏 한글 글꼴 실폭으로 조판을 재현합니다**: 한컴돋움·HY견고딕·휴먼명조 등 8종의 실제 글자 폭으로 줄바꿈을 계산합니다(한글 2024 PDF 154줄 전부 일치). 마지막 줄이 짧으면(줄 폭 절반 이하) 자간·장평을 최대 15%까지 줄여 한 줄 올리고, 한 줄에 맞춘다는 판단엔 2% 여유를 둬 한글에서 넘치지 않게 했습니다. 공백 없는 긴 목록("구(강남·강서·…·종로)가")처럼 한 줄보다 긴 어절이 있는 문단은 그 문단만 글자 단위로 조판하되 가운뎃점·쉼표 뒤에서만 끊어, 앞 줄이 크게 벌어지지 않습니다.
254
- - **🧩 PPTX 감지** (#80, PR #81 @LimePencil): PPTX 를 넣으면 "HWPX 섹션 없음" 대신 지원하지 않는 형식이라고 알려 줍니다.
255
- - **🧹 기술부채**: 1,200줄 넘던 `mcp.ts`·`cli.ts`·`hwp5/parser.ts`·`svg-render.ts` 를 기능별 모듈로 나눴고, 파서가 묶음 빈칸을 버리던 것과 `kordoc render` 미리보기의 탭 폭·줄 끝 공백 정렬을 고쳤습니다.
256
-
257
- ## v4.14.0 변경사항
258
-
259
- - **🏛️ 중앙부처 업무보고 프리셋 `업무보고`**: 재정경제부 「2차 업무보고 서면보고자료」(2026. 7. 15., 17쪽)를 글꼴·크기·색·도형 좌표까지 전수 실측해 국회 서면보고형 골격을 그대로 냅니다 — 표지(대외주의 박스 `--cover-label`, 파란 바 두 줄), 목차 박스, `##` Ⅰ. 장 띠(그라데이션+파랑 이중선, 장마다 새 쪽), `###` 파란 숫자칸 절, `####` 남색 소제목 박스, `#####` ① 하늘색 항목 띠, `> ▪ …` 연노랑 성과 요약박스, `## 별첨 …` 별첨 띠. 본문 함초롬바탕 15·각주 맑은 고딕 12·표 맑은 고딕 12, **□ 뒤 `(키워드)`·`[키워드]` 는 파랑 굵게**(ㅇ 뒤는 검정 굵게), ❶⇒↳ 부호 보존. `kordoc generate 보고.md --preset 업무보고 --date "2026. 7. 15." --cover-label 대외주의`. 한글 COM 실렌더로 원본과 대조했습니다.
260
- - **🎨 표 셀 테두리에 색·굵기 직접 지정**(파랑 이중선·하늘색 0.4mm 등)과 LINEAR 그라데이션 — 3색 그라데이션은 한글에서 검게 칠해져 2색으로 고정.
261
-
262
- ## v4.13.1 변경사항
263
-
264
- - **🖼️ HWP(5.x)도 조판 그대로 렌더**: `kordoc render 문서.hwp --format png -d ./pages`, `renderDocument("문서.hwp", …)`, MCP `render_document`·`crop_regions` 가 `.hwp` 를 받습니다. 한컴이 저장한 HWP5 의 조판 캐시(줄 좌표·셀 격자·개체 앵커)를 HWPX 와 같은 렌더러로 그리므로, 같은 문서를 hwp/hwpx 로 각각 저장한 10쌍에서 페이지 수·표 위치·괘선 굵기가 HWPX 렌더와 일치합니다. 표 영역 id 는 문서 순번(`t1`, `t2`…)이고 파서 결과의 `sourceId` 와 같아 `kordoc tables 문서.hwp --visual all -d ./tables` 도 HWP 에서 조직도 crop 까지 됩니다. 머리말·꼬리말·수식은 HWPX 와 같이 미렌더.
265
- - **🧭 MCP `extract_tables`**: 표 분류(데이터표/조직도류/불확실) + 페이지·bbox + crop 을 MCP 에서도. 응답에 crop 8장까지, `output_dir` 에 tables.json.
266
- - **🖨️ MCP `render_document` 포맷 확장**: `format: jpeg|html|pdf` 추가(html/pdf/svg 는 `output_path` 로 저장), `max_width_px`.
267
- - **🎨 띠 제목 색**: `--band-color #DFE6F7 --band-text-color #000000`(교육청형 밝은 띠). 기본은 실측 최다 남색 #003366·흰 글자.
268
- - **📏 개조식 문단 간격 재검증**: 실결재 428건을 `hp:case`(한컴 실렌더 값) 기준으로 다시 재어 □·ㅇ·- 문단 위 간격 0 이 84~96% 임을 확인, 엔진 값 유지. `docs/gongmunseo-reference.md` 2.8 에 □ 앞 간격 10pt 근거 추가.
269
- - **🔧 crop 이 표의 좌상단 1/4 만 잘리던 결함 수정**: sharp 최신판이 pt 단위 SVG 의 배율을 제곱으로 적용해 래스터가 보고값의 2배로 나오던 것 — 이제 실제 픽셀 크기로 환산하고 `render_document` 의 PNG 도 `max_width_px` 를 지킵니다 (v4.13.0 HWPX `crop_regions`·`kordoc crop` 도 같은 결함이었음).
270
-
271
- ## v4.13.0 변경사항
272
-
273
- 공문서 생성 엔진을 다시 설계했습니다(v5). 서울 정보소통광장 결재문서 **629건을 전수 실측**해 위계·글꼴·들여쓰기·골격을 스킴 하나에 박고, 기안문·보고서·계획서·통지·회의록 생성을 새 엔진으로 갈아탔습니다. 어떤 형태의 마크다운(`#/##/###`, 리스트, □ ㅇ - 1. 가. 명시 부호)을 던져도 같은 위계로 나옵니다.
274
-
275
- - **📐 위계 = 실결재 그대로**: 개조식형 □ HY견고딕 17pt 굵게(0타) → ㅇ 한컴돋움 15pt 굵게(1타) → - 휴먼명조 14pt(3타) → ㆍ, ※ 한컴돋움 14. 법정형 굴림체 12pt 160%, `1.` 0타 → `가.` 2타 → `1)` 4타(2타 계단), 내어쓰기는 부호폭+1타. 표는 한컴돋움 12(헤더 굵게·#DFE6F7). 근거는 `docs/gongmunseo-reference.md` 2.8.
276
- - **📏 □·제목은 절대 두 줄로 안 꺾입니다**: 장평 → 자간 → 글자 크기 순으로 줄여 한 줄에 담고(실측 관행), 그래도 안 되면 경고합니다. 표 셀도 12→11→10pt 자동 축소.
277
- - **📋 기안문 두문표·결문표**: 실결재 표 그대로 — 슬로건·기관명(굴림 20 굵게 자간띄움)·수신/(경유)/제목, 발신명의·회색 띠·결재선(직위/성명/일자)·협조자·시행/접수·주소·전화/전송/이메일/공개구분. 수신이 '내부결재'면 발신명의 생략. `--doc-head "org=광진구,slogan=…,to=…,title=…"` `--doc-foot "sender=…,drafter=주무관 홍길동,…,zip=…"`.
278
- - **📑 보고서(서울 실무 보고서형)**: 제목표(HY헤드라인M 25 굵게, 상 0.4mm/하 0.15mm) + 담당자 행(`--report-info`) + 요약박스(`--summary` 또는 제목 직후 `>`) + `Ⅰ. Ⅱ.` 장(h2) + □/ㅇ/- 본문. `--cover --dept 스마트도시과 --doc-info "docNum=…"` 로 표지(문서정보표·결재선·파랑 띠 제목·기관명).
279
- - **✍️ 실무 디테일**: `출처:`/`자료:`/`근거:` 줄은 자동으로 ※ 참고(13pt 작은 글씨), 둘째 줄이 한 줄의 20% 이내로 삐져나온 항목은 자간을 줄여(Shift+Alt+N 관행) 한 줄에 담고, 줄바꿈은 실결재 76%가 쓰는 글자 단위+양쪽정렬(어절 유지는 긴 어절 앞 줄이 벌어져 기각, 영문·숫자는 단어 유지), 법령 코드 `(282791)`·KOSIS 표 ID `DT_…`·"○○ MCP 조회" 같은 내부 식별자는 자동 제거. 보고서는 제목 직후 인용문(`>`) 한 문장("…하고자 함", 부호 없이 3줄 이내)이 요약박스가 됩니다(없거나 넘치면 경고).
280
- - **⚙️ 기본값 변경**: 기안문 본문 굴림체 12(함초롬바탕은 `--font myeongjo`), 보고서·계획서 여백 13/13/18/18·줄간격 180%·2단계 부호 ㅇ(중앙부처 개조식만 ○), h2는 장(`--h2-marker roman|number|box|none`). 개조식(`개조식` 프리셋)·보도자료는 종전 그대로.
281
- - **🏷️ 장 제목 띠 표**: 보고서·계획서의 `## 장 제목`이 로마자 채움 칸 + 제목 칸(밑줄) 표로 나옵니다(계획서 실결재 37~39% 관행). 종전 `Ⅰ. 제목` 텍스트는 `--h2-marker roman`. 맥 한컴에서 글꼴 칸이 비고 스타일이 영문명으로 뜨던 산출물 결함도 수정(7개 언어 글꼴 슬롯 통일·`langID`).
282
- - **📊 표 열폭은 중요도 순**: `비고`·`근거`·`참고`·`담당`·`연락처` 열과 내용 열이 있는 표의 마지막 열은 25% 이내로 좁게(실결재 462표에서 비고 열 중앙값 21%), `내용`·`사항`·`실적`·`계획`류 열은 넓게(중앙값 51%) 왼쪽 정렬. 긴 근거 텍스트가 최광열을 차지하고 내용이 두 줄로 꺾이던 결함. 요약박스는 문단 좌우 여백 1000(실측 55%)으로 글자가 테두리에 붙지 않습니다.
283
- - **📊 코퍼스**: `bench/corpus-gen/opengov-2609/` 7종 필터 280건 추가(계획·결과보고·방침·안내·회의·2024·2022).
284
- - **💰 금액 한글 병기 자동 계산**: `금113,560원` 처럼 한글 병기가 빠진 금액은 경고와 함께 `금113,560원(금일십일만삼천오백육십원)` 실제 값을 제안합니다(규정 시행규칙 제2조). 하이픈 날짜 `2026-07-18` 도 잡아서 `2026. 7. 18.` 로 바꿔 줍니다(URL·파일명·표 셀은 제외).
285
- - **🔣 항목 부호 확장**: `❑ ❏ ㅁ`(대항목)·`◎`(중항목)·`ㅡ ‣ ▪ ▫`(세부) 도 □/ㅇ/- 와 같은 위계로 읽습니다.
286
- - **🖼️ 렌더 통합 계층 (#75)**: `renderDocument(파일, { format: "svg"|"html"|"png"|"jpeg"|"pdf", pages: "2-4" })` 하나로 페이지별 SVG·자급자족 레이아웃 HTML·페이지별 PNG/JPEG·PDF(Chromium 필요)를 냅니다. 렌더 결과에는 문단·표·이미지·도형의 **페이지 로컬 pt bbox 와 결정적 id**(`RenderScene.regions`, SVG `data-kordoc-id`)가 붙고, `extractRenderedRegions()` / `kordoc crop 문서.hwpx --target table -d ./regions` 로 그 영역을 실배율로 잘라냅니다. `kordoc render --format png --pages 2-4 -d ./pages`, MCP `render_document` 의 `pages`, `crop_regions`. HWP5 렌더는 후속.
287
- - **🧭 표 분류·시각 추출 (#76)**: `parse(파일, { classifyTables: true })` 로 표마다 `semantic-table`(데이터표) / `non-tabular-layout`(표를 캔버스로 쓴 조직도·연락망) / `uncertain` 분류가 붙습니다(휴리스틱, 네트워크·LLM 없음, 중첩표 독립 분류). `extractTables(파일)` / `kordoc tables 문서.hwpx --visual non-tabular-and-uncertain -d ./tables` 는 분류 + 렌더 영역 + crop 을 한 번에 — 조직도는 이미지로, 데이터표는 구조로 넘기는 멀티모달 파이프라인용. 병합 없는 단순 표의 셀 이미지가 마크다운에서 사라지던 결함도 함께 수정.
288
-
289
- ## v4.12.3 변경사항
290
-
291
- 공문서 작성 쪽 라운드. 실결재 기안문 206건·보고서 337건의 항목부호 단계별 글꼴을 실측해 **위계 폰트 옵션**을 넣고, 표기법 검수의 쌍점·날짜 룰 오탐을 법정 서식 근거로 좁혔습니다. 별표 코퍼스 3차(183쌍)를 더해 게이트 모수는 hwpx ~1,390·pdf ~950·hwp쌍 ~860 입니다.
292
-
293
- - **🔤 위계 폰트 `levels`**: 항목부호 단계(0~7)마다 글꼴·크기·굵기를 지정합니다 — `--levels "0=HY견고딕/17/bold,1=한컴돋움/15/bold,2=휴먼명조/14"`, MCP `levels`. 실측: 법정 8단계(1. 가. 1))는 본문과 동일이 90% 라 기본값은 그대로이고, 전자결재 기안문의 □/ㅇ/- 계열은 □ HY견고딕 +2~3pt 굵게 · ㅇ 한컴돋움 굵게 · - 휴먼명조가 지배 관행입니다(`docs/gongmunseo-reference.md` 2.7). 내어쓰기 폭도 그 단계 글자 크기로 맞춥니다. 미지정이면 산출물 불변.
294
- - **📝 표기법 룰 좁힘**: `COLON_SPACE` 는 표 셀을 건너뜁니다 — 법정 별지서식 자체가 "성 명 :" 라벨 셀을 쓰므로(서식 595건의 표 셀 801건 전부 라벨) 규칙 대상이 아닙니다. 본문의 "일 시 : 값"(기안문 206건 중 100건, 414건)은 편람상 위반이라 그대로 경고합니다. HTML 태그·마크다운 강조 닫힘 뒤 쌍점("성명:</td>", "과정:** 붙임") 오탐도 제거. `DATE_NO_SPACE` 는 법제처 연혁 표기(`<개정 2012.2.14>`·`삭제 <2016.2.29.>`·`[시행일:2017.9.8.]`)를 제외합니다(서식 156건 → 10건). 기안문의 문서번호 인용 "○○과-123(2026.7.1.)호"(103건)는 편람 날짜 표기 위반이라 유지.
295
- - **📄 PDF 가운뎃점**: 한컴 PDF 가 가운뎃점(ㆍ)을 조합형 아래아(U+119E)로 내던 것을 되돌립니다(별지서식 573쌍 실측 340자·41문서).
296
- - **📄 PDF 1×1 표 줄 보존**: 선서문 안쪽 상자처럼 중첩표 없는 1×1 표는 줄마다 문단으로 내어 "선 서 나는 헌법을 …" 처럼 제목 줄이 본문에 붙지 않습니다(표 셀 줄바꿈 보존 정책과 정합).
297
- - **📄 PDF 제목 아래 틀**: 테두리 없는 1칸 틀(별표 1×1 프레임·선서문 바깥 틀)을 "페이지 반복 클립" 으로 가르려던 가설은 반증됐고(본문 영역 클립은 쪽마다 좌표가 다르고 별표는 1쪽), 대신 **제목 아래(윗변 ≥ 20%)·본문 폭(≥ 60%)·위에 글이 있는** 컨테이너를 틀로 봅니다. 채용공고 2단 상자는 폭 조건으로 제외(pdf-table-gt 게이트 유지).
298
- - **🖋️ HWP 결재 기호 하나 더**: 한컴 PDF 실렌더로 확인한 네모 안 "인"(U+F00E1, HWP5 A0E1)을 "(인)" 으로 냅니다. HWP5 하이픈 제어문자(0x18)는 한컴이 그리지 않으므로("60g/㎡") 더 이상 "-" 를 내지 않습니다.
299
- - **📊 별표 코퍼스 3차**: `licbyl-byl2/` 183쌍(seed 20260907). 하위 30건 분해 — 1×1 틀 PDF 미감지 8 · HWP 레이아웃 해체 vs PDF 1열 유지 15 · 구조 차 7(수식 셀·쪽 경계 셀 분할·줄바꿈, 파서 결함 없음). HWP↔PDF 셀 일치 별표 0.728 → 0.746, 3차 0.772 → 0.787, 서식 0.965 유지.
300
-
301
-
302
- ## v4.12.2 변경사항
303
-
304
- 별지서식 라운드 2. 코퍼스를 서식 279쌍·별표 90쌍 더 모아(총 1,995파일) HWP·HWPX·PDF 세 파서가 **같은 IR 모양**을 내도록 맞췄습니다. HWP↔PDF 셀 텍스트 일치 0.927 → 0.965, PDF 표 GT cellExact 0.945 → 0.980 을 실측했고 게이트 플로어를 그 수치로 올렸습니다.
305
-
306
- - **🪆 PDF 틀 안 중첩표**: 지정서·영치증·선서문처럼 1칸 틀 안에 "발신명의 | 직인" 표가 든 서식에서, PDF 가 그 표를 틀 뒤 별도 블록으로 흘리던 것을 틀 셀 안에 문단·표 원문 순서로 넣습니다(HWPX 파서와 같은 모양). 네 변에 테두리가 그려진 1칸 틀은 표로, 그 안에 홀로 든 상자는 중첩표로 살립니다. 테두리 없는 바깥 틀은 한컴 본문 영역 클립과 구분할 수 없어 표로 삼지 않습니다.
307
- - **🖋️ HWP 결재란 "(인)" 복원**: HWP 5.x 는 2바이트 문자라 한컴 전용 기호(U+F012B)를 U+A000대에 접어 저장합니다. `ꄫ` 로 새던 결재란 기호가 "(인)" 으로 나옵니다.
308
- - **📋 HWP 서식 틀 보존**: 중첩표를 품은 별지서식 3×1 틀(제목행·틀·꼬리행)을 레이아웃 표로 보고 문단으로 해체하던 것을 표로 남깁니다. 페이지 사슬 레이아웃 표(글이 많은)는 종전대로 해체합니다.
309
- - **📝 표기법 검수 정밀화**: 실결재 기안문 206건·별지서식 595건으로 오탐을 실측해 `TILDE_SPACE` 는 숫자·날짜·시각 범위에만, `DUEUM_ERROR` 는 서식 기입란(" 년도", "( 년간)")과 표 머리글을 건너뜁니다.
310
- - **🧾 양식 인식이 틀 안 중첩표까지**: `extractFormFields` 가 셀 안 중첩표의 라벨-값을 훑습니다(`fillHwpx` 와 같은 경계).
311
- - **📊 `bench/cmp-hwp-pdf.mjs`**: HWP↔PDF 셀 텍스트 Jaccard 보고 지표 정식 편입. `--linebreaks` 로 PDF 셀 줄바꿈 자리의 어절 경계 비율(85%·글자 단위 13%)을 실측해 줄 결합은 하지 않기로 했습니다.
312
-
313
- ## v4.12.1 변경사항
314
-
315
- 법령 **별지서식**(법제처 별표·서식) 파싱을 HWP·HWPX·PDF 세 포맷에서 맞췄습니다. 법제처 OpenAPI로 서식 300건의 HWP 원본과 PDF를 쌍으로 모으고 rhwp 변환 HWPX를 붙인 900파일 코퍼스를 게이트에 편입해, HWP↔PDF 셀 텍스트 일치 0.79 → 0.92, PDF 표 GT cellExact 0.73 → 0.96 을 실측했습니다.
316
-
317
- - **📐 PDF 셀 클립 그리드**: 한컴 PDF는 표 셀마다 클립 사각형을 깔고 글을 찍습니다. 별지서식 외곽 표는 테두리 "없음" 셀이라 괘선만 보는 종전 경로가 표를 놓치고 제목·기입란이 헤딩과 문단으로 흩어졌는데, 이제 클립 사각형 묶음을 **셀 기하가 확정된 표**로 바로 씁니다. 도선사면허 신청서가 HWP 파서와 같은 23열·같은 병합 구조로 나옵니다. 클립의 포함 관계로 층을 나눠, 지정서·증서의 1칸 테두리 틀은 제목행·꼬리행과 한 표의 셀이 되고 안쪽 "발신명의 | 직인" 표는 별도 표로 살립니다.
318
- - **➔ 심볼 폰트 글리프 복원**: 처리절차 화살표·체크박스를 Wingdings로 찍은 PDF에서 `è` 같은 깨진 라틴 문자가 남던 것을 유니코드(➔ □ ✔ ☑)로 되돌립니다.
319
- - **📝 표기법 검수 6룰 보강**: 금액 괄호 한글 병기(`MONEY_NO_HANGUL`), 물결표 앞뒤 붙여쓰기(`TILDE_SPACE`), 두음법칙 '년도→연도'(`DUEUM_ERROR`), 외래어 오기 40종(`LOANWORD_ERROR`, 표준 표기 제안), 차별·비하 표현 25종(`DISCRIMINATORY_TERM`, 순화어 제안), 붙임 있는데 "끝." 없음(`END_MARK_MISSING`, `kordoc lint` 전용). 시중 HWPX 공문 도구(pyhwpxlib·hwpx-skill)의 검사 항목과 대조해 비어 있던 축을 채웠습니다.
320
- - **🏛️ 내부결재 문서의 발신명의 생략**: 수신이 "내부결재"인 기안문은 결문에 발신명의를 적지 않습니다(행정업무규정 시행규칙).
321
- - **🩹 기입 빈칸 보존**: "년 월 일"·"시 분"이 "년월일"로 붙던 것, 청구서류 1열 틀 표의 기입 항목이 한 줄로 뭉개지던 것을 고쳤습니다.
322
-
323
- ## v4.12.0 변경사항
324
-
325
- v4.11.0 이 걷어낸 HWP3 유실의 **잔여 전량**을 처리했습니다. rhwp `samples/` 의 HWP3 원본 9종을 한컴 변환본과 문자 멀티셋으로 대조하면, 이제 변환본에만 있는 문자는 그림 대체텍스트를 빼면 **0** 입니다.
326
-
327
- - **🖼️ 그리기 개체 안 글상자 텍스트 회수 (#73)**: HWP3 의 ch=11(그림) 확장 블록은 `pic_type` 이 3 이면 그림이 아니라 **그리기 개체 트리**입니다. 표지 제목·순서도 라벨처럼 눈에 잘 띄는 본문이 그 안 글상자에 들어가는데, 종전에는 블록을 통째로 건너뛰어 사라졌습니다(sample16 618자, sample19 86자). 프레임 헤더 → 도형 트리(형제·자식 비트)를 훑어 글상자 문단 리스트를 본문과 같은 파서로 읽습니다. 비-글상자 도형이 '글상자로 만들기' 로 같은 블록을 실은 경우도, **잉여 길이가 스펙 표 78 과 정확히 맞을 때만** 회수합니다. 트리 해석은 확장 블록 슬라이스 안에서만 하므로 실패해도 본문 스트림 동기가 깨지지 않고 종전 동작으로 되돌아갑니다(재귀 256단계·도형 4,096개 상한).
328
- - **🔣 비가시 마커가 대체 문자로 새던 것**: 새 번호·쪽 번호 위치·쪽 감추기 제어 문자 자리에 ``(U+FFFC)를 심어 마크다운에 그대로 남았습니다. 한글도 그 자리에 아무 글자를 내지 않습니다 — 차례 표식과 같은 계약으로 맞췄습니다.
329
-
330
- ## v4.11.0 변경사항
331
-
332
- rhwp 최신 devel(v0.8.3~0.8.4)을 훑어 kordoc 의 HWP3 디코더에 반영했습니다. 네 결함 모두 **글자가 경고 없이 사라지는** 형태라, 변환은 성공했는데 본문 일부가 비어 있는 식으로만 드러납니다.
333
-
334
- - **🈳 HWP3 기호·한자 유실 (완성형 좌표 규칙 누락)**: HWP3 은 KS X 1001 기호행(0xA1~0xAC)을 행 간격 96, 한자행(0xCA~0xFD)을 행 간격 94 로 편 코드에 문자를 담습니다. rhwp 에서 johab 디코더를 포팅할 때 하드코딩 표만 옮기고 **이 규칙 두 개를 빠뜨려**, 표에 없는 코드는 전부 삭제됐습니다 — `채권(債權)조서` 가 `채권()조서` 가 되고 `※ ○ 【】 ∴ ☞ ₩ ━` 같은 기호가 통째로 사라집니다. 규칙을 이식하고, 표준 매핑과 한컴 표기가 갈리는 두 자리(`∼→~`, `⊙→◉`)를 보정했습니다.
335
- - **🔤 사적 코드 실측 표 확장**: 라틴 확장(`ü·ö·ä·ß` 가 지워져 "für"→"fr" 이 되던 것)과 rhwp #5860 의 실측 28코드를 이식하고, rhwp 표에도 없던 코드를 직접 실측해 더했습니다 — 괘선 조각 6종, 겹줄, 원문자·괄호문자 계열(`⓪①②`·`ⓐ~ⓕ`·`㉠~㉢`), 글머리표 `•` 등. 근거는 HWP3 원본과 같은 문서의 한컴 변환본을 문맥 정렬한 것으로, **코드 출현 횟수가 변환본의 해당 문자 개수와 정확히 맞는 것만** 실었습니다.
336
- - **✒️ 초성 없는 아래아 음절 유실**: 초성이 '채움'인 아래아 음절(`ㆍ` 단독)을 무효 초성으로 보고 버렸습니다. 한컴 변환본과 같이 자모 하나로 남깁니다.
337
- - **🧱 서식 run 이 PUA 정리를 건너뛰던 결함 (전 포맷)**: 굵게·밑줄이 걸린 문단은 span 경로를 타는데 여기서만 PUA 치환·제거를 하지 않아, 한컴 전용 글머리표·괘선 조각이 **원시 PUA(두부 문자) 그대로** 마크다운에 실렸습니다. HWP5·HWPX·HWP3 공통 경로입니다. 함께 rhwp 의 한컴 PUA 검증표(한컴 PDF 대조 확정)에서 빠져 있던 22종(`┌┬┐└┘│`·`┈═━`·`⓪①②④~⑨`·`✺◇▸□`·`⊞⊟`)을 동기화했습니다.
338
-
339
- 검증은 rhwp `samples/` 의 HWP3 원본 9종을 같은 문서의 한컴 변환본과 문자 멀티셋으로 대조했습니다. 변환본에만 있는 문자(=HWP3 경로에서 잃은 문자)가 `hwp3-sample11` 189→0, `hwp3-sample16` 1,139→618, `hwp3-sample10` 172→11 로 줄었습니다. 남은 618자는 **그리기 개체 안 글상자**(표지 제목·순서도 라벨)로, kordoc 은 아직 그리기 개체를 건너뜁니다.
340
-
341
- ## v4.10.0 변경사항
342
-
343
- - **📡 실패 계약 전 포맷 확장 (#69)**: 실패 JSON(`success:false` + `code`)이 `--format json` 전용에서 **markdown·chunks 포함 전 포맷**으로 확장됐습니다. 헤드리스 파이프라인이 stderr 한국어 문구 파싱 없이 원인 코드로 분기할 수 있습니다. `code` 값 집합·종료 코드 규칙·안정성 보증을 README [실패 계약](#실패-계약--기계-판독-가능한-실패-json-v4100-69) 표로 문서화했습니다. (@sorbetsharkroundhand 제보)
344
- - **🖼️ `images/<문서 이름>/manifest.json` (#70·#98)**: 이미지 저장 시 `[ { name, mimeType, bytes, source } ]` manifest 를 함께 방출합니다. `mimeType` 은 매직바이트 실측 우선이라 확장자 추측이 필요 없습니다. DOCX 이미지의 미지 확장자를 `image/png` 로 단정하던 오폴백도 실측 판별로 교체. (@sorbetsharkroundhand 제보)
345
- - **🩹 patchHwp soft-wrap 축소 치환 무결성 (#71)**: 자동 줄바꿈으로 LINE_SEG 가 2세그먼트 이상인 문단을 더 짧은 한 줄로 치환하면 잔존 세그먼트의 textpos 가 새 nChars 밖을 가리켜 **한컴이 "문서 손상/변조" 경고로 열기를 거부**하던 버그 수정 — 범위를 벗어난 꼬리 세그먼트만 잘라내고 lineSegCount 를 정합화합니다(유효 세그먼트는 보존해 soft-wrap 렌더 유지). 재작성 압축 스트림에 한컴 실저장본의 8바이트 꼬리(CRC32+비압축 크기, 코퍼스 실측 7/7)도 복원합니다. (@heesun-woodi 제보)
346
-
347
- ## v4.9.2 변경사항
348
-
349
- `<right>` 출처행이 두 줄을 넘어가는 실제 보고서를 한글로 열어 확인하고 고쳤습니다.
350
-
351
- - **📌 다줄 출처행 왼쪽정렬 폴백**: `<right>` 문단(출처행·발신일자)이 본문폭을 넘겨 줄바꿈되면 오른쪽정렬 특성상 **왼쪽 끝이 줄마다 들쭉날쭉**해져 들여쓰기가 깨진 듯 보였습니다(한글 실렌더 QA — 3줄 출처행). 이제 조판 폭을 미리 계산해 한 줄에 들어가면 종전대로 우측 배치(실측 GT6/GT7/GT11 관행), 넘치면 왼쪽정렬로 방출합니다.
352
- - **🔤 출처행 캡션체**: `출처:`·`자료:`로 시작하는 `<right>` 행은 본문과 같은 크기라 캡션으로 읽히지 않았습니다. 전용 charPr을 신설해 **본문보다 3pt 작게**(하한 10pt), 실측 프리셋(개조식·보고서·계획서)은 한양중고딕, 그 외 프리셋은 본문 폰트로 방출합니다. 표 아래 출처를 본문과 구분하는 실무 관행을 따른 것으로, 발신일자 등 다른 `<right>` 행은 본문 크기를 유지합니다.
353
-
354
- ## v4.9.1 변경사항
355
-
356
- 실제 자치구 검토보고서를 뽑아 한컴으로 열어보고, 눈에 걸리는 것을 코퍼스 555건 실측과 대조해 고쳤습니다. 세 결함 모두 **양식 파일 하나의 저장값을 실무 관행보다 앞세운 것**이 원인이었습니다.
357
-
358
- - **📐 `○` 항목 사이 간격 정상화**: 문단 위 간격 기본값이 `□15pt/○10pt/-6pt`(「2_보고서 양식」 한 파일의 저장값)였는데, 코퍼스 실측은 정반대입니다 — `□` 582문단 중 **88%**, `○` 799문단 중 **88%**가 간격 **0**이고 종전 값은 상위 6위에도 없습니다(2위가 1.5pt). 줄간격 160%에 10pt가 더해져 항목 사이가 1.4줄로 벌어지던 것을 실측 최빈값으로 되돌렸습니다(`□`만 절 구분용으로 실측 2위인 1.5pt 유지).
359
- - **📑 `□` 아래 `가./나.` 소제목 들여쓰기**: h3·h4 문단에 왼쪽 여백이 없어, `□`가 내어쓰기로 부호를 왼쪽에 내미는 동안 소제목이 대항목보다 **왼쪽**에 놓이며 계층이 사라졌습니다(실렌더 확인). `□` 부호폭만큼 들여써 대항목 제목 글자와 맞춥니다.
360
- - **📎 붙임 항목 번호 유실 수정**: `붙임 1. …` 다음 줄의 들여쓴 `2. …`가 마크다운 리스트로 잡혀 항목부호(`ㆍ`)로 치환되며 **번호가 사라지던** 결함을 고쳤습니다. 붙임 전용 블록으로 처리해 실측 관행을 따릅니다 — 앞에 빈 줄 한 줄(실측 343건 중 95%), 번호 `1. 2. 3.` 유지(부호 치환 실측 0건), 둘째 항목 선행 공백 보존으로 첫 번호와 세로 정렬(실측 123건 중 122건).
361
- - **✍️ 개조식 문체 검수 (`kordoc lint --munche`)**: 표기법과 축이 다른 **문체** 12룰을 추가했습니다 — 서술형 `~다` 종결(실측 0%), `~해야 한다` 당위, `A가 아니라 B(추상어)` 수사(실측 0건), 물음표·느낌표(본문 0건), 대구 슬로건, 항목 70자·결론 60자 초과, 리드문 `~하고자 함.`. 보고서·계획서·개조식 프리셋의 `generate` 경고 채널에 자동으로 붙고(생성은 막지 않음), `kordoc lint` 에서는 `--munche` 로 옵트인입니다. 기안문(경어 종결)·통지·회의록·보도자료는 문체 관행이 달라 적용하지 않습니다. 실측 근거와 규칙표는 [`docs/gaejosik-munche.md`](docs/gaejosik-munche.md). 통계 출처는 [jkf87/hwpx-skill](https://github.com/jkf87/hwpx-skill) v1.17.0 의 실무부서 보고서 15건(2,288줄) 실측입니다.
362
-
363
- ## v4.9.0 변경사항
364
-
365
- - **🧹 공문서 AI 슬롭 검수 (lint `AI_*` 룰)**: 생성형 AI 초안이 공문서로 흘러들 때 남는 문체 흔적을 표기법 검수에 추가했습니다 — 줄표 사용(`AI_EM_DASH`: — – ―), 한 줄 강조(볼드) 3회 이상(`AI_BOLD_OVERUSE`). `kordoc lint` 와 `generate` 경고 채널에 공통 적용되는 조언용 warning 이며, 편람 13룰 원전과 구분되는 kordoc 자체 룰입니다. 실제 자치구 검토보고서를 공문서 HWPX 로 뽑는 작업에서 역도출했습니다. 작성 지침은 `.claude/skills/gongmunseo/`(줄표→쉼표·괄호, 강조는 핵심 한 곳, 외래어→행정용어)에 함께 반영.
366
- - **📄 렌더러가 명시적 쪽 나눔(`pageBreak="1"`)을 존중**: 한컴 '쪽 나누기'로 저장되는 `<hp:p pageBreak="1">` 속성을 reflow 렌더가 무시하고 이어 그리던 것을 수정 — 이제 해당 문단부터 새 페이지로 조판합니다. 개조식 프리셋의 장 배너, 후처리로 쪽을 나눈 생성본이 한컴과 같은 페이지 경계로 렌더됩니다.
367
-
368
- ## v4.8.0 변경사항
369
-
370
- - **📑 `--format json` 에 페이지별 마크다운 (#68)**: JSON 결과에 `pages: [{ pageNumber, markdown }]` 이 기본으로 들어갑니다. v4.7.3 의 실제 페이지 경계(`pageMode: "layout"`)를 그대로 써서, RAG 인용 각주나 뷰어 페이지 동기화에 쪽 단위 본문을 바로 쓸 수 있습니다. 문서를 다시 파싱하지 않고 이미 만든 블록의 `pageNumber` 로 가르기 때문에 비용은 파싱 시간의 1% 미만입니다(24쪽 PDF 기준 parse 399ms / pages 2.5ms). `--pages 3-5` 를 쓰면 `pages` 에도 3~5쪽만 담기고, 페이지 번호를 매기지 않는 포맷(DOCX 등)에서는 `pages` 자체가 없습니다. 라이브러리에서는 `blocksToPages(blocks)` 로도 같은 값을 얻습니다. (@sorbetsharkroundhand 제안)
371
-
372
- ```jsonc
373
- {
374
- "markdown": "# 전체 문서…",
375
- "pages": [
376
- { "pageNumber": 1, "markdown": "# 1페이지…" },
377
- { "pageNumber": 2, "markdown": "2페이지…" }
378
- ],
379
- "blocks": [/* … */],
380
- "metadata": { "pageCount": 2, "pageMode": "layout" }
381
- }
382
- ```
383
-
384
- 여러 페이지에 걸친 표는 현 IR 구조상 시작 페이지의 한 블록이라, 표가 이어지는 중간 페이지의 `markdown` 은 빈 문자열입니다(항목 자체는 남깁니다 — 배열 길이 = 쪽수).
385
-
386
- ## v4.7.3 변경사항
387
-
388
- - **📄 HWP/HWPX 실제 페이지 경계 복원 (#66)**: 한컴 저장본의 조판 캐시(HWPX `linesegarray` / HWP5 `PARA_LINE_SEG`)로 실제 페이지를 복원합니다 — `pageCount`·블록별 `pageNumber` 가 섹션 근사가 아닌 **실제 쪽 번호**가 되고, `parse(buffer, { pages: "3-5" })` 가 실제 3~5쪽을 반환합니다. 신호 4종(vertpos 역행·명시 쪽나눔·분할 표의 셀 흐름 리셋·분할 직후 이중 카운트 억제)을 결합해 실코퍼스 hwp↔hwpx↔pdf 쌍 대조에서 페이지 수 전수 일치. 조판 캐시가 없는 생성 파일은 종전대로 섹션 근사로 동작하며, 새 메타데이터 **`pageMode: "layout" | "section"`** 으로 구분됩니다(근사 상태에서 `pages` 필터 사용 시 `PAGE_BOUNDARY_APPROXIMATE` 경고). RAG 페이지 인용·뷰어 동기화용. (@sorbetsharkroundhand 제안)
389
-
390
- ## v4.7.2 변경사항
391
-
392
- - **🔒 폐쇄망(내부망) 배포 대응**: `KORDOC_OFFLINE=1` 로 모든 아웃바운드 통신(OCR 모델 다운로드·watch webhook)을 요청 발신 전에 차단하고, `KORDOC_ROOT=<디렉토리>` 로 MCP 서버의 파일 읽기·쓰기를 해당 디렉토리 하위로 제한합니다(둘 다 opt-in — 미설정 시 기존 동작 그대로). 인터넷 없는 망에 반입하는 오프라인 설치 번들은 `node scripts/pack-offline.mjs [--with-ocr] [--with-models]` 로 만들고, OCR 모델은 `kordoc models --export/--import` 로 SHA-256 검증과 함께 옮깁니다. 절차와 보안성 검토용 근거는 [docs/offline-deployment.md](docs/offline-deployment.md) 참조.
393
- - **🧹 의존성 취약점 정리**: `npm audit --omit=dev` 0건 (sharp ^0.35.0, adm-zip ^0.6.0 override, MCP SDK 체인 권고 해소).
394
-
395
- ## v4.7.1 변경사항
396
-
397
- - **✏️ 밑줄 보존 전 포맷 확장**: v4.7.0 이 PDF 에서 복원한 밑줄을 HWPX·HWP5 에서도 `<u>…</u>` 로 방출합니다. 한컴이 밑줄 없는 글자에도 `type="NONE"` 을 넣는 특성 때문에 존재 여부가 아니라 밑줄 **종류**로 판별합니다.
398
- - **🔗 PDF 링크 어노테이션 추출**: `/Annots` 의 Link(`/URI`)를 텍스트와 상관해 `[text](url)` 로 방출합니다 (스킴 화이트리스트 살균).
399
-
400
- ## v4.7.0 변경사항
401
-
402
- - **✏️ PDF 밑줄 감지**: baseline 바로 아래 밀착한 얇은 수평선을 텍스트와 상관해 `<u>…</u>` 로 보존합니다. PDF 에는 밑줄 폰트 플래그가 없어 종전 파서는 이를 전부 버렸습니다. 표 괘선·배지 오탐 방어 5겹. 코퍼스 48문서 중 32문서에서 강조 밑줄 복원.
403
-
404
- ## v4.6.1 변경사항
405
-
406
- - **📰 2단 지면 읽기 순서 수리 (#64)**: 시험지·잡지처럼 좌우 2단으로 조판된 PDF에서 좌·우 단이 행 단위로 섞여 나오던 문제. 프로즈 특성(정렬·긴 줄)에 기대지 않는 기하 전용 2단 검출을 신설해, 전폭 요소(머리글·쪽번호)를 경계로 좌단 전체 → 우단 전체 순서로 복원합니다 — 기본 모드·`--no-tables`·OCR 경로 모두. 2026학년도 수능 문제지 실측: `--no-tables` 기준 물리학Ⅰ·생활과윤리 20/20 문항 순서 역전 0회(종전 13문항/역전 3회), 국어 44/45 역전 0회. 일반 문서 오발화는 행 구조 가드 3종으로 차단(코퍼스 커버리지 게이트 베이스라인 동등). (@choa712 블록 덤프 제보)
407
-
408
- ## v4.6.0 변경사항
409
-
410
- - **🚫 PDF 표 감지 opt-out (#64)**: `--no-tables`(CLI) / `tables: false`(API·MCP) — 시각적 테두리 박스(2단 시험지의 안내문·보기 상자)가 선 기반 표로 잡혀 주변 본문이 셀로 끌려가고 읽기 순서가 뒤집히던 문서에서, 표 감지를 통째로 끄고 자연 읽기순 텍스트만 뽑습니다. 기본값은 종전 그대로 표 감지 켬. (@choa712 제보)
411
- - **🖼️ 이미지 다량 문서의 JSON 출력 수리 (#65)**: 이미지가 수백 장인 HWP는 base64 총량이 문자열 한계를 넘어 `--format json`이 **비-JSON**으로 끝나고 실패 계약도 안 나왔습니다. 이제 `--image-refs`로 이미지 바이트 대신 저장 경로만 남길 수 있고, 한계를 넘으면 자동으로 참조 모드로 강등하며, 출력 단계 예외도 원인 코드(`OUTPUT_TOO_LARGE` 등)가 담긴 실패 JSON으로 나옵니다. (@choa712 제보)
412
- - **🔤 중첩 인라인 강조 수리 (#61)**: `**굵게 *기울임* 다시**` 같은 중첩 강조가 별 리터럴 노출·굵게 소실·서식 역전으로 깨지던 문제 — LLM이 쓴 마크다운에 흔한 패턴이라 `generate_document` 경로 노출이 컸습니다. (@LeeYudok 제보 + PR #62)
413
- - **📄 선두 BOM XML 파트 수리 (#63)**: 일부 OpenXML 라이터가 `[Content_Types].xml`·`.rels` 앞에 붙이는 UTF-8 BOM 때문에 xlsx·docx가 통째로 `PARSE_ERROR`로 거부되던 문제(엑셀·리브레오피스는 정상 열림). (@soyesenna 제보)
414
-
415
- ## v4.5.0 변경사항
416
-
417
- - **📄 페이지 옵션 신설**: 용지 크기(A4·A3·B4·B5·Letter·커스텀 mm)·**가로 방향**·**다단**(1~8단)·**머리말/꼬리말**을 생성 옵션으로 지원합니다 — API `page`, CLI `--paper/--landscape/--columns/--header/--footer`, MCP `generate_document` 동명 파라미터. 머리말/꼬리말은 실물 저장본과 동일한 `<hp:header>`/`<hp:footer>` 구조라 한글에서 그대로 조판됩니다.
418
- - **🔗 하이퍼링크 생성**: `[text](url)`이 텍스트로 뭉개지지 않고 실측 6-param `HYPERLINK` 필드로 방출됩니다 — 한글에서 실제 클릭 가능, 재파싱하면 `[text](url)` 복원(왕복 보존). 표 셀 안 링크 포함.
419
- - **📎 각주 개체 생성**: `[^1]` 마커 + `[^1]: 본문` 정의가 진짜 `<hp:footNote>` 개체로 방출돼 한글이 페이지 하단에 각주를 조판합니다.
420
- - **🖼️ 이미지 실데이터 임베드**: `images` 옵션(url→바이트)·`data:` URI·CLI/MCP `--image-dir`로 PNG/JPEG/GIF/BMP를 BinData에 실제로 넣습니다 — 픽셀 치수 자동 환산(96dpi)·본문폭 초과 시 비례 축소. 바이트가 없으면 종전처럼 참조를 placeholder로 보존합니다.
421
- - **🔍 OCR 저신뢰 폐기 관측**: 내장 OCR이 신뢰도<0.5로 조용히 버리던 라인 수를 `OCR_LOW_CONF` 경고로 노출합니다.
422
- - **📏 스캔 표 실측 채점 교정**: 표 구조가 아닌 정답(클립아트 장식 그리드·1×1 텍스트박스, 비어있지 않은 셀 <3)을 모수에서 제외하고 제외 수를 투명하게 노출 — 교정 후 스캔 표 매칭 **71.4%**·cellF1 **0.595** (문자 recall·CER은 v4.4.1 실측과 동일).
423
-
424
- ## v4.4.1 변경사항
425
-
426
- - **🖼️ MCP 이미지 입력 수리**: `parse_document` 등 파싱 도구가 PNG/JPG/WebP를 확장자 단계에서 거부하던 것(CLI는 정상, MCP만 차단)을 해제 — 스크린샷·스캔 이미지를 MCP로도 바로 변환합니다.
427
- - **📏 내장 OCR 정확도 첫 실측** (`bench/ocr-accuracy.mjs` 신설): 코퍼스 PDF 41문서 82페이지, 클린 텍스트층 대조 — 문자 recall 90.0%(글꼴 미렌더 1건 제외 94.0%)·precision 95.1%, 한글 음절 recall median 99.9%, 0.96s/페이지. 스캔 표 구조는 매칭 63%·cellF1 0.50로 아직 약한 지점입니다. 정답이 클린 렌더라 실스캔 노이즈는 미반영(상한치).
428
-
429
- ## v4.4.0 변경사항
430
-
431
- - **🔑 비밀번호 문서 열기 (#59)**: 열기 암호가 걸린 **HWPX·HWP3·HWP5** 문서를 `--password`(CLI) / `password`(API·MCP)로 엽니다. 세 포맷의 암호화 방식이 각각 다릅니다 — HWPX는 ODF 표준(AES-256-CBC + PBKDF2), HWP3는 단일 DES-ECB, HWP5는 EncryptVersion 4의 비트 단위 CFB. 틀린 비밀번호는 성공으로 위장하지 않고 `ENCRYPTED`로 실패합니다. (한컴 DRM 문서보안은 별개라 해당 없음, @jangster77 제보)
432
- - **🩹 HWP3 본문 대량 유실 수리**: 그림 뒤에 오는 캡션 문단 리스트를 읽지 않아 그림 하나당 43바이트씩 스트림이 어긋났고, 어긋난 자리가 "문단 끝" 표식으로 읽히면 **경고 없이** 나머지를 통째로 버렸습니다. 945KB 문서가 0자로 나오던 수준입니다(rhwp 대조 실측 6종 복구, 최대 100만 자). 표 셀 상한 256도 실존 문서(367셀)를 죽여 잔여 스트림 기준으로 교체했습니다.
433
- - **🔤 CDATA 텍스트 소실 수리**: `<hp:t><![CDATA[본문]]></hp:t>` 로 저장된 텍스트를 경고 없이 버리던 결함 — 공용 XML 헬퍼에 있어 HWPX·HWPML·DOCX 세 포맷에 걸쳐 있었습니다.
434
- - **🔣 사라지던 글자 복원**: 한컴 검증 PUA 11종(결재란 `(인)`, 편람 글머리표, 사각 안 숫자 ①~⑳ 등)과 HWP3 johab 매핑 2종(표 셀 글머리표 ▸, 머리말 회사명) — 매핑에 없는 코드는 삭제되는 구조라 누락이 곧 글자 증발이었습니다.
435
- - **ᄒᆞᆫ HWP3 아래아(옛한글) 복원**: 완성형에 대응 음절이 없어 통째로 빠지던 아래아 음절('ᄒᆞᆫ글 97' 등)을 한컴 HWP5/HWPX 변환본과 같은 자모열(초성+ᆞ+종성)로 보존합니다.
436
- - **🐛 그 외**: 동명 누름틀이 여러 곳에 있는 서식에서 `require_unique`가 한 곳도 채우지 않던 것, 수식 토큰이 `constructor`·`toString`이면 출력이 오염되던 프로토타입 체인 결함, 빈 셀 채우기가 엔티티(`&#32;`) 원문을 지우던 것(fail-closed 통일), fill 계열 ZIP 폭탄 가드, CLI `--format json`의 실패 JSON 출력(원인 코드 포함). npm 배포본에 NOTICE·THIRD_PARTY 라이선스 고지도 포함됩니다.
437
-
438
- ## v4.3.0 변경사항
439
-
440
- - **🏛️ 정부 표준 기안문 서식 내장 + 누름틀 채우기**: 별지 제1호(일반기안문, 누름틀 23곳)·제2호(간이기안문, 13곳) 서식을 번들 — `kordoc fill --template gian` / `--list-templates` / MCP `fill_form`의 `template` 파라미터로 파일 없이 표준 기안문을 채워 만듭니다. 누름틀(CLICK_HERE) 필드 채우기 신설 (서식·안내문 보존, 안내문 충돌 침묵 유실 구조적 차단).
441
- - **📄 빈 문단 보존 옵션 (#57)**: `--keep-empty-paragraphs` — 행 줄맞춤 서식 문서(원규 번호표 등)에서 빈 문단이 사라져 값이 다른 항목에 붙던 문제의 opt-in 해법. 본문은 빈 블록, 표 셀은 빈 줄로 "원문 문단 수 = 줄 수" 대응 유지. (@jumaniac 제보)
442
- - **🐛 에러 계약·MCP 정합 7건**: 암호화 XLS가 성공으로 위장하던 것, HWPX DRM 오분류, HWP3/HWPML/sharp 진단 메시지 소실, MCP `parse_pages` 배포용 HWP 폴백 불발, `.xls`를 `hwp`로 오판, `pageCount` 미전파 등 — 전면 리뷰에서 발굴·일괄 수리.
443
- - **📐 TAC 인라인 표 outMargin 정합 (rhwp 포팅)**: 결재란 등 글자취급 표의 가로 여백이 무시돼 문단이 밀리던 렌더 결함 수리 — 실코퍼스 60건 스윕 검증.
444
- - **⚡ 성능**: HWP 바이너리 패치의 섹터 할당 O(n²) 제거(+4MB 삽입 **637배** 가속), PDF 괘선 표 감지 버킷화. MCP 핸들러 전면 비동기화로 대용량 문서 I/O 중 서버 멈춤 제거.
445
- - **🧰 내부 정비**: 수식 변환기 hwp5→hwpx 정본 통합(포맷별 LaTeX 갈림 해소), 번호서식·XML 헬퍼 공용화, CI 타입체크 신설, 발행 게이트 복원(`--ignore-scripts` 관행 제거).
446
-
447
- ## v4.2.6 변경사항
448
-
449
- - **📐 개조식·보고서 본문 왼쪽정렬**: 개조식·보고서 공문서의 본문 항목(□/○/-)을 양쪽정렬에서 왼쪽정렬로 바꿨습니다. 어절 유지 + 양쪽정렬이 겹치면 다음 어절이 길어 짧게 끊긴 줄(예: 25자 줄을 34자 폭으로)의 어절 간격이 과하게 벌어져 문단이 깨진 것처럼 보이던 문제입니다. 「행정업무운영편람」 개조식 예시의 왼쪽정렬 관례와도 일치합니다. 기안문(official) 등 서술형 본문의 양쪽정렬은 그대로 유지합니다.
450
-
451
- ## v4.2.3 변경사항
452
-
453
- - **인라인 표·텍스트 순서 보존 (#49·#50)**: 한 문단이나 한 셀 안에서 글자취급(treatAsChar) 표와 텍스트가 번갈아 놓일 때(`[날짜] 부터 [날짜] 까지` 같은 서식 기간 입력란) 텍스트가 표 앞으로 끌려나오던 순서 역전을 수정했습니다. 최상위 `blocks`와 `IRCell.blocks` 모두 원문 배치 순서를 따르고, 생성기도 같은 순서로 방출해 라운드트립이 대칭입니다. float·페이지 앵커 표는 종전대로 텍스트 흐름에 불참합니다. (@jumaniac 제보)
454
-
455
- ## v4.2.2 변경사항
456
-
457
- - **~~취소선~~ 추출 (HWP5·HWPX)**: 법령 개정문 등의 삭제 표시가 마크다운 `~~취소선~~`으로 살아납니다. 판정은 취소선 **모양** whitelist — 한컴은 취소선 없는 문자에도 취소선 비트를 기본값으로 저장하므로(원저장소 rhwp 실측) 모양이 실제 선 종류일 때만 인정, 미지 값은 안전하게 무시. HWPX는 부분 취소선까지, HWP5는 문단 단위.
458
- - **ZIP 해제 한도 100→256MB**: 단일 섹션 XML이 75MB에 달하는 대형 실문서(rhwp 1만 건 서베이 실측)가 ZIP bomb으로 오인 거부되던 것을 상향.
459
-
460
- ## v4.2.1 변경사항
461
-
462
- - **🖼️ 이미지(PNG/JPG/WebP) 직접 입력**: 스크린샷·스캔 이미지를 PDF 래핑 없이 바로 변환합니다 — `kordoc 서식.png` / `parse(buffer)` / MCP `parse_document`. 텍스트층이 없으므로 내장 OCR 이 자동 적용됩니다 (플래그 불필요, 디코딩은 optional dependency `sharp`).
463
- - **📐 스캔본 표 괘선 감지**: OCR 경로에 래스터 괘선 감지기 신설 — 페이지 픽셀에서 수평/수직 괘선을 직접 찾아(이진화+런렝스, ML 없음) 선 기반 표 파이프라인에 태웁니다. 세로 병합 라벨 셀 + 다중줄 서술형 서식(정부 제출 서식류)도 rowspan/colspan 이 살아있는 표로 복원됩니다. 종전 클러스터 감지는 괘선이 안 잡히는 무테두리 표의 fallback 으로 유지.
464
-
465
- ## v4.2.0 변경사항
466
-
467
- - **👓 내장 텍스트 OCR (PP-OCRv5 korean)**: 스캔/이미지 PDF를 **API 키 없이 로컬 추론**으로 읽습니다 — `parse(buffer, { ocr: true })` / CLI `--ocr` / MCP `parse_document`의 `ocr` 옵션. 모델 ~18MB는 첫 사용 시 자동 다운로드+SHA 검증. 품질 신호가 가리키는 페이지(스캔·글꼴 매핑 깨짐)만 정밀 OCR 하고 정상 페이지는 그대로 두며, OCR 라인 좌표를 표 감지 파이프라인에 태워 **스캔본에서도 표가 복원**됩니다. 한국어 사전 11,945자(완성형 한글 11,172자 전량 + 자모·라틴·기호), 1페이지 ≈1초.
468
- - **🩹 HWP3 구버전 파서 정합 3종**: 탭/필드코드/책갈피의 스트림 소비 결함으로 이후 텍스트가 통째로 오염되던 것과, 로마숫자(Ⅰ~Ⅹ)·원문자(①~⑩)·따옴표·글머리가 증발하던 것을 원저장소(rhwp) 후속 패치 반영으로 수리.
469
- - **🐛 수식 OCR 페이지 off-by-one 수리**: `--pages` 필터가 한 페이지 밀리고 수식이 이전 페이지에 붙던 잠복 결함. XLSX/XLS `--keep-empty-cols` 배선 누락도 수리.
470
-
471
- ## v4.1.0 변경사항
472
-
473
- - **👁️ `render_document` MCP 도구 신설**: 생성·패치·양식 채움 결과 HWPX를 조판 그대로 **PNG 이미지로 응답에 직접 반환** — AI가 자기가 만든 문서를 눈으로 확인하고 다시 고치는 루프가 MCP 안에서 닫힙니다 (한컴 저장본은 조판 캐시, 생성본은 reflow 조판, 검색어 형광펜 지원).
474
- - **🕶️ `kordoc redact` + `redact_document` MCP 신설**: 개인정보(주민번호·전화·이메일·카드·계좌, 여권·운전면허 opt-in)를 탐지해 **원본 서식 그대로 마스킹**(`850315-●●●●●●●`)한 HWPX/HWP를 출력합니다. 생년월일·Luhn 검증으로 오탐 축소, 리포트에 원본 개인정보 미포함. 자동 검출 보조 도구 — 최종 공개 전 사람 확인 필수.
475
- - **📦 `--format chunks` + `parse_chunks` MCP 신설**: RAG용 구조 청크 JSON — 헤딩·개조식 위계(□○- / 1.·가.·1))를 breadcrumb 경로로 보존하고 표는 독립 청크로 분리합니다.
476
- - **📋 표 오른쪽 끝 빈 열 보존 (#47)**: 서식 문서의 빈 입력란 열이 파싱에서 통째로 삭제되던 문제 — `keepTrailingEmptyCols` 파싱 옵션(CLI `--keep-empty-cols`)으로 보존할 수 있고, **양식 경로(parse_form·fill)는 항상 보존**해 fill이 채울 수 있는 필드가 목록에서 빠지지 않습니다. 제보 [@jumaniac](https://github.com/jumaniac).
477
- - **📅 XLSX/XLS 날짜 변환**: 날짜 셀이 시리얼 숫자("45306")로 나오던 것을 ISO("2024-01-15")로 변환합니다 (내장·커스텀 numFmt 감지, date1904 반영).
478
- - **🛡️ 프로덕션 전면 리뷰 ~80건 수정**: 악성 파일 대응(수식 정규식 ReDoS·HTML 표 span 폭주·HWP3 압축 폭탄·XLSX 그리드 폭주), 표-전용 PDF의 OCR 오판, HWP5 제어문자(하이픈/고정폭 공백) 매핑, DOCX 변경추적 삽입 유실, 양식 채움 라벨 오탐 4종, HWP5 패치 삭제 텍스트 잔존 제로화, MCP 쓰기 경로 검증, PDF 괘선 표 성능(62,500 교차점 3.1s→0.2s) 등 — 자세한 내역은 [CHANGELOG](CHANGELOG.md).
479
-
480
- ## v4.0.8 변경사항
481
-
482
- - **🖼️ PDF 이미지 추출 신설**: 종전에는 이미지 영역 좌표만 계산하고 바이너리는 전량 유실되던 것을, 이미지 XObject를 디코딩 픽셀로 받아 순수 JS PNG 인코딩으로 추출합니다 (pdfjs 비동기 디코딩 대기 포함 — 코퍼스 52 PDF 중 45파일 731장 실측). 페이지 말미 위치에 `![image](...)` 참조, 로고·워터마크 페이지 간 중복 억제, 페이지 경계 표 병합 비간섭.
483
- - **🖼️ HWPX/HWP5 미참조 BinData 스윕**: 꼬리말·머리말 안 그림(보도자료 사진 스트립), 결재문서 셀 배경 이미지(borderFill imgBrush) 등 본문 워크가 닿지 않아 유실되던 이미지를 문서 끝에 보강 추출합니다 — 코퍼스 이미지 복구율 HWPX 686/686·HWP5 92/92 (100%).
484
- - **🖼️ DOCX `w:object` 이미지**: OLE 개체 미리보기(`v:imagedata`)가 추출에서 빠지던 것 수정 (mc:Fallback 사본은 종전대로 중복 제외).
485
-
486
- ## v4.0.7 변경사항
487
-
488
- - **🔗 DOCX 하이퍼링크 대량 손실 수정**: 한 문단에 링크가 여러 개면 마지막 1개만 남던 구조를 제거하고 문서 순서대로 링크별 인라인 `[text](url)` 생성. 워드·구글독스가 흔히 쓰는 **필드코드 HYPERLINK**(`fldSimple`/`fldChar`)와 내부 anchor 링크도 처리 (실측 176→21 손실이던 문서 전량 복원).
489
- - **🖼️ DOCX 이미지 본문 링크**: 이미지가 파일로는 추출되나 마크다운에 `![image](...)` 참조가 안 들어가던 것을 문단 위치 인라인 방출로 수정.
490
- - **🔍 PDF 오매핑 mojibake 감지**: ToUnicode 오매핑으로 정상 한글 영역의 엉뚱한 글자가 무경고 통과하던 것을 종성(받침) 분포 신호로 감지 — `garbled_hangul` 사유의 페이지별 NEEDS_OCR 경고.
491
-
492
- ## v4.0.6 변경사항
493
-
494
- - **📊 PDF 무괘선 밴드 표 파편화 수정**: 세출예산 사업명세서류의 요약행 밴드에서 수직선이 끊겨 부서명이 유실되던 것을 4중 가드 수직선 브리지로 복원 (광진구 2026 세출예산 637곳 실측).
495
- - **📝 HWPX 캡션 안 중첩표 보존 (#46)**: 캡션 속 표 내용이 통째로 사라지던 것(304자 중 297자)을 문서 순서 평탄화로 보존. 제보 [@jumaniac](https://github.com/jumaniac), 조언 @hiSandog.
496
-
497
- ## v4.0.5 변경사항
498
-
499
- - **🖍️ 인라인 강조 복원 일반화**: kordoc이 만들지 않은 **일반 한컴 문서**도 볼드·이탤릭이 마크다운 `**`·`*` 마커로 복원됩니다 (charPr 실속성 기반, 한컴 편집 이력으로 쪼개진 run 자동 병합). 표 헤더행처럼 셀 전체가 볼드인 구조 서식은 마커를 만들지 않습니다.
500
- - **🏛️ 공문서 모드 인라인 강조 왕복**: 공문서 HWPX를 재파싱해도 항목 안 `**강조**`가 보존됩니다 — 보고서 1단계 □ 전체 굵게 같은 구조 볼드와 자동 구분.
501
- - **↔️ 리스트 depth 왕복**: 공문서 재파싱 산출물의 `1)`·`-` 항목이 재생성 시 1단계로 붕괴('1)'→'2.', '-'→□)하던 것을 들여쓰기 역산 선행 공백으로 해소 — 기안문·보고서·개조식 2차 왕복이 동일 결과로 수렴.
502
-
503
- ## v4.0.4 변경사항
504
-
505
- - **🖋️ reflow 개체 흐름 모델**: float 표(두문 결재표)는 텍스트를 개체 아래로, 페이지 앵커·글 뒤 개체는 흐름 불참, inline 표는 실효높이+행간 전진 — 실결재 코퍼스 자기일관성 **59/59 (100%)**. 한컴 저장본을 프로그램 편집해 일부 문단만 캐시가 없는 **혼합 캐시 문서**도 reflow로 정확한 위치에 렌더됩니다.
506
- - **🎨 서식 프로필 0.3.0**: 셀 **글꼴 이름 왕복**(`fontName_hangul` — 원본 없이 글꼴 재현), 첫 행 지문 `anchor_row`(첫 셀이 빈 크로스탭 매칭), 행0 전체 병합 표의 열폭 보존, 손편집 JSON의 괘선 값(type·mm·색상) 사전 검증.
507
- - **🧰 CLI/MCP 옵션 표면 통일**: 공문서 옵션 조립·값 집합을 단일 SSOT로 — 두 인터페이스의 옵션 드리프트 원천 차단(MCP 프리셋 별칭 6종 회복 포함). 리팩터 전후 산출물 바이트 동일 검증.
508
- - **↔️ 왕복 보강**: 표 셀 안 볼드·이탤릭·코드 마커 왕복, 파서 문단 들여쓰기 관찰 슬롯(`IRBlock.indent`), 원문자 15+/51+ 폴백 파서 정합, 중첩표 높이 정밀화, 결문 구분선 컬럼폭 적응.
509
- - 게이트: 테스트 1,012 · 실렌더 시각 오라클 14종 해밍0 (default 모드 h1~h4 개요번호 미강제 실렌더 확증 포함).
510
-
511
- ## v4.0.3 변경사항
512
-
513
- - **🛡️ 프로덕션 하드닝**: v4 전체 2중 리뷰에서 확정된 결함 수정 — 잘못된 수치 옵션(NaN·비정상 범위)의 명시적 거부, 폰트명 XML 이스케이프, 표지·목차를 전 프리셋으로 확대(보도자료 제외), 표지·목차 사용 시 본문 폰트 지정이 무시되던 결함과 13pt 이하 헤딩 위계 역전, 셀 안 개체 캡션 오귀속·거짓 손실 경고 수정.
514
- - **📏 기안문 기본 12pt·계획서 □→ㅇ→\* 체계**: 실결재·실측 계획안 지배값을 기본값으로 (종전 값은 `--pt 15`·`numbering: 'standard'`로 복원 가능).
515
-
516
- ## v4.0.2 변경사항
517
-
518
- - **📏 실측 벤치마킹**: 서울 정보소통광장 **실결재 기안문 60건** + 부처별 양식을 전수 디코드해 생성 결과와의 괴리 17건을 목록화, 9건 반영.
519
- - **조판영역 근본수정**: 단 컬럼 정의(`colPr`) 미방출로 본문이 좌우 10mm씩 좁게 잡히고 표가 우측 여백을 침범하던 결함 해소 — 전 프리셋 실렌더 조판영역 초과 0건.
520
- - **기안문 두문·결문** (`--doc-head`/`--doc-foot`, 별지 제1호서식) · **보도자료 프리셋 `press`** (머리박스·담당 표) · **공고문 두문** (`--notice-head`) · **보고정보 행** (`--report-info`).
521
- - 표 열폭 배분 재작성(열 하한 = 최장 어절 폭 — 짧은 열이 글자 단위로 세로 쪼개지지 않음), 표 셀 12pt, 기안문 여백 실결재 지배값 20/15/20/15, 2단계 부호 ㅇ/○ 프리셋 분화(`--bullet2`).
522
- - 실무자(현직 공무원) 눈 QA 게이트 통과 후 릴리스.
523
-
524
- ## v4.0.1 변경사항
525
-
526
- - **✍️ 실무자 QA 3건 수정**: bold 시 폰트가 HY견고딕/Arial Black으로 바뀌던 "정체모를 폰트" 제거, h2 섹션 제목 말머리 `--h2-marker`(box □ / number / none), 개조식 소분류 부호 ― → 실무 관행 하이픈 `-`.
527
- - **📝 공문서 표기법 검수 19룰**: 날짜·시간·금액·붙임·외래어·차별 표현 등을 검수하는 `kordoc lint <file>` + 공문서 생성 시 경고 병기.
528
-
529
- ## v4.0.0 변경사항
530
-
531
- - **🏛️ 정부 표준 개조식 보고서 완성**: 실제 정부 공문서 16종 전수 디코드·대조 기반 — 표지(gradient 제목박스·장식 바)·목차 배너·로마숫자 장헤더·쪽번호("- 1 -", 표지·목차 제외)·결재란·"끝." 자동·본문 제목박스. 표는 실측 문법(헤더 음영+이중선, 외곽 0.4mm 위계, 내용 비례 열폭, 우측 배치) 자동 적용. `--preset 개조식`.
532
-
533
- ## v3.18.0 변경사항
534
-
535
- - **🎨 서식 프로필**: 표의 위상뿐 아니라 **테두리·음영·열 실측폭·셀 글꼴까지** 원본 문서 없이 재현합니다. `hwpxToProfile(hwpx)`로 레퍼런스 서식만 JSON으로 추출하고, `markdownToHwpx(md, { profile })`로 다른 문서에 그 서식을 입힙니다 — 원본 유출 없이 기관 서식만 공유·재현(이슈 #41, 스키마 [`docs/format-profile-spec.md`](docs/format-profile-spec.md)). 스키마·예시 기여: [@ai-localgov-officer](https://github.com/ai-localgov-officer) (PR #42).
536
-
537
- ## v3.17.0 변경사항
538
-
539
- - **🖼️ 레이아웃 렌더 충실도**: 글꼴이 문서 지정대로 나오는 per-run 폰트(고딕 제목이 바탕체로 나오던 것 해소), 표지+본문 다구역 문서 전체 렌더, 가로(landscape) 문서 프레임 회전(우측 잘림 해소), 연속 표 문단의 페이지 포개짐 분리.
540
- - **✍️ 결재란 겹침 해소 (reflow)**: 조판 캐시 없는 문서에서 결재란 라벨표·스탬프표가 같은 자리에 포개 찍히던 것을 한컴과 동일하게 나란히 배치. 중첩표 셀 높이 과소측정도 함께 수정.
541
-
542
- ## v3.16 변경사항
543
-
544
- - **📊 차트 생성**: 마크다운 ```chart 펜스(type/cat/계열 라인)가 한컴 네이티브 차트(OOXML chartSpace)로 생성됩니다 — 막대·선·원·도넛·영역·분산·방사형 등 20종, 계열/조각 색 지정, 잘못된 펜스는 코드블록 폴백.
545
- - **🔴 도장/서명 자동 날인**: `kordoc seal` — "(인)"·"서명 또는 인" 앵커를 찾아 도장 PNG를 글 앞 부유로 배치. 표/페이지를 키우지 않아 날인 후 서식이 밀리지 않습니다 (MCP `place_seal` 포함). 중첩표·글상자·탭/줄바꿈 문단은 위치가 근사이며 결과 `warnings` 로 고지됩니다 — 한컴에서 확인 후 `--dx`/`--dy`(dx_mm/dy_mm)로 미세조정하세요.
546
- - **🔌 Claude Code 플러그인**: `/plugin marketplace add chrisryugj/kordoc` → `.hwp`/`.hwpx`/공문서 요청에 kordoc 스킬 자동 활성화.
547
- - **🩹 3.16.1 패치**: 통합 검증 리뷰 결함 55건 일괄 수정 — 도장 배치(rowspan·colspan·중첩표 원점), 차트 값 파서(천단위 콤마·CRLF 마크다운), 양식 채우기 가드(require_unique), CLI `fill -o` 출력 등 "성공 메시지 뒤에 조용히 틀린 산출물" 계열 소탕.
548
- - **🩹 3.16.2 패치**: 신구조문대비표의 `<신 설>` 표기를 텍스트 상자로 오인해 표 전체를 문단으로 해체하던 PDF 파서 결함 수정 — 30p 개정안 대비표가 통째 1표로 복원.
549
-
550
- ## v3.15.0 변경사항
551
-
552
- - **🖋️ reflow 렌더 (캐시 없는 파일도 조판)**: `markdownToHwpx` 산출물·AI 생성본·편집본처럼 조판 캐시(linesegarray)가 없어 렌더가 거부되던 HWPX를 `renderHwpxToSvg(buf, { reflow: true })` / `kordoc render`(CLI는 기본 켬, `--no-reflow`로 끔)로 순수 TS 조판합니다. 검증된 줄나눔 엔진(실측 98% 일치) + 실측 세로 모델로 lineseg를 합성해 기존 렌더 파이프(정렬·표·이미지·형광펜·다페이지)를 재사용합니다. 단문단·표 셀·표 밀어내기·자동 페이지 분할. 한컴 저장본은 캐시 재생 그대로(무회귀).
553
- - **🔺 그리기 도형 렌더**: 사각형·타원·선·다각형·호를 SVG로 그립니다(선 색·굵기·점선, 채움, 크기 스케일). 조직도·화살표 등 "원본과 다르게 보이던" 큰 원인을 해결했습니다.
554
- - **🚀 persistent 렌더 워커**: `kordoc render-worker`가 프로세스를 유지하며 연속 렌더 요청(stdin NDJSON)을 처리해 node 콜드스타트를 없앱니다(미리보기 앱 연동용).
555
-
556
- ## v3.14.0 변경사항
557
-
558
- - **📄 렌더 다페이지 지원**: `kordoc render`가 전 페이지를 세로 스택 SVG로 그립니다(페이지별 흰 배경·경계선·클립, `data-page` 속성, `RenderSvgResult.pageCount`). 기존엔 전 페이지가 첫 장 한 장에 겹쳐 그려졌습니다.
559
- - **🖍️ 렌더 검색어 형광펜**: `--highlight <쉼표구분어>` / `RenderSvgOptions.highlights` — 텍스트를 매치 경계로 분할해 매치 세그먼트에만 배경을 깝니다(대소문자 무시, `textLength` 동일 계산으로 정렬 오차 없음).
560
- - **📐 줄 경계 정합**: lineseg `textpos`를 HWP5 문자 스트림 슬롯(컨트롤 8·문자형 컨트롤 1·서로게이트 2슬롯) 기준으로 재구성해, 컨트롤·탭이 섞인 문단에서 첫 줄에 글자가 몰리고 다음 줄이 비던 어긋남을 해결했습니다(데모 1,132개 멀티라인 문단 검증).
561
- - **🖼️ 이미지 크롭 오판 수정**: `imgClip`을 `imgDim`(내용 상자) 기준으로 해석 — 삽입 후 리사이즈된 이미지(로고 대부분)가 좌상단 코너로 잘못 잘려 깨지던 문제를 해결했습니다(데모 pic 267개 검증).
562
-
563
- ## v3.13.0 변경사항
564
-
565
- - **📄 프로즈 박스 감지**: 상단 라벨탭(제목 칩)이 박스 테두리에 걸쳐 만든 가짜 열 위로 본문이 전폭으로 흐르는 표(검정고시 응시자격 박스 등)를 감지해, 셀 경계에서 조각나던 본문을 자연 읽기 순서의 문단으로 복원합니다. 기하(전폭 행 지배)와 텍스트(긴 프로즈 셀)의 교집합에서만 발동해 정규 표는 건드리지 않습니다.
566
- - **📝 HML 표 캡션 보존**: 표에 딸린 도형 캡션(`※ …참조` 등)이 hwpml 파싱에서 통째로 소실되던 문제를 수정했습니다. 캡션을 표 앞/뒤 문단으로 보존합니다.
151
+ 표 점수 차이는 대부분 HwpForge 의 파이프 표가 병합 칸을 못 담는 데서 옵니다.
567
152
 
568
153
  ---
569
154
 
570
- ## v3.12.0 변경사항
571
-
572
- - **🏷️ 라벨 헤더 표 강등 면제**: 첫 행이 라벨(채용분야|담당업무|우대조건, 성명|응시분야|비고)인 표가 본문 셀의 ○/ㅇ 항목부호나 빈 기입란 때문에 텍스트 박스로 오인돼 문단으로 강등되던 문제를 수정했습니다. 예산표·업무분장표·양식 표가 표로 살아납니다.
573
- - **🔗 개방 변 합성 체인 뷰**: 중간 괘선을 셀 경계마다 쪼개 그은 표(문의처 연락처 표 등)도 세그먼트를 논리적으로 이어 좌우 개방 변을 닫습니다. 물리 선은 건드리지 않아 기존 표의 셀 배치에 부작용이 없습니다.
574
- - **📊 PDF 표 구조 벤치**: 표 매칭 90.3→98.6%, 완전 일치 58.3→65.2%, cellF1 0.652→0.724. 채점기에 잡매칭 차단(bag 교집합 0)·세밀 분할 프로즈 박스 구제(접두 유사도 폴백)를 더하고, 흐름띠·빈 스캐폴딩 표는 모수에서 제외했습니다.
575
-
576
- ## v3.11.0 변경사항
577
-
578
- - **📐 PDF 개방형 표 복원**: 한국 행정문서에 흔한 좌/우 바깥 테두리 생략 표(수평 괘선만 전폭, 수직선은 내부 구분선만)에서 가장자리 열이 통째로 사라지던 것을 가상 테두리 합성으로 복원합니다. 채용공고류에서 6열 표가 4열로 찢기고 남은 열·본문이 13x2 유령 표로 흡수되던 병리가 완치됐습니다.
579
- - **🎨 글상자 음영의 괘선 오염 차단**: 한컴 PDF가 제목 글상자의 그라디언트 배경을 촘촘한 수평선 수십 개로 내보내, 선 병합 단계에서 실제 상하 테두리까지 삼키던 것을 음영 스택 필터로 걸러냅니다.
580
- - **📊 PDF 표 구조 벤치**: hwpx↔pdf 쌍 GT 대조에서 표 매칭 84.7→90.3%, 완전 일치 54.2→58.3%, cellF1 0.632→0.652.
581
-
582
- ## v3.10.0 변경사항
583
-
584
- - **🖼️ 레이아웃 보존 렌더**: `kordoc render 문서.hwpx -o 문서.svg` / `renderHwpxToSvg(buffer)` (v4.13.0: `renderDocument` 로 HTML·PNG·JPEG·PDF·페이지 선택·영역 crop) — 한컴이 저장한 조판 캐시(줄 좌표·셀 그리드·개체 앵커)를 SVG 절대배치로 그려 원본 레이아웃을 재현합니다. run별 글자 크기/굵기/색/장평/자간, 문단 정렬, 셀 배경·테두리, 병합 셀, 인라인 개체, 이미지 크롭까지. 한컴 저장본 전용(1페이지).
585
- - **🔧 uint32 음수 좌표**: `vertOffset="4294967103"`(= −193) 같은 uint32 저장 음수를 올바르게 해석합니다.
586
- - **🔧 셀 내부 COLUMN 기준계**: 셀 안 개체의 `horzRelTo="COLUMN"`을 셀 영역 기준으로 해석합니다 (사진이 페이지 왼쪽으로 튀던 문제).
587
-
588
- ## v3.9.0 변경사항
589
-
590
- - **🧮 Markdown 수식 → HWPX native 수식 생성**: `$$ \frac{a}{b} $$` 같은 display math 블록이 한컴 수식 개체(`<hp:equation>`)로 생성됩니다. `\frac`·`\sqrt`·첨자·그리스 문자·적분/극한·행렬(matrix/pmatrix/bmatrix)·`\left(` 구분자·`\text` 리터럴 지원. 생성한 수식은 kordoc으로 다시 파싱해도 같은 LaTeX로 돌아옵니다 (#38, @leehuiso 기여).
591
- - **🛡️ 수식 입력 가드**: 닫히지 않은 `$$`가 문서 전체를 삼키던 문제(일반 문단 폴백), 중괄호 폭탄 크래시(깊이/길이 상한), 닫는 `$$` 뒤 텍스트 소실을 수정했습니다.
592
- - **📋 공문 모드 번호 연속**: 항목 사이에 수식이 끼어도 항목 번호가 이어집니다 (표와 동일).
593
- - **⚖️ 법령 문서 왕복 무결성 게이트**: 조문 번호 뒤 분리·문장 중간 끊김이 없음을 실측(민원처리법 전문 228문단)하고 벤치 게이트로 고정했습니다.
594
-
595
- ## v3.8.4 변경사항
596
-
597
- - **📑 DOCX 병합표·텍스트박스 복구**: 병합표에서 셀이 통째로 빠지던 버그(세로 병합 미동작 포함)와 텍스트박스 내용 전체 유실을 수정 — 신고서류·KS표준안류 회수율 0.67/0.92 → **1.0/0.998**.
598
- - **🔒 개인정보 마스킹 별표 보호**: `******`·`홍**` 같은 마스킹 별표가 마크다운 수평선/볼드로 오독되던 것을 이스케이프로 보존합니다 (별표 각주 `* 단, …`의 리스트 오인도 해소).
599
- - **🔁 md→HWPX 왕복 충실도 0.947→0.9996**: 재변환한 HWPX를 다시 파싱해도 헤딩 레벨·리스트 번호(2. 3. 4. 시작 보존)·마스킹 문자가 그대로 살아납니다. 생성 문서는 한컴 "문서 찾아가기"에 개요 구조로 표시됩니다.
600
- - **#️⃣ 개요 번호 발명 수정**: 번호 서식을 비운 개요 문단(한컴 "번호 없음")에 파서가 "1." 접두를 만들어 붙이던 버그 수정.
601
-
602
- ## v3.8.3 변경사항
603
-
604
- - **📰 2단 조판 문서(속기록류) 읽기 복원**: 2단 본문이 2열 표로 흡수돼 좌우 단의 문장이 뒤섞이던 버그 수정 — 단을 분리해 올바른 순서로 읽습니다.
605
- - **🛡️ 손상 PDF 처리 시간 폭주 가드**: 좌표가 오염된 비정상 PDF에서 파싱이 144초까지 걸리던 것을 2초대로 차단합니다 (정상 문서 출력 무변화).
606
- - **📗 한셀(HCell)로 저장한 XLSX 복구**: "시트가 없습니다"로 실패하던 한셀 저장 파일이 정상 파싱됩니다.
607
- - **📋 HML 문서의 표 소실 수정**: 문단에 앵커된 표가 통째로 빠지고 중첩표 내용이 사라지던 버그 수정 — 표 텍스트 회수율 0.23 → 0.99.
608
-
609
- ## v3.8.2 변경사항
610
-
611
- - **📐 변환(축소/플립) 깔린 PDF의 괘선 표 복구**: 성과계획서류처럼 콘텐츠에 변환 행렬이 깔린 문서에서 표 감지가 통째로 실패하던 버그 수정 — 2줄 머리글 셀도 rowspan 병합으로 정상 복원됩니다.
612
- - **📄 스캔 PDF의 임베디드 텍스트층 복구**: CID 폰트 텍스트가 조용히 소실되던 문제 수정 — "스캔본"으로 보이던 의사록에서 전문 텍스트를 추출합니다 (일부 문서는 pdftotext보다 잘 읽습니다).
613
-
614
- ## v3.8.1 변경사항
615
-
616
- - **🔄 PDF 회전 텍스트 복구**: 90°로 눕힌 사이드탭 목차·세로 표(계속비 총괄표 등)가 숨김 텍스트로 오인돼 통째로 빠지던 버그 수정 — 이제 보이는 회전 텍스트도 추출됩니다. (숨김텍스트 prompt-injection 방어는 그대로)
617
- - **🧱 내부 구조 정리**: PDF 선 감지(1,247줄)·HWPX 파서(1,619줄) 대형 파일을 목적별 15개 모듈로 분리 — 공개 API·출력 100% 동일 (실파일 87건 해시 검증).
618
-
619
- ## v3.8.0 변경사항
620
-
621
- - **✏️ HWP 5.x 빈 셀 채우기** (`patchHwp`): 원본에서 비어 있던 표 셀에 마크다운 편집으로 값을 넣으면 이제 HWP 바이너리에도 삽입됩니다. 한컴이 빈 문단을 저장하는 방식(텍스트 레코드 생략형 포함)을 실파일 실측으로 지원. *(실파일 12건 무손상 검증)*
622
- - **🏛️ 공문 항목 사이 표**: "1. 항목 → 근거 표 → 2. 항목"처럼 리스트 사이에 표가 끼어도 항목 번호가 이어집니다 (공문서 모드).
623
- - **🚀 이미지 대량 참조 메모리 폭발 해소**: 같은 이미지를 수천 개 도형이 참조하는 문서(HWP·HWPX)에서 참조마다 데이터를 복사하다 **피크 17GB로 죽던 것을 445MB·0.2초 완주**로 수정.
624
- - **📢 DOCX 관측성**: 이미지/스타일/번호/각주/메타 파싱 실패를 조용히 넘기지 않고 `warnings`로 보고합니다.
625
-
626
- ## v3.7.0 변경사항
627
-
628
- - **📋 표 행 추가/삭제** (`patchHwpx`): 마크다운에서 표에 행을 새로 넣거나 지워도 이제 원본에 반영됩니다. 새 행은 **인접 행의 서식(테두리·글꼴·높이)을 그대로 복제**해 셀 텍스트만 바꿔 넣고, `rowCnt`·셀 좌표·표 높이까지 함께 갱신합니다. 세로 병합을 가로지르거나 행에 이미지/중첩표가 있는 등 위험한 경우엔 문서를 건드리지 않고 사유와 함께 skip합니다. *(실제 결재문서 45건 검증 — 손상 0)*
629
- - **✏️ 양식 채우기 정확도**: 라벨 칸이 병합(colspan)된 서식에서 값이 **소리 없이 사라지던 버그** 수정, 표 안의 표(중첩표) 속 라벨도 채웁니다. `fillFormFields`(IR)와 `fillHwpx`(원본 보존) 두 경로가 같은 결과를 내도록 정합.
630
- - **🔎 라벨 인식 확장**: "연번1"·"제1항목"처럼 숫자가 낀 라벨, "제1소위원회위원장" 같은 9자 이상 라벨, "Name"·"Date of Birth" 같은 콜론 없는 영문 라벨을 인식합니다. "6개월"·"1억원"·"해당없음" 같은 값을 라벨로 오인하지 않는 거름망 포함.
631
- - **📢 정직한 부분 적용 보고**: `PatchSkip.partial` 신설 — "적용은 됐지만 원형 그대로는 아님"(셀 내 줄 병합·빈 문단 잔존 등)을 구분해 보고합니다.
632
-
633
- ## v3.6.0 변경사항
634
-
635
- - **📐 실측 텍스트 메트릭 엔진**: 함초롬바탕 정품 TTF에서 글자 폭을 전수 추출해 한글 프로그램 없이 줄폭·줄바꿈을 계산합니다. 실제 결재문서의 조판 결과와 대조해 **줄바꿈점 98% 일치** 검증.
636
- - **🪗 자동 장평(`autoFit`)**: 한두 글자가 다음 줄로 넘어가는(orphan) 문단만 골라 장평을 95→90%로 줄여 한 줄에 담습니다. 공문서 작성 관행 그대로.
637
- - **📊 HTML 표 생성**: 병합(colspan/rowspan)·중첩 표가 든 마크다운도 `markdownToHwpx`로 구조 그대로 HWPX 표가 됩니다 — parse↔generate 표 라운드트립 완성.
638
- - **🗂️ 다중값 채우기**: `fillForm` 값에 배열(`string[]`)을 주면 같은 라벨의 등장 순서대로 하나씩 소진 — 반복 양식·명부형 표(헤더+여러 행) 채우기.
639
- - **🛡️ 무결성 픽스**: 채우기/패치 후 한컴이 "문서가 변조되었습니다" 경고를 띄우던 문제(줄 레이아웃 캐시 잔존), 생성 표 테두리가 보이지 않던 문제(borderFill id 규약) 수정.
640
-
641
- ## v3.5.0 변경사항
642
-
643
- - **📊 문장을 표로 — 인플레이스 변환** (`patchHwpx`): 기존 한글파일(HWPX) 안의 문단을 마크다운 표(`| … |`)로 편집해 `patch`에 넘기면, **원본 서식을 그대로 둔 채 그 문장만 표로** 바꿔줍니다. 셀 테두리는 자동 생성, 나머지 문단·표·서식은 1바이트도 건드리지 않고 무손실 검증을 통과합니다. CLI `kordoc patch`·MCP `patch_document`가 자동 지원. *(HWP 5.x 바이너리는 미지원 — `generate`로 새 문서 생성 권장)*
644
- - **🆕 MCP `generate_document` 도구**: AI 에이전트가 마크다운(표 포함)을 바로 HWPX로 생성. `parse_document`로 읽은 내용을 표로 재구성해 다시 한글파일로 출력하는 워크플로가 완성됩니다. 공문서 프리셋(`보고서`·`기안문`…)·글꼴·글자크기 옵션 지원.
645
- - **🐛 공문서 한글 프리셋 크래시 수정**: `markdownToHwpx(md, { gongmun: { preset: "보고서" } })`처럼 라이브러리/MCP에서 한글 프리셋명을 직접 넘기면 터지던 버그 수정(`normalizeGongmunPreset`). CLI는 영향 없었음.
646
-
647
- ## v3.2.0 변경사항
648
-
649
- - **🏛️ 공문서 모드 `markdownToHwpx(md, { gongmun })`** — 마크다운을 한국 행정 공문서 표준 서식의 HWPX로 렌더링. 행정안전부 「행정업무운영편람」·시행규칙 근거.
650
- - **항목부호 8단계 자동화** — 중첩 리스트 깊이 → `1. 가. 1) 가) (1) (가) ① ㉮` (마크다운 마커 종류 무시, 깊이로 강제). 가나다 소진 시 단모음 연속(거·너·더), 상위 항목 진행 시 하위 카운터 리셋, 단일 형제 부호 생략.
651
- - **둘째 줄 내어쓰기 정렬** — OWPML `<hc:intent>`(음수 hanging) + `<hc:left>`(단계별 누적)로 둘째 줄이 내용 첫 글자에 정렬. *(실제 한컴 공문서 paraPr 구조와 동일하게 검증)*
652
- - 프리셋별 실측 여백·글자 크기 적용(기안문 본문 12pt, 보고서·계획서 15pt) + 맑은 고딕 옵션.
653
- - **문서종류 프리셋** `official`(기안문)·`report`(보고서, □○-ㆍ 불릿)·`plan`·`notice`·`minutes`.
654
-
655
- ```ts
656
- import { markdownToHwpx } from "kordoc"
657
-
658
- const md = "1. 첫째 항목\n - 둘째 항목\n - 셋째 항목"
659
- const hwpx = await markdownToHwpx(md, { gongmun: { preset: "보고서" } })
660
- // → 1. / 가. / 1) 항목부호 + 내어쓰기 + 공식 여백 자동 적용
661
- ```
662
-
663
- CLI: `kordoc generate doc.md -o out.hwpx --preset 보고서` (별칭 `gen`, `--font`/`--pt`/`--line-spacing`/`--plain`). 표준 레퍼런스: `docs/gongmunseo-reference.md`, 작성 스킬: `.claude/skills/gongmunseo/`.
664
-
665
- ## v3.1.0 변경사항
666
-
667
- - **🖊️ 에디터 통합 API `HwpxSession`** — 블록 클릭-편집형 에디터를 위한 증분 패치 세션. `openHwpxDocument(bytes)`로 열고, `session.patchBlocks(edits)`로 블록 인덱스 기반 직접 편집 (문단 텍스트 / 표 셀). **n회 연속 증분 패치 ≡ 일괄 `patchHwpx`** 바이트 동일 동등성을 CI 게이트로 보장합니다.
668
-
669
- ```ts
670
- import { openHwpxDocument } from "kordoc"
671
-
672
- const session = await openHwpxDocument(new Uint8Array(buf))
673
- session.capability(3) // "text" | "cell-text" | "locked" — 편집 전 잠금 판정
674
- const res = await session.patchBlocks([
675
- { blockIndex: 3, newText: "개최 완료" },
676
- { blockIndex: 5, cells: [{ row: 1, col: 2, text: "홍길동" }] },
677
- ])
678
- // session.bytes — 서식 그대로, 텍스트만 바뀐 HWPX (증분 누적)
679
- ```
680
-
681
- - **📋 양식 필드 스키마 `extractFormSchema(blocks)`** — 양식 인식에 타입 추론을 더해 폼 UI 자동 생성 지원. 필드 타입 7종(`text`/`date`/`phone`/`email`/`amount`/`checkbox`/`idnum`) + `required`(필수 표시 감지) + `empty`(채움 대상 판정).
682
- - **`fillHwpx` splice 전환** — 수정 범위 외 섹션 XML을 원본 바이트 그대로 보존하도록 전면 재작성 (동작·결과는 v3.0과 패리티).
683
- - **CJS 빌드 수정** — `require("kordoc")` 시 `import.meta` SyntaxError 나던 버그 수정.
684
-
685
- ## v3.0.1 변경사항
686
-
687
- - **🔄 HWP 5.x 바이너리 서식 보존 패치** — `patchHwp(원본HWP, 편집된마크다운)` 신규 API. HWPX 패치(`patchHwpx`)의 HWP 5.x(OLE2 바이너리) 대응으로, 변경된 문단/표 셀의 PARA_TEXT만 레코드 안에서 치환합니다 (PARA_HEADER 글자수·CHAR_SHAPE·LINE_SEG 연쇄 갱신).
688
- - **섹터 레벨 컨테이너 수술**: CFB 전체 재조립 없이 대상 스트림의 섹터/FAT 체인/디렉토리 엔트리만 갱신 — 수정 외 영역은 원본과 바이트 동일 (실측: 133섹터 중 5섹터만 변경)
689
- - 안전 게이트: 레코드 재직렬화 바이트 동일성 검증, 순수 텍스트 문단만 수정, 암호화/배포용/DRM 거부, 미지원 편집은 `skipped[]`로 graceful skip
690
- - CLI `kordoc patch`가 .hwp/.hwpx를 매직바이트로 자동 분기
691
- - **CI**: Node 18 ESM `__dirname` 미정의로 테스트 매트릭스가 실패하던 문제 수정
692
-
693
- ## v3.0.0 변경사항
694
-
695
- - **🔄 서식 보존 무손실 라운드트립** — `patchHwpx(원본HWPX, 편집된마크다운)` 신규 API. 변경된 문단/셀의 텍스트만 원본 XML 안에서 in-place 치환하고 나머지 ZIP 엔트리는 바이트 그대로 보존. 미지원 편집(블록 추가/삭제, 표 구조 변경)은 원본을 건드리지 않고 `skipped[]`로 정직하게 보고하며, 패치 후 자동 재파싱 검증 리포트(`verification`)를 제공합니다.
696
-
697
- ```ts
698
- import { parse, patchHwpx } from "kordoc"
699
-
700
- const r = await parse(buf) // HWPX → 마크다운
701
- const edited = r.markdown.replace("개최 예정", "개최 완료") // LLM이 편집했다고 가정
702
- const res = await patchHwpx(new Uint8Array(buf), edited)
703
- // res.data — 서식 그대로, 텍스트만 바뀐 HWPX 바이트
704
- // res.applied / res.skipped / res.verification — 적용·미지원·검증 리포트
705
- ```
706
-
707
- - **🎯 "99.9% 정확도" 파서 대도약** — 실측 공문서 코퍼스 324건(정부 보도자료 + 서울시 결재문서 + 2014~2016 옛 문서) 자기참조 채점 기준:
708
-
709
- | 지표 | v2.9.1 | v3.0.0 |
710
- |------|--------|--------|
711
- | HWPX 텍스트 재현율 | 99.699% | **99.998%** |
712
- | HWPX 표 구조 정확일치 | 99.875% | **100%** (1,421표 · 중첩표 343 포함) |
713
- | PDF coverage | 97.013% | **99.16%** |
714
- | HWP5↔HWPX 쌍 유사도 | — | **99.94%** |
715
-
716
- 중첩표 구조 보존(`IRCell.blocks`), 한컴 PUA 매핑, HWP5 이미지 추출(0→90건), 자동번호 카운터, 머리말/각주 정밀 처리 등. 채점기·코퍼스 수집기·게이트는 `bench/`에 포함 — `node bench/score.mjs`로 재현 가능.
717
-
718
- ## v2.9.0 변경사항
719
-
720
- - **📊 PDF 텍스트 품질 신호 + OCR 필요 판정** — PDF는 텍스트층이 있어도 ToUnicode/CMap 이 깨져 한글이 깨진 글리프로 떨어지거나 NUL 등 제어문자가 섞이는 경우가 많습니다. `parsePdf` 결과에 페이지별 품질 신호(`pageQuality`)와 문서 요약(`qualitySummary`)을 추가 — `needsOcr`/`ocrReason` 으로 OCR 큐 자동 라우팅이 가능. (당시에는 신호만 노출 — **v4.2.0부터 내장 OCR 탑재**, 이 신호가 내장 OCR의 자동 발동 조건으로 쓰입니다.) 전국 지자체 주요업무계획 PDF 190건(45,399쪽) 대량 처리 중 도출. (아래 [PDF 텍스트 품질 신호](#pdf-텍스트-품질-신호-v290) 참고)
721
-
722
- ## v2.8.0 변경사항
723
-
724
- - **🎨 `markdownToHwpx` 테마 옵션** (#31) — 헤딩/본문/인용/표 헤더 셀의 텍스트 색상과 표 헤더 굵기를 옵션으로 지정 가능. 새 export 타입 `HwpxTheme`, `MarkdownToHwpxOptions`. 옵션 미지정 시 기존과 동일하게 검정으로 출력(baseline 백워드 호환).
725
-
726
- <details>
727
- <summary>v2.7.2 변경사항</summary>
728
-
729
- - **🐛 HWPX 양식 채우기 빈 셀 버그픽스** (#29, #30) — 한컴오피스에서 HWP→HWPX 로 변환한 양식의 빈 값 셀(`<hp:run>` 이 `<hp:t>` 자식 없이 self-closing)에 값이 삽입되지 않으면서 결과에는 성공으로 보고되던 false-positive 수정. `setRunText` 가 `<hp:t>` 없는 run 에 새로 생성해 텍스트 삽입. 기여: @amnotyoung
730
-
731
- </details>
732
-
733
- <details>
734
- <summary>v2.7.1 변경사항</summary>
735
-
736
- - **🕰️ HWP 3.0 (구버전) 파서 추가** — 1996~2002년 한컴이 쓰던 단일 binary 포맷 (`"HWP Document File V3.00"` 시그니처) 텍스트 추출. 기존 kordoc 이 거부하던 구버전 판결문/공문서 등이 검색 인덱싱 가능. 상용조합형(johab) → 유니코드 + 5,893개 한자/기호 lookup. 표 cell / 머리말 / 각주 의 nested paragraph 재귀 추출. [@edwardkim/rhwp](https://github.com/edwardkim/rhwp) 의 Rust 구현을 TypeScript 로 포팅.
737
-
738
- </details>
739
-
740
- <details>
741
- <summary>v2.5.0 변경사항</summary>
742
-
743
- - **🏛️ macOS 한컴오피스 호환 HWPX 생성** (#4) — `markdownToHwpx()` 가 만든 HWPX 가 macOS 한컴에서 "파일이 깨졌다"며 거부되던 문제 해결. 테이블 XML 을 최소 스켈레톤에서 완전 스펙 형태로 재작성 — `<hp:tbl>` 필수 속성 10종 + `<hp:sz>`/`<hp:pos>`/`<hp:outMargin>`/`<hp:inMargin>`, `<hp:tc>` 안에 `<hp:subList>` 래퍼 + `<hp:cellAddr>`/`<hp:cellSpan>`/`<hp:cellSz>`/`<hp:cellMargin>`, paragraph 래핑. `Preview/PrvText.txt` 추가 + `borderFill` id=1(SOLID 0.12mm) 추가.
744
- - **🔓 HWP 5.x 배포용 문서 COM fallback** (#25) — `.hwp` 바이너리에서 "이 문서는 상위 버전의 배포용 문서입니다..." 경고 플레이스홀더만 나오는 케이스에서, Windows + 한컴오피스 환경이면 자동으로 `HWPFrame.HwpObject` COM API 로 재시도. v2.4.0 의 HWPX DRM fallback 인프라를 `.hwp` 에도 확장.
745
-
746
- </details>
747
-
748
- <details>
749
- <summary>v2.4.0 변경사항</summary>
750
-
751
- - **🔓 HWPX DRM 배포용 문서 자동 추출** — 공공기관 배포용 DRM이 걸린 HWPX 파일을 한컴 오피스 COM API로 자동 텍스트 추출. `manifest.xml`에서 암호화 감지 → `HWPFrame.HwpObject`의 `GetPageText`로 페이지별 추출 → Markdown 변환. Windows + 한컴 오피스 설치 환경에서 별도 설정 없이 동작.
752
-
753
- </details>
754
-
755
- <details>
756
- <summary>v2.3.0 변경사항</summary>
757
-
758
- - **📄 HWPML 2.x 파서 추가** — XML 기반 한컴 문서(`.hwp` XML 방식) 파싱 지원. `npx kordoc <file.hwp>`에서 `지원하지 않는 파일 형식` 오류가 나던 XML 기반 공문서를 이제 Markdown으로 변환할 수 있습니다. HWP 5.x 바이너리와 자동 구분(XML 시그니처 감지).
759
- - **🧩 중첩 테이블 마커** — HWPX/HWP5에서 셀 내부 중첩 테이블이 있던 위치에 `[중첩 테이블 #N]` 마커 삽입. 큰 중첩 테이블(≥3행 + ≥2열)은 별도 블록으로 분리, 작은 것은 셀 내 평탄화. HWP5는 기존에 내용이 완전히 손실되던 것을 마커로 복구.
760
- - **🖼️ HWPX 이미지 추출 버그 수정** — `binaryItemIDRef`가 확장자 없이(`"image1"`) 저장된 HWPX에서 이미지 추출이 실패하던 문제 해결. ZIP 내 파일명 regex 매칭으로 복원.
761
- - **📄 PDF 머리글/바닥글 감지 개선** — 텍스트 반복 패턴 + y좌표 클러스터링 하이브리드. 페이지마다 달라지는 동적 머리글(챕터명 등)도 위치 기반으로 감지. 감지 영역 10% → 12%로 확장.
762
-
763
- </details>
764
-
765
- <details>
766
- <summary>v2.2.4 변경사항</summary>
767
-
768
- - **📝 양식 자동 채우기 (Form Filler)** — 공문서 양식 템플릿에 값을 자동으로 채워넣습니다. 라벨-값 셀 패턴, 체크박스(`□`→`☑`), 괄호 빈칸(`일반( )통`→`일반(3)통`), 어노테이션(`(한자:)`→`(한자:金)`) 지원.
769
- - **🏛️ HWPX 원본 서식 보존 모드** — `fillHwpx()`로 HWPX XML을 직접 조작하여 글꼴, 크기, 정렬 등 원본 서식 100% 유지한 채 값만 교체.
770
- - **📊 병합 셀 HTML 테이블 출력** — `colspan`/`rowspan`이 있는 복잡한 표를 GFM 대신 HTML `<table>`로 출력하여 구조 보존.
771
- - **🔧 markdownToHwpx 서식 강화** — 역변환 시 heading/bold/italic/table 등 서식 지원 대폭 개선.
772
- - **🤖 MCP fill_form 도구** — AI 에이전트가 양식을 직접 채울 수 있는 새 MCP 도구 추가 (총 8개).
773
-
774
- </details>
775
-
776
- <details>
777
- <summary>v2.2.1 변경사항</summary>
778
-
779
- - **🔧 마크다운 렌더링 개선** — GFM 특수문자(`~`) 이스케이프로 취소선 오해석 방지, 테이블 셀 내 `|` 문자 이스케이프, 중첩 테이블 텍스트 구분자 `|` → `/` 변경으로 GFM 파서 충돌 방지.
780
- - **📝 문단 간격 정상화** — paragraph 블록 사이 빈 줄 삽입으로 마크다운에서 별도 문단으로 렌더링.
781
-
782
- </details>
783
-
784
- <details>
785
- <summary>v2.2.0 변경사항</summary>
786
-
787
- - **🛡️ 보안 강화 7건** — XLSX/DOCX Billion Laughs(XXE) 방지, Watch SSRF 리다이렉트·10진수IP·symlink 차단, HWP5 lenient decompression bomb 방지, CFB FAT 섹터 상한, buildTableDirect 메모리 폭주 방지.
788
- - **💥 Crash 방지** — `Math.min/max(...spread)` 스택 오버플로 수정 (15개소), Watch 동시 처리 제한(MAX_CONCURRENT=3).
789
- - **🐛 정확성 개선** — Levenshtein 동일 길이 유사도 1.0 버그 수정, MCP `parse_metadata` XLSX/DOCX 오분류 수정, PDF 폰트 크기 통계 메모리 최적화(40MB→~50엔트리).
790
- - **📦 품질** — CLI JSON Uint8Array base64 변환, `isPathTraversal` 합법적 파일명 오탐 수정.
791
-
792
- </details>
793
-
794
- <details>
795
- <summary>v2.1.0 변경사항</summary>
796
-
797
- - **📄 대형 HWPX 정부문서 파싱** — `<p>><run>><tbl>` 구조의 중첩 테이블 파싱 누락 수정.
798
- - **📰 PDF 2단 레이아웃 감지** — 다단 논문·보고서의 컬럼 구조를 감지하여 읽기 순서대로 추출.
799
- - **🛡️ 입력 검증 강화** — 폰트 크기 NaN/음수 가드, colSpan/rowSpan NaN 가드.
800
-
801
- </details>
802
-
803
- <details>
804
- <summary>v2.0 변경사항</summary>
805
-
806
- - **🔓 배포용(열람 제한) HWP 파싱 지원** — 관공서에서 배포용으로 잠근 HWP 파일도 이제 파싱됩니다. AES-128 ECB 복호화, 순수 JS 구현. [rhwp](https://github.com/edwardkim/rhwp)(MIT) 알고리즘 포팅.
807
- - **손상된 HWP 파일 복구** — 표준 CFB 모듈이 거부하는 파일을 직접 FAT/디렉토리 파싱으로 복구. rhwp LenientCfbReader 포팅.
808
- - **HWP5 각주/미주/하이퍼링크 추출** — 각주 본문 텍스트 연결, 하이퍼링크 URL 추출 및 XSS 살균.
809
- - **HWPX 표 병합 밀림 수정** — colspan/rowspan 그리드 계산 버그 수정.
810
- - **보안 강화** — CFB 섹터 크기 검증, sanitizeHref 3중 경로 일관 적용.
811
-
812
- </details>
813
-
814
- <details>
815
- <summary>v1.8.0 변경사항</summary>
816
-
817
- - **XLSX 파서 추가** — Excel 스프레드시트 파싱. 공유 문자열, 병합 셀, 다중 시트 지원. 시트별 heading + table 블록 생성.
818
- - **DOCX 파서 추가** — Word 문서 파싱. 스타일 기반 heading, 번호 매기기(리스트), 각주, 하이퍼링크, 이미지 추출, vMerge/gridSpan 테이블 병합.
819
- - **파싱 품질 대폭 개선** — PDF/HWPX/HWP5/XLSX 전 포맷 품질 점수 73→93점.
820
- - **프로덕션 리뷰 17건 수정** — CLI `--no-header-footer` 플래그 반전 버그, MCP XLSX/DOCX 확장자 허용, ZIP bomb 보호 공유 유틸화, href XSS 살균 강화, PDF timeout 타이머 정리, HWP5 BinData O(n) 최적화, cluster indexOf O(n²)→O(n), SSRF IPv6 차단 등.
821
-
822
- </details>
823
-
824
- <details>
825
- <summary>v1.7.x 변경사항</summary>
826
-
827
- - **이미지 추출 (HWP/HWPX)** — ZIP 엔트리와 HWP5 BinData 스트림에서 바이너리 이미지 추출.
828
- - **부분 파싱 (Graceful Degradation)** — 개별 페이지 실패가 전체 파싱을 중단하지 않음.
829
- - **진행률 콜백** — `onProgress` 콜백. CLI에서 `[3/15 pages]` 형태 표시.
830
- - **파일 경로 직접 입력** — `parse("path/to/file.hwp")` 문자열 오버로드.
831
- - **PDF 머리글/바닥글 필터링** — `removeHeaderFooter` 옵션.
832
- - **보안 강화** — ZIP bomb 추적, SSRF 방지, XSS 방어, 널바이트 감지, PDF 타임아웃.
833
- - **pdfjs-dist v5 호환** — constructPath 연산자 형식 변경 대응.
834
-
835
- </details>
836
-
837
- <details>
838
- <summary>v1.6.1 수정사항</summary>
839
-
840
- - **HWP5 테이블 셀 오프셋 수정** — LIST_HEADER 파싱 시 2바이트 오프셋 밀림으로 rowAddr를 colSpan으로 잘못 읽던 치명적 버그 수정. 3열 테이블이 6열로 뻥튀기되던 문제 해결. colAddr/rowAddr 기반 직접 배치로 병합 테이블 정확도 향상.
841
- - **HWP5 TAB 제어문자 수정** — TAB(0x0009) 인라인 컨트롤의 14바이트 확장 데이터 스킵 누락으로 `࣐Ā` 쓰레기 문자가 출력되던 버그 수정.
842
-
843
- </details>
844
-
845
- <details>
846
- <summary>v1.6.0 기능</summary>
847
-
848
- - **클러스터 기반 테이블 감지 (PDF)** — 선 없는 PDF에서 텍스트 정렬 패턴으로 테이블 구조 추론. baseline 그룹핑 + X좌표 클러스터링으로 2열 이상 테이블 감지. 선 기반 감지가 실패한 경우의 중간 계층 fallback.
849
- - **한국어 특수 테이블 감지** — `구분/항목/종류/기준` 등 한국 공문서 key-value 패턴을 자동으로 2열 테이블로 변환.
850
- - **한국어 어절 끊김 복원** — PDF 셀 내 한글 문자별 렌더링으로 인한 미세 갭 처리 개선. 셀 줄바꿈 병합 임계값 8자로 확장, 1글자 조사 자동 연결.
851
- - **빈 테이블 필터링** — 장식용 선에서 생긴 빈 테이블 자동 제거.
852
-
853
- </details>
854
-
855
- <details>
856
- <summary>v1.5.0 기능</summary>
857
-
858
- - **선 기반 테이블 감지 (PDF)** — OpenDataLoader 핵심 알고리즘 포팅. PDF 그래픽 명령에서 수평/수직 선을 추출하고, 교차점으로 그리드 구성, bbox overlap으로 텍스트→셀 매핑. colspan/rowspan 자동 감지. 선 없는 PDF는 기존 휴리스틱 fallback.
859
- - **IRBlock v2** — 6가지 블록 타입: `heading`, `paragraph`, `table`, `list`, `image`, `separator`. 새 필드: `bbox`, `style`, `pageNumber`, `level`, `href`, `footnoteText`.
860
- - **ParseResult v2** — `outline` (문서 구조), `warnings` (스킵된 요소, 숨김 텍스트) 필드 추가.
861
- - **PDF 개선** — XY-Cut 읽기 순서, 폰트 크기 기반 헤딩 감지, hidden text 필터링 (프롬프트 인젝션 방어), 모든 블록에 바운딩 박스.
862
- - **HWP5 개선** — CHAR_SHAPE 파싱, 스타일 기반 헤딩 감지, OLE/이미지 스킵 경고.
863
- - **HWPX 개선** — header.xml 스타일 파싱, 하이퍼링크/각주 추출.
864
- - **리스트 감지** — 테이블 뒤 번호 문단을 ordered list 블록으로 자동 변환.
865
- - **MCP 서버** — parse_document 응답에 `outline`, `warnings` 포함.
866
-
867
- </details>
868
-
869
- <details>
870
- <summary>v1.4.x 기능</summary>
871
-
872
- - **문서 비교 (Diff)** — IR 레벨 블록 비교로 신구대조표 생성. HWP↔HWPX 크로스 포맷 지원.
873
- - **양식 인식** — 공문서 테이블에서 label-value 쌍 자동 추출. 성명, 소속, 전화번호 등.
874
- - **구조화 파싱** — `IRBlock[]`과 `DocumentMetadata`에 직접 접근. 마크다운 넘어선 데이터 활용.
875
- - **페이지 범위** — `parse(buffer, { pages: "1-3" })` — 필요한 페이지만 빠르게. 한컴 저장본은 실제 쪽 번호 기준(`metadata.pageMode: "layout"`), 조판 캐시 없는 생성 파일은 섹션 근사(`"section"`).
876
- - **Markdown → HWPX** — 역변환. AI가 생성한 내용을 바로 공문서로.
877
- - **OCR** — 스캔/이미지 PDF 텍스트 추출. 내장 엔진(`ocr: true`, PP-OCRv5 korean) 또는 외부 프로바이더(Tesseract, Claude Vision 등).
878
- - **Watch 모드** — `kordoc watch ./수신함 -d ./변환결과 --webhook https://...`
879
- - **MCP 7개 도구** — parse_document, detect_format, parse_metadata, parse_pages, parse_table, compare_documents, parse_form
880
- - **에러 코드** — `"ENCRYPTED"`, `"ZIP_BOMB"`, `"IMAGE_BASED_PDF"` 등 구조화된 에러 핸들링
881
-
882
- </details>
883
-
884
- ---
885
-
886
- ## 설치
887
-
888
- ```bash
889
- npm install kordoc
890
- ```
891
-
892
- PDF 파싱(pdfjs-dist)·OCR(onnxruntime·sharp·pdfium) 등 선택 의존성은 **기본 설치**됩니다 (optionalDependencies).
893
- 설치 용량을 줄이려면 `npm install kordoc --omit=optional` 로 스킵할 수 있습니다 —
894
- 이 경우 PDF 파싱·OCR·PNG 래스터 등 일부 기능이 제한됩니다.
895
-
896
- `markdownToPdf`/`blocksToPdf`(인쇄 렌더)만 `puppeteer-core` 를 **선택적 peer 의존성**으로 쓰며
897
- 자동 설치되지 않습니다 — 쓰려면 `npm install puppeteer-core` 를 따로 하세요.
898
-
899
- ## 빠른 시작
155
+ ## 🚀 빠른 시작
900
156
 
901
157
  ### 문서 파싱
902
158
 
@@ -904,434 +160,372 @@ PDF 파싱(pdfjs-dist)·OCR(onnxruntime·sharp·pdfium) 등 선택 의존성은
904
160
  import { parse } from "kordoc"
905
161
  import { readFileSync } from "fs"
906
162
 
907
- const buffer = readFileSync("사업계획서.hwpx")
908
- const result = await parse(buffer)
163
+ const result = await parse(readFileSync("사업계획서.hwpx")) // 파일 경로 문자열도 받음
909
164
 
910
165
  if (result.success) {
911
- console.log(result.markdown) // 마크다운 텍스트
912
- console.log(result.blocks) // IRBlock[] 구조화 데이터
913
- console.log(result.metadata) // { title, author, createdAt, pageMode, ... }
914
- console.log(result.pages) // [{ pageNumber, markdown }] — 쪽 단위 본문 (v4.8)
166
+ result.markdown // 마크다운
167
+ result.blocks // IRBlock[] 구조화 데이터
168
+ result.metadata // { title, author, createdAt, pageMode, ... }
169
+ result.pages // [{ pageNumber, markdown }] 쪽 단위 본문
915
170
  }
916
171
  ```
917
172
 
918
- `pages` 는 블록에 쪽 번호가 붙는 포맷(HWP·HWPX·PDF, XLS(X)는 시트=한 쪽)에서만 나옵니다 —
919
- DOCX 처럼 쪽을 매기지 않는 포맷에서는 없는 사실을 만들지 않고 필드 자체를 생략합니다.
920
- 경계의 신뢰도는 `metadata.pageMode` (`"layout"` = 조판 캐시 기반 실제 쪽 /
921
- `"section"` = 섹션 근사)로 구분합니다.
173
+ - `pages` 는 블록에 쪽 번호가 붙는 포맷(HWP·HWPX·PDF, XLS(X)는 시트 = 한 쪽)에서만 나옵니다. 쪽을 매기지 않는 DOCX 에서는 필드를 생략합니다.
174
+ - 쪽 경계 신뢰도는 `metadata.pageMode` — `"layout"`(조판 캐시 기반 실제 쪽) / `"section"`(섹션 근사).
175
+
176
+ **파싱 옵션** (`parse(buffer, options)` · CLI 플래그)
177
+
178
+ | 옵션 | CLI | 설명 |
179
+ | --- | --- | --- |
180
+ | `pages` | `-p, --pages` | `"1-3"`·`"1,3,5-7"`·`[1, 5, 10]` — PDF·한컴 저장본은 실제 쪽, 조판 캐시 없으면 섹션 근사 |
181
+ | `ocr` | `--ocr` · `--ocr-force` | 기본: 스캔 쪽 + 큰 그림 속 글(모델 캐시 있을 때) · `true`: + 작은 그림·로고까지(모델 ~18MB 자동 다운로드) · `"force"`: 전 쪽 · `false`: 끔 · 함수: 외부 OCR |
182
+ | `formulaOcr` | `--formula-ocr` | PDF 수식 OCR(MFD+MFR, 모델 ~155MB) — 감지한 수식을 `$…$`·`$$…$$` 로 |
183
+ | `images` | `--no-images` | `false` 면 이미지 바이트를 싣지 않음(그림 자리 표시는 남김, PDF 는 PNG 인코딩 생략) |
184
+ | `plain` | `--plain` | 그림 자리 표시·링크 URL·밑줄·굵게 없이 글 위주 Markdown(제목·목록·표 구조 유지, `blocks` 는 그대로) |
185
+ | `htmlTables` | `--html-tables` | 모든 표를 HTML 로, 태그마다 한 줄씩 들여써서(첫 행 `<th>`) |
186
+ | `password` | `--password` | 열기 암호 문서(HWPX·HWP3·HWP5, 한컴 DRM 은 해당 없음) |
187
+ | `tables` | `--no-tables` | `false` 면 PDF 표 감지 끔(테두리 상자가 표로 잡혀 순서가 뒤집히는 2단 시험지 등) |
188
+ | `removeHeaderFooter` | `--no-header-footer` | PDF 머리글/바닥글 제거(기본 켬, 3쪽 이상) |
189
+ | `keepTrailingEmptyCols` | `--keep-empty-cols` | 표 오른쪽 끝 빈 열(서식 입력란) 보존 |
190
+ | `keepEmptyParagraphs` | `--keep-empty-paragraphs` | 빈 문단 보존 — 원문 문단 수 = 줄 수(HWPX) |
191
+ | `includeFieldPlaceholders` | `--include-field-placeholders` | 미기입 누름틀 안내문도 출력(HWPX·HWP5) |
192
+ | `dedupeRunningHeaders` | `--dedupe-headers` | HWP5 레이아웃 표의 쪽마다 반복된 러닝 헤더 제거(붙임별 재번호도 지울 수 있어 opt-in) |
193
+ | `inlineImages` | `--inline-images` | 이미지를 base64 data URI 로 인라인(BMP→PNG, HWP5) |
194
+ | `classifyTables` | — | 표를 의미표/레이아웃/불확실로 분류해 `IRTable.classification` 에 |
195
+ | `onProgress` | — | 진행률 콜백 `(current, total)` |
922
196
 
923
197
  ### 문서 비교 (신구대조표)
924
198
 
925
199
  ```typescript
926
200
  import { compare } from "kordoc"
927
201
 
928
- const diff = await compare(구버전Buffer, 신버전Buffer)
202
+ const diff = await compare(구버전Buffer, 신버전Buffer) // HWP ↔ HWPX 교차 비교 가능
929
203
  // diff.stats → { added: 3, removed: 1, modified: 5, unchanged: 42 }
930
- // diff.diffs → BlockDiff[] (테이블은 셀 단위 diff 포함)
204
+ // diff.diffs → BlockDiff[] (표는 셀 단위 diff 포함)
931
205
  ```
932
206
 
933
- HWP vs HWPX 크로스 포맷 비교도 가능합니다.
934
-
935
- ### 양식 필드 추출
207
+ ### 양식 필드 추출 · 채우기
936
208
 
937
209
  ```typescript
938
- import { parse, extractFormFields } from "kordoc"
939
-
940
- const result = await parse(buffer)
941
- if (result.success) {
942
- const form = extractFormFields(result.blocks)
943
- // form.fields → [{ label: "성명", value: "홍길동", row: 0, col: 0 }, ...]
944
- // form.confidence → 0.85
945
- }
946
- ```
947
-
948
- ### 양식 자동 채우기
949
-
950
- ```typescript
951
- import { fillForm } from "kordoc"
210
+ import { parse, extractFormFields, fillForm } from "kordoc"
952
211
  import { readFileSync, writeFileSync } from "fs"
953
212
 
954
- const template = readFileSync("신청서.hwpx")
213
+ const r = await parse(buffer)
214
+ if (r.success) {
215
+ const form = extractFormFields(r.blocks)
216
+ // form.fields → [{ label: "성명", value: "홍길동", row: 0, col: 0 }, ...], form.confidence → 0.85
217
+ }
955
218
 
956
- // HWPX 원본 서식 보존 모드 — 글꼴, 크기, 정렬 100% 유지
957
- const result = await fillForm(template, {
958
- 성명: "홍길동",
959
- 주민등록번호: "900101-1234567",
960
- 주소: "서울특별시 광진구 능동로 120",
219
+ // HWPX 원본 서식 보존 모드 — 글꼴·크기·정렬 유지
220
+ const filled = await fillForm(readFileSync("신청서.hwpx"), {
221
+ 성명: "홍길동", 주민등록번호: "900101-1234567", 주소: "서울특별시 광진구 능동로 120",
961
222
  }, "hwpx-preserve")
962
-
963
- writeFileSync("신청서_작성완료.hwpx", Buffer.from(result.output as ArrayBuffer))
964
- // result.fill.filled → [{ label: "성명", value: "홍길동" }, ...]
965
- // result.fill.unmatched → 매칭 실패한 키 목록
223
+ writeFileSync("신청서_작성완료.hwpx", Buffer.from(filled.output as ArrayBuffer))
224
+ // filled.fill.filled → 채운 필드, filled.fill.unmatched → 매칭 실패한 키
966
225
  ```
967
226
 
968
- ### 내장 정부 표준 기안문 서식 + 누름틀 채우기
227
+ ### 내장 표준 기안문 서식 + 누름틀 채우기
969
228
 
970
- 「행정 효율과 협업 촉진에 관한 규정 시행규칙」 별지 서식 기반 **표준 기안문 HWPX가
971
- 패키지에 내장**되어, 파일 없이 이름만으로 실물 배치 품질의 공문서를 만들 수 있습니다
972
- (서식 자산: [rhwp](https://github.com/edwardkim/rhwp) tools/forms, MIT — THIRD_PARTY/rhwp-forms.txt):
229
+ 표준 기안문 서식(「행정 효율과 협업 촉진에 관한 규정 시행규칙」 별지)이 패키지에 들어 있어 이름만으로 공문서를 만듭니다. 서식 출처: [rhwp](https://github.com/edwardkim/rhwp) (MIT).
973
230
 
974
231
  | 이름 | 서식 | 용도 | 누름틀 |
975
232
  |------|------|------|--------|
976
233
  | `gian` (일반기안문) | 별지 제1호서식 | 대외 시행문·협조문 | 23곳 — 행정기관명·수신자·경유·제목·본문·붙임·발신명의·기안자·검토자·결재권자·시행번호 등 |
977
- | `gian-simple` (간이기안문) | 별지 제2호서식 | 내부결재 보고서·계획서 (결재란 표) | 13곳 — 생산등록번호·결재직위1~4·제목·요약설명·작성일 등 |
234
+ | `gian-simple` (간이기안문) | 별지 제2호서식 | 내부결재 보고서·계획서(결재란 표) | 13곳 — 생산등록번호·결재직위1~4·제목·요약설명·작성일 등 |
978
235
 
979
236
  ```bash
980
- npx kordoc fill --list-templates # 내장 서식 목록 + 필드 나열
237
+ npx kordoc fill --list-templates # 내장 서식 목록 + 필드
981
238
  npx kordoc fill --template gian -j 값.json -o 기안문.hwpx
982
- npx kordoc fill templates:간이기안문 -f '제목=…' -o 보고.hwpx # 위치 인자 표기도 동일
239
+ npx kordoc fill templates:간이기안문 -f '제목=…' -o 보고.hwpx # 위치 인자 표기도 같음
983
240
  ```
984
241
 
985
- 채우기 엔진이 **누름틀(CLICK_HERE 필드)을 이름으로 정확 매칭해 우선 채우고**, 남은
986
- 키는 기존 라벨 매칭으로 처리합니다 — 누름틀이 있는 어떤 HWPX 서식(메일머지 양식 등)에도
987
- 동작합니다. `본문`처럼 `\n`이 든 값은 문단 내 줄바꿈으로 들어가고, 안내문과 동일한 값을
988
- 채워도 유실되지 않으며, 원본 charPr(서식)은 그대로 보존됩니다. API로는
989
- `extractClickHereFields(buf)`(필드 조사)와 `readBuiltinTemplate(resolveBuiltinTemplate("gian")!)`
990
- (서식 로드) → `fillHwpx(buf, 값)` 조합입니다. MCP `fill_form` 도구도 `template` 파라미터로
991
- 같은 서식을 씁니다.
242
+ - 누름틀 이름으로 먼저 채우고 남은 키는 라벨로 찾습니다 — 누름틀이 있는 어떤 HWPX 서식에도 동작.
243
+ - 여러 줄 값은 문단 안 줄바꿈으로 들어가고, 원본 서식은 그대로입니다.
244
+ - API: `extractClickHereFields` → `fillHwpx(buf, 값)`. MCP `fill_form` 은 `template` 파라미터로 같은 서식을 씁니다.
992
245
 
993
- ### HWPX 생성 (역변환)
246
+ ### HWPX 생성 (Markdown → HWPX)
994
247
 
995
248
  ```typescript
996
249
  import { markdownToHwpx } from "kordoc"
997
250
 
998
- const hwpxBuffer = await markdownToHwpx("# 제목\n\n본문 텍스트\n\n| 이름 | 직급 |\n| --- | --- |\n| 홍길동 | 과장 |")
999
- writeFileSync("출력.hwpx", Buffer.from(hwpxBuffer))
251
+ const hwpx = await markdownToHwpx("# 제목\n\n본문\n\n| 이름 | 직급 |\n| --- | --- |\n| 홍길동 | 과장 |")
1000
252
 
1001
- // display math block은 HWPX native 수식(<hp:equation>)으로 생성됩니다.
1002
- // 초기 지원 범위는 \frac, \sqrt, 첨자/위첨자, Greek, 적분/극한,
1003
- // 화살표, 관계 연산자, matrix 계열의 제한된 LaTeX-like subset입니다.
1004
- const withEquation = await markdownToHwpx("피타고라스\n\n$$a^2 + b^2 = c^2$$")
253
+ // display math → HWPX 네이티브 수식(<hp:equation>) — \frac·\sqrt·첨자·Greek·적분/극한·화살표·관계 연산자·matrix 의 LaTeX-like subset
254
+ await markdownToHwpx("피타고라스\n\n$$a^2 + b^2 = c^2$$")
1005
255
 
1006
256
  // 공문서 모드 — 항목부호 8단계 + 내어쓰기 + 공식 여백/명조 자동
1007
- const gongmun = await markdownToHwpx("1. 추진배경\n - 세부 항목\n2. 추진계획", {
1008
- gongmun: { preset: "보고서" }, // official | report | plan | notice | minutes | gaejosik | press | ministry(업무보고)
1009
- })
257
+ // preset: official | report | plan | notice | minutes | gaejosik | press | ministry(업무보고) | bangchim(서울방침)
258
+ await markdownToHwpx("1. 추진배경\n - 세부 항목\n2. 추진계획", { gongmun: { preset: "보고서" } })
1010
259
 
1011
- // 정부 표준 개조식 보고서 (v4.0) — 표지·목차(장식 배너)·로마숫자 장헤더·
1012
- // 본문 제목박스·쪽번호("- 1 -", 표지·목차 제외)까지 실측 정부 양식 그대로
1013
- const report = await markdownToHwpx(md, {
260
+ // 정부 표준 개조식 보고서 — 표지·목차(장식 배너)·로마숫자 장헤더·본문 제목박스·쪽번호("- 1 -", 표지·목차 제외)
261
+ await markdownToHwpx(md, {
1014
262
  gongmun: {
1015
263
  preset: "개조식",
1016
264
  cover: { org: "기관명", date: "2026. 7. 11." },
1017
- toc: true, // h2 목록 → Ⅰ Ⅱ Ⅲ 목차 (개조식 기본 켜짐)
1018
- approval: ["담당", "팀장", "과장"], // 결재란 (선택)
1019
- pageNumbers: true, // 쪽번호 (개조식·보고서·계획서 기본 켜짐)
1020
- endMark: false, // 본문 끝 "끝." (기안문 기본 켜짐)
265
+ toc: true, // h2 목록 → Ⅰ Ⅱ Ⅲ 목차 (개조식 기본 켬)
266
+ approval: ["담당", "팀장", "과장"], // 결재란 (선택)
267
+ pageNumbers: true, // 쪽번호 (개조식·보고서·계획서 기본 켬)
268
+ endMark: false, // 본문 끝 "끝." (기안문 기본 켬)
1021
269
  },
1022
270
  })
1023
- // 표는 실측 정부 문법 자동 적용: 헤더 음영+bold+하변 이중선, 외곽 0.4mm 위계,
1024
- // 라벨열 음영, 내용 비례 열폭(수치 열 실폭 고정), 본문폭보다 좁게 + 우측 배치
1025
271
  ```
1026
272
 
1027
- CLI로도: `kordoc generate 보고서.md -o 보고서.hwpx --preset 개조식 --org 기관명 --approval 담당,팀장,과장`
1028
- (`--toc/--no-toc` `--cover/--no-cover` `--page-numbers` `--end-mark` `--no-body-title-box` `--fonts` `--sizes`)
273
+ - 표는 정부 문서 관행(머리행 음영·이중 하변·내용 비례 열폭)을 자동 적용합니다. 테마·참조 문서 표 서식(`hwpxToProfile`)도 받습니다.
274
+ - CLI: `kordoc generate 보고서.md -o 보고서.hwpx --preset 개조식 --org 기관명 --approval 담당,팀장,과장`
1029
275
 
1030
- ### 레이아웃 보존 렌더 (HWPX → SVG)
276
+ ### 레이아웃 보존 렌더
1031
277
 
1032
- 한컴이 HWPX에 저장하는 조판 캐시(줄 좌표·셀 그리드·개체 앵커)를 그대로 SVG 절대배치로
1033
- 그립니다. 조판 엔진 없이 빠르고, 서버에 한컴 설치 없이 원본 모양 미리보기를 만들 수
1034
- 있습니다. 다페이지 세로 스택·검색어 형광펜·그리기 도형 지원(v3.14~15). 조판 캐시가 없는
1035
- 파일(`markdownToHwpx` 산출물·AI 생성본·편집본)은 `reflow: true`를 주면 **순수 TS reflow
1036
- 엔진**이 직접 조판합니다(v3.15). 수식 개체는 미지원.
278
+ 한컴이 HWPX 에 저장한 조판 캐시로 원본 모양을 그대로 그립니다(서버에 한컴 불필요). 캐시가 없는 생성본은 내장 reflow 엔진이 조판합니다. 수식 개체는 아직 미지원.
1037
279
 
1038
280
  ```typescript
1039
- import { renderHwpxToSvg } from "kordoc"
281
+ import { renderHwpxToSvg, renderDocument, extractRenderedRegions } from "kordoc"
1040
282
 
1041
283
  const r = await renderHwpxToSvg(readFileSync("결재문서.hwpx"), { highlights: ["예산"] })
1042
- writeFileSync("결재문서.svg", r.svg)
1043
- // r.width/r.height (pt), r.pageCount, r.stats { texts, images, tables }, r.warnings
284
+ // r.svg, r.width/r.height (pt), r.pageCount, r.stats { texts, images, tables }, r.warnings
285
+ const g = await renderHwpxToSvg(generatedHwpx, { reflow: true }) // 조판 캐시 없는 생성본
1044
286
 
1045
- const g = await renderHwpxToSvg(generatedHwpx, { reflow: true }) // 조판 캐시 없는 생성본
1046
-
1047
- // 통합 렌더 — HWPX·HWP(5.x) 모두, 페이지별 PNG + 표 영역 crop (v4.13, HWP v4.13.1)
287
+ // 통합 렌더 — HWPX·HWP(5.x), 페이지별 PNG + 표 영역 crop
1048
288
  const { scene, assets } = await renderDocument("결재문서.hwp", { format: "png", pages: "1-2" })
1049
289
  const crops = await extractRenderedRegions("결재문서.hwp", { types: ["table"] })
1050
290
  ```
1051
291
 
1052
- CLI로도: `kordoc render 결재문서.hwpx -o 결재문서.svg` — 조판 캐시 없는 문서는 기본으로
1053
- reflow 조판되며 `--no-reflow`로 끌 수 있습니다 (`--highlight 예산,집행` 지원),
1054
- 연속 렌더는 `kordoc render-worker`(stdin NDJSON, 미리보기 앱 연동용)
292
+ CLI: `kordoc render 결재문서.hwpx -o 결재문서.svg` (`--highlight 예산`, `--no-reflow`). 미리보기 앱 연동용 상주 렌더는 `kordoc render-worker`.
1055
293
 
1056
- ### 글자만 필요할 때 (이미지 끄기·상주 파싱 워커)
294
+ ### 대량 변환 — 상주 파싱 워커
1057
295
 
1058
296
  ```typescript
1059
- const result = await parse(buffer, { images: false }) // 이미지 바이트 없이 (그림 자리 표시는 남김)
1060
- const plain = await parse(buffer, { plain: true, htmlTables: true }) // 글 위주 + 모든 표를 들여쓴 HTML 로
297
+ await parse(buffer, { images: false }) // 이미지 바이트 없이
298
+ await parse(buffer, { plain: true, htmlTables: true }) // 글 위주 + 모든 표를 HTML 로
1061
299
  ```
1062
300
 
1063
- CLI 는 `kordoc 문서.pdf --format json --no-images`. 대량 변환은 `kordoc parse-worker` 를 띄워 두고
1064
- stdin 으로 한 줄씩 요청합니다 (한 줄 = 한 JSON).
301
+ `kordoc parse-worker` 는 프로세스를 띄워 둔 채 stdin JSON 한 줄마다 한 줄로 답합니다(파일마다 node 를 새로 띄우지 않음).
1065
302
 
1066
303
  ```text
1067
- 시작 {"ready":true,"version":"4.15.0","protocol":1}
304
+ 시작 {"ready":true,"version":"4.16.0","protocol":1}
1068
305
  요청 {"id":1,"file":"문서.hwpx","images":false,"ocr":"off"}
1069
306
  응답 {"id":1,"rss":183500800,"result":{ …--format json 과 같은 결과, 실패도 success:false 로… }}
1070
307
  종료 {"cmd":"quit"} (또는 stdin 닫기)
1071
308
  ```
1072
309
 
1073
- `ocr` 은 `"off"`·`"auto"`(OCR 필요 페이지만)·`"force"`, 그 밖에 `formulaOcr`·`password` 를 받습니다.
1074
-
1075
- ### 페이지 범위 지정
1076
-
1077
- ```typescript
1078
- const result = await parse(buffer, { pages: "1-3" }) // 1~3 페이지만
1079
- const result = await parse(buffer, { pages: [1, 5, 10] }) // 특정 페이지
1080
- ```
310
+ 요청은 `ocr`(`"off"`·`"auto"`·`"force"`)·`formulaOcr`·`password` 를 받고, 응답의 `rss`(메모리)로 워커 교체 시점을 정하면 됩니다.
1081
311
 
1082
- ### OCR (스캔/이미지 PDF) — 내장 엔진 (v4.2.0+)
312
+ ### OCR (스캔·이미지 PDF)
1083
313
 
1084
314
  ```typescript
1085
- // 내장 OCR (PP-OCRv5 korean, 첫 사용 시 모델 ~18MB 자동 다운로드)
1086
- const result = await parse(buffer, { ocr: true }) // OCR 필요 페이지만 자동 판정
1087
- const result = await parse(buffer, { ocr: "force" }) // 전 페이지 강제 OCR
1088
- ```
1089
-
1090
- - **API 키·외부 서비스 불필요** — det(선 검출)+rec(CTC 인식) ONNX 를 로컬 CPU 로 추론합니다
1091
- (PaddlePaddle 공식 변환본, Apache-2.0 / 한국어 사전 11,945자 — 완성형 한글 11,172자 전량 + 자모·라틴·기호).
1092
- - **페이지 단위 정밀 적용** — 텍스트층이 없는 스캔 페이지, ToUnicode 가 깨진 페이지
1093
- (`needsOcr` 신호)만 OCR 하고 정상 페이지의 파싱 결과는 그대로 유지합니다.
1094
- - **표 복원** — OCR 라인 좌표를 기존 블록 파이프라인(xy-cut 읽기 순서 + 클러스터 표
1095
- 감지)에 태우므로 스캔본에서도 표 구조가 복원됩니다.
1096
- - 외부 OCR 을 쓰고 싶으면 종전대로 프로바이더 함수를 전달하세요:
1097
-
1098
- ```typescript
1099
- const result = await parse(buffer, {
1100
- ocr: async (pageImage, pageNumber, mimeType) => {
1101
- return await myOcrService.recognize(pageImage) // Claude Vision, Tesseract 등
1102
- }
315
+ await parse(buffer, { ocr: true }) // OCR 필요 쪽 + 그림 속 글 (PP-OCRv5 korean, 첫 사용 시 모델 ~18MB 다운로드)
316
+ await parse(buffer, { ocr: "force" }) // 전 쪽 강제
317
+ await parse(buffer, { // 외부 OCR (Claude Vision, Tesseract 등)
318
+ ocr: async (pageImage, pageNumber, mimeType) => myOcrService.recognize(pageImage),
1103
319
  })
1104
320
  ```
1105
321
 
1106
- ### PDF 텍스트 품질 신호 (v2.9.0+)
322
+ - API 키 없이 로컬 CPU 로 읽습니다(PP-OCRv5 korean ONNX — 완성형 한글 11,172자 전량).
323
+ - 텍스트층이 없거나 깨진 쪽만 OCR 하고, 스캔본의 표 구조도 복원합니다.
324
+ - 모델 관리: `kordoc models --status` (폐쇄망은 `--export`/`--import`).
1107
325
 
1108
- PDF는 텍스트층이 있어도 ToUnicode/CMap이 깨졌거나 NUL 등 제어문자가 섞이는 경우가 많다. `parsePdf` 결과는 페이지별 품질 신호를 함께 반환한다.
326
+ ### PDF 텍스트 품질 신호
327
+
328
+ `parsePdf` 는 쪽별 품질 신호를 줍니다 — 텍스트층이 깨진 쪽을 골라 OCR 로 보낼 수 있습니다.
1109
329
 
1110
330
  ```typescript
1111
331
  const r = await parsePdf(buffer)
1112
- if (r.success && r.qualitySummary?.needsOcr) {
1113
- // 내장 OCR 로 재시도 (v4.2.0+) — 또는 외부 OCR 큐로 라우팅
1114
- const retried = await parse(buffer, { ocr: true })
1115
- }
1116
-
1117
- // 페이지 단위 신호
1118
- for (const p of r.pageQuality ?? []) {
1119
- if (p.needsOcr) console.log(`p${p.page} 검토 필요: ${p.ocrReason}`)
1120
- }
332
+ if (r.success && r.qualitySummary?.needsOcr) await parse(buffer, { ocr: true }) // 또는 외부 OCR 큐로
333
+ for (const p of r.pageQuality ?? []) if (p.needsOcr) console.log(`p${p.page} 검토 필요: ${p.ocrReason}`)
1121
334
  ```
1122
335
 
1123
- 신호 키: `textChars`, `hangulRatio`, `controlCharRatio`, `replacementCharRatio`, `puaRatio` / `needsOcr` (페이지·문서 단위) / `ocrReason` (`low_text` | `high_pua` | `high_control` | `high_replacement` | `garbled_hangul` | `vector_text`). `vector_text` 는 글자를 곡선으로 그려 텍스트층에 글이 없는 쪽입니다(v4.14.4).
336
+ 신호: `textChars`·`hangulRatio`·`controlCharRatio`·`replacementCharRatio`·`puaRatio`, `needsOcr`, `ocrReason`(`low_text`·`high_pua`·`high_control`·`high_replacement`·`garbled_hangul`·`vector_text`).
337
+
338
+ ---
1124
339
 
1125
- ## CLI
340
+ ## 💻 CLI
1126
341
 
1127
342
  ```bash
1128
- npx kordoc 사업계획서.hwpx # 터미널 출력
1129
- npx kordoc 보고서.hwp -o 보고서.md # 파일 저장
1130
- npx kordoc *.pdf -d ./변환결과/ # 일괄 변환
1131
- npx kordoc 검토서.hwpx --format json # JSON (blocks + pages + metadata 포함)
1132
- npx kordoc 검토서.pdf --format chunks # RAG용 구조 청크 JSON (breadcrumb + 표 독립 청크)
1133
- npx kordoc 보고서.hwpx --pages 1-3 # 페이지 범위
1134
- npx kordoc 스캔본.pdf --ocr # 내장 OCR (필요 페이지 자동 판정, --ocr-force로 전 페이지)
1135
- npx kordoc 잠긴문서.hwpx --password '암호' # 열기 암호가 걸린 HWPX/HWP3/HWP5
1136
- npx kordoc 시험지.pdf --no-tables # PDF 표 감지 끄기 (2단 시험지 등 읽기순 우선)
1137
- npx kordoc fill 신청서.hwpx -f '성명=홍길동,주소=서울' -o 결과.hwpx # 양식 채우기
1138
- npx kordoc fill 신청서.hwpx -j values.json -o 결과.hwpx # JSON 파일로 채우기
1139
- npx kordoc fill 신청서.hwpx --dry-run # 필드 목록만 확인 (누름틀 포함)
1140
- npx kordoc fill --template gian -j 값.json -o 기안문.hwpx # 내장 표준 기안문 서식 채우기
1141
- npx kordoc fill --list-templates # 내장 서식 목록 + 필드
1142
- npx kordoc fill 신청서.hwpx -j 값.json --formats '{"날짜":"yy.mm.dd"}' # 필드별 값 서식 (라벨→포맷)
1143
- npx kordoc fill 신청서.hwpx -j 값.json --require-unique # 한 키가 2곳 이상 매칭되면 거부 (반복 라벨 오염 방지)
1144
- npx kordoc fill 신청서.hwpx -j 값.json --mask # 채운 값 미노출 (stdout 에 본문 대신 안내만)
1145
- npx kordoc generate 보고서.md -o 보고서.hwpx --preset 보고서 # 마크다운 → 공문서 HWPX
1146
- npx kordoc patch 원본.hwpx 편집.md -o 반영.hwpx # 서식 보존 라운드트립 패치 (.hwp도 자동 분기)
1147
- npx kordoc seal 신청서.hwpx --image 도장.png --anchor "(인)" -o 날인.hwpx # 도장/서명 날인
1148
- npx kordoc validate 산출물.hwpx # HWPX 구조 검증 (ZIP·필수 파트·XML)
1149
- npx kordoc lint 보고서.md # 공문서 표기법 검수 19룰 — 입력은 md/txt('-'=stdin), error 있으면 exit 1
1150
- npx kordoc redact 민원서류.hwpx -o 마스킹.hwpx # 개인정보 탐지 + 서식 보존 마스킹 (머리말·각주·미리보기·문서 정보 포함, 잔존 재검사 — 남으면 exit 2)
1151
- npx kordoc redact 민원서류.hwpx --mask-char '*' -o 마스킹.hwpx # 마스크 문자 지정 (기본 ●)
1152
- npx kordoc redact 계약서.hwp --rules rrn,phone,crn --json --dry-run # 룰 선택(법인등록번호 crn·IP 는 opt-in) + 위치별 리포트만
343
+ # 변환
344
+ npx kordoc 사업계획서.hwpx # 터미널 출력
345
+ npx kordoc 보고서.hwp -o 보고서.md # 파일 저장 (그림은 images/보고서/)
346
+ npx kordoc *.pdf -d ./변환결과/ # 일괄 변환
347
+ npx kordoc 검토서.hwpx --format json # JSON (blocks + pages + metadata)
348
+ npx kordoc 검토서.pdf --format chunks # RAG 구조 청크 JSON (breadcrumb + 표 독립 청크)
349
+ npx kordoc 보고서.hwpx --pages 1-3 # 쪽 범위
350
+ npx kordoc 스캔본.pdf --ocr # 내장 OCR (--ocr-force 로 전 쪽)
351
+ npx kordoc 잠긴문서.hwpx --password '암호' # 열기 암호 HWPX/HWP3/HWP5
352
+ npx kordoc 시험지.pdf --no-tables # PDF 표 감지 끄기
353
+ npx kordoc 문서.pdf --format json --no-images # 이미지 없이 (--plain·--html-tables 도 있음)
354
+
355
+ # 양식 채우기
356
+ npx kordoc fill 신청서.hwpx -f '성명=홍길동,주소=서울' -o 결과.hwpx
357
+ npx kordoc fill 신청서.hwpx -j values.json -o 결과.hwpx
358
+ npx kordoc fill 신청서.hwpx --dry-run # 필드 목록만 (누름틀 포함)
359
+ npx kordoc fill 신청서.hwpx -j 값.json --formats '{"날짜":"yy.mm.dd"}' # 필드별 값 서식
360
+ npx kordoc fill 신청서.hwpx -j 값.json --require-unique # 한 키가 2곳 이상 매칭되면 거부
361
+ npx kordoc fill 신청서.hwpx -j 값.json --mask # stdout 에 채운 값 대신 안내만
362
+ npx kordoc fill --template gian -j 값.json -o 기안문.hwpx # 내장 표준 기안문 (--list-templates)
363
+
364
+ # 생성 · 편집 · 검증
365
+ npx kordoc generate 보고서.md -o 보고서.hwpx --preset 보고서 # Markdown → 공문서 HWPX
366
+ npx kordoc patch 원본.hwpx 편집.md -o 반영.hwpx # 서식 보존 패치 (.hwp 도 자동)
367
+ npx kordoc seal 신청서.hwpx --image 도장.png --anchor "(인)" -o 날인.hwpx
368
+ npx kordoc validate 산출물.hwpx # HWPX 구조 검증 (ZIP·필수 파트·XML)
369
+ npx kordoc lint 보고서.md # 공문서 표기법 19룰 (md/txt, '-'=stdin, error 면 exit 1)
370
+ npx kordoc profile 기관서식.hwpx # 표 서식 프로필 JSON → generate --profile
371
+
372
+ # 개인정보 마스킹
373
+ npx kordoc redact 민원서류.hwpx -o 마스킹.hwpx # 서식 보존 마스킹 + 잔존 재검사 (남으면 exit 2)
374
+ npx kordoc redact 민원서류.hwpx --mask-char '*' -o 마스킹.hwpx # 마스크 문자 (기본 ●)
375
+ npx kordoc redact 계약서.hwp --rules rrn,phone,crn --json --dry-run # 룰 선택 + 위치별 리포트만 (crn·IP 는 opt-in)
1153
376
  npx kordoc redact 민원서류.hwpx --rules rrn,phone,email,name,address -o 마스킹.hwpx # 이름·주소까지 (opt-in)
1154
- npx kordoc redact 공문.pdf # PDF·DOCX 등은 원본을 고치지 않고 마스킹된 마크다운(.redacted.md)만 — PDF 가림 미지원
1155
- npx kordoc profile 기관서식.hwpx # 표 서식 프로필(JSON) 추출 → generate --profile 로 재현
1156
- npx kordoc render 결재문서.hwpx -o 미리보기.svg # 레이아웃 보존 SVG 렌더 (캐시 없는 문서는 자동 reflow 조판, --no-reflow로 끔)
1157
- npx kordoc render 결재문서.hwpx --reflow-mode charAll -o 미리보기.svg # reflow 줄바꿈: keep(어절, 기본) | charAll(글자)
1158
- npx kordoc models --status # OCR 모델 상태 (--export/--import 로 폐쇄망 사이드로드)
1159
- npx kordoc check-ocr-models --status-only # OCR 모델 상태만 JSON 출력 (옵션 빼면 없는 모델을 받는다)
1160
- npx kordoc check-formula-models --status-only # 수식 OCR 모델(MFD+MFR+tokenizer, ~155MB) 상태만 출력
1161
- npx kordoc watch ./수신함 -d ./변환결과 # 폴더 감시 모드
377
+ npx kordoc redact 공문.pdf # PDF·DOCX 등은 마스킹된 .redacted.md 만
378
+
379
+ # 렌더
380
+ npx kordoc render 결재문서.hwpx -o 미리보기.svg # 레이아웃 보존 SVG (캐시 없으면 reflow)
381
+ npx kordoc render 결재문서.hwpx --format png --pages 2-4 -d ./pages # PNG·JPEG·HTML·PDF 도
382
+ npx kordoc render 결재문서.hwpx --reflow-mode charAll -o 미리보기.svg # reflow 줄바꿈 keep(어절, 기본)|charAll(글자)
383
+
384
+ # 모델 · 감시
385
+ npx kordoc models --status # OCR 모델 상태 (--export/--import 폐쇄망 사이드로드)
386
+ npx kordoc check-ocr-models --status-only # 상태만 JSON (옵션 빼면 없는 모델을 받음)
387
+ npx kordoc check-formula-models --status-only # 수식 OCR 모델(MFD+MFR+tokenizer, ~155MB) 상태만
388
+ npx kordoc watch ./수신함 -d ./변환결과 # 폴더 감시 (하위 폴더 구조 유지)
1162
389
  npx kordoc watch ./문서 --webhook https://api/hook # 웹훅 알림
1163
390
  ```
1164
391
 
1165
- `watch -d` 는 감시 폴더 안의 하위 폴더 구조를 출력에도 그대로 둡니다: `수신함/팀/보고서.hwpx` → `변환결과/팀/보고서.md`(또는 `.json`). `수신함` 바로 아래 파일은 종전처럼 `변환결과` 바로 아래에 씁니다.
1166
-
1167
- > `check-ocr-models` · `check-formula-models` 는 이름과 달리 **없거나 SHA 가 안 맞으면 내려받습니다**.
1168
- > 상태만 보려면 `--status-only` 를 붙이세요 (수식 모델은 ~155MB).
1169
- >
1170
- > `kordoc lint` 는 **텍스트(마크다운/txt)를 검사합니다** — HWPX 를 바로 넘기면 안 됩니다.
1171
- > 생성물을 검수하려면 원고 마크다운을 넘기거나, `kordoc 문서.hwpx | kordoc lint -` 로 파이프하세요.
1172
- > (공문서 생성 시에는 `generate` 가 같은 룰의 경고를 함께 냅니다. `END_MARK_MISSING` 은 완성 원고를 보는 `lint` 에서만 켜집니다.)
392
+ - `watch -d` 는 하위 폴더 구조를 그대로 둡니다.
393
+ - `check-ocr-models`·`check-formula-models` 는 없는 모델을 내려받습니다 — 상태만 보려면 `--status-only`.
394
+ - `lint` 는 Markdown·텍스트용입니다. HWPX 는 `kordoc 문서.hwpx | kordoc lint -`.
1173
395
 
1174
- ### 실패 계약 — 기계 판독 가능한 실패 JSON (v4.10.0+, #69)
396
+ ### 실패 계약 — 기계 판독 가능한 실패 JSON
1175
397
 
1176
- 변환 실패는 **모든 `--format`(markdown·json·chunks)에서** stdout 에 동일한 실패 JSON 을 내고 exit 1 로 끝납니다. 호출자는 stderr 문구가 아니라 `code` 로 분기하면 됩니다.
398
+ 변환 실패는 모든 `--format` 에서 stdout 에 아래 JSON 을 내고 exit 1 로 끝납니다 — `code` 로 분기하세요.
1177
399
 
1178
400
  ```json
1179
401
  { "success": false, "fileType": "hwpx", "file": "보고서.hwpx", "error": "암호화된 문서입니다 …", "code": "ENCRYPTED" }
1180
402
  ```
1181
403
 
1182
- 성공 출력과 충돌하지 않습니다 — `markdown` 성공은 마크다운 텍스트, `chunks` 성공은 JSON **배열**, 실패는 언제나 `success:false` **객체**입니다. `-o`/`-d` 지정 시 실패한 파일의 출력물은 생성되지 않습니다. 여러 파일 입력 시 실패한 파일마다 실패 JSON 이 한 개씩 나옵니다.
404
+ - 실패는 언제나 `success:false` 객체라 성공 출력(텍스트·배열)과 겹치지 않습니다. 여러 파일이면 실패마다 한 줄씩 나옵니다.
405
+ - 종료 코드와 필드는 유지되고 `code` 값은 추가만 됩니다. `error` 문구는 사람용이라 계약이 아닙니다.
1183
406
 
1184
407
  | `code` | 의미 |
1185
408
  |---|---|
1186
409
  | `ENCRYPTED` | 열기 암호 문서 (`--password` 필요 또는 불일치) |
1187
410
  | `DRM_PROTECTED` | 한컴 문서보안(DRM) — 열 수 없음 |
1188
- | `UNSUPPORTED_FORMAT` | 지원하지 않는 파일 형식 |
411
+ | `UNSUPPORTED_FORMAT` | 지원하지 않는 형식 |
1189
412
  | `CORRUPTED` | 시그니처 불일치·복구 불가 손상 |
1190
413
  | `IMAGE_BASED_PDF` | 텍스트층 없는 스캔 PDF (`--ocr` 필요) |
1191
414
  | `ZIP_BOMB` / `DECOMPRESSION_BOMB` | 압축 폭탄 방어 발동 |
1192
415
  | `NO_SECTIONS` | 본문 섹션 없음 |
1193
- | `OUTPUT_TOO_LARGE` | 출력 직렬화가 런타임 문자열 한계 초과 (#65) |
416
+ | `OUTPUT_TOO_LARGE` | 출력 직렬화가 런타임 문자열 한계 초과 |
1194
417
  | `MISSING_DEPENDENCY` | 선택 의존성 미설치 (pdfjs-dist 등) |
1195
418
  | `EMPTY_INPUT` | 빈 입력 |
1196
- | `FILE_NOT_FOUND` | 입력 경로가 존재하지 않음 (ENOENT) — 문서 파싱 이전 단계 |
419
+ | `FILE_NOT_FOUND` | 입력 경로 없음 (ENOENT) |
1197
420
  | `PARSE_ERROR` | 그 외 파싱 실패 |
1198
421
 
1199
- **안정성 보증**: 종료 코드 규칙(성공 0 / 실패 1)과 실패 JSON 의 필드(`success`·`fileType`·`error`·`code`)는 유지되고, `code` 값 집합은 **추가만** 됩니다(기존 값 변경·제거 없음). 어느 입력에서 났는지 알 수 있는 `file`(basename) 필드도 **추가만** 됩니다. `error` 문구는 사람용이라 계약이 아닙니다.
1200
-
1201
- ### 이미지 번들 — `images/<문서 이름>/manifest.json` (v4.10.0+, #70 · #98)
422
+ ### 이미지 번들 — `images/<문서 이름>/manifest.json`
1202
423
 
1203
- `-o`/`-d` 로 저장할 때 추출 이미지는 문서마다 `images/<문서 이름>/` 에 저장되고(문서 이름 = `-o` 출력 파일 이름 또는 `-d` 입력 파일 이름에서 확장자를 뺀 것), 같은 폴더에 `manifest.json` 이 함께 나옵니다. 마크다운 링크도 `images/<문서 이름>/image_001.png` 를 가리킵니다(공백·괄호는 퍼센트 인코딩). 여러 문서를 같은 폴더로 변환해도 그림이 서로 덮어쓰이지 않습니다. 소비자는 확장자·매직바이트 추측 없이 manifest 로 형식을 분기하면 됩니다.
424
+ `-o`/`-d` 로 저장하면 그림은 문서마다 `images/<문서 이름>/` 에 `manifest.json` 과 함께 저장됩니다(같은 폴더로 여러 문서를 변환해도 안 덮어씀). `--format json --image-refs` 는 바이트 대신 경로만 남깁니다.
1204
425
 
1205
426
  ```json
1206
427
  [ { "name": "image_001.png", "mimeType": "image/png", "bytes": 68, "source": "BinData/image1.png" } ]
1207
428
  ```
1208
429
 
1209
- - `mimeType` 은 **매직바이트 실측 우선**(PNG/JPEG/GIF/BMP/WMF/EMF 판별) — 확장자 유래 선언값과 다르면 실측이 이깁니다. 실측 불가 형식(TIFF·SVG 등)은 선언값을 그대로 둡니다.
1210
- - `source` 는 원본 컨테이너 항목명(HWPX/DOCX ZIP 경로, HWP5 BinData 스토리지명). PDF 처럼 재인코딩으로 합성된 이미지에는 없습니다.
1211
- - 나올 수 있는 확장자 집합: **PDF 는 항상 `png`**(순수 JS 재인코딩) · HWP5 는 스니프 유래 `png/jpg/gif/bmp`(WMF/EMF 는 `bin`) · HWPX 는 원본 확장자 유래 `png/jpg/gif/bmp/tif/wmf/emf/svg`(미지 확장자는 `bin`) · DOCX 는 원본 확장자 유지. 이미지 재인코딩은 하지 않습니다(원형 유지) — 형식 판정은 manifest 를 믿으세요.
1212
-
1213
- ## MCP 서버 (Claude / Cursor / Windsurf / Codex)
1214
-
1215
- **자동 설치 (추천)**:
1216
-
1217
- ```bash
1218
- npx -y kordoc setup
1219
- ```
430
+ - `mimeType` 은 파일 머리(매직바이트)로 확인한 값, `source` 는 원본 컨테이너 안 경로입니다.
431
+ - 그림은 원본 그대로 저장합니다(PDF 만 PNG 로 재인코딩). 형식은 확장자보다 `mimeType` 을 믿으세요.
1220
432
 
1221
- 대화형으로 AI 클라이언트를 감지해 설정 파일을 자동 패치. Windows 에서 `cmd /c npx` 래핑도 자동. 상세는 위 [30초 설치](#-30초-설치-ai-에이전트-연동) 섹션.
433
+ ---
1222
434
 
1223
- Codex는 설정 파일을 직접 수정하지 않고 `codex mcp add` 명령으로 안전하게 등록합니다.
435
+ ## 🤖 MCP 서버
1224
436
 
1225
- **Codex 수동 등록**:
437
+ 자동 설치는 [`npx -y kordoc setup`](#ai-에이전트-연동-mcp--30초). 수동 등록:
1226
438
 
1227
439
  ```bash
1228
- codex mcp add kordoc -- npx -y kordoc mcp
440
+ codex mcp add kordoc -- npx -y kordoc mcp # Codex
1229
441
  ```
1230
442
 
1231
- **수동 등록 (macOS / Linux)**:
1232
-
1233
443
  ```json
1234
- {
1235
- "mcpServers": {
1236
- "kordoc": {
1237
- "command": "npx",
1238
- "args": ["-y", "kordoc", "mcp"]
1239
- }
1240
- }
1241
- }
444
+ { "mcpServers": { "kordoc": { "command": "npx", "args": ["-y", "kordoc", "mcp"] } } }
1242
445
  ```
1243
446
 
1244
- **수동 등록 (Windows — Claude Desktop 이 `.cmd` 를 못 찾을 때)**:
447
+ Windows 에서 Claude Desktop 이 `.cmd` 를 못 찾으면 `"command": "cmd", "args": ["/c", "npx", "-y", "kordoc", "mcp"]`.
1245
448
 
1246
- ```json
1247
- {
1248
- "mcpServers": {
1249
- "kordoc": {
1250
- "command": "cmd",
1251
- "args": ["/c", "npx", "-y", "kordoc", "mcp"]
1252
- }
1253
- }
1254
- }
1255
- ```
1256
-
1257
- **17개 도구:**
449
+ **17개 도구**
1258
450
 
1259
451
  | 도구 | 설명 |
1260
452
  |------|------|
1261
- | `parse_document` | HWP/HWPX/PDF/XLSX/DOCX → 마크다운 (메타데이터 포함) |
453
+ | `parse_document` | HWP/HWPX/PDF/XLSX/DOCX → Markdown (메타데이터 포함) |
1262
454
  | `detect_format` | 매직 바이트로 포맷 감지 |
1263
- | `parse_metadata` | 메타데이터만 빠르게 추출 |
1264
- | `parse_pages` | 특정 페이지 범위만 파싱 |
1265
- | `parse_table` | N번째 테이블만 추출 |
1266
- | `compare_documents` | 두 문서 비교 (크로스 포맷) |
1267
- | `parse_form` | 양식 필드를 JSON으로 추출 |
1268
- | `fill_form` | 양식 템플릿에 값 채우기 (HWPX 원본 서식 보존, 서식/유일성 가드) |
1269
- | `patch_document` | 편집된 마크다운을 원본 HWPX/HWP에 서식 보존 반영 (v3.3) |
1270
- | `extract_profile` | 참조 HWPX에서 표 서식 프로필(JSON) 추출 — generate_document의 profile_path로 재현 |
1271
- | `generate_document` | 마크다운(표·수식·차트 포함) → HWPX 생성, 공문서 프리셋 (v3.5) |
1272
- | `place_seal` | 도장/서명 이미지를 앵커 문구 위에 부유 배치 (v3.16) |
1273
- | `render_document` | HWPX·HWP를 조판 그대로 PNG/JPEG 이미지(응답)·SVG/HTML/PDF 파일로 렌더 — 생성·수정 결과를 AI가 눈으로 검증 (v4.1, HWP·포맷 확장 v4.13.1) |
1274
- | `redact_document` | 개인정보(주민·외국인등록번호·전화·이메일·카드·계좌·사업자등록번호·여권·운전면허, opt-in 법인등록번호·IP·이름·주소) 탐지 + 서식 보존 마스킹 — HWPX/HWP는 머리말·각주·미리보기·문서 정보까지 가리고 잔존 재검사, 그 외 포맷은 마스킹된 마크다운만 (v4.1) |
1275
- | `parse_chunks` | RAG용 구조 청크 JSON — 헤딩·개조식 위계 breadcrumb + 표 독립 청크 (v4.1) |
1276
- | `crop_regions` | 렌더 영역(표·이미지·문단·도형)을 페이지 이미지에서 실배율로 잘라 저장 + regions.json (v4.13) |
1277
- | `extract_tables` | 표 분류(데이터표/조직도류/불확실) + 페이지·bbox + 정책별 crop — 조직도는 이미지로, 데이터표는 구조로 (v4.13.1) |
1278
-
1279
- ## API
1280
-
1281
- ### 핵심 함수
455
+ | `parse_metadata` | 메타데이터만 빠르게 |
456
+ | `parse_pages` | 특정 쪽 범위만 |
457
+ | `parse_table` | N번째 표만 |
458
+ | `parse_chunks` | RAG 구조 청크 JSON — 헤딩·개조식 위계 breadcrumb + 표 독립 청크 |
459
+ | `compare_documents` | 두 문서 비교 (교차 포맷) |
460
+ | `parse_form` | 양식 필드를 JSON 으로 |
461
+ | `fill_form` | 양식에 값 채우기 (HWPX 서식 보존, 서식·유일성 가드, 내장 `template`) |
462
+ | `patch_document` | 편집한 Markdown 을 원본 HWPX/HWP 에 서식 보존 반영 |
463
+ | `extract_profile` | 참조 HWPX 의 표 서식 프로필 JSON — `generate_document` 의 `profile_path` 로 재현 |
464
+ | `generate_document` | Markdown(표·수식·차트) → HWPX, 공문서 프리셋 |
465
+ | `place_seal` | 도장/서명 이미지를 앵커 문구 위에 부유 배치 |
466
+ | `render_document` | HWPX·HWP 를 조판 그대로 PNG/JPEG(응답 이미지)·SVG/HTML/PDF(파일)로 — 생성·수정 결과를 AI 가 눈으로 검증 |
467
+ | `redact_document` | 개인정보 탐지 + 서식 보존 마스킹 (HWPX/HWP 는 머리말·각주·미리보기·문서 정보까지 + 잔존 재검사, 그 외는 마스킹된 Markdown) |
468
+ | `crop_regions` | 렌더 영역(표·이미지·문단·도형)을 쪽 이미지에서 실배율로 잘라 저장 + regions.json |
469
+ | `extract_tables` | 표 분류(데이터표/조직도류/불확실) + 쪽·bbox + 정책별 crop — 조직도는 이미지로, 데이터표는 구조로 |
470
+
471
+ ---
472
+
473
+ ## 📚 API
474
+
475
+ ### 파싱
1282
476
 
1283
477
  | 함수 | 설명 |
1284
478
  |------|------|
1285
- | `parse(buffer, options?)` | 포맷 자동 감지 → Markdown + IRBlock[] |
1286
- | `parseHwpx(buffer, options?)` | HWPX 전용 |
1287
- | `parseHwp(buffer, options?)` | HWP 5.x 전용 |
1288
- | `parseHwp3(buffer, options?)` | HWP 3.x (1996~2002 구버전) 전용 |
1289
- | `parsePdf(buffer, options?)` | PDF 전용 |
1290
- | `parseXlsx(buffer, options?)` | XLSX 전용 |
1291
- | `parseXls(buffer, options?)` | XLS (Excel 97~2003, BIFF8) 전용 |
1292
- | `parseDocx(buffer, options?)` | DOCX 전용 |
1293
- | `parseHwpml(buffer, options?)` | HWPML (XML 기반 HWP) 전용 |
1294
- | `parseImage(buffer, options?)` | 이미지(PNG/JPG/WebP) 전용 — 내장 OCR 상시 적용 (v4.2.1) |
1295
- | `detectFormat(buffer)` | 동기 매직 바이트 감지 — 하위 호환을 위해 ZIP은 `hwpx`, OLE2는 `hwp` 반환 |
1296
- | `await detectZipFormat(buffer)` | ZIP 내부 구조로 `hwpx`, `xlsx`, `docx`, `pptx`, `unknown` 구분 |
1297
- | `detectOle2Format(buffer)` | OLE2 내부 스트림으로 `hwp`, `xls`, `unknown` 구분 |
1298
-
1299
- PPTX는 감지만 지원합니다. `parse()`는 PPTX 입력에 `success: false`, `fileType: "pptx"`,
1300
- `code: "UNSUPPORTED_FORMAT"`을 반환합니다. ZIP을 구분해 라우팅하는 래퍼/API는
1301
- `detectFormat()` 결과가 `hwpx`일 때 `await detectZipFormat(buffer)`로 세분화하세요.
1302
-
1303
- ### 고급 함수
479
+ | `parse(buffer, options?)` | 포맷 자동 감지 → Markdown + `IRBlock[]` (파일 경로 문자열도 받음) |
480
+ | `parseHwpx` · `parseHwp` · `parseHwp3` · `parseHwpml` | HWPX · HWP 5.x · HWP 3.x(1996~2002) · HWPML 전용 — 모두 `(buffer, options?)` |
481
+ | `parsePdf` · `parseDocx` · `parseXlsx` · `parseXls` | PDF · DOCX · XLSX · XLS(Excel 97~2003, BIFF8) 전용 |
482
+ | `parseImage(buffer, options?)` | 이미지(PNG/JPG/WebP) 전용 — 내장 OCR 상시 적용 |
483
+ | `detectFormat(buffer)` | 동기 매직 바이트 감지 — 하위 호환을 위해 ZIP 은 `hwpx`, OLE2 는 `hwp` 반환 |
484
+ | `await detectZipFormat(buffer)` | ZIP 내부 구조로 `hwpx`·`xlsx`·`docx`·`pptx`·`unknown` 구분 |
485
+ | `detectOle2Format(buffer)` | OLE2 내부 스트림으로 `hwp`·`xls`·`unknown` 구분 |
486
+
487
+ PPTX 는 감지만 합니다(`parse()` 는 `UNSUPPORTED_FORMAT`). ZIP 종류를 가르려면 `detectFormat()` 이 `hwpx` 일 때 `await detectZipFormat(buffer)` 를 쓰세요.
488
+
489
+ ### 비교 · 양식 · 편집
1304
490
 
1305
491
  | 함수 | 설명 |
1306
492
  |------|------|
1307
493
  | `compare(bufferA, bufferB, options?)` | IR 레벨 문서 비교 |
1308
- | `extractFormFields(blocks)` | IRBlock[]에서 양식 필드 인식 |
1309
- | `extractFormSchema(blocks)` | 양식 필드 인식 + 타입/필수/빈값 추론 (v3.1) |
1310
- | `fillForm(input, values, outputFormat?)` | 양식 템플릿에 값 채우기 — outputFormat: `"markdown"`(기본)/`"hwpx"`/`"hwpx-preserve"`, 반환 `{ output, format, fill }` |
494
+ | `extractFormFields(blocks)` / `extractFormSchema(blocks)` | 양식 필드 인식 / + 타입·필수·빈값 추론 |
495
+ | `fillForm(input, values, outputFormat?)` | 양식 채우기 — `"markdown"`(기본)·`"hwpx"`·`"hwpx-preserve"`, 반환 `{ output, format, fill }` |
1311
496
  | `fillFormFields(blocks, values)` | IRBlock[] 기반 필드 값 교체 |
1312
497
  | `fillHwpx(buffer, values)` | HWPX XML 직접 조작 (원본 서식 보존) |
1313
- | `extractClickHereFields(buffer)` | HWPX 누름틀(CLICK_HERE) 필드 조사 — 이름·안내문 목록 (v4.3) |
1314
- | `resolveBuiltinTemplate(name)` / `readBuiltinTemplate(t)` | 내장 표준 기안문 서식 조회·로드 (`gian`/`gian-simple`, v4.3) |
1315
- | `patchHwpx(original, editedMarkdown, options?)` | 편집 마크다운 → 원본 HWPX 서식 보존 in-place 패치 (v3.0) |
1316
- | `patchHwp(original, editedMarkdown, options?)` | 편집 마크다운 → 원본 HWP 5.x 바이너리 서식 보존 패치 (v3.0.1) |
1317
- | `openHwpxDocument(bytes, options?)` | 에디터용 블록 단위 증분 패치 세션 `HwpxSession` (v3.1) |
1318
- | `patchHwpxBlocks(bytes, edits, options?)` | 세션 없이 블록 편집 1회 패치 (v3.1) |
1319
- | `markdownToHwpx(markdown, options?)` | Markdown → HWPX 역변환 (테마·서식 프로필·페이지 옵션 지원) |
1320
- | `hwpxToProfile(buffer)` | 참조 HWPX → 표 서식 프로필 JSON — `markdownToHwpx(md, { profile })` 로 재현 (v3.18) |
1321
- | `markdownToPdf(markdown, options?)` | Markdown → PDF 생성 (Print Renderer — `puppeteer-core` 별도 설치 필요) |
1322
- | `blocksToPdf(blocks, options?)` | IRBlock[] → PDF 생성 (동일하게 `puppeteer-core` 필요) |
1323
- | `renderHtml(blocks, options?)` | IRBlock[] → 인쇄용 HTML (puppeteer 불필요) |
1324
- | `renderHwpxToSvg(buffer, options?)` | HWPX → 레이아웃 보존 SVG — 다페이지·형광펜·도형, 캐시 없으면 `reflow` (v3.10~15) |
1325
- | `renderDocument(입력, { format, pages?, … })` | HWPX·HWP(5.x) → 페이지별 svg/png/jpeg·문서 html/pdf 자산 + `RenderScene`(페이지 로컬 pt bbox·결정적 region id) (v4.13, HWP v4.13.1) |
1326
- | `extractRenderedRegions(입력, { types?, pages?, … })` | 표·이미지·문단·도형 region 을 페이지 이미지에서 실배율 crop (v4.13) |
1327
- | `extractTables(입력, { policy?, … })` | 표 분류(의미표/조직도류/불확실) + 렌더 region 조인 + 정책별 crop (v4.13, HWP v4.13.1) |
1328
- | `placeSealHwpx(buffer, seals)` | 도장/서명 이미지를 앵커 문구 위에 부유 배치 (v3.16) |
1329
- | `validateHwpx(buffer)` | HWPX 구조 검증 — ZIP·mimetype·필수 파트·XML 웰폼드 (v3.16) |
1330
- | `lintGongmunText(text, { document? })` | 공문서 표기법 검수 19룰 + AI 슬롭 2룰 — 텍스트/마크다운 입력 (v4.0.1, v4.12.1 보강). `document: true` 면 붙임/"끝." 문서 단위 검사 포함 |
1331
- | `redactMarkdown(text, options?)` / `redactText(...)` | 개인정보 탐지 + 마스킹 (기본 룰: 주민·외국인등록번호·전화·이메일·카드·계좌·사업자등록번호·여권·운전면허, opt-in: 법인등록번호·IP — 텍스트 단위, 파일 단위 마스킹은 CLI `redact`/MCP `redact_document`, v4.1) |
1332
- | `blocksToChunks(blocks, options?)` | RAG용 구조 청크 — 헤딩·개조식 위계 breadcrumb + 표 독립 청크 (v4.1) |
1333
- | `blocksToMarkdown(blocks)` | IRBlock[] → Markdown 문자열 |
1334
- | `blocksToPages(blocks)` | IRBlock[] → `[{ pageNumber, markdown }]` 페이지별 마크다운 (v4.8) |
498
+ | `extractClickHereFields(buffer)` | HWPX 누름틀(CLICK_HERE) 필드 조사 — 이름·안내문 |
499
+ | `resolveBuiltinTemplate(name)` / `readBuiltinTemplate(t)` | 내장 표준 기안문 서식 조회·로드 (`gian`·`gian-simple`) |
500
+ | `patchHwpx(original, editedMarkdown, options?)` | 편집 Markdown → 원본 HWPX 서식 보존 패치 |
501
+ | `patchHwp(original, editedMarkdown, options?)` | 편집 Markdown → 원본 HWP 5.x 바이너리 서식 보존 패치 |
502
+ | `openHwpxDocument(bytes, options?)` | 에디터용 블록 단위 증분 패치 세션 `HwpxSession` |
503
+ | `patchHwpxBlocks(bytes, edits, options?)` | 세션 없이 블록 편집 1회 패치 |
504
+ | `placeSealHwpx(buffer, seals)` | 도장/서명 이미지를 앵커 문구 위에 부유 배치 |
505
+ | `validateHwpx(buffer)` | HWPX 구조 검증 — ZIP·mimetype·필수 파트·XML 웰폼드 |
506
+
507
+ ### 생성 · 렌더
508
+
509
+ | 함수 | 설명 |
510
+ |------|------|
511
+ | `markdownToHwpx(markdown, options?)` | Markdown → HWPX (테마·서식 프로필·쪽 옵션·공문서 프리셋) |
512
+ | `hwpxToProfile(buffer)` | 참조 HWPX → 표 서식 프로필 JSON (`markdownToHwpx(md, { profile })` 로 재현) |
513
+ | `markdownToPdf(markdown, options?)` / `blocksToPdf(blocks, options?)` | Markdown·IRBlock[] → PDF (`puppeteer-core` 별도 설치) |
514
+ | `renderHtml(blocks, options?)` | IRBlock[] → 인쇄용 HTML (puppeteer 불필요, 원문 HTML 은 허용 태그만 통과 + CSP) |
515
+ | `renderHwpxToSvg(buffer, options?)` | HWPX → 레이아웃 보존 SVG — 다페이지·형광펜·도형, 캐시 없으면 `reflow` |
516
+ | `renderDocument(입력, { format, pages?, … })` | HWPX·HWP(5.x) → 쪽별 svg/png/jpeg·문서 html/pdf 자산 + `RenderScene`(쪽 로컬 pt bbox·결정적 region id) |
517
+ | `extractRenderedRegions(입력, { types?, pages?, … })` | 표·이미지·문단·도형 영역을 쪽 이미지에서 실배율 crop |
518
+ | `extractTables(입력, { policy?, … })` | 표 분류(의미표/조직도류/불확실) + 렌더 영역 조인 + 정책별 crop |
519
+
520
+ ### 텍스트 · 변환 도구
521
+
522
+ | 함수 | 설명 |
523
+ |------|------|
524
+ | `lintGongmunText(text, { document? })` | 공문서 표기법 19룰 + AI 슬롭 2룰 (`document: true` 면 붙임·"끝." 문서 단위 검사 포함) |
525
+ | `redactMarkdown(text, options?)` / `redactText(...)` | 개인정보 탐지 + 마스킹 — 텍스트 단위 (파일 단위는 CLI `redact`·MCP `redact_document`) |
526
+ | `blocksToChunks(blocks, options?)` | RAG 구조 청크 — 헤딩·개조식 위계 breadcrumb + 표 독립 청크 |
527
+ | `blocksToMarkdown(blocks)` | IRBlock[] → Markdown |
528
+ | `blocksToPages(blocks)` | IRBlock[] → `[{ pageNumber, markdown }]` |
1335
529
 
1336
530
  ### 타입
1337
531
 
@@ -1356,49 +550,65 @@ import type {
1356
550
  } from "kordoc"
1357
551
  ```
1358
552
 
1359
- ## 지원 포맷
553
+ ---
554
+
555
+ ## 📂 지원 포맷
1360
556
 
1361
557
  | 포맷 | 엔진 | 특징 |
1362
558
  |------|------|------|
1363
- | **HWPX** (한컴 2020+) | ZIP + XML DOM | 매니페스트, 중첩 테이블, 병합 셀, 손상 ZIP 복구, 조판 캐시 기반 실제 페이지 경계, 열기 암호 |
1364
- | **HWP 5.x** (한컴 레거시) | OLE2 + CFB | 배포용 복호화, 열기 암호, 손상 CFB 복구, 각주/하이퍼링크, 21종 제어문자, 이미지 추출, 실제 페이지 경계 |
1365
- | **HWP 3.x** (1996~2002) | 단일 binary | 상용조합형→유니코드, 5,893자 한자/기호 lookup, nested paragraph 추출, 아래아(옛한글), 열기 암호 |
1366
- | **HWPML 2.x** (XML 기반 HWP) | XML DOM | HeadingType 기반 헤딩 감지, 병합 셀, DoS 방어 |
1367
- | **PDF** | pdfjs-dist | 선 기반 테이블, XY-Cut 읽기 순서, 2단 지면, 헤딩 감지, OCR, 밑줄·링크, 이미지 추출, 텍스트 품질 신호 |
1368
- | **XLSX** (Excel) | ZIP + XML DOM | 공유 문자열, 병합 셀, 다중 시트, 수식 표시, 날짜 셀 ISO 변환 |
1369
- | **XLS** (Excel 97~2003) | OLE2 + BIFF8 | Workbook 스트림, SST 공유 문자열, 셀/시트 추출 |
1370
- | **DOCX** (Word) | ZIP + XML DOM | 스타일 heading, 번호 매기기, 각주, 하이퍼링크, 이미지 추출 |
1371
- | **이미지** (PNG/JPG/WebP) | sharp + 내장 OCR | 스크린샷·스캔 이미지 직접 입력, 래스터 괘선 감지로 표 복원 |
559
+ | **HWPX** (한컴 2020+) | ZIP + XML DOM | 매니페스트, 중첩 표, 병합 셀, 손상 ZIP 복구, 조판 캐시 기반 실제 쪽 경계, 열기 암호, 양식 선택 상자·라디오 단추 |
560
+ | **HWP 5.x** (한컴 레거시) | OLE2 + CFB | 배포용 복호화, 열기 암호, 손상 CFB 복구, 각주·하이퍼링크, 21종 제어문자, 이미지 추출, 실제 쪽 경계 |
561
+ | **HWP 3.x** (1996~2002) | 단일 binary | 상용조합형 → 유니코드, 한자·기호 5,893자 lookup, 중첩 문단, 아래아(옛한글), 열기 암호 |
562
+ | **HWPML 2.x** (XML HWP) | XML DOM | HeadingType 기반 헤딩, 병합 셀, DoS 방어 |
563
+ | **PDF** | pdfjs-dist | 선·클립·무괘선 표, XY-Cut 읽기 순서, 2단 지면, 헤딩, 각주·미주, 수식 글꼴 복원, OCR, 밑줄·링크, 이미지 추출, 텍스트 품질 신호 |
564
+ | **XLSX** (Excel) | ZIP + XML DOM | 공유 문자열, 병합 셀, 다중 시트, 수식 표시, 날짜 셀 ISO 변환, 대형 시트 스트리밍 |
565
+ | **XLS** (Excel 97~2003) | OLE2 + BIFF8 | Workbook 스트림, SST 공유 문자열, 셀·시트 추출 |
566
+ | **DOCX** (Word) | ZIP + XML DOM | 스타일 헤딩, 번호 매기기(실제 번호 라벨), 각주, 하이퍼링크, 이미지 추출 |
567
+ | **이미지** (PNG/JPG/WebP) | sharp + 내장 OCR | 스크린샷·스캔 이미지 직접 입력, 래스터 괘선으로 표 복원 |
568
+
569
+ ---
570
+
571
+ ## 🔒 보안
1372
572
 
1373
- ## 보안
573
+ - ZIP·압축 폭탄, XXE, 경로 순회, SSRF 를 막습니다 — 자세한 내용은 [SECURITY.md](./SECURITY.md).
574
+ - **폐쇄망**: `KORDOC_OFFLINE=1` 은 모든 외부 통신을 막고, `KORDOC_ROOT=<디렉토리>` 는 MCP 파일 접근을 그 안으로 제한합니다. 오프라인 설치 번들·모델 옮기기는 [docs/offline-deployment.md](docs/offline-deployment.md).
1374
575
 
1375
- 프로덕션급 보안 강화: ZIP bomb 방지, XXE/Billion Laughs 방지, 압축 폭탄 방지, 경로 순회 차단, MCP 에러 정제, 파일 크기 제한(500MB). 자세한 내용은 [SECURITY.md](./SECURITY.md) 참조.
576
+ ---
1376
577
 
1377
- **폐쇄망(내부망) 배포** — `KORDOC_OFFLINE=1` 로 모든 아웃바운드 통신(OCR 모델 다운로드·watch webhook)을 차단하고, `KORDOC_ROOT=<디렉토리>` 로 MCP 서버의 파일 접근을 해당 디렉토리 하위로 제한합니다(둘 다 opt-in). 절차·근거는 [docs/offline-deployment.md](docs/offline-deployment.md) 참조. (v4.7.2)
578
+ ## 📝 최근 변경
579
+
580
+ ### v4.16.1
581
+ - 서울 방침서 프리셋 `서울방침` — 제목표·장 상자·숫자 위계, 표지·사전 검토항목 점검표·목차 (실결재 16건 실측)
582
+ - PDF 기본값 자동 OCR 이 큰 그림 속 글도 읽음 — ODL 기본 0.940 → 0.960
583
+ - PDF 표 완전 일치 97.4%·글 어절 F1 98.8%, OCR 글자 재현율 99.0%·정확도 99.4%
584
+ - 개조식 장 헤더 제목 칸 글자 폭 맞춤 `chapterFit` (#103)
585
+
586
+ ### v4.16.0
587
+ - PDF 글·표 정확도 향상 — 원본 HWPX 대비 글자 재현율 99.8%, 표 완전 일치 97.0%
588
+ - 텍스트층 없는 쪽 자동 OCR (모델 캐시가 있을 때, `ocr: false` 로 끔)
589
+ - DOCX 번호 목록을 실제 번호로 ("[3]"·"5.1")
590
+ - CLI 그림 경로 `images/<문서 이름>/` (#98) · `render --reflow` 호환 (#97) · 보안 보강 (#100)
591
+
592
+ 전체 이력은 [CHANGELOG.md](CHANGELOG.md).
593
+
594
+ ---
1378
595
 
1379
596
  ## 만든 사람
1380
597
 
1381
- 대한민국 지방공무원. 광진구청에서 7년간 HWP 파일과 싸우다가 이걸 만들었습니다.
1382
- 5개 공공 프로젝트에서 수천 건의 실제 관공서 문서를 파싱하며 검증했습니다.
598
+ 대한민국 지방공무원. 광진구청에서 7년간 HWP 파일과 싸우다가 이걸 만들었습니다. 5개 공공 프로젝트에서 수천 건의 실제 관공서 문서를 파싱하며 검증했습니다.
1383
599
 
1384
600
  ## 라이선스
1385
601
 
1386
- [MIT](./LICENSE)
602
+ [MIT](./LICENSE). 이 프로젝트는 아래 오픈소스를 포함합니다:
1387
603
 
1388
- 이 프로젝트는 아래 오픈소스를 포함합니다:
1389
- - **rhwp** (MIT, edwardkim) — HWP5 배포용 복호화 및 lenient CFB 파싱 알고리즘,
1390
- `templates/` 의 기안문 서식
1391
- - **claw-hwp** (MIT, DoHyun468) — OOXML chartSpace 조립, 도장 부유 배치 메트릭,
1392
- secure-fill 포맷엔진, validate 검사셋
1393
- - **OpenDataLoader PDF** (Apache 2.0, Hancom Inc.) — PDF 테이블 감지 알고리즘
604
+ - **rhwp** (MIT, edwardkim) — HWP5 배포용 복호화·lenient CFB 파싱 알고리즘, `templates/` 의 기안문 서식
605
+ - **claw-hwp** (MIT, DoHyun468) — OOXML chartSpace 조립, 도장 부유 배치 메트릭, secure-fill 포맷엔진, validate 검사셋
606
+ - **OpenDataLoader PDF** (Apache 2.0, Hancom Inc.) — PDF 표 감지 알고리즘
1394
607
  - **hml-equation-parser** (Apache 2.0, Open Bapul) — HML 수식 파싱
1395
608
  - **PaddleOCR** (Apache 2.0, PaddlePaddle) — 텍스트 OCR 엔진 파생 (PP-OCRv5 korean)
1396
- - **Pix2Text** (MIT, breezedeus) — 수식 OCR(MFD/MFR) 알고리즘 포팅. 모델은 런타임
1397
- 다운로드이며 재배포하지 않습니다 — MFD 가중치의 기반인 Ultralytics YOLOv8 은
1398
- AGPL-3.0 이므로, 수식 OCR 에 의존하는 상용·비공개 제품은 해당 조건을 별도 확인하세요
609
+ - **Pix2Text** (MIT, breezedeus) — 수식 OCR(MFD/MFR) 알고리즘 포팅. 모델은 런타임 다운로드이며 재배포하지 않습니다 — MFD 가중치의 기반인 Ultralytics YOLOv8 은 AGPL-3.0 이므로 수식 OCR 에 의존하는 상용·비공개 제품은 해당 조건을 별도 확인하세요
1399
610
  - **cfb** (Apache 2.0, SheetJS) — HWP5 OLE2 컨테이너 파싱
1400
611
  - **pdfjs-dist** (Apache 2.0, Mozilla) — PDF 텍스트 추출
1401
612
  - **JSZip** (MIT, Stuart Knightley 외) — ZIP 기반 포맷 파싱
1402
613
 
1403
- 전체 고지는 [NOTICE](./NOTICE), 라이선스 전문은 `THIRD_PARTY/` 를 참조하세요.
1404
- 둘 다 npm 배포 패키지에 함께 포함됩니다.
614
+ 전체 고지는 [NOTICE](./NOTICE), 라이선스 전문은 `THIRD_PARTY/` — 둘 다 npm 배포 패키지에 포함됩니다.