kordoc 4.2.8 → 4.3.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 (166) hide show
  1. package/README.md +922 -885
  2. package/dist/{-SCTT2TBZ.js → -DYHT3HWH.js} +28 -20
  3. package/dist/{chunk-J2RZ444H.js → chunk-3FFPHTXD.js} +1 -1
  4. package/dist/chunk-3FFPHTXD.js.map +1 -0
  5. package/dist/{chunk-IJUAWDOS.js → chunk-4DBCUF3C.js} +38 -7
  6. package/dist/chunk-4DBCUF3C.js.map +1 -0
  7. package/dist/{chunk-5RPYBC2Q.js → chunk-6GBLFQMA.js} +1 -1
  8. package/dist/chunk-6GBLFQMA.js.map +1 -0
  9. package/dist/{chunk-JP5MNHUK.js → chunk-6RAYTNQT.js} +5 -5
  10. package/dist/chunk-6RAYTNQT.js.map +1 -0
  11. package/dist/{chunk-GEPINOL6.js → chunk-6XPXIG57.js} +2 -2
  12. package/dist/chunk-6XPXIG57.js.map +1 -0
  13. package/dist/{chunk-WP4VD5T7.js → chunk-7MFO4TKP.js} +4 -3
  14. package/dist/chunk-7MFO4TKP.js.map +1 -0
  15. package/dist/{chunk-CELJIGBQ.cjs → chunk-AL34DGS5.cjs} +6 -6
  16. package/dist/chunk-AL34DGS5.cjs.map +1 -0
  17. package/dist/{chunk-FS3XQ3ZQ.js → chunk-ARXNKC5D.js} +3 -3
  18. package/dist/chunk-ARXNKC5D.js.map +1 -0
  19. package/dist/{chunk-2BTKLDNX.cjs → chunk-AUOEZKJE.cjs} +21 -21
  20. package/dist/chunk-AUOEZKJE.cjs.map +1 -0
  21. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  22. package/dist/{chunk-7GEBDS6B.js → chunk-DRQUAYNT.js} +4 -3
  23. package/dist/chunk-DRQUAYNT.js.map +1 -0
  24. package/dist/{chunk-YNAPK4H6.js → chunk-DRZUJ2RK.js} +1521 -1144
  25. package/dist/chunk-DRZUJ2RK.js.map +1 -0
  26. package/dist/{chunk-GKSFNCVP.js → chunk-EFVAY5VE.js} +71 -29
  27. package/dist/chunk-EFVAY5VE.js.map +1 -0
  28. package/dist/{chunk-UKEZ46VL.js → chunk-EZHW3RNQ.js} +37 -6
  29. package/dist/chunk-EZHW3RNQ.js.map +1 -0
  30. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  31. package/dist/{chunk-4FTXKIGT.js → chunk-GSMPTZC7.js} +4 -4
  32. package/dist/chunk-GSMPTZC7.js.map +1 -0
  33. package/dist/{chunk-OQNFGKRY.cjs → chunk-JFV7QT5T.cjs} +4 -3
  34. package/dist/chunk-JFV7QT5T.cjs.map +1 -0
  35. package/dist/{chunk-ROKHZE76.cjs → chunk-LPQWKQQQ.cjs} +44 -13
  36. package/dist/chunk-LPQWKQQQ.cjs.map +1 -0
  37. package/dist/{chunk-H7OWEYH2.cjs → chunk-MGGU3ZRK.cjs} +1 -1
  38. package/dist/chunk-MGGU3ZRK.cjs.map +1 -0
  39. package/dist/chunk-MOL7MDBG.js +0 -0
  40. package/dist/{chunk-55QIELGV.js → chunk-NMWXBJ5B.js} +4 -3
  41. package/dist/chunk-NMWXBJ5B.js.map +1 -0
  42. package/dist/{chunk-DZIXKL7E.js → chunk-O5EK52TG.js} +1 -1
  43. package/dist/chunk-O5EK52TG.js.map +1 -0
  44. package/dist/{chunk-COHHXQSP.js → chunk-SPKBIFSK.js} +2 -2
  45. package/dist/chunk-SPKBIFSK.js.map +1 -0
  46. package/dist/{chunk-QZCAIFUW.js → chunk-US4WFSIQ.js} +4 -4
  47. package/dist/chunk-US4WFSIQ.js.map +1 -0
  48. package/dist/{chunk-EOVJ27WC.js → chunk-VXMLENCV.js} +15 -11
  49. package/dist/chunk-VXMLENCV.js.map +1 -0
  50. package/dist/{chunk-7FCNOKOR.js → chunk-W34QSJFF.js} +1 -1
  51. package/dist/chunk-W34QSJFF.js.map +1 -0
  52. package/dist/{chunk-URKX77GB.js → chunk-WFOQFJI4.js} +2 -2
  53. package/dist/chunk-WFOQFJI4.js.map +1 -0
  54. package/dist/{chunk-NNDVFNMJ.js → chunk-YNJDNP44.js} +1 -1
  55. package/dist/chunk-YNJDNP44.js.map +1 -0
  56. package/dist/{chunk-LM7C6HI2.js → chunk-ZSL6MQVE.js} +1 -1
  57. package/dist/chunk-ZSL6MQVE.js.map +1 -0
  58. package/dist/chunks-Q7T4SQGW.js +10 -0
  59. package/dist/cli.js +75 -39
  60. package/dist/cli.js.map +1 -1
  61. package/dist/{formula-SXC6LRRC.js → formula-7ZPC54JO.js} +2 -2
  62. package/dist/formula-7ZPC54JO.js.map +1 -0
  63. package/dist/{formula-KSNAEBZ2.js → formula-EQRHSMYW.js} +2 -2
  64. package/dist/formula-EQRHSMYW.js.map +1 -0
  65. package/dist/{formula-SB3NHDUM.cjs → formula-IWV5IJ3Y.cjs} +8 -8
  66. package/dist/formula-IWV5IJ3Y.cjs.map +1 -0
  67. package/dist/{image-ocr-ANSXR5KJ.js → image-ocr-G2LYTEUH.js} +8 -6
  68. package/dist/image-ocr-G2LYTEUH.js.map +1 -0
  69. package/dist/{image-ocr-ORATZW73.js → image-ocr-R44RYDBU.js} +10 -8
  70. package/dist/image-ocr-R44RYDBU.js.map +1 -0
  71. package/dist/{image-ocr-GHQ6YKJH.cjs → image-ocr-XEJFXH6M.cjs} +13 -11
  72. package/dist/image-ocr-XEJFXH6M.cjs.map +1 -0
  73. package/dist/index.cjs +2017 -1868
  74. package/dist/index.cjs.map +1 -1
  75. package/dist/index.d.cts +75 -4
  76. package/dist/index.d.ts +75 -4
  77. package/dist/index.js +2139 -1990
  78. package/dist/index.js.map +1 -1
  79. package/dist/mcp.js +123 -78
  80. package/dist/mcp.js.map +1 -1
  81. package/dist/{models-KA4GLQK5.js → models-6VMAVZR6.js} +3 -3
  82. package/dist/page-range-737B4EZW.js +0 -0
  83. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  84. package/dist/{parser-BPVTYM5K.js → parser-2G245MMN.js} +6 -6
  85. package/dist/parser-2G245MMN.js.map +1 -0
  86. package/dist/{parser-GZDOZCHM.js → parser-W3POOYQQ.js} +8 -8
  87. package/dist/parser-W3POOYQQ.js.map +1 -0
  88. package/dist/{parser-KTNIUF2N.cjs → parser-W3ZGQUCQ.cjs} +25 -25
  89. package/dist/parser-W3ZGQUCQ.cjs.map +1 -0
  90. package/dist/pdf-ocr-3GDTJSHF.js +15 -0
  91. package/dist/pdf-ocr-N7IU23UV.cjs +13 -0
  92. package/dist/pdf-ocr-N7IU23UV.cjs.map +1 -0
  93. package/dist/pdf-ocr-U7MEGNBZ.js +12 -0
  94. package/dist/{profile-io-I2TH44EO.js → profile-io-72R3B7KO.js} +3 -3
  95. package/dist/{rasterize-QBA43YAQ.js → rasterize-GNJQVURE.js} +2 -2
  96. package/dist/rasterize-GNJQVURE.js.map +1 -0
  97. package/dist/{redact-7ILEAUTS.js → redact-W5K3ILQH.js} +2 -2
  98. package/dist/render-6OW6U4L4.js +10 -0
  99. package/dist/seal-GTNLVBJX.js +10 -0
  100. package/dist/{setup-GUQT5ELJ.js → setup-I4I75QJU.js} +1 -1
  101. package/dist/setup-I4I75QJU.js.map +1 -0
  102. package/dist/{watch-7V6X7LHV.js → watch-2ADAJLIJ.js} +12 -14
  103. package/dist/watch-2ADAJLIJ.js.map +1 -0
  104. package/package.json +101 -98
  105. package/templates//352/260/204/354/235/264/352/270/260/354/225/210/353/254/270_/354/204/234/354/213/235.hwpx +0 -0
  106. 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 -0
  107. package/templates//354/235/274/353/260/230/352/270/260/354/225/210/353/254/270_/354/204/234/354/213/235.hwpx +0 -0
  108. 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 +25 -0
  109. package/dist/chunk-2BTKLDNX.cjs.map +0 -1
  110. package/dist/chunk-4FTXKIGT.js.map +0 -1
  111. package/dist/chunk-55QIELGV.js.map +0 -1
  112. package/dist/chunk-5RPYBC2Q.js.map +0 -1
  113. package/dist/chunk-7FCNOKOR.js.map +0 -1
  114. package/dist/chunk-7GEBDS6B.js.map +0 -1
  115. package/dist/chunk-CELJIGBQ.cjs.map +0 -1
  116. package/dist/chunk-COHHXQSP.js.map +0 -1
  117. package/dist/chunk-DZIXKL7E.js.map +0 -1
  118. package/dist/chunk-EOVJ27WC.js.map +0 -1
  119. package/dist/chunk-FS3XQ3ZQ.js.map +0 -1
  120. package/dist/chunk-GEPINOL6.js.map +0 -1
  121. package/dist/chunk-GKSFNCVP.js.map +0 -1
  122. package/dist/chunk-H7OWEYH2.cjs.map +0 -1
  123. package/dist/chunk-IJUAWDOS.js.map +0 -1
  124. package/dist/chunk-J2RZ444H.js.map +0 -1
  125. package/dist/chunk-JP5MNHUK.js.map +0 -1
  126. package/dist/chunk-LM7C6HI2.js.map +0 -1
  127. package/dist/chunk-NNDVFNMJ.js.map +0 -1
  128. package/dist/chunk-OQNFGKRY.cjs.map +0 -1
  129. package/dist/chunk-QZCAIFUW.js.map +0 -1
  130. package/dist/chunk-ROKHZE76.cjs.map +0 -1
  131. package/dist/chunk-TCJRGFYY.js +0 -280
  132. package/dist/chunk-TCJRGFYY.js.map +0 -1
  133. package/dist/chunk-UKEZ46VL.js.map +0 -1
  134. package/dist/chunk-URKX77GB.js.map +0 -1
  135. package/dist/chunk-WP4VD5T7.js.map +0 -1
  136. package/dist/chunk-YNAPK4H6.js.map +0 -1
  137. package/dist/chunks-KKYJDJT7.js +0 -10
  138. package/dist/detect-FX2ZHKZP.js +0 -26
  139. package/dist/formula-KSNAEBZ2.js.map +0 -1
  140. package/dist/formula-SB3NHDUM.cjs.map +0 -1
  141. package/dist/formula-SXC6LRRC.js.map +0 -1
  142. package/dist/image-ocr-ANSXR5KJ.js.map +0 -1
  143. package/dist/image-ocr-GHQ6YKJH.cjs.map +0 -1
  144. package/dist/image-ocr-ORATZW73.js.map +0 -1
  145. package/dist/parser-BPVTYM5K.js.map +0 -1
  146. package/dist/parser-GZDOZCHM.js.map +0 -1
  147. package/dist/parser-KTNIUF2N.cjs.map +0 -1
  148. package/dist/pdf-ocr-6XKLXZTF.cjs +0 -13
  149. package/dist/pdf-ocr-6XKLXZTF.cjs.map +0 -1
  150. package/dist/pdf-ocr-HWPNMKKC.js +0 -12
  151. package/dist/pdf-ocr-W2PA2K3U.js +0 -15
  152. package/dist/rasterize-QBA43YAQ.js.map +0 -1
  153. package/dist/render-MDJDKKNH.js +0 -10
  154. package/dist/seal-Z52JR2GX.js +0 -10
  155. package/dist/seal-Z52JR2GX.js.map +0 -1
  156. package/dist/setup-GUQT5ELJ.js.map +0 -1
  157. package/dist/watch-7V6X7LHV.js.map +0 -1
  158. /package/dist/{-SCTT2TBZ.js.map → -DYHT3HWH.js.map} +0 -0
  159. /package/dist/{chunks-KKYJDJT7.js.map → chunks-Q7T4SQGW.js.map} +0 -0
  160. /package/dist/{detect-FX2ZHKZP.js.map → models-6VMAVZR6.js.map} +0 -0
  161. /package/dist/{models-KA4GLQK5.js.map → pdf-ocr-3GDTJSHF.js.map} +0 -0
  162. /package/dist/{pdf-ocr-HWPNMKKC.js.map → pdf-ocr-U7MEGNBZ.js.map} +0 -0
  163. /package/dist/{pdf-ocr-W2PA2K3U.js.map → profile-io-72R3B7KO.js.map} +0 -0
  164. /package/dist/{profile-io-I2TH44EO.js.map → redact-W5K3ILQH.js.map} +0 -0
  165. /package/dist/{redact-7ILEAUTS.js.map → render-6OW6U4L4.js.map} +0 -0
  166. /package/dist/{render-MDJDKKNH.js.map → seal-GTNLVBJX.js.map} +0 -0
