kordoc 4.12.1 → 4.12.3

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 (121) hide show
  1. package/README.md +22 -0
  2. package/dist/{-BUYIUNW3.js → -FWGDIOV4.js} +8 -8
  3. package/dist/{chunk-OS2IOI3W.js → chunk-2DAFWDV2.js} +95 -27
  4. package/dist/chunk-2DAFWDV2.js.map +1 -0
  5. package/dist/{chunk-5RPYBC2Q.js → chunk-3KQOI6R6.js} +27 -2
  6. package/dist/chunk-3KQOI6R6.js.map +1 -0
  7. package/dist/{chunk-ST3JBCIL.js → chunk-6L3B44DH.js} +2 -2
  8. package/dist/{chunk-MXZXZTKW.cjs → chunk-7BIXTQZ3.cjs} +110 -42
  9. package/dist/chunk-7BIXTQZ3.cjs.map +1 -0
  10. package/dist/{chunk-F2KL5Z5T.js → chunk-B63QEHYL.js} +3 -3
  11. package/dist/{chunk-L5DNHSX4.js → chunk-BAZMKQZW.js} +2 -2
  12. package/dist/{chunk-CBPO5IZZ.cjs → chunk-BOATAEXU.cjs} +23 -17
  13. package/dist/chunk-BOATAEXU.cjs.map +1 -0
  14. package/dist/{chunk-JD4H6Y33.js → chunk-CKYDP34A.js} +3 -3
  15. package/dist/{chunk-BPKJTQAW.js → chunk-DUGUCM7K.js} +95 -27
  16. package/dist/chunk-DUGUCM7K.js.map +1 -0
  17. package/dist/{chunk-EOGPRO3U.cjs → chunk-FFITS3NY.cjs} +6 -6
  18. package/dist/{chunk-EOGPRO3U.cjs.map → chunk-FFITS3NY.cjs.map} +1 -1
  19. package/dist/{chunk-WGUVKNBX.js → chunk-HB56YANA.js} +9 -3
  20. package/dist/chunk-HB56YANA.js.map +1 -0
  21. package/dist/{chunk-CJPVVR2Y.js → chunk-IC6HHXFL.js} +2 -2
  22. package/dist/{chunk-VO37BVAH.js → chunk-INFYDSZL.js} +143 -33
  23. package/dist/chunk-INFYDSZL.js.map +1 -0
  24. package/dist/{chunk-6MF7JRG3.cjs → chunk-ISCI5LKN.cjs} +2 -2
  25. package/dist/{chunk-6MF7JRG3.cjs.map → chunk-ISCI5LKN.cjs.map} +1 -1
  26. package/dist/{chunk-7U34YNFR.js → chunk-KLO6RHX2.js} +2 -2
  27. package/dist/{chunk-D2XNLIFU.js → chunk-KRE2SREV.js} +2 -2
  28. package/dist/{chunk-ELCPCTJ6.js → chunk-OJDMMZ5Z.js} +2 -2
  29. package/dist/{chunk-L6FIUSD2.js → chunk-PL4V523B.js} +3 -3
  30. package/dist/{chunk-E37VAV46.js → chunk-PUZZFYLV.js} +2 -2
  31. package/dist/{chunk-FNCBOTM5.js → chunk-SJEPQOVW.js} +2 -2
  32. package/dist/{chunk-DXKNPBXX.js → chunk-TWKXX343.js} +3 -3
  33. package/dist/{chunk-WJOWM27X.cjs → chunk-W3H2H55O.cjs} +3 -3
  34. package/dist/{chunk-WJOWM27X.cjs.map → chunk-W3H2H55O.cjs.map} +1 -1
  35. package/dist/{chunk-EWSH6O6S.js → chunk-YBZJMAE2.js} +2 -2
  36. package/dist/{chunk-JQBF4PSF.js → chunk-ZASGH5EN.js} +9 -3
  37. package/dist/chunk-ZASGH5EN.js.map +1 -0
  38. package/dist/chunks-VICNQMNE.js +10 -0
  39. package/dist/cli.js +30 -23
  40. package/dist/cli.js.map +1 -1
  41. package/dist/{formula-RZNSECGO.js → formula-R7IWAAYR.js} +3 -3
  42. package/dist/{formula-7TN26AKF.js → formula-YJWKEPUK.js} +4 -4
  43. package/dist/{formula-XKRMKGAW.cjs → formula-ZYHUXTK5.cjs} +9 -9
  44. package/dist/{formula-XKRMKGAW.cjs.map → formula-ZYHUXTK5.cjs.map} +1 -1
  45. package/dist/{image-ocr-XDZONLD6.cjs → image-ocr-6O6ZXKUD.cjs} +11 -11
  46. package/dist/{image-ocr-XDZONLD6.cjs.map → image-ocr-6O6ZXKUD.cjs.map} +1 -1
  47. package/dist/{image-ocr-TNU4ILLU.js → image-ocr-G4JVF2UH.js} +7 -7
  48. package/dist/{image-ocr-3O34O7XS.js → image-ocr-OLTSKQWD.js} +5 -5
  49. package/dist/index.cjs +520 -410
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +17 -0
  52. package/dist/index.d.ts +17 -0
  53. package/dist/index.js +139 -29
  54. package/dist/index.js.map +1 -1
  55. package/dist/mcp.js +20 -17
  56. package/dist/mcp.js.map +1 -1
  57. package/dist/{model-bundle-NCYN375I.js → model-bundle-IFD2D5CW.js} +5 -5
  58. package/dist/{models-J4G4VZMU.js → models-FJ3IUNTB.js} +5 -5
  59. package/dist/{parser-5WZBCP7H.js → parser-I7TC35V6.js} +31 -10
  60. package/dist/parser-I7TC35V6.js.map +1 -0
  61. package/dist/{parser-6IH62P6U.js → parser-JRT3B6OK.js} +31 -10
  62. package/dist/parser-JRT3B6OK.js.map +1 -0
  63. package/dist/{parser-LVMTVO2N.cjs → parser-YLLIYOIZ.cjs} +59 -38
  64. package/dist/parser-YLLIYOIZ.cjs.map +1 -0
  65. package/dist/pdf-ocr-ADUP2LPU.cjs +14 -0
  66. package/dist/{pdf-ocr-IAO273AQ.cjs.map → pdf-ocr-ADUP2LPU.cjs.map} +1 -1
  67. package/dist/pdf-ocr-ATJTB2B3.js +13 -0
  68. package/dist/pdf-ocr-P75KSZFV.js +16 -0
  69. package/dist/{profile-io-ANPS7DTT.js → profile-io-BPJQQZLQ.js} +3 -3
  70. package/dist/{rasterize-FKXCEFHV.js → rasterize-ALCNU53T.js} +2 -2
  71. package/dist/render-MDLTQUUV.js +10 -0
  72. package/dist/seal-K4K3KPE3.js +10 -0
  73. package/dist/{setup-DEAFODGI.js → setup-O2DALZKF.js} +3 -3
  74. package/dist/{watch-B5X7IAMK.js → watch-GPWYJAS2.js} +9 -9
  75. package/package.json +1 -1
  76. package/dist/chunk-5RPYBC2Q.js.map +0 -1
  77. package/dist/chunk-BPKJTQAW.js.map +0 -1
  78. package/dist/chunk-CBPO5IZZ.cjs.map +0 -1
  79. package/dist/chunk-JQBF4PSF.js.map +0 -1
  80. package/dist/chunk-MXZXZTKW.cjs.map +0 -1
  81. package/dist/chunk-OS2IOI3W.js.map +0 -1
  82. package/dist/chunk-VO37BVAH.js.map +0 -1
  83. package/dist/chunk-WGUVKNBX.js.map +0 -1
  84. package/dist/chunks-G2U2XUZU.js +0 -10
  85. package/dist/parser-5WZBCP7H.js.map +0 -1
  86. package/dist/parser-6IH62P6U.js.map +0 -1
  87. package/dist/parser-LVMTVO2N.cjs.map +0 -1
  88. package/dist/pdf-ocr-5PLJOKGI.js +0 -13
  89. package/dist/pdf-ocr-7FPCZCV7.js +0 -16
  90. package/dist/pdf-ocr-IAO273AQ.cjs +0 -14
  91. package/dist/render-NWQI3AXU.js +0 -10
  92. package/dist/seal-N3GCBTVA.js +0 -10
  93. /package/dist/{-BUYIUNW3.js.map → -FWGDIOV4.js.map} +0 -0
  94. /package/dist/{chunk-ST3JBCIL.js.map → chunk-6L3B44DH.js.map} +0 -0
  95. /package/dist/{chunk-F2KL5Z5T.js.map → chunk-B63QEHYL.js.map} +0 -0
  96. /package/dist/{chunk-L5DNHSX4.js.map → chunk-BAZMKQZW.js.map} +0 -0
  97. /package/dist/{chunk-JD4H6Y33.js.map → chunk-CKYDP34A.js.map} +0 -0
  98. /package/dist/{chunk-CJPVVR2Y.js.map → chunk-IC6HHXFL.js.map} +0 -0
  99. /package/dist/{chunk-7U34YNFR.js.map → chunk-KLO6RHX2.js.map} +0 -0
  100. /package/dist/{chunk-D2XNLIFU.js.map → chunk-KRE2SREV.js.map} +0 -0
  101. /package/dist/{chunk-ELCPCTJ6.js.map → chunk-OJDMMZ5Z.js.map} +0 -0
  102. /package/dist/{chunk-L6FIUSD2.js.map → chunk-PL4V523B.js.map} +0 -0
  103. /package/dist/{chunk-E37VAV46.js.map → chunk-PUZZFYLV.js.map} +0 -0
  104. /package/dist/{chunk-FNCBOTM5.js.map → chunk-SJEPQOVW.js.map} +0 -0
  105. /package/dist/{chunk-DXKNPBXX.js.map → chunk-TWKXX343.js.map} +0 -0
  106. /package/dist/{chunk-EWSH6O6S.js.map → chunk-YBZJMAE2.js.map} +0 -0
  107. /package/dist/{chunks-G2U2XUZU.js.map → chunks-VICNQMNE.js.map} +0 -0
  108. /package/dist/{formula-RZNSECGO.js.map → formula-R7IWAAYR.js.map} +0 -0
  109. /package/dist/{formula-7TN26AKF.js.map → formula-YJWKEPUK.js.map} +0 -0
  110. /package/dist/{image-ocr-TNU4ILLU.js.map → image-ocr-G4JVF2UH.js.map} +0 -0
  111. /package/dist/{image-ocr-3O34O7XS.js.map → image-ocr-OLTSKQWD.js.map} +0 -0
  112. /package/dist/{model-bundle-NCYN375I.js.map → model-bundle-IFD2D5CW.js.map} +0 -0
  113. /package/dist/{models-J4G4VZMU.js.map → models-FJ3IUNTB.js.map} +0 -0
  114. /package/dist/{pdf-ocr-5PLJOKGI.js.map → pdf-ocr-ATJTB2B3.js.map} +0 -0
  115. /package/dist/{pdf-ocr-7FPCZCV7.js.map → pdf-ocr-P75KSZFV.js.map} +0 -0
  116. /package/dist/{profile-io-ANPS7DTT.js.map → profile-io-BPJQQZLQ.js.map} +0 -0
  117. /package/dist/{rasterize-FKXCEFHV.js.map → rasterize-ALCNU53T.js.map} +0 -0
  118. /package/dist/{render-NWQI3AXU.js.map → render-MDLTQUUV.js.map} +0 -0
  119. /package/dist/{seal-N3GCBTVA.js.map → seal-K4K3KPE3.js.map} +0 -0
  120. /package/dist/{setup-DEAFODGI.js.map → setup-O2DALZKF.js.map} +0 -0
  121. /package/dist/{watch-B5X7IAMK.js.map → watch-GPWYJAS2.js.map} +0 -0
