kordoc 4.15.7 → 4.16.0

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 (149) hide show
  1. package/README.md +22 -10
  2. package/dist/{-H6WNRHK6.js → -VQNLLLPE.js} +11 -11
  3. package/dist/{chunk-YFHV4QP3.js → chunk-3JWMDL3S.js} +5 -4
  4. package/dist/chunk-3JWMDL3S.js.map +1 -0
  5. package/dist/{chunk-WMLGS5QL.js → chunk-43FKGDDO.js} +200 -105
  6. package/dist/chunk-43FKGDDO.js.map +1 -0
  7. package/dist/{chunk-O5SDUPB2.js → chunk-46DZF3RK.js} +2 -2
  8. package/dist/{chunk-C572DIHX.js → chunk-476OYHGZ.js} +13 -6
  9. package/dist/{chunk-C572DIHX.js.map → chunk-476OYHGZ.js.map} +1 -1
  10. package/dist/{chunk-UGSRP7E2.js → chunk-6LMKQM7I.js} +3 -3
  11. package/dist/{chunk-IHCQ4IE7.js → chunk-A3QDRMVD.js} +2 -2
  12. package/dist/{chunk-ZXAH4AFS.cjs → chunk-B5NEGP65.cjs} +18 -71
  13. package/dist/chunk-B5NEGP65.cjs.map +1 -0
  14. package/dist/{chunk-TRB6OYMU.js → chunk-C75WE52C.js} +2 -2
  15. package/dist/{chunk-S7PEBTU3.js → chunk-CB5XDCSL.js} +134 -46
  16. package/dist/chunk-CB5XDCSL.js.map +1 -0
  17. package/dist/{chunk-OE6XLTMC.cjs → chunk-DRY3L5KQ.cjs} +9 -8
  18. package/dist/chunk-DRY3L5KQ.cjs.map +1 -0
  19. package/dist/{chunk-AWXXLLIG.js → chunk-DYENR3PX.js} +2 -2
  20. package/dist/{chunk-WQWXQVOQ.js → chunk-GOQBON77.js} +4 -2
  21. package/dist/{chunk-F5FHYJKJ.js.map → chunk-GOQBON77.js.map} +1 -1
  22. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  23. package/dist/{chunk-R45AG5K6.cjs → chunk-H6KMTYYZ.cjs} +3 -3
  24. package/dist/chunk-H6KMTYYZ.cjs.map +1 -0
  25. package/dist/{chunk-X3PBLSV5.js → chunk-HIVK5V27.js} +274 -98
  26. package/dist/chunk-HIVK5V27.js.map +1 -0
  27. package/dist/chunk-J55UQC5H.cjs.map +1 -1
  28. package/dist/{chunk-6ULM2YCN.js → chunk-JB5X5NNL.js} +7 -7
  29. package/dist/chunk-JB5X5NNL.js.map +1 -0
  30. package/dist/{chunk-4ANWGSUT.cjs → chunk-MH6MAL64.cjs} +320 -144
  31. package/dist/chunk-MH6MAL64.cjs.map +1 -0
  32. package/dist/{chunk-KJSD2WNF.js → chunk-N6TPODYL.js} +15 -2
  33. package/dist/chunk-N6TPODYL.js.map +1 -0
  34. package/dist/{chunk-NZMPU24H.js → chunk-NNYF2AGC.js} +5 -4
  35. package/dist/chunk-NNYF2AGC.js.map +1 -0
  36. package/dist/{chunk-J6ZU7AIR.cjs → chunk-NWODPYBD.cjs} +5 -3
  37. package/dist/chunk-NWODPYBD.cjs.map +1 -0
  38. package/dist/{chunk-L5MDTA6T.js → chunk-PIMB2YNO.js} +2 -2
  39. package/dist/{chunk-ZWPRA2SE.js → chunk-PQRP4NSF.js} +2 -2
  40. package/dist/{chunk-W2UTB3RG.js → chunk-QJERJZJ6.js} +1 -1
  41. package/dist/{chunk-YNJLZVEO.cjs.map → chunk-QJERJZJ6.js.map} +1 -1
  42. package/dist/{chunk-F5FHYJKJ.js → chunk-RC4C7Q33.js} +4 -2
  43. package/dist/{chunk-WQWXQVOQ.js.map → chunk-RC4C7Q33.js.map} +1 -1
  44. package/dist/{chunk-WHZUJH2O.js → chunk-TGD7UFHD.js} +5 -4
  45. package/dist/{chunk-WHZUJH2O.js.map → chunk-TGD7UFHD.js.map} +1 -1
  46. package/dist/{chunk-ORMMGTNK.js → chunk-UDX7TSOC.js} +2 -2
  47. package/dist/{chunk-NVG2GQNA.js → chunk-UQ6TZDW6.js} +1 -1
  48. package/dist/chunk-UQ6TZDW6.js.map +1 -0
  49. package/dist/{chunk-ZYJY44BT.js → chunk-XHOZB2WP.js} +16 -66
  50. package/dist/chunk-XHOZB2WP.js.map +1 -0
  51. package/dist/{chunk-YNJLZVEO.cjs → chunk-ZY5K3LPM.cjs} +1 -1
  52. package/dist/chunk-ZY5K3LPM.cjs.map +1 -0
  53. package/dist/chunks-JD25E5PM.js +10 -0
  54. package/dist/cli.js +35 -33
  55. package/dist/cli.js.map +1 -1
  56. package/dist/{formula-CUSB5R3P.js → formula-5775PTP2.js} +4 -4
  57. package/dist/{formula-UKLCJINT.js → formula-FNQYHMIY.js} +3 -3
  58. package/dist/{formula-YJIUNSWW.cjs → formula-VEFKYXPB.cjs} +9 -9
  59. package/dist/formula-VEFKYXPB.cjs.map +1 -0
  60. package/dist/{image-ocr-BMCOWLLB.cjs → image-ocr-6JZR734G.cjs} +17 -15
  61. package/dist/image-ocr-6JZR734G.cjs.map +1 -0
  62. package/dist/{image-ocr-OEEGTGDU.js → image-ocr-PYZU4TO7.js} +14 -9
  63. package/dist/image-ocr-PYZU4TO7.js.map +1 -0
  64. package/dist/{image-ocr-S5OLK6WH.js → image-ocr-YA2MQBRP.js} +9 -11
  65. package/dist/image-ocr-YA2MQBRP.js.map +1 -0
  66. package/dist/index.cjs +726 -630
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.d.cts +3 -0
  69. package/dist/index.d.ts +3 -0
  70. package/dist/index.js +152 -56
  71. package/dist/index.js.map +1 -1
  72. package/dist/mcp.js +60 -49
  73. package/dist/mcp.js.map +1 -1
  74. package/dist/{model-bundle-A6QPDJZ3.js → model-bundle-O6J7ORVC.js} +5 -5
  75. package/dist/{models-GAP4U6WU.js → models-YMOKZGCI.js} +7 -5
  76. package/dist/page-range-QHHFTL7X.cjs.map +1 -1
  77. package/dist/{parser-MRTO7R2Y.js → parser-3B6I2XL7.js} +344 -23
  78. package/dist/parser-3B6I2XL7.js.map +1 -0
  79. package/dist/{parser-BCN3SZZC.js → parser-PXATL5DM.js} +340 -22
  80. package/dist/parser-PXATL5DM.js.map +1 -0
  81. package/dist/{parser-DZMXH4V5.cjs → parser-YAQV7FBO.cjs} +454 -136
  82. package/dist/parser-YAQV7FBO.cjs.map +1 -0
  83. package/dist/pdf-ocr-2TTO5PX2.js +15 -0
  84. package/dist/pdf-ocr-T2JXQATA.cjs +16 -0
  85. package/dist/pdf-ocr-T2JXQATA.cjs.map +1 -0
  86. package/dist/pdf-ocr-WF2NQA6X.js +18 -0
  87. package/dist/{profile-io-XAZ2MDXD.js → profile-io-ZTV2MKRT.js} +3 -3
  88. package/dist/{rasterize-2YKKVEIR.js → rasterize-MMUZ537K.js} +3 -3
  89. package/dist/{redact-doc-XPV546P6.js → redact-doc-S42XGV5D.js} +11 -11
  90. package/dist/{render-ASDQSRPX.js → render-WCWPZKJ7.js} +7 -7
  91. package/dist/seal-WHK4GOHR.js +10 -0
  92. package/dist/{setup-M3MH2DHU.js → setup-D35LKZCH.js} +3 -3
  93. package/dist/visual-WL4TDE5B.js +20 -0
  94. package/dist/{watch-3DB2LMAP.js → watch-AX64CIMN.js} +41 -19
  95. package/dist/watch-AX64CIMN.js.map +1 -0
  96. package/package.json +1 -1
  97. package/dist/chunk-4ANWGSUT.cjs.map +0 -1
  98. package/dist/chunk-6ULM2YCN.js.map +0 -1
  99. package/dist/chunk-J6ZU7AIR.cjs.map +0 -1
  100. package/dist/chunk-KJSD2WNF.js.map +0 -1
  101. package/dist/chunk-NVG2GQNA.js.map +0 -1
  102. package/dist/chunk-NZMPU24H.js.map +0 -1
  103. package/dist/chunk-OE6XLTMC.cjs.map +0 -1
  104. package/dist/chunk-R45AG5K6.cjs.map +0 -1
  105. package/dist/chunk-S7PEBTU3.js.map +0 -1
  106. package/dist/chunk-W2UTB3RG.js.map +0 -1
  107. package/dist/chunk-WMLGS5QL.js.map +0 -1
  108. package/dist/chunk-X3PBLSV5.js.map +0 -1
  109. package/dist/chunk-YFHV4QP3.js.map +0 -1
  110. package/dist/chunk-ZXAH4AFS.cjs.map +0 -1
  111. package/dist/chunk-ZYJY44BT.js.map +0 -1
  112. package/dist/chunks-PPHEGDUD.js +0 -10
  113. package/dist/formula-YJIUNSWW.cjs.map +0 -1
  114. package/dist/image-ocr-BMCOWLLB.cjs.map +0 -1
  115. package/dist/image-ocr-OEEGTGDU.js.map +0 -1
  116. package/dist/image-ocr-S5OLK6WH.js.map +0 -1
  117. package/dist/parser-BCN3SZZC.js.map +0 -1
  118. package/dist/parser-DZMXH4V5.cjs.map +0 -1
  119. package/dist/parser-MRTO7R2Y.js.map +0 -1
  120. package/dist/pdf-ocr-4V7U52JI.js +0 -18
  121. package/dist/pdf-ocr-A3Q4YMCG.js +0 -15
  122. package/dist/pdf-ocr-CZDODSMI.cjs +0 -16
  123. package/dist/pdf-ocr-CZDODSMI.cjs.map +0 -1
  124. package/dist/seal-3XWSKD6C.js +0 -10
  125. package/dist/visual-Z6BCNJMU.js +0 -20
  126. package/dist/watch-3DB2LMAP.js.map +0 -1
  127. /package/dist/{-H6WNRHK6.js.map → -VQNLLLPE.js.map} +0 -0
  128. /package/dist/{chunk-O5SDUPB2.js.map → chunk-46DZF3RK.js.map} +0 -0
  129. /package/dist/{chunk-UGSRP7E2.js.map → chunk-6LMKQM7I.js.map} +0 -0
  130. /package/dist/{chunk-IHCQ4IE7.js.map → chunk-A3QDRMVD.js.map} +0 -0
  131. /package/dist/{chunk-TRB6OYMU.js.map → chunk-C75WE52C.js.map} +0 -0
  132. /package/dist/{chunk-AWXXLLIG.js.map → chunk-DYENR3PX.js.map} +0 -0
  133. /package/dist/{chunk-L5MDTA6T.js.map → chunk-PIMB2YNO.js.map} +0 -0
  134. /package/dist/{chunk-ZWPRA2SE.js.map → chunk-PQRP4NSF.js.map} +0 -0
  135. /package/dist/{chunk-ORMMGTNK.js.map → chunk-UDX7TSOC.js.map} +0 -0
  136. /package/dist/{chunks-PPHEGDUD.js.map → chunks-JD25E5PM.js.map} +0 -0
  137. /package/dist/{formula-CUSB5R3P.js.map → formula-5775PTP2.js.map} +0 -0
  138. /package/dist/{formula-UKLCJINT.js.map → formula-FNQYHMIY.js.map} +0 -0
  139. /package/dist/{model-bundle-A6QPDJZ3.js.map → model-bundle-O6J7ORVC.js.map} +0 -0
  140. /package/dist/{models-GAP4U6WU.js.map → models-YMOKZGCI.js.map} +0 -0
  141. /package/dist/{pdf-ocr-4V7U52JI.js.map → pdf-ocr-2TTO5PX2.js.map} +0 -0
  142. /package/dist/{pdf-ocr-A3Q4YMCG.js.map → pdf-ocr-WF2NQA6X.js.map} +0 -0
  143. /package/dist/{profile-io-XAZ2MDXD.js.map → profile-io-ZTV2MKRT.js.map} +0 -0
  144. /package/dist/{rasterize-2YKKVEIR.js.map → rasterize-MMUZ537K.js.map} +0 -0
  145. /package/dist/{redact-doc-XPV546P6.js.map → redact-doc-S42XGV5D.js.map} +0 -0
  146. /package/dist/{render-ASDQSRPX.js.map → render-WCWPZKJ7.js.map} +0 -0
  147. /package/dist/{seal-3XWSKD6C.js.map → seal-WHK4GOHR.js.map} +0 -0
  148. /package/dist/{setup-M3MH2DHU.js.map → setup-D35LKZCH.js.map} +0 -0
  149. /package/dist/{visual-Z6BCNJMU.js.map → visual-WL4TDE5B.js.map} +0 -0
