kordoc 4.12.0 → 4.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +15 -5
  2. package/dist/{-B3EUUBPV.js → -BUYIUNW3.js} +8 -8
  3. package/dist/{chunk-BQSWSMRG.cjs → chunk-6MF7JRG3.cjs} +2 -2
  4. package/dist/chunk-6MF7JRG3.cjs.map +1 -0
  5. package/dist/{chunk-7JQ2ITT2.js → chunk-7U34YNFR.js} +2 -2
  6. package/dist/{chunk-SMGSCP57.js → chunk-BPKJTQAW.js} +231 -40
  7. package/dist/chunk-BPKJTQAW.js.map +1 -0
  8. package/dist/{chunk-TYYJSOZS.cjs → chunk-CBPO5IZZ.cjs} +7 -6
  9. package/dist/chunk-CBPO5IZZ.cjs.map +1 -0
  10. package/dist/{chunk-QKFCGJJY.js → chunk-CJPVVR2Y.js} +2 -2
  11. package/dist/{chunk-MJPRWCI5.js → chunk-D2XNLIFU.js} +2 -2
  12. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  13. package/dist/{chunk-DRLKMFDV.js → chunk-DXKNPBXX.js} +3 -3
  14. package/dist/{chunk-4PKNWBXJ.js → chunk-E37VAV46.js} +2 -2
  15. package/dist/{chunk-2IR3PEZU.js → chunk-ELCPCTJ6.js} +2 -2
  16. package/dist/{chunk-HFJWCAJ2.cjs → chunk-EOGPRO3U.cjs} +6 -6
  17. package/dist/chunk-EOGPRO3U.cjs.map +1 -0
  18. package/dist/{chunk-I2OETEEL.js → chunk-EWSH6O6S.js} +2 -2
  19. package/dist/{chunk-46UDI56N.js → chunk-F2KL5Z5T.js} +3 -3
  20. package/dist/{chunk-COGFPMEB.js → chunk-FNCBOTM5.js} +2 -2
  21. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  22. package/dist/{chunk-TF5DS74F.js → chunk-JD4H6Y33.js} +3 -3
  23. package/dist/{chunk-GHVXLV52.js → chunk-JQBF4PSF.js} +5 -4
  24. package/dist/chunk-JQBF4PSF.js.map +1 -0
  25. package/dist/{chunk-BQE7AMFS.js → chunk-L5DNHSX4.js} +2 -2
  26. package/dist/{chunk-TJYZY2O6.js → chunk-L6FIUSD2.js} +3 -3
  27. package/dist/{chunk-AT23GFSK.cjs → chunk-MXZXZTKW.cjs} +236 -45
  28. package/dist/chunk-MXZXZTKW.cjs.map +1 -0
  29. package/dist/{chunk-SKGYPM6L.js → chunk-OS2IOI3W.js} +231 -40
  30. package/dist/chunk-OS2IOI3W.js.map +1 -0
  31. package/dist/{chunk-YASFVKDT.js → chunk-ST3JBCIL.js} +2 -2
  32. package/dist/chunk-T7GRHAIW.cjs.map +1 -1
  33. package/dist/{chunk-PFUW4I4I.js → chunk-VO37BVAH.js} +142 -11
  34. package/dist/{chunk-PFUW4I4I.js.map → chunk-VO37BVAH.js.map} +1 -1
  35. package/dist/{chunk-HGUNWAXK.js → chunk-WGUVKNBX.js} +5 -4
  36. package/dist/chunk-WGUVKNBX.js.map +1 -0
  37. package/dist/{chunk-TROD36DK.cjs → chunk-WJOWM27X.cjs} +3 -3
  38. package/dist/chunk-WJOWM27X.cjs.map +1 -0
  39. package/dist/chunks-G2U2XUZU.js +10 -0
  40. package/dist/cli.js +21 -21
  41. package/dist/cli.js.map +1 -1
  42. package/dist/{formula-WGKKR5OQ.js → formula-7TN26AKF.js} +4 -4
  43. package/dist/{formula-G4CD5KQ6.js → formula-RZNSECGO.js} +3 -3
  44. package/dist/{formula-YMGXXRBQ.cjs → formula-XKRMKGAW.cjs} +9 -9
  45. package/dist/formula-XKRMKGAW.cjs.map +1 -0
  46. package/dist/{image-ocr-MBWZK3ZT.js → image-ocr-3O34O7XS.js} +5 -5
  47. package/dist/{image-ocr-M4RP4INO.js → image-ocr-TNU4ILLU.js} +7 -7
  48. package/dist/{image-ocr-BE2HHMVN.cjs → image-ocr-XDZONLD6.cjs} +11 -11
  49. package/dist/image-ocr-XDZONLD6.cjs.map +1 -0
  50. package/dist/index.cjs +446 -315
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +5 -2
  53. package/dist/index.d.ts +5 -2
  54. package/dist/index.js +138 -7
  55. package/dist/index.js.map +1 -1
  56. package/dist/mcp.js +15 -15
  57. package/dist/{model-bundle-BIMP5DIH.js → model-bundle-NCYN375I.js} +5 -5
  58. package/dist/{models-QPOEGHVA.js → models-J4G4VZMU.js} +5 -5
  59. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  60. package/dist/{parser-NR3XO4SM.js → parser-5WZBCP7H.js} +307 -6
  61. package/dist/parser-5WZBCP7H.js.map +1 -0
  62. package/dist/{parser-CMNHBVTL.js → parser-6IH62P6U.js} +307 -6
  63. package/dist/parser-6IH62P6U.js.map +1 -0
  64. package/dist/{parser-QJY5W47V.cjs → parser-LVMTVO2N.cjs} +334 -33
  65. package/dist/parser-LVMTVO2N.cjs.map +1 -0
  66. package/dist/pdf-ocr-5PLJOKGI.js +13 -0
  67. package/dist/pdf-ocr-7FPCZCV7.js +16 -0
  68. package/dist/pdf-ocr-IAO273AQ.cjs +14 -0
  69. package/dist/pdf-ocr-IAO273AQ.cjs.map +1 -0
  70. package/dist/{profile-io-UZ43CLWX.js → profile-io-ANPS7DTT.js} +3 -3
  71. package/dist/{rasterize-JC2JVVOF.js → rasterize-FKXCEFHV.js} +2 -2
  72. package/dist/render-NWQI3AXU.js +10 -0
  73. package/dist/seal-N3GCBTVA.js +10 -0
  74. package/dist/{setup-WDUVPFX3.js → setup-DEAFODGI.js} +3 -3
  75. package/dist/{watch-UI7OXIES.js → watch-B5X7IAMK.js} +9 -9
  76. package/package.json +1 -1
  77. package/dist/chunk-AT23GFSK.cjs.map +0 -1
  78. package/dist/chunk-BQSWSMRG.cjs.map +0 -1
  79. package/dist/chunk-GHVXLV52.js.map +0 -1
  80. package/dist/chunk-HFJWCAJ2.cjs.map +0 -1
  81. package/dist/chunk-HGUNWAXK.js.map +0 -1
  82. package/dist/chunk-SKGYPM6L.js.map +0 -1
  83. package/dist/chunk-SMGSCP57.js.map +0 -1
  84. package/dist/chunk-TROD36DK.cjs.map +0 -1
  85. package/dist/chunk-TYYJSOZS.cjs.map +0 -1
  86. package/dist/chunks-Q4GCPRRU.js +0 -10
  87. package/dist/formula-YMGXXRBQ.cjs.map +0 -1
  88. package/dist/image-ocr-BE2HHMVN.cjs.map +0 -1
  89. package/dist/parser-CMNHBVTL.js.map +0 -1
  90. package/dist/parser-NR3XO4SM.js.map +0 -1
  91. package/dist/parser-QJY5W47V.cjs.map +0 -1
  92. package/dist/pdf-ocr-DF3OTBKH.js +0 -13
  93. package/dist/pdf-ocr-R4DIKF4Q.js +0 -16
  94. package/dist/pdf-ocr-T3RHIZKY.cjs +0 -14
  95. package/dist/pdf-ocr-T3RHIZKY.cjs.map +0 -1
  96. package/dist/render-KSQ6UGCP.js +0 -10
  97. package/dist/seal-EOFFQ4JR.js +0 -10
  98. /package/dist/{-B3EUUBPV.js.map → -BUYIUNW3.js.map} +0 -0
  99. /package/dist/{chunk-7JQ2ITT2.js.map → chunk-7U34YNFR.js.map} +0 -0
  100. /package/dist/{chunk-QKFCGJJY.js.map → chunk-CJPVVR2Y.js.map} +0 -0
  101. /package/dist/{chunk-MJPRWCI5.js.map → chunk-D2XNLIFU.js.map} +0 -0
  102. /package/dist/{chunk-DRLKMFDV.js.map → chunk-DXKNPBXX.js.map} +0 -0
  103. /package/dist/{chunk-4PKNWBXJ.js.map → chunk-E37VAV46.js.map} +0 -0
  104. /package/dist/{chunk-2IR3PEZU.js.map → chunk-ELCPCTJ6.js.map} +0 -0
  105. /package/dist/{chunk-I2OETEEL.js.map → chunk-EWSH6O6S.js.map} +0 -0
  106. /package/dist/{chunk-46UDI56N.js.map → chunk-F2KL5Z5T.js.map} +0 -0
  107. /package/dist/{chunk-COGFPMEB.js.map → chunk-FNCBOTM5.js.map} +0 -0
  108. /package/dist/{chunk-TF5DS74F.js.map → chunk-JD4H6Y33.js.map} +0 -0
  109. /package/dist/{chunk-BQE7AMFS.js.map → chunk-L5DNHSX4.js.map} +0 -0
  110. /package/dist/{chunk-TJYZY2O6.js.map → chunk-L6FIUSD2.js.map} +0 -0
  111. /package/dist/{chunk-YASFVKDT.js.map → chunk-ST3JBCIL.js.map} +0 -0
  112. /package/dist/{chunks-Q4GCPRRU.js.map → chunks-G2U2XUZU.js.map} +0 -0
  113. /package/dist/{formula-WGKKR5OQ.js.map → formula-7TN26AKF.js.map} +0 -0
  114. /package/dist/{formula-G4CD5KQ6.js.map → formula-RZNSECGO.js.map} +0 -0
  115. /package/dist/{image-ocr-MBWZK3ZT.js.map → image-ocr-3O34O7XS.js.map} +0 -0
  116. /package/dist/{image-ocr-M4RP4INO.js.map → image-ocr-TNU4ILLU.js.map} +0 -0
  117. /package/dist/{model-bundle-BIMP5DIH.js.map → model-bundle-NCYN375I.js.map} +0 -0
  118. /package/dist/{models-QPOEGHVA.js.map → models-J4G4VZMU.js.map} +0 -0
  119. /package/dist/{pdf-ocr-DF3OTBKH.js.map → pdf-ocr-5PLJOKGI.js.map} +0 -0
  120. /package/dist/{pdf-ocr-R4DIKF4Q.js.map → pdf-ocr-7FPCZCV7.js.map} +0 -0
  121. /package/dist/{profile-io-UZ43CLWX.js.map → profile-io-ANPS7DTT.js.map} +0 -0
  122. /package/dist/{rasterize-JC2JVVOF.js.map → rasterize-FKXCEFHV.js.map} +0 -0
  123. /package/dist/{render-KSQ6UGCP.js.map → render-NWQI3AXU.js.map} +0 -0
  124. /package/dist/{seal-EOFFQ4JR.js.map → seal-N3GCBTVA.js.map} +0 -0
  125. /package/dist/{setup-WDUVPFX3.js.map → setup-DEAFODGI.js.map} +0 -0
  126. /package/dist/{watch-UI7OXIES.js.map → watch-B5X7IAMK.js.map} +0 -0
