kordoc 4.5.0 → 4.6.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 (184) hide show
  1. package/LICENSE +21 -21
  2. package/NOTICE +176 -176
  3. package/README.md +958 -951
  4. package/THIRD_PARTY/apache-2.0.LICENSE +201 -201
  5. package/THIRD_PARTY/claw-hwp.LICENSE +21 -21
  6. package/THIRD_PARTY/hml-equation-parser.LICENSE +201 -201
  7. package/THIRD_PARTY/hml-equation-parser.txt +35 -35
  8. package/THIRD_PARTY/opendataloader-pdf.txt +32 -32
  9. package/THIRD_PARTY/pix2text.txt +17 -17
  10. package/THIRD_PARTY/rhwp-forms.txt +35 -35
  11. package/dist/{-CWYIC4ES.js → -DEVLQKIT.js} +12 -12
  12. package/dist/{chunk-M4I5F6GK.js → chunk-2CABQYTT.js} +9 -9
  13. package/dist/chunk-2CABQYTT.js.map +1 -0
  14. package/dist/{chunk-NBOVSNSR.js → chunk-2CENWSAW.js} +10 -10
  15. package/dist/chunk-2CENWSAW.js.map +1 -0
  16. package/dist/{chunk-3H5X63BQ.js → chunk-3FFPHTXD.js} +1 -1
  17. package/dist/chunk-3FFPHTXD.js.map +1 -0
  18. package/dist/{chunk-MOL7MDBG.js → chunk-3TBUDJDE.js} +1 -1
  19. package/dist/chunk-3TBUDJDE.js.map +1 -0
  20. package/dist/{chunk-5RPYBC2Q.js → chunk-6GBLFQMA.js} +1 -1
  21. package/dist/chunk-6GBLFQMA.js.map +1 -0
  22. package/dist/{chunk-GZDOWYJM.js → chunk-6XPXIG57.js} +2 -2
  23. package/dist/chunk-6XPXIG57.js.map +1 -0
  24. package/dist/{chunk-45EUJ2P6.js → chunk-7BTO44Q7.js} +37 -21
  25. package/dist/chunk-7BTO44Q7.js.map +1 -0
  26. package/dist/{chunk-GE43BE46.js → chunk-AIQ3ISQU.js} +1 -1
  27. package/dist/chunk-AIQ3ISQU.js.map +1 -0
  28. package/dist/{chunk-GLPLLT4Q.cjs → chunk-CARNWWGI.cjs} +4 -4
  29. package/dist/chunk-CARNWWGI.cjs.map +1 -0
  30. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  31. package/dist/{chunk-TCPO6XAY.cjs → chunk-HPRGVU6A.cjs} +11 -11
  32. package/dist/chunk-HPRGVU6A.cjs.map +1 -0
  33. package/dist/{chunk-U3ZNF4SO.cjs → chunk-I42DND3B.cjs} +17 -17
  34. package/dist/chunk-I42DND3B.cjs.map +1 -0
  35. package/dist/{chunk-ZHNUH2IW.js → chunk-IBVMJIHR.js} +2 -2
  36. package/dist/chunk-IBVMJIHR.js.map +1 -0
  37. package/dist/{chunk-XYSTZIIL.js → chunk-IHK5LCYX.js} +2 -2
  38. package/dist/chunk-IHK5LCYX.js.map +1 -0
  39. package/dist/{chunk-TP4P7M4I.js → chunk-KC63W72C.js} +2 -2
  40. package/dist/chunk-KC63W72C.js.map +1 -0
  41. package/dist/{chunk-GCHH4WHT.js → chunk-KQOTL4WI.js} +2 -2
  42. package/dist/chunk-KQOTL4WI.js.map +1 -0
  43. package/dist/{chunk-ZV5OKQ3Z.cjs → chunk-MGGU3ZRK.cjs} +1 -1
  44. package/dist/chunk-MGGU3ZRK.cjs.map +1 -0
  45. package/dist/{chunk-FT2EXMMP.js → chunk-MSMSHU2F.js} +9 -9
  46. package/dist/chunk-MSMSHU2F.js.map +1 -0
  47. package/dist/{chunk-DZIXKL7E.js → chunk-O5EK52TG.js} +1 -1
  48. package/dist/chunk-O5EK52TG.js.map +1 -0
  49. package/dist/{chunk-CBUC73JM.js → chunk-OPL22SYF.js} +3 -3
  50. package/dist/chunk-OPL22SYF.js.map +1 -0
  51. package/dist/{chunk-2CF2WXBG.js → chunk-PQDNCWAZ.js} +11 -11
  52. package/dist/chunk-PQDNCWAZ.js.map +1 -0
  53. package/dist/{chunk-UEWW3DH2.js → chunk-PSAHY5XW.js} +1 -1
  54. package/dist/chunk-PSAHY5XW.js.map +1 -0
  55. package/dist/{chunk-EYLOFSA5.js → chunk-UGKXJA3W.js} +4 -3
  56. package/dist/chunk-UGKXJA3W.js.map +1 -0
  57. package/dist/{chunk-WDIPTY3Q.js → chunk-UQ4NSK66.js} +3 -3
  58. package/dist/chunk-UQ4NSK66.js.map +1 -0
  59. package/dist/{chunk-7ZQKECQT.js → chunk-VNEV6VKD.js} +2 -2
  60. package/dist/chunk-VNEV6VKD.js.map +1 -0
  61. package/dist/{chunk-7FCNOKOR.js → chunk-W34QSJFF.js} +1 -1
  62. package/dist/chunk-W34QSJFF.js.map +1 -0
  63. package/dist/{chunk-UQ6KDJWE.cjs → chunk-WX7WQTOT.cjs} +4 -3
  64. package/dist/chunk-WX7WQTOT.cjs.map +1 -0
  65. package/dist/{chunk-DCZVOIEO.cjs → chunk-X34YWRL5.cjs} +1 -1
  66. package/dist/chunk-X34YWRL5.cjs.map +1 -0
  67. package/dist/{chunk-UAFSPWU7.js → chunk-YNJDNP44.js} +1 -1
  68. package/dist/chunk-YNJDNP44.js.map +1 -0
  69. package/dist/{chunk-TTEHX4FN.js → chunk-ZYQ5X6PG.js} +4 -3
  70. package/dist/chunk-ZYQ5X6PG.js.map +1 -0
  71. package/dist/chunks-Y2VE2WCJ.js +10 -0
  72. package/dist/cli.js +54 -29
  73. package/dist/cli.js.map +1 -1
  74. package/dist/{formula-JLYS73QL.js → formula-ETB6MESN.js} +2 -2
  75. package/dist/formula-ETB6MESN.js.map +1 -0
  76. package/dist/{formula-LIVQSCMM.js → formula-KW4NI5IO.js} +2 -2
  77. package/dist/formula-KW4NI5IO.js.map +1 -0
  78. package/dist/{formula-CG4S7TWO.cjs → formula-WKGP3D3Y.cjs} +8 -8
  79. package/dist/formula-WKGP3D3Y.cjs.map +1 -0
  80. package/dist/{image-ocr-BWQIUUVC.js → image-ocr-C6U7YHRC.js} +6 -6
  81. package/dist/image-ocr-C6U7YHRC.js.map +1 -0
  82. package/dist/{image-ocr-EDZOUU6Q.js → image-ocr-YIBJCOUQ.js} +8 -8
  83. package/dist/image-ocr-YIBJCOUQ.js.map +1 -0
  84. package/dist/{image-ocr-KZ4MGVJ3.cjs → image-ocr-YID6TDN5.cjs} +11 -11
  85. package/dist/image-ocr-YID6TDN5.cjs.map +1 -0
  86. package/dist/index.cjs +209 -193
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.d.cts +13 -1
  89. package/dist/index.d.ts +13 -1
  90. package/dist/index.js +31 -15
  91. package/dist/index.js.map +1 -1
  92. package/dist/mcp.js +25 -23
  93. package/dist/mcp.js.map +1 -1
  94. package/dist/{models-6SIKLKRH.js → models-6VMAVZR6.js} +3 -3
  95. package/dist/page-range-CIRRJPXJ.js +7 -0
  96. package/dist/page-range-OF5I4PQY.js +8 -0
  97. package/dist/page-range-TPIRSA3J.cjs +8 -0
  98. package/dist/page-range-TPIRSA3J.cjs.map +1 -0
  99. package/dist/{parser-7G2QFIYZ.cjs → parser-EYCVDUJA.cjs} +33 -33
  100. package/dist/parser-EYCVDUJA.cjs.map +1 -0
  101. package/dist/{parser-JLEKXA2Z.js → parser-GFXZV2VJ.js} +11 -11
  102. package/dist/parser-GFXZV2VJ.js.map +1 -0
  103. package/dist/{parser-6OJOFJ64.js → parser-SHM3JEH6.js} +9 -9
  104. package/dist/parser-SHM3JEH6.js.map +1 -0
  105. package/dist/pdf-ocr-TMAWPOHC.cjs +13 -0
  106. package/dist/pdf-ocr-TMAWPOHC.cjs.map +1 -0
  107. package/dist/pdf-ocr-UKFYODLP.js +15 -0
  108. package/dist/pdf-ocr-ZG4UVUIT.js +12 -0
  109. package/dist/{profile-io-FMPT6VEZ.js → profile-io-FQZNNA3O.js} +3 -3
  110. package/dist/{rasterize-LARCX6CS.js → rasterize-QGKFNTXO.js} +2 -2
  111. package/dist/rasterize-QGKFNTXO.js.map +1 -0
  112. package/dist/{redact-7ILEAUTS.js → redact-W5K3ILQH.js} +2 -2
  113. package/dist/render-YFIWQYVB.js +10 -0
  114. package/dist/seal-GZ7NDWLR.js +10 -0
  115. package/dist/{setup-GUQT5ELJ.js → setup-I4I75QJU.js} +1 -1
  116. package/dist/setup-I4I75QJU.js.map +1 -0
  117. package/dist/{watch-Y2VKD2MN.js → watch-OIVXH2RN.js} +12 -12
  118. package/dist/watch-OIVXH2RN.js.map +1 -0
  119. package/package.json +103 -103
  120. package/templates//352/260/204/354/235/264/352/270/260/354/225/210/353/254/270_/354/230/210/354/213/234/352/260/222.json +9 -9
  121. package/templates//354/235/274/353/260/230/352/270/260/354/225/210/353/254/270_/354/230/210/354/213/234/352/260/222.json +24 -24
  122. package/dist/chunk-2CF2WXBG.js.map +0 -1
  123. package/dist/chunk-3H5X63BQ.js.map +0 -1
  124. package/dist/chunk-45EUJ2P6.js.map +0 -1
  125. package/dist/chunk-5RPYBC2Q.js.map +0 -1
  126. package/dist/chunk-7FCNOKOR.js.map +0 -1
  127. package/dist/chunk-7ZQKECQT.js.map +0 -1
  128. package/dist/chunk-CBUC73JM.js.map +0 -1
  129. package/dist/chunk-DCZVOIEO.cjs.map +0 -1
  130. package/dist/chunk-DZIXKL7E.js.map +0 -1
  131. package/dist/chunk-EYLOFSA5.js.map +0 -1
  132. package/dist/chunk-FT2EXMMP.js.map +0 -1
  133. package/dist/chunk-GCHH4WHT.js.map +0 -1
  134. package/dist/chunk-GE43BE46.js.map +0 -1
  135. package/dist/chunk-GLPLLT4Q.cjs.map +0 -1
  136. package/dist/chunk-GZDOWYJM.js.map +0 -1
  137. package/dist/chunk-M4I5F6GK.js.map +0 -1
  138. package/dist/chunk-MOL7MDBG.js.map +0 -1
  139. package/dist/chunk-NBOVSNSR.js.map +0 -1
  140. package/dist/chunk-TCPO6XAY.cjs.map +0 -1
  141. package/dist/chunk-TP4P7M4I.js.map +0 -1
  142. package/dist/chunk-TTEHX4FN.js.map +0 -1
  143. package/dist/chunk-U3ZNF4SO.cjs.map +0 -1
  144. package/dist/chunk-UAFSPWU7.js.map +0 -1
  145. package/dist/chunk-UEWW3DH2.js.map +0 -1
  146. package/dist/chunk-UQ6KDJWE.cjs.map +0 -1
  147. package/dist/chunk-WDIPTY3Q.js.map +0 -1
  148. package/dist/chunk-XYSTZIIL.js.map +0 -1
  149. package/dist/chunk-ZHNUH2IW.js.map +0 -1
  150. package/dist/chunk-ZV5OKQ3Z.cjs.map +0 -1
  151. package/dist/chunks-EYDY4Y6O.js +0 -10
  152. package/dist/formula-CG4S7TWO.cjs.map +0 -1
  153. package/dist/formula-JLYS73QL.js.map +0 -1
  154. package/dist/formula-LIVQSCMM.js.map +0 -1
  155. package/dist/image-ocr-BWQIUUVC.js.map +0 -1
  156. package/dist/image-ocr-EDZOUU6Q.js.map +0 -1
  157. package/dist/image-ocr-KZ4MGVJ3.cjs.map +0 -1
  158. package/dist/page-range-737B4EZW.js +0 -8
  159. package/dist/page-range-LNMXJU6W.js +0 -7
  160. package/dist/page-range-P7SDW6LR.cjs +0 -8
  161. package/dist/page-range-P7SDW6LR.cjs.map +0 -1
  162. package/dist/parser-6OJOFJ64.js.map +0 -1
  163. package/dist/parser-7G2QFIYZ.cjs.map +0 -1
  164. package/dist/parser-JLEKXA2Z.js.map +0 -1
  165. package/dist/pdf-ocr-364VNE6N.cjs +0 -13
  166. package/dist/pdf-ocr-364VNE6N.cjs.map +0 -1
  167. package/dist/pdf-ocr-OWUZLWQ3.js +0 -15
  168. package/dist/pdf-ocr-YQGMCT53.js +0 -12
  169. package/dist/rasterize-LARCX6CS.js.map +0 -1
  170. package/dist/render-4WHQ7JCX.js +0 -10
  171. package/dist/seal-BX6YA6PI.js +0 -10
  172. package/dist/setup-GUQT5ELJ.js.map +0 -1
  173. package/dist/watch-Y2VKD2MN.js.map +0 -1
  174. /package/dist/{-CWYIC4ES.js.map → -DEVLQKIT.js.map} +0 -0
  175. /package/dist/{chunks-EYDY4Y6O.js.map → chunks-Y2VE2WCJ.js.map} +0 -0
  176. /package/dist/{models-6SIKLKRH.js.map → models-6VMAVZR6.js.map} +0 -0
  177. /package/dist/{page-range-737B4EZW.js.map → page-range-CIRRJPXJ.js.map} +0 -0
  178. /package/dist/{page-range-LNMXJU6W.js.map → page-range-OF5I4PQY.js.map} +0 -0
  179. /package/dist/{pdf-ocr-OWUZLWQ3.js.map → pdf-ocr-UKFYODLP.js.map} +0 -0
  180. /package/dist/{pdf-ocr-YQGMCT53.js.map → pdf-ocr-ZG4UVUIT.js.map} +0 -0
  181. /package/dist/{profile-io-FMPT6VEZ.js.map → profile-io-FQZNNA3O.js.map} +0 -0
  182. /package/dist/{redact-7ILEAUTS.js.map → redact-W5K3ILQH.js.map} +0 -0
  183. /package/dist/{render-4WHQ7JCX.js.map → render-YFIWQYVB.js.map} +0 -0
  184. /package/dist/{seal-BX6YA6PI.js.map → seal-GZ7NDWLR.js.map} +0 -0