package/README.md CHANGED
@@ -11,7 +11,7 @@ HWP 3.x/5.x, HWPX, HWPML, PDF, XLS, XLSX, DOCX, 이미지(PNG/JPG/WebP) — 관
11
11
 
12
12
  [English](./README-EN.md)
13
13
 
14
- > 📊 **PDF 공개 벤치(opendataloader-bench, 200문서) 종합 0.937(OCR 0.960·OCR+plain 0.967) — 공개된 12개 PDF 파서(상용 포함) 모두보다 높고, 쪽당 0.03초.** 한국 공문서는 원본 HWPX를 정답으로 채점해 HWPX 표 13,041개 전부 칸까지 일치합니다. → [성능 한눈에 보기](#-성능-한눈에-보기)
14
+ > 📊 **PDF 공개 벤치(opendataloader-bench, 200문서) 종합 0.940(OCR 0.960·OCR+plain 0.967) — 공개된 12개 PDF 파서(상용 포함) 모두보다 높고, 쪽당 0.03초.** 한국 공문서는 원본 HWPX를 정답으로 채점해 HWPX 표 13,041개 전부 칸까지 일치합니다. → [성능 한눈에 보기](#-성능-한눈에-보기)
15
15
 
16
16
  [![Kordoc 활용하기 — 영상 보기](./docs/video-demo.jpg)](https://youtu.be/Q13GmgDcIw0)
17
17
 
@@ -56,6 +56,11 @@ Windows 도 자동으로 `cmd /c npx` 래핑. 수동 JSON 편집 불필요. 재
56
56
  > ```
57
57
  > 이후 PowerShell 재시작 → `npx -y kordoc setup` 그대로 됨.
58
58
 
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
+ > ```
63
+
59
64
  ### Claude Code 플러그인으로 설치
60
65
 
61
66
  MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
@@ -99,12 +104,13 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
99
104
 
100
105
  | 순위 | 엔진 | 종합 | 읽기 순서 | 표 | 제목 | 쪽당 시간 |
101
106
  | ---: | --- | ---: | ---: | ---: | ---: | ---: |
102
- | **1** | **kordoc 기본값** | **0.937** | **0.938** | **0.936** | **0.933** | **0.03초** |
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초 |
103
109
  | 참고 | kordoc `plain: true` (그림 자리 표시·링크 URL 없이) | 0.946 | 0.947 | 0.937 | 0.940 | 0.03초 |
104
110
  | 참고 | kordoc `ocr: true` (그림 속 글까지) | 0.960 | 0.960 | 0.979 | 0.949 | 0.46초 |
105
111
  | 참고 | kordoc `ocr: true, plain: true` | 0.967 | 0.968 | 0.981 | 0.956 | 0.46초 |
106
112
  | 참고 | kordoc `plain: true, htmlTables: true` | 0.949 | 0.954 | 0.940 | 0.943 | 0.03초 |
107
- | 참고 | kordoc `ocr: true, plain: true, htmlTables: true` | 0.971 | 0.975 | 0.983 | 0.958 | 0.46초 |
113
+ | 참고 | kordoc `ocr: true, plain: true, htmlTables: true` | 0.973 | 0.977 | 0.983 | 0.959 | 0.46초 |
108
114
  | 2 | opendataloader-hybrid | 0.907 | 0.934 | 0.928 | 0.821 | 0.46초 |
109
115
  | 3 | nutrient (상용) | 0.885 | 0.925 | 0.708 | 0.819 | 0.01초 |
110
116
  | 4 | docling | 0.882 | 0.898 | 0.887 | 0.824 | 0.76초 |
@@ -119,9 +125,9 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
119
125
  | 13 | liteparse | 0.576 | 0.866 | 0.000 | 0.000 | 1.06초 |
120
126
 
121
127
  - 기본값만으로 **종합·읽기 순서·표·제목 네 항목 모두 1위**입니다. GPU·클라우드 API·LLM 없이 Node.js 하나로, 쪽당 0.04초에 돕니다.
122
- - `ocr: true`를 켜면 차트·로고·스캔처럼 **그림 속 글자까지** 로컬에서 읽어 0.960이 됩니다. `plain: true`는 그림 자리 표시·링크 URL·밑줄/굵게 표기를 빼 색인·RAG 용 글만 냅니다(정답지가 이런 표기를 담지 않아 점수도 오릅니다). 기본값은 OCR을 돌리지 않고, 그림·스캔 쪽이 있으면 `NEEDS_OCR`·`SKIPPED_IMAGE` 경고로 알려 `ocr: true`로 다시 파싱할 수 있게 합니다.
128
+ - `ocr: true`를 켜면 차트·로고·스캔처럼 **그림 속 글자까지** 로컬에서 읽어 0.960이 됩니다. `plain: true`는 그림 자리 표시·링크 URL·밑줄/굵게 표기를 빼 색인·RAG 용 글만 냅니다(정답지가 이런 표기를 담지 않아 점수도 오릅니다). 기본값은 OCR 모델이 이미 캐시에 있으면(`kordoc models`·앞선 `ocr: true` 로 받은 경우) **텍스트층이 없는 쪽(스캔·곡선 글자)만** 자동으로 읽고, 모델이 없으면 다운로드하지 않고 `NEEDS_OCR` 경고로 알립니다. 글이 있는 쪽 안의 그림 속 글은 `SKIPPED_IMAGE` 경고로 알리고 `ocr: true` 로 읽습니다(`ocr: false` 는 자동 OCR 도 끔).
123
129
  - 다른 엔진 점수는 벤치 저장소가 공개한 결과(Apple M4 32GB)이고, kordoc은 같은 PDF·같은 정답·**수정하지 않은 원본 채점기**로 재었습니다(Apple M4 24GB, 200문서 한 프로세스 순차 8초). 벤치 저장소의 opendataloader-hybrid 예측을 같은 채점기로 다시 채점하면 0.9066으로 공개 점수와 같습니다. 재현: `node bench/odl-bench.mjs <벤치 클론>` → 벤치의 `src/evaluator.py`.
124
- - 벤치 저장소에 결과만 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.971 로 두 줄 모두 앞섭니다.
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 로 두 줄 모두 앞섭니다.
125
131
 
126
132
  ### 한국 공문서: 원본 HWPX를 정답으로 채점합니다
127
133
 
@@ -131,8 +137,8 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
131
137
  | --- | --- | --- |
132
138
  | HWPX 본문·표 | 2,286문서, 표 13,041개 | 글 누락 0 · 표 13,041개 전부 칸까지 일치 · 읽기 순서 100% |
133
139
  | HWP 5.x | HWPX와 짝 1,120쌍 | HWPX 결과와 전부 일치 |
134
- | PDF 글 | 751쌍 (HWPX 정답) | 글자 재현율 99.5% · 정확도 97.0% · 읽기 순서 98.3% |
135
- | PDF 표 | 716쌍, 표 2,692개 (HWPX 정답) | 표 찾기 98.9% · 칸까지 완전 일치 94.2% · 칸 F1 0.966 |
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 |
136
142
  | PDF 전체 | 1,911문서 (텍스트층 채점 1,724) | 글 커버리지 99.8% |
137
143
  | 스캔 OCR (내장, 로컬 CPU) | 54문서 104쪽 (216dpi 렌더) | 글자 재현율 98.1% · 한글 재현율 99.3% · 정확도 97.5% · 쪽당 0.9초 |
138
144
  | DOCX·XLSX·XLS·HML | 88문서 | 글·숫자 누락 0 |
@@ -140,6 +146,8 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
140
146
 
141
147
  모든 수치는 `npm run bench:gate`로 재현되며, 배포(`npm publish`) 때마다 이 게이트를 통과해야만 올라갑니다.
142
148
 
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
+
143
151
  ### HWP·HWPX → Markdown: HwpForge와 같은 채점기로
144
152
 
145
153
  같은 코퍼스를 [HwpForge](https://github.com/ai-screams/HwpForge) 0.16.6(`to_md`, lossy)과 kordoc으로 각각 Markdown으로 바꾸고, **원본 HWPX XML을 정답**으로 같은 채점기에 넣었습니다(표는 두 출력 모두 같은 Markdown 표 파서로 격자화, 1열 표는 양쪽 모두 표 채점에서 제외).
@@ -159,6 +167,10 @@ HwpForge는 생성·편집 중심 라이브러리이고, Markdown 표가 파이
159
167
 
160
168
  ---
161
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
+
162
174
  ## v4.15.7 변경사항
163
175
 
164
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` 한글 이름)를 고쳤습니다.
@@ -329,7 +341,7 @@ rhwp 최신 devel(v0.8.3~0.8.4)을 훑어 kordoc 의 HWP3 디코더에 반영했
329
341
  ## v4.10.0 변경사항
330
342
 
331
343
  - **📡 실패 계약 전 포맷 확장 (#69)**: 실패 JSON(`success:false` + `code`)이 `--format json` 전용에서 **markdown·chunks 포함 전 포맷**으로 확장됐습니다. 헤드리스 파이프라인이 stderr 한국어 문구 파싱 없이 원인 코드로 분기할 수 있습니다. `code` 값 집합·종료 코드 규칙·안정성 보증을 README [실패 계약](#실패-계약--기계-판독-가능한-실패-json-v4100-69) 표로 문서화했습니다. (@sorbetsharkroundhand 제보)
332
- - **🖼️ `images/manifest.json` (#70)**: 이미지 저장 시 `[ { name, mimeType, bytes, source } ]` manifest 를 함께 방출합니다. `mimeType` 은 매직바이트 실측 우선이라 확장자 추측이 필요 없습니다. DOCX 이미지의 미지 확장자를 `image/png` 로 단정하던 오폴백도 실측 판별로 교체. (@sorbetsharkroundhand 제보)
344
+ - **🖼️ `images/<문서 이름>/manifest.json` (#70·#98)**: 이미지 저장 시 `[ { name, mimeType, bytes, source } ]` manifest 를 함께 방출합니다. `mimeType` 은 매직바이트 실측 우선이라 확장자 추측이 필요 없습니다. DOCX 이미지의 미지 확장자를 `image/png` 로 단정하던 오폴백도 실측 판별로 교체. (@sorbetsharkroundhand 제보)
333
345
  - **🩹 patchHwp soft-wrap 축소 치환 무결성 (#71)**: 자동 줄바꿈으로 LINE_SEG 가 2세그먼트 이상인 문단을 더 짧은 한 줄로 치환하면 잔존 세그먼트의 textpos 가 새 nChars 밖을 가리켜 **한컴이 "문서 손상/변조" 경고로 열기를 거부**하던 버그 수정 — 범위를 벗어난 꼬리 세그먼트만 잘라내고 lineSegCount 를 정합화합니다(유효 세그먼트는 보존해 soft-wrap 렌더 유지). 재작성 압축 스트림에 한컴 실저장본의 8바이트 꼬리(CRC32+비압축 크기, 코퍼스 실측 7/7)도 복원합니다. (@heesun-woodi 제보)
334
346
 
335
347
  ## v4.9.2 변경사항
@@ -1186,9 +1198,9 @@ npx kordoc watch ./문서 --webhook https://api/hook # 웹훅 알림
1186
1198
 
1187
1199
  **안정성 보증**: 종료 코드 규칙(성공 0 / 실패 1)과 실패 JSON 의 필드(`success`·`fileType`·`error`·`code`)는 유지되고, `code` 값 집합은 **추가만** 됩니다(기존 값 변경·제거 없음). 어느 입력에서 났는지 알 수 있는 `file`(basename) 필드도 **추가만** 됩니다. `error` 문구는 사람용이라 계약이 아닙니다.
1188
1200
 
1189
- ### 이미지 번들 — `images/manifest.json` (v4.10.0+, #70)
1201
+ ### 이미지 번들 — `images/<문서 이름>/manifest.json` (v4.10.0+, #70 · #98)
1190
1202
 
1191
- `-o`/`-d` 로 저장할 때 추출 이미지는 `images/` 에 저장되고, 같은 폴더에 `manifest.json` 이 함께 나옵니다. 소비자는 확장자·매직바이트 추측 없이 manifest 로 형식을 분기하면 됩니다.
1203
+ `-o`/`-d` 로 저장할 때 추출 이미지는 문서마다 `images/<문서 이름>/` 에 저장되고(문서 이름 = `-o` 출력 파일 이름 또는 `-d` 입력 파일 이름에서 확장자를 뺀 것), 같은 폴더에 `manifest.json` 이 함께 나옵니다. 마크다운 링크도 `images/<문서 이름>/image_001.png` 를 가리킵니다(공백·괄호는 퍼센트 인코딩). 여러 문서를 같은 폴더로 변환해도 그림이 서로 덮어쓰이지 않습니다. 소비자는 확장자·매직바이트 추측 없이 manifest 로 형식을 분기하면 됩니다.
1192
1204
 
1193
1205
  ```json
1194
1206
  [ { "name": "image_001.png", "mimeType": "image/png", "bytes": 68, "source": "BinData/image1.png" } ]
@@ -50,17 +50,17 @@ import {
50
50
  unknownFontWarnings,
51
51
  usesGaejosikMunche,
52
52
  validateHwpx
53
- } from "./chunk-S7PEBTU3.js";
53
+ } from "./chunk-CB5XDCSL.js";
54
54
  import {
55
55
  blocksToPages
56
- } from "./chunk-AWXXLLIG.js";
56
+ } from "./chunk-DYENR3PX.js";
57
57
  import {
58
58
  applySplices,
59
59
  buildParagraphSplices,
60
60
  buildRangeSplices,
61
61
  placeSealHwpx,
62
62
  scanSectionXml
63
- } from "./chunk-UGSRP7E2.js";
63
+ } from "./chunk-6LMKQM7I.js";
64
64
  import {
65
65
  DEFAULT_REDACT_RULES,
66
66
  redactMarkdown,
@@ -68,7 +68,7 @@ import {
68
68
  } from "./chunk-3WSNLH4B.js";
69
69
  import {
70
70
  blocksToChunks
71
- } from "./chunk-L5MDTA6T.js";
71
+ } from "./chunk-PIMB2YNO.js";
72
72
  import {
73
73
  SPACE_EM_FIXED,
74
74
  SPACE_EM_FONT,
@@ -93,20 +93,20 @@ import {
93
93
  renderSceneToHtml,
94
94
  simulateWrap,
95
95
  simulateWrapKeepWord
96
- } from "./chunk-C572DIHX.js";
96
+ } from "./chunk-476OYHGZ.js";
97
97
  import "./chunk-7FCNOKOR.js";
98
- import "./chunk-NVG2GQNA.js";
98
+ import "./chunk-UQ6TZDW6.js";
99
99
  import "./chunk-VQFA55IC.js";
100
- import "./chunk-O5SDUPB2.js";
100
+ import "./chunk-46DZF3RK.js";
101
101
  import {
102
102
  blocksToMarkdown,
103
103
  flattenLayoutTables,
104
104
  hasStructuredCellContent
105
- } from "./chunk-WHZUJH2O.js";
106
- import "./chunk-YFHV4QP3.js";
105
+ } from "./chunk-TGD7UFHD.js";
106
+ import "./chunk-3JWMDL3S.js";
107
107
  import {
108
108
  VERSION
109
- } from "./chunk-F5FHYJKJ.js";
109
+ } from "./chunk-RC4C7Q33.js";
110
110
  export {
111
111
  BUILTIN_TEMPLATES,
112
112
  DEFAULT_REDACT_RULES,
@@ -196,4 +196,4 @@ export {
196
196
  usesGaejosikMunche,
197
197
  validateHwpx
198
198
  };
199
- //# sourceMappingURL=-H6WNRHK6.js.map
199
+ //# sourceMappingURL=-VQNLLLPE.js.map
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- KordocError
4
- } from "./chunk-F5FHYJKJ.js";
3
+ KordocError,
4
+ OPTIONAL_DEP_INSTALL_HINT
5
+ } from "./chunk-RC4C7Q33.js";
5
6
 