package/README.md CHANGED
@@ -76,7 +76,7 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
76
76
  * **📊 복잡한 표(Table) 복원 — 숫자로 잠금**: 병합·중첩 표를 구조 그대로 살립니다. HWPX 코퍼스 291문서 **표 1,673/1,673·셀 27,714/27,714 무손실**(독립 추출기 대조 게이트 1.0), 동일문서 hwpx↔pdf 대조(6쌍 69표)는 **매칭 98.6%·완전일치 65.2%**. 선 없는 PDF 표도 감지해 복원하고, 법령 개정안 PDF의 신구조문대비표도 통째로 살립니다 (v3.16.2).
77
77
  * **🔍 신구대조표 자동 생성**: 두 문서의 차이점을 분석하여 무엇이 바뀌었는지 한눈에 보여줍니다. (HWP와 HWPX 간의 비교도 가능!)
78
78
  * **📝 마크다운을 다시 HWPX로**: AI가 작성한 내용을 다시 보고서 양식(`HWPX`)으로 되돌려줍니다. 이제 복사-붙여넣기 노가다에서 해방되세요.
79
- * **🏛️ 정부 표준 공문서 생성 (v4.0)**: 실제 정부 양식 16종 + 실결재 기안문 60건을 전수 디코드·대조해 만든 공문서 엔진. 개조식 보고서(표지·목차 배너·로마숫자 장헤더·쪽번호·결재란), 기안문(별지 제1호서식 두문·결문, "끝." 자동), 공고문·보도자료 프리셋, 항목부호 8단계(1. 가. 1) 가)…) 자동, 공문서 표기법 검수 13룰(`kordoc lint`)까지 — 한글 COM 실렌더로 조판까지 실측 검증했습니다.
79
+ * **🏛️ 정부 표준 공문서 생성 (v4.0)**: 실제 정부 양식 16종 + 실결재 기안문 60건을 전수 디코드·대조해 만든 공문서 엔진. 개조식 보고서(표지·목차 배너·로마숫자 장헤더·쪽번호·결재란), 기안문(별지 제1호서식 두문·결문, "끝." 자동), 공고문·보도자료 프리셋, 항목부호 8단계(1. 가. 1) 가)…) 자동, 공문서 표기법 검수 19룰(`kordoc lint`)까지 — 한글 COM 실렌더로 조판까지 실측 검증했습니다.
80
80
  * **🔄 서식 보존 무손실 라운드트립 (v3.0)**: 변환된 마크다운을 편집해서 `patchHwpx`(HWPX) / `patchHwp`(HWP 5.x 바이너리)에 넘기면, **원본 서식을 1바이트도 건드리지 않고** 바뀐 문단/표 셀의 텍스트만 원본 안에서 교체합니다. v3.7부터는 **표에 행을 추가/삭제하는 편집**도 원본 서식을 승계하며 반영되고, v3.8부터는 HWP 5.x의 **빈 셀에 값 넣기**도 지원합니다.
81
81
  * **🖼️ 레이아웃 보존 렌더 (v3.10~3.15)**: 한컴이 저장한 조판 캐시 좌표로 원본 레이아웃을 SVG로 재현하고, 캐시가 없는 파일(AI가 만든 HWPX·편집본)은 **순수 TS reflow 엔진**이 직접 조판합니다. 다페이지·표·그리기 도형·검색어 형광펜까지. 서버에 한컴 없이 HWPX 미리보기를 만들 수 있습니다.