package/README.md CHANGED
@@ -89,6 +89,28 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
89
89
 
90
90
  ---
91
91
 
92
+ ## v4.12.3 변경사항
93
+
94
+ 공문서 작성 쪽 라운드. 실결재 기안문 206건·보고서 337건의 항목부호 단계별 글꼴을 실측해 **위계 폰트 옵션**을 넣고, 표기법 검수의 쌍점·날짜 룰 오탐을 법정 서식 근거로 좁혔습니다. 별표 코퍼스 3차(183쌍)를 더해 게이트 모수는 hwpx ~1,390·pdf ~950·hwp쌍 ~860 입니다.
95
+
96
+ - **🔤 위계 폰트 `levels`**: 항목부호 단계(0~7)마다 글꼴·크기·굵기를 지정합니다 — `--levels "0=HY견고딕/17/bold,1=한컴돋움/15/bold,2=휴먼명조/14"`, MCP `levels`. 실측: 법정 8단계(1. 가. 1))는 본문과 동일이 90% 라 기본값은 그대로이고, 전자결재 기안문의 □/ㅇ/- 계열은 □ HY견고딕 +2~3pt 굵게 · ㅇ 한컴돋움 굵게 · - 휴먼명조가 지배 관행입니다(`docs/gongmunseo-reference.md` 2.7). 내어쓰기 폭도 그 단계 글자 크기로 맞춥니다. 미지정이면 산출물 불변.
97
+ - **📝 표기법 룰 좁힘**: `COLON_SPACE` 는 표 셀을 건너뜁니다 — 법정 별지서식 자체가 "성 명 :" 라벨 셀을 쓰므로(서식 595건의 표 셀 801건 전부 라벨) 규칙 대상이 아닙니다. 본문의 "일 시 : 값"(기안문 206건 중 100건, 414건)은 편람상 위반이라 그대로 경고합니다. HTML 태그·마크다운 강조 닫힘 뒤 쌍점("성명:</td>", "과정:** 붙임") 오탐도 제거. `DATE_NO_SPACE` 는 법제처 연혁 표기(`<개정 2012.2.14>`·`삭제 <2016.2.29.>`·`[시행일:2017.9.8.]`)를 제외합니다(서식 156건 → 10건). 기안문의 문서번호 인용 "○○과-123(2026.7.1.)호"(103건)는 편람 날짜 표기 위반이라 유지.
98
+ - **📄 PDF 가운뎃점**: 한컴 PDF 가 가운뎃점(ㆍ)을 조합형 아래아(U+119E)로 내던 것을 되돌립니다(별지서식 573쌍 실측 340자·41문서).
99
+ - **📄 PDF 1×1 표 줄 보존**: 선서문 안쪽 상자처럼 중첩표 없는 1×1 표는 줄마다 문단으로 내어 "선 서 나는 헌법을 …" 처럼 제목 줄이 본문에 붙지 않습니다(표 셀 줄바꿈 보존 정책과 정합).
100
+ - **📄 PDF 제목 아래 틀**: 테두리 없는 1칸 틀(별표 1×1 프레임·선서문 바깥 틀)을 "페이지 반복 클립" 으로 가르려던 가설은 반증됐고(본문 영역 클립은 쪽마다 좌표가 다르고 별표는 1쪽), 대신 **제목 아래(윗변 ≥ 20%)·본문 폭(≥ 60%)·위에 글이 있는** 컨테이너를 틀로 봅니다. 채용공고 2단 상자는 폭 조건으로 제외(pdf-table-gt 게이트 유지).
101
+ - **🖋️ HWP 결재 기호 하나 더**: 한컴 PDF 실렌더로 확인한 네모 안 "인"(U+F00E1, HWP5 A0E1)을 "(인)" 으로 냅니다. HWP5 하이픈 제어문자(0x18)는 한컴이 그리지 않으므로("60g/㎡") 더 이상 "-" 를 내지 않습니다.
102
+ - **📊 별표 코퍼스 3차**: `licbyl-byl2/` 183쌍(seed 20260907). 하위 30건 분해 — 1×1 틀 PDF 미감지 8 · HWP 레이아웃 해체 vs PDF 1열 유지 15 · 구조 차 7(수식 셀·쪽 경계 셀 분할·줄바꿈, 파서 결함 없음). HWP↔PDF 셀 일치 별표 0.728 → 0.746, 3차 0.772 → 0.787, 서식 0.965 유지.
103
+
104
+
105
+ 별지서식 라운드 2. 코퍼스를 서식 279쌍·별표 90쌍 더 모아(총 1,995파일) HWP·HWPX·PDF 세 파서가 **같은 IR 모양**을 내도록 맞췄습니다. HWP↔PDF 셀 텍스트 일치 0.927 → 0.965, PDF 표 GT cellExact 0.945 → 0.980 을 실측했고 게이트 플로어를 그 수치로 올렸습니다.
106
+
107
+ - **🪆 PDF 틀 안 중첩표**: 지정서·영치증·선서문처럼 1칸 틀 안에 "발신명의 | 직인" 표가 든 서식에서, PDF 가 그 표를 틀 뒤 별도 블록으로 흘리던 것을 틀 셀 안에 문단·표 원문 순서로 넣습니다(HWPX 파서와 같은 모양). 네 변에 테두리가 그려진 1칸 틀은 표로, 그 안에 홀로 든 상자는 중첩표로 살립니다. 테두리 없는 바깥 틀은 한컴 본문 영역 클립과 구분할 수 없어 표로 삼지 않습니다.
108
+ - **🖋️ HWP 결재란 "(인)" 복원**: HWP 5.x 는 2바이트 문자라 한컴 전용 기호(U+F012B)를 U+A000대에 접어 저장합니다. `ꄫ` 로 새던 결재란 기호가 "(인)" 으로 나옵니다.
109
+ - **📋 HWP 서식 틀 보존**: 중첩표를 품은 별지서식 3×1 틀(제목행·틀·꼬리행)을 레이아웃 표로 보고 문단으로 해체하던 것을 표로 남깁니다. 페이지 사슬 레이아웃 표(글이 많은)는 종전대로 해체합니다.
110
+ - **📝 표기법 검수 정밀화**: 실결재 기안문 206건·별지서식 595건으로 오탐을 실측해 `TILDE_SPACE` 는 숫자·날짜·시각 범위에만, `DUEUM_ERROR` 는 서식 기입란(" 년도", "( 년간)")과 표 머리글을 건너뜁니다.
111
+ - **🧾 양식 인식이 틀 안 중첩표까지**: `extractFormFields` 가 셀 안 중첩표의 라벨-값을 훑습니다(`fillHwpx` 와 같은 경계).
112
+ - **📊 `bench/cmp-hwp-pdf.mjs`**: HWP↔PDF 셀 텍스트 Jaccard 보고 지표 정식 편입. `--linebreaks` 로 PDF 셀 줄바꿈 자리의 어절 경계 비율(85%·글자 단위 13%)을 실측해 줄 결합은 하지 않기로 했습니다.
113
+
92
114
  ## v4.12.1 변경사항
