kordoc 4.15.6 → 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.
- package/README.md +50 -9
- package/dist/{-F73GANY7.js → -VQNLLLPE.js} +11 -11
- package/dist/{chunk-UKD7C2WS.js → chunk-3JWMDL3S.js} +5 -4
- package/dist/chunk-3JWMDL3S.js.map +1 -0
- package/dist/{chunk-FWT24USP.js → chunk-43FKGDDO.js} +354 -135
- package/dist/chunk-43FKGDDO.js.map +1 -0
- package/dist/{chunk-KJKMBV5K.js → chunk-46DZF3RK.js} +5 -4
- package/dist/chunk-46DZF3RK.js.map +1 -0
- package/dist/{chunk-NK5XFKL4.js → chunk-476OYHGZ.js} +24 -7
- package/dist/chunk-476OYHGZ.js.map +1 -0
- package/dist/{chunk-QC736WXT.js → chunk-6LMKQM7I.js} +3 -3
- package/dist/{chunk-U7J7BFV3.js → chunk-A3QDRMVD.js} +2 -2
- package/dist/{chunk-SQJ7Q2Q5.cjs → chunk-B5NEGP65.cjs} +61 -76
- package/dist/chunk-B5NEGP65.cjs.map +1 -0
- package/dist/{chunk-OQ3DY7RF.js → chunk-C75WE52C.js} +2 -2
- package/dist/{chunk-FA5JIHUV.js → chunk-CB5XDCSL.js} +272 -74
- package/dist/chunk-CB5XDCSL.js.map +1 -0
- package/dist/{chunk-2DLW4CMC.cjs → chunk-DRY3L5KQ.cjs} +57 -61
- package/dist/chunk-DRY3L5KQ.cjs.map +1 -0
- package/dist/{chunk-2OKMF2KY.js → chunk-DYENR3PX.js} +2 -2
- package/dist/{chunk-UGIESTA6.js → chunk-GOQBON77.js} +10 -2
- package/dist/{chunk-UGIESTA6.js.map → chunk-GOQBON77.js.map} +1 -1
- package/dist/{chunk-QJX4IR77.cjs → chunk-H6KMTYYZ.cjs} +3 -3
- package/dist/{chunk-QJX4IR77.cjs.map → chunk-H6KMTYYZ.cjs.map} +1 -1
- package/dist/{chunk-RSKR6A6S.js → chunk-HIVK5V27.js} +428 -128
- package/dist/chunk-HIVK5V27.js.map +1 -0
- package/dist/{chunk-ISJRNC2G.js → chunk-JB5X5NNL.js} +50 -12
- package/dist/chunk-JB5X5NNL.js.map +1 -0
- package/dist/{chunk-SOM7JIKG.cjs → chunk-MH6MAL64.cjs} +468 -168
- package/dist/chunk-MH6MAL64.cjs.map +1 -0
- package/dist/{chunk-DRQW5SYI.js → chunk-N6TPODYL.js} +15 -2
- package/dist/chunk-N6TPODYL.js.map +1 -0
- package/dist/{chunk-A63JPQEE.js → chunk-NNYF2AGC.js} +45 -49
- package/dist/chunk-NNYF2AGC.js.map +1 -0
- package/dist/{chunk-FBZCWSPN.cjs → chunk-NWODPYBD.cjs} +11 -3
- package/dist/chunk-NWODPYBD.cjs.map +1 -0
- package/dist/{chunk-765SAMGS.js → chunk-PIMB2YNO.js} +2 -2
- package/dist/{chunk-HT2TGO46.js → chunk-PQRP4NSF.js} +2 -2
- package/dist/{chunk-U6SS43O6.js → chunk-QJERJZJ6.js} +1 -1
- package/dist/chunk-QJERJZJ6.js.map +1 -0
- package/dist/{chunk-JX7URXOL.js → chunk-RC4C7Q33.js} +10 -2
- package/dist/{chunk-JX7URXOL.js.map → chunk-RC4C7Q33.js.map} +1 -1
- package/dist/{chunk-O6A6SK55.js → chunk-TGD7UFHD.js} +45 -49
- package/dist/chunk-TGD7UFHD.js.map +1 -0
- package/dist/{chunk-6OQTREJ5.js → chunk-UDX7TSOC.js} +2 -2
- package/dist/{chunk-3PDCVDSO.js → chunk-UQ6TZDW6.js} +1 -1
- package/dist/chunk-UQ6TZDW6.js.map +1 -0
- package/dist/{chunk-WXEXWGPT.js → chunk-XHOZB2WP.js} +59 -71
- package/dist/chunk-XHOZB2WP.js.map +1 -0
- package/dist/{chunk-CND4NRNF.cjs → chunk-ZY5K3LPM.cjs} +1 -1
- package/dist/chunk-ZY5K3LPM.cjs.map +1 -0
- package/dist/chunks-JD25E5PM.js +10 -0
- package/dist/cli.js +62 -39
- package/dist/cli.js.map +1 -1
- package/dist/{formula-HU2APQRR.js → formula-5775PTP2.js} +4 -4
- package/dist/{formula-P54CLKTH.js → formula-FNQYHMIY.js} +3 -3
- package/dist/{formula-LWU5I4NX.cjs → formula-VEFKYXPB.cjs} +9 -9
- package/dist/{formula-LWU5I4NX.cjs.map → formula-VEFKYXPB.cjs.map} +1 -1
- package/dist/{image-ocr-RCM3KL27.cjs → image-ocr-6JZR734G.cjs} +17 -15
- package/dist/image-ocr-6JZR734G.cjs.map +1 -0
- package/dist/{image-ocr-2UCO6DQD.js → image-ocr-PYZU4TO7.js} +14 -9
- package/dist/image-ocr-PYZU4TO7.js.map +1 -0
- package/dist/{image-ocr-I62V7QB2.js → image-ocr-YA2MQBRP.js} +9 -11
- package/dist/image-ocr-YA2MQBRP.js.map +1 -0
- package/dist/index.cjs +967 -751
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +301 -85
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +67 -50
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-MSYSZSDJ.js → model-bundle-O6J7ORVC.js} +5 -5
- package/dist/{models-BCUGEIQX.js → models-YMOKZGCI.js} +7 -5
- package/dist/{parser-SI4DSZ6J.js → parser-3B6I2XL7.js} +854 -39
- package/dist/parser-3B6I2XL7.js.map +1 -0
- package/dist/{parser-KZDC4LLY.js → parser-PXATL5DM.js} +850 -38
- package/dist/parser-PXATL5DM.js.map +1 -0
- package/dist/{parser-M26S76U2.cjs → parser-YAQV7FBO.cjs} +962 -150
- package/dist/parser-YAQV7FBO.cjs.map +1 -0
- package/dist/pdf-ocr-2TTO5PX2.js +15 -0
- package/dist/pdf-ocr-T2JXQATA.cjs +16 -0
- package/dist/{pdf-ocr-IOQA7ZQM.cjs.map → pdf-ocr-T2JXQATA.cjs.map} +1 -1
- package/dist/pdf-ocr-WF2NQA6X.js +18 -0
- package/dist/{profile-io-QHKLJKAM.js → profile-io-ZTV2MKRT.js} +3 -3
- package/dist/{rasterize-JY72CIBX.js → rasterize-MMUZ537K.js} +3 -3
- package/dist/{redact-doc-BWS4KRNH.js → redact-doc-S42XGV5D.js} +11 -11
- package/dist/{render-53DA6GD5.js → render-WCWPZKJ7.js} +7 -7
- package/dist/seal-WHK4GOHR.js +10 -0
- package/dist/{setup-GHLZX5VK.js → setup-D35LKZCH.js} +3 -3
- package/dist/visual-WL4TDE5B.js +20 -0
- package/dist/{watch-SYDX4ZLX.js → watch-AX64CIMN.js} +41 -19
- package/dist/watch-AX64CIMN.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-2DLW4CMC.cjs.map +0 -1
- package/dist/chunk-3PDCVDSO.js.map +0 -1
- package/dist/chunk-A63JPQEE.js.map +0 -1
- package/dist/chunk-CND4NRNF.cjs.map +0 -1
- package/dist/chunk-DRQW5SYI.js.map +0 -1
- package/dist/chunk-FA5JIHUV.js.map +0 -1
- package/dist/chunk-FBZCWSPN.cjs.map +0 -1
- package/dist/chunk-FWT24USP.js.map +0 -1
- package/dist/chunk-ISJRNC2G.js.map +0 -1
- package/dist/chunk-KJKMBV5K.js.map +0 -1
- package/dist/chunk-NK5XFKL4.js.map +0 -1
- package/dist/chunk-O6A6SK55.js.map +0 -1
- package/dist/chunk-RSKR6A6S.js.map +0 -1
- package/dist/chunk-SOM7JIKG.cjs.map +0 -1
- package/dist/chunk-SQJ7Q2Q5.cjs.map +0 -1
- package/dist/chunk-U6SS43O6.js.map +0 -1
- package/dist/chunk-UKD7C2WS.js.map +0 -1
- package/dist/chunk-WXEXWGPT.js.map +0 -1
- package/dist/chunks-RJXLSR5O.js +0 -10
- package/dist/image-ocr-2UCO6DQD.js.map +0 -1
- package/dist/image-ocr-I62V7QB2.js.map +0 -1
- package/dist/image-ocr-RCM3KL27.cjs.map +0 -1
- package/dist/parser-KZDC4LLY.js.map +0 -1
- package/dist/parser-M26S76U2.cjs.map +0 -1
- package/dist/parser-SI4DSZ6J.js.map +0 -1
- package/dist/pdf-ocr-IOQA7ZQM.cjs +0 -16
- package/dist/pdf-ocr-KA6BQTAB.js +0 -18
- package/dist/pdf-ocr-TPTWVV57.js +0 -15
- package/dist/seal-TJX6N5EE.js +0 -10
- package/dist/visual-JSTROEWO.js +0 -20
- package/dist/watch-SYDX4ZLX.js.map +0 -1
- /package/dist/{-F73GANY7.js.map → -VQNLLLPE.js.map} +0 -0
- /package/dist/{chunk-QC736WXT.js.map → chunk-6LMKQM7I.js.map} +0 -0
- /package/dist/{chunk-U7J7BFV3.js.map → chunk-A3QDRMVD.js.map} +0 -0
- /package/dist/{chunk-OQ3DY7RF.js.map → chunk-C75WE52C.js.map} +0 -0
- /package/dist/{chunk-2OKMF2KY.js.map → chunk-DYENR3PX.js.map} +0 -0
- /package/dist/{chunk-765SAMGS.js.map → chunk-PIMB2YNO.js.map} +0 -0
- /package/dist/{chunk-HT2TGO46.js.map → chunk-PQRP4NSF.js.map} +0 -0
- /package/dist/{chunk-6OQTREJ5.js.map → chunk-UDX7TSOC.js.map} +0 -0
- /package/dist/{chunks-RJXLSR5O.js.map → chunks-JD25E5PM.js.map} +0 -0
- /package/dist/{formula-HU2APQRR.js.map → formula-5775PTP2.js.map} +0 -0
- /package/dist/{formula-P54CLKTH.js.map → formula-FNQYHMIY.js.map} +0 -0
- /package/dist/{model-bundle-MSYSZSDJ.js.map → model-bundle-O6J7ORVC.js.map} +0 -0
- /package/dist/{models-BCUGEIQX.js.map → models-YMOKZGCI.js.map} +0 -0
- /package/dist/{pdf-ocr-KA6BQTAB.js.map → pdf-ocr-2TTO5PX2.js.map} +0 -0
- /package/dist/{pdf-ocr-TPTWVV57.js.map → pdf-ocr-WF2NQA6X.js.map} +0 -0
- /package/dist/{profile-io-QHKLJKAM.js.map → profile-io-ZTV2MKRT.js.map} +0 -0
- /package/dist/{rasterize-JY72CIBX.js.map → rasterize-MMUZ537K.js.map} +0 -0
- /package/dist/{redact-doc-BWS4KRNH.js.map → redact-doc-S42XGV5D.js.map} +0 -0
- /package/dist/{render-53DA6GD5.js.map → render-WCWPZKJ7.js.map} +0 -0
- /package/dist/{seal-TJX6N5EE.js.map → seal-WHK4GOHR.js.map} +0 -0
- /package/dist/{setup-GHLZX5VK.js.map → setup-D35LKZCH.js.map} +0 -0
- /package/dist/{visual-JSTROEWO.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.
|
|
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
|
[](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,8 +104,13 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
|
|
|
99
104
|
|
|
100
105
|
| 순위 | 엔진 | 종합 | 읽기 순서 | 표 | 제목 | 쪽당 시간 |
|
|
101
106
|
| ---: | --- | ---: | ---: | ---: | ---: | ---: |
|
|
102
|
-
| **1** | **kordoc 기본값** | **0.
|
|
103
|
-
| 참고 | kordoc
|
|
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초 |
|
|
104
114
|
| 2 | opendataloader-hybrid | 0.907 | 0.934 | 0.928 | 0.821 | 0.46초 |
|
|
105
115
|
| 3 | nutrient (상용) | 0.885 | 0.925 | 0.708 | 0.819 | 0.01초 |
|
|
106
116
|
| 4 | docling | 0.882 | 0.898 | 0.887 | 0.824 | 0.76초 |
|
|
@@ -115,8 +125,9 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
|
|
|
115
125
|
| 13 | liteparse | 0.576 | 0.866 | 0.000 | 0.000 | 1.06초 |
|
|
116
126
|
|
|
117
127
|
- 기본값만으로 **종합·읽기 순서·표·제목 네 항목 모두 1위**입니다. GPU·클라우드 API·LLM 없이 Node.js 하나로, 쪽당 0.04초에 돕니다.
|
|
118
|
-
- `ocr: true`를 켜면 차트·로고·스캔처럼 **그림 속 글자까지** 로컬에서 읽어 0.
|
|
128
|
+
- `ocr: true`를 켜면 차트·로고·스캔처럼 **그림 속 글자까지** 로컬에서 읽어 0.960이 됩니다. `plain: true`는 그림 자리 표시·링크 URL·밑줄/굵게 표기를 빼 색인·RAG 용 글만 냅니다(정답지가 이런 표기를 담지 않아 점수도 오릅니다). 기본값은 OCR 모델이 이미 캐시에 있으면(`kordoc models`·앞선 `ocr: true` 로 받은 경우) **텍스트층이 없는 쪽(스캔·곡선 글자)만** 자동으로 읽고, 모델이 없으면 다운로드하지 않고 `NEEDS_OCR` 경고로 알립니다. 글이 있는 쪽 안의 그림 속 글은 `SKIPPED_IMAGE` 경고로 알리고 `ocr: true` 로 읽습니다(`ocr: false` 는 자동 OCR 도 끔).
|
|
119
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 로 두 줄 모두 앞섭니다.
|
|
120
131
|
|
|
121
132
|
### 한국 공문서: 원본 HWPX를 정답으로 채점합니다
|
|
122
133
|
|
|
@@ -126,8 +137,8 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
|
|
|
126
137
|
| --- | --- | --- |
|
|
127
138
|
| HWPX 본문·표 | 2,286문서, 표 13,041개 | 글 누락 0 · 표 13,041개 전부 칸까지 일치 · 읽기 순서 100% |
|
|
128
139
|
| HWP 5.x | HWPX와 짝 1,120쌍 | HWPX 결과와 전부 일치 |
|
|
129
|
-
| PDF 글 |
|
|
130
|
-
| PDF 표 |
|
|
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 |
|
|
131
142
|
| PDF 전체 | 1,911문서 (텍스트층 채점 1,724) | 글 커버리지 99.8% |
|
|
132
143
|
| 스캔 OCR (내장, 로컬 CPU) | 54문서 104쪽 (216dpi 렌더) | 글자 재현율 98.1% · 한글 재현율 99.3% · 정확도 97.5% · 쪽당 0.9초 |
|
|
133
144
|
| DOCX·XLSX·XLS·HML | 88문서 | 글·숫자 누락 0 |
|
|
@@ -135,8 +146,35 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
|
|
|
135
146
|
|
|
136
147
|
모든 수치는 `npm run bench:gate`로 재현되며, 배포(`npm publish`) 때마다 이 게이트를 통과해야만 올라갑니다.
|
|
137
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
|
+
|
|
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열 표는 양쪽 모두 표 채점에서 제외).
|
|
154
|
+
|
|
155
|
+
| | kordoc | HwpForge 0.16.6 |
|
|
156
|
+
| --- | ---: | ---: |
|
|
157
|
+
| HWPX 2,305문서 — 변환 실패 | **0** | 123 |
|
|
158
|
+
| HWPX — 글 재현율 (변환 성공 문서만) | **100.00%** (100.00%) | 59.23% (98.64%) |
|
|
159
|
+
| HWPX — 표 완전 일치 (9,123표) | **100.0%** (9,122) | 32.2% |
|
|
160
|
+
| HWPX — 칸 F1 | **1.000** | 0.428 |
|
|
161
|
+
| HWP 5.x 1,108문서 — 변환 실패 | **0** | 19 |
|
|
162
|
+
| HWP — 글 재현율 | **100.00%** | 86.41% |
|
|
163
|
+
| HWP — 표 완전 일치 (3,111표) | **100%** | 27.0% |
|
|
164
|
+
| HWP — 칸 F1 | **1.000** | 0.349 |
|
|
165
|
+
|
|
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
|
+
|
|
138
168
|
---
|
|
139
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
|
+
|
|
140
178
|
## v4.15.6 변경사항
|
|
141
179
|
|
|
142
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%)는 그대로 유지했습니다.
|
|
@@ -202,6 +240,8 @@ PDF 표 복원과 대형 입력의 안정성을 보완했습니다.
|
|
|
202
240
|
- **💲 본문의 `$` 가 수식이 되지 않습니다**: 한글 문서(HWPX·HWP·HWP3)의 리터럴 `$` 는 `\$` 로 내고 `$…$` 는 수식에만 씁니다. "echo $HOME $PATH" 같은 글이 마크다운 렌더러에서 수식으로 바뀌던 것. 역슬래시 뒤 구두점("C:\.Pls")에서 역슬래시가 사라지던 것도 고쳤습니다.
|
|
203
241
|
- **📝 미기입 누름틀 안내문을 빼고 읽습니다**: 한컴이 화면에만 흐리게 보이고 인쇄하지 않는 HWPX 안내문을 마크다운에서 뺍니다(블록 글에는 남김). 누름틀을 채우면 수정 표시를 켭니다.
|
|
204
242
|
- **🖼️ 이미지 끄기** (`images: false` / `--no-images`): 결과에 이미지 바이트를 싣지 않습니다. 그림 자리 표시(``)와 본문 글자는 그대로라 그림 위치는 알 수 있습니다. 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 표만 다루는 소비자용.
|
|
205
245
|
- **🚀 상주 파싱 워커** (`kordoc parse-worker`): 프로세스를 띄워 둔 채 stdin 요청 한 줄마다 한 줄 JSON 을 돌려줍니다. 파일마다 node 를 새로 띄우지 않아 대량 색인에 알맞습니다. 응답에 메모리 사용량(`rss`)을 실어 호스트가 워커 교체 시점을 정할 수 있습니다.
|
|
206
246
|
|
|
207
247
|
## v4.14.2 변경사항
|
|
@@ -301,7 +341,7 @@ rhwp 최신 devel(v0.8.3~0.8.4)을 훑어 kordoc 의 HWP3 디코더에 반영했
|
|
|
301
341
|
## v4.10.0 변경사항
|
|
302
342
|
|
|
303
343
|
- **📡 실패 계약 전 포맷 확장 (#69)**: 실패 JSON(`success:false` + `code`)이 `--format json` 전용에서 **markdown·chunks 포함 전 포맷**으로 확장됐습니다. 헤드리스 파이프라인이 stderr 한국어 문구 파싱 없이 원인 코드로 분기할 수 있습니다. `code` 값 집합·종료 코드 규칙·안정성 보증을 README [실패 계약](#실패-계약--기계-판독-가능한-실패-json-v4100-69) 표로 문서화했습니다. (@sorbetsharkroundhand 제보)
|
|
304
|
-
- **🖼️ `images
|
|
344
|
+
- **🖼️ `images/<문서 이름>/manifest.json` (#70·#98)**: 이미지 저장 시 `[ { name, mimeType, bytes, source } ]` manifest 를 함께 방출합니다. `mimeType` 은 매직바이트 실측 우선이라 확장자 추측이 필요 없습니다. DOCX 이미지의 미지 확장자를 `image/png` 로 단정하던 오폴백도 실측 판별로 교체. (@sorbetsharkroundhand 제보)
|
|
305
345
|
- **🩹 patchHwp soft-wrap 축소 치환 무결성 (#71)**: 자동 줄바꿈으로 LINE_SEG 가 2세그먼트 이상인 문단을 더 짧은 한 줄로 치환하면 잔존 세그먼트의 textpos 가 새 nChars 밖을 가리켜 **한컴이 "문서 손상/변조" 경고로 열기를 거부**하던 버그 수정 — 범위를 벗어난 꼬리 세그먼트만 잘라내고 lineSegCount 를 정합화합니다(유효 세그먼트는 보존해 soft-wrap 렌더 유지). 재작성 압축 스트림에 한컴 실저장본의 8바이트 꼬리(CRC32+비압축 크기, 코퍼스 실측 7/7)도 복원합니다. (@heesun-woodi 제보)
|
|
306
346
|
|
|
307
347
|
## v4.9.2 변경사항
|
|
@@ -1017,6 +1057,7 @@ reflow 조판되며 `--no-reflow`로 끌 수 있습니다 (`--highlight 예산,
|
|
|
1017
1057
|
|
|
1018
1058
|
```typescript
|
|
1019
1059
|
const result = await parse(buffer, { images: false }) // 이미지 바이트 없이 (그림 자리 표시는 남김)
|
|
1060
|
+
const plain = await parse(buffer, { plain: true, htmlTables: true }) // 글 위주 + 모든 표를 들여쓴 HTML 로
|
|
1020
1061
|
```
|
|
1021
1062
|
|
|
1022
1063
|
CLI 는 `kordoc 문서.pdf --format json --no-images`. 대량 변환은 `kordoc parse-worker` 를 띄워 두고
|
|
@@ -1157,9 +1198,9 @@ npx kordoc watch ./문서 --webhook https://api/hook # 웹훅 알림
|
|
|
1157
1198
|
|
|
1158
1199
|
**안정성 보증**: 종료 코드 규칙(성공 0 / 실패 1)과 실패 JSON 의 필드(`success`·`fileType`·`error`·`code`)는 유지되고, `code` 값 집합은 **추가만** 됩니다(기존 값 변경·제거 없음). 어느 입력에서 났는지 알 수 있는 `file`(basename) 필드도 **추가만** 됩니다. `error` 문구는 사람용이라 계약이 아닙니다.
|
|
1159
1200
|
|
|
1160
|
-
### 이미지 번들 — `images
|
|
1201
|
+
### 이미지 번들 — `images/<문서 이름>/manifest.json` (v4.10.0+, #70 · #98)
|
|
1161
1202
|
|
|
1162
|
-
`-o`/`-d` 로 저장할 때 추출 이미지는 `images
|
|
1203
|
+
`-o`/`-d` 로 저장할 때 추출 이미지는 문서마다 `images/<문서 이름>/` 에 저장되고(문서 이름 = `-o` 출력 파일 이름 또는 `-d` 입력 파일 이름에서 확장자를 뺀 것), 같은 폴더에 `manifest.json` 이 함께 나옵니다. 마크다운 링크도 `images/<문서 이름>/image_001.png` 를 가리킵니다(공백·괄호는 퍼센트 인코딩). 여러 문서를 같은 폴더로 변환해도 그림이 서로 덮어쓰이지 않습니다. 소비자는 확장자·매직바이트 추측 없이 manifest 로 형식을 분기하면 됩니다.
|
|
1163
1204
|
|
|
1164
1205
|
```json
|
|
1165
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-
|
|
53
|
+
} from "./chunk-CB5XDCSL.js";
|
|
54
54
|
import {
|
|
55
55
|
blocksToPages
|
|
56
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
96
|
+
} from "./chunk-476OYHGZ.js";
|
|
97
97
|
import "./chunk-7FCNOKOR.js";
|
|
98
|
-
import "./chunk-
|
|
98
|
+
import "./chunk-UQ6TZDW6.js";
|
|
99
99
|
import "./chunk-VQFA55IC.js";
|
|
100
|
-
import "./chunk-
|
|
100
|
+
import "./chunk-46DZF3RK.js";
|
|
101
101
|
import {
|
|
102
102
|
blocksToMarkdown,
|
|
103
103
|
flattenLayoutTables,
|
|
104
104
|
hasStructuredCellContent
|
|
105
|
-
} from "./chunk-
|
|
106
|
-
import "./chunk-
|
|
105
|
+
} from "./chunk-TGD7UFHD.js";
|
|
106
|
+
import "./chunk-3JWMDL3S.js";
|
|
107
107
|
import {
|
|
108
108
|
VERSION
|
|
109
|
-
} from "./chunk-
|
|
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=-
|
|
199
|
+
//# sourceMappingURL=-VQNLLLPE.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
KordocError
|
|
4
|
-
|
|
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-
|
|
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"]}
|