kordoc 4.14.2 → 4.14.4
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.
- package/README.md +51 -6
- package/dist/{-AQKXLFIM.js → -Z4TJ3QGK.js} +13 -11
- package/dist/{chunk-QOMNEWCQ.cjs → chunk-5LAZQCUJ.cjs} +305 -31
- package/dist/chunk-5LAZQCUJ.cjs.map +1 -0
- package/dist/{chunk-7IAQ4HOK.js → chunk-6BWU6SQX.js} +1 -1
- package/dist/chunk-6BWU6SQX.js.map +1 -0
- package/dist/{chunk-FOJZNMMK.js → chunk-6CUIXJOC.js} +1 -1
- package/dist/chunk-6CUIXJOC.js.map +1 -0
- package/dist/{chunk-ZWD734EY.js → chunk-7SE4EZ45.js} +2 -2
- package/dist/{chunk-TSFS5VKD.js → chunk-D4U2TXDC.js} +332 -141
- package/dist/chunk-D4U2TXDC.js.map +1 -0
- package/dist/chunk-DPWWXUOG.js +2222 -0
- package/dist/chunk-DPWWXUOG.js.map +1 -0
- package/dist/{chunk-GDW4YP7K.js → chunk-GLSBEEZJ.js} +2 -2
- package/dist/{chunk-JQEFP45L.js → chunk-GUJ4Q73I.js} +2 -2
- package/dist/{chunk-ZWOFQRIB.js → chunk-HIJUKZHM.js} +3 -3
- package/dist/chunk-IMAAFSAX.cjs +1352 -0
- package/dist/chunk-IMAAFSAX.cjs.map +1 -0
- package/dist/{chunk-TNCKOL2O.js → chunk-LCW7SEPG.js} +2 -2
- package/dist/chunk-LFSQAMSM.js +1352 -0
- package/dist/chunk-LFSQAMSM.js.map +1 -0
- package/dist/chunk-MG6DJEX4.js +1299 -0
- package/dist/chunk-MG6DJEX4.js.map +1 -0
- package/dist/{chunk-QC5T6D4J.js → chunk-NLU6LBLY.js} +299 -25
- package/dist/chunk-NLU6LBLY.js.map +1 -0
- package/dist/{chunk-EQZI77PP.cjs → chunk-QEJUY6SK.cjs} +3 -3
- package/dist/{chunk-EQZI77PP.cjs.map → chunk-QEJUY6SK.cjs.map} +1 -1
- package/dist/{chunk-VQ5W4MYO.js → chunk-QIF2VXBQ.js} +298 -24
- package/dist/chunk-QIF2VXBQ.js.map +1 -0
- package/dist/{chunk-UET462HB.js → chunk-TITDJWVZ.js} +2 -2
- package/dist/{chunk-247ANM72.cjs → chunk-USHQCF5T.cjs} +870 -147
- package/dist/chunk-USHQCF5T.cjs.map +1 -0
- package/dist/{chunk-C7FQBSON.js → chunk-V3DX4DUF.js} +2 -2
- package/dist/{chunk-DOGPOY4B.js → chunk-VDN7LMPL.js} +2 -2
- package/dist/{chunk-EDNJLUSE.js → chunk-VJIL6PIL.js} +2 -2
- package/dist/{chunk-L7DMSMAG.cjs → chunk-VWUYVEFN.cjs} +1 -1
- package/dist/chunk-VWUYVEFN.cjs.map +1 -0
- package/dist/{chunk-HR2XNERB.cjs → chunk-WJHAU73B.cjs} +2 -2
- package/dist/{chunk-HR2XNERB.cjs.map → chunk-WJHAU73B.cjs.map} +1 -1
- package/dist/{chunk-NS55SM7A.js → chunk-WJJGW5PL.js} +2 -2
- package/dist/{chunk-EISO3POT.js → chunk-XVXHXQIO.js} +788 -194
- package/dist/chunk-XVXHXQIO.js.map +1 -0
- package/dist/{chunk-ILU76UF3.js → chunk-YAKC46HZ.js} +2 -2
- package/dist/chunk-YAKC46HZ.js.map +1 -0
- package/dist/{chunk-3DUADAY2.js → chunk-YAVHVFRY.js} +851 -128
- package/dist/chunk-YAVHVFRY.js.map +1 -0
- package/dist/{chunk-A7QC2ZVP.js → chunk-YT3GC62E.js} +851 -128
- package/dist/chunk-YT3GC62E.js.map +1 -0
- package/dist/chunks-CCMRL4MJ.js +10 -0
- package/dist/cli.js +228 -97
- package/dist/cli.js.map +1 -1
- package/dist/{formula-PJJ6S3Z2.cjs → formula-5LOVHAKE.cjs} +9 -9
- package/dist/{formula-PJJ6S3Z2.cjs.map → formula-5LOVHAKE.cjs.map} +1 -1
- package/dist/{formula-JGFTXWYT.js → formula-7RANCW5L.js} +3 -3
- package/dist/{formula-5S7WWXGH.js → formula-7UYJOVTB.js} +4 -4
- package/dist/{image-ocr-WCAYXLOA.js → image-ocr-I6JOCATS.js} +34 -16
- package/dist/image-ocr-I6JOCATS.js.map +1 -0
- package/dist/{image-ocr-ZTYAGV3W.cjs → image-ocr-NYBJEOH2.cjs} +36 -18
- package/dist/image-ocr-NYBJEOH2.cjs.map +1 -0
- package/dist/{image-ocr-FLIL7MGN.js → image-ocr-ZV6PDBUQ.js} +32 -14
- package/dist/image-ocr-ZV6PDBUQ.js.map +1 -0
- package/dist/index.cjs +4048 -1216
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -28
- package/dist/index.d.ts +66 -28
- package/dist/index.js +3487 -655
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +74 -53
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-FSPZZ2EM.js → model-bundle-2C5IYDLE.js} +5 -5
- package/dist/{models-CJPAQK5R.js → models-SEQZHZFT.js} +5 -5
- package/dist/{parser-SCUTZAIJ.js → parser-3FNXEP6T.js} +708 -250
- package/dist/parser-3FNXEP6T.js.map +1 -0
- package/dist/{parser-A4TMPJAM.js → parser-FJBJXRJK.js} +708 -250
- package/dist/parser-FJBJXRJK.js.map +1 -0
- package/dist/{parser-VCXE4I5A.cjs → parser-HVEKF73B.cjs} +733 -275
- package/dist/parser-HVEKF73B.cjs.map +1 -0
- package/dist/pdf-ocr-JRDBJ6TA.cjs +14 -0
- package/dist/{pdf-ocr-MBZZ57LD.cjs.map → pdf-ocr-JRDBJ6TA.cjs.map} +1 -1
- package/dist/pdf-ocr-UUBWBEHL.js +16 -0
- package/dist/pdf-ocr-VJSF2SQC.js +13 -0
- package/dist/{profile-io-LBM263K7.js → profile-io-RUFZOZO6.js} +3 -3
- package/dist/{rasterize-46RG4ZF6.js → rasterize-IOYXR5SI.js} +3 -3
- package/dist/redact-NMIKVHMP.js +18 -0
- package/dist/redact-doc-QBHNJ2BU.js +1284 -0
- package/dist/redact-doc-QBHNJ2BU.js.map +1 -0
- package/dist/{render-NGP5T24S.js → render-YZ2TEC3N.js} +7 -7
- package/dist/seal-ECV44TUH.js +10 -0
- package/dist/{setup-AU4OCHNH.js → setup-MPIEP6XT.js} +3 -3
- package/dist/visual-ICMOWUCR.js +19 -0
- package/dist/{watch-MKPUCPXM.js → watch-RYO7MN5B.js} +12 -12
- package/package.json +2 -2
- package/dist/chunk-247ANM72.cjs.map +0 -1
- package/dist/chunk-3DUADAY2.js.map +0 -1
- package/dist/chunk-422427XJ.cjs +0 -571
- package/dist/chunk-422427XJ.cjs.map +0 -1
- package/dist/chunk-7IAQ4HOK.js.map +0 -1
- package/dist/chunk-A7QC2ZVP.js.map +0 -1
- package/dist/chunk-DZIXKL7E.js +0 -145
- package/dist/chunk-DZIXKL7E.js.map +0 -1
- package/dist/chunk-EISO3POT.js.map +0 -1
- package/dist/chunk-EP7KOI2V.js +0 -571
- package/dist/chunk-EP7KOI2V.js.map +0 -1
- package/dist/chunk-FOJZNMMK.js.map +0 -1
- package/dist/chunk-ILU76UF3.js.map +0 -1
- package/dist/chunk-KVWV4ULP.js +0 -516
- package/dist/chunk-KVWV4ULP.js.map +0 -1
- package/dist/chunk-L7DMSMAG.cjs.map +0 -1
- package/dist/chunk-QC5T6D4J.js.map +0 -1
- package/dist/chunk-QOMNEWCQ.cjs.map +0 -1
- package/dist/chunk-TSFS5VKD.js.map +0 -1
- package/dist/chunk-VQ5W4MYO.js.map +0 -1
- package/dist/chunks-SX77P5CV.js +0 -10
- package/dist/image-ocr-FLIL7MGN.js.map +0 -1
- package/dist/image-ocr-WCAYXLOA.js.map +0 -1
- package/dist/image-ocr-ZTYAGV3W.cjs.map +0 -1
- package/dist/parser-A4TMPJAM.js.map +0 -1
- package/dist/parser-SCUTZAIJ.js.map +0 -1
- package/dist/parser-VCXE4I5A.cjs.map +0 -1
- package/dist/pdf-ocr-76SAUXMZ.js +0 -13
- package/dist/pdf-ocr-CPU55CVF.js +0 -16
- package/dist/pdf-ocr-MBZZ57LD.cjs +0 -14
- package/dist/redact-7ILEAUTS.js +0 -12
- package/dist/seal-RGUPOLXD.js +0 -10
- package/dist/visual-NZLYIYEM.js +0 -19
- /package/dist/{-AQKXLFIM.js.map → -Z4TJ3QGK.js.map} +0 -0
- /package/dist/{chunk-ZWD734EY.js.map → chunk-7SE4EZ45.js.map} +0 -0
- /package/dist/{chunk-GDW4YP7K.js.map → chunk-GLSBEEZJ.js.map} +0 -0
- /package/dist/{chunk-JQEFP45L.js.map → chunk-GUJ4Q73I.js.map} +0 -0
- /package/dist/{chunk-ZWOFQRIB.js.map → chunk-HIJUKZHM.js.map} +0 -0
- /package/dist/{chunk-TNCKOL2O.js.map → chunk-LCW7SEPG.js.map} +0 -0
- /package/dist/{chunk-UET462HB.js.map → chunk-TITDJWVZ.js.map} +0 -0
- /package/dist/{chunk-C7FQBSON.js.map → chunk-V3DX4DUF.js.map} +0 -0
- /package/dist/{chunk-DOGPOY4B.js.map → chunk-VDN7LMPL.js.map} +0 -0
- /package/dist/{chunk-EDNJLUSE.js.map → chunk-VJIL6PIL.js.map} +0 -0
- /package/dist/{chunk-NS55SM7A.js.map → chunk-WJJGW5PL.js.map} +0 -0
- /package/dist/{chunks-SX77P5CV.js.map → chunks-CCMRL4MJ.js.map} +0 -0
- /package/dist/{formula-JGFTXWYT.js.map → formula-7RANCW5L.js.map} +0 -0
- /package/dist/{formula-5S7WWXGH.js.map → formula-7UYJOVTB.js.map} +0 -0
- /package/dist/{model-bundle-FSPZZ2EM.js.map → model-bundle-2C5IYDLE.js.map} +0 -0
- /package/dist/{models-CJPAQK5R.js.map → models-SEQZHZFT.js.map} +0 -0
- /package/dist/{pdf-ocr-76SAUXMZ.js.map → pdf-ocr-UUBWBEHL.js.map} +0 -0
- /package/dist/{pdf-ocr-CPU55CVF.js.map → pdf-ocr-VJSF2SQC.js.map} +0 -0
- /package/dist/{profile-io-LBM263K7.js.map → profile-io-RUFZOZO6.js.map} +0 -0
- /package/dist/{rasterize-46RG4ZF6.js.map → rasterize-IOYXR5SI.js.map} +0 -0
- /package/dist/{redact-7ILEAUTS.js.map → redact-NMIKVHMP.js.map} +0 -0
- /package/dist/{render-NGP5T24S.js.map → render-YZ2TEC3N.js.map} +0 -0
- /package/dist/{seal-RGUPOLXD.js.map → seal-ECV44TUH.js.map} +0 -0
- /package/dist/{setup-AU4OCHNH.js.map → setup-MPIEP6XT.js.map} +0 -0
- /package/dist/{visual-NZLYIYEM.js.map → visual-ICMOWUCR.js.map} +0 -0
- /package/dist/{watch-MKPUCPXM.js.map → watch-RYO7MN5B.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pdf/image-extract.ts","../src/pdf/quality.ts","../src/pdf/vector-glyphs.ts","../src/pdf/table-parts.ts","../src/pdf/cell-continuation.ts","../src/pdf/symbol-fonts.ts","../src/pdf/text-clean.ts","../src/pdf/links.ts","../src/pdf/formula-ocr.ts","../src/pdf/polyfill.ts","../src/pdf/parser.ts"],"sourcesContent":["/**\n * PDF 이미지 XObject 바이트 추출 — 페이지 operatorList의 paint 이미지를\n * page.objs/commonObjs에서 디코딩된 픽셀로 받아 PNG로 인코딩한다.\n *\n * getOperatorList()가 이미 디코딩 비용을 지불하므로(선 감지용으로 항상 호출)\n * 여기서는 RGBA 변환 + PNG deflate만 추가된다. 순수 JS (node:zlib) 전용.\n */\n\nimport { OPS, ImageKind } from \"pdfjs-dist/legacy/build/pdf.mjs\"\nimport { encodePng } from \"../image/transcode.js\"\nimport type { ExtractedImage, IRBlock, ParseWarning } from \"../types.js\"\n\n/** 디코딩된 이미지 픽셀 (pdfjs page.objs 반환 형태의 최소 부분) */\ninterface PdfImgData {\n width: number\n height: number\n kind?: number\n data?: Uint8Array | Uint8ClampedArray\n}\n\n/** 페이지 프록시의 최소 형태 — objs/commonObjs 만 사용 */\ninterface PdfObjects {\n get(id: string, callback?: (data: unknown) => void): unknown\n}\ninterface PdfPageObjs {\n objs: PdfObjects\n commonObjs: PdfObjects\n}\n\n/** 개별 이미지 디코딩 대기 상한 — 실패 시에도 null 로 resolve 되므로 안전망 성격 */\nconst IMAGE_RESOLVE_TIMEOUT_MS = 5_000\n\n/**\n * 이미지 객체 대기 해제 — 디코딩은 getOperatorList() resolve 후에도 워커에서\n * 비동기로 진행되므로(pdfjs buildPaintImageXObject 는 await 하지 않음) 동기\n * has()/get() 은 이미 완료된 것만 잡힌다. 콜백형 get()으로 완료를 기다리며,\n * 디코딩 실패는 pdfjs 가 null 로 resolve 하므로 무한 대기는 없다.\n */\nfunction resolveImgData(page: PdfPageObjs, objId: string): Promise<PdfImgData | null> {\n // canvas.js 규약과 동일 — \"g_\" 접두사는 문서 공용 객체\n const store = objId.startsWith(\"g_\") ? page.commonObjs : page.objs\n return new Promise(resolve => {\n let done = false\n const timer = setTimeout(() => { if (!done) { done = true; resolve(null) } }, IMAGE_RESOLVE_TIMEOUT_MS)\n try {\n store.get(objId, (data: unknown) => {\n if (!done) { done = true; clearTimeout(timer); resolve((data ?? null) as PdfImgData | null) }\n })\n } catch {\n if (!done) { done = true; clearTimeout(timer); resolve(null) }\n }\n })\n}\n\n/** 폭·높이 하한 — 괘선/불릿 등 장식 조각 제외 */\nconst MIN_DIM = 8\n/** 총 픽셀 상한 — transcode.ts MAX_PIXELS와 동일 근거 (deflateSync 동기 블로킹 방지) */\nconst MAX_PIXELS = 36_000_000\n/** 문서당 추출 이미지 수 상한 */\nconst MAX_IMAGES_PER_DOC = 200\n/** 문서당 PNG 누적 바이트 상한 (128MB) */\nconst MAX_TOTAL_IMAGE_BYTES = 128 * 1024 * 1024\n\n/** 문서 단위 추출 상태 — 페이지 간 중복(로고·워터마크) 억제 + 상한 추적 */\nexport interface PdfImageState {\n imageIndex: number\n totalBytes: number\n /** 내용 해시 → 파일명 (이전 페이지에서 이미 추출된 이미지) */\n seen: Map<string, string>\n capWarned: boolean\n}\n\nexport function createPdfImageState(): PdfImageState {\n return { imageIndex: 0, totalBytes: 0, seen: new Map(), capWarned: false }\n}\n\n/** FNV-1a 32bit — 픽셀 버퍼 내용 해시 (crypto 불필요, 충돌은 dims 결합으로 완화) */\nfunction fnv1a(data: Uint8Array | Uint8ClampedArray): number {\n let h = 0x811c9dc5\n for (let i = 0; i < data.length; i++) {\n h ^= data[i]\n h = Math.imul(h, 0x01000193)\n }\n return h >>> 0\n}\n\n/** kind별 픽셀 → 8bit RGBA. 미지원/불일치 형태는 null. */\nfunction toRgba(img: PdfImgData): Uint8Array | null {\n const { width: w, height: h, kind, data } = img\n if (!data || !w || !h) return null\n const rgba = new Uint8Array(w * h * 4)\n\n if (kind === ImageKind.RGBA_32BPP) {\n if (data.length < w * h * 4) return null\n rgba.set(data.subarray(0, w * h * 4))\n return rgba\n }\n if (kind === ImageKind.RGB_24BPP) {\n if (data.length < w * h * 3) return null\n for (let i = 0, s = 0, d = 0; i < w * h; i++, s += 3, d += 4) {\n rgba[d] = data[s]; rgba[d + 1] = data[s + 1]; rgba[d + 2] = data[s + 2]; rgba[d + 3] = 255\n }\n return rgba\n }\n if (kind === ImageKind.GRAYSCALE_1BPP) {\n // 행 단위 비트 패킹 (stride = ceil(w/8)), 1=백 0=흑\n const stride = (w + 7) >> 3\n if (data.length < stride * h) return null\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const bit = (data[y * stride + (x >> 3)] >> (7 - (x & 7))) & 1\n const v = bit ? 255 : 0\n const d = (y * w + x) * 4\n rgba[d] = v; rgba[d + 1] = v; rgba[d + 2] = v; rgba[d + 3] = 255\n }\n }\n return rgba\n }\n return null\n}\n\n/**\n * 한 페이지의 operatorList에서 이미지를 추출해 image 블록·바이너리로 반환.\n * 같은 페이지 내 반복 paint(타일링)는 1회만, 이전 페이지와 동일 내용(로고·\n * 워터마크)은 블록·바이너리 모두 재방출하지 않는다.\n *\n * `withBytes` 가 false(images:false)면 PNG 인코딩을 건너뛰고 image 블록만 낸다.\n * 거르기·중복 판정은 같아서 자리 표시가 바이트를 뽑을 때와 같다. 바이트 누적 상한은\n * 메모리 보호용이라 이때는 걸리지 않는다.\n */\nexport async function extractPageImages(\n page: PdfPageObjs,\n fnArray: Uint32Array | number[],\n argsArray: unknown[][],\n pageNumber: number,\n state: PdfImageState,\n warnings: ParseWarning[],\n withBytes = true,\n): Promise<{ blocks: IRBlock[]; images: ExtractedImage[] }> {\n const blocks: IRBlock[] = []\n const images: ExtractedImage[] = []\n const pageSeenIds = new Set<string>()\n\n for (let i = 0; i < fnArray.length; i++) {\n const op = fnArray[i]\n let imgData: PdfImgData | null = null\n let dedupeId: string | null = null\n\n if (op === OPS.paintImageXObject || op === OPS.paintImageXObjectRepeat) {\n const objId = argsArray[i]?.[0]\n if (typeof objId !== \"string\" || pageSeenIds.has(objId)) continue\n pageSeenIds.add(objId)\n dedupeId = objId\n } else if (op !== OPS.paintInlineImageXObject) {\n continue\n }\n\n // 상한 도달 검사 — resolveImgData(이미지당 최대 5초 대기) 앞에서 중단해야\n // 상한 이후 이미지들이 페이지마다 디코딩 대기 시간만 소모하지 않는다\n if (state.imageIndex >= MAX_IMAGES_PER_DOC || state.totalBytes >= MAX_TOTAL_IMAGE_BYTES) {\n if (!state.capWarned) {\n state.capWarned = true\n warnings.push({ page: pageNumber, message: `이미지 추출 상한 도달 (${MAX_IMAGES_PER_DOC}개/128MB) — 이후 이미지 생략`, code: \"SKIPPED_IMAGE\" })\n }\n continue\n }\n\n if (dedupeId) {\n imgData = await resolveImgData(page, dedupeId)\n } else {\n imgData = argsArray[i]?.[0] as PdfImgData\n }\n\n if (!imgData?.data || !imgData.width || !imgData.height) continue\n const { width: w, height: h } = imgData\n if (w < MIN_DIM || h < MIN_DIM) continue // 괘선/불릿 장식\n if (w * h > MAX_PIXELS) {\n warnings.push({ page: pageNumber, message: `이미지 크기 초과로 추출 생략 (${w}×${h})`, code: \"SKIPPED_IMAGE\" })\n continue\n }\n\n // 페이지 간 내용 중복 — 로고·워터마크 재추출 방지\n const hash = `${w}x${h}k${imgData.kind ?? \"?\"}h${fnv1a(imgData.data)}${dedupeId ? \"\" : \"inline\"}`\n if (state.seen.has(hash)) continue\n\n const rgba = toRgba(imgData)\n if (!rgba) continue // 미지원 픽셀 형태 (bitmap 전용 등)\n\n state.imageIndex++\n const filename = `image_${String(state.imageIndex).padStart(3, \"0\")}.png`\n state.seen.set(hash, filename)\n if (withBytes) {\n const png = encodePng(w, h, rgba)\n state.totalBytes += png.length\n images.push({ filename, data: png, mimeType: \"image/png\" })\n }\n blocks.push({ type: \"image\", text: filename, pageNumber })\n }\n\n return { blocks, images }\n}\n\n/**\n * 페이지별 image 블록을 본문 블록 열에 주입 — 각 페이지의 마지막 블록 뒤.\n * 페이지 루프 중 바로 끼워 넣으면 페이지 경계에서 표 블록 인접성이 깨져\n * mergeCrossPageTables가 무산되므로, 병합이 끝난 뒤 호출해야 한다.\n */\nexport function injectPageImageBlocks(blocks: IRBlock[], pageImages: Map<number, IRBlock[]>): void {\n if (pageImages.size === 0) return\n\n // 페이지별 마지막 블록 위치 → 위치별 페이지 역인덱스\n const lastIndexForPage = new Map<number, number>()\n for (let i = 0; i < blocks.length; i++) {\n const p = blocks[i].pageNumber\n if (p !== undefined) lastIndexForPage.set(p, i)\n }\n const pageAtIndex = new Map<number, number>()\n for (const [p, last] of lastIndexForPage) pageAtIndex.set(last, p)\n\n const result: IRBlock[] = []\n const injected = new Set<number>()\n for (let i = 0; i < blocks.length; i++) {\n result.push(blocks[i])\n const p = pageAtIndex.get(i)\n if (p !== undefined && pageImages.has(p)) {\n result.push(...pageImages.get(p)!)\n injected.add(p)\n }\n }\n // 텍스트 블록이 하나도 없는 페이지(전면 이미지 등) — 페이지 순서 위치에 삽입\n for (const p of [...pageImages.keys()].sort((a, b) => a - b)) {\n if (injected.has(p)) continue\n let at = result.length\n for (let i = 0; i < result.length; i++) {\n const bp = result[i].pageNumber\n if (bp !== undefined && bp > p) { at = i; break }\n }\n result.splice(at, 0, ...pageImages.get(p)!)\n }\n\n blocks.length = 0\n blocks.push(...result)\n}\n","/**\n * PDF 페이지별 텍스트 품질 신호 수집.\n *\n * pdfjs가 텍스트층을 추출했더라도, 폰트의 ToUnicode/CMap이 불완전하면\n * 한글이 PUA(Private Use Area) 글리프 코드로 그대로 떨어진다. 또 일부 PDF는\n * NUL/제어문자가 본문에 섞여 있다. 본 모듈은 그런 신호를 페이지 단위로 수집해\n * \"OCR 검토 필요\" 판정에 사용한다.\n *\n * 더 까다로운 케이스: ToUnicode가 \"잘못\" 매핑되면 글리프가 PUA/FFFD가 아니라\n * 정상 한글 음절 영역(0xAC00-0xD7A3)의 엉뚱한 글자로 떨어진다(\"GPU쭒컫 많핂슪\").\n * 이건 PUA/제어/대체문자 신호로 못 잡는다. 대신 자소를 분해해 종성(받침) 분포를\n * 본다 — CID 스크램블은 받침을 균등하게 흩뿌리므로, 자연 한국어(받침없음 다수 +\n * 겹받침 희소)와 통계적으로 갈린다. 이것이 garbled_hangul 신호다.\n *\n * 텍스트층 밖 신호 하나: 글자를 채운 곡선 경로로 그린 쪽(vector-glyphs.ts 가 연산자 목록 기하로 센 글자 수를\n * 받는다). 텍스트층에는 ASCII 몇 자만 남아 위 신호로는 멀쩡해 보인다. 이것이 vector_text 신호다.\n */\n\nexport interface PageQuality {\n /** 1-based 페이지 번호 */\n page: number\n /** 공백 제외 문자 수 */\n textChars: number\n /** 한글 음절(0xAC00-0xD7A3) 비율 (0~1) */\n hangulRatio: number\n /** C0/C1 제어문자 비율 (tab/newline/CR 제외) */\n controlCharRatio: number\n /** U+FFFD replacement character 비율 */\n replacementCharRatio: number\n /** PUA 비율 — 글꼴 매핑 실패의 핵심 신호 */\n puaRatio: number\n /** 한글 음절 중 받침 없는 음절 비율 (자연 한국어 ~0.5, mojibake ~0.04) */\n hangulNoBatchimRatio: number\n /** 한글 음절 중 겹받침/희귀 받침 비율 (자연 한국어 <0.06, mojibake ~0.5) */\n hangulRareBatchimRatio: number\n /** OCR 검토 권장 여부 (pdfjs 텍스트층 기준 원신호 — OCR 적용 후에도 보존) */\n needsOcr: boolean\n /** needsOcr=true일 때 사유 (단일 신호로 충분, 가장 강한 신호 선택) */\n ocrReason?: \"vector_text\" | \"low_text\" | \"high_pua\" | \"high_control\" | \"high_replacement\" | \"garbled_hangul\"\n /** 이 페이지에 OCR 이 실제 적용되어 본문이 OCR 결과로 대체됨 */\n ocrApplied?: boolean\n}\n\n/**\n * 희귀 종성(받침) 인덱스 집합. 종성 인덱스 = (code - 0xAC00) % 28,\n * 0 = 받침 없음. 겹받침(ㄳㄵㄶㄺㄻㄼㄽㄾㄿㅀㅄ) + 거의 안 쓰는 홑받침(ㅋㅌㅍ).\n * 자연 한국어에서 이들의 합산 비율은 매우 낮다(<6%).\n */\nconst RARE_JONG = new Set<number>([3, 5, 6, 9, 10, 11, 12, 13, 14, 15, 18, 24, 25, 26])\n\n/** garbled_hangul 판정 최소 한글 음절 수 — 통계적 유의성 확보 */\nconst MOJIBAKE_MIN_HANGUL = 30\n/** 받침 없는 음절 비율이 이 값 미만이면 비정상 (자연 한국어는 훨씬 높음) */\nconst MOJIBAKE_MAX_NOBATCHIM = 0.15\n/** 희귀 받침 비율이 이 값 이상이면 비정상 (자연 한국어는 훨씬 낮음) */\nconst MOJIBAKE_MIN_RAREBATCHIM = 0.25\n\n/**\n * vector_text 하한: 텍스트층 밖 곡선 글자(vector-glyphs.ts 글줄다운 조각의 글자 수). 코퍼스 비-cairo 표지 로고 곡선\n * 글자는 34개 이하(국가교통통계 표지 로고 3개 34·기록물관리 지침 표지 30)라 못 넘고, 글줄 몇 개면 넘는다\n */\nconst VECTOR_TEXT_MIN_GLYPHS = 40\n/**\n * 곡선 글자가 쪽 글자(곡선 + 텍스트층)에서 차지할 최소 비율 — 텍스트층 글이 대부분인 쪽(차트 라벨만 곡선인 보도자료\n * 안내서 62/434 = 0.14)을 OCR 로 갈아엎지 않는다. 대가: 텍스트층 ASCII 가 대부분인 cairo 숫자 칸 표 쪽(632 + 곡선 65 =\n * 0.09)과, 한글 대부분이 텍스트층에 있고 일부만 곡선인 cairo 쪽(0.10~0.24)은 OCR 대상이 아니다\n */\nconst VECTOR_TEXT_MIN_SHARE = 0.25\n\n/** 페이지 텍스트에서 품질 메트릭을 계산한다. vectorGlyphs 는 텍스트층 밖 곡선 글자 수 (vector-glyphs.ts) */\nexport function computePageQuality(page: number, text: string, vectorGlyphs = 0): PageQuality {\n let total = 0\n let hangul = 0\n let hangulNoBatchim = 0\n let hangulRareBatchim = 0\n let control = 0\n let replacement = 0\n let pua = 0\n\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i)\n // 공백류는 분모에서 제외\n if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) continue\n total++\n\n // C0 제어문자 (0x00-0x1F 중 tab/lf/cr 제외) + DEL(0x7F) + C1(0x80-0x9F)\n if ((code < 0x20) || code === 0x7f || (code >= 0x80 && code <= 0x9f)) {\n control++\n continue\n }\n if (code === 0xfffd) {\n replacement++\n continue\n }\n // 한글 음절 — 종성(받침) 분포로 mojibake 판정\n if (code >= 0xac00 && code <= 0xd7a3) {\n hangul++\n const jong = (code - 0xac00) % 28\n if (jong === 0) hangulNoBatchim++\n else if (RARE_JONG.has(jong)) hangulRareBatchim++\n continue\n }\n // PUA: BMP(E000-F8FF) + SPUA-A(F0000-FFFFD) + SPUA-B(100000-10FFFD)\n // 서로게이트 페어는 high만 검사해도 충분 (Plane 15/16 high surrogate 범위 DB80-DBFF)\n if ((code >= 0xe000 && code <= 0xf8ff) || (code >= 0xdb80 && code <= 0xdbff)) {\n pua++\n continue\n }\n }\n\n const denom = total || 1\n const puaRatio = pua / denom\n const controlCharRatio = control / denom\n const replacementCharRatio = replacement / denom\n const hangulNoBatchimRatio = hangul > 0 ? hangulNoBatchim / hangul : 0\n const hangulRareBatchimRatio = hangul > 0 ? hangulRareBatchim / hangul : 0\n\n // 받침 분포 이상 = ToUnicode 오매핑 mojibake. 받침없음 희소 + 희귀받침 과다를\n // 둘 다(AND) 만족해야 발화 — 자연 한국어(받침없음 다수)에서 오탐 방지.\n const garbledHangul =\n hangul >= MOJIBAKE_MIN_HANGUL &&\n hangulNoBatchimRatio < MOJIBAKE_MAX_NOBATCHIM &&\n hangulRareBatchimRatio >= MOJIBAKE_MIN_RAREBATCHIM\n\n let needsOcr = false\n let ocrReason: PageQuality[\"ocrReason\"] | undefined\n // 우선순위: vector_text > low_text > high_pua > high_control > high_replacement > garbled_hangul.\n // vector_text 가 low_text 보다 앞선다 — 텍스트층이 비어도 빈 쪽(표지·간지)이 아니라 글이 곡선으로 가득한 쪽이다\n if (vectorGlyphs >= VECTOR_TEXT_MIN_GLYPHS && vectorGlyphs >= (vectorGlyphs + total) * VECTOR_TEXT_MIN_SHARE) {\n needsOcr = true; ocrReason = \"vector_text\"\n }\n else if (total < LOW_TEXT_THRESHOLD) { needsOcr = true; ocrReason = \"low_text\" }\n else if (puaRatio >= HIGH_PUA_THRESHOLD) { needsOcr = true; ocrReason = \"high_pua\" }\n else if (controlCharRatio >= HIGH_CONTROL_THRESHOLD) { needsOcr = true; ocrReason = \"high_control\" }\n else if (replacementCharRatio >= HIGH_REPLACEMENT_THRESHOLD) { needsOcr = true; ocrReason = \"high_replacement\" }\n else if (garbledHangul) { needsOcr = true; ocrReason = \"garbled_hangul\" }\n\n return {\n page,\n textChars: total,\n hangulRatio: hangul / denom,\n controlCharRatio,\n replacementCharRatio,\n puaRatio,\n hangulNoBatchimRatio,\n hangulRareBatchimRatio,\n needsOcr,\n ocrReason,\n }\n}\n\n/** 페이지별 품질에서 문서 단위 요약을 계산한다. */\nexport interface DocumentQualitySummary {\n totalPages: number\n totalTextChars: number\n avgHangulRatio: number\n avgControlCharRatio: number\n avgReplacementCharRatio: number\n avgPuaRatio: number\n /** textChars 매우 적은 페이지 수 (이미지/빈 페이지 후보) */\n lowTextPageCount: number\n /** PUA 비율 임계 초과 페이지 수 (글꼴 매핑 깨짐 후보) */\n highPuaPageCount: number\n /** 문서 전체에 OCR 검토가 권장되는지 — needsOcr 페이지 비율 또는 평균 신호 기반 */\n needsOcr: boolean\n /** needsOcr=true인 페이지 번호 목록 */\n ocrCandidatePages: number[]\n}\n\nconst LOW_TEXT_THRESHOLD = 20\nconst HIGH_PUA_THRESHOLD = 0.2\nconst HIGH_CONTROL_THRESHOLD = 0.05\nconst HIGH_REPLACEMENT_THRESHOLD = 0.05\n/** 페이지 중 needsOcr 비율이 이 값 이상이면 문서 전체에 OCR 권장 */\nconst DOC_NEEDS_OCR_PAGE_RATIO = 0.3\n\n/**\n * 비표시 제어문자를 제거한다. C0(NUL 등, tab/lf/cr 제외) + DEL + C1.\n * PUA는 사용자가 글꼴 매핑 실패를 시각적으로 확인할 수 있도록 보존.\n */\nexport function stripControlChars(text: string): string {\n // eslint-disable-next-line no-control-regex\n return text.replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F\\x80-\\x9F]/g, \"\")\n}\n\nexport function summarizeDocumentQuality(pages: PageQuality[]): DocumentQualitySummary {\n if (pages.length === 0) {\n return {\n totalPages: 0,\n totalTextChars: 0,\n avgHangulRatio: 0,\n avgControlCharRatio: 0,\n avgReplacementCharRatio: 0,\n avgPuaRatio: 0,\n lowTextPageCount: 0,\n highPuaPageCount: 0,\n needsOcr: false,\n ocrCandidatePages: [],\n }\n }\n\n let textChars = 0\n let hangul = 0\n let control = 0\n let replacement = 0\n let pua = 0\n let lowText = 0\n let highPua = 0\n const ocrCandidatePages: number[] = []\n\n for (const p of pages) {\n textChars += p.textChars\n hangul += p.hangulRatio\n control += p.controlCharRatio\n replacement += p.replacementCharRatio\n pua += p.puaRatio\n if (p.textChars < LOW_TEXT_THRESHOLD) lowText++\n if (p.puaRatio >= HIGH_PUA_THRESHOLD) highPua++\n // OCR 이 이미 적용된 페이지는 후보에서 제외 — \"OCR 했는데 또 하라\" 자기모순 방지\n if (p.needsOcr && !p.ocrApplied) ocrCandidatePages.push(p.page)\n }\n\n const n = pages.length\n return {\n totalPages: n,\n totalTextChars: textChars,\n avgHangulRatio: hangul / n,\n avgControlCharRatio: control / n,\n avgReplacementCharRatio: replacement / n,\n avgPuaRatio: pua / n,\n lowTextPageCount: lowText,\n highPuaPageCount: highPua,\n needsOcr: ocrCandidatePages.length / n >= DOC_NEEDS_OCR_PAGE_RATIO,\n ocrCandidatePages,\n }\n}\n","/**\n * 벡터 글자 감지 — 글자를 글꼴이 아니라 채운 곡선 경로로 그린 쪽 (needsOcr 사유 vector_text).\n *\n * rhwp 자체 렌더러(Producer \"cairo 1.18.0\")는 DejaVu 글꼴에 없는 한글을 자모를 조합한 채움 경로로 그린다.\n * 텍스트층에는 ASCII·기호만 남고(한글 0자) 눈으로는 한글이 읽힌다 — rhwp/hwpx_sample2 1쪽 연산자는\n * constructPath 975·fill 857 vs showText 66. \"글꼴을 곡선으로\" 인쇄한 PDF 도 같은 모양이다. 텍스트층만 보는\n * 품질 신호(quality.ts)는 이런 쪽을 못 알아봐(ASCII 가 조금 있어 low_text 도 아님) 한글 없는 마크다운을 경고 없이 냈다.\n *\n * 판정은 연산자 목록 기하로만 한다:\n * ① 글자 모양 채움 경로 — 높이 3~40pt·폭 ≤ 높이×2·선분 8개+·닫힌 윤곽 2개+(한글 음절은 초성·중성이 떨어진\n * 윤곽이라 2개 이상이다. 원·사각·삼각·화살표 같은 도형 조각과 라틴 한 획 글자는 여기서 빠진다), 가운데가\n * 텍스트층 글 상자 밖(투명 텍스트층을 얹은 윤곽 PDF 는 글이 있는 쪽이다).\n * ② 글줄 조각(run) — 세로 가운데가 반 높이 안인 경로를 한 줄로, 줄 안에서 x 틈 1.5em 이내를 한 조각으로.\n * ③ 글줄다움 — 3개+·윗선이나 밑선이 가지런함(중앙 편차 ≤ 0.05em)·가로 겹침 드묾(≤ 20%)·한 색(≥ 90%)·\n * 모양이 제각각(서로 다른 모양 ≥ 50%).\n * 코퍼스 PDF 1,561개(16,775쪽) 실측 — 비-cairo 15,875쪽 가운데 ①의 경로가 있는 쪽 275, ② 조각이 있는 쪽 71,\n * ③을 넘는 글자가 있는 쪽 39. 그 가운데 가장 많은 쪽이 68개(광진소식 지면: 텍스트층 0자, 본문 전부 곡선 — 진짜\n * 벡터 글자)이고 다음이 차트 라벨 62(보도자료 안내서, 텍스트층 372자)·표지 로고 34·30 이다. ③이 떨어낸 것:\n * 표지·간지 원형 아이콘 무리(ice-arc: 정렬 편차 0.1~0.3em·겹침 0.2~0.75), 서식 점선 칸 줄(synam-001: 모양 1종\n * 반복), 클립아트 세부(hwp3-sample11 네트워크 그림: 겹침 ~1.0·색 제각각). ③의 조건을 하나씩 빼 봐도 오탐은 없지만\n * (여유가 줄 뿐), ③을 통째로 빼면 비-cairo 17쪽이 판정을 넘는다 — 텍스트층 0자 광진소식 5쪽 말고도 삽화·아이콘 쪽 12\n * (cbe-record-guide 표지·간지 6, ice-arc-2026 간지 6). 쪽 판정(글자 수·비율)은 quality.ts 가 한다.\n */\n\nimport { OPS } from \"pdfjs-dist/legacy/build/pdf.mjs\"\n\n/** 글자 모양 채움 경로 (쪽 좌표, CTM 적용) */\nexport interface GlyphPath {\n x1: number; y1: number; x2: number; y2: number\n /** 선분(직선·곡선) 수 */\n segs: number\n /** 닫힌 윤곽(부분 경로) 수 */\n subs: number\n /** 채움 색 (0xRRGGBB) */\n color: number\n /** 이 경로를 만든 연산자 번호 (constructPath 들 + 채움) — OCR 경로가 괘선만 남길 때 뺀다 */\n ops: number[]\n}\n\nconst GLYPH_MIN_H = 3\nconst GLYPH_MAX_H = 40\nconst GLYPH_MAX_ASPECT = 2\nconst GLYPH_MIN_SEGS = 8\nconst GLYPH_MIN_SUBS = 2\n/** 줄 안 이웃 경로 x 틈 상한 (em) — 낱말 사이 띄어쓰기·양쪽 정렬 벌어짐까지 한 조각 */\nconst RUN_GAP_EM = 1.5\nconst RUN_MIN = 3\n/** 윗선·밑선 중앙 편차 상한 (em) — cairo 글줄 0.00~0.07, 아이콘 무리 0.1~0.3 */\nconst RUN_ALIGN_EM = 0.05\n/** 앞 경로 끝보다 0.1em 넘게 앞에서 시작하는 이웃 비율 상한 — 글자는 차례로 놓인다 */\nconst RUN_MAX_OVERLAP = 0.2\nconst RUN_MIN_COLOR = 0.9\nconst RUN_MIN_SHAPES = 0.5\n\ntype Mat = number[]\nconst mul = (m: Mat, t: Mat): Mat => [\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/**\n * 글자 모양 채움 경로 수집. pdfjs v4 constructPath 인자 [subOps, coords, minMax] 의 minMax 는 곡선 끝점을\n * 빼먹으므로 좌표를 직접 훑는다. 사각형(re)만으로 된 채움(칸 음영·막대·체크박스)은 글자가 아니다.\n */\nexport function collectGlyphPaths(fnArray: Uint32Array | number[], argsArray: unknown[][]): GlyphPath[] {\n const out: GlyphPath[] = []\n let ctm: Mat = [1, 0, 0, 1, 0, 0]\n let color = 0\n const stack: Array<[Mat, number]> = []\n let x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity\n let segs = 0, subs = 0, rects = 0\n let ops: number[] = []\n const add = (x: number, y: number) => {\n const tx = ctm[0] * x + ctm[2] * y + ctm[4], ty = ctm[1] * x + ctm[3] * y + ctm[5]\n if (tx < x1) x1 = tx\n if (tx > x2) x2 = tx\n if (ty < y1) y1 = ty\n if (ty > y2) y2 = ty\n }\n const reset = () => { x1 = Infinity; y1 = Infinity; x2 = -Infinity; y2 = -Infinity; segs = 0; subs = 0; rects = 0; ops = [] }\n for (let i = 0; i < fnArray.length; i++) {\n const op = fnArray[i]\n const args = argsArray[i]\n switch (op) {\n case OPS.save: stack.push([ctm, color]); break\n case OPS.restore: [ctm, color] = stack.pop() ?? [[1, 0, 0, 1, 0, 0], 0]; break\n case OPS.transform: ctm = mul(ctm, args as number[]); break\n case OPS.paintFormXObjectBegin: {\n stack.push([ctm, color])\n const m = (args as unknown[])[0]\n if (Array.isArray(m) && m.length >= 6) ctm = mul(ctm, m as number[])\n break\n }\n case OPS.paintFormXObjectEnd: [ctm, color] = stack.pop() ?? [[1, 0, 0, 1, 0, 0], 0]; break\n case OPS.setFillRGBColor: {\n const c = args as unknown as ArrayLike<number>\n color = (c[0] << 16) | (c[1] << 8) | c[2]\n break\n }\n case OPS.constructPath: {\n const [subOps, coords] = args as [number[], number[]]\n if (!Array.isArray(subOps) || !Array.isArray(coords)) break\n ops.push(i)\n let ci = 0\n for (const s of subOps) {\n if (s === OPS.moveTo) { add(coords[ci], coords[ci + 1]); ci += 2; subs++ }\n else if (s === OPS.lineTo) { add(coords[ci], coords[ci + 1]); ci += 2; segs++ }\n else if (s === OPS.curveTo) { add(coords[ci], coords[ci + 1]); add(coords[ci + 2], coords[ci + 3]); add(coords[ci + 4], coords[ci + 5]); ci += 6; segs++ }\n else if (s === OPS.curveTo2 || s === OPS.curveTo3) { add(coords[ci], coords[ci + 1]); add(coords[ci + 2], coords[ci + 3]); ci += 4; segs++ }\n else if (s === OPS.rectangle) {\n const rx = coords[ci], ry = coords[ci + 1], rw = coords[ci + 2], rh = coords[ci + 3]\n add(rx, ry); add(rx + rw, ry + rh); add(rx, ry + rh); add(rx + rw, ry)\n ci += 4; rects++; subs++; segs += 4\n }\n }\n break\n }\n case OPS.fill: case OPS.eoFill: case OPS.fillStroke: case OPS.eoFillStroke:\n case OPS.closeFillStroke: case OPS.closeEOFillStroke: {\n const w = x2 - x1, h = y2 - y1\n if (segs > rects * 4 && h >= GLYPH_MIN_H && h <= GLYPH_MAX_H && w <= h * GLYPH_MAX_ASPECT &&\n segs >= GLYPH_MIN_SEGS && subs >= GLYPH_MIN_SUBS) {\n out.push({ x1, y1, x2, y2, segs, subs, color, ops: [...ops, i] })\n }\n reset()\n break\n }\n case OPS.stroke: case OPS.closeStroke: case OPS.endPath:\n reset()\n break\n }\n }\n return out\n}\n\n/** 텍스트층 글 상자 (NormItem 과 같은 모양 — y 는 베이스라인) */\nexport interface TextBox { text: string; x: number; y: number; w: number; h: number }\n\n/** 가운데가 텍스트층 글 상자 안 — 글 위에 겹쳐 그린 윤곽(투명 텍스트층을 얹은 윤곽 PDF, 글자 테두리 효과) */\nfunction coveredByText(b: GlyphPath, texts: TextBox[]): boolean {\n const cx = (b.x1 + b.x2) / 2, cy = (b.y1 + b.y2) / 2\n for (const t of texts) {\n if (cx >= t.x - 1 && cx <= t.x + t.w + 1 && cy >= t.y - t.h * 0.3 - 1 && cy <= t.y + t.h + 1 && t.text.trim()) return true\n }\n return false\n}\n\nconst median = (a: number[]): number => { const s = a.slice().sort((x, y) => x - y); return s[s.length >> 1] }\nconst spread = (a: number[]): number => { const c = median(a); return median(a.map(v => Math.abs(v - c))) }\n\n/** 글줄다움 — 머리 주석 ③ (m 은 x 순) */\nfunction isTextRun(m: GlyphPath[], em: number): boolean {\n if (m.length < RUN_MIN) return false\n if (Math.min(spread(m.map(b => b.y2)), spread(m.map(b => b.y1))) > em * RUN_ALIGN_EM) return false\n let overlaps = 0\n for (let i = 1; i < m.length; i++) if (m[i].x1 < m[i - 1].x2 - em * 0.1) overlaps++\n if (overlaps > (m.length - 1) * RUN_MAX_OVERLAP) return false\n const colors = new Map<number, number>()\n for (const b of m) colors.set(b.color, (colors.get(b.color) ?? 0) + 1)\n if (Math.max(...colors.values()) < m.length * RUN_MIN_COLOR) return false\n const shapes = new Set(m.map(b => `${Math.round((b.x2 - b.x1) * 2)}:${Math.round((b.y2 - b.y1) * 2)}:${b.segs}`))\n return shapes.size >= m.length * RUN_MIN_SHAPES\n}\n\nexport interface VectorGlyphScan {\n /** 글줄다운 조각에 든 글자 모양 경로 수 — 텍스트층에 없는 곡선 글자 수 추정 */\n glyphs: number\n /** 글자 모양 채움 경로 전부 (텍스트층이 덮은 것 포함) */\n paths: GlyphPath[]\n}\n\n/** 쪽의 벡터 글자 신호. texts 는 그 쪽 텍스트층 글 상자 */\nexport function scanVectorGlyphs(fnArray: Uint32Array | number[], argsArray: unknown[][], texts: TextBox[]): VectorGlyphScan {\n const paths = collectGlyphPaths(fnArray, argsArray)\n if (paths.length < RUN_MIN) return { glyphs: 0, paths }\n const free = paths.filter(b => !coveredByText(b, texts))\n // 줄 묶기: 세로 가운데 순으로 훑어 첫 경로 가운데에서 반 높이 안이면 같은 줄\n const sorted = free.slice().sort((a, b) => (a.y1 + a.y2) - (b.y1 + b.y2))\n let glyphs = 0\n let row: GlyphPath[] = []\n let rowCy = 0, rowH = 0\n const flushRow = () => {\n if (row.length < RUN_MIN) return\n row.sort((a, b) => a.x1 - b.x1)\n const em = median(row.map(b => b.y2 - b.y1))\n let start = 0, maxX = row[0].x2\n for (let i = 1; i <= row.length; i++) {\n if (i < row.length && row[i].x1 - maxX <= em * RUN_GAP_EM) { if (row[i].x2 > maxX) maxX = row[i].x2; continue }\n const run = row.slice(start, i)\n if (isTextRun(run, em)) glyphs += run.length\n if (i < row.length) { start = i; maxX = row[i].x2 }\n }\n }\n for (const b of sorted) {\n const cy = (b.y1 + b.y2) / 2, h = b.y2 - b.y1\n if (row.length && Math.abs(cy - rowCy) <= Math.max(h, rowH) * 0.5) { row.push(b); continue }\n flushRow()\n row = [b]; rowCy = cy; rowH = h\n }\n flushRow()\n return { glyphs, paths }\n}\n\n/**\n * OCR 로 가는 벡터 글자 쪽의 그래픽 — 글자 모양 경로와 클립을 뺀 연산자 목록(괘선·채움·그림). 그 쪽 표 구조를 래스터\n * 괘선 감지 대신 실제 괘선으로 복원한다(pdf-ocr.ts).\n *\n * 클립을 빼는 까닭: 클립 칸 격자(clip-cells.ts)는 한컴 PDF 내보내기의 \"칸마다 W n 클립\" 관행에 기댄 경로인데, 글자를 곡선으로\n * 그린 쪽은 코퍼스에서 전부 비한컴 제작기(rhwp cairo)이고 한컴 PDF 1,480개(10,976쪽)에는 한 쪽도 없다. cairo 는 음영 칸과\n * 그림에만 클립을 깔아(hwpx_sample2 2쪽: 클립 17 = 같은 기하 채움 17) 부분 클립 격자가 선 격자와 한 표를 둘로 쪼갠다 — kftc\n * 붙임 표는 머리 행이 따로 1×5 표, 본문 행은 3×6 에 섞였다. 텍스트층 없음 13쌍 46표 OCR 실측: 클립 포함 exact 16·F1 0.510·\n * NED 0.310, 클립 뺌 exact 20·F1 0.587·NED 0.445 (종전 래스터 괘선 14·0.502·0.295)\n */\nexport function ocrVectorOps(\n opList: { fnArray: Uint32Array | number[]; argsArray: unknown[][] },\n paths: GlyphPath[],\n): { fnArray: number[]; argsArray: unknown[][] } {\n const drop = new Set<number>()\n for (const p of paths) for (const i of p.ops) drop.add(i)\n const fnArray: number[] = []\n const argsArray: unknown[][] = []\n for (let i = 0; i < opList.fnArray.length; i++) {\n const op = opList.fnArray[i]\n if (drop.has(i) || op === OPS.clip || op === OPS.eoClip) continue\n fnArray.push(op)\n argsArray.push(opList.argsArray[i])\n }\n return { fnArray, argsArray }\n}\n","/**\n * 쪽 넘김으로 갈라진 표 잇기 (mergeCrossPageTables) — 클립 표 조각은 열 경계 합집합 격자에 두 조각을 다시 놓는다.\n *\n * 한컴 PDF 는 쪽마다 그 쪽에 그려진 칸만 클립으로 깐다. 긴 서식 표(규제영향분석서 24×13)가 쪽을 넘으면 앞 조각은\n * 앞쪽 행들이 쓰는 열 경계만(13×9), 뒤 조각은 뒤쪽 행들이 쓰는 열 경계만(11×6) 갖고, 앞 쪽에서 세로 병합으로\n * 넘어온 칸(\"기타\"·\"12.규제일몰제\")은 글이 없어 뒤 쪽에 클립조차 없다. 열 수가 달라 종전 병합(열 수 동일)은\n * 두 조각을 따로 냈다. 여기서는\n * 1) 두 조각의 열 경계 x 를 합쳐 공통 격자를 만들고 각 칸을 x 로 다시 놓은 뒤,\n * 2) 뒤 조각 첫 행부터 칸이 비어 있는 열은 앞 조각 마지막 행에서 그 열을 덮던 칸의 세로 병합을 이어 늘린다.\n * 원본(HWPX) 표 격자는 모든 행의 경계 합집합이라 이 재배치가 같은 격자를 되살린다.\n */\n\nimport type { IRBlock, IRCell, IRTable } from \"../types.js\"\nimport { CELL_LINES, CLIP_TABLES, EMPTY_PARTS, FILLER_CELLS, IMAGE_CELLS, TABLE_COLXS } from \"./table-meta.js\"\n\n/** 두 조각의 경계를 같은 것으로 보는 거리 (pt) — 클립 좌표 오차 0.05pt, 조각 간 반올림 여유 */\nconst PART_COL_TOL = 1\n\ninterface Anchor { r: number; c: number; rs: number; cs: number; cell: IRCell }\n\n/** IR 격자 → 앵커 목록 (병합으로 덮인 칸 제외, 행 우선) */\nfunction anchorsOf(table: IRTable): Anchor[] {\n const out: Anchor[] = []\n const covered = new Set<number>()\n for (let r = 0; r < table.rows; r++) {\n for (let c = 0; c < table.cols; c++) {\n if (covered.has(r * 100000 + c)) continue\n const cell = table.cells[r]?.[c]\n if (!cell) continue\n out.push({ r, c, rs: cell.rowSpan, cs: cell.colSpan, cell })\n for (let dr = 0; dr < cell.rowSpan; dr++) {\n for (let dc = 0; dc < cell.colSpan; dc++) if (dr || dc) covered.add((r + dr) * 100000 + (c + dc))\n }\n }\n }\n return out\n}\n\n/** 오름차순 좌표 합집합 — 앞 조각 경계는 그대로 두고(조각 안 경계는 클립 격자가 0.3pt 로 이미 가름), 뒤 조각\n * 경계 가운데 앞 조각 경계와 PART_COL_TOL 안에 겹치는 것만 같은 경계로 본다 */\nfunction unionCoords(a: number[], b: number[]): number[] {\n const out = [...a]\n for (const v of b) if (!a.some(x => Math.abs(x - v) <= PART_COL_TOL)) out.push(v)\n return out.sort((x, y) => x - y)\n}\n\nconst indexOf = (coords: number[], x: number): number => {\n let best = -1, bestD = Infinity\n for (let i = 0; i < coords.length; i++) {\n const d = Math.abs(coords[i] - x)\n if (d < bestD) { bestD = d; best = i }\n }\n return bestD <= PART_COL_TOL ? best : -1\n}\n\nconst rowText = (anchors: Anchor[], r: number): string =>\n anchors.filter(a => a.r === r).map(a => a.cell.text.replace(/\\s+/g, \"\")).join(\"|\")\n/** 행 글(rowText)에 칸 구분자 말고 글이 있나 */\nconst hasText = (t: string): boolean => t.replace(/\\|/g, \"\") !== \"\"\n\n/**\n * 두 조각을 합집합 격자로 잇는다. 경계 대응이 안 되면(조각 경계가 합집합에서 사라짐) null.\n * @param pcx·ccx 앞·뒤 조각의 열 경계 x (오름차순, 뒤 조각은 앞 조각 자리로 옮긴 좌표)\n * @param dx 뒤 조각을 옮긴 거리 — 뒤 조각 칸 글줄도 같이 옮겨 앞 조각 글줄과 맞댄다 (짝·홀 쪽 대칭 여백)\n * @param prevBottom 앞 조각 밑변 y — 쪽 경계에 걸친 세로 병합 칸 글을 가린다 (mergeStraddlingCells)\n * @returns split — 경계 행을 쪼개진 행으로 보고 합쳤는지\n */\nexport function joinSplitParts(prev: IRTable, pcx: number[], curr: IRTable, ccx: number[], dx = 0, prevBottom?: number): { table: IRTable; colXs: number[]; split: boolean; header: boolean } | null {\n if (pcx.length !== prev.cols + 1 || ccx.length !== curr.cols + 1) return null\n const U = unionCoords(pcx, ccx)\n const cols = U.length - 1\n if (cols < 1) return null\n const pa = anchorsOf(prev), ca = anchorsOf(curr)\n\n // 반복 머리 행 — 한컴은 머리 칸이 있는 표만 조각마다 머리 행(여러 줄일 수 있음)을 되풀이한다. 표 첫 조각은 머리 행 위에\n // 제목 상자가 붙어 나올 수 있어(편람 [별표 4] \"[별표4] <개정…>\" 행) 앞 조각 두 행 아래까지 어긋나게 맞춰 본다 (어긋난 맞춤은 글 있는 행만)\n let skip = 0\n while (skip < Math.min(3, curr.rows - 1, prev.rows) && rowText(ca, skip) !== \"\" && rowText(ca, skip) === rowText(pa, skip)) skip++\n for (let h = 1; h <= 2 && !skip; h++) {\n while (skip < Math.min(3, curr.rows - 1, prev.rows - h) && hasText(rowText(ca, skip)) && rowText(ca, skip) === rowText(pa, h + skip)) skip++\n }\n\n const rows = prev.rows + curr.rows - skip\n const grid: IRCell[][] = Array.from({ length: rows }, () => Array.from({ length: cols }, () => ({ text: \"\", colSpan: 1, rowSpan: 1 })))\n const owner: (Anchor | null)[][] = Array.from({ length: rows }, () => new Array<Anchor | null>(cols).fill(null))\n const place = (a: Anchor, x: number[], rowOff: number, shift = 0): boolean => {\n const c1 = indexOf(U, x[a.c]), c2 = indexOf(U, x[a.c + a.cs])\n if (c1 < 0 || c2 <= c1) return false\n const r = a.r + rowOff\n const placed: Anchor = { r, c: c1, rs: a.rs, cs: c2 - c1, cell: a.cell }\n for (let dr = 0; dr < a.rs; dr++) for (let dc = c1; dc < c2; dc++) {\n if (r + dr < rows) owner[r + dr][dc] = placed\n }\n grid[r][c1] = { ...a.cell, colSpan: c2 - c1, rowSpan: a.rs }\n // 곁정보는 복사한 칸으로 옮긴다 — 세 쪽 넘게 이어질 때 다음 이음(글줄)과 후행 빈 열 정리(그림 칸)가 본다\n const lines = CELL_LINES.get(a.cell)\n if (lines) CELL_LINES.set(grid[r][c1], shift ? lines.map(l => ({ ...l, l: l.l + shift, r: l.r + shift })) : lines)\n if (IMAGE_CELLS.has(a.cell)) IMAGE_CELLS.add(grid[r][c1])\n return true\n }\n // 채움 칸(클립 없던 자리)은 놓지 않는다 — 빈 자리로 남아 아래 세로 병합 잇기가 채운다\n for (const a of pa) if (!FILLER_CELLS.has(a.cell) && !place(a, pcx, 0)) return null\n for (const a of ca) {\n if (a.r < skip || FILLER_CELLS.has(a.cell)) continue\n if (!place(a, ccx, prev.rows - skip, dx)) return null\n }\n\n // 뒤 조각 첫 행부터 비어 있는 열 — 앞 조각 마지막 행에서 그 열을 덮던 칸의 세로 병합을 잇는다\n const first = prev.rows\n for (let c = 0; c < cols; c++) {\n const above = owner[first - 1]?.[c]\n if (!above) continue\n let r = first\n while (r < rows && owner[r][c] === null) r++\n if (r === first) continue\n // 이어 늘릴 칸은 그 열 폭을 전부 덮는 칸이어야 한다 — 자기 폭 안의 모든 열이 같은 구간만큼 비어 있을 때만\n const span = r - first\n let ok = true\n for (let dc = above.c; dc < above.c + above.cs && ok; dc++) {\n for (let rr = first; rr < first + span; rr++) if (owner[rr][dc] !== null) { ok = false; break }\n }\n if (!ok) continue\n const newRs = above.rs + span\n grid[above.r][above.c].rowSpan = newRs\n const grown: Anchor = { ...above, rs: newRs }\n for (let rr = above.r; rr < above.r + newRs; rr++) for (let dc = above.c; dc < above.c + above.cs; dc++) owner[rr][dc] = grown\n }\n\n // 아무 칸도 덮지 않은 자리는 채움 칸으로 남긴다 — 이 표가 다시 앞 쪽 조각과 이어질 때(세 쪽 넘게) 빈 자리로 보고 세로 병합을 잇게\n for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) if (owner[r][c] === null) FILLER_CELLS.add(grid[r][c])\n const table: IRTable = { rows, cols, cells: grid, hasHeader: prev.hasHeader, ...(prev.caption ? { caption: prev.caption } : {}) }\n const split = rows > prev.rows && mergeSplitRow(table, owner, prev.rows, U)\n if (rows > prev.rows && !split && prevBottom !== undefined) mergeStraddlingCells(table, owner, prev.rows, prevBottom)\n // 머리 행 증거는 되풀이된 행에 글 있는 칸이 둘 이상일 때만 — 한 칸짜리 제목 행은 같은 제목의 상자를 쪽마다 새로 놓은 것과 구별이\n // 안 된다 (편람 기안문 \"작성방법\" 상자가 짝·홀 쪽에 하나씩 놓여 한 표로 이어졌다, HWPX 는 두 표)\n return { table, colXs: U, split, header: ca.filter(a => a.r < skip && a.cell.text.trim()).length >= 2 }\n}\n\n/**\n * 쪽 경계에 걸친 세로 병합 칸 — 행은 새로 시작해도(쪼개진 행 아님) 앞 쪽에서 두 행 이상 덮은 칸의 짧은 글(가운데 정렬)이 앞 조각\n * 바닥에 붙어 있으면 그 글은 쪽 경계에 걸쳐 다음 쪽 첫 행 칸으로 이어진다 (시험기준표 분류 칸 \"건축구조용 표면처리 / 경량형강\n * (KS D 3854)\"·\"석고보드 / (GB-R)\"). 두 칸을 한 세로 병합 칸으로 잇는다. 쪽 넘김 후보를 HWPX 로 대조한 세 세트(시험기준표·법령 별표\n * 2세트)에서 이 모양 11건이 모두 같은 칸 — 새 분류 칸은 글이 칸 가운데라 앞 조각 바닥에서 떨어져 있고, 긴 글 칸은\n * 줄 수로 거른다(위→아래로 차 바닥에 닿아도 다음 쪽이 새 칸일 수 있다: 복제 규칙 \"라. 항공경찰관 점퍼 …\" 12줄 → \"마. …\").\n * 쪽 경계가 행 묶음 안을 지날 때만 본다 — 다른 열 칸이 경계를 넘어 이어져야 한다(뒤 조각 첫 행 자리에 클립 없음, 11건 모두).\n * 모든 칸이 경계에서 끝나면 다음 쪽 첫 행은 새 묶음이다 (연락처 표 되풀이된 \"<공동>\" 5건, 과제 목록 두 행 칸 \"4-29\" 한 줄이\n * 바닥 가까이 있어도 다음 쪽 \"4-30\" 은 새 과제)\n */\nfunction mergeStraddlingCells(table: IRTable, owner: (Anchor | null)[][], first: number, prevBottom: number): void {\n const last = first - 1\n if (!owner[first].some((o, c) => o !== null && o === owner[last][c])) return\n for (let c = 0; c < table.cols;) {\n const u = owner[last][c], d = owner[first][c]\n if (!u) { c++; continue }\n c = u.c + u.cs\n if (!d || d === u || u.r >= last || u.r + u.rs - 1 !== last || d.r !== first || d.c !== u.c || d.cs !== u.cs) continue\n const a = table.cells[u.r][u.c], b = table.cells[first][d.c]\n const U = CELL_LINES.get(a)\n if (!U?.length || U.length > STRADDLE_MAX_LINES || !hasContent(b)) continue\n const lu = U[U.length - 1]\n if (lu.y - prevBottom > STRADDLE_BOTTOM * (lu.h || 10)) continue\n appendCell(a, b)\n a.rowSpan = first + d.rs - u.r\n table.cells[first][d.c] = { text: \"\", colSpan: 1, rowSpan: 1 }\n const grown: Anchor = { ...u, rs: a.rowSpan }\n for (let rr = u.r; rr < u.r + grown.rs; rr++) for (let dc = u.c; dc < u.c + u.cs; dc++) owner[rr][dc] = grown\n }\n}\n\n/** 끝줄이 칸 글 폭 오른끝에 이만큼(글자 크기 배) 안이면 꽉 찬 줄 */\nconst FULL_LINE_TOL = 1\n/** 끝줄 폭이 칸 폭의 이 비율 이상이어야 꽉 찬 줄 (좁은 칸의 짧은 낱말 제외) */\nconst FULL_LINE_MIN_FRAC = 0.5\n/** 앞 쪽 한 줄짜리 칸: 끝줄과 칸 오른끝 사이 최대 거리 — 셀 안쪽 여백(기본 1.8mm ≈ 5pt) + 여유(pt), 어절 하나 폭(글자 크기 배) */\nconst FULL_LINE_EDGE_MIN = 12\nconst SINGLE_LINE_WORD_GAP = 3.5\n/** 왼쪽 정렬 증거 — 왼끝에서 시작해 오른끝보다 글자 크기의 이 배수 넘게 짧은 줄 */\nconst SHORT_LINE_GAP = 1.5\n/** 쪽 경계에 걸친 세로 병합 칸 — 글 줄 수 상한, 끝줄 기준선이 앞 조각 밑변 위 글자 크기의 이 배수 안 */\nconst STRADDLE_MAX_LINES = 3\nconst STRADDLE_BOTTOM = 1.2\n/** 칸 조각 이어짐 증거로 보는 빈 자리 열의 최소 폭 (pt) — 클립 격자의 셀 최소 폭과 같다 */\nconst CARRIED_MIN_COL_W = 4\n/** 끝난 이름표 — 한 줄 칸 오른쪽에 남은 자리가 글자 크기의 이 배수 이상 (숫자 머리 \"4.\" 한 어절이 들어간다) */\nconst LABEL_ROOM = 1.5\n/** 내어쓰기 이어짐 — 앞 끝줄이 칸 글 왼끝보다 글자 크기의 이 배수 이상 들어가 있고, 뒤 첫 줄 왼끝이 이 거리(pt) 안에서 같다 */\nconst HANGING_MIN = 0.5\nconst HANGING_TOL = 1\n\n/**\n * 앞 쪽 칸(u)의 글이 뒤 쪽 칸(d)으로 이어지는지 — 앞 쪽 끝줄이 칸 글 폭 오른끝까지 차 있으면 문단 중간에서 끊긴 것이다\n * (줄은 다음 어절이 안 들어갈 때만 바뀐다). 가운데 정렬 칸은 끝줄이 가장 긴 줄일 뿐이어도 오른끝에 닿으므로, 왼끝에서\n * 시작해 오른끝에 못 미친 줄(문단 끝줄)이 두 조각 어딘가에 있는 칸만 본다.\n */\nfunction continuesAcross(u: IRCell, d: IRCell, x1: number, x2: number): boolean {\n const U = CELL_LINES.get(u), D = CELL_LINES.get(d)\n if (!U?.length || !D?.length) return false\n const last = U[U.length - 1]\n const fs = last.h || 10\n let minL = Infinity, maxR = -Infinity\n for (const l of U) { minL = Math.min(minL, l.l); maxR = Math.max(maxR, l.r) }\n for (const l of D) { minL = Math.min(minL, l.l); maxR = Math.max(maxR, l.r) }\n // 칸 글 오른끝 — 가장 긴 줄의 오른끝과 칸 오른끝(왼쪽 안쪽 여백을 오른쪽에도 둔) 가운데 먼 쪽. 줄이 모두 짧은 칸은 끝줄이 가장 긴\n // 줄이어도 꽉 찬 게 아니다 (편람 대비표 \"제44조의2 [이동 …]\" 두 줄이 다 334pt 에서 끝나는데 칸 글 오른끝은 371pt). 가운데 정렬 칸은\n // 가장 긴 줄의 좌우 여백이 같아 두 값이 같다\n maxR = Math.max(maxR, x2 - (minL - x1))\n if (maxR - last.r > FULL_LINE_TOL * fs) return false\n // 앞 쪽에 한 줄뿐이면 그 줄이 곧 가장 긴 줄이라 위 비교는 늘 참이다 — 그때는 칸 오른끝에서 어절 하나 폭 안까지 닿아야 한다\n // (오른쪽이 들쭉날쭉한 칸은 다음 어절이 안 들어가 그만큼 남기고 줄이 바뀐다: \"…규범적 / 성격의 규제\" 37pt, 13pt 글자)\n if (U.length === 1 && x2 - last.r > Math.max(FULL_LINE_EDGE_MIN, SINGLE_LINE_WORD_GAP * fs)) return false\n if (last.r - last.l < FULL_LINE_MIN_FRAC * (x2 - x1)) return false\n const leftAligned = (l: { l: number; r: number }) => l.l - minL <= fs && maxR - l.r > SHORT_LINE_GAP * fs\n // 양쪽 맞춤이라 모든 줄이 꽉 차 문단 끝줄이 없는 칸 — 앞 쪽 끝줄이 내어쓴 자리(문단 둘째 줄 이후)에서 시작하고 뒤 쪽 첫 줄이\n // 같은 자리에서 이어지면 같은 문단이다 (제약기업 인증 규정 별첨 대비표 \"1. 다음 각 목의 … 해 / 당하는 … 받지 않거 / 나, 그 행정처분을…\":\n // 93 → 104 → 104). 새 문단이면 뒤 첫 줄이 문단 머리 자리에서 시작한다 (물관리 제안요청서 \"‧다국적 …\" 다음 \"‧수질오염 …\")\n const hanging = U.length >= 2 && U.every(l => maxR - l.r <= FULL_LINE_TOL * fs)\n && last.l - minL >= HANGING_MIN * fs && Math.abs(D[0].l - last.l) <= HANGING_TOL\n return U.some(leftAligned) || D.some(leftAligned) || hanging\n}\n\n/** 한 줄로 끝난 왼쪽 정렬 칸 — 오른쪽에 남은 자리(왼쪽 안쪽 여백만큼 뺀)가 글자 크기의 LABEL_ROOM 배 이상이라 다음 어절이 들어갈 수 있었다.\n * 가운데 정렬 칸은 좌우 여백이 같아 해당하지 않는다 */\nfunction lineEnded(c: IRCell, x1: number, x2: number): boolean {\n const L = CELL_LINES.get(c)\n if (!L || L.length !== 1) return false\n const l = L[0]\n return (x2 - l.r) - (l.l - x1) >= LABEL_ROOM * (l.h || 10)\n}\n\n/**\n * 칸 조각 b 를 a 뒤에 잇는다. 어느 한쪽이 블록(칸 안 표를 품은 틀 칸의 문단·표)을 가지면 블록으로 잇는다 — 렌더는 블록을 먼저\n * 보므로 글만 이으면 사라진다 (코드 예시 틀 칸이 쪽을 넘을 때 다음 쪽 코드가 통째로 빠졌다). 블록 없는 쪽 글은 줄마다 문단으로\n */\nfunction appendCell(a: IRCell, b: IRCell): void {\n if (a.blocks?.length || b.blocks?.length) {\n const asBlocks = (c: IRCell): IRBlock[] => c.blocks?.length ? c.blocks\n : c.text.split(\"\\n\").map(l => l.trim()).filter(Boolean).map((text): IRBlock => ({ type: \"paragraph\", text }))\n a.blocks = [...asBlocks(a), ...asBlocks(b)]\n }\n if (b.text.trim()) a.text = a.text.trim() ? a.text + \"\\n\" + b.text : b.text\n}\n\n/** 칸에 내용이 있나 — 글·블록·그림 (그림은 PDF 에서 칸 글과 따로 뽑혀 text 가 빈다) */\nconst hasContent = (cell: IRCell): boolean => !!cell.text.trim() || !!cell.blocks?.length || IMAGE_CELLS.has(cell)\n\n/**\n * 쪽 넘김으로 쪼개진 행 — 뒤 조각 첫 행을 앞 조각 마지막 행에 합친다. 한컴은 칸 단위로 나누지 않는 표의 긴 행을 글줄\n * 사이에서 끊어 두 쪽에 나눠 그리고, 쪽 끝 행은 쪼개졌든 아니든 본문 바닥까지 늘려 그리므로 기하로는 구별이 안 된다.\n * 두 행의 칸 짜임(열 범위)이 같고, 다음 두 증거 가운데 하나가 있을 때만 합친다.\n * 1) 글 이어짐 — 어느 한 열에서 글이 앞 쪽 끝줄을 꽉 채우고 뒤 쪽으로 이어진다(continuesAcross).\n * 2) 칸 조각 이어짐 — 한컴은 칸의 첫 조각에는 글이 없어도 클립을 깐다(편람 대비표 빈 시행규칙 칸). 앞 쪽 끝 행에서 시작한\n * 칸(또는 앞 쪽에서도 클립이 없던 칸)이 다음 쪽에 클립이 없으면 그 칸은 다음 쪽 첫 행 띠까지 이어진 것이다. 글 있는 열 쌍이\n * 하나뿐이고 새 칸 증거(앞 쪽 빈 칸 뒤의 글)가 없으면 행이 쪼개진 것으로 본다. 칸 안 문단 경계에서 쪽이 넘어가 글 증거가 없는\n * 행(편람 대비표 \"…제1항 / ② 중앙행정기관…\")을 여기서 잡는다. 쪽 넘김 후보를 HWPX 로 같은 칸인지 확인한 대조: 표 GT 한컴\n * 407쌍에서 이 조건 42건이 전부 쪼개진 행, 따로 뗀 법령 서식·별표 4세트(rhwp 변환 HWPX)에서 121건 중 새 행 2건.\n * 글 있는 열 쌍이 둘 이상이면 세로 병합 칸만 이어지고 행은 새로 시작하는 경우(시험기준표 \"플라이애시 / 시멘트\")와\n * 섞여 쓰지 않는다. 위에서 내려온 세로 병합 칸이 클립 없이 넘어간 것은 새 행에서도 똑같아 증거가 아니다\n */\nfunction mergeSplitRow(table: IRTable, owner: (Anchor | null)[][], first: number, colXs: number[]): boolean {\n const last = first - 1\n // 열마다 앞 행 칸과 뒤 행 칸을 맞춘다 — 두 행을 다 덮는 세로 병합 칸(앞 쪽에서 넘어와 이어 늘린 칸)은 그대로 두고,\n // 나머지는 앞 행에서 끝나는 칸과 뒤 행에서 시작하는 칸의 열 범위가 같아야 한다\n const pairs: Array<[Anchor, Anchor]> = []\n let carried = false\n for (let c = 0; c < table.cols;) {\n const a = owner[last][c], b = owner[first][c]\n // 두 쪽 모두 클립이 없던 자리 — 글 없는 칸이 쪽을 넘은 부분이다 (행정업무운영 편람 신구조문 대비표: 시행규칙 쪽이 빈 조문 행이\n // 네 쪽에 걸쳐 끊겨 103행 → 119행). 칸이 들어설 수 없는 좁은 열은 조각끼리 왼끝이 조금 어긋나 합집합 격자에 생긴 틈이라 증거가 아니다\n // (서식 쪽마다 왼끝 56.6/58.0 → 1.4pt 틈 열: 바닥글 \"210mm×297mm…\" 행이 다음 쪽 \"(4쪽 중 제3쪽)\" 행을 삼켰다)\n if (!a && !b) { if (colXs[c + 1] - colXs[c] >= CARRIED_MIN_COL_W) carried = true; c++; continue }\n if (!a || !b) return false\n if (a !== b) {\n if (a.c !== b.c || a.cs !== b.cs || a.r + a.rs - 1 !== last || b.r !== first || b.rs !== 1) return false\n pairs.push([a, b])\n } else if (a.r === last && a.rs === 2) carried = true // 앞 쪽 끝 행에서 시작한 칸을 뒤 쪽 첫 행 빈 자리로만 이어 늘렸다 — 더 아래까지\n // 비어 있으면 뒤 쪽 여러 행을 덮는 세로 병합 칸이다(규제영향분석서 \"기타\"). 쪼개진 행의 이어진 조각은 첫 행 띠 하나다\n c = a.c + a.cs\n }\n // 뒤 쪽 칸 조각의 글 없는 클립은 증거가 아니다 — 한컴 PDF 판(1.3.0.546·538)과 칸에 따라 이어진 빈 조각에도 클립을 깐다\n // (법령 별표 쪼개진 행 17건에 있음). 앞 쪽 빈 칸 뒤에 글이 오는 것만 새 칸 증거다 (글은 칸 위에서부터 흐른다)\n const carriedSplit = carried && pairs.filter(([u, d]) => hasContent(u.cell) && hasContent(d.cell)).length <= 1\n && !pairs.some(([u, d]) => !hasContent(u.cell) && hasContent(d.cell))\n // 글줄은 격자에 놓은 복사본 칸에서 본다 — 옮겨 맞댄 뒤 조각(짝·홀 쪽·단 넘김)은 복사본에만 옮긴 글줄이 있다\n const cell = (a: Anchor): IRCell => table.cells[a.r][a.c]\n if (!carriedSplit) {\n // 앞 쪽 칸이 한 줄로 끝난 이름표(다음 어절이 들어갈 자리가 남음)인데 뒤 쪽 같은 열에 다른 글이 있으면 그 열은 새 칸이다 — 끝난 칸의\n // 이어진 조각은 글이 없어 클립조차 없다. 다른 열 끝줄이 꽉 찬 것(글 이어짐)보다 이 모순이 앞선다 (편람 [별표 4] 가로 판\n // \"8. 글자 | …꽉 찬 끝줄\" 다음 쪽 \"9. 한글과 함께 적는 외국글자 | 가. 단어를 …\", 시험기준표 \"액성한계·소성한계 | KS F 2303\" 다음 쪽\n // \"세립토 비율 | KS F 2309\"). 같은 글이면 문단마다 붙는 표지다 (신구조문 대비표 \"<신 설>\" 이 큰 행 두 조각에 하나씩)\n const norm = (c: IRCell): string => c.text.replace(/\\s+/g, \"\")\n if (pairs.some(([u, d]) => hasContent(d.cell) && norm(cell(d)) !== norm(cell(u)) && lineEnded(cell(u), colXs[u.c], colXs[u.c + u.cs]))) return false\n if (!pairs.some(([u, d]) => continuesAcross(cell(u), cell(d), colXs[u.c], colXs[u.c + u.cs]))) return false\n }\n for (const [u, d] of pairs) appendCell(table.cells[u.r][u.c], table.cells[d.r][d.c])\n // 위에서 내려와 두 행에 걸친 세로 병합 칸은 한 행 줄어든다\n for (let r = 0; r < first; r++) for (let c = 0; c < table.cols; c++) {\n const o = owner[r][c]\n if (o && o.r === r && o.c === c && r + table.cells[r][c].rowSpan > first) table.cells[r][c].rowSpan--\n }\n table.cells.splice(first, 1)\n table.rows--\n return true\n}\n\n/**\n * 페이지 걸친 표 병합 — ODL TableBorderProcessor.checkNeighborTables 포팅.\n * Original work: Copyright 2025-2026 Hancom Inc. (Apache-2.0)\n *\n * 페이지 N의 마지막 표와 페이지 N+1의 첫 표가:\n * - 블록 배열에서 인접 (사이에 본문 블록 없음 — 머리글/바닥글 제거 후 기준)\n * - 열 수 동일\n * - 좌우 경계 근접 (폭 대비 0.2 비율 이내, ODL NEIGHBOUR_TABLE_EPSILON)\n * 이면 한 표로 병합. 반복 헤더 행(첫 행 텍스트 동일)은 제거.\n * 두 조각 사이에 자기 쪽 표 조각과 가로로 겹치지 않는 블록(besideOwn)만 끼어 있으면 인접으로 본다 (그 블록은 제자리에 둔다).\n * 같은 쪽 2단 넘김은 먼저 mergeColumnFlow 가 잇는다.\n */\nconst NEIGHBOR_TABLE_EPSILON = 0.2\n\n/**\n * 표 두 조각 사이에 끼어도 이음을 막지 않는 블록 — 자기 쪽 표 조각과 가로 범위가 겹치지 않는 글(쪽 가장자리 장 표시 세로글 —\n * 행정업무운영 편람), 앞 표가 2단 지면의 오른쪽 단일 때 그 왼쪽 단에 놓인 글·표(입찰동향 붙임: 오른쪽 단 입찰내역표 앞에\n * 왼쪽 단 지역별 현황표)\n */\nfunction besideOwn(b: IRBlock, p: IRBlock, c: IRBlock): boolean {\n if (!b.bbox) return false\n if (b.type === \"table\") return leftColumnOf(b, p)\n const t = (b.pageNumber === p.pageNumber ? p : c).bbox!\n const bx1 = b.bbox.x, bx2 = b.bbox.x + b.bbox.width\n return bx2 <= t.x + 1 || bx1 >= t.x + t.width - 1\n}\n\n/** 같은 쪽에서 표 t 의 왼쪽 단에 온전히 놓인 블록 */\nfunction leftColumnOf(b: IRBlock, t: IRBlock): boolean {\n return !!b.bbox && b.pageNumber === t.pageNumber && b.bbox.x + b.bbox.width <= t.bbox!.x + 1\n}\n\n/**\n * 이은 표를 앞 조각(blocks[i]) 자리에 두고 뒤 조각(blocks[j])을 뺀다. 건너뛴 블록 가운데 앞 조각 왼쪽 단의 것이 있으면 이은 표를\n * 그 뒤로 옮긴다 — 블록은 윗변 순이라 2단 지면 오른쪽 단 표가 왼쪽 단 글·표보다 먼저 나오는데, 읽는 순서로는 왼쪽 단이 앞이다\n * (입찰동향 붙임: 오른쪽 단에서 시작해 다음 쪽으로 이어진 입찰내역표가 왼쪽 단 현황표 두 개보다 앞에 놓여 표 순서가 뒤바뀜)\n */\nfunction placeJoined(blocks: IRBlock[], i: number, j: number, table: IRTable): void {\n const prev = blocks[i]\n blocks[i] = { ...prev, table }\n blocks.splice(j, 1)\n let k = j - 1\n while (k > i && !leftColumnOf(blocks[k], prev)) k--\n if (k > i) blocks.splice(k, 0, blocks.splice(i, 1)[0])\n}\n\n/** 같은 쪽에서 표 테두리 안에 든 블록 — 칸 안 예시 상자처럼 표 칸에 붙지 못하고 표 뒤에 따로 나온 조각 */\nfunction insideTable(b: IRBlock, t: IRBlock): boolean {\n if (!b.bbox || b.pageNumber !== t.pageNumber) return false\n const o = t.bbox!\n return b.bbox.x >= o.x - 1 && b.bbox.x + b.bbox.width <= o.x + o.width + 1 && b.bbox.y >= o.y - 1 && b.bbox.y + b.bbox.height <= o.y + o.height + 1\n}\n\nexport function mergeCrossPageTables(blocks: IRBlock[], pageHeights?: Map<number, number>): void {\n mergeColumnFlow(blocks, pageHeights)\n for (let i = blocks.length - 2; i >= 0; i--) {\n const prev = blocks[i]\n if (prev.type !== \"table\" || !prev.table || !prev.bbox || !prev.pageNumber) continue\n // 다음 표 — 다음 쪽까지만 훑는다 (글만 긴 문서에서 블록마다 끝까지 훑지 않게). 앞 표 테두리 안에 든 표(칸 안 예시 상자가 따로 나온\n // 것)와 앞 표 왼쪽 단의 표(2단 지면)는 건너뛴다 — 편람 [별표 4] 서식 설계기준표는 \"3. 쪽번호\" 칸 안 예시 상자가 쪽 마지막 표가\n // 되어 다음 쪽 조각과 끊겼다\n let j = i + 1\n while (j < blocks.length && (blocks[j].type !== \"table\" || insideTable(blocks[j], prev) || leftColumnOf(blocks[j], prev)) && (blocks[j].pageNumber ?? 0) <= prev.pageNumber + 1) j++\n const curr = blocks[j]\n if (!curr || curr.type !== \"table\" || !curr.table || !curr.bbox || curr.pageNumber !== prev.pageNumber + 1) continue\n const joined = j === i + 1 || blocks.slice(i + 1, j).every(b => besideOwn(b, prev, curr) || insideTable(b, prev))\n ? (looksContinued(prev, curr, pageHeights) && !restartsTable(blocks, i, curr.table, pageHeights) ? joinClipParts(prev, curr, pageHeights) ?? false : null)\n : null\n if (joined) {\n // 한컴 클립 표 조각 — 열 경계 합집합 격자로 이었다 (쪽마다 열 구성이 달라도)\n placeJoined(blocks, i, j, joined)\n continue\n }\n // 앞 표의 이어짐이 못 된 빈 조각은 버리고 같은 앞 표로 그다음 표를 다시 본다 — 쪽 첫머리 장식 띠 같은 빈 클립 표가 진짜\n // 이어짐을 가로막지 않게 (빈 조각의 뒤쪽 이음은 이미 앞선 차례에 시도했다)\n if (EMPTY_PARTS.has(curr.table)) { blocks.splice(j, 1); i++; continue }\n if (joined === null) continue\n if (prev.table.cols !== curr.table.cols || EMPTY_PARTS.has(prev.table)) continue\n\n // 좌우 경계 근접 검증 (폭 대비 비율)\n const width = Math.max(prev.bbox.width, curr.bbox.width, 1)\n const leftDiff = Math.abs(prev.bbox.x - curr.bbox.x)\n const rightDiff = Math.abs((prev.bbox.x + prev.bbox.width) - (curr.bbox.x + curr.bbox.width))\n if (leftDiff > width * NEIGHBOR_TABLE_EPSILON || rightDiff > width * NEIGHBOR_TABLE_EPSILON) continue\n // 열 경계까지 같아야 한 표의 이어짐 — 열 수·좌우 끝만 보면 다음 쪽 첫머리의 \"붙임 2 | 제목\" 머리상자(1×3)가\n // 앞 쪽 일정표(3열)의 꼬리 행으로 붙는다(보도자료 붙임 실측). 경계 좌표가 없는 표(클러스터)는 종전대로.\n // 짝·홀 쪽 대칭 여백(책자형 편람: 쪽마다 본문이 11.4~12.5pt 옮겨짐)은 모든 경계가 같은 거리만큼 옮겨진 것이라 같은 경계로 본다\n // — 선 표 쪽 넘김 후보 가운데 이렇게 옮겨진 15건 중 사이 글 없이 쪽 끝·첫머리에 놓인 10건이 모두 같은 표다. 클립 표는\n // joinClipParts 가 쪼개진 행·반복 머리 행 증거가 있을 때만 옮겨 잇는다 (여기서 옮김을 받으면 연달아 놓인 Q&A 상자가 12×5 로 이어진다)\n const px = TABLE_COLXS.get(prev.table), cx = TABLE_COLXS.get(curr.table)\n if (px && cx && !shiftedSame(px, cx, !CLIP_TABLES.has(prev.table) && !CLIP_TABLES.has(curr.table))) continue\n\n // 반복 헤더 행 제거: 다음 표 첫 행이 이전 표 첫 행과 동일하면 중복 헤더\n let currCells = curr.table.cells\n if (currCells.length > 1 && prev.table.cells.length > 0 &&\n rowTextsEqual(prev.table.cells[0], currCells[0])) {\n currCells = currCells.slice(1)\n }\n if (currCells.length === 0) {\n blocks.splice(j, 1)\n continue\n }\n\n const merged: IRTable = {\n rows: prev.table.rows + currCells.length,\n cols: prev.table.cols,\n cells: [...prev.table.cells, ...currCells],\n hasHeader: prev.table.hasHeader,\n caption: prev.table.caption,\n }\n if (px ?? cx) TABLE_COLXS.set(merged, (px ?? cx)!)\n if (CLIP_TABLES.has(prev.table) || CLIP_TABLES.has(curr.table)) CLIP_TABLES.add(merged)\n placeJoined(blocks, i, j, merged)\n }\n // 잇지 못한 빈 클립 표 조각은 버린다 (쪽 추출 단계에서 빈 표를 버리던 종전 동작)\n for (let i = blocks.length - 1; i >= 0; i--) {\n const t = blocks[i].table\n if (blocks[i].type === \"table\" && t && EMPTY_PARTS.has(t)) blocks.splice(i, 1)\n }\n}\n\n/**\n * 같은 쪽 단 넘김 — 2단 지면(가로 쪽 두 단 편람 [별표 4], 보도자료 붙임 두 쪽 모아 찍기)에서 왼쪽 단 바닥까지 찬 클립 표 조각과 같은\n * 쪽 오른쪽 단 첫머리의 표 조각을 잇는다. 쪽 넘김과 같은 모양(앞 조각이 바닥까지, 뒤 조각이 첫머리부터)에 뒤 조각이 단 거리만큼\n * 옮겨져 있어 증거도 쪽 넘김 평행 이동과 같다(쪼개진 행 또는 글 있는 머리 행 되풀이). 블록 순서는 윗변 기준이라 오른쪽 단 조각이\n * 먼저 나오기도 해 순서와 무관하게 짝을 찾고, 이은 표는 두 자리 중 앞선 자리에 둔다. 이은 표의 bbox 는 x·윗변을 왼쪽 조각(열 경계\n * 좌표계)에서, 밑변을 오른쪽 조각(흐름의 끝)에서 가져온다 — 다음 쪽 이음은 흐름 끝이 쪽 바닥까지 찼는지 봐야 한다\n */\nfunction mergeColumnFlow(blocks: IRBlock[], pageHeights?: Map<number, number>): void {\n if (!pageHeights) return\n for (let i = 0; i < blocks.length; i++) {\n const L = blocks[i]\n if (L.type !== \"table\" || !L.table || !L.bbox || !L.pageNumber || !CLIP_TABLES.has(L.table) || EMPTY_PARTS.has(L.table)) continue\n const ph = pageHeights.get(L.pageNumber)\n if (!ph || L.bbox.y > ph * PAGE_EDGE_BAND) continue\n // 오른쪽 단 첫머리 표 — 같은 쪽(블록은 쪽 순서라 같은 쪽 구간만 훑는다), L 오른쪽에 온전히, 쪽 첫머리 띠 안에서 시작하는 가장 위 표\n let s0 = i, s1 = i\n while (s0 > 0 && blocks[s0 - 1].pageNumber === L.pageNumber) s0--\n while (s1 + 1 < blocks.length && blocks[s1 + 1].pageNumber === L.pageNumber) s1++\n let k = -1\n for (let q = s0; q <= s1; q++) {\n const R = blocks[q]\n if (q === i || R.type !== \"table\" || !R.table || !R.bbox || !leftColumnOf(L, R)) continue\n const top = R.bbox.y + R.bbox.height\n if (top < ph * (1 - PAGE_EDGE_BAND)) continue\n if (k < 0 || top > blocks[k].bbox!.y + blocks[k].bbox!.height) k = q\n }\n if (k < 0) continue\n const R = blocks[k]\n if (Math.abs(R.bbox!.width - L.bbox.width) > Math.max(R.bbox!.width, L.bbox.width) * NEIGHBOR_TABLE_EPSILON) continue\n if (!looksContinued(L, R) || restartsTable(blocks, i, R.table!, pageHeights)) continue\n const joined = joinClipParts(L, R, pageHeights)\n if (!joined) continue\n const top = L.bbox.y + L.bbox.height\n const at = Math.min(i, k)\n blocks[at] = { ...L, table: joined, bbox: { ...L.bbox, y: R.bbox!.y, height: Math.max(0, top - R.bbox!.y) } }\n blocks.splice(Math.max(i, k), 1)\n i = at - 1 // 이은 표가 다시 오른쪽 단으로 이어질 수 있다 (세 단)\n }\n}\n\n/**\n * 클립 표 두 조각이 한 표의 쪽 넘김인지 보고 이어 붙인다. 열 경계가 같으면 종전처럼 이어짐으로 보고, 다르면\n * 쪽 넘김 기하(앞 조각이 쪽 밑까지, 뒤 조각이 쪽 위부터 — PAGE_EDGE_BAND)와 오른쪽 끝 일치·뒤 조각 왼쪽 끝이 앞 조각\n * 경계 위에 있음을 요구한다(뒤 쪽에 세로 병합 이어진 칸이 비어 왼쪽 열이 빠진 경우).\n */\nfunction joinClipParts(prev: IRBlock, curr: IRBlock, pageHeights?: Map<number, number>): IRTable | null {\n const pt = prev.table!, ct = curr.table!\n if (!CLIP_TABLES.has(pt) || !CLIP_TABLES.has(ct)) return null\n const px = TABLE_COLXS.get(pt), cx = TABLE_COLXS.get(ct)\n if (!px || !cx) return null\n let xs = cx, shifted = false, dx = 0\n if (!shiftedSame(px, cx, false)) {\n // 열 구성이 다르면 쪽 넘김 기하를 반드시 확인 (looksContinued 는 쪽 높이를 모르면 통과시킨다)\n if (!pageHeights?.get(prev.pageNumber!) || !pageHeights?.get(curr.pageNumber!)) return null\n // 짝·홀 쪽 대칭 여백(11.4pt)이나 2단 지면의 단 넘김(420pt)으로 표 전체가 옮겨졌으면 뒤 조각을 앞 조각 자리로 옮겨 맞댄다. 다만\n // 같은 틀 상자가 쪽마다 새로 놓인 것(편람 Q&A 상자 55개)과 가르려고 경계 행이 쪼개진 행이거나 글 있는 머리 행이 되풀이될 때만\n // 잇는다 — 새 상자의 첫 행은 빈 칸이고 앞 상자 끝 행과 칸 짜임이 달라 둘 다 될 수 없다. 이 조건 없이 옮김만 넣으면 연달아\n // 놓인 상자끼리 이어져 편람 −14표 (클립 표 평행 이동 후보 47건 중 같은 표 5건)\n dx = px[px.length - 1] - cx[cx.length - 1]\n if (Math.abs(dx) > CONTINUATION_COL_TOL) { xs = cx.map(x => x + dx); shifted = true } else dx = 0\n if (!px.some(x => Math.abs(x - xs[0]) <= CONTINUATION_COL_TOL)) return null\n }\n const res = joinSplitParts(pt, px, ct, xs, dx, prev.bbox?.y)\n if (!res || (shifted && !res.split && !res.header)) return null\n TABLE_COLXS.set(res.table, res.colXs)\n CLIP_TABLES.add(res.table)\n return res.table\n}\n\n/** 붙임·참고·별지 등 첨부 머리표 — 쪽 첫머리에 이런 칸으로 시작하는 표는 앞 쪽 표의 이어짐이 아니다 */\nconst ANNEX_HEAD_RE = /^\\s*[<\\[(【]?\\s*(?:붙\\s*임|참\\s*고|별\\s*첨|별\\s*지|별\\s*표|첨\\s*부|부\\s*록)(?:\\s*\\d|\\s*$|\\s*[>\\])】])|^\\s*■/\n\n/**\n * 쪽 넘김 이어짐 판정 — 앞 표가 쪽 아래 PAGE_EDGE_BAND 안까지 차고 뒤 표가 쪽 위 PAGE_EDGE_BAND 안에서 시작하며, 뒤 표\n * 첫 칸이 첨부 머리표가 아니어야 한다. 보도자료·편람 쪽 넘김 후보 278건 대조(HWPX 로 같은 표인지 확인): 쪽 위쪽에 놓인\n * 머리 상자끼리(붙임 2 → 붙임 3, 시험지 쪽머리표)와 쪽 끝 담당자 표 → 다음 쪽 \"붙임\" 상자를 잇던 오병합이 이 두 조건으로\n * 빠지고, 참 이어짐은 1건만 놓친다. 쪽 높이를 모르면(외부 호출) 기하 조건은 건너뛴다\n */\nfunction looksContinued(prev: IRBlock, curr: IRBlock, pageHeights?: Map<number, number>): boolean {\n const ph = pageHeights?.get(prev.pageNumber!), ch = pageHeights?.get(curr.pageNumber!)\n if (ph && ch && (prev.bbox!.y > ph * PAGE_EDGE_BAND || curr.bbox!.y + curr.bbox!.height < ch * (1 - PAGE_EDGE_BAND))) return false\n const firstText = curr.table!.cells[0]?.find(c => c.text.trim())?.text ?? \"\"\n return !ANNEX_HEAD_RE.test(firstText)\n}\n\n/** 표 첫 행 앵커 — 열 경계 x 범위와 글 */\nfunction firstRowSig(t: IRTable): Array<{ x1: number; x2: number; t: string }> | null {\n const x = TABLE_COLXS.get(t)\n if (!x || x.length !== t.cols + 1) return null\n return anchorsOf(t).filter(a => a.r === 0).map(a => ({ x1: x[a.c], x2: x[a.c + a.cs], t: a.cell.text.replace(/\\s+/g, \"\") }))\n}\n\n/**\n * 뒤 표 첫 행이 앞 조각이 속한 표(쪽 넘김 사슬 첫 조각)의 첫 행을 다시 시작하는가 — 앵커 칸 짜임(x 범위, 대칭 여백\n * 이동 허용)이 같고 이름표 칸 둘 이상이 같은 글인데 값 칸이 하나 이상 다르다. 한 서식을 품목마다 되풀이한 문서(과제 품목 명세서\n * 5벌, 서식마다 두 쪽)에서 앞 서식 끝 조각은 본문 바닥 근처에서 끝나고 다음 쪽 첫머리가 새 서식이라 쪽 넘김 기하로는 이어짐과\n * 같다(141×22 한 표로 뭉침). 반복 머리 행은 글이 모두 같아 해당 없고, 이어진 조각의 첫 행은 표 첫 행과 칸 짜임부터 다르다\n */\nfunction restartsTable(blocks: IRBlock[], i: number, curr: IRTable, pageHeights?: Map<number, number>): boolean {\n const cs = firstRowSig(curr)\n if (!cs || cs.length < RESTART_MIN_ANCHORS) return false\n const hs = firstRowSig(blocks[chainHead(blocks, i, pageHeights)].table!)\n if (!hs || hs.length !== cs.length) return false\n const dx = hs[hs.length - 1].x2 - cs[cs.length - 1].x2\n if (!hs.every((h, n) => Math.abs(h.x1 - cs[n].x1 - dx) <= CONTINUATION_COL_TOL && Math.abs(h.x2 - cs[n].x2 - dx) <= CONTINUATION_COL_TOL)) return false\n let same = 0, diff = 0\n for (let n = 0; n < hs.length; n++) {\n if (!hs[n].t && !cs[n].t) continue\n if (hs[n].t === cs[n].t) same++\n else diff++\n }\n return same >= 2 && diff >= 1\n}\n\n/**\n * blocks[i] 표 조각이 속한 쪽 넘김 사슬의 첫 조각 위치 — 앞 쪽 마지막 표가 이음 모양(사이 글 없음·쪽 끝과 첫머리·열 경계 호환)이면\n * 거슬러 올라간다 (뒤에서부터 잇는 본 판정 전이라 아직 안 이은 조각끼리 본다). 이어진 조각의 첫 행은 데이터 행이라 거기와\n * 견주면 값이 되풀이되는 표(평가지표표 \"실증을 통한 내부평가\")가 새 표로 잘린다 — 표 첫 행과만 견준다\n */\nfunction chainHead(blocks: IRBlock[], i: number, pageHeights?: Map<number, number>): number {\n let k = i\n for (let steps = 0; steps < RESTART_LOOKBACK_PAGES; steps++) {\n const cur = blocks[k]\n let p = k - 1\n while (p >= 0 && blocks[p].type !== \"table\" && (blocks[p].pageNumber ?? 0) >= (cur.pageNumber ?? 0) - 1) p--\n if (p < 0 || blocks[p].type !== \"table\" || !blocks[p].table || !blocks[p].bbox) break\n // 칸 안 예시 상자처럼 앞 표 테두리 안에 든 표면 그 앞 표로\n for (let q = p - 1; q >= 0 && blocks[q].pageNumber === blocks[p].pageNumber; q--) {\n if (blocks[q].type === \"table\" && blocks[q].bbox && insideTable(blocks[p], blocks[q])) { p = q; break }\n }\n const pb = blocks[p]\n if (pb.pageNumber !== (cur.pageNumber ?? 0) - 1) break\n if (!blocks.slice(p + 1, k).every(b => besideOwn(b, pb, cur) || insideTable(b, pb))) break\n if (!looksContinued(pb, cur, pageHeights)) break\n const px = TABLE_COLXS.get(pb.table!), cx = TABLE_COLXS.get(cur.table!)\n if (!px || !cx) break\n const dx = px[px.length - 1] - cx[cx.length - 1]\n if (!shiftedSame(px, cx) && !px.some(x => Math.abs(x - (cx[0] + dx)) <= CONTINUATION_COL_TOL)) break\n k = p\n }\n return k\n}\n\n/** 두 행의 셀 텍스트가 모두 동일한지 (공백 정규화 후 비교) */\nfunction rowTextsEqual(a: IRCell[], b: IRCell[]): boolean {\n if (a.length !== b.length) return false\n const norm = (t: string) => t.replace(/\\s+/g, \"\")\n for (let i = 0; i < a.length; i++) {\n if (norm(a[i].text) !== norm(b[i].text)) return false\n }\n // 빈 행끼리의 비교는 의미 없음\n return a.some(c => c.text.trim() !== \"\")\n}\n\n/** 쪽 넘김 이음 판정 — 열 경계가 이 거리(pt) 안에서 전부 맞아야 같은 표 */\nconst CONTINUATION_COL_TOL = 2\n\n/** 두 조각의 열 경계가 같은가 — allowShift 면 모든 경계가 같은 거리만큼 옮겨진 것도 같다고 본다 (짝·홀 쪽 대칭 여백) */\nfunction shiftedSame(px: number[], cx: number[], allowShift = true): boolean {\n if (px.length !== cx.length) return false\n const dx = allowShift ? px[px.length - 1] - cx[cx.length - 1] : 0\n return px.every((x, k) => Math.abs(x - cx[k] - dx) <= CONTINUATION_COL_TOL)\n}\n/** 쪽 넘김 조각 판정 — 앞 조각 밑변이 쪽 아래 이 비율 안, 뒤 조각 윗변이 쪽 위 이 비율 안 (본문 여백 안쪽까지 찬 표) */\nconst PAGE_EDGE_BAND = 0.16\n/** 새 표 시작 판정 — 첫 행 앵커가 이만큼 이상인 표만 (칸 짜임이 같다는 게 우연이 아닐 만큼), 사슬 첫 조각은 이 쪽 수까지 거슬러 찾는다 */\nconst RESTART_MIN_ANCHORS = 3\nconst RESTART_LOOKBACK_PAGES = 5\n","/**\n * 쪽을 넘는 칸 잇기 (mergeContinuedCells) — 앞 쪽 칸의 이어짐으로 판정된 1칸 조각(CONT_PARTS, clip-cells)을 앞 쪽 표의 그 칸에 붙인다.\n *\n * 한컴 PDF 는 쪽을 넘는 칸을 쪽마다 그 쪽에 그려진 부분만 클립으로 깐다. 여러 칸 행이 넘어가면 뒤 쪽에도 칸 묶음이 생겨 쪽 넘김\n * 잇기(table-parts)가 잇지만, 한 칸만 넘어가면(1열 표의 본문 칸, 규제영향분석서 \"근거설명 | 내용\" 의 내용 칸) 뒤 쪽 조각은 1칸이다.\n * 그 조각을 새 행으로 붙이면 한 칸이 쪽 수만큼 행으로 갈리고, 조각에 든 표·글은 원본(HWPX) 칸 밖에 놓인다. 여기서는 조각의 글과\n * 표를 앞 쪽 칸 뒤에 이어 붙이고, 칸 안에서 쪽 경계로 갈린 표는 쪽 넘김 잇기로 다시 잇는다.\n */\n\nimport type { IRBlock, IRCell, IRTable } from \"../types.js\"\nimport { CONT_PARTS, EMPTY_PARTS, TABLE_COLXS } from \"./table-meta.js\"\nimport { mergeCrossPageTables } from \"./table-parts.js\"\n\n/** 조각의 좌우 변과 앞 표 열 경계를 같은 것으로 보는 거리 (pt) — 격자 열 경계는 클립 좌표 묶음(0.3pt)의 평균 */\nconst COL_MATCH_TOL = 0.5\n\n/** 표 마지막 행까지 내려오는 칸 가운데 좌우 변이 x1~x2 인 칸 (열 경계를 모르는 1열 표는 그 칸) */\nfunction lastRowCell(t: IRTable, x1: number, x2: number): IRCell | undefined {\n const xs = TABLE_COLXS.get(t)\n const covered = new Set<number>()\n for (let r = 0; r < t.rows; r++) {\n for (let c = 0; c < t.cols; c++) {\n if (covered.has(r * 100000 + c)) continue\n const cell = t.cells[r]?.[c]\n if (!cell) continue\n for (let dr = 0; dr < cell.rowSpan; dr++) for (let dc = 0; dc < cell.colSpan; dc++) covered.add((r + dr) * 100000 + c + dc)\n if (r + cell.rowSpan !== t.rows) continue\n if (xs ? Math.abs(xs[c] - x1) <= COL_MATCH_TOL && Math.abs(xs[c + cell.colSpan] - x2) <= COL_MATCH_TOL : t.cols === 1) return cell\n }\n }\n return undefined\n}\n\n/** 칸 내용을 블록으로 — blocks 가 없으면 text 줄마다 문단 (틀 칸 blocks 와 같은 모양) */\nconst cellBlocks = (cell: IRCell, pageNumber?: number): IRBlock[] =>\n cell.blocks ?? cell.text.split(\"\\n\").map(t => t.trim()).filter(Boolean).map(text => ({ type: \"paragraph\" as const, text, pageNumber }))\n\n/** 표 블록들을 거꾸로 훑어 이어짐 조각을 앞 쪽 표 칸에 붙이고 조각은 뺀다 (세 쪽 넘게 이어지면 뒤 조각부터 앞 조각에 모인다) */\nexport function mergeContinuedCells(blocks: IRBlock[], pageHeights?: Map<number, number>): void {\n for (let j = blocks.length - 1; j > 0; j--) {\n const part = blocks[j]\n const from = part.table ? CONT_PARTS.get(part.table) : undefined\n if (!from || !part.table) continue\n // 앞 표 — 사이 글은 앞 쪽 꼬리말·이 쪽 머리말 띠뿐이다 (clip-cells 가 조각을 쪽 첫 내용, 앞 칸을 쪽 마지막 내용으로 골랐다)\n let i = j - 1\n while (i >= 0 && blocks[i].type !== \"table\") i--\n const prev = blocks[i]\n if (i < 0 || !prev.table || prev.pageNumber !== (part.pageNumber ?? 0) - 1) continue\n const cell = lastRowCell(prev.table, from.x1, from.x2)\n const add = part.table.cells[0]?.[0]\n if (!cell || !add) continue\n if (cell.blocks || add.blocks) cell.blocks = [...cellBlocks(cell, prev.pageNumber), ...cellBlocks(add, part.pageNumber)]\n cell.text = [cell.text, add.text].filter(s => s.trim()).join(\"\\n\")\n // 앞 쪽 조각이 빈 칸뿐이던 표(쪽 끝에 머리 행만 남은 칸)도 이어진 글을 받았으면 더는 빈 조각이 아니다 — 쪽 넘김 잇기가 버리지 않게\n if (cell.text.trim() || cell.blocks?.length) EMPTY_PARTS.delete(prev.table)\n blocks.splice(j, 1)\n // 칸 안에서 쪽 경계로 갈린 표 (반제품 아이스팩 기준 틀의 2×2 계산 예시: 첫 행만 앞 쪽에 남은 것)\n if (cell.blocks) mergeCrossPageTables(cell.blocks, pageHeights)\n }\n}\n","/**\n * 심볼 폰트(Wingdings 등) 글리프 코드 → 유니코드 복원.\n *\n * 한컴 PDF 는 서식의 처리절차 화살표·체크박스를 Wingdings 심볼 폰트로 찍는다. pdfjs 는\n * 심볼릭 폰트의 코드를 Latin-1 문자로 그대로 돌려주므로(0xE8 → \"è\") 마크다운에 깨진\n * 라틴 문자가 남는다 (법제처 별지서식 PDF 실측, v4.12.1). 폰트 실명(commonObjs)이\n * Wingdings 이면 코드를 표준 유니코드로 되돌린다.\n *\n * 코드표는 src/shared/symbol-fonts.ts (HWP/HWPX 심볼 PUA 와 공용 SSOT).\n */\n\nimport type { NormItem } from \"./text-line.js\"\nimport { WINGDINGS } from \"../shared/symbol-fonts.js\"\n\n/** WinAnsi(CP1252) 0x80~0x9F 구간 — pdfjs 가 이미 CP1252 문자로 돌려준 경우 코드 복원 */\nconst WINANSI_REVERSE: Record<number, number> = {\n 0x20AC: 0x80, 0x201A: 0x82, 0x0192: 0x83, 0x201E: 0x84, 0x2026: 0x85, 0x2020: 0x86, 0x2021: 0x87,\n 0x02C6: 0x88, 0x2030: 0x89, 0x0160: 0x8A, 0x2039: 0x8B, 0x0152: 0x8C, 0x017D: 0x8E, 0x2018: 0x91,\n 0x2019: 0x92, 0x201C: 0x93, 0x201D: 0x94, 0x2022: 0x95, 0x2013: 0x96, 0x2014: 0x97, 0x02DC: 0x98,\n 0x2122: 0x99, 0x0161: 0x9A, 0x203A: 0x9B, 0x0153: 0x9C, 0x017E: 0x9E, 0x0178: 0x9F,\n}\n\n/** 폰트 실명이 심볼 폰트인지 — 서브셋 접두(ABCDEF+)·\"-Regular\" 접미 무관 */\nexport function symbolFontTable(fontName: string | undefined): readonly string[] | undefined {\n if (!fontName) return undefined\n // Wingdings 2·3, Webdings 는 별도 코드표 — 매핑 없이 두면 오히려 잘못된 글자가 되므로 제외\n if (/wingdings(?![\\s-]*[23])/i.test(fontName)) return WINGDINGS\n return undefined\n}\n\n/** 한 문자열을 심볼 코드표로 되돌린다 — 표 밖 코드(제어·공백)는 그대로 */\nexport function remapSymbolText(text: string, table: readonly string[]): string {\n let out = \"\"\n for (const ch of text) {\n let code = ch.codePointAt(0)!\n if (code >= 0x80 && WINANSI_REVERSE[code] !== undefined) code = WINANSI_REVERSE[code]\n if (code >= 0x21 && code <= 0xFF) {\n const mapped = table[code - 0x21]\n out += mapped !== undefined && mapped !== \"\" ? mapped : ch\n } else {\n out += ch\n }\n }\n return out\n}\n\n/**\n * 페이지 텍스트 아이템의 심볼 폰트 글리프를 유니코드로 복원 (제자리).\n * @param resolveFontName pdfjs loadedName(g_d0_f6) → 폰트 실명. 미로드·실패 시 undefined\n * @returns 바뀐 아이템 수\n */\nexport function remapSymbolFontItems(items: NormItem[], resolveFontName: (loadedName: string) => string | undefined): number {\n const cache = new Map<string, readonly string[] | undefined>()\n let changed = 0\n for (const it of items) {\n if (!it.fontName || !it.text) continue\n let table = cache.get(it.fontName)\n if (!cache.has(it.fontName)) {\n table = symbolFontTable(resolveFontName(it.fontName))\n cache.set(it.fontName, table)\n }\n if (!table) continue\n const mapped = remapSymbolText(it.text, table)\n if (mapped !== it.text) { it.text = mapped; changed++ }\n }\n return changed\n}\n","/**\n * PDF 마크다운 최종 정리.\n *\n * 페이지 번호 제거, 균등배분 후처리, 취소선 복원, 한글 줄바꿈 병합.\n * blocksToMarkdown 이후의 문자열 수준 후처리를 담당한다.\n */\n\nimport type { IRBlock } from \"../types.js\"\nimport { stripControlChars } from \"./quality.js\"\nimport { collapseEvenSpacing } from \"./text-line.js\"\n\n/**\n * 한컴 PDF 가 가운뎃점(ㆍ U+318D)을 조합형 중성 아래아(ᆞ U+119E)로 내는 것을 되돌린다.\n * 실측(v4.12.3, 별지서식·별표 573쌍): PDF 출력의 가운뎃점 3,238자 중 340자(41문서)가 U+119E,\n * HWP5 원본은 전부 U+318D. 옛한글 자모 결합(앞이 초성 U+1100~115F)만 남긴다.\n */\nexport function normalizeAraea(text: string): string {\n return text.replace(/(?<![\\u1100-\\u115F])\\u119E/g, \"\\u318D\")\n}\n\n/**\n * 한컴 PDF 의 \"유니코드 없는 글리프\" 자리표시 U+F000 제거. 한컴 PDF 는 ToUnicode 를 못 만든 글리프를 전부 U+F000 으로\n * 낸다 — 자동 글머리표·자동 번호(□·①), 일부 괄호(《), 칸 채움 글자까지 같은 코드라(보도자료 246건 실측: 줄머리 33·줄 안\n * 125) 원래 글자를 되살릴 수 없고, 남기면 두부 글자로 찍힌다. 페이지 품질 신호(PUA 비율) 계산 뒤에 지운다\n */\nconst stripNoUnicodeGlyph = (text: string): string => (text.includes(\"\\uF000\") ? text.replace(/\\uF000/g, \"\") : text)\n\nconst cleanChars = (text: string): string => normalizeAraea(stripNoUnicodeGlyph(stripControlChars(text)))\n\n/** 블록 트리의 텍스트에서 비표시 제어문자 제거 + 조합형 가운뎃점 정규화 (in-place, 셀 blocks 포함) */\nexport function sanitizeBlockControlChars(blocks: IRBlock[]): void {\n for (const b of blocks) {\n if (b.text) b.text = cleanChars(b.text)\n if (b.table) {\n for (const row of b.table.cells) {\n for (const cell of row) {\n if (cell.text) cell.text = cleanChars(cell.text)\n if (cell.blocks) sanitizeBlockControlChars(cell.blocks)\n }\n }\n }\n if (b.children) sanitizeBlockControlChars(b.children)\n }\n}\n\n/**\n * 최상위 1×1 표(중첩표 없음)를 줄마다 문단 블록으로 편다 (PDF 전용, v4.12.3).\n * 1×1 셀 줄바꿈은 표 셀 줄바꿈 보존 정책(v4.12.1 1열 다행)과 같이 지켜야 하는데, 1×1 은\n * tableToMarkdown 이 \"줄\\n줄\" 로 내고 cleanPdfText 의 mergeKoreanLines 가 한글 줄을 이어 붙여\n * \"선 서 나는 헌법을 …\"(선서문 안쪽 상자, 제목 줄+본문 줄 결합)이 됐다. 문단 블록 사이는 빈 줄이라\n * 병합 대상이 아니다. 실측 파급: 별지서식·별표·보고서 897 PDF 중 10문서 12표.\n */\nexport function splitSingleCellTables(blocks: IRBlock[]): IRBlock[] {\n const out: IRBlock[] = []\n for (const b of blocks) {\n const t = b.type === \"table\" ? b.table : undefined\n const cell = t && t.rows === 1 && t.cols === 1 ? t.cells[0]?.[0] : undefined\n if (!cell || cell.blocks?.some((x) => x.type === \"table\")) { out.push(b); continue }\n const lines = (cell.text ?? \"\").split(/\\n/).map((l) => l.trim()).filter(Boolean)\n if (lines.length === 0) continue\n for (const text of lines) out.push({ type: \"paragraph\", text, pageNumber: b.pageNumber, bbox: b.bbox })\n }\n return out\n}\n\nexport function cleanPdfText(text: string): string {\n return mergeKoreanLines(\n normalizeAraea(stripControlChars(text))\n // 문서 시작 단독 페이지 번호\n .replace(/^\\d{1,4}\\n/, \"\")\n // \"- 2 -\" 스타일 페이지 번호 (독립 라인 및 목록 항목 형태 포함)\n .replace(/^[\\s]*[-–—]\\s*[-–—]?\\d+[-–—]?[\\s]*[-–—]?[\\s]*$/gm, \"\")\n // \"1 / 5\" 스타일 페이지 번호\n .replace(/^\\s*\\d+\\s*\\/\\s*\\d+\\s*$/gm, \"\")\n // 단독 페이지 번호 (줄 끝에 혼자 있는 숫자)\n .replace(/\\n\\d{1,4}\\n/g, \"\\n\")\n // 문서 마지막 단독 페이지 번호\n .replace(/\\n\\d{1,4}$/, \"\")\n // 단독 숫자 헤딩 제거 (\"# 6\\n재무과\" → \"\\n재무과\")\n .replace(/^#{1,6}\\s*\\d{1,4}\\s*$/gm, \"\")\n )\n // 균등배분 문자열 후처리 (pdfjs가 합친 TextItem + buildGridTable 셀 텍스트)\n // LaTeX 수식 라인 ($...$ / $$...$$) 은 공백이 토큰 구분자라 collapse 시 `\\cdot d` → `\\cdotd` 로 망가짐 — skip\n .replace(/^(?!\\| ---).*$/gm, line => {\n if (/^\\s*\\${1,2}.+\\${1,2}\\s*$/.test(line)) return line\n // 마크다운 머리 표지(\"# \"·\"- \")는 한 글자 토큰으로 세지 않는다 — \"# 목 차\" 가 \"#목차\"(헤딩 표지 깨짐)로 붙던 것\n const mark = /^(?:#{1,6}|-) /.exec(line)?.[0] ?? \"\"\n return mark + collapseEvenSpacing(line.slice(mark.length))\n })\n // 마커 뒤 2글자 균등배분 합침 (\"□ 일 시\" → \"□ 일시\", \"□ 장 소\" → \"□ 장소\")\n .replace(/([□■◆○●▶ㅇ])\\s+([가-힣])\\s+([가-힣])/g, \"$1 $2$3\")\n // 취소선 복원: builder escapeGfm이 ~를 \\~로 이스케이프 — 쌍(~~)만 되살림\n .replace(/\\\\~\\\\~/g, \"~~\")\n // 인접 취소선 run이 붙어 생긴 빈 마크(~~~~) 정리\n .replace(/~~~~/g, \"\")\n // 내용이 사라져 빈 밑줄 쌍(<u></u>) 정리 (escapeGfm은 <>를 건드리지 않아 복원 불필요)\n .replace(/<u>\\s*<\\/u>/g, \"\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim()\n}\n\nfunction startsWithMarker(line: string): boolean {\n const t = line.trimStart()\n return /^[가-힣ㄱ-ㅎ][.)]/.test(t) || /^\\d+[.)]/.test(t) || /^\\([가-힣ㄱ-ㅎ\\d]+\\)/.test(t) ||\n /^[○●※▶▷◆◇■□★☆\\-·]\\s/.test(t) || /^제\\d+[조항호장절]/.test(t)\n}\n\nfunction isStandaloneHeader(line: string): boolean {\n return /^제\\d+[조항호장절](\\([^)]*\\))?(\\s+\\S+){0,7}$/.test(line.trim())\n}\n\nfunction mergeKoreanLines(text: string): string {\n if (!text) return \"\"\n const lines = text.split(\"\\n\")\n if (lines.length <= 1) return text\n const result: string[] = [lines[0]]\n\n for (let i = 1; i < lines.length; i++) {\n const prev = result[result.length - 1]\n const curr = lines[i]\n const currTrimmed = curr.trim()\n // 마크다운 헤딩/테이블/구분선은 병합하지 않음\n if (/^#{1,6}\\s/.test(prev) || /^#{1,6}\\s/.test(curr) || /^\\|/.test(currTrimmed) || /^---/.test(currTrimmed)) {\n result.push(curr)\n continue\n }\n // 쉼표로 끝나는 줄 + 다음 줄 = 연속 문장\n if (/,$/.test(prev.trim()) && currTrimmed.length > 0) {\n result[result.length - 1] = prev + \"\\n\" + curr\n continue\n }\n // (※ 로 시작하는 줄 = 이전 줄의 부연설명\n if (/^\\(※/.test(currTrimmed)) {\n result[result.length - 1] = prev + \" \" + currTrimmed\n continue\n }\n // 한글 줄바꿈 병합 — 마커(○, □ 등)로 시작하는 이전 줄은 합치지 않음\n if (/[가-힣·,\\-]$/.test(prev) && /^[가-힣(]/.test(curr) &&\n !startsWithMarker(curr) && !isStandaloneHeader(prev) &&\n !startsWithMarker(prev)) {\n result[result.length - 1] = prev + \" \" + curr\n } else {\n result.push(curr)\n }\n }\n return result.join(\"\\n\")\n}\n","/**\n * PDF 링크 어노테이션 → 마크다운 링크.\n *\n * /Annots 의 Link(/URI) 어노테이션 rect 를 텍스트 아이템과 상관해 해당 run 을\n * [text](url) 로 감싼다. pdfjs getAnnotations() 가 주는 rect 는 PDF 사용자 좌표\n * (pt, bottom-up)로 텍스트 아이템과 같은 공간. escapeGfm 은 `](스킴…)` 링크\n * 스팬을 보호하므로 마커가 그대로 살아남는다.\n */\n\nimport { sanitizeHref } from \"../utils.js\"\nimport type { NormItem } from \"./text-line.js\"\n\n/** pdfjs getAnnotations() 반환 원소 중 이 모듈이 쓰는 필드 */\nexport interface PdfAnnotation {\n subtype?: string\n url?: string\n rect?: number[]\n}\n\n/** rect 와 아이템의 세로 겹침 판정 여유 (pt) */\nconst LINK_Y_TOL = 2\n/** 아이템이 rect 에 속한다고 볼 최소 수평 겹침 비율 */\nconst LINK_MIN_OVERLAP_RATIO = 0.5\n\n/**\n * Link 어노테이션 rect 안의 텍스트 run 을 [text](url) 로 감싼다.\n * 시각적 줄 단위로 래핑 — 여러 줄에 걸친 링크는 줄마다 같은 url 로 반복.\n * rect 안에 텍스트가 없는 어노테이션(아이콘 링크 등)은 무시.\n */\nexport function applyLinkAnnotations(items: NormItem[], annots: PdfAnnotation[]): void {\n if (items.length === 0 || annots.length === 0) return\n const wrapped = new Set<NormItem>() // 겹치는 어노테이션의 이중 래핑 방지\n\n for (const a of annots) {\n if (a.subtype !== \"Link\" || !a.url || !a.rect || a.rect.length < 4) continue\n const url = sanitizeHref(a.url)\n if (!url) continue\n const x1 = Math.min(a.rect[0], a.rect[2]), x2 = Math.max(a.rect[0], a.rect[2])\n const y1 = Math.min(a.rect[1], a.rect[3]), y2 = Math.max(a.rect[1], a.rect[3])\n\n const matches: NormItem[] = []\n for (const item of items) {\n if (wrapped.has(item) || item.w <= 0 || !item.text.trim()) continue\n // baseline 이 rect 세로 범위 안 (밑줄형 링크 rect 가 baseline 아래로 살짝\n // 내려가는 경우 대비 여유 2pt)\n if (item.y < y1 - LINK_Y_TOL || item.y > y2 + LINK_Y_TOL) continue\n const overlap = Math.min(x2, item.x + item.w) - Math.max(x1, item.x)\n if (overlap / item.w < LINK_MIN_OVERLAP_RATIO) continue\n matches.push(item)\n }\n if (matches.length === 0) continue\n\n // 줄 단위 그룹핑 (y ±3, 취소선/밑줄 래핑과 동일 컨벤션) 후 x 순 래핑\n const lines = new Map<number, NormItem[]>()\n for (const m of matches) {\n const key = Math.round(m.y / 3)\n const arr = lines.get(key) || []\n arr.push(m)\n lines.set(key, arr)\n }\n for (const arr of lines.values()) {\n arr.sort((p, q) => p.x - q.x)\n arr[0].text = \"[\" + arr[0].text\n arr[arr.length - 1].text = arr[arr.length - 1].text + `](${url})`\n for (const m of arr) wrapped.add(m)\n }\n }\n}\n","/**\n * 수식 OCR 통합 (optional)\n *\n * 페이지 이미지 렌더 후 수식만 검출/인식해 blocks 에 formula paragraph 를 삽입.\n * 모델은 최초 실행 시 자동 다운로드 (./formula/index.js 에 위임).\n */\n\nimport type { IRBlock, ParseWarning } from \"../types.js\"\n\n/**\n * 수식 OCR 을 적용하여 blocks 에 formula paragraph 를 삽입한다.\n *\n * 좌표 매핑:\n * - pdfium 픽셀 bbox (top-left origin) → PDF 포인트 (bottom-left origin) 변환\n * - 수식 bbox 의 y center 와 같은 페이지 내 pdfjs block 의 y center 비교로 삽입 위치 결정\n * - pdfjs 가 이미 뽑은 수식 흔적(block) 과 겹치면 해당 block 제거 (중복 방지)\n *\n * 실패/trivial 수식(latex === \"\") 은 삽입하지 않는다.\n */\nexport async function applyFormulaOcr(\n buffer: ArrayBuffer,\n blocks: IRBlock[],\n pageFilter: Set<number> | null,\n effectivePageCount: number,\n warnings: ParseWarning[],\n _onProgress?: (current: number, total: number) => void,\n): Promise<void> {\n const formulaMod = await import(\"./formula/index.js\")\n const { FormulaPipeline, ensureFormulaModels } = formulaMod\n\n // 모델 준비 — 없으면 자동 다운로드. 진행률은 stderr 로 출력.\n await ensureFormulaModels((p) => {\n if (p.phase === \"download\" && p.total) {\n const pct = Math.floor((p.downloaded / p.total) * 100)\n process.stderr.write(`\\r[kordoc-formula] ${p.spec.name} ${pct}% (${formatMb(p.downloaded)}/${formatMb(p.total)})`)\n if (p.downloaded >= p.total) process.stderr.write(\"\\n\")\n } else if (p.phase === \"verify\") {\n process.stderr.write(`[kordoc-formula] ${p.spec.name} SHA-256 검증 중...\\n`)\n } else if (p.phase === \"done\") {\n process.stderr.write(`[kordoc-formula] ${p.spec.name} 준비 완료\\n`)\n } else if (p.phase === \"skip\") {\n // 조용히 스킵\n }\n })\n\n const pipeline = await FormulaPipeline.create()\n try {\n // MAX_PAGES 상한을 수식 OCR 에도 적용 — 필터 없으면 텍스트 추출과 같은 범위만\n // (없으면 대형 PDF 에서 상한 밖 페이지까지 ONNX 추론 + 결과가 문서 끝에 오배치)\n const effectiveFilter = pageFilter\n ?? new Set(Array.from({ length: effectivePageCount }, (_, i) => i + 1))\n const pagesResult = await pipeline.runOnBuffer(buffer, effectiveFilter)\n\n if (pagesResult.length === 0) return\n\n let insertedCount = 0\n let removedDupCount = 0\n\n for (const page of pagesResult) {\n const pageNumber = page.pageNumber\n const pdfHeight = page.pdfHeight\n const scaleX = page.renderedWidth > 0 ? page.pdfWidth / page.renderedWidth : 0.5\n const scaleY = page.renderedHeight > 0 ? page.pdfHeight / page.renderedHeight : 0.5\n\n // 1) 수식 → (PDF 포인트 bbox, latex) 정규화 + trivial 제외\n interface FormulaCandidate {\n block: IRBlock\n pdfBbox: { x1: number; x2: number; yTop: number; yBottom: number }\n centerY: number // PDF bottom-up\n }\n const candidates: FormulaCandidate[] = []\n for (const r of page.regions) {\n if (!r.latex || !r.latex.trim()) continue\n const wrapped = r.kind === \"display\" ? `$$${r.latex}$$` : `$${r.latex}$`\n\n const x1 = r.bbox.x1 * scaleX\n const x2 = r.bbox.x2 * scaleX\n // pdfium 픽셀 y → PDF bottom-up\n const yTop = pdfHeight - r.bbox.y1 * scaleY\n const yBottom = pdfHeight - r.bbox.y2 * scaleY\n const centerY = (yTop + yBottom) / 2\n const width = x2 - x1\n const height = yTop - yBottom\n\n candidates.push({\n block: {\n type: \"paragraph\",\n text: wrapped,\n pageNumber,\n bbox: { page: pageNumber, x: x1, y: yBottom, width, height },\n },\n pdfBbox: { x1, x2, yTop, yBottom },\n centerY,\n })\n }\n if (candidates.length === 0) continue\n\n // 2) 같은 페이지의 pdfjs block 중 수식 bbox 와 크게 겹치는 것 제거\n // (pdfjs 가 수식을 텍스트로 파편 추출한 경우 — overlap ratio ≥ 0.6 이면 중복으로 간주)\n const OVERLAP_THRESHOLD = 0.6\n const indicesToRemove = new Set<number>()\n for (let i = 0; i < blocks.length; i++) {\n const b = blocks[i]\n if (b.pageNumber !== pageNumber) continue\n if (b.type === \"table\") continue // 표는 건드리지 않음\n if (!b.bbox || b.bbox.width <= 0 || b.bbox.height <= 0) continue\n const blockArea = b.bbox.width * b.bbox.height\n if (blockArea <= 0) continue\n\n for (const c of candidates) {\n const ox1 = Math.max(b.bbox.x, c.pdfBbox.x1)\n const ox2 = Math.min(b.bbox.x + b.bbox.width, c.pdfBbox.x2)\n const oy1 = Math.max(b.bbox.y, c.pdfBbox.yBottom)\n const oy2 = Math.min(b.bbox.y + b.bbox.height, c.pdfBbox.yTop)\n const interArea = Math.max(0, ox2 - ox1) * Math.max(0, oy2 - oy1)\n if (interArea / blockArea >= OVERLAP_THRESHOLD) {\n indicesToRemove.add(i)\n break\n }\n }\n }\n\n if (indicesToRemove.size > 0) {\n // 내림차순으로 제거해야 인덱스가 밀리지 않음\n const sorted = [...indicesToRemove].sort((a, b) => b - a)\n for (const idx of sorted) blocks.splice(idx, 1)\n removedDupCount += indicesToRemove.size\n }\n\n // 3) 각 수식을 y 좌표 기준 적절한 위치에 삽입\n // 수식들을 위→아래(centerY 큰 것부터) 정렬 후, 각 수식마다 현재 blocks 에서\n // \"center y < 수식 center y\" 인 첫 블록(= 수식보다 아래) 앞에 삽입.\n candidates.sort((a, b) => b.centerY - a.centerY)\n\n for (const c of candidates) {\n let insertIdx = -1\n let pageFirstIdx = -1\n let pageLastIdx = -1\n for (let i = 0; i < blocks.length; i++) {\n const b = blocks[i]\n if (b.pageNumber !== pageNumber) continue\n if (pageFirstIdx === -1) pageFirstIdx = i\n pageLastIdx = i\n if (!b.bbox) continue\n const blockCenter = b.bbox.y + b.bbox.height / 2\n if (blockCenter < c.centerY) {\n insertIdx = i\n break\n }\n }\n\n if (insertIdx !== -1) {\n blocks.splice(insertIdx, 0, c.block)\n } else if (pageLastIdx !== -1) {\n blocks.splice(pageLastIdx + 1, 0, c.block)\n } else {\n // 해당 페이지에 텍스트 블록 없음 — 맨 끝에 추가\n blocks.push(c.block)\n }\n insertedCount++\n }\n }\n\n if (insertedCount > 0 || removedDupCount > 0) {\n process.stderr.write(\n `[kordoc-formula] ${insertedCount}개 수식 삽입, ${removedDupCount}개 중복 block 제거 (${pagesResult.length}개 페이지)\\n`,\n )\n }\n } finally {\n await pipeline.destroy().catch(() => {})\n }\n}\n\nfunction formatMb(bytes: number): string {\n return `${(bytes / 1024 / 1024).toFixed(1)}MB`\n}\n","/**\n * 서버리스/Node.js 환경에서 pdfjs-dist가 요구하는 브라우저 API polyfill.\n * pdfjs-dist import보다 먼저 실행되어야 함 (ES 모듈 호이스팅 대응으로 별도 파일 분리).\n *\n * 1. DOMMatrix / Path2D polyfill — pdfjs-dist가 참조하지만 Node.js에 없음\n * 2. pdfjsWorker 사전 주입 — fake worker의 동적 import를 우회\n */\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst g = globalThis as any\n\nif (typeof g.DOMMatrix === \"undefined\") {\n g.DOMMatrix = class DOMMatrix {\n m: number[] = [1, 0, 0, 1, 0, 0]\n constructor(init?: number[]) { if (init) this.m = init }\n }\n}\n\nif (typeof g.Path2D === \"undefined\") {\n g.Path2D = class Path2D {}\n}\n\n// worker 모듈 static import → fake worker가 동적 import를 건너뜀\n// @ts-expect-error pdfjs-dist worker has no type declarations\nimport * as pdfjsWorker from \"pdfjs-dist/legacy/build/pdf.worker.mjs\"\ng.pdfjsWorker = pdfjsWorker\n","/**\n * PDF 텍스트 추출 (pdfjs-dist static import 기반)\n *\n * polyfill을 먼저 import해야 DOMMatrix/Path2D/pdfjsWorker가 주입됨.\n * ES 모듈 호이스팅 때문에 별도 파일로 분리되어 있음.\n *\n * 이 파일은 엔트리(파이프라인 오케스트레이션)와 메타데이터 추출만 담당한다.\n * 세부 단계는 모듈로 분리: text-line(아이템/줄), xy-cut(읽기 순서),\n * columns(열 감지), page-blocks(블록 추출), block-detect(후처리 감지),\n * text-clean(마크다운 정리), formula-ocr(수식).\n */\n\nimport type { InternalParseResult, IRBlock, DocumentMetadata, ExtractedImage, ParseOptions, ParseWarning, OutlineItem } from \"../types.js\"\nimport { KordocError } from \"../utils.js\"\nimport { parsePageRange } from \"../page-range.js\"\nimport { blocksToMarkdown, escapeLiteralDollar } from \"../table/builder.js\"\nimport { extractImageRegions } from \"./line-detector.js\"\nimport { createPdfImageState, extractPageImages, injectPageImageBlocks } from \"./image-extract.js\"\nimport { computePageQuality, summarizeDocumentQuality, type PageQuality } from \"./quality.js\"\nimport { scanVectorGlyphs, ocrVectorOps } from \"./vector-glyphs.js\"\nimport { type PdfTextItem, normalizeItems, filterHiddenText } from \"./text-line.js\"\nimport { extractPageBlocksWithLines, type PageCarry } from \"./page-blocks.js\"\nimport { WrapLexicon, joinPageBreakWraps } from \"./line-wrap.js\"\nimport { mergeCrossPageTables } from \"./table-parts.js\"\nimport { mergeContinuedCells } from \"./cell-continuation.js\"\nimport { trimTrailingEmptyTableCols } from \"./table-trim.js\"\nimport { remapSymbolFontItems } from \"./symbol-fonts.js\"\nimport { computeMedianFontSizeFromFreq, detectHeadings, detectMarkerHeadings, detectTableCaptions, detectKoreanListBlocks, removeHeaderFooterBlocks } from \"./block-detect.js\"\nimport { sanitizeBlockControlChars, cleanPdfText, splitSingleCellTables } from \"./text-clean.js\"\nimport { applyLinkAnnotations } from \"./links.js\"\nimport { applyFormulaOcr } from \"./formula-ocr.js\"\n// polyfill 먼저 (ES 모듈 호이스팅되므로 별도 파일 필수)\nimport \"./polyfill.js\"\nimport { getDocument, GlobalWorkerOptions } from \"pdfjs-dist/legacy/build/pdf.mjs\"\nimport { createRequire } from \"node:module\"\nimport { dirname, join } from \"node:path\"\n\n// 기존 공개 API 경로 유지 — 이동된 함수의 re-export\nexport { mergeCrossPageTables }\nexport { cleanPdfText }\nexport { detectTableCaptions, detectKoreanListBlocks, removeHeaderFooterBlocks }\n\n// worker 비활성화 (polyfill에서 pdfjsWorker를 이미 주입했으므로)\nGlobalWorkerOptions.workerSrc = \"\"\n\n// ─── 안전 한계값 (구조적 파싱과 무관) ────────────────\nconst MAX_PAGES = 5000\nconst MAX_TOTAL_TEXT = 100 * 1024 * 1024 // 100MB\n/** PDF 로딩 타임아웃 (30초) — 악성/대용량 PDF 무한 대기 방지 */\nconst PDF_LOAD_TIMEOUT_MS = 30_000\n\n// CID 폰트 자산(cmaps/standard_fonts) 경로 — 미지정이면 CMap 필요 폰트의 텍스트가\n// \"loadFont failed: cMapUrl 필요\" 경고와 함께 통째로 소실된다 (성과계획서류 숫자 전멸).\n// pdfjs-dist 패키지 위치에서 해석하고, 실패 시 미지정(기존 동작) 유지.\nconst pdfjsAssets: { cMapUrl?: string; cMapPacked?: boolean; standardFontDataUrl?: string } = {}\ntry {\n const _require = createRequire(import.meta.url)\n const pkgDir = dirname(_require.resolve(\"pdfjs-dist/package.json\"))\n pdfjsAssets.cMapUrl = join(pkgDir, \"cmaps\") + \"/\"\n pdfjsAssets.cMapPacked = true\n pdfjsAssets.standardFontDataUrl = join(pkgDir, \"standard_fonts\") + \"/\"\n} catch { /* optional dep — 경로 해석 실패 시 cMap 없이 진행 */ }\n\n/** getDocument + 타임아웃 래퍼 */\nasync function loadPdfWithTimeout(buffer: ArrayBuffer) {\n const loadingTask = getDocument({\n data: new Uint8Array(buffer),\n useSystemFonts: true,\n disableFontFace: true,\n isEvalSupported: false,\n ...pdfjsAssets,\n })\n let timer: ReturnType<typeof setTimeout> | undefined\n try {\n return await Promise.race([\n loadingTask.promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => { loadingTask.destroy(); reject(new KordocError(\"PDF 로딩 타임아웃 (30초 초과)\")) }, PDF_LOAD_TIMEOUT_MS)\n }),\n ])\n } finally {\n if (timer !== undefined) clearTimeout(timer)\n }\n}\n\nexport async function parsePdfDocument(buffer: ArrayBuffer, options?: ParseOptions): Promise<InternalParseResult> {\n // pdfjs-dist 는 전달받은 buffer 의 underlying storage 를 detach 할 수 있다.\n // 수식/텍스트 OCR 은 같은 버퍼를 pdfium 으로 재사용해야 하므로 옵션 on 일 때만 clone 을 보관.\n const formulaBuffer: ArrayBuffer | null = options?.formulaOcr ? buffer.slice(0) : null\n const ocrBuffer: ArrayBuffer | null = options?.ocr ? buffer.slice(0) : null\n const doc = await loadPdfWithTimeout(buffer)\n\n try {\n const pageCount = doc.numPages\n if (pageCount === 0) throw new KordocError(\"PDF에 페이지가 없습니다.\")\n\n // 메타데이터 추출 (best-effort) — PDF는 항상 실제 페이지 단위 (#66)\n const metadata: DocumentMetadata = { pageCount, pageMode: \"layout\" }\n await extractPdfMetadata(doc, metadata)\n\n const blocks: IRBlock[] = []\n const warnings: ParseWarning[] = []\n const pageQuality: PageQuality[] = []\n let totalChars = 0\n let totalTextBytes = 0\n const effectivePageCount = Math.min(pageCount, MAX_PAGES)\n\n // 페이지 범위 필터링\n const pageFilter = options?.pages ? parsePageRange(options.pages, effectivePageCount) : null\n const totalTarget = pageFilter ? pageFilter.size : effectivePageCount\n\n // 전체 문서의 폰트 크기 빈도 수집 (헤딩 감지용) — 빈도 Map으로 메모리 절약\n const fontSizeFreq = new Map<number, number>()\n const pageHeights = new Map<number, number>()\n // 큰 이미지가 있는 페이지 (needsOcr 경고 노이즈 필터 + SKIPPED_IMAGE)\n const pagesWithLargeImage = new Set<number>()\n // 텍스트 없는 큰 이미지 영역: page → count\n const skippedImagePages = new Map<number, number>()\n // 이미지 XObject 바이트 추출 상태 (문서 단위 중복 억제·상한).\n // image 블록은 페이지 경계 표 병합(mergeCrossPageTables)의 인접성을 깨지 않도록\n // 페이지별로 모아뒀다가 병합 후 주입한다.\n const imageState = createPdfImageState()\n const extractedImages: ExtractedImage[] = []\n const pageImageBlocks = new Map<number, IRBlock[]>()\n // 쪽을 넘는 칸 — 앞 쪽 마지막 칸을 다음 쪽 클립 판정에 넘긴다 (clip-cells continues, 문서 단계 mergeContinuedCells)\n const carry: PageCarry = {}\n // OCR 로 갈 벡터 글자 쪽의 그래픽(글자 경로·클립 뺀 연산자 목록) — OCR 글자에 이 쪽의 실제 괘선을 붙인다\n const vectorPageOps = new Map<number, { fnArray: number[]; argsArray: unknown[][] }>()\n // 줄 꺾임 이음의 문서 어휘 증거 — 쪽을 처리할 때마다 그 쪽 줄 글이 더해진다 (line-wrap.ts)\n const wrapLexicon = new WrapLexicon()\n\n let parsedPages = 0\n for (let i = 1; i <= effectivePageCount; i++) {\n if (pageFilter && !pageFilter.has(i)) continue\n try {\n const page = await doc.getPage(i)\n const tc = await page.getTextContent()\n const viewport = page.getViewport({ scale: 1 })\n pageHeights.set(i, viewport.height)\n const rawItems = tc.items as PdfTextItem[]\n const items = normalizeItems(rawItems)\n\n // hidden text 필터링 + 경고 수집\n const { visible, hiddenCount } = filterHiddenText(items, viewport.width, viewport.height)\n if (hiddenCount > 0) {\n warnings.push({ page: i, message: `${hiddenCount}개 숨겨진 텍스트 요소 필터링됨`, code: \"HIDDEN_TEXT_FILTERED\" })\n }\n\n // 폰트 크기 빈도 수집\n for (const item of visible) {\n if (item.fontSize > 0) fontSizeFreq.set(item.fontSize, (fontSizeFreq.get(item.fontSize) || 0) + 1)\n }\n\n // 링크 어노테이션(/Annots /URI) → [text](url) 래핑 — 실패해도 텍스트 파싱 진행\n try {\n const annots = await page.getAnnotations()\n applyLinkAnnotations(visible, annots)\n } catch { /* 어노테이션 파싱 실패 무시 */ }\n\n // 선 기반 테이블 감지를 위한 operatorList\n const opList = await page.getOperatorList()\n\n // 심볼 폰트(Wingdings) 글리프 복원 — 폰트 실명은 operatorList 로드 뒤에야 commonObjs 에 있다\n remapSymbolFontItems(visible, (loadedName) => {\n try { return page.commonObjs.has(loadedName) ? (page.commonObjs.get(loadedName) as { name?: string } | null)?.name : undefined }\n catch { return undefined }\n })\n // 리터럴 $ 는 \\$ — $…$ 는 수식 스팬 전용(IR 규약, HWPX·HWP5 와 같음). 종전엔 \"단가(US $) … 금액(US $)\" 사이가\n // 마크다운에서 수식으로 읽혀 사라졌다(야생생물 신고서·어셈블리 \"lda $30,-16($30)\"). 심볼 글꼴 복원 뒤라야 글자 표가 안 어긋난다\n for (const it of visible) if (it.text.includes(\"$\")) it.text = escapeLiteralDollar(it.text)\n\n // 이미지 영역 감지 — 텍스트 없는 큰 이미지는 무음 정보손실이므로 가시화 (ODL 아이디어)\n const pageArea = viewport.width * viewport.height\n if (pageArea > 0) {\n const imageRegions = extractImageRegions(opList.fnArray, opList.argsArray)\n let uncovered = 0\n for (const r of imageRegions) {\n const area = (r.x2 - r.x1) * (r.y2 - r.y1)\n if (area < pageArea * 0.05) continue // 작은 장식 이미지 무시\n pagesWithLargeImage.add(i)\n const hasText = visible.some(it => {\n const cx = it.x + it.w / 2\n const cy = it.y + (it.h || it.fontSize) / 2\n return cx >= r.x1 && cx <= r.x2 && cy >= r.y1 && cy <= r.y2\n })\n if (!hasText) uncovered++\n }\n if (uncovered > 0) skippedImagePages.set(i, uncovered)\n }\n\n const pageBlocks = extractPageBlocksWithLines(visible, i, opList, viewport.width, viewport.height, undefined, options?.tables !== false, carry, wrapLexicon)\n for (const b of pageBlocks) blocks.push(b)\n\n // 이미지 XObject 바이트 추출 — 블록 주입은 표 병합 후(injectPageImageBlocks)\n // images:false 면 PNG 인코딩을 건너뛰고 자리 표시 블록만 받는다\n try {\n const { blocks: imgBlocks, images: pageImages } = await extractPageImages(page, opList.fnArray, opList.argsArray, i, imageState, warnings, options?.images !== false)\n if (imgBlocks.length > 0) pageImageBlocks.set(i, imgBlocks)\n extractedImages.push(...pageImages)\n } catch { /* 이미지 추출 실패가 텍스트 파싱을 막지 않도록 */ }\n\n // 이미지 기반 PDF 감지 + 크기 제한용 문자 수 집계 + 페이지 품질 신호\n let pageText = \"\"\n for (const b of pageBlocks) {\n let t = b.text || \"\"\n // 표 블록은 text 없이 table만 가지므로 셀 텍스트도 집계 — 괘선 양식(표-전용)\n // 문서가 totalChars=0으로 이미지 기반 오판되어 정상 파싱 표가 OCR로\n // 대체되는 것 방지. 페이지 품질(computePageQuality) 집계도 같은 뿌리.\n if (b.type === \"table\" && b.table) {\n const cellText = b.table.cells.map(row => row.map(c => c.text).join(\" \")).join(\"\\n\")\n t = t ? t + \"\\n\" + cellText : cellText\n }\n totalChars += t.replace(/\\s/g, \"\").length\n totalTextBytes += t.length * 2\n pageText += pageText ? \"\\n\" + t : t\n }\n // 텍스트층 밖 곡선 글자 — 글자를 채운 경로로 그린 쪽은 텍스트층 신호만으론 멀쩡해 보인다 (vector-glyphs.ts)\n const vector = scanVectorGlyphs(opList.fnArray, opList.argsArray, visible)\n const quality = computePageQuality(i, pageText, vector.glyphs)\n pageQuality.push(quality)\n // 회전·원점 이동 쪽은 pdfium 래스터 좌표가 사용자 공간과 어긋나 종전대로(래스터 괘선) 둔다\n if (options?.ocr && quality.ocrReason === \"vector_text\" && page.rotate % 360 === 0 && page.view[0] === 0 && page.view[1] === 0) {\n vectorPageOps.set(i, ocrVectorOps(opList, vector.paths))\n }\n if (totalTextBytes > MAX_TOTAL_TEXT) throw new KordocError(\"텍스트 추출 크기 초과\")\n parsedPages++\n options?.onProgress?.(parsedPages, totalTarget)\n } catch (pageErr) {\n // 크기 초과는 전체 중단\n if (pageErr instanceof KordocError) throw pageErr\n warnings.push({ page: i, message: `페이지 ${i} 파싱 실패: ${pageErr instanceof Error ? pageErr.message : \"알 수 없는 오류\"}`, code: \"PARTIAL_PARSE\" })\n }\n }\n\n const parsedPageCount = parsedPages || (pageFilter ? pageFilter.size : effectivePageCount)\n // 문서 단위 이미지 기반 판정 (평균 10자/페이지 미만 = 텍스트층 부재)\n const isImageBased = totalChars / Math.max(parsedPageCount, 1) < 10\n\n // ── OCR 실행 (옵션) — 페이지 단위 선정·병합 ──\n // 대상: \"force\"=전 페이지 / 문서가 이미지 기반=전 페이지 /\n // 그 외=품질 신호가 OCR 을 권하는 페이지만 (깨진 텍스트층 포함 — F1,\n // 혼합 문서의 스캔 페이지 포함 — F2). 정상 페이지 파싱 결과는 유지 (F3).\n const ocrDone = new Set<number>()\n if (options?.ocr && ocrBuffer) {\n const inScope = (p: number) => !pageFilter || pageFilter.has(p)\n const targets = new Set<number>()\n if (options.ocr === \"force\" || isImageBased) {\n for (let i = 1; i <= effectivePageCount; i++) if (inScope(i)) targets.add(i)\n } else {\n for (const pq of pageQuality) {\n if (!pq.needsOcr) continue\n // low_text 는 빈 페이지(표지/간지)일 수 있으므로 큰 이미지가 있는 페이지만\n if (pq.ocrReason === \"low_text\" && !pagesWithLargeImage.has(pq.page)) continue\n targets.add(pq.page)\n }\n }\n if (targets.size > 0) {\n try {\n const { runPdfOcr } = await import(\"../ocr/pdf-ocr.js\")\n const mode = typeof options.ocr === \"function\" ? options.ocr : (\"builtin\" as const)\n const ocrPageBlocks = await runPdfOcr(ocrBuffer, targets, mode, warnings, options.onProgress, options.tables !== false, vectorPageOps)\n if (ocrPageBlocks.size > 0) {\n // 페이지 단위 교체: OCR 성공 페이지의 기존(깨진/빈) 블록 제거 후 병합\n for (const p of ocrPageBlocks.keys()) ocrDone.add(p)\n const merged = blocks.filter(b => !(b.pageNumber && ocrDone.has(b.pageNumber)))\n for (const obs of ocrPageBlocks.values()) merged.push(...obs)\n merged.sort((a, b) => (a.pageNumber ?? 0) - (b.pageNumber ?? 0)) // stable — 페이지 내 순서 보존\n blocks.length = 0\n blocks.push(...merged)\n for (const pq of pageQuality) if (ocrDone.has(pq.page)) pq.ocrApplied = true\n warnings.push({\n message: `${ocrDone.size}개 페이지에 OCR 적용 (${mode === \"builtin\" ? \"내장 PP-OCRv5\" : \"사용자 프로바이더\"})`,\n code: \"OCR_APPLIED\",\n })\n }\n } catch (e) {\n // 환경 오류(의존성·모델) — 아래 NEEDS_OCR 폴백이 가시화, 원인은 별도 경고로 보존 (F6)\n warnings.push({\n message: `OCR 실행 불가: ${e instanceof Error ? e.message : String(e)}`,\n code: \"OCR_FAILED\",\n })\n }\n }\n }\n\n if (isImageBased && ocrDone.size === 0) {\n // OCR 미설정/실패 — 빈 출력을 무경고로 내보내지 않고 경고 + 플래그로 가시화 (v3.0)\n warnings.push({\n message: `이미지 기반 PDF (${pageCount}페이지, 텍스트 ${totalChars}자) — 텍스트 레이어가 없어 OCR이 필요합니다`,\n code: \"NEEDS_OCR\",\n })\n }\n\n // 페이지 단위 needsOcr 경고 — 텍스트+스캔 혼합 문서에서 스캔 페이지 무음 손실 방지.\n // low_text는 빈 페이지(표지/간지)일 수 있으므로 큰 이미지가 있는 페이지만 경고.\n // OCR 이 적용된 페이지는 해소된 것이므로 제외.\n if (!isImageBased) {\n const OCR_REASON_MESSAGES: Record<string, string> = {\n vector_text: \"글자를 곡선(벡터 경로)으로 그린 페이지 (텍스트층에 글자 없음)\",\n low_text: \"텍스트가 거의 없는 페이지 (스캔/이미지 추정)\",\n high_pua: \"글꼴 매핑 실패 (PUA 비율 높음) — 추출 텍스트 신뢰 불가\",\n high_control: \"제어문자 비율 높음 — 추출 텍스트 신뢰 불가\",\n high_replacement: \"대체문자(U+FFFD) 비율 높음 — 추출 텍스트 신뢰 불가\",\n garbled_hangul: \"글꼴 매핑 실패 (한글 자소 분포 이상) — 추출 텍스트가 깨졌을 수 있음\",\n }\n for (const pq of pageQuality) {\n if (!pq.needsOcr || !pq.ocrReason || pq.ocrApplied) continue\n if (pq.ocrReason === \"low_text\" && !pagesWithLargeImage.has(pq.page)) continue\n warnings.push({ page: pq.page, message: `${OCR_REASON_MESSAGES[pq.ocrReason]} — OCR 검토 필요`, code: \"NEEDS_OCR\" })\n }\n }\n\n // 텍스트 없는 큰 이미지 영역 경고 — 그림/차트/도장 무음 누락 가시화\n // (문서 전체가 이미지 기반이면 위의 NEEDS_OCR 단일 경고로 충분)\n if (!isImageBased) {\n for (const [page, count] of [...skippedImagePages.entries()].sort((a, b) => a[0] - b[0])) {\n warnings.push({ page, message: `${count}개 이미지 영역에 추출 가능한 텍스트 없음 (그림/차트/도장 내용 누락 가능)`, code: \"SKIPPED_IMAGE\" })\n }\n }\n\n // 머리글/바닥글 필터링 (기본 ON — 명시적 false일 때만 비활성화)\n if (options?.removeHeaderFooter !== false && parsedPageCount >= 3) {\n const removed = removeHeaderFooterBlocks(blocks, pageHeights, warnings)\n // 필터링된 블록 제거 (뒤에서부터 삭제)\n for (let ri = removed.length - 1; ri >= 0; ri--) {\n blocks.splice(removed[ri], 1)\n }\n }\n\n // 쪽을 넘는 칸의 1칸 조각을 앞 쪽 표 그 칸에 붙인 뒤(행으로 갈리지 않게) 페이지 걸친 표 병합 —\n // 머리글/바닥글 제거 후 인접해진 표를 하나로 (ODL TableBorderProcessor.checkNeighborTables 포팅)\n mergeContinuedCells(blocks, pageHeights)\n mergeCrossPageTables(blocks, pageHeights)\n // 후행 빈 열 정리 — HWP 계열 표 빌더와 같은 규칙 (병합 뒤: 쪽마다 같은 열 구조일 때 이어 붙인 다음)\n if (!options?.keepTrailingEmptyCols) trimTrailingEmptyTableCols(blocks)\n // 쪽 넘김으로 꺾인 본문 문단 잇기 — 머리말·꼬리말 제거 뒤, 쪽 끝 그림 주입 전 (line-wrap.ts)\n joinPageBreakWraps(blocks, wrapLexicon)\n\n // 추출 이미지 참조를 페이지 말미 위치에 주입 (표 병합 뒤 — 인접성 보존)\n injectPageImageBlocks(blocks, pageImageBlocks)\n\n // 수식 OCR (선택) — 기본 텍스트 추출과 별개로 페이지 이미지 렌더 후 수식만 검출/인식.\n // 실패 시 경고만 기록하고 일반 텍스트 추출 결과는 그대로 반환한다.\n if (options?.formulaOcr && formulaBuffer) {\n try {\n await applyFormulaOcr(formulaBuffer, blocks, pageFilter, effectivePageCount, warnings, options.onProgress)\n } catch (e) {\n warnings.push({\n message: `수식 OCR 실패: ${e instanceof Error ? e.message : String(e)}`,\n code: \"PARTIAL_PARSE\",\n })\n }\n }\n\n // 헤딩 감지: 폰트 크기 기반\n const medianFontSize = computeMedianFontSizeFromFreq(fontSizeFreq)\n if (medianFontSize > 0) {\n detectHeadings(blocks, medianFontSize)\n }\n\n // □/■ 마커 기반 서브헤딩 감지 (ODL 패턴)\n detectMarkerHeadings(blocks)\n\n // 표 캡션 감지 — 표 직전/직후 '표 N./그림 N' 패턴 텍스트를 IRTable.caption으로\n detectTableCaptions(blocks)\n\n // 한국어 리스트 감지 — 공문서 계층 라벨(1.→가.→1)→가)→①) 시퀀스 검증\n detectKoreanListBlocks(blocks)\n\n // outline 구축\n const outline: OutlineItem[] = blocks\n .filter(b => b.type === \"heading\" && b.level && b.text)\n .map(b => ({ level: b.level!, text: b.text!, pageNumber: b.pageNumber }))\n\n // 메트릭 수집 끝났으니 블록 텍스트의 C0/C1 제어문자(NUL 등) 정리\n sanitizeBlockControlChars(blocks)\n // 1×1 표(중첩 없음)는 줄마다 문단으로 — 셀 줄바꿈이 mergeKoreanLines 에 붙지 않게 (v4.12.3)\n const outBlocks = splitSingleCellTables(blocks)\n\n // blocksToMarkdown로 통일 — 헤딩 마크다운 반영 (HWP5/HWPX와 일관성)\n let markdown = cleanPdfText(blocksToMarkdown(outBlocks))\n\n return {\n markdown,\n blocks,\n metadata,\n outline: outline.length > 0 ? outline : undefined,\n warnings: warnings.length > 0 ? warnings : undefined,\n isImageBased: isImageBased || undefined,\n pageQuality,\n qualitySummary: summarizeDocumentQuality(pageQuality),\n images: extractedImages.length > 0 ? extractedImages : undefined,\n }\n } finally {\n await doc.destroy().catch(() => {})\n }\n}\n\n// ─── PDF 메타데이터 추출 ────────────────────────────\n\nasync function extractPdfMetadata(doc: { getMetadata(): Promise<unknown> }, metadata: DocumentMetadata): Promise<void> {\n try {\n const result = await doc.getMetadata() as { info?: Record<string, unknown> } | null\n if (!result?.info) return\n const info = result.info\n\n if (typeof info.Title === \"string\" && info.Title.trim()) metadata.title = info.Title.trim()\n if (typeof info.Author === \"string\" && info.Author.trim()) metadata.author = info.Author.trim()\n if (typeof info.Creator === \"string\" && info.Creator.trim()) metadata.creator = info.Creator.trim()\n if (typeof info.Subject === \"string\" && info.Subject.trim()) metadata.description = info.Subject.trim()\n if (typeof info.Keywords === \"string\" && info.Keywords.trim()) {\n metadata.keywords = info.Keywords.split(/[,;]/).map((k: string) => k.trim()).filter(Boolean)\n }\n if (typeof info.CreationDate === \"string\") metadata.createdAt = parsePdfDate(info.CreationDate)\n if (typeof info.ModDate === \"string\") metadata.modifiedAt = parsePdfDate(info.ModDate)\n } catch {\n // best-effort\n }\n}\n\n/** PDF 날짜 형식 (D:YYYYMMDDHHmmSS) → ISO 8601 변환 */\nfunction parsePdfDate(dateStr: string): string | undefined {\n const m = dateStr.match(/D:(\\d{4})(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?/)\n if (!m) return undefined\n const [, year, month = \"01\", day = \"01\", hour = \"00\", min = \"00\", sec = \"00\"] = m\n return `${year}-${month}-${day}T${hour}:${min}:${sec}`\n}\n\n/** 메타데이터만 추출 (전체 파싱 없이) — MCP parse_metadata용 */\nexport async function extractPdfMetadataOnly(buffer: ArrayBuffer): Promise<DocumentMetadata> {\n const doc = await loadPdfWithTimeout(buffer)\n\n try {\n const metadata: DocumentMetadata = { pageCount: doc.numPages }\n await extractPdfMetadata(doc, metadata)\n return metadata\n } finally {\n await doc.destroy().catch(() => {})\n }\n}\n"],"mappings":"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","names":["OPS","R","top","hasText"]}
|