@@ -1,11 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ BUILTIN_TEMPLATES,
3
4
  HwpxSession,
4
5
  PRESET_ALIAS,
5
6
  ValueCursor,
6
7
  blocksToPdf,
7
8
  compare,
9
+ detectFormat,
10
+ detectOle2Format,
11
+ detectZipFormat,
8
12
  diffBlocks,
13
+ extractClickHereFields,
9
14
  extractFormFields,
10
15
  extractFormSchema,
11
16
  fillForm,
@@ -17,8 +22,12 @@ import {
17
22
  hwpxToProfile,
18
23
  incompatibleGongmunWarnings,
19
24
  inferFieldType,
25
+ isHwpxFile,
20
26
  isKnownFont,
21
27
  isLabelCell,
28
+ isOldHwpFile,
29
+ isPdfFile,
30
+ isZipFile,
22
31
  lintGongmunText,
23
32
  markdownToHwpx,
24
33
  markdownToPdf,
@@ -37,21 +46,15 @@ import {
37
46
  patchHwp,
38
47
  patchHwpx,
39
48
  patchHwpxBlocks,
49
+ readBuiltinTemplate,
50
+ readBuiltinTemplateSample,
40
51
  renderHtml,
52
+ resolveBuiltinTemplate,
41
53
  unknownFontWarnings,
42
54
  validateHwpx
43
- } from "./chunk-YNAPK4H6.js";
44
- import "./chunk-7FCNOKOR.js";
45
- import "./chunk-LM7C6HI2.js";
46
- import {
47
- detectFormat,
48
- detectOle2Format,
49
- detectZipFormat,
50
- isHwpxFile,
51
- isOldHwpFile,
52
- isPdfFile,
53
- isZipFile
54
- } from "./chunk-TCJRGFYY.js";
55
+ } from "./chunk-DRZUJ2RK.js";
56
+ import "./chunk-W34QSJFF.js";
57
+ import "./chunk-ZSL6MQVE.js";
55
58
  import "./chunk-MOL7MDBG.js";