93
115
 
94
116
  법령 **별지서식**(법제처 별표·서식) 파싱을 HWP·HWPX·PDF 세 포맷에서 맞췄습니다. 법제처 OpenAPI로 서식 300건의 HWP 원본과 PDF를 쌍으로 모으고 rhwp 변환 HWPX를 붙인 900파일 코퍼스를 게이트에 편입해, HWP↔PDF 셀 텍스트 일치 0.79 → 0.92, PDF 표 GT cellExact 0.73 → 0.96 을 실측했습니다.
@@ -56,7 +56,7 @@ import {
56
56
  unknownFontWarnings,
57
57
  usesGaejosikMunche,
58
58
  validateHwpx
59
- } from "./chunk-VO37BVAH.js";
59
+ } from "./chunk-INFYDSZL.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-JD4H6Y33.js";
69
+ } from "./chunk-CKYDP34A.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-ST3JBCIL.js";
77
+ } from "./chunk-6L3B44DH.js";
78
78
  import {
79
79
  blocksToMarkdown
80
- } from "./chunk-JQBF4PSF.js";
80
+ } from "./chunk-ZASGH5EN.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-F2KL5Z5T.js";
91
- import "./chunk-EWSH6O6S.js";
90
+ } from "./chunk-B63QEHYL.js";
91
+ import "./chunk-YBZJMAE2.js";
92
92
  import {
93
93
  VERSION
94
- } from "./chunk-E37VAV46.js";
94
+ } from "./chunk-PUZZFYLV.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=-BUYIUNW3.js.map
172
+ //# sourceMappingURL=-FWGDIOV4.js.map
@@ -7,7 +7,7 @@ import {
7
7
  import {
8
8
  safeMax,
9
9
  safeMin
10
- } from "./chunk-E37VAV46.js";
10
+ } from "./chunk-PUZZFYLV.js";
11
11
 