@@ -240,4 +240,4 @@ export {
240
240
  ensureFormulaModels,
241
241
  ensureSingleModel
242
242
  };
243
- //# sourceMappingURL=chunk-3H5X63BQ.js.map
243
+ //# sourceMappingURL=chunk-3FFPHTXD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pdf/formula/models.ts"],"sourcesContent":["/**\r\n * 수식 OCR 모델 자동 다운로드 + SHA-256 검증\r\n *\r\n * 캐시 위치: `~/.cache/kordoc/models/pix2text/`\r\n * 총 ~155MB (MFD 44MB + MFR encoder 87MB + MFR decoder 30MB + tokenizer 40KB)\r\n *\r\n * HF URL + SHA 는 Docufinder archive/rust-formula-ocr-wip 에서 검증 완료됨.\r\n * 모델 버전 갱신 시 URL + SHA 를 함께 갱신해야 한다.\r\n */\r\n\r\nimport { createHash } from \"crypto\"\r\nimport { createReadStream } from \"fs\"\r\nimport { mkdir, stat, unlink, rename } from \"fs/promises\"\r\nimport { createWriteStream } from \"fs\"\r\nimport { homedir } from \"os\"\r\nimport { join, dirname } from \"path\"\r\nimport { pipeline } from \"stream/promises\"\r\nimport { Readable } from \"stream\"\r\n\r\nexport interface ModelSpec {\r\n name: string\r\n filename: string\r\n url: string\r\n sha256: string\r\n sizeMb: number\r\n}\r\n\r\n/** 모델 스펙 (SHA-256 은 실제 다운로드로 검증됨 — 변경 금지) */\r\nexport const MFD_MODEL: ModelSpec = {\r\n name: \"Pix2Text MFD\",\r\n filename: \"mfd.onnx\",\r\n url: \"https://huggingface.co/breezedeus/pix2text-mfd/resolve/main/mfd-v20240618.onnx\",\r\n sha256: \"51a8854743b17ae654729af8db82a630c1ccfa06debf4856c8b28055f87d02c1\",\r\n sizeMb: 42,\r\n}\r\n\r\nexport const MFR_ENCODER_MODEL: ModelSpec = {\r\n name: \"Pix2Text MFR encoder\",\r\n filename: \"encoder_model.onnx\",\r\n url: \"https://huggingface.co/breezedeus/pix2text-mfr/resolve/main/encoder_model.onnx\",\r\n sha256: \"bd8d5c322792e9ec45793af5569e9748f82a3d728a9e00213dbfc56c1486f37d\",\r\n sizeMb: 87,\r\n}\r\n\r\nexport const MFR_DECODER_MODEL: ModelSpec = {\r\n name: \"Pix2Text MFR decoder\",\r\n filename: \"decoder_model.onnx\",\r\n url: \"https://huggingface.co/breezedeus/pix2text-mfr/resolve/main/decoder_model.onnx\",\r\n sha256: \"fd0f92d7a012f3dae41e1ac79421aea0ea888b5a66cb3f9a004e424f82f3daed\",\r\n sizeMb: 30,\r\n}\r\n\r\nexport const MFR_TOKENIZER: ModelSpec = {\r\n name: \"Pix2Text MFR tokenizer\",\r\n filename: \"tokenizer.json\",\r\n url: \"https://huggingface.co/breezedeus/pix2text-mfr/resolve/main/tokenizer.json\",\r\n sha256: \"3e2ab757277d22639bec28c9d7972e352d3d1dba223051fa674002dc5ab64df3\",\r\n sizeMb: 1,\r\n}\r\n\r\nexport const ALL_FORMULA_MODELS: ReadonlyArray<ModelSpec> = [\r\n MFD_MODEL,\r\n MFR_ENCODER_MODEL,\r\n MFR_DECODER_MODEL,\r\n MFR_TOKENIZER,\r\n]\r\n\r\n/**\r\n * 모델 캐시 하위 디렉토리 경로. 환경 변수 `KORDOC_MODEL_CACHE` 가 설정되면\r\n * 해당 경로를 우선 사용, 없으면 `~/.cache/kordoc/models/<subdir>/`.\r\n * (수식 OCR 외 텍스트 OCR 등 다른 모델군도 공용 — subdir 로 격리)\r\n */\r\nexport function getModelsDir(subdir: string): string {\r\n const override = process.env.KORDOC_MODEL_CACHE\r\n if (override && override.trim()) {\r\n return join(override, subdir)\r\n }\r\n return join(homedir(), \".cache\", \"kordoc\", \"models\", subdir)\r\n}\r\n\r\nexport function getFormulaModelsDir(): string {\r\n return getModelsDir(\"pix2text\")\r\n}\r\n\r\nexport interface ModelStatus {\r\n spec: ModelSpec\r\n localPath: string\r\n exists: boolean\r\n /** SHA 검증까지 끝났는가 (exists && valid) */\r\n verified: boolean\r\n /** 검증 실패 시 이유 */\r\n invalidReason?: string\r\n}\r\n\r\n/** 지정 모델군의 존재/유효성 상태 반환 (다운로드 없이 확인만) */\r\nexport async function getModelStatusIn(dir: string, specs: ReadonlyArray<ModelSpec>): Promise<ModelStatus[]> {\r\n const result: ModelStatus[] = []\r\n for (const spec of specs) {\r\n const localPath = join(dir, spec.filename)\r\n let exists = false\r\n try {\r\n const s = await stat(localPath)\r\n exists = s.isFile() && s.size > 0\r\n } catch {\r\n exists = false\r\n }\r\n if (!exists) {\r\n result.push({ spec, localPath, exists: false, verified: false })\r\n continue\r\n }\r\n try {\r\n const actual = await sha256OfFile(localPath)\r\n if (actual === spec.sha256) {\r\n result.push({ spec, localPath, exists: true, verified: true })\r\n } else {\r\n result.push({\r\n spec,\r\n localPath,\r\n exists: true,\r\n verified: false,\r\n invalidReason: `SHA256 mismatch: expected ${spec.sha256}, got ${actual}`,\r\n })\r\n }\r\n } catch (e) {\r\n result.push({\r\n spec,\r\n localPath,\r\n exists: true,\r\n verified: false,\r\n invalidReason: `SHA compute failed: ${(e as Error).message}`,\r\n })\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/** 모든 수식 모델의 존재/유효성 상태 반환 (다운로드 없이 확인만) */\r\nexport async function getFormulaModelStatus(): Promise<ModelStatus[]> {\r\n return getModelStatusIn(getFormulaModelsDir(), ALL_FORMULA_MODELS)\r\n}\r\n\r\nexport interface DownloadProgress {\r\n spec: ModelSpec\r\n /** 현재까지 다운로드한 바이트 */\r\n downloaded: number\r\n /** 전체 바이트 (알 수 있으면) */\r\n total: number | null\r\n /** \"download\" | \"verify\" | \"done\" | \"skip\" */\r\n phase: \"download\" | \"verify\" | \"done\" | \"skip\" | \"error\"\r\n message?: string\r\n}\r\n\r\nexport type ProgressHandler = (p: DownloadProgress) => void\r\n\r\n/**\r\n * 지정 모델군을 다운로드/검증한다.\r\n * - 이미 있고 SHA 일치 → skip\r\n * - 없음 → 다운로드 후 SHA 검증. 실패 시 파일 삭제.\r\n */\r\nexport async function ensureModelsIn(dir: string, specs: ReadonlyArray<ModelSpec>, onProgress?: ProgressHandler): Promise<void> {\r\n await mkdir(dir, { recursive: true })\r\n\r\n for (const spec of specs) {\r\n const localPath = join(dir, spec.filename)\r\n\r\n if (await isExistingValid(localPath, spec.sha256)) {\r\n onProgress?.({\r\n spec,\r\n downloaded: 0,\r\n total: null,\r\n phase: \"skip\",\r\n message: \"이미 존재 + SHA 일치\",\r\n })\r\n continue\r\n }\r\n\r\n // 기존 파일 있지만 SHA 불일치 → 삭제\r\n try {\r\n await unlink(localPath)\r\n } catch {\r\n // 없을 수 있음\r\n }\r\n\r\n await downloadToFile(spec, localPath, onProgress)\r\n }\r\n}\r\n\r\n/** 필요한 모든 수식 모델을 다운로드/검증한다. */\r\nexport async function ensureFormulaModels(onProgress?: ProgressHandler): Promise<void> {\r\n return ensureModelsIn(getFormulaModelsDir(), ALL_FORMULA_MODELS, onProgress)\r\n}\r\n\r\n/** 단일 모델만 확인/다운로드 (진단 UI 에서 개별 상태 새로고침용) */\r\nexport async function ensureSingleModel(spec: ModelSpec, onProgress?: ProgressHandler): Promise<void> {\r\n const dir = getFormulaModelsDir()\r\n await mkdir(dir, { recursive: true })\r\n const localPath = join(dir, spec.filename)\r\n if (await isExistingValid(localPath, spec.sha256)) {\r\n onProgress?.({ spec, downloaded: 0, total: null, phase: \"skip\" })\r\n return\r\n }\r\n try {\r\n await unlink(localPath)\r\n } catch {\r\n // 재다운로드 전 손상/잔존 파일 정리 best-effort — 애초에 없으면 실패해도 무방\r\n }\r\n await downloadToFile(spec, localPath, onProgress)\r\n}\r\n\r\nasync function isExistingValid(localPath: string, sha256Expected: string): Promise<boolean> {\r\n try {\r\n const s = await stat(localPath)\r\n if (!s.isFile() || s.size === 0) return false\r\n } catch {\r\n return false\r\n }\r\n try {\r\n const actual = await sha256OfFile(localPath)\r\n return actual === sha256Expected\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\nasync function downloadToFile(\r\n spec: ModelSpec,\r\n localPath: string,\r\n onProgress?: ProgressHandler,\r\n): Promise<void> {\r\n // 먼저 .part 로 받고 검증 후 rename — 중단된 다운로드가 \"정상 파일\"로 오인되는 걸 방지\r\n const partPath = `${localPath}.part`\r\n await mkdir(dirname(localPath), { recursive: true })\r\n\r\n const resp = await fetch(spec.url, {\r\n headers: {\r\n // HF CDN 은 UA 없으면 가끔 403 을 뱉는다\r\n \"User-Agent\": \"kordoc-formula-ocr/1.0 (+https://github.com/chrisryugj/kordoc)\",\r\n },\r\n })\r\n if (!resp.ok || !resp.body) {\r\n throw new Error(\r\n `${spec.name} 다운로드 실패: HTTP ${resp.status} ${resp.statusText} (${spec.url})`,\r\n )\r\n }\r\n\r\n const lenHeader = resp.headers.get(\"content-length\")\r\n const total = lenHeader ? Number.parseInt(lenHeader, 10) : null\r\n let downloaded = 0\r\n\r\n const ws = createWriteStream(partPath)\r\n try {\r\n const reader = Readable.fromWeb(resp.body as unknown as import(\"stream/web\").ReadableStream)\r\n reader.on(\"data\", (chunk: Buffer | Uint8Array) => {\r\n downloaded += chunk.length\r\n onProgress?.({\r\n spec,\r\n downloaded,\r\n total,\r\n phase: \"download\",\r\n })\r\n })\r\n await pipeline(reader, ws)\r\n } catch (e) {\r\n try {\r\n await unlink(partPath)\r\n } catch {\r\n // .part 임시파일 정리 best-effort — 본 에러(스트리밍 실패)를 가리지 않도록 무시\r\n }\r\n throw new Error(`${spec.name} 스트리밍 실패: ${(e as Error).message}`)\r\n }\r\n\r\n onProgress?.({\r\n spec,\r\n downloaded,\r\n total,\r\n phase: \"verify\",\r\n })\r\n\r\n // SHA 검증\r\n let actual: string\r\n try {\r\n actual = await sha256OfFile(partPath)\r\n } catch (e) {\r\n try {\r\n await unlink(partPath)\r\n } catch {\r\n // .part 임시파일 정리 best-effort — 본 에러(SHA 계산 실패)를 가리지 않도록 무시\r\n }\r\n throw new Error(`${spec.name} SHA 계산 실패: ${(e as Error).message}`)\r\n }\r\n\r\n if (actual !== spec.sha256) {\r\n try {\r\n await unlink(partPath)\r\n } catch {\r\n // 오염된 .part 정리 best-effort — 본 에러(SHA mismatch)를 가리지 않도록 무시\r\n }\r\n throw new Error(\r\n `${spec.name} SHA256 mismatch: expected ${spec.sha256}, got ${actual} — 모델 URL 이 오염되었거나 전송 중 손상되었습니다.`,\r\n )\r\n }\r\n\r\n await rename(partPath, localPath)\r\n onProgress?.({\r\n spec,\r\n downloaded,\r\n total,\r\n phase: \"done\",\r\n })\r\n}\r\n\r\nasync function sha256OfFile(p: string): Promise<string> {\r\n const h = createHash(\"sha256\")\r\n const stream = createReadStream(p)\r\n await pipeline(stream, async function* (src) {\r\n for await (const chunk of src) {\r\n h.update(chunk)\r\n // yield 안 함 (소비만)\r\n }\r\n })\r\n return h.digest(\"hex\")\r\n}\r\n"],"mappings":"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","names":[]}
@@ -32,4 +32,4 @@ function parsePageRange(spec, maxPages) {
32
32
  export {
33
33
  parsePageRange
34
34
  };
35
- //# sourceMappingURL=chunk-MOL7MDBG.js.map
35
+ //# sourceMappingURL=chunk-3TBUDJDE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/page-range.ts"],"sourcesContent":["/** 페이지/섹션 범위 파싱 유틸리티 */\r\n\r\n/**\r\n * 페이지 범위 지정을 1-based Set<number>로 변환.\r\n *\r\n * @param spec - [1,2,3] 또는 \"1-3\" 또는 \"1,3,5-7\"\r\n * @param maxPages - 최대 페이지 수 (클램핑 상한)\r\n * @returns 1-based 페이지 번호 Set\r\n */\r\nexport function parsePageRange(spec: number[] | string, maxPages: number): Set<number> {\r\n const result = new Set<number>()\r\n if (maxPages <= 0) return result\r\n\r\n if (Array.isArray(spec)) {\r\n for (const n of spec) {\r\n const page = Math.round(n)\r\n if (page >= 1 && page <= maxPages) result.add(page)\r\n }\r\n return result\r\n }\r\n\r\n if (typeof spec !== \"string\" || spec.trim() === \"\") return result\r\n\r\n const parts = spec.split(\",\")\r\n for (const part of parts) {\r\n const trimmed = part.trim()\r\n if (!trimmed) continue\r\n\r\n const rangeMatch = trimmed.match(/^(\\d+)\\s*-\\s*(\\d+)$/)\r\n if (rangeMatch) {\r\n const start = Math.max(1, parseInt(rangeMatch[1], 10))\r\n const end = Math.min(maxPages, parseInt(rangeMatch[2], 10))\r\n for (let i = start; i <= end; i++) result.add(i)\r\n } else {\r\n const page = parseInt(trimmed, 10)\r\n if (!isNaN(page) && page >= 1 && page <= maxPages) result.add(page)\r\n }\r\n }\r\n\r\n return result\r\n}\r\n"],"mappings":";;;AASO,SAAS,eAAe,MAAyB,UAA+B;AACrF,QAAM,SAAS,oBAAI,IAAY;AAC/B,MAAI,YAAY,EAAG,QAAO;AAE1B,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,eAAW,KAAK,MAAM;AACpB,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAI,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpD;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,GAAI,QAAO;AAE3D,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AAEd,UAAM,aAAa,QAAQ,MAAM,qBAAqB;AACtD,QAAI,YAAY;AACd,YAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AACrD,YAAM,MAAM,KAAK,IAAI,UAAU,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AAC1D,eAAS,IAAI,OAAO,KAAK,KAAK,IAAK,QAAO,IAAI,CAAC;AAAA,IACjD,OAAO;AACL,YAAM,OAAO,SAAS,SAAS,EAAE;AACjC,UAAI,CAAC,MAAM,IAAI,KAAK,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -85,4 +85,4 @@ export {
85
85
  APPROVAL_MAX,
86
86
  buildGongmunOptions
87
87
  };