56
59
  import {
57
60
  applySplices,
@@ -59,18 +62,18 @@ import {
59
62
  buildRangeSplices,
60
63
  placeSealHwpx,
61
64
  scanSectionXml
62
- } from "./chunk-55QIELGV.js";
65
+ } from "./chunk-NMWXBJ5B.js";
63
66
  import {
64
67
  DEFAULT_REDACT_RULES,
65
68
  redactMarkdown,
66
69
  redactText
67
- } from "./chunk-DZIXKL7E.js";
70
+ } from "./chunk-O5EK52TG.js";
68
71
  import {
69
72
  blocksToChunks
70
- } from "./chunk-COHHXQSP.js";
73
+ } from "./chunk-SPKBIFSK.js";
71
74
  import {
72
75
  blocksToMarkdown
73
- } from "./chunk-4FTXKIGT.js";
76
+ } from "./chunk-GSMPTZC7.js";
74
77
  import {
75
78
  SPACE_EM_FIXED,
76
79
  SPACE_EM_FONT,
@@ -80,12 +83,13 @@ import {
80
83
  renderHwpxToSvg,
81
84
  simulateWrap,
82
85
  simulateWrapKeepWord
83
- } from "./chunk-EOVJ27WC.js";
84
- import "./chunk-GKSFNCVP.js";
86
+ } from "./chunk-VXMLENCV.js";
87
+ import "./chunk-EFVAY5VE.js";
85
88
  import {
86
89
  VERSION
87
- } from "./chunk-7GEBDS6B.js";
90
+ } from "./chunk-DRQUAYNT.js";
88
91
  export {
92
+ BUILTIN_TEMPLATES,
89
93
  DEFAULT_REDACT_RULES,
90
94
  HwpxSession,
91
95
  PRESET_ALIAS,
@@ -105,6 +109,7 @@ export {
105
109
  detectOle2Format,
106
110
  detectZipFormat,
107
111
  diffBlocks,
112
+ extractClickHereFields,
108
113
  extractFormFields,
109
114
  extractFormSchema,
110
115
  fillForm,
@@ -143,14 +148,17 @@ export {
143
148
  patchHwpx,
144
149
  patchHwpxBlocks,
145
150
  placeSealHwpx,
151
+ readBuiltinTemplate,
152
+ readBuiltinTemplateSample,
146
153
  redactMarkdown,
147
154
  redactText,
148
155
  renderHtml,
149
156
  renderHwpxToSvg,
157
+ resolveBuiltinTemplate,
150
158
  scanSectionXml,
151
159
  simulateWrap,
152
160
  simulateWrapKeepWord,
153
161
  unknownFontWarnings,
154
162
  validateHwpx
155
163
  };
156
- //# sourceMappingURL=-SCTT2TBZ.js.map
164
+ //# sourceMappingURL=-DYHT3HWH.js.map
@@ -240,4 +240,4 @@ export {
240
240
  ensureFormulaModels,
241
241
  ensureSingleModel
242
242
  };
243
- //# sourceMappingURL=chunk-J2RZ444H.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":[]}
@@ -3,11 +3,11 @@ import {
3
3
  HEADING_RATIO_H1,
4
4
  HEADING_RATIO_H2,
5
5
  HEADING_RATIO_H3
6
- } from "./chunk-LM7C6HI2.js";
6
+ } from "./chunk-ZSL6MQVE.js";
7
7
  import {
8
8
  safeMax,
9
9
  safeMin
10
- } from "./chunk-7GEBDS6B.js";
10
+ } from "./chunk-DRQUAYNT.js";
11
11
 