12
12
  // src/pdf/image-regions.ts
13
13
  import { OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
@@ -963,6 +963,9 @@ function linesIntersect(a, b) {
963
963
  var CLIP_EDGE_TOL = 1.5;
964
964
  var CLIP_MIN_GROUP = 2;
965
965
  var CLIP_MAX_PAGE_FRAC = 0.75;
966
+ var TITLED_FRAME_MIN_TOP = 0.2;
967
+ var TITLED_FRAME_MIN_WIDTH = 0.6;
968
+ var HEADER_BAND = 0.08;
966
969
  var CLIP_MIN_W2 = 4;
967
970
  var CLIP_MIN_H2 = 2;
968
971
  var STROKE_NEAR = 2;
@@ -1008,10 +1011,16 @@ function edgeStroked(lines, dir, pos, a1, a2) {
1008
1011
  }
1009
1012
  return false;
1010
1013
  }
1011
- function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight) {
1014
+ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight, textPoints = []) {
1012
1015
  const pageArea = pageWidth * pageHeight;
1013
- 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));
1014
- if (cells.length < CLIP_MIN_GROUP) return { grids: [], containers: [] };
1016
+ const sameRect = (a, b) => Math.abs(a.x1 - b.x1) <= CLIP_EDGE_TOL && Math.abs(a.x2 - b.x2) <= CLIP_EDGE_TOL && Math.abs(a.y1 - b.y1) <= CLIP_EDGE_TOL && Math.abs(a.y2 - b.y2) <= CLIP_EDGE_TOL;
1017
+ const cells = [];
1018
+ for (const r of rects) {
1019
+ if (r.x2 - r.x1 < CLIP_MIN_W2 || r.y2 - r.y1 < CLIP_MIN_H2) continue;
1020
+ if (pageArea > 0 && (r.x2 - r.x1) * (r.y2 - r.y1) >= pageArea * CLIP_MAX_PAGE_FRAC) continue;
1021
+ if (!cells.some((c) => sameRect(c, r))) cells.push(r);
1022
+ }
1023
+ if (cells.length < 1) return { grids: [], containers: [] };
1015
1024
  const parent = new Array(cells.length).fill(-1);