6
7
  // src/render/rasterize.ts
7
8
  var densityExponent = null;
@@ -40,7 +41,7 @@ async function loadSharp() {
40
41
  return mod.default ?? mod;
41
42
  } catch {
42
43
  throw new KordocError(
43
- 'PNG \uB798\uC2A4\uD130\uC5D0\uB294 sharp\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (npm install sharp). sharp \uC5C6\uC774 \uC4F0\uB824\uBA74 format: "svg" + output_path\uB85C SVG \uD30C\uC77C \uC800\uC7A5\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
44
+ 'PNG \uB798\uC2A4\uD130\uC5D0\uB294 sharp\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (npm install sharp). sharp \uC5C6\uC774 \uC4F0\uB824\uBA74 format: "svg" + output_path\uB85C SVG \uD30C\uC77C \uC800\uC7A5\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.' + OPTIONAL_DEP_INSTALL_HINT
44
45
  );
45
46
  }
46
47
  }
@@ -87,4 +88,4 @@ export {
87
88
  rasterizeSvg,
88
89
  rasterizePageSvg
89
90
  };
90
- //# sourceMappingURL=chunk-YFHV4QP3.js.map
91
+ //# sourceMappingURL=chunk-3JWMDL3S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/render/rasterize.ts"],"sourcesContent":["/** SVG → PNG/JPEG 래스터 — sharp optional 의존 (미설치 시 KordocError, SVG 경로는 sharp 불필요) */\n\nimport { KordocError, OPTIONAL_DEP_INSTALL_HINT } from \"../utils.js\"\n\nexport interface RasterizeOptions {\n /** 출력 최대 폭 px (기본 1400) */\n maxWidthPx?: number\n /** 출력 최대 높이 px (기본 8000 — 멀티페이지 세로 스택 대비) */\n maxHeightPx?: number\n /** PNG 바이트 상한 (기본 4MB) — 초과 시 절반 스케일로 1회 재시도 */\n maxBytes?: number\n}\n\nexport interface RasterizeResult {\n png: Buffer\n widthPx: number\n heightPx: number\n /** pt → px 배율 */\n scale: number\n}\n\n/** sharp 최소 타입 — optional 의존이라 정적 import 없이 동적 로드 */\nexport type SharpLike = (input: Buffer, opts?: { density?: number; limitInputPixels?: number }) => {\n png(): { toBuffer(): Promise<Buffer> }\n jpeg(opts?: { quality?: number }): { toBuffer(): Promise<Buffer> }\n extract(region: { left: number; top: number; width: number; height: number }): ReturnType<SharpLike>\n toBuffer(): Promise<Buffer>\n metadata(): Promise<{ width?: number; height?: number }>\n}\n\n/**\n * pt 단위 SVG 의 density 배율 지수 — libvips/rsvg 조합에 따라 `density: 72×s` 가 s px/pt(선형)를 내기도,\n * s² px/pt 를 내기도 한다(sharp 0.35·vips 8.18·rsvg 2.62: pt 단위는 제곱, px 단위는 선형 — 실측 2026-09-06).\n * 배율을 가정하면 crop 픽셀 사각형이 실제 래스터와 어긋나 표 영역의 좌상단 1/4 만 잘려 나온다(v4.13.0 실사고).\n * 프로세스당 1회 프로브로 지수를 재고, 산출 픽셀 크기는 항상 실제 이미지에서 다시 읽는다.\n */\nlet densityExponent: number | null = null\nconst PROBE_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" width=\"100pt\" height=\"100pt\"><rect width=\"100\" height=\"100\" fill=\"#fff\"/></svg>`\n\n/** PNG IHDR 폭 (바이트 16~19, BE) — 프로브는 metadata 없이 읽는다 */\nfunction pngWidth(buf: Buffer): number {\n return buf.length >= 24 && buf[0] === 0x89 && buf[1] === 0x50 ? buf.readUInt32BE(16) : 0\n}\n\nasync function probeDensityExponent(sharp: SharpLike): Promise<number> {\n if (densityExponent !== null) return densityExponent\n try {\n const png = await sharp(Buffer.from(PROBE_SVG), { density: 144 }).png().toBuffer()\n const k = pngWidth(png) / 100\n const e = k > 0 ? Math.log(k) / Math.log(2) : 1\n densityExponent = Number.isFinite(e) && e > 0.5 ? Math.round(e * 4) / 4 : 1\n } catch {\n densityExponent = 1\n }\n return densityExponent\n}\n\n/** 요청 배율(px/pt) → sharp density */\nfunction densityFor(scale: number, exponent: number): number {\n return 72 * Math.pow(scale, 1 / exponent)\n}\n\n/** 실제 픽셀 크기 — 보고값은 가정이 아니라 산출 이미지에서 읽는다 */\nasync function actualSize(sharp: SharpLike, data: Buffer, fallbackW: number, fallbackH: number): Promise<{ w: number; h: number }> {\n const w = pngWidth(data)\n if (w > 0 && data.length >= 24) return { w, h: data.readUInt32BE(20) }\n try {\n const m = await sharp(data).metadata()\n if (m.width && m.height) return { w: m.width, h: m.height }\n } catch { /* 폴백 */ }\n return { w: fallbackW, h: fallbackH }\n}\n\nexport async function loadSharp(): Promise<SharpLike> {\n try {\n const mod: any = await import(\"sharp\")\n return mod.default ?? mod\n } catch {\n throw new KordocError(\n 'PNG 래스터에는 sharp가 필요합니다 (npm install sharp). sharp 없이 쓰려면 format: \"svg\" + output_path로 SVG 파일 저장을 사용하세요.' +\n OPTIONAL_DEP_INSTALL_HINT,\n )\n }\n}\n\n/** SVG 문자열을 PNG로 래스터. widthPt/heightPt는 SVG 캔버스 크기(pt). */\nexport async function rasterizeSvg(\n svg: string,\n widthPt: number,\n heightPt: number,\n options?: RasterizeOptions,\n): Promise<RasterizeResult> {\n const sharp = await loadSharp()\n if (!(widthPt > 0) || !(heightPt > 0)) throw new KordocError(`잘못된 SVG 크기: ${widthPt}x${heightPt}pt`)\n const maxW = options?.maxWidthPx ?? 1400\n const maxH = options?.maxHeightPx ?? 8000\n const maxBytes = options?.maxBytes ?? 4 * 1024 * 1024\n\n const exponent = await probeDensityExponent(sharp)\n const render = async (scale: number): Promise<RasterizeResult> => {\n // sharp 의 SVG 기본 밀도 72DPI 기준. 배율 지수는 프로브 값, 픽셀 크기·실배율은 산출물에서 읽는다\n const png = await sharp(Buffer.from(svg), { density: densityFor(scale, exponent), limitInputPixels: 268402689 })\n .png()\n .toBuffer()\n const { w, h } = await actualSize(sharp, png, Math.round(widthPt * scale), Math.round(heightPt * scale))\n return { png, widthPx: w, heightPx: h, scale: w / widthPt }\n }\n\n const scale = Math.min(maxW / widthPt, maxH / heightPt, 2)\n let result = await render(scale)\n if (result.png.length > maxBytes && scale > 0.25) {\n result = await render(scale / 2)\n }\n if (result.png.length > maxBytes) {\n throw new KordocError(\n `PNG가 상한(${(maxBytes / 1024 / 1024).toFixed(0)}MB)을 초과합니다 (${(result.png.length / 1024 / 1024).toFixed(1)}MB) — format: \"svg\" + output_path로 파일 저장을 사용하세요`,\n )\n }\n return result\n}\n\n// ─── 페이지 단위 래스터 (#75 Task 5) ────────────────────\n\nexport type RasterFormat = \"png\" | \"jpeg\"\n\nexport interface PageRasterOptions {\n format?: RasterFormat\n /** 출력 최대 폭 px (기본 1400) */\n maxWidthPx?: number\n /** 출력 최대 높이 px (기본 4000 — 페이지 1장) */\n maxHeightPx?: number\n /** JPEG 품질 (기본 85) */\n quality?: number\n}\n\nexport interface PageRasterResult {\n data: Buffer\n mimeType: \"image/png\" | \"image/jpeg\"\n widthPx: number\n heightPx: number\n /** pt → px 실배율 — crop 픽셀 좌표는 이 값으로만 환산한다 (DPI 재추정 금지) */\n scale: number\n}\n\n/**\n * 페이지 standalone SVG 1장 → PNG/JPEG. 두 포맷은 같은 SVG·같은 배율을 쓴다.\n * 픽셀 크기·scale 은 산출 이미지에서 읽은 실제값(crop 은 이 scale 로만 환산).\n */\nexport async function rasterizePageSvg(svg: string, widthPt: number, heightPt: number, options: PageRasterOptions = {}): Promise<PageRasterResult> {\n const sharp = await loadSharp()\n if (!(widthPt > 0) || !(heightPt > 0)) throw new KordocError(`잘못된 페이지 크기: ${widthPt}x${heightPt}pt`)\n const maxW = options.maxWidthPx ?? 1400\n const maxH = options.maxHeightPx ?? 4000\n const scale = Math.min(maxW / widthPt, maxH / heightPt, 2)\n const format = options.format ?? \"png\"\n const exponent = await probeDensityExponent(sharp)\n const pipeline = sharp(Buffer.from(svg), { density: densityFor(scale, exponent), limitInputPixels: 268402689 })\n const data = format === \"jpeg\" ? await pipeline.jpeg({ quality: options.quality ?? 85 }).toBuffer() : await pipeline.png().toBuffer()\n const { w, h } = await actualSize(sharp, data, Math.round(widthPt * scale), Math.round(heightPt * scale))\n return { data, mimeType: format === \"jpeg\" ? \"image/jpeg\" : \"image/png\", widthPx: w, heightPx: h, scale: w / widthPt }\n}\n"],"mappings":"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","names":["scale"]}