12
12
  // src/pdf/image-regions.ts
13
13
  import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
@@ -560,11 +560,24 @@ var CUT_INTERIOR_MATCH_TOL = 8;
560
560
  var CUT_MAX_INTERIOR_OVERLAP = 0.5;
561
561
  var CUT_VCHAIN_X_TOL = 1.5;
562
562
  var CUT_VCHAIN_GAP = 1;
563
+ var VERTEX_BUCKET_CELL = 100;
563
564
  function buildVertices(horizontals, verticals) {
564
565
  const vertices = [];
565
566
  const tol = CONNECT_TOL;
567
+ const buckets = /* @__PURE__ */ new Map();
568
+ for (const v of verticals) {
569
+ const b1 = Math.floor((v.y1 - tol) / VERTEX_BUCKET_CELL);
570
+ const b2 = Math.floor((v.y2 + tol) / VERTEX_BUCKET_CELL);
571
+ for (let b = b1; b <= b2; b++) {
572
+ const arr = buckets.get(b);
573
+ if (arr) arr.push(v);
574
+ else buckets.set(b, [v]);
575
+ }
576
+ }
566
577
  for (const h of horizontals) {
567
- for (const v of verticals) {
578
+ const cand = buckets.get(Math.floor(h.y1 / VERTEX_BUCKET_CELL));
579
+ if (!cand) continue;
580
+ for (const v of cand) {
568
581
  if (v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol && h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol) {
569
582
  const radius = Math.max(h.lineWidth, v.lineWidth, 1);
570
583
  vertices.push({ x: v.x1, y: h.y1, radius });
@@ -638,6 +651,17 @@ function buildTableGrids(horizontals, verticals) {
638
651
  for (const b of bands) groups.push({ lines: b, fromSplit: bands.length > 1 });
639
652
  }
640
653
  const grids = [];
654
+ const byY = [...vertices].sort((a, b) => a.y - b.y);
655
+ const vertexYs = byY.map((v) => v.y);
656
+ const lowerBoundY = (key) => {
657
+ let lo = 0, hi = vertexYs.length;
658
+ while (lo < hi) {
659
+ const mid = lo + hi >> 1;
660
+ if (vertexYs[mid] < key) lo = mid + 1;
661
+ else hi = mid;
662
+ }
663
+ return lo;
664
+ };
641
665
  for (const { lines: group, fromSplit } of groups) {
642
666
  const hLines = group.filter((l) => l.type === "h");
643
667
  const vLines = group.filter((l) => l.type === "v");
@@ -657,9 +681,16 @@ function buildTableGrids(horizontals, verticals) {
657
681
  x2: gx2 + CONNECT_TOL,
658
682
  y2: gy2 + CONNECT_TOL
659
683
  };
660
- const groupVertices = fromSplit ? mergeVertices(buildVertices(hLines, vLines)) : vertices.filter(
661
- (v) => v.x >= groupBbox.x1 && v.x <= groupBbox.x2 && v.y >= groupBbox.y1 && v.y <= groupBbox.y2
662
- );
684
+ let groupVertices;
685
+ if (fromSplit) {
686
+ groupVertices = mergeVertices(buildVertices(hLines, vLines));
687
+ } else {
688
+ groupVertices = [];
689
+ for (let k = lowerBoundY(groupBbox.y1); k < byY.length && vertexYs[k] <= groupBbox.y2; k++) {
690
+ const v = byY[k];
691
+ if (v.x >= groupBbox.x1 && v.x <= groupBbox.x2) groupVertices.push(v);
692
+ }
693
+ }
663
694
  const groupRadius = groupVertices.length > 0 ? groupVertices.reduce((max, v) => Math.max(max, v.radius), 1) : globalRadius;
664
695
  const coordMergeTol = Math.max(VERTEX_MERGE_FACTOR * groupRadius, MIN_COORD_MERGE_TOL);
665
696
  const rawYs = [
@@ -3343,4 +3374,4 @@ export {
3343
3374
  extractPageBlocksWithLines,
3344
3375
  mergeCrossPageTables
3345
3376
  };
3346
- //# sourceMappingURL=chunk-IJUAWDOS.js.map
3377
+ //# sourceMappingURL=chunk-4DBCUF3C.js.map