1016
1025
  for (let i = 0; i < cells.length; i++) {
1017
1026
  for (let j = 0; j < cells.length; j++) {
@@ -1046,15 +1055,31 @@ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight) {
1046
1055
  if (g) g.push(i);
1047
1056
  else groups.set(r, [i]);
1048
1057
  }
1049
- const containers = [];
1050
- for (let i = 0; i < cells.length; i++) {
1051
- if (!isContainer[i]) continue;
1052
- const r = cells[i];
1053
- 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);
1054
- }
1058
+ const containers = cells.filter((_, i) => isContainer[i]);
1059
+ const groupSize = (i) => groups.get(find(i))?.length ?? 0;
1060
+ const isGridMember = (i) => groupSize(i) >= CLIP_MIN_GROUP;
1061
+ const framed = (r) => edgeStroked(strokedH, "h", r.y1, r.x1, r.x2) && edgeStroked(strokedH, "h", r.y2, r.x1, r.x2) && edgeStroked(strokedV, "v", r.x1, r.y1, r.y2) && edgeStroked(strokedV, "v", r.x2, r.y1, r.y2);
1062
+ const titledFrame = (r) => r.y1 >= pageHeight * TITLED_FRAME_MIN_TOP && r.x2 - r.x1 >= pageWidth * TITLED_FRAME_MIN_WIDTH && textPoints.some((p) => p.y < r.y1 && p.y > pageHeight * HEADER_BAND && p.x >= r.x1 - CLIP_EDGE_TOL && p.x <= r.x2 + CLIP_EDGE_TOL) && textPoints.some((p) => p.x > r.x1 && p.x < r.x2 && p.y > r.y1 && p.y < r.y2);
1063
+ const loneFrame = (i) => isContainer[i] && !isGridMember(i) && (framed(cells[i]) || titledFrame(cells[i]));
1064
+ const parentAttachable = (i) => parent[i] >= 0 && (isGridMember(parent[i]) || loneFrame(parent[i]));
1065
+ const onlyChild = (i) => parent[i] >= 0 && parent.filter((p) => p === parent[i]).length === 1;
1055
1066
  const grids = [];