88
- //# sourceMappingURL=chunk-5RPYBC2Q.js.map
88
+ //# sourceMappingURL=chunk-6GBLFQMA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hwpx/gongmun-surface.ts"],"sourcesContent":["/**\r\n * 공문서 옵션 표면 SSOT (v4.0.4, 인벤토리 영역1-1) — CLI(commander)와 MCP(zod)가\r\n * 각자 손배선하던 GongmunOptions 조립을 한 곳으로 모은다.\r\n *\r\n * 실측 드리프트의 뿌리는 두 가지였다:\r\n * 1. 값 집합(열거·중첩 키 목록·범위)이 세 곳(gongmun.ts 타입 / commander 검증 /\r\n * zod shape)에 각자 사경 — line_spacing이 MCP에 빠지고 sizes.bodyTitle이\r\n * zod에 누락되는 식. → 여기의 상수에서 양쪽이 파생한다.\r\n * 2. 조립 의미론(cover 우선순위, 조건부 대입)이 두 벌 복붙. → buildGongmunOptions\r\n * 하나로 통일 — 옵션 신설 시 이 파일과 표면별 플래그/파라미터 선언만 만지면 된다.\r\n *\r\n * 표면 문법(플래그 이름·설명문·kv 파싱)은 각 표면의 것 — 여기는 값과 의미만 담는다.\r\n */\r\n\r\nimport type { GongmunOptions, GongmunPreset } from \"./gongmun.js\"\r\n\r\n// ─── 값 집합 (CLI 검증·MCP zod 공통 파생원) ──────────────\r\n\r\nexport const BODY_FONTS = [\"myeongjo\", \"gothic\"] as const\r\nexport const H2_MARKERS = [\"box\", \"number\", \"none\"] as const\r\nexport const BULLET2_CHARS = [\"ㅇ\", \"○\"] as const\r\n\r\n/** 요소별 글꼴 오버라이드 역할 키 (GongmunOptions.fonts) */\r\nexport const FONT_ROLE_KEYS = [\"body\", \"heading\", \"ref\", \"table\"] as const\r\n/** 개조식 요소별 크기 키 (gaejosik.ts GaejosikSizeOverrides와 정합 — 드리프트 시 컴파일 에러) */\r\nexport const SIZE_KEYS = [\r\n \"dae\", \"cham\", \"chapter\", \"coverTitle\", \"coverSub\",\r\n \"tocLabel\", \"tocRoman\", \"tocItem\", \"table\", \"bodyTitle\",\r\n] as const\r\n/** 기안문 두문 키 (별지 제1호서식) */\r\nexport const DOC_HEAD_KEYS = [\"org\", \"to\", \"via\", \"title\"] as const\r\n/** 기안문 결문 키 */\r\nexport const DOC_FOOT_KEYS = [\r\n \"sender\", \"drafter\", \"reviewer\", \"approver\", \"cooperator\", \"docNum\", \"receive\",\r\n \"address\", \"site\", \"phone\", \"fax\", \"email\", \"disclosure\",\r\n] as const\r\n/** 공고문 두문·결문 키 */\r\nexport const NOTICE_HEAD_KEYS = [\"no\", \"date\", \"sender\"] as const\r\n/** 보도자료 담당 표 키 */\r\nexport const PRESS_CONTACT_KEYS = [\"dept\", \"manager\", \"phone\"] as const\r\n\r\n/** 수치 범위 — MCP zod와 문서화가 공유 (라이브러리 검증은 gongmun.ts 방어선) */\r\nexport const BODY_PT_RANGE = { min: 6, max: 40 } as const\r\nexport const LINE_SPACING_RANGE = { min: 50, max: 300 } as const\r\nexport const SIZE_PT_RANGE = { min: 6, max: 60 } as const\r\nexport const APPROVAL_MAX = 6\r\n\r\n// SIZE_KEYS ⊆ GaejosikSizeOverrides 컴파일 타임 잠금 — gaejosik.ts에 키가 늘면\r\n// 여기(와 이를 파생하는 MCP zod·CLI 안내문)도 갱신을 강제한다\r\ntype SizeKey = (typeof SIZE_KEYS)[number]\r\ntype _SizeKeysMatch = SizeKey extends keyof NonNullable<GongmunOptions[\"sizes\"]>\r\n ? (keyof NonNullable<GongmunOptions[\"sizes\"]> extends SizeKey ? true : never)\r\n : never\r\nconst _sizeKeysLock: _SizeKeysMatch = true\r\nvoid _sizeKeysLock\r\n\r\n// ─── 조립 (두 표면 공통 의미론) ──────────────────────────\r\n\r\n/**\r\n * 표면 중립 입력 — CLI는 플래그·kv 파싱 결과를, MCP는 zod 통과 파라미터를 이 모양으로\r\n * 넘긴다. 모든 필드가 \"명시된 것만 정의\"(미지정 undefined) 규약 — --no-x 기본값 같은\r\n * 표면 고유 사정은 어댑터가 undefined로 정돈해서 넘겨야 한다.\r\n */\r\nexport interface GongmunSurfaceInput {\r\n preset: GongmunPreset\r\n font?: (typeof BODY_FONTS)[number]\r\n bodyPt?: number\r\n lineSpacing?: number\r\n org?: string\r\n date?: string\r\n cover?: boolean\r\n toc?: boolean\r\n approval?: string[]\r\n pageNumbers?: boolean\r\n endMark?: boolean\r\n bodyTitleBox?: boolean\r\n h2Marker?: (typeof H2_MARKERS)[number]\r\n fonts?: NonNullable<GongmunOptions[\"fonts\"]>\r\n sizes?: NonNullable<GongmunOptions[\"sizes\"]>\r\n bullet2?: (typeof BULLET2_CHARS)[number]\r\n suppressSingle?: boolean\r\n docHead?: NonNullable<GongmunOptions[\"docHead\"]>\r\n docFoot?: NonNullable<GongmunOptions[\"docFoot\"]>\r\n reportInfo?: string\r\n noticeHead?: NonNullable<GongmunOptions[\"noticeHead\"]>\r\n press?: NonNullable<GongmunOptions[\"press\"]>\r\n}\r\n\r\n/**\r\n * 중립 입력 → GongmunOptions. 종전 CLI(cli.ts:535~612)·MCP(mcp.ts:792~814) 복붙\r\n * 두 벌의 의미론을 그대로 보존한 단일 조립:\r\n * - cover: false(끄기)가 최우선 → org/date 지정 시 객체(=켜짐) → true(강제 켜기)\r\n * → 미지정이면 프리셋 기본(resolveGongmun)\r\n * - 나머지는 \"명시된 것만 대입\" — 프리셋 기본값을 undefined로 덮지 않는다\r\n */\r\nexport function buildGongmunOptions(input: GongmunSurfaceInput): GongmunOptions {\r\n const g: GongmunOptions = { preset: input.preset }\r\n if (input.font) g.bodyFont = input.font\r\n // NaN도 대입 — CLI의 비숫자 플래그(--pt abc)가 라이브러리 방어선(assertFiniteRange)\r\n // 에서 명시적 에러로 죽는 기존 경로 보존 (truthiness로 거르면 무증상 폐기가 된다)\r\n if (input.bodyPt !== undefined) g.bodyPt = input.bodyPt\r\n if (input.lineSpacing !== undefined) g.lineSpacing = input.lineSpacing\r\n if (input.cover === false) {\r\n g.cover = false\r\n } else if (input.org || input.date) {\r\n g.cover = { ...(input.org ? { org: input.org } : {}), ...(input.date ? { date: input.date } : {}) }\r\n } else if (input.cover === true) {\r\n g.cover = true\r\n }\r\n if (input.toc !== undefined) g.toc = input.toc\r\n if (input.approval && input.approval.length > 0) g.approval = input.approval\r\n if (input.pageNumbers !== undefined) g.pageNumbers = input.pageNumbers\r\n if (input.endMark !== undefined) g.endMark = input.endMark\r\n if (input.bodyTitleBox !== undefined) g.bodyTitleBox = input.bodyTitleBox\r\n if (input.h2Marker) g.h2Marker = input.h2Marker\r\n if (input.fonts) g.fonts = input.fonts\r\n if (input.sizes) g.sizes = input.sizes\r\n if (input.bullet2) g.bullet2 = input.bullet2\r\n if (input.suppressSingle !== undefined) g.suppressSingle = input.suppressSingle\r\n if (input.docHead) g.docHead = input.docHead\r\n if (input.docFoot) g.docFoot = input.docFoot\r\n if (input.reportInfo) g.reportInfo = input.reportInfo\r\n if (input.noticeHead) g.noticeHead = input.noticeHead\r\n if (input.press) g.press = input.press\r\n return g\r\n}\r\n"],"mappings":";;;AAkBO,IAAM,aAAa,CAAC,YAAY,QAAQ;AACxC,IAAM,aAAa,CAAC,OAAO,UAAU,MAAM;AAC3C,IAAM,gBAAgB,CAAC,UAAK,QAAG;AAG/B,IAAM,iBAAiB,CAAC,QAAQ,WAAW,OAAO,OAAO;AAEzD,IAAM,YAAY;AAAA,EACvB;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAW;AAAA,EAAc;AAAA,EACxC;AAAA,EAAY;AAAA,EAAY;AAAA,EAAW;AAAA,EAAS;AAC9C;AAEO,IAAM,gBAAgB,CAAC,OAAO,MAAM,OAAO,OAAO;AAElD,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EAAU;AAAA,EAAW;AAAA,EAAY;AAAA,EAAY;AAAA,EAAc;AAAA,EAAU;AAAA,EACrE;AAAA,EAAW;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAO;AAAA,EAAS;AAC9C;AAEO,IAAM,mBAAmB,CAAC,MAAM,QAAQ,QAAQ;AAEhD,IAAM,qBAAqB,CAAC,QAAQ,WAAW,OAAO;AAGtD,IAAM,gBAAgB,EAAE,KAAK,GAAG,KAAK,GAAG;AACxC,IAAM,qBAAqB,EAAE,KAAK,IAAI,KAAK,IAAI;AAC/C,IAAM,gBAAgB,EAAE,KAAK,GAAG,KAAK,GAAG;AACxC,IAAM,eAAe;AAkDrB,SAAS,oBAAoB,OAA4C;AAC9E,QAAM,IAAoB,EAAE,QAAQ,MAAM,OAAO;AACjD,MAAI,MAAM,KAAM,GAAE,WAAW,MAAM;AAGnC,MAAI,MAAM,WAAW,OAAW,GAAE,SAAS,MAAM;AACjD,MAAI,MAAM,gBAAgB,OAAW,GAAE,cAAc,MAAM;AAC3D,MAAI,MAAM,UAAU,OAAO;AACzB,MAAE,QAAQ;AAAA,EACZ,WAAW,MAAM,OAAO,MAAM,MAAM;AAClC,MAAE,QAAQ,EAAE,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,GAAI,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC,EAAG;AAAA,EACpG,WAAW,MAAM,UAAU,MAAM;AAC/B,MAAE,QAAQ;AAAA,EACZ;AACA,MAAI,MAAM,QAAQ,OAAW,GAAE,MAAM,MAAM;AAC3C,MAAI,MAAM,YAAY,MAAM,SAAS,SAAS,EAAG,GAAE,WAAW,MAAM;AACpE,MAAI,MAAM,gBAAgB,OAAW,GAAE,cAAc,MAAM;AAC3D,MAAI,MAAM,YAAY,OAAW,GAAE,UAAU,MAAM;AACnD,MAAI,MAAM,iBAAiB,OAAW,GAAE,eAAe,MAAM;AAC7D,MAAI,MAAM,SAAU,GAAE,WAAW,MAAM;AACvC,MAAI,MAAM,MAAO,GAAE,QAAQ,MAAM;AACjC,MAAI,MAAM,MAAO,GAAE,QAAQ,MAAM;AACjC,MAAI,MAAM,QAAS,GAAE,UAAU,MAAM;AACrC,MAAI,MAAM,mBAAmB,OAAW,GAAE,iBAAiB,MAAM;AACjE,MAAI,MAAM,QAAS,GAAE,UAAU,MAAM;AACrC,MAAI,MAAM,QAAS,GAAE,UAAU,MAAM;AACrC,MAAI,MAAM,WAAY,GAAE,aAAa,MAAM;AAC3C,MAAI,MAAM,WAAY,GAAE,aAAa,MAAM;AAC3C,MAAI,MAAM,MAAO,GAAE,QAAQ,MAAM;AACjC,SAAO;AACT;","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  ensureModelsIn,
4
4
  getModelStatusIn,