82
82
  * **📊 차트 생성 (v3.16)**: 마크다운의 ```chart 펜스(type/cat/계열 라인)가 한컴 네이티브 차트(OOXML chartSpace)로 생성됩니다 — 막대·선·원·도넛·영역·분산·방사형 등 20종, 계열/조각 색 지정 가능.
@@ -89,6 +89,16 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
89
89
 
90
90
  ---
91
91
 
92
+ ## v4.12.1 변경사항
93
+
94
+ 법령 **별지서식**(법제처 별표·서식) 파싱을 HWP·HWPX·PDF 세 포맷에서 맞췄습니다. 법제처 OpenAPI로 서식 300건의 HWP 원본과 PDF를 쌍으로 모으고 rhwp 변환 HWPX를 붙인 900파일 코퍼스를 게이트에 편입해, HWP↔PDF 셀 텍스트 일치 0.79 → 0.92, PDF 표 GT cellExact 0.73 → 0.96 을 실측했습니다.
95
+
96
+ - **📐 PDF 셀 클립 그리드**: 한컴 PDF는 표 셀마다 클립 사각형을 깔고 글을 찍습니다. 별지서식 외곽 표는 테두리 "없음" 셀이라 괘선만 보는 종전 경로가 표를 놓치고 제목·기입란이 헤딩과 문단으로 흩어졌는데, 이제 클립 사각형 묶음을 **셀 기하가 확정된 표**로 바로 씁니다. 도선사면허 신청서가 HWP 파서와 같은 23열·같은 병합 구조로 나옵니다. 클립의 포함 관계로 층을 나눠, 지정서·증서의 1칸 테두리 틀은 제목행·꼬리행과 한 표의 셀이 되고 안쪽 "발신명의 | 직인" 표는 별도 표로 살립니다.
97
+ - **➔ 심볼 폰트 글리프 복원**: 처리절차 화살표·체크박스를 Wingdings로 찍은 PDF에서 `è` 같은 깨진 라틴 문자가 남던 것을 유니코드(➔ □ ✔ ☑)로 되돌립니다.
98
+ - **📝 표기법 검수 6룰 보강**: 금액 괄호 한글 병기(`MONEY_NO_HANGUL`), 물결표 앞뒤 붙여쓰기(`TILDE_SPACE`), 두음법칙 '년도→연도'(`DUEUM_ERROR`), 외래어 오기 40종(`LOANWORD_ERROR`, 표준 표기 제안), 차별·비하 표현 25종(`DISCRIMINATORY_TERM`, 순화어 제안), 붙임 있는데 "끝." 없음(`END_MARK_MISSING`, `kordoc lint` 전용). 시중 HWPX 공문 도구(pyhwpxlib·hwpx-skill)의 검사 항목과 대조해 비어 있던 축을 채웠습니다.
99
+ - **🏛️ 내부결재 문서의 발신명의 생략**: 수신이 "내부결재"인 기안문은 결문에 발신명의를 적지 않습니다(행정업무규정 시행규칙).
100
+ - **🩹 기입 빈칸 보존**: "년 월 일"·"시 분"이 "년월일"로 붙던 것, 청구서류 1열 틀 표의 기입 항목이 한 줄로 뭉개지던 것을 고쳤습니다.
101
+
92
102
  ## v4.12.0 변경사항
93
103
 
94
104
  v4.11.0 이 걷어낸 HWP3 유실의 **잔여 전량**을 처리했습니다. rhwp `samples/` 의 HWP3 원본 9종을 한컴 변환본과 문자 멀티셋으로 대조하면, 이제 변환본에만 있는 문자는 그림 대체텍스트를 빼면 **0** 입니다.
@@ -293,7 +303,7 @@ rhwp 최신 devel(v0.8.3~0.8.4)을 훑어 kordoc 의 HWP3 디코더에 반영했
293
303
  ## v4.0.1 변경사항
294
304
 
295
305
  - **✍️ 실무자 QA 3건 수정**: bold 시 폰트가 HY견고딕/Arial Black으로 바뀌던 "정체모를 폰트" 제거, h2 섹션 제목 말머리 `--h2-marker`(box □ / number / none), 개조식 소분류 부호 ― → 실무 관행 하이픈 `-`.
296
- - **📝 공문서 표기법 검수 13룰**: 날짜·시간·금액·붙임 표기 등을 검수하는 `kordoc lint <file>` + 공문서 생성 시 경고 병기.
306
+ - **📝 공문서 표기법 검수 19룰**: 날짜·시간·금액·붙임·외래어·차별 표현 등을 검수하는 `kordoc lint <file>` + 공문서 생성 시 경고 병기.
297
307
 
298
308
  ## v4.0.0 변경사항
299
309
 
@@ -892,7 +902,7 @@ npx kordoc generate 보고서.md -o 보고서.hwpx --preset 보고서 #
892
902
  npx kordoc patch 원본.hwpx 편집.md -o 반영.hwpx # 서식 보존 라운드트립 패치 (.hwp도 자동 분기)
893
903
  npx kordoc seal 신청서.hwpx --image 도장.png --anchor "(인)" -o 날인.hwpx # 도장/서명 날인
894
904
  npx kordoc validate 산출물.hwpx # HWPX 구조 검증 (ZIP·필수 파트·XML)
895
- npx kordoc lint 보고서.md # 공문서 표기법 검수 13룰 — 입력은 md/txt('-'=stdin), error 있으면 exit 1
905
+ npx kordoc lint 보고서.md # 공문서 표기법 검수 19룰 — 입력은 md/txt('-'=stdin), error 있으면 exit 1
896
906
  npx kordoc redact 민원서류.hwpx -o 마스킹.hwpx # 개인정보 탐지 + 서식 보존 마스킹 (v4.1)
897
907
  npx kordoc redact 민원서류.hwpx --mask-char '*' -o 마스킹.hwpx # 마스크 문자 지정 (기본 ●)
898
908
  npx kordoc profile 기관서식.hwpx # 표 서식 프로필(JSON) 추출 → generate --profile 로 재현
@@ -910,7 +920,7 @@ npx kordoc watch ./문서 --webhook https://api/hook # 웹훅 알림
910
920
  >
911
921
  > `kordoc lint` 는 **텍스트(마크다운/txt)를 검사합니다** — HWPX 를 바로 넘기면 안 됩니다.
912
922
  > 생성물을 검수하려면 원고 마크다운을 넘기거나, `kordoc 문서.hwpx | kordoc lint -` 로 파이프하세요.
913
- > (공문서 생성 시에는 `generate` 가 같은 13룰 경고를 함께 냅니다.)
923
+ > (공문서 생성 시에는 `generate` 가 같은 룰의 경고를 함께 냅니다. `END_MARK_MISSING` 은 완성 원고를 보는 `lint` 에서만 켜집니다.)
914
924
 
915
925
  ### 실패 계약 — 기계 판독 가능한 실패 JSON (v4.10.0+, #69)
916
926
 
@@ -1056,7 +1066,7 @@ codex mcp add kordoc -- npx -y kordoc mcp
1056
1066
  | `renderHwpxToSvg(buffer, options?)` | HWPX → 레이아웃 보존 SVG — 다페이지·형광펜·도형, 캐시 없으면 `reflow` (v3.10~15) |
1057
1067
  | `placeSealHwpx(buffer, seals)` | 도장/서명 이미지를 앵커 문구 위에 부유 배치 (v3.16) |
1058
1068
  | `validateHwpx(buffer)` | HWPX 구조 검증 — ZIP·mimetype·필수 파트·XML 웰폼드 (v3.16) |
1059
- | `lintGongmunText(text)` | 공문서 표기법 검수 13룰 — 텍스트/마크다운 입력 (v4.0.1) |
1069
+ | `lintGongmunText(text, { document? })` | 공문서 표기법 검수 19+ AI 슬롭 2룰 — 텍스트/마크다운 입력 (v4.0.1, v4.12.1 보강). `document: true` 면 붙임/"끝." 문서 단위 검사 포함 |
1060
1070
  | `redactMarkdown(text, options?)` / `redactText(...)` | 개인정보 탐지 + 마스킹 (기본 룰: 주민번호·전화·이메일·카드·계좌, v4.1) |
1061
1071
  | `blocksToChunks(blocks, options?)` | RAG용 구조 청크 — 헤딩·개조식 위계 breadcrumb + 표 독립 청크 (v4.1) |
1062
1072
  | `blocksToMarkdown(blocks)` | IRBlock[] → Markdown 문자열 |
@@ -56,7 +56,7 @@ import {
56
56
  unknownFontWarnings,
57
57
  usesGaejosikMunche,
58
58
  validateHwpx
59
- } from "./chunk-PFUW4I4I.js";
59
+ } from "./chunk-VO37BVAH.js";
60
60
  import "./chunk-7FCNOKOR.js";
61
61
  import "./chunk-N3DLQ2YH.js";
62
62
  import "./chunk-MOL7MDBG.js";
@@ -66,7 +66,7 @@ import {
66
66
  buildRangeSplices,
67
67
  placeSealHwpx,
68
68
  scanSectionXml
69
- } from "./chunk-TF5DS74F.js";
69
+ } from "./chunk-JD4H6Y33.js";
70
70
  import {
71
71
  DEFAULT_REDACT_RULES,
72
72
  redactMarkdown,
@@ -74,10 +74,10 @@ import {
74
74
  } from "./chunk-DZIXKL7E.js";
75
75
  import {
76
76
  blocksToChunks
77
- } from "./chunk-YASFVKDT.js";
77
+ } from "./chunk-ST3JBCIL.js";
78
78
  import {
79
79
  blocksToMarkdown
80
- } from "./chunk-GHVXLV52.js";
80
+ } from "./chunk-JQBF4PSF.js";
81
81
  import {
82
82
  SPACE_EM_FIXED,
83
83
  SPACE_EM_FONT,
@@ -87,11 +87,11 @@ import {
87
87
  renderHwpxToSvg,
88
88
  simulateWrap,
89
89
  simulateWrapKeepWord
90
- } from "./chunk-46UDI56N.js";
91
- import "./chunk-I2OETEEL.js";
90
+ } from "./chunk-F2KL5Z5T.js";
91
+ import "./chunk-EWSH6O6S.js";
92
92
  import {
93
93
  VERSION
94
- } from "./chunk-4PKNWBXJ.js";
94
+ } from "./chunk-E37VAV46.js";
95
95
  export {
96
96
  BUILTIN_TEMPLATES,
97
97
  DEFAULT_REDACT_RULES,
@@ -169,4 +169,4 @@ export {
169
169
  usesGaejosikMunche,
170
170
  validateHwpx
171
171
  };
172
- //# sourceMappingURL=-B3EUUBPV.js.map
172
+ //# sourceMappingURL=-BUYIUNW3.js.map
@@ -1,5 +1,5 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/utils.ts
2
- var VERSION = true ? "4.12.0" : "0.0.0-dev";
2
+ var VERSION = true ? "4.12.1" : "0.0.0-dev";
3
3
  function toArrayBuffer(buf) {
4
4
  if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
5
5
  return buf.buffer;
@@ -121,4 +121,4 @@ function classifyError(err) {
121
121
 
122
122
 
123
123
  exports.VERSION = VERSION; exports.toArrayBuffer = toArrayBuffer; exports.KordocError = KordocError; exports.sanitizeError = sanitizeError; exports.isPathTraversal = isPathTraversal; exports.normalizeSectionHref = normalizeSectionHref; exports.compareSectionPaths = compareSectionPaths; exports.precheckZipSize = precheckZipSize; exports.stripDtd = stripDtd; exports.sanitizeHref = sanitizeHref; exports.safeMin = safeMin; exports.safeMax = safeMax; exports.classifyError = classifyError;
124
- //# sourceMappingURL=chunk-BQSWSMRG.cjs.map
124
+ //# sourceMappingURL=chunk-6MF7JRG3.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/chris_gomdori/workspace/kordoc/dist/chunk-6MF7JRG3.cjs","../src/utils.ts"],"names":[],"mappings":"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","file":"/Users/chris_gomdori/workspace/kordoc/dist/chunk-6MF7JRG3.cjs","sourcesContent":[null,"/** kordoc 공용 유틸리티 */\n\n/** 빌드 타임에 tsup define으로 주입되는 버전 */\ndeclare const __KORDOC_VERSION__: string\nexport const VERSION: string = typeof __KORDOC_VERSION__ !== \"undefined\" ? __KORDOC_VERSION__ : \"0.0.0-dev\"\n\n/**\n * Node.js Buffer → ArrayBuffer 변환\n * pool Buffer의 공유 ArrayBuffer 문제를 안전하게 처리.\n * offset=0이고 전체 ArrayBuffer를 차지하면 복사 없이 직접 반환.\n */\nexport function toArrayBuffer(buf: Buffer): ArrayBuffer {\n if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {\n return buf.buffer as ArrayBuffer\n }\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\n/**\n * kordoc 내부 에러 클래스 — 사용자에게 노출해도 안전한 메시지만 포함.\n * MCP 에러 정제에서 instanceof로 판별하여 allowlist 패턴 매칭 없이 안전하게 통과.\n */\nexport class KordocError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"KordocError\"\n }\n}\n\n/**\n * 에러 메시지 정제 — KordocError는 그대로, 나머지는 일반 메시지로 대체.\n * 파일시스템 경로, 스택 트레이스 등 내부 정보 노출 방지.\n */\nexport function sanitizeError(err: unknown): string {\n if (err instanceof KordocError) return err.message\n return \"문서 처리 중 오류가 발생했습니다\"\n}\n\n/**\n * ZIP 엔트리 경로의 경로 순회 여부 판별.\n * 백슬래시 정규화, .., 절대경로, Windows 드라이브 문자 모두 차단.\n */\nexport function isPathTraversal(name: string): boolean {\n if (name.includes(\"\\x00\")) return true\n const normalized = name.replace(/\\\\/g, \"/\")\n const segments = normalized.split(\"/\")\n return segments.some(s => s === \"..\") || normalized.startsWith(\"/\") || /^[A-Za-z]:/.test(normalized)\n}\n\n// ─── HWPX 섹션 href 해석 (parser/roundtrip 공용) ────────────────────\n\n/**\n * manifest href를 본문 섹션 경로(`Contents/sectionN.xml`)로 정규화.\n * 비본문 XML(header/script/settings)·path traversal·드라이브 경로는 null로 거른다.\n * 백슬래시 구분자를 슬래시로 통일해 parser와 roundtrip이 동일한 목록을 만든다.\n */\nexport function normalizeSectionHref(href: string): string | null {\n if (!href) return null\n let normalized = href.replace(/\\\\/g, \"/\").replace(/^\\/+/, \"\")\n if (isPathTraversal(normalized)) return null\n if (/^[Ss]ection\\d+\\.xml$/.test(normalized)) normalized = \"Contents/\" + normalized\n return /(?:^|\\/)[Ss]ection\\d+\\.xml$/.test(normalized) ? normalized : null\n}\n\n/** sectionN.xml을 N 숫자 순서로 정렬 (section10 > section2). */\nexport function compareSectionPaths(a: string, b: string): number {\n const ai = Number(a.match(/[Ss]ection(\\d+)\\.xml$/)?.[1] ?? Number.MAX_SAFE_INTEGER)\n const bi = Number(b.match(/[Ss]ection(\\d+)\\.xml$/)?.[1] ?? Number.MAX_SAFE_INTEGER)\n return ai === bi ? a.localeCompare(b) : ai - bi\n}\n\n// ─── ZIP 안전 로딩 (ZIP bomb 방지) ────────────────────\n\n/**\n * ZIP bomb 사전 검사 — Central Directory에서 비압축 합계와 엔트리 수 확인.\n * HWPX/XLSX/DOCX 등 모든 ZIP 기반 포맷에서 공통 사용.\n */\nexport function precheckZipSize(\n buffer: ArrayBuffer,\n maxUncompressedSize = 256 * 1024 * 1024, // parser-shared MAX_DECOMPRESS_SIZE 와 동기\n maxEntries = 500,\n): { totalUncompressed: number; entryCount: number } {\n try {\n const data = new DataView(buffer)\n const len = buffer.byteLength\n // EOCD 시그니처 역방향 스캔\n let eocdOffset = -1\n for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {\n if (data.getUint32(i, true) === 0x06054b50) { eocdOffset = i; break }\n }\n if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 }\n\n const entryCount = data.getUint16(eocdOffset + 10, true)\n if (entryCount > maxEntries) {\n throw new KordocError(`ZIP 엔트리 수 초과: ${entryCount} (최대 ${maxEntries})`)\n }\n\n const cdSize = data.getUint32(eocdOffset + 12, true)\n const cdOffset = data.getUint32(eocdOffset + 16, true)\n if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount }\n\n let totalUncompressed = 0\n let pos = cdOffset\n for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {\n if (data.getUint32(pos, true) !== 0x02014b50) break\n totalUncompressed += data.getUint32(pos + 24, true)\n const nameLen = data.getUint16(pos + 28, true)\n const extraLen = data.getUint16(pos + 30, true)\n const commentLen = data.getUint16(pos + 32, true)\n pos += 46 + nameLen + extraLen + commentLen\n }\n\n if (totalUncompressed > maxUncompressedSize) {\n throw new KordocError(`ZIP 비압축 크기 초과: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (최대 ${maxUncompressedSize / 1024 / 1024}MB)`)\n }\n\n return { totalUncompressed, entryCount }\n } catch (err) {\n if (err instanceof KordocError) throw err\n return { totalUncompressed: 0, entryCount: 0 }\n }\n}\n\n/**\n * XML 파싱 전 프롤로그 정리 — 선두 BOM 제거 + DOCTYPE 제거(XXE/Billion Laughs 방지).\n *\n * BOM: 일부 OpenXML 라이터가 `[Content_Types].xml`·`.rels` 앞에 EF BB BF를 붙인다.\n * XML 스펙상 적법(엔티티는 BOM으로 시작할 수 있음)이고 엑셀·리브레오피스는 여는데,\n * xmldom은 \"선언이 문서 시작이 아님\" fatalError로 파일 전체를 거부했다 (#63).\n */\nexport function stripDtd(xml: string): string {\n return xml.replace(/^\\uFEFF/, \"\").replace(/<!DOCTYPE\\s[^[>]*(\\[[\\s\\S]*?\\])?\\s*>/gi, \"\")\n}\n\n/** 하이퍼링크 URL 살균 — javascript: 등 XSS 위험 스킴 차단 */\nconst SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i\nexport function sanitizeHref(href: string): string | null {\n const trimmed = href.trim()\n if (!trimmed || !SAFE_HREF_RE.test(trimmed)) return null\n // 괄호 percent-encoding — 마크다운 링크 목적지에서 불균형 ')'는 링크를 조기 종료시켜\n // 문법이 깨진다 (원문 하이퍼링크에 ')'가 박힌 실문서 존재). %28/%29는 의미 동일.\n return trimmed.replace(/\\(/g, \"%28\").replace(/\\)/g, \"%29\")\n}\n\n// ─── 안전한 min/max (스택 오버플로 방지) ─────────────\n\n/** Math.min(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMin(arr: number[]): number {\n let min = Infinity\n for (let i = 0; i < arr.length; i++) if (arr[i] < min) min = arr[i]\n return min\n}\n\n/** Math.max(...arr) 대체 — 대형 배열에서 스택 오버플로 방지 */\nexport function safeMax(arr: number[]): number {\n let max = -Infinity\n for (let i = 0; i < arr.length; i++) if (arr[i] > max) max = arr[i]\n return max\n}\n\n// ─── 에러 분류 ──────────────────────────────────────\n\nimport type { ErrorCode } from \"./types.js\"\n\n/** 에러를 구조화된 ErrorCode로 분류 — KordocError 메시지 패턴 매칭 */\nexport function classifyError(err: unknown): ErrorCode {\n if (!(err instanceof Error)) return \"PARSE_ERROR\"\n const msg = err.message\n // DRM 을 암호화보다 먼저 — \"DRM 암호화된 HWPX…\" 류 메시지가 ENCRYPTED 로 떨어지지 않도록\n if (msg.includes(\"DRM\")) return \"DRM_PROTECTED\"\n if (msg.includes(\"암호화\") || msg.includes(\"암호로 보호\")) return \"ENCRYPTED\"\n if (msg.includes(\"optional dependency\")) return \"MISSING_DEPENDENCY\"\n // V8 문자열 한계 초과 — 이미지 base64 총량이 큰 문서의 JSON 직렬화 (#65)\n if (msg.includes(\"Invalid string length\") || msg.includes(\"Cannot create a string longer\")) return \"OUTPUT_TOO_LARGE\"\n if (msg.includes(\"ZIP bomb\") || msg.includes(\"ZIP 비압축 크기 초과\") || msg.includes(\"ZIP 엔트리 수 초과\")) return \"ZIP_BOMB\"\n if (msg.includes(\"bomb\") || msg.includes(\"크기 초과\") || msg.includes(\"압축 해제\")) return \"DECOMPRESSION_BOMB\"\n if (msg.includes(\"이미지 기반\")) return \"IMAGE_BASED_PDF\"\n if (msg.includes(\"섹션\") && (msg.includes(\"찾을 수 없\") || msg.includes(\"없음\"))) return \"NO_SECTIONS\"\n if (msg.includes(\"시그니처\") || msg.includes(\"복구할 수 없\")) return \"CORRUPTED\"\n return \"PARSE_ERROR\"\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  KordocError
3
- } from "./chunk-MJPRWCI5.js";
3
+ } from "./chunk-D2XNLIFU.js";
4
4
 
5
5
  // src/pdf/formula/models.ts
6
6
  import { createHash } from "crypto";
@@ -266,4 +266,4 @@ export {
266
266
  ensureFormulaModels,
267
267
  ensureSingleModel
268
268
  };
269
- //# sourceMappingURL=chunk-7JQ2ITT2.js.map
269
+ //# sourceMappingURL=chunk-7U34YNFR.js.map
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  safeMax,
8
8
  safeMin
9
- } from "./chunk-MJPRWCI5.js";
9
+ } from "./chunk-D2XNLIFU.js";
10
10
 
11
11
  // src/pdf/image-regions.ts
12
12
  import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
@@ -68,6 +68,8 @@ var MAX_LINE_WIDTH = 5;
68
68
  function extractLines(fnArray, argsArray) {
69
69
  const horizontals = [];
70
70
  const verticals = [];
71
+ const clipRects = [];
72
+ let pendingClip = false;
71
73
  let lineWidth = 1;
72
74
  let ctm = [1, 0, 0, 1, 0, 0];
73
75
  const ctmStack = [];
@@ -97,9 +99,12 @@ function extractLines(fnArray, argsArray) {
97
99
  }
98
100
  function flushPath(isStroke, fromFill = false) {
99
101
  if (!isStroke) {
102
+ if (pendingClip) captureClipRect(currentPath, clipRects);
103
+ pendingClip = false;
100
104
  currentPath = [];
101
105
  return;
102
106
  }
107
+ pendingClip = false;
103
108
  const effWidth = lineWidth * ctmScale();
104
109
  for (const seg of currentPath) {
105
110
  classifyAndAdd(seg, effWidth, horizontals, verticals, fromFill);
@@ -227,9 +232,29 @@ function extractLines(fnArray, argsArray) {
227
232
  case OPS2.endPath:
228
233
  flushPath(false);
229
234
  break;
235
+ case OPS2.clip:
236
+ case OPS2.eoClip:
237
+ pendingClip = true;
238
+ break;
230
239
  }
231
240
  }
232
- return { horizontals, verticals };
241
+ return { horizontals, verticals, clipRects };
242
+ }
243
+ var CLIP_MIN_W = 4;
244
+ var CLIP_MIN_H = 2;
245
+ function captureClipRect(path, out) {
246
+ if (path.length < 3 || path.length > 5) return;
247
+ let x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity;
248
+ for (const s of path) {
249
+ const dx = Math.abs(s.x2 - s.x1), dy = Math.abs(s.y2 - s.y1);
250
+ if (dx > ORIENTATION_TOL && dy > ORIENTATION_TOL) return;
251
+ x1 = Math.min(x1, s.x1, s.x2);
252
+ x2 = Math.max(x2, s.x1, s.x2);
253
+ y1 = Math.min(y1, s.y1, s.y2);
254
+ y2 = Math.max(y2, s.y1, s.y2);
255
+ }
256
+ if (x2 - x1 < CLIP_MIN_W || y2 - y1 < CLIP_MIN_H) return;
257
+ out.push({ x1, y1, x2, y2 });
233
258
  }
234
259
  function classifyAndAdd(seg, lineWidth, horizontals, verticals, fromFill = false) {
235
260
  const dx = Math.abs(seg.x2 - seg.x1);
@@ -442,9 +467,9 @@ function mergeParallelLines(lines, dir) {
442
467
  const prevEnd = dir === "h" ? prev.x2 : prev.y2;
443
468
  const currStart = dir === "h" ? curr.x1 : curr.y1;
444
469
  const currEnd = dir === "h" ? curr.x2 : curr.y2;
445
- const overlap = Math.min(prevEnd, currEnd) - Math.max(prevStart, currStart);
470
+ const overlap2 = Math.min(prevEnd, currEnd) - Math.max(prevStart, currStart);
446
471
  const minLen = Math.min(prevEnd - prevStart, currEnd - currStart);
447
- if (overlap > minLen * 0.3) {
472
+ if (overlap2 > minLen * 0.3) {
448
473
  if (dir === "h") {
449
474
  prev.x1 = Math.min(prev.x1, curr.x1);
450
475
  prev.x2 = Math.max(prev.x2, curr.x2);
@@ -933,6 +958,164 @@ function linesIntersect(a, b) {
933
958
  return v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol && h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol;
934
959
  }
935
960
 
961
+ // src/pdf/clip-cells.ts
962
+ var CLIP_EDGE_TOL = 1.5;
963
+ var CLIP_MIN_GROUP = 2;
964
+ var CLIP_MAX_PAGE_FRAC = 0.75;
965
+ var CLIP_MIN_W2 = 4;
966
+ var CLIP_MIN_H2 = 2;
967
+ var STROKE_NEAR = 2;
968
+ var STROKE_COVER = 0.5;
969
+ var CLIP_MIN_INVISIBLE = 0;
970
+ var overlap = (a1, a2, b1, b2) => Math.min(a2, b2) - Math.max(a1, b1);
971
+ function adjacent(a, b) {
972
+ if ((Math.abs(a.x2 - b.x1) <= CLIP_EDGE_TOL || Math.abs(b.x2 - a.x1) <= CLIP_EDGE_TOL) && overlap(a.y1, a.y2, b.y1, b.y2) > CLIP_EDGE_TOL) return true;
973
+ if ((Math.abs(a.y2 - b.y1) <= CLIP_EDGE_TOL || Math.abs(b.y2 - a.y1) <= CLIP_EDGE_TOL) && overlap(a.x1, a.x2, b.x1, b.x2) > CLIP_EDGE_TOL) return true;
974
+ return false;
975
+ }
976
+ function contains(a, b) {
977
+ return b.x1 >= a.x1 - CLIP_EDGE_TOL && b.x2 <= a.x2 + CLIP_EDGE_TOL && b.y1 >= a.y1 - CLIP_EDGE_TOL && b.y2 <= a.y2 + CLIP_EDGE_TOL && (b.x2 - b.x1 < a.x2 - a.x1 - CLIP_EDGE_TOL || b.y2 - b.y1 < a.y2 - a.y1 - CLIP_EDGE_TOL);
978
+ }
979
+ function clusterCoords(values) {
980
+ const sorted = [...values].sort((a, b) => a - b);
981
+ const out = [];
982
+ let run = [];
983
+ for (const v of sorted) {
984
+ if (run.length > 0 && v - run[run.length - 1] > CLIP_EDGE_TOL) {
985
+ out.push(run.reduce((s, x) => s + x, 0) / run.length);
986
+ run = [];
987
+ }
988
+ run.push(v);
989
+ }
990
+ if (run.length > 0) out.push(run.reduce((s, x) => s + x, 0) / run.length);
991
+ return out;
992
+ }
993
+ var nearestIndex = (coords, v) => {
994
+ let best = 0;
995
+ for (let i = 1; i < coords.length; i++) if (Math.abs(coords[i] - v) < Math.abs(coords[best] - v)) best = i;
996
+ return best;
997
+ };
998
+ function edgeStroked(lines, dir, pos, a1, a2) {
999
+ const len = a2 - a1;
1000
+ if (len <= 0) return true;
1001
+ for (const l of lines) {
1002
+ if (dir === "h") {
1003
+ if (Math.abs(l.y1 - pos) <= STROKE_NEAR && overlap(l.x1, l.x2, a1, a2) >= len * STROKE_COVER) return true;
1004
+ } else if (Math.abs(l.x1 - pos) <= STROKE_NEAR && overlap(l.y1, l.y2, a1, a2) >= len * STROKE_COVER) {
1005
+ return true;
1006
+ }
1007
+ }
1008
+ return false;
1009
+ }
1010
+ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight) {
1011
+ const pageArea = pageWidth * pageHeight;
1012
+ const cells = rects.filter((r) => r.x2 - r.x1 >= CLIP_MIN_W2 && r.y2 - r.y1 >= CLIP_MIN_H2 && (pageArea <= 0 || (r.x2 - r.x1) * (r.y2 - r.y1) < pageArea * CLIP_MAX_PAGE_FRAC));
1013
+ if (cells.length < CLIP_MIN_GROUP) return { grids: [], containers: [] };
1014
+ const parent = new Array(cells.length).fill(-1);
1015
+ for (let i = 0; i < cells.length; i++) {
1016
+ for (let j = 0; j < cells.length; j++) {
1017
+ if (i === j || !contains(cells[j], cells[i])) continue;
1018
+ const cur = parent[i];
1019
+ if (cur < 0 || contains(cells[cur], cells[j])) parent[i] = j;
1020
+ }
1021
+ }
1022
+ const isContainer = new Array(cells.length).fill(false);
1023
+ for (const p of parent) if (p >= 0) isContainer[p] = true;
1024
+ const root = cells.map((_, i) => i);
1025
+ const find = (i) => {
1026
+ while (root[i] !== i) {
1027
+ root[i] = root[root[i]];
1028
+ i = root[i];
1029
+ }
1030
+ return i;
1031
+ };
1032
+ for (let i = 0; i < cells.length; i++) {
1033
+ for (let j = i + 1; j < cells.length; j++) {
1034
+ if (parent[i] !== parent[j]) continue;
1035
+ if (adjacent(cells[i], cells[j])) {
1036
+ const ra = find(i), rb = find(j);
1037
+ if (ra !== rb) root[ra] = rb;
1038
+ }
1039
+ }
1040
+ }
1041
+ const groups = /* @__PURE__ */ new Map();
1042
+ for (let i = 0; i < cells.length; i++) {
1043
+ const r = find(i);
1044
+ const g = groups.get(r);
1045
+ if (g) g.push(i);
1046
+ else groups.set(r, [i]);
1047
+ }
1048
+ const containers = [];
1049
+ for (let i = 0; i < cells.length; i++) {
1050
+ if (!isContainer[i]) continue;
1051
+ const r = cells[i];
1052
+ if (!containers.some((c) => Math.abs(c.x1 - r.x1) <= CLIP_EDGE_TOL && Math.abs(c.x2 - r.x2) <= CLIP_EDGE_TOL && Math.abs(c.y1 - r.y1) <= CLIP_EDGE_TOL && Math.abs(c.y2 - r.y2) <= CLIP_EDGE_TOL)) containers.push(r);
1053
+ }
1054
+ const grids = [];
1055
+ for (const idxs of groups.values()) {
1056
+ if (idxs.length < CLIP_MIN_GROUP) continue;
1057
+ const members = idxs.map((i) => cells[i]);
1058
+ let edges = 0, invisible = 0;
1059
+ for (const r of members) {
1060
+ edges += 4;
1061
+ if (!edgeStroked(strokedH, "h", r.y1, r.x1, r.x2)) invisible++;
1062
+ if (!edgeStroked(strokedH, "h", r.y2, r.x1, r.x2)) invisible++;
1063
+ if (!edgeStroked(strokedV, "v", r.x1, r.y1, r.y2)) invisible++;
1064
+ if (!edgeStroked(strokedV, "v", r.x2, r.y1, r.y2)) invisible++;
1065
+ }
1066
+ if (invisible / edges < CLIP_MIN_INVISIBLE) continue;
1067
+ const colXs = clusterCoords(members.flatMap((r) => [r.x1, r.x2]));
1068
+ const rowYs = clusterCoords(members.flatMap((r) => [r.y1, r.y2])).reverse();
1069
+ const numRows = rowYs.length - 1, numCols = colXs.length - 1;
1070
+ if (numRows < 1 || numCols < 1) continue;
1071
+ const occupied = Array.from({ length: numRows }, () => new Array(numCols).fill(false));
1072
+ const out = [];
1073
+ for (const r of members) {
1074
+ const c0 = nearestIndex(colXs, r.x1), c1 = nearestIndex(colXs, r.x2);
1075
+ const r0 = nearestIndex(rowYs, r.y2), r1 = nearestIndex(rowYs, r.y1);
1076
+ if (c1 <= c0 || r1 <= r0) continue;
1077
+ let clash = false;
1078
+ for (let rr = r0; rr < r1 && !clash; rr++) for (let cc = c0; cc < c1; cc++) if (occupied[rr][cc]) {
1079
+ clash = true;
1080
+ break;
1081
+ }
1082
+ if (clash) continue;
1083
+ for (let rr = r0; rr < r1; rr++) for (let cc = c0; cc < c1; cc++) occupied[rr][cc] = true;
1084
+ out.push({ row: r0, col: c0, rowSpan: r1 - r0, colSpan: c1 - c0, bbox: { x1: r.x1, y1: r.y1, x2: r.x2, y2: r.y2 } });
1085
+ }
1086
+ for (let rr = 0; rr < numRows; rr++) {
1087
+ for (let cc = 0; cc < numCols; cc++) {
1088
+ if (occupied[rr][cc]) continue;
1089
+ out.push({ row: rr, col: cc, rowSpan: 1, colSpan: 1, bbox: { x1: colXs[cc], y1: rowYs[rr + 1], x2: colXs[cc + 1], y2: rowYs[rr] } });
1090
+ }
1091
+ }
1092
+ grids.push({
1093
+ rowYs,
1094
+ colXs,
1095
+ bbox: { x1: colXs[0], y1: rowYs[numRows], x2: colXs[numCols], y2: rowYs[0] },
1096
+ vertexRadius: 1,
1097
+ cells: out
1098
+ });
1099
+ }
1100
+ return { grids, containers };
1101
+ }
1102
+ function dropGridsInside(lineGrids, clipGrids, containers = []) {
1103
+ if (clipGrids.length === 0 && containers.length === 0) return lineGrids;
1104
+ const area = (b) => Math.max(0, b.x2 - b.x1) * Math.max(0, b.y2 - b.y1);
1105
+ const overlapsHalf = (g, b) => {
1106
+ const ix = Math.min(g.x2, b.x2) - Math.max(g.x1, b.x1);
1107
+ const iy = Math.min(g.y2, b.y2) - Math.max(g.y1, b.y1);
1108
+ if (ix <= 0 || iy <= 0) return false;
1109
+ const ga = area(g);
1110
+ return ga > 0 && ix * iy / ga >= 0.5;
1111
+ };
1112
+ return lineGrids.filter((g) => {
1113
+ if (clipGrids.some((c) => overlapsHalf(g.bbox, c.bbox))) return false;
1114
+ if (containers.some((c) => overlapsHalf(g.bbox, c))) return false;
1115
+ return true;
1116
+ });
1117
+ }
1118
+
936
1119
  // src/pdf/cell-extract.ts
937
1120
  function extractCells(grid, horizontals, verticals) {
938
1121
  const { rowYs, colXs } = grid;
@@ -1011,8 +1194,8 @@ function hasVerticalLine(verticals, x, topY, botY, vertexRadius) {
1011
1194
  if (cellH < 0.1) continue;
1012
1195
  const overlapTop = Math.min(v.y2, topY);
1013
1196
  const overlapBot = Math.max(v.y1, botY);
1014
- const overlap = overlapTop - overlapBot;
1015
- if (overlap >= cellH * 0.75) return true;
1197
+ const overlap2 = overlapTop - overlapBot;
1198
+ if (overlap2 >= cellH * 0.75) return true;
1016
1199
  }
1017
1200
  }
1018
1201
  return false;
@@ -1025,8 +1208,8 @@ function hasHorizontalLine(horizontals, y, leftX, rightX, vertexRadius) {
1025
1208
  if (cellW < 0.1) continue;
1026
1209
  const overlapLeft = Math.max(h.x1, leftX);
1027
1210
  const overlapRight = Math.min(h.x2, rightX);
1028
- const overlap = overlapRight - overlapLeft;
1029
- if (overlap >= cellW * 0.75) return true;
1211
+ const overlap2 = overlapRight - overlapLeft;
1212
+ if (overlap2 >= cellW * 0.75) return true;
1030
1213
  }
1031
1214
  }
1032
1215
  return false;
@@ -1133,9 +1316,9 @@ function mergeSuperscriptRows(lines) {
1133
1316
  const curr = lines[i];
1134
1317
  const a = band(prev);
1135
1318
  const b = band(curr);
1136
- const overlap = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom);
1137
- const prevIsFrag = isFrag(prev) && a.height <= b.height * 0.8 && overlap >= a.height * 0.5;
1138
- const currIsFrag = isFrag(curr) && b.height <= a.height * 0.8 && overlap >= b.height * 0.5;
1319
+ const overlap2 = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom);
1320
+ const prevIsFrag = isFrag(prev) && a.height <= b.height * 0.8 && overlap2 >= a.height * 0.5;
1321
+ const currIsFrag = isFrag(curr) && b.height <= a.height * 0.8 && overlap2 >= b.height * 0.5;
1139
1322
  if (prevIsFrag || currIsFrag) {
1140
1323
  result[result.length - 1] = [...prev, ...curr];
1141
1324
  } else {
@@ -1354,14 +1537,17 @@ function filterHiddenText(items, pageWidth, pageHeight) {
1354
1537
  function collapseEvenSpacing(text) {
1355
1538
  const tokens = text.split(" ");
1356
1539
  const singleCharCount = tokens.filter((t) => t.length === 1).length;
1357
- if (tokens.length >= 3 && singleCharCount / tokens.length >= 0.7) {
1540
+ if (tokens.length >= 3 && singleCharCount / tokens.length >= 0.7 && !isDateUnitBlank(tokens)) {
1358
1541
  return tokens.join("");
1359
1542
  }
1360
1543
  return text.replace(
1361
1544
  /(?<![가-힣])[가-힣](?: [가-힣\d]){2,}(?![가-힣])/g,
1362
- (match) => match.replace(/ /g, "")
1545
+ (match) => isDateUnitBlank(match.split(" ")) ? match : match.replace(/ /g, "")
1363
1546
  );
1364
1547
  }
1548
+ function isDateUnitBlank(tokens) {
1549
+ return tokens.every((t) => t.length !== 1 || !/[가-힣]/.test(t) || /[년월일시분초]/.test(t));
1550
+ }
1365
1551
  function computeBBox(items, pageNum) {
1366
1552
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1367
1553
  for (const i of items) {
@@ -1502,9 +1688,9 @@ function mergeSuperscriptLines(lines) {
1502
1688
  const curr = lines[i];
1503
1689
  const a = band(prev);
1504
1690
  const b = band(curr);
1505
- const overlap = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom);
1506
- const prevIsFrag = isFrag(prev) && a.height <= b.height * 0.8 && overlap >= a.height * 0.5;
1507
- const currIsFrag = isFrag(curr) && b.height <= a.height * 0.8 && overlap >= b.height * 0.5;
1691
+ const overlap2 = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom);
1692
+ const prevIsFrag = isFrag(prev) && a.height <= b.height * 0.8 && overlap2 >= a.height * 0.5;
1693
+ const currIsFrag = isFrag(curr) && b.height <= a.height * 0.8 && overlap2 >= b.height * 0.5;
1508
1694
  if (prevIsFrag || currIsFrag) {
1509
1695
  result[result.length - 1] = [...prev, ...curr];
1510
1696
  } else {
@@ -1653,8 +1839,8 @@ function detectTableCaptions(blocks) {
1653
1839
  const tblBottom = table.bbox.y;
1654
1840
  const gap = capBottom >= tblTop ? capBottom - tblTop : tblBottom - capTop;
1655
1841
  if (gap > CAPTION_MAX_GAP) return false;
1656
- const overlap = Math.min(b.bbox.x + b.bbox.width, table.bbox.x + table.bbox.width) - Math.max(b.bbox.x, table.bbox.x);
1657
- if (overlap < Math.min(b.bbox.width, table.bbox.width) * 0.3) return false;
1842
+ const overlap2 = Math.min(b.bbox.x + b.bbox.width, table.bbox.x + table.bbox.width) - Math.max(b.bbox.x, table.bbox.x);
1843
+ if (overlap2 < Math.min(b.bbox.width, table.bbox.width) * 0.3) return false;
1658
1844
  }
1659
1845
  return true;
1660
1846
  };
@@ -1918,8 +2104,8 @@ function removeHeaderFooterBlocks(blocks, pageHeights, warnings) {
1918
2104
  if (!o.bbox || o.bbox.page !== cb.page) continue;
1919
2105
  const oNorm = o.text?.trim().replace(/\d+/g, "#");
1920
2106
  if (oNorm && repeatedPatterns.has(oNorm)) continue;
1921
- const overlap = Math.min(cb.y + cb.height, o.bbox.y + o.bbox.height) - Math.max(cb.y, o.bbox.y);
1922
- if (overlap >= cb.height * 0.5) {
2107
+ const overlap2 = Math.min(cb.y + cb.height, o.bbox.y + o.bbox.height) - Math.max(cb.y, o.bbox.y);
2108
+ if (overlap2 >= cb.height * 0.5) {
1923
2109
  sharesLine = true;
1924
2110
  break;
1925
2111
  }
@@ -2191,9 +2377,9 @@ function mergeOverlappingRows(rows) {
2191
2377
  const curr = rows[i];
2192
2378
  const a = rowBand(prev);
2193
2379
  const b = rowBand(curr);
2194
- const overlap = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom);
2195
- const prevIsFrag = isFragmentRow(prev) && a.height <= b.height * 0.8 && overlap >= a.height * 0.5;
2196
- const currIsFrag = isFragmentRow(curr) && b.height <= a.height * 0.8 && overlap >= b.height * 0.5;
2380
+ const overlap2 = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom);
2381
+ const prevIsFrag = isFragmentRow(prev) && a.height <= b.height * 0.8 && overlap2 >= a.height * 0.5;
2382
+ const currIsFrag = isFragmentRow(curr) && b.height <= a.height * 0.8 && overlap2 >= b.height * 0.5;
2197
2383
  if (prevIsFrag || currIsFrag) {
2198
2384
  const baseY = prevIsFrag ? curr.y : prev.y;
2199
2385
  result[result.length - 1] = { y: baseY, items: [...prev.items, ...curr.items] };
@@ -3070,8 +3256,8 @@ function markUnderlineItems(items, horizontals, verticals) {
3070
3256
  if (h <= 0 || item.w <= 0 || !item.text.trim()) continue;
3071
3257
  const below = Math.max(h * UNDER_BELOW_EM, UNDER_BELOW_MIN_PT);
3072
3258
  if (line.y1 < item.y - below || line.y1 > item.y + UNDER_ABOVE_PT) continue;
3073
- const overlap = Math.min(line.x2, item.x + item.w) - Math.max(line.x1, item.x);
3074
- if (overlap / item.w < UNDER_MIN_OVERLAP_RATIO) continue;
3259
+ const overlap2 = Math.min(line.x2, item.x + item.w) - Math.max(line.x1, item.x);
3260
+ if (overlap2 / item.w < UNDER_MIN_OVERLAP_RATIO) continue;
3075
3261
  matches.push(item);
3076
3262
  }
3077
3263
  if (matches.length === 0) continue;
@@ -3115,8 +3301,8 @@ function hasBoxTopPair(line, maxH, horizontals) {
3115
3301
  if (dy < maxH * UNDER_BOX_PAIR_MIN_EM || dy > maxH * UNDER_BOX_PAIR_MAX_EM) continue;
3116
3302
  const ow = o.x2 - o.x1;
3117
3303
  if (ow <= 0) continue;
3118
- const overlap = Math.min(line.x2, o.x2) - Math.max(line.x1, o.x1);
3119
- if (overlap / Math.max(w, ow) >= UNDER_BOX_PAIR_OVERLAP) return true;
3304
+ const overlap2 = Math.min(line.x2, o.x2) - Math.max(line.x1, o.x1);
3305
+ if (overlap2 / Math.max(w, ow) >= UNDER_BOX_PAIR_OVERLAP) return true;
3120
3306
  }
3121
3307
  return false;
3122
3308
  }
@@ -3127,8 +3313,8 @@ function isRepeatedSpanRule(line, horizontals) {
3127
3313
  for (const o of horizontals) {
3128
3314
  const ow = o.x2 - o.x1;
3129
3315
  if (ow <= 0) continue;
3130
- const overlap = Math.min(line.x2, o.x2) - Math.max(line.x1, o.x1);
3131
- if (overlap / Math.max(w, ow) < UNDER_SPAN_OVERLAP_RATIO) continue;
3316
+ const overlap2 = Math.min(line.x2, o.x2) - Math.max(line.x1, o.x1);
3317
+ if (overlap2 / Math.max(w, ow) < UNDER_SPAN_OVERLAP_RATIO) continue;
3132
3318
  if (!ys.some((y) => Math.abs(y - o.y1) < 2)) ys.push(o.y1);
3133
3319
  }
3134
3320
  return ys.length >= UNDER_REPEATED_SPAN_LEVELS;
@@ -3162,7 +3348,10 @@ function wrapUnderlineRuns(items) {
3162
3348
  // src/pdf/page-blocks.ts
3163
3349
  function extractPageBlocksWithLines(items, pageNum, opList, pageWidth, pageHeight, extraLines, detectTables = true) {
3164
3350
  if (items.length === 0) return [];
3165
- let { horizontals, verticals } = extractLines(opList.fnArray, opList.argsArray);
3351
+ const extracted = extractLines(opList.fnArray, opList.argsArray);
3352
+ let { horizontals, verticals } = extracted;
3353
+ const clipResult = detectTables ? buildClipCellGrids(extracted.clipRects, horizontals, verticals, pageWidth, pageHeight) : { grids: [], containers: [] };
3354
+ const clipGrids = clipResult.grids;
3166
3355
  if (extraLines) {
3167
3356
  horizontals = horizontals.concat(extraLines.horizontals);
3168
3357
  verticals = verticals.concat(extraLines.verticals);
@@ -3176,7 +3365,8 @@ function extractPageBlocksWithLines(items, pageNum, opList, pageWidth, pageHeigh
3176
3365
  wrapStrikethroughRuns(items);
3177
3366
  markUnderlineItems(items, horizontals, verticals);
3178
3367
  wrapUnderlineRuns(items);
3179
- const grids = detectTables ? buildTableGrids(horizontals, verticals) : [];
3368
+ const lineGrids = detectTables ? buildTableGrids(horizontals, verticals) : [];
3369
+ const grids = [...clipGrids, ...dropGridsInside(lineGrids, clipGrids, clipResult.containers)];
3180
3370
  if (grids.length > 0) {
3181
3371
  return extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals);
3182
3372
  }
@@ -3198,8 +3388,8 @@ function markStrikethroughItems(items, horizontals) {
3198
3388
  if (line.lineWidth > h * STRIKE_MAX_THICKNESS_RATIO) continue;
3199
3389
  const centerY = item.y + h * 0.4;
3200
3390
  if (Math.abs(line.y1 - centerY) > h * STRIKE_CENTER_TOLERANCE) continue;
3201
- const overlap = Math.min(line.x2, item.x + item.w) - Math.max(line.x1, item.x);
3202
- if (overlap / item.w < STRIKE_MIN_OVERLAP_RATIO) continue;
3391
+ const overlap2 = Math.min(line.x2, item.x + item.w) - Math.max(line.x1, item.x);
3392
+ if (overlap2 / item.w < STRIKE_MIN_OVERLAP_RATIO) continue;
3203
3393
  matches.push(item);
3204
3394
  }
3205
3395
  if (matches.length === 0) continue;
@@ -3276,12 +3466,13 @@ function isProseBoxGrid(grid, verticals, table) {
3276
3466
  function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
3277
3467
  const blocks = [];
3278
3468
  const usedItems = /* @__PURE__ */ new Set();
3279
- const sortedGrids = [...grids].sort((a, b) => b.bbox.y2 - a.bbox.y2);
3469
+ const gridArea = (g) => (g.bbox.x2 - g.bbox.x1) * (g.bbox.y2 - g.bbox.y1);
3470
+ const sortedGrids = [...grids].sort((a, b) => (b.cells ? 1 : 0) - (a.cells ? 1 : 0) || (a.cells && b.cells ? gridArea(a) - gridArea(b) : 0) || b.bbox.y2 - a.bbox.y2);
3280
3471
  for (const grid of sortedGrids) {
3281
3472
  const numGridRows = grid.rowYs.length - 1;
3282
3473
  const numGridCols = grid.colXs.length - 1;
3283
- if (numGridRows === 1 && numGridCols >= 2) continue;
3284
- if (numGridCols === 1 && numGridRows >= 2) continue;
3474
+ if (!grid.cells && numGridRows === 1 && numGridCols >= 2) continue;
3475
+ if (!grid.cells && numGridCols === 1 && numGridRows >= 2) continue;
3285
3476
  const tableItems = [];
3286
3477
  const pad = 3;
3287
3478
  const gridW = grid.bbox.x2 - grid.bbox.x1;
@@ -3293,7 +3484,7 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
3293
3484
  tableItems.push(item);
3294
3485
  usedItems.add(item);
3295
3486
  }
3296
- const cells = extractCells(grid, horizontals, verticals);
3487
+ const cells = grid.cells ?? extractCells(grid, horizontals, verticals);
3297
3488
  if (cells.length === 0) continue;
3298
3489
  const textItems = tableItems.map((i) => ({
3299
3490
  text: i.text,
@@ -3356,7 +3547,7 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
3356
3547
  };
3357
3548
  const hasContent = finalGrid.some((row) => row.some((cell) => cell.text.trim() !== ""));
3358
3549
  if (!hasContent) continue;
3359
- if (isProseBoxGrid(grid, verticals, irTable)) {
3550
+ if (!grid.cells && isProseBoxGrid(grid, verticals, irTable)) {
3360
3551
  for (const it of tableItems) usedItems.delete(it);
3361
3552
  continue;
3362
3553
  }
@@ -3367,7 +3558,7 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
3367
3558
  width: grid.bbox.x2 - grid.bbox.x1,
3368
3559
  height: grid.bbox.y2 - grid.bbox.y1
3369
3560
  };
3370
- if (shouldDemoteTable(irTable)) {
3561
+ if (!grid.cells && shouldDemoteTable(irTable)) {
3371
3562
  const demoted = demoteTableToText(irTable);
3372
3563
  if (demoted) {
3373
3564
  const text = numGridRows === 1 ? "\n" + demoted + "\n" : demoted;
@@ -3686,4 +3877,4 @@ export {
3686
3877
  extractPageBlocksWithLines,
3687
3878
  mergeCrossPageTables
3688
3879
  };
3689
- //# sourceMappingURL=chunk-SMGSCP57.js.map
3880
+ //# sourceMappingURL=chunk-BPKJTQAW.js.map