1056
1067
  for (const idxs of groups.values()) {
1057
- if (idxs.length < CLIP_MIN_GROUP) continue;
1068
+ const first = idxs[0];
1069
+ const parentRect = parentAttachable(first) ? cells[parent[first]] : void 0;
1070
+ if (idxs.length < CLIP_MIN_GROUP) {
1071
+ if (!loneFrame(first) && !((parentRect || onlyChild(first)) && framed(cells[first]))) continue;
1072
+ const r = cells[first];
1073
+ grids.push({
1074
+ rowYs: [r.y2, r.y1],
1075
+ colXs: [r.x1, r.x2],
1076
+ bbox: { x1: r.x1, y1: r.y1, x2: r.x2, y2: r.y2 },
1077
+ vertexRadius: 1,
1078
+ cells: [{ row: 0, col: 0, rowSpan: 1, colSpan: 1, bbox: { x1: r.x1, y1: r.y1, x2: r.x2, y2: r.y2 } }],
1079
+ ...parentRect ? { clipParent: parentRect } : {}
1080
+ });
1081
+ continue;
1082
+ }
1058
1083
  const members = idxs.map((i) => cells[i]);
1059
1084
  let edges = 0, invisible = 0;
1060
1085
  for (const r of members) {
@@ -1095,7 +1120,8 @@ function buildClipCellGrids(rects, strokedH, strokedV, pageWidth, pageHeight) {
1095
1120
  colXs,
1096
1121
  bbox: { x1: colXs[0], y1: rowYs[numRows], x2: colXs[numCols], y2: rowYs[0] },
1097
1122
  vertexRadius: 1,
1098
- cells: out
1123
+ cells: out,
1124
+ ...parentRect ? { clipParent: parentRect } : {}
1099
1125
  });
1100
1126
  }