5
5
  getModelsDir
6
- } from "./chunk-UAFSPWU7.js";
6
+ } from "./chunk-YNJDNP44.js";
7
7
 
8
8
  // src/ocr/models.ts
9
9
  import { join } from "path";
@@ -80,4 +80,4 @@ export {
80
80
  parseCharacterDict,
81
81
  ocrModelPath
82
82
  };
83
- //# sourceMappingURL=chunk-GZDOWYJM.js.map
83
+ //# sourceMappingURL=chunk-6XPXIG57.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ocr/models.ts"],"sourcesContent":["/**\r\n * 내장 텍스트 OCR 모델 (PP-OCRv5 korean) 스펙 + 다운로드/검증.\r\n *\r\n * 캐시 위치: `~/.cache/kordoc/models/ppocr/` — 총 ~18MB (det 4.6 + rec 12.8 + dict 0.1)\r\n * 다운로드 인프라는 수식 OCR 과 공용 (formula/models.ts ensureModelsIn).\r\n *\r\n * 모델 출처: PaddlePaddle 공식 HuggingFace org 의 ONNX 변환본 (Apache-2.0).\r\n * SHA-256 은 실제 다운로드 + HF API lfs.oid 대조로 검증됨 — 변경 금지.\r\n * 한국어 사전은 rec 리포의 inference.yml `PostProcess.character_dict` 에\r\n * 내장 (11,945자 — 완성형 한글 11,172 음절 전량 + 자모/라틴/기호).\r\n */\r\n\r\nimport { join } from \"path\"\r\nimport {\r\n type ModelSpec,\r\n type ModelStatus,\r\n type ProgressHandler,\r\n ensureModelsIn,\r\n getModelStatusIn,\r\n getModelsDir,\r\n} from \"../pdf/formula/models.js\"\r\n\r\nexport const OCR_DET_MODEL: ModelSpec = {\r\n name: \"PP-OCRv5 mobile det\",\r\n filename: \"det.onnx\",\r\n url: \"https://huggingface.co/PaddlePaddle/PP-OCRv5_mobile_det_onnx/resolve/main/inference.onnx\",\r\n sha256: \"a431985659dc921974177a95adcfbb90fd9e51989a5e04d70d0b75f597b6e61d\",\r\n sizeMb: 5,\r\n}\r\n\r\nexport const OCR_REC_MODEL: ModelSpec = {\r\n name: \"PP-OCRv5 korean rec\",\r\n filename: \"rec_korean.onnx\",\r\n url: \"https://huggingface.co/PaddlePaddle/korean_PP-OCRv5_mobile_rec_onnx/resolve/main/inference.onnx\",\r\n sha256: \"92f0b7785e64fc9090106a241cf4c1eb97472824558272751b88a2a4476d3a08\",\r\n sizeMb: 13,\r\n}\r\n\r\nexport const OCR_REC_DICT: ModelSpec = {\r\n name: \"PP-OCRv5 korean dict\",\r\n filename: \"rec_korean.yml\",\r\n url: \"https://huggingface.co/PaddlePaddle/korean_PP-OCRv5_mobile_rec_onnx/resolve/main/inference.yml\",\r\n sha256: \"f757fa1c40e99edcf27e9cce879b93eb2a51fa46f5ef39095689b8c37dd75998\",\r\n sizeMb: 1,\r\n}\r\n\r\nexport const ALL_OCR_MODELS: ReadonlyArray<ModelSpec> = [OCR_DET_MODEL, OCR_REC_MODEL, OCR_REC_DICT]\r\n\r\nexport function getOcrModelsDir(): string {\r\n return getModelsDir(\"ppocr\")\r\n}\r\n\r\n/** 모든 텍스트 OCR 모델 다운로드/검증 (있으면 skip) */\r\nexport async function ensureOcrModels(onProgress?: ProgressHandler): Promise<void> {\r\n return ensureModelsIn(getOcrModelsDir(), ALL_OCR_MODELS, onProgress)\r\n}\r\n\r\n/** 텍스트 OCR 모델 상태 (다운로드 없이 확인만) */\r\nexport async function getOcrModelStatus(): Promise<ModelStatus[]> {\r\n return getModelStatusIn(getOcrModelsDir(), ALL_OCR_MODELS)\r\n}\r\n\r\n/**\r\n * inference.yml 에서 `PostProcess.character_dict` 리스트를 추출.\r\n * 전체 YAML 파서 없이 해당 블록의 `- <char>` 라인만 순서대로 읽는다\r\n * (공식 yml 구조 고정 — 들여쓰기 2칸, 한 줄 한 글자).\r\n *\r\n * CTC 클래스 배열: index 0 = blank, 1..N = 사전 순서, N+1(마지막) = space.\r\n */\r\nexport function parseCharacterDict(yml: string): string[] {\r\n const lines = yml.split(\"\\n\")\r\n const chars: string[] = []\r\n let inDict = false\r\n let dictIndent = -1\r\n for (const line of lines) {\r\n if (!inDict) {\r\n const m = /^(\\s*)character_dict:\\s*$/.exec(line)\r\n if (m) {\r\n inDict = true\r\n dictIndent = m[1].length\r\n }\r\n continue\r\n }\r\n // 리스트 항목 — YAML 은 키와 같은 들여쓰기의 \"- x\" 도 허용 (공식 yml 실측: 둘 다 2칸)\r\n const m = /^(\\s*)- (.*)$/.exec(line)\r\n if (m && m[1].length >= dictIndent) {\r\n let v = m[2]\r\n // YAML 인용 문자 처리 ('#' 같은 예약 문자는 인용됨)\r\n if (v.length >= 2 && ((v.startsWith(\"'\") && v.endsWith(\"'\")) || (v.startsWith('\"') && v.endsWith('\"')))) {\r\n v = v.slice(1, -1).replace(/''/g, \"'\")\r\n }\r\n chars.push(v)\r\n continue\r\n }\r\n // 들여쓰기가 얕아지면 블록 종료 (빈 줄은 통과)\r\n if (line.trim() !== \"\") break\r\n }\r\n return chars\r\n}\r\n\r\nexport function ocrModelPath(spec: ModelSpec): string {\r\n return join(getOcrModelsDir(), spec.filename)\r\n}\r\n"],"mappings":";;;;;;;;AAYA,SAAS,YAAY;AAUd,IAAM,gBAA2B;AAAA,EACtC,MAAM;AAAA,EACN,UAAU;AAAA,EACV,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,QAAQ;AACV;AAEO,IAAM,gBAA2B;AAAA,EACtC,MAAM;AAAA,EACN,UAAU;AAAA,EACV,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,QAAQ;AACV;AAEO,IAAM,eAA0B;AAAA,EACrC,MAAM;AAAA,EACN,UAAU;AAAA,EACV,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,QAAQ;AACV;AAEO,IAAM,iBAA2C,CAAC,eAAe,eAAe,YAAY;AAE5F,SAAS,kBAA0B;AACxC,SAAO,aAAa,OAAO;AAC7B;AAGA,eAAsB,gBAAgB,YAA6C;AACjF,SAAO,eAAe,gBAAgB,GAAG,gBAAgB,UAAU;AACrE;AAGA,eAAsB,oBAA4C;AAChE,SAAO,iBAAiB,gBAAgB,GAAG,cAAc;AAC3D;AASO,SAAS,mBAAmB,KAAuB;AACxD,QAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS;AACb,MAAI,aAAa;AACjB,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,QAAQ;AACX,YAAMA,KAAI,4BAA4B,KAAK,IAAI;AAC/C,UAAIA,IAAG;AACL,iBAAS;AACT,qBAAaA,GAAE,CAAC,EAAE;AAAA,MACpB;AACA;AAAA,IACF;AAEA,UAAM,IAAI,gBAAgB,KAAK,IAAI;AACnC,QAAI,KAAK,EAAE,CAAC,EAAE,UAAU,YAAY;AAClC,UAAI,IAAI,EAAE,CAAC;AAEX,UAAI,EAAE,UAAU,MAAO,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,KAAO,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,IAAK;AACvG,YAAI,EAAE,MAAM,GAAG,EAAE,EAAE,QAAQ,OAAO,GAAG;AAAA,MACvC;AACA,YAAM,KAAK,CAAC;AACZ;AAAA,IACF;AAEA,QAAI,KAAK,KAAK,MAAM,GAAI;AAAA,EAC1B;AACA,SAAO;AACT;AAEO,SAAS,aAAa,MAAyB;AACpD,SAAO,KAAK,gBAAgB,GAAG,KAAK,QAAQ;AAC9C;","names":["m"]}
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  inlineImagesIntoMarkdown
4
- } from "./chunk-7FCNOKOR.js";
4
+ } from "./chunk-W34QSJFF.js";
5
5
  import {
6
6
  HEADING_RATIO_H1,
7
7
  HEADING_RATIO_H2,
8
8
  HEADING_RATIO_H3
9
- } from "./chunk-UEWW3DH2.js";
9
+ } from "./chunk-PSAHY5XW.js";
10
10
  import {
11
11
  parsePageRange
12
- } from "./chunk-MOL7MDBG.js";
12
+ } from "./chunk-3TBUDJDE.js";
13
13
  import {
14
14
  allLinesegRemovalSplices,
15
15
  applySplices,
@@ -22,7 +22,7 @@ import {
22
22
  paraTextPureT,
23
23
  patchZipEntries,
24
24
  scanSectionXml
25
- } from "./chunk-CBUC73JM.js";
25
+ } from "./chunk-OPL22SYF.js";
26
26
  import {
27
27
  MAX_COLS,
28
28
  MAX_ROWS,
@@ -32,14 +32,14 @@ import {
32
32
  dedupeRunningHeaders,
33
33
  flattenLayoutTables,
34
34
  mapPuaText
35
- } from "./chunk-ZHNUH2IW.js";
35
+ } from "./chunk-IBVMJIHR.js";
36
36
  import {
37
37
  SPACE_EM_FIXED,
38
38
  charWidthEm1000,
39
39
  fitRatioForFewerLines,
40
40
  measureTextWidth,
41
41
  simulateWrap
42
- } from "./chunk-WDIPTY3Q.js";
42
+ } from "./chunk-UQ4NSK66.js";
43
43
  import {
44
44
  MAX_DECOMPRESS_SIZE,
45
45
  MAX_XML_DEPTH,
@@ -55,7 +55,7 @@ import {
55
55
  findChildByLocalName,
56
56
  localName,
57
57
  rawTextContent
58
- } from "./chunk-TP4P7M4I.js";
58
+ } from "./chunk-KC63W72C.js";
59
59
  import {
60
60
  KordocError,
61
61
  classifyError,
@@ -67,7 +67,7 @@ import {
67
67
  sanitizeHref,
68
68
  stripDtd,
69
69
  toArrayBuffer
70
- } from "./chunk-TTEHX4FN.js";
70
+ } from "./chunk-ZYQ5X6PG.js";
71
71
 
