kordoc 4.21.20 → 4.21.21

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 (124) hide show
  1. package/README.md +6 -5
  2. package/dist/{-QHQ2QKM7.js → -ZYF6HX4M.js} +11 -11
  3. package/dist/{chunk-FU3SCGE7.js → chunk-2ZGP7A5T.js} +2 -2
  4. package/dist/{chunk-TYYKK5DX.js → chunk-3TCF5HPP.js} +2 -2
  5. package/dist/{chunk-WWMHVDRG.cjs → chunk-57VNFMMS.cjs} +3 -3
  6. package/dist/{chunk-WWMHVDRG.cjs.map → chunk-57VNFMMS.cjs.map} +1 -1
  7. package/dist/{chunk-75INFYVC.js → chunk-5HTFWUJE.js} +2 -2
  8. package/dist/{chunk-G6G25HUO.js → chunk-7YAJCXCD.js} +2 -2
  9. package/dist/{chunk-DBFAQJCX.js → chunk-BC54LBST.js} +3 -3
  10. package/dist/{chunk-PALYDA3M.js → chunk-BG6LUCQL.js} +2 -2
  11. package/dist/{chunk-42XYVZ2B.js → chunk-CY4QM2KQ.js} +2 -2
  12. package/dist/{chunk-2FALZL3E.cjs → chunk-DTZIZ5PP.cjs} +13 -13
  13. package/dist/{chunk-2FALZL3E.cjs.map → chunk-DTZIZ5PP.cjs.map} +1 -1
  14. package/dist/{chunk-AINZICPE.js → chunk-FBJVDBMO.js} +2 -2
  15. package/dist/{chunk-7ITWGPNB.js → chunk-FZMGF6XU.js} +2 -2
  16. package/dist/{chunk-CKEO54YN.cjs → chunk-H2MS4VG3.cjs} +3 -3
  17. package/dist/{chunk-CKEO54YN.cjs.map → chunk-H2MS4VG3.cjs.map} +1 -1
  18. package/dist/{chunk-KBL24ZCP.js → chunk-HWIW3WK2.js} +3 -3
  19. package/dist/{chunk-E5L6DUOE.js → chunk-J5PBI3ZC.js} +3 -3
  20. package/dist/{chunk-LY4ITNAL.js → chunk-KBV4WFZ6.js} +80 -5
  21. package/dist/chunk-KBV4WFZ6.js.map +1 -0
  22. package/dist/{chunk-JDVOYDIA.js → chunk-KTLHADU4.js} +2 -2
  23. package/dist/{chunk-M37GOXT5.cjs → chunk-MCUX2Y2C.cjs} +2 -2
  24. package/dist/{chunk-M37GOXT5.cjs.map → chunk-MCUX2Y2C.cjs.map} +1 -1
  25. package/dist/{chunk-ZMGWKGSC.js → chunk-PTF4NCFM.js} +2 -2
  26. package/dist/{chunk-N47I6OS7.js → chunk-QHNVH4TC.js} +6 -6
  27. package/dist/{chunk-LO5A36FU.js → chunk-RBRHO7WL.js} +2 -2
  28. package/dist/{chunk-6HIDMA5O.js → chunk-T3DTTK23.js} +2 -2
  29. package/dist/{chunk-KS2SASZH.cjs → chunk-T7PN5D4D.cjs} +6 -6
  30. package/dist/{chunk-KS2SASZH.cjs.map → chunk-T7PN5D4D.cjs.map} +1 -1
  31. package/dist/{chunk-MOT7WOP5.cjs → chunk-TMUUDMDH.cjs} +100 -25
  32. package/dist/chunk-TMUUDMDH.cjs.map +1 -0
  33. package/dist/{chunk-I4HF4UCA.js → chunk-TRO4CQHJ.js} +4 -4
  34. package/dist/{chunk-WINHSKFD.js → chunk-WGMYVRSA.js} +2 -2
  35. package/dist/{chunk-LQSF4JSV.js → chunk-YHPJTLCA.js} +2 -2
  36. package/dist/{chunk-BLKAJD2G.js → chunk-YM3K3LCD.js} +10 -10
  37. package/dist/{chunk-F5KWC7I3.js → chunk-YQZOPT3A.js} +81 -6
  38. package/dist/chunk-YQZOPT3A.js.map +1 -0
  39. package/dist/{chunk-F5PISJEI.js → chunk-ZZIQWSGC.js} +2 -2
  40. package/dist/chunks-MDMJRCJ7.js +10 -0
  41. package/dist/cli.js +30 -30
  42. package/dist/{formula-XAPMN6HX.js → formula-6XNIXZ5L.js} +3 -3
  43. package/dist/{formula-G5LTBMGC.cjs → formula-A3I5VAP7.cjs} +9 -9
  44. package/dist/{formula-G5LTBMGC.cjs.map → formula-A3I5VAP7.cjs.map} +1 -1
  45. package/dist/{formula-KHPAZKOK.js → formula-P7M7IH2L.js} +4 -4
  46. package/dist/{image-ocr-RNPHQ4C2.js → image-ocr-3C6W3KZO.js} +6 -6
  47. package/dist/{image-ocr-N5LAIHRV.js → image-ocr-ITNY454I.js} +9 -9
  48. package/dist/{image-ocr-MHPVAYOB.cjs → image-ocr-UJRSQ2T3.cjs} +15 -15
  49. package/dist/{image-ocr-MHPVAYOB.cjs.map → image-ocr-UJRSQ2T3.cjs.map} +1 -1
  50. package/dist/index.cjs +276 -276
  51. package/dist/index.js +5 -5
  52. package/dist/mcp.js +22 -22
  53. package/dist/{model-bundle-XP4AHFLT.js → model-bundle-LAWQH75O.js} +5 -5
  54. package/dist/{models-AD2KAUNC.js → models-6QTD2CFM.js} +5 -5
  55. package/dist/{parse-worker-v2-7UHOQ2TK.js → parse-worker-v2-FEPJG3ER.js} +12 -12
  56. package/dist/{parser-5WGRQKPI.js → parser-56I364YM.js} +16 -8
  57. package/dist/{parser-2RN5WNPJ.js.map → parser-56I364YM.js.map} +1 -1
  58. package/dist/{parser-2RN5WNPJ.js → parser-KFCY3NIN.js} +19 -11
  59. package/dist/{parser-5WGRQKPI.js.map → parser-KFCY3NIN.js.map} +1 -1
  60. package/dist/{parser-ZPCCXCVO.cjs → parser-VKWUMUML.cjs} +142 -134
  61. package/dist/parser-VKWUMUML.cjs.map +1 -0
  62. package/dist/pdf-ocr-MME37BL4.js +18 -0
  63. package/dist/pdf-ocr-OKAHNEJA.cjs +19 -0
  64. package/dist/{pdf-ocr-SFIWMJQL.cjs.map → pdf-ocr-OKAHNEJA.cjs.map} +1 -1
  65. package/dist/pdf-ocr-ZZ6AJL6Z.js +22 -0
  66. package/dist/{profile-io-UP7H2J6I.js → profile-io-OCITHUQF.js} +3 -3
  67. package/dist/{rasterize-XBCDG4MD.js → rasterize-SFCG4YOI.js} +3 -3
  68. package/dist/{redact-doc-CJ6W6YNR.js → redact-doc-5SMPXIGG.js} +11 -11
  69. package/dist/{render-K5UDHXRI.js → render-FGPG5A2N.js} +7 -7
  70. package/dist/seal-GA7NZDTG.js +10 -0
  71. package/dist/{setup-S66RLBBM.js → setup-JQVYZO7I.js} +3 -3
  72. package/dist/visual-YMG644YW.js +20 -0
  73. package/dist/{watch-PB7IOD73.js → watch-C77BUSP7.js} +12 -12
  74. package/package.json +1 -1
  75. package/dist/chunk-F5KWC7I3.js.map +0 -1
  76. package/dist/chunk-LY4ITNAL.js.map +0 -1
  77. package/dist/chunk-MOT7WOP5.cjs.map +0 -1
  78. package/dist/chunks-BJRBKGKZ.js +0 -10
  79. package/dist/parser-ZPCCXCVO.cjs.map +0 -1
  80. package/dist/pdf-ocr-KHY3P4EP.js +0 -18
  81. package/dist/pdf-ocr-LFDH6DFD.js +0 -22
  82. package/dist/pdf-ocr-SFIWMJQL.cjs +0 -19
  83. package/dist/seal-YCK6TM5I.js +0 -10
  84. package/dist/visual-S5ZNFWFH.js +0 -20
  85. /package/dist/{-QHQ2QKM7.js.map → -ZYF6HX4M.js.map} +0 -0
  86. /package/dist/{chunk-FU3SCGE7.js.map → chunk-2ZGP7A5T.js.map} +0 -0
  87. /package/dist/{chunk-TYYKK5DX.js.map → chunk-3TCF5HPP.js.map} +0 -0
  88. /package/dist/{chunk-75INFYVC.js.map → chunk-5HTFWUJE.js.map} +0 -0
  89. /package/dist/{chunk-G6G25HUO.js.map → chunk-7YAJCXCD.js.map} +0 -0
  90. /package/dist/{chunk-DBFAQJCX.js.map → chunk-BC54LBST.js.map} +0 -0
  91. /package/dist/{chunk-PALYDA3M.js.map → chunk-BG6LUCQL.js.map} +0 -0
  92. /package/dist/{chunk-42XYVZ2B.js.map → chunk-CY4QM2KQ.js.map} +0 -0
  93. /package/dist/{chunk-AINZICPE.js.map → chunk-FBJVDBMO.js.map} +0 -0
  94. /package/dist/{chunk-7ITWGPNB.js.map → chunk-FZMGF6XU.js.map} +0 -0
  95. /package/dist/{chunk-KBL24ZCP.js.map → chunk-HWIW3WK2.js.map} +0 -0
  96. /package/dist/{chunk-E5L6DUOE.js.map → chunk-J5PBI3ZC.js.map} +0 -0
  97. /package/dist/{chunk-JDVOYDIA.js.map → chunk-KTLHADU4.js.map} +0 -0
  98. /package/dist/{chunk-ZMGWKGSC.js.map → chunk-PTF4NCFM.js.map} +0 -0
  99. /package/dist/{chunk-N47I6OS7.js.map → chunk-QHNVH4TC.js.map} +0 -0
  100. /package/dist/{chunk-LO5A36FU.js.map → chunk-RBRHO7WL.js.map} +0 -0
  101. /package/dist/{chunk-6HIDMA5O.js.map → chunk-T3DTTK23.js.map} +0 -0
  102. /package/dist/{chunk-I4HF4UCA.js.map → chunk-TRO4CQHJ.js.map} +0 -0
  103. /package/dist/{chunk-WINHSKFD.js.map → chunk-WGMYVRSA.js.map} +0 -0
  104. /package/dist/{chunk-LQSF4JSV.js.map → chunk-YHPJTLCA.js.map} +0 -0
  105. /package/dist/{chunk-BLKAJD2G.js.map → chunk-YM3K3LCD.js.map} +0 -0
  106. /package/dist/{chunk-F5PISJEI.js.map → chunk-ZZIQWSGC.js.map} +0 -0
  107. /package/dist/{chunks-BJRBKGKZ.js.map → chunks-MDMJRCJ7.js.map} +0 -0
  108. /package/dist/{formula-XAPMN6HX.js.map → formula-6XNIXZ5L.js.map} +0 -0
  109. /package/dist/{formula-KHPAZKOK.js.map → formula-P7M7IH2L.js.map} +0 -0
  110. /package/dist/{image-ocr-RNPHQ4C2.js.map → image-ocr-3C6W3KZO.js.map} +0 -0
  111. /package/dist/{image-ocr-N5LAIHRV.js.map → image-ocr-ITNY454I.js.map} +0 -0
  112. /package/dist/{model-bundle-XP4AHFLT.js.map → model-bundle-LAWQH75O.js.map} +0 -0
  113. /package/dist/{models-AD2KAUNC.js.map → models-6QTD2CFM.js.map} +0 -0
  114. /package/dist/{parse-worker-v2-7UHOQ2TK.js.map → parse-worker-v2-FEPJG3ER.js.map} +0 -0
  115. /package/dist/{pdf-ocr-KHY3P4EP.js.map → pdf-ocr-MME37BL4.js.map} +0 -0
  116. /package/dist/{pdf-ocr-LFDH6DFD.js.map → pdf-ocr-ZZ6AJL6Z.js.map} +0 -0
  117. /package/dist/{profile-io-UP7H2J6I.js.map → profile-io-OCITHUQF.js.map} +0 -0
  118. /package/dist/{rasterize-XBCDG4MD.js.map → rasterize-SFCG4YOI.js.map} +0 -0
  119. /package/dist/{redact-doc-CJ6W6YNR.js.map → redact-doc-5SMPXIGG.js.map} +0 -0
  120. /package/dist/{render-K5UDHXRI.js.map → render-FGPG5A2N.js.map} +0 -0
  121. /package/dist/{seal-YCK6TM5I.js.map → seal-GA7NZDTG.js.map} +0 -0
  122. /package/dist/{setup-S66RLBBM.js.map → setup-JQVYZO7I.js.map} +0 -0
  123. /package/dist/{visual-S5ZNFWFH.js.map → visual-YMG644YW.js.map} +0 -0
  124. /package/dist/{watch-PB7IOD73.js.map → watch-C77BUSP7.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pdf/line-extract.ts","../src/pdf/image-regions.ts","../src/pdf/vertical-bridge.ts","../src/pdf/line-types.ts","../src/pdf/table-grid.ts","../src/pdf/clip-cells.ts","../src/pdf/cell-extract.ts","../src/pdf/script-items.ts","../src/pdf/line-wrap.ts","../src/pdf/cell-text.ts","../src/pdf/undersegmented.ts","../src/pdf/text-line.ts","../src/pdf/table-roles.ts","../src/pdf/cluster-cell-text.ts","../src/pdf/cluster-detector.ts","../src/pdf/page-regions.ts","../src/pdf/local-regions.ts","../src/pdf/xy-cut.ts","../src/pdf/paragraph-lines.ts","../src/pdf/block-detect.ts","../src/pdf/table-meta.ts","../src/pdf/table-trim.ts","../src/pdf/image-bullets.ts","../src/pdf/flow-boxes.ts","../src/pdf/side-tabs.ts","../src/pdf/ruled-columns.ts","../src/pdf/two-column.ts","../src/pdf/image-panels.ts","../src/pdf/blank-fills.ts","../src/pdf/columns.ts","../src/pdf/underline.ts","../src/pdf/grid-header-line.ts","../src/pdf/crossing-frames.ts","../src/pdf/spread-labels.ts","../src/pdf/cell-edges.ts","../src/pdf/table-unit-row.ts","../src/pdf/page-frame.ts","../src/pdf/shaded-open-table.ts","../src/pdf/open-table-ends.ts","../src/pdf/header-box-rows.ts","../src/pdf/ruled-band-tables.ts","../src/pdf/text-box-table.ts","../src/pdf/frame-cell-blocks.ts","../src/pdf/nested-shaded-headers.ts","../src/pdf/side-tab-blocks.ts","../src/pdf/page-blocks.ts","../src/pdf/ocr-region-merge.ts"],"sourcesContent":["/**\n * PDF 그래픽 명령에서 수평/수직 선을 추출하는 모듈 (line-detector.ts에서 분리).\n *\n * 선 전처리 파이프라인은 OpenDataLoader PDF의 LinesPreprocessingConsumer를\n * 참고하여 TypeScript로 clean-room 재구현한 것입니다.\n * Original algorithm: Copyright 2025-2026 Hancom, Inc. (Apache 2.0)\n * https://github.com/opendataloader-project/opendataloader-pdf\n */\n\nimport { OPS } from \"pdfjs-dist/legacy/build/pdf.mjs\"\nimport type { LineSegment } from \"./line-types.js\"\n\n// ─── pdfjs-dist v5 DrawOPS ──\nconst enum DrawOPS {\n moveTo = 0,\n lineTo = 1,\n curveTo = 2,\n quadraticCurveTo = 3,\n closePath = 4,\n}\n\n/** 수평/수직 판별 허용 오차 (pt) */\nconst ORIENTATION_TOL = 2\n/** 최소 선 길이 — 짧은 장식선(체크박스 테두리 등) 무시 */\nconst MIN_LINE_LENGTH = 15\n/** 굵은 선 필터 — ODL: MAX_LINE_WIDTH = 5.0 (배경 채움/장식 사각형 제외) */\nconst MAX_LINE_WIDTH = 5.0\n/** 채움 괘선 두께 상한 (pt) — Word 는 칸 테두리를 변 조각마다 0.5~1pt 채움 사각형으로 그린다(ODL 146·150 실측) */\nconst THIN_FILL_MAX = 1.5\n/** 같은 색 채움 안에 든 채움 판정 여유 (pt) — Word 문단 음영·칸 여백 띠는 칸 배경 채움보다 0.3pt 넘치기도 한다(ODL 146) */\nconst HIDDEN_FILL_TOL = 0.5\n\n// ─── 선 추출 ──────────────────────────────────────────\n\n/**\n * pdfjs operatorList에서 수평/수직 선을 추출.\n * constructPath(91) 내의 moveTo→lineTo, rectangle 패턴을 인식.\n */\nexport function extractLines(\n fnArray: Uint32Array | number[],\n argsArray: unknown[][],\n): { horizontals: LineSegment[]; verticals: LineSegment[]; clipRects: ClipRect[]; fillRects: ClipRect[]; hiddenBoxes: ClipRect[]; shortH: LineSegment[]; shortV: LineSegment[]; nonRules: Set<LineSegment> } {\n const horizontals: LineSegment[] = []\n const verticals: LineSegment[] = []\n // MIN_LINE_LENGTH 미만 획 조각 — 칸 클립 격자가 없는 쪽에서 같은 좌표 조각 사슬로 이어 붙일 후보 (chainShortSegments, page-blocks)\n const shortH: LineSegment[] = []\n const shortV: LineSegment[] = []\n const clipRects: ClipRect[] = []\n // 채움 사각형 — 한컴은 폭 3pt 안팎의 좁은 빈 칸에는 셀 클립을 깔지 않고 배경 채움만 그린다(행정업무운영 편람\n // 머리 상자 양옆 회색 띠 실측). 클립 격자에 빠진 가장자리 칸을 이것으로 되살린다 (clip-cells)\n const fillRects: ClipRect[] = []\n /** 이번 경로의 re 사각형 (얇은 사각형은 세그먼트가 가운데 선 하나로 접히므로 원본 bbox 를 따로 둔다) */\n let pathRects: ClipRect[] = []\n /** pathRects 마다 currentPath 안 세그먼트 범위·얇은 괘선 여부 */\n let pathRectSegs: Array<{ start: number; end: number; thin: boolean }> = []\n // 채움 색 추적 — 같은 색 채움 안에 칠한 채움은 보이는 경계가 없다 (아래 flushPath)\n let fillColor = 0\n const colorStack: number[] = []\n // 채움·획 불투명도(ExtGState ca·CA) — 0 이면 그려도 보이지 않는다\n let fillAlpha = 1, strokeAlpha = 1\n const alphaStack: Array<[number, number]> = []\n // 칸 테두리 선이 아닌 선 — 선 격자·클립 판정은 종전대로 쓰고 칸 테두리 가시성(cell-edges)만 뺀다.\n // ① 흰(0xFFFFFF) 획·채움 — 흰 바탕에 안 보인다. 한컴은 흰 테두리 칸 변도 흰 획으로 긋는다(규제영향분석서 10쪽 y=503.09 흰 획\n // 99.07~132.77). 흰 획이 먼저 그은 검은 선을 덮어 지우는 경우는 가리지 않는다\n // ② 두꺼운(THIN_FILL_MAX 넘는) 채움 영역의 윤곽 — 칸 음영 배경이다. 한컴은 음영 칸을 m/l 사각형 채움으로 칠해(정책브리핑 지진 대응\n // 보도자료 붙임 1 \"개 회 식\" 행 242,242,242) 그 변이 칸 좌우 세로선으로 읽혔다. HWPX 는 채우기만 있고 테두리 \"없음\"인 칸이다\n let strokeColor = 0\n const strokeColorStack: number[] = []\n const nonRules = new Set<LineSegment>()\n /** 보이지 않는(ca=0) 채움 사각형 — 슬라이드 제작기가 글상자마다 까는 틀 (text-box-table) */\n const hiddenBoxes: ClipRect[] = []\n /** 지금까지 칠한 사각형 채움 (색별) */\n const paintedFills = new Map<number, ClipRect[]>()\n /** 얇은 채움 사각형 괘선 — 짧은 조각은 thinShort 로, 긴 조각은 thinFill 로 모아 끝에서 사슬 잇기 */\n const thinFill = new Set<LineSegment>()\n const thinShortH: LineSegment[] = []\n const thinShortV: LineSegment[] = []\n // 클립 경로 추적 — 한컴 PDF 는 표 셀마다 `W n`(clip + endPath) 사각형을 깐다. 획이 없는\n // 셀(테두리 \"없음\"인 별지서식 외곽 표)도 클립은 있으므로 그리드 복원의 근거가 된다 (v4.12.1)\n let pendingClip = false\n let lineWidth = 1\n\n // CTM 추적 — 경로 좌표는 구성 시점의 CTM 공간에 있다. 콘텐츠 스트림이\n // 축소/플립 변환을 깔면(성과계획서류: [0.75,0,0,-0.75,0,H]) 선이 텍스트\n // (getTextContent는 CTM 적용 완료)와 다른 좌표계에 놓여 그리드-텍스트 매핑이\n // 전멸한다. extractImageRegions와 동일하게 save/restore/transform을 따라간다.\n let ctm = [1, 0, 0, 1, 0, 0]\n const ctmStack: number[][] = []\n const applyCtm = (x: number, y: number): [number, number] =>\n [ctm[0] * x + ctm[2] * y + ctm[4], ctm[1] * x + ctm[3] * y + ctm[5]]\n /** CTM 평균 스케일 — lineWidth를 사용자 공간 두께로 환산 */\n const ctmScale = () =>\n (Math.hypot(ctm[0], ctm[1]) + Math.hypot(ctm[2], ctm[3])) / 2\n\n let currentPath: Array<{ x1: number; y1: number; x2: number; y2: number }> = []\n let pathStartX = 0, pathStartY = 0\n let curX = 0, curY = 0\n\n /** 원시 좌표 세그먼트를 CTM 적용해 경로에 추가 */\n function pushSeg(x1: number, y1: number, x2: number, y2: number) {\n const [tx1, ty1] = applyCtm(x1, y1)\n const [tx2, ty2] = applyCtm(x2, y2)\n currentPath.push({ x1: tx1, y1: ty1, x2: tx2, y2: ty2 })\n }\n\n function pushRectangle(rx: number, ry: number, rw: number, rh: number) {\n const [ax, ay] = applyCtm(rx, ry), [bx, by] = applyCtm(rx + rw, ry + rh)\n pathRects.push({ x1: Math.min(ax, bx), y1: Math.min(ay, by), x2: Math.max(ax, bx), y2: Math.max(ay, by) })\n // 얇은 사각형(선으로 그린 괘선) 판별은 CTM 적용 후 실제 두께 기준\n const effH = Math.abs(rh) * Math.hypot(ctm[2], ctm[3])\n const effW = Math.abs(rw) * Math.hypot(ctm[0], ctm[1])\n const start = currentPath.length\n if (effH < ORIENTATION_TOL * 2) {\n pushSeg(rx, ry + rh / 2, rx + rw, ry + rh / 2)\n } else if (effW < ORIENTATION_TOL * 2) {\n pushSeg(rx + rw / 2, ry, rx + rw / 2, ry + rh)\n } else {\n pushSeg(rx, ry, rx + rw, ry)\n pushSeg(rx + rw, ry, rx + rw, ry + rh)\n pushSeg(rx + rw, ry + rh, rx, ry + rh)\n pushSeg(rx, ry + rh, rx, ry)\n }\n pathRectSegs.push({ start, end: currentPath.length, thin: Math.min(effH, effW) <= THIN_FILL_MAX })\n }\n\n function flushPath(isStroke: boolean, fromFill = false, filled = fromFill) {\n // 불투명도 0 으로 칠하거나 그은 경로는 보이지 않는다 — 괘선·채움 칸 증거가 아니다. 슬라이드 제작기는 글상자마다 ca=0 채움\n // 틀을 깔아, 그 변이 칸마다 높이가 다른 어긋난 격자와 글줄 위 가짜 취소선이 됐다(ODL 200: 보이는 회색 행 괘선은 래스터 그림,\n // 벡터 괘선 0·틀 45개). 채움 틀은 글상자 칸 증거로 따로 모은다 (text-box-table)\n if (isStroke && (fromFill ? fillAlpha === 0 : strokeAlpha === 0 && !(filled && fillAlpha > 0))) {\n if (fromFill) {\n if (pathRects.length) hiddenBoxes.push(...pathRects)\n else captureClipRect(currentPath, hiddenBoxes)\n }\n pathRects = []\n pathRectSegs = []\n pendingClip = false\n currentPath = []\n return\n }\n if (filled && fillAlpha === 0) filled = false\n if (filled) {\n if (pathRects.length) for (const r of pathRects) fillRects.push(r)\n else captureClipRect(currentPath, fillRects, 0.3, 0.3)\n }\n // 순수 채움 사각형의 세그먼트 역할 — 같은 색으로 먼저 칠한 채움 안에 든 사각형은 경계가 보이지 않는다: Word 는 칸 배경\n // 채움 안에 문단 음영(칸 변에서 5.5pt 안쪽)·칸 여백 띠를 같은 색으로 한 번 더 칠해, 그 변이 가짜 행·열 괘선이 되어 표가\n // 조각났다(ODL 146 \"Area | Competence\", 150). 얇은(≤1.5pt) 채움 사각형은 Word 가 칸 변 조각마다 따로 칠한 괘선이다 —\n // 가로 괘선이 지나는 자리의 세로 괘선을 5.5pt 짜리 토막으로 끊어 그려 MIN_LINE_LENGTH 에 버려지면 세로 괘선이 행마다\n // 끊겨 표가 행 묶음마다 갈렸다. 토막은 끝에서 맞닿은 조각과 사슬로 잇는다 (chainShortSegments)\n const segRole: Array<\"hidden\" | \"thin\" | undefined> = []\n if (fromFill && pathRects.length) {\n const painted = paintedFills.get(fillColor) ?? []\n pathRects.forEach((r, i) => {\n const segs = pathRectSegs[i]\n if (!segs) return\n const hidden = painted.some(c => r.x1 >= c.x1 - HIDDEN_FILL_TOL && r.x2 <= c.x2 + HIDDEN_FILL_TOL\n && r.y1 >= c.y1 - HIDDEN_FILL_TOL && r.y2 <= c.y2 + HIDDEN_FILL_TOL)\n const role = hidden ? \"hidden\" : segs.thin ? \"thin\" : undefined\n for (let k = segs.start; k < segs.end; k++) segRole[k] = role\n })\n for (const r of pathRects) painted.push(r)\n paintedFills.set(fillColor, painted)\n }\n const areaFill = fromFill && (pathRects.length > 0 || thickPath(currentPath))\n pathRects = []\n pathRectSegs = []\n if (!isStroke) {\n if (pendingClip) captureClipRect(currentPath, clipRects)\n pendingClip = false\n currentPath = []\n return\n }\n pendingClip = false\n const effWidth = lineWidth * ctmScale()\n const outs = [horizontals, verticals, shortH, shortV, thinShortH, thinShortV]\n const white = (fromFill ? fillColor : strokeColor) === 0xffffff\n const from = white || areaFill ? outs.map(o => o.length) : undefined\n currentPath.forEach((seg, k) => {\n const role = segRole[k]\n if (role === \"hidden\") return\n if (role === \"thin\") {\n const nh = horizontals.length, nv = verticals.length\n classifyAndAdd(seg, effWidth, horizontals, verticals, fromFill, { h: thinShortH, v: thinShortV })\n for (const l of [...horizontals.slice(nh), ...verticals.slice(nv)]) thinFill.add(l)\n return\n }\n classifyAndAdd(seg, effWidth, horizontals, verticals, fromFill, fromFill ? undefined : { h: shortH, v: shortV })\n })\n // 채움 영역 윤곽은 얇은 채움 괘선(thinFill·짧은 조각 thinShort)을 빼고\n if (from) outs.forEach((o, k) => { for (let n = from[k]; n < o.length; n++) if (white || (k < 2 && !thinFill.has(o[n]))) nonRules.add(o[n]) })\n currentPath = []\n }\n\n for (let i = 0; i < fnArray.length; i++) {\n const op = fnArray[i]\n const args = argsArray[i]\n\n switch (op) {\n case OPS.setLineWidth:\n lineWidth = (args as number[])[0] || 1\n break\n\n case OPS.save:\n ctmStack.push(ctm.slice())\n colorStack.push(fillColor)\n strokeColorStack.push(strokeColor)\n alphaStack.push([fillAlpha, strokeAlpha])\n break\n\n case OPS.restore:\n ctm = ctmStack.pop() ?? [1, 0, 0, 1, 0, 0]\n fillColor = colorStack.pop() ?? 0\n strokeColor = strokeColorStack.pop() ?? 0\n ;[fillAlpha, strokeAlpha] = alphaStack.pop() ?? [1, 1]\n break\n\n case OPS.setGState: {\n // pdfjs 인자는 [[키, 값], …] 목록 — 불투명도 ca(채움)·CA(획)만 본다\n const entries = (args as unknown[])[0]\n if (!Array.isArray(entries)) break\n for (const e of entries) {\n if (!Array.isArray(e) || typeof e[1] !== \"number\") continue\n if (e[0] === \"ca\") fillAlpha = e[1]\n else if (e[0] === \"CA\") strokeAlpha = e[1]\n }\n break\n }\n\n case OPS.setFillRGBColor: {\n const c = args as unknown as ArrayLike<number>\n fillColor = (c[0] << 16) | (c[1] << 8) | c[2]\n break\n }\n case OPS.setStrokeRGBColor: {\n const c = args as unknown as ArrayLike<number>\n strokeColor = (c[0] << 16) | (c[1] << 8) | c[2]\n break\n }\n\n case OPS.transform:\n case OPS.paintFormXObjectBegin: {\n // Form XObject 는 /Matrix 를 CTM 에 곱한 공간에서 그린다(End 에서 복원) — 무시하면 0.7배 폼 안 4×3 표 괘선이 글과\n // 어긋나 뭉개졌다. pdfjs 인자는 [matrix, bbox], matrix 없으면 항등(vector-glyphs.ts 와 같은 처리)\n let t = args as number[]\n if (op === OPS.paintFormXObjectBegin) {\n ctmStack.push(ctm.slice())\n colorStack.push(fillColor)\n strokeColorStack.push(strokeColor)\n alphaStack.push([fillAlpha, strokeAlpha])\n const m = (args as unknown[])[0]\n if (!Array.isArray(m) || m.length < 6) break\n t = m as number[]\n }\n ctm = [\n ctm[0] * t[0] + ctm[2] * t[1],\n ctm[1] * t[0] + ctm[3] * t[1],\n ctm[0] * t[2] + ctm[2] * t[3],\n ctm[1] * t[2] + ctm[3] * t[3],\n ctm[0] * t[4] + ctm[2] * t[5] + ctm[4],\n ctm[1] * t[4] + ctm[3] * t[5] + ctm[5],\n ]\n break\n }\n case OPS.paintFormXObjectEnd:\n ctm = ctmStack.pop() ?? [1, 0, 0, 1, 0, 0]\n fillColor = colorStack.pop() ?? 0\n strokeColor = strokeColorStack.pop() ?? 0\n ;[fillAlpha, strokeAlpha] = alphaStack.pop() ?? [1, 1]\n break\n\n case OPS.constructPath: {\n const arg0 = args[0]\n\n if (Array.isArray(arg0)) {\n // ── pdfjs-dist v4 형식 ──\n const subOps = arg0 as number[]\n const coords = (args as [number[], number[]])[1]\n let ci = 0\n\n for (const subOp of subOps) {\n if (subOp === OPS.moveTo) {\n curX = coords[ci++]; curY = coords[ci++]\n pathStartX = curX; pathStartY = curY\n } else if (subOp === OPS.lineTo) {\n const x2 = coords[ci++], y2 = coords[ci++]\n pushSeg(curX, curY, x2, y2)\n curX = x2; curY = y2\n } else if (subOp === OPS.rectangle) {\n const rx = coords[ci++], ry = coords[ci++]\n const rw = coords[ci++], rh = coords[ci++]\n pushRectangle(rx, ry, rw, rh)\n } else if (subOp === OPS.closePath) {\n if (curX !== pathStartX || curY !== pathStartY) {\n pushSeg(curX, curY, pathStartX, pathStartY)\n }\n curX = pathStartX; curY = pathStartY\n } else if (subOp === OPS.curveTo) {\n ci += 6\n } else if (subOp === OPS.curveTo2 || subOp === OPS.curveTo3) {\n ci += 4\n }\n }\n } else {\n // ── pdfjs-dist v5 형식 ──\n const afterOp = arg0 as number\n const dataArr = args[1] as unknown[]\n const pathData = dataArr?.[0] as Record<number, number> | undefined\n if (pathData && typeof pathData === \"object\") {\n const len = Object.keys(pathData).length\n let di = 0\n while (di < len) {\n const drawOp = pathData[di++]\n if (drawOp === DrawOPS.moveTo) {\n curX = pathData[di++]; curY = pathData[di++]\n pathStartX = curX; pathStartY = curY\n } else if (drawOp === DrawOPS.lineTo) {\n const x2 = pathData[di++], y2 = pathData[di++]\n pushSeg(curX, curY, x2, y2)\n curX = x2; curY = y2\n } else if (drawOp === DrawOPS.curveTo) {\n di += 6\n } else if (drawOp === DrawOPS.quadraticCurveTo) {\n di += 4\n } else if (drawOp === DrawOPS.closePath) {\n if (curX !== pathStartX || curY !== pathStartY) {\n pushSeg(curX, curY, pathStartX, pathStartY)\n }\n curX = pathStartX; curY = pathStartY\n } else {\n break\n }\n }\n }\n\n if (afterOp === OPS.stroke || afterOp === OPS.closeStroke) {\n flushPath(true)\n } else if (afterOp === OPS.fill || afterOp === OPS.eoFill) {\n flushPath(true, true) // 순수 fill — 그라디언트 밴드 스택(스테일 폭)\n } else if (afterOp === OPS.fillStroke || afterOp === OPS.eoFillStroke ||\n afterOp === OPS.closeFillStroke || afterOp === OPS.closeEOFillStroke) {\n flushPath(true, false, true) // fill+stroke — stroke 폭 유효\n } else if (afterOp === OPS.endPath) {\n flushPath(false)\n }\n }\n break\n }\n\n case OPS.stroke:\n case OPS.closeStroke:\n flushPath(true)\n break\n\n case OPS.fill:\n case OPS.eoFill:\n flushPath(true, true) // 순수 fill — 그라디언트 밴드 스택(스테일 폭)\n break\n case OPS.fillStroke:\n case OPS.eoFillStroke:\n case OPS.closeFillStroke:\n case OPS.closeEOFillStroke:\n flushPath(true, false, true) // fill+stroke — stroke 폭 유효\n break\n\n case OPS.endPath:\n flushPath(false)\n break\n\n case OPS.clip:\n case OPS.eoClip:\n pendingClip = true\n break\n }\n }\n\n return {\n horizontals: chainShortSegments(horizontals, thinShortH, \"h\", l => thinFill.has(l)),\n verticals: chainShortSegments(verticals, thinShortV, \"v\", l => thinFill.has(l)),\n clipRects, fillRects, hiddenBoxes, shortH, shortV, nonRules,\n }\n}\n\n// ─── 짧은 괘선 조각 잇기 ──────────────────────────────\n\n/** 같은 괘선으로 보는 좌표 차 (pt) */\nconst SHORT_CHAIN_POS_TOL = 0.5\n/** 끝이 맞닿았다고 보는 간격 (pt) — 행마다 끊어 그은 실선 조각은 끝 좌표가 같다(부천 예산서 0). 한컴 점선 테두리\n * (0.5pt 획·0.7pt 간격)를 실선으로 이으면 테두리 \"없음\" 상자가 1칸 틀이 되어 안쪽 표가 한 단계 더 중첩된다\n * (rhwp rowbreak-problem-pages 13쪽 발췌 상자 실측) */\nconst SHORT_CHAIN_GAP = 0.2\n\n/**\n * 짧은 괘선 조각 잇기 — 괘선을 행(열)마다 짧게 끊어 긋는 제작기(지자체 예산 시스템: 재원 \"국·균·도·시\" 행마다 12pt 세로\n * 조각)는 조각이 MIN_LINE_LENGTH 미만이라 전부 버려져 표 세로선에 48pt 구멍이 나고, 격자가 여러 조각 표·클러스터 표로\n * 쪼개진다(부천 세출예산사업명세서 1쪽: 26x5·10x6·6x5). 같은 좌표에서 끝이 맞닿은 획 조각 사슬에 짧은 조각이 하나라도\n * 끼면 사슬 전체를 한 선으로 잇는다 — 칸 테두리 판정(cell-extract hasVerticalLine)이 선분 하나가 칸 높이 75% 를 덮어야\n * 하므로 긴 조각까지 함께 이어야 병합 행의 세로 테두리가 선다. 짧은 조각이 없는 사슬은 종전대로 둔다(긴 선분끼리의 물리\n * 병합은 셀 배치를 바꾼 실측이 있다 — chainCollinearRules 주석). 채움 경로(배경 사각형 변·글자 윤곽)는 대상이 아니다.\n * 이은 사슬이 여전히 짧으면(체크박스 테두리 같은 장식) 버린다. 호출측(page-blocks)은 칸 클립 격자가 없는 쪽에서만 부른다.\n * 얇은(≤1.5pt) 채움 사각형 괘선은 `chainable` 로 따로 extractLines 끝에서 잇는다 — 채움으로 그은 괘선이라 획 조각과 섞지 않는다.\n */\nexport function chainShortSegments(longs: LineSegment[], shorts: LineSegment[], dir: \"h\" | \"v\", chainable = (l: LineSegment) => !l.fromFill): LineSegment[] {\n if (shorts.length === 0) return longs\n const pos = (l: LineSegment) => (dir === \"h\" ? l.y1 : l.x1)\n const lo = (l: LineSegment) => (dir === \"h\" ? l.x1 : l.y1)\n const hi = (l: LineSegment) => (dir === \"h\" ? l.x2 : l.y2)\n const shortSet = new Set(shorts)\n const all = [...longs.filter(chainable), ...shorts].sort((a, b) => pos(a) - pos(b) || lo(a) - lo(b))\n const absorbed = new Set<LineSegment>()\n const chained: LineSegment[] = []\n const flush = (chain: LineSegment[]) => {\n if (!chain.some(l => shortSet.has(l))) return\n let s = Infinity, e = -Infinity, p = 0, w = 0\n for (const l of chain) { s = Math.min(s, lo(l)); e = Math.max(e, hi(l)); p += pos(l); w = Math.max(w, l.lineWidth) }\n if (e - s < MIN_LINE_LENGTH) return\n p /= chain.length\n for (const l of chain) absorbed.add(l)\n const fill = chain.every(l => l.fromFill) ? { fromFill: true } : {}\n chained.push(dir === \"h\" ? { x1: s, y1: p, x2: e, y2: p, lineWidth: w, ...fill } : { x1: p, y1: s, x2: p, y2: e, lineWidth: w, ...fill })\n }\n let band: LineSegment[] = []\n const flushBand = () => {\n band.sort((a, b) => lo(a) - lo(b))\n let chain: LineSegment[] = []\n let end = -Infinity\n for (const l of band) {\n if (chain.length && lo(l) > end + SHORT_CHAIN_GAP) { flush(chain); chain = [] }\n chain.push(l)\n end = chain.length === 1 ? hi(l) : Math.max(end, hi(l))\n }\n if (chain.length) flush(chain)\n band = []\n }\n for (const l of all) {\n if (band.length && pos(l) - pos(band[0]) > SHORT_CHAIN_POS_TOL) flushBand()\n band.push(l)\n }\n if (band.length) flushBand()\n if (chained.length === 0) return longs\n return [...longs.filter(l => !absorbed.has(l)), ...chained]\n}\n\n// ─── 클립 사각형 → 셀 그리드 선 ──────────────────────\n\n/** 클립 경로의 축 정렬 사각형 (CTM 적용, 페이지 좌표) */\nexport interface ClipRect { x1: number; y1: number; x2: number; y2: number }\n\n/** 클립 사각형 최소 치수 (pt) — 셀 판정은 clip-cells.ts */\nconst CLIP_MIN_W = 4\nconst CLIP_MIN_H = 2\n\n/** 경로 전체 bbox 의 짧은 변이 THIN_FILL_MAX 를 넘는가 — 두꺼운 채움 영역(칸 음영)과 채움으로 그은 얇은 괘선을 가른다 */\nfunction thickPath(path: Array<{ x1: number; y1: number; x2: number; y2: number }>): boolean {\n if (!path.length) return false\n let x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity\n for (const s of path) {\n x1 = Math.min(x1, s.x1, s.x2); x2 = Math.max(x2, s.x1, s.x2)\n y1 = Math.min(y1, s.y1, s.y2); y2 = Math.max(y2, s.y1, s.y2)\n }\n return Math.min(x2 - x1, y2 - y1) > THIN_FILL_MAX\n}\n\n/** 현재 경로가 축 정렬 사각형(3~5 세그먼트, 전부 수평/수직)이면 bbox 를 등록 */\nfunction captureClipRect(path: Array<{ x1: number; y1: number; x2: number; y2: number }>, out: ClipRect[], minW = CLIP_MIN_W, minH = CLIP_MIN_H): void {\n if (path.length < 3 || path.length > 5) return\n let x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity\n for (const s of path) {\n const dx = Math.abs(s.x2 - s.x1), dy = Math.abs(s.y2 - s.y1)\n if (dx > ORIENTATION_TOL && dy > ORIENTATION_TOL) return // 사선/곡선 클립\n x1 = Math.min(x1, s.x1, s.x2); x2 = Math.max(x2, s.x1, s.x2)\n y1 = Math.min(y1, s.y1, s.y2); y2 = Math.max(y2, s.y1, s.y2)\n }\n if (x2 - x1 < minW || y2 - y1 < minH) return\n out.push({ x1, y1, x2, y2 })\n}\n\n\n\nfunction classifyAndAdd(\n seg: { x1: number; y1: number; x2: number; y2: number },\n lineWidth: number,\n horizontals: LineSegment[],\n verticals: LineSegment[],\n fromFill = false,\n short?: { h: LineSegment[]; v: LineSegment[] },\n) {\n const dx = Math.abs(seg.x2 - seg.x1)\n const dy = Math.abs(seg.y2 - seg.y1)\n const length = Math.sqrt(dx * dx + dy * dy)\n\n if (length < MIN_LINE_LENGTH) {\n // 짧은 획 조각은 사슬 잇기 후보로만 (chainShortSegments) — 기울어진 조각·점은 제외\n if (!short || length <= 0) return\n if (dy <= ORIENTATION_TOL && dx > dy) {\n const y = (seg.y1 + seg.y2) / 2\n short.h.push({ x1: Math.min(seg.x1, seg.x2), y1: y, x2: Math.max(seg.x1, seg.x2), y2: y, lineWidth, fromFill })\n } else if (dx <= ORIENTATION_TOL && dy > dx) {\n const x = (seg.x1 + seg.x2) / 2\n short.v.push({ x1: x, y1: Math.min(seg.y1, seg.y2), x2: x, y2: Math.max(seg.y1, seg.y2), lineWidth, fromFill })\n }\n return\n }\n\n if (dy <= ORIENTATION_TOL) {\n const y = (seg.y1 + seg.y2) / 2\n const x1 = Math.min(seg.x1, seg.x2)\n const x2 = Math.max(seg.x1, seg.x2)\n horizontals.push({ x1, y1: y, x2, y2: y, lineWidth, fromFill })\n } else if (dx <= ORIENTATION_TOL) {\n const x = (seg.x1 + seg.x2) / 2\n const y1 = Math.min(seg.y1, seg.y2)\n const y2 = Math.max(seg.y1, seg.y2)\n verticals.push({ x1: x, y1, x2: x, y2, lineWidth, fromFill })\n }\n}\n\n// ─── 선 전처리 파이프라인 (ODL LinesPreprocessingConsumer 포팅) ──\n\n/**\n * 선 전처리: 굵은 선 필터 → 음영 스택 제거 → 근접 선 병합 → 장식선 필터링\n * ODL의 LinesPreprocessingConsumer가 하는 핵심 로직.\n */\nexport function preprocessLines(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n nonRules?: Set<LineSegment>,\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\n // 1. 굵은 선 필터링 (배경 채움 사각형, 장식 테두리 등)\n let h = horizontals.filter(l => l.lineWidth <= MAX_LINE_WIDTH)\n let v = verticals.filter(l => l.lineWidth <= MAX_LINE_WIDTH)\n\n // 1.5. 음영/그라디언트 스택 제거 — 근접 병합 전에 해야 실제 괘선을 삼키지 않음\n h = dropShadingStacks(h, \"h\")\n v = dropShadingStacks(v, \"v\")\n\n // 2. 근접 평행 선 병합 (인쇄 잔상, 이중선)\n h = mergeParallelLines(h, \"h\", nonRules)\n v = mergeParallelLines(v, \"v\", nonRules)\n\n return { horizontals: h, verticals: v }\n}\n\n// ─── 음영 스택 필터 ───────────────────────────────────\n\n/** 음영 스택 판정: 같은 범위 평행선의 연속 간격이 이 값 미만 (실제 괘선 행 높이는 6pt+) */\nconst STACK_GAP = 2\n/** 음영 스택 최소 줄 수 — 괘선 이중 인쇄(2줄)와 구분 */\nconst STACK_MIN_LINES = 6\n\n/**\n * 음영/그라디언트 스택 제거 — 한컴 PDF는 글상자 배경 그라디언트를 같은 범위의\n * 가는 평행선 수십 개(0.5pt 간격)로 그린다. 이대로 두면 근접 평행 선 병합이\n * 스택과 함께 주변의 실제 상하 테두리까지 연쇄 흡수해 박스 괘선이 소실된다.\n */\nfunction dropShadingStacks(lines: LineSegment[], dir: \"h\" | \"v\"): LineSegment[] {\n if (lines.length < STACK_MIN_LINES) return lines\n\n // 같은 (시작~끝) 범위끼리 그룹 — 스택은 같은 그리기 루프 산물이라 좌표가 동일\n const groups = new Map<string, LineSegment[]>()\n for (const l of lines) {\n const key = dir === \"h\"\n ? `${Math.round(l.x1)}:${Math.round(l.x2)}`\n : `${Math.round(l.y1)}:${Math.round(l.y2)}`\n const arr = groups.get(key)\n if (arr) arr.push(l)\n else groups.set(key, [l])\n }\n\n const dropped = new Set<LineSegment>()\n const coord = (l: LineSegment): number => (dir === \"h\" ? l.y1 : l.x1)\n for (const group of groups.values()) {\n if (group.length < STACK_MIN_LINES) continue\n group.sort((a, b) => coord(a) - coord(b))\n let runStart = 0\n for (let i = 1; i <= group.length; i++) {\n const gap = i < group.length ? coord(group[i]) - coord(group[i - 1]) : Infinity\n if (gap < STACK_GAP) continue\n if (i - runStart >= STACK_MIN_LINES) {\n // 말단 트리밍 — 스택은 같은 그리기 루프 산물이라 lineWidth·줄간격(pitch)이\n // 균일하다. 지배 폭과 다르거나 내부 pitch보다 크게 벌어진 말단 줄은\n // 플러시(패딩 0) 글상자의 실제 상하 테두리이므로 스택과 함께 삼키지 않는다.\n let s = runStart\n let e = i - 1\n const wKey = (l: LineSegment): number => Math.round(l.lineWidth * 100)\n const wCount = new Map<number, number>()\n for (let j = s; j <= e; j++) wCount.set(wKey(group[j]), (wCount.get(wKey(group[j])) ?? 0) + 1)\n let domW = 0\n let domN = 0\n for (const [w, n] of wCount) if (n > domN) { domW = w; domN = n }\n const pitches: number[] = []\n for (let j = s + 1; j <= e; j++) pitches.push(coord(group[j]) - coord(group[j - 1]))\n pitches.sort((a, b) => a - b)\n const medPitch = pitches[Math.floor(pitches.length / 2)] ?? 0\n // 스택 본체가 fill 유래(그라디언트)면, stroke 로 그은 말단(글상자 실 테두리)은\n // fromFill 이 달라, 스테일 폭이 우연히 스택과 같아도 트리밍해 보존한다 (pline-3)\n let fillN = 0\n for (let j = s; j <= e; j++) if (group[j].fromFill) fillN++\n const stackIsFill = fillN * 2 > (e - s + 1)\n const edgeAlien = (j: number, inwardGap: number): boolean =>\n (stackIsFill && !group[j].fromFill)\n || wKey(group[j]) !== domW || (medPitch > 0 && inwardGap > medPitch * 1.8)\n while (e - s + 1 >= STACK_MIN_LINES) {\n if (edgeAlien(s, coord(group[s + 1]) - coord(group[s]))) { s++; continue }\n if (edgeAlien(e, coord(group[e]) - coord(group[e - 1]))) { e--; continue }\n break\n }\n if (e - s + 1 >= STACK_MIN_LINES) {\n for (let j = s; j <= e; j++) dropped.add(group[j])\n }\n }\n runStart = i\n }\n }\n return dropped.size ? lines.filter(l => !dropped.has(l)) : lines\n}\n\n// ─── 개방 변 표 테두리 합성 ──────────────────────────\n\n/** 수평 괘선 끝점 정렬 그룹 허용 오차 (pt) */\nconst EDGE_ALIGN_TOL = 3\n/** 합성 최소 괘선 수 (3줄 = 2행 이상) */\nconst EDGE_MIN_RULES = 3\n/** 그룹 y-스팬 최소 (2행 × 최소 행 높이) — 장식 겹줄 배제 */\nconst EDGE_MIN_SPAN = 12\n/** 내부 수직선 판정: 변에서 이만큼 안쪽 (table-grid MIN_COL_WIDTH와 동일) */\nconst EDGE_INSET = 15\n/** 기존 수직선이 이 거리 안에 있으면 그 변은 이미 닫힌 것 */\nconst EDGE_NEAR = 10\n/** 선 교차 판정 여유 (table-grid CONNECT_TOL과 동일) */\nconst EDGE_CONNECT_TOL = 5\n/** 상하 스택 표 분할: 그룹 내 세로 간격이 중앙값의 이 배를 넘으면 표 경계로 보고 쪼갬 */\nconst EDGE_YGAP_SPLIT_K = 2.5\n/** y-간격 분할 절대 하한(pt) — 이중괘선 중앙값 붕괴 시 정상 행간을 표 경계로 오인하지 않게 */\nconst EDGE_YGAP_ABS_MIN = 30\n/** 체인 뷰: 같은 논리 괘선으로 묶는 y 허용 오차 (pt) */\nconst CHAIN_Y_TOL = 1.5\n/** 체인 뷰: 콜리니어 세그먼트 연결 최대 간격 (pt) */\nconst CHAIN_GAP = 3\n\n/**\n * 체인 뷰 — 같은 y의 콜리니어 세그먼트(셀 단위로 쪼개 그은 괘선)를 논리\n * 괘선 하나로 잇는다. closeOpenTableEdges의 끝점 정렬 판정 전용이며 물리\n * 수평선은 수정하지 않는다 (물리 병합은 셀 배치 변질 실측으로 폐기, 9차).\n * 근접 평행 겹줄(장식 이중선)도 한 논리 괘선으로 흡수된다.\n */\nexport function chainCollinearRules(horizontals: LineSegment[]): LineSegment[] {\n if (horizontals.length <= 1) return horizontals\n const sorted = [...horizontals].sort((a, b) => a.y1 - b.y1 || a.x1 - b.x1)\n const rules: LineSegment[] = []\n let bandStart = 0\n const flushBand = (end: number) => {\n const band = sorted.slice(bandStart, end).sort((a, b) => a.x1 - b.x1)\n let cur = { ...band[0] }\n for (let i = 1; i < band.length; i++) {\n const seg = band[i]\n if (seg.x1 - cur.x2 <= CHAIN_GAP) {\n if (seg.x2 > cur.x2) cur.x2 = seg.x2\n if (seg.lineWidth > cur.lineWidth) cur.lineWidth = seg.lineWidth\n } else {\n rules.push(cur)\n cur = { ...seg }\n }\n }\n rules.push(cur)\n }\n for (let i = 1; i <= sorted.length; i++) {\n if (i === sorted.length || sorted[i].y1 - sorted[bandStart].y1 > CHAIN_Y_TOL) {\n flushBand(i)\n bandStart = i\n }\n }\n return rules\n}\n\n/**\n * 개방 변 표 테두리 합성 — 한국 행정문서 표는 좌/우 바깥 테두리를 생략하는\n * 스타일이 흔하다 (수평 괘선은 전폭으로 긋고 수직선은 내부 구분선만).\n * 교차점(Vertex) 기반 그리드 구성은 수직선 없는 변의 열을 통째로 잃으므로,\n * 끝점이 정렬된 수평 괘선 묶음(≥3줄)에 그 묶음 괘선 2개 이상과 교차하는 내부\n * 수직선이 실존할 때에 한해 끝점 x에 가상 수직 테두리를 합성해 그리드를 닫는다.\n * 판정은 체인 뷰(콜리니어 세그먼트를 이은 논리 괘선) 기준 — 셀 단위로 쪼개\n * 그은 중간 괘선도 끝점 정렬 그룹에 합류한다.\n *\n * 전역 끝점 그룹핑이라 폭이 비슷한 표가 위아래로 쌓인 페이지에선 그룹 하나로\n * 뭉쳐 y-범위가 넓어지고, 가장자리에 이미 수직선이 있는 경우가 많아 발동이\n * 보수적이다 — 의도된 안전 특성 (컴포넌트 단위 정밀화는 실측에서 demote 연쇄\n * 부작용으로 보류, 9차 세션 기록 참조).\n */\nexport function closeOpenTableEdges(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n): LineSegment[] {\n if (horizontals.length < EDGE_MIN_RULES) return verticals\n\n // 1) 끝점 정렬 그룹핑 (x1·x2 모두 근접) — 체인 뷰의 논리 괘선 기준\n const groups: LineSegment[][] = []\n for (const hl of chainCollinearRules(horizontals)) {\n let placed = false\n for (const g of groups) {\n if (Math.abs(g[0].x1 - hl.x1) <= EDGE_ALIGN_TOL && Math.abs(g[0].x2 - hl.x2) <= EDGE_ALIGN_TOL) {\n g.push(hl)\n placed = true\n break\n }\n }\n if (!placed) groups.push([hl])\n }\n\n // 1b) y-간격 상한 분할 — 폭이 같아 한 그룹에 묶인 상하 스택 표를, 표 사이의 큰 세로\n // 간격에서 쪼갠다(사이 본문을 관통하는 가상 테두리 용접 방지). 간격이 중앙값의\n // EDGE_YGAP_SPLIT_K배를 넘고 양쪽이 각각 최소 괘선 수를 만족할 때만 분할하므로,\n // 행 간격이 균일한 연속 단일 표에는 발동하지 않는다(무회귀).\n const splitGroups: LineSegment[][] = []\n for (const g of groups) {\n const sorted = [...g].sort((a, b) => a.y1 - b.y1)\n const gaps: number[] = []\n for (let i = 1; i < sorted.length; i++) gaps.push(sorted[i].y1 - sorted[i - 1].y1)\n const median = gaps.length ? [...gaps].sort((a, b) => a - b)[gaps.length >> 1] : 0\n const threshold = median * EDGE_YGAP_SPLIT_K\n let cur: LineSegment[] = sorted.length ? [sorted[0]] : []\n for (let i = 1; i < sorted.length; i++) {\n const yLo = sorted[i - 1].y1, yHi = sorted[i].y1\n const gap = yHi - yLo\n // 밴드 아래 행 수직선(위 끝이 밴드 하단에 닿음)과 위 행 수직선(아래 끝이 밴드 상단에\n // 닿음)이 같은 x 로 다리를 놓으면, 이 간격은 표 사이가 아니라 표 내부 병합 큰 행이므로\n // 분할하지 않는다 — 정당한 개방변 표를 두 표로 절단하던 회귀 방지 (pline-1).\n // 밴드 y구간을 관통하는 수직선(병합 셀 좌우변·내부 구분선)이 있으면 표 내부 병합 큰\n // 행이므로 분할하지 않는다. 표 사이 프로즈에는 관통 수직선이 없다 — 두 별개 표는 각자\n // 내부에서만 수직선이 끝나므로, 두 표 용접 방지(원 #3)와 양립한다 (pline-1).\n // 관통 수직선은 이 표의 가로 범위(밴드를 사이에 둔 괘선들의 x-구간) 안에 있어야\n // 다리로 인정한다 — 페이지 어디든 있는 무관한 수직선(좌측 여백선·변경바)이 정당한 표\n // 분할을 죽여 별개 표를 용접하던 것 방지 (pline-2: verticals 전역 스캔 over-reach).\n const gx1 = Math.min(sorted[i - 1].x1, sorted[i].x1)\n const gx2 = Math.max(sorted[i - 1].x2, sorted[i].x2)\n const bridged = verticals.some(vl =>\n vl.y1 <= yLo + EDGE_NEAR && vl.y2 >= yHi - EDGE_NEAR &&\n vl.x1 >= gx1 - EDGE_CONNECT_TOL && vl.x1 <= gx2 + EDGE_CONNECT_TOL)\n if (median > 0 && gap > threshold && gap > EDGE_YGAP_ABS_MIN && !bridged\n && cur.length >= EDGE_MIN_RULES && sorted.length - i >= EDGE_MIN_RULES) {\n splitGroups.push(cur)\n cur = []\n }\n cur.push(sorted[i])\n }\n if (cur.length) splitGroups.push(cur)\n }\n\n const synthesized: LineSegment[] = []\n for (const g of splitGroups) {\n if (g.length < EDGE_MIN_RULES) continue\n let yMin = Infinity, yMax = -Infinity, x1 = 0, x2 = 0\n for (const hl of g) {\n if (hl.y1 < yMin) yMin = hl.y1\n if (hl.y1 > yMax) yMax = hl.y1\n x1 += hl.x1\n x2 += hl.x2\n }\n x1 /= g.length\n x2 /= g.length\n if (yMax - yMin < EDGE_MIN_SPAN) continue\n\n // 2) 내부 수직선 실존 확인 — 그룹 괘선 2개 이상과 교차해야 표 기하\n const crossCount = (v: LineSegment) => {\n let n = 0\n for (const hl of g) {\n if (v.x1 >= hl.x1 - EDGE_CONNECT_TOL && v.x1 <= hl.x2 + EDGE_CONNECT_TOL &&\n hl.y1 >= v.y1 - EDGE_CONNECT_TOL && hl.y1 <= v.y2 + EDGE_CONNECT_TOL) n++\n }\n return n\n }\n const hasInterior = verticals.some(v =>\n v.x1 > x1 + EDGE_INSET && v.x1 < x2 - EDGE_INSET && crossCount(v) >= 2)\n if (!hasInterior) continue\n\n // 3) 변마다 근처에 기존 수직선이 없으면 가상 테두리 합성\n for (const edgeX of [x1, x2]) {\n const closed = verticals.some(v =>\n Math.abs(v.x1 - edgeX) <= EDGE_NEAR &&\n v.y1 <= yMax + EDGE_CONNECT_TOL && v.y2 >= yMin - EDGE_CONNECT_TOL)\n if (!closed) {\n synthesized.push({ x1: edgeX, y1: yMin, x2: edgeX, y2: yMax, lineWidth: 0.5 })\n }\n }\n }\n\n return synthesized.length ? [...verticals, ...synthesized] : verticals\n}\n\n\n/**\n * 근접 평행 선 병합 — 같은 방향의 가까운 선을 하나로 합침.\n * 이중선, 인쇄 잔상, PDF 렌더링 미세 차이로 인한 중복 선 제거.\n */\nfunction mergeParallelLines(lines: LineSegment[], dir: \"h\" | \"v\", nonRules?: Set<LineSegment>): LineSegment[] {\n if (lines.length <= 1) return lines\n\n // 수평선: y로 정렬, 수직선: x로 정렬\n const sorted = [...lines].sort((a, b) => {\n const posA = dir === \"h\" ? a.y1 : a.x1\n const posB = dir === \"h\" ? b.y1 : b.x1\n if (Math.abs(posA - posB) > 0.1) return posA - posB\n // 같은 위치면 시작 좌표로\n return dir === \"h\" ? (a.x1 - b.x1) : (a.y1 - b.y1)\n })\n\n const MERGE_TOL = 3 // 3pt 이내 평행 선 병합\n\n const result: LineSegment[] = [sorted[0]]\n for (let i = 1; i < sorted.length; i++) {\n const prev = result[result.length - 1]\n const curr = sorted[i]\n\n const prevPos = dir === \"h\" ? prev.y1 : prev.x1\n const currPos = dir === \"h\" ? curr.y1 : curr.x1\n\n if (Math.abs(prevPos - currPos) <= MERGE_TOL) {\n // 범위가 겹치는지 확인\n const prevStart = dir === \"h\" ? prev.x1 : prev.y1\n const prevEnd = dir === \"h\" ? prev.x2 : prev.y2\n const currStart = dir === \"h\" ? curr.x1 : curr.y1\n const currEnd = dir === \"h\" ? curr.x2 : curr.y2\n\n const overlap = Math.min(prevEnd, currEnd) - Math.max(prevStart, currStart)\n const minLen = Math.min(prevEnd - prevStart, currEnd - currStart)\n\n if (overlap > minLen * 0.3) {\n // A visible stroke sharing a background edge remains visible after merging.\n if (nonRules?.has(prev) && !nonRules.has(curr)) nonRules.delete(prev)\n // 병합: 범위 확장, lineWidth는 최대값 유지\n if (dir === \"h\") {\n prev.x1 = Math.min(prev.x1, curr.x1)\n prev.x2 = Math.max(prev.x2, curr.x2)\n prev.y1 = (prev.y1 + curr.y1) / 2\n prev.y2 = prev.y1\n } else {\n prev.y1 = Math.min(prev.y1, curr.y1)\n prev.y2 = Math.max(prev.y2, curr.y2)\n prev.x1 = (prev.x1 + curr.x1) / 2\n prev.x2 = prev.x1\n }\n prev.lineWidth = Math.max(prev.lineWidth, curr.lineWidth)\n continue\n }\n }\n result.push(curr)\n }\n return result\n}\n\n// ─── 페이지 경계(클립) 선 필터링 ──────────────────────\n\nexport function filterPageBorderLines(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n pageWidth: number,\n pageHeight: number,\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\n const margin = 5\n return {\n horizontals: horizontals.filter(l =>\n !(Math.abs(l.y1) < margin || Math.abs(l.y1 - pageHeight) < margin) ||\n (l.x2 - l.x1) < pageWidth * 0.9\n ),\n verticals: verticals.filter(l =>\n !(Math.abs(l.x1) < margin || Math.abs(l.x1 - pageWidth) < margin) ||\n (l.y2 - l.y1) < pageHeight * 0.9\n ),\n }\n}\n","/**\n * 페이지 내 이미지 XObject 영역 추출 (정보손실 가시화용) — line-detector.ts에서 분리.\n */\n\nimport { OPS } from \"pdfjs-dist/legacy/build/pdf.mjs\"\n\nexport interface ImageRegion {\n x1: number; y1: number; x2: number; y2: number\n}\n\n/** 2D 어파인 행렬 곱 — t 적용 후 m 적용 (pdfjs Util.transform과 동일 순서) */\nfunction multiplyTransform(m: number[], t: number[]): number[] {\n return [\n m[0] * t[0] + m[2] * t[1],\n m[1] * t[0] + m[3] * t[1],\n m[0] * t[2] + m[2] * t[3],\n m[1] * t[2] + m[3] * t[3],\n m[0] * t[4] + m[2] * t[5] + m[4],\n m[1] * t[4] + m[3] * t[5] + m[5],\n ]\n}\n\nfunction transformedRect(ctm: number[], x: number, y: number, w: number, h: number): ImageRegion {\n const corners = [[x, y], [x + w, y], [x, y + h], [x + w, y + h]]\n const xs = corners.map(([u, v]) => ctm[0] * u + ctm[2] * v + ctm[4])\n const ys = corners.map(([u, v]) => ctm[1] * u + ctm[3] * v + ctm[5])\n return { x1: Math.min(...xs), y1: Math.min(...ys), x2: Math.max(...xs), y2: Math.max(...ys) }\n}\n\nfunction intersect(a: ImageRegion, b: ImageRegion | null): ImageRegion {\n return b ? { x1: Math.max(a.x1, b.x1), y1: Math.max(a.y1, b.y1), x2: Math.min(a.x2, b.x2), y2: Math.min(a.y2, b.y2) } : a\n}\n\n/**\n * pdfjs operatorList에서 이미지 paint 영역을 추출.\n * save/restore/transform으로 CTM을 추적하고, 이미지는 단위 정사각형(0,0)-(1,1)에\n * CTM을 적용한 bbox로 계산한다 (PDF 이미지 렌더링 규약).\n * respectClip은 읽기 순서용 가시 영역. OCR·그림 칸 판정은 기존 원본 영역을 유지한다.\n */\nexport function extractImageRegions(\n fnArray: Uint32Array | number[],\n argsArray: unknown[][],\n respectClip = false,\n): ImageRegion[] {\n const regions: ImageRegion[] = []\n let ctm = [1, 0, 0, 1, 0, 0]\n let clip: ImageRegion | null = null\n const stack: Array<{ ctm: number[]; clip: ImageRegion | null }> = []\n // 단일 사각형만 확정한다. 복합·곡선 경로는 보수적으로 기존 영역을 유지한다.\n let pathRect: ImageRegion | null | undefined\n let pendingClip = false\n\n for (let i = 0; i < fnArray.length; i++) {\n const op = fnArray[i]\n switch (op) {\n case OPS.save:\n stack.push({ ctm, clip })\n break\n case OPS.restore: {\n const state = stack.pop()\n ctm = state?.ctm ?? [1, 0, 0, 1, 0, 0]\n clip = state?.clip ?? null\n break\n }\n case OPS.transform: {\n const t = argsArray[i] as number[]\n if (Array.isArray(t) && t.length >= 6) ctm = multiplyTransform(ctm, t)\n break\n }\n // Form XObject 는 /Matrix 공간에서 그린다 — pdfjs 인자 [matrix, bbox], End 에서 복원 (line-extract.ts 와 같음)\n case OPS.paintFormXObjectBegin: {\n stack.push({ ctm, clip })\n const m = (argsArray[i] as unknown[])?.[0]\n if (Array.isArray(m) && m.length >= 6) ctm = multiplyTransform(ctm, m as number[])\n break\n }\n case OPS.paintFormXObjectEnd: {\n const state = stack.pop()\n ctm = state?.ctm ?? [1, 0, 0, 1, 0, 0]\n clip = state?.clip ?? null\n break\n }\n case OPS.constructPath: {\n const [ops, coords] = argsArray[i] as number[][]\n pathRect = pathRect === undefined && ops?.length === 1 && ops[0] === OPS.rectangle && coords?.length >= 4\n ? transformedRect(ctm, coords[0], coords[1], coords[2], coords[3]) : null\n break\n }\n case OPS.clip: case OPS.eoClip:\n pendingClip = true\n break\n case OPS.endPath: case OPS.stroke: case OPS.closeStroke: case OPS.fill: case OPS.eoFill:\n case OPS.fillStroke: case OPS.eoFillStroke: case OPS.closeFillStroke: case OPS.closeEOFillStroke:\n if (respectClip && pendingClip && pathRect) clip = intersect(pathRect, clip)\n pathRect = undefined\n pendingClip = false\n break\n case OPS.paintImageXObject:\n case OPS.paintInlineImageXObject:\n case OPS.paintImageMaskXObject:\n case OPS.paintImageXObjectRepeat: {\n const { x1, y1, x2, y2 } = intersect(transformedRect(ctm, 0, 0, 1, 1), clip)\n if (x2 - x1 > 0 && y2 - y1 > 0) regions.push({ x1, y1, x2, y2 })\n break\n }\n }\n }\n return regions\n}\n","/**\n * 열 수직선 브리지 (line-detector.ts 계열 전처리).\n *\n * 예산서류 대형 표는 요약행 밴드(재원구분 시/구 행 등)에 수직 괘선을 아예\n * 긋지 않는 스타일이 있다. 이 무괘선 밴드에서 동일 열의 수직선이 위/아래로\n * 끊기면 Union-Find 그룹이 파편화되어 표 상단(헤더행·부서/정책 요약행)이\n * 그리드에서 탈락한다 — 세출예산 사업명세서 부서명 유실의 근본원인 (실측:\n * .claude/plans/budget-dept-merge-fix.md).\n *\n * 같은 열 x에서 끊긴 수직선 쌍을, 아래 조건을 모두 만족할 때만 잇는다:\n * (a) 간격이 CONNECT_TOL 초과(이하면 이미 연결됨) ~ 상한 이내\n * (b) 간격 내부에 그 x를 가로지르는 수평선이 실존 (내용 행 증거 —\n * 별개 표 사이 빈 공간에는 내부 수평선이 없다)\n * (c) 같은 y-밴드에서 3열 이상이 동시에 끊김 (한 표의 열 구조 증거)\n * (d) 밴드 내부 수평선의 끝점이 내부 열 경계와 정합 (셀 단위로 쪼개 그은\n * 행 괘선 증거 — 표 사이 단일 전폭 구분선/제목 밑줄 오탐 방지)\n *\n * 합성 세그먼트는 간격만이 아니라 위/아래 이웃 세그먼트 전체를 덮는다 —\n * cell-extract의 hasVerticalLine이 단일 세그먼트 75% 커버 기준이라, 간격만\n * 이으면 브리지·기존 세그먼트가 각각 병합 행 밴드의 75%에 못 미친다.\n */\n\nimport type { LineSegment } from \"./line-types.js\"\nimport { chainCollinearRules } from \"./line-extract.js\"\n\n/** 같은 논리 수직선으로 묶는 x 허용 오차 (pt) — table-grid CUT_VCHAIN_X_TOL과 동일 */\nconst BRIDGE_X_TOL = 1.5\n/** 브리지 최소 간격 (pt) — table-grid CONNECT_TOL 이하는 이미 같은 그룹 */\nconst BRIDGE_MIN_GAP = 5\n/** 브리지 최대 간격 (pt) — 무괘선 밴드 상한 (실측 74.3pt, 여유 포함) */\nconst BRIDGE_MAX_GAP = 120\n/** 후보 간 같은 y-밴드 판정 허용 오차 (pt) */\nconst BRIDGE_BAND_TOL = 6\n/** 같은 밴드에서 동시에 끊긴 최소 열 수 */\nconst BRIDGE_MIN_COLUMNS = 3\n/** 수평선 끝점-열 경계 정합 판정 허용 오차 (pt) */\nconst BRIDGE_ENDPOINT_TOL = 2\n/** 밴드 내부 수평선 판정 여유 (pt) — 밴드 가장자리 선(그리드 경계선) 배제 */\nconst BRIDGE_EDGE_EPS = 2\n\ninterface BridgeCandidate {\n /** 열 x 좌표 (아래/위 세그먼트 x 평균) */\n x: number\n /** 간격 하단 (아래 세그먼트 상단 y) */\n lo: number\n /** 간격 상단 (위 세그먼트 하단 y) */\n hi: number\n /** 합성 범위 하단 (아래 세그먼트 하단 y) */\n spanLo: number\n /** 합성 범위 상단 (위 세그먼트 상단 y) */\n spanHi: number\n lineWidth: number\n}\n\n/**\n * 무괘선 밴드로 끊긴 동일 열 수직선을 합성 세그먼트로 잇는다.\n * 조건 미달이면 입력을 그대로 반환한다.\n */\nexport function bridgeSplitColumnVerticals(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n): LineSegment[] {\n if (verticals.length < 4 || horizontals.length === 0) return verticals\n\n // 1) x-밴드 그룹핑 후 각 밴드의 연속 세그먼트 쌍에서 간격 후보 수집\n const sorted = [...verticals].sort((a, b) => a.x1 - b.x1 || a.y1 - b.y1)\n const candidates: BridgeCandidate[] = []\n let bandStart = 0\n const collectBand = (end: number) => {\n const band = sorted.slice(bandStart, end).sort((a, b) => a.y1 - b.y1)\n for (let i = 1; i < band.length; i++) {\n const lower = band[i - 1], upper = band[i]\n const gap = upper.y1 - lower.y2\n if (gap <= BRIDGE_MIN_GAP || gap > BRIDGE_MAX_GAP) continue\n const x = (lower.x1 + upper.x1) / 2\n // (b) 간격 내부에 이 x를 가로지르는 수평선 실존\n const hasInteriorH = horizontals.some(h =>\n h.y1 > lower.y2 + BRIDGE_EDGE_EPS && h.y1 < upper.y1 - BRIDGE_EDGE_EPS &&\n h.x1 <= x + BRIDGE_ENDPOINT_TOL && h.x2 >= x - BRIDGE_ENDPOINT_TOL)\n if (!hasInteriorH) continue\n candidates.push({\n x, lo: lower.y2, hi: upper.y1,\n spanLo: lower.y1, spanHi: upper.y2,\n lineWidth: Math.min(lower.lineWidth, upper.lineWidth),\n })\n }\n }\n for (let i = 1; i <= sorted.length; i++) {\n if (i === sorted.length || sorted[i].x1 - sorted[bandStart].x1 > BRIDGE_X_TOL) {\n collectBand(i)\n bandStart = i\n }\n }\n if (candidates.length < BRIDGE_MIN_COLUMNS) return verticals\n\n // 2) 같은 y-밴드 후보 클러스터링\n const clusters: BridgeCandidate[][] = []\n for (const c of candidates) {\n let placed = false\n for (const cl of clusters) {\n if (Math.abs(cl[0].lo - c.lo) <= BRIDGE_BAND_TOL && Math.abs(cl[0].hi - c.hi) <= BRIDGE_BAND_TOL) {\n cl.push(c)\n placed = true\n break\n }\n }\n if (!placed) clusters.push([c])\n }\n\n const synthesized: LineSegment[] = []\n for (const cl of clusters) {\n // (c) 3열 이상 동시 단절\n if (cl.length < BRIDGE_MIN_COLUMNS) continue\n\n // (d) 밴드 내부 수평선 끝점이 내부 열 경계와 정합 — 셀 단위 행 괘선 증거.\n // 최좌/최우 열은 표 외곽이라 단일 전폭 구분선 끝점과도 일치하므로 제외.\n const xs = cl.map(c => c.x).sort((a, b) => a - b)\n const interiorXs = xs.slice(1, -1)\n const bandLo = Math.min(...cl.map(c => c.lo))\n const bandHi = Math.max(...cl.map(c => c.hi))\n const segmented = horizontals.some(h =>\n h.y1 > bandLo + BRIDGE_EDGE_EPS && h.y1 < bandHi - BRIDGE_EDGE_EPS &&\n interiorXs.some(ix =>\n Math.abs(h.x1 - ix) <= BRIDGE_ENDPOINT_TOL || Math.abs(h.x2 - ix) <= BRIDGE_ENDPOINT_TOL))\n if (!segmented) continue\n\n for (const c of cl) {\n synthesized.push({ x1: c.x, y1: c.spanLo, x2: c.x, y2: c.spanHi, lineWidth: c.lineWidth })\n }\n }\n\n return synthesized.length ? [...verticals, ...synthesized] : verticals\n}\n\n/**\n * 한 행 높이만큼 끊긴 열 수직선 잇기 — 줄무늬 표의 흰 행처럼 세로선을 한 행씩 건너뛰어 그은 표(ODL 078).\n * 같은 높이에서 셋 이상의 열이 함께 끊기고, 그 틈 위·아래에 가로 괘선이 있으며(한 행), 틈 안 글이 끊긴 열 경계를\n * 가로지르지 않을 때만 잇는다. 병합 칸(글이 경계를 넘는 행)은 그대로 둔다.\n */\nexport function bridgeSkippedRowVerticals(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n items: Array<{ x: number; y: number; w: number; h: number; fontSize: number }>,\n): LineSegment[] {\n if (verticals.length < 6 || items.length === 0) return verticals\n const byX = new Map<number, LineSegment[]>()\n for (const v of verticals) {\n const key = [...byX.keys()].find(k => Math.abs(k - v.x1) <= BRIDGE_X_TOL) ?? v.x1\n byX.set(key, [...(byX.get(key) ?? []), v])\n }\n const gaps: Array<{ x: number; lo: number; hi: number; spanLo: number; spanHi: number }> = []\n for (const [x, segs] of byX) {\n const s = [...segs].sort((a, b) => a.y1 - b.y1)\n for (let i = 1; i < s.length; i++) {\n const lo = s[i - 1].y2, hi = s[i].y1\n if (hi - lo > BRIDGE_MIN_GAP && hi - lo <= 40) gaps.push({ x, lo, hi, spanLo: s[i - 1].y1, spanHi: s[i].y2 })\n }\n }\n const rules = chainCollinearRules(horizontals)\n const added: LineSegment[] = []\n const done = new Set<typeof gaps[number]>()\n for (const g of gaps) {\n if (done.has(g)) continue\n const band = gaps.filter(o => Math.abs(o.lo - g.lo) <= BRIDGE_BAND_TOL / 3 && Math.abs(o.hi - g.hi) <= BRIDGE_BAND_TOL / 3)\n for (const o of band) done.add(o)\n if (band.length < BRIDGE_MIN_COLUMNS) continue\n const xs = band.map(o => o.x)\n const x1 = Math.min(...xs), x2 = Math.max(...xs)\n const ruled = (y: number) => rules.some(h => Math.abs(h.y1 - y) <= BRIDGE_ENDPOINT_TOL && h.x1 <= x1 + BRIDGE_ENDPOINT_TOL && h.x2 >= x2 - BRIDGE_ENDPOINT_TOL)\n if (!ruled(g.lo) || !ruled(g.hi)) continue\n const rowItems = items.filter(it => { const cy = it.y + (it.h || it.fontSize) / 2; return cy > g.lo && cy < g.hi })\n if (rowItems.length === 0 || rowItems.some(it => xs.some(x => it.x < x - 1 && it.x + it.w > x + 1))) continue\n for (const o of band) added.push({ x1: o.x, y1: o.spanLo, x2: o.x, y2: o.spanHi, lineWidth: 0.5 })\n }\n return added.length ? [...verticals, ...added] : verticals\n}\n","/**\n * 선 기반 테이블 감지 공유 타입/상수.\n * line-detector.ts에서 분리 — 알고리즘 출처는 line-detector.ts 헤더 참조.\n */\n\n// ─── 타입 ─────────────────────────────────────────────\n\nexport interface LineSegment {\n x1: number; y1: number\n x2: number; y2: number\n lineWidth: number\n /** fill 연산(그라디언트 밴드 스택)에서 나온 선분 — PDF w 연산자는 stroke 전용이라\n * fill 선분의 lineWidth 는 마지막 stroke 상태를 상속한 스테일 값이다. 음영 스택 폭\n * 판별(dropShadingStacks)이 스테일 폭에 속지 않게 표기한다 (pline-3). */\n fromFill?: boolean\n}\n\nexport interface TableGrid {\n /** 행 Y 좌표 경계 (위→아래 내림차순) */\n rowYs: number[]\n /** 열 X 좌표 경계 (좌→우 오름차순) */\n colXs: number[]\n /** 테이블 바운딩 박스 */\n bbox: { x1: number; y1: number; x2: number; y2: number }\n /** 그리드 내 교차점 반경 (동적 tolerance용) */\n vertexRadius: number\n /** 미리 확정된 셀 — 클립 사각형 그리드(clip-cells)가 채움. 있으면 extractCells 를 건너뛴다 */\n cells?: ExtractedCell[]\n /** 이 그리드를 품는 바깥 셀(틀)의 클립 사각형 — 중첩표. 소비측(page-blocks)이 그 셀의\n * `IRCell.blocks` 에 표로 넣는다 (v4.12.2, HWP 파서의 중첩표 IR 과 같은 모양) */\n clipParent?: { x1: number; y1: number; x2: number; y2: number }\n /** 칸 안에 떠 있는 닫힌 선 격자(중첩표, table-grid splitNestedBoxes) — 소비측이 그 bbox 를 품는 칸의 blocks 에 넣는다 */\n lineNested?: true\n /** 앞 쪽 칸의 이어짐인 1칸 조각이면 그 앞 쪽 칸의 클립 사각형 (clip-cells) — 문서 단계가 앞 쪽 표의 그 칸에 붙인다 */\n continues?: { x1: number; y1: number; x2: number; y2: number }\n}\n\nexport interface ExtractedCell {\n row: number; col: number\n rowSpan: number; colSpan: number\n /** 셀 바운딩 박스 */\n bbox: { x1: number; y1: number; x2: number; y2: number }\n /** 클립이 없어 격자를 메우려고 만든 1×1 칸 (clip-cells) — 원본의 칸이 아니다 */\n filler?: boolean\n}\n\nexport interface TextItem {\n text: string\n x: number; y: number; w: number; h: number\n fontSize: number; fontName: string\n /** pdfjs 공백 아이템이 이 아이템 직전에 있었음 — 단어 경계 힌트 (parser.ts NormItem에서 전파) */\n hasSpaceBefore?: boolean\n /** 직전 공백이 pdfjs 가 글자 틈으로 만든 것뿐(글리프 흐름에 공백 글리프 없음, tracked-text markSyntheticSpaces) — 균등배분 run 을 끊지 않는다 */\n syntheticSpace?: boolean\n /** 줄 끝 공백 글리프 (NormItem.spaceAfter 전파 — 칸 줄 꺾임이 어절 경계) */\n spaceAfter?: boolean\n /** 콘텐츠 스트림 순번 (NormItem.seq 전파 — 겹친 글자 순서 복원) */\n seq?: number\n}\n\n/** Vertex 기반 좌표 병합 시 radius 배수 — ODL: VERTEX_TABLE_FACTOR */\nexport const VERTEX_MERGE_FACTOR = 4\n","/**\n * 선 교차점(Vertex) 기반 테이블 그리드 구성 (line-detector.ts에서 분리).\n *\n * OpenDataLoader PDF의 TableBorderBuilder를 참고하여 TypeScript로\n * clean-room 재구현한 것입니다. Vertex 기반 동적 tolerance 포함.\n * Original algorithm: Copyright 2025-2026 Hancom, Inc. (Apache 2.0)\n * https://github.com/opendataloader-project/opendataloader-pdf\n * Core algorithm concepts from veraPDF-wcag-algs (GPLv3+/MPLv2+)\n */\n\nimport type { LineSegment, TableGrid } from \"./line-types.js\"\nimport { VERTEX_MERGE_FACTOR } from \"./line-types.js\"\n\n/** 선 교차점 (Vertex) — ODL의 핵심 개념 */\ninterface Vertex {\n x: number\n y: number\n /** 교차하는 선들의 최대 lineWidth → tolerance 계산에 사용 */\n radius: number\n}\n\n/** 두 선이 같은 테이블에 속하는지 판별하는 거리 */\nconst CONNECT_TOL = 5\n/** 최소 열 폭 (pt) — 이보다 좁은 열은 인접 열과 병합 */\nconst MIN_COL_WIDTH = 15\n/** 좁은 열 무리의 최소 열 폭 (pt) — 20pt 안 열이 셋 넘게 이어진 숫자 칸 무리(평가 기준 별표2 \"참여업체수에 따른 배분업체수\" 2~20 열,\n * 14~17pt)는 12pt 까지 열이다. 15pt 에서 둘씩 합쳐져 29열 표가 21열이 되고 두 칸 값이 \"1 1\" 로 한 칸에 들었다. 홀로 좁은 들여쓰기 열은\n * 종전대로 합친다(영문 계약서 틀 표 — 들여쓰기 열이 서자 문단까지 표로 품었다) */\nconst NARROW_RUN_MIN_WIDTH = 12\nconst NARROW_RUN_MAX_WIDTH = 20\nconst NARROW_RUN_MIN = 4\n/** 최소 행 높이 (pt) */\nconst MIN_ROW_HEIGHT = 6\n/** 좌표 병합 최소 tolerance (pt) — vertexRadius가 작아도 이 값 이하로 내려가지 않음 */\nconst MIN_COORD_MERGE_TOL = 8\n\n// ─── 적층 표 분리 (컷 라인) ──────────────────────────\n/** 컷 후보 수평선이 그룹 폭에서 차지해야 할 최소 비율 */\nconst CUT_FULLWIDTH_RATIO = 0.9\n/** 관통/소속 판정 y 여유 (pt) */\nconst CUT_CROSS_EPS = 2\n/** 컷 양쪽에 각각 필요한 최소 수직선 수 (독립 표 성립 조건) */\nconst CUT_MIN_SIDE_VERTICALS = 2\n/** 내부 수직선 판정 — 그룹 좌우 가장자리에서 이만큼 안쪽 (pt) */\nconst CUT_EDGE_MARGIN = 12\n/** 내부 수직선 x 동일 판정 tolerance (pt) */\nconst CUT_INTERIOR_MATCH_TOL = 8\n/** 내부 수직선 x-집합 겹침 허용 상한 — 초과하면 같은 열 구조의 연속(분리 금지) */\nconst CUT_MAX_INTERIOR_OVERLAP = 0.5\n/** 컷 체인 뷰: 같은 논리 수직선으로 묶는 x 허용 오차 (pt) */\nconst CUT_VCHAIN_X_TOL = 1.5\n/** 컷 체인 뷰: 콜리니어 수직 세그먼트 연결 최대 간격 (pt) — 한 표의 섹션별 세그먼트는\n * 정확히 맞닿고(gap≈0), 별개 표 사이에는 실간격(실측 2.9pt+)이 있다. line-extract\n * CHAIN_GAP(3)보다 좁게 잡아 별개 표의 경계 간격을 잇지 않는다 */\nconst CUT_VCHAIN_GAP = 1.0\n\n// ─── 선 격자 중첩표 ──────────────────────────────────\n/** 한 표의 괘선은 끝점이 맞닿는다 — 이 허용차로 이은 성분이 괘선이 실제로 이어진 표 하나다 */\nconst NEST_STRICT_TOL = 0.5\n/** 중첩 상자는 바깥 상자 안쪽에 칸 여백만큼 떠 있다(한컴 기본 안 여백 1.8mm ≈ 5pt, 아래 여백 1pt 실측) */\nconst NEST_MIN_GAP = 0.8\n/** 상자 변: 그 변을 이루는 선이 변 길이의 이만큼 이상을 덮어야 닫힌 상자 */\nconst NEST_EDGE_COVER = 0.95\n/** 이중 테두리: 안쪽 선이 바깥 선에서 이 거리(pt) 안에 네 쪽 모두 붙어 있으면 두 줄 테두리 한 벌 */\nconst NEST_DOUBLE_RULE_GAP = 6\n\n// ─── Vertex(교차점) 생성 ─────────────────────────────\n\n/**\n * 수평선과 수직선의 교차점(Vertex)을 생성.\n * ODL의 TableBorderBuilder.addLine()이 교차점을 자동 생성하는 것과 동일.\n * 각 Vertex는 교차하는 선들의 lineWidth로 radius를 계산 → 동적 tolerance.\n *\n * O(H×V) 회피: 수직선을 y-대역 버킷에 등록하고 각 수평선은 자기 y가 속한 버킷의\n * 후보만 검사 — 세로로 떨어진 다른 표의 수직선과의 교차 검사(전수 이중루프의 낭비\n * 대부분)를 건너뛴다. 수직선은 [y1-tol, y2+tol]을 덮는 모든 버킷에 인덱스 오름차순\n * 으로 등록되므로, h 순회 × 버킷 내 v 오름차순 = 기존 이중루프와 vertex 배열 순서가\n * 동일하다 (mergeVertices의 병합 결과가 순서에 민감).\n */\n/** 수직선 y-대역 버킷 크기 (pt) */\nconst VERTEX_BUCKET_CELL = 100\n\n/** 오름차순 배열에서 key 이상인 첫 위치 */\nfunction lowerBound(sorted: number[], key: number): number {\n let lo = 0, hi = sorted.length\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (sorted[mid] < key) lo = mid + 1\n else hi = mid\n }\n return lo\n}\n\nfunction buildVertices(horizontals: LineSegment[], verticals: LineSegment[]): Vertex[] {\n const vertices: Vertex[] = []\n const tol = CONNECT_TOL\n\n // 수직선은 수평선이 실제로 조회하는 버킷에만 등록한다 — [b1, b2] 전 구간을 돌면 루프가 선 길이/100 번이라, 좌표가 큰 괘선\n // 하나(726B PDF 의 길이 1e9 세로선)에 버킷 1,000만 개·3.2GB 를 만들다 \"Map maximum size exceeded\" 로 쪽을 잃었다. 조회되는\n // 버킷은 모두 수평선 y 의 버킷이라 등록 집합이 같다(교차 후보·vertex 순서 불변)\n const queried = [...new Set(horizontals.map(h => Math.floor(h.y1 / VERTEX_BUCKET_CELL)))].filter(Number.isFinite).sort((a, b) => a - b)\n const buckets = new Map<number, Array<LineSegment>>()\n for (const v of verticals) {\n const b1 = Math.floor((v.y1 - tol) / VERTEX_BUCKET_CELL)\n const b2 = Math.floor((v.y2 + tol) / VERTEX_BUCKET_CELL)\n for (let k = lowerBound(queried, b1); k < queried.length && queried[k] <= b2; k++) {\n const b = queried[k]\n const arr = buckets.get(b)\n if (arr) arr.push(v)\n else buckets.set(b, [v])\n }\n }\n\n for (const h of horizontals) {\n // h.y1 ∈ [v.y1-tol, v.y2+tol] 인 v는 h.y1의 버킷에 반드시 등록되어 있음\n const cand = buckets.get(Math.floor(h.y1 / VERTEX_BUCKET_CELL))\n if (!cand) continue\n for (const v of cand) {\n // 수평선의 X범위에 수직선의 X가 포함되고\n // 수직선의 Y범위에 수평선의 Y가 포함되면 → 교차\n if (v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol &&\n h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol) {\n const radius = Math.max(h.lineWidth, v.lineWidth, 1)\n vertices.push({ x: v.x1, y: h.y1, radius })\n }\n }\n }\n\n return vertices\n}\n\n/**\n * 근접 Vertex 병합 — 같은 교차점의 미세 위치 차이를 하나로 합침.\n * O(V²) 회피: 좌표 버킷 그리드로 근접 후보만 비교. mergeTol은 항상\n * VERTEX_MERGE_FACTOR × (전체 최대 radius) 이하이므로 셀 크기를 그 값으로 잡으면\n * ±1 셀 이웃만 봐도 기존 전수 비교와 결과(병합 집합·순서)가 동일하다.\n */\nfunction mergeVertices(vertices: Vertex[]): Vertex[] {\n if (vertices.length <= 1) return vertices\n\n let maxRadiusAll = 1\n for (const v of vertices) { if (v.radius > maxRadiusAll) maxRadiusAll = v.radius }\n const cell = Math.max(VERTEX_MERGE_FACTOR * maxRadiusAll, 1)\n const buckets = new Map<number, Map<number, number[]>>()\n for (let i = 0; i < vertices.length; i++) {\n const cx = Math.floor(vertices[i].x / cell)\n const cy = Math.floor(vertices[i].y / cell)\n let column = buckets.get(cx)\n if (!column) { column = new Map(); buckets.set(cx, column) }\n const arr = column.get(cy)\n if (arr) arr.push(i)\n else column.set(cy, [i])\n }\n\n const merged: Vertex[] = []\n const used = new Array(vertices.length).fill(false)\n\n for (let i = 0; i < vertices.length; i++) {\n if (used[i]) continue\n let sumX = vertices[i].x, sumY = vertices[i].y\n let maxRadius = vertices[i].radius\n let count = 1\n\n // ±1 셀 이웃의 j > i 후보만 — 기존 j 오름차순 스캔과 동일 순서 유지\n const cx = Math.floor(vertices[i].x / cell)\n const cy = Math.floor(vertices[i].y / cell)\n const candidates: number[] = []\n for (let dx = -1; dx <= 1; dx++) {\n for (let dy = -1; dy <= 1; dy++) {\n const arr = buckets.get(cx + dx)?.get(cy + dy)\n if (!arr) continue\n for (const j of arr) { if (j > i && !used[j]) candidates.push(j) }\n }\n }\n candidates.sort((a, b) => a - b)\n\n for (const j of candidates) {\n if (used[j]) continue\n const mergeTol = VERTEX_MERGE_FACTOR * Math.max(maxRadius, vertices[j].radius)\n if (Math.abs(vertices[i].x - vertices[j].x) <= mergeTol &&\n Math.abs(vertices[i].y - vertices[j].y) <= mergeTol) {\n sumX += vertices[j].x\n sumY += vertices[j].y\n maxRadius = Math.max(maxRadius, vertices[j].radius)\n count++\n used[j] = true\n }\n }\n\n merged.push({ x: sumX / count, y: sumY / count, radius: maxRadius })\n }\n\n return merged\n}\n\n// ─── 테이블 그리드 구성 (Vertex 기반) ─────────────────\n\n/**\n * 수평/수직 선에서 테이블 그리드를 추출.\n * ODL과 동일한 흐름:\n * 1. 선 전처리 (preprocessLines — 호출측에서 수행)\n * 2. 교차점(Vertex) 생성 + 병합\n * 3. 교차하는 선들을 그룹화 (연결 컴포넌트)\n * 4. 각 그룹에서 Vertex의 X/Y 좌표를 동적 tolerance로 클러스터링\n * 5. 그리드 검증 (최소 열 폭, 최소 행 높이)\n */\nexport function buildTableGrids(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n /** 좁은 열 무리를 12pt 까지 열로 둔다 — 텍스트층 벡터 괘선만. OCR 래스터 괘선은 조직도 상자 변이 좁은 열 무리처럼 보인다(web068) */\n narrowRuns = true,\n): TableGrid[] {\n if (horizontals.length < 2 || verticals.length < 2) return []\n\n // 1. 교차점 생성\n const allVertices = buildVertices(horizontals, verticals)\n const vertices = mergeVertices(allVertices)\n\n if (vertices.length < 4) return [] // 최소 4꼭짓점 필요 (사각형)\n\n // 전체 vertex의 대표 radius (동적 tolerance)\n const globalRadius = vertices.reduce((max, v) => Math.max(max, v.radius), 1)\n\n // 2. 선들을 교차 관계로 그룹화\n const allLines = [\n ...horizontals.map((l, i) => ({ ...l, type: \"h\" as const, id: i })),\n ...verticals.map((l, i) => ({ ...l, type: \"v\" as const, id: i + horizontals.length })),\n ]\n\n // 적층 표 분리 — 분리된 밴드는 공유 컷라인 위 vertex가 반대편 표의 수직선 x를\n // 나르므로(교차점이 경계선상에 생김) 전역 vertex 대신 밴드 자기 선으로 재계산한다\n const groups: Array<{ lines: TypedLine[]; fromSplit: boolean; nested?: boolean }> = []\n for (const g0 of groupConnectedLines(allLines)) {\n // 칸 안에 떠 있는 닫힌 표(선 격자 중첩표)는 CONNECT_TOL 로 바깥 괘선에 붙어 한 격자로 뭉친다 — 먼저 떼어 따로 세운다\n const { outer: g, nested } = splitNestedBoxes(g0)\n for (const n of nested) groups.push({ lines: n, fromSplit: true, nested: true })\n if (g.length === 0) continue\n const bands = splitStackedGroup(g)\n for (const b of bands) groups.push({ lines: b, fromSplit: bands.length > 1 || nested.length > 0 })\n }\n const grids: TableGrid[] = []\n\n // 그룹별 vertex bbox 필터의 O(그룹수×V) 회피 — y 정렬 후 그룹 y범위만 이진탐색 스캔.\n // groupVertices는 최대 radius와 clusterCoordinates(내부 정렬) 입력으로만 쓰여\n // 순서 무관 — 전수 filter와 같은 다중집합이면 결과 동일.\n const byY = [...vertices].sort((a, b) => a.y - b.y)\n const vertexYs = byY.map(v => v.y)\n const lowerBoundY = (key: number): number => {\n let lo = 0, hi = vertexYs.length\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (vertexYs[mid] < key) lo = mid + 1\n else hi = mid\n }\n return lo\n }\n\n for (const { lines: group, fromSplit, nested } of groups) {\n const hLines = group.filter(l => l.type === \"h\")\n const vLines = group.filter(l => l.type === \"v\")\n\n if (hLines.length < 2 || vLines.length < 2) continue\n\n // 3. 이 그룹의 Vertex만 수집\n let gx1 = Infinity, gy1 = Infinity, gx2 = -Infinity, gy2 = -Infinity\n for (const l of vLines) { if (l.x1 < gx1) gx1 = l.x1; if (l.x1 > gx2) gx2 = l.x1 }\n for (const l of hLines) { if (l.y1 < gy1) gy1 = l.y1; if (l.y1 > gy2) gy2 = l.y1 }\n const groupBbox = {\n x1: gx1 - CONNECT_TOL,\n y1: gy1 - CONNECT_TOL,\n x2: gx2 + CONNECT_TOL,\n y2: gy2 + CONNECT_TOL,\n }\n\n let groupVertices: Vertex[]\n if (fromSplit) {\n groupVertices = mergeVertices(buildVertices(hLines, vLines))\n } else {\n groupVertices = []\n for (let k = lowerBoundY(groupBbox.y1); k < byY.length && vertexYs[k] <= groupBbox.y2; k++) {\n const v = byY[k]\n if (v.x >= groupBbox.x1 && v.x <= groupBbox.x2) groupVertices.push(v)\n }\n }\n\n // 그룹 vertex의 대표 radius\n const groupRadius = groupVertices.length > 0\n ? groupVertices.reduce((max, v) => Math.max(max, v.radius), 1)\n : globalRadius\n\n // 4. Vertex 기반 좌표 클러스터링 (동적 tolerance)\n const coordMergeTol = Math.max(VERTEX_MERGE_FACTOR * groupRadius, MIN_COORD_MERGE_TOL)\n\n // 좁은 표의 오른쪽 셀 안에서 같은 바깥 테두리까지 반복된 밑줄은 행 경계가\n // 아니다. 전역적으로 1교차 선을 버리면 부분 괘선인 실제 표까지 손상된다.\n const leftEdge = Math.min(...vLines.map(v => v.x1))\n const rightEdge = Math.max(...vLines.map(v => v.x1))\n const underlines = hLines.filter(h => {\n const crossings = new Set<number>()\n for (const v of vLines) {\n if (v.x1 >= h.x1 - CONNECT_TOL && v.x1 <= h.x2 + CONNECT_TOL\n && h.y1 >= v.y1 - CONNECT_TOL && h.y1 <= v.y2 + CONNECT_TOL) crossings.add(v.x1)\n }\n return crossings.size === 1 && Math.abs(h.x2 - rightEdge) <= CONNECT_TOL\n && h.x1 > (leftEdge + rightEdge) / 2\n && h.x2 - h.x1 < (rightEdge - leftEdge) * 0.6\n })\n const rowLines = underlines.length >= 3 && hLines.length - underlines.length <= 4\n ? hLines.filter(h => !underlines.includes(h)) : hLines\n const rowVertices = rowLines.length === hLines.length ? groupVertices : groupVertices.filter(v =>\n rowLines.some(h => Math.abs(v.y - h.y1) <= CONNECT_TOL))\n\n // Y좌표: 행 경계 수평선 y + 해당 Vertex y\n const rawYs = [\n ...rowLines.map(l => l.y1),\n ...rowVertices.map(v => v.y),\n ]\n const rowYs = clusterCoordinates(rawYs, coordMergeTol).sort((a, b) => b - a)\n\n // X좌표: 수직선 x + Vertex x\n const rawXs = [\n ...vLines.map(l => l.x1),\n ...groupVertices.map(v => v.x),\n ]\n const colXs = clusterCoordinates(rawXs, coordMergeTol).sort((a, b) => a - b)\n\n if (rowYs.length < 2 || colXs.length < 2) continue\n\n // 5. 그리드 검증: 최소 열 폭, 최소 행 높이\n const validColXs = enforceMinWidth(colXs, MIN_COL_WIDTH, narrowRuns)\n const validRowYs = enforceMinHeight(rowYs, MIN_ROW_HEIGHT)\n\n if (validRowYs.length < 2 || validColXs.length < 2) continue\n\n const bbox = {\n x1: validColXs[0], y1: validRowYs[validRowYs.length - 1],\n x2: validColXs[validColXs.length - 1], y2: validRowYs[0],\n }\n\n grids.push({ rowYs: validRowYs, colXs: validColXs, bbox, vertexRadius: groupRadius, ...(nested ? { lineNested: true } : {}) })\n }\n\n // 중첩표는 제자리에 둔다 — 같은 열 수로 위아래 붙은 칸 안 표 둘을 한 표로 잇지 않는다\n return [...mergeAdjacentGrids(grids.filter(g => !g.lineNested)), ...grids.filter(g => g.lineNested)]\n}\n\n/** 음영 클립 판정: 클립 칸과 채움 사각형 좌표 허용 차 (pt) */\nconst SHADE_CLIP_TOL = 1.5\n/** 음영 클립 격자가 선 격자에서 차지하는 면적 상한 — 넘으면 표 전체를 덮는 클립(한컴 방식)으로 본다 */\nconst SHADE_CLIP_MAX_AREA = 0.5\n\n/**\n * 음영 칸 클립 격자 버리기 — 한컴 PDF 는 표의 모든 칸에 클립을 깔지만 cairo(rhwp 렌더) 등은 배경을 칠하는 칸에만\n * 채움용 클립을 건다. 그 클립이 머리행 1x2 같은 조각 격자를 이뤄 먼저 글을 가져가면, 괘선으로 온전한 3x2 선 표는\n * 머리행이 빈 채 강등돼 본문 행이 \"중견기업 : 4개까지 수행 가능\" 문단으로 흩어진다(연구개발 지원 공고 cairo 2쪽).\n * 클립 칸이 전부 같은 좌표의 채움 사각형이고, 그 격자가 열 경계가 맞는 선 격자 안에 들며, 그 선 격자 안의 클립 격자를\n * 다 합쳐도 선 격자 면적의 절반 이하이면(선 표 대부분에 클립이 없음) 음영이지 표가 아니다 — 선 격자가 표를 맡는다.\n * 한컴은 모든 칸에 클립을 깔아 선 격자가 여러 표에 걸쳐 커져도 그 안이 클립 격자로 덮인다(벤처투자조합 등록신청서:\n * 흰색으로 칠한 \"신청인\" 4x3 클립 표가 서식 전체 선 격자 14x4 안에 있으나 나머지도 클립 표들이 덮는다).\n * 음영 칸 사이가 획 없이 흰 틈으로만 갈린 머리행(한컴 구버전 성과지표 표 \"실적 | 목표치\")은 클립이 선에 없는 칸\n * 경계를 주므로 둔다 — 클립 격자의 안쪽 칸 경계마다 그 띠를 덮는 세로 괘선이 있을 때만 버린다.\n */\nexport function dropShadingClipGrids(clipGrids: TableGrid[], lineGrids: TableGrid[], fillRects: Array<{ x1: number; y1: number; x2: number; y2: number }>, verticals: LineSegment[] = []): TableGrid[] {\n if (clipGrids.length === 0 || lineGrids.length === 0 || fillRects.length === 0) return clipGrids\n const near = (a: number, b: number) => Math.abs(a - b) <= SHADE_CLIP_TOL\n const area = (b: TableGrid[\"bbox\"]) => Math.max(0, b.x2 - b.x1) * Math.max(0, b.y2 - b.y1)\n const inter = (a: TableGrid[\"bbox\"], b: TableGrid[\"bbox\"]) => area({ x1: Math.max(a.x1, b.x1), y1: Math.max(a.y1, b.y1), x2: Math.min(a.x2, b.x2), y2: Math.min(a.y2, b.y2) })\n return clipGrids.filter(c => {\n if (!c.cells?.length || c.clipParent) return true\n const sourceCells = c.cells.filter(cell => !cell.filler)\n if (sourceCells.length === 0) return true\n const shaded = sourceCells.every(cell => fillRects.some(f => near(f.x1, cell.bbox.x1) && near(f.x2, cell.bbox.x2) && near(f.y1, cell.bbox.y1) && near(f.y2, cell.bbox.y2)))\n if (!shaded) return true\n // 칸 경계마다 괘선 — 칸의 오른변(표 오른끝 제외)이 그 칸 높이의 75% 이상 세로선에 덮여야 한다\n const ruled = sourceCells.every(cell => cell.bbox.x2 >= c.bbox.x2 - SHADE_CLIP_TOL || verticals.some(v =>\n Math.abs(v.x1 - cell.bbox.x2) <= SHADE_CLIP_TOL * 2\n && Math.min(v.y2, cell.bbox.y2) - Math.max(v.y1, cell.bbox.y1) >= (cell.bbox.y2 - cell.bbox.y1) * 0.75))\n if (!ruled) return true\n const host = lineGrids.find(l =>\n // Inferred filler cells carry no boundary evidence. Replace their frame only\n // with a finer ruled grid of the same extent, never a larger surrounding form.\n (!c.cells!.some(cell => cell.filler) || (\n near(c.bbox.x1, l.bbox.x1) && near(c.bbox.x2, l.bbox.x2)\n && near(c.bbox.y1, l.bbox.y1) && near(c.bbox.y2, l.bbox.y2)\n && l.rowYs.length > c.rowYs.length\n && sourceCells.every(cell => [cell.bbox.y1, cell.bbox.y2].every(y => l.rowYs.some(ly => near(y, ly))))\n )) &&\n c.bbox.x1 >= l.bbox.x1 - SHADE_CLIP_TOL && c.bbox.x2 <= l.bbox.x2 + SHADE_CLIP_TOL\n && c.bbox.y1 >= l.bbox.y1 - SHADE_CLIP_TOL && c.bbox.y2 <= l.bbox.y2 + SHADE_CLIP_TOL\n && c.colXs.every(x => l.colXs.some(lx => near(lx, x)))\n && (clipGrids.reduce((s, o) => s + (o.cells?.some(cell => cell.filler)\n ? o.cells.filter(cell => !cell.filler).reduce((a, cell) => a + inter(cell.bbox, l.bbox), 0)\n : inter(o.bbox, l.bbox)), 0) <= area(l.bbox) * SHADE_CLIP_MAX_AREA\n // 상단 음영 두 행만 클립이고 마지막 흰 행은 선으로만 그린 표. 클립 면적이 절반을 넘어도\n // 선 격자가 같은 열·행 경계에 마지막 행 하나를 더 가지면 온전한 선 격자를 쓴다.\n || (l.rowYs.length === c.rowYs.length + 1 && c.rowYs.every((y, i) => near(y, l.rowYs[i]))\n && l.colXs.length === c.colXs.length && c.colXs.every((x, i) => near(x, l.colXs[i]))\n && !clipGrids.some(o => o !== c && inter(o.bbox, l.bbox) > 0))))\n return !host\n })\n}\n\n/** 여백 클립 판정: 클립 격자 위·아래 변이 선 격자 행 괘선에서 떨어진 최소 거리 (pt) — Word 칸 위·아래 여백 5~6pt 실측 */\nconst INSET_CLIP_MIN = 2\n\n/**\n * 칸 여백 클립 격자 버리기 — Word 는 칸 글 영역(칸 테두리에서 위·아래 여백만큼 안쪽)에 클립을 건다. 행마다 여백으로 떨어져\n * 클립 격자가 행 하나씩 따로 서고, 클립이 없는 전폭 행(\"Learning Outcomes\")은 표 밖 문단이 되어 한 표가 여러 조각으로\n * 갈렸다(ODL 146·150). 한컴 클립은 칸 테두리와 같은 좌표라 선 격자 행 괘선 위에 놓인다. 1행 클립 격자가 선 격자의 한 행 띠\n * 안에 위·아래 모두 떠 있고 좌우 끝은 선 격자 끝과 같으면 칸 여백 클립이다 — 온전한 선 격자가 표를 맡는다.\n */\nexport function dropInsetClipGrids(clipGrids: TableGrid[], lineGrids: TableGrid[]): TableGrid[] {\n if (clipGrids.length === 0 || lineGrids.length === 0) return clipGrids\n const near = (a: number, b: number) => Math.abs(a - b) <= SHADE_CLIP_TOL\n return clipGrids.filter(c => c.clipParent || c.rowYs.length !== 2 || !lineGrids.some(l =>\n l.rowYs.length >= 3 && near(c.bbox.x1, l.bbox.x1) && near(c.bbox.x2, l.bbox.x2)\n && l.rowYs.some((top, r) => r + 1 < l.rowYs.length\n && top - c.bbox.y2 >= INSET_CLIP_MIN && c.bbox.y1 - l.rowYs[r + 1] >= INSET_CLIP_MIN)))\n}\n\n/**\n * 쪽 넘김 머리 행 클립 띠 버리기 — 한컴은 이어진 쪽의 되풀이 머리 행(과 그 아래 몇 칸)을 따로 클립해 그린다. 그 클립 격자가 선 격자와\n * 열 경계가 같고, 선 격자 윗변에 붙어 행 경계가 모두 선 격자 행 경계 위에 놓이면 같은 표의 윗줄을 두 번 잡은 것이다 — 클립 격자가\n * 먼저 글을 가져가 선 표 첫 행들이 빈 채 남고 쪽 넘김 잇기가 끊겼다(국제기능올림픽 선수단 명단 57행 → 29행 + 30행). 선 격자 나머지를\n * 다른 클립이 덮으면(클립으로 그린 표 여럿이 한 틀 안에) 두고, 중첩표 클립(clipParent)은 대상이 아니다\n */\nexport function dropHeadBandClipGrids(clipGrids: TableGrid[], lineGrids: TableGrid[]): TableGrid[] {\n if (clipGrids.length === 0 || lineGrids.length === 0) return clipGrids\n const near = (a: number, b: number) => Math.abs(a - b) <= SHADE_CLIP_TOL\n const overlaps = (a: TableGrid[\"bbox\"], b: TableGrid[\"bbox\"]) => Math.min(a.x2, b.x2) > Math.max(a.x1, b.x1) && Math.min(a.y2, b.y2) > Math.max(a.y1, b.y1)\n return clipGrids.filter(c => c.clipParent || !c.cells?.length || !lineGrids.some(l =>\n l.rowYs.length > c.rowYs.length && l.colXs.length === c.colXs.length && c.colXs.every((x, k) => near(x, l.colXs[k]))\n && c.rowYs.every((y, k) => near(y, l.rowYs[k]))\n && !clipGrids.some(o => o !== c && overlaps(o.bbox, l.bbox))))\n}\n\n/** 최소 열 폭 보장 — 너무 좁은 열은 인접 열과 병합 */\nfunction enforceMinWidth(colXs: number[], minWidth: number, narrowRuns = true): number[] {\n if (colXs.length <= 2) return colXs\n // 20pt 안 열이 NARROW_RUN_MIN 개 넘게 이어진 무리 안 경계는 12pt 까지 둔다\n const inRun = new Array(colXs.length).fill(false)\n for (let s = 0; s + 1 < colXs.length;) {\n let e = s\n while (e + 1 < colXs.length && colXs[e + 1] - colXs[e] < NARROW_RUN_MAX_WIDTH) e++\n if (narrowRuns && e - s >= NARROW_RUN_MIN) for (let k = s; k <= e; k++) inRun[k] = true\n s = Math.max(e, s + 1)\n }\n const result: number[] = [colXs[0]]\n for (let i = 1; i < colXs.length; i++) {\n const prevX = result[result.length - 1]\n if (colXs[i] - prevX < (inRun[i] ? NARROW_RUN_MIN_WIDTH : minWidth) && i < colXs.length - 1) {\n // 너무 좁으면 스킵 (다음 열과 병합)\n continue\n }\n result.push(colXs[i])\n }\n return result\n}\n\n/** 최소 행 높이 보장 — 너무 낮은 행은 인접 행과 병합 */\nfunction enforceMinHeight(rowYs: number[], minHeight: number): number[] {\n if (rowYs.length <= 2) return rowYs\n // rowYs는 내림차순 (위→아래)\n const result: number[] = [rowYs[0]]\n for (let i = 1; i < rowYs.length; i++) {\n const prevY = result[result.length - 1]\n if (prevY - rowYs[i] < minHeight && i < rowYs.length - 1) {\n continue\n }\n result.push(rowYs[i])\n }\n return result\n}\n\n/** 같은 열 구조를 가진 인접 그리드를 병합 */\nfunction mergeAdjacentGrids(grids: TableGrid[]): TableGrid[] {\n if (grids.length <= 1) return grids\n const sorted = [...grids].sort((a, b) => b.bbox.y2 - a.bbox.y2)\n const merged: TableGrid[] = [sorted[0]]\n\n for (let i = 1; i < sorted.length; i++) {\n const prev = merged[merged.length - 1]\n const curr = sorted[i]\n\n if (prev.colXs.length === curr.colXs.length) {\n const mergeTol = Math.max(VERTEX_MERGE_FACTOR * Math.max(prev.vertexRadius, curr.vertexRadius), 6) * 3\n const colMatch = prev.colXs.every((x, ci) => Math.abs(x - curr.colXs[ci]) <= mergeTol)\n const verticalGap = prev.bbox.y1 - curr.bbox.y2\n if (colMatch && verticalGap >= -CONNECT_TOL && verticalGap <= 20) {\n const allRowYs = [...new Set([...prev.rowYs, ...curr.rowYs])].sort((a, b) => b - a)\n merged[merged.length - 1] = {\n rowYs: allRowYs,\n colXs: prev.colXs,\n bbox: {\n x1: Math.min(prev.bbox.x1, curr.bbox.x1),\n y1: Math.min(prev.bbox.y1, curr.bbox.y1),\n x2: Math.max(prev.bbox.x2, curr.bbox.x2),\n y2: Math.max(prev.bbox.y2, curr.bbox.y2),\n },\n vertexRadius: Math.max(prev.vertexRadius, curr.vertexRadius),\n }\n continue\n }\n }\n merged.push(curr)\n }\n return merged\n}\n\n/** 좌표값 클러스터링 — 동적 tolerance 기반 (ODL의 vertex radius 반영) */\nfunction clusterCoordinates(values: number[], tolerance: number): number[] {\n if (values.length === 0) return []\n const sorted = [...values].sort((a, b) => a - b)\n const clusters: { sum: number; count: number }[] = [{ sum: sorted[0], count: 1 }]\n\n for (let i = 1; i < sorted.length; i++) {\n const last = clusters[clusters.length - 1]\n const avg = last.sum / last.count\n if (Math.abs(sorted[i] - avg) <= tolerance) {\n last.sum += sorted[i]\n last.count++\n } else {\n clusters.push({ sum: sorted[i], count: 1 })\n }\n }\n\n return clusters.map(c => c.sum / c.count)\n}\n\ntype TypedLine = LineSegment & { type: \"h\" | \"v\"; id: number }\n\n/**\n * 컷 관통 판정용 체인 뷰 — 같은 x의 콜리니어 수직 세그먼트(섹션별로 쪼개 그은 외곽선·\n * 구분선)를 논리 수직선 하나로 잇는다. 한 표의 세그먼트는 정확히 맞닿고(gap≈0) 별개 표\n * 사이에는 실간격이 있어 잇지 않는다. 판정 전용 — 물리 수직선은 수정하지 않는다\n * (물리 병합은 셀 배치 변질 실측으로 폐기 — line-extract chainCollinearRules 주석 참조).\n */\nfunction chainVerticals(vs: TypedLine[]): Array<{ y1: number; y2: number }> {\n if (vs.length <= 1) return vs.map(v => ({ y1: v.y1, y2: v.y2 }))\n const sorted = [...vs].sort((a, b) => a.x1 - b.x1 || a.y1 - b.y1)\n const rules: Array<{ y1: number; y2: number }> = []\n let bandStart = 0\n const flushBand = (end: number) => {\n const band = sorted.slice(bandStart, end).sort((a, b) => a.y1 - b.y1)\n let cur = { y1: band[0].y1, y2: band[0].y2 }\n for (let i = 1; i < band.length; i++) {\n const seg = band[i]\n if (seg.y1 - cur.y2 <= CUT_VCHAIN_GAP) {\n if (seg.y2 > cur.y2) cur.y2 = seg.y2\n } else {\n rules.push(cur)\n cur = { y1: seg.y1, y2: seg.y2 }\n }\n }\n rules.push(cur)\n }\n for (let i = 1; i <= sorted.length; i++) {\n if (i === sorted.length || sorted[i].x1 - sorted[bandStart].x1 > CUT_VCHAIN_X_TOL) {\n flushBand(i)\n bandStart = i\n }\n }\n return rules\n}\n\n/**\n * 적층 표 분리 — 위아래로 붙은 별개 표 두 개가 경계 수평선 하나를 공유해 Union-Find가\n * 한 그룹으로 묶은 경우(채용공고 머리 스트립+응시원서 본표 실측)를 세로 밴드로 분리.\n *\n * 컷 라인 판정: 그룹 전폭급 수평선 중\n * (a) 이를 관통하는 논리 수직선(체인 뷰)이 하나도 없고 — 별개 표는 외곽 수직선이 각자\n * 영역에서 끊기지만, 한 표 내부의 전폭 구조변화 경계(섹션 헤더 행 등)는 외곽\n * 수직선이 연속(또는 맞닿은 세그먼트 체인)으로 관통한다\n * (b) 양쪽에 독립 표를 이룰 수직선이 2+개씩 있으며\n * (c) 내부 수직선 x-집합이 절반 넘게 겹치지 않는 경우 — 같은 열 구조의 연속이면 한 표\n * 컷 라인 자체는 양쪽 밴드에 복제된다 (위 표의 하변이자 아래 표의 상변).\n */\nfunction splitStackedGroup(group: TypedLine[]): TypedLine[][] {\n const hs = group.filter(l => l.type === \"h\")\n const vs = group.filter(l => l.type === \"v\")\n if (hs.length < 3 || vs.length < 4) return [group]\n\n let gx1 = Infinity, gx2 = -Infinity\n for (const l of group) {\n if (l.x1 < gx1) gx1 = l.x1\n if (l.x2 > gx2) gx2 = l.x2\n }\n const groupW = gx2 - gx1\n if (groupW <= 0) return [group]\n\n const isInterior = (v: TypedLine) => v.x1 > gx1 + CUT_EDGE_MARGIN && v.x1 < gx2 - CUT_EDGE_MARGIN\n const chained = chainVerticals(vs)\n const cuts: number[] = []\n for (const h of hs) {\n const y = h.y1\n if (h.x2 - h.x1 < groupW * CUT_FULLWIDTH_RATIO) continue\n if (cuts.some(c => Math.abs(c - y) <= CUT_CROSS_EPS)) continue\n // (a) 관통 논리 수직선 존재 → 같은 표 내부 경계\n if (chained.some(v => v.y1 < y - CUT_CROSS_EPS && v.y2 > y + CUT_CROSS_EPS)) continue\n // (b) 양쪽 수직선 수 — 컷이 성립하려면 위아래 각각 표 꼴이어야 함\n const above = vs.filter(v => v.y1 >= y - CUT_CROSS_EPS)\n const below = vs.filter(v => v.y2 <= y + CUT_CROSS_EPS)\n if (above.length < CUT_MIN_SIDE_VERTICALS || below.length < CUT_MIN_SIDE_VERTICALS) continue\n // (c) 내부 수직선 x-집합 겹침\n const ia = above.filter(isInterior)\n const ib = below.filter(isInterior)\n if (ia.length === 0 || ib.length === 0) continue\n let matched = 0\n for (const a of ia) if (ib.some(b => Math.abs(a.x1 - b.x1) <= CUT_INTERIOR_MATCH_TOL)) matched++\n if (matched / Math.min(ia.length, ib.length) > CUT_MAX_INTERIOR_OVERLAP) continue\n cuts.push(y)\n }\n if (cuts.length === 0) return [group]\n\n cuts.sort((a, b) => b - a) // 위→아래\n // y 위치가 속한 밴드: 컷 c보다 위(y > c)면 그 컷 앞 밴드\n const bandOf = (y: number) => {\n let k = 0\n while (k < cuts.length && y < cuts[k]) k++\n return k\n }\n const bands: TypedLine[][] = Array.from({ length: cuts.length + 1 }, () => [])\n for (const v of vs) bands[bandOf((v.y1 + v.y2) / 2)].push(v) // (a)에 의해 컷을 안 걸침\n for (const h of hs) {\n const atCut = cuts.findIndex(c => Math.abs(h.y1 - c) <= CUT_CROSS_EPS)\n if (atCut >= 0) {\n // 공유 경계선 — 양쪽 밴드에 복제\n bands[atCut].push(h)\n bands[atCut + 1].push(h)\n } else {\n bands[bandOf(h.y1)].push(h)\n }\n }\n return bands.filter(b => b.length > 0)\n}\n\n/**\n * 선 격자 중첩표 떼어내기 — 칸 클립이 없는 PDF(rhwp·워드 등)에서 틀 칸 안의 표는 칸 여백(≈5pt)만큼 떨어져 그려지는데\n * CONNECT_TOL(5) 이 그 틈을 이어 틀과 안쪽 표가 격자 하나(개인정보 분석 별지 17×11)로 뭉쳤다. 끝점이 실제로 맞닿는\n * 선끼리(NEST_STRICT_TOL) 이은 성분 가운데 네 변이 닫힌 상자이고, 다른 성분의 상자 안쪽에 네 변 모두 NEST_MIN_GAP 이상\n * 떨어져 있으며, 다른 성분의 선이 그 안을 지나지 않는 것을 중첩표로 본다. 한 표 안의 괘선은 맞닿으므로 떨어지지 않는다\n */\nfunction splitNestedBoxes(group: TypedLine[]): { outer: TypedLine[]; nested: TypedLine[][] } {\n const comps = groupConnectedLines(group, NEST_STRICT_TOL)\n if (comps.length < 2) return { outer: group, nested: [] }\n const boxes = comps.map(c => {\n const hs = c.filter(l => l.type === \"h\"), vs = c.filter(l => l.type === \"v\")\n if (hs.length < 2 || vs.length < 2) return null\n const x1 = Math.min(...vs.map(v => v.x1)), x2 = Math.max(...vs.map(v => v.x1))\n const y1 = Math.min(...hs.map(h => h.y1)), y2 = Math.max(...hs.map(h => h.y1))\n if (x2 - x1 < 1 || y2 - y1 < 1) return null\n const cover = (segs: Array<[number, number]>, a: number, b: number) =>\n segs.reduce((s, [p, q]) => s + Math.max(0, Math.min(q, b) - Math.max(p, a)), 0) >= (b - a) * NEST_EDGE_COVER\n const edgeH = (y: number) => cover(hs.filter(h => Math.abs(h.y1 - y) <= NEST_STRICT_TOL).map(h => [h.x1, h.x2]), x1, x2)\n const edgeV = (x: number) => cover(vs.filter(v => Math.abs(v.x1 - x) <= NEST_STRICT_TOL).map(v => [v.y1, v.y2]), y1, y2)\n return edgeH(y1) && edgeH(y2) && edgeV(x1) && edgeV(x2) ? { x1, y1, x2, y2 } : null\n })\n const bboxOf = (c: TypedLine[]) => ({\n x1: Math.min(...c.map(l => Math.min(l.x1, l.x2))), x2: Math.max(...c.map(l => Math.max(l.x1, l.x2))),\n y1: Math.min(...c.map(l => Math.min(l.y1, l.y2))), y2: Math.max(...c.map(l => Math.max(l.y1, l.y2))),\n })\n const extents = comps.map(bboxOf)\n const nestedIdx = new Set<number>()\n comps.forEach((_, i) => {\n const b = boxes[i]\n if (!b) return\n const e = extents[i]\n // 안쪽에 떠 있기: 다른 성분(좌우 세로선이 있는 틀 — 쪽을 넘어 윗변·아랫변이 없는 틀 조각 포함)이 네 쪽 모두 틈을 두고 감싼다\n // 안쪽 괘선 없는 상자(네 변뿐)가 바깥 상자에 네 쪽 모두 바짝 붙어 있으면 이중 테두리 글상자다(인천 현장실습 매뉴얼 2쪽:\n // 65~490 / 69~486 이중선 상자 둘) — 중첩표로 떼면 바깥 틀이 산문 상자로 강등되며 글이 사라졌다\n const uniq = (xs: number[]) => xs.filter((x, k) => xs.findIndex(y => Math.abs(y - x) <= NEST_STRICT_TOL) === k).length\n const bare = uniq(comps[i].filter(l => l.type === \"h\").map(l => l.y1)) === 2 && uniq(comps[i].filter(l => l.type === \"v\").map(l => l.x1)) === 2\n const inside = comps.some((c, j) => {\n if (j === i || c.filter(l => l.type === \"v\").length < 2 || !c.some(l => l.type === \"h\")) return false\n const p = extents[j]\n const gaps = [e.x1 - p.x1, p.x2 - e.x2, e.y1 - p.y1, p.y2 - e.y2]\n return gaps.every(g => g >= NEST_MIN_GAP) && !(bare && gaps.every(g => g <= NEST_DOUBLE_RULE_GAP))\n })\n if (!inside) return\n // 다른 성분의 선이 상자 안을 지나면(격자의 한 조각) 중첩표가 아니다\n const crossed = comps.some((c, j) => j !== i && c.some(l => l.type === \"h\"\n ? l.y1 > e.y1 + NEST_MIN_GAP && l.y1 < e.y2 - NEST_MIN_GAP && Math.min(l.x2, e.x2) - Math.max(l.x1, e.x1) > NEST_MIN_GAP\n : l.x1 > e.x1 + NEST_MIN_GAP && l.x1 < e.x2 - NEST_MIN_GAP && Math.min(l.y2, e.y2) - Math.max(l.y1, e.y1) > NEST_MIN_GAP)\n && !(extents[j].x1 >= e.x1 && extents[j].x2 <= e.x2 && extents[j].y1 >= e.y1 && extents[j].y2 <= e.y2))\n if (!crossed) nestedIdx.add(i)\n })\n if (nestedIdx.size === 0) return { outer: group, nested: [] }\n return {\n outer: comps.filter((_, i) => !nestedIdx.has(i)).flat(),\n nested: comps.filter((_, i) => nestedIdx.has(i)),\n }\n}\n\n/** 선 bbox 버킷 그리드 셀 크기 (pt) — 근접 후보 열거용 */\nconst GROUP_BUCKET_CELL = 100\n\n/**\n * 교차하는 선들을 Union-Find로 그룹화.\n * O(L²) 회피: 선 bbox(±CONNECT_TOL)를 좌표 버킷 그리드에 등록하고 같은 셀을\n * 공유하는 쌍만 교차 판정. 교차 가능한 쌍(bbox 겹침)은 반드시 셀을 공유하므로\n * 연결 컴포넌트가 동일하고, 그룹 출력 순서(컴포넌트 최소 선 인덱스 순·그룹 내\n * 인덱스 순)는 union 순서와 무관해 기존과 결과가 같다.\n */\nfunction groupConnectedLines(lines: TypedLine[], tol = CONNECT_TOL): TypedLine[][] {\n const parent = lines.map((_, i) => i)\n\n function find(x: number): number {\n while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x] }\n return x\n }\n function union(a: number, b: number) {\n const ra = find(a), rb = find(b)\n if (ra !== rb) parent[ra] = rb\n }\n\n // 선마다 bbox 가 덮는 셀 전부가 아니라, 열·행이 어떤 선의 시작 셀(cx1·cy1)인 셀에만 등록한다. 두 bbox 가 겹치면 겹친 구간의\n // 최소 모서리 (max(cx1), max(cy1)) 는 둘 중 한 선의 시작 열·행이고 두 선 모두 덮으므로 거기서 만난다 — 교차 판정 쌍이 같고\n // 등록 수는 종전 이하. 전 구간 등록은 좌표가 큰 괘선 하나에 셀 수천만 개를 만들었다(buildVertices 주석)\n const span = lines.map(l => [\n Math.floor((Math.min(l.x1, l.x2) - CONNECT_TOL) / GROUP_BUCKET_CELL),\n Math.floor((Math.max(l.x1, l.x2) + CONNECT_TOL) / GROUP_BUCKET_CELL),\n Math.floor((Math.min(l.y1, l.y2) - CONNECT_TOL) / GROUP_BUCKET_CELL),\n Math.floor((Math.max(l.y1, l.y2) + CONNECT_TOL) / GROUP_BUCKET_CELL),\n ])\n const startXs = [...new Set(span.map(s => s[0]))].filter(Number.isFinite).sort((a, b) => a - b)\n const startYs = [...new Set(span.map(s => s[2]))].filter(Number.isFinite).sort((a, b) => a - b)\n const cellMap = new Map<string, number[]>()\n for (let i = 0; i < lines.length; i++) {\n const [cx1, cx2, cy1, cy2] = span[i]\n for (let a = lowerBound(startXs, cx1); a < startXs.length && startXs[a] <= cx2; a++) {\n for (let b = lowerBound(startYs, cy1); b < startYs.length && startYs[b] <= cy2; b++) {\n const key = startXs[a] + \",\" + startYs[b]\n const arr = cellMap.get(key)\n if (arr) arr.push(i)\n else cellMap.set(key, [i])\n }\n }\n }\n\n const tested = new Set<number>()\n for (const arr of cellMap.values()) {\n for (let a = 0; a < arr.length; a++) {\n for (let b = a + 1; b < arr.length; b++) {\n const i = Math.min(arr[a], arr[b])\n const j = Math.max(arr[a], arr[b])\n const key = i * lines.length + j\n if (tested.has(key)) continue\n tested.add(key)\n if (linesIntersect(lines[i], lines[j], tol)) {\n union(i, j)\n }\n }\n }\n }\n\n const groups = new Map<number, TypedLine[]>()\n for (let i = 0; i < lines.length; i++) {\n const root = find(i)\n if (!groups.has(root)) groups.set(root, [])\n groups.get(root)!.push(lines[i])\n }\n\n return [...groups.values()]\n}\n\n/** 수평선과 수직선의 교차 판정 (tolerance 포함) */\nfunction linesIntersect(a: TypedLine, b: TypedLine, tol = CONNECT_TOL): boolean {\n if (a.type === b.type) {\n if (a.type === \"h\") {\n if (Math.abs(a.y1 - b.y1) > tol) return false\n return Math.min(a.x2, b.x2) >= Math.max(a.x1, b.x1) - tol\n } else {\n if (Math.abs(a.x1 - b.x1) > tol) return false\n return Math.min(a.y2, b.y2) >= Math.max(a.y1, b.y1) - tol\n }\n }\n\n const h = a.type === \"h\" ? a : b\n const v = a.type === \"h\" ? b : a\n\n return (\n v.x1 >= h.x1 - tol && v.x1 <= h.x2 + tol &&\n h.y1 >= v.y1 - tol && h.y1 <= v.y2 + tol\n )\n}\n","/**\n * 셀 클립 사각형 → 표 그리드 (테두리 없는 표 복원).\n *\n * 한컴 PDF 는 표 셀마다 `W n`(clip + endPath) 사각형을 깔고 그 안에 글을 찍는다. 법령 별지서식의\n * 외곽 표는 테두리 \"없음\" 셀이 대부분이라 획 괘선만 보는 line 파이프라인은 표를 못 잡고\n * 제목·기입란이 헤딩·문단으로 흩어진다 (법제처 별지서식 PDF 실측, v4.12.1). 클립 사각형은\n * 병합 셀까지 그대로 담은 실제 셀 기하라, 선→교차점→클러스터 경로 없이 곧장 셀로 쓴다 —\n * 전 페이지를 한 그리드로 합칠 때 행마다 다른 열 경계가 MIN_COL_WIDTH 병합으로 뭉개지는\n * 것도 피한다.\n *\n * 적용 범위는 보수적으로 잡는다: 서로 변을 맞대는 클립 묶음이 CLIP_MIN_GROUP 개 이상이고,\n * 그 변 가운데 획 괘선이 없는 비율이 CLIP_MIN_INVISIBLE 이상일 때만 이 경로를 탄다. 실선\n * 테두리 표는 검증된 line 경로가 그대로 맡는다. 글상자·그림·머리말처럼 이웃 없는 단독\n * 클립은 묶음이 안 돼 제외된다 — 단 다른 클립을 품는 틀과 틀 안에 홀로 든 클립은 1칸 표다\n * (v4.12.2, 선서문·지정서·영치증 실측). 중첩 관계는 `TableGrid.clipParent` 로 넘겨 소비측이\n * 틀 셀의 `IRCell.blocks` 에 안쪽 표를 넣는다 (HWP 파서 IR 과 같은 모양).\n */\n\nimport type { ClipRect } from \"./line-extract.js\"\nimport type { ExtractedCell, LineSegment, TableGrid } from \"./line-types.js\"\n\n/** 이웃 셀 판정 — 변 공유·좌표 클러스터 허용 오차 (pt) */\nconst CLIP_EDGE_TOL = 1.5\n/** 이웃 셀의 공유 변 간격 상한 (pt) — 한컴 PDF 는 같은 표 셀 클립이 변을 정확히 공유한다(코퍼스 16,296쌍\n * 0.05pt 미만, 0.05~0.2pt 1쌍). 그보다 벌어지거나 겹친 사각형은 표 셀이 아니다: 표 캡션 클립은 표 윗변에\n * 0.3pt 겹쳐 깔리고(보도자료 \"거래방식별 평균 대금…\" 캡션이 5열 병합 첫 행으로 붙던 것), 행간을 두고 쌓인\n * 글줄 클립(자료형 표 셀 안 \"BYTE\"/\"WORD\" 0.72pt)·머리말 영역 클립(본문 틀과 1.0pt)도 1.5pt 로 붙이면 머리말+본문\n * 틀이 2×3 가짜 표가 되어 그 안의 실제 표가 중첩으로 묻힌다(행정업무운영 편람 45표 실측) */\nconst CLIP_ADJ_GAP = 0.15\n/** 그리드로 인정하는 최소 셀 수 — 글상자 한두 개짜리 클립은 표가 아님 */\nconst CLIP_MIN_GROUP = 2\n/** 페이지 면적 대비 이 비율 이상인 클립은 페이지/본문 영역 — 셀 아님 */\nconst CLIP_MAX_PAGE_FRAC = 0.75\n/** 셀 최소 치수 (pt) */\n/** 걸침 판정 — 상대 클립이 이 비율 넘게, 1-이 비율 못 미치게 들어 있어야 걸침(표 위 덮개). 그 밖은 넘친 안쪽 칸·가장자리 닿음 */\nconst STRADDLE_MIN = 0.1\n/** 제목 아래 틀 판정 — 틀 윗변이 페이지 높이의 이 비율 아래에서 시작해야 한다 (본문 영역 클립은 9~11%, 별표 틀 26~52% 실측) */\nconst TITLED_FRAME_MIN_TOP = 0.2\n/** 제목 아래 틀 판정 — 틀 폭이 페이지 폭의 이 비율 이상 (별표 틀 70~86% 실측, 2단 상자 38% 제외) */\nconst TITLED_FRAME_MIN_WIDTH = 0.6\n/** 머리말 띠 — 이 비율 위의 글(머리말)은 \"틀 위 본문 글\" 로 세지 않는다 */\nconst HEADER_BAND = 0.08\nconst CLIP_MIN_W = 4\n/** 가장자리 채움 칸 최소 폭 (pt) — 이보다 가는 채움 사각형은 괘선 */\nconst NARROW_FILL_MIN_W = 1\nconst CLIP_MIN_H = 2\n/** 획 괘선이 변을 덮는다고 보는 거리(pt)·길이 비율 */\nconst STROKE_NEAR = 2\nconst STROKE_COVER = 0.5\n/** 묶음 변 중 획 괘선 없는 비율이 이 이상이어야 \"테두리 없는 표\" — 실선 표는 line 경로에 양보 */\nconst CLIP_MIN_INVISIBLE = 0\n\n/** 열·행 경계 좌표 묶음 오차 (pt) — 클립 좌표는 원본 셀 경계(HWPUNIT)를 그대로 옮겨, 같은 경계는 0.05pt 안에서\n * 겹치고 원본에서 다른 경계는 0.5pt 차이라도 따로 선다. 원본 표 격자(HWPX colAddr)는 그 미세한 차이를 별도 열로\n * 세므로(주거 유형 표: 211.6/212.5·320.3/321.8/322.3 → 9열) 1.5pt 로 묶으면 열이 합쳐진다(9열 → 6열) */\nconst CLIP_COORD_TOL = 0.3\n\nconst overlap = (a1: number, a2: number, b1: number, b2: number): number => Math.min(a2, b2) - Math.max(a1, b1)\n\nfunction adjacent(a: ClipRect, b: ClipRect): boolean {\n // 세로변 공유(좌우 이웃) 또는 가로변 공유(상하 이웃) — 겹침이 양수여야 모서리만 닿은 대각 이웃 제외.\n // 같은 표의 이웃 칸은 맞닿은 변과 직각인 변도 하나는 격자선을 같이 쓴다(위아래 이웃은 왼쪽 또는 오른쪽 끝,\n // 좌우 이웃은 윗변 또는 밑변). 표 밑변에 정확히 붙은 주석 상자(※…, 표보다 좌우 1.4pt 넓음)처럼 격자선을\n // 하나도 같이 쓰지 않는 사각형은 표의 칸이 아니다 — 붙이면 가장자리에 폭 1pt 짜리 유령 열이 생긴다\n const al = (u: number, v: number): boolean => Math.abs(u - v) <= CLIP_COORD_TOL\n if ((Math.abs(a.x2 - b.x1) <= CLIP_ADJ_GAP || Math.abs(b.x2 - a.x1) <= CLIP_ADJ_GAP) && overlap(a.y1, a.y2, b.y1, b.y2) > CLIP_EDGE_TOL\n && (al(a.y1, b.y1) || al(a.y2, b.y2))) return true\n if ((Math.abs(a.y2 - b.y1) <= CLIP_ADJ_GAP || Math.abs(b.y2 - a.y1) <= CLIP_ADJ_GAP) && overlap(a.x1, a.x2, b.x1, b.x2) > CLIP_EDGE_TOL\n && (al(a.x1, b.x1) || al(a.x2, b.x2))) return true\n return false\n}\n\n/** a 가 b 를 품는가 (중첩표의 바깥 셀 클립) */\nfunction contains(a: ClipRect, b: ClipRect): boolean {\n 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\n && ((b.x2 - b.x1) < (a.x2 - a.x1) - CLIP_EDGE_TOL || (b.y2 - b.y1) < (a.y2 - a.y1) - CLIP_EDGE_TOL)\n}\n\n\n/** 좌표 클러스터 — 오름차순 정렬 후 CLIP_COORD_TOL 이내는 평균으로 합침 */\nfunction clusterCoords(values: number[]): number[] {\n const sorted = [...values].sort((a, b) => a - b)\n const out: number[] = []\n let run: number[] = []\n for (const v of sorted) {\n if (run.length > 0 && v - run[run.length - 1] > CLIP_COORD_TOL) { out.push(run.reduce((s, x) => s + x, 0) / run.length); run = [] }\n run.push(v)\n }\n if (run.length > 0) out.push(run.reduce((s, x) => s + x, 0) / run.length)\n return out\n}\n\n/** 표 바깥 끝에 1pt 도 안 되는 칸은 없다 — 칸마다 사각형을 따로 그려 바깥 변이 0.3pt 남짓 어긋나 묶음이 둘로 갈린 것(ODL 119\n * 102.4·102.7)은 하나로. 안쪽의 좁은 열은 한컴 원본 격자에도 있어(칸 폭이 조금씩 다른 행) 건드리지 않는다 */\nfunction dropSliverGaps(coords: number[]): number[] {\n const out = [...coords]\n if (out.length > 2 && out[1] - out[0] < 1) out.splice(0, 2, (out[0] + out[1]) / 2)\n const n = out.length\n if (n > 2 && out[n - 1] - out[n - 2] < 1) out.splice(n - 2, 2, (out[n - 2] + out[n - 1]) / 2)\n return out\n}\n\nconst nearestIndex = (coords: number[], v: number): number => {\n let best = 0\n for (let i = 1; i < coords.length; i++) if (Math.abs(coords[i] - v) < Math.abs(coords[best] - v)) best = i\n return best\n}\n\n/** 변이 획 괘선으로 덮여 있는가 */\nfunction edgeStroked(lines: LineSegment[], dir: \"h\" | \"v\", pos: number, a1: number, a2: number): boolean {\n const len = a2 - a1\n if (len <= 0) return true\n for (const l of lines) {\n if (dir === \"h\") {\n if (Math.abs(l.y1 - pos) <= STROKE_NEAR && overlap(l.x1, l.x2, a1, a2) >= len * STROKE_COVER) return true\n } else if (Math.abs(l.x1 - pos) <= STROKE_NEAR && overlap(l.y1, l.y2, a1, a2) >= len * STROKE_COVER) {\n return true\n }\n }\n return false\n}\n\n/**\n * 클립 사각형 묶음에서 테두리 없는 표 그리드를 만든다. 셀은 `TableGrid.cells` 로 미리 확정해\n * 넘기므로 소비측은 extractCells 대신 이를 쓴다.\n * @param strokedH/strokedV 획 괘선 (전처리 전) — 실선 표 판정용\n */\nexport interface ClipCellResult {\n /** 셀 그리드 — 틀(중첩표 바깥 셀·1칸 테두리)은 자기 층의 셀로 들어가고, 안쪽 표는 별도 그리드.\n * 소비측은 면적 오름차순으로 처리해 안쪽 표가 글을 먼저 가져가게 한다 */\n grids: TableGrid[]\n /** 다른 클립을 품어 셀이 아니라 틀로 판정된 사각형(중복 제거) — line 그리드 정리(dropGridsInside)에 쓴다 */\n containers: ClipRect[]\n /** 다음 쪽 판정에 넘길 이 쪽 사실 — 쪽 넘김 이어짐(continues) */\n page: ClipPage\n}\n\n/** 쪽 넘김 이어짐 판정에 쓰는 한 쪽의 클립 사실 */\nexport interface ClipPage {\n /** 이 쪽의 마지막 내용인 최상위 표 칸(그 아래엔 꼬리말 띠 글만) — 다음 쪽 첫 클립이 이 칸의 이어짐인지 가른다 */\n lastCells: ClipRect[]\n /** 이 쪽의 클립(중복 제거) — 다음 쪽 첫 클립과 같은 사각형이 있으면 쪽마다 같은 자리에 놓인 틀 요소(절 제목 띠)다 */\n clips: ClipRect[]\n}\n\n/** 쪽 넘김 이어짐 — 앞 쪽 칸과 이 쪽 클립의 좌우 변이 이 거리(pt) 안에서 같아야 한 칸. 한컴은 같은 칸을 쪽마다 같은 좌표로\n * 깐다(거대 칸 48쪽·규제영향분석서 이어짐 전부 0.01pt 안). 이웃 쪽의 다른 상자(\"5 | 시험 방법\" 머리 상자, 0.12pt 차)는 가른다 */\nconst CONT_X_TOL = 0.1\n\nexport function buildClipCellGrids(\n rects: ClipRect[],\n strokedH: LineSegment[],\n strokedV: LineSegment[],\n pageWidth: number,\n pageHeight: number,\n textPoints: ReadonlyArray<{ x: number; y: number }> = [],\n fillRects: ClipRect[] = [],\n /** 바로 앞 쪽의 클립 사실(앞 쪽 결과의 page) — 없으면 쪽 넘김 이어짐을 보지 않는다 */\n prev: ClipPage = { lastCells: [], clips: [] },\n): ClipCellResult {\n const pageArea = pageWidth * pageHeight\n const sameRect = (a: ClipRect, b: ClipRect): boolean =>\n 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\n // 같은 사각형은 셀 안 문단마다 반복 클립된다(틀 3회·선서문 안쪽 1칸 표 2회 실측) — 좌표로 중복 제거.\n // 중복을 남기면 1칸 표가 서로 이웃도 포함도 아닌 단독 클립 여러 개로 흩어져 묶이지 않는다\n const cells: ClipRect[] = []\n // x1·y1 근처만 비교한다. 반복 클립이 많은 쪽에서 전체 선행 셀을 다시 훑지 않는다.\n const buckets = new Map<number, Map<number, ClipRect[]>>()\n for (const r of rects) {\n if ((r.x2 - r.x1) < CLIP_MIN_W || (r.y2 - r.y1) < CLIP_MIN_H) continue\n if (pageArea > 0 && (r.x2 - r.x1) * (r.y2 - r.y1) >= pageArea * CLIP_MAX_PAGE_FRAC) continue\n const bx = Math.floor(r.x1 / CLIP_EDGE_TOL), by = Math.floor(r.y1 / CLIP_EDGE_TOL)\n let duplicate = false\n for (let dx = -1; dx <= 1 && !duplicate; dx++) {\n const ys = buckets.get(bx + dx)\n for (let dy = -1; dy <= 1 && !duplicate; dy++) {\n duplicate = ys?.get(by + dy)?.some(c => sameRect(c, r)) ?? false\n }\n }\n if (duplicate) continue\n cells.push(r)\n let ys = buckets.get(bx)\n if (!ys) { ys = new Map(); buckets.set(bx, ys) }\n const row = ys.get(by)\n if (row) row.push(r)\n else ys.set(by, [r])\n }\n if (cells.length < 1) return { grids: [], containers: [], page: { lastCells: [], clips: [] } }\n\n // 포함 관계로 층을 나눈다 — 각 사각형의 부모 = 자기를 품는 가장 작은 사각형. 중첩표 셀은 바깥\n // 셀 안에 있으므로 같은 부모(그 바깥 셀)끼리만 묶이고, 바깥 셀은 자기 층(최상위 또는 그 위 셀)의\n // 이웃과 묶인다. 이렇게 하면 지정서·영치증의 1칸 틀도 위 제목행·아래 꼬리행과 한 표의 셀이 되고\n // (HWP 파서가 내는 1열 표와 같은 모양), 안쪽 \"발신명의 | 직인\" 표는 자기들끼리 별도 그리드가 된다.\n // 틀 안 자유 문단이 바깥 격자의 채움 셀로 찢기지 않고, 클러스터 표 감지에 걸려 가짜 다열 표가\n // 되지도 않는다 (영치증 \"성 명:/주 소:\" 실측)\n const parent = new Array<number>(cells.length).fill(-1)\n const area = (r: ClipRect): number => (r.x2 - r.x1) * (r.y2 - r.y1)\n // 다른 클립과 걸쳐 겹치는(교차하되 서로 품지 않는) 사각형은 부모가 못 된다 — 한 표의 칸끼리는 겹치지 않으므로 이런\n // 사각형은 표 위에 얹힌 그림·글상자다. 이것이 우연히 품은 칸들을 자식으로 삼으면 한 표가 그 테두리에서 층이 갈려 둘로\n // 쪼개진다: 복학원서 워터마크 그림(371pt 정사각)이 4×4 서명란 가운데 두 열만 품어 4×1·4×2·2×1 로, 결재문서 결문표\n // 아래쪽 10행을 덮은 빈 1칸 표(발신명의 행 중간까지)가 12×39 를 2×3·10×36 으로 갈랐다. 교차 폭은 두 축 다 CLIP_EDGE_TOL\n // 넘게 — 변을 맞댄 이웃·표 윗변에 0.3pt 겹쳐 깔리는 캡션 클립은 걸침이 아니다. b 가 a 안에 대부분(90% 넘게) 들었거나 거의 안\n // 들었으면(10% 미만) 걸침이 아니라 칸 폭을 조금 넘친 안쪽 표의 칸이다 — 공문 작성 안내서 \"참고 | 첨부물 표시\" 칩이 제 칸보다\n // 5.9pt 넓어 옆 칸에 걸친 것까지 걸침으로 보면 칩이 칸을 잃고 따로 표가 된다\n const straddles = (a: ClipRect, b: ClipRect): boolean => {\n const ix = overlap(a.x1, a.x2, b.x1, b.x2), iy = overlap(a.y1, a.y2, b.y1, b.y2)\n if (ix <= CLIP_EDGE_TOL || iy <= CLIP_EDGE_TOL || contains(a, b) || contains(b, a)) return false\n const inside = (ix * iy) / area(b)\n return inside > STRADDLE_MIN && inside < 1 - STRADDLE_MIN\n }\n const canParent = cells.map((a, i) => !cells.some((b, j) => i !== j && straddles(a, b)))\n for (let i = 0; i < cells.length; i++) {\n for (let j = 0; j < cells.length; j++) {\n // 부모는 면적이 엄격히 큰 사각형만 — 포함 판정에 오차가 있어 비슷한 크기끼리는 서로를 품을 수 있고, 그러면 부모 사슬이 고리가 된다\n if (i === j || !canParent[j] || area(cells[j]) <= area(cells[i]) || !contains(cells[j], cells[i])) continue\n const cur = parent[i]\n if (cur < 0 || contains(cells[cur], cells[j])) parent[i] = j\n }\n }\n const ruledGaps = findRuledGaps(cells, parent, strokedH, strokedV)\n const isContainer = new Array<boolean>(cells.length).fill(false)\n for (const p of parent) if (p >= 0) isContainer[p] = true\n // 표 겉 클립 — 글자처럼 놓인 표는 칸 클립들 바깥에 표 테두리와 같은 사각형 클립이 하나 더 깔린다(행정업무운영\n // 편람 Q&A 상자 6×5 55개 실측). 틀로 보면 1×1 틀이 글을 다 가져가고 안쪽 표는 빈 표로 버려진다 — 자식 클립들을 합친\n // 테두리가 자기 사각형과 좌표 오차 안에서 같으면 틀이 아니라 그 표의 겉 클립이라 셀로 쓰지 않는다. 단 맞닿은 이웃이 모두 같은\n // 겉 클립 후보일 때만(위아래로 붙은 상자들) — 보통 칸이 이웃이면 여백 없이 중첩표를 품은 표의 칸이라 구멍을 내면 안 된다\n const tileParent = new Array<boolean>(cells.length).fill(false)\n const tableClip = new Array<boolean>(cells.length).fill(false)\n for (let p = 0; p < cells.length; p++) {\n if (!isContainer[p]) continue\n let n = 0, x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity\n for (let i = 0; i < cells.length; i++) {\n if (parent[i] !== p) continue\n const k = cells[i]\n n++\n x1 = Math.min(x1, k.x1); y1 = Math.min(y1, k.y1); x2 = Math.max(x2, k.x2); y2 = Math.max(y2, k.y2)\n }\n if (n < CLIP_MIN_GROUP) continue\n const c = cells[p]\n // 겉 클립은 칸 클립 묶음과 좌표까지 같다 — 틀 셀은 안쪽 여백(1.4pt 실측, 결재란 품은 마지막 행)만큼 벌어진다\n if (Math.max(Math.abs(x1 - c.x1), Math.abs(x2 - c.x2), Math.abs(y1 - c.y1), Math.abs(y2 - c.y2)) <= CLIP_COORD_TOL) tileParent[p] = true\n }\n for (let p = 0; p < cells.length; p++) {\n if (tileParent[p]) tableClip[p] = !cells.some((q, i) => i !== p && parent[i] === parent[p] && !tileParent[i] && adjacent(q, cells[p]))\n }\n /** 안쪽 표를 받을 부모 — 표 겉 클립과, 셀도 틀도 아닌 감싸개 클립(칸 안쪽 여백·글상자 클립: 쪽을 넘어온 큰 칸 안의\n * 단위 표들, 규제영향분석서 실측)은 건너뛰고 그것을 품은 셀·틀에 넣는다 */\n const effParent = (i: number): number => {\n let p = parent[i]\n for (let steps = 0; p >= 0 && steps <= cells.length; steps++) { // 면적이 커지는 사슬이라 고리는 없지만 걸음 수도 묶어 둔다\n if (!tableClip[p] && (isGridMember(p) || loneFrame(p) || continues(p))) return p\n p = parent[p]\n }\n return -1\n }\n // 연결 컴포넌트 (같은 층의 변 공유 이웃). 변 좌표가 가까운 사각형만 후보로 뽑는다.\n // 셀이 많은 쪽에서도 서로 멀리 있는 모든 쌍을 비교하지 않는다.\n const root = cells.map((_, i) => i)\n const find = (i: number): number => { while (root[i] !== i) { root[i] = root[root[i]]; i = root[i] } return i }\n const edgeBin = (v: number): number => Math.floor(v / CLIP_ADJ_GAP)\n const startsX = new Map<number, number[]>(), endsX = new Map<number, number[]>()\n const startsY = new Map<number, number[]>(), endsY = new Map<number, number[]>()\n const addEdge = (map: Map<number, number[]>, v: number, i: number): void => {\n const k = edgeBin(v), row = map.get(k)\n if (row) row.push(i)\n else map.set(k, [i])\n }\n for (let i = 0; i < cells.length; i++) {\n addEdge(startsX, cells[i].x1, i); addEdge(endsX, cells[i].x2, i)\n addEdge(startsY, cells[i].y1, i); addEdge(endsY, cells[i].y2, i)\n }\n for (let i = 0; i < cells.length; i++) {\n if (tableClip[i]) continue\n const candidates = new Set<number>()\n const near = (map: Map<number, number[]>, v: number): void => {\n const k = edgeBin(v)\n for (let d = -1; d <= 1; d++) for (const j of map.get(k + d) ?? []) if (j > i) candidates.add(j)\n }\n near(startsX, cells[i].x2); near(endsX, cells[i].x1)\n near(startsY, cells[i].y2); near(endsY, cells[i].y1)\n for (const j of [...candidates].sort((a, b) => a - b)) {\n if (tableClip[j] || parent[i] !== parent[j]) continue\n if (adjacent(cells[i], cells[j])) { const ra = find(i), rb = find(j); if (ra !== rb) root[ra] = rb }\n }\n }\n // 괘선 틈을 사이에 둔 이웃(셀 간격 표·짧은 칸 클립)도 한 표 — 좌표는 격자를 만들 때 닫는다(closeGaps).\n // 단 위아래 묶음이 저마다 틈 없이 맞닿아 쌓인 행을 가졌고 틈 양끝에 괘선이 따로 그어졌고(위 표 밑 테두리·아래 표 윗 테두리) 틈이 표 안\n // 틈보다 넓으면 그 가로 틈은 표 안 행 간격이 아니라 따로 쌓은 두 표 사이다 — 셀 간격 표는 모든 칸 사이가, 짧게 깐 칸 클립 표(문서번호\n // 표)는 모든 행 사이가 떠 있어 맞닿아 쌓인 행이 없다 (경찰복제 특수복식 8쪽: \"경찰화\" 행으로 끝난 표 밑변 548.04 와 \"기타 | 사. 항공\n // 휘장\" 표 윗변 545.28 사이 2.76pt, 경계변경 실태조사서 5쪽 \"관할구역도 작성방법\" 2.76pt — HWPX 두 표). 한 표 안의 틈은 이보다 좁다:\n // 이중선 테두리 행 경계 0.95~1.08pt(지적공부 열람 신청서·자율방범대 신고서, 양끝 괘선), 클립 없는 얇은 빈 행 0.96pt(수수료 표, 한쪽 괘선),\n // 셀 간격 표 1.92~2.52pt. 재해유족급여 청구서의 청구인 칸 묶음 사이(1.92·2.04pt)도 이 폭이라 종전대로 한 표로 둔다(정답 대조로 가를 근거 없음)\n const stacked = new Set<number>()\n if (ruledGaps.some(g => g.axis === \"y\" && g.both && g.hi - g.lo > STACKED_TABLE_GAP)) {\n for (let i = 0; i < cells.length; i++) {\n if (tableClip[i]) continue\n const k = edgeBin(cells[i].y2)\n for (let d = -1; d <= 1; d++) {\n if ((startsY.get(k + d) ?? []).some(j => j !== i && !tableClip[j] && parent[j] === parent[i] && adjacent(cells[i], cells[j]))) stacked.add(find(i))\n }\n }\n }\n const comp = cells.map((_, i) => find(i))\n for (const g of ruledGaps) {\n if (tableClip[g.i] || tableClip[g.j]) continue\n if (g.axis === \"y\" && g.both && g.hi - g.lo > STACKED_TABLE_GAP && comp[g.i] !== comp[g.j] && stacked.has(comp[g.i]) && stacked.has(comp[g.j])) continue\n const ra = find(g.i), rb = find(g.j)\n if (ra !== rb) root[ra] = rb\n }\n // 쪽 첫머리 띠의 떨어진 조각 — 앞 쪽에서 쪼개져 넘어온 행의 이 쪽 조각은 글 있는 칸에만 클립이 있어(빈 칸 조각은 클립 없음) 한 띠의\n // 클립이 틈을 두고 떨어진다. 변을 맞대지 못한 조각은 따로 묶여 표가 갈렸다(석유사업법 과태료 10쪽: 금액 칸 조각 \"만 원\"·\"0만 원\"\n // 9개와 3개가 1×9·1×3 표로, 첫 열 조각만 아래 전폭 행과 한 표). 앞 쪽이 표 칸으로 끝났고, 쪽 첫 내용인 한 띠(윗변·밑변이 같음)의\n // 조각들이 밑변에 맞붙은 한 칸 위에 모두 놓이며 그 가운데 하나가 그 칸과 이웃이면 한 표로 묶는다 — 띠의 빈 자리는 채움 칸이 된다\n if (prev.lastCells.length) {\n const headTop = pageHeight * (1 - HEADER_BAND)\n let top = -Infinity\n for (let i = 0; i < cells.length; i++) if (parent[i] < 0 && !tableClip[i]) top = Math.max(top, cells[i].y2)\n const band = cells.flatMap((c, i) => parent[i] < 0 && !tableClip[i] && Math.abs(c.y2 - top) <= CLIP_COORD_TOL ? [i] : [])\n const y1 = band.length ? cells[band[0]].y1 : 0\n if (band.length >= 2 && band.every(i => Math.abs(cells[i].y1 - y1) <= CLIP_COORD_TOL) && !textPoints.some(p => p.y > top + CLIP_EDGE_TOL && p.y < headTop)) {\n for (let f = 0; f < cells.length; f++) {\n const b = cells[f]\n if (parent[f] >= 0 || tableClip[f] || Math.abs(b.y2 - y1) > CLIP_ADJ_GAP) continue\n const on = band.filter(i => cells[i].x1 >= b.x1 - CLIP_COORD_TOL && cells[i].x2 <= b.x2 + CLIP_COORD_TOL)\n if (on.length < 2 || !on.some(i => adjacent(cells[i], b))) continue\n for (const i of on) { const ra = find(i), rb = find(f); if (ra !== rb) root[ra] = rb }\n }\n }\n }\n const groups = new Map<number, number[]>()\n for (let i = 0; i < cells.length; i++) {\n const r = find(i)\n const g = groups.get(r)\n if (g) g.push(i)\n else groups.set(r, [i])\n }\n const containers: ClipRect[] = cells.filter((_, i) => isContainer[i])\n const groupSize = (i: number): number => groups.get(find(i))?.length ?? 0\n const isGridMember = (i: number): boolean => groupSize(i) >= CLIP_MIN_GROUP\n // 네 변이 모두 획 괘선인 사각형 — 서식의 1칸 틀(지정서·영치증·선서문 실측). 한컴은 본문 영역\n // (여백 안쪽 전체)에도 클립을 깔고 그 안에 페이지의 모든 표가 들어가므로, 획 없는 큰 컨테이너를\n // 틀로 보면 페이지가 통째로 1×1 표가 된다 (채용공고 PDF 실측 회귀) — 획 4변을 요구해 가른다\n const framed = (r: ClipRect): boolean =>\n edgeStroked(strokedH, \"h\", r.y1, r.x1, r.x2) && edgeStroked(strokedH, \"h\", r.y2, r.x1, r.x2)\n && edgeStroked(strokedV, \"v\", r.x1, r.y1, r.y2) && edgeStroked(strokedV, \"v\", r.x2, r.y1, r.y2)\n // 테두리 없는 1칸 틀(별표·선서문 바깥 틀) 과 본문 영역 클립의 구분 — 문서 단위 반복 통계는 반증됐다\n // (v4.12.3 실측: 채용공고 본문 영역 클립은 페이지마다 y1 이 73~91 로 달라 같은 사각형이 아니고, 별표는\n // 1쪽짜리라 반복 자체가 없다). 대신 기하로 가른다: 본문 영역은 여백 바로 안쪽(y1 ≈ 9~11%)에서 시작해\n // 머리말·쪽번호 말고는 바깥에 글이 없지만, 별표 틀은 제목(\"■ ○○법 [별표 N]\"·별표명) 아래(y1 26~52%)\n // 에서 시작해 그 위에 본문 글이 있다. 위쪽 머리말 띠(8%)를 뺀 곳에 글이 있고 틀 안에도 글이 있어야 한다\n // 폭 조건: 별표 틀은 본문 폭 대부분(70~86%)을 차지한다 — 2단 채용공고의 단 상자(폭 38%, pair06)는 제외\n const titledFrame = (r: ClipRect): boolean =>\n r.y1 >= pageHeight * TITLED_FRAME_MIN_TOP\n && (r.x2 - r.x1) >= pageWidth * TITLED_FRAME_MIN_WIDTH\n && 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)\n && textPoints.some(p => p.x > r.x1 && p.x < r.x2 && p.y > r.y1 && p.y < r.y2)\n /** 홀로 선 틀 — 다른 클립을 품고, 그리드 멤버가 아니며, 테두리가 그려져 있거나 제목 아래 틀이다 */\n const loneFrame = (i: number): boolean => isContainer[i] && !isGridMember(i) && (framed(cells[i]) || titledFrame(cells[i]))\n const headY = pageHeight * (1 - HEADER_BAND)\n /** 앞 쪽에서 넘어온 칸의 이어짐이면 그 앞 쪽 칸 — 한컴은 쪽을 넘는 칸을 쪽마다 그 쪽에 그려진 부분만 클립으로 깔아, 뒤 쪽\n * 조각은 이웃 없는 홀로 선 클립이 된다. 테두리 없는 표(경사형 휠체어리프트 기준 5×1 의 본문 칸이 47쪽을 넘음)는 틀로도\n * 안 잡혀 그 안의 표들이 칸을 잃고 최상위로 빠졌다. 앞 쪽 마지막 내용인 칸과 좌우 변이 같고(CONT_X_TOL) 이 쪽 첫 내용인\n * 최상위 클립(그 위엔 머리말 띠 글만)만 이어짐으로 본다 — 쪽 첫머리에 서식 번호 \"(서식 5)\" 가 붙은 다음 서식 틀은 가른다.\n * 앞 쪽에도 같은 사각형이 있으면(앞 칸 자신 말고) 쪽마다 같은 자리에 놓인 절 제목 띠다(공문 작성 안내서 \"[2]\"·\"[3]\" 띠가\n * 앞 쪽 마지막 틀과 폭이 같아 그 틀에 붙던 것) */\n const continues = (i: number): ClipRect | undefined => {\n if (!prev.lastCells.length || parent[i] >= 0 || tableClip[i] || isGridMember(i)) return undefined\n const c = cells[i]\n const from = prev.lastCells.find(b => Math.abs(b.x1 - c.x1) <= CONT_X_TOL && Math.abs(b.x2 - c.x2) <= CONT_X_TOL)\n if (!from) return undefined\n if (prev.clips.some(k => sameRect(k, c) && !sameRect(k, from))) return undefined\n if (textPoints.some(p => p.y > c.y2 && p.y < headY)) return undefined\n if (cells.some(o => o.y1 >= c.y2 - CLIP_EDGE_TOL && (o.y1 + o.y2) / 2 < headY)) return undefined\n return from\n }\n /** 이 클립의 부모가 안쪽 표를 받을 수 있는 셀인가 — 그리드의 셀이거나 홀로 선 틀 */\n const parentAttachable = (i: number): boolean => effParent(i) >= 0\n /** 부모 안에 이 클립 하나뿐인가 — 테두리 없는 바깥 틀 안에 테두리 있는 1칸 표 하나(선서문·서약서류).\n * 테두리 없는 틀은 본문 영역 클립과 구분이 안 돼 표로 못 삼지만, 그 안에 상자 하나만 있는 꼴은\n * 본문 영역(칩·표가 여럿)과 다르다 — 안쪽 상자만이라도 1×1 표로 낸다 */\n const onlyChild = (i: number): boolean => parent[i] >= 0 && parent.filter(p => p === parent[i]).length === 1\n const grids: TableGrid[] = []\n for (const idxs of groups.values()) {\n const first = idxs[0]\n if (tableClip[first]) continue\n const parentRect = parentAttachable(first) ? cells[effParent(first)] : undefined\n if (idxs.length < CLIP_MIN_GROUP) {\n // 이웃 없는 단독 클립은 원칙적으로 표가 아니다(글상자·그림·머리말·본문 영역). 예외 두 가지 —\n // 1칸 틀: ① 다른 클립을 품고 테두리가 그려진 틀(선서문·각서류의 바깥 1칸 표) ② 그런 틀 안에\n // 홀로 든 테두리 있는 클립(그 안의 1칸 표). 둘 다 HWP 에서는 1×1 표이고 안에 문단·표가 층으로\n // 들어 있다 — 1×1 그리드로 내서 소비측이 틀 셀의 blocks 에 안쪽 표를 넣게 한다. 앞 쪽 칸의 이어짐도 1칸 조각으로\n // 내고 표시해 둔다 — 문서 단계(mergeContinuedCells)가 앞 쪽 그 칸에 붙인다\n const from = continues(first)\n if (!from && !loneFrame(first) && !((parentRect || onlyChild(first)) && framed(cells[first]))) continue\n const r = cells[first]\n grids.push({\n rowYs: [r.y2, r.y1], colXs: [r.x1, r.x2],\n bbox: { x1: r.x1, y1: r.y1, x2: r.x2, y2: r.y2 },\n vertexRadius: 1,\n cells: [{ row: 0, col: 0, rowSpan: 1, colSpan: 1, bbox: { x1: r.x1, y1: r.y1, x2: r.x2, y2: r.y2 } }],\n ...(parentRect ? { clipParent: parentRect } : {}),\n ...(from ? { continues: from } : {}),\n })\n continue\n }\n const gs = ruledGaps.filter(g => find(g.i) === find(first))\n const members = gs.length ? closeGaps(idxs.map(i => cells[i]), gs) : idxs.map(i => cells[i])\n\n // 테두리 없는 표 판정 — 변 4개씩 획 괘선 유무\n let edges = 0, invisible = 0\n for (const r of members) {\n edges += 4\n if (!edgeStroked(strokedH, \"h\", r.y1, r.x1, r.x2)) invisible++\n if (!edgeStroked(strokedH, \"h\", r.y2, r.x1, r.x2)) invisible++\n if (!edgeStroked(strokedV, \"v\", r.x1, r.y1, r.y2)) invisible++\n if (!edgeStroked(strokedV, \"v\", r.x2, r.y1, r.y2)) invisible++\n }\n if (invisible / edges < CLIP_MIN_INVISIBLE) continue\n\n const colXs = dropSliverGaps(clusterCoords(members.flatMap(r => [r.x1, r.x2])))\n const rowYs = clusterCoords(members.flatMap(r => [r.y1, r.y2])).reverse() // 위→아래 내림차순\n const band = parentRect ? undefined : carriedBandTop(colXs, rowYs[0], strokedH, strokedV, textPoints, cells, prev.lastCells, headY)\n if (band !== undefined) rowYs.unshift(band) // 띠 칸은 아래 채움 칸으로 들어간다\n const numRows = rowYs.length - 1, numCols = colXs.length - 1\n if (numRows < 1 || numCols < 1) continue\n\n const occupied = Array.from({ length: numRows }, () => new Array<boolean>(numCols).fill(false))\n const out: ExtractedCell[] = []\n for (const r of members) {\n const c0 = nearestIndex(colXs, r.x1), c1 = nearestIndex(colXs, r.x2)\n const r0 = nearestIndex(rowYs, r.y2), r1 = nearestIndex(rowYs, r.y1)\n if (c1 <= c0 || r1 <= r0) continue\n let clash = false\n for (let rr = r0; rr < r1 && !clash; rr++) for (let cc = c0; cc < c1; cc++) if (occupied[rr][cc]) { clash = true; break }\n if (clash) continue // 겹치는 클립(이중 그리기) — 먼저 온 셀 유지\n for (let rr = r0; rr < r1; rr++) for (let cc = c0; cc < c1; cc++) occupied[rr][cc] = true\n 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 } })\n }\n // 클립이 안 덮은 칸(중첩표 바깥 셀의 잔여 영역 등)은 1×1 빈 셀로 채워 그 자리 글이 표 밖으로 새지 않게 한다\n for (let rr = 0; rr < numRows; rr++) {\n for (let cc = 0; cc < numCols; cc++) {\n if (occupied[rr][cc]) continue\n 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] }, filler: true })\n }\n }\n addNarrowEdgeCols(colXs, rowYs, out, fillRects)\n grids.push({\n rowYs, colXs,\n bbox: { x1: colXs[0], y1: rowYs[numRows], x2: colXs[colXs.length - 1], y2: rowYs[0] },\n vertexRadius: 1,\n cells: out,\n ...(parentRect ? { clipParent: parentRect } : {}),\n })\n }\n // 이 쪽 마지막 내용인 최상위 칸 — 가장 낮은 칸들이고, 그 아래엔 꼬리말 띠(HEADER_BAND) 글·클립만 있어야 한다. 본문 영역\n // 클립(채용공고: 쪽마다 본문 전체를 감싼 클립) 안의 칸은 좌우 변이 같은 그 클립에 싸여 있어 빼낸다 — 다음 쪽 본문 영역\n // 클립이 쪽 전체를 이어짐으로 삼키지 않게\n const tops = grids.filter(g => !g.clipParent).flatMap(g => g.cells!.filter(c => !c.filler).map(c => c.bbox))\n let lastCells: ClipRect[] = []\n if (tops.length) {\n const bottom = Math.min(...tops.map(b => b.y1))\n const footY = pageHeight * HEADER_BAND\n const below = (y: number): boolean => y < bottom - CLIP_EDGE_TOL && y > footY\n if (!textPoints.some(p => below(p.y)) && !cells.some(o => o.y2 < bottom && below((o.y1 + o.y2) / 2))) {\n lastCells = tops.filter(b => Math.abs(b.y1 - bottom) <= CLIP_COORD_TOL\n && !cells.some(k => Math.abs(k.x1 - b.x1) <= CONT_X_TOL && Math.abs(k.x2 - b.x2) <= CONT_X_TOL && k.y2 > b.y2 + CLIP_EDGE_TOL && contains(k, b)))\n }\n }\n return { grids, containers, page: { lastCells, clips: cells } }\n}\n\n/** 괘선 그어진 틈의 최대 폭 (pt) — 셀 간격 표(행정업무운영 편람 설계 기준 표 1.92~2.52pt)·아래 여백만큼 짧은 칸 클립\n * (결재문서 문서번호 표 1.32~1.44pt) 실측 */\nconst CLIP_SPACING_MAX = 3\n/** 쌓은 두 표 사이로 보는 가로 틈의 최소 폭 (pt) — 한 표 안 틈의 실측 최대(셀 간격 2.52pt)보다 넓어야 한다. 따로 쌓은 표 2.76pt 실측 */\nconst STACKED_TABLE_GAP = 2.6\n\n/** 괘선 틈 쌍 — 두 클립(i 가 위·왼쪽), 축, 양끝 좌표(y 틈의 lo 는 아래 칸 윗변·hi 는 위 칸 밑변), 두 클립이 겹친 직각 구간 e1~e2,\n * 괘선으로 본 닫을 좌표(ruleEnd) */\ninterface RuledGap { i: number; j: number; axis: \"x\" | \"y\"; lo: number; hi: number; e1: number; e2: number; to: number; both: boolean }\n\n/**\n * 괘선이 그어진 좁은 틈을 사이에 둔 같은 층 이웃 클립 쌍 — 한 표의 칸으로 묶는다(좌표는 격자를 만들 때 closeGaps 가 닫는다).\n * 한컴은 셀 간격(cellSpacing)이 있는 표는 칸 클립 사이를 그만큼 띄우고, 결재문서 문서번호 표(4×2)는 칸 클립을 아래 여백\n * (1.41pt)만큼 짧게 깐다. 변 공유(CLIP_ADJ_GAP 0.15pt)로는 이웃이 안 돼 칸마다 홀로 떨어져, 문서번호 표는 행마다 1×2 표로\n * 흩어지고 설계 기준 표는 획 경로가 내면서 칸 안 서식 예시(4×7)가 제자리를 잃고 1칸 틀로 따로 나갔다.\n * 틈 쌍은 틈이 비어 있어야 한다(다른 클립이 없음 — 높이 2.76pt 짜리 진짜 빈 행을 틈으로 먹지 않게, 신구조문대비표 실측).\n * ① 직각 두 변이 다 맞고 틈 한쪽 끝(칸 클립 변)에 겹친 폭의 절반 이상을 덮는 괘선이 그어진 쌍 — 표의 틈 폭을 확인한다.\n * ② 괘선이 있거나 직각 두 변이 다 맞는 쌍 가운데, 틈 폭이 ①로 확인한 묶음의 틈 폭과 같은(0.3pt 안) 쌍 — 셀 간격은 가로세로 같은\n * 값이다. 머리 칸 아래 여러 칸(직각 변 한쪽만 맞음, 정책연구 주체별 역할 표)과 세로 테두리를 안 그린 셀 간격 표(응시번호 3×4 가\n * 열마다 3×1 로 갈리던 것)를 잇는다. 직각 변이 한쪽만 맞는 쌍은 같은 축 틈 폭만 보증한다 — 조직도 상자 사이 2.88pt 가로 틈이\n * 3pt 아래 다른 표와의 세로 틈을 잇지 않게(mel-001).\n * 괘선 없는 틈(칸 안 글줄 클립 0.72pt, 머리말 영역과 본문 틀 1.0pt)은 종전대로 가른다.\n */\nfunction findRuledGaps(cells: ClipRect[], parent: number[], strokedH: LineSegment[], strokedV: LineSegment[]): RuledGap[] {\n const al = (u: number, v: number): boolean => Math.abs(u - v) <= CLIP_COORD_TOL\n /** 틈 사각형 안에 같은 층 다른 클립이 있는가 */\n const occupied = (i: number, j: number, x1: number, y1: number, x2: number, y2: number): boolean =>\n cells.some((k, n) => n !== i && n !== j && parent[n] === parent[i] && overlap(k.x1, k.x2, x1, x2) > CLIP_ADJ_GAP && overlap(k.y1, k.y2, y1, y2) > CLIP_ADJ_GAP)\n /** i·j 사이 틈 — 세로(i 위 j 아래)·가로(i 왼쪽 j 오른쪽) 가운데 조건 맞는 것 */\n const gapBetween = (i: number, j: number): RuledGap | undefined => {\n const a = cells[i], b = cells[j]\n const gv = a.y1 - b.y2\n if (gv > CLIP_ADJ_GAP && gv <= CLIP_SPACING_MAX && overlap(a.x1, a.x2, b.x1, b.x2) > CLIP_EDGE_TOL && (al(a.x1, b.x1) || al(a.x2, b.x2))\n && !occupied(i, j, Math.max(a.x1, b.x1), b.y2, Math.min(a.x2, b.x2), a.y1)) {\n const e1 = Math.max(a.x1, b.x1), e2 = Math.min(a.x2, b.x2)\n return { i, j, axis: \"y\", lo: b.y2, hi: a.y1, e1, e2, ...ruleEnd(strokedH, \"h\", b.y2, a.y1, e1, e2) }\n }\n const gh = b.x1 - a.x2\n if (gh > CLIP_ADJ_GAP && gh <= CLIP_SPACING_MAX && overlap(a.y1, a.y2, b.y1, b.y2) > CLIP_EDGE_TOL && (al(a.y1, b.y1) || al(a.y2, b.y2))\n && !occupied(i, j, a.x2, Math.max(a.y1, b.y1), b.x1, Math.min(a.y2, b.y2))) {\n const e1 = Math.max(a.y1, b.y1), e2 = Math.min(a.y2, b.y2)\n return { i, j, axis: \"x\", lo: a.x2, hi: b.x1, e1, e2, ...ruleEnd(strokedV, \"v\", a.x2, b.x1, e1, e2) }\n }\n return undefined\n }\n /** 틈 한쪽 끝(0.5pt 안)에 e1~e2 의 절반 이상을 덮는 괘선이 있는가 — 칸 테두리는 칸 클립 변에 그어진다. 틈 한가운데 뜬 괘선은\n * 글줄마다 클립을 까는 PDF 의 줄 사이 표 괘선이다(Microsoft Print To PDF 성과보고서: 행 괘선이 위아래 글줄 클립 사이 0.72pt 안쪽 —\n * 잇으면 \"업무(①-1)\" 의 ① 글줄들이 세로 1열 표로 빠져나갔다) */\n const ruled = (g: RuledGap): boolean => (g.axis === \"y\" ? strokedH : strokedV).some(l => {\n const pos = g.axis === \"y\" ? l.y1 : l.x1\n return (Math.abs(pos - g.lo) <= 0.5 || Math.abs(pos - g.hi) <= 0.5)\n && (g.axis === \"y\" ? overlap(l.x1, l.x2, g.e1, g.e2) : overlap(l.y1, l.y2, g.e1, g.e2)) >= (g.e2 - g.e1) * STROKE_COVER\n })\n const root = cells.map((_, i) => i)\n const find = (i: number): number => { while (root[i] !== i) { root[i] = root[root[i]]; i = root[i] } return i }\n const found: RuledGap[] = []\n /** 묶음 뿌리 → 확인된 틈 (축·폭) */\n const widths = new Map<number, Array<{ axis: \"x\" | \"y\"; w: number }>>()\n const link = (g: RuledGap): void => {\n found.push(g)\n const ri = find(g.i), rj = find(g.j)\n const w = [...(widths.get(rj) ?? []), ...(ri !== rj ? widths.get(ri) ?? [] : []), { axis: g.axis, w: g.hi - g.lo }]\n root[ri] = rj\n widths.set(rj, w)\n }\n const cand: Array<[RuledGap, boolean]> = []\n for (let i = 0; i < cells.length; i++) {\n for (let j = 0; j < cells.length; j++) {\n if (i === j || parent[i] !== parent[j]) continue\n const g = gapBetween(i, j)\n if (!g) continue\n const a = cells[i], b = cells[j]\n const both = g.axis === \"y\" ? al(a.x1, b.x1) && al(a.x2, b.x2) : al(a.y1, b.y1) && al(a.y2, b.y2)\n const isRuled = ruled(g)\n if (both && isRuled) link(g)\n else if (both || isRuled) cand.push([g, both])\n }\n }\n if (!found.length) return []\n // ② 확인된 틈 폭과 같은 틈 — 이은 묶음이 다시 다른 쌍을 보증할 수 있어 더 늘지 않을 때까지\n for (let grew = true; grew;) {\n grew = false\n for (let k = cand.length - 1; k >= 0; k--) {\n const [g, both] = cand[k], w = g.hi - g.lo\n if (![find(g.i), find(g.j)].some(r => (widths.get(r) ?? []).some(s => (both || s.axis === g.axis) && Math.abs(s.w - w) <= CLIP_COORD_TOL))) continue\n link(g)\n cand.splice(k, 1)\n grew = true\n }\n }\n return found\n}\n\n/** 틈을 닫을 좌표 — 겹친 폭 e1~e2 의 절반 이상을 덮는 괘선이 한쪽 끝(0.5pt 안)에만 있으면 그 끝(행 경계는 괘선 자리 — 문서번호 표는\n * 아래 칸 윗변), 양끝·없음이면 가운데(칸마다 제 테두리를 그리는 셀 간격 표) */\nfunction ruleEnd(lines: LineSegment[], dir: \"h\" | \"v\", lo: number, hi: number, e1: number, e2: number): { to: number; both: boolean } {\n const at = (p: number): boolean => lines.some(l =>\n Math.abs((dir === \"h\" ? l.y1 : l.x1) - p) <= 0.5 && (dir === \"h\" ? overlap(l.x1, l.x2, e1, e2) : overlap(l.y1, l.y2, e1, e2)) >= (e2 - e1) * STROKE_COVER)\n const atLo = at(lo), atHi = at(hi)\n return { to: atLo && !atHi ? lo : atHi && !atLo ? hi : (lo + hi) / 2, both: atLo && atHi }\n}\n\n/**\n * 한 표(묶음) 칸들의 괘선 틈을 닫은 사본 — 같은 틈(축·양끝)의 쌍들을 한 줄로 모아(직각 구간은 합친 범위), 그 범위에 걸치거나 셀 간격\n * 하나 거리로 이어진 칸 가운데 틈 끝에 선 변을 한 좌표로 옮긴다. 쌍을 못 이룬 칸(병합 칸, 쪽 넘김으로 옆 칸 클립이 없는 칸)의 변도\n * 같은 경계가 된다 — 옮긴 변과\n * 안 옮긴 변이 따로 서면 유령 열·행이 생긴다(관인 종류 표 9×3 → 9×7, 설계 기준 표 5×2 → 5×3). 닫을 좌표는 이 표 안에서 틈\n * 없이 맞닿은 칸들이 이미 그 끝을 경계로 쓰면 그 끝(조직도 하단: 한 열에서만 아래 칸이 1.08pt 짧음 — 다른 열의 경계와 같은\n * 좌표여야 유령 행이 없다), 아니면 괘선 자리(ruleEnd)\n */\nfunction closeGaps(members: ClipRect[], gaps: RuledGap[]): ClipRect[] {\n const al = (u: number, v: number): boolean => Math.abs(u - v) <= CLIP_COORD_TOL\n const lines: Array<{ axis: \"x\" | \"y\"; lo: number; hi: number; e1: number; e2: number; to: number; both: boolean }> = []\n for (const g of gaps) {\n const l = lines.find(k => k.axis === g.axis && al(k.lo, g.lo) && al(k.hi, g.hi))\n if (l) { l.e1 = Math.min(l.e1, g.e1); l.e2 = Math.max(l.e2, g.e2); l.both &&= g.both } else lines.push({ ...g })\n }\n const spacing = gaps.some(g => g.axis === \"x\") && gaps.some(g => g.axis === \"y\")\n // 실제 빈 열은 같은 좁은 틈이 표 높이 대부분의 여러 행에서 반복된다(정답 17×11: 6행,\n // 27×5: 19~24행). 독립 상자 사이의 시각적 간격은 한 번뿐이다(창원·조직도·머리표).\n // 한 축의 양끝 괘선만으로 빈 행까지 추정하지 않는다.\n const repeatedBlankColumn = (l: typeof lines[number]): boolean => {\n if (spacing || l.axis !== \"x\" || !l.both) return false\n const spans = gaps.filter(g => g.axis === \"x\" && al(g.lo, l.lo) && al(g.hi, l.hi))\n .map(g => [g.e1, g.e2] as const).sort((a, b) => a[0] - b[0])\n if (spans.length < 3) return false\n let covered = 0, end = -Infinity, distinct = 0\n for (const [lo, hi] of spans) {\n if (hi <= end + CLIP_COORD_TOL) continue\n covered += hi - Math.max(lo, end)\n end = hi\n distinct++\n }\n const height = Math.max(...members.map(r => r.y2)) - Math.min(...members.map(r => r.y1))\n return distinct >= 3 && covered >= height * 0.65\n }\n const out = members.map(r => ({ ...r }))\n for (const l of lines) {\n // 틈 없이 맞닿은 칸 쌍이 틈 한쪽 끝을 경계로 쓰는가 (y: 위 칸 밑변 = 아래 칸 윗변)\n const shared = (p: number): boolean => members.some(u => members.some(v => u !== v\n && (l.axis === \"y\"\n ? Math.abs(u.y1 - v.y2) <= CLIP_ADJ_GAP && al(u.y1, p) && overlap(u.x1, u.x2, v.x1, v.x2) > CLIP_EDGE_TOL\n : Math.abs(u.x2 - v.x1) <= CLIP_ADJ_GAP && al(u.x2, p) && overlap(u.y1, u.y2, v.y1, v.y2) > CLIP_EDGE_TOL)))\n const atLo = shared(l.lo), atHi = shared(l.hi)\n if (repeatedBlankColumn(l) && !atLo && !atHi) continue\n const to = atLo && !atHi ? l.lo : atHi && !atLo ? l.hi : l.to\n // 틈 끝에 선 칸 — 틈 줄 범위에 걸치거나 셀 간격 하나 거리로 이어지는 칸까지 (옆 열이 셀 간격만큼 떨어져 있고 그 열 칸의 짝이\n // 쪽 넘김으로 없어도 같은 행 경계다: 설계 기준 표 \"구분\" 칸)\n const onEdge = (r: ClipRect): boolean => l.axis === \"y\" ? al(r.y2, l.lo) || al(r.y1, l.hi) : al(r.x2, l.lo) || al(r.x1, l.hi)\n const span = (r: ClipRect): [number, number] => l.axis === \"y\" ? [r.x1, r.x2] : [r.y1, r.y2]\n const take = new Set<number>()\n for (let grew = true; grew;) {\n grew = false\n for (let n = 0; n < members.length; n++) {\n if (take.has(n) || !onEdge(members[n])) continue\n const [a1, a2] = span(members[n])\n if (overlap(a1, a2, l.e1, l.e2) <= -(CLIP_SPACING_MAX + CLIP_COORD_TOL)) continue\n take.add(n)\n l.e1 = Math.min(l.e1, a1); l.e2 = Math.max(l.e2, a2)\n grew = true\n }\n }\n for (const n of take) {\n const r = members[n], s = out[n]\n if (l.axis === \"y\") {\n if (al(r.y2, l.lo)) s.y2 = to\n if (al(r.y1, l.hi)) s.y1 = to\n } else {\n if (al(r.x2, l.lo)) s.x2 = to\n if (al(r.x1, l.hi)) s.x1 = to\n }\n }\n }\n return out\n}\n\n/**\n * 쪽 첫머리 클립 없는 띠의 윗변 — 앞 쪽에서 쪼개져 넘어온 행의 이 쪽 조각에 글이 한 칸도 없으면 한컴은 그 조각에 클립을 깔지\n * 않고 괘선만 그린다. 클립 격자는 그 아래 새 행부터 시작해, 쪽 넘김 잇기가 새 행을 쪼개진 행의 나머지로 합친다(농어촌정비법\n * 시설기준 \"조식 제공시설\" 칸 아래 새 행 \"※ 위 가목부터 …\" 가 앞 칸 글 끝에 붙었다). 앞 쪽이 표 칸으로 끝났고, 격자 윗변 위에\n * 격자 폭을 다 덮는 가로 괘선과 그 괘선까지 올라간 양끝 세로 괘선이 있으며, 그 사이와 위(머리말 띠 밖)에 글·클립이 없으면\n * 그 괘선을 윗변으로 한 빈 행을 격자 첫 행으로 더한다. 첫 조각은 글이 없어도 클립을 까니 새 표의 첫 행은 이렇게 비지 않는다\n */\nfunction carriedBandTop(colXs: number[], top: number, strokedH: LineSegment[], strokedV: LineSegment[],\n textPoints: ReadonlyArray<{ x: number; y: number }>, cells: ClipRect[], prevLast: ClipRect[], headY: number): number | undefined {\n const x1 = colXs[0], x2 = colXs[colXs.length - 1]\n if (!prevLast.some(b => overlap(b.x1, b.x2, x1, x2) > CLIP_EDGE_TOL)) return undefined\n let y: number | undefined\n for (const l of strokedH) {\n if (l.y1 > top + CLIP_MIN_H && l.x1 <= x1 + STROKE_NEAR && l.x2 >= x2 - STROKE_NEAR && (y === undefined || l.y1 < y)) y = l.y1\n }\n if (y === undefined || !edgeStroked(strokedV, \"v\", x1, top, y) || !edgeStroked(strokedV, \"v\", x2, top, y)) return undefined\n // 띠 안과 띠 위(머리말 띠 밖)에 글·클립이 없어야 쪽 첫 내용이다\n const above = Math.max(y, headY)\n const inX = (x: number): boolean => x > x1 && x < x2\n if (textPoints.some(p => inX(p.x) && p.y > top && p.y < above)) return undefined\n if (cells.some(c => overlap(c.x1, c.x2, x1, x2) > CLIP_EDGE_TOL && c.y1 >= top - CLIP_EDGE_TOL && c.y1 < above)) return undefined\n return y\n}\n\n/**\n * 격자 왼끝·오른끝에 붙은 좁은 채움 사각형을 열로 더한다 (colXs·셀 목록 제자리 수정). 한컴은 폭 CLIP_MIN_W 미만의\n * 빈 칸에는 셀 클립을 깔지 않고 배경 채움만 그려(행정업무운영 편람 머리 상자 \"1 | 제목\" 양옆 2.9pt 회색 띠 54개 실측),\n * 원본 표의 가장자리 열이 격자에서 빠진다. 채움이 격자 끝에 맞붙고 위아래 변이 행 경계와 맞을 때만 그 표의 칸으로 본다.\n */\nfunction addNarrowEdgeCols(colXs: number[], rowYs: number[], out: ExtractedCell[], fills: ClipRect[]): void {\n if (!fills.length) return\n const rowIdx = (y: number): number => { const i = nearestIndex(rowYs, y); return Math.abs(rowYs[i] - y) <= CLIP_COORD_TOL ? i : -1 }\n for (const side of [\"l\", \"r\"] as const) {\n const edge = side === \"l\" ? colXs[0] : colXs[colXs.length - 1]\n const hits = fills.filter(f => {\n const w = f.x2 - f.x1\n // 폭 1pt 미만은 채움으로 그린 괘선이다 — 칸이 아니다\n if (w < NARROW_FILL_MIN_W || w >= CLIP_MIN_W || Math.abs((side === \"l\" ? f.x2 : f.x1) - edge) > CLIP_COORD_TOL) return false\n const r0 = rowIdx(f.y2), r1 = rowIdx(f.y1)\n return r0 >= 0 && r1 > r0\n })\n if (!hits.length) continue\n let x = side === \"l\" ? Infinity : -Infinity\n for (const f of hits) x = side === \"l\" ? Math.min(x, f.x1) : Math.max(x, f.x2)\n if (side === \"l\") { colXs.unshift(x); for (const c of out) c.col++ } else colXs.push(x)\n const col = side === \"l\" ? 0 : colXs.length - 2\n const [cx1, cx2] = [colXs[col], colXs[col + 1]]\n const taken = new Array<boolean>(rowYs.length - 1).fill(false)\n for (const f of hits) {\n const r0 = rowIdx(f.y2), r1 = rowIdx(f.y1)\n if (taken.slice(r0, r1).some(Boolean)) continue\n taken.fill(true, r0, r1)\n out.push({ row: r0, col, rowSpan: r1 - r0, colSpan: 1, bbox: { x1: cx1, y1: rowYs[r1], x2: cx2, y2: rowYs[r0] } })\n }\n for (let r = 0; r < taken.length; r++) {\n if (!taken[r]) out.push({ row: r, col, rowSpan: 1, colSpan: 1, bbox: { x1: cx1, y1: rowYs[r + 1], x2: cx2, y2: rowYs[r] }, filler: true })\n }\n }\n}\n\n/**\n * 클립 정보로 line 그리드 정리 — 같은 표를 두 번 내거나 한 셀의 글을 격자로 썰지 않는다.\n * - 클립 그리드 안에 든 line 그리드: 제거 (실선 내부표는 클립 셀이 이미 담고 있다)\n * - 틀(container) 안에 든 line 그리드: 제거 — 지정서·영치증의 1칸 테두리 틀은 클립상 셀 하나인데,\n * 틀 안 작은 표(발신명의 | 직인)의 괘선이 틀 괘선과 교차해 line 경로가 틀 전체를 3×3 격자로\n * 만들고 본문 문단을 열로 찢는다(영치증 실측: \"1. 위 자동차는 자동차세(방세법」…\"). 틀 안 글은\n * 문단 경로로 흐르고 작은 표는 클립 그리드가 낸다 — HWP5 파서의 1칸 레이아웃 표 해체와 같은 모양\n * - 위아래로 쌓인 클립 표들과 그 사이 틈 글줄만 품은 line 그리드: 제거 — 틈을 지나는 안쪽 세로 괘선(verticals)이 없을 때\n */\nexport function dropGridsInside(lineGrids: TableGrid[], clipGrids: TableGrid[], containers: ClipRect[] = [], verticals: LineSegment[] = []): TableGrid[] {\n if (clipGrids.length === 0 && containers.length === 0) return lineGrids\n type Box = { x1: number; y1: number; x2: number; y2: number }\n const area = (b: Box): number => Math.max(0, b.x2 - b.x1) * Math.max(0, b.y2 - b.y1)\n // \"안에 든다\" 가 아니라 면적 절반 이상 겹치면 버린다 — 틀 괘선이 위 행들의 외곽선과 이어져\n // line 그리드가 틀보다 위로 뻗는 경우(영치증: 틀 144~668 vs line 그리드 144~766)도 잡는다.\n // 겹친 바깥 부분은 클립 그리드가 이미 셀로 담고 있어 잃는 글이 없다\n const overlapsHalf = (g: Box, b: Box): boolean => {\n const ix = Math.min(g.x2, b.x2) - Math.max(g.x1, b.x1)\n const iy = Math.min(g.y2, b.y2) - Math.max(g.y1, b.y1)\n if (ix <= 0 || iy <= 0) return false\n const ga = area(g)\n return ga > 0 && (ix * iy) / ga >= 0.5\n }\n // 안에 온전히 든 클립 표 둘 이상이 절반 넘게 덮고 표 사이·위아래 틈을 지나는 안쪽 세로 괘선이 없으면 버린다 — 바깥 세로 괘선(쪽 테두리 포함)이\n // 위아래로 쌓인 표들을 이어 line 그리드가 표들과 그 사이 글줄(지역 머리·각주·표 제목)을 한 표로 묶었고, 그 글줄이 틀 칸이 되어 표들보다 먼저\n // 풀렸다(수출 보도자료 12쪽 표 6개, hwp3 쪽 테두리 안 표 여럿). 틈을 안쪽 세로 괘선이 지나면 클립 없는 행이 든 온전한 표다(보도자료 공표 일정 5×2)\n const inside = (c: Box, g: Box): boolean => c.x1 >= g.x1 - 3 && c.x2 <= g.x2 + 3 && c.y1 >= g.y1 - 3 && c.y2 <= g.y2 + 3\n const stackedOnly = (g: TableGrid): boolean => {\n const stacked = clipGrids.filter(c => inside(c.bbox, g.bbox)).sort((a, b) => a.bbox.y1 - b.bbox.y1)\n if (stacked.length < 2 || g.colXs.length < 3 || stacked.reduce((s, c) => s + area(c.bbox), 0) < area(g.bbox) * 0.5) return false\n const crossed = (lo: number, hi: number): boolean => verticals.some(v =>\n v.x1 > g.bbox.x1 + 3 && v.x1 < g.bbox.x2 - 3 && Math.max(v.y1, v.y2) > lo + 1.5 && Math.min(v.y1, v.y2) < hi - 1.5)\n let cur = g.bbox.y1\n for (const c of stacked) {\n if (c.bbox.y1 > cur + 1.5 && crossed(cur, c.bbox.y1)) return false\n cur = Math.max(cur, c.bbox.y2)\n }\n return g.bbox.y2 <= cur + 1.5 || !crossed(cur, g.bbox.y2)\n }\n return lineGrids.filter(g => {\n if (clipGrids.some(c => overlapsHalf(g.bbox, c.bbox)) || stackedOnly(g)) return false\n if (containers.some(c => overlapsHalf(g.bbox, c))) return false\n return true\n })\n}\n","/**\n * 테이블 그리드에서 병합 셀 구조 추출 (line-detector.ts에서 분리).\n *\n * OpenDataLoader PDF의 createMatrix() 알고리즘을 참고하여 TypeScript로\n * clean-room 재구현한 것입니다.\n * Original algorithm: Copyright 2025-2026 Hancom, Inc. (Apache 2.0)\n * https://github.com/opendataloader-project/opendataloader-pdf\n */\n\nimport type { LineSegment, TableGrid, ExtractedCell } from \"./line-types.js\"\nimport { VERTEX_MERGE_FACTOR } from \"./line-types.js\"\n\n// ─── 셀 구조 추출 (Vertex 기반 정밀 병합 셀 감지) ─────\n\n/**\n * 테이블 그리드에서 셀 목록을 추출.\n * ODL의 createMatrix() 알고리즘:\n * - 수직선 존재 여부로 colSpan 감지 (75% 커버 기준)\n * - 수평선 존재 여부로 rowSpan 감지 (75% 커버 기준)\n * - 우하단→좌상단 propagation으로 병합 셀 정리\n * - 중복 행/열 제거\n */\nexport function extractCells(\n grid: TableGrid,\n horizontals: LineSegment[],\n verticals: LineSegment[],\n): ExtractedCell[] {\n const { rowYs, colXs } = grid\n const numRows = rowYs.length - 1\n const numCols = colXs.length - 1\n if (numRows <= 0 || numCols <= 0) return []\n\n // 경계선 존재 여부를 행렬로 사전 계산\n // vKinds[r][c] = colXs[c]에 row r 구간의 수직선 — 0 없음, SOLID 선 하나가 덮음, PIECED 토막 합집합으로만 덮음\n const vKinds: number[][] = Array.from({ length: numRows },\n (_, r) => Array.from({ length: numCols + 1 },\n (_, c) => verticalLineKind(verticals, colXs[c], rowYs[r], rowYs[r + 1], grid.vertexRadius)))\n const vBorders: boolean[][] = vKinds.map(row => row.map(k => k !== NO_LINE))\n\n // hBorders[r][c] = rowYs[r]에 col c 구간의 수평선이 있는지\n const hBorders: boolean[][] = Array.from({ length: numRows + 1 },\n (_, r) => Array.from({ length: numCols },\n (_, c) => hasHorizontalLine(horizontals, rowYs[r], colXs[c], colXs[c + 1], grid.vertexRadius)))\n\n // 셀이 이미 병합된 셀에 포함되는지 추적\n const occupied = Array.from({ length: numRows }, () => Array(numCols).fill(false))\n const cells: ExtractedCell[] = []\n\n for (let r = 0; r < numRows; r++) {\n for (let c = 0; c < numCols; c++) {\n if (occupied[r][c]) continue\n\n let colSpan = 1\n let rowSpan = 1\n\n // colSpan: 오른쪽 내부 경계에 수직선이 없으면 병합\n while (c + colSpan < numCols && !vBorders[r][c + colSpan]) {\n // 추가 검증: 확장하려는 영역의 모든 행에서 수직선이 없어야 함\n let canExpand = true\n for (let dr = 0; dr < rowSpan; dr++) {\n if (vBorders[r + dr][c + colSpan]) { canExpand = false; break }\n }\n if (!canExpand) break\n colSpan++\n }\n\n // rowSpan: 아래쪽 내부 경계에 수평선이 없으면 병합\n while (r + rowSpan < numRows) {\n let hasLine = false\n for (let dc = 0; dc < colSpan; dc++) {\n if (hBorders[r + rowSpan][c + dc]) { hasLine = true; break }\n }\n if (hasLine) break\n // 토막 합집합으로만 선 오른쪽 경계는 그 토막이 끊긴 아래 행까지 칸을 늘리지 않는다 — 위계 들여쓰기 선이 부모 행에서만 끊긴\n // 예산서(고흥 세출예산서: 좁은 위계 칸이 부모 행 \"고흥역사·문화인물조사\" 까지 내려가 글을 갈랐다)\n if (c + colSpan < numCols && vKinds[r][c + colSpan] === PIECED && !vBorders[r + rowSpan][c + colSpan]) break\n rowSpan++\n }\n\n // 병합 영역 마킹\n for (let dr = 0; dr < rowSpan; dr++) {\n for (let dc = 0; dc < colSpan; dc++) {\n occupied[r + dr][c + dc] = true\n }\n }\n\n cells.push({\n row: r, col: c, rowSpan, colSpan,\n bbox: {\n x1: colXs[c], y1: rowYs[r + rowSpan],\n x2: colXs[c + colSpan], y2: rowYs[r],\n },\n })\n }\n }\n\n return cells\n}\n\n/**\n * 특정 X 위치에 수직선이 Y 범위를 커버하는지 확인.\n * v2: 75% 커버 기준 + 동적 tolerance (vertex radius 기반)\n */\nconst NO_LINE = 0, SOLID = 1, PIECED = 2\n\nfunction verticalLineKind(\n verticals: LineSegment[], x: number, topY: number, botY: number, vertexRadius: number,\n): number {\n const tol = Math.max(VERTEX_MERGE_FACTOR * vertexRadius, 4)\n const cellH = Math.abs(topY - botY)\n if (cellH < 0.1) return NO_LINE\n const spans: Array<[number, number]> = []\n for (const v of verticals) {\n if (Math.abs(v.x1 - x) <= tol) {\n const overlapTop = Math.min(v.y2, topY)\n const overlapBot = Math.max(v.y1, botY)\n // 75% 커버 기준 (기존 50% → 병합 셀 내부 단선 오탐 방지)\n if (overlapTop - overlapBot >= cellH * 0.75) return SOLID\n if (overlapTop > overlapBot) spans.push([overlapBot, overlapTop])\n }\n }\n // 줄마다 끊어 그린 괘선은 토막 하나가 75% 를 못 덮는다 — 같은 x 토막들의 합집합으로 잰다(예산서 상세 칸: 19pt 토막 30개, 금액이\n // 이름 칸에 몰려 다섯 칸이 한 칸이 됐다)\n return spans.length > 1 && unionLength(spans) >= cellH * 0.75 ? PIECED : NO_LINE\n}\n\n/** 끝이 맞닿아(0.5pt 안) 이어진 토막 줄기 가운데 토막 셋 이상인 가장 긴 것의 길이 — 줄마다 끊어 그은 괘선은 끝 좌표가 같고 토막이\n * 여럿이다. 따로 그린 상자 옆변들은 상자 사이가 떠 있고(벡터 개념도를 표로 읽던 KS X ISO 704), 긴 선을 두 토막으로 나눠 그은 것은 종전\n * 판정 그대로 둔다(성과지표 표의 칸 구조가 바뀌는데 그 표의 정답이 없어 판단을 보류) */\nfunction unionLength(spans: Array<[number, number]>): number {\n spans.sort((a, b) => a[0] - b[0])\n let best = 0, [lo, hi] = spans[0], pieces = 1\n for (const [a, b] of spans.slice(1)) {\n if (a > hi + 0.5) { if (pieces >= 3) best = Math.max(best, hi - lo); lo = a; hi = b; pieces = 1 } else { hi = Math.max(hi, b); pieces++ }\n }\n return pieces >= 3 ? Math.max(best, hi - lo) : best\n}\n\n/**\n * 특정 Y 위치에 수평선이 X 범위를 커버하는지 확인.\n * v2: 75% 커버 기준 + 동적 tolerance\n */\nfunction hasHorizontalLine(\n horizontals: LineSegment[], y: number, leftX: number, rightX: number, vertexRadius: number,\n): boolean {\n const tol = Math.max(VERTEX_MERGE_FACTOR * vertexRadius, 4)\n for (const h of horizontals) {\n if (Math.abs(h.y1 - y) <= tol) {\n const cellW = Math.abs(rightX - leftX)\n if (cellW < 0.1) continue\n const overlapLeft = Math.max(h.x1, leftX)\n const overlapRight = Math.min(h.x2, rightX)\n const overlap = overlapRight - overlapLeft\n if (overlap >= cellW * 0.75) return true\n }\n }\n return false\n}\n\n","/**\n * PDF 위·아래첨자 — 한 시각 줄에서 본문보다 작은 글자 조각이 왼쪽 글자에 붙어(간격 0.35em 안) 기준선이 올라가면 위첨자,\n * 내려가면 아래첨자다(\"10⁴\"·\"m²\"·\"H₂O\"·\"x_i\"·\"commitments.⁴\"·\"Kim∗†\"). 줄 머리에 홀로 선 작은 조각(각주 본문 첫머리 \"¹ …\")은\n * 붙은 글자가 없어 건드리지 않는다.\n *\n * 태그는 글을 다 이은 뒤에 넣는다(tagScripts) — 줄 이음·칸 줄 병합 판정은 글 끝 글자를 보므로 평문으로 끝내야 종전과 같다.\n * 완성된 글에서 조각 글을 읽기 순서로 짚어 가다 조각 사이에 공백 아닌 차이가 보이면(하이픈 지움 등) 그 글은 태그 없이 둔다.\n */\n\nimport { tidyScriptTags, type ScriptKind } from \"../script-tags.js\"\n\ninterface ScriptItem { text: string; x: number; y: number; w: number; fontSize: number }\n\n/** x 순으로 놓인 한 시각 줄 조각의 첨자 종류 */\nexport function scriptKindsOfLine(sorted: ScriptItem[]): (ScriptKind | null)[] {\n const kinds: (ScriptKind | null)[] = sorted.map(() => null)\n if (sorted.length < 2) return kinds\n // 본문 글자 크기 = 글자 수로 가중한 최빈 크기, 본문 기준선 = 그 크기 조각 y 의 가운데값\n const weight = new Map<number, number>()\n for (const it of sorted) {\n const k = Math.round(it.fontSize * 2) / 2\n weight.set(k, (weight.get(k) ?? 0) + it.text.trim().length)\n }\n let bodyFs = 0, best = -1\n for (const [fs, n] of weight) if (n > best || (n === best && fs > bodyFs)) { best = n; bodyFs = fs }\n if (bodyFs <= 0) return kinds\n const ys = sorted.filter(it => Math.abs(it.fontSize - bodyFs) <= bodyFs * 0.12).map(it => it.y).sort((a, b) => a - b)\n const bodyY = ys[ys.length >> 1]\n const isBody = (it: ScriptItem) => it.fontSize > bodyFs * 0.85\n for (let i = 1; i < sorted.length; i++) {\n const it = sorted[i], prev = sorted[i - 1]\n const t = it.text.trim()\n if (!t || t.length > 16 || isBody(it)) continue\n if (!isBody(prev) && !kinds[i - 1]) continue\n const gap = it.x - (prev.x + prev.w)\n if (gap > bodyFs * 0.35 || gap < -bodyFs * 0.5) continue\n const dy = it.y - bodyY\n if (dy >= bodyFs * 0.15) kinds[i] = \"sup\"\n else if (dy <= -bodyFs * 0.08) kinds[i] = \"sub\"\n }\n return kinds\n}\n\n/**\n * 완성된 글에 첨자 태그를 넣는다 — lines 는 글을 만든 순서의 시각 줄(각 줄은 x 순). 조각 글이 순서대로,\n * 사이에 공백(공백·탭·줄바꿈)만 두고 나오지 않으면 원문 그대로 돌려준다\n */\nexport function tagScripts(text: string, lines: ScriptItem[][]): string {\n const marks: Array<[number, number, ScriptKind]> = []\n let cursor = 0\n for (const line of lines) {\n const kinds = scriptKindsOfLine(line)\n for (let k = 0; k < line.length; k++) {\n const t = line[k].text\n if (!t) continue\n const at = text.indexOf(t, cursor)\n if (at < 0 || /\\S/.test(text.slice(cursor, at))) return text\n if (kinds[k]) marks.push([at, at + t.length, kinds[k]!])\n cursor = at + t.length\n }\n }\n if (!marks.length) return text\n let out = text\n for (let m = marks.length - 1; m >= 0; m--) {\n const [s, e, kind] = marks[m]\n out = out.slice(0, s) + `<${kind}>` + out.slice(s, e) + `</${kind}>` + out.slice(e)\n }\n return tidyScriptTags(out)\n}\n","/**\n * PDF 줄 꺾임 이음 — 오른끝까지 찬 줄이 다음 줄로 넘어간 자리(자동 꺾임)를 원문 띄어쓰기대로 잇는다.\n *\n * 한컴은 한글 줄나눔 기준이 \"글자\"인 문단(HWPX breakNonLatinWord=KEEP_WORD — 본문의 80% 넘게 이 설정인 문서가 hwpx↔pdf\n * 424쌍 중 162쌍)을 어절 중간에서도 꺾는다(\"재⏎일학도의용군인\", \"공상공무⏎원\", \"또⏎는\"). 텍스트층은 줄 끝 공백 글자를 싣지 않고\n * 양쪽 정렬은 꺾인 자리와 무관하게 줄을 오른끝까지 채우므로 끝 x 로는 못 가른다. 양쪽 정렬이 어절 간격에 나눠 준 여유(slack)도\n * 재 보았으나 글자 모드 문서에서조차 여유 0~1.25em 구간마다 붙음 비율이 0.38~0.55 로 섞여 신호가 못 된다(좌표 정수 반올림·문단별 자간).\n * 가르는 신호는 글 자체에 있다 — hwpx↔pdf 417쌍 꺾임 후보 22,911곳을 HWPX 원문 띄어쓰기로 채점한 실측(2026-09-24):\n * 1. 다음 줄 첫 어절이 조사·어미뿐(\"는\", \"을\", \"에서\", \"다.\") → 앞 어절에 붙는다 (조사 어절 1,580곳 중 잘못 9곳)\n * 2. 앞 줄 끝 어절이 받침에 맞는 조사·어미로 끝난 온전한 어절(\"…을\", \"…는\", \"…히\", 받침 뒤 \"…이\") → 띄운다\n * (줄 끝 \"을\" 876곳 중 3·\"를\" 552곳 중 0·\"는\" 851곳 중 14 만 어절 중간)\n * 3. 문서 어휘 증거 — 꺾인 자리를 가로지르는 앞뒤 두 글자(없으면 한 글자)가 같은 문서의 줄 안에서 한 어절 안에만 나오면\n * 붙이고(재일학도·공상공무원), 어절 경계를 사이에 두고만 나오면 띄운다 (두 글자 증거 정밀도: 붙음 98.3%·띄움 99.3%).\n * 글자 쌍 증거가 갈리면 이어 붙인 꼴이 줄 안 한 어절로 나왔는지 본다(띄어 쓴 이웃 어절로도 나왔으면 아님, v4.15)\n * 근거가 없으면 띄운다 — 규칙이 모두 비껴간 자리(두 음절+ 조각)는 원문 붙음이 18%(804/4,455)뿐이다. 대부분 명사+명사\n * (\"기후⏎위기\" 원문 붙음 / \"비만⏎치료\" 원문 띄움)라 글로도 가를 수 없다. 잘못 붙임과 못 붙임의 어절 F1 비용은 거의 같다\n * (F1=2I/(Na+Nb): 잘못 붙임 −3.05/N, 못 붙임 −2.95/N).\n */\n\nimport type { BoundingBox, IRBlock } from \"../types.js\"\n\n/** 줄 글에서 판정에 방해되는 마크업 — 밑줄·취소선 표시 (page-blocks 가 아이템 글에 감싼 것) */\nconst MARKUP = /<\\/?u>|~~/g\n/** 줄마다 감싼 링크 표기 `[글](주소)` (links.ts applyLinkAnnotations) — 판정용 글에선 글만 남긴다 */\nconst LINK_MARKUP = /(?<!!)\\[([^\\]\\n]*)\\]\\([^)\\s]+\\)/g\nconst bump = <K>(m: Map<K, number>, k: K): void => { m.set(k, (m.get(k) ?? 0) + 1) }\n/** 두 글자(UTF-16 단위) → 수 키 — 문자열 키를 만들지 않아 쪽마다 모든 글을 훑는 비용을 줄인다 */\nconst pairKey = (a: number, b: number): number => a * 0x10000 + b\nconst bump2 = (m: Map<number, Map<number, number>>, k1: number, k2: number): void => {\n let inner = m.get(k1)\n if (!inner) m.set(k1, (inner = new Map()))\n bump(inner, k2)\n}\n\n/**\n * 문서 어휘 증거 — 줄 안 어절에서 모은 이웃 글자 쌍(한 어절 안 = 붙음)과 어절 경계를 사이에 둔 글자 쌍(띄움).\n * 한 글자+한 글자, 두 글자+두 글자 두 벌. 쪽을 처리하기 전에 그 쪽 줄을 더해(addLine) 앞 쪽들과 현재 쪽이 증거가 된다\n * (문서 전체를 먼저 모으는 것 대비 이득의 99% — 순이득 6,998 대 7,083 — 쪽 단위만은 6,567).\n * 줄 끝 어절도 넣는다: 꺾인 조각이어도 그 안의 이웃 글자는 참이고, 꺾인 자리 자체는 한 줄 안에 없다.\n */\nexport class WrapLexicon {\n private readonly joined1 = new Map<number, number>()\n private readonly joined2 = new Map<number, Map<number, number>>()\n private readonly spaced1 = new Map<number, number>()\n private readonly spaced2 = new Map<number, Map<number, number>>()\n /** 줄 안(줄 첫·끝 어절 제외 — 꺾인 조각일 수 있다) 홀로 선 어절 수 — 한 음절 조각이 낱말(\"등\"·\"및\"·\"그\")인지 가른다 */\n private readonly words = new Map<string, number>()\n /** 줄 안에서 이웃한 두 어절(\"개인정보 처리\") — 이어 붙인 꼴 증거(joinedWord)의 거부권 */\n private readonly pairs = new Set<string>()\n /** 줄 안 어절 첫머리 한글(두~여섯 음절) — 꺾인 두 조각이 어절 머리로 이어지나(startsWord) */\n private readonly heads = new Set<string>()\n /** 줄 안 이웃 어절의 앞 어절 한글 꼬리 + 뒤 어절 첫 음절(\"지식재산 보\") — 어절 머리 증거의 거부권 */\n private readonly spacedHeads = new Set<string>()\n\n /** 줄 글 한 줄을 증거로 더한다 — 탭(큰 갭)으로 나뉜 조각은 다른 칸·단이라 조각 사이는 어절 경계 증거로 쓰지 않는다 */\n addLine(text: string): void {\n for (const seg of text.replace(MARKUP, \"\").split(\"\\t\")) {\n const toks = seg.split(\" \").filter(t => t && !t.includes(\"](\"))\n for (let i = 0; i < toks.length; i++) {\n const t = toks[i], n = t.length\n if (i > 0 && i < toks.length - 1) {\n bump(this.words, t)\n const run = t.match(/^[가-힣]+/)?.[0] ?? \"\"\n for (let j = 2; j <= Math.min(run.length, 6); j++) this.heads.add(run.slice(0, j))\n }\n if (i + 1 < toks.length) {\n this.pairs.add(t + \" \" + toks[i + 1])\n const tail = t.match(/[가-힣]+$/)?.[0]\n if (tail) this.spacedHeads.add(tail + \" \" + toks[i + 1].charAt(0))\n }\n for (let a = 0; a + 1 < n; a++) {\n const c1 = t.charCodeAt(a), c2 = t.charCodeAt(a + 1)\n bump(this.joined1, pairKey(c1, c2))\n if (a >= 1 && a + 2 < n) bump2(this.joined2, pairKey(t.charCodeAt(a - 1), c1), pairKey(c2, t.charCodeAt(a + 2)))\n }\n if (i + 1 < toks.length) {\n const u = toks[i + 1]\n bump(this.spaced1, pairKey(t.charCodeAt(n - 1), u.charCodeAt(0)))\n if (n >= 2 && u.length >= 2) bump2(this.spaced2, pairKey(t.charCodeAt(n - 2), t.charCodeAt(n - 1)), pairKey(u.charCodeAt(0), u.charCodeAt(1)))\n }\n }\n }\n }\n\n /** 문서 줄 안에서 홀로 선 어절로 나온 적 있나 */\n isWord(word: string): boolean {\n return (this.words.get(word) ?? 0) > 0\n }\n\n /** 꺾인 두 조각을 이어 붙인 꼴이 줄 안 한 어절로 나왔고, 두 조각이 줄 안에서 띄어 쓴 이웃 어절로는 나온 적 없나 */\n joinedWord(left: string, right: string): boolean {\n return this.isWord(left + right) && !this.pairs.has(left + \" \" + right)\n }\n\n /** 줄 안 어절이 이 한글로 시작한 적 있나 */\n startsWord(head: string): boolean {\n return this.heads.has(head)\n }\n\n /** 줄 안에서 이 한글 꼬리로 끝난 어절 뒤에 이 음절로 시작하는 어절을 띄어 쓴 적 있나 */\n spacedHead(tail: string, syllable: string): boolean {\n return this.spacedHeads.has(tail + \" \" + syllable)\n }\n\n /** 두 글자+두 글자 증거만 — 한 글자 쌍으로 물러서지 않는다 (표 칸 조각처럼 줄 꺾임이 아닌 자리에 쓸 때) */\n evidence2(left: string, right: string): \"\" | \" \" | null {\n const n = left.length\n if (n < 2 || right.length < 2) return null\n const k1 = pairKey(left.charCodeAt(n - 2), left.charCodeAt(n - 1)), k2 = pairKey(right.charCodeAt(0), right.charCodeAt(1))\n return decideCounts(this.joined2.get(k1)?.get(k2) ?? 0, this.spaced2.get(k1)?.get(k2) ?? 0)\n }\n\n /** 꺾인 자리 증거: \"\" 붙음 · \" \" 띄움 · null 모름 (두 글자 증거가 갈리거나 없으면 한 글자) */\n evidence(left: string, right: string): \"\" | \" \" | null {\n const n = left.length\n if (n >= 2 && right.length >= 2) {\n const k1 = pairKey(left.charCodeAt(n - 2), left.charCodeAt(n - 1)), k2 = pairKey(right.charCodeAt(0), right.charCodeAt(1))\n const v = decideCounts(this.joined2.get(k1)?.get(k2) ?? 0, this.spaced2.get(k1)?.get(k2) ?? 0)\n if (v !== null) return v\n }\n if (n && right.length) {\n const k = pairKey(left.charCodeAt(n - 1), right.charCodeAt(0))\n return decideCounts(this.joined1.get(k) ?? 0, this.spaced1.get(k) ?? 0)\n }\n return null\n }\n}\nconst decideCounts = (joined: number, spaced: number): \"\" | \" \" | null => (joined && !spaced ? \"\" : spaced && !joined ? \" \" : null)\n\nconst hasBatchim = (c: string): boolean => { const k = c.charCodeAt(0) - 0xac00; return k >= 0 && k < 11172 && k % 28 !== 0 }\n/** 한자·가나·CJK 문장부호·전각 꼴 */\nconst CJK = /[\\u3000-\\u303F\\u3040-\\u30FF\\u3400-\\u4DBF\\u4E00-\\u9FFF\\uF900-\\uFAFF\\uFF00-\\uFFEF]/\n/** 중·일문 글자 사이 꺾임은 공백 없는 경계다. 한글이 섞인 국한문 어절은 제외한다. */\nconst unspacedCjkBoundary = (prev: string, next: string): boolean =>\n CJK.test(prev.trimEnd().slice(-1)) && CJK.test(next.trimStart().charAt(0)) && !/[가-힣ㄱ-ㅎㅏ-ㅣ]/.test(prev + next)\nconst CLOSE_TAIL = /[’”」』)\\]〉》>]+$/\nconst TAIL = \"[.,)」』’”]*\"\n/** 어절 첫머리에 오지 않는 조사·어미 — 다음 줄 첫 어절이 이것뿐이면 앞 어절의 꼬리다 (\"다.\" 는 줄을 넘어온 문장 끝).\n * 겹조사(\"수준⏎에서도\", \"신고자⏎에게는\", \"자문단원들⏎께서\", \"가입⏎보다도\")와 계사 \"이다·이며·이나…\"(모음 뒤 \"구조⏎이나\", \"자료⏎이다.\")도\n * 홀로 서는 낱말이 없다 — 보도자료 100쌍 출력 판정에서 기본 띄움이 틀린 135곳 중 15곳, 원문 띄움 0곳 */\nconst PARTICLE = new RegExp(`^(?:다\\\\.|의|에|에서|에는|에게|에도|에서는|까지|부터|처럼|마다|만|도|들|들이|들의|들은|들을|들에게|서는|서도|라고|라며|은|는|을|를|으로|로|으로서|로서|으로써|로써` +\n `|에서도|에서의|에서만|에서부터|에만|에의|에게는|에게도|께서|께서는|께서도|으로는|로는|으로도|로도|으로부터|로부터|부터는|까지는|까지도|보다도|라도|이라도` +\n `|들과|들과의|들로|들도|들에|들에서|들만|이다|이며|이나|이라는|이라고|이라며|인데|이므로|이지만|이면서)${TAIL}$`)\n/** 받침 뒤에서만 조사 — \"이\"(관형사)·\"과\"(부서명)는 홀로 설 수 있어 앞 음절 받침으로 가린다 */\nconst PARTICLE_AFTER_CONS = new RegExp(`^(?:이|과|과의|이고|인|인지|임|임을|임에도|이어야)${TAIL}$`)\nconst PARTICLE_AFTER_VOWEL = new RegExp(`^(?:가|와|와의|라는)${TAIL}$`)\nconst ENDING = new RegExp(`^(?:어야|아야|여야|므로|으므로|지만|면서|으면서|으며|거나|더라도|든지|었다|았다|였다|었으며|았으며|였으며|었고|았고|였고|였던|겠다|겠고|겠으며|습니다|니다|니까)${TAIL}$`)\nconst ADNOMINAL_HAN = new RegExp(`^한${TAIL}$`)\nconst SUFFIX_JEOK = new RegExp(`^적(?:인|으로|이다|이며|이고|임|인데|이라|이지만|으로서)${TAIL}$`)\n/** 줄 머리로 넘어온 접미사 — -화 하다·되다 활용(\"구체⏎화할\"), 홀로 서지 않는 -량·-률(율)·-성 + 조사(\"수확⏎량을\", \"정확⏎성을\";\n * \"성과\"는 낱말이라 -성 뒤 \"과\"는 뺀다) */\nconst SUFFIX_NOUN = new RegExp(`^(?:화(?:하|한|할|함|했|해|된|되|됨|돼|됐)[가-힣]*|[량률율](?:을|이|은|의|에|도|과|으로|이다)|성(?:을|이|은|의|에|도|으로|이다))${TAIL}$`)\n/** 줄 끝 한 음절 + 다음 줄 머리로 갈린 조사 (에|서, 이|나) */\nconst SPLIT_PARTICLE: [string, RegExp][] = [[\"에\", new RegExp(`^서(?:는|도|의|부터|만)?${TAIL}$`)], [\"이\", new RegExp(`^나${TAIL}$`)]]\n/** 하다·되다 활용 — 명사 뒤면 접미사(달성⏎하였으며). -야·-도록·-으로·-게 따위 뒤는 보조 용언이라 띄어 쓴다\n * (다음 줄 \"하고\" 101곳 중 23·\"하며\" 28곳 중 20 이 원문 띄움 — \"해야⏎하며\", \"원칙으로⏎하고\"). \"하나·한국·해외\"처럼\n * 하/한/해로 시작하는 낱말이 있어 활용 꼴을 낱낱이 든다 */\nconst VERB_FORM = new RegExp(`^(?:하는|하여|하고|하며|하였다|하였으며|하였고|하였다고|했다|했으며|했고|했다고|했다며|한다|한다고|한다며|한다는|할|함|하겠다|하겠다고|하겠습니다|하기로|하면|하면서|하도록|해야|되는|되어|된|된다|된다고|된다는|되었다|되었으며|되었고|됐다|됐으며|되며|되고|되면|됨|돼|하기|하게|하지|하거나|해서|하는데|함으로써|함에|함을|함이|하기에|하기도|되지|되기|되도록|되거나|되면서)${TAIL}$`)\n/** 인용 \"(이하 …)이라⏎한다\" 의 -라 뒤 하다도 보조 용언 (잘못 붙임 3곳↓). -려(\"하려⏎하거나\")·까지(\"’28.6월까지⏎할\") 뒤도 보조 용언.\n * \"지\" 는 뺐다 — 명사 끝(유지·방지·금지⏎하기)이 대부분이라 붙여야 맞다(hwpx↔pdf 꺾임 모의 순이득 +3).\n * 록·자·고 는 어미 꼴(-도록·-토록, -고자, 인용 -다고·-라고…)로만 — 명사 끝 \"기록⏎하고\", \"투자⏎하며\" 는 붙는다. 인용 따옴표는\n * 걷고 본다(\"도출하겠다”고⏎하였다\"). \"도\" 는 그대로 — 조사 \"나도⏎할\" 과 명사 끝 \"주도⏎하였다\" 를 글로 못 가른다 */\nconst AUX_BEFORE = /(?:야|도록|토록|로|게|도|히|고자|를|을|면|서|라고|다고|려고|자고|냐고|며|라|려|까지)$/\nconst auxBefore = (h: string): boolean => AUX_BEFORE.test(h.replace(/[”’\"」』“‘]/g, \"\"))\n/** 되다 활용 앞 \"…이\" 는 주격 조사 — \"승인이⏎되며\", \"3개월이⏎되어\" (잘못 붙임 8곳↓·맞는 붙임 손실 0). \"…가\" 는 \"평가⏎되어\"(피동\n * 한 낱말)와 겹치고, 하다 활용 앞 \"이\" 는 \"용이⏎하도록\" 이 한 낱말이라 넣지 않는다 */\nconst BECOME = /^[되된됐됨돼]/\n/** 홀로 쓰는 한 음절 낱말(기능어·관형사·의존명사 — 닫힌 부류) — 앞 쪽들에 아직 홀로 나온 적 없어도 꺾인 조각으로 보지 않는다\n * (첫 쪽의 \"보전⏎및\" 이 \"보전및\" 로 붙던 것; 이 목록으로 잘못 붙임 85곳↓·맞는 붙임 25곳↓. 시·할·데·뒤·된 — \"신청 시⏎제출한\",\n * \"…할⏎수 있다\", \"…하는 데⏎기여\", \"원인이 된⏎사실\" — 를 더해 잘못 붙임 21곳↓·맞는 붙임 1곳↓. \"바\"는 서식의 \"주시기 바⏎랍니다\"를\n * 끊어 넣지 않는다) */\nconst STANDALONE_SYLLABLE = /^(?:및|등|수|것|그|이|저|더|또|각|약|총|중|간|때|뿐|듯|채|전|후|내|외|한|두|세|네|몇|새|첫|온|본|곧|꼭|잘|못|안|좀|늘|다|왜|뭐|시|할|데|뒤|된)$/\n/** 날짜로 끝난 줄 — 서식 서명란 \"년 월 일⏎신고인 (서명 또는 인)\" 의 \"일\" 은 조각이 아니다(작은 서식 문서는 \"일\" 이 줄 안에 홀로 선\n * 적이 없어 한 음절 조각으로 붙던 것). 다음 줄이 괄호면 \"7월 28일⏎(목)부터\" 처럼 붙는 글이라 뺀다 (hwpx↔pdf 판정 변화 0) */\nconst DATE_DAY_END = /(?:^|\\s)\\d*월\\s+\\d*일$/\n/** 수 뒤 단위 — \"위원 2⏎명을\", \"월의 1⏎일부터\" */\nconst COUNTER = /^(?:명|일|월|년|개|원|건|회|차|호|조|항|층|톤|대|곳|시|분|초|배|위|점|주|종|억|만|천|%|퍼센트)/\n\n/** 조사·어미로 끝난 온전한 어절 (두 음절 이상) — 줄 끝이 이러면 꺾인 자리는 어절 경계다 */\nfunction endsAsWord(word: string): boolean {\n const h = word.replace(CLOSE_TAIL, \"\")\n if (!/^[가-힣]{2,}$/.test(h)) return false\n if (/(?:을|를|는|은|히|른|할|야|게|의)$/.test(h)) return true\n const prev = h[h.length - 2]\n if (/(?:이|과)$/.test(h)) return hasBatchim(prev)\n if (/(?:가|와)$/.test(h)) return !hasBatchim(prev)\n return false\n}\n\n/** 다음 줄 첫 어절이 앞 어절에 붙는 조사·어미인가 — 숫자·영문 뒤도 조사는 붙는다(제7조의9⏎에), 받침으로 가리는 조사만 한글 뒤 */\nfunction particleContinues(left: string, rightWord: string): boolean {\n // 닫는 따옴표 뒤는 인용 조사(\"…하겠다”라고\")라 그 앞까지만 본다\n const right = rightWord.replace(/[”’\"」』].+$/, \"\")\n const h = left.replace(CLOSE_TAIL, \"\")\n const last = h[h.length - 1]\n if (!last || !/[가-힣A-Za-z0-9]/.test(last)) return false\n if (PARTICLE.test(right)) return true\n if (!/[가-힣]/.test(last)) return false\n // 접미사 -적 이 줄 머리로 넘어온 것 — \"자체⏎적으로\", \"근본⏎적인\" (\"적은·적게\"(적다)는 제외)\n if (SUFFIX_JEOK.test(right) || (h.length >= 2 && SUFFIX_NOUN.test(right))) return true\n // 어절 머리에 못 오는 어미 — \"갖추⏎어야\", \"가지⏎므로\"\n if (ENDING.test(right)) return true\n // 줄을 넘어온 문장 끝 — \"…만족해야 한⏎다;<개정 2008. 9. 8>\" (문장 부호 뒤에 붙은 개정 표기까지 한 어절)\n if (/^다[.;:!?]/.test(right)) return true\n // 두 음절 조사가 줄에서 갈린 것 — \"국외에⏎서 처리\", \"서면이⏎나 전자수단\"\n if (SPLIT_PARTICLE.some(([a, b]) => last === a && b.test(right))) return true\n if (PARTICLE_AFTER_CONS.test(right)) return hasBatchim(last)\n if (PARTICLE_AFTER_VOWEL.test(right)) return !hasBatchim(last)\n // \"필요⏎한\", \"다양⏎한\" — 하다 관형형은 두 음절+ 명사 뒤만 (\"그 중⏎한 명\"의 수 관형사와 겹친다)\n if (ADNOMINAL_HAN.test(right)) return h.length >= 2 && !auxBefore(h) && !/(?:는|은|한|다|어|년)$/.test(h)\n return VERB_FORM.test(right) && !auxBefore(h) && !((last === \"이\" || (last === \"가\" && h.length >= 3)) && BECOME.test(right))\n}\n\n/** 줄 머리가 새 항목(글머리표·번호·조항)인가 — 번호 뒤 숫자(9.8%)·\"-\" 뒤 글자(생산-가공)는 이어진 글.\n * 앞 줄이 한글로 끝나면 \"다.\" 는 줄을 넘어온 문장 끝이다(…하였⏎다. 본문 꺾임 136곳 전부) */\nconst ITEM_HEAD = /^(?:[□■◆◇○●◎◦▪▫•※▶▷►❍❏❑✓✔➢➤☞]|[①-⑳]|[-–·∙ㆍ*](?=\\s)|\\(?\\d{1,2}[.)](?!\\d)|\\(?[가-하][.)]|\\([가-하\\d]{1,2}\\)|[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]|제\\d+[조항호장절])/\nexport function startsNewItem(prevText: string, nextText: string): boolean {\n const t = nextText.replace(MARKUP, \"\").trimStart()\n return ITEM_HEAD.test(t) && !(/^다\\./.test(t) && /[가-힣]$/.test(prevText.replace(MARKUP, \"\").trimEnd()))\n}\n\n/**\n * 꺾인 자리의 이음 — \"\" (어절 중간, 붙임) | \" \" (어절 경계). prevText 는 꺾인 줄, nextText 는 이어지는 줄.\n * 판정 순서와 근거는 머리 주석. 어휘 사전이 없으면(단독 호출) 형태 규칙만 쓴다.\n */\nexport function wrapJoiner(prevText: string, nextText: string, lex?: WrapLexicon, heads = true): \"\" | \" \" {\n const prev = prevText.replace(MARKUP, \"\").trimEnd(), next = nextText.replace(MARKUP, \"\").trimStart()\n const a = prev[prev.length - 1], b = next[0]\n if (!a || !b || /[,;:!?]/.test(a) || (DATE_DAY_END.test(prev) && b !== \"(\")) return \" \"\n // 중·일문은 띄어쓰기가 없다 — 한글 없는 두 줄의 한자·가나·전각 문장부호끼리 꺾임은 붙인다(kpipa 계약서 중문 \"代⏎理中介商\")\n if (unspacedCjkBoundary(prev, next)) return \"\"\n // 앞 줄 끝 어절 — 뒤에서 공백까지 거꾸로 (칸에서 이어 붙인 긴 글에 /\\S+$/ 를 돌리면 어절 길이 제곱이 든다)\n let s = prev.length\n while (s > 0 && !/\\s/.test(prev[s - 1])) s--\n const left = prev.slice(s), right = next.match(/^\\S+/)![0]\n if (particleContinues(left, right)) return \"\"\n if (endsAsWord(left)) return \" \"\n // Latin line-end hyphens can be typesetting breaks. Preserve the token boundary\n // unless an intact hyphenated compound is attested within this document.\n // Korean prose wraps Latin terms at their real hyphens; retain that policy.\n if (/[A-Za-z]-$/.test(left) && /^[A-Za-z]/.test(right) && !/[가-힣]/.test(prev + next)) {\n return lex?.joinedWord(left, right.replace(/[.,;:!?)]*$/, \"\")) ? \"\" : \" \"\n }\n // 가운뎃점·붙임표로 끝난 줄은 이어진 낱말 (소방·⏎가스, 생산-가공⏎-유통 — 줄 끝 \"·\" 105/109 붙음). 가운뎃점을 겹쳐 쓴 말줄임(\"주의···⏎관세청\")은 뺀다\n if (/[·ㆍ‧-]/.test(a) && !/[·ㆍ‧…]{2}$/.test(prev) && /[가-힣A-Za-z0-9]/.test(b)) return \"\"\n // 영어 낱말은 한컴 영문 줄나눔 기본(단어)이라 줄에서 잘리지 않는다 — 한 글자 증거(data 의 \"a|t\")가 속이지 않게 먼저.\n // 숫자끼리도 한 글자 증거는 숫자 안 이웃(2026 의 \"0|2\")이라 믿지 않는다(11:00⏎2026. 9. 2)\n if ((/[A-Za-z]/.test(a) && /[A-Za-z]/.test(b)) || (/\\d/.test(a) && /\\d/.test(b))) return \" \"\n const ev = lex?.evidence(left, right)\n if (ev != null) return ev\n // 이어 붙인 꼴이 문서 줄 안에 한 어절로 나왔고 두 조각을 띄어 쓴 이웃 어절로는 나온 적 없으면 붙인다 — 글자 쌍 증거가 갈리는(한 문서에\n // \"개인정보 처리\"·\"개인정보처리자\" 가 섞인) 명사+명사 꺾임을 어절 통째로 가른다(\"개인정보⏎처리자가\" → 문서 안 \"개인정보처리자가\").\n // hwpx↔pdf 752쌍 꺾임 자리(문서 전체 어휘로 모의, 판정 없던 자리만): 맞게 붙임 +208·잘못 붙임 +21. 띄어 쓴 이웃 어절 거부권이 없으면\n // +246·+78 이다(한 문서 안에서 \"배달 종사자\"·\"배달종사자\" 를 섞어 쓴 곳)\n if (lex && /[가-힣A-Za-z0-9]$/.test(left) && /^[가-힣A-Za-z0-9]/.test(right) && lex.joinedWord(left, right.replace(/[.,)」』’”;:]+$/, \"\"))) return \"\"\n if (/\\d$/.test(left) && COUNTER.test(right)) return \"\"\n // 한 음절 조각 — 문서 줄 안에서 홀로 선 어절로 나온 적 없으면 꺾인 조각이다 (\"살⏎펴보고\", \"공상공무⏎원\";\n // 앞 조각 한 음절 붙음 0.53 → 이 검사로 \"등·및·한·그\" 를 거른다). 둘 다 한 음절이면 붙인다(79/91)\n if (lex && /^[가-힣]$/.test(left) && /^[가-힣]/.test(right) && !STANDALONE_SYLLABLE.test(left)\n && ([...right].length === 1 || !lex.isWord(left))) return \"\"\n const r1 = right.replace(/[.,)」』’”]+$/, \"\")\n if (lex && /[가-힣]$/.test(left) && /^[가-힣]$/.test(r1) && !STANDALONE_SYLLABLE.test(r1) && !lex.isWord(r1) && !lex.isWord(right)) return \"\"\n // 앞 조각 한글 꼬리 + 뒤 조각 첫 두 음절이 문서 줄 안 어절의 머리로 나오고(\"개⏎최하고\" ← \"개최하여\"), 그 꼬리 뒤에 뒤 조각 첫 음절로\n // 시작하는 어절을 줄 안에서 띄어 쓴 적이 없으면 붙인다. 증거 없는 꺾임을 띄우던 기본값이 보도자료 99쌍에서 22% 틀렸다(낱말 가운데 꺾임).\n // 첫 음절만 보면 다른 복합어가 증거가 됐다(\"처리⏎방안\" ← \"처리방법\"). 칸 글은 빼고(heads=false) — 세로 이름표 \"학⏎위⏎기⏎준\" 은 원문도 한 자씩이다\n if (lex && heads) {\n const tail = left.match(/[가-힣]+$/)?.[0], run = right.match(/^[가-힣]+/)?.[0]\n if (tail && run && !STANDALONE_SYLLABLE.test(tail) && lex.startsWord(tail + run.slice(0, 2)) && !lex.spacedHead(tail, run[0])) return \"\"\n // 다음 줄이 그 어절 안에서 닫히는 짧은 괄호로 열면 앞 낱말의 풀이다(\"누리집⏎(safetyreport.go.kr)이나\", \"전자공시시스템⏎(DART)에\") —\n // 두 음절 넘는 한글 낱말 뒤에 붙여 쓴다. 한 음절(\"등⏎(국회의원)\")·조사·서술 끝(-함·-다 …) 뒤, 단위만 든 괄호(\"이삭비율⏎(%)\"),\n // 어절 밖으로 이어지는 긴 덧말(\"건축공사⏎(추정가격 …\")은 원문도 띄운 곳이 섞인다\n if (/[가-힣]{2}$/.test(left) && /^\\([^()\\s]*\\)/.test(right) && !endsAsWord(left) && !/(?:함|음|됨|임|다)$/.test(left) &&\n !BARE_UNIT_PAREN.test(right) && !/^\\(\\d/.test(right)) return \"\"\n }\n return \" \"\n}\n/** 단위·약어만 든 괄호 (\"(%)\", \"(천원)\", \"(GMP)\") */\nconst BARE_UNIT_PAREN = /^\\((?:%|‰|[가-힣]{1,3}|[A-Za-z]{1,5}|[A-Za-z]+\\/[A-Za-z]+)\\)[.,]?$/\n\n/** 문단 블록의 끝줄 기하 — 쪽 넘김 꺾임 판정용 (page-blocks 가 본문 줄을 문단으로 묶을 때 남긴다, 공개 IR 에 안 나감).\n * 키는 블록의 bbox 객체 — 목록 감지(detectListBlocks)가 블록을 {...block} 으로 새로 만들어도 bbox 는 그대로 넘어간다 */\nexport const PARA_LAST_LINE = new WeakMap<BoundingBox, { right: number; width: number; fontSize: number; spaceAfter?: boolean }>()\n/** 문단 블록의 첫 줄 왼끝 — 쪽 넘김 잇기가 다음 쪽 첫 줄 들여쓰기를 볼 때 (키는 PARA_LAST_LINE 과 같다) */\nexport const PARA_FIRST_LEFT = new WeakMap<BoundingBox, number>()\n\n/** 쪽 번호로 끝나는 목차·슬라이드 바닥 줄 — \"2.3. 회의록의 구성 \\t 7\"·\"…………… 53\"·\"·· 167\"·\"\\t13/39\" */\nconst PAGE_REF_TAIL = /(?:\\t|…|·{2}|\\.{3})\\s*\\d{1,4}(?:\\s*\\/\\s*\\d{1,4})?\\s*$/\n/** 쪽 머리에서 새 항목·제목을 여는 기호 — ITEM_HEAD 밖: 사용자 정의 영역 글머리(U+F000 등, 글꼴 기호), 자모 \"ㅇ \", 꺾쇠·대괄호 제목\n * \"< … >\"·\"[1]\"·\"[그림 Ⅱ-78]\"·\"〔서식 4-1〕\", 사각·딩뱃 기호 \"▮\"·\"▣\"·\"❐\", 제어 문자. 쪽 넘김 잇기에만 쓴다 — 같은 쪽 줄 잇기는\n * 줄 간격 근거가 따로 있다. PDF 코퍼스(98문서) 쪽 넘김 잇기 138곳 가운데 이 기호로 여는 30곳이 모두 새 항목·제목이었다 */\nconst PAGE_HEAD_MARK = /^(?:[\\u0000-\\u001f\\ue000-\\uf8ff\\u25a0-\\u25ff\\u2750-\\u275f<〈《〔\\[]|ㅇ\\s)/\n/** 쪽 머리의 새 항목 — ITEM_HEAD 밖: 짧은 머리 + 탭인 용어·설명 행(\"N\\t- 앞쪽에서 …\"), 점으로 여는 조항 번호(\".13 사용되는 …\") */\nconst PAGE_ITEM_HEAD = /^(?:\\S{1,4}\\t|\\.\\d{1,3}\\s)/\n/** 문장 끝 — 마침표·물음표·느낌표 뒤 닫는 따옴표·괄호까지 (\"격려했다.\" \"전했다.”\") */\nconst SENTENCE_END = /[.?!。][”\"’」』)]?$/\n/** 짧은 제목꼴 — 한 줄 40자 이하, 문장 끝이 아님 (\"세부 행사 일정\"·\"위치도\"·\"행정정보 데이터세트 폐기\") */\nconst titleLike = (text: string): boolean => {\n const t = text.replace(MARKUP, \"\").trim()\n return !t.includes(\"\\n\") && [...t].length <= 40 && !SENTENCE_END.test(t)\n}\n/** 쪽 넘김 잇기로 이은 문단의 쪽 경계 (#136) — 키는 이은 블록의 bbox(PARA_LAST_LINE 과 같은 이유). head 는 뒤 쪽 몫 앞까지의 글,\n * gap 은 그 사이 이음자 길이, tail 은 뒤 쪽 블록(쪽 번호·기하·모양만 쓴다). 세 쪽 넘게 이으면 앞에서부터 차례로 쌓인다 */\nconst PAGE_BREAK_JOINS = new WeakMap<BoundingBox, { head: string; gap: number; tail: IRBlock }[]>()\n\n/**\n * 쪽 넘김 꺾임 잇기 — 쪽 끝 문단의 끝줄이 그 쪽 본문 오른끝까지 차 있고 다음 쪽 첫 블록이 같은 글자 크기의 이어지는 문단이면\n * 한 문단으로 (…보여준다. 아이 ⏎ [다음 쪽] 들은 인공지능…). 머리말·꼬리말을 지운 뒤라 두 블록이 배열에서 이웃하면 쪽 끝과 쪽 머리다.\n * 쪽 본문 오른끝은 그 쪽 문단 블록 오른끝의 최댓값. 이은 문단은 앞 쪽 소속으로 남는다 (제자리 수정) —\n * 쪽별 마크다운은 splitPageBreakWraps 로 다시 갈라 뒤 쪽 몫을 뒤 쪽에 둔다\n */\nexport function joinPageBreakWraps(blocks: IRBlock[], lex?: WrapLexicon): void {\n const pageRight = new Map<number, number>()\n for (const b of blocks) {\n if (b.type !== \"paragraph\" || !b.bbox || !b.pageNumber) continue\n pageRight.set(b.pageNumber, Math.max(pageRight.get(b.pageNumber) ?? -Infinity, b.bbox.x + b.bbox.width))\n }\n const pageLeft = new Map<number, number>()\n for (const b of blocks) {\n if (b.type !== \"paragraph\" || !b.bbox || !b.pageNumber) continue\n pageLeft.set(b.pageNumber, Math.min(pageLeft.get(b.pageNumber) ?? Infinity, b.bbox.x))\n }\n for (let i = blocks.length - 1; i > 0; i--) {\n const a = blocks[i - 1], b = blocks[i]\n if (!a.pageNumber || b.pageNumber !== a.pageNumber + 1 || !a.text || !b.text) continue\n if ((a.type !== \"paragraph\" && a.type !== \"list\") || b.type !== \"paragraph\") continue\n const last = a.bbox && PARA_LAST_LINE.get(a.bbox), fs = last ? last.fontSize : 0\n if (!last || fs <= 0 || (pageRight.get(a.pageNumber) ?? Infinity) - last.right >= BODY_FULL_TOL * fs) continue\n if (last.width < BODY_MIN_WIDTH_EM * fs || Math.abs((b.style?.fontSize ?? 0) - fs) > 0.15 * fs) continue\n // 쪽 사이에는 줄 간격 근거가 없다 — HWPX 쌍 대조(한컴 PDF 쌍의 잇기 후보 325곳, 원문 문단 경계와 비교)로 정한 가드:\n // · 목차 줄·항목 기호·용어 행·조항 번호로 여는 블록은 새 줄. 단 앞 문단 안에 같은 사각 기호가 이미 있으면 문장 속 나열이 이어진 것\n // (\"…주요 내용은 ▲ 가계조사 … 구축 ⏎ ▲빈곤선 설정 …\")\n // · 앞 쪽 문단이 문장으로 끝나고 다음 쪽 첫 줄이 들여 시작하면 새 문단: 문장 끝 26곳 중 들여쓴 20곳은 원문 경계 19, 왼끝에서\n // 시작한 4곳은 같은 문단 3\n // · 글자 크기(정수 반올림)가 다른 짧은 제목꼴 블록은 제목·캡션. 크기만 다른 블록 34곳 중 같은 문단 24(긴 본문 — 쪽마다 반올림\n // 크기가 13·12 로 흔들리는 문서)라 크기만으로는 가르지 않는다\n const bt = b.text.replace(MARKUP, \"\")\n if (startsNewItem(a.text, b.text) || PAGE_REF_TAIL.test(a.text) || PAGE_ITEM_HEAD.test(bt)) continue\n if (PAGE_HEAD_MARK.test(bt) && !(/^[\\u25a0-\\u25ff]/.test(bt) && a.text.includes(bt[0]))) continue\n const firstLeft = b.bbox && PARA_FIRST_LEFT.get(b.bbox)\n const indented = firstLeft === undefined || firstLeft - (pageLeft.get(b.pageNumber!) ?? firstLeft) >= 0.3 * fs\n if (indented && SENTENCE_END.test(a.text.replace(MARKUP, \"\").trimEnd())) continue\n if (Math.round(b.style?.fontSize ?? 0) !== Math.round(fs) && titleLike(b.text)) continue\n // 탭으로 칸을 나눈 행(\"‘상록수’의 별도 정의\\t일 년 내내 …\") 다음 쪽 첫 줄이 짧은 제목꼴이면 표 같은 목록이 끝나고 새 제목이 선 것이다\n // (lo-pairs DOCX 쌍 3곳). 문장 속 탭 뒤로 문장이 이어지는 옛 문서(\"…crash로 인해서\\tclinet에 … option은 ⏎ 무엇인가?\")와 줄 앞 조항\n // 번호 뒤 탭(\".2.3\\t손상을 입은 후 … 고 ⏎ 가정한다;\")은 칸 구분이 아니다\n if (/\\t/.test(a.text.slice(a.text.lastIndexOf(\"\\n\") + 1).replace(/^\\S{1,8}\\t/, \"\")) && titleLike(bt.trim().split(\"\\n\")[0])) continue\n // 앞 쪽 끝줄 끝 아이템 뒤에 공백 글리프가 찍혔으면 어절 경계다 — 같은 쪽 줄 잇기(pushLineParagraphs)와 같은 거부 (markTrailingSpaceGlyphs)\n const joined = wrapJoiner(a.text, b.text, lex)\n const head = a.text, joiner = joined === \"\" && last.spaceAfter ? \" \" : joined\n a.text += joiner + b.text\n // b 가 이미 다음 쪽과 이어졌으면 그 경계는 a 글 안에서 head + 이음자만큼 뒤로 밀린다\n const later = (b.bbox && PAGE_BREAK_JOINS.get(b.bbox)) || []\n PAGE_BREAK_JOINS.set(a.bbox!, [{ head, gap: joiner.length, tail: b }, ...later.map(c => ({ ...c, head: head + joiner + c.head }))])\n const bl = b.bbox && PARA_LAST_LINE.get(b.bbox)\n if (bl) PARA_LAST_LINE.set(a.bbox!, bl)\n else PARA_LAST_LINE.delete(a.bbox!)\n blocks.splice(i, 1)\n }\n}\n\n/**\n * 쪽별 사영용 블록 — 쪽 넘김 잇기로 이은 문단을 이음 자리에서 다시 쪽마다 가른다 (#136). 쪽 본문이 통째로 한 문단 블록이면 잇기가 뒤 쪽\n * 글 전체를 앞 쪽으로 가져가 그 쪽 항목이 비거나 빠졌다. 문서 블록·마크다운은 이은 그대로 두고 새 배열만 낸다. 이음 뒤 단계(각주 넣기 등)가\n * 앞 글을 바꿔 경계를 못 찾으면 거기서부터는 가르지 않는다\n */\nexport function splitPageBreakWraps(blocks: IRBlock[]): IRBlock[] {\n return blocks.flatMap(b => {\n const cuts = b.bbox && PAGE_BREAK_JOINS.get(b.bbox)\n const text = b.text\n if (!cuts || !text) return [b]\n const out: IRBlock[] = []\n let from = 0, cur = b\n for (const c of cuts) {\n if (c.head.length < from || !text.startsWith(c.head)) break\n out.push({ ...cur, text: text.slice(from, c.head.length) })\n from = c.head.length + c.gap\n cur = c.tail\n }\n if (!out.length) return [b]\n out.push({ ...cur, text: text.slice(from) })\n return out\n })\n}\n\n/** 본문 줄 기하 — 꺾임 판정 입력 (y 는 기준선, PDF 좌표라 아래 줄이 작다) */\nexport interface WrapLine { text: string; left: number; right: number; y: number; fontSize: number }\n\n/** 여백 밖에 매달리는 줄 끝 전각 구두점 */\nconst HANGING = /[,。、:;!?)」』】〕》〉.]\\s*$/\n/** 다단계 번호 머리 — \"5.1 채취용기\", \"7.1.1.2 장기 가동중단\" (공백까지 있어야 \"1.5L\"·\"3.5kg\" 과 갈린다) */\nconst MULTI_LEVEL_NUMBER = /^\\d{1,2}(?:\\.\\d{1,3})+\\.?\\s/\n/** 찬 줄: 묶음 오른끝에 글자 크기 0.25배 안 — 양쪽 정렬 본문은 꺾인 줄이 오른끝까지 찬다 */\nconst BODY_FULL_TOL = 0.25\n/** 꺾인 줄 최소 폭(글자 크기 배) — 좁은 줄(가운데 정렬 제목·서명란)은 묶음에서 가장 넓어 오른끝에 닿아도 꺾임이 아니다\n * (폭 150pt 미만 찬 줄의 86~100% 가 문단 경계) */\nconst BODY_MIN_WIDTH_EM = 12\n/** 이어지는 줄 기준선 간격 상한(글자 크기 배) — 줄 간격 160% 본문이 1.6, 문단 사이 띄움은 2 이상 (2 이상의 23~100% 가 문단 경계) */\nconst BODY_MAX_PITCH_EM = 2\n/** 줄 간격 200% 이상인 본문(보도자료 흔함)은 줄 간격 자체가 2em 을 넘는다 — 같은 묶음 다른 줄쌍의 가장 좁은 간격의 이 배 안이면\n * 한 문단의 줄 간격이다(hwpx↔pdf 752쌍: 2em 이상이면서 이 안인 찬 줄 263곳 중 원문 문단 경계 12곳, 붙음 104·띄움 147).\n * 상한은 글자 크기 3.5배 — 그보다 넓은 간격이 이어진 묶음은 표제·서명란 나열이다 */\nconst BODY_PITCH_REL = 1.05\nconst BODY_MAX_PITCH_ABS_EM = 3.5\n\n/**\n * 본문 줄 묶음(한 XY-Cut 그룹, 위→아래)에서 이웃한 두 줄 사이 이음자 — 반환 [i] 는 lines[i] 와 lines[i+1] 사이:\n * \"\\n\" 잇지 않음 · \"\" 어절 중간 꺾임 · \" \" 어절 경계 꺾임. 꺾임은 앞 줄이 묶음 오른끝까지 차고, 넓고, 다음 줄이 한 줄 간격 안·같은\n * 글자 크기·새 항목 머리가 아닐 때 (hwpx↔pdf 417쌍 본문: 이 조건의 이웃 줄 15,653곳 중 원문 문단 경계 297곳 = 1.9%)\n */\nexport function bodyLineJoins(lines: WrapLine[], lex?: WrapLexicon): string[] {\n // 표시 태그는 글자 폭·줄 끝 구두점·번호 판정의 글자가 아니다. 원래 줄은 보존하고 판정용 글만 한 번 정리한다.\n // 링크 표기 `[글](주소)` 도 — 주소 글자가 다음 줄 글자 폭 어림을 줄여 왼쪽 정렬 줄의 꺾임을 문단 끝으로 봤다(ODL 094 번호 목록)\n lines = lines.map(l => {\n const text = l.text.replace(MARKUP, \"\").replace(LINK_MARKUP, \"$1\")\n return text === l.text ? l : { ...l, text }\n })\n let right = -Infinity\n for (const l of lines) if (l.right > right) right = l.right\n // 여백 밖에 매단 줄 끝 구두점(LibreOffice 동아시아 조판 \",\" \"。\")은 오른끝을 한 글자 남짓 끌어올린다 — 매단 줄을 뺀 오른끝도\n // 찬 줄 기준으로 인정한다(글자 크기 1.2배 안에서만). kpipa 계약서 중문 각주: 찬 줄 522, \",\" 줄만 533\n let inner = -Infinity\n for (const l of lines) if (!HANGING.test(l.text) && l.right > inner) inner = l.right\n const full = (l: WrapLine) => right - l.right < BODY_FULL_TOL * l.fontSize ||\n (right - inner <= 1.2 * l.fontSize && inner - l.right < BODY_FULL_TOL * l.fontSize)\n // 왼쪽 정렬(Word 등)은 어절 단위로 넘겨 꺾인 줄 끝이 들쭉날쭉하다 — 다음 줄 첫 어절이 이 줄 끝 남은 자리에 못 들어갔으면 꺾임.\n // 글자 폭은 다음 줄 평균으로 어림한다\n const nextWordNoRoom = (a: WrapLine, b: WrapLine) => {\n const w = b.text.trim().match(/^\\S+/)?.[0]\n const n = [...b.text.trim()].length\n if (!w || n === 0) return false\n const em = (b.right - b.left) / n\n // 중·일문은 단어 사이 공백이 없고 글자 사이에서도 꺾인다 — 다음 줄 전체를 한 어절로 재면 짧은 문단도 잇게 된다\n const advance = unspacedCjkBoundary(a.text, b.text) ? 1 : [...w].length + 1\n return right - a.right < advance * em\n }\n // 찬 줄 또는 실제 꺾임 후보에서 찬 줄이 절반 이상이면 양쪽 정렬이다. 덜 찬 마지막 줄과 다음 어절이 넉넉히\n // 들어가는 짧은 끝줄은 정렬 근거가 아니다 — 이를 분모에 넣으면 두 줄 문단 + 한 줄 문단만으로도 왼쪽 정렬로 뒤집힌다\n const candidates = lines.filter((l, i) => full(l) || (i + 1 < lines.length && nextWordNoRoom(l, lines[i + 1])))\n const ragged = candidates.filter(full).length * 2 < candidates.length\n const pitch = (k: number) => lines[k].y - lines[k + 1].y\n const sameSize = (k: number) => Math.abs(lines[k + 1].fontSize - lines[k].fontSize) <= 0.15 * lines[k].fontSize\n // 줄쌍 간격 가운데 가장 좁은 둘 — i 번째 쌍 자신을 뺀 최솟값을 O(1) 로 (같은 글자 크기 쌍만)\n let p1 = Infinity, p2 = Infinity, i1 = -1\n for (let k = 0; k + 1 < lines.length; k++) {\n const p = pitch(k)\n // 폭 3em 미만 줄(쪽 번호 \"1\")이 낀 쌍은 줄 간격 근거가 아니다 — 본문 끝줄 바로 아래 쪽 번호가 가장 좁은 간격으로 잡혀\n // 두 줄 간격 본문의 상대 상한을 끌어내렸다(kpipa 계약서 중문 각주 22pt 간격 줄들이 쪽 번호 14pt 간격에 막힘)\n if (p <= 0 || !sameSize(k) || Math.min(lines[k].right - lines[k].left, lines[k + 1].right - lines[k + 1].left) < 3 * lines[k].fontSize) continue\n if (p < p1) { p2 = p1; p1 = p; i1 = k } else if (p < p2) p2 = p\n }\n const out: string[] = []\n for (let i = 0; i + 1 < lines.length; i++) {\n const a = lines[i], b = lines[i + 1]\n const fs = a.fontSize\n const others = i === i1 ? p2 : p1 // 다른 쌍이 없으면 Infinity — 상대 기준 없이 2em\n const maxPitch = Number.isFinite(others) ? Math.min(BODY_MAX_PITCH_ABS_EM * fs, Math.max(BODY_MAX_PITCH_EM * fs, others * BODY_PITCH_REL)) : BODY_MAX_PITCH_EM * fs\n // 다단계 번호(\"5.1 \", \"7.1.2 \")로 여는 줄은 새 조항이다 — ITEM_HEAD 는 한 단계 번호만 본다\n const wordWrap = ragged && !full(a) && nextWordNoRoom(a, b) && !MULTI_LEVEL_NUMBER.test(b.text.trimStart())\n const wraps = fs > 0\n && (full(a) || wordWrap)\n && a.right - a.left >= BODY_MIN_WIDTH_EM * fs\n && a.y - b.y > 0 && a.y - b.y < maxPitch\n && Math.abs(b.fontSize - fs) <= 0.15 * fs\n && !startsNewItem(a.text, b.text)\n // 어절 단위 줄넘김은 띄우되 중·일문의 글자 단위 꺾임에는 공백을 넣지 않는다\n out.push(!wraps ? \"\\n\" : wordWrap ? (unspacedCjkBoundary(a.text, b.text) ? \"\" : \" \") : wrapJoiner(a.text, b.text, lex))\n }\n return out\n}\n\n/** 칸 안쪽 오른 여백 상한(pt) — 왼 여백(가장 왼쪽 줄 시작 − 칸 왼끝)과 같다고 보되, 가운데 정렬 칸에서 부풀지 않게 한컴 기본\n * 1.8mm(5.1pt) 남짓으로 묶는다. 상한이 없으면 가운데 정렬 칸의 가장 넓은 줄이 찬 줄로 보여 원문 문단 경계를 공백 없이 붙이는\n * 오결합이 95곳(상한 6pt 는 55) — 대신 맞는 붙임 121곳을 덜 한다(어절 F1 약 0.0002). 새 오결합을 덜 만드는 쪽을 택했다 */\nconst CELL_PAD_MAX = 6\n\n/**\n * 칸 안 줄이 꺾여 다음 줄로 넘어갔나 — 다음 줄 첫 글자가 앞 줄 오른끝 뒤에 칸 안쪽으로 반 글자 넘게 못 들어가는가.\n * 칸 글은 가운데·왼쪽 정렬이 섞여 오른끝 도달만으로는 못 가르므로 \"다음 글자가 들어갈 자리가 있었나\"로 본다\n * (table-parts 쪽 넘김 쪼개진 행의 continuesAcross 와 같은 기하)\n */\nexport function cellLineWraps(box: { x1: number; x2: number }, contentLeft: number, prevRight: number, fontSize: number, nextFirstCharW: number): boolean {\n const pad = Math.min(CELL_PAD_MAX, Math.max(0, contentLeft - box.x1))\n return fontSize > 0 && prevRight + nextFirstCharW - (box.x2 - pad) > 0.5 * fontSize\n}\n\n/**\n * 칸 안 줄이 칸 글의 오른끝까지 찼나 — 세 줄 이상인 칸에서 줄이 그 칸 줄들의 가장 오른끝에 글자 크기 0.25배 안으로 닿고 칸 안쪽 폭의\n * 80% 이상이면 양쪽 정렬로 꺾인 줄이다. 오른 안 여백이 왼 여백보다 넓은 칸은 cellLineWraps 의 \"다음 글자가 못 들어감\" 기하가 반 글자\n * 모자라 꺾임을 놓쳤다(규제영향분석서 칸 \"국⏎외에서\"·\"해⏎당하는\" — 칸 오른 안쪽 끝에서 0.54em 앞에서 줄마다 끝남).\n * hwpx↔pdf 752쌍: 이 조건으로 새로 꺾임이 된 칸 줄 가운데 맞게 붙임 151·잘못 붙임 23(다음 줄이 \"(\" 로 시작하는 새 항목은 뺌)\n */\nexport function cellLineFills(box: { x1: number; x2: number }, contentLeft: number, cellRight: number, lineRight: number, fontSize: number): boolean {\n const pad = Math.min(CELL_PAD_MAX, Math.max(0, contentLeft - box.x1))\n return fontSize > 0 && cellRight - lineRight < BODY_FULL_TOL * fontSize && lineRight - contentLeft >= 0.8 * (box.x2 - box.x1 - 2 * pad)\n}\n","/**\n * 셀 텍스트 매핑/조립 (line-detector.ts에서 분리).\n * 텍스트 아이템 → 셀 매핑(교차 비율), 셀 텍스트 문자열 조립(균등배분·탭 갭),\n * 셀 내 줄바꿈 병합.\n *\n * getIntersectionPercent 방식은 OpenDataLoader PDF 참고 clean-room 재구현.\n * Original algorithm: Copyright 2025-2026 Hancom, Inc. (Apache 2.0)\n * https://github.com/opendataloader-project/opendataloader-pdf\n */\n\nimport type { ExtractedCell, TextItem } from \"./line-types.js\"\nimport { sortLineByX, isCjkLatinAutospace, collapseEvenSpacing, isAttachedScripts } from \"./text-line.js\"\nimport { tagScripts } from \"./script-items.js\"\n\n/** 시도·전국 이름표 — 두 음절 배분 칸을 문서 어휘 증거 없이도 붙이는 닫힌 목록. \"전 체\"·\"구 분\" 같은 표 머리글은 원고에서 띄어 쓰기도\n * 해서(해외직접투자 보도자료 정답 \"전 체\") 넣지 않는다 */\nexport const REGION_LABELS = new Set([\"서울\", \"부산\", \"대구\", \"인천\", \"광주\", \"대전\", \"울산\", \"세종\", \"경기\", \"강원\", \"충북\", \"충남\", \"전북\", \"전남\", \"경북\", \"경남\", \"제주\", \"전국\"])\nimport { type WrapLexicon, cellLineWraps, cellLineFills, startsNewItem, wrapJoiner } from \"./line-wrap.js\"\n\n/** 셀 경계 내부 판별 여유 (텍스트 매핑용) */\nconst CELL_PADDING = 2\n\n/** 셀 텍스트 정리 — 페이지 번호 표시 제거 + 줄별 균등배분 공백 정리 */\nexport function cleanCellText(text: string): string {\n const stripped = text.replace(/^[\\s]*[-–—]\\s*\\d+\\s*[-–—][\\s]*$/gm, \"\").trim()\n return stripped.split(\"\\n\").map(line => collapseEvenSpacing(line)).join(\"\\n\")\n}\n\n/**\n * 공백 삽입 갭 임계값 — 폰트 크기 비례.\n * 절대 px(3px) 기준은 Type3 폰트(예: fontSize 10.5에서 단어 갭 2.7px)에서 공백이\n * 소실되고, 작은 폰트에서는 과다 삽입됨. fontSize×0.17 비례 기준으로 교체\n * (veraPDF wcag-algs TEXT_LINE_SPACE_RATIO 아이디어의 클린룸 재구현 — 코드 비복사).\n */\nexport const SPACE_GAP_RATIO = 0.17\n\nexport function spaceGapThreshold(fontSize: number): number {\n return Math.max(fontSize * SPACE_GAP_RATIO, 1)\n}\n\n/**\n * 텍스트 아이템을 셀에 매핑.\n * v2: ODL의 getIntersectionPercent 방식 — 텍스트 bbox와 셀 bbox의 교차 비율로 판별.\n * 중심점만 보는 기존 방식보다 정확 (긴 텍스트가 셀 경계를 걸치는 경우 처리).\n */\nexport function mapTextToCells(\n items: TextItem[],\n cells: ExtractedCell[],\n): Map<ExtractedCell, TextItem[]> {\n const result = new Map<ExtractedCell, TextItem[]>()\n for (const cell of cells) {\n result.set(cell, [])\n }\n const candidates = items.length * cells.length >= 5000 ? cellBandLookup(cells) : () => cells\n\n for (const item of items) {\n const pad = CELL_PADDING\n\n let bestCell: ExtractedCell | null = null\n let bestScore = 0\n\n for (const cell of candidates(item.y - pad, item.y + (item.h || item.fontSize) + pad)) {\n // 텍스트 bbox와 셀 bbox의 교차 영역 계산\n const ix1 = Math.max(item.x, cell.bbox.x1 - pad)\n const ix2 = Math.min(item.x + item.w, cell.bbox.x2 + pad)\n const iy1 = Math.max(item.y, cell.bbox.y1 - pad)\n const iy2 = Math.min(item.y + (item.h || item.fontSize), cell.bbox.y2 + pad)\n\n if (ix1 >= ix2 || iy1 >= iy2) continue\n\n const intersectArea = (ix2 - ix1) * (iy2 - iy1)\n const itemArea = Math.max(item.w, 1) * Math.max(item.h || item.fontSize, 1)\n const score = intersectArea / itemArea // ODL의 MIN_CELL_CONTENT_INTERSECTION_PERCENT\n\n if (score > bestScore) {\n bestScore = score\n bestCell = cell\n }\n }\n\n // 교차 비율 > 0.3이면 셀에 할당 (ODL은 0.6이지만 PDF 텍스트 좌표 오차 고려)\n if (bestCell && bestScore > 0.3) {\n result.get(bestCell)!.push(item)\n }\n }\n\n keepWordsInOneCell(result)\n return result\n}\n\n/**\n * 칸 여럿에 걸친 한 조각 — 제작기가 표 한 행의 칸 글을 한 글 조각으로 내면(평가 기준 별표2 \"2 100 1.0 2.0 … 10.0\" 9칸, 가계동향조사\n * 머리 행 \"근로소득 사업소득 재산소득 이전소득\" 4칸) 어느 칸에도 30% 넘게 겹치지 않아 칸 밖 글로 샜다. 걸친 칸(한 행, 서로 다른 열)\n * 수와 낱말 수가 같으면 왼→오로 칸마다 한 낱말. 그 밖은 그대로\n */\nexport function splitAcrossCells(item: TextItem, cells: ExtractedCell[]): TextItem[] {\n const words = item.text.trim().split(/\\s+/)\n if (words.length < 2) return [item]\n const ih = item.h || item.fontSize\n const spanned = cells.filter(c => Math.min(item.y + ih, c.bbox.y2) - Math.max(item.y, c.bbox.y1) >= ih * 0.5 &&\n Math.min(item.x + item.w, c.bbox.x2) - Math.max(item.x, c.bbox.x1) > 1).sort((a, b) => a.bbox.x1 - b.bbox.x1)\n if (spanned.length !== words.length) return [item]\n for (let k = 1; k < spanned.length; k++) if (spanned[k].bbox.x1 < spanned[k - 1].bbox.x2 - 1) return [item]\n // 한 칸에 30% 넘게 겹치면 종전대로 그 칸 글이다\n const itemArea = Math.max(item.w, 1) * Math.max(ih, 1)\n if (spanned.some(c => (Math.min(item.x + item.w, c.bbox.x2) - Math.max(item.x, c.bbox.x1)) * ih / itemArea > 0.3)) return [item]\n return words.map((text, k) => {\n const b = spanned[k].bbox\n return { ...item, text, x: b.x1 + 1, w: Math.max(1, b.x2 - b.x1 - 2), hasSpaceBefore: false }\n })\n}\n\n/** 세로 범위로 칸 후보를 좁힌다. 병합 칸도 포함하고, 동률 배정은 원래 칸 순서를 그대로 따른다. */\nfunction cellBandLookup(cells: ExtractedCell[]): (lo: number, hi: number) => ExtractedCell[] {\n const sorted = cells.map((cell, index) => ({ cell, index })).sort((a, b) => a.cell.bbox.y1 - b.cell.bbox.y1)\n interface Band { start: number; end: number; lo: number; hi: number; left?: Band; right?: Band }\n const build = (start: number, end: number): Band => {\n let lo = Infinity, hi = -Infinity\n for (let i = start; i < end; i++) { lo = Math.min(lo, sorted[i].cell.bbox.y1); hi = Math.max(hi, sorted[i].cell.bbox.y2) }\n const band: Band = { start, end, lo, hi }\n if (end - start > 8) {\n const mid = (start + end) >> 1\n band.left = build(start, mid); band.right = build(mid, end)\n }\n return band\n }\n const root = build(0, sorted.length)\n return (lo, hi) => {\n const found: typeof sorted = []\n const visit = (band: Band): void => {\n if (band.hi <= lo || band.lo >= hi) return\n if (band.left && band.right) { visit(band.left); visit(band.right) }\n else for (let i = band.start; i < band.end; i++) {\n const entry = sorted[i]\n if (entry.cell.bbox.y2 > lo && entry.cell.bbox.y1 < hi) found.push(entry)\n }\n }\n visit(root)\n return found.sort((a, b) => a.index - b.index).map(entry => entry.cell)\n }\n}\n\n/**\n * 낱말 응집 — 한 줄에서 공백 없이 붙은 조각(한 낱말)이 두 칸에 나뉘면 폭을 더 많이 담은 칸으로 모은다. 선 격자는 좁은\n * 들여쓰기 열(8.5pt 상자 좌변)을 최소 열폭으로 합쳐 경계 하나를 지우는데, 그 행에 괘선이 없는 경계를 걸친 낱말은 첫\n * 글자만 위 칸(행 병합)으로 떨어졌다(\"사|회적기업 사업개발비\", \"단|체교섭\" — 부천·속초 세출예산사업명세서, 글자마다 따로\n * 그린 굴림체). 괘선이 있는 경계는 글이 걸칠 수 없으므로(획·칸 클립) 낱말의 어떤 글자가 제 칸 밖으로 1pt 넘게\n * 삐져나올 때(글이 경계를 실제로 가로지름)만 모은다 — 좁은 칸에 바짝 붙은 서로 다른 칸 글(\"10월|11월|12월\", 창원\n * 월별 일정표 머리행)은 글자가 제 칸 안에 있어 그대로 둔다\n */\nfunction keepWordsInOneCell(result: Map<ExtractedCell, TextItem[]>): void {\n const owner = new Map<TextItem, ExtractedCell>()\n for (const [cell, arr] of result) for (const it of arr) owner.set(it, cell)\n if (result.size < 2 || owner.size < 2) return\n const all = [...owner.keys()].sort((a, b) => b.y - a.y || a.x - b.x)\n for (let i = 0; i < all.length;) {\n let j = i + 1\n while (j < all.length && Math.abs(all[j].y - all[i].y) <= 1) j++\n const line = all.slice(i, j).sort((a, b) => a.x - b.x)\n for (let k = 0; k < line.length;) {\n let e = k + 1\n while (e < line.length && !line[e].hasSpaceBefore\n && line[e].x - (line[e - 1].x + line[e - 1].w) <= spaceGapThreshold((line[e].fontSize + line[e - 1].fontSize) / 2)) e++\n const word = line.slice(k, e)\n const width = new Map<ExtractedCell, number>()\n for (const it of word) { const c = owner.get(it)!; width.set(c, (width.get(c) ?? 0) + it.w) }\n const crosses = word.some(it => { const b = owner.get(it)!.bbox; return it.x < b.x1 - 1 || it.x + it.w > b.x2 + 1 })\n if (width.size > 1 && crosses) {\n const target = [...width].sort((a, b) => b[1] - a[1])[0][0]\n for (const it of word) {\n const c = owner.get(it)!\n if (c === target) continue\n result.set(c, result.get(c)!.filter(x => x !== it))\n result.get(target)!.push(it)\n owner.set(it, target)\n }\n }\n k = e\n }\n i = j\n }\n}\n\n/**\n * 셀 내 텍스트 아이템을 읽기 순서로 정렬 후 합치기.\n * Y 내림차순 (위→아래) → X 오름차순 (좌→우)\n * @param wrap 칸 상자·문서 어휘 증거 — 있으면 줄 꺾임을 기하·어휘로 판정해 어절 중간 꺾임만 붙인다(line-wrap.ts).\n * 없으면(과소분할 재구성 등 칸 상자 없는 호출) 종전 조각 규칙\n */\nexport function cellTextToString(items: TextItem[], wrap?: { box: { x1: number; x2: number }; lex?: WrapLexicon }): string {\n if (items.length === 0) return \"\"\n if (items.length === 1) return items[0].text\n\n // Y좌표로 행 그룹핑 (tolerance: max(3, fontSize*0.6))\n const sorted = [...items].sort((a, b) => b.y - a.y || a.x - b.x)\n const lines: TextItem[][] = []\n let curLine: TextItem[] = [sorted[0]]\n let curY = sorted[0].y\n\n for (let i = 1; i < sorted.length; i++) {\n const tol = Math.max(3, Math.min(sorted[i].fontSize, curLine[0].fontSize) * 0.6)\n if (Math.abs(sorted[i].y - curY) <= tol) {\n curLine.push(sorted[i])\n } else {\n lines.push(curLine)\n curLine = [sorted[i]]\n curY = sorted[i].y\n }\n }\n lines.push(curLine)\n\n // 첨자 행 흡수 — 본문 행보다 살짝 떠 있는 작은 심볼 조각(▲·원문자·각주 마커)이\n // tolerance를 벗어나 별도 행이 된 것을 수직 겹침으로 되돌린다\n // (text-line.ts mergeSuperscriptLines와 동일 규칙: 조각 ≤3개·각 ≤8자·\n // 높이가 인접 행의 80% 이하·수직 겹침 ≥ 조각 높이 50%)\n const merged = mergeSuperscriptRows(lines).map(line => sortLineByX(line))\n\n // 각 행을 텍스트로 변환 — 좌표 기반 균등배분 감지 포함\n const textLines = merged.map(s => {\n if (s.length === 1) return s[0].text\n // 두 음절 배분 정렬 칸(\"중 동\"·\"유 럽\") — 세 글자 이상 균등배분(detectEvenSpacedItems)과 같은 조판인데 두 글자라 빠졌다.\n // 음절 사이가 글자 크기의 1.5배 이상이고 두 음절이 칸 폭을 꽉 채우며(배분 정렬은 첫 글자를 칸 왼쪽, 끝 글자를 오른쪽 여백에\n // 붙인다) 붙인 꼴이 같은 문서 줄 안에서 한 어절로 나오거나 두 음절이 어절 안에서만 이웃할 때만(line-wrap 문서 어휘 증거).\n // 원문에 공백을 친 채 벌린 칸은 그림이 같아 기하로 못 가른다: 좁은 칸 \"과 장\"(보도자료 연락처)은 간격 1.0배 안팎이라 문턱에서,\n // 서식 이름표 \"성 명\"은 본문에 한 어절로 잘 안 나와 어휘에서 걸러진다. 본문에도 나오는 낱말을 크게 벌린 이름표(\"경 력\"·\"은 행\")는\n // 못 가른다(hwpx↔pdf 751쌍: 건설업조사·해외직접투자 보도자료 등 나아짐, 2문서 1~4어절 나빠짐)\n // 통계표 지역 이름표(시도·전국)는 본문에 한 어절로 안 나와도 붙인다 — 띄어 쓰는 일이 없는 닫힌 목록(건설업조사 보도자료 \"서 울\" 102곳)\n if (s.length === 2 && /^[가-힣]$/.test(s[0].text) && /^[가-힣]$/.test(s[1].text) && !!wrap?.lex &&\n (wrap.lex.isWord(s[0].text + s[1].text) || wrap.lex.evidence(s[0].text, s[1].text) === \"\" || REGION_LABELS.has(s[0].text + s[1].text)) &&\n s[1].x - (s[0].x + s[0].w) >= Math.max(s[0].fontSize, s[1].fontSize) * 1.5 &&\n s[1].x + s[1].w - s[0].x >= (wrap.box.x2 - wrap.box.x1) - Math.max(s[0].fontSize, s[1].fontSize) * 2) return s[0].text + s[1].text\n\n // 균등배분 구간 감지 (좌표 기반)\n const evenSpaced = detectEvenSpacedItems(s, true)\n\n let result = s[0].text\n for (let j = 1; j < s.length; j++) {\n // 균등배분 구간이면 무조건 공백 없이 합침\n if (evenSpaced[j]) {\n result += s[j].text\n continue\n }\n\n const gap = s[j].x - (s[j - 1].x + s[j - 1].w)\n const avgFs = (s[j].fontSize + s[j - 1].fontSize) / 2\n // pdfjs 공백 아이템 힌트 — 단어 경계 확정 (Type3 폰트 글자 분리 셀 텍스트 복원)\n if (isCjkLatinAutospace(s[j - 1].text, s[j].text, gap, avgFs)) {\n result += s[j].text\n } else if (s[j].hasSpaceBefore && gap >= avgFs * 0.05) {\n result += \" \" + s[j].text\n } else if (gap > spaceGapThreshold(avgFs)) {\n result += \" \" + s[j].text\n } else {\n result += s[j].text\n }\n }\n return result\n })\n\n // 첨자 태그는 줄 병합 판정(평문)을 다 한 뒤에 — 판정은 글 끝 글자를 본다\n const scripted = (s: string) => tagScripts(s, merged)\n if (!wrap) return scripted(mergeCellTextLines(textLines))\n // 줄마다 \"다음 줄로 꺾여 넘어갔나\" — 다음 줄 첫 글자가 이 줄 뒤에 칸 안쪽으로 못 들어갈 때\n let contentLeft = Infinity\n for (const it of items) if (it.x < contentLeft) contentLeft = it.x\n const lineEnds = merged.map(s => {\n let right = -Infinity, left = Infinity\n for (const it of s) { if (it.x + it.w > right) right = it.x + it.w; if (it.x < left) left = it.x }\n const first = s[0]\n // 아이템에 붙인 표시 태그는 글리프가 아니다 — 다음 줄 첫 글자 폭은 실제 글자 수로 나눈다.\n const visible = first.text.replace(/<\\/?u>|~~/g, \"\")\n return { left, right, fontSize: first.fontSize, firstCharW: first.w / Math.max(1, [...visible].length) }\n })\n let cellRight = -Infinity\n for (const e of lineEnds) if (e.right > cellRight) cellRight = e.right\n const wraps = lineEnds.slice(0, -1).map((a, i) => cellLineWraps(wrap.box, contentLeft, a.right, a.fontSize, lineEnds[i + 1].firstCharW)\n || (lineEnds.length >= 3 && !textLines[i + 1].startsWith(\"(\") && cellLineFills(wrap.box, contentLeft, cellRight, a.right, a.fontSize)))\n // 줄이 칸 안쪽 폭을 채웠나 — 다음 줄 첫 글자가 이 줄 앞뒤 어디에도 못 들어갈 때만 꺾여 잘린 숫자다. 오른쪽 정렬 금액(\"500\")은 칸 오른끝에\n // 닿아 꺾임으로 보여도 칸을 채운 줄이 아니고, 좁은 쪽 번호 칸에 쌓인 세 자리 번호(\"124\" / \"135\" → 종전 \"124135\", 현장실습 매뉴얼 목차 —\n // 칸 안쪽 폭의 64%라 종전 60% 문턱을 넘었다)도 그렇다. 안 여백은 글 왼끝·오른끝 쪽 빈자리 가운데 좁은 쪽 — 오른쪽 정렬 칸의 왼쪽 빈자리는 여백이 아니다\n const pad = Math.min(6, Math.max(0, contentLeft - wrap.box.x1), Math.max(0, wrap.box.x2 - cellRight))\n const inner = wrap.box.x2 - wrap.box.x1 - 2 * pad\n const wide = lineEnds.slice(0, -1).map((a, i) => a.right - a.left + 1.5 * lineEnds[i + 1].firstCharW > inner)\n // 줄 끝 아이템 뒤 공백 글리프 — 그 꺾임은 어절 경계라 붙이지 않는다 (markTrailingSpaceGlyphs)\n const spaced = merged.map(s => !!s[s.length - 1].spaceAfter)\n return scripted(mergeCellTextLines(textLines, { wraps, lex: wrap.lex, wide, spaced }))\n}\n\n/** 첨자 행 병합 — cellTextToString 행 그룹핑 결과에 적용 (규칙은 text-line.ts와 동일) */\nfunction mergeSuperscriptRows(lines: TextItem[][]): TextItem[][] {\n if (lines.length <= 1) return lines\n const band = (line: TextItem[]) => {\n let bottom = Infinity, top = -Infinity\n for (const i of line) {\n const h = i.h > 0 ? i.h : i.fontSize\n if (i.y < bottom) bottom = i.y\n if (i.y + h > top) top = i.y + h\n }\n return { bottom, top, height: top - bottom }\n }\n const isFrag = (line: TextItem[]) => {\n if (line.length > 8) return false\n let total = 0\n for (const i of line) total += i.text.trim().length\n return total > 0 && total <= 10\n }\n\n const result: TextItem[][] = [lines[0]]\n for (let i = 1; i < lines.length; i++) {\n const prev = result[result.length - 1]\n const curr = lines[i]\n const a = band(prev)\n const b = band(curr)\n const overlap = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom)\n // 글자마다 붙은 첨자 여럿(서식 칸 \"□예 □아니오\" 의 작은 \"예\"·\"아니오\" 가 한 줄에 여섯)도 본문 줄과 같은 규칙으로 (isAttachedScripts)\n const prevIsFrag = (isFrag(prev) || isAttachedScripts(prev, curr)) && a.height <= b.height * 0.8 && overlap >= a.height * 0.5\n const currIsFrag = (isFrag(curr) || isAttachedScripts(curr, prev)) && b.height <= a.height * 0.8 && overlap >= b.height * 0.5\n if (prevIsFrag || currIsFrag) {\n result[result.length - 1] = [...prev, ...curr]\n } else {\n result.push(curr)\n }\n }\n return result\n}\n\n/**\n * 좌표 기반 균등배분 감지 — TextItem 배열에서 한글 1~2자 아이템이\n * 일정 간격으로 3개+ 연속되면 균등배분으로 판단.\n * ODL TextLineProcessor의 핵심 로직을 좌표 기반으로 구현.\n */\nfunction detectEvenSpacedItems(items: TextItem[], cellLine = false): boolean[] {\n const result = new Array(items.length).fill(false)\n if (items.length < 3) return result\n const visible = items.map(it => it.text.replace(/<\\/?u>|~~/g, \"\"))\n // 칸 한 줄이 한 음절 글자뿐이고 글자 틈이 모두 벌어졌는데 공백 글리프가 하나도 없으면(pdfjs 가 틈에 만든 공백뿐) 배분 정렬 칸이다 —\n // \"보 [-777.8] 험 [-777.8] 업\"(해외직접투자 보도자료), 틈 일부에만 합성 공백이 든 \"법 무 연 수 원\"(교정공무원 인사). 공백 글리프가 있거나\n // 붙은 글자가 있으면(한 글자씩 찍고 낱말 틈만 벌린 글) 아래 종전 규칙(공백에서 끊음)대로.\n // 칸 글에서만 — 쪽 줄(mergeLineSimple)은 칸 경계를 넘어 한 글자씩 찍은 행(\"대구교도소장 김진아\")을 한 낱말로 붙였다\n if (cellLine && visible.every(text => /^[가-힣]$/.test(text)) && items.slice(1).every(it => !it.hasSpaceBefore || it.syntheticSpace)\n && items.slice(1).every((it, k) => it.x - (items[k].x + items[k].w) >= it.fontSize * 0.1)) {\n markEvenRun(items, visible, result, 0, items.length)\n // 줄 전체가 고른 배분이 아니면(틈 비율 3배 넘음 — \"전 문 업 종 건 설 업\") 종전 규칙으로 부분 run 을 본다\n if (result.some(Boolean)) return result\n }\n\n let runStart = -1\n for (let i = 0; i < items.length; i++) {\n // 균등배분 = 한글 1자 개별 배치. 2자 단어는 균등배분이 아니라 실제 단어.\n const isShortKorean = /^[가-힣]{1}$/.test(visible[i]) || /^[\\d]{1}$/.test(visible[i])\n\n // 명시적 공백 글리프가 직전에 있으면 단어 경계 — 균등배분 run 분리.\n // (Type3 폰트가 글자를 1자씩 배치하면서 공백 글리프를 따로 두는 경우,\n // 진짜 단어 경계를 균등배분으로 오판해 문장 전체가 붙는 것을 방지)\n if (isShortKorean && runStart >= 0 && items[i].hasSpaceBefore) {\n if (i - runStart >= 3) markEvenRun(items, visible, result, runStart, i)\n runStart = i\n continue\n }\n\n // 이전 아이템과의 갭이 fontSize*3+ 이면 run 끊기 (다른 영역)\n if (isShortKorean && runStart >= 0 && i > 0) {\n const gap = items[i].x - (items[i - 1].x + items[i - 1].w)\n const maxRunGap = Math.max(items[i].fontSize * 3, 30)\n if (gap > maxRunGap) {\n if (i - runStart >= 3) markEvenRun(items, visible, result, runStart, i)\n runStart = i\n continue\n }\n }\n\n if (isShortKorean) {\n if (runStart < 0) runStart = i\n } else {\n if (runStart >= 0 && i - runStart >= 3) {\n markEvenRun(items, visible, result, runStart, i)\n }\n runStart = -1\n }\n }\n if (runStart >= 0 && items.length - runStart >= 3) {\n markEvenRun(items, visible, result, runStart, items.length)\n }\n\n return result\n}\n\nfunction markEvenRun(items: TextItem[], visible: string[], result: boolean[], start: number, end: number): void {\n // 별지서식 기입 빈칸 \"년 월 일\"·\"시 분\" 은 균등배분이 아니다 — 글자 단위가 전부 날짜·시각 단위면 두고 문자열 안전망\n // (collapseEvenSpacing isDateUnitBlank)과 같게. 공백 글리프 없이 칸을 벌린 서식은 틈이 3em 안이면 여기서 붙었다\n // (hwpx↔pdf 칸 줄: 한 글자 조각이 날짜 단위뿐인 줄 46곳 중 원문이 붙여 쓴 곳 0 — 행정업무운영 편람 서식 \"년 월 일\")\n let dateOnly = true\n for (let i = start; i < end; i++) if (!/^[년월일시분초]$/.test(visible[i])) { dateOnly = false; break }\n if (dateOnly) return\n const gaps: number[] = []\n for (let i = start + 1; i < end; i++) {\n gaps.push(items[i].x - (items[i - 1].x + items[i - 1].w))\n }\n const posGaps = gaps.filter(g => g > 0)\n if (posGaps.length < 2) return\n\n let minGap = Infinity, maxGap = -Infinity\n for (const g of posGaps) { if (g < minGap) minGap = g; if (g > maxGap) maxGap = g }\n const avgFs = items[start].fontSize\n\n // 간격이 fontSize의 0.1~3배 사이이고, 최대/최소 비율 3배 이내\n if (minGap >= avgFs * 0.1 && maxGap <= avgFs * 3 && maxGap / Math.max(minGap, 0.1) <= 3) {\n for (let i = start + 1; i < end; i++) {\n result[i] = true\n }\n }\n}\n\nexport { detectEvenSpacedItems }\n\n/**\n * 셀 내 텍스트 아이템을 읽기 순서로 정렬 후 합치기 — 줄바꿈 병합 전용.\n * (cellTextToString 내부에서 사용)\n * wrap 이 있으면 한글 줄 이음은 꺾임 판정(wraps[i] = textLines[i] 가 다음 줄로 꺾였나)과 어절 판정(wrapJoiner)으로:\n * 어절 중간 꺾임만 붙이고 어절 경계 꺾임·문단 경계는 줄바꿈으로 둔다(칸은 가운데 정렬 칸의 넓은 줄이 찬 줄처럼 보여 문단\n * 경계를 공백으로 잇는 건 삼간다). 종전 조각 규칙 — 8자 이하 한글 조각 붙임·쉼표/여는 괄호 뒤 15자 붙임 — 은 hwpx↔pdf 417쌍\n * 칸 줄 이음 실측에서 정밀도 10%(맞음 101·틀림 874)·2%(맞음 5·틀림 316)라 칸 상자가 있는 호출에서는 쓰지 않는다\n */\nfunction mergeCellTextLines(textLines: string[], wrap?: { wraps: boolean[]; lex?: WrapLexicon; wide?: boolean[]; spaced?: boolean[] }): string {\n // 셀 내 줄바꿈 병합 — 잘린 단어/숫자 조각 복구\n if (textLines.length <= 1) return textLines[0] || \"\"\n const numberWrapped = (k: number) => !!wrap?.wraps[k] && (wrap.wide?.[k] ?? true)\n const merged: string[] = [textLines[0]]\n for (let i = 1; i < textLines.length; i++) {\n const prev = merged[merged.length - 1]\n const curr = textLines[i]\n if (wrap\n ? wrap.wraps[i - 1] && !wrap.spaced?.[i - 1] && !startsNewItem(prev, curr) && wrapJoiner(prev, curr, wrap.lex, false) === \"\"\n : /[가-힣]$/.test(prev) && /^[가-힣]+$/.test(curr) && curr.length <= 8 && !curr.includes(\" \")) {\n merged[merged.length - 1] = prev + curr\n }\n else if (curr.trim().length <= 3 && /^[)\\]%}]/.test(curr.trim())) {\n merged[merged.length - 1] = prev + curr.trim()\n }\n else if (!wrap && /[,(]$/.test(prev.trim()) && curr.trim().length <= 15) {\n merged[merged.length - 1] = prev + curr.trim()\n }\n // 줄바꿈에 잘린 숫자 조각(\"1,234,5\" / \"67\")은 잇되, 온전한 숫자 뒤 숫자 줄은 잇지 않는다 — 병합 칸에 쌓인 천 단위 숫자\n // (\"20,775,661\" / \"5,187,590\")와 쉼표 없는 세 자리 이하 숫자(\"810\" / \"810\" → 종전 \"810810810\", \"2,240\" / \"0\" →\n // \"2,2400\", 괴산·부천 예산서 텍스트층·OCR 공통). 쉼표 뒤 세 자리로 끝난 줄에 숫자 줄을 이으면 \",dddd\" 꼴이 되므로 늘 끊고,\n // 쉼표 없는 세 자리 이하 숫자는 다음 줄도 온전한 숫자일 때 끊는다. 줄에 홀로 선 정수(첫머리·공백 뒤) 다음 정수 줄도 끊는다 —\n // 칸에 줄마다 적은 \"600\" / \"1000\"·\"1500\" / \"2500\"(table_giant_cell_overfill). 칸 폭을 채우고 꺾인 줄(wraps)은 잘린 한 숫자라\n // 잇고, 글 뒤에 붙은 번호 조각(\"02-123\" / \"4567\")은 홀로 선 수가 아니라 종전대로 잇는다. 꺾임은 칸을 채운 줄만 — 오른쪽 정렬 금액은\n // 칸 오른끝에 닿아도 잘린 숫자가 아니다(속초 세출예산서 OCR 칸 \"500\" / \"500\" → \"500500\")\n else if (/[\\d,]$/.test(prev) && /^[\\d,]+[)\\]]?$/.test(curr.trim()) && curr.trim().length <= 10\n && !(/\\d,\\d{3}$/.test(prev) && /^\\d/.test(curr.trim()))\n // 칸 상자를 알면 쉼표 든 숫자도 칸을 채우고 꺾인 줄만 잇는다 — 값 위에 작게 얹은 각주 첨자 \"3,4\" 가 아래 줄 값 \"60\" 과 \"3,460\",\n // 첨자끼리 \"3\" / \"3,4\" 가 \"33,4\" 로 붙었다(고속선 안전 코드 화재 구역 표 \"₃,₄⏎60\", 정답 첨자). 상자 없는 호출은 종전대로 잇는다\n && !(wrap && !numberWrapped(i - 1))\n && !(/(?:^|[^\\d,])\\d{1,3}$/.test(prev) && /^(\\d{1,3}(,\\d{3})+|\\d{1,3})$/.test(curr.trim()) && !numberWrapped(i - 1))\n && !(/(?:^|\\s)\\d{1,7}$/.test(prev) && /^\\d{1,7}$/.test(curr.trim()) && !numberWrapped(i - 1))) {\n merged[merged.length - 1] = prev + curr.trim()\n }\n // 글이 든 줄이 쉼표로 끝나고 다음 줄이 숫자뿐이면 낱말 사이 꺾임이다 — 칸을 다 채운 줄이 아니라 숫자 이음은 아니고(종전엔 숫자째 붙여\n // \"April 30,2022\"), 줄바꿈으로 두면 날짜가 갈린다. 공백으로 잇는다(정답 \"April 30, 2022\", ODL 180 판 이력 표). 글 없는 \"3,4\" 첨자 줄은 그대로\n else if (wrap && /[A-Za-z가-힣]/.test(prev) && /,$/.test(prev.trim()) && /^[\\d,]+[)\\]]?$/.test(curr.trim()) && curr.trim().length <= 10) {\n merged[merged.length - 1] = prev.trimEnd() + \" \" + curr.trim()\n }\n else {\n merged.push(curr)\n }\n }\n return merged.join(\"\\n\")\n}\n\n/** 칸 안 라틴 글 줄 꺾임 — 두 줄 모두 한글 없이 라틴 낱말을 품고, 다음 줄이 새 항목(글머리 부호·번호)이 아닐 때 */\nexport function latinSoftWrap(prev: string, curr: string): boolean {\n const a = prev.trim(), b = curr.trim()\n // 다음 줄은 라틴 낱말이나 괄호 덧붙임(\"Reservation⏎(1994)\")으로 시작한다\n if (!a || !b || /[가-힣]/.test(a + b) || !/[A-Za-z]{2}/.test(a) || !/[A-Za-z]/.test(b) && !/^\\(.*\\)$/.test(b)) return false\n if (/^(?:[•●○◦▪▫■□◆◇➢➤►▶✓✔\\-–—*·]|\\(?\\d{1,2}[.)]\\s|\\(?[a-z][.)]\\s|\\([ivx]{1,4}\\)\\s)/.test(b)) return false\n return !startsNewItem(a, b)\n}\n","/**\n * 과소분할 표 재구성 (line-detector.ts에서 분리).\n *\n * OpenDataLoader PDF의 TableStructureNormalizer를 참고하여 TypeScript로\n * clean-room 재구현한 것입니다.\n * Original algorithm: Copyright 2025-2026 Hancom, Inc. (Apache 2.0)\n * https://github.com/opendataloader-project/opendataloader-pdf\n */\n\nimport type { TextItem } from \"./line-types.js\"\nimport { cellTextToString } from \"./cell-text.js\"\n\n// ─── 과소분할 표 재구성 (ODL TableStructureNormalizer 포팅) ──\n//\n// 행 구분선이 생략된 표(헤더 아래만 선이 있는 한국 공문서 표)는 본문 전체가\n// 소수의 긴 행으로 합쳐진다. 셀 안에 텍스트 줄이 8개+ 뭉친 경우 줄의 centerY로\n// row band를 재유도해 행을 복원한다. 품질이 개선될 때만 교체.\n//\n// Original work: Copyright 2025-2026 Hancom Inc. (Apache-2.0)\n// https://github.com/opendataloader-project/opendataloader-pdf\n\nconst MAX_UNDERSEGMENTED_ROWS = 5\nconst MIN_UNDERSEGMENTED_COLUMNS = 3\nconst MIN_UNDERSEGMENTED_TEXT_LINES = 8\nconst MIN_ROW_BAND_MISMATCH = 2\nconst MIN_ROW_BAND_EPSILON = 3.0\nconst ROW_BAND_EPSILON_RATIO = 0.6\n\ninterface RowBand {\n centerY: number\n avgHeight: number\n topY: number\n bottomY: number\n lineCount: number\n /** 컬럼별 아이템 */\n itemsByCol: TextItem[][]\n}\n\n/** 아이템 중심 Y (h가 0이면 fontSize 대용) */\nfunction itemCenterY(item: TextItem): number {\n return item.y + (item.h > 0 ? item.h : item.fontSize) / 2\n}\n\nfunction itemHeight(item: TextItem): number {\n return item.h > 0 ? item.h : item.fontSize\n}\n\n/** 아이템을 colXs 경계 기준 컬럼에 배정 (중심 X 기준, 범위 밖이면 최근접) */\nfunction findColumnIndex(item: TextItem, colXs: number[]): number {\n const cx = item.x + item.w / 2\n for (let c = 0; c < colXs.length - 1; c++) {\n if (cx >= colXs[c] && cx <= colXs[c + 1]) return c\n }\n let best = 0\n let bestDist = Infinity\n for (let c = 0; c < colXs.length - 1; c++) {\n const center = (colXs[c] + colXs[c + 1]) / 2\n const d = Math.abs(cx - center)\n if (d < bestDist) { bestDist = d; best = c }\n }\n return best\n}\n\n/** 아이템들을 Y 기준 시각적 줄로 그룹핑 */\nfunction groupItemsToVisualLines(items: TextItem[]): TextItem[][] {\n if (items.length === 0) return []\n const sorted = [...items].sort((a, b) => b.y - a.y || a.x - b.x)\n const lines: TextItem[][] = []\n let cur: TextItem[] = [sorted[0]]\n let curY = sorted[0].y\n for (let i = 1; i < sorted.length; i++) {\n const tol = Math.max(3, Math.min(sorted[i].fontSize, cur[0].fontSize) * 0.6)\n if (Math.abs(sorted[i].y - curY) <= tol) {\n cur.push(sorted[i])\n } else {\n lines.push(cur)\n cur = [sorted[i]]\n curY = sorted[i].y\n }\n }\n lines.push(cur)\n return lines\n}\n\n/**\n * 과소분할 표 재구성. 조건(행≤5 + 열≥3 + dense 컬럼 2개+)을 만족하고\n * row band 재유도가 품질을 개선할 때만 새 셀 행렬을 반환, 아니면 null.\n *\n * @param originalCells 기존 셀 행렬 (품질 비교용)\n * @param colXs 그리드 열 경계\n * @param items 표 영역 내 텍스트 아이템\n */\nexport function normalizeUndersegmentedTable(\n originalCells: { text: string }[][],\n colXs: number[],\n items: TextItem[],\n rowYs?: number[],\n): string[][] | null {\n const numRows = originalCells.length\n const numCols = colXs.length - 1\n if (numRows > MAX_UNDERSEGMENTED_ROWS || numCols < MIN_UNDERSEGMENTED_COLUMNS) return null\n if (items.length === 0) return null\n if (numRows >= 3) {\n if (rowYs?.length !== numRows + 1) return null\n const rebuilt: string[][] = []\n let expanded = 0\n for (let r = 0; r < numRows; r++) {\n const row = originalCells[r].map(cell => cell.text)\n if (r === 0 || (numRows === 3 && r === 1)) { rebuilt.push(row); continue }\n const rowItems = items.filter(item => itemCenterY(item) <= rowYs[r] + 1 && itemCenterY(item) >= rowYs[r + 1] - 1)\n const body = normalizeUndersegmentedTable([originalCells[r]], colXs, rowItems)\n if (body) { rebuilt.push(...body); expanded++ }\n else rebuilt.push(row)\n }\n return expanded ? rebuilt : null\n }\n\n // 1) 컬럼별 의미있는 줄 수 — dense 컬럼(8줄+) 2개 이상이어야 과소분할로 판정\n const itemsByCol: TextItem[][] = Array.from({ length: numCols }, () => [])\n for (const item of items) {\n if (!item.text.trim()) continue\n itemsByCol[findColumnIndex(item, colXs)].push(item)\n }\n let denseColumns = 0\n for (const colItems of itemsByCol) {\n if (groupItemsToVisualLines(colItems).length >= MIN_UNDERSEGMENTED_TEXT_LINES) denseColumns++\n }\n if (denseColumns < 2) return null\n\n // 2) 전체 줄에서 row band 유도 — centerY 근접(epsilon) 또는 수직 겹침이면 같은 band\n const allLines = groupItemsToVisualLines(items.filter(i => i.text.trim()))\n const bands: RowBand[] = []\n for (const line of allLines) {\n let cy = 0, h = 0\n for (const it of line) { cy += itemCenterY(it); h += itemHeight(it) }\n cy /= line.length\n h /= line.length\n const top = cy + h / 2\n const bottom = cy - h / 2\n\n let matched: RowBand | null = null\n for (const band of bands) {\n const epsilon = Math.max(MIN_ROW_BAND_EPSILON, Math.min(band.avgHeight, h) * ROW_BAND_EPSILON_RATIO)\n if (Math.abs(band.centerY - cy) <= epsilon ||\n (bottom <= band.topY && top >= band.bottomY)) {\n matched = band\n break\n }\n }\n if (!matched) {\n matched = { centerY: 0, avgHeight: 0, topY: -Infinity, bottomY: Infinity, lineCount: 0, itemsByCol: Array.from({ length: numCols }, () => []) }\n bands.push(matched)\n }\n matched.centerY = (matched.centerY * matched.lineCount + cy) / (matched.lineCount + 1)\n matched.avgHeight = (matched.avgHeight * matched.lineCount + h) / (matched.lineCount + 1)\n matched.topY = Math.max(matched.topY, top)\n matched.bottomY = Math.min(matched.bottomY, bottom)\n matched.lineCount++\n for (const it of line) {\n matched.itemsByCol[findColumnIndex(it, colXs)].push(it)\n }\n }\n\n // 3) band 수가 기존 행 수 + 2 이상이어야 재구축 의미 있음\n if (bands.length < numRows + MIN_ROW_BAND_MISMATCH) return null\n\n bands.sort((a, b) => b.centerY - a.centerY)\n\n // 4) 셀 행렬 재구축\n const rebuilt: string[][] = bands.map(band =>\n band.itemsByCol.map(colItems => colItems.length > 0 ? cellTextToString(colItems) : \"\"),\n )\n\n // 5) 품질 검증: 비어있지 않은 행 수가 늘고, 비어있지 않은 열 수가 줄지 않아야 교체\n const countNonEmptyRows = (cells: { text: string }[][] | string[][]) =>\n cells.filter(row => row.some(c => (typeof c === \"string\" ? c : c.text).trim() !== \"\")).length\n const countNonEmptyCols = (cells: { text: string }[][] | string[][], cols: number) => {\n let n = 0\n for (let c = 0; c < cols; c++) {\n if (cells.some(row => row[c] != null && (typeof row[c] === \"string\" ? row[c] as string : (row[c] as { text: string }).text).trim() !== \"\")) n++\n }\n return n\n }\n\n if (countNonEmptyRows(rebuilt) <= countNonEmptyRows(originalCells)) return null\n // 열 비교는 본문 행만 — 머리 행 병합 칸(\"부서·장·관·항·목\", 0~2열)은 앵커 열 하나로 세지만 재구성은 글자 중심 x 로\n // 다시 나눠, 제대로 된 재구성을 열이 줄었다고 버리던 것 (괴산 예산서 OCR 실측)\n const origBody = numRows > 1 ? originalCells.slice(1) : originalCells\n if (countNonEmptyCols(rebuilt, numCols) < countNonEmptyCols(origBody, numCols)) return null\n\n return rebuilt\n}\n","/**\n * PDF 텍스트 아이템/줄 수준 유틸.\n *\n * pdfjs TextItem → NormItem 정규화(가짜 볼드 dedupe, 균등배분 분해, 공백 힌트 전파),\n * 줄 그룹핑(groupByY, 첨자 병합), 줄 텍스트 조립(mergeLineSimple),\n * 균등배분 공백 제거(collapseEvenSpacing), bbox/스타일 계산.\n */\n\nimport type { BoundingBox } from \"../types.js\"\nimport type { TextItem } from \"./line-types.js\"\nimport { detectEvenSpacedItems, spaceGapThreshold } from \"./line-detector.js\"\n\nexport interface PdfTextItem {\n str: string\n transform: number[]\n width: number\n height: number\n fontName?: string\n}\n\nexport interface NormItem {\n text: string\n x: number\n y: number\n w: number\n h: number\n /** 폰트 높이(≈폰트 크기) — 헤딩 감지용 */\n fontSize: number\n fontName: string\n /** hidden text 여부 (투명/0pt) */\n isHidden: boolean\n /** pdfjs 공백 아이템이 이 아이템 직전에 있었음 — 단어 경계 힌트 */\n hasSpaceBefore?: boolean\n /** 직전 공백이 pdfjs 가 글자 틈으로 만든 것뿐(글리프 흐름에 공백 글리프 없음, tracked-text markSyntheticSpaces) — 균등배분 run 을 끊지 않는다 */\n syntheticSpace?: boolean\n /** 줄 끝 아이템 뒤 글리프가 공백 글리프인가(tracked-text markTrailingSpaceGlyphs) — true 면 이 줄의 꺾임은 어절 경계, false 는 줄 끝 공백을\n * 찍는 쪽에서 공백이 없던 줄 끝, 없으면 모름 */\n spaceAfter?: boolean\n /** 취소선이 그어진 텍스트 (신구조문대비표 삭제 표시 등) */\n strike?: boolean\n /** 밑줄이 그어진 텍스트 (개정문 추가·변경 표시, 제목 강조 등) */\n underline?: boolean\n /** 콘텐츠 스트림 순번 — 좌표가 겹친 글자의 순서를 되살리는 데만 쓴다 (sortLineByX) */\n seq?: number\n /** 세로로 돌린 글 (글자 진행 방향이 y)의 세로 길이 — 쪽 여백 도장(\"arXiv:… [cs.CL]\") 가르기용 */\n rotated?: number\n /** 돌린 글이 아래로 진행(시계 방향 90°) — 없으면 위로(반시계) */\n rotatedDown?: true\n}\n\n/** 같은 줄 아이템 x 정렬 — x 가 1pt 이내로 붙은 이웃은 콘텐츠 스트림 순서를 따른다. 좌표를 정수로 반올림하므로\n * 자간을 줄인 숫자에서 뒤 글자가 앞 글자 위로 0.1pt 겹치면(“.”@251.5 → 252, “8”@251.4 → 251) x 만으로는\n * \"147.8\" 이 \"1478.\" 로 뒤집힌다(해외직접투자 보도자료 표 실측). 제자리 정렬 후 반환 */\nexport function sortLineByX<T extends { x: number; seq?: number }>(items: T[]): T[] {\n items.sort((a, b) => a.x - b.x)\n for (let i = 1; i < items.length; i++) {\n for (let j = i; j > 0; j--) {\n const a = items[j - 1], b = items[j]\n if (b.x - a.x > 1 || a.seq === undefined || b.seq === undefined || b.seq >= a.seq) break\n items[j - 1] = b\n items[j] = a\n }\n }\n return items\n}\n\n// ═══════════════════════════════════════════════════════\n// Hidden text 필터링 (prompt injection 방어)\n// ═══════════════════════════════════════════════════════\n\n/** 한자·가나와 라틴 글자·숫자 사이의 좁은 틈(글자 크기 0.3배 미만)인가 — 조판기의 아시아-라틴 자동 간격이라 공백이 아니다(한글 제외) */\nexport function isCjkLatinAutospace(prevText: string, nextText: string, gap: number, fontSize: number): boolean {\n if (!(gap < fontSize * 0.3)) return false\n const a = prevText.slice(-1), b = nextText[0] ?? \"\"\n const cjk = /[\\u3040-\\u30ff\\u3400-\\u9fff\\uf900-\\ufaff]/, latin = /[A-Za-z0-9]/\n return (cjk.test(a) && latin.test(b)) || (latin.test(a) && cjk.test(b))\n}\n\nexport function filterHiddenText(items: NormItem[], pageWidth: number, pageHeight: number, originX = 0, originY = 0): { visible: NormItem[]; hiddenCount: number } {\n let hiddenCount = 0\n const visible: NormItem[] = []\n\n for (const item of items) {\n // 0pt 폰트 / 너비 0 → 숨겨진 텍스트\n if (item.isHidden) { hiddenCount++; continue }\n // 페이지 범위 밖 (여백 10% 허용)\n const margin = Math.max(pageWidth, pageHeight) * 0.1\n if (item.x < originX - margin || item.x > originX + pageWidth + margin || item.y < originY - margin || item.y > originY + pageHeight + margin) {\n hiddenCount++; continue\n }\n visible.push(item)\n }\n\n return { visible, hiddenCount }\n}\n\n/**\n * 문자열 기반 균등배분 제거.\n * normalizeItems에서 분해 + 좌표 기반 감지가 주 경로이고, 여기는 안전망.\n * pdfjs가 이미 합친 \"홍 보 담 당 관\" 같은 TextItem 문자열에 적용.\n * @param whole 줄 전체 한 글자 비율 규칙(1)까지 쓸지 — false 면 홀로 선 한 글자 셋 이상 연속(2)만. 마크다운 최종 정리·헤딩은 false\n * (1이 기호·쌍점·등호 토큰까지 한 글자로 세어 \"□ 개 요\"·\"N = 잠수펌프의 수\" 의 원문 띄어쓰기를 통째로 지웠다)\n */\nexport function collapseEvenSpacing(text: string, whole = true): string {\n // 1. 전체가 균등배분: 토큰의 70%가 1글자\n if (whole) {\n const tokens = text.split(\" \")\n // 표시 서식은 글자 비율·날짜 빈칸 판정에 영향이 없다. 출력 태그는 원래 토큰으로 보존한다.\n const visible = tokens.map(t => t.replace(/<\\/?u>|~~/g, \"\"))\n const singleCharCount = visible.filter(t => t.length === 1).length\n if (tokens.length >= 3 && singleCharCount / tokens.length >= 0.7 && !isDateUnitBlank(visible)) return tokens.join(\"\")\n }\n\n // 2. 부분 균등배분: 한글 1자가 3개+ 연속 (2자 단어는 건드리지 않음)\n // \"홍 보 담 당 관\" → \"홍보담당관\", \"지 역 경 제 과\" → \"지역경제과\"\n // \"중동 사태 대응\" (2자 단어)는 매칭 안 됨 → 공백 유지\n // 앞뒤가 공백·줄 끝(또는 밑줄 표지 <u>·</u>)인 한 글자만 — 한글만 막던 종전 경계는 \"10월 중 첫\"의 \"월\"(앞이 숫자)·\"6명 등 33명\"의\n // \"3\"(뒤가 숫자)·\"제12조 및 제13조\"의 \"조\"를 홀로 선 글자로 보고 붙였다(\"10월중첫\" — 한컴 PDF 는 숫자와 한글을 다른 아이템으로 낸다).\n // hwpx↔pdf 752쌍: 96문서 나아짐·4문서 나빠짐(각 1~2어절)\n return text.replace(\n /(?<![^\\s>])[가-힣](?: [가-힣\\d]){2,}(?![^\\s<])/g,\n (match, offset: number) => {\n // 표지는 낱말 경계가 아니다 — \"할 수 있</u>다\"의 \"있\"은 홀로 선 한 글자가 아니다.\n const before = text.slice(0, offset).replace(/(?:<\\/?u>|~~)+$/, \"\")\n const after = text.slice(offset + match.length).replace(/^(?:<\\/?u>|~~)+/, \"\")\n if (/\\S$/.test(before) || /^\\S/.test(after)) return match\n return isDateUnitBlank(match.split(\" \")) ? match : match.replace(/ /g, \"\")\n },\n )\n}\n\n/** 별지서식 기입 빈칸 \"년 월 일\"·\"시 분\" — 한 글자 토큰이 전부 날짜·시각 단위면 균등배분이 아니다 (v4.12.1) */\nfunction isDateUnitBlank(tokens: string[]): boolean {\n return tokens.every(t => t.length !== 1 || !/[가-힣]/.test(t) || /[년월일시분초]/.test(t))\n}\n\n/** 아이템 그룹에서 바운딩 박스 계산 */\nexport function computeBBox(items: NormItem[], pageNum: number): BoundingBox {\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n for (const i of items) {\n if (i.x < minX) minX = i.x\n if (i.y < minY) minY = i.y\n if (i.x + i.w > maxX) maxX = i.x + i.w\n // h가 0인 경우 fontSize를 높이 대용으로 사용 (pdfjs가 height를 제공하지 않는 경우)\n const effectiveH = i.h > 0 ? i.h : i.fontSize\n if (i.y + effectiveH > maxY) maxY = i.y + effectiveH\n }\n return { page: pageNum, x: minX, y: minY, width: maxX - minX, height: maxY - minY }\n}\n\n/** 아이템 그룹의 대표 스타일 (최빈 폰트 크기) */\nexport function dominantStyle(items: NormItem[]): { fontSize: number; fontName?: string } | undefined {\n if (items.length === 0) return undefined\n // 최빈 폰트 크기 찾기 — 아이템 수가 아니라 글자·숫자 수로 센다: 큰 \"1.8X\" 뒤 작은 위첨자 \"↑\",\"1\" 두 조각이 줄 크기를 뺏던 것,\n // 목차 리더 점 조각은 세지 않는다\n const freq = new Map<number, number>()\n let maxCount = 0, dominantSize = 0\n for (const i of items) {\n if (i.fontSize <= 0) continue\n const count = (freq.get(i.fontSize) || 0) + Math.max(1, i.text.match(/[\\p{L}\\p{N}]/gu)?.length ?? 0)\n freq.set(i.fontSize, count)\n if (count > maxCount) { maxCount = count; dominantSize = i.fontSize }\n }\n if (dominantSize === 0) return undefined\n // 대표 폰트명 (빈 문자열은 undefined로)\n const fontName = items.find(i => i.fontSize === dominantSize)?.fontName || undefined\n return fontName ? { fontSize: dominantSize, fontName } : { fontSize: dominantSize }\n}\n\nexport function normalizeItems(rawItems: PdfTextItem[]): NormItem[] {\n const items: NormItem[] = []\n // pdfjs 공백 아이템 위치 수집 — 단어 경계 힌트로 활용\n const spacePositions: { x: number; y: number; synthetic?: boolean }[] = []\n\n let seq = 0\n for (const i of rawItems) {\n seq++\n if (typeof i.str !== \"string\") continue\n const x = Math.round(i.transform[4])\n const y = Math.round(i.transform[5])\n\n if (!i.str.trim()) {\n // 공백 전용 아이템: 위치만 기록 (단어 구분 힌트)\n spacePositions.push({ x, y, synthetic: (i as { synthetic?: boolean }).synthetic })\n continue\n }\n\n // 회전 텍스트 대응: 90° 회전 시 [0,s,-s,0] 꼴로 대각 성분이 0이 되므로\n // 열벡터 노름으로 실제 글리프 스케일을 구한다 (사이드탭·회전 표가 hidden 오분류되던 버그)\n const scaleX = Math.hypot(i.transform[0], i.transform[1])\n const scaleY = Math.hypot(i.transform[2], i.transform[3])\n const fontSize = Math.round(Math.max(scaleY, scaleX))\n let w = Math.round(i.width)\n const h = Math.round(i.height)\n // 공백 글리프를 U+0000 으로 매긴 글꼴 — 낱말 끝에 붙은 NUL 이 공백 폭을 차지한 채 제어 문자 정리에서 지워져 \"다음 글을 읽고\"가\n // \"다음글을읽고\"가 됐다(온새미로 교재). 글 뒤에 붙은 끝 NUL 런은 공백이다: 폭에서 공백 몫(0.3em)을 덜고 다음 아이템에 공백 힌트\n if (/[^\\u0000]\\u0000+$/.test(i.str)) {\n w = Math.max(1, Math.round(i.width - Math.round(Math.max(Math.hypot(i.transform[2], i.transform[3]), Math.hypot(i.transform[0], i.transform[1]))) * 0.3))\n spacePositions.push({ x: x + w, y })\n }\n const isHidden = fontSize === 0 || (i.width === 0 && i.str.trim().length > 0)\n\n // letterSpacing이 적용된 숫자/기호 문자열 정규화\n // \"45 0 -7 3 40 )\" → \"450-7340)\" (전화번호, 금액 등). 쉼표·마침표로 시작해 띄운 조각(앞 낱말 뒤 \", 5.\")은 낱말 사이 공백이다 (ODL 192).\n // 소수 덩어리가 둘 이상(\"1.0 2.0\")이면 따로 선 수들이다 — 붙이면 한 조각에 든 표 한 행 값이 \"21001.02.03.0…\" 이 됐다(평가 기준 별표2)\n // 강희 부수(U+2F00~2FD5)로 매긴 한자 글리프는 통합 한자로 — \"자성(⾃性)\" → \"자성(自性)\" (NFKC, 부수 블록만)\n let text = i.str.trim().replace(/[\\u2F00-\\u2FD5]/g, c => c.normalize(\"NFKC\"))\n if (/^[\\d\\s\\-().·,☎]+$/.test(text) && /\\d/.test(text) && / /.test(text) && !/^[,.] /.test(text) &&\n text.split(/\\s+/).filter(t => /^\\d+\\.\\d+$/.test(t)).length < 2) {\n text = text.replace(/ /g, \"\")\n }\n // 글자마다 띄운 영문 대문자 표시 글(큰 제목 \"H O W\") — 한 아이템이 한 낱말이다\n if (fontSize >= 14 && /^[A-Z0-9?!&'’](?: [A-Z0-9?!&'’]){2,}$/.test(text)) text = text.replace(/ /g, \"\")\n\n // 균등배분 TextItem 분해: \"홍 보 지 원 반\" → 개별 글자 아이템으로. 세로로 돌린 글은 가로 축으로 쪼개면 글자 자리가 틀어진다\n // (돌려 찍은 표의 사업명 \"복선 전 철\" 조각이 칸 밖으로 샜다)\n const turned = Math.abs(i.transform[1]) > Math.abs(i.transform[0]) * 4\n const split = turned ? null : splitEvenSpacedItem(text, x, w, fontSize)\n const spaceAfter = (i as { spaceAfter?: boolean }).spaceAfter\n if (split) {\n split.forEach((s, k) => {\n items.push({ text: s.text, x: s.x, y, w: s.w, h, fontSize, fontName: i.fontName || \"\", isHidden, seq: seq + k / 1000,\n ...(spaceAfter !== undefined && k === split.length - 1 ? { spaceAfter } : {}) })\n })\n } else {\n const rotated = Math.abs(i.transform[1]) > Math.abs(i.transform[0]) * 4\n // 세로 글의 가로 폭은 글자 높이다 — 진행 길이(width)를 가로 폭으로 두면 나란한 세로 라벨이 서로 겹쳐 붙는다(\"01/201903/2019\")\n const rw = rotated ? Math.max(1, fontSize) : w\n const rx = rotated && i.transform[1] > 0 ? x - rw : x\n items.push({ text, x: rx, y, w: rw, h, fontSize, fontName: i.fontName || \"\", isHidden, seq, ...(spaceAfter !== undefined ? { spaceAfter } : {}),\n ...(rotated ? { rotated: Math.max(1, w), ...(i.transform[1] < 0 ? { rotatedDown: true as const } : {}) } : {}) })\n }\n }\n\n const sorted = items.sort((a, b) => b.y - a.y || a.x - b.x)\n\n // 1. 가짜 볼드 중복 제거: 같은 텍스트가 거의 동일한 좌표(±3px)에 2~3회 겹쳐진 경우\n // PDF에서 볼드 효과를 위해 텍스트를 여러 번 렌더링하는 기법\n const deduped: NormItem[] = []\n for (let i = 0; i < sorted.length; i++) {\n let isDup = false\n // Y 정렬(desc)이므로 역순 스캔 — Y 차이가 tolerance를 넘으면 중단\n for (let j = deduped.length - 1; j >= 0; j--) {\n const prev = deduped[j]\n if (prev.y - sorted[i].y > 3) break // 이전 아이템이 너무 높음 → 중단\n // x 허용치는 글리프 폭 비례 — 고정 ±3px는 좁은 글리프(괄호 w≈3)에서 나란히\n // 놓인 진짜 두 글자(\"경북))\")까지 삼킨다. 가짜 볼드 오프셋은 폭의 절반 미만.\n const xTol = Math.min(3, Math.max(0.5, sorted[i].w * 0.5))\n if (Math.abs(prev.y - sorted[i].y) <= 3 &&\n prev.text === sorted[i].text && Math.abs(prev.x - sorted[i].x) <= xTol) {\n isDup = true\n break\n }\n }\n if (!isDup) deduped.push(sorted[i])\n }\n\n // 1.5. 겹친 런 분해 — 빈칸 위에 다른 글꼴 글자를 얹은 런을 빈칸에서 가른다 (splitOverlaidRuns, 제자리)\n splitOverlaidRuns(deduped)\n\n // 2. 공백 아이템 위치를 NormItem.hasSpaceBefore로 전파\n // 같은 Y라인(±3px)에서 공백 바로 오른쪽의 \"가장 가까운\" 아이템에만 표시.\n // (기존: 20px 윈도 내 모든 아이템 마킹 → \"기관 [공백] 내부에서\"의 '부'까지\n // 오마킹되어 \"내 부에서\" 과다 공백 발생 — 인접 아이템 1개로 제한)\n if (spacePositions.length > 0) {\n for (const sp of spacePositions) {\n let nearest: NormItem | null = null\n for (const item of deduped) {\n if (Math.abs(sp.y - item.y) > 3) continue\n const dist = item.x - sp.x\n if (dist >= -1 && dist <= 20 && (!nearest || item.x < nearest.x)) {\n nearest = item\n }\n }\n if (nearest) {\n // 진짜 공백이 하나라도 닿으면 진짜 경계\n nearest.syntheticSpace = (nearest.hasSpaceBefore ? nearest.syntheticSpace === true : true) && sp.synthetic === true\n nearest.hasSpaceBefore = true\n }\n }\n }\n\n return deduped\n}\n\n// ─── 겹친 런 분해 ──────────────────────────────────────\n/** 낱말 사이 공백 추정 폭 (글자 크기 대비) — MS Print To PDF 보도자료 실측 0.29em */\nconst WORD_SPACE_EM = 0.3\n/** 얹힌 글이 런 속 같은 글의 추정 위치에서 이만큼(글자 크기 대비) 안이면 중복 그림(입체·그림자 제목) */\nconst OVERLAY_DUP_EM = 0.5\n/** 분해 뒤 추정 끝과 실제 런 끝의 허용 차 (글자 크기 대비) — 넘으면 글자 폭 모델이 안 맞는 런이라 가르지 않는다 */\nconst OVERLAY_FIT_EM = 0.6\n\n/** 한글·한자·전각·도형 기호는 1em, 나머지(라틴·숫자·반각 구두점)는 0.5em 으로 보는 폭 단위 */\nfunction glyphUnits(s: string): number {\n let u = 0\n for (const ch of s) u += (ch.codePointAt(0) ?? 0) >= 0x2190 ? 1 : 0.5\n return u\n}\n\n/**\n * 겹친 런 분해 — 한 글꼴의 글자를 먼저 긋고 다른 글꼴 글자(숫자·괄호·가운뎃점)를 그 빈칸 위에 나중에 얹는\n * 제작기(MS Print To PDF, rhwp cairo 렌더, ezPDF 일부)는 pdfjs 가 앞 런을 빈칸까지 공백으로 품은 한 아이템\n * (\"기구인 중장기전략위원회 제 차 전체회의를 개최하였다\" x 56.6~394.3)으로 합치고 얹힌 \"4\"(x 228.1)는 따로 준다.\n * 줄을 x 로만 세우면 \"4\" 가 런 뒤로 밀려 \"…개최하였다4.\" 가 된다(중장기위원회 보도자료, 개인정보 규제영향분석서\n * \"제31조제 항1\"). 같은 기준선에서 런의 x 범위 안에 통째로 든 아이템을 찾아, 얹힌 글이 들어갈 빈칸을 추정\n * 위치로 골라 그 빈칸에서 런을 가른다. 글자 폭은 한글 1em·반각 0.5em 비례로 잡고 빈칸마다 얹힌 글 끝으로 다시\n * 맞춰(anchor) 오차가 쌓이지 않게 한다. 같은 글을 조금씩 밀어 여러 번 그린 입체 제목(한컴 여수 계획서 26겹)은\n * 런 속 같은 글의 추정 위치와 겹쳐 중복으로 걸러진다.\n */\nfunction splitOverlaidRuns(items: NormItem[]): void {\n let replaced: Map<NormItem, NormItem[]> | null = null\n for (let ci = 0; ci < items.length; ci++) {\n const c = items[ci]\n if (c.w <= 0 || !/\\S\\s+\\S/.test(c.text)) continue\n // y 내림차순 정렬이라 같은 기준선(±1) 아이템은 앞뒤로 붙어 있다\n const overlays: NormItem[] = []\n for (let j = ci - 1; j >= 0 && items[j].y - c.y <= 1; j--) if (isInside(items[j], c)) overlays.push(items[j])\n for (let j = ci + 1; j < items.length && c.y - items[j].y <= 1; j++) if (isInside(items[j], c)) overlays.push(items[j])\n if (overlays.length === 0) continue\n const pieces = splitAroundOverlays(c, overlays)\n if (pieces) (replaced ??= new Map()).set(c, pieces)\n }\n if (!replaced) return\n const out: NormItem[] = []\n for (const it of items) { const p = replaced.get(it); if (p) out.push(...p); else out.push(it) }\n items.length = 0\n for (const item of out.sort((a, b) => b.y - a.y || a.x - b.x)) items.push(item)\n}\n\nfunction isInside(o: NormItem, c: NormItem): boolean {\n return o.x >= c.x + 1 && o.x + o.w <= c.x + c.w + 1 && o.w < c.w\n}\n\nfunction splitAroundOverlays(c: NormItem, overlays: NormItem[]): NormItem[] | null {\n const words = c.text.split(/\\s+/)\n const fs = c.fontSize > 0 ? c.fontSize : (c.h > 0 ? c.h : 10)\n const ws = WORD_SPACE_EM * fs\n const totalU = words.reduce((s, w) => s + glyphUnits(w), 0)\n if (totalU <= 0) return null\n // 중복 그림 가드 — 얹힌 글이 런 속 같은 글의 추정 위치에 있으면 빈칸에 든 글이 아니다\n const a0 = (c.w - (words.length - 1) * ws) / totalU\n const ov = overlays.filter(o => {\n for (let at = c.text.indexOf(o.text); at >= 0; at = c.text.indexOf(o.text, at + 1)) {\n const prefix = c.text.slice(0, at)\n const est = c.x + glyphUnits(prefix.replace(/\\s+/g, \"\")) * a0 + (prefix.match(/\\s+/g)?.length ?? 0) * ws\n if (Math.abs(est - o.x) <= OVERLAY_DUP_EM * fs) return false\n }\n return true\n }).sort((p, q) => p.x - q.x)\n if (ov.length === 0) return null\n\n // 글자 폭 단위 a: 런 폭에서 얹힌 글(빈칸 하나씩)과 낱말 공백을 뺀 나머지\n const holes = Math.min(ov.length, words.length - 1)\n let ovW = 0\n for (const o of ov) ovW += o.w\n const a = (c.w - ovW - (words.length - 1 - holes) * ws) / totalU\n if (!(a > 0)) return null\n\n // 낱말마다 추정 시작·끝을 걸어가며 재고, 얹힌 글이 드는 빈칸(hole) 뒤에서만 새 조각을 연다\n const starts: number[] = [], ends: number[] = [], holeAfter: boolean[] = []\n let p = c.x\n let k = 0\n let assigned = 0\n for (let i = 0; i < words.length; i++) {\n starts.push(p)\n p += glyphUnits(words[i]) * a\n ends.push(p)\n if (i === words.length - 1) break\n // 다음 낱말 가운데보다 앞에서 시작하는 얹힌 글은 이 빈칸 몫. 낱말 속(빈칸 아님)에서 시작하는 것은 건드리지 않는다\n while (k < ov.length && ov[k].x < p - a * 0.6) k++\n const half = glyphUnits(words[i + 1]) * a / 2\n let hole = false\n while (k < ov.length && ov[k].x < p + half) {\n hole = true\n p = Math.max(p, ov[k].x + ov[k].w)\n k++\n }\n holeAfter.push(hole)\n if (hole) assigned++; else p += ws\n }\n if (assigned === 0 || Math.abs(p - (c.x + c.w)) > Math.max(3, OVERLAY_FIT_EM * fs)) return null\n // 조각은 빈칸에서만 가른다 — 낱말마다 가르면 아이템 수가 불어 문서 글꼴 크기 중앙값(아이템 수 기준 헤딩 판정)이\n // 흔들린다(성과평가 보고서 MS Print: 13pt 낱말 조각 +535개로 중앙값 14→13, 16pt 본문 2,180줄이 ### 헤딩으로)\n const pieces: NormItem[] = []\n for (let s = 0; s < words.length;) {\n let e = s\n while (e < words.length - 1 && !holeAfter[e]) e++\n pieces.push({\n ...c, text: words.slice(s, e + 1).join(\" \"), x: Math.round(starts[s]), w: Math.max(1, Math.round(ends[e] - starts[s])),\n hasSpaceBefore: s === 0 ? c.hasSpaceBefore : false, seq: c.seq === undefined ? undefined : c.seq + pieces.length / 1e4,\n })\n s = e + 1\n }\n return pieces\n}\n\n/**\n * 균등배분 TextItem 감지 및 분해.\n * \"홍 보 지 원 반\" (1자+공백 패턴) → [{text:\"홍\",x,w}, {text:\"보\",x,w}, ...]\n * 분해하면 이후 detectEvenSpacedItems가 좌표 기반으로 정확히 감지할 수 있음.\n */\nfunction splitEvenSpacedItem(\n text: string, itemX: number, itemW: number, fontSize: number,\n): { text: string; x: number; w: number }[] | null {\n // 한글/숫자 1자 + 공백이 3회+ 반복되는 패턴\n // \"홍 보 지 원 반\", \"세 무 1 과\", \"주 요 내 용\"\n if (!/^[가-힣\\d](?: [가-힣\\d]){2,}$/.test(text)) return null\n\n const chars = text.split(\" \")\n if (chars.length < 3) return null\n\n // 글자당 폭 계산 — 전체 width를 글자 수로 나눔\n const charW = itemW / chars.length\n // 글자 폭이 너무 크면 균등배분이 아님 (한 글자가 fontSize의 2배 넘으면 이상)\n if (charW > fontSize * 2) return null\n\n return chars.map((ch, idx) => ({\n text: ch,\n x: Math.round(itemX + idx * charW),\n w: Math.round(charW * 0.8), // 실제 글자 폭은 간격보다 좁음\n }))\n}\n\nexport function groupByY(items: NormItem[]): NormItem[][] {\n if (items.length === 0) return []\n const lines: NormItem[][] = []\n let curY = items[0].y\n let curLine: NormItem[] = [items[0]]\n\n for (let i = 1; i < items.length; i++) {\n // Y좌표 허용 오차 3px — PDF 렌더링 미세 오차 보정, 별표 행 경계 감지에 최적화된 값\n if (Math.abs(items[i].y - curY) > 3) {\n lines.push(curLine)\n curLine = []\n curY = items[i].y\n }\n curLine.push(items[i])\n }\n if (curLine.length > 0) lines.push(curLine)\n return lines\n}\n\n/**\n * 첨자 줄 병합 — 본문 줄보다 살짝 위에 뜬 작은 글자 조각(각주 마커 `*`, 원문자 ①,\n * 덧말)이 groupByY에서 별도 줄로 분리된 것을 본문 줄에 흡수한다.\n * 조각 줄(아이템 ≤3개·각 ≤8자·글자 박스가 인접 줄보다 확실히 작음)이 인접 줄과\n * 수직으로 겹치면 같은 시각적 줄이다. mergeLineSimple이 x순 정렬하므로\n * 병합 후 원래 인라인 위치(\"①근로자...\")가 복원된다.\n */\n/** 글자 앞에 붙는 첨자 꼴 — 라틴 글자·수식 기호가 없다 (\"(’10)\", \"행안부\", \"①\"; 적분 위끝 \"3a+x\" 는 아님) */\nexport const PREFIX_SCRIPT = /^[^A-Za-z+=×÷<>≤≥∫∑∏√∞^_{}\\\\/]+$/\n/** 첨자 조각 줄 — 짧은 기호 조각 여럿(각 3자 이하 — 저자 줄 소속 표시 ∗·†·a·1)은 합이 10자를 넘어도, 조각마다 옆 줄 글자 오른끝에\n * 붙어(0.35em 안) 있으면 조각이다. 글자 위에 얹힌 수식 조각(∑ 위아래 극한)은 붙어 있지 않다.\n * 라틴 글자·수식 기호 없는 8자 이하 첨자는 글자 앞(빈칸 하나 0.6em 안)에 붙어도 같다 — 연도 위첨자 \"(’10)10.2만건 → (’25)17.1만건\"·\n * 부처 첨자 \"행안부급경사지, 국토부도로비탈면\"·원문자 \"①디스플레이, ②항공\" 이 한 줄에 여럿. 적분 위끝(\"3a+x\")은 다음 글자 앞에 붙어 있어 뺀다.\n * 글자 상자가 겹치는 폭은 0.25em 까지 — 좌표를 정수로 반올림하면(normalizeItems) 0.8pt 겹친 첨자가 2pt 로 늘어 한 줄 열둘 가운데 하나가\n * 0.1em 허용을 넘었고 첨자 줄이 통째로 따로 남았다(특별교통대책 보도자료 \"부산(‘22년10:00\"). 본문 줄(mergeSuperscriptLines)과 칸 글(cellTextToString)이 같이 쓴다 */\nexport function isAttachedScripts(line: ReadonlyArray<{ text: string; x: number; w: number }>, host: ReadonlyArray<{ x: number; w: number; fontSize: number }>): boolean {\n return line.length > 1 && line.length <= 16 && line.every(i => {\n const t = i.text.trim(), n = t.length\n if (n > 8 || !n) return false\n return host.some(h => {\n const after = i.x - (h.x + h.w), before = h.x - (i.x + i.w)\n return (n <= 3 && after <= h.fontSize * 0.35 && after >= -h.fontSize * 0.25) ||\n (PREFIX_SCRIPT.test(t) && before <= h.fontSize * 0.6 && before >= -h.fontSize * 0.25)\n })\n })\n}\nexport function mergeSuperscriptLines(lines: NormItem[][]): NormItem[][] {\n if (lines.length <= 1) return lines\n const band = (line: NormItem[]) => {\n let bottom = Infinity, top = -Infinity\n for (const i of line) {\n const h = i.h > 0 ? i.h : i.fontSize\n if (i.y < bottom) bottom = i.y\n if (i.y + h > top) top = i.y + h\n }\n return { bottom, top, height: top - bottom }\n }\n // 조각 판정 — 글자 단위로 흩어진 소형 라벨(\"과기정통부\" 1자×5)도 흡수하도록\n // 아이템 수 대신 총 글자수로 제한 (높이비·수직겹침 가드가 과병합을 막는다)\n const isFrag = (line: NormItem[]) => {\n if (line.length > 8) return false\n let total = 0\n for (const i of line) total += i.text.trim().length\n return total > 0 && total <= 10\n }\n const isMarkers = isAttachedScripts\n\n const result: NormItem[][] = [lines[0]]\n for (let i = 1; i < lines.length; i++) {\n const prev = result[result.length - 1]\n const curr = lines[i]\n const a = band(prev)\n const b = band(curr)\n const overlap = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom)\n const prevIsFrag = (isFrag(prev) || isMarkers(prev, curr)) && a.height <= b.height * 0.8 && overlap >= a.height * 0.5\n const currIsFrag = (isFrag(curr) || isMarkers(curr, prev)) && b.height <= a.height * 0.8 && overlap >= b.height * 0.5\n if (prevIsFrag || currIsFrag) {\n result[result.length - 1] = [...prev, ...curr]\n } else {\n result.push(curr)\n }\n }\n return result\n}\n\nexport function mergeLineSimple(items: TextItem[]): string {\n if (items.length <= 1) return items[0]?.text || \"\"\n const sorted = sortLineByX([...items])\n\n // 좌표 기반 균등배분 감지 (ODL TextLineProcessor 방식)\n const isEvenSpaced = detectEvenSpacedItems(sorted)\n\n let result = sorted[0].text\n for (let i = 1; i < sorted.length; i++) {\n const gap = sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w)\n const avgFs = (sorted[i].fontSize + sorted[i - 1].fontSize) / 2\n\n // 탭 갭은 항상 탭으로 — 균등배분보다 우선\n // 기준: fontSize의 2배 이상 또는 30px+ (균등배분 간격은 보통 fontSize*1.5 이하)\n const tabThreshold = Math.max(avgFs * 2, 30)\n if (gap > tabThreshold) {\n result += \"\\t\"\n result += sorted[i].text\n continue\n }\n\n // 균등배분 구간이면 공백 없이 합침\n if (isEvenSpaced[i]) {\n result += sorted[i].text\n continue\n }\n\n // 한자·가나와 라틴 글자·숫자 사이의 좁은 틈(글자 크기 0.3배 미만)은 조판기의 아시아-라틴 자동 간격이다 — 원문에 공백이 없다.\n // pdfjs 는 이 틈에도 공백 아이템을 만들어 넣으니 아래 공백 힌트보다 먼저 본다\n // (\"第1条\"·\"1番地\" 가 \"第 1 条\"·\"1 番地\" 로, LibreOffice·Word 일본어·중국어 문서). 한글은 \"1 번지\" 처럼 실제로 띄어 써 제외\n if (isCjkLatinAutospace(sorted[i - 1].text, sorted[i].text, gap, avgFs)) {\n result += sorted[i].text\n continue\n }\n // pdfjs 공백 아이템이 있었으면 단어 경계 — 갭 크기 무관하게 공백 삽입\n if (sorted[i].hasSpaceBefore && gap >= avgFs * 0.05) {\n result += \" \"\n result += sorted[i].text\n continue\n }\n // 마커(□○▶ 등) 뒤에 한글이 오면 항상 공백 보장 — \"□장소\" → \"□ 장소\"\n if (/[□■○●▶◆◇ㅇ]$/.test(sorted[i - 1].text) && /^[가-힣]/.test(sorted[i].text) && gap > 1) {\n result += \" \"\n result += sorted[i].text\n continue\n }\n // 폰트 크기 비례 공백 임계값 — 고정 px 기준은 Type3/대형 폰트에서 공백 소실·과다 유발\n if (gap > spaceGapThreshold(avgFs)) result += \" \"\n result += sorted[i].text\n }\n return result\n}\n","/**\n * 괘선 없는 표 후보의 역할 판정 — 목차와 산문은 열이 맞아 보여도 데이터 표가 아니다.\n *\n * 클러스터 감지는 글 조각의 x 정렬만으로 열을 세우므로 목차(항목 + 쪽번호)와 단 사이·수식\n * 옆에 벌어진 본문 줄도 표로 묶는다. 여기서는 셀 글의 역할 증거만 본다:\n * - 목차: 마지막 열이 위에서 아래로 증가하는 쪽번호(로마 숫자 머리말 뒤 아라비아 숫자 재시작 허용)\n * - 산문: 한 칸에 문장 여러 줄이 뭉친 긴 칸이 표 글의 과반이고 짧은 라벨 열이 없음\n */\n\nimport type { BoundingBox, IRBlock, IRTable } from \"../types.js\"\nimport type { NormItem } from \"./text-line.js\"\n\n/** Paragraph blocks made from a table of contents — a title between two of them is a TOC entry too. */\nexport const TOC_BLOCKS = new WeakSet<IRBlock>()\n\nconst PAGE_LABEL = /^(?:\\d{1,4}|[ivxlc]{1,7})$/i\n\nfunction romanValue(label: string): number {\n const digits: Record<string, number> = { i: 1, v: 5, x: 10, l: 50, c: 100 }\n let total = 0\n const chars = label.toLowerCase()\n for (let i = 0; i < chars.length; i++) {\n const value = digits[chars[i]], next = digits[chars[i + 1]] ?? 0\n total += value < next ? -value : value\n }\n return total\n}\n\n/** Entries followed by page labels that only grow down the column. A row may carry two entries' labels when the\n * candidate merged two TOC lines into one row (\"51 65\" — 교육청 학교 매뉴얼 목차) */\nexport function isTableOfContents(table: IRTable): boolean {\n if (table.rows < 3 || table.cols < 2) return false\n const labels: string[] = []\n let entries = 0\n for (const row of table.cells) {\n const texts = row.map(cell => cell.text.trim())\n const last = texts[texts.length - 1]\n if (!last) continue\n const parts = last.split(/\\s+/)\n if (!parts.every(p => PAGE_LABEL.test(p))) return false\n if (/\\p{L}/u.test(texts.slice(0, -1).join(\"\"))) entries += parts.length\n labels.push(...parts)\n }\n if (labels.length < 3 || entries < labels.length * 0.8) return false\n let previous: { roman: boolean; value: number } | undefined\n for (const label of labels) {\n const roman = !/^\\d/.test(label)\n const value = roman ? romanValue(label) : Number(label)\n // Front matter numbered in roman figures restarts at arabic 1.\n if (previous && previous.roman === roman && value < previous.value) return false\n if (previous && previous.roman && !roman) previous = undefined\n previous = { roman, value }\n }\n return true\n}\n\n/** Several wrapped sentences collapsed into single cells carry most of the text,\n * spread over many rows (a real table may only absorb one trailing paragraph). */\nexport function isProseTable(table: IRTable): boolean {\n let total = 0, long = 0, rows = 0, longRows = 0\n for (const row of table.cells) {\n const lengths = row.map(cell => cell.text.replace(/\\s+/g, \" \").trim().length)\n if (lengths.some(Boolean)) rows++\n if (lengths.some(length => length >= 80)) longRows++\n for (const length of lengths) {\n total += length\n if (length >= 80) long += length\n }\n }\n if (!(long > 0 && long >= total * 0.5 && longRows >= rows * 0.4)) return false\n // A real table keeps a short label column beside its long descriptions (필드 | 항목 | 의미).\n for (let c = 0; c < table.cols; c++) {\n const filled = table.cells.map(row => row[c]).filter(cell => cell && cell.colSpan < table.cols && cell.text.trim())\n .map(cell => cell!.text.trim())\n if (filled.length >= 3 && filled.length >= rows * 0.6 &&\n filled.every(text => text.length <= 30 && (text.match(/\\p{L}/gu)?.length ?? 0) >= 2)) return false\n }\n return true\n}\n\n/** A TOC keeps its \"entry page\" lines in reading order as one text block. */\nexport function tocBlock(table: IRTable, pageNum: number, bbox: BoundingBox, style?: IRBlock[\"style\"]): IRBlock {\n const text = table.cells.map(row => row.map(cell => cell.text.replace(/\\s+/g, \" \").trim()).filter(Boolean).join(\" \"))\n .filter(Boolean).join(\"\\n\")\n const block: IRBlock = { type: \"paragraph\", text, pageNumber: pageNum, bbox, ...(style ? { style } : {}) }\n TOC_BLOCKS.add(block)\n return block\n}\n\nconst NUMERIC_CELL = /^[\\s\\d.,%()+\\-–−$€£¥]*\\d[\\s\\d.,%()+\\-–−$€£¥]*$/\n\n/** The longest run of four or more values falling by one constant step, as [start, length, step]. */\nfunction fallingRun(values: number[]): [number, number, number] | null {\n let best: [number, number, number] | null = null\n for (let start = 0; start + 3 < values.length; start++) {\n const step = values[start] - values[start + 1]\n if (step <= 0) continue\n let run = 2\n while (start + run < values.length && Math.abs(values[start + run - 1] - values[start + run] - step) <= step * 1e-6) run++\n if (run >= 4 && (!best || run > best[1])) best = [start, run, step]\n }\n return best\n}\n\n/** A value axis: four or more ticks read top to bottom with one constant step down. */\nfunction hasValueAxis(values: number[]): boolean {\n return fallingRun(values) !== null\n}\n\nconst TICK = /^-?[\\d,]+(?:\\.\\d+)?%?$/\nconst QUANTITY = /^[-−–]?[\\d,]*\\.?\\d+%?$/\nconst quantity = (token: string) => Number(token.replace(/^[−–]/, \"-\").replace(/[,%]/g, \"\"))\n\n/** A value axis inside a chart grid read from the text layer — the column's numbers, or the first number of each of its lines (a\n * tick sharing its line with a bar value, \"80 45\"), fall by one step over at least 80% of them, and the grid's other numbers sit on\n * that scale (a step past either end at most; years are category labels, not plotted values — \"2014 2015 2016\"). A statistics\n * table's data column holds such a run by chance (\"23 21 19 17\" among 46 values, 가계동향조사), and a column of years or scores\n * (\"10.0 9.0 8.0 7.0\") stands beside values far off its scale (예산서 계속비 연도 열, 평가 배점표). */\nfunction isValueAxisColumn(table: IRTable, c: number): boolean {\n const lines = table.cells.flatMap(row => (row[c]?.text ?? \"\").split(\"\\n\").map(line => line.trim().split(/\\s+/)))\n const others = table.cells.flatMap(row => row.filter((_, k) => k !== c).map(cell => cell.text.trim().split(/\\s+/)))\n .filter(tokens => tokens.every(token => QUANTITY.test(token))).flat().map(quantity)\n .filter(v => !(Number.isInteger(v) && v >= 1900 && v <= 2100))\n for (const ticks of [lines.flat().filter(t => TICK.test(t)).map(quantity), lines.map(tokens => tokens[0]).filter(t => TICK.test(t)).map(quantity)]) {\n const run = fallingRun(ticks)\n if (!run || run[1] < ticks.length * 0.8) continue\n const [start, length, step] = run\n const hi = ticks[start] + step, lo = ticks[start + length - 1] - step\n if (others.filter(v => v >= lo && v <= hi).length >= others.length * 0.8) return true\n }\n return false\n}\n\n/** Three rows running each with three or more filled cells — values aligned in rows are a table even in a mostly empty grid\n * (호봉표 whose Korean labels have no text layer). A chart scatters its value labels. */\nfunction hasAlignedRows(table: IRTable): boolean {\n let run = 0\n for (const row of table.cells) {\n run = row.filter(cell => cell.text.trim()).length >= 3 ? run + 1 : 0\n if (run >= 3) return true\n }\n return false\n}\n\n/** Value-axis ticks set just outside a chart grid's left or right edge — four or more numbers falling by one step top to bottom.\n * They are chart text: left in the page flow they formed one unit with the legend and source below it, read before the chart values (ODL 077). */\nexport function valueAxisBeside(free: NormItem[], box: { x1: number; y1: number; x2: number; y2: number }): NormItem[] {\n for (const left of [true, false]) {\n const ticks = free.filter(it => /^-?[\\d,]+(?:\\.\\d+)?%?$/.test(it.text.trim()) && it.y >= box.y1 - it.fontSize && it.y <= box.y2 + it.fontSize &&\n (left ? it.x + it.w <= box.x1 + 3 && it.x + it.w >= box.x1 - it.fontSize * 2 : it.x >= box.x2 - 3 && it.x <= box.x2 + it.fontSize * 2))\n .sort((a, b) => b.y - a.y)\n if (hasValueAxis(ticks.map(it => Number(it.text.trim().replace(/[,%]/g, \"\"))))) return ticks\n }\n return []\n}\n\n/** Bars and gridlines of a chart drawn as vectors look like a sparse numeric grid.\n * Evidence: a value-axis column, or mostly empty cells whose text is mostly numbers scattered rather than aligned in rows.\n * A grid read by OCR from a chart image keeps the looser test — any four numbers falling by one step in a column: OCR puts two ticks\n * on a line (\"100% 90%\"), bar values among the ticks and a second axis beside the first (보도자료 산업활동동향·주택통계 그래프 그림),\n * while every data table taken for a chart was built from a text layer */\nexport function isChartTable(table: IRTable, ocr = false): boolean {\n const texts = table.cells.flat().map(cell => cell.text.trim()).filter(Boolean)\n if (table.rows < 3 || texts.length === 0) return false\n const numeric = texts.filter(text => NUMERIC_CELL.test(text)).length\n if (numeric < texts.length * 0.5) return false\n for (let c = 0; c < table.cols; c++) {\n if (!ocr ? isValueAxisColumn(table, c)\n : hasValueAxis(table.cells.flatMap(row => (row[c]?.text ?? \"\").split(/\\s+/)).filter(t => TICK.test(t)).map(t => Number(t.replace(/[,%]/g, \"\"))))) return true\n }\n // Chart values read as quantities (3,230 · 2.5% · -6.4); hyphenated identifiers such as phone numbers do not.\n const values = texts.filter(text => text.split(/\\s+/).every(token => QUANTITY.test(token))).length\n const cells = table.rows * table.cols\n return cells - texts.length >= cells * 0.55 && values >= texts.length * 0.8 && (ocr || !hasAlignedRows(table))\n}\n\n/** Display math laid out on several baselines (fraction bars, limits, \"d/dx\") looks like a sparse grid of short cells.\n * Evidence: a relation sign in some cell and most filled cells being math tokens — a lone variable or an operator-bearing term. */\nexport function isFormulaTable(table: IRTable): boolean {\n const texts = table.cells.flat().map(cell => cell.text.trim()).filter(Boolean)\n if (texts.length < 3 || table.rows > 8 || !texts.some(text => /=/.test(text))) return false\n const math = texts.filter(text => /^[a-z]{1,2}$/.test(text) || /[=∂∑∫∣Γ∇√∞]/.test(text)).length\n return math >= texts.length * 0.6\n}\n\n/** 시험지 선택지·수식 배치 — 수식 글꼴 글(`$…$`)과 선택지 부호가 줄마다 벌어져 무괘선 표 후보가 된다(수능 모의고사 \"① $1$ ② $2$ ③ $3$\",\n * 해설 \"문4) … ③ | $g(x)=…$\"). HWPX 원본엔 표가 없다. 선택지 부호(①~⑤)나 문항 표시(\"문N)\")가 있고, 채운 칸의 60% 이상이\n * 수식·선택지 부호·기호 조각(\"′\")일 때만 — 선택지 부호 없는 수식 값 표(\"$x$ | $f(x)$\")는 표로 둔다 */\nexport function isExamLayoutTable(table: IRTable): boolean {\n const texts = table.cells.flat().map(cell => cell.text.trim()).filter(Boolean)\n if (texts.length < 3 || !texts.some(text => /[①-⑤]|문\\d+)/.test(text))) return false\n const layout = texts.filter(text => /\\$|[①-⑤]|문\\d+)/.test(text) || !/[\\p{L}\\p{N}]/u.test(text)).length\n return layout >= texts.length * 0.6\n}\n","/**\n * 괘선 없는 표(cluster-detector) 칸 글 — 칸 아이템을 글줄로 묶고(clusterCellLines) 글줄을 잇는다(joinClusterCellLines).\n */\n\nimport { spaceGapThreshold } from \"./cell-text.js\"\nimport { isCjkLatinAutospace } from \"./text-line.js\"\nimport { cellLineWraps, startsNewItem, wrapJoiner, type WrapLexicon } from \"./line-wrap.js\"\nimport type { ClusterItem } from \"./cluster-detector.js\"\n\n/** 칸 글줄 — 잇기 판정 재료(왼끝·오른끝·첫 글자 폭·줄 끝 공백 글리프) */\nexport interface ClusterCellLine { text: string; left: number; right: number; fontSize: number; firstCharW: number; spaceAfter?: boolean }\n\n/**\n * 칸 아이템 → 글줄. 글자를 한 자씩 따로 긋는 제작기(지자체 예산 시스템 — 부천 세출예산사업명세서 굴림체, 글자마다\n * 0~1pt 간격)는 칸 아이템이 글자 하나씩이라 종전처럼 공백으로 이으면 \"2 0 , 7 7 5 , 6 6 1\"·\"활 성 화\" 가 된다.\n * 선 표 칸(cellTextToString)과 같게 간격이 낱말 공백 임계(spaceGapThreshold)를 넘거나 pdfjs 공백 힌트가 있을 때만 띄운다.\n * 한 행에 합쳐진 여러 줄(mergeMultiLineRows)은 세로로 겹치는 아이템끼리 한 줄로 묶어 위→아래로 세운다 — x 로만\n * 세우면 글자 단위 칸에서 두 줄의 글자가 번갈아 섞인다. 첨자(각주 부호·원문자)는 본문 줄과 세로로 겹쳐 같은 줄에 든다\n */\nexport function clusterCellLines(items: ClusterItem[]): ClusterCellLine[] {\n const hOf = (i: ClusterItem) => (i.h > 0 ? i.h : i.fontSize)\n const lines: { bottom: number; top: number; items: ClusterItem[] }[] = []\n for (const it of [...items].sort((a, b) => b.y - a.y || a.x - b.x)) {\n const bottom = it.y, top = it.y + hOf(it)\n const line = lines.find(l => Math.min(l.top, top) - Math.max(l.bottom, bottom) >= Math.min(l.top - l.bottom, top - bottom) * 0.5)\n if (line) { line.items.push(it); line.bottom = Math.min(line.bottom, bottom); line.top = Math.max(line.top, top) }\n else lines.push({ bottom, top, items: [it] })\n }\n return lines.sort((a, b) => b.top - a.top).map(({ items: line }) => {\n line.sort((a, b) => a.x - b.x)\n let s = line[0].text\n for (let i = 1; i < line.length; i++) {\n const gap = line[i].x - (line[i - 1].x + line[i - 1].w)\n const fs = (line[i].fontSize + line[i - 1].fontSize) / 2\n s += (!isCjkLatinAutospace(line[i - 1].text, line[i].text, gap, fs) && ((line[i].hasSpaceBefore && gap >= fs * 0.05) || gap > spaceGapThreshold(fs)) ? \" \" : \"\") + line[i].text\n }\n const last = line.reduce((a, b) => (b.x + b.w > a.x + a.w ? b : a))\n return { text: s, left: line[0].x, right: last.x + last.w, fontSize: line[0].fontSize, spaceAfter: last.spaceAfter,\n firstCharW: line[0].w / Math.max(1, [...line[0].text.replace(/<\\/?u>|~~/g, \"\")].length) }\n })\n}\n\n/**\n * 칸 글줄 잇기 — 종전엔 늘 공백으로 이어 어절 가운데서 꺾인 줄(\"남⏎편이\"·\"보았더⏎니\")이 띄워졌다(SO-SUEOP 쪽마다 둘러친 틀 표 —\n * 괘선 없는 표는 PDF 줄 하나가 행 하나라 이어지는 줄을 앞 행 칸에 붙인다). 칸 상자를 모르니 열 글줄들의 왼끝·오른끝(box)을 칸 안쪽으로\n * 보고, 오른끝까지 차서 꺾인(cellLineWraps) 줄을 선 표 칸(cellTextToString)·본문과 같은 어절 판정(wrapJoiner)으로 붙인다.\n * 줄 끝에 공백 글리프가 찍힌(spaceAfter) 꺾임은 어절 경계라 띄운다. 두 줄 다 열 왼끝 가까이(WRAP_INDENT_MAX 글자 안 — 들여쓰기·내어쓰기)에서\n * 시작해야 한 문단의 꺾임이다 — 가운데 맞춘 \"부 칙\" 의 \"칙\" 도 열 오른끝에 닿아 아래 \"이 규칙은\" 과 붙었다(제약산업 시행규칙 별첨).\n * 꺾인 낱말은 밑줄도 이어진다 — 밑줄 친 개정 글 끝 \"…계약의 체\" 와 밑줄 없는 다음 줄 \"결\" 은 원문에서도 다른 글상자다(신구조문 대비표)\n */\nconst WRAP_INDENT_MAX = 3\n\nexport function joinClusterCellLines(lines: ClusterCellLine[], box: { x1: number; x2: number }, lex?: WrapLexicon): string {\n let out = lines[0]?.text ?? \"\"\n for (let i = 1; i < lines.length; i++) {\n const a = lines[i - 1], b = lines[i]\n const join = !a.spaceAfter && cellLineWraps(box, box.x1, a.right, a.fontSize, b.firstCharW) &&\n a.left - box.x1 <= WRAP_INDENT_MAX * a.fontSize && b.left - box.x1 <= WRAP_INDENT_MAX * b.fontSize &&\n /<\\/u>$/.test(a.text) === /^<u>/.test(b.text) &&\n !startsNewItem(out, b.text) && wrapJoiner(out, b.text, lex, false) === \"\"\n out += (join ? \"\" : \" \") + b.text\n }\n return out\n}\n","/**\n * 클러스터 기반 테이블 감지 — 선이 없는 PDF에서 텍스트 정렬 패턴으로 테이블 구조 추론.\n *\n * Original work: Copyright 2025-2026 Hancom, Inc.\n * Licensed under the Apache License, Version 2.0\n * https://github.com/opendataloader-project/opendataloader-pdf\n *\n * ODL의 ClusterTableConsumer를 kordoc 컨텍스트에 맞게 단순화한 구현.\n * Modifications: TypeScript 재구현, 최소 2열 감지, 한국어 PDF 특화 최적화.\n *\n * 핵심 아이디어:\n * 1. 균등배분(개별 글자) 아이템을 사전 병합하여 노이즈 제거\n * 2. 헤더 행(짧은 라벨 + 넓은 갭) 감지 → 열 앵커로 사용\n * 3. 헤더 없으면 기존 X좌표 클러스터링으로 열 감지\n * 4. 다중행 셀(같은 논리 행이 여러 Y에 걸침) 병합\n */\n\nimport type { IRTable, IRCell, BoundingBox } from \"../types.js\"\nimport { PREFIX_SCRIPT } from \"./text-line.js\"\nimport { isProseTable } from \"./table-roles.js\"\nimport { clusterCellLines, joinClusterCellLines, type ClusterCellLine } from \"./cluster-cell-text.js\"\nimport type { WrapLexicon } from \"./line-wrap.js\"\n\n/** parser.ts의 NormItem과 동일한 인터페이스 */\nexport interface ClusterItem {\n text: string\n x: number\n y: number\n w: number\n h: number\n fontSize: number\n fontName: string\n /** pdfjs 공백 아이템이 직전에 있었음 — 단어 경계 힌트 */\n hasSpaceBefore?: boolean\n /** 직전 공백이 pdfjs 가 글자 틈으로 만든 것뿐(글리프 흐름에 공백 글리프 없음, tracked-text markSyntheticSpaces) — 균등배분 run 을 끊지 않는다 */\n syntheticSpace?: boolean\n /** 줄 끝 공백 글리프 (tracked-text markTrailingSpaceGlyphs) — 칸 줄 꺾임이 어절 경계 */\n spaceAfter?: boolean\n}\n\n// ─── 상수 ──────────────────────────────────────────────\n/** baseline 그룹핑 허용 오차 (pt) */\nconst Y_TOL = 3\n/** 열 클러스터링 허용 오차 (pt) */\nconst COL_CLUSTER_TOL = 15\n/** 테이블로 인정하기 위한 최소 행 수 */\nconst MIN_ROWS = 3\n/** 테이블로 인정하기 위한 최소 열 수 */\nconst MIN_COLS = 2\n/** 같은 행 내 아이템 간 최소 갭 (테이블 컬럼 구분) — fontSize 배수 */\nconst MIN_GAP_FACTOR = 2.0\n/** 같은 행 내 아이템 간 최소 갭 절대값 (pt) */\nconst MIN_GAP_ABSOLUTE = 20\n/** 열에 값이 있는 행의 비율 최소 기준 */\nconst MIN_COL_FILL_RATIO = 0.4\n\ninterface RowGroup {\n y: number // 대표 Y좌표 (평균 baseline)\n items: ClusterItem[]\n}\n\ninterface ColCluster {\n x: number // 열 X좌표 (왼쪽 경계)\n count: number // 이 열에 속한 아이템 수\n}\n\n/** 헤더 감지 결과: 열 앵커 + 헤더 행 인덱스 */\ninterface HeaderResult {\n columns: ColCluster[]\n headerIdx: number\n}\n\nexport interface ClusterTableResult {\n table: IRTable\n bbox: BoundingBox\n usedItems: Set<ClusterItem>\n}\n\n/**\n * 클러스터 기반 테이블 감지. 선이 없는 PDF의 fallback 경로에서 호출.\n */\nexport function detectClusterTables(items: ClusterItem[], pageNum: number, rejected?: { prose: number }, lex?: WrapLexicon): ClusterTableResult[] {\n // 쪽 옆 세로 색인 탭 글자는 표 열 후보가 아니다 — 빠진 글자는 usedItems 밖이라 호출 측이 본문 글로 낸다\n const tab = sideTabGlyphs(items)\n if (tab.size) items = items.filter(i => !tab.has(i))\n if (items.length < MIN_ROWS * MIN_COLS) return []\n\n // 0. 균등배분 아이템 사전 병합 (개별 글자 → 단어)\n const { merged, originMap } = mergeEvenSpacedClusters(items)\n\n // 1. Y좌표로 행 그룹핑 + 첨자 행 복원\n // 본문 줄에서 살짝 올라간 각주 마커(*)·덧말은 baseline 그룹핑(Y_TOL)에서 별도 행으로\n // 떨어진다. 이런 조각 행이 표 헤더/열 앵커로 오인되면 본문 문단이 통째로 표에 흡수되므로\n // 수직으로 겹치는 행은 같은 시각적 줄로 병합한다.\n const rows = mergeOverlappingRows(groupByBaseline(merged))\n if (rows.length < MIN_ROWS) {\n const compact = detectCompactStatisticalTables(rows, pageNum)\n for (const table of compact) expandUsedItems(table.usedItems, originMap)\n return compact\n }\n\n const results: ClusterTableResult[] = []\n\n // 2. 헤더 행 기반 열 감지 시도\n const headerResult = detectHeaderRow(rows)\n\n if (headerResult) {\n // 헤더 기반: 헤더 이후 행에서 boundary 기반 열 구조 매칭\n const { columns, headerIdx } = headerResult\n const headerRow = rows[headerIdx]\n const headerItems = [...headerRow.items].sort((a, b) => a.x - b.x)\n const headerAndBelow = rows.slice(headerIdx)\n // 다중행 셀 병합\n const mergedRows = mergeMultiLineRows(headerAndBelow, columns)\n // boundary 기반 region 탐색 (proximity 대신 headerItems 범위 사용)\n const tableRegions = findTableRegionsByHeader(mergedRows, columns, headerItems)\n for (const region of tableRegions) {\n const table = buildClusterTable(region.rows, columns, pageNum, lex)\n if (table) {\n expandUsedItems(table.usedItems, originMap)\n results.push(table)\n }\n }\n }\n\n if (results.length === 0) {\n // 3. 기존 방식: suspicious gaps + column clustering\n const suspiciousRows = rows.filter(row => hasSuspiciousGaps(row))\n if (suspiciousRows.length >= MIN_ROWS) {\n const columns = extractColumnClusters(suspiciousRows)\n if (columns.length >= MIN_COLS) {\n const tableRegions = findTableRegions(rows, columns)\n for (const region of tableRegions) {\n // 열은 영역 안 행으로 다시 세운다 — 같은 쪽 다른 표의 열 x 가 섞이면 가짜 사이 열이 생긴다.\n // 영역 열이 더 적을 때만(섞인 열을 걷어낼 때만), 그 표의 몸통 칸 글이 모두 짧고 글꼴 사용자 영역(수식 기호) 문자가 없을 때만 쓴다 — 행이 적은 영역은 기준 수가\n // 낮아 기호 줄·문장 줄에서 가짜 표가 살아난다(수학 시험지·보도자료 본문)\n const own = extractColumnClusters(region.rows.filter(row => hasSuspiciousGaps(row)))\n const ownTable = own.length >= MIN_COLS && own.length < columns.length\n ? buildClusterTable(mergeMultiLineRows(region.rows, own), own, pageNum, lex) : null\n const table = ownTable && ownTable.table.cells.every((row, r) => row.every(cell => (r === 0 || cell.colSpan >= ownTable.table.cols || cell.text.trim().length <= 30) &&\n !/\\p{Co}/u.test(cell.text)))\n ? ownTable : buildClusterTable(mergeMultiLineRows(region.rows, columns), columns, pageNum, lex)\n if (table) {\n expandUsedItems(table.usedItems, originMap)\n results.push(table)\n }\n }\n }\n }\n }\n\n if (results.length === 0) results.push(...detectAlignedTwoColumnTables(rows, pageNum))\n if (results.length === 0) results.push(...detectSparseTwoColumnTables(rows, pageNum))\n {\n // 다른 표가 찾아진 쪽도 남은 행에서 2행 표를 찾는다 (표 사이 문장 뒤 작은 표)\n const taken = new Set(results.flatMap(r => [...r.usedItems]))\n const rest = results.length === 0 ? rows : rows.filter(row => !row.items.some(i => taken.has(i)))\n for (const table of detectCompactStatisticalTables(rest, pageNum)) {\n expandUsedItems(table.usedItems, originMap)\n results.push(table)\n }\n }\n\n // 4. 2단 조판 본문 오인 강등 — 걸러진 아이템은 usedItems에서 빠져\n // XY-Cut(단 분리) 경로로 흘러가 올바른 읽기 순서로 복원된다.\n // 산문 역할의 후보도 표가 아니다 (table-roles.ts). 목차·차트는 호출 측이 그 영역의 글로 바꾼다\n return results.filter(r => {\n const prose = isTwoColumnProse(r) || isProseTable(r.table)\n if (prose && rejected) rejected.prose++\n return !prose\n })\n}\n\n/** 세로 색인 탭 기둥으로 볼 최소 글자 수 */\nconst SIDE_TAB_MIN = 6\n\n/** 머리 표지 때문에만 기존 바깥 판정을 못 받은 탭 — 원본 표지 뒤 읽기 위치를 보존한다. */\nexport const SIDE_TAB_CAP_GROUPS = new WeakMap<object, Map<object, Pick<ClusterItem, \"x\" | \"y\" | \"w\" | \"fontSize\">>>()\n\n/**\n * 쪽 옆 세로 색인 탭 — 책자형 문서는 장·책 이름(\"응⏎급⏎의⏎료⏎기⏎관…\")을 쪽 바깥 좌우 띠에 한 자씩 세로로 찍는다.\n * 여러 쪽 되풀이로 걷는 removeSideTabs(side-tabs.ts)는 표 감지 뒤에 돌고, 한 쪽만 변환(pages)하면 되풀이도 없다. 그 전에\n * 이 글자 기둥이 표의 첫(끝) 열 앵커가 되면 옆 본문 줄(\"○ (평가체계)\"·\"- 1차 년도 : 서면평가\")이 모두 2열 이상 맞는 행이 되어\n * 본문이 표로 묶이고, 탭 글자가 첫 열 칸(\"료 기\"·\"관 평\")으로 들어간다.\n * 기둥: 같은 x 의 한 글자 아이템이 SIDE_TAB_MIN 개 이상, 글자 높이 3배 안 간격으로 쌓였고, 쪽의 다른 글이 모두\n * 한쪽(오른쪽 또는 왼쪽)으로 글자 하나 이상 떨어져 있으며, 기둥 글자의 40% 이상이 제 baseline 줄에 혼자 선다(본문 줄 간격과\n * 탭 글자 간격이 어긋난다). 표의 번호 열(1⏎2⏎3…)은 글자마다 같은 줄에 다른 칸이 있어 걸리지 않는다.\n */\nexport function sideTabGlyphs<T extends Pick<ClusterItem, \"text\" | \"x\" | \"y\" | \"w\" | \"fontSize\">>(items: T[]): Set<T> {\n const found = new Set<T>()\n const singles = items.filter(i => i.fontSize > 0 && [...i.text.trim()].length === 1)\n if (singles.length < SIDE_TAB_MIN) return found\n const cols: T[][] = []\n for (const it of [...singles].sort((a, b) => a.x - b.x)) {\n const c = cols.find(col => Math.abs(col[0].x - it.x) <= Math.max(1.5, it.fontSize * 0.25))\n if (c) c.push(it)\n else cols.push([it])\n }\n // 기둥 후보 — 글자 수·글자/숫자 비율·세로 간격만 본다\n const cands: { col: T[]; fs: number }[] = []\n for (const col of cols) {\n if (col.length < 2) continue\n // 책·장 이름은 글자·숫자다 — 글머리 기호 기둥(▷·◦·□, 쪽 맨 왼쪽 목록)은 탭이 아니다\n if (col.filter(g => /[\\p{L}\\p{N}]/u.test(g.text)).length < col.length * 0.8) continue\n col.sort((a, b) => b.y - a.y)\n const fs = [...col.map(i => i.fontSize)].sort((a, b) => a - b)[col.length >> 1]\n if (col.slice(1).some((it, k) => col[k].y - it.y > fs * 3)) continue\n cands.push({ col, fs })\n }\n // 두 줄로 세운 탭(\"구급차의 의료장비·\" | \"구급의약품 및 통신장비\") — 바로 붙은 기둥 둘 이상이 높이가 겹치면 한 띠로 본다.\n // 따로 보면 서로가 \"쪽의 다른 글\"이 되어 바깥 판정(outside)을 둘 다 놓치고, 탭 글자가 클러스터 표의 끝 열(\"운 태· 행\")이 됐다\n const bands: { col: T[]; fs: number; anchored: boolean }[] = []\n for (const c of cands.sort((a, b) => a.col[0].x - b.col[0].x)) {\n const last = bands[bands.length - 1]\n if (last) {\n const lastRight = Math.max(...last.col.map(i => i.x + i.w))\n const gap = Math.min(...c.col.map(i => i.x)) - lastRight\n const lTop = Math.max(...last.col.map(i => i.y)), lBot = Math.min(...last.col.map(i => i.y))\n const cTop = c.col[0].y, cBot = c.col[c.col.length - 1].y\n if (gap <= Math.max(last.fs, c.fs) * 1.5 && Math.min(lTop, cTop) >= Math.max(lBot, cBot)) {\n last.col.push(...c.col)\n last.fs = Math.max(last.fs, c.fs)\n last.anchored ||= c.col.length >= SIDE_TAB_MIN\n continue\n }\n }\n bands.push({ col: [...c.col], fs: c.fs, anchored: c.col.length >= SIDE_TAB_MIN })\n }\n for (const { col, fs, anchored } of bands) {\n // 짧은 형제 기둥만 합쳐 최소 글자 수를 채우지는 않는다 — 긴 탭 기둥이 하나는 있어야 한다\n if (!anchored) continue\n col.sort((a, b) => b.y - a.y)\n const members = new Set(col)\n const top = col[0].y + fs, bottom = col[col.length - 1].y - fs\n const others = items.filter(i => !members.has(i) && i.text.trim() && i.y <= top && i.y >= bottom)\n // 기둥 밖의 본문 글이 높이 안에 있어야 한다 — 표만 있거나 제목이 위에 있는 쪽은 every/alone 이 자명하게 참이 된다\n if (!others.length) continue\n const left = Math.min(...col.map(i => i.x)), right = Math.max(...col.map(i => i.x + i.w))\n // 쪽의 다른 글이 모두 한쪽 바깥에 있어야 한다(높이가 겹치는 글만이 아니라) — 서식 표의 행 번호 열(1⏎2⏎…⏎9, 빈 행)·세로로 쓴\n // 칸 이름·\"-\" 자리표시 열도 한 글자 기둥이라, 높이 겹침만 보면 표에서 빠져 쪽 앞에 한 글자씩 나왔다(4.18.2 회귀)\n const rest = items.filter(i => !members.has(i) && i.text.trim())\n // 기둥 바로 위 같은 띠의 짧은 머리 표지는 탭의 일부다. 글 흐름에는 그대로 남기되\n // 탭 바깥 본문 판정의 경쟁 열로 세지 않는다 — 큰 제목·멀리 떨어진 라벨은 제외.\n const cap = (i: T) => i.y > top && i.y - top <= fs * 3 && i.w <= fs * 3 &&\n i.fontSize <= fs * 3 && [...i.text.trim()].length <= 3 &&\n Math.abs(i.x + i.w / 2 - (left + right) / 2) <= fs * 0.5\n const bodyRest = rest.filter(i => !cap(i))\n const outside = bodyRest.every(i => i.x >= right + fs) || bodyRest.every(i => i.x + i.w <= left - fs)\n if (!outside) continue\n const alone = col.filter(g => !others.some(i => Math.abs(i.y - g.y) <= Y_TOL)).length\n if (alone < col.length * 0.4) continue\n const legacyOutside = rest.every(i => i.x >= right + fs) || rest.every(i => i.x + i.w <= left - fs)\n const head = !legacyOutside ? rest.find(cap) : undefined\n for (const g of col) {\n found.add(g)\n if (head) {\n const caps = SIDE_TAB_CAP_GROUPS.get(found) ?? new Map()\n caps.set(g, head)\n SIDE_TAB_CAP_GROUPS.set(found, caps)\n }\n }\n }\n return found\n}\n\n/** Two-row numeric tables have too few rows for the generic column clusters.\n * Repeated starts across four or more short cells provide the cell graph. */\nfunction detectCompactStatisticalTables(rows: RowGroup[], pageNum: number): ClusterTableResult[] {\n const found: ClusterTableResult[] = []\n // 붙어 있는 조각(한 낱말이 두 아이템으로 갈린 \"Glucos|e Solution\")은 한 칸 — 원본은 parts 에\n const parts = new Map<ClusterItem, ClusterItem[]>()\n const cellsOf = (row: RowGroup): ClusterItem[] => {\n const out: ClusterItem[] = []\n for (const it of [...row.items].sort((a, b) => a.x - b.x)) {\n const last = out[out.length - 1]\n if (last && it.x - (last.x + last.w) <= Math.max(0.5, it.fontSize * 0.05) && !it.hasSpaceBefore) {\n const joined = { ...last, text: last.text + it.text, w: it.x + it.w - last.x }\n parts.set(joined, [...(parts.get(last) ?? [last]), it])\n out[out.length - 1] = joined\n } else out.push(it)\n }\n return out\n }\n for (let r = 0; r < rows.length - 1; r++) {\n const header = cellsOf(rows[r])\n const values = cellsOf(rows[r + 1])\n if (header.length < 4 || header.length > 12 || values.length !== header.length ||\n (/^\\d+[.)]$/.test(header[0].text.trim()) && /^\\d+[.)]$/.test(values[0].text.trim())) ||\n header.some(c => !c.text.trim() || c.text.length > 32) ||\n values.some(c => !c.text.trim() || c.text.length > 16) ||\n rows[r].y - rows[r + 1].y > Math.max(25, header[0].fontSize * 3)) continue\n // 값 절반 이상이 수치이거나, 머리행이 값과 다른 한 글꼴이고 칸 왼끝이 모두 맞는 표 (머리 굵게 · 값 보통)\n const numeric = values.filter(c => /\\d/.test(c.text) && !/\\p{L}/u.test(c.text)).length >= Math.ceil(values.length / 2)\n const styledHeader = header.every(c => c.fontName === header[0].fontName) &&\n values.every(c => c.fontName === values[0].fontName) && header[0].fontName !== values[0].fontName &&\n values.every((c, i) => Math.abs(c.x - header[i].x) <= 2)\n if (!numeric && !styledHeader) continue\n let aligned = true\n for (let c = 0; c < header.length; c++) {\n const gap = c + 1 < header.length ? header[c + 1].x - header[c].x : Infinity\n if (gap < Math.max(4, header[c].fontSize * 0.4) ||\n Math.abs(values[c].x - header[c].x) > Math.max(14, gap * 0.3)) { aligned = false; break }\n }\n if (!aligned) continue\n const all = [...header, ...values]\n const minX = Math.min(...all.map(c => c.x)), minY = Math.min(...all.map(c => c.y))\n const maxX = Math.max(...all.map(c => c.x + c.w))\n const maxY = Math.max(...all.map(c => c.y + (c.h || c.fontSize)))\n found.push({\n table: { rows: 2, cols: header.length, hasHeader: true, cells: [header, values].map(row =>\n row.map(c => ({ text: c.text, colSpan: 1, rowSpan: 1 }))) },\n bbox: { page: pageNum, x: minX, y: minY, width: maxX - minX, height: maxY - minY },\n usedItems: new Set(all.flatMap(c => parts.get(c) ?? [c])),\n })\n r++\n }\n return found\n}\n\n/** Recover compact, borderless tables from repeated cell starts, even when\n * the inter-column whitespace is narrower than the generic gap threshold. */\nfunction detectAlignedTwoColumnTables(rows: RowGroup[], pageNum: number): ClusterTableResult[] {\n const found: ClusterTableResult[] = []\n let i = 0\n while (i < rows.length - 2) {\n const first = [...rows[i].items].sort((a, b) => a.x - b.x)\n if (first.length !== 2 || first.some(c => !c.text.trim() || c.text.length > 48) ||\n first[1].x - first[0].x < 45 || first[1].x - first[0].x > 260 ||\n /[.!?]$/.test(first[0].text)) { i++; continue }\n const body: RowGroup[] = []\n let j = i\n for (; j < rows.length; j++) {\n const cells = [...rows[j].items].sort((a, b) => a.x - b.x)\n const previous = body.at(-1)\n if (cells.length !== 2 || cells.some(c => !c.text.trim() || c.text.length > 48) ||\n Math.abs(cells[0].x - first[0].x) > 5 || Math.abs(cells[1].x - first[1].x) > 5 ||\n (previous && previous.y - rows[j].y > Math.max(24, first[0].fontSize * 2.8))) break\n body.push(rows[j])\n }\n if (body.length < 3) { i++; continue }\n const top = rows[i - 1]\n const title = top?.items.length === 1 && Math.abs(top.items[0].x - first[0].x) <= 5 &&\n top.items[0].text.length <= 65 && top.y - first[0].y <= Math.max(24, first[0].fontSize * 2.8)\n ? top : undefined\n const tableRows = title ? [title, ...body] : body\n const cells: IRCell[][] = tableRows.map((row, idx) => {\n if (title && idx === 0) return [{ text: row.items[0].text, colSpan: 2, rowSpan: 1 }, { text: \"\", colSpan: 1, rowSpan: 1 }]\n const [left, right] = [...row.items].sort((a, b) => a.x - b.x)\n return [{ text: left.text, colSpan: 1, rowSpan: 1 }, { text: right.text, colSpan: 1, rowSpan: 1 }]\n })\n const usedItems = new Set(tableRows.flatMap(row => row.items))\n const all = [...usedItems]\n const minX = Math.min(...all.map(c => c.x)), minY = Math.min(...all.map(c => c.y))\n const maxX = Math.max(...all.map(c => c.x + c.w))\n const maxY = Math.max(...all.map(c => c.y + (c.h || c.fontSize)))\n found.push({ table: { rows: cells.length, cols: 2, cells, hasHeader: Boolean(title) },\n bbox: { page: pageNum, x: minX, y: minY, width: maxX - minX, height: maxY - minY }, usedItems })\n i = j\n }\n return found\n}\n\n/** A ruled-looking form may omit all vertical strokes and leave its second\n * data column blank. Its two-cell header and repeated short first-column\n * values still establish a real grid. */\nfunction detectSparseTwoColumnTables(rows: RowGroup[], pageNum: number): ClusterTableResult[] {\n const found: ClusterTableResult[] = []\n for (let i = 0; i < rows.length - 3; i++) {\n const header = [...rows[i].items].sort((a, b) => a.x - b.x)\n if (header.length !== 2 || header.some(c => !/[A-Za-z가-힣]/.test(c.text) || c.text.length > 80) ||\n header[1].x - (header[0].x + header[0].w) < 5 ||\n /[.!?]$/.test(header[0].text)) continue\n const data: RowGroup[] = []\n for (let j = i + 1; j < rows.length; j++) {\n const row = rows[j]\n const cell = row.items[0]\n if (row.items.length !== 1 || cell.text.length > 20 || !cell.text.trim() ||\n Math.abs(cell.x - header[0].x) > 4 ||\n rows[j - 1].y - row.y > Math.max(24, cell.fontSize * 2.8)) break\n data.push(row)\n }\n if (data.length < 3) continue\n const tableRows = [rows[i], ...data]\n const cells: IRCell[][] = [header.map(c => ({ text: c.text, colSpan: 1, rowSpan: 1 }))]\n for (const row of data) cells.push([{ text: row.items[0].text, colSpan: 1, rowSpan: 1 }, { text: \"\", colSpan: 1, rowSpan: 1 }])\n const usedItems = new Set(tableRows.flatMap(row => row.items))\n const all = [...usedItems]\n const minX = Math.min(...all.map(c => c.x)), minY = Math.min(...all.map(c => c.y))\n const maxX = Math.max(...all.map(c => c.x + c.w))\n const maxY = Math.max(...all.map(c => c.y + (c.h || c.fontSize)))\n found.push({ table: { rows: cells.length, cols: 2, cells, hasHeader: true },\n bbox: { page: pageNum, x: minX, y: minY, width: maxX - minX, height: maxY - minY }, usedItems })\n i += data.length\n }\n return found\n}\n\n// ─── 2단 조판 본문 판별 ─────────────────────────────────\n\n/**\n * 감지된 \"표\"가 실은 2단 조판 본문(국회 속기록·고전 관보류)인지 판별.\n *\n * 2단 본문에서는 문단 끝의 짧은 줄 쌍(\"한 것이올시다.\" | \"다.\")이 헤더 조건을\n * 우연히 만족해 페이지 본문 전체가 2열 표로 흡수된다. 진짜 표와 가르는 신호:\n * 양쪽 \"열\" 모두 긴 문장 조각(라벨 열 부재) + 숫자 희박 + 마커 희박 +\n * 좌우 단 폭 대칭 + 각 단의 오른쪽 끝이 justify로 정렬.\n * (코퍼스 42건 전수 시뮬레이션에서 속기록 4페이지만 발화, 오발화 0 검증)\n */\nfunction isTwoColumnProse(r: ClusterTableResult): boolean {\n const t = r.table\n if (t.rows < 8) return false\n\n // 실질 열: 채움률 30%+ 인 열이 정확히 2개 (유령 열은 무시)\n const dense: number[] = []\n for (let c = 0; c < t.cols; c++) {\n const filled = t.cells.filter(row => row[c]?.text.trim()).length\n if (filled / t.rows >= 0.3) dense.push(c)\n }\n if (dense.length !== 2) return false\n\n // 두 실질 열 모두 평균 셀 길이가 길어야 함 (진짜 표엔 짧은 라벨/숫자 열 존재)\n for (const c of dense) {\n const lens = t.cells.map(row => row[c]?.text.replace(/\\s+/g, \"\").length ?? 0).filter(n => n > 0)\n const avg = lens.reduce((s, v) => s + v, 0) / (lens.length || 1)\n if (avg < 12) return false\n }\n\n return findTwoColumnProseCutX([...r.usedItems]) !== null\n}\n\n/**\n * 아이템 집합이 좌우 2단 조판 본문인지 — 줄-투표로 중앙 빈 띠를 찾고\n * 양쪽이 모두 prose(긴 줄 + 숫자·마커 희박 + justify 정렬 + 폭 대칭)인지 검사.\n * 2단 본문이면 단 사이 컷 x좌표를, 아니면 null을 반환.\n * page-blocks에서 레거시 컬럼 감지 가드 + 강제 단 분리에 쓰인다.\n */\nexport function findTwoColumnProseCutX(items: ClusterItem[]): number | null {\n const lines = groupByBaseline(items)\n if (lines.length < 8) return null\n\n let minX = Infinity\n let maxX = -Infinity\n for (const i of items) {\n if (i.x < minX) minX = i.x\n if (i.x + i.w > maxX) maxX = i.x + i.w\n }\n // 비유한/과대 span 가드 — 손상 PDF의 오염된 좌표(±Infinity, 1e9 등)가 스캔 루프를\n // 폭주시키는 것 차단 (fuzz: bflip에서 페이지당 수십억 회 반복 실측)\n if (!Number.isFinite(maxX - minX)) return null\n if (maxX - minX < 100) return null\n\n // 중앙부(30~70%)를 2pt 격자로 스캔: 각 x를 덮는 줄 수가 최소인 지점 = 단 사이 빈 띠.\n // 프로젝션 최대 갭 방식과 달리 전폭 제목 몇 줄이 있어도 빈 띠를 찾는다.\n // 후보 수 상한 400 — 정상 판형(span<2000pt)은 2pt 그대로, 오염 좌표만 성긴 샘플링\n const lo = minX + (maxX - minX) * 0.3\n const hi = minX + (maxX - minX) * 0.7\n const step = Math.max(2, (hi - lo) / 400)\n let cutX = 0\n let bestCover = Infinity\n for (let x = lo; x <= hi; x += step) {\n let cover = 0\n for (const line of lines) {\n if (line.items.some(i => i.x < x && i.x + i.w > x)) cover++\n }\n if (cover < bestCover) { bestCover = cover; cutX = x }\n }\n // 빈 띠를 15% 넘는 줄이 가로지르면 2단 아님\n if (bestCover / lines.length > 0.15) return null\n\n // 줄 다수(55%+)가 컷 양쪽에 모두 텍스트를 가져야 함 (한쪽 들여쓰기 문서 배제).\n // 55%: 상단 1/3이 전폭 목차인 속기록 1면(57%)까지 포함하는 값 — 코퍼스 전수에서\n // 타 문서 오발화 0 확인\n // 단별 통계는 양쪽에 글이 있는 줄로만 — 한쪽에만 글이 있는 줄(2단 위 목차·제목 띠)이 섞이면 왼 단 양쪽 정렬 비율이\n // 기준 아래로 떨어져 줄 단위로 섞여 읽힌다 (국회 회의록 OCR 실측)\n const left: ClusterItem[] = []\n const right: ClusterItem[] = []\n let twoSide = 0\n for (const line of lines) {\n const L: ClusterItem[] = [], R: ClusterItem[] = []\n for (const i of line.items) {\n if (i.x + i.w <= cutX) L.push(i)\n else if (i.x >= cutX) R.push(i)\n }\n if (L.length && R.length) { twoSide++; left.push(...L); right.push(...R) }\n }\n if (twoSide / lines.length < 0.55) return null\n if (left.length < 5 || right.length < 5) return null\n\n // 숫자·기호 위주(예산/통계표) 제외\n const allText = items.map(i => i.text).join(\"\").replace(/\\s+/g, \"\")\n const digits = (allText.match(/[\\d,.%△—-]/g) || []).length\n if (allText.length === 0 || digits / allText.length > 0.15) return null\n\n // 각 단: 줄 텍스트 길이 + 마커 밀도 + 폭 + 오른쪽 끝 justify 비율\n const stats = [left, right].map(side => {\n let sMinX = Infinity\n let sMaxR = -Infinity\n let fsSum = 0\n const rowEnds = new Map<number, number>()\n const rowLens = new Map<number, number>()\n const rowFirst = new Map<number, ClusterItem>()\n for (const i of side) {\n if (i.x < sMinX) sMinX = i.x\n const rgt = i.x + i.w\n if (rgt > sMaxR) sMaxR = rgt\n fsSum += i.fontSize\n const key = Math.round(i.y / 4)\n rowEnds.set(key, Math.max(rowEnds.get(key) ?? -Infinity, rgt))\n rowLens.set(key, (rowLens.get(key) ?? 0) + i.text.replace(/\\s+/g, \"\").length)\n const f = rowFirst.get(key)\n if (!f || i.x < f.x) rowFirst.set(key, i)\n }\n const ends = [...rowEnds.values()].sort((a, b) => a - b)\n const p85 = ends[Math.floor(ends.length * 0.85)] ?? sMaxR\n const fs = fsSum / side.length\n const justified = ends.filter(e => Math.abs(e - p85) <= fs).length / ends.length\n const lens = [...rowLens.values()]\n const avgLen = lens.reduce((s, v) => s + v, 0) / (lens.length || 1)\n const markers = [...rowFirst.values()].filter(i => /^[•▪◦‣∙·\\-–—□■◇◆▶※*]/.test(i.text)).length\n return { width: sMaxR - sMinX, justified, avgLen, markerRatio: markers / (rowFirst.size || 1) }\n })\n\n // 양쪽 모두 긴 문장 줄 (진짜 표엔 짧은 라벨/숫자 열 존재)\n if (stats.some(s => s.avgLen < 12)) return null\n // 불릿/마커 구조(SWOT 4분면·불릿 리스트) 제외\n if (stats.some(s => s.markerRatio > 0.1)) return null\n const widthSym = Math.min(stats[0].width, stats[1].width) / Math.max(stats[0].width, stats[1].width)\n if (widthSym < 0.6) return null\n // 본문 단은 마지막 문단 줄을 제외한 대부분 행이 오른쪽 끝까지 채워진다\n if (Math.min(stats[0].justified, stats[1].justified) < 0.55) return null\n return cutX\n}\n\n// ─── 균등배분 사전 병합 ──────────────────────────────────\n\n/** 균등배분(1글자+균일간격) 아이템을 단어 단위로 병합. 원본→병합 매핑도 반환. */\nfunction mergeEvenSpacedClusters(\n items: ClusterItem[],\n): { merged: ClusterItem[]; originMap: Map<ClusterItem, ClusterItem[]> } {\n const originMap = new Map<ClusterItem, ClusterItem[]>()\n const rows = groupByBaseline(items)\n const merged: ClusterItem[] = []\n\n for (const row of rows) {\n const sorted = [...row.items].sort((a, b) => a.x - b.x)\n let i = 0\n while (i < sorted.length) {\n if (/^[가-힣\\d]$/.test(sorted[i].text)) {\n let runEnd = i + 1\n while (runEnd < sorted.length && /^[가-힣\\d]$/.test(sorted[runEnd].text)) {\n // 명시적 공백 글리프 = 단어 경계 → run 분리 (Type3 글자 분리 배치 오판 방지)\n if (sorted[runEnd].hasSpaceBefore) break\n const gap = sorted[runEnd].x - (sorted[runEnd - 1].x + sorted[runEnd - 1].w)\n const fs = sorted[runEnd].fontSize\n if (gap < fs * 0.1 || gap > fs * 3) break\n runEnd++\n }\n if (runEnd - i >= 3) {\n const gaps: number[] = []\n for (let g = i + 1; g < runEnd; g++) {\n gaps.push(sorted[g].x - (sorted[g - 1].x + sorted[g - 1].w))\n }\n let minG = Infinity, maxG = -Infinity\n for (const g of gaps) { if (g < minG) minG = g; if (g > maxG) maxG = g }\n if (minG > 0 && maxG / minG <= 3) {\n const run = sorted.slice(i, runEnd)\n const text = run.map(r => r.text).join(\"\")\n const first = run[0], last = run[runEnd - i - 1]\n const item: ClusterItem = {\n text, x: first.x, y: first.y,\n w: (last.x + last.w) - first.x, h: first.h,\n fontSize: first.fontSize, fontName: first.fontName,\n }\n originMap.set(item, run)\n merged.push(item)\n i = runEnd\n continue\n }\n }\n }\n merged.push(sorted[i])\n i++\n }\n }\n return { merged, originMap }\n}\n\n/** 병합된 ClusterItem의 usedItems를 원본 아이템으로 확장 */\nfunction expandUsedItems(usedItems: Set<ClusterItem>, originMap: Map<ClusterItem, ClusterItem[]>): void {\n const toAdd: ClusterItem[] = []\n for (const item of usedItems) {\n const origins = originMap.get(item)\n if (origins) for (const o of origins) toAdd.push(o)\n }\n for (const a of toAdd) usedItems.add(a)\n}\n\n// ─── 헤더 행 기반 열 감지 ───────────────────────────────\n\n/**\n * 상위 행에서 테이블 헤더 후보를 탐색.\n * 조건: 2~6개 짧은 아이템, 최소 1개 큰 갭(>2.5x fontSize), 넓은 X 범위,\n * 한글 포함, 후속 행에서 최소 MIN_ROWS개가 헤더 열에 매칭\n */\nfunction detectHeaderRow(rows: RowGroup[]): HeaderResult | null {\n const allItems = rows.flatMap(r => r.items)\n if (allItems.length === 0) return null\n let allMinX = Infinity, allMaxX = -Infinity\n for (const i of allItems) { if (i.x < allMinX) allMinX = i.x; const r = i.x + i.w; if (r > allMaxX) allMaxX = r }\n const pageSpan = allMaxX - allMinX\n if (pageSpan <= 0) return null\n\n // 전체 행에서 헤더 후보 탐색 — 가드가 충분히 엄격하므로 범위 제한 불필요\n for (let ri = 0; ri < rows.length; ri++) {\n const row = rows[ri]\n // 2~6 아이템\n if (row.items.length < MIN_COLS || row.items.length > 6) continue\n // 모든 아이템 짧아야 함 (각 8자 이내)\n if (row.items.some(i => i.text.length > 8)) continue\n // 한글 포함 아이템이 최소 1개\n if (!row.items.some(i => /[가-힣]/.test(i.text))) continue\n // 마커(□○●·※▶-) 시작 아이템 제외\n if (row.items.some(i => /^[□■○●·※▶▷◆◇\\-]/.test(i.text))) continue\n\n const sorted = [...row.items].sort((a, b) => a.x - b.x)\n const xSpan = (sorted[sorted.length - 1].x + sorted[sorted.length - 1].w) - sorted[0].x\n\n // X 범위가 전체의 40%+ 차지\n if (xSpan / pageSpan < 0.4) continue\n\n // 최소 1개 갭이 avgFontSize의 2.5배 이상\n const avgFs = sorted.reduce((s, i) => s + i.fontSize, 0) / sorted.length\n let hasLargeGap = false\n for (let i = 1; i < sorted.length; i++) {\n const gap = sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w)\n if (gap >= avgFs * 2.5) { hasLargeGap = true; break }\n }\n if (!hasLargeGap) continue\n\n // 열 앵커 생성\n const columns: ColCluster[] = sorted.map(item => ({ x: item.x, count: 0 }))\n\n // 후속 행 검증: 헤더 이후 MIN_ROWS+ 행이 2+ 열에 매칭\n let matchCount = 0\n for (let j = ri + 1; j < rows.length && matchCount < MIN_ROWS + 2; j++) {\n const matched = countMatchedColumnsRange(rows[j], columns, sorted)\n if (matched >= MIN_COLS) matchCount++\n }\n if (matchCount < MIN_ROWS) continue\n\n return { columns, headerIdx: ri }\n }\n return null\n}\n\n/**\n * 수직으로 겹치는 인접 행 병합 — 첨자(각주 마커/덧말)가 별도 행으로 분리된 것 복원.\n * 첨자 조각 행: 아이템이 적고(≤3) 짧으며(≤8자), 겹치는 행보다 글자 박스가 확실히\n * 작은 행(높이 ≤0.8배). 이런 행만 본문 줄에 흡수한다.\n * (rowspan 라벨처럼 키가 큰 셀이 이웃 행을 덮는 경우는 병합하지 않음 — 정상 표 구조)\n */\nfunction mergeOverlappingRows(rows: RowGroup[]): RowGroup[] {\n if (rows.length <= 1) return rows\n const result: RowGroup[] = [rows[0]]\n for (let i = 1; i < rows.length; i++) {\n const prev = result[result.length - 1]\n const curr = rows[i]\n const a = rowBand(prev)\n const b = rowBand(curr)\n const overlap = Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom)\n const prevIsFrag = isFragmentRow(prev, curr) && a.height <= b.height * 0.8 && overlap >= a.height * 0.5\n const currIsFrag = isFragmentRow(curr, prev) && b.height <= a.height * 0.8 && overlap >= b.height * 0.5\n if (prevIsFrag || currIsFrag) {\n // 본문 줄(흡수하는 쪽)의 y를 대표값으로 유지\n const baseY = prevIsFrag ? curr.y : prev.y\n result[result.length - 1] = { y: baseY, items: [...prev.items, ...curr.items] }\n } else {\n result.push(curr)\n }\n }\n return result\n}\n\n/** 첨자 후보 행: 모두 짧은 텍스트(≤8자)이고 아이템 ≤3개 — 넘으면 라틴 글자·수식 기호 없는 첨자가 아이템마다 옆 행 글자 앞뒤에 붙어야\n * 한다(연도 위첨자 \"(’10)10.2만건 → (’25)17.1만건\" 이 한 줄에 넷 — 넓은 간격의 넷 칸 행이 되어 본문 쪽이 통째로 4열 표가 됐다) */\nfunction isFragmentRow(row: RowGroup, host: RowGroup): boolean {\n if (!row.items.every(i => i.text.length <= 8)) return false\n return row.items.length <= 3 || (row.items.length <= 16 && row.items.every(i => PREFIX_SCRIPT.test(i.text.trim()) && host.items.some(h => scriptAttached(i, h))))\n}\n\n/** 첨자 조각이 본문 글자 뒤에 맞붙었거나(틈 −0.1em ~ 0.35em) 앞에 빈칸 하나 안(0.6em)으로 붙었나 */\nfunction scriptAttached(i: { x: number; w: number }, h: { x: number; w: number; fontSize: number }): boolean {\n const after = i.x - (h.x + h.w), before = h.x - (i.x + i.w)\n return (after <= h.fontSize * 0.35 && after >= -h.fontSize * 0.1) || (before <= h.fontSize * 0.6 && before >= -h.fontSize * 0.1)\n}\n\n/** 행 아이템들의 수직 범위 (bottom=min y, top=max y+h) */\nfunction rowBand(row: RowGroup): { bottom: number; top: number; height: number } {\n let bottom = Infinity, top = -Infinity\n for (const i of row.items) {\n const h = i.h > 0 ? i.h : i.fontSize\n if (i.y < bottom) bottom = i.y\n if (i.y + h > top) top = i.y + h\n }\n return { bottom, top, height: top - bottom }\n}\n\n// ─── 다중행 셀 병합 ────────────────────────────────────\n\n/** 연속 행에서 MIN_COLS 미만 열만 사용하고 Y갭이 작으면 이전 행에 병합 */\nfunction mergeMultiLineRows(rows: RowGroup[], columns: ColCluster[]): RowGroup[] {\n if (rows.length <= 1) return rows\n const result: RowGroup[] = [rows[0]]\n const allFontSizes = rows.flatMap(r => r.items).map(i => i.fontSize)\n const avgFontSize = allFontSizes.length > 0\n ? allFontSizes.reduce((s, v) => s + v, 0) / allFontSizes.length : 12\n\n for (let i = 1; i < rows.length; i++) {\n const prev = result[result.length - 1]\n const curr = rows[i]\n const yGap = Math.abs(prev.y - curr.y)\n const matchedCols = countMatchedColumns(curr, columns)\n\n // 병합 조건: Y갭이 작고 + 아이템 수가 적음 (연속 행 = 1~2개 아이템)\n // 아이템이 많은 행(3+)은 독립적인 데이터 행 → 병합하지 않음\n if (yGap < avgFontSize * 1.8 && curr.items.length <= 2 && (matchedCols < MIN_COLS || curr.items.length === 1) &&\n !startsNewRecord(prev, curr, columns)) {\n result[result.length - 1] = {\n y: prev.y,\n items: [...prev.items, ...curr.items],\n }\n } else {\n result.push(curr)\n }\n }\n return result\n}\n\n/**\n * 칸 안에서 꺾인 연속 줄이 아니라 새 레코드의 첫 줄인지 — 아이템이 자기 열을 넘어 다음 열까지 뻗고, 위 행의 같은 열 칸이 이미\n * 차 있으면 연속 줄일 수 없다(꺾인 줄은 자기 칸 안에 머문다). 영수증의 품목 두 줄 묶음(품명 줄 + 바코드·단가·금액 줄)에서\n * 다음 품명 줄이 앞 품목 바코드 행에 붙던 것(#141). 위 행이 두 열 이상 찬 데이터 행일 때만 — 위 행이 한 칸뿐인 글줄이면 산문이 꺾여 이어진\n * 것이다(ODL 039 두 단 본문·141 카드 글이 줄마다 행으로 남아 산문 표 판정을 비껴갔다)\n */\nfunction startsNewRecord(prev: RowGroup, curr: RowGroup, columns: ColCluster[]): boolean {\n const colOf = (x: number) => {\n let ci = -1\n for (let c = 0; c < columns.length; c++) if (columns[c].x <= x + COL_CLUSTER_TOL) ci = c\n return ci\n }\n if (new Set(prev.items.map(p => colOf(p.x))).size < 2) return false\n return curr.items.some(it => {\n const ci = colOf(it.x)\n if (ci < 0 || ci + 1 >= columns.length || it.x + it.w <= columns[ci + 1].x + COL_CLUSTER_TOL) return false\n return prev.items.some(p => colOf(p.x) === ci)\n })\n}\n\n// ─── 기본 유틸 ──────────────────────────────────────────\n\n/** 아이템을 baseline(Y좌표)으로 그룹핑 */\nfunction groupByBaseline(items: ClusterItem[]): RowGroup[] {\n if (items.length === 0) return []\n\n const sorted = [...items].sort((a, b) => b.y - a.y || a.x - b.x)\n const rows: RowGroup[] = []\n let curItems: ClusterItem[] = [sorted[0]]\n let curY = sorted[0].y\n\n for (let i = 1; i < sorted.length; i++) {\n if (Math.abs(sorted[i].y - curY) <= Y_TOL) {\n curItems.push(sorted[i])\n } else {\n rows.push({ y: curY, items: curItems })\n curItems = [sorted[i]]\n curY = sorted[i].y\n }\n }\n if (curItems.length > 0) rows.push({ y: curY, items: curItems })\n\n return rows\n}\n\n/** 행 내 아이템 간 \"의심스러운\" 갭 존재 여부 (테이블 열 구분 후보) */\nfunction hasSuspiciousGaps(row: RowGroup): boolean {\n if (row.items.length < 2) return false\n\n const sorted = [...row.items].sort((a, b) => a.x - b.x)\n // 가드: 2아이템 행에서 두 번째 아이템이 긴 텍스트면 들여쓰기 단락\n if (sorted.length === 2 && sorted[1].text.length > 20) return false\n\n const avgFontSize = sorted.reduce((s, i) => s + i.fontSize, 0) / sorted.length\n const minGap = Math.max(avgFontSize * MIN_GAP_FACTOR, MIN_GAP_ABSOLUTE)\n\n for (let i = 1; i < sorted.length; i++) {\n const gap = sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w)\n if (gap >= minGap) return true\n }\n return false\n}\n\n/** 의심스러운 행들의 X좌표에서 열 클러스터 추출 */\nfunction extractColumnClusters(rows: RowGroup[]): ColCluster[] {\n const allX: number[] = []\n for (const row of rows) {\n for (const item of row.items) allX.push(item.x)\n }\n if (allX.length === 0) return []\n\n allX.sort((a, b) => a - b)\n\n const clusters: ColCluster[] = []\n let clusterStart = 0\n\n for (let i = 1; i <= allX.length; i++) {\n if (i === allX.length || allX[i] - allX[i - 1] > COL_CLUSTER_TOL) {\n const slice = allX.slice(clusterStart, i)\n const avg = Math.round(slice.reduce((s, v) => s + v, 0) / slice.length)\n clusters.push({ x: avg, count: slice.length })\n clusterStart = i\n }\n }\n\n const minCount = Math.max(2, Math.floor(rows.length * MIN_COL_FILL_RATIO))\n return clusters\n .filter(c => c.count >= minCount)\n .sort((a, b) => a.x - b.x)\n}\n\n/** 헤더 기반 테이블 영역 찾기 — boundary 기반 열 매칭, 관대한 종료 조건 */\nfunction findTableRegionsByHeader(\n allRows: RowGroup[], columns: ColCluster[], headerItems: ClusterItem[],\n): { rows: RowGroup[] }[] {\n const regions: { rows: RowGroup[] }[] = []\n let currentRegion: RowGroup[] = []\n let missStreak = 0 // 연속 비매칭 행 수\n\n for (const row of allRows) {\n const matchedCols = countMatchedColumnsRange(row, columns, headerItems)\n if (matchedCols >= MIN_COLS) {\n currentRegion.push(row)\n missStreak = 0\n } else if (currentRegion.length > 0 && (row.items.length <= 2 || missStreak === 0) && !isProseLine(row, columns)) {\n // 단일 비매칭은 허용 (multi-line 셀 or 단일 아이템)\n currentRegion.push(row)\n missStreak++\n } else {\n // 연속 2+ 비매칭 → 테이블 종료\n // 끝에 붙은 비매칭 행 제거\n while (currentRegion.length > 0) {\n const last = currentRegion[currentRegion.length - 1]\n if (countMatchedColumnsRange(last, columns, headerItems) >= MIN_COLS) break\n currentRegion.pop()\n }\n if (currentRegion.length >= MIN_ROWS) {\n regions.push({ rows: [...currentRegion] })\n }\n currentRegion = []\n missStreak = 0\n }\n }\n\n // 끝에 붙은 비매칭 행 정리\n while (currentRegion.length > 0) {\n const last = currentRegion[currentRegion.length - 1]\n if (countMatchedColumnsRange(last, columns, headerItems) >= MIN_COLS) break\n currentRegion.pop()\n }\n if (currentRegion.length >= MIN_ROWS) {\n regions.push({ rows: currentRegion })\n }\n\n return regions\n}\n\n/** 표 사이 문단 줄 — 한 줄 글이 열 시작점을 둘 이상 가로지른다(여러 줄 칸의 이어진 줄은 제 열 안에 머문다) */\nfunction isProseLine(row: RowGroup, columns: ColCluster[]): boolean {\n const text = row.items.map(item => item.text).join(\" \").trim()\n if (text.length < 40) return false\n // 문장 증거 — 머리행 이름 나열(H6 ARC HellaSwag …)은 한 아이템이라도 문단이 아니다\n const words = text.split(/\\s+/)\n const sentence = /[.!?:]$|다\\.?$/.test(text) || words.filter(word => /^[a-z]/.test(word)).length >= words.length * 0.5\n if (!sentence) return false\n const left = Math.min(...row.items.map(item => item.x))\n const right = Math.max(...row.items.map(item => item.x + item.w))\n return columns.filter(col => col.x > left + COL_CLUSTER_TOL && col.x < right - COL_CLUSTER_TOL).length >= 2\n}\n\n/** 연속된 테이블 행 영역 찾기 */\nfunction findTableRegions(allRows: RowGroup[], columns: ColCluster[]): { rows: RowGroup[] }[] {\n const regions: { rows: RowGroup[] }[] = []\n let currentRegion: RowGroup[] = []\n\n for (const row of allRows) {\n const matchedCols = countMatchedColumns(row, columns)\n if (matchedCols >= MIN_COLS) {\n currentRegion.push(row)\n } else if (row.items.length === 1 && !isProseLine(row, columns)) {\n if (currentRegion.length > 0) {\n currentRegion.push(row)\n }\n } else {\n if (currentRegion.length >= MIN_ROWS) {\n regions.push({ rows: [...currentRegion] })\n }\n currentRegion = []\n }\n }\n\n if (currentRegion.length >= MIN_ROWS) {\n regions.push({ rows: currentRegion })\n }\n\n return regions\n}\n\n/** 행의 아이템이 몇 개의 열에 매칭되는지 (X좌표 근접) */\nfunction countMatchedColumns(row: RowGroup, columns: ColCluster[]): number {\n const matched = new Set<number>()\n for (const item of row.items) {\n for (let ci = 0; ci < columns.length; ci++) {\n if (Math.abs(item.x - columns[ci].x) <= COL_CLUSTER_TOL * 2) {\n matched.add(ci)\n break\n }\n }\n }\n return matched.size\n}\n\n/**\n * 범위 기반 열 매칭 — 헤더 아이템의 x~x+w 범위 내에 있는지 확인.\n * 헤더 열 간 중간점을 경계로 사용하여 넓은 범위 매칭.\n */\nfunction countMatchedColumnsRange(\n row: RowGroup, columns: ColCluster[], headerItems: ClusterItem[],\n): number {\n // 열 경계 계산: 헤더 아이템 사이 중간점\n const boundaries: { left: number; right: number }[] = []\n for (let ci = 0; ci < headerItems.length; ci++) {\n const left = ci === 0 ? 0 : (headerItems[ci - 1].x + headerItems[ci - 1].w + headerItems[ci].x) / 2\n const right = ci === headerItems.length - 1\n ? Infinity\n : (headerItems[ci].x + headerItems[ci].w + headerItems[ci + 1].x) / 2\n boundaries.push({ left, right })\n }\n\n const matched = new Set<number>()\n for (const item of row.items) {\n for (let ci = 0; ci < boundaries.length; ci++) {\n if (item.x >= boundaries[ci].left && item.x < boundaries[ci].right) {\n matched.add(ci)\n break\n }\n }\n }\n return matched.size\n}\n\n/**\n * 행별 갭 분석으로 아이템을 열에 배정.\n *\n * 전역 경계선 대신 각 행을 개별 분석:\n * 1) 행 내 N-1개의 가장 큰 갭으로 N개 그룹 분할\n * 2) 각 그룹의 중심 X를 헤더 열 중심과 비교하여 최적 열 배정\n * 3) 갭이 불충분한 행(아이템 적음)은 헤더 중간점 기반 fallback\n *\n * 이 방식은 열 폭이 행마다 달라도 정확하게 분리.\n */\nfunction assignRowItems(\n items: ClusterItem[], columns: ColCluster[], numCols: number, headerFace?: string,\n): { col: number; items: ClusterItem[] }[] {\n if (items.length === 0) return []\n const sorted = [...items].sort((a, b) => a.x - b.x)\n\n // 머리행: 붙은 조각(한 낱말이 갈린 \"Glucos|e Solution\")을 묶은 무리의 왼끝이 모두 서로 다른 열 앵커와 맞으면 앵커대로 —\n // 머리 글이 좁은 틈(4pt)으로 붙어 있어도 열 시작점이 칸을 가른다\n const runs: ClusterItem[][] = []\n for (const it of sorted) {\n const last = runs[runs.length - 1]\n const prev = last?.[last.length - 1]\n if (prev && it.x - (prev.x + prev.w) <= Math.max(0.5, it.fontSize * 0.05) && !it.hasSpaceBefore) last.push(it)\n else runs.push([it])\n }\n const tight = runs.slice(1).some((run, k) => run[0].x - Math.max(...runs[k].map(i => i.x + i.w)) < 12)\n const shortRuns = runs.every(run => run.reduce((n, i) => n + i.text.length, 0) <= 30)\n if (headerFace && sorted.every(i => i.fontName === headerFace) && runs.length >= 2 && tight && shortRuns) {\n const anchor = runs.map(run => columns.findIndex(c => Math.abs(c.x - run[0].x) <= 2))\n if (anchor.every(ci => ci >= 0) && new Set(anchor).size === runs.length) return runs.map((run, k) => ({ col: anchor[k], items: run }))\n }\n\n // 헤더 열 중심 좌표\n const colCenters = columns.map(c => c.x)\n\n // 행 내 갭 계산\n const gaps: { idx: number; size: number }[] = []\n for (let i = 1; i < sorted.length; i++) {\n gaps.push({ idx: i, size: sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w) })\n }\n\n // N-1개의 가장 큰 갭 선택 — 적응형 임계값\n // 아이템이 적은 행(헤더, 라벨 행): 낮은 절대 임계값 (모든 갭이 유의미할 가능성 높음)\n // 아이템이 많은 행(본문 데이터): 상대적 임계값 (워드 갭 vs 컬럼 갭 구분)\n const gapSizes = gaps.map(g => g.size).sort((a, b) => a - b)\n const medianGap = gapSizes.length > 0 ? gapSizes[Math.floor(gapSizes.length / 2)] : 0\n const gapThreshold = sorted.length <= numCols + 1\n ? 12 // 희소 행: 낮은 절대 임계값\n : Math.max(medianGap * 2.5, 12) // 밀집 행: 상대적 임계값\n const significantGaps = gaps\n .filter(g => g.size >= gapThreshold)\n .sort((a, b) => b.size - a.size)\n .slice(0, numCols - 1)\n .sort((a, b) => a.idx - b.idx) // 위치 순 복원\n\n // 가용 갭으로 그룹 분할 (갭이 부족해도 있는 만큼 분할)\n const groups: ClusterItem[][] = []\n let start = 0\n for (const gap of significantGaps) {\n groups.push(sorted.slice(start, gap.idx))\n start = gap.idx\n }\n groups.push(sorted.slice(start))\n\n // 각 그룹 → 가장 가까운 헤더 열에 배정\n const result: { col: number; items: ClusterItem[] }[] = []\n const usedCols = new Set<number>()\n // 그룹별 중심 X 계산\n const groupCenters = groups.map(g => {\n let minX = Infinity, maxX = -Infinity\n for (const i of g) { if (i.x < minX) minX = i.x; const r = i.x + i.w; if (r > maxX) maxX = r }\n return (minX + maxX) / 2\n })\n\n // 탐욕적 배정: 각 그룹을 가장 가까운 미사용 열에 배정\n const assignments: { gi: number; ci: number; dist: number }[] = []\n for (let gi = 0; gi < groups.length; gi++) {\n for (let ci = 0; ci < numCols; ci++) {\n assignments.push({ gi, ci, dist: Math.abs(groupCenters[gi] - colCenters[ci]) })\n }\n }\n assignments.sort((a, b) => a.dist - b.dist)\n\n const assignedGroups = new Set<number>()\n for (const { gi, ci } of assignments) {\n if (assignedGroups.has(gi) || usedCols.has(ci)) continue\n result.push({ col: ci, items: groups[gi] })\n assignedGroups.add(gi)\n usedCols.add(ci)\n }\n // 남은 그룹 (numGroups > numCols인 경우 — 가장 가까운 열에 추가)\n for (let gi = 0; gi < groups.length; gi++) {\n if (assignedGroups.has(gi)) continue\n let bestCol = 0, bestDist = Infinity\n for (let ci = 0; ci < numCols; ci++) {\n const d = Math.abs(groupCenters[gi] - colCenters[ci])\n if (d < bestDist) { bestDist = d; bestCol = ci }\n }\n result.push({ col: bestCol, items: groups[gi] })\n }\n\n return result\n}\n\n/** 클러스터 테이블을 IRTable로 구성 */\nfunction buildClusterTable(\n rows: RowGroup[],\n columns: ColCluster[],\n pageNum: number,\n lex?: WrapLexicon,\n): ClusterTableResult | null {\n const numCols = columns.length\n const numRows = rows.length\n\n if (numRows < MIN_ROWS || numCols < MIN_COLS) return null\n\n const cells: IRCell[][] = Array.from(\n { length: numRows },\n () => Array.from({ length: numCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 })),\n )\n\n const usedItems = new Set<ClusterItem>()\n // 칸 글줄 — 칸 글은 다 모은 뒤 열 글줄들의 왼끝·오른끝을 칸 안쪽으로 보고 잇는다(joinClusterCellLines). 후처리 1 이 이어지는 행을 붙일 때도 같은 판정\n const lines: ClusterCellLine[][][] = cells.map(row => row.map(() => []))\n\n // 머리행이 몸통과 다른 한 서체(굵은 머리)면 그 서체 — 머리 글 사이 틈이 좁아도 열 앵커로 칸을 가른다\n const headFaces = new Set(rows[0]?.items.map(i => i.fontName))\n const headerFace = headFaces.size === 1 && rows.slice(1).every(row => row.items.every(i => !headFaces.has(i.fontName)))\n ? [...headFaces][0] : undefined\n for (let r = 0; r < numRows; r++) {\n const row = rows[r]\n // 단일 아이템 행 → 전체 행 병합 (colSpan)\n if (row.items.length === 1 && numCols > 1) {\n cells[r][0] = { text: row.items[0].text, colSpan: numCols, rowSpan: 1 }\n usedItems.add(row.items[0])\n continue\n }\n\n // 행별 갭 분석 기반 열 배정\n const assignments = assignRowItems(row.items, columns, numCols, r === 0 ? headerFace : undefined)\n for (const { col, items } of assignments) {\n const cell = lines[r][col]\n if (cell.length) cell[cell.length - 1].spaceAfter = true // 한 행 같은 열에 무리가 여럿이면 종전대로 띄운다\n cell.push(...clusterCellLines(items))\n for (const item of items) usedItems.add(item)\n }\n }\n const boxes = columns.map((_, c) => {\n const col = lines.flatMap(row => row[c])\n return { x1: Math.min(...col.map(l => l.left)), x2: Math.max(...col.map(l => l.right)) }\n })\n const textOf = (r: number, c: number) => joinClusterCellLines(lines[r][c], boxes[c], lex)\n for (let r = 0; r < numRows; r++) for (let c = 0; c < numCols; c++) if (lines[r][c].length) cells[r][c].text = textOf(r, c)\n\n // 검증: 빈 행이 너무 많으면 테이블 아님\n let emptyRows = 0\n for (const row of cells) {\n if (row.every(c => c.text === \"\")) emptyRows++\n }\n if (emptyRows > numRows * 0.5) return null\n\n // 검증: 모든 열에 최소 1개 값이 있어야 함\n for (let c = 0; c < numCols; c++) {\n const hasValue = cells.some(row => row[c].text !== \"\")\n if (!hasValue) return null\n }\n\n // 후처리 1: 단일 열 연속 행 → 이전 행에 역방향 병합 (multi-line 셀)\n // 조건: 1개 열만 내용 + col0 비어있음 + 리스트 마커(○-·)로 시작하지 않음\n for (let r = numRows - 1; r >= 1; r--) {\n const nonEmptyCols = cells[r].filter(c => c.text.trim()).length\n if (nonEmptyCols !== 1) continue\n if (cells[r][0].text.trim() !== \"\") continue\n const contentText = cells[r].find(c => c.text.trim())?.text.trim() || \"\"\n // 리스트 마커로 시작하면 새 항목 → 병합하지 않음\n if (/^[○●▶\\-·]/.test(contentText)) continue\n for (let pr = r - 1; pr >= 0; pr--) {\n if (cells[pr].some(c => c.text.trim())) {\n // 전폭 병합 행(한 아이템 행)의 가려진 칸에 쓰면 렌더에서 글이 사라진다 — 제 행으로 둔다\n // (\"없음\" 행 뒤 \"3) 행정규제 : …\"·\"일부개정령안\" 뒤 본문 첫 줄이 마크다운에서 빠지던 것, 제약산업 시행규칙 개정령안)\n if (cells[pr][0].colSpan > 1) break\n for (let c = 0; c < numCols; c++) {\n if (!cells[r][c].text.trim()) continue\n lines[pr][c].push(...lines[r][c])\n cells[pr][c].text = textOf(pr, c)\n }\n for (let c = 0; c < numCols; c++) cells[r][c].text = \"\"\n break\n }\n }\n }\n\n // 후처리 2: 번호만 있는 행(col0+마지막열만 값) → 다음 내용 행과 순방향 병합\n // 예: 목차 \"|3|||3|\" + \"|청사 내 에너지...|행정지원과||\" → \"|3|청사...|행정지원과|3|\"\n for (let r = 0; r < cells.length - 1; r++) {\n const row = cells[r]\n const hasCol0 = row[0].text.trim() !== \"\"\n const hasColLast = numCols > 1 && row[numCols - 1].text.trim() !== \"\"\n const midEmpty = row.slice(1, numCols - 1).every(c => c.text.trim() === \"\")\n if (hasCol0 && hasColLast && midEmpty) {\n // 다음 행에 col0이 비어있으면 병합\n const next = cells[r + 1]\n if (next[0].text.trim() === \"\" && next.some(c => c.text.trim())) {\n for (let c = 1; c < numCols; c++) {\n const curr = next[c].text.trim()\n if (curr) row[c].text = row[c].text.trim() ? row[c].text.trim() + \" \" + curr : curr\n }\n for (let c = 0; c < numCols; c++) next[c].text = \"\"\n }\n }\n }\n\n // 빈 행 제거\n const filteredCells = cells.filter(row => row.some(c => c.text.trim()))\n const finalRowCount = filteredCells.length\n\n // 검증\n if (finalRowCount < MIN_ROWS) return null\n\n const irTable: IRTable = {\n rows: finalRowCount,\n cols: numCols,\n cells: filteredCells,\n hasHeader: finalRowCount > 1,\n }\n\n // BBox 계산\n const allItems = rows.flatMap(r => r.items)\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n for (const i of allItems) {\n if (i.x < minX) minX = i.x\n if (i.y < minY) minY = i.y\n if (i.x + i.w > maxX) maxX = i.x + i.w\n const h = i.h > 0 ? i.h : i.fontSize\n if (i.y + h > maxY) maxY = i.y + h\n }\n\n return {\n table: irTable,\n bbox: { page: pageNum, x: minX, y: minY, width: maxX - minX, height: maxY - minY },\n usedItems,\n }\n}\n","/**\n * 쪽 지면 영역 분리 (page-blocks.ts에서 분리) — 두 단 본문 밴드, 두 단 위 표 밴드, 3단 머리 표,\n * 겹쳐 쌓인 캡션 표 밴드, 카드·인포그래픽 3열 영역. 표 감지 전에 읽기 영역을 먼저 나눈다.\n */\n\nimport type { IRBlock, IRCell } from \"../types.js\"\nimport { detectClusterTables } from \"./cluster-detector.js\"\nimport { type NormItem, computeBBox, groupByY, mergeLineSimple } from \"./text-line.js\"\nimport { type ColRect } from \"./two-column.js\"\n\n/**\n * 2단 조판 본문을 읽기 순서 그룹으로 분리 — 전폭 줄(제목·목차)의 y를 경계로\n * 세로 밴드를 나누고, 각 밴드에서 좌단 전체 → 우단 전체 순으로 배열한다.\n */\nexport function splitTwoColumnProse(items: NormItem[], cutX: number): NormItem[][] {\n const left: NormItem[] = []\n const right: NormItem[] = []\n const cross: NormItem[] = []\n for (const i of items) {\n if (i.x + i.w <= cutX) left.push(i)\n else if (i.x >= cutX) right.push(i)\n else cross.push(i)\n }\n if (cross.length === 0) return columnPair(left, right)\n\n // 전폭 아이템을 y 근접(3pt)으로 경계 줄 묶음 (y 내림차순 = 위→아래)\n cross.sort((a, b) => b.y - a.y)\n const crossLines: NormItem[][] = []\n for (const c of cross) {\n const last = crossLines[crossLines.length - 1]\n if (last && Math.abs(last[0].y - c.y) <= 3) last.push(c)\n else crossLines.push([c])\n }\n // 경계 줄과 같은 y의 좌/우 아이템은 그 경계 줄에 편입 (목차 줄의 나란한 조각). 경계 줄 글보다 작은 첨자는 기준선이\n // 3pt 넘게 떠도 위 0.6em·아래 0.35em 안에서 경계 줄 조각 오른끝에 붙어(0.35em 안) 시작하면 편입 — 저자 줄\n // \"Dahyun Kim∗, Chanjun Park∗†\" 의 ∗† (8pt, 0.36em 위)가 단 경계에서 좌·우 조각 줄로 갈려 이름 위에 따로 섰다 (ODL 185).\n // 글자 위에 얹힌 조각(수식 ∑ 위아래 극한)은 붙어 있지 않아 제외 — 2단 시험지 수식 줄이 섞였다\n const scriptOf = (i: NormItem, cl: NormItem[]) => {\n const fs = cl[0].fontSize, dy = i.y - cl[0].y\n if (i.fontSize > fs * 0.85 || dy > fs * 0.6 || dy < -fs * 0.35) return false\n return cl.some(c => c !== i && c.fontSize > fs * 0.85 && i.x - (c.x + c.w) <= fs * 0.35 && i.x - (c.x + c.w) >= -fs * 0.1)\n }\n const bandItem = (arr: NormItem[], fits: (i: NormItem, cl: NormItem[]) => boolean) => arr.filter(i => {\n for (const cl of crossLines) {\n if (fits(i, cl)) { cl.push(i); return false }\n }\n return true\n })\n // 같은 y 조각을 다 붙여 줄 폭이 정해진 뒤에 첨자를 붙인다\n const sameY = (i: NormItem, cl: NormItem[]) => Math.abs(cl[0].y - i.y) <= 3\n const leftRest = bandItem(bandItem(left, sameY), scriptOf)\n const rightRest = bandItem(bandItem(right, sameY), scriptOf)\n\n // 밴드 k = 경계줄 k-1 아래 ~ 경계줄 k 위 (PDF y는 위가 큼)\n const boundYs = crossLines.map(cl => cl[0].y)\n const bandOf = (y: number) => {\n let k = 0\n while (k < boundYs.length && y < boundYs[k]) k++\n return k\n }\n const groups: NormItem[][] = []\n for (let k = 0; k <= crossLines.length; k++) {\n const L = leftRest.filter(i => bandOf(i.y) === k)\n const R = rightRest.filter(i => bandOf(i.y) === k)\n groups.push(...columnPair(L, R))\n // 편입한 조각이 뒤에 붙어 있어 y 순으로 — groupByY 는 y 순서를 전제한다(떠 있는 첨자가 줄을 번갈아 쪼갠다)\n if (k < crossLines.length) groups.push(crossLines[k].sort((a, b) => b.y - a.y || a.x - b.x))\n }\n return groups\n}\n\n/** 각주 번호로 시작하는 줄 — \"34 Richard…\"·\"* …\". \"1. Lee…\" 같은 목록 번호(참고문헌)는 흐름 안의 글이다 */\nconst NOTE_START = /^\\s*(?:\\d{1,3}\\s|[*†‡§¹²³⁴⁵⁶⁷⁸⁹])/\n\nfunction medianOf(values: number[]): number {\n const s = values.filter(v => v > 0).sort((a, b) => a - b)\n return s.length ? s[s.length >> 1] : 0\n}\n\n/**\n * 한 밴드의 좌·우 단 읽기 순서. 기본은 좌단 전체 → 우단 전체이되,\n * - 두 단 맨 위 같은 높이의 짧은 줄(쪽번호·머리말)은 먼저,\n * - 한 단에만 있고 다른 단 첫 줄보다 위에 큰 틈으로 떨어진 짧은 줄(그림 옆 캡션)은 두 단보다 먼저,\n * - 단 아래쪽 본문보다 작은 글자로 이어지는 각주(번호 줄 포함)는 두 단 본문 뒤에 좌 → 우로 낸다.\n */\n/** 단 아래 각주 띠의 줄 수 — 아래에서부터 본문보다 작은 글자 줄이고 그중 하나가 각주 번호로 시작할 때 */\nexport function noteLines(lines: NormItem[][]): number {\n // 본문 크기는 줄의 25% 이상을 차지하는 가장 큰 글자 크기 — 각주 줄이 본문보다 많은 단은 중앙값이 각주 크기가 되고,\n // 단 맨 위가 작은 캡션인 단(ODL 008 우단)은 첫 줄이 본문이 아니다\n const sizes = lines.map(l => Math.max(...l.map(i => i.fontSize)))\n const body = Math.max(0, ...sizes.filter(s => sizes.filter(o => Math.abs(o - s) < 0.5).length >= lines.length * 0.25))\n let n = 0\n while (n < lines.length && Math.max(...lines[lines.length - 1 - n].map(i => i.fontSize)) <= body * 0.9) n++\n if (n === 0 || lines.length - n < 3) return 0\n const tail = lines.slice(lines.length - n)\n return tail.some(l => NOTE_START.test(mergeLineSimple(l))) ? n : 0\n}\n\nfunction columnPair(left: NormItem[], right: NormItem[]): NormItem[][] {\n if (left.length === 0 || right.length === 0) return [left, right].filter(g => g.length > 0)\n const byLine = (side: NormItem[]) => groupByY([...side].sort((a, b) => b.y - a.y || a.x - b.x))\n let L = byLine(left), R = byLine(right)\n const out: NormItem[][] = []\n const pitch = (lines: NormItem[][]) => medianOf(lines.slice(1).map((l, i) => lines[i][0].y - l[0].y))\n const fs = medianOf([...left, ...right].map(i => i.fontSize)) || 10\n // 두 단 맨 위 같은 높이 줄 — 아래로 큰 틈이 있으면 쪽 머리. 틈은 그 줄 글자 크기로도 잰다: 슬라이드 두 패널 제목(11pt, 줄 간격 21pt)은\n // 쪽 글 대부분이 5pt 차트 라벨이라 중앙값 기준으로는 꺾인 제목의 줄 간격도 큰 틈이 되어 두 제목이 한 줄로 붙었다(ODL 199)\n const headFs = (line: NormItem[]) => Math.max(fs, ...line.map(i => i.fontSize))\n if (L.length > 1 && R.length > 1 && Math.abs(L[0][0].y - R[0][0].y) <= 2 &&\n L[0][0].y - L[1][0].y >= headFs(L[0]) * 2 && R[0][0].y - R[1][0].y >= headFs(R[0]) * 2) {\n out.push([...L[0], ...R[0]])\n L = L.slice(1); R = R.slice(1)\n }\n // 다른 단보다 위에 떨어진 머리 띠\n const lift = (A: NormItem[][], B: NormItem[][]): number => {\n if (A.length < 2 || B.length === 0) return 0\n let n = 0\n while (n < A.length && A[n][0].y > B[0][0].y + 2) n++\n // 캡션급 짧은 띠만 — 단 본문 전체가 각주 위에 떨어져 있는 경우(ODL 013)는 머리 띠가 아니다\n if (n === 0 || n >= A.length || n > 6 || n * 2 >= A.length) return 0\n return A[n - 1][0].y - A[n][0].y >= Math.max(pitch(A.slice(n)) * 1.8, fs * 2) ? n : 0\n }\n const nR = lift(R, L), nL = nR ? 0 : lift(L, R)\n if (nR) { out.push(R.slice(0, nR).flat()); R = R.slice(nR) }\n if (nL) { out.push(L.slice(0, nL).flat()); L = L.slice(nL) }\n const kL = noteLines(L), kR = noteLines(R)\n if (kL === 0 && kR === 0) return [...out, L.flat(), R.flat()].filter(g => g.length > 0)\n return [...out, L.slice(0, L.length - kL).flat(), R.slice(0, R.length - kR).flat(),\n L.slice(L.length - kL).flat(), R.slice(R.length - kR).flat()].filter(g => g.length > 0)\n}\n\n/** Keep a compact multi-column table above two-column prose in its own band. */\nexport function topTableBand(items: NormItem[]): { top: NormItem[]; rest: NormItem[] } | null {\n const lines = groupByY(items)\n if (lines.length < 12) return null\n for (let n = 4; n < Math.min(lines.length - 5, 16); n++) {\n const upper = lines.slice(0, n)\n if (upper.filter(line => line.length >= 3).length < 3) continue\n const gap = upper[n - 1][0].y - lines[n][0].y\n const sizes = upper.flat().map(i => i.fontSize).filter(size => size > 0).sort((a, b) => a - b)\n if (gap < Math.max(18, (sizes[Math.floor(sizes.length / 2)] ?? 10) * 1.8)) continue\n const top = upper.flat()\n const candidate = detectClusterTables(top.map(i => ({\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\n fontSize: i.fontSize, fontName: i.fontName, hasSpaceBefore: i.hasSpaceBefore, syntheticSpace: i.syntheticSpace, spaceAfter: i.spaceAfter,\n })), 1)\n if (!candidate.some(t => t.table.cols >= 3 && t.table.rows >= 3 && t.usedItems.size >= top.length * 0.75)) continue\n return { top, rest: lines.slice(n).flat() }\n }\n return null\n}\n\n/** Rebuild a three-tier header whose grouped labels sit above repeated data columns. */\nexport function tieredHeaderTable(items: NormItem[], pageNum: number): IRBlock | null {\n const lines = groupByY(items).map(line => [...line].sort((a, b) => a.x - b.x))\n if (lines.length < 5) return null\n const cols = lines[3].length\n if (cols < 5 || (cols - 1) % 2 !== 0 || lines[0].length !== 1 ||\n lines[1].length !== 3 || lines[2].length !== cols - 1 ||\n !lines.slice(3).every(line => line.length === cols) ||\n Math.abs(lines[1][0].x - lines[3][0].x) > 30) return null\n const splitX = (lines[1][1].x + lines[1][2].x) / 2\n const half = (cols - 1) / 2\n if (lines[2].slice(0, half).some(i => i.x >= splitX) ||\n lines[2].slice(half).some(i => i.x < splitX)) return null\n const cell = (text: string, colSpan = 1, rowSpan = 1, isHeader = false): IRCell =>\n ({ text, colSpan, rowSpan, ...(isHeader ? { isHeader: true } : {}) })\n const empty = () => cell(\"\")\n const grid: IRCell[][] = [\n [cell(lines[1][0].text, 1, 3, true), cell(lines[0][0].text, cols - 1, 1, true), ...Array.from({ length: cols - 2 }, empty)],\n [empty(), cell(lines[1][1].text, half, 1, true), ...Array.from({ length: half - 1 }, empty),\n cell(lines[1][2].text, half, 1, true), ...Array.from({ length: half - 1 }, empty)],\n [empty(), ...lines[2].map(i => cell(i.text, 1, 1, true))],\n ...lines.slice(3).map(line => line.map(i => cell(i.text))),\n ]\n return {\n type: \"table\", pageNumber: pageNum, bbox: computeBBox(items, pageNum),\n table: { rows: grid.length, cols, cells: grid, hasHeader: true },\n }\n}\n\n/** Separate stacked, captioned tables before a two-column body is examined. */\nexport function stackedTableBands(items: NormItem[]): { tables: NormItem[][]; between: NormItem[][]; caption: NormItem[]; body: NormItem[] } | null {\n const lines = groupByY(items)\n if (lines.length < 16) return null\n const dense = lines.map(line => line.length >= 6 &&\n Math.max(...line.map(i => i.x + i.w)) - Math.min(...line.map(i => i.x)) >= 300)\n const runs: Array<{ start: number; end: number }> = []\n for (let i = 0; i < dense.length;) {\n if (!dense[i]) { i++; continue }\n const start = i\n while (i < dense.length && dense[i]) i++\n if (i - start >= 2) runs.push({ start, end: i })\n }\n if (runs.length < 2 || runs[0].start > 2) return null\n const caption = (start: number, end: number) =>\n lines.slice(start, end).some(line => /^Table\\s+\\d+\\s*[:.]/i.test(mergeLineSimple(line).trim()))\n while (runs.length > 0 && !caption(runs[runs.length - 1].end, lines.length)) runs.pop()\n if (runs.length < 2) return null\n if (runs.some((run, i) => !caption(run.end, runs[i + 1]?.start ?? lines.length))) return null\n const tables = runs.map(run => lines.slice(run.start, run.end).flat())\n const between = runs.slice(0, -1).map((run, i) => lines.slice(run.end, runs[i + 1].start).flat())\n const tail = lines.slice(runs[runs.length - 1].end)\n let bodyStart = tail.length\n for (let i = 1; i < Math.min(tail.length, 10); i++) {\n if (tail[i - 1][0].y - tail[i][0].y >= 24) { bodyStart = i; break }\n }\n return { tables, between, caption: tail.slice(0, bodyStart).flat(), body: tail.slice(bodyStart).flat() }\n}\n\n/** Three sparse title cards are independent reading regions, not table columns. */\nexport function threeColumnCards(items: NormItem[]): NormItem[][] | null {\n const lines = groupByY(items)\n if (lines.length < 5) return null\n for (let n = 1; n < lines.length - 2; n++) {\n const labels = [...lines[n]].sort((a, b) => a.x - b.x)\n if (labels.length !== 3 || labels.some(i => i.text.trim().length < 3 || i.text.length > 40)) continue\n if (labels[1].x - (labels[0].x + labels[0].w) < 70 ||\n labels[2].x - (labels[1].x + labels[1].w) < 70) continue\n if (!labels.every(i => i.fontName === labels[0].fontName && Math.abs(i.fontSize - labels[0].fontSize) < 1)) continue\n // A title separated from the cards, followed by three aligned value regions.\n if (lines[n - 1][0].y - labels[0].y < labels[0].fontSize * 2) continue\n const below = lines.slice(n + 1)\n const firstContent = below[0]\n if (labels[0].y - firstContent[0].y < labels[0].fontSize * 2) continue\n const boundaries = [\n (labels[0].x + labels[0].w + labels[1].x) / 2,\n (labels[1].x + labels[1].w + labels[2].x) / 2,\n ]\n if (!boundaries.every((x, i) => x > labels[i].x + labels[i].w && x < labels[i + 1].x)) continue\n let end = below.length\n for (let j = 1; j < below.length; j++) {\n if (below[j - 1][0].y - below[j][0].y > labels[0].fontSize * 5) { end = j; break }\n }\n const region = [labels, ...below.slice(0, end)].flat()\n const cards = [0, 1, 2].map(c => region.filter(i => c === 0 ? i.x < boundaries[0] : c === 1 ? i.x >= boundaries[0] && i.x < boundaries[1] : i.x >= boundaries[1]))\n if (cards.some(c => c.length < 2 || !c.some(i => i.y < labels[0].y))) continue\n const upper = lines.slice(0, n).flat()\n const lower = below.slice(end).flat()\n return [upper, ...cards, lower].filter(g => g.length > 0)\n }\n return null\n}\n\n/** A wide infographic may place three card titles at different heights above aligned body columns. */\nexport function threeColumnInfographic(items: NormItem[]): NormItem[][] | null {\n const lines = groupByY(items)\n if (lines.length < 12) return null\n for (let n = 4; n < lines.length - 6; n++) {\n if (lines[n - 1][0].y - lines[n][0].y < 35) continue\n const lower = lines.slice(n)\n const counts = new Map<number, number>()\n for (const item of lower.flat()) {\n if (item.text.trim().length < 12) continue\n const x = Math.round(item.x / 5) * 5\n counts.set(x, (counts.get(x) ?? 0) + 1)\n }\n const anchors = [...counts].filter(([, count]) => count >= 2).map(([x]) => x).sort((a, b) => a - b)\n if (anchors.length !== 3 || anchors[1] - anchors[0] < 120 || anchors[2] - anchors[1] < 120) continue\n const cuts = [(anchors[0] + anchors[1]) / 2, (anchors[1] + anchors[2]) / 2]\n let footerStart = lower.length\n for (let j = 1; j < lower.length; j++) {\n if (lower[j - 1][0].y - lower[j][0].y >= 70) { footerStart = j; break }\n }\n const region = lower.slice(0, footerStart).flat()\n const cards = [\n region.filter(i => i.x < cuts[0]),\n region.filter(i => i.x >= cuts[0] && i.x < cuts[1]),\n region.filter(i => i.x >= cuts[1]),\n ]\n if (cards.some(card => card.length < 4 || !card.some(i => i.text.length >= 50))) continue\n return [lines.slice(0, n).flat(), ...cards, lower.slice(footerStart).flat()].filter(group => group.length > 0)\n }\n return null\n}\n\n/**\n * 그림이 단마다 엇갈려 놓인 쪽 — 본문 단 옆에 그림·캡션 단이 나란한 띠와 전폭 본문 띠가 번갈아 온다(ODL 140). 쪽 전체로는\n * 전폭 줄이 거터를 가려 두 단이 안 보이므로, 전폭으로 이어진 본문 줄을 경계로 띠를 나누고 띠마다 글과 그 높이의 그림이\n * 하나도 걸치지 않는 세로 빈 띠(8pt 이상, 가운데 30~70%)를 찾는다. 두 단으로 갈린 띠가 하나라도 있으면 띠 순서대로\n * [좌 단, 우 단] 또는 [띠] 로 낸다.\n */\nexport function figureColumnBands(items: NormItem[], figures: ColRect[]): NormItem[][] | null {\n const lines = groupByY([...items].sort((a, b) => b.y - a.y || a.x - b.x))\n if (lines.length < 10) return null\n const minX = Math.min(...items.map(i => i.x)), maxX = Math.max(...items.map(i => i.x + i.w))\n const span = maxX - minX\n if (span < 200) return null\n const full = (line: NormItem[]) => {\n const s = [...line].sort((a, b) => a.x - b.x)\n const fs = Math.max(...s.map(i => i.fontSize))\n if (s[s.length - 1].x + s[s.length - 1].w - s[0].x < span * 0.75) return false\n // 본문 줄과 옆 단 캡션 줄이 같은 높이에 나란하면 한 줄로 묶인다 — 틈 양쪽 글자 크기가 다르면 두 단의 줄이다\n const sizeOf = (part: NormItem[]) => {\n const by = new Map<number, number>()\n for (const it of part) by.set(it.fontSize, (by.get(it.fontSize) ?? 0) + it.text.length)\n return [...by].sort((a, b) => b[1] - a[1])[0][0]\n }\n if (s.slice(1).some((it, k) => it.x - (s[k].x + s[k].w) >= fs * 0.6 &&\n Math.abs(sizeOf(s.slice(0, k + 1)) - sizeOf(s.slice(k + 1))) >= 0.5)) return false\n return s.slice(1).every((it, k) => it.x - (s[k].x + s[k].w) < fs * 2)\n }\n const segs: { full: boolean; lines: NormItem[][] }[] = []\n for (const line of lines) {\n const f = full(line), last = segs[segs.length - 1]\n if (last && last.full === f) last.lines.push(line)\n else segs.push({ full: f, lines: [line] })\n }\n let split = false\n const out: NormItem[][] = []\n for (const seg of segs) {\n const flat = seg.lines.flat()\n if (seg.full || seg.lines.length < 3) { out.push(flat); continue }\n const top = Math.max(...flat.map(i => i.y + (i.h || i.fontSize))), bottom = Math.min(...flat.map(i => i.y))\n const rects: ColRect[] = [...flat.map(i => ({ x: i.x, y: i.y, w: i.w, h: i.h || i.fontSize })),\n ...figures.filter(f => f.y < top && f.y + f.h > bottom)]\n let best: { x: number; w: number } | null = null, run: number | null = null\n // 후보 수 상한 400 — 오염 좌표 쪽만 성기게 (detectPanelGutters 와 같음)\n const step = Math.max(2, span * 0.4 / 400)\n for (let x = minX + span * 0.3; x <= minX + span * 0.7; x += step) {\n const empty = !rects.some(r => r.x < x && r.x + r.w > x)\n if (empty && run === null) run = x\n if ((!empty || x + step > minX + span * 0.7) && run !== null) {\n if (x - run >= 8 && (!best || x - run > best.w)) best = { x: (run + x) / 2, w: x - run }\n run = null\n }\n }\n if (!best) { out.push(flat); continue }\n const left = flat.filter(i => i.x + i.w <= best!.x), right = flat.filter(i => i.x >= best!.x)\n // 단 안 줄에 큰 틈이 있으면(표 행) 가르지 않는다 — 그림 옆 표가 반으로 갈린다(ODL 116)\n const gapped = (side: NormItem[]) => groupByY([...side].sort((a, b) => b.y - a.y || a.x - b.x)).some(l => {\n const s = [...l].sort((a, b) => a.x - b.x)\n return s.slice(1).some((it, k) => it.x - (s[k].x + s[k].w) >= Math.max(it.fontSize, s[k].fontSize) * 2)\n })\n if (left.length === 0 || right.length === 0 || gapped(left) || gapped(right)) { out.push(flat); continue }\n split = true\n // 한쪽 \"단\" 이 반대쪽과 같은 높이의 줄 하나뿐이면 전폭 줄의 반 토막이고 그 틈은 낱말 간격이다(KS X ISO 704 21쪽: 첫 낱말 크기 차로 전폭\n // 줄이 두 단 줄로 잘못 잡힌 문단 끝 석 줄에서 8pt 낱말 틈을 단 틈으로 가르자 줄 반쪽이 다음 줄 뒤로 갔다) — 그 무리는 가르지 않는다. 쪽은 그대로\n // 띠마다 읽는다: 띠를 놓으면 쪽 전체 클러스터 표 감지가 그림 라벨과 본문을 6열 가짜 표로 묶었다. 반대쪽에 없는 높이의 한 줄(쪽 꼬리말)은 단이다\n const halfLine = (side: NormItem[], other: NormItem[]) =>\n groupByY([...side].sort((a, b) => b.y - a.y || a.x - b.x)).length === 1 && other.some(o => Math.abs(o.y - side[0].y) <= 3)\n if (halfLine(left, right) || halfLine(right, left)) { out.push(flat); continue }\n // 한 단의 끝줄이 다른 단 맨 아래보다도 아래에 있으면(쪽 꼬리말 \"312 | Grouper …\") 두 단 뒤에 온다. 같은 단 줄 간격으로 이어진 줄은\n // 그 단의 글이다 — 큰 틈(글자 크기 3배 넘게) 뒤의 줄부터 꼬리(옆 단 캡션보다 아래로 내려온 문단 끝 두 줄이 캡션 뒤로 갔다, ODL 133)\n const low = (side: NormItem[], other: NormItem[]) => {\n const floor = Math.min(...other.map(i => i.y)) - Math.max(...other.map(i => i.fontSize))\n const ls = groupByY([...side].sort((a, b) => b.y - a.y || a.x - b.x))\n const k = ls.findIndex((l, n) => l[0].y < floor && (n === 0 || ls[n - 1][0].y - l[0].y > 3 * Math.max(...ls[n - 1].map(i => i.fontSize))))\n return k < 0 ? [] : ls.slice(k).flat()\n }\n const tail = [...low(left, right), ...low(right, left)]\n out.push(left.filter(i => !tail.includes(i)), right.filter(i => !tail.includes(i)), tail)\n }\n return split ? out.filter(g => g.length > 0) : null\n}\n","import type { IRBlock } from \"../types.js\"\nimport { computeBBox, dominantStyle, groupByY, mergeLineSimple, type NormItem } from \"./text-line.js\"\nimport { noteLines } from \"./page-regions.js\"\n\n/** Attach an oversized initial to the first body line it visually starts. */\nexport function attachDropCaps(lines: NormItem[][]): NormItem[][] {\n const result = lines.map(line => [...line])\n const sizes = result.flat().map(item => item.fontSize).filter(size => size > 0).sort((a, b) => a - b)\n const bodySize = sizes[Math.floor(sizes.length / 2)] ?? 0\n if (!bodySize) return result\n for (let source = 0; source < result.length; source++) {\n const cap = result[source].find(item => /^[A-Z]$/.test(item.text) && item.fontSize >= bodySize * 2.5 &&\n item.h >= bodySize * 2.5)\n if (!cap) continue\n const target = result.findIndex((line, index) => index < source && line.some(item =>\n item !== cap && /^[a-z]/.test(item.text) &&\n item.x >= cap.x + cap.w && item.x - (cap.x + cap.w) <= bodySize &&\n item.y > cap.y && item.y < cap.y + cap.h))\n if (target < 0) continue\n result[source].splice(result[source].indexOf(cap), 1)\n result[target].push(cap)\n }\n return result.filter(line => line.length > 0)\n}\n\n/** A large title in the left margin can precede a long, independent prose region. */\nexport function splitSidebarTitleRegion(items: NormItem[]): NormItem[][] | null {\n if (items.length < 30) return null\n const sizes = items.map(item => item.fontSize).filter(size => size > 0).sort((a, b) => a - b)\n const bodySize = sizes[Math.floor(sizes.length / 2)]\n const minX = Math.min(...items.map(item => item.x))\n const maxX = Math.max(...items.map(item => item.x + item.w))\n const minY = Math.min(...items.map(item => item.y))\n const maxY = Math.max(...items.map(item => item.y))\n const spanX = maxX - minX\n const spanY = maxY - minY\n if (spanX < 300 || spanY < 300) return null\n const titles = items.filter(item => item.fontSize >= bodySize * 2.2 &&\n item.x < minX + spanX * 0.4 && item.y > minY + spanY * 0.55 &&\n item.text.trim().length >= 2)\n if (titles.length < 2 || titles.length > 4) return null\n const titleLeft = Math.min(...titles.map(item => item.x))\n const titleRight = Math.max(...titles.map(item => item.x + item.w))\n const titleTop = Math.max(...titles.map(item => item.y))\n if (Math.max(...titles.map(item => Math.abs(item.x - titleLeft))) > bodySize * 2 ||\n titleRight > minX + spanX * 0.55) return null\n const prose = items.filter(item => item.x >= titleRight + 20 && item.fontSize < bodySize * 2.2 &&\n item.y <= titleTop + bodySize && item.y >= minY + spanY * 0.08)\n if (prose.length < 20 || prose.reduce((n, item) => n + item.text.length, 0) < 500) return null\n const proseLeft = Math.min(...prose.map(item => item.x))\n const bodyBottom = Math.min(...prose.map(item => item.y))\n const bodyTop = Math.max(...prose.map(item => item.y))\n if (bodyTop < titleTop - bodySize * 3 || bodyBottom > minY + spanY * 0.4) return null\n const titleSet = new Set(titles)\n const upper: NormItem[] = [], sidebar: NormItem[] = [], body: NormItem[] = [], footer: NormItem[] = []\n for (const item of items) {\n if (titleSet.has(item)) sidebar.push(item)\n else if (item.y > titleTop + bodySize * 3) upper.push(item)\n else if (item.y < bodyBottom - bodySize * 3) footer.push(item)\n else if (item.x >= proseLeft - bodySize) body.push(item)\n else return null\n }\n return [upper, sidebar, body, footer]\n}\n\n/** A short column pair below a separate full-width region. Keep source items intact. */\nexport function splitTrailingColumnRegion(items: NormItem[]): NormItem[][] | null {\n if (items.length < 16) return null\n const lines = groupByY(items)\n if (lines.length < 9) return null\n const ys = lines.map(line => line.reduce((sum, item) => sum + item.y, 0) / line.length)\n const gaps = ys.slice(0, -1).map((y, index) => y - ys[index + 1])\n const regular = gaps.filter(gap => gap > 2 && gap < 24).sort((a, b) => a - b)\n if (regular.length < 4) return null\n const leading = regular[Math.floor(regular.length / 2)]\n let start = -1\n for (let i = 0; i < gaps.length; i++) {\n if (gaps[i] > Math.max(22, leading * 1.8) && lines.length - i - 1 >= 5) start = i + 1\n }\n if (start < 2) return null\n const upper = lines.slice(0, start).flat()\n const lowerLines = lines.slice(start)\n const lower = lowerLines.flat()\n const fontSizes = lower.map(item => item.fontSize).filter(size => size > 0).sort((a, b) => a - b)\n const bodySize = fontSizes[Math.floor(fontSizes.length / 2)] ?? 0\n const minX = Math.min(...lower.map(item => item.x))\n const maxX = Math.max(...lower.map(item => item.x + item.w))\n if (maxX - minX < 300) return null\n\n let best: { x: number; paired: number; balance: number } | null = null\n // 후보 수 상한 400 — 오염 좌표 쪽만 성기게 (detectPanelGutters 와 같음)\n const step = Math.max(2, (maxX - minX) * 0.3 / 400)\n for (let x = minX + (maxX - minX) * 0.35; x <= minX + (maxX - minX) * 0.65; x += step) {\n let paired = 0, crossed = 0, leftRows = 0, rightRows = 0\n let leftChars = 0, rightChars = 0\n for (const line of lowerLines) {\n const left = line.filter(item => item.x + item.w <= x)\n const right = line.filter(item => item.x >= x)\n if (line.length !== left.length + right.length) { crossed++; continue }\n if (left.length) leftRows++\n if (right.length) rightRows++\n if (!left.length || !right.length) continue\n const gap = Math.min(...right.map(item => item.x)) - Math.max(...left.map(item => item.x + item.w))\n const lChars = left.reduce((n, item) => n + item.text.length, 0)\n const rChars = right.reduce((n, item) => n + item.text.length, 0)\n if (gap < Math.max(10, bodySize * 1.25) || lChars < 28 || rChars < 28) continue\n paired++\n leftChars += lChars\n rightChars += rChars\n }\n if (paired < 4 || leftRows < 4 || rightRows < 4 || crossed > 1) continue\n const balance = Math.min(leftChars, rightChars) / Math.max(leftChars, rightChars)\n if (balance < 0.55) continue\n if (!best || paired > best.paired || (paired === best.paired && balance > best.balance)) best = { x, paired, balance }\n }\n if (!best) return null\n const left = lower.filter(item => item.x + item.w <= best.x)\n const right = lower.filter(item => item.x >= best.x)\n if (upper.length + left.length + right.length !== items.length) return null\n // 단 아래 작은 글자 각주 띠는 두 단 본문 뒤에 — 왼 단 각주가 오른 단보다 먼저 읽혔다(ODL 015 \"72 Gubash and Lootah, …\", 두 단 산문 columnPair 와 같은 규칙)\n const [L, R] = [left, right].map(side => groupByY([...side].sort((a, b) => b.y - a.y || a.x - b.x)))\n const kL = noteLines(L), kR = noteLines(R)\n if (kL === 0 && kR === 0) return [upper, left, right]\n return [upper, L.slice(0, L.length - kL).flat(), R.slice(0, R.length - kR).flat(), L.slice(L.length - kL).flat(), R.slice(R.length - kR).flat()]\n .filter(region => region.length > 0)\n}\n\n/**\n * 패널 글을 서체 런(같은 서체로 이어진 줄들)마다 문단으로 편다. 다른 서체 글이 뒤따르는 짧은 런(두 줄·60자 이하, 문장부호로\n * 끝나지 않음)은 패널 소제목이다 — 슬라이드·교재 옆 띠의 굵은 라벨(\"Cellular Cycle and Replication\" 다음 본문 서체 설명, ODL 118).\n */\nexport function panelBlocks(items: NormItem[], panel: IRBlock, pageNum: number): IRBlock[] {\n const lines = groupByY([...items].sort((a, b) => b.y - a.y || a.x - b.x))\n const faceOf = (line: NormItem[]) => dominantStyle(line)?.fontName ?? \"\"\n const runs: NormItem[][][] = []\n for (const line of lines) {\n const last = runs[runs.length - 1]\n if (last && faceOf(last[last.length - 1]) === faceOf(line)) last.push(line)\n else runs.push([line])\n }\n if (runs.length < 2) return [panel]\n return runs.map((run, k) => {\n const text = run.map(line => mergeLineSimple(line).trim()).filter(Boolean).join(\" \")\n const title = k + 1 < runs.length && run.length <= 2 && text.length <= 60 && /\\p{L}/u.test(text) && !/[.!?:;,]$/.test(text)\n const all = run.flat()\n return title\n ? { type: \"heading\" as const, level: 3, text, pageNumber: pageNum, bbox: computeBBox(all, pageNum), style: dominantStyle(all) }\n : { type: \"paragraph\" as const, text, pageNumber: pageNum, bbox: computeBBox(all, pageNum), style: dominantStyle(all) }\n })\n}\n","/**\n * XY-Cut++ 읽기 순서 알고리즘 (arXiv:2504.10258)\n *\n * OpenDataLoader PDF의 XYCutPlusPlusSorter를 TypeScript로 포팅.\n * Original work: Copyright 2025-2026 Hancom Inc. (Apache-2.0)\n * https://github.com/opendataloader-project/opendataloader-pdf\n *\n * 기존 XY-Cut 대비 개선 3종:\n * ① cross-layout 전폭 요소(제목 등) 마스크 후 Y 위치로 재삽입\n * ② 좁은 요소(쪽번호류) 아웃라이어 필터 후 수직 컷 재시도\n * ③ 양방향(수평/수직) 컷을 모두 계산해 더 큰 갭 선택 + 최소 갭 5pt\n */\n\nimport { groupByY, mergeLineSimple, dominantStyle, computeBBox, type NormItem } from \"./text-line.js\"\nimport { bodyLineJoins } from \"./line-wrap.js\"\n\n/** 재귀 깊이 제한 — 수천 아이템의 pathological 레이아웃에서 스택 오버플로 방지 */\nconst MAX_XYCUT_DEPTH = 50\n/** 분할 최소 갭 (pt) — 미세 갭(1px급) 분할 방지 (ODL MIN_GAP_THRESHOLD) */\nconst XYCUT_MIN_GAP = 5\n/** cross-layout 판정: 최대폭 대비 비율 (ODL beta) — ODL 기본값 2.0 (사실상 비활성) */\nconst CROSS_LAYOUT_BETA = 2.0\n/** cross-layout 판정: 수평 겹침 비율 최소값 */\nconst CROSS_OVERLAP_RATIO = 0.1\n/** cross-layout 판정: 최소 겹침 요소 수 */\nconst CROSS_MIN_OVERLAPS = 2\n/** cross-layout 마스크 상한 — 전체의 20% 초과 마스크 시 비활성 (단일 컬럼 문서 보호) */\nconst CROSS_MAX_MASK_RATIO = 0.2\n/** 좁은 요소 아웃라이어 필터: 영역 폭 대비 비율 (쪽번호·각주 마커) */\nconst NARROW_ELEMENT_WIDTH_RATIO = 0.1\n\n/** 산문 두 단 사이 거터 최소 폭 (pt) — 양쪽 정렬 두 단은 줄 끝 공백 폭을 빼면 8pt 안팎까지 좁다(낱말 사이는 3pt 안팎) */\nconst PROSE_GUTTER_MIN_GAP = 6\n\ninterface CutInfo {\n position: number\n gap: number\n}\n\ninterface WrapBand { top: number; bottom: number; sources: { item: NormItem; state: NormItem }[] }\n\n/** Geometrically confirmed wrap boundaries in the parent reading region. Keys are\n * original items in a leaf; row grouping retains their identity. Metadata expires\n * with the items and never enters the public IR. */\nexport const XY_WRAP_BANDS = new WeakMap<NormItem, WrapBand[]>()\nconst XY_WRAP_ITEMS = new WeakMap<NormItem, NormItem[]>()\nconst leaf = (items: NormItem[], wrapped: WrapBand[]): NormItem[][] => {\n wrapped = validWrapBands(items, wrapped)\n const source = wrapped.length ? [...items] : []\n for (const item of items) {\n if (wrapped.length) { XY_WRAP_BANDS.set(item, wrapped); XY_WRAP_ITEMS.set(item, source) }\n else { XY_WRAP_BANDS.delete(item); XY_WRAP_ITEMS.delete(item) }\n }\n return [items]\n}\n\n/** 하위 무리를 다시 자를 때 물려줄 단(세로) 절단 최소 틈 — 벡터 차트 바로 밑의 짧은 띠(네 줄 이하: 축 이름·범례·출처)만 쪽 문턱을 받는다.\n * 띠 높이로 다시 잰 문턱(최소 15pt)은 범례 항목 사이 18pt 틈을 단 경계로 갈랐다(ODL 077). 차트 밑이 아닌 무리는 종전대로 제 높이로 잰다 —\n * 서명란·숫자 행·한 줄 제목은 15~22pt 틈에서 갈라야 글 벤치가 맞고, 줄 많은 무리는 그 문턱으로 좁은 단을 가른다. 가로 절단은 늘 무리 문턱\n * (쌓인 짧은 줄이 한 줄로 붙었다, 중문 계약서) */\nexport function chartBandGap(group: NormItem[], gap: number, charts: { x1: number; x2: number; y1: number }[]): number {\n if (charts.length === 0 || groupByY([...group].sort((a, b) => b.y - a.y || a.x - b.x)).length > 4) return 0\n const x1 = Math.min(...group.map(i => i.x)), x2 = Math.max(...group.map(i => i.x + i.w))\n const top = Math.max(...group.map(i => i.y + i.fontSize)), em = Math.max(...group.map(i => i.fontSize))\n return charts.some(c => x1 >= c.x1 - 2 && x2 <= c.x2 + 2 && top <= c.y1 + 2 && c.y1 - top <= 2 * em) ? gap : 0\n}\n\nexport function xyCutOrder(items: NormItem[], gapThreshold: number, depth = 0, wrapped?: WrapBand[], columnGap = 0): NormItem[][] {\n if (items.length === 0) return []\n const newRegion = wrapped === undefined\n wrapped ??= inheritedWrapBands(items)\n if (items.length <= 2 || depth >= MAX_XYCUT_DEPTH) return leaf(items, wrapped)\n\n // Phase 1 (최상위에서만): cross-layout 전폭 요소 마스크\n if (depth === 0 && items.length >= 3) {\n const cross = identifyCrossLayoutItems(items)\n if (cross.size > 0 && cross.size <= items.length * CROSS_MAX_MASK_RATIO) {\n const rest = items.filter(i => !cross.has(i))\n if (rest.length > 0) {\n const groups = xyCutOrder(rest, gapThreshold, 1, undefined, columnGap)\n return mergeCrossLayoutGroups(groups, [...cross])\n }\n }\n }\n\n // Phase 3: 양방향 컷 계산 → 더 큰 갭 선택 (기존: Y 무조건 우선 → 2단 인터리브)\n const minGap = Math.max(XYCUT_MIN_GAP, gapThreshold)\n const sortedY = [...items].sort((a, b) => b.y - a.y)\n let hCut = findHorizontalCut(sortedY, wrapped)\n // Text assembly cannot affect a horizontal cut below its minimum gap.\n // Reuse the sorted projection when a fresh region needs wrap protection.\n if (newRegion && hCut.gap >= minGap) {\n wrapped = wrappedLineBands(items, wrapped)\n hCut = findHorizontalCut(sortedY, wrapped)\n }\n // 단(세로) 절단만 물려받은 최소 틈을 쓴다 — 가로 절단(쌓인 줄 사이)은 이 무리 문턱 그대로 (shortBandGap)\n const vMinGap = Math.max(minGap, columnGap)\n const vCut = findVerticalCutWithOutlierFilter(items, vMinGap)\n\n const hValid = hCut.gap >= minGap\n const vValid = (vCut.gap >= vMinGap || (vCut.gap >= PROSE_GUTTER_MIN_GAP && isProseGutter(items, vCut.position))) &&\n !splitsSpacedLabel(items, vCut.position)\n\n // 축 선택: 기본 Y 우선 (한국 공문서는 단일 컬럼 위주 — 코퍼스 검증 결과 Y 우선이 안정적).\n // 단, 수직 갭이 수평 갭보다 명백히 크면(1.5×) 컬럼 분리로 보고 X 우선\n // → 2단 레이아웃에서 문단 간 수평 갭이 단 사이 수직 갭보다 먼저 잡혀 행 단위로\n // 인터리브되는 문제 방지 (XY-Cut++ 양방향 컷의 보수적 적용)\n let useHorizontal: boolean\n if (hValid && vValid) useHorizontal = vCut.gap <= hCut.gap * 1.5 || staggeredSides(items, vCut.position)\n else if (hValid) useHorizontal = true\n else if (vValid) useHorizontal = false\n else return splitEdgeSpannedColumns(items, gapThreshold, depth) ?? leaf(items, wrapped) // 분할 불가 → 리프 노드\n\n if (useHorizontal) {\n const upper = items.filter(i => i.y > hCut.position)\n const lower = items.filter(i => i.y <= hCut.position)\n if (upper.length > 0 && lower.length > 0 && upper.length < items.length) {\n return [...xyCutOrder(upper, gapThreshold, depth + 1, wrapped, columnGap), ...xyCutOrder(lower, gapThreshold, depth + 1, wrapped, columnGap)]\n }\n } else {\n const left = items.filter(i => i.x + i.w / 2 < vCut.position)\n const right = items.filter(i => i.x + i.w / 2 >= vCut.position)\n if (left.length > 0 && right.length > 0 && left.length < items.length) {\n return [...xyCutOrder(left, gapThreshold, depth + 1, undefined, columnGap), ...xyCutOrder(right, gapThreshold, depth + 1, undefined, columnGap)]\n }\n }\n\n return leaf(items, wrapped)\n}\n\n/**\n * 균등배분 이름표(\"가. 일 시: …\")의 글자 사이 빈틈 — 양쪽이 걸친 줄마다 컷 바로 옆이 홀로 선 한글 한 글자씩이면\n * 단 경계가 아니라 한 낱말 안이다(issue1948). 두 단 본문은 컷 옆이 낱말·문장 조각이다.\n */\nfunction splitsSpacedLabel(items: NormItem[], cutX: number): boolean {\n const lone = (i: NormItem) => /^[가-힣]$/.test(i.text.trim())\n const sameRow = (a: NormItem, b: NormItem) => Math.min(a.y, b.y) > Math.max(a.y - a.h, b.y - b.h)\n const left = items.filter(i => i.x + i.w / 2 < cutX)\n const right = items.filter(i => i.x + i.w / 2 >= cutX)\n let spaced = 0\n for (const r of right) {\n if (right.some(o => o !== r && o.x < r.x && sameRow(o, r))) continue\n const row = left.filter(l => sameRow(l, r))\n if (row.length === 0) continue\n const l = row.reduce((a, b) => (b.x + b.w > a.x + a.w ? b : a))\n if (!lone(l) || !lone(r)) return false\n spaced++\n }\n return spaced > 0\n}\n\n/**\n * 세로 컷 양쪽이 같은 줄을 한 번도 나누지 않고 위아래로 번갈아(좌·우·좌·우) 놓이면 두 단이 아니라\n * 한 단 안의 들쭉날쭉한 줄이다 — 제목 줄마다 아래 오른쪽 끝에 붙인 \"(단위 : …)\" 줄(hwpx-02).\n * 두 단 본문은 양쪽 줄이 같은 높이에 나란히 놓인다.\n */\nfunction staggeredSides(items: NormItem[], cutX: number): boolean {\n const left = items.filter(i => i.x + i.w / 2 < cutX)\n const right = items.filter(i => i.x + i.w / 2 >= cutX)\n if (left.length * right.length > 200_000) return false\n const shareRow = (a: NormItem, b: NormItem) => Math.min(a.y, b.y) > Math.max(a.y - a.h, b.y - b.h)\n if (left.some(a => right.some(b => shareRow(a, b)))) return false\n const sides = [...items].sort((a, b) => b.y - a.y).map(i => i.x + i.w / 2 < cutX)\n let switches = 0\n for (let k = 1; k < sides.length; k++) if (sides[k] !== sides[k - 1]) switches++\n return switches >= 3\n}\n\n/**\n * cross-layout 요소 식별: 폭 ≥ beta×최대폭 + 다른 요소 2개 이상과 수평 겹침.\n * 전폭 제목/헤더가 컬럼 분할을 가로막는 것을 방지.\n */\nfunction identifyCrossLayoutItems(items: NormItem[]): Set<NormItem> {\n const cross = new Set<NormItem>()\n if (items.length < 3) return cross\n\n let maxWidth = 0\n for (const i of items) { if (i.w > maxWidth) maxWidth = i.w }\n const threshold = CROSS_LAYOUT_BETA * maxWidth\n\n for (const item of items) {\n if (item.w < threshold) continue\n let overlaps = 0\n for (const other of items) {\n if (other === item) continue\n const left = Math.max(item.x, other.x)\n const right = Math.min(item.x + item.w, other.x + other.w)\n const overlapW = right - left\n if (overlapW <= 0) continue\n const smaller = Math.min(item.w, other.w)\n if (smaller > 0 && overlapW / smaller >= CROSS_OVERLAP_RATIO) {\n overlaps++\n if (overlaps >= CROSS_MIN_OVERLAPS) break\n }\n }\n if (overlaps >= CROSS_MIN_OVERLAPS) cross.add(item)\n }\n return cross\n}\n\n/** cross-layout 요소를 Y 위치 기준으로 그룹 시퀀스에 재삽입 (각자 단독 그룹) */\nfunction mergeCrossLayoutGroups(groups: NormItem[][], cross: NormItem[]): NormItem[][] {\n if (cross.length === 0) return groups\n const sortedCross = [...cross].sort((a, b) => (b.y + b.h) - (a.y + a.h) || a.x - b.x)\n const groupTop = (g: NormItem[]) => {\n let top = -Infinity\n for (const i of g) { const t = i.y + i.h; if (t > top) top = t }\n return top\n }\n\n const result: NormItem[][] = []\n let gi = 0, ci = 0\n while (gi < groups.length || ci < sortedCross.length) {\n if (ci >= sortedCross.length) { result.push(groups[gi++]); continue }\n if (gi >= groups.length) { result.push([sortedCross[ci++]]); continue }\n const crossTop = sortedCross[ci].y + sortedCross[ci].h\n if (crossTop >= groupTop(groups[gi])) result.push([sortedCross[ci++]])\n else result.push(groups[gi++])\n }\n return result\n}\n\n/** Keep parent proof only while both original rows survive unchanged in this leaf. */\nfunction validWrapBands(items: NormItem[], bands: WrapBand[]): WrapBand[] {\n if (!bands.length) return []\n const present = new Set(items)\n return bands.filter(b => b.sources.every(({ item: a, state: s }) => present.has(a) &&\n a.text === s.text && a.x === s.x && a.y === s.y && a.w === s.w && a.h === s.h &&\n a.fontSize === s.fontSize && a.fontName === s.fontName && a.isHidden === s.isHidden &&\n a.hasSpaceBefore === s.hasSpaceBefore && a.syntheticSpace === s.syntheticSpace &&\n a.strike === s.strike && a.underline === s.underline && a.seq === s.seq && a.rotated === s.rotated))\n}\n\n/** A second extraction pass may reuse only the same unchanged reading leaf. */\nfunction inheritedWrapBands(items: NormItem[]): WrapBand[] {\n const source = XY_WRAP_ITEMS.get(items[0])\n const present = source?.length === items.length ? new Set(items) : null\n const sameLeaf = present && source!.every(item => present.has(item))\n return sameLeaf ? validWrapBands(items, XY_WRAP_BANDS.get(items[0]) ?? []) : []\n}\n\n/** Two completed records with a shared page-number column are independent rows.\n * tab-leaders keeps the leader's geometry as a whitespace item. Require a separate\n * numeric run and a wide label-to-number gap; a bare number at a prose end is not proof. */\nexport function tocRecordBoundaries(rows: NormItem[][]): Set<number> {\n const out = new Set<number>()\n if (rows.length < 2) return out\n const records = rows.map((row, index) => {\n const visible = row.filter(i => i.text.trim()).sort((a, b) => a.x - b.x)\n let start = visible.length - 1\n if (start < 1 || !/^\\d+$/.test(visible[start].text.trim())) return null\n for (; start > 0; start--) {\n const a = visible[start - 1], b = visible[start]\n if (!/^\\d+$/.test(a.text.trim()) || b.x - (a.x + a.w) > 0.5 * b.fontSize) break\n }\n const nums = visible.slice(start), label = visible.slice(0, start).filter(i => !/^[·.⋯…]{4,}$/.test(i.text.trim()))\n const fs = nums[0].fontSize\n if (!/^\\d{1,4}$/.test(nums.map(i => i.text.trim()).join(\"\")) || fs <= 0 ||\n !label.some(i => /[\\p{L}]/u.test(i.text)) ||\n nums[0].x - Math.max(...label.map(i => i.x + i.w)) < Math.max(2 * fs, 30)) return null\n return { right: Math.max(...nums.map(i => i.x + i.w)), fs, index }\n }).filter(record => record !== null).sort((a, b) => a.right - b.right)\n // A wrapped label can intervene between completed entries in the same page column.\n // Protect only boundaries after completed records, leaving its own wrap intact.\n for (let i = 0; i + 1 < records.length; i++) {\n const a = records[i], b = records[i + 1]\n if (Math.abs(a.right - b.right) <= 0.5 * Math.min(a.fs, b.fs) &&\n Math.abs(a.fs - b.fs) <= 0.15 * Math.min(a.fs, b.fs)) {\n if (a.index + 1 < rows.length) out.add(a.index)\n if (b.index + 1 < rows.length) out.add(b.index)\n }\n }\n return out\n}\n\n/** Fresh wrap evidence is needed only for a region with a possible horizontal cut. */\nfunction wrappedLineBands(items: NormItem[], inherited: WrapBand[]): WrapBand[] {\n // Reuse the paragraph assembler's width, pitch, font and new-item guards before\n // cutting a wide line-spacing band (e.g. 30pt pitch in a 10pt body).\n const rows = groupByY(items)\n const records = tocRecordBoundaries(rows)\n const lines = rows.map(row => {\n const box = computeBBox(row, 0)\n return { text: mergeLineSimple(row).replace(/<\\/?u>|~~/g, \"\"), left: box.x, right: box.x + box.width,\n y: row.reduce((n, i) => n + i.y, 0) / row.length, fontSize: dominantStyle(row)?.fontSize ?? 0,\n sources: row.map(item => ({ item, state: { ...item } })) }\n })\n const joins = bodyLineJoins(lines)\n const fresh = lines.slice(0, -1).flatMap((line, i) => joins[i] === \"\\n\" || records.has(i) || line.text.trim() === lines[i + 1].text.trim() ? [] :\n [{ top: line.y, bottom: lines[i + 1].y, sources: [...line.sources, ...lines[i + 1].sources] }])\n const freshKeys = new Set(fresh.map(b => `${b.top}:${b.bottom}`))\n return [...inherited.filter(b => !freshKeys.has(`${b.top}:${b.bottom}`)), ...fresh].sort((a, b) => b.top - a.top)\n}\n\n/** 수평 컷 — 기존 y-h 프로젝션 모델을 유지하고 확인된 줄 꺾임만 제외한다. */\nfunction findHorizontalCut(sorted: NormItem[], wrapped: WrapBand[]): CutInfo {\n if (sorted.length < 2) return { position: 0, gap: 0 }\n let largestGap = 0\n let position = 0\n let wrapIndex = 0\n\n for (let i = 1; i < sorted.length; i++) {\n const prevBottom = sorted[i - 1].y - sorted[i - 1].h\n const currTop = sorted[i].y\n const gap = prevBottom - currTop\n if (gap <= 0) continue // Same-row/overlapping glyphs cannot advance the descending cut cursor.\n const at = (prevBottom + currTop) / 2\n while (wrapIndex < wrapped.length && at <= wrapped[wrapIndex].bottom) wrapIndex++\n const band = wrapped[wrapIndex]\n if (gap > largestGap && !(band && at < band.top && at > band.bottom)) {\n largestGap = gap\n position = at\n }\n }\n return { position, gap: largestGap }\n}\n\n/**\n * 수직 컷(X축 분할) — 갭이 안 나오면 좁은 요소(쪽번호류) 제외 후 재시도.\n * 쪽번호가 2단 컬럼 사이 갭을 가로막는 경우 복구 (ODL ②).\n */\nfunction findVerticalCutWithOutlierFilter(items: NormItem[], minGap: number): CutInfo {\n const edgeCut = findVerticalCut(items)\n if (edgeCut.gap >= minGap) return edgeCut\n\n if (items.length >= 3) {\n let minX = Infinity, maxX = -Infinity\n for (const i of items) {\n if (i.x < minX) minX = i.x\n const r = i.x + i.w\n if (r > maxX) maxX = r\n }\n const narrowThreshold = (maxX - minX) * NARROW_ELEMENT_WIDTH_RATIO\n const filtered = items.filter(i => i.w >= narrowThreshold)\n // 아웃라이어는 소수여야 함 (쪽번호 1~2개) — 단어 단위 아이템이 대량 필터되면\n // 본문에서 가짜 컬럼 갭이 만들어지므로 70% 이상 유지될 때만 재시도\n if (filtered.length >= 2 && filtered.length < items.length && filtered.length >= items.length * 0.7) {\n const filteredCut = findVerticalCut(filtered)\n // 뺀 좁은 조각이 컷 자리에 걸쳐 같은 줄 양옆에 글을 두면 쪽번호가 아니라 줄 안 낱말이다 — 한 줄 제목\n // \"1-2) 폭력 대비 및 대응의 적절성\" 의 \"및\"(11pt)을 빼고 낸 23pt 틈으로 제목이 두 블록으로 갈렸다\n const sameRow = (a: NormItem, b: NormItem) => Math.abs(a.y - b.y) <= Math.max(a.fontSize, b.fontSize) * 0.5\n const inLine = items.some(r => r.w < narrowThreshold && r.x < filteredCut.position && r.x + r.w > filteredCut.position &&\n filtered.some(o => sameRow(o, r) && o.x + o.w <= r.x + 1) && filtered.some(o => sameRow(o, r) && o.x >= r.x + r.w - 1))\n if (filteredCut.gap > edgeCut.gap && filteredCut.gap >= minGap && !inLine) {\n return filteredCut\n }\n }\n }\n return edgeCut\n}\n\n/** 수직 컷 — X 프로젝션에서 가장 넓은 갭 */\nfunction findVerticalCut(items: NormItem[]): CutInfo {\n if (items.length < 2) return { position: 0, gap: 0 }\n const sorted = [...items].sort((a, b) => a.x - b.x || (a.x + a.w) - (b.x + b.w))\n let largestGap = 0\n let position = 0\n let prevRight: number | null = null\n\n for (const it of sorted) {\n const left = it.x\n const right = it.x + it.w\n if (prevRight !== null && left > prevRight) {\n const gap = left - prevRight\n if (gap > largestGap) {\n largestGap = gap\n position = (prevRight + left) / 2\n }\n }\n prevRight = prevRight === null ? right : Math.max(prevRight, right)\n }\n return { position, gap: largestGap }\n}\n\n/** A narrow vertical gap is a column gutter when both sides hold several full\n * prose lines side by side (not a label column or stacked blocks). */\nfunction isProseGutter(items: NormItem[], x: number): boolean {\n const sideLines = (side: NormItem[]) => {\n const lines: { y: number; chars: number; left: number; right: number }[] = []\n for (const item of [...side].sort((a, b) => b.y - a.y)) {\n const line = lines.find(l => Math.abs(l.y - item.y) <= 2)\n if (line) { line.chars += item.text.length; line.left = Math.min(line.left, item.x); line.right = Math.max(line.right, item.x + item.w) }\n else lines.push({ y: item.y, chars: item.text.length, left: item.x, right: item.x + item.w })\n }\n return lines\n }\n const left = sideLines(items.filter(i => i.x + i.w / 2 < x))\n const right = sideLines(items.filter(i => i.x + i.w / 2 >= x))\n if (left.length < 4 || right.length < 4) return false\n const prose = (lines: typeof left) => {\n const width = Math.max(...lines.map(l => l.right)) - Math.min(...lines.map(l => l.left))\n const full = lines.filter(l => l.chars >= 25 && l.right - l.left >= width * 0.6).length\n return full >= lines.length * 0.6\n }\n if (!prose(left) || !prose(right)) return false\n const span = (lines: typeof left) => [Math.min(...lines.map(l => l.y)), Math.max(...lines.map(l => l.y))]\n const [l0, l1] = span(left), [r0, r1] = span(right)\n return Math.min(l1, r1) - Math.max(l0, r0) >= Math.min(l1 - l0, r1 - r0) * 0.5\n}\n\n/** Two prose columns whose band starts or ends with a line crossing the gutter\n * (a caption or title under/over both columns): set that line apart at its edge\n * and read the columns left then right. Lines crossing in the middle keep the leaf. */\nfunction splitEdgeSpannedColumns(items: NormItem[], gapThreshold: number, depth: number): NormItem[][] | null {\n let minX = Infinity, maxX = -Infinity\n for (const i of items) { minX = Math.min(minX, i.x); maxX = Math.max(maxX, i.x + i.w) }\n const narrow = items.filter(i => i.w < (maxX - minX) * 0.5)\n if (narrow.length === items.length || narrow.length < 8) return null\n const cut = findVerticalCut(narrow)\n if (cut.gap < PROSE_GUTTER_MIN_GAP || !isProseGutter(narrow, cut.position)) return null\n const crossing = items.filter(i => i.x < cut.position && i.x + i.w > cut.position)\n const rest = items.filter(i => !crossing.includes(i))\n const restTop = Math.max(...rest.map(i => i.y)), restBottom = Math.min(...rest.map(i => i.y))\n const above = crossing.filter(i => i.y > restTop), below = crossing.filter(i => i.y < restBottom)\n if (above.length + below.length !== crossing.length) return null\n const left = rest.filter(i => i.x + i.w / 2 < cut.position), right = rest.filter(i => i.x + i.w / 2 >= cut.position)\n return [\n ...(above.length ? [above] : []),\n ...xyCutOrder(left, gapThreshold, depth + 1),\n ...xyCutOrder(right, gapThreshold, depth + 1),\n ...(below.length ? [below] : []),\n ]\n}\n","import type { IRBlock } from \"../types.js\"\nimport { attachDropCaps } from \"./local-regions.js\"\nimport { WrapLexicon, bodyLineJoins, wrapJoiner, startsNewItem, PARA_LAST_LINE, PARA_FIRST_LEFT } from \"./line-wrap.js\"\nimport { computeBBox, dominantStyle, mergeLineSimple, sortLineByX, type NormItem } from \"./text-line.js\"\nimport { tagScripts } from \"./script-items.js\"\nimport { XY_WRAP_BANDS, tocRecordBoundaries } from \"./xy-cut.js\"\n\n/** 문단 블록의 서체별 글자 수 — 앞머리만 굵은 문장(\"Definition 1. A universe…\")은 서체 차이로 제목이 아니다 */\nexport const FACE_CHARS = new WeakMap<IRBlock, Map<string, number>>()\n\n/** A numbered title and its differently styled subtitle precede body prose. */\nfunction hasNumberedStyledTitle(lines: NormItem[][]): boolean {\n if (lines.length < 4) return false\n const [title, subtitle, body] = lines\n const face = (line: NormItem[]) => line.every(i => i.fontName === line[0].fontName) ? line[0].fontName : null\n const a = face(title), b = face(subtitle), c = face(body)\n if (!a || !b || !c || a === b || b === c || a === c ||\n !/^\\d+(?:\\.\\d+)*\\.\\s+/.test(mergeLineSimple(title)) ||\n mergeLineSimple(title).length + mergeLineSimple(subtitle).length > 140 ||\n Math.abs(title[0].x - subtitle[0].x) > 30 ||\n title[0].y - subtitle[0].y > 30 || subtitle[0].y - body[0].y > 30) return false\n return true\n}\n\n/** Join wrapped source lines inside one reading region without changing item coordinates. */\nexport function pushLineParagraphs(out: IRBlock[], yLines: NormItem[][], pageNum: number, lex?: WrapLexicon): void {\n const lines = attachDropCaps(yLines).map(items => ({ items, text: mergeLineSimple(items) })).filter(l => l.text.trim())\n const geo = lines.map(l => {\n const b = computeBBox(l.items, pageNum)\n return { text: l.text, left: b.x, right: b.x + b.width, y: l.items.reduce((s, i) => s + i.y, 0) / l.items.length, fontSize: dominantStyle(l.items)?.fontSize ?? 0 }\n })\n const joins = bodyLineJoins(geo, lex)\n // A two-line leaf can lose the other line-pitch samples from its parent region.\n // Restore only those exact boundaries already confirmed before XY-cut.\n const first = lines[0]?.items[0]\n const bands = first ? XY_WRAP_BANDS.get(first) ?? [] : []\n const hasBand = (top: number, bottom: number) => {\n let lo = 0, hi = bands.length\n while (lo < hi) {\n const mid = (lo + hi) >>> 1\n if (bands[mid].top > top + 3) lo = mid + 1\n else hi = mid\n }\n for (; lo < bands.length && bands[lo].top >= top - 3; lo++) {\n if (Math.abs(bands[lo].bottom - bottom) <= 3) return true\n }\n return false\n }\n for (let i = 0; i + 1 < geo.length; i++) {\n if (joins[i] === \"\\n\" && Math.abs(geo[i + 1].fontSize - geo[i].fontSize) <= 0.15 * geo[i].fontSize &&\n !startsNewItem(geo[i].text, geo[i + 1].text) &&\n hasBand(geo[i].y, geo[i + 1].y)) {\n joins[i] = wrapJoiner(geo[i].text, geo[i + 1].text, lex)\n }\n }\n // 줄 끝 아이템 뒤에 공백 글리프가 찍힌 꺾임은 어절 경계다 — 어휘 증거·형태 규칙이 붙인 것도 띄운다 (markTrailingSpaceGlyphs)\n // 거꾸로 줄 끝 공백을 찍는 쪽에서 오른끝까지 찬 한글 줄이 공백 없이 끝나고 다음 줄이 영문·경로·연도 괄호(\"(ODA)\"·\"(/tmp\"·\"(’27~)\")로\n // 열면 앞 낱말에 붙은 원어·풀이다(\"공적개발원조⏎(ODA)\", \"하네스⏎(safety harness)\" — 약어만 든 괄호는 원문도 띄운 곳이 섞여 형태 규칙이\n // 띄운다). hwpx↔pdf 744쌍에서 이 꼴 78곳 모두 원문이 붙여 썼다. 한글·숫자로 여는 괄호(\"등⏎(국회의원)\"·\"지역⏎(2024년\")는 원문도 띄운\n // 곳이 섞이고, 덜 찬 줄은 원문 줄바꿈이다(ASEAN 노동장관회의 \"…역량 인증⏎(Advancing …)\")\n const blockRight = Math.max(...geo.map(g => g.right))\n for (let i = 0; i + 1 < lines.length; i++) {\n const last = lines[i].items.reduce((a, b) => (b.x + b.w > a.x + a.w ? b : a))\n if (joins[i] === \"\" && last.spaceAfter) joins[i] = \" \"\n else if (joins[i] === \" \" && last.spaceAfter === false && blockRight - geo[i].right < 0.2 * geo[i].fontSize &&\n /[가-힣]$/.test(geo[i].text.replace(/<\\/?u>|~~/g, \"\").trimEnd()) && /^\\((?:[A-Za-z/]|[’'‘])/.test(geo[i + 1].text.replace(/<\\/?u>|~~/g, \"\").trimStart())) joins[i] = \"\"\n }\n // Completed TOC entries remain independent even inside a short-pitch leaf.\n for (const i of tocRecordBoundaries(lines.map(line => line.items))) joins[i] = \"\\n\"\n // 큰 글자로 따로 선 장 번호(\"2\")는 아래 제목 줄과 다른 문단이다 (ODL 021)\n for (let i = 0; i + 1 < geo.length; i++) {\n if (/^\\d{1,2}$/.test(geo[i].text.trim()) && geo[i].fontSize >= geo[i + 1].fontSize * 1.2) joins[i] = \"\\n\"\n }\n if (hasNumberedStyledTitle(lines.map(line => line.items))) {\n joins[0] = \"\\n\"\n joins[1] = \"\\n\"\n }\n for (let i = 0; i < lines.length;) {\n const first = i\n let text = lines[i].text\n const items = [...lines[i].items]\n const srcLines = [lines[i].items]\n for (; i + 1 < lines.length && joins[i] !== \"\\n\"; i++) {\n text += joins[i] + lines[i + 1].text\n items.push(...lines[i + 1].items)\n srcLines.push(lines[i + 1].items)\n }\n // 첨자 태그 — 줄 이음 판정(평문)을 다 한 뒤에\n text = tagScripts(text, srcLines.map(l => sortLineByX([...l])))\n const block: IRBlock = { type: \"paragraph\", text, pageNumber: pageNum, bbox: computeBBox(items, pageNum), style: dominantStyle(items) }\n const faces = new Map<string, number>()\n for (const it of items) faces.set(it.fontName, (faces.get(it.fontName) ?? 0) + it.text.length)\n FACE_CHARS.set(block, faces)\n // 끝줄 기하 — 쪽 넘김 꺾임 잇기(joinPageBreakWraps) 재료\n PARA_LAST_LINE.set(block.bbox!, { right: geo[i].right, width: geo[i].right - geo[i].left, fontSize: geo[i].fontSize,\n spaceAfter: lines[i].items.reduce((a, b) => (b.x + b.w > a.x + a.w ? b : a)).spaceAfter })\n PARA_FIRST_LEFT.set(block.bbox!, geo[first].left)\n out.push(block)\n i++\n }\n}\n","/**\n * IRBlock[] 후처리 감지/변환.\n *\n * 헤딩 승격(폰트 크기·□마커), 의사 테이블 demote, 표 캡션 연결,\n * 한국어 리스트(공문서 계층 라벨 시퀀스), key-value 특수표,\n * 머리글/바닥글 반복 패턴 제거.\n */\n\nimport type { IRBlock, IRTable, ParseWarning } from \"../types.js\"\nimport { HEADING_RATIO_H1, HEADING_RATIO_H2, HEADING_RATIO_H3 } from \"../types.js\"\nimport { collapseEvenSpacing } from \"./text-line.js\"\nimport { FACE_CHARS } from \"./paragraph-lines.js\"\nimport type { PageNotes } from \"./footnotes.js\"\n\n// ═══════════════════════════════════════════════════════\n// 헤딩 감지 (폰트 크기 기반)\n// ═══════════════════════════════════════════════════════\n\nexport function computeMedianFontSizeFromFreq(freq: Map<number, number>): number {\n if (freq.size === 0) return 0\n let total = 0\n for (const count of freq.values()) total += count\n const sorted = [...freq.entries()].sort((a, b) => a[0] - b[0])\n const mid = Math.floor(total / 2)\n let cumulative = 0\n for (const [size, count] of sorted) {\n cumulative += count\n if (cumulative > mid) return size\n }\n return sorted[sorted.length - 1][0]\n}\n\n/**\n * 블록의 폰트 크기를 median과 비교하여 헤딩으로 승격.\n * - 150%+ → heading level 1\n * - 130%+ → heading level 2\n * - 115%+ → heading level 3\n * 조건: 짧은 텍스트 (200자 미만), 숫자만으로 구성되지 않음\n */\nexport function detectHeadings(blocks: IRBlock[], medianFontSize: number): void {\n for (let bi = 0; bi < blocks.length; bi++) {\n const block = blocks[bi]\n if (block.type !== \"paragraph\" || !block.text || !block.style?.fontSize) continue\n const text = block.text.trim()\n if (text.length === 0 || text.length > 200) continue\n // 숫자만이면 헤딩 아님 — 본문 2.5배 넘는 한두 자리 숫자가 바로 아래 큰 제목 위에 선 장 번호는 제목(ODL 021 \"2\" / \"The Lost Homeland\")\n if (/^\\d+$/.test(text) && !isChapterNumber(block, blocks[bi + 1], medianFontSize)) continue\n\n const ratio = block.style.fontSize / medianFontSize\n let level = 0\n if (ratio >= HEADING_RATIO_H1) level = 1\n else if (ratio >= HEADING_RATIO_H2) level = 2\n else if (ratio >= HEADING_RATIO_H3) level = 3\n\n if (level > 0) {\n block.type = \"heading\"\n block.level = level\n // PDF 균등배분 스페이스 제거 (\"기 본 현 황\" → \"기본현황\") — 홀로 선 한 글자 셋 이상 연속만. 줄 전체 한 글자 비율\n // 규칙(whole)은 기호·쌍점 토큰까지 한 글자로 세어 헤딩의 원문 띄어쓰기를 통째로 지웠다(\"□ 개 요\" → \"□개요\",\n // \"성 명 :\" → \"성명:\" — 끄면 hwpx↔pdf 9문서 나아지고 나빠진 문서 0)\n block.text = collapseEvenSpacing(text, false)\n }\n }\n}\n\nfunction isChapterNumber(block: IRBlock, next: IRBlock | undefined, medianFontSize: number): boolean {\n const size = block.style?.fontSize ?? 0\n const nextSize = next?.style?.fontSize ?? 0\n const text = next?.text?.trim() ?? \"\"\n const a = block.bbox, b = next?.bbox\n return /^\\d{1,2}$/.test(block.text?.trim() ?? \"\") && size >= medianFontSize * 2.5 &&\n !!next && (next.type === \"paragraph\" || next.type === \"heading\") && next.pageNumber === block.pageNumber &&\n nextSize >= medianFontSize * HEADING_RATIO_H1 && text.length > 0 && text.length <= 80 && !/^[\\d\\s.]+$/.test(text) &&\n !!a && !!b && a.y > b.y && a.y - (b.y + b.height) <= size * 2\n}\n\n/** A display title can be emitted as one heading block per visual line. Keep\n * the words together when the lines share an anchor and unusually large type. */\nexport function mergeStackedHeadingLines(blocks: IRBlock[], medianFontSize: number): void {\n for (let i = 0; i < blocks.length - 1;) {\n const a = blocks[i], b = blocks[i + 1]\n const ab = a.bbox, bb = b.bbox\n const af = a.style?.fontSize ?? 0, bf = b.style?.fontSize ?? 0\n const gap = ab && bb ? ab.y - (bb.y + bb.height) : Infinity\n const centered = ab && bb && a.style?.fontName === b.style?.fontName &&\n Math.abs((ab.x + ab.width / 2) - (bb.x + bb.width / 2)) <= Math.max(5, af * 0.5)\n const sameAnchor = ab && bb && Math.abs(ab.x - bb.x) <= 5\n // 같은 서체·크기로 왼쪽을 맞춰 줄간격만큼 붙은 두 제목 줄은 한 제목이 꺾인 것이다 — 꺾인 첫 줄은 여러 낱말로\n // 다음 줄보다 넓다(짧은 제목 둘이 붙은 \"Scope\"/\"Methods\" 는 별개)\n const wrapped = sameAnchor && a.style?.fontName === b.style?.fontName && af === bf &&\n ab.width >= bb.width * 0.95 && (a.text?.trim().split(/\\s+/).length ?? 0) >= 3\n // 가운데 맞춘 표시 제목은 줄마다 크기를 달리해도(33pt·24pt) 거의 붙어 있으면 한 제목이다\n const displayStack = centered && gap <= Math.min(af, bf) * 0.3 && Math.max(af, bf) <= Math.min(af, bf) * 1.45\n // 장 번호(\"2\")는 아래 제목과 다른 제목이다(isChapterNumber)\n if (a.type !== \"heading\" || b.type !== \"heading\" || !a.text || !b.text || /^\\d+$/.test(a.text.trim()) ||\n !ab || !bb || ab.page !== bb.page ||\n !(sameAnchor && af >= medianFontSize * 2 && bf >= medianFontSize * 2) && !wrapped &&\n !(centered && af >= medianFontSize * 1.25 && bf >= medianFontSize * 1.25) ||\n Math.abs(af - bf) > Math.max(af, bf) * 0.15 && !displayStack ||\n gap < -2 || gap > Math.max(af, bf) * 0.45 ||\n a.text.length > (centered ? 120 : 50) || b.text.length > (centered ? 120 : 50)) { i++; continue }\n a.text = `${a.text.trim()} ${b.text.trim()}`\n a.bbox = {\n ...ab,\n x: Math.min(ab.x, bb.x), y: Math.min(ab.y, bb.y),\n width: Math.max(ab.x + ab.width, bb.x + bb.width) - Math.min(ab.x, bb.x),\n height: Math.max(ab.y + ab.height, bb.y + bb.height) - Math.min(ab.y, bb.y),\n }\n blocks.splice(i + 1, 1)\n }\n}\n\n/**\n * A PDF heading can use the body point size but a separate face (often bold).\n * Font IDs are document-local, so compare each page's face with its prose face\n * and require surrounding whitespace before promoting a short, single line.\n */\nexport function detectTypographyHeadings(blocks: IRBlock[]): void {\n const byPage = new Map<number, IRBlock[]>()\n for (const block of blocks) {\n if (!block.pageNumber) continue\n const page = byPage.get(block.pageNumber) ?? []\n page.push(block)\n byPage.set(block.pageNumber, page)\n }\n\n for (const page of byPage.values()) {\n // Size-based headings already establish this page's typography; a second\n // face-based pass there tends to promote captions and emphasized prose.\n if (page.some(block => block.type === \"heading\")) continue\n const faceWeight = new Map<string, number>()\n for (const block of page) {\n if (block.type !== \"paragraph\" || !block.text || !block.style?.fontName) continue\n // 서체별 실제 글자 수로 센다 — 블록 대표 서체는 첫 조각 서체라, 굵은 머리말로 시작한 긴 문단(\"Alignment tuning. In …\")이\n // 문단 전체를 굵은 서체 몫으로 넘겨 본문 서체를 뒤집는다\n const faces = FACE_CHARS.get(block)\n if (faces) for (const [face, n] of faces) faceWeight.set(face, (faceWeight.get(face) ?? 0) + n)\n else faceWeight.set(block.style.fontName, (faceWeight.get(block.style.fontName) ?? 0) + block.text.length)\n }\n const bodyFace = [...faceWeight].sort((a, b) => b[1] - a[1])[0]?.[0]\n if (!bodyFace || (faceWeight.get(bodyFace) ?? 0) < 250) continue\n // A title set only in another face is never smaller than the prose around it;\n // smaller distinct faces are running heads, bylines, captions and notes.\n const sizeWeight = new Map<number, number>()\n for (const block of page) {\n if (block.type !== \"paragraph\" || !block.style?.fontSize) continue\n // 본문 서체 글자 수로 크기도 센다 — 굵은 머리말로 시작한 본문만 있어도 작은 캡션을 제목으로 올리지 않는다.\n const faces = FACE_CHARS.get(block)\n const chars = faces ? (faces.get(bodyFace) ?? 0) : block.style.fontName === bodyFace ? (block.text?.length ?? 0) : 0\n if (chars) sizeWeight.set(block.style.fontSize, (sizeWeight.get(block.style.fontSize) ?? 0) + chars)\n }\n const bodySize = [...sizeWeight].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 0\n\n for (let i = 0; i < page.length; i++) {\n const block = page[i]\n const { text, bbox, style } = block\n if (block.type !== \"paragraph\" || !text || !bbox || !style?.fontName || !style.fontSize) continue\n const title = text.trim()\n const numbered = /^\\d+(?:\\.\\d+)*\\.?\\s+[A-Z가-힣]/.test(title)\n // 본문 서체가 아닌 글자가 대부분이어야 한다 — 앞머리 굵은 표지 + 본문 서체 문장은 제목이 아니다\n const faces = FACE_CHARS.get(block)\n const total = faces ? [...faces.values()].reduce((a, b) => a + b, 0) : 0\n const bodyShare = faces && total ? (faces.get(bodyFace) ?? 0) / total : 0\n if (style.fontName === bodyFace || bodyShare > 0.4 || style.fontSize < bodySize * 0.95 || title.length < 3 || title.length > 120 ||\n /^\\d+$/.test(title) || /^(?:table|figure|fig\\.?|표|그림)\\s*\\d/i.test(title) ||\n /^(?:doi:|https?:|[•●○▪▫])/i.test(title) ||\n /^(?:over|under)\\s+\\d+$/i.test(title) || /^[\\d\\s.,:%+\\-–]+$/.test(title) ||\n bbox.height > style.fontSize * (numbered ? 2.4 : 1.6) || title.includes(\"\\n\")) continue\n\n const centerX = bbox.x + bbox.width / 2\n const nearby = page.filter(other => other !== block && other.bbox &&\n other.type !== \"image\" && other.type !== \"separator\" &&\n other.bbox.x < centerX && centerX < other.bbox.x + other.bbox.width)\n const above = nearby.filter(other => other.bbox!.y >= bbox.y + bbox.height)\n .sort((a, b) => a.bbox!.y - b.bbox!.y)[0]\n const below = nearby.filter(other => other.bbox!.y + other.bbox!.height <= bbox.y)\n .sort((a, b) => b.bbox!.y - a.bbox!.y)[0]\n const gapAbove = above ? above.bbox!.y - bbox.y - bbox.height : Infinity\n const gapBelow = below ? bbox.y - below.bbox!.y - below.bbox!.height : Infinity\n if (Math.max(gapAbove, gapBelow) < style.fontSize * (numbered ? 0.7 : 1.2)) continue\n\n block.type = \"heading\"\n block.level = 2\n }\n }\n}\n\n/** Restore titles from repeated card labels and a numbered title with a styled subtitle. */\nexport function detectDocumentStyleHeadings(blocks: IRBlock[]): void {\n const byPage = new Map<number, IRBlock[]>()\n for (const block of blocks) {\n if (!block.pageNumber) continue\n const page = byPage.get(block.pageNumber) ?? []\n page.push(block)\n byPage.set(block.pageNumber, page)\n }\n for (const page of byPage.values()) {\n const faceChars = new Map<string, number>()\n for (const block of page) {\n if (block.type !== \"paragraph\" || !block.text || !block.style?.fontName) continue\n faceChars.set(block.style.fontName, (faceChars.get(block.style.fontName) ?? 0) + block.text.length)\n }\n const bodyFace = [...faceChars].sort((a, b) => b[1] - a[1])[0]?.[0]\n if (!bodyFace || (faceChars.get(bodyFace) ?? 0) < 100) continue\n\n const labels = page.filter(b => b.type === \"paragraph\" && b.text && b.bbox && b.style?.fontName &&\n b.style.fontName !== bodyFace && b.text.trim().length >= 3 && b.text.trim().length <= 40)\n for (const label of labels) {\n const peers = labels.filter(b => b.style?.fontName === label.style?.fontName &&\n Math.abs(b.bbox!.y - label.bbox!.y) <= 3 && Math.abs(b.bbox!.x - label.bbox!.x) >= 60)\n if (peers.length < 2) continue\n const value = page.find(b => b.type === \"paragraph\" && b.bbox && b.style?.fontName !== label.style?.fontName &&\n b.bbox.x >= label.bbox!.x - 5 && b.bbox.x <= label.bbox!.x + 20 &&\n b.bbox.y < label.bbox!.y && label.bbox!.y - (b.bbox.y + b.bbox.height) <= 60)\n if (value) { label.type = \"heading\"; label.level = 2 }\n }\n\n const first = page.find(b => b.type !== \"image\" && b.type !== \"separator\")\n const second = page[page.indexOf(first!) + 1]\n const third = page[page.indexOf(first!) + 2]\n if (!first || !second || !third || (first.type !== \"list\" && first.type !== \"paragraph\") || second.type !== \"paragraph\" ||\n !first.text || !second.text || !first.bbox || !second.bbox || !first.style?.fontName || !second.style?.fontName ||\n !/^\\d+(?:\\.\\d+)*\\.\\s+/.test(first.text.trim()) ||\n first.style.fontName === bodyFace || second.style.fontName === bodyFace ||\n first.text.length + second.text.length > 140 ||\n Math.abs(first.bbox.x - second.bbox.x) > 30 ||\n first.bbox.y - (second.bbox.y + second.bbox.height) > (first.style.fontSize ?? 0) * 1.5 ||\n third.style?.fontName !== bodyFace) continue\n first.type = \"heading\"\n first.level = 1\n first.text = `${first.text.trim()} ${second.text.trim()}`\n first.bbox = {\n ...first.bbox,\n x: Math.min(first.bbox.x, second.bbox.x),\n y: Math.min(first.bbox.y, second.bbox.y),\n width: Math.max(first.bbox.x + first.bbox.width, second.bbox.x + second.bbox.width) - Math.min(first.bbox.x, second.bbox.x),\n height: Math.max(first.bbox.y + first.bbox.height, second.bbox.y + second.bbox.height) - Math.min(first.bbox.y, second.bbox.y),\n }\n blocks.splice(blocks.indexOf(second), 1)\n }\n}\n\n/** Carry an established heading face to its unrecognized siblings on the same page. */\nexport function detectSiblingStyleHeadings(blocks: IRBlock[]): void {\n const byPage = new Map<number, IRBlock[]>()\n for (const block of blocks) {\n const page = byPage.get(block.pageNumber ?? 0) ?? []\n page.push(block)\n byPage.set(block.pageNumber ?? 0, page)\n }\n for (const page of byPage.values()) {\n const bodyChars = new Map<string, number>()\n for (const block of page) {\n if (block.type !== \"paragraph\" || !block.text || !block.style?.fontName) continue\n const face = block.style.fontName\n bodyChars.set(face, (bodyChars.get(face) ?? 0) + block.text.length)\n }\n const bodyFace = [...bodyChars].sort((a, b) => b[1] - a[1])[0]?.[0]\n const anchors = page.filter(b => b.type === \"heading\" && b.text && b.style?.fontName && b.style.fontName !== bodyFace)\n for (const anchor of anchors) {\n const numbered = /^([A-Z])\\.\\d+\\s+/.exec(anchor.text!.trim())\n const lettered = /^([A-Z])\\s+/.exec(anchor.text!.trim())\n const peers = anchors.filter(b => b.style?.fontName === anchor.style?.fontName &&\n Math.abs((b.style?.fontSize ?? 0) - (anchor.style?.fontSize ?? 0)) < 0.5)\n if (peers.length < 2 && !numbered && !lettered) continue\n const candidates = page.filter(b => b.type === \"paragraph\" && b.text && b.bbox && b.style?.fontName === anchor.style?.fontName &&\n Math.abs((b.style?.fontSize ?? 0) - (anchor.style?.fontSize ?? 0)) < 0.5 &&\n b.text.trim().length >= 3 && b.text.trim().length <= 80 &&\n b.bbox.height <= (b.style?.fontSize ?? 0) * 1.6 &&\n /^[A-Z]/.test(b.text.trim()) && !/[.!?:;,]$/.test(b.text.trim()) &&\n !/^(?:Table|Figure|Fig\\.?|Appendix)\\s+\\d/i.test(b.text.trim()))\n const siblings = candidates.filter(b => numbered\n ? new RegExp(`^${numbered[1]}\\\\.\\\\d+\\\\s+`).test(b.text!.trim())\n : lettered ? /^[A-Z]\\s+/.test(b.text!.trim()) && Math.abs(anchor.bbox!.x - b.bbox!.x) < 8\n : peers.some(h => h.bbox && Math.abs(h.bbox.x - b.bbox!.x) < 8))\n if (siblings.length < (numbered || lettered ? 1 : 2)) continue\n for (const sibling of siblings) { sibling.type = \"heading\"; sibling.level = anchor.level ?? 2 }\n }\n }\n}\n\n/** Recognize a repeated series of styled page labels as section titles. */\nexport function detectRepeatedPageLabels(blocks: IRBlock[]): void {\n const byPage = new Map<number, IRBlock[]>()\n for (const block of blocks) {\n const page = byPage.get(block.pageNumber ?? 0) ?? []\n page.push(block)\n byPage.set(block.pageNumber ?? 0, page)\n }\n for (const page of byPage.values()) {\n const bodyChars = new Map<string, number>()\n for (const b of page) if (b.type === \"paragraph\" && b.text && b.style?.fontName) {\n const faces = FACE_CHARS.get(b)\n if (faces) for (const [face, n] of faces) bodyChars.set(face, (bodyChars.get(face) ?? 0) + n)\n else bodyChars.set(b.style.fontName, (bodyChars.get(b.style.fontName) ?? 0) + b.text.length)\n }\n const bodyFace = [...bodyChars].sort((a, b) => b[1] - a[1])[0]?.[0]\n const candidates = page.filter(b => b.type === \"paragraph\" && b.text && b.bbox && b.style?.fontName &&\n b.style.fontName !== bodyFace && b.bbox.height <= (b.style.fontSize ?? 0) * 1.6 &&\n b.text.trim().length >= 5 && b.text.trim().length <= 80)\n const groups = new Map<string, IRBlock[]>()\n for (const b of candidates) {\n const key = `${b.style!.fontName}:${b.style!.fontSize}`\n const group = groups.get(key) ?? []\n group.push(b)\n groups.set(key, group)\n }\n for (const group of groups.values()) {\n const colons = group.filter(b => /^[A-Z][^:]{2,60}:$/.test(b.text!.trim()))\n const numbered = group.filter(b => /^0?[1-9]\\d?\\s*[-–]\\s+[A-Z]/.test(b.text!.trim()))\n if (colons.length >= 2) for (const b of colons) { b.type = \"heading\"; b.level = 2 }\n if (numbered.length >= 4) for (const b of numbered) { b.type = \"heading\"; b.level = 2 }\n for (const b of colons) if (b.text!.trim() === \"Procedure:\") { b.type = \"heading\"; b.level = 2 }\n }\n }\n}\n\n/** The first content block, or the title below a running header, may be a section title. */\nexport function detectPageLeadHeadings(blocks: IRBlock[]): void {\n const byPage = new Map<number, IRBlock[]>()\n for (const b of blocks) {\n const page = byPage.get(b.pageNumber ?? 0) ?? []\n page.push(b)\n byPage.set(b.pageNumber ?? 0, page)\n }\n for (const page of byPage.values()) {\n // OCR 로 끼운 그림 속 글(style 없음 — mergeOcrImageRegions, 쪽 머리 로고)은 쪽 첫머리 판정에 끼지 않는다\n const content = page.filter(b => b.type !== \"image\" && b.type !== \"separator\" && !(b.type === \"paragraph\" && !b.style))\n const [first, second, third] = content\n if (!first?.bbox || !first.text || !first.style?.fontSize || !second) continue\n const firstText = first.text.trim()\n const captionLike = /^(?:Figure|Fig\\.?|Table|표|그림)\\s*\\d/i\n const hasImage = page.some(b => b.type === \"image\")\n const firstIsTitle = first.type === \"paragraph\" && firstText.length >= 5 && firstText.length <= 80 &&\n first.bbox.height <= first.style.fontSize * 1.6 &&\n (/^(?:CONTENTS|Table of Contents)$/i.test(firstText) ||\n (hasImage && /^[A-Z]/.test(firstText) && !captionLike.test(firstText) &&\n second.type === \"paragraph\" && /^[A-Z]/.test(second.text?.trim() ?? \"\") &&\n (second.text?.length ?? 0) >= 100 && second.bbox &&\n first.bbox.y - (second.bbox.y + second.bbox.height) >= first.style.fontSize))\n // A long prose region directly below an isolated first line is title evidence\n // even when a superscript makes the first line's box taller than its font.\n const firstAboveProse = first.type === \"paragraph\" && firstText.length >= 5 && firstText.length <= 80 &&\n first.bbox.height <= first.style.fontSize * 2 && second.type === \"paragraph\" &&\n second.bbox && second.bbox.height >= first.style.fontSize * 4 &&\n Math.abs(first.bbox.x - second.bbox.x) <= first.style.fontSize * 2 &&\n first.bbox.y - (second.bbox.y + second.bbox.height) >= first.style.fontSize * 2 &&\n !captionLike.test(firstText) && !/^(?:doi:|https?:|www\\.)/i.test(firstText)\n const firstIsSection = first.type === \"list\" && /^\\d+\\.\\s+[A-Z][A-Z\\s]{12,}$/.test(firstText) &&\n second.type === \"paragraph\" && (second.text?.length ?? 0) >= 100\n if (firstIsTitle || firstAboveProse || firstIsSection) { first.type = \"heading\"; first.level = 1 }\n\n if (first.type !== \"heading\" || captionLike.test(firstText) || second.type !== \"paragraph\" || !second.text || !second.bbox ||\n !second.style?.fontName || !second.style.fontSize || !third?.text || !third.bbox ||\n second.text.trim().length < 5 || second.text.trim().length > 80 ||\n captionLike.test(second.text.trim()) || /^(?:doi:|https?:)/i.test(second.text.trim()) ||\n second.bbox.height > second.style.fontSize * 1.6 ||\n Math.abs(second.bbox.x - third.bbox.x) > 30) continue\n const distinctFace = third.style?.fontName !== second.style.fontName\n const namedContents = /^Table of Contents$/i.test(second.text.trim())\n if ((distinctFace && third.text.length >= 60) || namedContents) { second.type = \"heading\"; second.level = 1 }\n }\n}\n\n/** Use the document's prose face and neighboring regions to recover section\n * labels on pages that already contain another heading. */\nexport function refineDocumentStyleHeadings(blocks: IRBlock[]): void {\n const faceChars = new Map<string, number>()\n for (const block of blocks) {\n if (block.type !== \"paragraph\" || !block.text || !block.style?.fontName) continue\n faceChars.set(block.style.fontName, (faceChars.get(block.style.fontName) ?? 0) + block.text.length)\n }\n const [bodyFace, bodyChars] = [...faceChars].sort((a, b) => b[1] - a[1])[0] ?? []\n if (!bodyFace || !bodyChars || bodyChars < 300) return\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n const text = block.text?.trim() ?? \"\"\n // Web links and document metadata are content even if their font is large.\n if (block.type === \"heading\" && /(?:https?:\\/\\/|www\\.|^arxiv:|^doi:)/i.test(text)) {\n block.type = \"paragraph\"\n block.level = undefined\n continue\n }\n const box = block.bbox, style = block.style\n if (block.type !== \"paragraph\" || !box || !style?.fontName || !style.fontSize ||\n style.fontName === bodyFace || (faceChars.get(style.fontName) ?? 0) > bodyChars * 0.25 ||\n text.length < 3 || text.length > 70 || text.includes(\"\\n\") ||\n box.height > style.fontSize * 1.6 ||\n /^(?:table|figure|fig\\.?|source|doi:|arxiv:|https?:|www\\.|[•●○▪▫⮚*∗†])/i.test(text) ||\n /(?:https?:\\/\\/|www\\.|@|[=¼≪þ])/i.test(text) ||\n /^definition\\s+\\d+[.:]?\\s+.*\\b(?:is|are|means)\\b/i.test(text) ||\n /^[\\d\\s.,:%+\\-–]+$/.test(text)) continue\n const previous = blocks[i - 1], next = blocks[i + 1]\n const pb = previous?.bbox, nb = next?.bbox\n if (!pb || !nb || previous.pageNumber !== block.pageNumber || next.pageNumber !== block.pageNumber ||\n (previous.type !== \"paragraph\" && previous.type !== \"heading\") ||\n next.type !== \"paragraph\" || next.style?.fontName !== bodyFace || !next.text || next.text.length < 40 ||\n style.fontSize < (next.style.fontSize ?? 0) * 0.95 ||\n pb.y - (box.y + box.height) < style.fontSize * 0.5 ||\n box.y - (nb.y + nb.height) < 0 || box.y - (nb.y + nb.height) > style.fontSize * 2 ||\n box.x + box.width / 2 < nb.x - 10 || box.x + box.width / 2 > nb.x + nb.width + 10) continue\n block.type = \"heading\"\n block.level = 2\n }\n}\n\n/**\n * 의사 테이블 감지: 실제 데이터 테이블이 아닌 텍스트가 우연히 테이블로 감지된 경우.\n */\nexport function shouldDemoteTable(table: IRTable): boolean {\n const allCells = table.cells.flatMap(row => row.map(c => c.text.trim())).filter(Boolean)\n const allText = allCells.join(\" \")\n\n // 라벨 헤더 표 가드: 2행2열+ + 첫 행 전체가 마커 없는 짧은 라벨 + 본문에\n // 내용 실존 (예: 채용분야|담당업무|우대조건, 성명|응시분야|비고) — 본문 셀의\n // ○/ㅇ 항목부호와 양식 표의 빈 기입란은 행정문서 표 관행이므로 강등하지 않음\n if (table.rows >= 2 && table.cols >= 2 &&\n table.cells[0].every(c => {\n const t = c.text.trim()\n return t.length > 0 && t.length <= 12 && !/[□■◆○●▶ㅇ<>]/.test(t)\n }) &&\n table.cells.slice(1).some(row => row.some(c => c.text.trim() !== \"\"))) return false\n\n // 텍스트 박스 패턴: 3행 이하 + 3열 이하 + <...> 또는 ㅇ 마커 포함 + 짧은 내용\n // 공문서 \"중점 추진사항\" 등 요약 박스. 긴 조문 셀을 가진 신구조문대비표(<신 설>\n // 표기가 <...> 마커로 오인됨)는 요약 박스가 아니므로 길이 가드로 제외 — 아래 200자\n // 초과 정상표 정책(allText.length > 200 → return false)과 정합시킨다 (pline-1).\n if (table.rows <= 3 && table.cols <= 3 && allText.length <= 200) {\n // 빈 셀이 과반 → 텍스트 박스 (테두리 안에 텍스트만 있는 형태)\n const totalCells = table.rows * table.cols\n const emptyCells = totalCells - allCells.length\n if (emptyCells >= totalCells * 0.3) return true\n\n // 마커 패턴 (ㅇ, □, ○, <> 등) → 텍스트성\n if (/[□■◆○●▶ㅇ]/.test(allText)) return true\n if (/<[^>]+>/.test(allText)) return true\n }\n\n if (allText.length > 200) return false\n // □, ○, ■ 마커 포함 + 3행 이하 → 텍스트성\n if (/[□■◆○●▶]/.test(allText) && table.rows <= 3) return true\n // 빈 셀이 과반 → 의사 테이블\n const totalCells = table.rows * table.cols\n const emptyCells = totalCells - allCells.length\n if (table.rows <= 2 && emptyCells > totalCells * 0.5) return true\n // 1행 + 숫자 데이터 없음 → 의사 테이블\n if (table.rows === 1 && !/\\d{2,}/.test(allText)) return true\n return false\n}\n\n/** demote된 테이블을 구조화된 텍스트로 변환 */\nexport function demoteTableToText(table: IRTable): string {\n const lines: string[] = []\n for (let r = 0; r < table.rows; r++) {\n const cells = table.cells[r].map(c => c.text.trim()).filter(Boolean)\n if (cells.length === 0) continue\n if (table.cols === 2 && cells.length === 2) {\n lines.push(`${cells[0]} : ${cells[1]}`)\n } else {\n // 각 셀 텍스트를 공백으로 합침 (br 태그는 줄바꿈으로 유지)\n lines.push(cells.join(\" \"))\n }\n }\n return lines.join(\"\\n\")\n}\n\n/** □/■ 마커 및 짧은 섹션명을 서브헤딩으로 변환 */\nexport function detectMarkerHeadings(blocks: IRBlock[]): void {\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n if (block.type !== \"paragraph\" || !block.text) continue\n const text = block.text.trim()\n // □/■ + 한글로 시작하는 짧은 텍스트 (50자 미만)\n if (text.length < 50 && /^[□■◆◇▶]\\s*[가-힣]/.test(text)) {\n block.type = \"heading\"\n block.level = 4\n continue\n }\n // 순수 한글 2-6자 + 앞뒤가 표/헤딩/빈블록 → 섹션 제목으로 추정\n // (예: \"사업설명\", \"사업효과\", \"추진경위\")\n if (/^[가-힣]{2,6}$/.test(text) && block.style?.fontSize) {\n const prev = blocks[i - 1]\n const next = blocks[i + 1]\n const prevIsStructural = !prev || prev.type === \"table\" || prev.type === \"heading\" || prev.type === \"separator\"\n const nextIsStructural = !next || next.type === \"table\" || next.type === \"heading\" || (next.type === \"paragraph\" && next.text && /^[□■◆○●]/.test(next.text.trim()))\n if (prevIsStructural || nextIsStructural) {\n block.type = \"heading\"\n block.level = 3\n }\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════\n// 표 캡션 감지 (ODL CaptionProcessor의 패턴 기반 서브셋)\n// ═══════════════════════════════════════════════════════\n\n/**\n * 캡션 라벨 패턴 — '표 1.', '<표 2>', '[표 3-1]', '그림 4', 'Table 1', 'Figure 2' 등.\n * 숫자(또는 원문자)가 반드시 있어야 함 — '표지', '그림자' 같은 일반 단어 오탐 방지.\n */\nconst TABLE_CAPTION_RE = /^[<\\[(【〈]?\\s*(표|그림|도표|Table|Figure|Fig\\.?)\\s*[\\d①-⑮][\\d.\\-]*\\s*[\\])】〉>]?[.:]?\\s*/i\n\n/** 캡션 후보 최대 길이 */\nconst CAPTION_MAX_LENGTH = 100\n/** 캡션-표 수직 거리 한계 (pt) */\nconst CAPTION_MAX_GAP = 30\n\n/**\n * 표 블록 직전/직후의 짧은 캡션 패턴 텍스트를 IRTable.caption으로 연결하고\n * 해당 paragraph 블록은 제거한다 (중복 출력 방지 — builder가 표 위에 캡션 출력).\n */\nexport function detectTableCaptions(blocks: IRBlock[]): void {\n const isCaptionCandidate = (b: IRBlock | undefined, table: IRBlock): b is IRBlock => {\n if (!b || b.type !== \"paragraph\" || !b.text) return false\n if (b.pageNumber !== table.pageNumber) return false\n const text = b.text.trim()\n if (!text || text.length > CAPTION_MAX_LENGTH || text.includes(\"\\n\")) return false\n if (!TABLE_CAPTION_RE.test(text)) return false\n // 수직 근접 + 수평 겹침 검증 (bbox 있을 때만)\n if (b.bbox && table.bbox) {\n const capTop = b.bbox.y + b.bbox.height\n const capBottom = b.bbox.y\n const tblTop = table.bbox.y + table.bbox.height\n const tblBottom = table.bbox.y\n const gap = capBottom >= tblTop ? capBottom - tblTop : tblBottom - capTop\n if (gap > CAPTION_MAX_GAP) return false\n const overlap = Math.min(b.bbox.x + b.bbox.width, table.bbox.x + table.bbox.width) -\n Math.max(b.bbox.x, table.bbox.x)\n if (overlap < Math.min(b.bbox.width, table.bbox.width) * 0.3) return false\n }\n return true\n }\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n if (block.type !== \"table\" || !block.table || block.table.caption) continue\n\n // 직전 블록 우선 (한국 공문서는 표 위 캡션이 일반적), 다음 블록 차선\n if (isCaptionCandidate(blocks[i - 1], block)) {\n block.table.caption = blocks[i - 1].text!.trim()\n blocks.splice(i - 1, 1)\n i--\n } else if (isCaptionCandidate(blocks[i + 1], block) && blocks[i + 2]?.type !== \"table\") {\n // 두 표 사이의 캡션은 아래 표의 머리 캡션이다\n block.table.caption = blocks[i + 1].text!.trim()\n blocks.splice(i + 1, 1)\n }\n }\n}\n\n// ═══════════════════════════════════════════════════════\n// 한국어 리스트 감지 — 공문서 계층 라벨 시퀀스 검증\n// (ODL ListProcessor의 한국어 서브셋 — 가나다 시퀀스, '붙임' 패턴)\n// ═══════════════════════════════════════════════════════\n\n/** 한국 공문서 항목 기호 시퀀스 (가나다순) */\nconst KOREAN_LIST_SEQ = \"가나다라마바사아자차카타파하\"\n\ninterface ListLabel {\n family: \"arabicDot\" | \"korDot\" | \"arabicParen\" | \"korParen\" | \"circled\"\n ord: number\n}\n\n/** 블록 텍스트에서 리스트 라벨 파싱 — 시퀀스 검증 가능한 라벨만 */\nfunction parseListLabel(text: string): ListLabel | null {\n let m = text.match(/^(\\d{1,2})\\.(?!\\d)\\s+/)\n if (m) return { family: \"arabicDot\", ord: parseInt(m[1], 10) }\n m = text.match(/^([가-하])\\.\\s+/)\n if (m) {\n const idx = KOREAN_LIST_SEQ.indexOf(m[1])\n if (idx >= 0) return { family: \"korDot\", ord: idx + 1 }\n }\n m = text.match(/^(\\d{1,2})\\)\\s*/)\n if (m) return { family: \"arabicParen\", ord: parseInt(m[1], 10) }\n m = text.match(/^([가-하])\\)\\s*/)\n if (m) {\n const idx = KOREAN_LIST_SEQ.indexOf(m[1])\n if (idx >= 0) return { family: \"korParen\", ord: idx + 1 }\n }\n m = text.match(/^([①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮])\\s*/)\n if (m) return { family: \"circled\", ord: m[1].charCodeAt(0) - 0x2460 + 1 }\n return null\n}\n\n/** '붙임' 패턴 (ODL ATTACHMENTS_PATTERN) — 공문서 첨부 표기 */\nconst ATTACHMENT_RE = /^붙\\s*임\\s*(\\d+[.:]?)?\\s/\n\n/**\n * 라벨 시퀀스 검증 기반 한국어 리스트 감지.\n *\n * 1) paragraph 블록의 선두 라벨(1./가./1)/가)/①)을 파싱\n * 2) 같은 family의 라벨이 +1씩 증가하는 체인(2개+)만 리스트로 확정\n * — \"2026. 6. 9.\" 같은 날짜/단발 번호 오탐 방지\n * 3) 상위 family 항목 사이에 낀 하위 family 항목은 children으로 중첩 (들여쓰기)\n * 4) '붙임 1 ...' 패턴은 시퀀스 없이도 리스트 항목으로 인정\n */\nexport function detectKoreanListBlocks(blocks: IRBlock[]): void {\n // ── 1단계: 라벨 수집 ──\n interface Labeled {\n idx: number\n label: ListLabel\n }\n const labeled: Labeled[] = []\n for (let i = 0; i < blocks.length; i++) {\n const b = blocks[i]\n if ((b.type !== \"paragraph\" && b.type !== \"list\") || !b.text) continue\n const label = parseListLabel(b.text.trim())\n if (label) labeled.push({ idx: i, label })\n }\n\n // ── 2단계: family별 시퀀스 체인 검증 ──\n // 체인: 같은 family + ord가 +1씩 증가 + 블록 간격 ≤ 20 (사이에 하위 항목/본문 허용)\n const validated = new Set<number>()\n const byFamily = new Map<string, Labeled[]>()\n for (const l of labeled) {\n const arr = byFamily.get(l.label.family) || []\n arr.push(l)\n byFamily.set(l.label.family, arr)\n }\n for (const arr of byFamily.values()) {\n let chain: Labeled[] = []\n for (const item of arr) {\n const prev = chain[chain.length - 1]\n if (prev && item.label.ord === prev.label.ord + 1 && item.idx - prev.idx <= 20) {\n chain.push(item)\n } else {\n if (chain.length >= 2) for (const c of chain) validated.add(c.idx)\n chain = [item]\n }\n }\n if (chain.length >= 2) for (const c of chain) validated.add(c.idx)\n }\n\n // ── 3단계: 변환 + 중첩 ──\n // familyStack: 현재 리스트 run에서 등장한 family 순서 (얕은 → 깊은)\n let familyStack: string[] = []\n let lastTopLevelList: IRBlock | null = null\n // 직전에 리스트로 변환된 블록 인덱스 — 중첩(children 끌어올림)은 직전 블록이\n // 같은 리스트 run일 때만 허용 (사이에 낀 본문을 건너뛰면 블록 순서가 재배열됨)\n let prevListIdx = -2\n const toRemove = new Set<number>()\n\n for (let i = 0; i < blocks.length; i++) {\n const b = blocks[i]\n\n // 표/헤딩/구분선은 리스트 run 종료\n if (b.type === \"table\" || b.type === \"heading\" || b.type === \"separator\") {\n familyStack = []\n lastTopLevelList = null\n continue\n }\n if ((b.type !== \"paragraph\" && b.type !== \"list\") || !b.text) continue\n\n const text = b.text.trim()\n\n // '붙임' 패턴 — 시퀀스 불요\n if (b.type === \"paragraph\" && ATTACHMENT_RE.test(text)) {\n blocks[i] = { ...b, type: \"list\", listType: \"unordered\" }\n continue\n }\n\n if (!validated.has(i)) continue\n const label = parseListLabel(text)!\n\n // family 깊이 결정 — 처음 보는 family는 스택에 push\n let depth = familyStack.indexOf(label.family)\n if (depth < 0) {\n familyStack.push(label.family)\n depth = familyStack.length - 1\n } else {\n // 상위 family로 복귀하면 더 깊은 family 제거\n familyStack = familyStack.slice(0, depth + 1)\n }\n\n const listType: \"ordered\" | \"unordered\" = label.family === \"arabicDot\" ? \"ordered\" : \"unordered\"\n const listBlock: IRBlock = { ...b, type: \"list\", listType }\n\n if (depth === 0) {\n blocks[i] = listBlock\n lastTopLevelList = listBlock\n } else if (lastTopLevelList && i === prevListIdx + 1) {\n // 하위 항목 → 직전 상위 항목의 children으로 (마크다운 들여쓰기)\n // 연속성 조건(i === prevListIdx + 1): 사이에 본문이 끼면 끌어올리지 않는다\n if (!lastTopLevelList.children) lastTopLevelList.children = []\n lastTopLevelList.children.push(listBlock)\n toRemove.add(i)\n } else {\n // 상위 항목 없이 시작됐거나 본문으로 연속성이 끊긴 하위 family — 평면 리스트로\n blocks[i] = listBlock\n lastTopLevelList = listBlock\n }\n prevListIdx = i\n }\n\n // 제거는 뒤에서부터\n if (toRemove.size > 0) {\n const sorted = [...toRemove].sort((a, b) => b - a)\n for (const idx of sorted) blocks.splice(idx, 1)\n }\n}\n\n// ═══════════════════════════════════════════════════════\n// 리스트 감지 — paragraph 블록 중 번호 패턴을 list 블록으로 변환\n// ═══════════════════════════════════════════════════════\n\n/**\n * 연속된 paragraph 블록에서 번호 리스트 패턴을 감지하여 list 블록으로 변환.\n * \"비고\" 헤더 뒤에 오는 \"1.\", \"2.\" 패턴이 대표적.\n */\nexport function detectListBlocks(blocks: IRBlock[]): IRBlock[] {\n const result: IRBlock[] = []\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n\n if (block.type === \"paragraph\" && block.text) {\n const text = block.text.trim()\n // 번호 리스트: \"1.\", \"2.\" 등\n if (/^\\d+\\.\\s/.test(text)) {\n result.push({ ...block, type: \"list\", listType: \"ordered\", text: block.text })\n continue\n }\n // 비번호 리스트: ○, -, ·, ※, ▶ 등\n if (/^[○●·※▶▷◆◇\\-]\\s/.test(text)) {\n result.push({ ...block, type: \"list\", listType: \"unordered\", text: block.text })\n continue\n }\n }\n\n result.push(block)\n }\n\n return result\n}\n\n// ═══════════════════════════════════════════════════════\n// 한국어 특수 테이블 감지 — \"구분/항목/종류\" 패턴 기반 key-value 테이블\n// ═══════════════════════════════════════════════════════\n\n/**\n * ODL SpecialTableProcessor 포팅: 연속된 \"구분:\", \"항목:\", \"종류:\" 등\n * 한국어 key-value 패턴을 2열 테이블로 변환.\n *\n * 동작:\n * 1) paragraph 블록의 텍스트에서 한국어 key-value 패턴 감지\n * 2) \":\"가 있으면 key | value 2열, 없으면 colSpan=2 (전체 행)\n * 3) 연속된 패턴을 하나의 테이블로 그룹화\n */\nconst KOREAN_TABLE_HEADER_RE = /^\\(?(구분|항목|종류|분류|유형|대상|내용|기간|금액|비율|방법|절차|요건|조건|근거|목적|범위|기준)\\)?[:\\s]/\n\n/** KV 오탐 패턴: 시간(14:30), URL(://), 숫자:숫자(3:2) */\nconst KV_FALSE_POSITIVE_RE = /\\d{1,2}:\\d{2}|:\\/\\/|\\d+:\\d+/\n\nexport function detectSpecialKoreanTables(blocks: IRBlock[]): IRBlock[] {\n const result: IRBlock[] = []\n let kvLines: { key: string; value: string; block: IRBlock }[] = []\n\n const flushKvTable = () => {\n if (kvLines.length < 2) {\n // 2행 미만이면 테이블로 만들 가치 없음 → 원래 블록 복원\n for (const kv of kvLines) result.push(kv.block)\n kvLines = []\n return\n }\n\n // 2열 테이블 생성\n const cells: import(\"../types.js\").IRCell[][] = kvLines.map(kv => {\n if (kv.value) {\n return [\n { text: kv.key, colSpan: 1, rowSpan: 1 },\n { text: kv.value, colSpan: 1, rowSpan: 1 },\n ]\n }\n // \":\" 없는 줄 → 전체 행 (colSpan=2)\n return [\n { text: kv.key, colSpan: 2, rowSpan: 1 },\n { text: \"\", colSpan: 1, rowSpan: 1 },\n ]\n })\n\n const irTable: IRTable = {\n rows: cells.length,\n cols: 2,\n cells,\n hasHeader: true,\n }\n\n // 첫 블록의 위치 정보 사용\n const firstBlock = kvLines[0].block\n result.push({\n type: \"table\",\n table: irTable,\n pageNumber: firstBlock.pageNumber,\n bbox: firstBlock.bbox,\n })\n kvLines = []\n }\n\n for (const block of blocks) {\n if (block.type !== \"paragraph\" || !block.text) {\n flushKvTable()\n result.push(block)\n continue\n }\n\n const text = block.text.trim()\n\n // \"구분: xxx\" 또는 \"항목: xxx\" 패턴 매칭\n if (KOREAN_TABLE_HEADER_RE.test(text)) {\n const colonIdx = text.indexOf(\":\")\n if (colonIdx >= 0) {\n kvLines.push({\n key: text.slice(0, colonIdx).trim(),\n value: text.slice(colonIdx + 1).trim(),\n block,\n })\n } else {\n // \":\" 없이 공백으로 구분된 경우: \"구분 xxx\"\n const spaceIdx = text.search(/\\s/)\n if (spaceIdx > 0) {\n kvLines.push({\n key: text.slice(0, spaceIdx).trim(),\n value: text.slice(spaceIdx + 1).trim(),\n block,\n })\n } else {\n kvLines.push({ key: text, value: \"\", block })\n }\n }\n continue\n }\n\n // key-value 패턴이 아닌 블록이 나오면 축적된 것을 flush\n // 단, 이미 수집 중이고 현재 블록이 \"label: value\" 형태면 계속 수집\n if (kvLines.length > 0 && text.includes(\":\")) {\n // 오탐 제외: 시간(14:30), URL(http://), 숫자:숫자(3:2), 괄호 포함\n if (!KV_FALSE_POSITIVE_RE.test(text) && !text.includes(\"(\") && !text.includes(\")\")) {\n const colonIdx = text.indexOf(\":\")\n const key = text.slice(0, colonIdx).trim()\n // key가 순수 한글 2~8자 (공백/괄호 없음)면 유효한 key-value 라인\n if (/^[가-힣]+$/.test(key) && key.length >= 2 && key.length <= 8) {\n kvLines.push({\n key,\n value: text.slice(colonIdx + 1).trim(),\n block,\n })\n continue\n }\n }\n }\n\n flushKvTable()\n result.push(block)\n }\n\n flushKvTable()\n return result\n}\n\n// ─── 머리글/바닥글 감지 ────────────────────────────\n\n/** 등장마다 같은 자리 숫자 하나가 쪽과 같이 느나(쪽 − 숫자 일정) — 띠 블록이 표에 흡수돼 따로 선 등장이 성긴 쪽 번호(hwp3-sample11) */\nfunction tracksPages(occ: { page: number; text: string }[]): boolean {\n const nums = occ.map(o => [...o.text.matchAll(/\\d+/g)].map(m => +m[0]))\n if (!nums.length || nums.some(n => n.length !== nums[0].length)) return false\n return nums[0].some((_, j) => new Set(occ.map((o, k) => o.page - nums[k][j])).size === 1)\n}\n/** 등장마다 바뀌는 숫자 자리가 낱말에 붙지 않았나 — 쪽 번호 \"- 3 -\"·\"DCT Technology Inc. 5\"·내려받기 도장 \"김유진 / 2026…7 /\" 은 떨어져 있고,\n * 첨부 이름표 \"<붙임2>\"·\"<별지 서식 제3호>\" 의 서식 번호는 낱말에 붙어 있다 */\nfunction looseVaryingNumber(occ: { text: string }[]): boolean {\n const nums = occ.map(o => [...o.text.matchAll(/\\d+/g)])\n if (!nums.length || nums.some(n => n.length !== nums[0].length)) return false\n const loose = (text: string, m: RegExpMatchArray) => !/\\p{L}/u.test(text[m.index! - 1] ?? \" \") && !/\\p{L}/u.test(text[m.index! + m[0].length] ?? \" \")\n for (let j = 0; j < nums[0].length; j++) {\n if (new Set(nums.map(n => n[j][0])).size > 1 && occ.every((o, k) => loose(o.text, nums[k][j]))) return true\n }\n return false\n}\n\n/**\n * 머리글/바닥글 감지 — 텍스트 반복 패턴 (숫자 normalization).\n *\n * v3.0.x: y 위치 클러스터 규칙(같은 y 버킷이 3+페이지 반복이면 텍스트가 달라도 제거)을\n * 삭제했다. 본문도 페이지마다 같은 y에서 시작/끝나므로 (균일한 상하 여백), 위치 반복만으로는\n * 머리글/바닥글과 본문 첫/마지막 줄을 구분할 수 없다 — 인사말씀·보고서류에서 본문 문단\n * 첫 줄과 섹션 제목(\"붙임 1\" 등)이 통째로 제거되는 사고가 corpus에서 다수 확인됨.\n * 페이지 번호(\"- 1 -\")처럼 가변 숫자가 있는 고정 문구는 # normalization으로 충분히 잡힌다.\n */\nexport function removeHeaderFooterBlocks(\n blocks: IRBlock[],\n pageHeights: Map<number, number>,\n warnings: ParseWarning[],\n notes?: Map<number, PageNotes>,\n tables = false,\n context: IRBlock[] = [],\n): number[] {\n if (context.length && !tables) {\n const removed = removeHeaderFooterBlocks([...blocks, ...context], pageHeights, [], notes)\n .filter(index => index < blocks.length)\n if (removed.length) warnings.push({ message: `${removed.length}개 머리글/바닥글 요소 제거됨`, code: \"HIDDEN_TEXT_FILTERED\" })\n return removed\n }\n const ZONE_RATIO = 0.12 // 상하 12% (10% 초과 여백 대응)\n const MIN_REPEAT = 3 // 최소 3페이지 반복\n\n type ZoneEntry = { blockIdx: number; page: number; text: string }\n const topEntries: ZoneEntry[] = []\n const bottomEntries: ZoneEntry[] = []\n\n for (let bi = 0; bi < blocks.length; bi++) {\n const b = blocks[bi]\n // tables: 괘선 상자로 찍은 머리말(시험지 \"2 | 홀수형\", 교재 짝수 쪽 장 제목)만 — 빈 칸을 뺀 칸 글로 같은 반복 규칙을 탄다.\n // 띠 안에 통째로 들어야 하므로(아래 영역 판정) 쪽을 넘는 본문 표·표 머리 행은 걸리지 않는다. 쪽 넘김 표 병합 뒤에 따로 부른다 —\n // 먼저 지우면 상자 쪽번호(\"- | 1 -\")가 갈라 두던 이웃 쪽 카드뉴스 표 둘이 한 표로 이어진다(의료방사선 보도자료)\n const text = !tables ? b.text?.trim() : b.type === \"table\" && b.table ? b.table.cells.flat().map(c => c.text.trim()).filter(Boolean).join(\" | \") : undefined\n if (!b.bbox || !b.pageNumber || !text) continue\n const ph = pageHeights.get(b.bbox.page) || pageHeights.get(b.pageNumber)\n if (!ph) continue\n\n const blockTop = ph - (b.bbox.y + b.bbox.height)\n const blockBottom = ph - b.bbox.y\n const entry: ZoneEntry = { blockIdx: bi, page: b.pageNumber, text }\n\n // blockTop/blockBottom은 페이지 상단 기준 거리 — 하단 경계가 상단 12% 안이면\n // 머리글 영역(top), 상단 경계가 하단 12% 안이면 바닥글 영역(bottom).\n // 머리말 상자 표는 괘선·안 여백만큼 띠 경계를 조금 넘는다(exam_kor 상자 아래끝 12.4%) — 쪽 높이 5% 이하 표는 세로 중심으로 본다\n const boxMid = b.type === \"table\" && b.bbox.height <= ph * 0.05 ? (blockTop + blockBottom) / 2 : undefined\n if ((boxMid ?? blockBottom) <= ph * ZONE_RATIO) topEntries.push(entry)\n else if ((boxMid ?? blockTop) >= ph * (1 - ZONE_RATIO)) bottomEntries.push(entry)\n }\n\n const removeSet = new Set<number>()\n\n for (const entries of [topEntries, bottomEntries]) {\n if (entries.length === 0) continue\n\n // (1) 텍스트 반복 패턴\n const patternCount = new Map<string, number>()\n const patternPages = new Map<string, Set<number>>()\n const patternNumbers = new Map<string, Set<string>>()\n const patternOcc = new Map<string, ZoneEntry[]>()\n for (const e of entries) {\n const norm = e.text.replace(/\\d+/g, \"#\")\n patternCount.set(norm, (patternCount.get(norm) || 0) + 1)\n const nums = patternNumbers.get(norm) || new Set<string>()\n nums.add((e.text.match(/\\d+/g) ?? []).join(\",\"))\n patternNumbers.set(norm, nums)\n const pages = patternPages.get(norm) || new Set<number>()\n pages.add(e.page)\n patternPages.set(norm, pages)\n patternOcc.set(norm, [...(patternOcc.get(norm) ?? []), e])\n }\n const repeatedPatterns = new Set<string>()\n for (const [p, count] of patternCount) {\n // 서로 다른 페이지에서 MIN_REPEAT번 이상 등장\n // 첫~끝 등장 쪽 구간의 40% 이상 쪽에 나와야 러닝 헤더다(홀짝 머리말 포함) — 서식마다 첫 쪽에 찍힌 절 제목은 드문드문 되풀이된다\n // (규제영향분석서 \"Ⅰ. 규제의 필요성\": 156쪽 중 10쪽, 약 15쪽 간격 — 원본 서식 제목을 머리글로 지웠다)\n const pages = [...(patternPages.get(p) ?? [])]\n const span = pages.length ? Math.max(...pages) - Math.min(...pages) + 1 : 0\n // 숫자가 등장마다 바뀌면(쪽 번호) 드문드문해도 러닝 머리·바닥글이다 — 일부 쪽에선 표에 흡수돼 따로 선 등장이 성기다(hwp3-sample11)\n // 표 상자는 번호만 바뀌는 드문 상자가 본문이다(안건 표지 \"제2차 재정운용전략협의회 | 26-2-1\", 56쪽 중 4쪽) — 밀도만 본다\n // 숫자가 바뀌는 꼴은 바뀌는 숫자가 낱말에 붙지 않았거나(쪽 번호·도장) 같은 글이 쪽마다 되풀이될 때(장 머리말 \"제1장 총칙\")만이다 —\n // 쪽마다 하나씩 바뀌는 첨부 이름표 \"<붙임2>\"·\"<별지 서식 제3호>\" 는 서식 번호라 본문이다(pair06·pr-1674, 연속 쪽이라 밀도도 넘는다)\n const varying = !tables && (patternNumbers.get(p)?.size ?? 0) > 1\n const occ = patternOcc.get(p) ?? []\n // 머리 띠에서는 낱말에 안 붙은 숫자라도 드문드문하면 쪽과 같이 늘어야 한다 — 교재 강의 번호 \"강의 01.\"·\"예제 02-02.\"(온새미로), 부속서 제목\n // \"부속서 3\"(선박 코드)은 쪽 번호가 아니다. 바닥 띠의 드문 번호 줄은 종전대로 지운다(각주 \"제19장 부속서 3의 2.3.5 참조 - 역주\" — 남기면 쪽 끝에 놓인다)\n const dense = pages.length >= span * 0.4\n const loose = looseVaryingNumber(occ) && (entries === bottomEntries || dense || tracksPages(occ))\n const pageNumbered = varying && (loose || (dense && new Set(occ.map(o => o.text)).size * 2 <= occ.length))\n if (count >= MIN_REPEAT && pages.length >= MIN_REPEAT && (varying ? pageNumbered : dense)) {\n repeatedPatterns.add(p)\n }\n }\n\n // 제거 대상: 텍스트 반복 패턴 매칭.\n // 단 같은 페이지의 비후보 블록과 y대역이 겹치면(후보 높이의 ≥50%) 러닝헤더가\n // 아니라 본문 라인의 일부다 — \"붙임 N\" 태그가 제목과 같은 줄에 있는 첨부 표제를\n // 3페이지 반복 + 숫자 정규화가 헤더로 오검출해 통째로 삼키는 사고 방지.\n for (const e of entries) {\n const norm = e.text.replace(/\\d+/g, \"#\")\n if (!repeatedPatterns.has(norm)) continue\n // 쪽 아래 각주(\"3) 제19장 부속서 3의 2.3.4 참조 - 역주\")는 숫자를 지우면 쪽마다 같은 꼴이다 — 같은 쪽 본문에 같은 위첨자 참조\n // 표시(footnotes.ts)가 있으면 꼬리말이 아니라 각주다(선박 코드 부속서 각주 25개가 러닝 푸터로 지워졌다)\n const noteMark = e.text.match(/^\\d{1,3}\\)/)?.[0]\n if (noteMark && notes?.get(e.page)?.marks.some(m => m.mark === noteMark)) continue\n const cand = blocks[e.blockIdx]\n const cb = cand.bbox!\n // 표 바로 아래의 출처·주석은 반복되어도 표 내용이다. CropBox를 원점으로 옮기면 같은 주석이\n // 하단 12% 띠에 들어올 수 있어, 영역/반복만으로 지우면 본문을 잃는다. 명시적 표지와\n // 표 폭 안 + 한 줄 높이 이내의 간격을 함께 요구해 떨어진 running footer는 종전대로 둔다.\n const tableNote = /^(?:주\\s*(?:\\d+\\s*[).:]|[::])|(?:자료|출처)\\s*[::])/.test(e.text.replace(/ \\| /g, \" \"))\n && blocks.some(o => {\n if (o.type !== \"table\" || !o.bbox || o.bbox.page !== cb.page) return false\n const gap = o.bbox.y - (cb.y + cb.height)\n return gap >= 0 && gap <= cb.height\n && cb.x >= o.bbox.x && cb.x + cb.width <= o.bbox.x + o.bbox.width\n })\n if (tableNote) continue\n let sharesLine = false\n for (let bi = 0; bi < blocks.length; bi++) {\n if (bi === e.blockIdx) continue\n const o = blocks[bi]\n if (!o.bbox || o.bbox.page !== cb.page) continue\n const oNorm = o.text?.trim().replace(/\\d+/g, \"#\")\n if (oNorm && repeatedPatterns.has(oNorm)) continue // 후보끼리(제목+날짜 헤더)는 무시\n const overlap = Math.min(cb.y + cb.height, o.bbox.y + o.bbox.height) - Math.max(cb.y, o.bbox.y)\n if (overlap >= cb.height * 0.5) { sharesLine = true; break }\n }\n if (!sharesLine) removeSet.add(e.blockIdx)\n }\n }\n\n if (removeSet.size > 0) {\n warnings.push({ message: `${removeSet.size}개 머리글/바닥글 요소 제거됨`, code: \"HIDDEN_TEXT_FILTERED\" })\n }\n\n return [...removeSet].sort((a, b) => a - b)\n}\n\n/**\n * 지운 바닥글 블록의 꼴(숫자 → #, 공백 접기) → 쪽 번호 차(쪽 − 줄 끝 숫자). 줄 끝 숫자가 쪽과 같이 느는 꼴만 — 숫자 없는 꼴은 쪽 아래에\n * 되풀이되는 본문일 수 있고(사업 계획서 쪽마다의 담당 \"축산과 가축위생팀장 김영수\"), 쪽 번호뿐인 \"- # -\" 은 표 끝 숫자 행과 같아진다\n */\nexport function runningLinePatterns(removed: IRBlock[], pageHeights: Map<number, number>): Map<string, number> {\n const occ = new Map<string, { page: number; nums: string }[]>()\n for (const b of removed) {\n const ph = b.bbox && b.pageNumber ? pageHeights.get(b.bbox.page) || pageHeights.get(b.pageNumber) : undefined\n const key = b.text ? runningKey(b.text) : \"\"\n if (!ph || !b.bbox || !b.pageNumber || !/\\p{L}/u.test(key) || ph - b.bbox.y <= ph * 0.5) continue\n ;(occ.get(key) ?? occ.set(key, []).get(key)!).push({ page: b.pageNumber, nums: (b.text!.match(/\\d+/g) ?? []).join(\",\") })\n }\n const out = new Map<string, number>()\n for (const [key, list] of occ) {\n if (!pageNumberAtEnd(key)) continue\n const offsets = new Set(list.map(o => o.page - +o.nums))\n if (offsets.size === 1) out.set(key, [...offsets][0])\n }\n return out\n}\nconst runningKey = (s: string): string => s.replace(/\\d+/g, \"#\").replace(/\\s+/g, \" \").trim()\n/** 꼴의 숫자가 하나뿐이고 줄 끝에 있나 (\"DCT Technology Inc. #\") */\nconst pageNumberAtEnd = (key: string): boolean => (key.match(/#/g) ?? []).length === 1 && /#\\s*[)\\]]?$/.test(key)\n\n/**\n * 큰 블록에 흡수된 바닥글 걷기 — 쪽 테두리 틀 안 바닥글(\"DCT Technology Inc. 26\")은 쪽 높이 문단의 끝 줄이 되거나 쪽 끝까지 내려온 표의 끝\n * 행으로 붙어 띠 판정(removeHeaderFooterBlocks)을 비켜 갔다(hwp3-sample11 37쪽). 다른 쪽에서 따로 선 블록으로 지운 꼴(runningLinePatterns)만,\n * 쪽 아래 12% 띠까지 닿은 문단의 끝 줄과 표의 끝 행에서, 그 쪽 번호가 맞을 때만 걷는다. 표 행은 병합 칸이 걸치지 않을 때만\n */\nexport function stripAbsorbedRunningLines(blocks: IRBlock[], pageHeights: Map<number, number>, running: Map<string, number>): IRBlock[] {\n if (!running.size) return blocks\n const matches = (line: string, page: number): boolean => {\n const offset = running.get(runningKey(line))\n return offset !== undefined && page - +(line.match(/\\d+/g) ?? []).pop()! === offset\n }\n const out: IRBlock[] = []\n for (const b of blocks) {\n const ph = b.bbox && b.pageNumber ? pageHeights.get(b.bbox.page) || pageHeights.get(b.pageNumber) : undefined\n if (!ph || !b.bbox || !b.pageNumber || b.bbox.y > ph * 0.12) { out.push(b); continue }\n if (b.type === \"paragraph\" && b.text) {\n const lines = b.text.split(\"\\n\")\n let hi = lines.length\n while (hi > 0 && !lines[hi - 1].trim()) hi--\n if (hi > 0 && matches(lines[hi - 1], b.pageNumber)) {\n const text = lines.slice(0, hi - 1).join(\"\\n\").trim()\n if (!text) continue\n b.text = text\n }\n } else if (b.type === \"table\" && b.table && b.table.cells.length) {\n const t = b.table, last = t.cells.length - 1\n if (matches(t.cells[last].map(c => c.text.trim()).filter(Boolean).join(\" \"), b.pageNumber) &&\n !t.cells.slice(0, last).some((row, i) => row.some(c => i + (c.rowSpan || 1) - 1 >= last))) {\n t.cells.pop()\n if (!t.cells.length) continue\n t.rows = t.cells.length\n t.hasHeader = t.hasHeader && t.rows > 1\n }\n }\n out.push(b)\n }\n return out\n}\n","/**\n * PDF 표 IR 곁정보 — 블록 조립 뒤 단계(같은 쪽 인접 병합·쪽 넘김 병합)가 쓰는 기하 사실.\n * IR 타입을 늘리지 않으려고 표 객체를 키로 한 약한 참조 표에 둔다 (공개 IR 에는 나가지 않는다).\n */\n\nimport type { IRBlock, IRCell, IRTable } from \"../types.js\"\nimport type { LineSegment } from \"./line-types.js\"\n\n/** 한컴 셀 클립으로 셀 기하가 확정된 표 — 클립 묶음이 이미 표 경계다 */\nexport const CLIP_TABLES = new WeakSet<IRTable>()\n/** 번호 칩·빈 간격·한 줄 제목으로 확인된 장식 상자 — keep 원본 표를 남기고 출력에서만 문단으로 편다 */\nexport const FRAME_TITLE_BLOCKS = new WeakMap<IRTable, IRBlock>()\n/** 표의 열 경계 x (그리드 좌표, 오름차순) */\nexport const TABLE_COLXS = new WeakMap<IRTable, number[]>()\n/** 클립 표 첫 조각(첫 쪽)의 행 경계 y (위→아래 내림차순) — 쪽 넘김으로 밀려난 첫 행 묶음의 높이를 잰다 (table-parts) */\nexport const TABLE_ROWYS = new WeakMap<IRTable, number[]>()\n/** 마지막 행 칸이 다음 쪽으로 넘어가 그 조각(CONT_PARTS)을 붙인 표 — 표가 실제로 끝나는 쪽과 그 쪽 조각의 밑변·높이\n * (cell-continuation 이 기록, 쪽 넘김 잇기가 표 끝 자리로 본다) */\nexport const TABLE_TAIL = new WeakMap<IRTable, { page: number; y: number; height: number }>()\n/** 쪽 넘김으로 이은 클립 표의 첫 조각 열 경계 — 이은 표의 TABLE_COLXS 는 모든 조각의 합집합이다 (table-parts) */\nexport const PART_COLXS = new WeakMap<IRTable, number[]>()\n/** 글 없는 클립 표 — 앞 쪽 표가 넘어온 조각일 수 있어(Q&A 상자 마지막 빈 행이 다음 쪽으로 넘어감) 쪽 넘김 잇기까지만\n * 두고, 잇지 못하면 버린다 (mergeCrossPageTables) */\nexport const EMPTY_PARTS = new WeakSet<IRTable>()\n/** 셀 영역에 그림이 놓인 칸 — PDF 는 그림을 셀 글과 따로 뽑아 셀 text 가 비지만, HWP 계열은 셀에 그림 참조가 들어가\n * 비지 않은 칸이다(보도자료 머리표 오른쪽 끝 로고 칸). 후행 빈 열 판정(table-trim)에서 빈 칸으로 보지 않는다 */\nexport const IMAGE_CELLS = new WeakSet<IRCell>()\n/** 클립이 없던 자리를 메운 칸 — 쪽 넘김 조각을 이을 때 비어 있는 자리로 본다 (앞 쪽 세로 병합이 이어진 칸) */\nexport const FILLER_CELLS = new WeakSet<IRCell>()\n/** 앞 쪽 칸의 이어짐인 1칸 조각 → 그 앞 쪽 칸의 클립 사각형 (clip-cells 판정, cell-continuation 이 앞 쪽 표 그 칸에 붙인다) */\nexport const CONT_PARTS = new WeakMap<IRTable, { x1: number; x2: number }>()\n\n/** 칸 글줄 상자 (쪽 좌표, y 는 기준선) — 쪽 넘김 이음 행이 한 칸의 두 조각인지 가를 때 쓴다 (table-parts) */\nexport interface LineBox { y: number; l: number; r: number; h: number }\n/** 클립 표 칸의 글줄 상자, 위→아래 */\nexport const CELL_LINES = new WeakMap<IRCell, LineBox[]>()\n\n/** 칸 글 조각을 글줄로 묶어 기록 — 기준선이 글자 크기 0.6배(최소 3pt) 안이면 한 줄 (cellTextToString 과 같은 묶음) */\nexport function recordCellLines(cell: IRCell, items: ReadonlyArray<{ x: number; y: number; w: number; fontSize: number; h: number }>): void {\n const lines: LineBox[] = []\n for (const it of [...items].sort((a, b) => b.y - a.y)) {\n const fs = it.fontSize || it.h\n const last = lines[lines.length - 1]\n if (last && Math.abs(last.y - it.y) <= Math.max(3, Math.min(fs, last.h) * 0.6)) {\n last.l = Math.min(last.l, it.x)\n last.r = Math.max(last.r, it.x + it.w)\n last.h = Math.max(last.h, fs)\n } else lines.push({ y: it.y, l: it.x, r: it.x + it.w, h: fs })\n }\n CELL_LINES.set(cell, lines)\n}\n\n/** 클립 표 가로 괘선 — 표 윗변·밑변이 그어졌는지, 칸 밑변(표 밑변 제외) 가운데 괘선이 그어진 자리·안 그어진 자리 수.\n * 쪽 넘김 쪼개진 행 판정이 본다: 행 경계마다 괘선을 긋는 표에서 쪽 경계만 괘선이 없으면 한 칸이 쪽을 넘은 것이다 (table-parts) */\nexport interface RowRules { top: boolean; bottom: boolean; innerRuled: number; innerOpen: number }\nexport const ROW_RULES = new WeakMap<IRTable, RowRules>()\n\ntype Box = { x1: number; y1: number; x2: number; y2: number }\n/** 가로 괘선이 칸 변(y, x1~x2)을 덮는 비율 — 괘선 y 가 1.5pt 안 */\nfunction ruleCover(hs: ReadonlyArray<LineSegment>, y: number, x1: number, x2: number): number {\n const segs = hs.filter(l => Math.abs(l.y1 - y) <= 1.5 && l.x2 > x1 && l.x1 < x2).map(l => [Math.max(l.x1, x1), Math.min(l.x2, x2)]).sort((a, b) => a[0] - b[0])\n let len = 0, end = x1\n for (const [a, b] of segs) if (b > end) { len += b - Math.max(a, end); end = b }\n return x2 > x1 ? len / (x2 - x1) : 0\n}\n\n/** 칸 상자(클립)와 가로 괘선으로 ROW_RULES 기록 — 괘선이 변 폭의 90% 이상을 덮으면 그어진 변. 클립 없이 메운 칸은 보지 않는다 */\nexport function recordRowRules(table: IRTable, cells: ReadonlyArray<{ bbox: Box; filler?: boolean }>, bbox: Box, hs: ReadonlyArray<LineSegment>): void {\n const real = cells.filter(c => !c.filler)\n const edge = (y: number, pick: (c: { bbox: Box }) => number): boolean => {\n let w = 0, covered = 0\n for (const c of real) if (Math.abs(pick(c) - y) <= 1) { w += c.bbox.x2 - c.bbox.x1; covered += ruleCover(hs, y, c.bbox.x1, c.bbox.x2) * (c.bbox.x2 - c.bbox.x1) }\n return w > 0 && covered >= 0.9 * w\n }\n let innerRuled = 0, innerOpen = 0\n for (const c of real) {\n if (Math.abs(c.bbox.y1 - bbox.y1) <= 1) continue\n if (ruleCover(hs, c.bbox.y1, c.bbox.x1 + 0.5, c.bbox.x2 - 0.5) >= 0.9) innerRuled++\n else innerOpen++\n }\n ROW_RULES.set(table, { top: edge(bbox.y2, c => c.bbox.y2), bottom: edge(bbox.y1, c => c.bbox.y1), innerRuled, innerOpen })\n}\n","/**\n * PDF 표 후행 빈 열 정리 — HWPX·HWP5 표 빌더(`table/builder.ts` trimAndReturn)와 같은 규칙.\n *\n * 한컴 PDF 의 클립 셀 그리드는 원본 표의 칸을 그대로 담아, 원본에서 글이 한 자도 없는 마지막 열도 열로 낸다.\n * HWP 계열 파서는 그 열을 잘라 같은 문서가 포맷마다 열 수가 달라졌다(보도자료 머리표 2×3 ↔ 2×4, 행정업무운영\n * 편람 서식 15×6 ↔ 15×7 실측). 칸 단위로 전부 빈 마지막 열을 자르고, 잘린 열에 걸친 병합 셀은 표 폭 안으로 줄인다.\n * `keepTrailingEmptyCols`(#47, 서식 입력란 보존) 옵션이면 건드리지 않는다.\n */\n\nimport type { IRBlock, IRCell, IRTable } from \"../types.js\"\nimport { IMAGE_CELLS } from \"./table-meta.js\"\n\nexport function markImageCell(cell: IRCell): void { IMAGE_CELLS.add(cell) }\n\nconst emptyCell = (cell: IRCell | undefined): boolean => !cell || (!cell.text?.trim() && !cell.blocks?.length && !IMAGE_CELLS.has(cell))\n\nfunction trimTable(table: IRTable): void {\n let cols = table.cols\n while (cols > 0 && table.cells.every(row => emptyCell(row[cols - 1]))) cols--\n if (cols === table.cols || cols === 0) return // 전부 빈 표는 그대로 (builder 와 동일)\n table.cells = table.cells.map(row => row.slice(0, cols))\n for (const row of table.cells) {\n for (let c = 0; c < row.length; c++) {\n if (c + row[c].colSpan > cols) row[c].colSpan = cols - c\n }\n }\n table.cols = cols\n}\n\n/** 블록 트리 전체(틀 셀 안 중첩표 포함)의 표에서 후행 빈 열을 제자리 정리 */\nexport function trimTrailingEmptyTableCols(blocks: IRBlock[]): void {\n for (const b of blocks) {\n if (b.type !== \"table\" || !b.table) continue\n for (const row of b.table.cells) for (const cell of row) if (cell.blocks?.length) trimTrailingEmptyTableCols(cell.blocks)\n trimTable(b.table)\n }\n}\n\n/**\n * 획·음영 사각형이 칸마다 조금씩 어긋나 생긴 실오라기 열(폭이 글자 크기 절반 미만)을 옆 열에 합친다 — 워드·구글 문서 음영 칸 표\n * (\"# chromosomes\" 칸만 왼끝이 2pt 밖에서 시작해 온 표에 빈 열이 하나 더 생김, ODL 119). 그 열의 칸이 모두 비었거나 옆 열로\n * 걸친 병합 칸일 때만 — 글이 든 좁은 열은 실제 열이다. 한컴 클립 표는 부르지 않는다(HWPX 정답에도 좁은 틈 열이 실재).\n * 합친 열 수를 돌려준다\n */\nexport function mergeSliverColumns(grid: IRCell[][], colXs: number[], maxWidth: number): number {\n let merged = 0\n for (let c = colXs.length - 2; c >= 0; c--) {\n const cols = colXs.length - 1\n if (cols <= 1 || colXs[c + 1] - colXs[c] >= maxWidth) continue\n // 칸 주인 — 병합 칸이 덮은 자리는 주인 칸\n const owner: (IRCell | null)[][] = grid.map(row => row.map(() => null))\n const anchorCol = new Map<IRCell, number>()\n for (let r = 0; r < grid.length; r++) for (let k = 0; k < cols; k++) {\n const cell = grid[r][k]\n if (!cell || owner[r][k]) continue\n anchorCol.set(cell, k)\n for (let dr = 0; dr < cell.rowSpan && r + dr < grid.length; dr++)\n for (let dc = 0; dc < cell.colSpan && k + dc < cols; dc++) owner[r + dr][k + dc] = cell\n }\n const ok = owner.every(row => {\n const o = row[c]\n return !!o && (o.colSpan > 1 || (!o.text.trim() && !o.blocks?.length))\n })\n if (!ok) continue\n // 이 열을 덮는 병합 칸은 한 칸 줄이고, 이 열에서 시작하는 병합 칸은 오른쪽 자리로 옮긴다\n const spanning = new Set<IRCell>()\n for (const row of owner) if (row[c]!.colSpan > 1) spanning.add(row[c]!)\n for (const cell of spanning) cell.colSpan--\n for (let r = 0; r < grid.length; r++) {\n if (grid[r][c] === owner[r][c] && spanning.has(grid[r][c])) grid[r][c + 1] = grid[r][c]\n grid[r].splice(c, 1)\n }\n colXs.splice(c + 1, 1)\n merged++\n }\n return merged\n}\n","/** Repeated hollow-circle raster markers are list text, not a trailing image placeholder. */\nimport { OPS, ImageKind } from \"pdfjs-dist/legacy/build/pdf.mjs\"\nimport type { NormItem } from \"./text-line.js\"\nimport { extractImageRegions, type ImageRegion } from \"./image-regions.js\"\n\nexport interface Pixels { width: number; height: number; kind?: number; data?: Uint8Array | Uint8ClampedArray }\nexport interface PageObjects {\n objs: { get(id: string, callback?: (value: unknown) => void): unknown }\n commonObjs: { get(id: string, callback?: (value: unknown) => void): unknown }\n}\ninterface Candidate { id: string; x: number; width: number; line: NormItem }\nconst multiply = (m: number[], t: number[]) => [\n m[0] * t[0] + m[2] * t[1], m[1] * t[0] + m[3] * t[1],\n m[0] * t[2] + m[2] * t[3], m[1] * t[2] + m[3] * t[3],\n m[0] * t[4] + m[2] * t[5] + m[4], m[1] * t[4] + m[3] * t[5] + m[5],\n]\n\nfunction hollowCircle(image: Pixels | null): boolean {\n if (!image?.data || image.width < 16 || image.width > 128 || image.height < 16 || image.height > 128 ||\n Math.abs(image.width - image.height) > Math.min(image.width, image.height) * 0.1) return false\n const stride = image.kind === ImageKind.RGB_24BPP ? 3 : image.kind === ImageKind.RGBA_32BPP ? 4 : 0\n const { width, height, data } = image\n if (!stride || data.length < width * height * stride) return false\n const dark: { x: number; y: number }[] = []\n let white = 0\n for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {\n const i = (y * width + x) * stride\n if (stride === 4 && data[i + 3] < 240) return false\n const min = Math.min(data[i], data[i + 1], data[i + 2]), max = Math.max(data[i], data[i + 1], data[i + 2])\n if (max - min > 15) return false\n if (min >= 235) white++\n if (max < 180) dark.push({ x, y })\n }\n if (white < width * height * 0.65 || dark.length < width * height * 0.025 || dark.length > width * height * 0.3) return false\n const x0 = Math.min(...dark.map(p => p.x)), x1 = Math.max(...dark.map(p => p.x))\n const y0 = Math.min(...dark.map(p => p.y)), y1 = Math.max(...dark.map(p => p.y))\n const w = x1 - x0 + 1, h = y1 - y0 + 1, radius = (w + h) / 4\n const cx = (x0 + x1) / 2, cy = (y0 + y1) / 2\n if (Math.abs(w - h) > Math.min(w, h) * 0.15 || w < width * 0.4 || w > width * 0.85 ||\n h < height * 0.4 || h > height * 0.85 ||\n x0 < width * 0.04 || y0 < height * 0.04 || x1 >= width * 0.96 || y1 >= height * 0.96 ||\n Math.abs(cx - width / 2) > width * 0.15 || Math.abs(cy - height / 2) > height * 0.15) return false\n const sectors = new Set<number>()\n for (const point of dark) {\n const r = Math.hypot(point.x - cx, point.y - cy) / radius\n if (r < 0.65 || r > 1.15) return false\n sectors.add(Math.floor((Math.atan2(point.y - cy, point.x - cx) + Math.PI) / (Math.PI * 2) * 8) % 8)\n }\n return sectors.size === 8\n}\n\nexport async function resolveImagePixels(page: PageObjects, id: string): Promise<Pixels | null> {\n const objects = id.startsWith(\"g_\") ? page.commonObjs : page.objs\n return new Promise(resolve => {\n const timer = setTimeout(() => resolve(null), 5000)\n try { objects.get(id, value => { clearTimeout(timer); resolve((value ?? null) as Pixels | null) }) }\n catch { clearTimeout(timer); resolve(null) }\n })\n}\n\n/** Visible small image paints; unknown clips and transparency never supply semantic evidence. */\nexport function smallVisibleImagePaints(fnArray: ArrayLike<number>, argsArray: unknown[][]): { id: string; box: ImageRegion; mirrored: boolean }[] {\n const paints: { id: string; box: ImageRegion; mirrored: boolean }[] = []\n let ctm = [1, 0, 0, 1, 0, 0]\n let alpha = 1, clip: ImageRegion | null = null, unknownClip = false\n const stack: { ctm: number[]; alpha: number; clip: ImageRegion | null; unknownClip: boolean }[] = []\n let pathRect: ImageRegion | null | undefined, pendingClip = false\n const intersectClip = (box: ImageRegion) => { clip = clip ? {\n x1: Math.max(clip.x1, box.x1), y1: Math.max(clip.y1, box.y1),\n x2: Math.min(clip.x2, box.x2), y2: Math.min(clip.y2, box.y2),\n } : box }\n const rectangle = (x: number, y: number, w: number, h: number) => extractImageRegions(\n [OPS.transform, OPS.transform, OPS.paintImageXObject], [ctm, [w, 0, 0, h, x, y], [\"bounds\"]],\n )[0]\n for (let i = 0; i < fnArray.length; i++) {\n const op = fnArray[i], args = argsArray[i]\n if (op === OPS.save || op === OPS.paintFormXObjectBegin) {\n stack.push({ ctm, alpha, clip, unknownClip })\n const matrix = op === OPS.paintFormXObjectBegin ? args?.[0] : undefined\n if (Array.isArray(matrix) && matrix.length >= 6) ctm = multiply(ctm, matrix as number[])\n const bounds = op === OPS.paintFormXObjectBegin ? args?.[1] : undefined\n if (Array.isArray(bounds) && bounds.length === 4) {\n const box = rectangle(bounds[0], bounds[1], bounds[2] - bounds[0], bounds[3] - bounds[1])\n if (box && ctm[1] === 0 && ctm[2] === 0) intersectClip(box)\n else unknownClip = true\n }\n } else if (op === OPS.restore || op === OPS.paintFormXObjectEnd) {\n const state = stack.pop()\n ctm = state?.ctm ?? [1, 0, 0, 1, 0, 0]\n alpha = state?.alpha ?? 1; clip = state?.clip ?? null; unknownClip = state?.unknownClip ?? false\n } else if (op === OPS.setGState) {\n for (const [key, value] of (args?.[0] ?? []) as [string, unknown][]) {\n if (key === \"ca\") alpha = typeof value === \"number\" ? value : 0\n }\n } else if (op === OPS.constructPath) {\n const [operations, coordinates] = args as number[][]\n pathRect = pathRect === undefined && operations?.length === 1 && operations[0] === OPS.rectangle &&\n coordinates?.length >= 4 && ctm[1] === 0 && ctm[2] === 0\n ? rectangle(coordinates[0], coordinates[1], coordinates[2], coordinates[3]) : null\n } else if (op === OPS.clip || op === OPS.eoClip) pendingClip = true\n else if ([OPS.endPath, OPS.stroke, OPS.closeStroke, OPS.fill, OPS.eoFill, OPS.fillStroke,\n OPS.eoFillStroke, OPS.closeFillStroke, OPS.closeEOFillStroke].includes(op)) {\n if (pendingClip) { if (pathRect) intersectClip(pathRect); else unknownClip = true }\n pendingClip = false; pathRect = undefined\n } else if (op === OPS.transform && Array.isArray(args) && args.length >= 6) ctm = multiply(ctm, args as number[])\n else if (op === OPS.paintImageXObject && typeof args?.[0] === \"string\") {\n if (typeof args[1] !== \"number\" || typeof args[2] !== \"number\" ||\n args[1] < 16 || args[1] > 128 || args[2] < 16 || args[2] > 128) continue\n const box = extractImageRegions([OPS.transform, OPS.paintImageXObject], [ctm, args])[0]\n // PDF producers can clip a pixel of the white image margin; classified ink\n // stays at least 4% from every edge, so a 3% crop cannot hide the circle.\n if (!box || ctm[1] !== 0 || ctm[2] !== 0 || alpha < 0.99 || unknownClip ||\n clip && (box.x1 + (box.x2 - box.x1) * 0.03 < clip.x1 ||\n box.y1 + (box.y2 - box.y1) * 0.03 < clip.y1 ||\n box.x2 - (box.x2 - box.x1) * 0.03 > clip.x2 ||\n box.y2 - (box.y2 - box.y1) * 0.03 > clip.y2)) continue\n const width = box.x2 - box.x1, height = box.y2 - box.y1\n if (width < 3 || width > 24 || height < 3 || height > 24 || width / height < 0.5 || width / height > 2) continue\n paints.push({ id: args[0], box, mirrored: ctm[0] < 0 })\n }\n }\n return paints\n}\n\n/** Require repeated markers or an explicit numbered list, aligned prose, and a visible white hollow ring. */\nexport async function restoreImageBullets(\n items: NormItem[], page: PageObjects, fnArray: ArrayLike<number>, argsArray: unknown[][],\n shapes = new Map<string, boolean>(),\n): Promise<number> {\n const candidates: Candidate[] = []\n for (const { id, box } of smallVisibleImagePaints(fnArray, argsArray)) {\n const width = box.x2 - box.x1, height = box.y2 - box.y1\n if (Math.abs(width - height) > Math.min(width, height) * 0.1) continue\n const line = items.filter(item => !item.isHidden && item.fontSize > 0 &&\n item.x >= box.x2 && item.x - box.x2 <= item.fontSize * 0.7 &&\n Math.abs(item.y + item.h / 2 - (box.y1 + box.y2) / 2) <= item.fontSize * 0.4 &&\n height >= item.fontSize * 0.5 && height <= item.fontSize * 1.2)\n .sort((a, b) => a.x - b.x)[0]\n if (!line || /^[○●•◦□■∙]/.test(line.text.trim()) || !/\\p{L}/u.test(line.text)) continue\n const row = items.filter(item => Math.abs(item.y - line.y) <= 2 && item.x >= line.x)\n if (row.reduce((n, item) => n + (item.text.match(/\\p{L}/gu)?.length ?? 0), 0) < 12 ||\n items.some(item => item !== line && Math.abs(item.y - line.y) <= 2 && item.x < line.x)) continue\n candidates.push({ id, x: box.x1, width, line })\n}\n let restored = 0\n const used = new Set<NormItem>()\n for (const candidate of candidates) {\n const same = candidates.filter(other => other.id === candidate.id && other.line !== candidate.line &&\n Math.abs(other.line.x - candidate.line.x) <= 1 && Math.abs(other.x - candidate.x) <= 1 &&\n Math.abs(other.width - candidate.width) <= 1 && other.line.fontSize === candidate.line.fontSize &&\n Math.abs(other.line.y - candidate.line.y) >= candidate.line.fontSize * 2)\n const numbered = items.filter(item => !item.isHidden && /^[①-⑮]\\s*/.test(item.text.trim()) &&\n item.fontName === candidate.line.fontName && item.fontSize === candidate.line.fontSize &&\n Math.abs(item.x - candidate.x) <= 1 && (item.text.match(/\\p{L}/gu)?.length ?? 0) >= 12)\n .sort((a, b) => b.y - a.y)\n const numberedList = numbered.some((item, index) => index > 0 &&\n item.text.trim().charCodeAt(0) === numbered[index - 1].text.trim().charCodeAt(0) + 1)\n if ((!same.length && !numberedList) || used.has(candidate.line)) continue\n if (!shapes.has(candidate.id)) shapes.set(candidate.id, hollowCircle(await resolveImagePixels(page, candidate.id)))\n if (!shapes.get(candidate.id)) continue\n used.add(candidate.line)\n items.push({ ...candidate.line, text: \"○\", x: candidate.x, w: candidate.width, strike: false, underline: false, hasSpaceBefore: false, syntheticSpace: false })\n restored++\n }\n return restored\n}\n","/** Keep connected closed boxes together when sorting a horizontal procedure band. */\nimport type { IRBlock } from \"../types.js\"\nimport type { TableGrid, LineSegment } from \"./line-types.js\"\nimport type { ImageRegion } from \"./image-regions.js\"\nimport { ImageKind } from \"pdfjs-dist/legacy/build/pdf.mjs\"\nimport { smallVisibleImagePaints, resolveImagePixels, type PageObjects, type Pixels } from \"./image-bullets.js\"\n\nconst contains = (outer: ImageRegion, inner: ImageRegion, tolerance = 1) =>\n inner.x1 >= outer.x1 - tolerance && inner.x2 <= outer.x2 + tolerance &&\n inner.y1 >= outer.y1 - tolerance && inner.y2 <= outer.y2 + tolerance\n\nfunction closed(grid: TableGrid, hs: LineSegment[], vs: LineSegment[]): boolean {\n const b = grid.bbox\n return [b.y1, b.y2].every(y => hs.some(h => Math.abs(h.y1 - y) <= 1 && h.x1 <= b.x1 + 1 && h.x2 >= b.x2 - 1)) &&\n [b.x1, b.x2].every(x => vs.some(v => Math.abs(v.x1 - x) <= 1 && v.y1 <= b.y1 + 1 && v.y2 >= b.y2 - 1))\n}\n\n/** Only four or more closed, equal-height boxes connected by matching small central images.\n * Each connector must already have verified right-pointing pixel evidence.\n * Inner table shapes and every source block are retained without text changes. */\nexport function groupFlowBoxUnits(\n blocks: IRBlock[], grids: TableGrid[], hs: LineSegment[], vs: LineSegment[], images: ImageRegion[],\n): IRBlock[][] {\n const candidates = grids.filter(g => g.bbox.y2 - g.bbox.y1 >= 30 && closed(g, hs, vs) &&\n !grids.some(other => other !== g && contains(other.bbox, g.bbox, 0) &&\n (other.bbox.x2 - other.bbox.x1) * (other.bbox.y2 - other.bbox.y1) >\n (g.bbox.x2 - g.bbox.x1) * (g.bbox.y2 - g.bbox.y1) + 1))\n const bands: TableGrid[][] = [], visited = new Set<TableGrid>()\n for (const first of candidates) {\n if (visited.has(first)) continue\n const row = candidates.filter(g => Math.abs(g.bbox.y1 - first.bbox.y1) <= 1 && Math.abs(g.bbox.y2 - first.bbox.y2) <= 1)\n .sort((a, b) => a.bbox.x1 - b.bbox.x1)\n row.forEach(g => visited.add(g))\n if (row.length < 4 || row.filter(g => g.rowYs.length === 2 && g.colXs.length === 2).length < 3) continue\n const connectors: ImageRegion[] = []\n for (let i = 1; i < row.length; i++) {\n const left = row[i - 1].bbox, right = row[i].bbox, gap = right.x1 - left.x2\n if (gap < 4 || gap > Math.min(left.x2 - left.x1, right.x2 - right.x1) * 0.6) break\n const centerX = (left.x2 + right.x1) / 2, centerY = (left.y1 + left.y2) / 2\n const connector = images.find(p => {\n const w = p.x2 - p.x1, h = p.y2 - p.y1\n return p.x1 >= left.x2 && p.x2 <= right.x1 && w >= gap * 0.4 && w <= gap * 0.9 &&\n h >= 3 && h <= (left.y2 - left.y1) * 0.2 && w / h >= 0.8 && w / h <= 1.6 &&\n Math.abs((p.x1 + p.x2) / 2 - centerX) <= gap * 0.15 &&\n Math.abs((p.y1 + p.y2) / 2 - centerY) <= (left.y2 - left.y1) * 0.1\n })\n if (!connector) break\n connectors.push(connector)\n }\n if (connectors.length !== row.length - 1 || connectors.some(p =>\n Math.abs((p.x2 - p.x1) - (connectors[0].x2 - connectors[0].x1)) > (connectors[0].x2 - connectors[0].x1) * 0.2 ||\n Math.abs((p.y2 - p.y1) - (connectors[0].y2 - connectors[0].y1)) > (connectors[0].y2 - connectors[0].y1) * 0.2)) continue\n bands.push(row)\n }\n const members = new Map<IRBlock, IRBlock[]>()\n for (const band of bands) {\n const columns = band.map(g => blocks.filter(b => b.bbox && contains(g.bbox, {\n x1: b.bbox.x, y1: b.bbox.y, x2: b.bbox.x + b.bbox.width, y2: b.bbox.y + b.bbox.height,\n })).sort((a, b) => b.bbox!.y + b.bbox!.height - a.bbox!.y - a.bbox!.height || a.bbox!.x - b.bbox!.x))\n if (columns.some(column => !column.length)) continue\n const unit = columns.flat()\n for (const block of unit) members.set(block, unit)\n }\n const units: IRBlock[][] = [], emitted = new Set<IRBlock[]>()\n for (const block of blocks) {\n const unit = members.get(block)\n if (!unit) units.push([block])\n else if (!emitted.has(unit)) { units.push(unit); emitted.add(unit) }\n }\n return units\n}\n\n/** White-backed raster arrow: thin central tail, broad head and a central tip. */\nfunction arrowDirection(image: Pixels | null): -1 | 0 | 1 {\n if (!image?.data || image.width < 16 || image.width > 128 || image.height < 16 || image.height > 128) return 0\n const { width, height, data } = image\n const stride = image.kind === ImageKind.RGB_24BPP ? 3 : image.kind === ImageKind.RGBA_32BPP ? 4 : 0\n if (!stride || data.length < width * height * stride) return 0\n const ink: { x: number; y: number }[] = []; let white = 0\n for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {\n const offset = (y * width + x) * stride\n if (stride === 4 && data[offset + 3] < 240) return 0\n const min = Math.min(data[offset], data[offset + 1], data[offset + 2])\n if (min >= 235) white++\n if (min < 230) ink.push({ x, y })\n }\n if (white < width * height * 0.55 || !ink.length) return 0\n const x0 = Math.min(...ink.map(p => p.x)), x1 = Math.max(...ink.map(p => p.x))\n const y0 = Math.min(...ink.map(p => p.y)), y1 = Math.max(...ink.map(p => p.y))\n const w = x1 - x0 + 1, h = y1 - y0 + 1, centerY = (y0 + y1) / 2\n if (w < width * 0.5 || h < height * 0.5 || x0 < width * 0.04 || y0 < height * 0.04 ||\n x1 >= width * 0.96 || y1 >= height * 0.96) return 0\n const pointsRight = (mirror: boolean) => {\n const points = ink.map(p => ({ x: mirror ? x0 + x1 - p.x : p.x, y: p.y }))\n const tail = points.filter(p => p.x <= x0 + w * 0.25), tip = points.filter(p => p.x >= x1 - w * 0.1)\n if (!tail.length || !tip.length) return false\n const span = (points: { y: number }[]) => Math.max(...points.map(p => p.y)) - Math.min(...points.map(p => p.y)) + 1\n if (span(tail) > h * 0.4 || span(tip) > h * 0.45 ||\n Math.abs((Math.max(...tail.map(p => p.y)) + Math.min(...tail.map(p => p.y))) / 2 - centerY) > h * 0.1 ||\n Math.abs((Math.max(...tip.map(p => p.y)) + Math.min(...tip.map(p => p.y))) / 2 - centerY) > h * 0.1) return false\n // A broad arrowhead sits beyond the tail and converges to the tip.\n let head = 0\n for (let x = Math.ceil(x0 + w * 0.45); x <= x0 + w * 0.75; x++) {\n const column = points.filter(p => p.x === x)\n if (column.length) head = Math.max(head, span(column))\n }\n const middle = new Set(points.filter(p => Math.abs(p.y - centerY) <= 1).map(p => p.x))\n return head >= h * 0.8 && middle.size >= w * 0.9\n }\n return pointsRight(false) ? 1 : pointsRight(true) ? -1 : 0\n}\n\nexport async function detectRightArrowRegions(\n page: PageObjects, fnArray: ArrayLike<number>, argsArray: unknown[][],\n directions = new Map<string, -1 | 0 | 1>(),\n): Promise<ImageRegion[]> {\n const regions: ImageRegion[] = []\n const paints = smallVisibleImagePaints(fnArray, argsArray)\n for (const paint of paints) {\n // No decode for isolated icons or vertical bullet runs: a four-box band\n // needs at least three repeated, horizontally separated connectors.\n const row = paints.filter(other => other.id === paint.id &&\n Math.abs(other.box.y1 - paint.box.y1) <= 1 && Math.abs(other.box.y2 - paint.box.y2) <= 1 &&\n Math.abs((other.box.x2 - other.box.x1) - (paint.box.x2 - paint.box.x1)) <= 1)\n const positions = new Set(row.map(other => Math.round(other.box.x1)))\n if (positions.size < 3) continue\n if (!directions.has(paint.id)) directions.set(paint.id, arrowDirection(await resolveImagePixels(page, paint.id)))\n const direction = directions.get(paint.id) ?? 0\n if (direction && (paint.mirrored ? -direction : direction) === 1) regions.push(paint.box)\n }\n return regions\n}\n","/**\n * 쪽 옆 색인 탭 — 책자형 문서는 장·절 이름을 쪽 바깥 좌우 띠에 작게, 세로로 한 자씩 찍어 쪽마다 되풀이한다(행정업무운영 편람\n * \"제1절\"·\"공·문·서\"). 바탕쪽 장식이라 HWPX 본문엔 없다. 머리·바닥글 제거는 상하 띠만 보므로 여기서 좌우 띠를 본다:\n * 쪽 폭 바깥 12% 띠 안, 그 쪽 본문 글줄 영역(긴 글 블록 좌우 끝의 분위수) 바깥에 통째로 든 짧은 글(10자 이하)이 3쪽 이상, 등장 쪽 묶음\n * (5쪽 넘게 끊기면 새 묶음)의 첫~끝 구간 40% 이상 쪽에 되풀이되면 그 묶음에서 뺀다 — 절 탭 \"제1절\" 은 장마다 몇 쪽씩 몰려 나와 책 전체\n * 구간으로 재면 성기다(편람: 첫~끝 구간 기준으론 한 번도 안 걸렸다).\n * 상자에 든 단원 탭(응급의료기관 평가 기준집 홀수 쪽 \"필수영역\"·\"필수 7\"·\"시⏎설⏎의⏎적⏎절⏎운⏎용\")은 괘선 1열 표로 읽힌다 — 글이 단원마다\n * 바뀌어 글 되풀이로는 못 잡으니 자리(같은 띠·같은 x)가 되풀이되면 뺀다 (#112)\n */\n\nimport type { IRBlock, IRTable } from \"../types.js\"\n\nconst BAND = 0.12\nconst MAX_CHARS = 10\nconst MIN_REPEAT = 3\n/** 등장 쪽 묶음 — 이보다 멀리 끊기면 새 묶음. 격쪽(짝·홀수 쪽만) 탭은 등장 간격 중앙값의 3배까지 — 간지 두 쪽이 끼면 간격 6 이라\n * 앞 두 쪽이 3쪽 미만 묶음으로 떨어져 남았다(응급의료기관 평가 기준집 60·62쪽 | 68쪽~) */\nconst RUN_GAP = 5\n/** 단원 탭 표 — 칸 글 상한, 쪽 높이 대비 최소 높이 */\nconst TAB_CELL_CHARS = 24\nconst TAB_MIN_HEIGHT = 0.3\n\n/** 쪽 옆 띠의 단원 탭 표 (isSideTabTable) — 틀 칸에 중첩시키지 않고 쪽 블록으로 두어 여기서 자리 되풀이로 뺀다 */\nexport const SIDE_TAB_TABLES = new WeakSet<IRTable>()\n\n/**\n * 단원 탭 표 — 쪽 바깥 12% 띠 안의 좁고(띠 폭 이하) 긴(쪽 높이 30% 이상) 1열 표, 칸 글이 짧고, 표와 높이가 겹치는 다른 글이 모두\n * 안쪽에 있다. 쪽 전체 틀 칸에 중첩되면 틀 칸 조립이 이 표의 위쪽 끝으로 글을 갈라, 옆 본문 첫 줄들이 본문 표들보다 앞에 몰려 나왔다\n */\nexport function isSideTabTable(box: { x1: number; y1: number; x2: number; y2: number }, table: IRTable,\n others: Array<{ x: number; y: number; w: number }>, pageWidth: number, pageHeight: number): boolean {\n if (table.cols !== 1 || box.x2 - box.x1 > pageWidth * BAND || box.y2 - box.y1 < pageHeight * TAB_MIN_HEIGHT) return false\n const side = box.x1 >= pageWidth * (1 - BAND) ? \"R\" : box.x2 <= pageWidth * BAND ? \"L\" : null\n if (!side) return false\n const texts = table.cells.flat().map(c => c.text.replace(/\\s+/g, \"\"))\n if (!texts.some(Boolean) || texts.some(t => [...t].length > TAB_CELL_CHARS)) return false\n const overlap = others.filter(it => it.y >= box.y1 && it.y <= box.y2)\n return overlap.length > 0 && overlap.every(it => side === \"R\" ? it.x + it.w <= box.x1 + 1 : it.x >= box.x2 - 1)\n}\n\nexport function removeSideTabs(blocks: IRBlock[], pageWidths: Map<number, number>): IRBlock[] {\n // 쪽마다 본문 글줄 영역(긴 글 블록의 좌우 끝) — 탭은 그 바깥에 선다. 두 단 왼단 첫머리 짧은 줄(\"따라서\")은 본문 왼끝에 붙어 있다\n // 극값이 아니라 분위수(왼끝 20%·오른끝 80%) — 쪽마다 한둘 튀어나온 넓은 블록(그림 캡션)에 본문 끝이 부풀지 않게\n const edges = new Map<number, { lefts: number[]; rights: number[] }>()\n for (const b of blocks) {\n if (!b.bbox || b.pageNumber === undefined || [...(b.text?.trim() ?? \"\")].length <= MAX_CHARS) continue\n const e = edges.get(b.pageNumber) ?? { lefts: [], rights: [] }\n e.lefts.push(b.bbox.x); e.rights.push(b.bbox.x + b.bbox.width)\n edges.set(b.pageNumber, e)\n }\n const q = (xs: number[], p: number) => { const v = [...xs].sort((a, b) => a - b); return v[Math.min(v.length - 1, Math.floor(p * v.length))] }\n const body = new Map<number, { left: number; right: number }>()\n for (const [page, e] of edges) body.set(page, { left: q(e.lefts, 0.2), right: q(e.rights, 0.8) })\n // 긴 글 블록이 없는 쪽(표·그림만)은 문서의 쪽별 본문 끝 중앙값으로\n const all = [...body.values()]\n const fallback = all.length ? { left: q(all.map(e => e.left), 0.5), right: q(all.map(e => e.right), 0.5) } : undefined\n const key = (b: IRBlock): string | null => {\n const w = b.pageNumber !== undefined ? pageWidths.get(b.pageNumber) : undefined\n const text = b.text?.trim()\n const area = (b.pageNumber !== undefined ? body.get(b.pageNumber) : undefined) ?? fallback\n if (b.type === \"table\") {\n // 단원 탭 표는 글 대신 자리(띠·x)로 — 단원마다 글이 바뀐다\n if (!w || !b.bbox || !b.table || !SIDE_TAB_TABLES.has(b.table)) return null\n return `\\u0001${b.bbox.x >= w * (1 - BAND) ? \"R\" : \"L\"}${Math.round(b.bbox.x / 8)}`\n }\n if (!w || !area || !b.bbox || !text || [...text].length > MAX_CHARS) return null\n const side = b.bbox.x + b.bbox.width <= Math.min(w * BAND, area.left - 1) ? \"L\" : b.bbox.x >= Math.max(w * (1 - BAND), area.right + 1) ? \"R\" : null\n return side && `${side}\\u0000${text.replace(/\\s+/g, \"\")}`\n }\n const pages = new Map<string, Set<number>>()\n for (const b of blocks) {\n const k = key(b)\n if (k) pages.set(k, (pages.get(k) ?? new Set()).add(b.pageNumber!))\n }\n /** 탭으로 볼 (글, 쪽) — 묶음마다 따로 잰다 */\n const running = new Set<string>()\n for (const [k, ps] of pages) {\n const list = [...ps].sort((a, b) => a - b)\n const steps = list.slice(1).map((p, i) => p - list[i]).sort((a, b) => a - b)\n const gap = Math.max(RUN_GAP, (steps[steps.length >> 1] ?? 1) * 3)\n let run: number[] = []\n const flush = () => {\n if (run.length >= MIN_REPEAT && run.length >= (run[run.length - 1] - run[0] + 1) * 0.4) for (const p of run) running.add(`${p}\\u0000${k}`)\n run = []\n }\n for (const p of list) { if (run.length && p - run[run.length - 1] > gap) flush(); run.push(p) }\n flush()\n }\n return running.size ? blocks.filter(b => { const k = key(b); return !k || !running.has(`${b.pageNumber}\\u0000${k}`) }) : blocks\n}\n","import type { ColRect } from './two-column.js'\nimport type { LineSegment } from './line-types.js'\n\n/** A long physical divider can separate columns whose prose is mostly inside tables. */\nexport function findRuledColumnDivider(\n regions: ColRect[], horizontals: LineSegment[], verticals: LineSegment[],\n pageWidth: number, pageHeight: number,\n): number | null {\n const candidates: number[] = []\n for (const v of verticals) {\n const x = v.x1\n if (x < pageWidth * 0.35 || x > pageWidth * 0.65 || v.y2 - v.y1 < pageHeight * 0.65) continue\n const body = regions.filter(r => r.w >= pageWidth * 0.15 && r.h >= 12 && r.y >= v.y1 && r.y + r.h <= v.y2)\n const left = body.filter(r => r.x + r.w <= x - 5)\n const right = body.filter(r => r.x >= x + 5)\n if (left.length < 2 || right.length < 2) continue\n const bounds = (rs: ColRect[]) => ({ bottom: Math.min(...rs.map(r => r.y)), top: Math.max(...rs.map(r => r.y + r.h)) })\n const a = bounds(left), b = bounds(right)\n const lo = Math.min(a.bottom, b.bottom), hi = Math.max(a.top, b.top)\n if (Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom) < (hi - lo) * 0.6) continue\n if (regions.some(r => r.x < x && r.x + r.w > x && r.y < hi && r.y + r.h > lo)) continue\n // A table's internal ruling joins horizontal borders; a column divider does not.\n if (horizontals.some(h => h.y1 > lo + 2 && h.y1 < hi - 2 && h.x1 < x - 5 && h.x2 > x + 5)) continue\n if (!candidates.some(c => Math.abs(c - x) < 3)) candidates.push(x)\n }\n return candidates.length === 1 ? candidates[0] : null\n}\n","/**\n * 기하 전용 2단 레이아웃 검출 + 밴드 읽기순 정렬 (#64)\n *\n * findTwoColumnProseCutX(cluster-detector)는 속기록류 2단 \"프로즈\" 전용이라\n * justify·마커 희박·긴 줄을 요구한다 — 시험지(①~⑤ 마커 다량·짧은 선지 줄)나\n * 표가 많은 2단 지면에서는 불발한다. 여기는 텍스트 특성 없이 순수 기하 신호만 본다:\n * ① 중앙(30~70%) 스캔에서 높이 가중 커버리지가 거의 0인 빈 띠(거터) 존재\n * ② 좌·우 양측 모두 충분한 요소 수·높이 합·수직 범위 (사이드바/서명란 배제)\n * ③ 좌우 단 폭 대칭 (라벨열+본문 구조 배제)\n * 전폭 요소(표 머리글·쪽번호 박스 등)는 폭 필터로 스캔에서 제외하고,\n * 정렬 단계에서 밴드 경계로 쓴다 (밴드 내 좌단 전체 → 우단 전체).\n */\n\n/** 검출·정렬 입력 사각형 (PDF pt, y는 아래가 원점 — bottom-up) */\nexport interface ColRect {\n x: number\n y: number\n w: number\n h: number\n}\n\n/** 거터 스캔 범위 (콘텐츠 폭 비율) */\nconst GUTTER_SCAN_LO = 0.3\nconst GUTTER_SCAN_HI = 0.7\n/** 스캔 샘플 수 상한 (오염 좌표 폭주 방지 — findTwoColumnProseCutX와 동일 발상) */\nconst GUTTER_MAX_SAMPLES = 400\n/** 전폭(cross) 요소 판정: 콘텐츠 폭 대비 비율 — 이보다 넓으면 스캔 제외 */\nconst WIDE_RECT_RATIO = 0.55\n/** 거터 허용 커버리지: 좁은 요소 높이 합 대비 (쪽번호 박스·중앙 꼬리표 몇 개 허용) */\nconst GUTTER_MAX_COVER_RATIO = 0.06\n/** 좌·우 각 측 최소 요소 수 */\nconst SIDE_MIN_COUNT = 5\n/** 좌·우 높이 합 비율 최소값 (작은 사이드바 배제) */\nconst SIDE_MIN_HEIGHT_RATIO = 0.3\n/** 좌·우 각 측 수직 범위 최소값 (전체 수직 범위 대비 — 상단 구석 요소 배제) */\nconst SIDE_MIN_VSPAN_RATIO = 0.45\n/** 좌우 단 폭 대칭 최소값 */\nconst SIDE_MIN_WIDTH_SYMMETRY = 0.5\n/** 2단으로 인정할 최소 콘텐츠 폭 (pt) */\nconst MIN_CONTENT_SPAN = 300\n/** 텍스트급 rect 판정 높이 상한 (pt) — 이보다 크면 표/블록 bbox로 본다 */\nconst TEXT_RECT_MAX_H = 20\n/** 같은 행 판정 y 오차 (pt) */\nconst ROW_Y_TOL = 2\n/** 좌·우 각 측 최소 텍스트 행 수 — 진짜 단은 세로로 길다 (코퍼스 실측: 시험지 25~60행,\n * 오발화 스파스 페이지 ~10행) */\nconst SIDE_MIN_TEXT_ROWS = 12\n/** 크로스 연속 행 판정 갭 (pt) — 같은 행의 좌측 끝~우측 시작이 이보다 좁으면\n * 거터가 문장/단어 사이를 뚫고 지나간 것 (진짜 단 마진은 30pt+) */\nconst CROSS_ROW_TINY_GAP = 12\n/** 허용 크로스 연속 행 수 (노이즈 1행 허용) */\nconst MAX_TINY_GAP_ROWS = 1\n/** 양측 동시 행 비율 상한 — 초과는 행 짝 구조(예산서·대비표: 라벨-값이 같은 행) */\nconst MAX_BOTH_SIDE_ROW_RATIO = 0.65\n/** 텍스트급 rect가 이보다 적으면 블록(표) 지배 지면 — 행 가드 생략 (블록 재배열은\n * 텍스트를 쪼개지 않아 안전) */\nconst BLOCK_DOMINANT_TEXT_RECTS = 10\n\n/**\n * 좁은 요소들의 x-프로젝션(높이 가중)에서 중앙 빈 띠를 찾아 거터 x를 반환.\n * 2단 레이아웃이 아니라고 판단되면 null.\n */\nexport function detectColumnGutter(rects: ColRect[]): number | null {\n if (rects.length < 8) return null\n\n let minX = Infinity\n let maxX = -Infinity\n for (const r of rects) {\n if (r.x < minX) minX = r.x\n if (r.x + r.w > maxX) maxX = r.x + r.w\n }\n const span = maxX - minX\n if (!Number.isFinite(span) || span < MIN_CONTENT_SPAN) return null\n\n // 전폭 요소 제외 — 표 머리글/전면 표는 밴드 경계일 뿐 단 구조 신호가 아님\n const narrow = rects.filter(r => r.w < span * WIDE_RECT_RATIO && r.w > 0 && r.h > 0)\n if (narrow.length < SIDE_MIN_COUNT * 2) return null\n\n let totalH = 0\n for (const r of narrow) totalH += r.h\n if (totalH <= 0) return null\n\n // 중앙부 스캔: 각 x를 덮는 좁은 요소의 높이 합이 최소인 지점 = 거터 후보\n const lo = minX + span * GUTTER_SCAN_LO\n const hi = minX + span * GUTTER_SCAN_HI\n const step = Math.max(2, (hi - lo) / GUTTER_MAX_SAMPLES)\n let gutterX = 0\n let bestCover = Infinity\n for (let x = lo; x <= hi; x += step) {\n let cover = 0\n for (const r of narrow) {\n if (r.x < x && r.x + r.w > x) cover += r.h\n }\n if (cover < bestCover) { bestCover = cover; gutterX = x }\n }\n if (bestCover > totalH * GUTTER_MAX_COVER_RATIO) return null\n\n // 좌·우 분류 (거터를 걸치는 요소는 어느 쪽도 아님 — 위에서 커버리지로 이미 소수 보장)\n const left: ColRect[] = []\n const right: ColRect[] = []\n for (const r of narrow) {\n if (r.x + r.w <= gutterX) left.push(r)\n else if (r.x >= gutterX) right.push(r)\n }\n if (left.length < SIDE_MIN_COUNT || right.length < SIDE_MIN_COUNT) return null\n\n const sideStats = (side: ColRect[]) => {\n let sMinX = Infinity, sMaxR = -Infinity, sMinY = Infinity, sMaxT = -Infinity, hSum = 0\n for (const r of side) {\n if (r.x < sMinX) sMinX = r.x\n if (r.x + r.w > sMaxR) sMaxR = r.x + r.w\n if (r.y < sMinY) sMinY = r.y\n if (r.y + r.h > sMaxT) sMaxT = r.y + r.h\n hSum += r.h\n }\n return { width: sMaxR - sMinX, vspan: sMaxT - sMinY, minY: sMinY, maxT: sMaxT, hSum }\n }\n const L = sideStats(left)\n const R = sideStats(right)\n\n // 높이 합 균형 — 본문 옆 작은 사이드바(부서명·서명란)로는 단 분리하지 않는다\n if (Math.min(L.hSum, R.hSum) / Math.max(L.hSum, R.hSum) < SIDE_MIN_HEIGHT_RATIO) return null\n\n // 양측 모두 지면 수직 범위의 상당 부분을 차지해야 함\n const unionVspan = Math.max(L.maxT, R.maxT) - Math.min(L.minY, R.minY)\n if (unionVspan <= 0) return null\n if (L.vspan / unionVspan < SIDE_MIN_VSPAN_RATIO) return null\n if (R.vspan / unionVspan < SIDE_MIN_VSPAN_RATIO) return null\n\n // 좌우 단 폭 대칭 — 라벨열+본문(비대칭)은 표/공문 구조일 가능성\n if (Math.min(L.width, R.width) / Math.max(L.width, R.width) < SIDE_MIN_WIDTH_SYMMETRY) return null\n\n // 행 구조 가드 — 오발화의 두 형태를 코퍼스 실측 지표로 기각한다:\n // (a) 거터가 프로즈 문장의 단어 갭을 뚫고 지나간 경우: 같은 행의 좌측 끝~우측\n // 시작 간격이 단어 갭 수준(<12pt)인 행이 생긴다. 진짜 단 마진은 30pt+.\n // (b) 예산서·대비표류 행 짝 구조: 거의 모든 행이 좌(라벨)·우(값) 동시 존재.\n // 진짜 2단은 단끼리 독립 조판이라 동시 행 비율이 낮다 (실측 0.11~0.59 vs 0.69~1.0).\n // 여기서 오발화하면 한 행이 두 단으로 찢겨 텍스트가 유실된다 (코퍼스 -0.7%p 실측).\n const textRects = (side: ColRect[]) => side.filter(r => r.h <= TEXT_RECT_MAX_H)\n const lText = textRects(left)\n const rText = textRects(right)\n if (lText.length + rText.length >= BLOCK_DOMINANT_TEXT_RECTS) {\n interface RowEdge { lEnd: number; rStart: number }\n const rows = new Map<number, RowEdge>()\n const rowKey = (y: number) => Math.round(y / ROW_Y_TOL)\n for (const r of lText) {\n const k = rowKey(r.y)\n const e = rows.get(k) ?? { lEnd: -Infinity, rStart: Infinity }\n if (r.x + r.w > e.lEnd) e.lEnd = r.x + r.w\n rows.set(k, e)\n }\n const lRowCount = rows.size\n for (const r of rText) {\n const k = rowKey(r.y)\n const e = rows.get(k) ?? { lEnd: -Infinity, rStart: Infinity }\n if (r.x < e.rStart) e.rStart = r.x\n rows.set(k, e)\n }\n let rRowCount = 0\n let bothRows = 0\n let tinyGapRows = 0\n for (const e of rows.values()) {\n if (e.rStart < Infinity) rRowCount++\n if (e.lEnd > -Infinity && e.rStart < Infinity) {\n bothRows++\n if (e.rStart - e.lEnd < CROSS_ROW_TINY_GAP) tinyGapRows++\n }\n }\n if (lRowCount < SIDE_MIN_TEXT_ROWS || rRowCount < SIDE_MIN_TEXT_ROWS) return null\n if (tinyGapRows > MAX_TINY_GAP_ROWS) return null\n if (bothRows / Math.min(lRowCount, rRowCount) > MAX_BOTH_SIDE_ROW_RATIO) return null\n }\n\n return gutterX\n}\n\n/**\n * A footer or full-width caption can hide an otherwise stable column gutter.\n * Require the same gutter in two overlapping upper-page bands before removing\n * any text from table detection. A one-off gutter in a form or table is not\n * enough evidence to partition the page.\n */\nexport function detectPersistentColumnGutter(rects: ColRect[]): number | null {\n if (rects.length < 24) return null\n let minY = Infinity, maxY = -Infinity\n for (const r of rects) {\n if (r.y < minY) minY = r.y\n if (r.y > maxY) maxY = r.y\n }\n const span = maxY - minY\n if (!Number.isFinite(span) || span < 200) return null\n const upper80 = detectColumnGutter(rects.filter(r => r.y >= minY + span * 0.2))\n const upper60 = upper80 === null ? null : detectColumnGutter(rects.filter(r => r.y >= minY + span * 0.4))\n if (upper80 !== null && upper60 !== null && Math.abs(upper80 - upper60) <= 10) return (upper80 + upper60) / 2\n\n // A full-width title and author band can mask the gutter in the upper\n // slices even when two independent prose columns persist below it.\n const lower80 = detectColumnGutter(rects.filter(r => r.y <= maxY - span * 0.2))\n if (lower80 === null) return null\n const lower60 = detectColumnGutter(rects.filter(r => r.y <= maxY - span * 0.4))\n return lower60 !== null && Math.abs(lower80 - lower60) <= 10 ? (lower80 + lower60) / 2 : null\n}\n\n/**\n * 거터 기준 밴드 읽기순 정렬 — 거터를 가로지르는 유닛(전폭 표·머리글·쪽번호)을\n * 위→아래 밴드 경계로 삼고, 각 밴드 안에서 좌단 전체(위→아래) → 우단 전체 순서.\n * 유닛 내부 순서는 건드리지 않는다.\n */\nexport function orderByGutter<T>(units: T[], rectOf: (u: T) => ColRect, gutterX: number): T[] {\n interface Tagged { u: T; top: number }\n const left: Tagged[] = []\n const right: Tagged[] = []\n const cross: Tagged[] = []\n for (const u of units) {\n const r = rectOf(u)\n const top = r.y + r.h\n if (r.x < gutterX && r.x + r.w > gutterX) cross.push({ u, top })\n else if (r.x + r.w <= gutterX) left.push({ u, top })\n else right.push({ u, top })\n }\n const byTopDesc = (a: Tagged, b: Tagged) => b.top - a.top\n cross.sort(byTopDesc)\n left.sort(byTopDesc)\n right.sort(byTopDesc)\n\n // 밴드 k = 자기보다 위에 있는(top이 더 큰) 경계 수\n const bandOf = (top: number) => {\n let k = 0\n while (k < cross.length && cross[k].top > top) k++\n return k\n }\n\n const ordered: T[] = []\n for (let k = 0; k <= cross.length; k++) {\n for (const t of left) { if (bandOf(t.top) === k) ordered.push(t.u) }\n for (const t of right) { if (bandOf(t.top) === k) ordered.push(t.u) }\n if (k < cross.length) ordered.push(cross[k].u)\n }\n return ordered\n}\n\n/**\n * 세 단 이상 패널 지면(슬라이드의 나란한 패널 3개 등)의 거터들. 전폭이 아닌 요소가 하나도 걸치지 않는 세로 빈 띠(8pt 이상)를\n * 가운데 70% 범위에서 찾고, 거터로 나뉜 단마다 요소가 3개 이상·세로로 요소 범위의 30% 이상을 차지할 때만 낸다(2단은 detectColumnGutter).\n */\nexport function detectPanelGutters(rects: ColRect[]): number[] | null {\n if (rects.length < 9) return null\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity\n for (const r of rects) {\n if (r.x < minX) minX = r.x\n if (r.x + r.w > maxX) maxX = r.x + r.w\n }\n const span = maxX - minX\n if (!Number.isFinite(span) || span < MIN_CONTENT_SPAN) return null\n // 전폭급 줄(같은 높이에서 큰 틈 없이 이어져 폭 절반 이상을 덮는 제목 줄)은 밴드 경계라 거터 판정에서 뺀다\n const wideLine = (r: ColRect) => {\n const same = rects.filter(o => Math.abs(o.y - r.y) <= 3).sort((a, b) => a.x - b.x)\n let start = same[0].x, end = same[0].x + same[0].w\n for (const o of same.slice(1)) {\n if (o.x - end > Math.max(o.h, r.h) * 2) {\n if (r.x >= start && r.x + r.w <= end) break\n start = o.x\n }\n end = Math.max(end, o.x + o.w)\n }\n return r.x >= start && r.x + r.w <= end + 1 && end - start >= span * 0.5\n }\n const narrow = rects.filter(r => r.w < span * WIDE_RECT_RATIO && r.w > 0 && r.h > 0 && !wideLine(r))\n for (const r of narrow) {\n if (r.y < minY) minY = r.y\n if (r.y + r.h > maxY) maxY = r.y + r.h\n }\n const gutters: number[] = []\n let runStart: number | null = null\n // 후보 수 상한 400 — 정상 판형은 2pt 그대로, 비트 뒤집힘 등으로 좌표가 터무니없는 요소가 낀 쪽만 성기게(종전엔 사실상 무한 루프)\n const step = Math.max(2, span * 0.7 / 400)\n for (let x = minX + span * 0.15; x <= maxX - span * 0.15; x += step) {\n const empty = !narrow.some(r => r.x < x && r.x + r.w > x)\n if (empty && runStart === null) runStart = x\n if (!empty && runStart !== null) {\n if (x - runStart >= 8) gutters.push((runStart + x) / 2)\n runStart = null\n }\n }\n if (gutters.length < 2) return null\n const bounds = [-Infinity, ...gutters, Infinity]\n for (let c = 0; c + 1 < bounds.length; c++) {\n const col = narrow.filter(r => r.x >= bounds[c] && r.x + r.w <= bounds[c + 1])\n if (col.length < 3) return null\n const top = Math.max(...col.map(r => r.y + r.h)), bottom = Math.min(...col.map(r => r.y))\n if (top - bottom < (maxY - minY) * 0.3) return null\n }\n return gutters\n}\n\n/** 여러 거터로 나뉜 패널 읽기 순서 — 거터를 걸치는 유닛을 밴드 경계로, 밴드 안에서 왼쪽 단부터 단마다 위→아래 */\nexport function orderByPanels<T>(units: T[], rectOf: (u: T) => ColRect, gutters: number[]): T[] {\n const colOf = (r: ColRect) => {\n for (let c = 0; c <= gutters.length; c++) {\n const lo = c === 0 ? -Infinity : gutters[c - 1], hi = c === gutters.length ? Infinity : gutters[c]\n if (r.x >= lo - 1 && r.x + r.w <= hi + 1) return c\n }\n return -1\n }\n const tagged = units.map(u => { const r = rectOf(u); return { u, top: r.y + r.h, col: colOf(r) } })\n const cross = tagged.filter(t => t.col < 0).sort((a, b) => b.top - a.top)\n const bandOf = (top: number) => { let k = 0; while (k < cross.length && cross[k].top > top) k++; return k }\n const ordered: T[] = []\n for (let k = 0; k <= cross.length; k++) {\n for (let c = 0; c <= gutters.length; c++) {\n for (const t of tagged.filter(t => t.col === c && bandOf(t.top) === k).sort((a, b) => b.top - a.top)) ordered.push(t.u)\n }\n if (k < cross.length) ordered.push(cross[k].u)\n }\n return ordered\n}\n","/** 그림으로 단이 드러나는 3단 리플릿 — 클러스터 표 감지 전에 독립 패널을 나눈다. */\nimport { detectPanelGutters, type ColRect } from \"./two-column.js\"\nimport { groupByY, type NormItem } from \"./text-line.js\"\n\nexport function splitImagePanels(items: NormItem[], figures: ColRect[]): NormItem[][] | null {\n if (figures.length === 0) return null\n const rects = items.map(i => ({ x: i.x, y: i.y, w: i.w, h: i.h > 0 ? i.h : i.fontSize }))\n const cuts = detectPanelGutters([...rects, ...figures])\n // 전폭 제목·표를 가르는 단은 기존 밴드 경로에 맡긴다.\n if (!cuts || cuts.length !== 2 || items.some(i => cuts.some(x => i.x < x && i.x + i.w > x))) return null\n const groups = [\n items.filter(i => i.x + i.w <= cuts[0]),\n items.filter(i => i.x >= cuts[0] && i.x + i.w <= cuts[1]),\n items.filter(i => i.x >= cuts[1]),\n ]\n const rows = groupByY(items)\n // 같은 행을 공유하는 세 열은 독립 조판보다 표의 증거다 (2단 거터와 같은 65% 가드).\n const sharedRows = rows.filter(row => new Set(row.map(i => i.x >= cuts[1] ? 2 : i.x >= cuts[0] ? 1 : 0)).size === 3).length\n if (sharedRows / Math.min(...groups.map(group => groupByY(group).length)) > 0.65) return null\n // 제목이 여러 PDF 텍스트 조각으로 나뉘어도 작은 어절 틈으로 이어진 전폭 줄은 가르지 않는다.\n for (const row of rows) {\n const ordered = [...row].sort((a, b) => a.x - b.x)\n let start = ordered[0].x, end = start + ordered[0].w, height = ordered[0].h || ordered[0].fontSize\n for (const i of ordered.slice(1)) {\n const h = i.h || i.fontSize\n if (i.x - end > Math.max(height, h) * 2) start = i.x\n end = Math.max(end, i.x + i.w)\n height = h\n if (cuts.some(x => start < x && end > x)) return null\n }\n }\n return groups\n}\n","/**\n * 밑줄 빈칸 — 서식 채움 \"翻译成 ______ (语言)\"·\"성명 ______ (인)\" 의 빈칸은 글이 없고 기준선에 밑줄만 긋는다.\n * 읽기 순서(XY-Cut)는 글 아이템만 보므로 빈칸 두 줄이 같은 x 에 걸치면 그 빈 띠를 단 사이로 잘라 줄 뒤쪽 조각을 문단 밖으로 뺐다\n * (kpipa 계약서 중문 \"…(Proprietor)____,与海外\" → \",与海外\" 가 다음 줄 뒤로), 클러스터 표 감지는 빈칸을 열 사이로 읽었다.\n * 빈칸 왼쪽 글 아이템을 빈칸 끝까지 넓히고 공백 한 칸을 붙이고, 빈칸 밑줄은 선 목록에서 뺀다(가로선만 있는 괘선 띠 표로\n * 읽히지 않게) — 원본(DOCX)의 빈칸도 밑줄 친 공백이다.\n * 같은 줄 글이 밑줄 양 끝에 맞닿아(6pt 안) 있고, 밑줄이 글자 두 자 넘게 길고, 끝이 세로선에 닿지 않아야(표 괘선 조각) 빈칸이다\n */\n\nimport type { LineSegment } from \"./line-types.js\"\nimport type { NormItem } from \"./text-line.js\"\n\n/** 밑줄 양 끝과 이웃 글 사이 허용 틈 (pt) */\nconst FILL_EDGE_GAP = 6\n/** 밑줄 최대 두께 (pt) */\nconst FILL_MAX_THICKNESS = 2\n\nexport function fillBlanks(items: NormItem[], horizontals: LineSegment[], verticals: LineSegment[] = []): { items: NormItem[]; horizontals: LineSegment[] } {\n const widened = new Map<NormItem, NormItem>()\n const used = new Set<LineSegment>()\n for (const h of horizontals) {\n if (h.lineWidth > FILL_MAX_THICKNESS || Math.abs(h.y1 - h.y2) > 1) continue\n const x1 = Math.min(h.x1, h.x2), x2 = Math.max(h.x1, h.x2)\n // 기준선 바로 아래(글자 크기 0.5배 안) 같은 줄 글\n const row = items.filter(i => i.y >= h.y1 && i.y - h.y1 <= Math.max(3, i.fontSize * 0.5))\n const left = row.find(i => i.x + i.w <= x1 + 1 && x1 - (i.x + i.w) <= FILL_EDGE_GAP)\n const right = row.find(i => i.x >= x2 - 1 && i.x - x2 <= FILL_EDGE_GAP)\n // 밑줄 위에 글이 있으면 글 밑줄이다\n if (!left || !right || widened.has(left) || row.some(i => i.x < x2 && i.x + i.w > x1) || x2 - x1 < 2 * left.fontSize) continue\n // 끝이 세로선에 닿으면 표 괘선 조각이다\n if (verticals.some(v => (Math.abs(v.x1 - x1) <= 3 || Math.abs(v.x1 - x2) <= 3) && Math.min(v.y1, v.y2) - 3 <= h.y1 && Math.max(v.y1, v.y2) + 3 >= h.y1)) continue\n widened.set(left, { ...left, text: left.text.trimEnd() + \" \", w: x2 - left.x })\n used.add(h)\n }\n if (!used.size) return { items, horizontals }\n return { items: items.map(i => widened.get(i) ?? i), horizontals: horizontals.filter(h => !used.has(h)) }\n}\n","/**\n * 열 경계 감지 + 열 기반 텍스트/테이블 추출 (선 없는 PDF fallback 경로).\n *\n * x-히스토그램 클러스터링으로 열 경계를 찾고(detectColumns),\n * y-라인을 열에 배치해 마크다운 표를 조립한다(extractWithColumns → buildGridTable).\n */\n\nimport { safeMin, safeMax } from \"../utils.js\"\nimport { type NormItem, mergeLineSimple, collapseEvenSpacing } from \"./text-line.js\"\n\n// ═══════════════════════════════════════════════════════\n// 열 경계 감지 — 빈도 기반 x-히스토그램 클러스터링\n// ═══════════════════════════════════════════════════════\n\n/** prose 라인 판별: 아이템 간 gap이 모두 작으면 문장 (단어 나열) */\nfunction isProseSpread(items: NormItem[]): boolean {\n if (items.length < 3) return false\n const sorted = [...items].sort((a, b) => a.x - b.x)\n const gaps: number[] = []\n for (let i = 1; i < sorted.length; i++) {\n gaps.push(sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w))\n }\n // gap의 최대값이 작고 평균 단어 길이가 짧으면 prose\n const maxGap = safeMax(gaps)\n const avgLen = items.reduce((s, i) => s + i.text.length, 0) / items.length\n // 짧은 단어들이 좁은 간격으로 나열 = prose (예: \"위 표 제3호나목에서 남은 유효기간...\")\n if (items.length >= 4 && maxGap < 40 && avgLen < 5) return true\n // 양쪽 정렬 영문 줄 — 낱말마다 아이템이고 줄 안 낱말 틈이 모두 같다(조판기가 남는 폭을 고르게 나눔). 좁은 단의 틈은 1em 을\n // 넘어 낱말 시작 x 가 가짜 열 3~4개를 만들고, 줄들이 한 표 행으로 뭉쳐 뒤섞였다(ODL 043 오른 단 인용문 \"interesting the and\n // COVID, …\"). 표 칸 사이 틈은 칸마다 다르다. 숫자·표시(O·X) 칸은 폭이 같아 틈도 같을 수 있어 소문자로 시작하는 낱말이\n // 절반 이상인 줄만 본다(표 칸은 대개 대문자·숫자로 시작, 한글은 대소문자가 없어 해당 없음)\n const minGap = safeMin(gaps)\n return minGap > 0 && maxGap - minGap <= Math.max(2, maxGap * 0.15) &&\n items.filter(i => /^\\P{L}*\\p{Ll}/u.test(i.text)).length * 2 >= items.length\n}\n\nexport function detectColumns(yLines: NormItem[][]): number[] | null {\n const allItems = yLines.flat()\n if (allItems.length === 0) return null\n const pageWidth = safeMax(allItems.map(i => i.x + i.w)) - safeMin(allItems.map(i => i.x))\n if (pageWidth < 100) return null\n\n // \"비고\" 이전 아이템만 사용 (비고 이후는 prose)\n let bigoLineIdx = -1\n for (let i = 0; i < yLines.length; i++) {\n if (yLines[i].length <= 2 && yLines[i].some(item => item.text === \"비고\")) {\n bigoLineIdx = i\n break\n }\n }\n const tableYLines = bigoLineIdx >= 0 ? yLines.slice(0, bigoLineIdx) : yLines\n\n // Step 1: 모든 아이템의 x를 수집 (prose 라인 제외)\n // CLUSTER_TOL 22px — 한국 공문서 PDF 열 간격에 최적화, 별표 표 열 감지 핵심값\n const CLUSTER_TOL = 22\n const xClusters: { center: number; count: number; minX: number }[] = []\n\n for (const line of tableYLines) {\n if (isProseSpread(line)) continue\n const byX = [...line].sort((a, b) => a.x - b.x)\n for (let k = 0; k < byX.length; k++) {\n const item = byX[k]\n // 앞 조각에 1em 미만 간격으로 이어진 조각은 열 시작이 아니다 — 한 줄이 글꼴 전환·겹친 런·양쪽 정렬 낱말로\n // 여러 아이템으로 쪼개진 본문(MS Print To PDF 보도자료 2쪽)은 조각 x 가 가짜 열 7개를 만들어 쪽 전체가 탭 섞인\n // 한 문단으로 뒤섞였다. 열 사이는 표에서 보통 1em 이상 비어 있다\n if (k > 0 && item.x - (byX[k - 1].x + byX[k - 1].w) < Math.max(item.fontSize, byX[k - 1].fontSize)) continue\n let found = false\n for (const c of xClusters) {\n if (Math.abs(item.x - c.center) <= CLUSTER_TOL) {\n c.center = Math.round((c.center * c.count + item.x) / (c.count + 1))\n c.minX = Math.min(c.minX, item.x)\n c.count++\n found = true\n break\n }\n }\n if (!found) {\n xClusters.push({ center: item.x, count: 1, minX: item.x })\n }\n }\n }\n\n // Step 2: 빈도 피크 — 최소 3회 이상 등장 (단발성 텍스트 노이즈 제거)\n const peaks = xClusters\n .filter(c => c.count >= 3)\n .sort((a, b) => a.minX - b.minX)\n\n // 최소 3개 열이 있어야 테이블로 판별 — 2열은 일반 2단 레이아웃과 구분 불가\n if (peaks.length < 3) return null\n\n // Step 3: 가까운 피크 병합 — MERGE_TOL 40px (같은 논리 열의 미세 위치 차이 흡수)\n const MERGE_TOL = 40\n const merged: { center: number; count: number; minX: number }[] = [peaks[0]]\n for (let i = 1; i < peaks.length; i++) {\n const prev = merged[merged.length - 1]\n if (peaks[i].minX - prev.minX < MERGE_TOL) {\n // 빈도 높은 쪽 유지, 최소 x는 작은 값\n if (peaks[i].count > prev.count) {\n prev.center = peaks[i].center\n }\n prev.count += peaks[i].count\n prev.minX = Math.min(prev.minX, peaks[i].minX)\n } else {\n merged.push({ ...peaks[i] })\n }\n }\n\n // 열 경계 = 각 클러스터의 minX (왼쪽 정렬 기준), 병합 후 재검증\n const rawColumns = merged.filter(c => c.count >= 3).map(c => c.minX)\n if (rawColumns.length < 3) return null\n\n // 최소 열 폭 검증: 30px 미만인 열은 인접 열과 병합 (한 글자 열 방지)\n const MIN_DETECT_COL_WIDTH = 30\n const columns: number[] = [rawColumns[0]]\n for (let i = 1; i < rawColumns.length; i++) {\n if (rawColumns[i] - columns[columns.length - 1] < MIN_DETECT_COL_WIDTH) continue\n columns.push(rawColumns[i])\n }\n if (columns.length < 3) return null\n\n // Repeated x starts alone are insufficient: equations and justified prose\n // can create three peaks without any actual multi-cell data row.\n const shortMultiColumnRows = tableYLines.filter(line => {\n if (mergeLineSimple(line).length > 80 || isProseSpread(line)) return false\n const used = new Set(line.map(item => findColumn(item.x, columns)))\n return used.size >= 3\n }).length\n return shortMultiColumnRows >= 2 ? columns : null\n}\n\nfunction findColumn(x: number, columns: number[]): number {\n for (let i = columns.length - 1; i >= 0; i--) {\n // 10px 왼쪽 허용 오차 — 셀 내 텍스트 미세 좌측 이탈 보정\n if (x >= columns[i] - 10) return i\n }\n return 0\n}\n\n// ═══════════════════════════════════════════════════════\n// 열 기반 추출 — 테이블/텍스트 영역 분리\n// ═══════════════════════════════════════════════════════\n\nexport function extractWithColumns(yLines: NormItem[][], columns: number[]): string {\n const result: string[] = []\n const colMin = columns[0]\n const colMax = columns[columns.length - 1]\n\n // \"비고\" 라인 감지 — 이후는 텍스트로 처리\n let bigoIdx = -1\n for (let i = 0; i < yLines.length; i++) {\n if (yLines[i].length <= 2 && yLines[i].some(item => item.text === \"비고\")) {\n bigoIdx = i\n break\n }\n }\n\n // 테이블 시작: 첫 번째 다열(3+ 열 사용) 라인\n let tableStart = -1\n for (let i = 0; i < (bigoIdx >= 0 ? bigoIdx : yLines.length); i++) {\n const usedCols = new Set(yLines[i].map(item => findColumn(item.x, columns)))\n if (usedCols.size >= 3) {\n tableStart = i\n break\n }\n }\n\n const tableEnd = bigoIdx >= 0 ? bigoIdx : yLines.length\n\n // 테이블 시작 이전 = 텍스트\n for (let i = 0; i < (tableStart >= 0 ? tableStart : tableEnd); i++) {\n result.push(mergeLineSimple(yLines[i]))\n }\n\n // 테이블 영역: 모든 라인을 그리드에 포함 (단일 아이템 라인도)\n if (tableStart >= 0) {\n const tableLines = yLines.slice(tableStart, tableEnd)\n // 테이블 x범위 밖의 라인만 텍스트로 분리\n // 좌측 20px, 우측 200px 허용 — 비고/주석 열이 오른쪽에 넓게 위치하는 공문서 특성 반영\n const gridLines: NormItem[][] = []\n for (const line of tableLines) {\n const inRange = line.some(item =>\n item.x >= colMin - 20 && item.x <= colMax + 200\n )\n if (inRange && !isProseSpread(line)) {\n gridLines.push(line)\n } else {\n // 그리드 밖 라인은 현재까지 축적된 그리드 출력 후 텍스트로\n if (gridLines.length > 0) {\n result.push(buildGridTable(gridLines.splice(0), columns))\n }\n result.push(mergeLineSimple(line))\n }\n }\n if (gridLines.length > 0) {\n result.push(buildGridTable(gridLines, columns))\n }\n }\n\n // 비고 영역\n if (bigoIdx >= 0) {\n result.push(\"\")\n for (let i = bigoIdx; i < yLines.length; i++) {\n result.push(mergeLineSimple(yLines[i]))\n }\n }\n\n return result.join(\"\\n\")\n}\n\n// ═══════════════════════════════════════════════════════\n// 그리드 테이블 빌더 — y-라인을 열에 배치 후 행 병합\n// ═══════════════════════════════════════════════════════\n\nfunction buildGridTable(lines: NormItem[][], columns: number[]): string {\n const numCols = columns.length\n\n // Step 1: 각 y-라인을 열에 배치 — 한 칸에 든 조각은 본문 줄과 같은 공백 규칙(mergeLineSimple)으로 잇는다. 종전처럼\n // 조각마다 공백을 넣으면 글꼴이 바뀔 때마다 쪼개진 낱말이 벌어졌다(\"입\"+\"력하\"+\"면\" → \"입 력하 면\", HWP3 변환 한컴 PDF)\n const yRows: string[][] = lines.map(items => {\n const byCol: NormItem[][] = Array.from({ length: numCols }, () => [])\n for (const item of items) byCol[findColumn(item.x, columns)].push(item)\n return byCol.map(cell => mergeLineSimple(cell))\n })\n\n // Step 2: 행 병합 — 새 논리적 행 판별\n // 데이터 열 기준점 (가격 등이 들어가는 오른쪽 열들)\n const dataColStart = Math.max(2, Math.floor(numCols / 2))\n const merged: string[][] = []\n\n for (const row of yRows) {\n if (row.every(c => c === \"\")) continue\n\n if (merged.length === 0) {\n merged.push([...row])\n continue\n }\n\n const prev = merged[merged.length - 1]\n const filledCols = row.map((c, i) => c ? i : -1).filter(i => i >= 0)\n const filledCount = filledCols.length\n\n let isNewRow = false\n\n // Rule 1: col 0에 텍스트 (3글자 이상) → 새 행 (단, \"권\"처럼 짧은 건 continuation)\n if (row[0] && row[0].length >= 3) {\n isNewRow = true\n }\n\n // Rule 2: col 1에 텍스트 → 항상 새 행 (새 항목 시작)\n if (!isNewRow && numCols > 1 && row[1]) {\n isNewRow = true\n }\n\n // Rule 3: 데이터 열(3+)에 새 값이 있고 이전 행 데이터 열에도 이미 값 있음 → 새 가격 행\n if (!isNewRow) {\n const hasData = row.slice(dataColStart).some(c => c !== \"\")\n const prevHasData = prev.slice(dataColStart).some(c => c !== \"\")\n if (hasData && prevHasData) {\n isNewRow = true\n }\n }\n\n // Exception: filledCount=1이고 col 0에 짧은 텍스트(≤2자) → word continuation (예: \"권\", \"여권\")\n if (isNewRow && filledCount === 1 && row[0] && row[0].length <= 2) {\n isNewRow = false\n }\n\n if (isNewRow) {\n merged.push([...row])\n } else {\n for (let c = 0; c < numCols; c++) {\n if (row[c]) {\n prev[c] = prev[c] ? prev[c] + \" \" + row[c] : row[c]\n }\n }\n }\n }\n\n if (merged.length < 2) {\n return merged.map(r => r.filter(c => c).join(\" \")).join(\"\\n\")\n }\n\n // Step 3: 헤더 행 병합 — 첫 N행이 모두 데이터열(dataColStart+)에 값이 없으면 헤더\n let headerEnd = 0\n for (let r = 0; r < merged.length; r++) {\n const hasDataValues = merged[r].slice(dataColStart).some(c => c && /\\d/.test(c))\n if (hasDataValues) break\n headerEnd = r + 1\n }\n\n if (headerEnd > 1) {\n // 헤더 행들을 하나로 합침\n const headerRow = Array(numCols).fill(\"\")\n for (let r = 0; r < headerEnd; r++) {\n for (let c = 0; c < numCols; c++) {\n if (merged[r][c]) {\n headerRow[c] = headerRow[c] ? headerRow[c] + \" \" + merged[r][c] : merged[r][c]\n }\n }\n }\n merged.splice(0, headerEnd, headerRow)\n }\n\n // Step 3.5: 셀 텍스트 균등배분 공백 제거 (\"경 제 총 괄 반\" → \"경제총괄반\")\n for (const row of merged) {\n for (let c = 0; c < row.length; c++) {\n if (row[c]) row[c] = collapseEvenSpacing(row[c])\n }\n }\n\n // Step 3.6: 테이블 품질 검증 — 선 없는 fallback 경로에서는 보수적으로\n const totalCells = merged.length * numCols\n const filledCells = merged.reduce((s, row) => s + row.filter(c => c).length, 0)\n // 빈 셀 과반, 행이 2 미만, 또는 3행 이하+7열 이상 → 텍스트로 복원\n if (filledCells < totalCells * 0.35 || merged.length < 2 ||\n (merged.length <= 3 && numCols >= 7)) {\n return merged.map(r => r.filter(c => c).join(\"\\t\")).join(\"\\n\")\n }\n\n // Step 4: 마크다운 테이블 — 셀 텍스트의 `|`는 열 구분자를 깨뜨리므로 이스케이프,\n // `\\t`는 공백으로 (그리드 경로 builder.ts 셀 조립과 정합)\n const escCell = (c: string) => c.replace(/\\t/g, \" \").replace(/\\|/g, \"\\\\|\")\n const md: string[] = []\n md.push(\"| \" + merged[0].map(escCell).join(\" | \") + \" |\")\n md.push(\"| \" + merged[0].map(() => \"---\").join(\" | \") + \" |\")\n for (let r = 1; r < merged.length; r++) {\n md.push(\"| \" + merged[r].map(escCell).join(\" | \") + \" |\")\n }\n return md.join(\"\\n\")\n}\n","/**\n * 밑줄 감지 — baseline 바로 아래에 밀착한 얇은 수평선을 텍스트 아이템과 상관.\n *\n * PDF 에는 밑줄 폰트 플래그가 없다 — 밑줄은 별도 그래픽(선/얇은 사각형)으로\n * 그려지므로, 추출된 텍스트와 기하로 대조해 복원한다. 개정문 추가·변경 표시,\n * 제목 강조 보존용. 표 괘선 오탐 방어 4겹 — 수직선 접촉, 동일 스팬 반복,\n * 런 union 밀착(containment + coverage), 런 사이 컬럼급 구멍.\n *\n * firecrawl/pdf-inspector underline 휴리스틱 참조 (MIT)\n * https://github.com/firecrawl/pdf-inspector — src/extractor/underline.rs\n */\n\nimport type { LineSegment } from \"./line-types.js\"\nimport type { NormItem } from \"./text-line.js\"\n\n/** 밑줄 최대 두께 (pt) — 굵은 선은 배경 채움/테두리 */\nconst UNDER_MAX_THICKNESS = 2.0\n/** baseline 아래 허용 깊이 — CJK 밑줄은 전각 박스 하단(~0.67em)까지 내려간다 */\nconst UNDER_BELOW_EM = 0.72\n/** baseline 아래 허용 깊이 최소값 (pt, 작은 폰트 라운딩) */\nconst UNDER_BELOW_MIN_PT = 3.0\n/** baseline 위 허용 (pt, 라운딩) — 다음 줄 텍스트가 위 줄 밑줄에 매칭되는 것 차단 */\nconst UNDER_ABOVE_PT = 1.0\n/** 선이 아이템을 덮어야 하는 최소 수평 비율 */\nconst UNDER_MIN_OVERLAP_RATIO = 0.6\n/** 선이 매칭 런 union 을 벗어날 수 있는 좌우 여유 (em 배수 / 최소 pt) */\nconst UNDER_OWNER_PAD_EM = 0.75\nconst UNDER_OWNER_PAD_MIN_PT = 4.0\n/** 매칭 텍스트 폭 합 / 선 폭 최소 비율 — 표 행 괘선은 셀 패딩·빈 열까지 걸쳐 미달 */\nconst UNDER_MIN_COVERAGE = 0.6\n/** 수직선 접촉 판정 오차 (pt) — 접촉하면 표 그리드/폼 박스의 변 */\nconst UNDER_GRID_EPS = 2.0\n/** 매칭 런 사이 구멍 한계 (em 배수) — 컬럼급 구멍이 있으면 표 행 괘선 */\nconst UNDER_COLUMN_GAP_EM = 2.0\n/** 동일 x-스팬 수평선이 반복되는 y 레벨 수 — 이 이상이면 수평 괘선만의 표/폼 */\nconst UNDER_REPEATED_SPAN_LEVELS = 3\n/** 동일 스팬 판정: 겹침 / 넓은 쪽 폭 최소 비율 */\nconst UNDER_SPAN_OVERLAP_RATIO = 0.8\n/** 박스 상변 짝 탐색 창 (em 배수) — 배지/칩 높이는 대략 1~2줄 */\nconst UNDER_BOX_PAIR_MIN_EM = 0.5\nconst UNDER_BOX_PAIR_MAX_EM = 2.2\n/** 상변 짝 스팬 유사성: 겹침 / 넓은 쪽 폭 (표 하변은 폭이 크게 달라 제외됨) */\nconst UNDER_BOX_PAIR_OVERLAP = 0.8\n\n/**\n * baseline 바로 아래에 밀착한 얇은 수평선을 찾아 해당 아이템에 underline 마킹.\n */\nexport function markUnderlineItems(items: NormItem[], horizontals: LineSegment[], verticals: LineSegment[], nonRules?: ReadonlySet<LineSegment>): LineSegment[] {\n const underlines: LineSegment[] = []\n // Filled background outlines and white paint are geometry, not underline ink.\n if (nonRules?.size) horizontals = horizontals.filter(line => !nonRules.has(line))\n if (items.length === 0 || horizontals.length === 0) return underlines\n\n // Repeated text underlines can share a span with table rules. Require separate,\n // tightly owned lines before overriding the repetition guard. Cache ownership\n // because the same candidate can support several lines in the paragraph.\n const tightOwners = new Map<LineSegment, boolean>()\n const tightlyOwned = (line: LineSegment): boolean => {\n const cached = tightOwners.get(line)\n if (cached !== undefined) return cached\n const owners = items.filter(item => {\n const below = item.y - line.y1\n return item.w > 0 && item.text.trim() && below >= 0.5 && below <= 3\n && Math.min(line.x2, item.x + item.w) > Math.max(line.x1, item.x)\n }).sort((a, b) => a.x - b.x)\n let owned = owners.length > 0\n && (Math.abs(owners[0].x - line.x1) <= UNDER_GRID_EPS\n // A single PDF run can include an un-underlined note marker before the line.\n || (owners.length === 1 && line.x1 > owners[0].x\n && line.x1 - owners[0].x <= Math.max(owners[0].h, owners[0].fontSize, 1) * 3))\n && Math.abs(owners[owners.length - 1].x + owners[owners.length - 1].w - line.x2) <= UNDER_GRID_EPS\n for (let i = 1; owned && i < owners.length; i++) {\n const em = Math.max(owners[i].h, owners[i].fontSize, 1)\n if (owners[i].x - (owners[i - 1].x + owners[i - 1].w) > em * UNDER_COLUMN_GAP_EM) owned = false\n }\n tightOwners.set(line, owned)\n return owned\n }\n const paragraphUnderline = (line: LineSegment): boolean => {\n if (!tightlyOwned(line)) return false\n const width = line.x2 - line.x1\n const ys: number[] = []\n for (const other of horizontals) {\n const overlap = Math.min(line.x2, other.x2) - Math.max(line.x1, other.x1)\n if (other.lineWidth > UNDER_MAX_THICKNESS || overlap / Math.max(width, other.x2 - other.x1) < UNDER_SPAN_OVERLAP_RATIO || !tightlyOwned(other)) continue\n if (!ys.some(y => Math.abs(y - other.y1) < UNDER_GRID_EPS)) ys.push(other.y1)\n if (ys.length >= UNDER_REPEATED_SPAN_LEVELS) return true\n }\n return false\n }\n\n for (const line of horizontals) {\n if (line.lineWidth > UNDER_MAX_THICKNESS) continue\n const enclosed = insideTallCell(line, verticals, 0)\n const repeated = isRepeatedSpanRule(line, horizontals)\n const textSeries = repeated && paragraphUnderline(line)\n if (!enclosed && (touchesVertical(line, verticals) || (repeated && !textSeries))) continue\n\n const matches: NormItem[] = []\n for (const item of items) {\n const h = item.h > 0 ? item.h : item.fontSize\n if (h <= 0 || item.w <= 0 || !item.text.trim()) continue\n const below = Math.max(h * UNDER_BELOW_EM, UNDER_BELOW_MIN_PT)\n if (line.y1 < item.y - below || line.y1 > item.y + UNDER_ABOVE_PT) continue\n const overlap = Math.min(line.x2, item.x + item.w) - Math.max(line.x1, item.x)\n if (overlap / item.w < UNDER_MIN_OVERLAP_RATIO) continue\n matches.push(item)\n }\n if (matches.length === 0) continue\n\n // 밀착 소유 검증: 선이 매칭 런 union 에 밀착해야 밑줄\n let x1 = Infinity, x2 = -Infinity, maxH = 0, covered = 0\n for (const m of matches) {\n x1 = Math.min(x1, m.x)\n x2 = Math.max(x2, m.x + m.w)\n maxH = Math.max(maxH, m.h > 0 ? m.h : m.fontSize)\n covered += m.w\n }\n // 긴 셀의 패딩 안쪽에 끝나는 반복 밑줄은 행 경계가 아니다. 실제 괘선은 셀 변에 닿는다.\n const inset = enclosed && insideTallCell(line, verticals, maxH)\n if (enclosed && !inset && (touchesVertical(line, verticals) || (repeated && !textSeries))) continue\n const pad = Math.max(maxH * UNDER_OWNER_PAD_EM, UNDER_OWNER_PAD_MIN_PT)\n if (line.x1 < x1 - pad || line.x2 > x2 + pad) continue\n if (covered < (line.x2 - line.x1) * UNDER_MIN_COVERAGE) continue\n\n // 위쪽에 같은 스팬의 수평선이 마주보면 배지/칩/제목박스의 하변 — 밑줄은 위짝이 없다.\n // 라운드 모서리 배지는 상하변이 직선으로만 나와 수직선 접촉 방어를 비껴가므로 필수.\n if (hasBoxTopPair(line, maxH, horizontals, inset || textSeries || tightlyOwned(line) ? items : undefined)) continue\n\n // 매칭 런 사이 컬럼급 구멍 → 표 행 괘선 (밑줄 줄은 단어 간격 수준으로 연속)\n matches.sort((a, b) => a.x - b.x)\n let hole = false\n for (let i = 1; i < matches.length; i++) {\n if (matches[i].x - (matches[i - 1].x + matches[i - 1].w) > maxH * UNDER_COLUMN_GAP_EM) {\n hole = true\n break\n }\n }\n if (hole) continue\n\n for (const m of matches) m.underline = true\n underlines.push(line)\n }\n return underlines\n}\n\n/** 여러 줄 높이의 두 셀 변 사이에 양끝이 엄격히 들어간 선. 중간 열 경계를 가로지르면 제외한다. */\nfunction insideTallCell(line: LineSegment, verticals: LineSegment[], em: number): boolean {\n let left = false, right = false\n for (const v of verticals) {\n const lo = Math.min(v.y1, v.y2), hi = Math.max(v.y1, v.y2)\n if (lo > line.y1 || hi < line.y1) continue\n if (v.x1 >= line.x1 - 0.5 && v.x1 <= line.x2 + 0.5) return false\n if (hi - lo <= em * UNDER_BOX_PAIR_MAX_EM) continue\n if (v.x1 < line.x1 - 0.5) left = true\n if (v.x1 > line.x2 + 0.5) right = true\n }\n return left && right\n}\n\n/** 선의 x-스팬 내에서 선 y 를 지나는(접촉 포함) 수직선 존재 여부 — 표 그리드/폼 박스 판정 */\nfunction touchesVertical(line: LineSegment, verticals: LineSegment[]): boolean {\n for (const v of verticals) {\n if (v.x1 < line.x1 - UNDER_GRID_EPS || v.x1 > line.x2 + UNDER_GRID_EPS) continue\n const lo = Math.min(v.y1, v.y2), hi = Math.max(v.y1, v.y2)\n if (hi - lo <= UNDER_GRID_EPS) continue\n if (lo <= line.y1 + UNDER_GRID_EPS && hi >= line.y1 - UNDER_GRID_EPS) return true\n }\n return false\n}\n\n/** 후보선 위쪽 0.5~2.2em 에 거의 같은 스팬의 수평선 존재 — 박스/배지의 상변 짝 */\nfunction hasBoxTopPair(line: LineSegment, maxH: number, horizontals: LineSegment[], cellItems?: NormItem[]): boolean {\n const w = line.x2 - line.x1\n if (w <= 0) return false\n for (const o of horizontals) {\n if (o === line) continue\n const dy = o.y1 - line.y1\n if (dy < maxH * UNDER_BOX_PAIR_MIN_EM || dy > maxH * UNDER_BOX_PAIR_MAX_EM) continue\n const ow = o.x2 - o.x1\n if (ow <= 0) continue\n const overlap = Math.min(line.x2, o.x2) - Math.max(line.x1, o.x1)\n if (overlap / Math.max(w, ow) < UNDER_BOX_PAIR_OVERLAP) continue\n // 긴 셀의 위짝도 바로 위 본문에 밀착한 밑줄이면 박스 상변이 아니다. 텍스트 없는 배지 상변은 지킨다.\n const covered = cellItems?.reduce((sum, item) => {\n const below = item.y - o.y1\n if (below < 0.5 || below > 3 || !item.text.trim()) return sum\n return sum + Math.max(0, Math.min(o.x2, item.x + item.w) - Math.max(o.x1, item.x))\n }, 0) ?? 0\n if (covered < ow * UNDER_MIN_COVERAGE) return true\n }\n return false\n}\n\n/** 거의 같은 x-스팬의 수평선이 3+ y 레벨에서 반복 — 수평 괘선만 쓰는 표/폼 (서식류) */\nfunction isRepeatedSpanRule(line: LineSegment, horizontals: LineSegment[]): boolean {\n const w = line.x2 - line.x1\n if (w <= 0) return false\n const ys: number[] = []\n for (const o of horizontals) {\n const ow = o.x2 - o.x1\n if (ow <= 0) continue\n const overlap = Math.min(line.x2, o.x2) - Math.max(line.x1, o.x1)\n if (overlap / Math.max(w, ow) < UNDER_SPAN_OVERLAP_RATIO) continue\n if (!ys.some(y => Math.abs(y - o.y1) < 2)) ys.push(o.y1)\n }\n return ys.length >= UNDER_REPEATED_SPAN_LEVELS\n}\n\n/**\n * underline 마킹된 아이템 run 을 <u>...</u> 로 감싼다 (GFM 은 밑줄 문법이 없어 인라인 HTML).\n * 같은 시각적 줄이라도 1em 넘게 떨어진 구간은 별도 run — 사이의 비밑줄 텍스트 오염 방지.\n */\nexport function wrapUnderlineRuns(items: NormItem[]): void {\n const marked = items.filter(i => i.underline)\n if (marked.length === 0) return\n\n const lines = new Map<number, NormItem[]>()\n for (const item of marked) {\n const key = Math.round(item.y / 3)\n const arr = lines.get(key) || []\n arr.push(item)\n lines.set(key, arr)\n }\n for (const arr of lines.values()) {\n arr.sort((a, b) => a.x - b.x)\n let runStart = 0\n for (let i = 1; i <= arr.length; i++) {\n const prev = arr[i - 1]\n const em = Math.max(prev.h > 0 ? prev.h : prev.fontSize, 1)\n const gap = i < arr.length ? arr[i].x - (prev.x + prev.w) : Infinity\n if (gap > em) {\n arr[runStart].text = \"<u>\" + arr[runStart].text\n prev.text = prev.text + \"</u>\"\n runStart = i\n }\n }\n }\n}\n","/**\n * 선 격자 바로 위 무괘선 머리행.\n *\n * 몸통만 괘선으로 가르고 머리행 위·아래에는 선을 긋지 않은 표(ODL 052 \"REGIONS | 2007-2010 | …\", 182 슬라이드 표)는\n * 격자가 몸통에서 끝나 머리 글이 표 위 문단으로 빠진다. 격자 윗변 바로 위 한 줄이 격자 폭 안에서 열마다 따로 놓이면\n * (칸 경계를 걸친 글 없이 두 열 이상) 그 줄을 머리행으로 붙인다.\n *\n * 텍스트층 글에만 쓴다 — OCR 글은 래스터 괘선과 함께 오고 머리 위 제목 줄 조각이 열마다 흩어져 보여\n * 괴산 예산서 표를 무너뜨렸다(cellF1 0.946 → 0.444).\n */\n\nimport type { NormItem } from \"./text-line.js\"\n\n/** 격자 윗변에서 머리 줄 기준선까지 허용 거리 (글자 크기 배수) */\nconst MAX_GAP_K = 2.2\n/** 격자 폭 밖 허용 (pt) */\nconst EDGE_TOL = 3\n\n/**\n * 격자(colXs, 윗변 top) 바로 위 머리 줄의 열별 글 — 없으면 null.\n * @param free 아직 어느 표·문단에도 안 쓰인 쪽 글\n */\nexport function headerLineAbove(free: NormItem[], colXs: number[], top: number): NormItem[][] | null {\n const x1 = colXs[0], x2 = colXs[colXs.length - 1]\n const above = free.filter(it => it.y > top && it.text.trim())\n if (above.length === 0) return null\n // 격자에 가장 가까운 줄\n const baseY = Math.min(...above.map(it => it.y))\n const line = above.filter(it => Math.abs(it.y - baseY) <= Math.max(2, it.fontSize * 0.3))\n const fs = Math.max(...line.map(it => it.fontSize))\n if (baseY - top > fs * MAX_GAP_K) return null\n // 표 캡션 줄(\"Table 7.1. | Types of …\")은 머리행이 아니다\n const text = [...line].sort((a, b) => a.x - b.x).map(it => it.text).join(\" \").trim()\n if (/^(?:Table|Figure|Fig\\.|Chart|Exhibit|Diagram|Source|Note|<?표|<?그림)\\s*[\\dIVX]/i.test(text)) return null\n if (line.some(it => it.seq === undefined || it.x < x1 - EDGE_TOL || it.x + it.w > x2 + EDGE_TOL)) return null\n // 같은 줄에 격자 밖 글이 있으면 본문 줄이다\n if (above.some(it => !line.includes(it) && Math.abs(it.y - baseY) <= fs * 0.5)) return null\n const cols: NormItem[][] = colXs.slice(1).map(() => [])\n for (const it of line) {\n const first = colXs.findIndex((x, k) => k < colXs.length - 1 && it.x + 1 >= x && it.x + 1 < colXs[k + 1])\n const last = colXs.findIndex((x, k) => k < colXs.length - 1 && it.x + it.w - 1 > x && it.x + it.w - 1 <= colXs[k + 1])\n if (first < 0 || first !== last) return null\n cols[first].push(it)\n }\n if (cols.filter(c => c.length > 0).length < Math.max(2, Math.ceil((colXs.length - 1) / 2))) return null\n return cols\n}\n","/**\n * 걸쳐 겹친 글상자 — 잡지식 쪽은 글상자 테두리를 서로 걸쳐 겹쳐 그린다(책 소개 쪽: 상자 다섯이 엇갈려 겹침). 선 격자 감지는 맞닿은 선을\n * 한 격자로 묶어 겹친 상자들을 9×4 표로 짰고, 줄 끝 낱말이 따로 열이 됐다(BookReview \"정년은 | 짧아지고,\"). 한 상자의 꼭짓점이 다른 상자\n * 안쪽에 들어가 있고 그 꼭짓점에서 두 변이 끝나면(선이 꼭짓점 밖으로 이어지지 않음) 걸친 상자다. 표 격자는 행 띠·열 띠가 겹쳐도 안쪽\n * 꼭짓점마다 선이 십자·T 로 지나가 끝나는 꼭짓점이 없다(ODL 078 행 띠 × 열 띠)\n */\n\nimport type { LineSegment, TableGrid } from \"./line-types.js\"\n\nconst TOL = 2\n\ninterface Rect { x1: number; x2: number; y1: number; y2: number }\n\nconst near = (a: number, b: number): boolean => Math.abs(a - b) <= TOL\nconst span = (a: number, b: number): [number, number] => (a <= b ? [a, b] : [b, a])\n\n/** 위·아래 가로선이 같은 x 범위이고 그 양 끝을 세로선이 덮는 닫힌 사각형들 */\nfunction closedRects(hs: LineSegment[], vs: LineSegment[]): Rect[] {\n const covers = (v: LineSegment, x: number, y1: number, y2: number): boolean => {\n const [lo, hi] = span(v.y1, v.y2)\n return near(v.x1, x) && lo <= y1 + TOL && hi >= y2 - TOL\n }\n const out: Rect[] = []\n for (let i = 0; i < hs.length; i++) {\n const [ax1, ax2] = span(hs[i].x1, hs[i].x2)\n for (let j = i + 1; j < hs.length; j++) {\n const [bx1, bx2] = span(hs[j].x1, hs[j].x2)\n if (!near(ax1, bx1) || !near(ax2, bx2) || near(hs[i].y1, hs[j].y1)) continue\n const y1 = Math.min(hs[i].y1, hs[j].y1), y2 = Math.max(hs[i].y1, hs[j].y1)\n if (!vs.some(v => covers(v, ax1, y1, y2)) || !vs.some(v => covers(v, ax2, y1, y2))) continue\n if (!out.some(o => near(o.x1, ax1) && near(o.x2, ax2) && near(o.y1, y1) && near(o.y2, y2))) out.push({ x1: ax1, x2: ax2, y1, y2 })\n }\n }\n return out\n}\n\n/** 꼭짓점 (x, y) 에서 두 변이 끝나나 — 사각형 밖 쪽(dx, dy 방향)으로 그 꼭짓점을 지나 이어지는 가로·세로선이 없다 */\nfunction freeCorner(x: number, y: number, dx: number, dy: number, hs: LineSegment[], vs: LineSegment[]): boolean {\n const beyondX = x + dx * 3 * TOL, beyondY = y + dy * 3 * TOL\n const h = hs.some(l => { const [lo, hi] = span(l.x1, l.x2); return near(l.y1, y) && lo <= beyondX && hi >= beyondX })\n const v = vs.some(l => { const [lo, hi] = span(l.y1, l.y2); return near(l.x1, x) && lo <= beyondY && hi >= beyondY })\n return !h && !v\n}\n\nconst strictlyInside = (x: number, y: number, r: Rect): boolean => x > r.x1 + TOL && x < r.x2 - TOL && y > r.y1 + TOL && y < r.y2 - TOL\nconst contains = (a: Rect, b: Rect): boolean => a.x1 <= b.x1 + TOL && a.x2 >= b.x2 - TOL && a.y1 <= b.y1 + TOL && a.y2 >= b.y2 - TOL\n\n/** a 의 꼭짓점 가운데 b 안쪽에 들어가 두 변이 끝나는 것이 있나 */\nfunction cornerIn(a: Rect, b: Rect, hs: LineSegment[], vs: LineSegment[]): boolean {\n for (const [x, dx] of [[a.x1, -1], [a.x2, 1]] as const) for (const [y, dy] of [[a.y1, -1], [a.y2, 1]] as const) {\n if (strictlyInside(x, y, b) && freeCorner(x, y, dx, dy, hs, vs)) return true\n }\n return false\n}\n\n/** 격자 선 가운데 서로 걸쳐 겹친 닫힌 사각형 쌍이 있나 */\nexport function hasCrossingFrames(grid: TableGrid, horizontals: LineSegment[], verticals: LineSegment[]): boolean {\n const b = grid.bbox\n const inside = (l: LineSegment): boolean => Math.min(l.x1, l.x2) >= b.x1 - TOL && Math.max(l.x1, l.x2) <= b.x2 + TOL &&\n Math.min(l.y1, l.y2) >= b.y1 - TOL && Math.max(l.y1, l.y2) <= b.y2 + TOL\n const hs = horizontals.filter(inside), vs = verticals.filter(inside)\n const rects = closedRects(hs, vs)\n for (let i = 0; i < rects.length; i++) for (let j = i + 1; j < rects.length; j++) {\n const a = rects[i], c = rects[j]\n // 한쪽이 다른 쪽을 통째로 품으면 중첩 상자다\n if (contains(a, c) || contains(c, a)) continue\n if (cornerIn(a, c, hs, vs) || cornerIn(c, a, hs, vs)) return true\n }\n return false\n}\n","/**\n * 통계표 행 머리의 배분 정렬 두 음절 — \"건 축\"·\"토 목\"·\"전 체\" 처럼 두 음절을 칸 폭에 벌려 둔 행 머리를 칸 글 조립(cellTextToString)은\n * 칸 하나만 보고 어휘로만 판정해 붙이지 못했다: 문서에 \"건축\" 이 홀로 선 어절로 안 나오고(합성어 속뿐) 표 칸의 \"건 축\" 자체가 줄 안 띄움\n * 증거로 잡혔다(2025 건설업조사 보도자료 행 머리 70곳). 값 칸 둘 이상이 숫자인 행에서, 두 음절이 칸 폭을 꽉 채운 배분 정렬이고(칸 폭에서\n * 두 글자 높이를 뺀 것 이상, 줄 폭은 글자 높이 3배 이상) 붙인 꼴이 문서 줄 안 어절의 머리로 나오거나 시도 이름이면 붙인다. 가운데에 띄어 쓴\n * 이름표(\"충 북\", 벼·고추 재배면적조사 — 원문도 띄움)는 칸을 채우지 않아, 서식 이름표(\"성 명\")는 값 칸이 숫자가 아니라 걸리지 않는다.\n * 두 음절 사이에 진짜 공백 글리프가 있으면 원문이 띄운 칸이라 붙이지 않는다(\"인 정\"·\"보 험\" — 제주4·3 보상 신청 표·가계대출 동향). 공백 글리프가\n * 없다고 원문이 붙인 것은 아니다(해외직접투자 보도자료 \"전 체\" 는 글리프 없이 띄움) — 그래서 어절 머리 증거는 그대로 둔다\n */\n\nimport type { IRCell } from \"../types.js\"\nimport { CELL_LINES } from \"./table-meta.js\"\nimport type { WrapLexicon } from \"./line-wrap.js\"\n\nconst NUMBER = /^[-+△▲▽]?\\d[\\d,]*(?:\\.\\d+)?%?$/\nconst SPREAD = /^([가-힣]) ([가-힣])$/\n\n/** 숫자 값 행의 벌어진 두 음절 머리 칸을 제자리에서 붙인다 */\nexport function joinSpreadRowLabels(grid: IRCell[][], lex: WrapLexicon, regions: ReadonlySet<string>,\n boxOf: (cell: IRCell) => { x1: number; x2: number } | undefined, spaced: (cell: IRCell) => boolean): void {\n for (const row of grid) {\n if (row.filter(c => NUMBER.test(c.text.trim())).length < 2) continue\n for (const cell of row) {\n const m = SPREAD.exec(cell.text.trim())\n if (!m) continue\n const lines = CELL_LINES.get(cell), box = boxOf(cell)\n if (!lines || lines.length !== 1 || !box || spaced(cell)) continue\n const { l, r, h } = lines[0]\n if (r - l < 3 * h || r - l < box.x2 - box.x1 - 2 * h) continue\n const word = m[1] + m[2]\n if (lex.startsWord(word) || lex.isWord(word) || regions.has(word)) cell.text = word\n }\n }\n}\n","/**\n * 한컴 칸 클립 격자 표의 보이는 변 — 보이지 않는 틀 표 풀기(table/layout-frames, v4.17.0)의 PDF 쪽 곁정보.\n *\n * 한컴 PDF 는 테두리 \"없음\" 칸에도 칸마다 클립(`W n`)을 깔아 kordoc 이 틀 표를 그대로 표로 복원한다(clip-cells) — 할부거래법\n * 시행령 [별표 1] 은 원본 그림이 글과 분수뿐인데 5열 표가 됐다. 한컴은 보이는 칸 테두리만 칸 경계 좌표 그대로 선(획)으로 긋는다\n * (같은 문서 분수 막대 236.39~316.15 @659.52 = 분자 칸 클립 236.51~315.91 밑변 ±0.12, 대기환경보전법 시행령 [별표 8] 표 괘선도\n * 칸 클립 변과 같은 좌표). 그래서 칸 사각형 네 변 자리에 실제로 그은 선이 변 길이의 대부분을 덮으면 보이는 변이다.\n *\n * 선은 line-extract 가 모은 추출 선만 본다 — 획과 가는(≤1.5pt) 채움 사각형 괘선, 칸 높이가 낮아 MIN_LINE_LENGTH 밑으로 버려지는\n * 짧은 조각까지. 불투명도 0 획은 line-extract 가 이미 뺐고, 흰 선(흰 바탕에 안 보임)·두꺼운 채움 영역의 윤곽(칸 음영)은 nonRules 로\n * 따로 받아 뺀다. kordoc 이 합성한 가상 테두리(개방 변 합성 open-table-ends·closeOpenTableEdges)는 이 선들 뒤에 더해지므로 들어오지 않는다.\n * 같은 문서 HWP·HWPX 의 칸 테두리(borderFill)와 대조: 모양이 같은 표끼리 단위 격자 선분(맞닿은 두 칸 변 합침)의 보임 여부가 법령 별표\n * 236쌍 25,625개 중 99.95%, 표 정답 세트(보도자료·rhwp) 5,365쌍 435,737개 중 99.73% 같다. 어긋남은 대부분 PDF 에만 보이는 선이다 —\n * 표 자체 테두리(hp:tbl borderFillIDRef, 칸 테두리 밖에 그려짐)와 맞닿은 다른 표의 테두리.\n *\n * 곁정보는 클립 격자(TableGrid.cells — clip-cells 가 만든 것)의 칸에만 둔다. 선 격자·클러스터(무괘선)·booktabs·글상자 표에는 두지\n * 않아 layout-frames 가 손대지 않는다(곁정보 없는 표는 그대로) — 영문 PDF 의 무괘선 표가 글로 풀리지 않게. 워드 등 다른 제작기도 칸마다\n * 글 클립을 깔아 클립 격자가 서므로 푸는 것 자체는 한컴 PDF 에서만 한다(parser.ts isHancomPdf).\n */\n\nimport type { IRCell } from \"../types.js\"\nimport { CELL_EDGES, type Edges } from \"../table/layout-frames.js\"\nimport type { ExtractedCell, LineSegment, TableGrid } from \"./line-types.js\"\n\n/** 클립 격자 칸 → 보이는 변 (page-blocks 가 IR 칸의 CELL_EDGES 로 옮긴다) */\nexport const CLIP_CELL_EDGES = new WeakMap<ExtractedCell, Edges>()\n\n/** 보이는 변이 하나도 없는 칸 — 클립 없던 자리를 메운 칸·표 위 단위 줄처럼 자기 테두리가 없는 칸 (이웃 칸 변이 선을 댄다) */\nexport const NO_EDGES: Readonly<Edges> = Object.freeze({ t: false, b: false, l: false, r: false })\n\n/** 선이 변 자리에 있다고 보는 거리 (pt) — 한컴은 칸 경계 좌표에 긋는다(0.01pt 안). 셀 간격 표는 괘선 틈을 닫은 좌표(clip-cells\n * closeGaps: 틈 가운데·괘선 쪽 끝, 틈 3pt 이하)라 괘선이 1.5pt 안에 있다. 칸 안 글 밑줄(기준선 아래)은 칸 밑변에서 이보다 멀다 */\nconst EDGE_NEAR = 1.6\n/** 보이는 변 — 변 자리 선이 변 길이의 이 비율 이상을 덮고, 덮이지 않은 길이가 EDGE_SLACK 이하여야 한다. 한컴은 보이는 변 전체에\n * 긋는다(변 양끝을 0.12pt 넘김) — 법령 별표 273건 클립 격자 칸 변 83,056개 가운데 선이 절반 넘게 덮은 변은 덮이지 않은 길이가 0.3pt\n * 안 56,668개·1.5pt 안 1,114개(점선 끝 틈)이고 1.5~5.6pt 는 하나도 없다. 그보다 많이 빈 변은 옆 칸 테두리가 걸친 것이다 — 틀 칸 안\n * 좌우 5.6pt 빈 칸 사이에 든 산식 상자(물환경보전법 시행령 [별표 14의2])의 윗·밑줄이 그 위아래 온 폭 틀 칸 변의 97.6% 를 덮지만\n * 양끝 10.9pt 가 빈다. 그 선은 상자 칸 변으로만 센다(layout-frames 가 맞닿은 두 칸 변을 합쳐 선을 본다) */\nconst EDGE_COVER = 0.6\nconst EDGE_SLACK = 3\n/** 이어 보는 선 조각 틈 (pt) — 점선·파선 테두리는 획 조각 사이가 떠 있다 */\nconst DASH_GAP = 3\n/** 옆 칸 괘선 끝 (pt) — 한컴은 괘선을 칸 변 양끝에서 선 굵기 절반만큼 넘겨 긋는다(0.24pt 선 0.12pt). 한쪽 끝만 넘어 들어온 이보다 짧은\n * 조각은 옆 칸 선의 끝이다. 좁은 빈 칸(상자 사이 간격 열)은 양옆 상자 괘선 끝 두 조각이 DASH_GAP 안에 마주 보여 온 변이 덮인 것처럼\n * 읽혔다(지역사랑상품권 기본계획 보도자료 추진과제 표 — 간격 열·간격 행이 가로선 띠로 남아 세 표가 한 표로 붙음) */\nconst TIP_MAX = 1.5\n\n/** 한 방향 선 목록 — 자리(가로선 y·세로선 x) 오름차순, 변마다 EDGE_NEAR 안 선만 이분 탐색으로 본다 (칸·선이 많은 쪽에서 모든 쌍을 훑지 않게) */\ninterface Rules { dir: \"h\" | \"v\"; pos: number[]; lines: LineSegment[] }\n\nfunction rules(lines: LineSegment[], dir: \"h\" | \"v\"): Rules {\n const sorted = [...lines].sort((a, b) => (dir === \"h\" ? a.y1 - b.y1 : a.x1 - b.x1))\n return { dir, pos: sorted.map(l => (dir === \"h\" ? l.y1 : l.x1)), lines: sorted }\n}\n\n/** 변(가로 y=at 의 x a1~a2, 세로 x=at 의 y a1~a2)이 보이는가 — 선이 덮은 길이로 판정 */\nfunction seen(r: Rules, at: number, a1: number, a2: number, near = EDGE_NEAR): boolean {\n const len = a2 - a1\n if (len <= 0) return false\n const covered = coverage(r, at, a1, a2, near)\n return covered >= len * EDGE_COVER && len - covered <= EDGE_SLACK\n}\n\n/** 변을 선이 덮은 길이 — 점선 조각 사이 틈(DASH_GAP 안)은 이어 본다 */\nfunction coverage(r: Rules, at: number, a1: number, a2: number, near: number): number {\n let i = 0, j = r.pos.length\n while (i < j) { const m = (i + j) >> 1; if (r.pos[m] < at - near) i = m + 1; else j = m }\n const spans: Array<[number, number]> = []\n for (; i < r.pos.length && r.pos[i] <= at + near; i++) {\n const l = r.lines[i]\n const l1 = r.dir === \"h\" ? Math.min(l.x1, l.x2) : Math.min(l.y1, l.y2), l2 = r.dir === \"h\" ? Math.max(l.x1, l.x2) : Math.max(l.y1, l.y2)\n const lo = Math.max(a1, l1), hi = Math.min(a2, l2)\n if (hi <= lo || ((l1 < a1) !== (l2 > a2) && hi - lo < TIP_MAX)) continue\n spans.push([lo, hi])\n }\n if (!spans.length) return 0\n spans.sort((p, q) => p[0] - q[0])\n let covered = 0, s = spans[0][0], e = spans[0][1]\n for (const [lo, hi] of spans.slice(1)) {\n if (lo <= e + DASH_GAP) e = Math.max(e, hi)\n else { covered += e - s; s = lo; e = hi }\n }\n return covered + e - s\n}\n\n/** 칸 사각형의 보이는 변 (PDF 좌표는 아래→위 — 윗변은 y2) */\nfunction edgesOf(box: { x1: number; y1: number; x2: number; y2: number }, h: Rules, v: Rules, hNear = EDGE_NEAR): Edges {\n return {\n t: seen(h, box.y2, box.x1, box.x2, hNear),\n b: seen(h, box.y1, box.x1, box.x2, hNear),\n l: seen(v, box.x1, box.y1, box.y2),\n r: seen(v, box.x2, box.y1, box.y2),\n }\n}\n\n/** 칸 사각형의 보이는 변 — 선 목록을 그대로 받는 단건 판정 (시험·디버그용) */\nexport function cellEdges(box: { x1: number; y1: number; x2: number; y2: number }, horizontals: LineSegment[], verticals: LineSegment[]): Edges {\n return edgesOf(box, rules(horizontals, \"h\"), rules(verticals, \"v\"))\n}\n\n/**\n * 클립 격자(grid.cells 가 있는 격자 — clip-cells 산출물)의 칸마다 보이는 변을 기록한다. 선 격자(cells 없음)는 건너뛴다.\n * @param horizontals·verticals 추출 선 (합성 테두리를 더하기 전, 짧은 조각 포함)\n * @param nonRules 칸 테두리 선이 아닌 선 (흰 선·채움 영역 윤곽, line-extract)\n */\nexport function recordClipCellEdges(grids: TableGrid[], horizontals: LineSegment[], verticals: LineSegment[], nonRules?: ReadonlySet<LineSegment>): void {\n if (!grids.some(g => g.cells)) return\n const h = rules(nonRules?.size ? horizontals.filter(l => !nonRules.has(l)) : horizontals, \"h\")\n const v = rules(nonRules?.size ? verticals.filter(l => !nonRules.has(l)) : verticals, \"v\")\n // 얇은 채움 띠는 같은 획이 양변으로 잡히지 않게 탐색 창을 줄인다. 실제 두 획과 세로 변은 보존한다.\n for (const g of grids) for (const c of g.cells ?? []) {\n const near = c.filler ? Math.min(EDGE_NEAR, (c.bbox.y2 - c.bbox.y1) / 3) : EDGE_NEAR\n CLIP_CELL_EDGES.set(c, edgesOf(c.bbox, h, v, near))\n }\n}\n\n/** 클립 격자 칸의 보이는 변을 그 칸으로 만든 IR 칸에 옮긴다 (클립 격자 칸이 아니면 아무것도 안 한다) */\nexport function takeClipCellEdges(from: ExtractedCell, to: IRCell): void {\n const e = CLIP_CELL_EDGES.get(from)\n if (e) CELL_EDGES.set(to, e)\n}\n\n/**\n * 쪽 경계로 갈린 한 칸의 두 조각을 이었을 때(위 조각 a 에 아래 조각 b 를 붙임) a 의 보이는 변 — 윗변은 위 조각, 밑변은 아래 조각에서\n * 가져오고 쪽 경계 쪽 변(a 밑변·b 윗변)은 버린다. 좌우는 어느 조각에서든 보이면 보인다. 한쪽이라도 곁정보가 없으면 그대로 둔다\n */\nexport function joinCellEdges(a: IRCell, b: IRCell): void {\n const u = CELL_EDGES.get(a), d = CELL_EDGES.get(b)\n if (!u || !d) return\n CELL_EDGES.set(a, { t: u.t, b: d.b, l: u.l || d.l, r: u.r || d.r })\n}\n","/** 표 위 무괘선 단위 줄 — 원 구조에서는 합성 행, 보이는 표에서는 자기 테두리 없는 문단이다. */\nimport type { IRCell } from \"../types.js\"\nimport type { NormItem } from \"./text-line.js\"\nimport type { TableGrid } from \"./line-types.js\"\nimport { cleanCellText } from \"./cell-text.js\"\nimport { CELL_EDGES } from \"../table/layout-frames.js\"\nimport { NO_EDGES } from \"./cell-edges.js\"\n\nconst UNIT = /^\\s*\\(\\s*단위\\s*[::]/\n\n/** 과소분할 표 재구축을 위한 단위 줄 증거 — 표 위 18pt 안의 줄을 아래부터 본다. */\nexport function rebuildUnitLine(items: NormItem[], grid: TableGrid): NormItem[] {\n const nearby = items.filter(item => item.y >= grid.bbox.y2 && item.y - grid.bbox.y2 <= 18\n && item.x >= grid.bbox.x1 - 3 && item.x + item.w <= grid.bbox.x2 + 3)\n for (const y of [...new Set(nearby.map(item => Math.round(item.y)))].sort((a, b) => a - b)) {\n const line = nearby.filter(item => Math.abs(item.y - y) <= 1).sort((a, b) => a.x - b.x)\n if (UNIT.test(line.map(item => item.text).join(\"\"))) return line\n }\n return []\n}\n\nexport function prependUnitRow(cells: IRCell[][], cols: number, line: NormItem[], usedItems: Set<NormItem>): void {\n const text = cleanCellText(line.map(item => item.text).join(\"\"))\n cells.unshift(Array.from({ length: cols }, (_, c) => ({ text: c === 0 ? text : \"\", colSpan: c === 0 ? cols : 1, rowSpan: 1 })))\n for (const cell of cells[0]) CELL_EDGES.set(cell, NO_EDGES)\n for (const item of line) usedItems.add(item)\n}\n\n/**\n * 칸 안쪽 여백(1.8mm≈5.1pt)만큼 들어간 단위 줄은 원 구조에 합친다.\n * 표 오른끝에 붙은 문단·칸 절반 넘게 빈 조각 격자는 제외한다.\n */\nexport function attachUnitRow(items: NormItem[], grid: TableGrid, cells: IRCell[][], cols: number, usedItems: Set<NormItem>): boolean {\n const all = cells.flat()\n if (all.filter(c => c.text.trim()).length * 2 < all.length || UNIT.test(cells[0]?.[0]?.text ?? \"\")) return false\n const above = items.filter(it => !usedItems.has(it) && it.y >= grid.bbox.y2 && it.y - grid.bbox.y2 <= 8\n && it.x >= grid.bbox.x1 - 3 && it.x + it.w <= grid.bbox.x2 + 3)\n let nearest = Infinity\n for (const it of above) nearest = Math.min(nearest, it.y)\n const line = above.filter(it => Math.abs(it.y - nearest) <= 1).sort((a, b) => a.x - b.x)\n const text = line.map(it => it.text).join(\"\")\n const inset = line.length ? grid.bbox.x2 - (line[line.length - 1].x + line[line.length - 1].w) : 0\n if (!line.length || !UNIT.test(text) || inset < 4.6 || inset > 5.6) return false\n prependUnitRow(cells, cols, line, usedItems)\n return true\n}\n","/**\n * 쪽 틀 — 쪽 테두리 상자(시험지·교재 바탕쪽)는 표가 아니다. 쪽 대부분을 덮는 선 격자가 큰 칸(쪽 면적 15% 이상) 몇 개로\n * 쪽의 절반 넘게 채워지면 그 칸들은 본문 영역이다(단 구분선이 틀에 닿으면 단마다 한 칸, 틀 안 표 괘선이 틀에 이어지면 더 쪼개진다).\n * 표로 세우면 두 단 본문이 한 칸 글로 줄마다 섞인다(수능 모의고사 \"문6)…문1)\" — 단 구분선이 틀에 닿지 않아 칸을 가르지 못함).\n * 표로 세우지 않고 글을 쪽 본문 경로(두 단 거터·괘선 구분선)에 넘긴다. 틀 안 표는 칸 클립 격자가 따로 세운다.\n * 몸통에 가로 괘선이 없는 표(예산서 — 머리 칸만 선, 몸통은 쪽 절반 넘는 한 칸)는 몸통 글이 머리 칸 열 경계 안에 들어앉는다.\n * 틀은 본문 글줄이 격자의 안쪽 열 경계(머리 띠·틀 안 표에서 온 x)를 가로지른다 — 가로지르는 글이 없으면 표로 둔다\n */\n\nimport type { TableGrid } from \"./line-detector.js\"\n\nexport function isPageFrameGrid(grid: TableGrid, cells: Array<{ bbox: { x1: number; y1: number; x2: number; y2: number } }>, pageWidth: number, pageHeight: number, items: Array<{ x: number; y: number; w: number; h: number }> = []): boolean {\n const b = grid.bbox\n if (grid.cells || b.x2 - b.x1 < pageWidth * 0.85 || b.y2 - b.y1 < pageHeight * 0.75) return false\n const pageArea = pageWidth * pageHeight\n const area = (c: { bbox: { x1: number; y1: number; x2: number; y2: number } }) => (c.bbox.x2 - c.bbox.x1) * (c.bbox.y2 - c.bbox.y1)\n const big = cells.filter(c => area(c) >= pageArea * 0.15)\n if (big.reduce((s, c) => s + area(c), 0) < pageArea * 0.5) return false\n const inner = grid.colXs.slice(1, -1)\n const inBig = items.filter(it => big.some(c => { const cx = it.x + it.w / 2, cy = it.y + it.h / 2; return cx > c.bbox.x1 && cx < c.bbox.x2 && cy > c.bbox.y1 && cy < c.bbox.y2 }))\n const crossing = inBig.filter(it => inner.some(x => x > it.x + 1 && x < it.x + it.w - 1)).length\n return inner.length === 0 || (crossing >= 1 && crossing >= inBig.length * 0.01)\n}\n","import type { LineSegment, TableGrid } from \"./line-types.js\"\n\n/** Shaded cell clips supply the omitted outer edges of an otherwise ruled table.\n * Require actual top/bottom rules and every internal divider through the body;\n * a header-only shade or white gaps between cells cannot create a table. */\nexport function closeShadedTableEdges(\n clipGrids: TableGrid[], horizontals: LineSegment[], verticals: LineSegment[], fillRects: TableGrid[\"bbox\"][],\n): { horizontals: LineSegment[]; verticals: LineSegment[]; restored: TableGrid[\"bbox\"][] } {\n const near = (a: number, b: number) => Math.abs(a - b) <= 1.5\n let hs = horizontals, vs = verticals\n const restored: TableGrid[\"bbox\"][] = []\n for (const grid of clipGrids) {\n if (grid.clipParent || grid.rowYs.length !== 2 || grid.colXs.length < 3 || !grid.cells?.length\n || grid.cells.some(c => c.filler || c.rowSpan !== 1 || c.colSpan !== 1)) continue\n const { x1, x2, y1: mid, y2: top } = grid.bbox\n if (!grid.cells.every(c => fillRects.some(f => near(f.x1, c.bbox.x1) && near(f.x2, c.bbox.x2)\n && near(f.y1, c.bbox.y1) && near(f.y2, c.bbox.y2)))) continue\n const topRule = hs.find(h => near(h.y1, top) && near(h.x1, x1) && near(h.x2, x2))\n if (!topRule) continue\n const dividers = grid.colXs.slice(1, -1).map(x => vs.find(v => near(v.x1, x)\n && near(v.y2, top) && v.y1 <= mid - 6))\n if (dividers.some(v => !v)) continue\n const bottom = dividers[0]!.y1\n if (!dividers.every(v => near(v!.y1, bottom))) continue\n const bottomRule = hs.find(h => near(h.y1, bottom) && near(h.x1, x1) && near(h.x2, x2))\n if (!bottomRule) continue\n const missing = [x1, x2].filter(x => !vs.some(v => near(v.x1, x) && v.y1 <= bottom + 1.5 && v.y2 >= top - 1.5))\n if (!missing.length) continue\n restored.push({ x1, x2, y1: bottom, y2: top })\n vs = vs.concat(missing.map(x => ({ x1: x, x2: x, y1: bottom, y2: top, lineWidth: topRule.lineWidth })))\n if (!hs.some(h => near(h.y1, mid) && near(h.x1, x1) && near(h.x2, x2))) {\n hs = hs.concat({ x1, x2, y1: mid, y2: mid, lineWidth: topRule.lineWidth })\n }\n }\n return { horizontals: hs, verticals: vs, restored }\n}\n\n/** A restored inner table is processed before its independent surrounding frame. */\nexport function nestRestoredShadedGrids(grids: TableGrid[], restored: TableGrid[\"bbox\"][], horizontals: LineSegment[], verticals: LineSegment[]): void {\n const near = (a: number, b: number) => Math.abs(a - b) <= 1.5\n if (restored.length === 0) return\n const nested = new Set<TableGrid>()\n for (const grid of grids) {\n const b = grid.bbox\n if (!restored.some(r => near(r.x1, b.x1) && near(r.x2, b.x2) && near(r.y1, b.y1) && near(r.y2, b.y2))) continue\n if (grids.some(g => g !== grid && b.x1 - g.bbox.x1 >= .8 && g.bbox.x2 - b.x2 >= .8\n && b.y1 - g.bbox.y1 >= .8 && g.bbox.y2 - b.y2 >= .8)) {\n grid.lineNested = true\n nested.add(grid)\n }\n }\n for (const frame of grids) {\n const b = frame.bbox\n if (frame.lineNested || ![...nested].some(g => g.bbox.x1 > b.x1 && g.bbox.x2 < b.x2 && g.bbox.y1 > b.y1 && g.bbox.y2 < b.y2)) continue\n const fullH = horizontals.filter(h => near(h.x1, b.x1) && near(h.x2, b.x2) && h.y1 >= b.y1 - 1.5 && h.y1 <= b.y2 + 1.5)\n if (!fullH.some(h => near(h.y1, b.y1)) || !fullH.some(h => near(h.y1, b.y2))\n || fullH.some(h => h.y1 > b.y1 + 1.5 && h.y1 < b.y2 - 1.5)) continue\n const fullV = verticals.filter(v => v.y1 <= b.y1 + 1.5 && v.y2 >= b.y2 - 1.5 && v.x1 >= b.x1 - 1.5 && v.x1 <= b.x2 + 1.5)\n if (!fullV.some(v => near(v.x1, b.x1)) || !fullV.some(v => near(v.x1, b.x2))\n || fullV.some(v => v.x1 > b.x1 + 1.5 && v.x1 < b.x2 - 1.5)) continue\n frame.colXs = [b.x1, b.x2]\n frame.rowYs = [b.y2, b.y1]\n }\n}\n","/**\n * 열린 위·아래 변 표 닫기 (line-detector.ts 계열 전처리, closeOpenTableEdges 의 짝).\n *\n * 서구 보고서 표는 머리행 위 테두리와 합계행 아래 테두리를 긋지 않고, 열 구분 수직선만\n * 그 행까지 내려 긋는 스타일이 있다(ODL 047: 수평선이 데이터 행만 감싸고 머리 두 줄과\n * `Total` 행은 격자 밖). 격자가 데이터 행에서 끝나면 머리행과 합계행이 따로 가짜 표가 된다.\n *\n * 같은 끝점에 이른 수직선이 그 증거다. 끝점 정렬 괘선 묶음(표 몸통)의 맨 위·아래 괘선을\n * 지나 같은 y 까지 뻗은 내부 수직선이 둘 이상이면, 그 y 에 몸통 폭의 가상 괘선을 합성한다.\n * 선이 증거이므로 글 배치로 행을 흡수하지 않는다. 클립 격자는 이 경로를 거치지 않는다(clip-cells).\n */\n\nimport type { LineSegment } from \"./line-types.js\"\nimport { chainCollinearRules } from \"./line-extract.js\"\n\n/** 끝점 정렬 괘선 묶음 허용 오차 (pt) — closeOpenTableEdges EDGE_ALIGN_TOL 과 같다 */\nconst ALIGN_TOL = 3\n/** 몸통 최소 괘선 수 (2행 이상) */\nconst MIN_RULES = 3\n/** 같은 논리 수직선으로 잇는 끊김 허용 (pt) — 셀마다 끊어 그은 세로 획 */\nconst V_CHAIN_GAP = 1.5\n/** 같은 열 x 판정 (pt) */\nconst V_X_TOL = 1.5\n/** 괘선에 닿음 판정 (pt) */\nconst TOUCH_TOL = 2\n/** 몸통 밖으로 뻗은 최소 길이 (pt) — 괘선 교차 삐침 배제, 한 글줄 이상 */\nconst MIN_REACH = 8\n/** 같은 끝점 판정 (pt) */\nconst END_TOL = 1.5\n/** 같은 끝점에 이른 최소 내부 수직선 수 */\nconst MIN_REACHING = 2\n/** 내부 수직선: 몸통 좌우 변에서 이만큼 안쪽 (table-grid MIN_COL_WIDTH) */\nconst INSET = 15\n\n/** 같은 x 에서 끊어 그은 수직 획을 논리 수직선으로 잇는다 (입력 불변) */\nfunction chainVerticals(verticals: LineSegment[]): LineSegment[] {\n const sorted = [...verticals].sort((a, b) => a.x1 - b.x1 || a.y1 - b.y1)\n const out: LineSegment[] = []\n for (const v of sorted) {\n const prev = [...out].reverse().find(o => Math.abs(o.x1 - v.x1) <= V_X_TOL && v.y1 - o.y2 <= V_CHAIN_GAP && v.y2 >= o.y1)\n if (prev) { if (v.y2 > prev.y2) prev.y2 = v.y2; if (v.y1 < prev.y1) prev.y1 = v.y1 }\n else out.push({ ...v })\n }\n return out\n}\n\n/** 몸통 밖 끝점들 가운데 내부 수직선이 가장 많이 모인 끝점 (MIN_REACHING 미만이면 null) */\nfunction commonEnd(ends: number[]): number | null {\n let best: number | null = null, bestN = 0\n for (const e of ends) {\n const n = ends.filter(o => Math.abs(o - e) <= END_TOL).length\n if (n > bestN || (n === bestN && best !== null && Math.abs(e) > Math.abs(best))) { best = e; bestN = n }\n }\n return bestN >= MIN_REACHING ? best : null\n}\n\n/**\n * 양끝이 같은 괘선 묶음을 세로선으로 이어진 몸통마다 가른다. 같은 폭으로 위아래 떨어져 놓인 표들(서식 칸 안 \"기관명 | 개설일 …\"\n * 표 넷, 개인정보 분석 별지)이 한 묶음이면 맨 아래 표의 열린 아래 변만 보고 나머지는 못 닫았다. 이웃 괘선 사이를 잇는 세로선이\n * 없으면 다른 몸통이다. 몸통(괘선 MIN_RULES 개 이상) 조각이 둘 이상일 때만 가른다 — 아니면 종전대로 한 묶음\n */\nfunction splitBodies(g: LineSegment[], chained: LineSegment[]): LineSegment[][] {\n if (g.length < MIN_RULES * 2) return [g]\n const x1 = Math.min(...g.map(r => r.x1)), x2 = Math.max(...g.map(r => r.x2))\n const sorted = [...g].sort((a, b) => b.y1 - a.y1)\n const parts: LineSegment[][] = [[sorted[0]]]\n for (let i = 1; i < sorted.length; i++) {\n const upper = sorted[i - 1].y1, lower = sorted[i].y1\n const joined = chained.some(v => v.x1 >= x1 - ALIGN_TOL && v.x1 <= x2 + ALIGN_TOL && v.y2 >= upper - TOUCH_TOL && v.y1 <= lower + TOUCH_TOL)\n if (joined) parts[parts.length - 1].push(sorted[i])\n else parts.push([sorted[i]])\n }\n return parts.filter(p => p.length >= MIN_RULES).length >= 2 ? parts : [g]\n}\n\n/**\n * 몸통 괘선 묶음의 위·아래로 같은 끝점까지 뻗은 내부 수직선이 둘 이상이면 그 끝점에 가상 수평 괘선을 더한다.\n * 조건 미달이면 입력을 그대로 반환한다.\n * @param textLayer 쪽 글이 모두 텍스트층(seq)일 때만 끝점이 제각각인 아래 변을 닫는다 — OCR 래스터 괘선은 끝점이 흔들린다\n */\nexport function closeOpenTableEnds(horizontals: LineSegment[], verticals: LineSegment[], textLayer = false): LineSegment[] {\n if (horizontals.length < MIN_RULES || verticals.length < MIN_REACHING) return horizontals\n const groups: LineSegment[][] = []\n for (const rule of chainCollinearRules(horizontals)) {\n const g = groups.find(gr => Math.abs(gr[0].x1 - rule.x1) <= ALIGN_TOL && Math.abs(gr[0].x2 - rule.x2) <= ALIGN_TOL)\n if (g) g.push(rule)\n else groups.push([rule])\n }\n const chained = chainVerticals(verticals)\n const added: LineSegment[] = []\n for (const g of groups.flatMap(gr => splitBodies(gr, chained))) {\n if (g.length < MIN_RULES) continue\n const x1 = Math.min(...g.map(r => r.x1)), x2 = Math.max(...g.map(r => r.x2))\n const yLo = Math.min(...g.map(r => r.y1)), yHi = Math.max(...g.map(r => r.y1))\n const interior = chained.filter(v => v.x1 > x1 + INSET && v.x1 < x2 - INSET && v.y1 <= yHi + TOUCH_TOL && v.y2 >= yLo - TOUCH_TOL)\n // 위: 맨 위 괘선에 닿아 위로 뻗은 수직선의 윗끝 / 아래: 맨 아래 괘선에 닿아 아래로 뻗은 수직선의 아랫끝\n const top = commonEnd(interior.filter(v => v.y1 <= yHi + TOUCH_TOL && v.y2 >= yHi + MIN_REACH).map(v => v.y2))\n const below = interior.filter(v => v.y2 >= yLo - TOUCH_TOL && v.y1 <= yLo - MIN_REACH)\n let bottom = commonEnd(below.map(v => v.y1))\n // 끝점이 제각각이어도 맨 아래 괘선에 닿은 내부 수직선이 모두(둘 이상) 아래로 뻗었으면 마지막 행은 아래 변만 열린 것 —\n // 모든 열 구분선이 아직 감싸는 가장 얕은 끝점에서 닫는다. ODL 182 슬라이드 표는 마지막 \"Highlight\" 행 아래 괘선 없이\n // 굵은 세로선(47pt 끝)과 옅은 구분선 둘(31·28pt 끝)만 내려 그어 그 행이 표 밖으로 떨어졌다\n if (bottom === null && textLayer && below.length >= MIN_REACHING &&\n below.length === interior.filter(v => v.y2 >= yLo - TOUCH_TOL && v.y1 <= yLo + TOUCH_TOL).length) bottom = Math.max(...below.map(v => v.y1))\n for (const y of [top, bottom]) {\n if (y === null) continue\n // 그 높이에 이미 몸통 폭을 덮는 괘선이 있으면 닫힌 변이다\n if (horizontals.some(h => Math.abs(h.y1 - y) <= TOUCH_TOL && h.x1 <= x1 + INSET && h.x2 >= x2 - INSET)) continue\n added.push({ x1, y1: y, x2, y2: y, lineWidth: 0.5 })\n }\n }\n return added.length ? [...horizontals, ...added] : horizontals\n}\n","/**\n * 머리 상자 아래 무괘선 행 (line-detector.ts 계열 전처리).\n *\n * 머리행만 칸 괘선(음영 상자)으로 두르고 데이터 행은 괘선 없이 같은 열에 늘어놓는 표가 있다\n * (ODL 127 감가상각표·130 수익률표). 선 격자는 머리 한 행이라 1행 격자로 버려지고(page-blocks),\n * 데이터 행은 클러스터 감지가 글 가장자리로 열을 다시 세워 빈 사이 열·잘린 행이 생긴다.\n *\n * 머리 상자의 칸 경계가 열 증거다. 상자 바로 아래 글줄이 저마다 상자 칸 하나 안에만 놓이고\n * 고른 줄 간격으로 이어지는 동안을 표 행으로 보고, 그 행 경계에 가상 괘선을, 칸 경계에 상자\n * 아래로 이어지는 가상 수직선을 더한다. 칸 경계를 가로지르는 글(본문 문장)·큰 간격·다른 괘선에서 멈춘다.\n * 클립 격자 쪽(한컴 PDF)은 클립이 진실이라 부르지 않는다.\n */\n\nimport type { LineSegment } from \"./line-types.js\"\nimport { buildTableGrids } from \"./table-grid.js\"\nimport { type NormItem, groupByY } from \"./text-line.js\"\n\n/** 머리 상자 최대 높이 (pt) — 세 줄 머리 */\nconst MAX_BOX_H = 48\n/** 칸 경계 넘침 허용 (pt) */\nconst EDGE_TOL = 2\n/** 상자 아래 첫 줄까지 최대 간격 (글줄 높이 배수) */\nconst FIRST_GAP_K = 2.5\n/** 줄 간격이 첫 행 간격의 이 배를 넘으면 표 끝 */\nconst PITCH_BREAK_K = 1.8\n/** 앞 줄과의 간격이 행 간격의 이 비율 미만이면 같은 행의 꺾인 줄 */\nconst WRAP_K = 0.75\n/** 최소 데이터 행 수 */\nconst MIN_ROWS = 2\n\n/** 칸 경계 목록에서 아이템이 온전히 든 칸 번호 (없으면 -1) */\nfunction columnOf(it: NormItem, colXs: number[]): number {\n for (let c = 0; c + 1 < colXs.length; c++) {\n if (it.x >= colXs[c] - EDGE_TOL && it.x + it.w <= colXs[c + 1] + EDGE_TOL) return c\n }\n return -1\n}\n\n/**\n * 1행 머리 상자 아래 무괘선 데이터 행에 가상 괘선을 더한다. 조건 미달이면 입력을 그대로 반환한다.\n */\nexport function extendHeaderBoxRows(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n items: NormItem[],\n): { horizontals: LineSegment[]; verticals: LineSegment[] } {\n const boxes = buildTableGrids(horizontals, verticals).filter(g => g.rowYs.length === 2 && g.colXs.length >= 3 &&\n g.bbox.y2 - g.bbox.y1 <= MAX_BOX_H)\n if (boxes.length === 0) return { horizontals, verticals }\n const addH: LineSegment[] = [], addV: LineSegment[] = []\n const lines = groupByY([...items].sort((a, b) => b.y - a.y || a.x - b.x))\n for (const box of boxes) {\n const { x1, x2, y1: boxBottom } = box.bbox\n const below = lines.filter(l => l[0].y + l[0].h < boxBottom + EDGE_TOL)\n const rows: NormItem[][][] = []\n let prevY = boxBottom, pitch = 0\n for (const line of below) {\n const y = line[0].y, h = Math.max(...line.map(it => it.h || it.fontSize))\n const gap = prevY - y\n if (rows.length === 0 ? boxBottom - (y + h) > h * FIRST_GAP_K : gap > (pitch || gap) * PITCH_BREAK_K) break\n // 줄에 표 밖 글이 있거나 칸 경계를 가로지르는 글이 있으면 표가 아니다\n if (line.some(it => it.x + it.w < x1 - EDGE_TOL || it.x > x2 + EDGE_TOL || columnOf(it, box.colXs) < 0)) break\n // 사이에 괘선이 있으면 다른 표·구획\n if (horizontals.some(hl => hl.y1 < prevY - EDGE_TOL && hl.y1 > y + h && hl.x1 < x2 && hl.x2 > x1)) break\n if (rows.length > 0 && pitch > 0 && gap < pitch * WRAP_K) rows[rows.length - 1].push(line)\n else {\n if (rows.length === 1) pitch = gap\n rows.push([line])\n }\n prevY = y\n }\n if (rows.length < MIN_ROWS || !rows.some(r => r.flat().length >= 2)) continue\n // 행 경계: 위 행 아래 끝과 아래 행 위 끝의 가운데, 마지막은 마지막 줄 아래\n const top = (r: NormItem[][]) => Math.max(...r.flat().map(it => it.y + (it.h || it.fontSize)))\n const bottom = (r: NormItem[][]) => Math.min(...r.flat().map(it => it.y))\n const cuts: number[] = []\n for (let i = 0; i + 1 < rows.length; i++) cuts.push((bottom(rows[i]) + top(rows[i + 1])) / 2)\n const tableBottom = bottom(rows[rows.length - 1]) - Math.max(2, (pitch - (top(rows[0]) - bottom(rows[0]))) / 2)\n cuts.push(tableBottom)\n for (const y of cuts) addH.push({ x1, y1: y, x2, y2: y, lineWidth: 0.5 })\n for (const x of box.colXs) addV.push({ x1: x, y1: tableBottom, x2: x, y2: boxBottom, lineWidth: 0.5 })\n }\n if (addH.length === 0) return { horizontals, verticals }\n return { horizontals: [...horizontals, ...addH], verticals: [...verticals, ...addV] }\n}\n","/**\n * 가로 괘선만 있는 표 (booktabs).\n *\n * 학술지 표는 위·머리 아래·아래에 같은 폭의 가로 괘선만 긋고 세로선을 긋지 않는다(ODL 190 Table 6·7).\n * 선 격자는 세로선이 없어 생기지 않고, 무괘선 경로에서는 쪽의 두 단 판정이 표 한가운데를 거터로 잘라\n * 좌우 반쪽 표가 된다.\n *\n * 같은 폭(끝점 정렬) 가로 괘선이 셋 이상 이어지고, 이웃 괘선 사이 띠마다 글이 괘선 폭 안에서 빈 세로 틈\n * (열 사이)을 두고 놓이면 그 띠들을 한 표로 본다. 열 경계는 몸통 띠 글의 x 투영 틈이고, 행은 몸통 글줄이다.\n * 머리 띠는 한 행이며, 머리 글이 여러 열에 걸치면 병합 칸이다. 산문 줄이 낀 띠는 투영 틈이 없어 표가 아니다.\n * 행 간격이 선 격자의 좌표 병합 허용(8pt)보다 좁은 촘촘한 표도 있어 가상 괘선 대신 표를 바로 만든다.\n * 세로선이 이미 있는 표는 선 격자가 맡는다.\n */\n\nimport type { IRBlock, IRCell } from \"../types.js\"\nimport type { LineSegment } from \"./line-types.js\"\nimport { chainCollinearRules } from \"./line-extract.js\"\nimport { cellTextToString } from \"./cell-text.js\"\nimport { type NormItem, groupByY } from \"./text-line.js\"\nimport { isTableOfContents } from \"./table-roles.js\"\n\n/** 끝점 정렬 괘선 묶음 허용 오차 (pt) */\nconst ALIGN_TOL = 3\n/** 괘선 폭 밖 글 허용 (pt) */\nconst EDGE_TOL = 2\n/** 최소 괘선 수 — 위·머리 아래·아래 */\nconst MIN_RULES = 3\n/** 최소 괘선 폭 (pt) */\nconst MIN_WIDTH = 120\n/** 열 사이 틈 최소 (글자 크기 배수) — 낱말 사이(0.25~0.35em)보다 넓다 */\nconst COL_GAP_K = 0.8\n/** 두 칸 이상 채운 몸통 행의 최소 비율 */\nconst MIN_FILLED_ROWS = 0.6\n/** 몸통 줄 간격이 행 간격의 이 비율 미만이면 같은 행의 꺾인 줄 */\nconst WRAP_K = 0.7\n\ntype Interval = [number, number]\nexport interface RuledTable { block: IRBlock; items: NormItem[] }\n\n/** 글 x 구간 합집합을 틈(minGap 이상)에서 끊은 구간들 */\nfunction projectColumns(items: NormItem[], minGap: number): Interval[] {\n const spans = items.map(it => [it.x, it.x + it.w] as Interval).sort((a, b) => a[0] - b[0])\n const out: Interval[] = []\n for (const s of spans) {\n const last = out[out.length - 1]\n if (last && s[0] - last[1] < minGap) last[1] = Math.max(last[1], s[1])\n else out.push([s[0], s[1]])\n }\n return out\n}\n\nfunction median(values: number[]): number {\n if (values.length === 0) return 0\n const s = [...values].sort((a, b) => a - b)\n return s[s.length >> 1]\n}\n\n/** 첫 줄이 몸통 열마다 하나씩 놓인 한 줄 머리행인지 — 여러 단 머리(걸친 묶음 제목)는 괘선 둘로 행을 가를 수 없다 */\nfunction flatHeader(lines: NormItem[][]): boolean {\n const body = lines.slice(1).flat()\n const fs = median(body.map(it => it.fontSize).filter(s => s > 0)) || 10\n const cols = projectColumns(body, fs * COL_GAP_K)\n const head = projectColumns(lines[0], fs * COL_GAP_K)\n return cols.length >= 2 && head.length === cols.length &&\n head.every(h => cols.filter(c => Math.min(c[1], h[1]) - Math.max(c[0], h[0]) > 0).length === 1)\n}\n\n/**\n * 위·아래 괘선 둘뿐인 띠의 머리 줄 수 — 첫 행은 몸통 행 간격보다 촘촘히 붙은 줄 묶음(칸 안에서 꺾인 머리 글,\n * 두 줄 칸 사이 가운데 맞춘 한 줄 칸)이다. 그 묶음 글이 몸통 열마다 하나 이하로 놓이면(빈 머리 칸 허용) 그 줄 수,\n * 아니면 0. ODL 170 Table 16.4 는 \"Contour / Farming\" 처럼 두 줄로 꺾인 머리 칸과 가운데 한 줄 칸이 섞인 첫 행,\n * Table 16.5 는 첫 열이 빈 채 셋만 놓인 머리 줄이라 flatHeader 로는 못 받았다\n */\nfunction headerLineCount(lines: NormItem[][]): number {\n // OCR 글(seq 없음)은 종전대로 한 줄 머리만 — 래스터 괘선 경로에서 무괘선 머리행을 흡수하다 OCR 문서가 무너진 전력(grid-header-line)\n if (lines.some(l => l.some(it => it.seq === undefined))) return flatHeader(lines) ? 1 : 0\n const pitch = median(lines.slice(1).map((l, i) => lines[i][0].y - l[0].y))\n let n = 1\n while (n < lines.length && lines[n - 1][0].y - lines[n][0].y < pitch * WRAP_K) n++\n if (lines.length - n < 3) return 0\n const head = lines.slice(0, n).flat(), body = lines.slice(n).flat()\n const fs = median(body.map(it => it.fontSize).filter(s => s > 0)) || 10\n const cols = projectColumns(body, fs * COL_GAP_K)\n const heads = projectColumns(head, fs * COL_GAP_K)\n const hit = heads.map(h => cols.flatMap((c, k) => Math.min(c[1], h[1]) - Math.max(c[0], h[0]) > 0 ? [k] : []))\n return cols.length >= 2 && heads.length >= 2 && hit.every(k => k.length === 1) &&\n new Set(hit.map(k => k[0])).size === heads.length ? n : 0\n}\n\n/** 띠 글줄들을 한 행(칸마다 글)으로 — 여러 열에 걸친 글은 병합 칸 */\nfunction buildRow(rowItems: NormItem[], bounds: number[], colSpans?: Interval[]): IRCell[] {\n const cols = bounds.length + 1\n const colOf = (x: number) => bounds.filter(b => x > b).length\n const cells: NormItem[][] = Array.from({ length: cols }, () => [])\n const span = new Array<number>(cols).fill(1)\n for (const it of rowItems) {\n let first = colOf(it.x + EDGE_TOL), last = colOf(it.x + it.w - EDGE_TOL)\n // 몸통 열 글 범위를 둘 이상 실제로 덮을 때만 병합 칸 — 좁은 값 열(\"O\") 위의 넓은 머리 글은 중심이 가까운 열 하나\n if (last > first && colSpans) {\n const covered = colSpans.map((c, k) => Math.min(c[1], it.x + it.w) - Math.max(c[0], it.x) > 0 ? k : -1).filter(k => k >= 0)\n if (covered.length <= 1) {\n const cx = it.x + it.w / 2\n first = last = covered[0] ?? colSpans.reduce((best, c, k) => Math.abs((c[0] + c[1]) / 2 - cx) < Math.abs((colSpans[best][0] + colSpans[best][1]) / 2 - cx) ? k : best, 0)\n } else { first = covered[0]; last = covered[covered.length - 1] }\n }\n cells[first].push(it)\n if (last > first) span[first] = Math.max(span[first], last - first + 1)\n }\n const row: IRCell[] = []\n for (let c = 0; c < cols;) {\n const n = Math.min(span[c], cols - c)\n const items = cells.slice(c, c + n).flat()\n row.push({ text: cellTextToString(items.map(it => ({ ...it }))), colSpan: n, rowSpan: 1 })\n for (let k = 1; k < n; k++) row.push({ text: \"\", colSpan: 1, rowSpan: 1 })\n c += n\n }\n return row\n}\n\n/**\n * 가로 괘선 전용 표를 찾아 IR 표 블록과 그 글 아이템을 낸다. 목차(항목 + 쪽번호)는 표가 아니라 뺀다.\n */\nexport function detectRuledBandTables(\n horizontals: LineSegment[],\n verticals: LineSegment[],\n items: NormItem[],\n pageNum: number,\n): RuledTable[] {\n if (horizontals.length < 2) return []\n const groups: LineSegment[][] = []\n for (const rule of chainCollinearRules(horizontals)) {\n if (rule.x2 - rule.x1 < MIN_WIDTH) continue\n const g = groups.find(gr => Math.abs(gr[0].x1 - rule.x1) <= ALIGN_TOL && Math.abs(gr[0].x2 - rule.x2) <= ALIGN_TOL)\n if (g) g.push(rule)\n else groups.push([rule])\n }\n const lines = groupByY([...items].sort((a, b) => b.y - a.y || a.x - b.x))\n const found: RuledTable[] = []\n type Band = { top: number; bottom: number; lines: NormItem[][] }\n const emit = (bands: Band[], x1: number, x2: number) => {\n const top = bands[0].top, bottom = bands[bands.length - 1].bottom\n // 세로선이 안에 있으면 선 격자가, 양끝에 있으면 테두리 상자(문단 테두리 1열 틀)다\n // 머리 상자 좌우변처럼 일부 높이만 덮는 양끝 선은 틀이 아니다\n if (verticals.some(v => v.y1 < top && v.y2 > bottom && (v.x1 > x1 + EDGE_TOL && v.x1 < x2 - EDGE_TOL ||\n Math.abs(v.x1 - x1) <= ALIGN_TOL + EDGE_TOL && Math.min(v.y2, top) - Math.max(v.y1, bottom) > (top - bottom) * 0.5 ||\n Math.abs(v.x1 - x2) <= ALIGN_TOL + EDGE_TOL && Math.min(v.y2, top) - Math.max(v.y1, bottom) > (top - bottom) * 0.5))) return\n const body = bands.slice(1).flatMap(b => b.lines)\n const fs = median(body.flat().map(it => it.fontSize).filter(s => s > 0)) || 10\n const cols = projectColumns(body.flat(), fs * COL_GAP_K)\n if (cols.length < 2) return\n const bounds = cols.slice(1).map((c, i) => (cols[i][1] + c[0]) / 2)\n const rows: IRCell[][] = [buildRow(bands[0].lines.flat(), bounds, cols)]\n for (const band of bands.slice(1)) {\n const pitch = median(band.lines.slice(1).map((l, i) => band.lines[i][0].y - l[0].y)) || Infinity\n const bandRows: NormItem[][] = []\n band.lines.forEach((l, i) => {\n if (i > 0 && band.lines[i - 1][0].y - l[0].y < pitch * WRAP_K) bandRows[bandRows.length - 1].push(...l)\n else bandRows.push([...l])\n })\n for (const r of bandRows) rows.push(buildRow(r, bounds))\n }\n const table = { rows: rows.length, cols: bounds.length + 1, cells: rows, hasHeader: true }\n // 몸통 행 대부분이 두 칸 이상을 채운다 — 한 칸짜리 목록·본문 줄 사이에 점선 리더 줄 몇 개가 낀 머리 상자(회의록 개회식 순서)는 표가 아니다\n const bodyRows = rows.slice(1)\n if (bodyRows.filter(r => r.filter(c => c.text.trim()).length >= 2).length < bodyRows.length * MIN_FILLED_ROWS) return\n if (isTableOfContents(table)) return\n const used = bands.flatMap(b => b.lines.flat())\n found.push({\n block: { type: \"table\", table, pageNumber: pageNum, bbox: { page: pageNum, x: x1, y: bottom, width: x2 - x1, height: top - bottom } },\n items: used,\n })\n }\n for (const g of groups) {\n if (g.length < 2) continue\n const rules = [...g].sort((a, b) => b.y1 - a.y1)\n const x1 = Math.min(...rules.map(r => r.x1)), x2 = Math.max(...rules.map(r => r.x2))\n let run: Band[] = []\n const flush = () => {\n if (run.length >= 2) emit(run, x1, x2)\n // 위·아래 괘선 둘뿐인 표(머리 아래 선 생략) — 첫 줄 묶음을 머리 띠로 (headerLineCount)\n else if (g.length === 2 && run.length === 1 && run[0].lines.length >= 4) {\n const [band] = run, n = headerLineCount(band.lines)\n if (n > 0) {\n const cut = (band.lines[n - 1][0].y + band.lines[n][0].y + Math.max(...band.lines[n].map(it => it.h || it.fontSize))) / 2\n emit([{ top: band.top, bottom: cut, lines: band.lines.slice(0, n) }, { top: cut, bottom: band.bottom, lines: band.lines.slice(n) }], x1, x2)\n }\n }\n run = []\n }\n let title: Band | null = null\n for (let i = 0; i + 1 < rules.length; i++) {\n const top = rules[i].y1, bottom = rules[i + 1].y1\n const band = lines.filter(l => l[0].y < top && l[0].y > bottom)\n const inside = !band.some(l => l.some(it => it.x < x1 - EDGE_TOL || it.x + it.w > x2 + EDGE_TOL))\n const fs = median(band.flat().map(it => it.fontSize).filter(s => s > 0)) || 10\n if (band.length > 0 && inside && projectColumns(band.flat(), fs * COL_GAP_K).length >= 2) {\n // 표 전체에 걸친 한 칸 제목 띠(2줄 이하)는 머리 띠다 (ODL 132 \"Fish species on IUCN Red List\")\n if (run.length === 0 && title) run.push(title)\n run.push({ top, bottom, lines: band })\n title = null\n } else {\n flush()\n title = band.length > 0 && band.length <= 2 && inside ? { top, bottom, lines: band } : null\n }\n }\n flush()\n }\n return found\n}\n","/**\n * 보이지 않는 글상자 표 (슬라이드 PDF).\n *\n * 슬라이드 제작기는 글상자마다 불투명도 0(ExtGState ca=0) 채움 사각형을 깔고, 표의 회색 행 괘선은 래스터 그림에 구워 둔다\n * (ODL 200 \"Service Stage | Function Name | Explanation | Expected Benefit\": 벡터 괘선 0, 글상자 틀 45개). 틀 변을 괘선으로 읽으면\n * 칸마다 높이가 다른 틀이 어긋난 격자와 가짜 취소선이 되고(TEDS 0.17), 무괘선 경로는 칸 안 꺾인 줄을 행으로 갈라 논리 행을 잃는다.\n * 틀 자체가 칸이다 — 왼끝·오른끝이 같은 틀이 세 개 이상 쌓인 열이 셋 이상이고, 여러 열 틀이 윗변을 맞춘 행이 셋 이상이면 표로 세운다.\n * 행은 윗변을 맞춘 틀 묶음, 칸 글은 틀 밖으로 넘친 줄까지 윗변 기준으로 그 행에 넣는다. 차트 슬라이드의 축 눈금·범례 틀(ODL 199)은\n * 같은 폭 열이 셋 이상 서지 않아 표가 아니다.\n */\n\nimport type { IRCell } from \"../types.js\"\nimport type { ClipRect } from \"./line-extract.js\"\nimport { cellTextToString } from \"./cell-text.js\"\nimport type { NormItem } from \"./text-line.js\"\nimport type { RuledTable } from \"./ruled-band-tables.js\"\n\n/** 같은 열 틀의 왼끝·오른끝 허용 (pt) */\nconst EDGE_TOL = 2\n/** 같은 행 틀의 윗변 허용 (pt) — 글상자 안 위 여백이 칸마다 2.3pt 다르다(ODL 200) */\nconst TOP_TOL = 3\n/** 표로 세울 최소 열·행 */\nconst MIN_COLS = 3\nconst MIN_ROWS = 3\n\nexport function detectTextBoxTables(boxes: ClipRect[], items: NormItem[], pageNum: number): RuledTable[] {\n // OCR 글(seq 없음)은 글상자 틀과 좌표 출처가 달라 쓰지 않는다\n const text = items.filter(it => it.seq !== undefined && it.text.trim())\n const inBox = (b: ClipRect, it: NormItem) => {\n const cx = it.x + it.w / 2, cy = it.y + (it.h || it.fontSize) / 2\n return cx >= b.x1 && cx <= b.x2 && cy >= b.y1 && cy <= b.y2\n }\n // 글이 든 틀만 — 그림 자리·빈 틀은 칸 증거가 아니다\n const filled = boxes.filter(b => text.some(it => inBox(b, it)))\n // 같은 폭 열: 왼끝·오른끝이 같은 틀 묶음\n const cols: ClipRect[][] = []\n for (const b of filled) {\n const c = cols.find(g => Math.abs(g[0].x1 - b.x1) <= EDGE_TOL && Math.abs(g[0].x2 - b.x2) <= EDGE_TOL)\n if (c) c.push(b)\n else cols.push([b])\n }\n const tableCols = cols.filter(g => g.length >= MIN_ROWS).sort((a, b) => a[0].x1 - b[0].x1)\n if (tableCols.length < MIN_COLS) return []\n // 열끼리 겹치면 같은 표의 칸 열이 아니다\n for (let i = 1; i < tableCols.length; i++) if (tableCols[i][0].x1 < tableCols[i - 1][0].x2 - EDGE_TOL) return []\n // 행: 두 열 이상 틀이 윗변을 맞춘 묶음 (위 → 아래)\n const tops = tableCols.flatMap((g, c) => g.map(b => ({ top: b.y2, bottom: b.y1, c }))).sort((a, b) => b.top - a.top)\n const rows: Array<{ top: number; bottom: number; cols: Set<number> }> = []\n for (const t of tops) {\n const last = rows[rows.length - 1]\n if (last && last.top - t.top <= TOP_TOL && !last.cols.has(t.c)) {\n last.cols.add(t.c)\n last.bottom = Math.min(last.bottom, t.bottom)\n } else rows.push({ top: t.top, bottom: t.bottom, cols: new Set([t.c]) })\n }\n const anchored = rows.filter(r => r.cols.size >= 2)\n if (anchored.length < MIN_ROWS || anchored.length < rows.length * 0.8) return []\n const x1 = tableCols[0][0].x1, x2 = tableCols[tableCols.length - 1][0].x2\n const top = anchored[0].top, bottom = anchored[anchored.length - 1].bottom\n // 칸 글: 표 폭 안, 첫 행 윗변 ~ 끝 행 아랫변 사이 — 틀 밖으로 넘친 줄도 윗변 기준으로 그 행\n const used = text.filter(it => {\n const cx = it.x + it.w / 2, itTop = it.y + (it.h || it.fontSize)\n return cx >= x1 - EDGE_TOL && cx <= x2 + EDGE_TOL && itTop <= top + TOP_TOL && it.y >= bottom - TOP_TOL\n })\n if (used.length === 0) return []\n const colOf = (it: NormItem) => {\n let c = 0\n for (let k = 1; k < tableCols.length; k++) if (it.x >= tableCols[k][0].x1 - EDGE_TOL) c = k\n return c\n }\n const rowOf = (it: NormItem) => {\n const itTop = it.y + (it.h || it.fontSize)\n let r = 0\n for (let k = 1; k < anchored.length; k++) if (itTop <= anchored[k].top + TOP_TOL) r = k\n return r\n }\n const grid: NormItem[][][] = anchored.map(() => tableCols.map(() => []))\n for (const it of used) grid[rowOf(it)][colOf(it)].push(it)\n const cells: IRCell[][] = grid.map(row => row.map(its => ({ text: cellTextToString(its.map(it => ({ ...it }))), colSpan: 1, rowSpan: 1 })))\n return [{\n block: {\n type: \"table\",\n table: { rows: cells.length, cols: tableCols.length, cells, hasHeader: true },\n pageNumber: pageNum,\n bbox: { page: pageNum, x: x1, y: bottom, width: x2 - x1, height: top - bottom },\n },\n items: used,\n }]\n}\n","import type { IRBlock, IRTable, BoundingBox } from \"../types.js\"\nimport type { TextItem } from \"./line-types.js\"\nimport { computeBBox, dominantStyle, groupByY, mergeSuperscriptLines, type NormItem } from \"./text-line.js\"\nimport { pushLineParagraphs } from \"./paragraph-lines.js\"\nimport { cleanCellText } from \"./cell-text.js\"\nimport type { WrapLexicon } from \"./line-wrap.js\"\nimport { CELL_EDGES } from \"../table/layout-frames.js\"\nimport { CLIP_TABLES, FRAME_TITLE_BLOCKS, TABLE_COLXS } from \"./table-meta.js\"\n\n/** Native frame bounds survive paragraph reflow and later OCR-region insertion. */\nexport const FRAME_READING_UNITS = new WeakMap<IRBlock, { blocks: IRBlock[]; bbox: BoundingBox }>()\nconst FRAME_SOURCE_BOUNDS = new WeakMap<IRBlock, BoundingBox>()\nexport function recordFrameReadingUnit(blocks: IRBlock[], bbox: BoundingBox): void {\n for (const block of blocks) FRAME_SOURCE_BOUNDS.set(block, bbox)\n if (blocks.length < 2) return\n const unit = { blocks, bbox }\n for (const block of blocks) FRAME_READING_UNITS.set(block, unit)\n}\n\n/** Paragraph reflow must not change the occupied rectangles used to detect\n * columns. Each native frame contributes its source bounds once, even when\n * its only paragraph is shorter or it contains several short clauses. */\nexport function frameLayoutBoxes(blocks: IRBlock[]): BoundingBox[] {\n const boxes: BoundingBox[] = [], seen = new Set<BoundingBox>()\n for (const block of blocks) {\n const frame = FRAME_SOURCE_BOUNDS.get(block)\n if (frame) {\n if (!seen.has(frame)) { boxes.push(frame); seen.add(frame) }\n } else if (block.bbox) boxes.push(block.bbox)\n }\n return boxes\n}\n\n/** A frame gutter must not cut a continuous full-width title or prose line.\n * Independent columns retain their larger gap; only touching text runs and\n * their immediately following indented continuation share a paragraph unit. */\nexport function takeFrameSpanningText(items: NormItem[], cutX: number, pageNum: number, lex?: WrapLexicon): IRBlock[] {\n let minX = Infinity, maxR = -Infinity\n for (const item of items) { minX = Math.min(minX, item.x); maxR = Math.max(maxR, item.x + item.w) }\n const span = maxR - minX\n const rows = groupByY([...items].sort((a, b) => b.y - a.y || a.x - b.x)).map(line => {\n const runs: NormItem[][] = []\n for (const item of [...line].sort((a, b) => a.x - b.x)) {\n const run = runs[runs.length - 1], prev = run?.[run.length - 1]\n if (prev && item.x - (prev.x + prev.w) <= Math.max(prev.fontSize, item.fontSize) * 0.6) run.push(item)\n else runs.push([item])\n }\n return runs\n })\n const taken = new Set<NormItem>(), blocks: IRBlock[] = []\n for (let r = 0; r < rows.length; r++) for (const run of rows[r]) {\n if (taken.has(run[0]) || run[0].x >= cutX || Math.max(...run.map(i => i.x + i.w)) <= cutX) continue\n const lines = [run], fs = Math.max(...run.map(i => i.fontSize))\n const left = run[0].x, right = Math.max(...run.map(i => i.x + i.w))\n if (right - left < span * 0.75) continue\n let prevY = run[0].y\n for (let next = r + 1; next < rows.length && rows[next].length === 1; next++) {\n const tail = rows[next][0], y = tail[0].y\n if (taken.has(tail[0]) || prevY - y > fs * 1.8 || tail[0].x < left - 1 || tail[0].x > left + fs * 2 ||\n tail.some(i => Math.abs(i.fontSize - fs) > fs * 0.15) || Math.max(...tail.map(i => i.x + i.w)) > right + 1) break\n lines.push(tail)\n prevY = y\n }\n for (const line of lines) for (const item of line) taken.add(item)\n pushLineParagraphs(blocks, mergeSuperscriptLines(lines), pageNum, lex)\n }\n let keep = 0\n for (const item of items) if (!taken.has(item)) items[keep++] = item\n items.length = keep\n return blocks\n}\n\n/** An XY-Cut unit may not span a full-width paragraph's reading boundary. */\nexport function frameColumnTextBands(items: NormItem[], spanning: IRBlock[], cutX: number): NormItem[][] {\n const tops = spanning.filter(b => b.bbox).map(b => b.bbox!.y + b.bbox!.height).sort((a, b) => b - a)\n const bands = new Map<number, NormItem[][]>()\n for (const item of items) {\n let band = 0\n while (band < tops.length && tops[band] > item.y + item.h) band++\n const sides = bands.get(band) ?? [[], [], []]\n sides[item.x + item.w <= cutX ? 0 : item.x >= cutX ? 2 : 1].push(item)\n bands.set(band, sides)\n }\n return [...bands].sort((a, b) => a[0] - b[0]).flatMap(([, sides]) => sides.filter(side => side.length))\n}\n\n/** Paragraphs share a native one-cell box's reading unit. A larger confirmed\n * flow band takes precedence; never split it to reconstruct a smaller frame. */\nexport function groupFrameParagraphUnits(units: IRBlock[][], frames: IRBlock[][]): IRBlock[][] {\n const source = new Map<IRBlock, IRBlock[]>()\n for (const unit of units) for (const block of unit) source.set(block, unit)\n const members = new Map<IRBlock, IRBlock[]>(), emitted = new Set<IRBlock[]>()\n for (const frame of frames) {\n if (frame.length < 2 || frame.some(block => block.type !== \"paragraph\" || source.get(block)?.length !== 1)) continue\n for (const block of frame) members.set(block, frame)\n }\n const out: IRBlock[][] = []\n for (const unit of units) {\n const frame = unit.length === 1 ? members.get(unit[0]) : undefined\n if (!frame) out.push(unit)\n else if (!emitted.has(frame)) { out.push(frame); emitted.add(frame) }\n }\n for (const unit of out) {\n if (unit.length < 2 || FRAME_READING_UNITS.get(unit[0])?.blocks === unit) continue\n const bs = unit.map(b => b.bbox).filter((b): b is BoundingBox => !!b)\n if (bs.length !== unit.length) continue\n const x = Math.min(...bs.map(b => b.x)), y = Math.min(...bs.map(b => b.y))\n recordFrameReadingUnit(unit, { page: bs[0].page, x, y,\n width: Math.max(...bs.map(b => b.x + b.width)) - x, height: Math.max(...bs.map(b => b.y + b.height)) - y })\n }\n return out\n}\n\n/** 번호 칩·좁은 빈 간격·한 줄 제목을 가진 무괘선 클립 상자만 문단으로 확인한다. */\nexport function recordFrameTitle(table: IRTable, source: NormItem[], pageNum: number): void {\n if (!CLIP_TABLES.has(table) || table.rows !== 1 || table.cols !== 3 || source.length < 2) return\n const cells = table.cells[0]\n if (cells.length !== 3 || cells.some(c => c.colSpan !== 1 || c.rowSpan !== 1 || c.blocks?.length || c.text.includes(\"\\n\"))) return\n const [label, spacer, title] = cells\n if (!/^(?:붙임|별첨|첨부|부록)\\s*\\d{1,3}$/.test(label.text.trim()) || spacer.text.trim() || !title.text.trim()) return\n if (cells.some(c => { const e = CELL_EDGES.get(c); return !e || e.t || e.b || e.l || e.r })) return\n const xs = TABLE_COLXS.get(table), style = dominantStyle(source), fs = style?.fontSize ?? 0\n if (!xs || xs.length !== 4 || fs <= 0) return\n const width = xs[3] - xs[0], gap = xs[2] - xs[1]\n if (width < fs * 12 || gap <= 0 || gap > fs * 1.5 || xs[1] - xs[0] > width * 0.25 || xs[3] - xs[2] < width * 0.6) return\n if (source.some(i => i.fontName !== source[0].fontName || Math.abs(i.fontSize - fs) > fs * 0.15)) return\n if (groupByY([...source].sort((a, b) => b.y - a.y)).length !== 1) return\n FRAME_TITLE_BLOCKS.set(table, { type: \"paragraph\", text: `${label.text.trim()} ${title.text.trim()}`,\n pageNumber: pageNum, bbox: computeBBox(source, pageNum), style })\n}\n\n/** 틀 셀 좌표와 같은 부모를 가진 중첩표를 pending 에서 꺼낸다 (제자리 제거) */\nexport const FRAME_RECT_TOL = 1.5\nexport function takePendingNested(\n pending: Array<{ parent: { x1: number; y1: number; x2: number; y2: number }; block: IRBlock; contained?: true }>,\n cellBox: { x1: number; y1: number; x2: number; y2: number },\n clipCell: boolean,\n): IRBlock[] {\n const out: IRBlock[] = []\n for (let i = pending.length - 1; i >= 0; i--) {\n const p = pending[i].parent\n // 선 격자 중첩표(contained)는 칸이 자기 상자를 품으면 그 칸의 표 — 클립 중첩표는 틀 칸 클립과 같은 사각형일 때만\n if (pending[i].contained\n ? p.x1 >= cellBox.x1 - FRAME_RECT_TOL && p.x2 <= cellBox.x2 + FRAME_RECT_TOL && p.y1 >= cellBox.y1 - FRAME_RECT_TOL && p.y2 <= cellBox.y2 + FRAME_RECT_TOL\n : clipCell && Math.abs(p.x1 - cellBox.x1) <= FRAME_RECT_TOL && Math.abs(p.x2 - cellBox.x2) <= FRAME_RECT_TOL\n && Math.abs(p.y1 - cellBox.y1) <= FRAME_RECT_TOL && Math.abs(p.y2 - cellBox.y2) <= FRAME_RECT_TOL) {\n out.push(pending[i].block)\n pending.splice(i, 1)\n }\n }\n return out\n}\n\n/**\n * 틀 셀의 blocks 조립 — 셀 자기 글(문단)과 안쪽 표를 위→아래 순서로 섞는다. 표의 y 띠 위·옆에\n * 있는 글은 표 앞 문단, 아래 글은 다음 덩어리. text 는 blocks 평탄화(하위 호환, IRCell 계약)\n */\nexport function buildFrameCellBlocks(cellItems: TextItem[], nested: IRBlock[], pageNum: number, lex?: WrapLexicon): { blocks: IRBlock[]; text: string } {\n const tables = [...nested].sort((a, b) => (b.bbox!.y + b.bbox!.height) - (a.bbox!.y + a.bbox!.height))\n const blocks: IRBlock[] = []\n let rest = [...cellItems]\n const pushParagraphs = (items: TextItem[]) => {\n if (items.length === 0) return\n const source: NormItem[] = items.map(it => ({ ...it, isHidden: false }))\n .sort((a, b) => b.y - a.y || a.x - b.x)\n const paragraphs: IRBlock[] = []\n pushLineParagraphs(paragraphs, mergeSuperscriptLines(groupByY(source)), pageNum, lex)\n for (const block of paragraphs) {\n block.text = cleanCellText(block.text ?? \"\")\n if (block.text) blocks.push(block)\n }\n }\n for (const tb of tables) {\n const bottom = tb.bbox!.y\n pushParagraphs(rest.filter(it => it.y >= bottom))\n rest = rest.filter(it => it.y < bottom)\n blocks.push(tb)\n }\n pushParagraphs(rest)\n const text = blocks\n .map(b => b.type === \"table\" && b.table ? b.table.cells.flat().map(c => c.text).filter(Boolean).join(\"\\n\") : (b.text ?? \"\"))\n .filter(Boolean)\n .join(\"\\n\")\n return { blocks, text }\n}\n","import type { LineSegment, TableGrid } from \"./line-types.js\"\nimport { extractCells } from \"./cell-extract.js\"\n\n/** A shaded heading inside a one-cell frame cannot consume an independently\n * ruled table's heading while the frame removes its body grid. Keep the same\n * parent and use the matching complete grid's cells; infer no new boundaries. */\nexport function extendNestedShadedHeaders(\n clipGrids: TableGrid[], lineGrids: TableGrid[], horizontals: LineSegment[], verticals: LineSegment[], fills: TableGrid[\"bbox\"][],\n): TableGrid[] {\n const near = (a: number, b: number) => Math.abs(a - b) <= 1.5\n const contains = (a: TableGrid[\"bbox\"], b: TableGrid[\"bbox\"]) =>\n b.x1 >= a.x1 - 1.5 && b.x2 <= a.x2 + 1.5 && b.y1 >= a.y1 - 1.5 && b.y2 <= a.y2 + 1.5\n const extended: TableGrid[] = []\n for (let i = 0; i < clipGrids.length; i++) {\n const c = clipGrids[i], parent = c.clipParent\n if (!parent || c.rowYs.length !== 2 || c.colXs.length < 3 || !c.cells?.length\n || c.cells.some(cell => cell.filler || cell.rowSpan !== 1 || cell.colSpan !== 1)) continue\n if (!clipGrids.some(g => g.rowYs.length === 2 && g.colXs.length === 2\n && near(g.bbox.x1, parent.x1) && near(g.bbox.x2, parent.x2)\n && near(g.bbox.y1, parent.y1) && near(g.bbox.y2, parent.y2))) continue\n if (!c.cells.every(cell => fills.some(f => near(f.x1, cell.bbox.x1) && near(f.x2, cell.bbox.x2)\n && near(f.y1, cell.bbox.y1) && near(f.y2, cell.bbox.y2)))) continue\n const host = lineGrids.find(l => l.rowYs.length > 2 && l.colXs.length === c.colXs.length\n && c.colXs.every((x, k) => near(x, l.colXs[k])) && c.rowYs.every((y, k) => near(y, l.rowYs[k]))\n && contains(parent, l.bbox)\n && l.rowYs.every(y => horizontals.some(h => near(h.y1, y) && near(h.x1, l.bbox.x1) && near(h.x2, l.bbox.x2)))\n && l.colXs.every(x => verticals.some(v => near(v.x1, x) && v.y1 <= l.bbox.y1 + 1.5 && v.y2 >= l.bbox.y2 - 1.5))\n && !clipGrids.some(o => o !== c && !contains(o.bbox, l.bbox)\n && Math.min(o.bbox.x2, l.bbox.x2) > Math.max(o.bbox.x1, l.bbox.x1)\n && Math.min(o.bbox.y2, l.bbox.y2) > Math.max(o.bbox.y1, l.bbox.y1)))\n if (!host) continue\n const grid = { ...host, clipParent: parent, cells: extractCells(host, horizontals, verticals) }\n clipGrids[i] = grid\n extended.push(grid)\n }\n return extended\n}\n","import type { IRBlock } from \"../types.js\"\nimport { SIDE_TAB_CAP_GROUPS } from \"./cluster-detector.js\"\nimport { computeBBox, dominantStyle, groupByY, mergeLineSimple, type NormItem } from \"./text-line.js\"\n\n/** 새로 분리한 탭만 기준선 글을 묶어 원본 머리 표지 뒤에 둔다. 기존 탭 앞배치는 유지한다. */\nexport function attachSideTabBlocks(tab: Set<NormItem>, body: IRBlock[], pageNum: number): IRBlock[] {\n const ordered = [...tab].sort((a, b) => b.y - a.y)\n const caps = SIDE_TAB_CAP_GROUPS.get(tab)\n const block = (items: NormItem[]): IRBlock => ({ type: \"paragraph\", text: mergeLineSimple(items).trim(),\n pageNumber: pageNum, bbox: computeBBox(items, pageNum), style: dominantStyle(items) })\n const prefix: IRBlock[] = ordered.filter(g => !caps?.has(g)).map(g => ({ type: \"paragraph\", text: g.text.trim(),\n pageNumber: pageNum, bbox: computeBBox([g], pageNum), style: dominantStyle([g]) }))\n const groups = new Map<Pick<NormItem, \"x\" | \"y\" | \"w\" | \"fontSize\">, NormItem[]>()\n for (const g of ordered) {\n const cap = caps?.get(g)\n if (cap) groups.set(cap, [...(groups.get(cap) ?? []), g])\n }\n for (const [cap, glyphs] of groups) {\n const blocks = groupByY(glyphs).map(block)\n const anchor = body.reduce((best, b, i) => {\n const box = b.bbox\n if (b.type === \"image\" || b.type === \"separator\" || !box || !b.text && !b.table ||\n box.x > cap.x + 1 || box.x + box.width < cap.x + cap.w - 1 ||\n box.y > cap.y + 1 || box.y + box.height < cap.y + cap.fontSize - 1) return best\n return best < 0 || box.width * box.height < body[best].bbox!.width * body[best].bbox!.height ? i : best\n }, -1)\n body.splice(anchor >= 0 ? anchor + 1 : body.length, 0, ...blocks)\n }\n return [...prefix, ...body]\n}\n","/**\n * 페이지 콘텐츠 추출 → IRBlock[] (v2: 바운딩 박스 + 페이지 번호)\n *\n * 선 기반 테이블 감지(line-detector) → 클러스터 감지(cluster-detector) →\n * XY-Cut 읽기 순서의 계층 fallback으로 페이지 텍스트를 블록화하고,\n * 페이지 걸친 표 병합까지 담당한다.\n */\n\nimport type { IRBlock, IRTable, IRCell, BoundingBox, InlineStyle } from \"../types.js\"\nimport { safeMin, safeMax } from \"../utils.js\"\nimport { buildClipCellGrids, dropGridsInside, type ClipPage } from \"./clip-cells.js\"\nimport { dropShadingClipGrids, dropInsetClipGrids, dropHeadBandClipGrids } from \"./table-grid.js\"\nimport { chainShortSegments } from \"./line-extract.js\"\nimport { extractLines, preprocessLines, filterPageBorderLines, closeOpenTableEdges, bridgeSplitColumnVerticals, buildTableGrids, extractCells, mapTextToCells, cellTextToString, normalizeUndersegmentedTable, type TextItem, type TableGrid, type LineSegment } from \"./line-detector.js\"\nimport { detectClusterTables, findTwoColumnProseCutX, sideTabGlyphs, type ClusterItem, type ClusterTableResult } from \"./cluster-detector.js\"\nimport { type NormItem, computeBBox, dominantStyle, groupByY, mergeSuperscriptLines, mergeLineSimple } from \"./text-line.js\"\nimport { findRuledColumnDivider } from \"./ruled-columns.js\"\nimport { chartBandGap, xyCutOrder } from \"./xy-cut.js\"\nimport { splitImagePanels } from \"./image-panels.js\"\nimport { fillBlanks } from \"./blank-fills.js\"\nimport { detectColumnGutter, detectPersistentColumnGutter, orderByGutter, detectPanelGutters, orderByPanels, type ColRect } from \"./two-column.js\"\nimport { detectColumns, extractWithColumns } from \"./columns.js\"\nimport { shouldDemoteTable, demoteTableToText, detectListBlocks, detectSpecialKoreanTables } from \"./block-detect.js\"\nimport { markUnderlineItems, wrapUnderlineRuns } from \"./underline.js\"\nimport { extractImageRegions, type ImageRegion } from \"./image-regions.js\"\nimport { markImageCell } from \"./table-trim.js\"\nimport { mergeSliverColumns } from \"./table-trim.js\"\nimport { headerLineAbove } from \"./grid-header-line.js\"\nimport { CLIP_TABLES, CONT_PARTS, EMPTY_PARTS, FILLER_CELLS, TABLE_COLXS, TABLE_ROWYS, recordCellLines, recordRowRules } from \"./table-meta.js\"\nimport { hasCrossingFrames } from \"./crossing-frames.js\"\nimport { joinSpreadRowLabels } from \"./spread-labels.js\"\nimport { recordClipCellEdges, takeClipCellEdges } from \"./cell-edges.js\"\nimport { cleanCellText, splitAcrossCells, REGION_LABELS } from \"./cell-text.js\"\nimport { rebuildUnitLine, prependUnitRow, attachUnitRow } from \"./table-unit-row.js\"\nimport { WrapLexicon } from \"./line-wrap.js\"\nimport { isPageFrameGrid } from \"./page-frame.js\"\nimport { closeShadedTableEdges, nestRestoredShadedGrids } from \"./shaded-open-table.js\"\nimport { closeOpenTableEnds } from \"./open-table-ends.js\"\nimport { extendHeaderBoxRows } from \"./header-box-rows.js\"\nimport { detectRuledBandTables, type RuledTable } from \"./ruled-band-tables.js\"\nimport { detectTextBoxTables } from \"./text-box-table.js\"\nimport { bridgeSkippedRowVerticals } from \"./vertical-bridge.js\"\nimport { splitSidebarTitleRegion, splitTrailingColumnRegion, panelBlocks } from \"./local-regions.js\"\nimport { pushLineParagraphs } from \"./paragraph-lines.js\"\nimport { buildFrameCellBlocks, takePendingNested, recordFrameTitle, groupFrameParagraphUnits, recordFrameReadingUnit, frameLayoutBoxes, takeFrameSpanningText, frameColumnTextBands, FRAME_RECT_TOL } from \"./frame-cell-blocks.js\"\nimport { groupFlowBoxUnits } from \"./flow-boxes.js\"\nimport { extendNestedShadedHeaders } from \"./nested-shaded-headers.js\"\nimport { isChartTable, isExamLayoutTable, isFormulaTable, isTableOfContents, tocBlock, valueAxisBeside } from \"./table-roles.js\"\nimport { isSideTabTable, SIDE_TAB_TABLES } from \"./side-tabs.js\"\nimport { attachSideTabBlocks } from \"./side-tab-blocks.js\"\nimport { splitTwoColumnProse, figureColumnBands, topTableBand, tieredHeaderTable, stackedTableBands, threeColumnCards, threeColumnInfographic } from \"./page-regions.js\"\n\n/** 쪽 사이로 넘기는 칸 이어짐 상태 — 앞 쪽 번호와 그 쪽 클립 사실 (다음 쪽 첫 클립이 앞 쪽 마지막 칸의 이어짐인지 가른다, clip-cells) */\nexport interface PageCarry { page?: number; clip?: ClipPage }\n\n/**\n * 선 기반 테이블 감지를 우선 시도, 실패 시 기존 휴리스틱 fallback.\n * @param extraLines 그래픽 ops 밖에서 얻은 선 (래스터 괘선 감지 등, PDF pt·bottom-up)\n * @param carry 쪽 순서대로 부를 때 넘기는 칸 이어짐 상태 — 바로 앞 쪽 것만 쓰고 이 쪽 것으로 바꿔 둔다\n * @param lexicon 문서 어휘 증거(줄 꺾임 이음 판정) — 문서 파싱 동안 쪽마다 쌓는다. 없으면 이 쪽만으로\n */\nexport function extractPageBlocksWithLines(\n items: NormItem[],\n pageNum: number,\n opList: { fnArray: Uint32Array | number[]; argsArray: unknown[][] },\n pageWidth: number,\n pageHeight: number,\n extraLines?: { horizontals: LineSegment[]; verticals: LineSegment[] },\n detectTables = true,\n carry?: PageCarry,\n lexicon?: WrapLexicon,\n verifiedRightArrows: ImageRegion[] = [],\n): IRBlock[] {\n if (items.length === 0) {\n if (carry) carry.clip = undefined\n return []\n }\n // 쪽 옆 세로 책등·색인 탭 글자 기둥은 글 흐름(표 감지·XY-Cut)에서 뺀다 — 같은 높이 본문 제목을 XY-Cut 이 세로로 자르고(\"1-2) 폭력 대비\" |\n // \"및 대응의 적절성\"), 클러스터 표의 첫 열이 됐다(#112·#119). 되풀이 제거용 글 블록은 기존 읽기 위치·기준선 묶음을 보존한다\n const tab = sideTabGlyphs(items)\n if (tab.size) {\n const body = extractPageBlocksWithLines(items.filter(i => !tab.has(i)), pageNum, opList, pageWidth, pageHeight, extraLines, detectTables, carry, lexicon, verifiedRightArrows)\n return attachSideTabBlocks(tab, body, pageNum)\n }\n // 줄 꺾임 이음 판정의 어휘 증거 — 이 쪽 줄 글을 먼저 더해 쪽 안 어디서 판정하든 쪽 전체가 증거가 된다\n const lex = lexicon ?? new WrapLexicon()\n for (const line of streamLines(items)) lex.addLine(mergeLineSimple(line))\n\n // 1단계: PDF 그래픽 명령에서 선 추출\n const extracted = extractLines(opList.fnArray, opList.argsArray)\n let { horizontals, verticals } = extracted\n // 칸 변 괘선 판정(recordRowRules)은 그어진 획 그대로 본다 — 전처리가 열린 표 끝에 합성하는 가상 괘선은 빼고\n const rawRules = extracted.horizontals.concat(extracted.shortH)\n // 밑줄 빈칸(\"翻译成 ____ (语言)\")은 앞 글에 공백으로 메우고 선에서 뺀다 — 빈칸 간격이 표 열·단 사이로 읽히지 않게 (blank-fills.ts)\n const filled = fillBlanks(items, horizontals, verticals)\n items = filled.items\n horizontals = filled.horizontals\n // 1.2단계: 셀 클립 사각형 → 테두리 없는 표 그리드 (법령 별지서식 외곽 표). 셀 기하가 확정돼\n // 있어 line 경로를 거치지 않고, 실선 표는 아래 line 경로가 그대로 맡는다 (clip-cells.ts)\n const prevPage = carry?.page === pageNum - 1 ? carry.clip : undefined\n const clipResult = detectTables\n ? buildClipCellGrids(extracted.clipRects, horizontals, verticals, pageWidth, pageHeight, items.map(it => ({ x: it.x + it.w / 2, y: it.y + it.h / 2 })), extracted.fillRects, prevPage)\n : { grids: [], containers: [], page: undefined }\n if (carry) { carry.page = pageNum; carry.clip = clipResult.page }\n // 글 조각마다 깐 클립(ezPDF Builder·MS Print To PDF)이 맞대어 선 한 줄은 표가 아니다 (isTextRunClipStrip)\n const clipGrids = clipResult.grids.filter(g => !isTextRunClipStrip(g, items))\n // 클립 격자 칸의 보이는 변 — 합성 테두리를 더하기 전의 추출 선(짧은 조각 포함)으로 (보이지 않는 틀 표 풀기, cell-edges)\n recordClipCellEdges(clipGrids, horizontals.concat(extracted.shortH), verticals.concat(extracted.shortV), extracted.nonRules)\n // 짧은 괘선 조각 잇기는 칸 클립 격자가 없는 쪽에서만 (line-extract chainShortSegments) — 칸마다 클립이 있는 쪽은 잇기가\n // 필요 없고, 한컴 조직도 박스 조각을 이으면 여러 클립 표를 가로지르는 큰 선 격자가 생겨 클립 격자 틈으로 살아남는다\n // (rhwp multi-table-002 조직도 17x19 빈 격자가 부서명을 삼킴). 예산서(부천·속초)·MS Print To PDF 글자 클립 쪽은 잇는다\n if (clipGrids.length === 0) {\n horizontals = chainShortSegments(horizontals, extracted.shortH, \"h\")\n verticals = chainShortSegments(verticals, extracted.shortV, \"v\")\n }\n if (extraLines) {\n horizontals = horizontals.concat(extraLines.horizontals)\n verticals = verticals.concat(extraLines.verticals)\n }\n ;({ horizontals, verticals } = filterPageBorderLines(horizontals, verticals, pageWidth, pageHeight))\n\n // 1.5단계: 선 전처리 (ODL LinesPreprocessingConsumer 포팅)\n // 굵은 선 필터 + 음영 스택 제거 + 근접 평행 선 병합\n ;({ horizontals, verticals } = preprocessLines(horizontals, verticals, extracted.nonRules))\n\n // 1.55단계: 취소선 감지 — 텍스트 중심을 가로지르는 얇은 수평선 (ODL StrikethroughProcessor)\n markStrikethroughItems(items, horizontals)\n wrapStrikethroughRuns(items)\n\n // 1.56단계: 밑줄 — 합성 테두리를 만들기 전에 원본 선으로 판정.\n // 감지 — baseline 바로 아래에 밀착한 얇은 수평선.\n // 개정문 추가·변경 표시, 제목 강조 보존용 (pdf-inspector underline 휴리스틱 참조)\n // 글자 밑줄은 표 행 경계가 아니다 — 칸 안 링크 밑줄이 행을 가르던 것(ODL 180)\n const underlines = new Set(markUnderlineItems(items, horizontals, verticals, extracted.nonRules))\n if (underlines.size) horizontals = horizontals.filter(l => !underlines.has(l))\n wrapUnderlineRuns(items)\n\n // 1.6단계: 개방 변 표 테두리 합성 — 좌/우 바깥 테두리 생략 스타일(행정문서 관행)의\n // 가장자리 열 소실 방지. 내부 수직선이 실존하는 정렬 괘선 묶음에만 발동.\n horizontals = closeOpenTableEnds(horizontals, verticals, items.every(it => it.seq !== undefined))\n verticals = closeOpenTableEdges(horizontals, verticals)\n\n // 1.65단계: 무괘선 요약행 밴드(예산서 재원구분 시/구 행 등)로 끊긴 동일 열\n // 수직선 브리지 — 표 파편화로 헤더행·부서/정책 요약행이 그리드에서 탈락하는 것 방지\n verticals = bridgeSplitColumnVerticals(horizontals, verticals)\n if (detectTables && clipGrids.length === 0) verticals = bridgeSkippedRowVerticals(horizontals, verticals, items)\n if (detectTables && clipGrids.length === 0) ({ horizontals, verticals } = extendHeaderBoxRows(horizontals, verticals, items))\n\n const shadedEdges = detectTables ? closeShadedTableEdges(clipGrids, horizontals, verticals, extracted.fillRects) : undefined\n if (shadedEdges) ({ horizontals, verticals } = shadedEdges)\n\n // 2단계: 선으로 테이블 그리드 구성 (표 감지 opt-out 시 건너뜀 — #64)\n const lineGrids = detectTables ? buildTableGrids(horizontals, verticals, !(items.length > 0 && items.every(it => it.fontName === \"ocr\"))) : []\n if (shadedEdges) nestRestoredShadedGrids(lineGrids, shadedEdges.restored, horizontals, verticals)\n // 배경 칠한 칸에만 클립을 거는 제작기(cairo·한컴 구버전)의 음영 조각 격자는 버리고 온전한 선 표에 맡긴다 (dropShadingClipGrids)\n // Word 칸 여백 클립(칸 테두리 안쪽 글 영역)의 행 조각 격자도 선 표에 맡긴다 (dropInsetClipGrids)\n // 쪽 넘김 되풀이 머리 행 클립 띠도 선 표에 맡긴다 (dropHeadBandClipGrids)\n const tableClipGrids = dropHeadBandClipGrids(dropInsetClipGrids(dropShadingClipGrids(clipGrids, lineGrids, extracted.fillRects, verticals), lineGrids), lineGrids)\n recordClipCellEdges(extendNestedShadedHeaders(tableClipGrids, lineGrids, horizontals, verticals, extracted.fillRects),\n extracted.horizontals.concat(extracted.shortH), extracted.verticals.concat(extracted.shortV), extracted.nonRules)\n const grids = [...tableClipGrids, ...dropGridsInside(lineGrids, tableClipGrids, clipResult.containers, verticals)]\n const figures = () => extractImageRegions(opList.fnArray, opList.argsArray, true).filter(r => r.x2 - r.x1 >= 40 && r.y2 - r.y1 >= 40)\n .map(r => ({ x: r.x1, y: r.y1, w: r.x2 - r.x1, h: r.y2 - r.y1 }))\n\n // A rotated illustration can project a one-cell square far beyond the page.\n // Its lines are not evidence that all page text belongs to one table.\n if (grids.length === 1 && grids[0].rowYs.length === 2 && grids[0].colXs.length === 2 &&\n grids[0].bbox.x2 - grids[0].bbox.x1 > pageWidth * 1.2 &&\n grids[0].bbox.y2 - grids[0].bbox.y1 > pageHeight * 1.2) {\n return extractPageBlocksFallback(items, pageNum, true, detectTables, lex, figures())\n }\n\n // 가로 괘선만 있는 표(booktabs)는 표를 먼저 세우고 나머지 글은 격자 경로의 두 단·밴드 순서를 따른다\n const ruled = detectTables && clipGrids.length === 0 ? detectRuledBandTables(horizontals, verticals, items, pageNum) : []\n // 괘선을 그림에 구운 슬라이드 표는 보이지 않는 글상자 틀이 칸이다 (text-box-table)\n if (detectTables && clipGrids.length === 0 && ruled.length === 0 && lineGrids.length === 0) ruled.push(...detectTextBoxTables(extracted.hiddenBoxes, items, pageNum))\n if (ruled.length > 0) {\n const imageRegions = extractImageRegions(opList.fnArray, opList.argsArray).filter(r => r.x2 - r.x1 >= 8 && r.y2 - r.y1 >= 8)\n return extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, horizontals, verticals, imageRegions, lex, ruled, rawRules, verifiedRightArrows)\n }\n\n // Repeated dense rows with explicit captions form independent table bands.\n // A broad decorative line grid can otherwise swallow the whole page.\n if (detectTables && stackedTableBands(items)) {\n return extractPageBlocksFallback(items, pageNum, true, detectTables, lex, figures())\n }\n\n // A small decorative box in the page margin is not a content grid. It must\n // not force a sidebar title and its prose through the table-first path.\n const sidebar = splitSidebarTitleRegion(items)\n if (sidebar && grids.every(grid => grid.rowYs.length === 2 && grid.colXs.length === 2 &&\n !items.some(item => item.x + item.w / 2 >= grid.bbox.x1 && item.x + item.w / 2 <= grid.bbox.x2 &&\n item.y + item.h / 2 >= grid.bbox.y1 && item.y + item.h / 2 <= grid.bbox.y2))) {\n return sidebar.flatMap((region, index) => extractPageBlocksFallback(region, pageNum, false, index === 2 ? false : detectTables, lex))\n }\n\n if (grids.length > 0) {\n // 셀 안 그림(로고·서명 등) — 8pt 미만 조각은 장식이라 제외\n const imageRegions = extractImageRegions(opList.fnArray, opList.argsArray).filter(r => r.x2 - r.x1 >= 8 && r.y2 - r.y1 >= 8)\n return extractBlocksWithGrids(items, pageNum, pageWidth, pageHeight, grids, horizontals, verticals, imageRegions, lex, [], rawRules, verifiedRightArrows)\n }\n\n // Fallback: 기존 휴리스틱 (선이 없는 PDF). 단 안의 그림은 글이 없는 자리라 거터 판정에 점유 사각형으로 넘긴다\n return extractPageBlocksFallback(items, pageNum, true, detectTables, lex, figures())\n}\n\n// ─── 취소선 감지 (ODL StrikethroughProcessor 포팅) ─────\n// Original work: Copyright 2025-2026 Hancom Inc. (Apache-2.0)\n// https://github.com/opendataloader-project/opendataloader-pdf\n\n/** 취소선 최대 두께 (pt) — 굵은 선은 배경 채움/테두리 */\nconst STRIKE_MAX_THICKNESS = 2.0\n/** 취소선 두께 / 텍스트 높이 최대 비율 */\nconst STRIKE_MAX_THICKNESS_RATIO = 0.25\n/** 선 Y와 텍스트 중심 Y의 허용 오차 (텍스트 높이 비율) */\nconst STRIKE_CENTER_TOLERANCE = 0.25\n/** 선이 텍스트를 덮어야 하는 최소 수평 비율 */\nconst STRIKE_MIN_OVERLAP_RATIO = 0.8\n/** 선 폭 / 매칭 텍스트 총폭 최대 비율 — 표 구분선/배경선 오탐 방지 */\nconst STRIKE_MAX_LINE_TO_TEXT_RATIO = 1.5\n\n/**\n * 텍스트 중심을 가로지르는 얇은 수평선을 찾아 해당 아이템에 strike 마킹.\n * 법령 개정문(신구조문대비표)의 삭제 표시 텍스트 보존용.\n */\nfunction markStrikethroughItems(items: NormItem[], horizontals: LineSegment[]): void {\n if (items.length === 0 || horizontals.length === 0) return\n\n for (const line of horizontals) {\n if (line.lineWidth > STRIKE_MAX_THICKNESS) continue\n const matches: NormItem[] = []\n for (const item of items) {\n // OCR 글상자는 잉크 외곽 어림이라 그림 속 눈금선이 한가운데를 지나기 쉽다(차트 라벨 \"Reading experience\", ODL 107)\n if (item.fontName === \"ocr\") continue\n const h = item.h > 0 ? item.h : item.fontSize\n if (h <= 0 || item.w <= 0) continue\n if (line.lineWidth > h * STRIKE_MAX_THICKNESS_RATIO) continue\n // 글자 중심 근사: baseline(y) + 높이의 40% (한글 x-height 중앙)\n const centerY = item.y + h * 0.4\n if (Math.abs(line.y1 - centerY) > h * STRIKE_CENTER_TOLERANCE) continue\n const overlap = Math.min(line.x2, item.x + item.w) - Math.max(line.x1, item.x)\n if (overlap / item.w < STRIKE_MIN_OVERLAP_RATIO) continue\n matches.push(item)\n }\n if (matches.length === 0) continue\n // 선 폭이 매칭 텍스트 총폭의 1.5배 이내여야 취소선 (표 괘선 오탐 방지)\n let totalW = 0\n for (const m of matches) totalW += m.w\n if (totalW <= 0 || (line.x2 - line.x1) / totalW > STRIKE_MAX_LINE_TO_TEXT_RATIO) continue\n for (const m of matches) m.strike = true\n }\n}\n\n/**\n * strike 마킹된 연속 아이템 run을 ~~...~~ 마크다운으로 감싼다.\n * (같은 시각적 줄에서 인접한 마킹 아이템들을 하나의 run으로 묶음)\n */\nfunction wrapStrikethroughRuns(items: NormItem[]): void {\n const struck = items.filter(i => i.strike)\n if (struck.length === 0) return\n\n // 줄 단위 그룹핑 (y ±3) 후 x 순 정렬\n const lines = new Map<number, NormItem[]>()\n for (const item of struck) {\n const key = Math.round(item.y / 3)\n const arr = lines.get(key) || []\n arr.push(item)\n lines.set(key, arr)\n }\n for (const arr of lines.values()) {\n arr.sort((a, b) => a.x - b.x)\n arr[0].text = \"~~\" + arr[0].text\n arr[arr.length - 1].text = arr[arr.length - 1].text + \"~~\"\n }\n}\n\n// ─── 프로즈 박스 감지 (라벨탭 위 전폭 프로즈) ──────────\n/** 내부 수직 구분선 없는 전폭 행이 표 높이에서 차지해야 할 최소 비율 (기하 신호) */\nconst PROSEBOX_FULLWIDTH_MIN = 0.6\n/** 긴 프로즈 셀 판정 글자수 */\nconst PROSEBOX_LONG_CELL_CHARS = 80\n/** 프로즈 박스 판정 최소 긴 셀 수 */\nconst PROSEBOX_LONG_CELL_MIN = 3\n/** 긴 셀이 채운 셀에서 차지해야 할 최소 비율 (텍스트 신호) */\nconst PROSEBOX_LONG_CELL_RATIO = 0.4\n/** 열 경계 부근 수직선 매칭 tolerance (pt) */\nconst PROSEBOX_X_TOL = 8\n\n/** 열 경계 x 부근 수직선들이 [yMin,yMax]를 덮는 union 길이 (프로즈 박스 판정용) */\nfunction verticalCoverageAt(verticals: LineSegment[], x: number, yMin: number, yMax: number): number {\n const tol = PROSEBOX_X_TOL\n const spans: Array<[number, number]> = []\n for (const v of verticals) {\n if (Math.abs(v.x1 - x) > tol) continue\n const lo = Math.max(v.y1, yMin), hi = Math.min(v.y2, yMax)\n if (hi > lo) spans.push([lo, hi])\n }\n if (spans.length === 0) return 0\n spans.sort((a, b) => a[0] - b[0])\n let total = 0, s = spans[0][0], e = spans[0][1]\n for (let i = 1; i < spans.length; i++) {\n if (spans[i][0] <= e) { if (spans[i][1] > e) e = spans[i][1] }\n else { total += e - s; s = spans[i][0]; e = spans[i][1] }\n }\n return total + (e - s)\n}\n\n/**\n * 90° 돌려 찍은 표 — 가로로 긴 표를 세로 쪽에 반시계로 돌려 넣은 쪽(예산서 계속비 연도별 표, 기록관리 서식)은 격자의 행·열이 읽는 표의\n * 열·행이고 칸 글이 위로 진행한다. 그대로 세우면 행·열이 뒤바뀌고 사업명이 칸으로 끊겼다(khs 2014 계속비 \"광양 | 복선전철\"). 칸 글의\n * 80% 이상이 위로 진행하면 그 격자의 글과 괘선을 읽는 방향(x′=y, y′=쪽폭−x)으로 돌려 같은 쪽 파이프라인으로 표를 세우고, 블록 자리는 격자\n * 자리로 둔다. 아래로 진행하는 글이 섞이면 돌리지 않는다\n */\nfunction rotatedGridBlocks(\n grid: TableGrid, tableItems: NormItem[], horizontals: LineSegment[], verticals: LineSegment[],\n pageNum: number, pageWidth: number, pageHeight: number, lex?: WrapLexicon,\n): IRBlock[] | null {\n const turned = tableItems.filter(it => it.rotated !== undefined)\n if (turned.length < tableItems.length * 0.8 || turned.some(it => it.rotatedDown)) return null\n const b = grid.bbox, pad = 3\n const inside = (l: LineSegment) => Math.min(l.x1, l.x2) >= b.x1 - pad && Math.max(l.x1, l.x2) <= b.x2 + pad &&\n Math.min(l.y1, l.y2) >= b.y1 - pad && Math.max(l.y1, l.y2) <= b.y2 + pad\n const W = pageWidth\n const items: NormItem[] = turned.map(it => ({ ...it, x: it.y, y: W - (it.x + it.w), w: it.rotated!, h: it.fontSize, rotated: undefined }))\n // 가로 괘선(y 일정)은 읽는 방향의 세로 괘선, 세로 괘선은 가로 괘선이 된다\n const vs = horizontals.filter(inside).map(l => ({ ...l, x1: l.y1, x2: l.y1, y1: W - Math.max(l.x1, l.x2), y2: W - Math.min(l.x1, l.x2) }))\n const hs = verticals.filter(inside).map(l => ({ ...l, x1: Math.min(l.y1, l.y2), x2: Math.max(l.y1, l.y2), y1: W - l.x1, y2: W - l.x1 }))\n const out = extractPageBlocksWithLines(items, pageNum, { fnArray: [], argsArray: [] }, pageHeight, pageWidth, { horizontals: hs, verticals: vs }, true, undefined, lex)\n if (!out.some(blk => blk.type === \"table\")) return null\n const bbox: BoundingBox = { page: pageNum, x: b.x1, y: b.y1, width: b.x2 - b.x1, height: b.y2 - b.y1 }\n return out.map(blk => ({ ...blk, bbox }))\n}\n\n/** 글 조각마다 깐 클립은 칸이 아니다 — ezPDF Builder·MS Print To PDF 는 글 조각을 그 시작에서 다음 조각 시작까지 한 글줄 높이로 클립해,\n * 맞댄 클립들이 한 행 표가 됐다(고흥 2026 계획서 라벨 \"배경 및 필요성\" 이 \"필요성\" 문단 + \"| 배경 | 및 |\" 표로 찢긴 곳 749, hwpspec\n * \"DocHistory : | 스토리지\" 187, 평가 보고서 표 머리 \"’22 | 년\"). 한 행 클립 격자의 칸마다 글이 칸 왼변에서 곧바로 시작하고(한컴 칸 클립은\n * 안 여백만큼 떨어진다) 행 높이가 글자 크기의 1.6배 이하일 때 */\nfunction isTextRunClipStrip(grid: TableGrid, items: NormItem[]): boolean {\n if (!grid.cells || grid.rowYs.length !== 2 || grid.cells.length < 2) return false\n const h = grid.rowYs[0] - grid.rowYs[1]\n return grid.cells.every(cell => {\n const b = cell.bbox\n const inside = items.filter(it => it.x + it.w / 2 > b.x1 && it.x + it.w / 2 < b.x2 && it.y + it.h / 2 > b.y1 && it.y + it.h / 2 < b.y2)\n return inside.length > 0 && Math.abs(Math.min(...inside.map(it => it.x)) - b.x1) <= 0.5 && h <= 1.6 * Math.max(...inside.map(it => it.fontSize))\n })\n}\n\n/**\n * 프로즈 박스 판정 — 상단 라벨탭(제목 칩)이 박스 테두리에 걸쳐 만든 가짜 열 위로\n * 본문이 전폭 프로즈로 흐르는 표(예: 검정고시 응시자격 박스). 셀 텍스트 조인(demote)은\n * 찢긴 조각을 그대로 이어 스크램블되므로, 이 표는 버리고 아이템을 프로즈 폴백(자연\n * 읽기순)으로 재추출한다. 판정은 두 신호의 교집합 —\n * (a) 기하: 내부 수직 구분선 없는 전폭 행의 높이 합이 표 높이의 60%+\n * (b) 텍스트: 80자+ 긴 셀이 3개+ 이고 채운 셀의 40%+\n * 기하만으론 다줄셀 정규표, 텍스트만으론 서술형 2열표(용어설명·Q&A)와 구분되지 않아\n * 둘 다 충족할 때만 발동한다.\n */\nfunction isProseBoxGrid(grid: TableGrid, verticals: LineSegment[], table: IRTable, items: NormItem[]): boolean {\n const numCols = grid.colXs.length - 1\n if (numCols < 2 || grid.rowYs.length < 3) return false\n\n const gyMax = grid.rowYs[0], gyMin = grid.rowYs[grid.rowYs.length - 1]\n const span = gyMax - gyMin\n if (span <= 0) return false\n const interior = grid.colXs.slice(1, -1)\n let fullWidthHeight = 0\n const divided: boolean[] = []\n for (let r = 0; r < grid.rowYs.length - 1; r++) {\n const top = grid.rowYs[r], bot = grid.rowYs[r + 1]\n const h = top - bot\n // 내부 열 경계 어느 하나라도 이 행의 절반 이상을 덮는 수직선이 있으면 구분된 행\n const hasDivider = h > 0 && interior.some(cx => verticalCoverageAt(verticals, cx, bot, top) >= h * 0.5)\n divided.push(hasDivider)\n if (h > 0 && !hasDivider) fullWidthHeight += h\n }\n if (fullWidthHeight < span * PROSEBOX_FULLWIDTH_MIN) return false\n if (isLabelTabBox(grid, divided, items, span)) return true\n\n const texts = table.cells.flat().map(c => c.text.trim()).filter(Boolean)\n const longCells = texts.filter(s => s.length > PROSEBOX_LONG_CELL_CHARS).length\n if (longCells < PROSEBOX_LONG_CELL_MIN || longCells < texts.length * PROSEBOX_LONG_CELL_RATIO) return false\n\n return true\n}\n\n/**\n * 라벨탭 상자 — 상자 윗변에 걸친 작은 제목 칩(\"목 차\")의 세로변이 가짜 열 경계가 된 표. 칩 줄(구분된 위 행들) 아래가 통째로 전폭\n * 행(표 높이 80%+)이고 칩 줄 높이의 글이 칩 안 짧은 제목(12자 이하, 표 폭 60% 이하)뿐이면 칸 글 길이와 상관없이 프로즈 박스다 — 목차처럼\n * 짧은 줄만 든 상자는 긴 칸 신호를 못 받아 3×2 표로 짜이고 칸 순서가 뒤섞였다(벼·고추 재배면적조사 목차: \"□ 통계표\" 가 맨 앞 칸)\n */\nfunction isLabelTabBox(grid: TableGrid, divided: boolean[], items: NormItem[], span: number): boolean {\n let k = divided.length\n while (k > 0 && !divided[k - 1]) k--\n if (k === 0 || k === divided.length) return false\n const bodyTop = grid.rowYs[k], b = grid.bbox\n if (bodyTop - grid.rowYs[grid.rowYs.length - 1] < 0.8 * span) return false\n const label = items.filter(it => {\n const cx = it.x + it.w / 2, cy = it.y + it.h / 2\n return cy > bodyTop && cy < b.y2 && cx > b.x1 && cx < b.x2 && it.text.trim()\n })\n if (!label.length) return false\n const x1 = Math.min(...label.map(it => it.x)), x2 = Math.max(...label.map(it => it.x + it.w))\n return label.reduce((n, it) => n + it.text.replace(/\\s/g, \"\").length, 0) <= 12 && x2 - x1 <= 0.6 * (b.x2 - b.x1)\n}\n\n/**\n * OCR 쪽의 성긴 산문 격자 — 인포그래픽의 장식 선·말풍선 틀이 격자를 이뤄 칸 넷 중 셋 이상이 비고 글은 긴 문장 칸에\n * 몰린 것(ODL 141 \"10 THINGS YOU SHOULD KNOW ABOUT COPYRIGHT\"). 표가 아니라 본문이다. 텍스트층 서식(빈 기입칸 많은 신청서)과\n * 섞이지 않게 OCR 로 읽은 쪽에서만 본다\n */\nfunction isSparseProseGrid(table: IRTable): boolean {\n if (table.rows < 4) return false\n const texts = table.cells.flat().map(c => c.text.trim())\n const filled = texts.filter(Boolean)\n if (filled.length === 0 || filled.length > texts.length * 0.25) return false\n const chars = filled.reduce((n, t) => n + t.length, 0)\n const prose = filled.filter(t => t.length >= 60 && /[.!?。]/.test(t)).reduce((n, t) => n + t.length, 0)\n return prose >= chars * 0.6\n}\n\n/**\n * 선 기반 그리드가 감지된 경우: 테이블 영역의 텍스트는 셀에 매핑,\n * 나머지는 일반 텍스트 블록으로 처리.\n */\nfunction extractBlocksWithGrids(\n items: NormItem[],\n pageNum: number,\n pageWidth: number,\n pageHeight: number,\n grids: TableGrid[],\n horizontals: LineSegment[],\n verticals: LineSegment[],\n imageRegions: ImageRegion[] = [],\n lex?: WrapLexicon,\n ruled: RuledTable[] = [],\n rawRules: LineSegment[] = horizontals,\n verifiedRightArrows: ImageRegion[] = [],\n): IRBlock[] {\n // OCR 로 읽은 쪽(글이 모두 인식 결과) — 성긴 산문 격자 판정(isSparseProseGrid)은 이 쪽에서만\n const ocrPage = items.length > 0 && items.every(i => i.fontName === \"ocr\")\n const blocks: IRBlock[] = []\n const frameParagraphUnits: IRBlock[][] = []\n const usedItems = new Set<NormItem>()\n /** 벡터 막대 차트 영역(값 축 포함) — 그 밑 짧은 띠의 단 절단 문턱(chartBandGap) */\n const charts: { x1: number; x2: number; y1: number }[] = []\n for (const r of ruled) {\n for (const it of r.items) usedItems.add(it)\n blocks.push(r.block)\n }\n const proseSidebars = new Set<IRBlock>()\n // 중첩 클립 그리드(clipParent)에서 만든 표 — 틀 셀을 처리할 때 그 셀의 blocks 로 들어간다.\n // 면적 오름차순 처리라 안쪽 표가 항상 틀보다 먼저 여기 쌓인다\n const pendingNested: Array<{ parent: { x1: number; y1: number; x2: number; y2: number }; block: IRBlock; contained?: true }> = []\n\n // 그리드를 Y좌표 내림차순 정렬 (위→아래). 셀이 확정된 클립 그리드가 먼저 글을 가져간다 —\n // 틀 표(3×3 테두리 등)가 위에서 먼저 삼키면 안쪽 \"발신명의 | 직인\" 표가 빈 채로 죽는다.\n // 클립 그리드끼리는 작은 것(중첩표)이 틀(1×1)보다 먼저다.\n // 블록 순서는 마지막에 Y 로 다시 정렬되므로 처리 순서가 출력 순서를 바꾸지 않는다\n const gridArea = (g: TableGrid): number => (g.bbox.x2 - g.bbox.x1) * (g.bbox.y2 - g.bbox.y1)\n const sortedGrids = [...grids].sort((a, b) =>\n (b.cells ? 1 : 0) - (a.cells ? 1 : 0)\n || (a.cells && b.cells ? gridArea(a) - gridArea(b) : 0) // 클립 그리드끼리는 면적 오름차순 — 중첩표가 틀보다 먼저\n // 선 격자 중첩표도 품는 칸보다 먼저, 안쪽 단부터(면적 오름차순)\n || (b.lineNested ? 1 : 0) - (a.lineNested ? 1 : 0)\n || (a.lineNested && b.lineNested ? gridArea(a) - gridArea(b) : 0)\n || b.bbox.y2 - a.bbox.y2)\n\n for (const grid of sortedGrids) {\n // 1행 다열 그리드는 테이블 헤더일 가능성 높음 → 스킵하여 클러스터 감지에 위임.\n // 클립 그리드(grid.cells)는 셀 기하가 확정된 실제 표라 1행·1열이어도 그대로 낸다\n // (지정서의 \"발신명의 | 직인\" 1×2 표 실측)\n const numGridRows = grid.rowYs.length - 1\n const numGridCols = grid.colXs.length - 1\n const gridW = grid.bbox.x2 - grid.bbox.x1\n // 선 격자 중첩표를 품은 틀은 머리행·쪽 레이아웃 틀로 거르지 않는다 — 걸러지면 안쪽 표가 칸을 잃고 따로 나간다\n const holdsNested = !grid.cells && pendingNested.some(p => p.contained &&\n p.parent.x1 >= grid.bbox.x1 - FRAME_RECT_TOL && p.parent.x2 <= grid.bbox.x2 + FRAME_RECT_TOL &&\n p.parent.y1 >= grid.bbox.y1 - FRAME_RECT_TOL && p.parent.y2 <= grid.bbox.y2 + FRAME_RECT_TOL)\n if (!grid.cells && !holdsNested && numGridRows === 1 && numGridCols >= 2) continue\n // Full-width one-column frames are usually page layout. The compact\n // repeated-row candidate is checked again after text is mapped to cells.\n if (!grid.cells && !holdsNested && numGridCols === 1 && numGridRows >= 2 &&\n (numGridRows < 5 || gridW > pageWidth * 0.7)) continue\n if (!holdsNested && isPageFrameGrid(grid, extractCells(grid, horizontals, verticals), pageWidth, pageHeight, items)) continue\n // 그리드 영역 내 텍스트 아이템 수집\n const tableItems: NormItem[] = []\n const pad = 3\n for (const item of items) {\n if (usedItems.has(item)) continue\n // Y 범위 체크\n if (item.y < grid.bbox.y1 - pad || item.y > grid.bbox.y2 + pad) continue\n // X 범위 체크 — 아이템의 시작과 끝이 모두 그리드 안에 있어야 함\n if (item.x < grid.bbox.x1 - pad || item.x + item.w > grid.bbox.x2 + pad) continue\n // 좁은 그리드(120px 미만)에서 큰 아이템이 경계에 걸치면 제외\n // 제목 텍스트가 인접 그리드에 잡히는 것을 방지\n if (gridW < 120 && item.x + item.w > grid.bbox.x2 - 2) continue\n tableItems.push(item)\n usedItems.add(item)\n }\n\n // 가로 표를 90° 돌려 찍은 쪽 — 칸 글이 거의 다 위로 진행하는 격자는 읽는 방향으로 돌려 그 자리에 다시 세운다 (rotatedGridBlocks)\n if (!grid.cells && tableItems.length >= 4) {\n const turned = rotatedGridBlocks(grid, tableItems, horizontals, verticals, pageNum, pageWidth, pageHeight, lex)\n if (turned) {\n for (const it of tableItems) if (it.rotated === undefined) usedItems.delete(it)\n blocks.push(...turned)\n continue\n }\n }\n\n // 셀 추출 — 클립 그리드는 셀이 확정돼 있다\n const cells = grid.cells ?? extractCells(grid, horizontals, verticals)\n if (cells.length === 0) continue\n\n // 텍스트→셀 매핑 (hasSpaceBefore 전파 — 셀 텍스트 단어 공백 복원)\n // 칸 여럿에 걸친 한 조각은 칸마다 한 낱말로 가른다 (splitAcrossCells) — 조각마다 원래 아이템(owners)을 기억해 미배정 환원에 쓴다\n const owners: NormItem[] = []\n const textItems: TextItem[] = tableItems.flatMap(i => splitAcrossCells({\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\n fontSize: i.fontSize, fontName: i.fontName, hasSpaceBefore: i.hasSpaceBefore, syntheticSpace: i.syntheticSpace, spaceAfter: i.spaceAfter, seq: i.seq,\n }, cells).map(t => { owners.push(i); return t }))\n const cellTextMap = mapTextToCells(textItems, cells)\n\n // 셀 미배정 아이템 수집 — mapTextToCells는 교차비율 > 0.3만 배정하므로,\n // 그리드 bbox 안에 있지만 어느 셀에도 못 붙은 아이템(세로쓰기 헤더 등)을\n // usedItems에 남겨두면 표에도 프로즈에도 없이 무음 소멸한다.\n const assignedItems = new Set<TextItem>()\n for (const arr of cellTextMap.values()) for (const it of arr) assignedItems.add(it)\n\n // IRTable 구성\n const numRows = grid.rowYs.length - 1\n const numCols = grid.colXs.length - 1\n const irGrid: import(\"../types.js\").IRCell[][] = Array.from(\n { length: numRows },\n () => Array.from({ length: numCols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 })),\n )\n\n let nestedAttached = false\n for (const cell of cells) {\n const cellItems = cellTextMap.get(cell) || []\n // 틀 셀 — 안쪽 클립 그리드가 낸 표를 이 셀의 blocks 에 원문 순서(위→아래)로 넣는다.\n // 지정서·영치증의 \"발신명의 | 직인\" 표가 틀 뒤 별도 블록으로 빠지던 것을 HWP 파서 IR 과\n // 같은 모양(셀 안 문단 + 중첩표)으로 (v4.12.2)\n const nested = pendingNested.length ? takePendingNested(pendingNested, cell.bbox, !!grid.cells) : []\n if (nested.length > 0) {\n nestedAttached = true\n const built = buildFrameCellBlocks(cellItems, nested, pageNum, lex)\n irGrid[cell.row][cell.col] = { text: built.text, colSpan: cell.colSpan, rowSpan: cell.rowSpan, blocks: built.blocks }\n // 틀 칸 자기 글의 글줄 — 쪽을 넘은 틀 칸의 글 이어짐 판정(table-parts)이 본다 (과제 명세서 \"□ 개념\" 칸이 다음 쪽 상자 칸으로 이어짐)\n if (cellItems.length) recordCellLines(irGrid[cell.row][cell.col], cellItems)\n takeClipCellEdges(cell, irGrid[cell.row][cell.col])\n continue\n }\n irGrid[cell.row][cell.col] = {\n text: cleanCellText(cellTextToString(cellItems, { box: cell.bbox, lex })),\n colSpan: cell.colSpan,\n rowSpan: cell.rowSpan,\n }\n // 1×1 상자는 시각적 글줄 대신 원문 기하로 만든 문단을 함께 둔다. 칸 text 는 keep 모드의 기존 글을 보존한다.\n if (numRows === 1 && numCols === 1 && cellItems.length) {\n irGrid[cell.row][cell.col].blocks = buildFrameCellBlocks(cellItems, [], pageNum, lex).blocks\n }\n const b = cell.bbox\n // 칸을 통째로 덮는 그림은 칸 배경이다(행정업무운영 편람 예시 상자: 칸마다 배경 그림) — 내용 그림으로 보지 않는다\n const isBackdrop = (r: ImageRegion) => r.x1 <= b.x1 + 1 && r.x2 >= b.x2 - 1 && r.y1 <= b.y1 + 1 && r.y2 >= b.y2 - 1\n if (imageRegions.some(r => { const cx = (r.x1 + r.x2) / 2, cy = (r.y1 + r.y2) / 2; return cx > b.x1 && cx < b.x2 && cy > b.y1 && cy < b.y2 && !isBackdrop(r) })) {\n markImageCell(irGrid[cell.row][cell.col])\n }\n if (cell.filler && !cellItems.length) FILLER_CELLS.add(irGrid[cell.row][cell.col])\n if (cellItems.length) recordCellLines(irGrid[cell.row][cell.col], cellItems)\n takeClipCellEdges(cell, irGrid[cell.row][cell.col])\n }\n\n // 숫자 값 행의 벌어진 두 음절 머리 칸(\"건 축\")을 붙인다 (spread-labels)\n if (lex) {\n const boxes = new Map(cells.map(c => [irGrid[c.row][c.col], c.bbox] as const))\n // 두 번째 음절 앞에 진짜 공백 글리프(pdfjs 가 틈에 만든 합성 공백 말고)가 있는 칸 — 원문이 띄웠다\n const spaced = new Set(cells.filter(c => {\n const its = [...(cellTextMap.get(c) ?? [])].sort((a, b) => a.x - b.x)\n return its.length === 2 && !!its[1].hasSpaceBefore && !its[1].syntheticSpace\n }).map(c => irGrid[c.row][c.col]))\n joinSpreadRowLabels(irGrid, lex, REGION_LABELS, cell => boxes.get(cell), cell => spaced.has(cell))\n }\n // 과소분할 표 재구성 (ODL TableStructureNormalizer):\n // 행≤5 + 열≥3 + 셀 안에 텍스트 줄이 뭉친 표는 줄 centerY 기반 row band로 행 복원\n // (중첩표를 품은 틀·셀 클립 그리드는 셀 구조가 확정된 것이라 재구성하지 않는다 — 클립 표에 돌리면 상자 안\n // 문단이 줄마다 행·열로 찢긴다: 보도자료 \"[참고] SDG 14\" 2×2 상자 → 10×3)\n let finalGrid = irGrid\n let finalRows = numRows\n let rebuiltUsed = false\n const unitLine = !grid.cells && numRows >= 3 && numRows <= 5 && numCols >= 3 ? rebuildUnitLine(items, grid) : []\n if (!grid.cells && numRows <= 5 && numCols >= 3 && !nestedAttached && (numRows <= 2 || unitLine.length > 0)) {\n const rebuilt = normalizeUndersegmentedTable(irGrid, grid.colXs, textItems, grid.rowYs)\n if (rebuilt) {\n rebuiltUsed = true\n finalGrid = rebuilt.map(row => row.map(rawText => ({ text: cleanCellText(rawText), colSpan: 1, rowSpan: 1 })))\n finalRows = finalGrid.length\n }\n }\n // 미배정 아이템을 프로즈 경로로 환원 — 과소분할 재구축(rebuiltUsed)은\n // textItems 전체(미배정 포함)를 셀에 재배치하므로 그때는 환원하지 않는다(중복 방지)\n if (!rebuiltUsed) {\n for (let ti = 0; ti < textItems.length; ti++) {\n if (!assignedItems.has(textItems[ti])) usedItems.delete(owners[ti])\n }\n }\n if (unitLine.length > 0 && rebuiltUsed && !/^\\s*\\(\\s*단위\\s*[::]/.test(finalGrid[0]?.[0]?.text ?? \"\")) {\n prependUnitRow(finalGrid, numCols, unitLine, usedItems)\n finalRows++\n }\n if (!rebuiltUsed && attachUnitRow(items, grid, finalGrid, numCols, usedItems)) finalRows++\n\n // Alternating empty bands are visual row spacing, not empty data records.\n // Only a repeated, populated sequence is a semantic one-column table.\n let semanticOneColumn = false\n if (!grid.cells && !nestedAttached && numCols === 1 && numGridRows >= 2) {\n const populatedRows = finalGrid.filter(row => row[0]?.text.trim())\n if (populatedRows.length >= 4) {\n const fontSizes = tableItems.map(item => item.fontSize).filter(size => size > 0).sort((a, b) => a - b)\n const medianFont = fontSizes[Math.floor(fontSizes.length / 2)] ?? 0\n const meanRowHeight = (grid.bbox.y2 - grid.bbox.y1) / populatedRows.length\n semanticOneColumn = meanRowHeight <= Math.max(30, medianFont * 2.5)\n }\n if (!semanticOneColumn) {\n for (const it of tableItems) usedItems.delete(it)\n continue\n }\n finalGrid = populatedRows\n finalRows = finalGrid.length\n }\n\n // 격자 바로 위 무괘선 머리행 (텍스트층 글만) — 몸통만 괘선으로 가른 표\n if (!grid.cells && !rebuiltUsed && numCols >= 2) {\n const head = headerLineAbove(items.filter(it => !usedItems.has(it)), grid.colXs, grid.bbox.y2)\n if (head) {\n finalGrid.unshift(head.map(col => ({ text: cleanCellText(cellTextToString(col.map(i => ({\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h, fontSize: i.fontSize, fontName: i.fontName, hasSpaceBefore: i.hasSpaceBefore, syntheticSpace: i.syntheticSpace, spaceAfter: i.spaceAfter, seq: i.seq,\n })))), colSpan: 1, rowSpan: 1 })))\n finalRows++\n for (const col of head) for (const it of col) usedItems.add(it)\n }\n }\n // 음영·획 사각형이 어긋나 생긴 빈 실오라기 열 (클립 표 제외 — 한컴 표는 좁은 틈 열이 정답에도 있다)\n let outCols = numCols\n if (!grid.cells && !rebuiltUsed && numCols >= 3) {\n const colXs = [...grid.colXs]\n const fs = tableItems.map(item => item.fontSize).filter(size => size > 0).sort((a, b) => a - b)\n outCols -= mergeSliverColumns(finalGrid, colXs, (fs[fs.length >> 1] ?? 10) * 0.5)\n if (outCols !== numCols) grid.colXs = colXs\n }\n const irTable: IRTable = {\n rows: finalRows,\n cols: outCols,\n cells: finalGrid,\n hasHeader: finalRows > 1,\n ...(semanticOneColumn ? { renderAsTable: true } : {}),\n }\n // 중첩표도 같은 쪽 넘김 규칙을 쓴다 — pendingNested 분기 전에 기하 출처를 기록한다.\n if (grid.cells) { CLIP_TABLES.add(irTable); recordRowRules(irTable, grid.cells, grid.bbox, rawRules) }\n TABLE_COLXS.set(irTable, grid.colXs)\n if (grid.cells) recordFrameTitle(irTable, tableItems, pageNum)\n if (grid.cells && finalRows === numRows) TABLE_ROWYS.set(irTable, grid.rowYs)\n if (grid.continues) CONT_PARTS.set(irTable, grid.continues)\n\n // 빈 테이블(모든 셀이 빈 문자열) 스킵\n const hasContent = finalGrid.some(row => row.some(cell => cell.text.trim() !== \"\"))\n // 글 없는 클립 표는 쪽 넘김 조각일 수 있어 잇기 단계까지 둔다 (못 이으면 mergeCrossPageTables 가 버린다)\n const emptyPart = !hasContent && !!grid.cells && !grid.clipParent && !nestedAttached\n if (!hasContent && !emptyPart) continue\n if (emptyPart) EMPTY_PARTS.add(irTable)\n\n // 쪽 옆 띠의 단원 탭 표 — 틀 칸에 중첩시키지 않는다(틀 칸 글이 이 표 위쪽 끝에서 갈려 읽기 순서가 깨진다).\n // 쪽 블록으로 두면 여러 쪽 변환에서 removeSideTabs 가 자리 되풀이로 뺀다 (#112)\n const ownItems = numCols === 1 ? new Set(tableItems) : null\n if (ownItems && isSideTabTable(grid.bbox, irTable, items.filter(it => !ownItems.has(it)), pageWidth, pageHeight)) {\n SIDE_TAB_TABLES.add(irTable)\n blocks.push({ type: \"table\", table: irTable, pageNumber: pageNum, bbox: { page: pageNum, x: grid.bbox.x1, y: grid.bbox.y1, width: gridW, height: grid.bbox.y2 - grid.bbox.y1 } })\n continue\n }\n // 중첩 클립 그리드 — 틀 셀이 처리될 때 그 셀의 blocks 로 들어간다 (틀은 면적이 커서 뒤에 온다)\n if (grid.clipParent) {\n const nb: BoundingBox = { page: pageNum, x: grid.bbox.x1, y: grid.bbox.y1, width: grid.bbox.x2 - grid.bbox.x1, height: grid.bbox.y2 - grid.bbox.y1 }\n pendingNested.push({ parent: grid.clipParent, block: { type: \"table\", table: irTable, pageNumber: pageNum, bbox: nb } })\n continue\n }\n\n // 프로즈 박스: 가짜 열 위로 전폭 프로즈가 흐르는 표 → 표를 버리고 아이템을\n // 프로즈 폴백으로 재추출 (셀 조인 demote는 찢긴 조각을 스크램블하므로 부적합)\n // 클립 그리드는 셀 기하가 확정된 실제 표 — 프로즈 박스·의사 표 강등을 적용하지 않는다\n // 중첩표를 품은 표는 강등하지 않는다 — 강등 경로는 자기 글 아이템만 되살려 붙은 중첩 블록이 통째로 사라진다\n if (!grid.cells && !nestedAttached && (isProseBoxGrid(grid, verticals, irTable, tableItems) || ocrPage && isSparseProseGrid(irTable))) {\n for (const it of tableItems) usedItems.delete(it)\n continue\n }\n // 벡터로 그린 막대 차트의 눈금선·막대 격자는 표가 아니다 — 값 글자는 차트 영역 안에서\n // 위→아래 줄 순서의 글로 둔다(쪽 본문과 섞으면 열 감지가 본문을 찢는다, table-roles.ts). 칸에 못 붙어 글 흐름으로 돌려보낸\n // 막대 끝 값(격자 위 끝에 걸친 \"374\")과 격자 옆 값 축 눈금도 차트 글이다(ODL 077 — 값이 두 번 나오고 차트가 범례·출처 뒤로 갔다)\n if (!grid.cells && !nestedAttached && isChartTable(irTable, ocrPage)) {\n for (const it of tableItems) usedItems.add(it)\n const axis = valueAxisBeside(items.filter(it => !usedItems.has(it)), grid.bbox)\n for (const it of axis) usedItems.add(it)\n const x1 = Math.min(grid.bbox.x1, ...axis.map(it => it.x)), x2 = Math.max(grid.bbox.x2, ...axis.map(it => it.x + it.w))\n charts.push({ x1, x2, y1: grid.bbox.y1 })\n blocks.push(chartBlock([...tableItems, ...axis].sort((a, b) => b.y - a.y || a.x - b.x), pageNum, { page: pageNum, x: x1, y: grid.bbox.y1, width: x2 - x1, height: grid.bbox.y2 - grid.bbox.y1 }))\n continue\n }\n // 걸쳐 겹친 글상자 테두리가 한 격자로 묶인 것도 표가 아니다 (crossing-frames) — 차트 판정 뒤에: 막대 사각형도 그림 영역과 엇갈려 겹친다(ODL 078)\n if (!grid.cells && !nestedAttached && hasCrossingFrames(grid, horizontals, verticals)) {\n for (const it of tableItems) usedItems.delete(it)\n continue\n }\n\n const tableBbox: BoundingBox = {\n page: pageNum,\n x: grid.bbox.x1, y: grid.bbox.y1,\n width: grid.bbox.x2 - grid.bbox.x1, height: grid.bbox.y2 - grid.bbox.y1,\n }\n\n // A tall, narrow one-cell clip beside an independent prose column is a\n // layout panel. Keep its original lines as one region, not as a data table.\n if (numRows === 1 && numCols === 1 && gridW < pageWidth * 0.35) {\n const prose = finalGrid[0]?.[0]?.text ?? \"\"\n const besideLines = (side: NormItem[]) => new Set(side.map(it => Math.round(it.y / 3))).size\n const rightLines = items.filter(it => it.x >= grid.bbox.x2 + 3 &&\n it.y >= grid.bbox.y1 && it.y <= grid.bbox.y2)\n const leftLines = items.filter(it => it.x + it.w <= grid.bbox.x1 - 3 &&\n it.y >= grid.bbox.y1 && it.y <= grid.bbox.y2)\n // 짧은 글의 오른쪽 좁은 패널(사이드바)도 왼쪽에 본문 단이 나란하면 패널이다 — 본문 뒤에 읽는다\n if ((prose.length >= 200 && prose.split(\"\\n\").length >= 8 &&\n (besideLines(rightLines) >= 8 || besideLines(leftLines) >= 8)) ||\n (gridW < pageWidth * 0.25 && besideLines(leftLines) >= 8 && besideLines(rightLines) === 0)) {\n const sidebar: IRBlock = { type: \"paragraph\", text: prose, pageNumber: pageNum,\n bbox: tableBbox, style: dominantStyle(tableItems) }\n blocks.push(sidebar)\n proseSidebars.add(sidebar)\n SIDEBAR_ITEMS.set(sidebar, tableItems)\n continue\n }\n }\n\n // 의사 테이블 필터: 텍스트성 내용 → paragraph로 복원 (구조 보존)\n if (!grid.cells && !nestedAttached && shouldDemoteTable(irTable)) {\n const paragraphs = numRows === 1 && numCols === 1 ? irTable.cells[0]?.[0]?.blocks : undefined\n if (paragraphs?.length) { blocks.push(...paragraphs); frameParagraphUnits.push(paragraphs); recordFrameReadingUnit(paragraphs, tableBbox); continue }\n const demoted = demoteTableToText(irTable)\n if (demoted) {\n // 텍스트 박스(1x1 또는 1행 그리드) demote 시 앞뒤 줄바꿈으로 본문과 분리\n const text = numGridRows === 1 ? \"\\n\" + demoted + \"\\n\" : demoted\n blocks.push({ type: \"paragraph\", text, pageNumber: pageNum, bbox: tableBbox, style: dominantStyle(tableItems) })\n }\n continue\n }\n\n // 선 격자 중첩표 — 자기 상자를 품는 칸(나중에 처리되는 바깥 표)의 blocks 로\n if (grid.lineNested) {\n pendingNested.push({ parent: grid.bbox, block: { type: \"table\", table: irTable, pageNumber: pageNum, bbox: tableBbox }, contained: true })\n continue\n }\n blocks.push({ type: \"table\", table: irTable, pageNumber: pageNum, bbox: tableBbox })\n }\n // 틀 셀에 못 붙은 중첩표(틀이 빈 표로 걸러졌거나 셀 좌표가 어긋난 경우) — 종전대로 독립 블록\n for (const p of pendingNested) blocks.push(p.block)\n\n // 테이블에 속하지 않은 나머지 텍스트 → 일반 블록\n let remaining = items.filter(i => !usedItems.has(i))\n const groupSizes: number[] = []\n let finalTextBlocks: IRBlock[] = []\n let gutterX: number | null = null\n let panels: number[] | null = null\n if (remaining.length > 0) {\n remaining.sort((a, b) => b.y - a.y || a.x - b.x)\n\n // 클러스터 기반 테이블 감지 (XY-Cut 전에 실행 — 테이블이 쪼개지지 않도록)\n const clusterItems: ClusterItem[] = remaining.map(i => ({\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\n fontSize: i.fontSize, fontName: i.fontName, hasSpaceBefore: i.hasSpaceBefore, syntheticSpace: i.syntheticSpace, spaceAfter: i.spaceAfter,\n }))\n // 두 단 본문은 쪽 전체 클러스터 표 감지 전에 가른다 — 아래 거터 경로가 단마다 표를 따로 찾는다\n // (fallback 경로의 earlyProseCut 과 같은 순서. 먼저 표로 묶이면 두 단 줄이 한 표 행으로 섞인다)\n const proseColumns = findTwoColumnProseCutX(clusterItems) !== null ||\n detectPersistentColumnGutter(remaining.map(i => ({ x: i.x, y: i.y, w: i.w, h: i.h > 0 ? i.h : i.fontSize }))) !== null\n // 표 폭을 거의 다 덮는 글 문단 블록(선 상자 칸, 차트 숫자 라벨 말고)을 행 사이에 품은 후보는 한 표가 아니다 — 상자 위아래 본문 줄의 빈칸 간격이 열로\n // 묶였다(계약서 \"著作权人 …\" 상자를 사이에 두고 위 제목·아래 \"翻译成 ____ (语言)\" 본문이 6열 표로, 상자보다 앞에 나왔다)\n // 같은 열 수의 선 격자 표를 세로로 통째 품은 후보도 한 표가 아니다 — 격자가 본문 줄을 가져간 자리를 건너 위 머리띠와 아래 날짜 줄이 4열 표로\n // 묶이고, 같은 열 수라 격자 표와 이어 붙으며 날짜가 본문 앞에 섰다(사이버브릿지 선발 절차 흐름도). 열 수가 다르면 이어 붙지 않는다.\n // OCR 래스터 괘선은 일부 행만 격자가 되고 클러스터 표가 나머지 행을 메운다 — 텍스트층 쪽에만(기록관리 지침 OCR 기울임 3° 의 3열 표)\n const clusterResults = (proseColumns ? [] : detectClusterTables(clusterItems, pageNum, undefined, lex)).filter(cr => {\n const b = cr.bbox\n const overlapX = (x: BoundingBox) => Math.min(x.x + x.width, b.x + b.width) - Math.max(x.x, b.x)\n return !blocks.some(x => x.bbox && x.bbox.y >= b.y - 2 && x.bbox.y + x.bbox.height <= b.y + b.height + 2 &&\n (x.type === \"paragraph\" ? (x.text?.match(/\\p{L}/gu)?.length ?? 0) >= 10 && overlapX(x.bbox) >= 0.8 * b.width\n : x.type === \"table\" && !ocrPage && x.table?.cols === cr.table.cols && overlapX(x.bbox) >= 0.5 * Math.min(x.bbox.width, b.width)))\n })\n if (clusterResults.length > 0) {\n const ciToIdx = new Map<ClusterItem, number>()\n for (let ci = 0; ci < clusterItems.length; ci++) ciToIdx.set(clusterItems[ci], ci)\n const usedClusterIndices = new Set<number>()\n for (const cr of clusterResults) {\n for (const ci of cr.usedItems) {\n const idx = ciToIdx.get(ci)\n if (idx !== undefined) usedClusterIndices.add(idx)\n }\n blocks.push(clusterTableBlock(cr, remaining.filter((_, idx) => cr.usedItems.has(clusterItems[idx])), pageNum, horizontals))\n }\n remaining = remaining.filter((_, idx) => !usedClusterIndices.has(idx))\n }\n }\n\n // 2단 지면 감지 (#64) — 남은 텍스트 아이템과 표 블록 bbox를 합친 기하 신호.\n // 시험지처럼 텍스트가 대부분 표에 흡수된 페이지도 표 bbox만으로 판단된다.\n {\n const rects: ColRect[] = remaining.map(i => ({ x: i.x, y: i.y, w: i.w, h: i.h > 0 ? i.h : i.fontSize }))\n // 글 없는 클립 조각은 빼고, 문단으로 푼 닫힌 상자는 원본 영역 하나로 지면을 판정한다.\n for (const b of frameLayoutBoxes(blocks.filter(b => !(b.table && EMPTY_PARTS.has(b.table))))) rects.push({ x: b.x, y: b.y, w: b.width, h: b.height })\n gutterX = detectColumnGutter(rects) ?? findRuledColumnDivider(\n blocks.filter(b => b.type === \"table\" && b.bbox && b.table && !EMPTY_PARTS.has(b.table))\n .map(b => ({ x: b.bbox!.x, y: b.bbox!.y, w: b.bbox!.width, h: b.bbox!.height })),\n horizontals, verticals, pageWidth, pageHeight,\n )\n if (gutterX === null) panels = detectPanelGutters(rects)\n }\n\n if (remaining.length > 0) {\n if (gutterX !== null) {\n // 2단 지면: 거터 기준 좌/우/걸침으로 가른 뒤, 각 단 안에서는 기존과 동일하게\n // XY-Cut 그룹 단위로 처리한다 — 단 전체를 한 덩어리로 넘기면 클러스터 표\n // 감지가 문항 사이를 건너뛰며 선지 행들을 거대 표로 흡수한다(granularity 보존).\n const gx = gutterX\n const spanning = detectListBlocks(takeFrameSpanningText(remaining, gx, pageNum, lex))\n for (const block of spanning) {\n finalTextBlocks.push(block); groupSizes.push(1)\n }\n const allY = remaining.map(i => i.y)\n const pageH = safeMax(allY) - safeMin(allY)\n const gapThreshold = Math.max(15, pageH * 0.03)\n const sides = frameColumnTextBands(remaining, spanning, gx)\n const textBlocks: IRBlock[] = []\n for (const side of sides) {\n if (side.length === 0) continue\n for (const group of xyCutOrder(side, gapThreshold)) {\n if (group.length === 0) continue\n const groupBlocks = extractPageBlocksFallback(group, pageNum, false, true, lex, [], chartBandGap(group, gapThreshold, charts))\n for (const b of groupBlocks) textBlocks.push(b)\n groupSizes.push(groupBlocks.length)\n }\n }\n finalTextBlocks.push(...detectListBlocks(textBlocks))\n } else {\n // XY-Cut으로 왼쪽 본문과 오른쪽 부서명 등을 분리 후 개별 처리\n const allY = remaining.map(i => i.y)\n const pageH = safeMax(allY) - safeMin(allY)\n const gapThreshold = Math.max(15, pageH * 0.03)\n const groups = xyCutOrder(remaining, gapThreshold)\n const textBlocks: IRBlock[] = []\n for (const group of groups) {\n if (group.length === 0) continue\n const besideProsePanel = [...proseSidebars].some(sidebar => sidebar.bbox &&\n group.every(item => item.x >= sidebar.bbox!.x + sidebar.bbox!.width + 3) &&\n new Set(group.filter(item => item.y >= sidebar.bbox!.y &&\n item.y <= sidebar.bbox!.y + sidebar.bbox!.height).map(item => Math.round(item.y / 3))).size >= 8)\n const groupBlocks = extractPageBlocksFallback(group, pageNum, false, !besideProsePanel, lex, [], chartBandGap(group, gapThreshold, charts))\n for (const b of groupBlocks) textBlocks.push(b)\n groupSizes.push(groupBlocks.length)\n }\n finalTextBlocks = detectListBlocks(textBlocks) // 1:1 변환 — 그룹 경계(groupSizes) 유지\n }\n }\n\n // 그룹 단위 Y-정렬 — 블록 단위 Y-정렬은 XY-Cut이 정한 컬럼 읽기 순서(좌단\n // 전체 → 우단 전체)를 행 단위로 재인터리브하므로, XY-Cut 그룹을 한 단위로\n // 묶어 그룹 대표 Y(최상단)로만 정렬하고 그룹 내부 순서는 보존한다.\n // 같은 1×1 상자의 문단은 함께 읽고, 확인된 처리절차 띠는 그보다 큰 단위로 둔다.\n const units: IRBlock[][] = groupFrameParagraphUnits(groupFlowBoxUnits(blocks, grids, horizontals, verticals, verifiedRightArrows), frameParagraphUnits)\n let off = 0\n for (const size of groupSizes) {\n const unit = finalTextBlocks.slice(off, off + size)\n off += size\n if (unit.length > 0) units.push(unit)\n }\n const unitTopY = (u: IRBlock[]) => {\n let top = 0\n for (const b of u) {\n if (b.bbox && b.bbox.y + b.bbox.height > top) top = b.bbox.y + b.bbox.height\n }\n return top\n }\n if (panels && units.length > 1) {\n const unitBox = (u: IRBlock[]): ColRect => {\n const bs = u.filter(b => b.bbox).map(b => b.bbox!)\n if (!bs.length) return { x: -1e6, y: 0, w: 2e6, h: 0 }\n const x = Math.min(...bs.map(b => b.x)), y = Math.min(...bs.map(b => b.y))\n return { x, y, w: Math.max(...bs.map(b => b.x + b.width)) - x, h: Math.max(...bs.map(b => b.y + b.height)) - y }\n }\n const ordered: IRBlock[] = []\n for (const u of orderByPanels(units, unitBox, panels)) for (const b of u) ordered.push(b)\n return mergeAdjacentTableBlocks(ordered)\n }\n if (gutterX !== null && units.length > 1) {\n // 밴드 정렬 (#64): 거터를 가로지르는 유닛(전폭 표·머리글·쪽번호)을 위→아래\n // 밴드 경계로 삼고, 밴드 안에서 좌단 전체(위→아래) → 우단 전체 순으로 배열.\n const gx = gutterX\n const unitRect = (u: IRBlock[]): ColRect => {\n let minX = Infinity, minY = Infinity, maxR = -Infinity, maxT = -Infinity\n for (const b of u) {\n if (!b.bbox) continue\n if (b.bbox.x < minX) minX = b.bbox.x\n if (b.bbox.y < minY) minY = b.bbox.y\n if (b.bbox.x + b.bbox.width > maxR) maxR = b.bbox.x + b.bbox.width\n if (b.bbox.y + b.bbox.height > maxT) maxT = b.bbox.y + b.bbox.height\n }\n // bbox 없는 유닛(방어) — 거터 걸침으로 취급해 맨 뒤 경계로 밀림\n if (!Number.isFinite(minX)) return { x: gx - 1, y: 0, w: 2, h: 0 }\n return { x: minX, y: minY, w: maxR - minX, h: maxT - minY }\n }\n const ordered: IRBlock[] = []\n for (const u of orderByGutter(units, unitRect, gx)) for (const b of u) ordered.push(b)\n return mergeAdjacentTableBlocks(ordered)\n }\n // A landscape sheet can contain two independent portrait pages. In that\n // layout, a slightly higher table on the right must not precede the left\n // page's tables and prose. Require all units to stay within one half.\n if (pageWidth > pageHeight * 1.2 && units.length > 1) {\n const mid = pageWidth / 2\n const sideOf = (unit: IRBlock[]) => {\n let left = false, right = false\n for (const block of unit) {\n if (!block.bbox) return 0\n if (block.bbox.x + block.bbox.width <= mid) left = true\n else if (block.bbox.x >= mid) right = true\n else return 0\n }\n return left && !right ? -1 : right && !left ? 1 : 0\n }\n const left = units.filter(u => sideOf(u) === -1)\n const right = units.filter(u => sideOf(u) === 1)\n if (left.length >= 2 && right.length >= 2 && left.length + right.length === units.length &&\n left.some(u => u.some(b => b.type === \"table\")) && right.some(u => u.some(b => b.type === \"table\"))) {\n const flatten = (side: IRBlock[][]) => side.sort((a, b) => unitTopY(b) - unitTopY(a)).flat()\n return [...mergeAdjacentTableBlocks(flatten(left)), ...mergeAdjacentTableBlocks(flatten(right))]\n }\n }\n units.sort((a, b) => unitTopY(b) - unitTopY(a)) // PDF는 y가 위가 큼 → 내림차순\n // A panel can start below the first line of its neighboring prose column.\n // When their vertical spans coincide, read the left column before the right one.\n for (const sidebar of proseSidebars) {\n const box = sidebar.bbox!\n const sidebarIndex = units.findIndex(unit => unit.includes(sidebar))\n const peerIndex = units.findIndex(unit => unit.some(b => b.type === \"paragraph\" && b.text && b.text.length >= 200 && b.bbox &&\n b.bbox.x >= box.x + box.width + 3 &&\n Math.max(0, Math.min(box.y + box.height, b.bbox.y + b.bbox.height) - Math.max(box.y, b.bbox.y)) >=\n Math.min(box.height, b.bbox.height) * 0.6))\n if (sidebarIndex > peerIndex && peerIndex >= 0) {\n const [unit] = units.splice(sidebarIndex, 1)\n units.splice(peerIndex, 0, unit)\n continue\n }\n // A panel on the right is read after the prose column beside it.\n let lastLeft = -1\n units.forEach((unit, index) => { if (isProseBesidePanel(unit, box)) lastLeft = index })\n const current = units.findIndex(unit => unit.includes(sidebar))\n if (lastLeft > current) {\n const [unit] = units.splice(current, 1)\n units.splice(lastLeft, 0, unit)\n }\n }\n const ordered: IRBlock[] = []\n for (const u of units) for (const b of u) {\n const panel = SIDEBAR_ITEMS.get(b)\n if (panel) ordered.push(...panelBlocks(panel, b, pageNum))\n else ordered.push(b)\n }\n return mergeAdjacentTableBlocks(ordered)\n}\n\n/**\n * 패널 왼쪽 본문 단인가 — 패널 왼쪽 줄들, 패널 아래로 이어진 줄은 폭이 넓어도 같은 단이다. 옆에서 시작해 패널 아래로\n * 내려오며 넓어진 문단(끝줄만 패널 밑으로 삐져나감)도 밑변이 패널 밑이면 같은 단이다 (ODL 157)\n */\nexport function isProseBesidePanel(unit: IRBlock[], box: { x: number; y: number; width: number; height: number }): boolean {\n const beside = (b: IRBlock) => !!b.bbox && b.bbox.x + b.bbox.width <= box.x - 3 &&\n b.bbox.y + b.bbox.height >= box.y && b.bbox.y <= box.y + box.height\n return unit.some(beside) && unit.every(b => beside(b) || (!!b.bbox && b.bbox.y < box.y))\n}\n\n/** 사이드바 패널 글 — 순서 정렬이 패널을 한 덩이로 옮긴 뒤 서체 런으로 편다 (panelBlocks) */\nconst SIDEBAR_ITEMS = new WeakMap<IRBlock, NormItem[]>()\n\n/** 무괘선 표 후보를 역할대로 낸다 — 목차는 \"항목 쪽번호\" 줄, 차트는 영역 안 위→아래 줄 글, 나머지는 표 */\nfunction clusterTableBlock(cr: ClusterTableResult, source: NormItem[], pageNum: number, horizontals: LineSegment[] = []): IRBlock {\n // 목차 역할은 괘선 증거가 없는 후보에만 — 폭 대부분을 가로지르는 괘선이 행을 가르는 목차는 원문에서도 표다(한컴 목차 표를 OCR 로 읽은 쪽).\n // 라벨 밑 짧은 밑줄은 증거가 아니다. 괘선이 행 절반도 못 가르면 장 제목 밑줄이다(교육청 학교 매뉴얼 목차: 장마다 제목 밑 색 괘선 하나 —\n // 16행 목차가 3열 표로 남았다)\n const b = cr.bbox\n const rules = horizontals.filter(h => h.y1 > b.y && h.y1 < b.y + b.height &&\n Math.min(h.x2, b.x + b.width) - Math.max(h.x1, b.x) >= b.width * 0.6).length\n const ruled = rules >= 3 && rules >= (cr.table.rows - 1) * 0.5\n if (!ruled && isTableOfContents(cr.table)) return tocBlock(cr.table, pageNum, cr.bbox, dominantStyle(source))\n if (isChartTable(cr.table, source.length > 0 && source.every(it => it.fontName === \"ocr\"))) return chartBlock(source, pageNum, cr.bbox)\n if (isFormulaTable(cr.table)) return chartBlock(source, pageNum, cr.bbox)\n // 시험지 선택지·수식 배치는 줄 글 — 줄 경계는 남긴다(\"① $1$ ② $2$ ③ $3$\" / \"④ $4$ ⑤ $5$\")\n if (isExamLayoutTable(cr.table)) return { ...chartBlock(source, pageNum, cr.bbox), text: groupByY(source).map(line => mergeLineSimple(line).trim()).filter(Boolean).join(\"\\n\") }\n return { type: \"table\", table: cr.table, pageNumber: pageNum, bbox: cr.bbox }\n}\n\nfunction chartBlock(source: NormItem[], pageNum: number, bbox: BoundingBox): IRBlock {\n // 값만 있는 줄이 쪽번호 줄 제거(cleanPdfText)에 지워지지 않게 한 문단 글로 잇는다 — 위→아래 줄 순서는 그대로\n const text = groupByY(source).map(line => mergeLineSimple(line).replace(/\\s+/g, \" \").trim()).filter(Boolean).join(\" \")\n return { type: \"paragraph\", text, pageNumber: pageNum, bbox, style: dominantStyle(source) }\n}\n\n/** 같은 열 수의 연속 테이블 블록을 하나로 합침 — 선 기반 그리드 파편 재조립용. 클립 표는 변을 공유하지 않는\n * 별개 표라 합치지 않는다(행정업무운영 편람 Q&A 상자 두 개가 4×1 로 뭉개지고 안쪽 6×5 표가 사라지던 것) */\n/** 행마다 클립 격자를 따로 깐 표를 이어 만든 표 */\nconst STACKED_ROWS = new WeakSet<IRTable>()\n\n/** 같은 쪽에서 1행 클립 표들이 셀 간격 수준의 틈으로 열 경계가 모두 같게 이어지면 한 표의 행이다.\n * 여러 행을 가진 클립 표끼리는 잇지 않는다 — 한컴 PDF 는 열 경계가 같은 별개 표를 좁은 틈으로 쌓는다(행정업무운영 편람 −40표 실측) */\nfunction isStackedClipRow(upper: IRTable, ub: BoundingBox, lower: IRTable, lb: BoundingBox): boolean {\n if (!CLIP_TABLES.has(upper) || !CLIP_TABLES.has(lower) || upper.cols < 2 || upper.cols !== lower.cols ||\n lower.rows !== 1 || !STACKED_ROWS.has(upper) && upper.rows !== 1 ||\n CONT_PARTS.has(upper) || CONT_PARTS.has(lower) || EMPTY_PARTS.has(upper) || EMPTY_PARTS.has(lower) ||\n ub.page !== lb.page) return false\n const ux = TABLE_COLXS.get(upper), lx = TABLE_COLXS.get(lower)\n if (!ux || !lx || ux.length !== lx.length || ux.some((x, i) => Math.abs(x - lx[i]) > 1)) return false\n const gap = ub.y - (lb.y + lb.height)\n return gap >= -1 && gap <= Math.min(12, Math.min(ub.height, lb.height) * 0.5)\n}\n\nfunction mergeAdjacentTableBlocks(blocks: IRBlock[]): IRBlock[] {\n if (blocks.length <= 1) return blocks\n const result: IRBlock[] = [blocks[0]]\n for (let i = 1; i < blocks.length; i++) {\n const prev = result[result.length - 1]\n const curr = blocks[i]\n const clipRows = prev.type === \"table\" && curr.type === \"table\" && prev.table && curr.table &&\n prev.bbox && curr.bbox && isStackedClipRow(prev.table, prev.bbox, curr.table, curr.bbox)\n if (clipRows) {\n // 행마다 클립 격자를 따로 깐 표(셀 간격만 둔 틈·열 경계 동일)는 한 표의 행들이다\n const merged: IRTable = { rows: prev.table!.rows + curr.table!.rows, cols: prev.table!.cols,\n cells: [...prev.table!.cells, ...curr.table!.cells], hasHeader: prev.table!.hasHeader }\n CLIP_TABLES.add(merged)\n STACKED_ROWS.add(merged)\n TABLE_COLXS.set(merged, TABLE_COLXS.get(prev.table!)!)\n const pb = prev.bbox!, cb = curr.bbox!\n result[result.length - 1] = { ...prev, table: merged,\n bbox: { ...pb, y: cb.y, height: pb.y + pb.height - cb.y } }\n } else if (prev.type === \"table\" && curr.type === \"table\" && prev.table && curr.table &&\n prev.table.cols === curr.table.cols && prev.table.renderAsTable === curr.table.renderAsTable &&\n !CLIP_TABLES.has(prev.table) && !CLIP_TABLES.has(curr.table) && !separateRepeatedHeadings(prev, curr)) {\n // 합치기: prev의 cells에 curr의 cells 추가\n const merged: IRTable = {\n rows: prev.table.rows + curr.table.rows,\n cols: prev.table.cols,\n cells: [...prev.table.cells, ...curr.table.cells],\n hasHeader: prev.table.hasHeader,\n ...(prev.table.renderAsTable ? { renderAsTable: true } : {}),\n }\n result[result.length - 1] = { ...prev, table: merged }\n } else {\n result.push(curr)\n }\n }\n return result\n}\n\n/** 같은 쪽에서 간격을 두고 자기 머리행을 다시 시작한 표는 독립 표다. 붙은 한 격자와 머리 없는 파편은 그대로 잇는다. */\nfunction separateRepeatedHeadings(upper: IRBlock, lower: IRBlock): boolean {\n const u = upper.table!, d = lower.table!, ub = upper.bbox, db = lower.bbox\n if (!ub || !db || ub.page !== db.page || u.rows < 2 || d.rows < 2 || !u.hasHeader || !d.hasHeader ||\n ub.y - (db.y + db.height) <= 1 || Math.abs(ub.x - db.x) > 1 || Math.abs(ub.width - db.width) > 1) return false\n const a = u.cells[0], b = d.cells[0], norm = (text: string) => text.replace(/<\\/?u>|~~|\\s+/g, \"\")\n return a.filter(cell => norm(cell.text)).length >= 2 && a.length === b.length && a.every((cell, c) =>\n cell.colSpan === b[c].colSpan && cell.rowSpan === b[c].rowSpan && norm(cell.text) === norm(b[c].text))\n}\n\n/**\n * 어휘 증거용 줄 — 콘텐츠 스트림 순서(seq)대로 이어 가다 기준선이 바뀌거나 왼쪽으로 되돌아가거나 탭만큼 벌어지면 끊는다.\n * 쪽 전체를 y 로만 묶으면 같은 높이의 옆 칸 줄이 한 줄로 붙어(\"…공상공무 원 및 특별공로순직자의\" — 왼 칸 끝 + 오른 칸 머리)\n * 가짜 띄움 증거가 생긴다. 한컴은 칸마다·줄마다 글을 차례로 내므로 스트림 순서가 칸 줄을 지킨다. seq 없는 아이템(OCR)은 y 묶음\n */\nfunction streamLines(items: NormItem[]): NormItem[][] {\n if (items.some(i => i.seq === undefined)) return groupByY([...items].sort((a, b) => b.y - a.y || a.x - b.x))\n const lines: NormItem[][] = []\n let cur: NormItem[] = []\n for (const it of [...items].sort((a, b) => a.seq! - b.seq!)) {\n const last = cur[cur.length - 1]\n if (last && (Math.abs(it.y - last.y) > 3 || it.x < last.x - 1 || it.x - (last.x + last.w) > Math.max(2 * it.fontSize, 30))) {\n lines.push(cur)\n cur = []\n }\n cur.push(it)\n }\n if (cur.length) lines.push(cur)\n return lines\n}\n\n/** Keep aligned-column tables structural instead of escaping their generated Markdown as prose. */\nexport function columnTextToBlocks(text: string, pageNum: number, bbox: BoundingBox, style?: InlineStyle): IRBlock[] {\n const lines = text.split(\"\\n\")\n const blocks: IRBlock[] = []\n const prose: string[] = []\n const flushProse = () => {\n const content = prose.join(\"\\n\").trim()\n if (content) blocks.push({ type: \"paragraph\", text: content, pageNumber: pageNum, bbox, style })\n prose.length = 0\n }\n const cells = (line: string) => line.trim().slice(1, -1).split(/(?<!\\\\)\\|/).map(c => c.trim().replace(/\\\\\\|/g, \"|\"))\n const tableLine = (line: string) => /^\\|.*\\|\\s*$/.test(line.trim())\n\n for (let i = 0; i < lines.length;) {\n if (tableLine(lines[i]) && i + 2 < lines.length && tableLine(lines[i + 1]) &&\n cells(lines[i + 1]).every(c => /^:?-{3,}:?$/.test(c))) {\n let end = i + 2\n while (end < lines.length && tableLine(lines[end])) end++\n const rows = [cells(lines[i]), ...lines.slice(i + 2, end).map(cells)]\n const cols = rows[0].length\n const values = rows.slice(1).flat().filter(Boolean)\n const numeric = values.filter(value => value.length <= 24 && /\\d/.test(value)).length\n if (cols >= 3 && rows.length >= 3 && rows.every(row => row.length === cols) &&\n rows[0].every(value => value.length <= 80) && numeric >= 3 && numeric / values.length >= 0.25) {\n flushProse()\n blocks.push({ type: \"table\", pageNumber: pageNum, bbox, table: {\n rows: rows.length, cols, hasHeader: true,\n cells: rows.map(row => row.map(value => ({ text: value, rowSpan: 1, colSpan: 1 }))),\n } })\n i = end\n continue\n }\n }\n // 표로 인정하지 않은 열 줄은 칸 글을 이어 원래 줄로 — \"| a | b |\" 모양을 그대로 두면 이스케이프된 파이프 문단이 된다\n if (tableLine(lines[i])) {\n const cellTexts = cells(lines[i])\n if (!cellTexts.every(c => /^:?-{3,}:?$/.test(c))) prose.push(cellTexts.filter(Boolean).join(\" \"))\n } else prose.push(lines[i])\n i++\n }\n flushProse()\n return blocks\n}\n\n/**\n * 윗부분 띠에서 찾은 거터가 쪽 전체 거터와 어긋나면 쪽 전체 쪽을 믿는다 — 윗띠 스캔은 가장 왼쪽 빈 자리를 고르므로, 한 단 안에서\n * 차트 옆이 넓게 빈 자리를 거터로 잡는다(ODL 199 슬라이드: 아래 띠의 캡션·각주를 뺀 윗띠 거터 285pt 는 왼 패널 차트 오른쪽이라\n * 왼 패널 캡션·각주가 단을 가르는 경계 줄이 됐고, 쪽 전체 거터 354pt 가 두 패널 사이다). 쪽 전체 거터는 좌우 균형·행 가드를 다 통과한 값이다\n */\nfunction persistentGutter(textRects: ColRect[]): number | null {\n const cut = detectPersistentColumnGutter(textRects)\n if (cut === null) return null\n const whole = detectColumnGutter(textRects)\n return whole !== null && Math.abs(whole - cut) > 10 ? whole : cut\n}\n\n/**\n * 기존 휴리스틱 기반 페이지 블록 추출 (선이 없는 PDF 대비 fallback).\n *\n * fullPage: 페이지 전체 아이템으로 호출됐을 때만 true — 2단 조판 본문 감지는\n * 전체 지면 기준 신호라, XY-Cut 그룹(부분 집합) 재호출에서는 오발화하므로 끈다.\n *\n * detectTables: false 면 표 감지(클러스터·다열 정렬·한국어 특수표)를 모두 끄고\n * 자연 읽기순 텍스트만 낸다 (#64 opt-out).\n */\nexport function extractPageBlocksFallback(items: NormItem[], pageNum: number, fullPage = false, detectTables = true, lex?: WrapLexicon, figures: ColRect[] = [], minGap = 0): IRBlock[] {\n if (items.length === 0) return []\n\n if (fullPage && detectTables) {\n const bands = stackedTableBands(items)\n if (bands) {\n const blocks: IRBlock[] = []\n for (let i = 0; i < bands.tables.length; i++) {\n blocks.push(...extractPageBlocksFallback(bands.tables[i], pageNum, false, true, lex))\n if (i < bands.between.length) blocks.push(...extractPageBlocksFallback(bands.between[i], pageNum, false, false, lex))\n }\n blocks.push(...extractPageBlocksFallback(bands.caption, pageNum, false, false, lex))\n blocks.push(...extractPageBlocksFallback(bands.body, pageNum, true, true, lex))\n return blocks\n }\n }\n if (fullPage) {\n const infographic = threeColumnInfographic(items)\n if (infographic) return infographic.flatMap(group => extractPageBlocksFallback(group, pageNum, false, detectTables, lex))\n const cards = threeColumnCards(items)\n if (cards) return cards.flatMap(group => extractPageBlocksFallback(group, pageNum, false, detectTables, lex))\n }\n\n const blocks: IRBlock[] = []\n\n // 1단계: 클러스터 기반 테이블 감지 우선 (헤더 감지 시 정확도 높음)\n const clusterItems: ClusterItem[] = items.map(i => ({\n text: i.text, x: i.x, y: i.y, w: i.w, h: i.h,\n fontSize: i.fontSize, fontName: i.fontName, hasSpaceBefore: i.hasSpaceBefore, syntheticSpace: i.syntheticSpace, spaceAfter: i.spaceAfter,\n }))\n // A page with two justified prose columns must be partitioned before\n // cluster-table detection. Otherwise paired footnotes and body lines can\n // become a single false table, and their source coordinates are lost.\n const textRects = items.map(i => ({ x: i.x, y: i.y, w: i.w, h: i.h > 0 ? i.h : i.fontSize }))\n const imagePanels = fullPage ? splitImagePanels(items, figures) : null\n if (imagePanels) return imagePanels.flatMap(group => extractPageBlocksFallback(group, pageNum, false, detectTables, lex))\n // 한 단 위쪽을 그림이 차지하면 글만으로는 거터가 끊겨 보인다 — 그림 사각형을 더해 쪽 전체 거터를 확정한다\n const earlyProseCut = fullPage && detectTables\n ? findTwoColumnProseCutX(clusterItems) ?? persistentGutter(textRects) ??\n (figures.length > 0 ? detectColumnGutter([...textRects, ...figures]) : null)\n : null\n if (earlyProseCut !== null) {\n const band = topTableBand(items)\n if (band) {\n const tiered = tieredHeaderTable(band.top, pageNum)\n return [\n ...(tiered ? [tiered] : extractPageBlocksFallback(band.top, pageNum, false, detectTables, lex)),\n ...extractPageBlocksFallback(band.rest, pageNum, true, detectTables, lex),\n ]\n }\n return splitTwoColumnProse(items, earlyProseCut)\n .flatMap(group => extractPageBlocksFallback(group, pageNum, false, detectTables, lex))\n }\n if (fullPage && detectTables && figures.length > 0) {\n const bands = figureColumnBands(items, figures)\n if (bands) return bands.flatMap(group => extractPageBlocksFallback(group, pageNum, false, detectTables, lex))\n }\n if (fullPage && detectTables) {\n const sidebar = splitSidebarTitleRegion(items)\n if (sidebar) return sidebar.flatMap((region, index) => extractPageBlocksFallback(region, pageNum, false, index === 2 ? false : detectTables, lex))\n const regions = splitTrailingColumnRegion(items)\n if (regions) return regions.flatMap(region => extractPageBlocksFallback(region, pageNum, false, detectTables, lex))\n }\n const rejected = { prose: 0 }\n const clusterResults = detectTables ? detectClusterTables(clusterItems, pageNum, rejected, lex) : []\n\n if (clusterResults.length > 0) {\n const ciToIdx = new Map<ClusterItem, number>()\n for (let ci = 0; ci < clusterItems.length; ci++) ciToIdx.set(clusterItems[ci], ci)\n const usedIndices = new Set<number>()\n for (const cr of clusterResults) {\n for (const ci of cr.usedItems) {\n const idx = ciToIdx.get(ci)\n if (idx !== undefined) usedIndices.add(idx)\n }\n blocks.push(clusterTableBlock(cr, items.filter((_, idx) => cr.usedItems.has(clusterItems[idx])), pageNum))\n }\n\n // 테이블에 속하지 않은 나머지 텍스트 → 일반 블록\n const remaining = items.filter((_, idx) => !usedIndices.has(idx))\n if (remaining.length > 0) pushLineParagraphs(blocks, mergeSuperscriptLines(groupByY(remaining)), pageNum, lex)\n\n blocks.sort((a, b) => {\n const ay = a.bbox ? (a.bbox.y + a.bbox.height) : 0\n const by = b.bbox ? (b.bbox.y + b.bbox.height) : 0\n return by - ay\n })\n } else {\n // 2단계: 레거시 컬럼 감지 (3+ 열)\n // 2단 조판 본문(속기록류)은 들여쓰기 x-피크가 3+ 열로 오인돼 페이지 전체가\n // 행 인터리브 탭 텍스트로 뭉개진다 → 단 분리 경로에 위임\n let proseCutX = fullPage ? findTwoColumnProseCutX(items) : null\n // 프로즈 전용 검출이 불발하는 2단 지면(시험지 등 — 마커 다량·짧은 선지 줄)은\n // 기하 전용 거터 검출로 보강 (#64)\n if (proseCutX === null && fullPage) {\n proseCutX = detectColumnGutter(items.map(i => ({ x: i.x, y: i.y, w: i.w, h: i.h > 0 ? i.h : i.fontSize })))\n }\n const allYLines = mergeSuperscriptLines(groupByY(items))\n // 정렬이 산문으로 판정된 쪽(rejected.prose)은 그 열이 표 열의 증거가 아니다 — 열 표로 묶지 않고 XY-Cut 으로 읽는다\n const columns = proseCutX !== null || !detectTables || rejected.prose > 0 ? null : detectColumns(allYLines)\n\n if (columns && columns.length >= 3) {\n const tableText = extractWithColumns(allYLines, columns)\n const bbox = computeBBox(items, pageNum)\n blocks.push(...columnTextToBlocks(tableText, pageNum, bbox, dominantStyle(items)))\n } else {\n // 3단계: XY-Cut으로 읽기 순서 결정.\n // 2단 조판 본문은 전폭 제목/목차 줄이 X 프로젝션을 막아 XY-Cut이 단을 못\n // 가르는 경우가 있어(속기록 1면) 검출된 컷으로 직접 분리한다.\n const allY = items.map(i => i.y)\n const pageHeight = safeMax(allY) - safeMin(allY)\n const gapThreshold = Math.max(15, pageHeight * 0.03)\n\n const orderedGroups = proseCutX !== null\n ? splitTwoColumnProse(items, proseCutX)\n : xyCutOrder(items, gapThreshold, 0, undefined, minGap)\n\n for (const group of orderedGroups) {\n if (group.length === 0) continue\n const yLines = mergeSuperscriptLines(groupByY(group))\n\n const groupColumns = detectTables ? detectColumns(yLines) : null\n if (groupColumns && groupColumns.length >= 3) {\n const tableText = extractWithColumns(yLines, groupColumns)\n const bbox = computeBBox(group, pageNum)\n blocks.push(...columnTextToBlocks(tableText, pageNum, bbox, dominantStyle(group)))\n } else {\n pushLineParagraphs(blocks, yLines, pageNum, lex)\n }\n }\n }\n }\n\n // 한국어 특수 테이블 감지 (구분/항목/종류 패턴)\n return detectTables ? detectSpecialKoreanTables(blocks) : blocks\n}\n","import type { IRBlock } from \"../types.js\"\nimport { FRAME_READING_UNITS, recordFrameReadingUnit } from \"./frame-cell-blocks.js\"\n\nexport interface ImageRegion { x1: number; y1: number; x2: number; y2: number }\n\n/** 세로 괘선이 없는 그림 영역(원그래프 범례·차트 눈금)에서 OCR 글 정렬만으로 묶은 표 — 표로 받지 않고 행 문단으로 (pdf-ocr 가 적는다) */\nexport const UNRULED_REGION_TABLES = new WeakSet<IRBlock>()\n\n/** Add OCR evidence only inside image regions with no PDF text layer. */\nexport function mergeOcrImageRegions(\n blocks: IRBlock[], page: number, regions: ImageRegion[], ocrBlocks: IRBlock[],\n): number {\n let added = 0\n for (const region of regions) {\n const candidates = ocrBlocks.filter(block => {\n const b = block.bbox\n if (!b || b.page !== page || (block.type !== \"table\" && block.type !== \"paragraph\")) return false\n const overlapW = Math.max(0, Math.min(b.x + b.width, region.x2) - Math.max(b.x, region.x1))\n const overlapH = Math.max(0, Math.min(b.y + b.height, region.y2) - Math.max(b.y, region.y1))\n return overlapW * overlapH >= b.width * b.height * 0.8\n })\n const labels = candidates.filter(b => b.type === \"paragraph\" && b.bbox!.height <= 24 &&\n (b.text?.match(/[\\p{L}\\p{N}]/gu)?.length ?? 0) >= 2)\n const axisFragments = rotatedAxisFragments(candidates)\n const accepted = candidates.filter(b => {\n // 그림 속 글(차트 축·범례·로고 글) — 텍스트층이 없는 그림 영역의 OCR 문단\n if (b.type === \"paragraph\") return (b.text?.match(/[\\p{L}\\p{N}]/gu)?.length ?? 0) >= 2 ||\n (!axisFragments.has(b) && (supportedDiagramLabel(b, labels, region) || axisTick(b, candidates)))\n const t = b.table\n // 원그래프 범례가 머리 있는 표로 받아들여졌다(ODL 124·140) — 그림 속 진짜 표(110 점도 표·122 실험 표)는 영역 안 세로 괘선 5·8개, 범례는 0개\n if (b.type !== \"table\" || !t || UNRULED_REGION_TABLES.has(b)) return false\n if (t.rows === 1 && t.cols === 1) {\n const text = t.cells[0]?.[0]?.text ?? \"\"\n return (text.match(/\\n/g)?.length ?? 0) >= 5 && (text.match(/\\d/g)?.length ?? 0) >= text.length * 0.25\n }\n const headerLabels = t.cells[0]?.filter(c => c.text.replace(/[^A-Za-z가-힣]/g, \"\").length >= 2).length ?? 0\n return t.rows >= 2 && t.cols >= 2 && headerLabels >= t.cols * 0.75 &&\n t.cells.slice(1).some(row => row.filter(c => c.text.trim()).length >= 2)\n })\n // 표 모양이 아닌 OCR 표(머리 행 없는 화면 캡처 글줄 — ODL 072 유튜브 채널)는 버리지 않고 행마다 문단으로 — 그림 속 문단과 같은 대우\n const selected = candidates.flatMap(b => accepted.includes(b) ? [b] : b.type === \"table\" && b.table ? rowParagraphs(b)\n .filter(p => /[\\p{L}\\p{N}]{2}/u.test(p.text ?? \"\")) : [])\n // 원문 겹침은 이 영역을 넣기 전 블록(텍스트층 + 앞 영역)과만 잰다 — 같은 영역에서 방금 넣은 이웃 OCR 줄과 상자가 겹친다고\n // 다음 줄을 버리면 글이 사라졌다(ODL 102 \"342 334\"). 겹치는 두 그림 영역이 같은 글을 두 번 넣는 것은 그대로 막는다\n const before = blocks.slice()\n const hasOriginal = (block: IRBlock) => before.some(existing => {\n const b = block.bbox!\n if (existing.pageNumber !== page || !existing.bbox || existing.type === \"image\") return false\n const e = existing.bbox\n const x = Math.max(0, Math.min(e.x + e.width, b.x + b.width) - Math.max(e.x, b.x))\n const y = Math.max(0, Math.min(e.y + e.height, b.y + b.height) - Math.max(e.y, b.y))\n return x * y > b.width * b.height * 0.2\n })\n const selectedSet = new Set(selected), seen = new Set<IRBlock>(), placedHere = new Set<IRBlock>()\n for (const block of selected) {\n if (seen.has(block)) continue\n const frame = FRAME_READING_UNITS.get(block)\n const source = frame?.blocks.every(member => selectedSet.has(member)) ? frame.blocks : [block]\n source.forEach(member => seen.add(member))\n const available = source.filter(member => !hasOriginal(member))\n // A partial accepted or native-covered frame cannot reintroduce omitted text.\n const units = frame && available.length === source.length && source === frame.blocks\n ? [{ blocks: available, bbox: frame.bbox, atomic: true }]\n : available.map(member => ({ blocks: [member], bbox: member.bbox!, atomic: false }))\n for (const unit of units) {\n const b = unit.bbox\n // Source frame bounds keep later axis labels outside the whole legend,\n // including when the frame's paragraphs were already placed by this pass.\n const index = blocks.findIndex(existing => {\n const e = FRAME_READING_UNITS.get(existing)?.bbox ?? existing.bbox\n if (existing.pageNumber !== page || !e) return false\n const beside = e.y < region.y2 && e.y + e.height > region.y1 && e.x >= region.x2 - 1\n // 이 영역에서 방금 넣은 같은 줄 조각(세로 중심이 낮은 쪽 높이의 0.25 안)과는 왼→오 — 아래 끝만 보면 XY-Cut 이 가른 눈금 행 조각이\n // 처리 순서대로 이어져 \"5 6 7 8 9 / 0 / 1 …\" 이 됐다(ODL 128, 어긋남 0). 범례 라벨과 먼 축 눈금(0.33~0.42, 057)은 위→아래 그대로\n if (placedHere.has(existing) && Math.abs(e.y + e.height / 2 - b.y - b.height / 2) <= Math.min(e.height, b.height) * 0.25) return e.x > b.x\n return e.y < b.y || beside\n })\n // OCR image text is not typographic heading evidence.\n const placed = unit.blocks.map(member => member.type === \"paragraph\" ? { ...member, style: undefined } : member)\n if (unit.atomic) recordFrameReadingUnit(placed, unit.bbox)\n blocks.splice(index < 0 ? blocks.length : index, 0, ...placed)\n for (const member of placed) placedHere.add(member)\n added += placed.length\n }\n }\n }\n return added\n}\n\n/** 숫자 한 글자 라벨이 차트 눈금인지 — 같은 행(가로 축: 세로 중심이 작은 쪽 높이의 0.35 안)이나 오른끝을 맞춘 열(세로 축)에 숫자 라벨이\n * 둘 이상 더 있으면 눈금이다. 한 글자라 잡음으로 버리면 1~6·0~8 처럼 한 자리 눈금이 통째로 빠졌다(ODL 027). 눈금 열 옆 회전 축 이름 조각은\n * 숫자가 아니거나(a·t·S) 숫자 열과 오른끝이 어긋나 받지 않는다 */\nfunction axisTick(block: IRBlock, candidates: IRBlock[]): boolean {\n const b = block.bbox!\n if (!/^\\d$/u.test(block.text?.trim() ?? \"\")) return false\n const numeric = candidates.filter(o => o !== block && o.type === \"paragraph\" && o.bbox && /^[\\d\\s.,%+−-]+$/u.test(o.text?.trim() ?? \"\") &&\n /\\d/u.test(o.text ?? \"\") && Math.max(o.bbox.height, b.height) <= Math.min(o.bbox.height, b.height) * 2)\n const cy = b.y + b.height / 2, right = b.x + b.width\n const row = numeric.filter(o => Math.abs(o.bbox!.y + o.bbox!.height / 2 - cy) <= Math.min(o.bbox!.height, b.height) * 0.35)\n const column = numeric.filter(o => Math.abs(o.bbox!.x + o.bbox!.width - right) <= Math.max(o.bbox!.height, b.height) * 0.5)\n return row.length >= 2 || column.length >= 2\n}\n\n/** A small one-character node label can be meaningful inside a numeric diagram.\n * Require several stronger labels, a word, two numeric rows and nearby aligned\n * labels both above and below; isolated OCR specks still have no such context. */\nfunction supportedDiagramLabel(block: IRBlock, labels: IRBlock[], region: ImageRegion): boolean {\n const b = block.bbox, text = block.text?.trim() ?? \"\"\n if (!b || !/^[\\p{L}\\p{N}]$/u.test(text) || b.height < 4 || b.width < 1 || b.width > b.height * 2 ||\n b.x < region.x1 || b.y < region.y1 || b.x + b.width > region.x2 || b.y + b.height > region.y2 ||\n b.width * b.height > (region.x2 - region.x1) * (region.y2 - region.y1) * 0.005 ||\n labels.length < 3 || !labels.some(l => /\\p{L}{3}/u.test(l.text ?? \"\")) ||\n labels.filter(l => !/\\p{L}/u.test(l.text ?? \"\") && (l.text?.match(/\\p{N}/gu)?.length ?? 0) >= 2).length < 2) return false\n let above = false, below = false\n for (const label of labels) {\n const a = label.bbox!, em = Math.max(b.height, a.height)\n if (b.x + b.width < a.x - em * 2 || b.x > a.x + a.width + em * 2) continue\n const upperGap = a.y - (b.y + b.height), lowerGap = b.y - (a.y + a.height)\n if (upperGap >= 0 && upperGap <= em * 4) above = true\n if (lowerGap >= 0 && lowerGap <= em * 4) below = true\n }\n return above && below\n}\n\n/** Rotated axis words can become a tight vertical stack of OCR fragments.\n * Require both a right-aligned numeric tick column and adjacent letter fragments\n * within six tick heights to its left; unrelated diagram nodes retain their context. */\nfunction rotatedAxisFragments(candidates: IRBlock[]): Set<IRBlock> {\n const paragraphs = candidates.filter(b => b.type === \"paragraph\" && b.bbox!.height <= 24)\n const axes: IRBlock[][] = []\n for (const block of paragraphs) {\n const b = block.bbox!, text = block.text?.trim() ?? \"\"\n if (!/^[\\d\\s.,%+−-]+$/u.test(text) || !/\\d/u.test(text) || b.width > b.height * 6) continue\n const axis = axes.find(a => Math.abs(a[0].bbox!.x + a[0].bbox!.width - b.x - b.width) <= Math.max(a[0].bbox!.height, b.height) * 0.5)\n if (axis) axis.push(block)\n else axes.push([block])\n }\n const fragments = new Set<IRBlock>()\n const aligned = (a: IRBlock, b: IRBlock) => {\n const x = a.bbox!, y = b.bbox!, tolerance = Math.max(x.height, y.height) * 0.5\n return Math.abs(x.x - y.x) <= tolerance || Math.abs(x.x + x.width - y.x - y.width) <= tolerance\n }\n for (const axis of axes) {\n if (axis.length < 3) continue\n const bottom = Math.min(...axis.map(a => a.bbox!.y)), top = Math.max(...axis.map(a => a.bbox!.y + a.bbox!.height))\n const em = Math.max(...axis.map(a => a.bbox!.height)), right = axis[0].bbox!.x + axis[0].bbox!.width\n if (top - bottom < em * 6) continue\n const left = paragraphs.filter(block => {\n const b = block.bbox!, text = block.text?.trim() ?? \"\"\n return /\\p{Script=Latin}/u.test(text) && (text.match(/[\\p{L}\\p{N}]/gu)?.length ?? 0) <= 3 &&\n b.width <= b.height * 3 && b.x + b.width >= right - em * 6 &&\n b.x + b.width <= right - Math.max(em, b.height) && b.y >= bottom && b.y + b.height <= top\n })\n const stack = left.filter(a => left.some(b => {\n if (a === b || !aligned(a, b)) return false\n const x = a.bbox!, y = b.bbox!, gap = Math.max(x.y, y.y) - Math.min(x.y + x.height, y.y + y.height)\n return gap >= -Math.min(x.height, y.height) * 0.5 && gap <= Math.max(x.height, y.height)\n }))\n if (stack.length < 2) continue\n for (const block of paragraphs) {\n const b = block.bbox!\n if (b.y >= bottom && b.y + b.height <= top && b.x + b.width >= right - em * 6 && b.x + b.width <= right - Math.max(em, b.height) &&\n /^[\\p{L}\\p{N}]$/u.test(block.text?.trim() ?? \"\") && stack.some(s => aligned(s, block))) fragments.add(block)\n }\n }\n return fragments\n}\n\n/** OCR 표 → 행마다 문단(빈 칸 뺀 칸 글을 공백으로), 행 높이만큼 나눈 상자 */\nfunction rowParagraphs(block: IRBlock): IRBlock[] {\n const t = block.table!, b = block.bbox!\n const h = b.height / t.rows\n return t.cells.map((row, r) => ({\n type: \"paragraph\" as const, pageNumber: block.pageNumber, text: row.map(c => c.text.trim()).filter(Boolean).join(\" \"),\n bbox: { ...b, y: b.y + h * (t.rows - 1 - r), height: h },\n }))\n}\n"],"mappings":"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","names":["rules","median","closed","overlap","OPS","rules","near","rules","span","CLIP_MIN_W","CLIP_MIN_H","ys","near","span","_","overlap","overlap","rebuilt","p","overlap","cells","overlap","span","left","right","l","span","first","totalCells","emptyCells","overlap","MIN_REPEAT","span","OPS","OPS","ImageKind","contains","ImageKind","span","points","overlap","span","t","overlap","contains","EDGE_NEAR","near","near","EDGE_NEAR","near","chainVerticals","EDGE_TOL","MIN_ROWS","ALIGN_TOL","EDGE_TOL","WRAP_K","span","EDGE_TOL","ALIGN_TOL","rules","EDGE_TOL","MIN_COLS","MIN_ROWS","seen","span","near","contains","overlap","span","b","ordered","left","right","rules","blocks","seen"]}