1101
1127
  return { grids, containers };
@@ -3351,7 +3377,7 @@ function extractPageBlocksWithLines(items, pageNum, opList, pageWidth, pageHeigh
3351
3377
  if (items.length === 0) return [];
3352
3378
  const extracted = extractLines(opList.fnArray, opList.argsArray);
3353
3379
  let { horizontals, verticals } = extracted;
3354
- const clipResult = detectTables ? buildClipCellGrids(extracted.clipRects, horizontals, verticals, pageWidth, pageHeight) : { grids: [], containers: [] };
3380
+ const clipResult = detectTables ? buildClipCellGrids(extracted.clipRects, horizontals, verticals, pageWidth, pageHeight, items.map((it) => ({ x: it.x + it.w / 2, y: it.y + it.h / 2 }))) : { grids: [], containers: [] };
3355
3381
  const clipGrids = clipResult.grids;
3356
3382
  if (extraLines) {
3357
3383
  horizontals = horizontals.concat(extraLines.horizontals);
@@ -3464,9 +3490,47 @@ function isProseBoxGrid(grid, verticals, table) {
3464
3490
  if (longCells < PROSEBOX_LONG_CELL_MIN || longCells < texts.length * PROSEBOX_LONG_CELL_RATIO) return false;
3465
3491
  return true;
3466
3492
  }
3493
+ function cleanCellText(text) {
3494
+ const stripped = text.replace(/^[\s]*[-–—]\s*\d+\s*[-–—][\s]*$/gm, "").trim();
3495
+ return stripped.split("\n").map((line) => collapseEvenSpacing(line)).join("\n");
3496
+ }
3497
+ var FRAME_RECT_TOL = 1.5;
3498
+ function takePendingNested(pending, cellBox) {
3499
+ const out = [];
3500
+ for (let i = pending.length - 1; i >= 0; i--) {
3501
+ const p = pending[i].parent;
3502
+ if (Math.abs(p.x1 - cellBox.x1) <= FRAME_RECT_TOL && Math.abs(p.x2 - cellBox.x2) <= FRAME_RECT_TOL && Math.abs(p.y1 - cellBox.y1) <= FRAME_RECT_TOL && Math.abs(p.y2 - cellBox.y2) <= FRAME_RECT_TOL) {
3503
+ out.push(pending[i].block);
3504
+ pending.splice(i, 1);
3505
+ }
3506
+ }
3507
+ return out;
3508
+ }
3509
+ function buildFrameCellBlocks(cellItems, nested, pageNum) {
3510
+ const tables = [...nested].sort((a, b) => b.bbox.y + b.bbox.height - (a.bbox.y + a.bbox.height));
3511
+ const blocks = [];
3512
+ let rest = [...cellItems];
3513
+ const pushParagraphs = (items) => {
3514
+ if (items.length === 0) return;
3515
+ for (const line of cleanCellText(cellTextToString(items)).split("\n")) {
3516
+ const t = line.trim();
3517
+ if (t) blocks.push({ type: "paragraph", text: t, pageNumber: pageNum });
3518
+ }
3519
+ };
3520
+ for (const tb of tables) {
3521
+ const bottom = tb.bbox.y;
3522
+ pushParagraphs(rest.filter((it) => it.y >= bottom));
3523
+ rest = rest.filter((it) => it.y < bottom);
3524
+ blocks.push(tb);
3525
+ }
3526
+ pushParagraphs(rest);
3527
+ const text = blocks.map((b) => b.type === "table" && b.table ? b.table.cells.flat().map((c) => c.text).filter(Boolean).join("\n") : b.text ?? "").filter(Boolean).join("\n");
3528
+ return { blocks, text };
3529
+ }
3467
3530
  function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
3468
3531
  const blocks = [];
3469
3532
  const usedItems = /* @__PURE__ */ new Set();
3533
+ const pendingNested = [];
3470
3534
  const gridArea = (g) => (g.bbox.x2 - g.bbox.x1) * (g.bbox.y2 - g.bbox.y1);
3471
3535
  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);
3472
3536
  for (const grid of sortedGrids) {
@@ -3506,13 +3570,18 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
3506
3570
  { length: numRows },
3507
3571
  () => Array.from({ length: numCols }, () => ({ text: "", colSpan: 1, rowSpan: 1 }))
3508
3572
  );
3573
+ let nestedAttached = false;
3509
3574
  for (const cell of cells) {
3510
3575
  const cellItems = cellTextMap.get(cell) || [];
3511
- let text = cellTextToString(cellItems);
3512
- text = text.replace(/^[\s]*[-–—]\s*\d+\s*[-–—][\s]*$/gm, "").trim();
3513
- text = text.split("\n").map((line) => collapseEvenSpacing(line)).join("\n");
3576
+ const nested = grid.cells ? takePendingNested(pendingNested, cell.bbox) : [];
3577
+ if (nested.length > 0) {
3578
+ nestedAttached = true;
3579
+ const built = buildFrameCellBlocks(cellItems, nested, pageNum);
3580
+ irGrid[cell.row][cell.col] = { text: built.text, colSpan: cell.colSpan, rowSpan: cell.rowSpan, blocks: built.blocks };
3581
+ continue;
3582
+ }
3514
3583
  irGrid[cell.row][cell.col] = {
3515
- text,
3584
+ text: cleanCellText(cellTextToString(cellItems)),
3516
3585
  colSpan: cell.colSpan,
3517
3586
  rowSpan: cell.rowSpan
3518
3587
  };
@@ -3520,18 +3589,11 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
3520
3589
  let finalGrid = irGrid;
3521
3590
  let finalRows = numRows;
3522
3591
  let rebuiltUsed = false;
3523
- if (numRows <= 2 && numCols >= 3) {
3592
+ if (numRows <= 2 && numCols >= 3 && !nestedAttached) {
3524
3593
  const rebuilt = normalizeUndersegmentedTable(irGrid, grid.colXs, textItems);
3525
3594
  if (rebuilt) {
3526
3595
  rebuiltUsed = true;
3527
- finalGrid = rebuilt.map((row) => row.map((rawText) => {
3528
- const cleaned = rawText.replace(/^[\s]*[-–—]\s*\d+\s*[-–—][\s]*$/gm, "").trim();
3529
- return {
3530
- text: cleaned.split("\n").map((line) => collapseEvenSpacing(line)).join("\n"),
3531
- colSpan: 1,
3532
- rowSpan: 1
3533
- };
3534
- }));
3596
+ finalGrid = rebuilt.map((row) => row.map((rawText) => ({ text: cleanCellText(rawText), colSpan: 1, rowSpan: 1 })));
3535
3597
  finalRows = finalGrid.length;
3536
3598
  }
3537
3599
  }
@@ -3548,6 +3610,11 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
3548
3610
  };
3549
3611
  const hasContent = finalGrid.some((row) => row.some((cell) => cell.text.trim() !== ""));
3550
3612
  if (!hasContent) continue;
3613
+ if (grid.clipParent) {
3614
+ const nb = { page: pageNum, x: grid.bbox.x1, y: grid.bbox.y1, width: grid.bbox.x2 - grid.bbox.x1, height: grid.bbox.y2 - grid.bbox.y1 };
3615
+ pendingNested.push({ parent: grid.clipParent, block: { type: "table", table: irTable, pageNumber: pageNum, bbox: nb } });
3616
+ continue;
3617
+ }
3551
3618
  if (!grid.cells && isProseBoxGrid(grid, verticals, irTable)) {
3552
3619
  for (const it of tableItems) usedItems.delete(it);
3553
3620
  continue;
@@ -3569,6 +3636,7 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
3569
3636
  }
3570
3637
  blocks.push({ type: "table", table: irTable, pageNumber: pageNum, bbox: tableBbox });
3571
3638
  }
3639
+ for (const p of pendingNested) blocks.push(p.block);
3572
3640
  let remaining = items.filter((i) => !usedItems.has(i));
3573
3641
  const groupSizes = [];
3574
3642
  let finalTextBlocks = [];
@@ -3878,4 +3946,4 @@ export {
3878
3946
  extractPageBlocksWithLines,
3879
3947
  mergeCrossPageTables
3880
3948
  };
3881
- //# sourceMappingURL=chunk-OS2IOI3W.js.map
3949
+ //# sourceMappingURL=chunk-2DAFWDV2.js.map