72
72
  // src/index.ts
73
73
  import { readFile } from "fs/promises";
@@ -17858,7 +17858,7 @@ async function parseXlsxDocument(buffer, options) {
17858
17858
  }
17859
17859
  let pageFilter = null;
17860
17860
  if (options?.pages) {
17861
- const { parsePageRange: parsePageRange2 } = await import("./page-range-737B4EZW.js");
17861
+ const { parsePageRange: parsePageRange2 } = await import("./page-range-OF5I4PQY.js");
17862
17862
  pageFilter = parsePageRange2(options.pages, sheets.length);
17863
17863
  }
17864
17864
  const blocks = [];
@@ -18558,7 +18558,7 @@ async function parseXlsDocument(buffer, options) {
18558
18558
  const totalSheets = Math.min(globals.sheets.length, MAX_SHEETS2);
18559
18559
  let pageFilter = null;
18560
18560
  if (options?.pages) {
18561
- const { parsePageRange: parsePageRange2 } = await import("./page-range-737B4EZW.js");
18561
+ const { parsePageRange: parsePageRange2 } = await import("./page-range-OF5I4PQY.js");
18562
18562
  pageFilter = parsePageRange2(options.pages, totalSheets);
18563
18563
  }
18564
18564
  const allBlocks = [];
@@ -21903,11 +21903,11 @@ function parseInlineMarkdown(text) {
21903
21903
  }
21904
21904
  return spans;
21905
21905
  }
21906
- function emphasisSpans(text) {
21906
+ var INLINE_REGEX = /(`[^`]+`|\*{3}(?:[^*]|\*(?!\*\*))+\*{3}|\*{2}(?:[^*]|\*(?!\*))+\*{2}|\*(?:[^*]|\*{2}(?:[^*]|\*(?!\*))+\*{2})+\*|(?<![^\s\p{P}\p{S}\x00])(?<!_)_{2}(?=\S)[^_]+(?<=\S)_{2}(?!_)(?![^\s\p{P}\p{S}\x00])|(?<![^\s\p{P}\p{S}\x00])(?<!_)_(?=\S)[^_]+(?<=\S)_(?!_)(?![^\s\p{P}\p{S}\x00]))/gu;
21907
+ function emphasisSpans(text, depth = 0) {
21907
21908
  const spans = [];
21908
- const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|(?<![^\s\p{P}\p{S}\x00])(?<!_)_{2}(?=\S)[^_]+(?<=\S)_{2}(?!_)(?![^\s\p{P}\p{S}\x00])|(?<![^\s\p{P}\p{S}\x00])(?<!_)_(?=\S)[^_]+(?<=\S)_(?!_)(?![^\s\p{P}\p{S}\x00]))/gu;
21909
21909
  let lastIdx = 0;
21910
- for (const match of text.matchAll(regex)) {
21910
+ for (const match of text.matchAll(INLINE_REGEX)) {
21911
21911
  const idx = match.index;
21912
21912
  if (idx > lastIdx) {
21913
21913
  spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
@@ -21915,12 +21915,28 @@ function emphasisSpans(text) {
21915
21915
  const raw = match[0];
21916
21916
  if (raw.startsWith("`")) {
21917
21917
  spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
21918
- } else if (raw.startsWith("***") || raw.startsWith("___")) {
21919
- spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
21920
- } else if (raw.startsWith("**") || raw.startsWith("__")) {
21921
- spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
21922
21918
  } else {
21923
- spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
21919
+ let inner;
21920
+ let bold = false;
21921
+ let italic = false;
21922
+ if (raw.startsWith("***") || raw.startsWith("___")) {
21923
+ inner = raw.slice(3, -3);
21924
+ bold = true;
21925
+ italic = true;
21926
+ } else if (raw.startsWith("**") || raw.startsWith("__")) {
21927
+ inner = raw.slice(2, -2);
21928
+ bold = true;
21929
+ } else {
21930
+ inner = raw.slice(1, -1);
21931
+ italic = true;
21932
+ }
21933
+ if (depth < 3 && /[`*_]/.test(inner)) {
21934
+ for (const child of emphasisSpans(inner, depth + 1)) {
21935
+ spans.push(child.code ? child : { ...child, bold: child.bold || bold, italic: child.italic || italic });
21936
+ }
21937
+ } else {
21938
+ spans.push({ text: inner, bold, italic, code: false });
21939
+ }
21924
21940
  }
21925
21941
  lastIdx = idx + raw.length;
21926
21942
  }
@@ -28561,7 +28577,7 @@ async function parse(input, options) {
28561
28577
  }
28562
28578
  async function parseImage(buffer, options) {
28563
28579
  try {
28564
- const { parseImageDocument } = await import("./image-ocr-EDZOUU6Q.js");
28580
+ const { parseImageDocument } = await import("./image-ocr-YIBJCOUQ.js");
28565
28581
  const { blocks, warnings } = await parseImageDocument(buffer, options);
28566
28582
  return {
28567
28583
  success: true,
@@ -28620,7 +28636,7 @@ async function parseHwp(buffer, options) {
28620
28636
  async function parsePdf(buffer, options) {
28621
28637
  let parsePdfDocument;
28622
28638
  try {
28623
- const mod = await import("./parser-JLEKXA2Z.js");
28639
+ const mod = await import("./parser-GFXZV2VJ.js");
28624
28640
  parsePdfDocument = mod.parsePdfDocument;
28625
28641
  } catch {
28626
28642
  return {
@@ -28766,4 +28782,4 @@ export {
28766
28782
  parseHwpml,
28767
28783
  fillForm
28768
28784
  };
28769
- //# sourceMappingURL=chunk-45EUJ2P6.js.map
28785
+ //# sourceMappingURL=chunk-7BTO44Q7.js.map