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.
Files changed (151) hide show
  1. package/README.md +51 -6
  2. package/dist/{-AQKXLFIM.js → -Z4TJ3QGK.js} +13 -11
  3. package/dist/{chunk-QOMNEWCQ.cjs → chunk-5LAZQCUJ.cjs} +305 -31
  4. package/dist/chunk-5LAZQCUJ.cjs.map +1 -0
  5. package/dist/{chunk-7IAQ4HOK.js → chunk-6BWU6SQX.js} +1 -1
  6. package/dist/chunk-6BWU6SQX.js.map +1 -0
  7. package/dist/{chunk-FOJZNMMK.js → chunk-6CUIXJOC.js} +1 -1
  8. package/dist/chunk-6CUIXJOC.js.map +1 -0
  9. package/dist/{chunk-ZWD734EY.js → chunk-7SE4EZ45.js} +2 -2
  10. package/dist/{chunk-TSFS5VKD.js → chunk-D4U2TXDC.js} +332 -141
  11. package/dist/chunk-D4U2TXDC.js.map +1 -0
  12. package/dist/chunk-DPWWXUOG.js +2222 -0
  13. package/dist/chunk-DPWWXUOG.js.map +1 -0
  14. package/dist/{chunk-GDW4YP7K.js → chunk-GLSBEEZJ.js} +2 -2
  15. package/dist/{chunk-JQEFP45L.js → chunk-GUJ4Q73I.js} +2 -2
  16. package/dist/{chunk-ZWOFQRIB.js → chunk-HIJUKZHM.js} +3 -3
  17. package/dist/chunk-IMAAFSAX.cjs +1352 -0
  18. package/dist/chunk-IMAAFSAX.cjs.map +1 -0
  19. package/dist/{chunk-TNCKOL2O.js → chunk-LCW7SEPG.js} +2 -2
  20. package/dist/chunk-LFSQAMSM.js +1352 -0
  21. package/dist/chunk-LFSQAMSM.js.map +1 -0
  22. package/dist/chunk-MG6DJEX4.js +1299 -0
  23. package/dist/chunk-MG6DJEX4.js.map +1 -0
  24. package/dist/{chunk-QC5T6D4J.js → chunk-NLU6LBLY.js} +299 -25
  25. package/dist/chunk-NLU6LBLY.js.map +1 -0
  26. package/dist/{chunk-EQZI77PP.cjs → chunk-QEJUY6SK.cjs} +3 -3
  27. package/dist/{chunk-EQZI77PP.cjs.map → chunk-QEJUY6SK.cjs.map} +1 -1
  28. package/dist/{chunk-VQ5W4MYO.js → chunk-QIF2VXBQ.js} +298 -24
  29. package/dist/chunk-QIF2VXBQ.js.map +1 -0
  30. package/dist/{chunk-UET462HB.js → chunk-TITDJWVZ.js} +2 -2
  31. package/dist/{chunk-247ANM72.cjs → chunk-USHQCF5T.cjs} +870 -147
  32. package/dist/chunk-USHQCF5T.cjs.map +1 -0
  33. package/dist/{chunk-C7FQBSON.js → chunk-V3DX4DUF.js} +2 -2
  34. package/dist/{chunk-DOGPOY4B.js → chunk-VDN7LMPL.js} +2 -2
  35. package/dist/{chunk-EDNJLUSE.js → chunk-VJIL6PIL.js} +2 -2
  36. package/dist/{chunk-L7DMSMAG.cjs → chunk-VWUYVEFN.cjs} +1 -1
  37. package/dist/chunk-VWUYVEFN.cjs.map +1 -0
  38. package/dist/{chunk-HR2XNERB.cjs → chunk-WJHAU73B.cjs} +2 -2
  39. package/dist/{chunk-HR2XNERB.cjs.map → chunk-WJHAU73B.cjs.map} +1 -1
  40. package/dist/{chunk-NS55SM7A.js → chunk-WJJGW5PL.js} +2 -2
  41. package/dist/{chunk-EISO3POT.js → chunk-XVXHXQIO.js} +788 -194
  42. package/dist/chunk-XVXHXQIO.js.map +1 -0
  43. package/dist/{chunk-ILU76UF3.js → chunk-YAKC46HZ.js} +2 -2
  44. package/dist/chunk-YAKC46HZ.js.map +1 -0
  45. package/dist/{chunk-3DUADAY2.js → chunk-YAVHVFRY.js} +851 -128
  46. package/dist/chunk-YAVHVFRY.js.map +1 -0
  47. package/dist/{chunk-A7QC2ZVP.js → chunk-YT3GC62E.js} +851 -128
  48. package/dist/chunk-YT3GC62E.js.map +1 -0
  49. package/dist/chunks-CCMRL4MJ.js +10 -0
  50. package/dist/cli.js +228 -97
  51. package/dist/cli.js.map +1 -1
  52. package/dist/{formula-PJJ6S3Z2.cjs → formula-5LOVHAKE.cjs} +9 -9
  53. package/dist/{formula-PJJ6S3Z2.cjs.map → formula-5LOVHAKE.cjs.map} +1 -1
  54. package/dist/{formula-JGFTXWYT.js → formula-7RANCW5L.js} +3 -3
  55. package/dist/{formula-5S7WWXGH.js → formula-7UYJOVTB.js} +4 -4
  56. package/dist/{image-ocr-WCAYXLOA.js → image-ocr-I6JOCATS.js} +34 -16
  57. package/dist/image-ocr-I6JOCATS.js.map +1 -0
  58. package/dist/{image-ocr-ZTYAGV3W.cjs → image-ocr-NYBJEOH2.cjs} +36 -18
  59. package/dist/image-ocr-NYBJEOH2.cjs.map +1 -0
  60. package/dist/{image-ocr-FLIL7MGN.js → image-ocr-ZV6PDBUQ.js} +32 -14
  61. package/dist/image-ocr-ZV6PDBUQ.js.map +1 -0
  62. package/dist/index.cjs +4048 -1216
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +66 -28
  65. package/dist/index.d.ts +66 -28
  66. package/dist/index.js +3487 -655
  67. package/dist/index.js.map +1 -1
  68. package/dist/mcp.js +74 -53
  69. package/dist/mcp.js.map +1 -1
  70. package/dist/{model-bundle-FSPZZ2EM.js → model-bundle-2C5IYDLE.js} +5 -5
  71. package/dist/{models-CJPAQK5R.js → models-SEQZHZFT.js} +5 -5
  72. package/dist/{parser-SCUTZAIJ.js → parser-3FNXEP6T.js} +708 -250
  73. package/dist/parser-3FNXEP6T.js.map +1 -0
  74. package/dist/{parser-A4TMPJAM.js → parser-FJBJXRJK.js} +708 -250
  75. package/dist/parser-FJBJXRJK.js.map +1 -0
  76. package/dist/{parser-VCXE4I5A.cjs → parser-HVEKF73B.cjs} +733 -275
  77. package/dist/parser-HVEKF73B.cjs.map +1 -0
  78. package/dist/pdf-ocr-JRDBJ6TA.cjs +14 -0
  79. package/dist/{pdf-ocr-MBZZ57LD.cjs.map → pdf-ocr-JRDBJ6TA.cjs.map} +1 -1
  80. package/dist/pdf-ocr-UUBWBEHL.js +16 -0
  81. package/dist/pdf-ocr-VJSF2SQC.js +13 -0
  82. package/dist/{profile-io-LBM263K7.js → profile-io-RUFZOZO6.js} +3 -3
  83. package/dist/{rasterize-46RG4ZF6.js → rasterize-IOYXR5SI.js} +3 -3
  84. package/dist/redact-NMIKVHMP.js +18 -0
  85. package/dist/redact-doc-QBHNJ2BU.js +1284 -0
  86. package/dist/redact-doc-QBHNJ2BU.js.map +1 -0
  87. package/dist/{render-NGP5T24S.js → render-YZ2TEC3N.js} +7 -7
  88. package/dist/seal-ECV44TUH.js +10 -0
  89. package/dist/{setup-AU4OCHNH.js → setup-MPIEP6XT.js} +3 -3
  90. package/dist/visual-ICMOWUCR.js +19 -0
  91. package/dist/{watch-MKPUCPXM.js → watch-RYO7MN5B.js} +12 -12
  92. package/package.json +2 -2
  93. package/dist/chunk-247ANM72.cjs.map +0 -1
  94. package/dist/chunk-3DUADAY2.js.map +0 -1
  95. package/dist/chunk-422427XJ.cjs +0 -571
  96. package/dist/chunk-422427XJ.cjs.map +0 -1
  97. package/dist/chunk-7IAQ4HOK.js.map +0 -1
  98. package/dist/chunk-A7QC2ZVP.js.map +0 -1
  99. package/dist/chunk-DZIXKL7E.js +0 -145
  100. package/dist/chunk-DZIXKL7E.js.map +0 -1
  101. package/dist/chunk-EISO3POT.js.map +0 -1
  102. package/dist/chunk-EP7KOI2V.js +0 -571
  103. package/dist/chunk-EP7KOI2V.js.map +0 -1
  104. package/dist/chunk-FOJZNMMK.js.map +0 -1
  105. package/dist/chunk-ILU76UF3.js.map +0 -1
  106. package/dist/chunk-KVWV4ULP.js +0 -516
  107. package/dist/chunk-KVWV4ULP.js.map +0 -1
  108. package/dist/chunk-L7DMSMAG.cjs.map +0 -1
  109. package/dist/chunk-QC5T6D4J.js.map +0 -1
  110. package/dist/chunk-QOMNEWCQ.cjs.map +0 -1
  111. package/dist/chunk-TSFS5VKD.js.map +0 -1
  112. package/dist/chunk-VQ5W4MYO.js.map +0 -1
  113. package/dist/chunks-SX77P5CV.js +0 -10
  114. package/dist/image-ocr-FLIL7MGN.js.map +0 -1
  115. package/dist/image-ocr-WCAYXLOA.js.map +0 -1
  116. package/dist/image-ocr-ZTYAGV3W.cjs.map +0 -1
  117. package/dist/parser-A4TMPJAM.js.map +0 -1
  118. package/dist/parser-SCUTZAIJ.js.map +0 -1
  119. package/dist/parser-VCXE4I5A.cjs.map +0 -1
  120. package/dist/pdf-ocr-76SAUXMZ.js +0 -13
  121. package/dist/pdf-ocr-CPU55CVF.js +0 -16
  122. package/dist/pdf-ocr-MBZZ57LD.cjs +0 -14
  123. package/dist/redact-7ILEAUTS.js +0 -12
  124. package/dist/seal-RGUPOLXD.js +0 -10
  125. package/dist/visual-NZLYIYEM.js +0 -19
  126. /package/dist/{-AQKXLFIM.js.map → -Z4TJ3QGK.js.map} +0 -0
  127. /package/dist/{chunk-ZWD734EY.js.map → chunk-7SE4EZ45.js.map} +0 -0
  128. /package/dist/{chunk-GDW4YP7K.js.map → chunk-GLSBEEZJ.js.map} +0 -0
  129. /package/dist/{chunk-JQEFP45L.js.map → chunk-GUJ4Q73I.js.map} +0 -0
  130. /package/dist/{chunk-ZWOFQRIB.js.map → chunk-HIJUKZHM.js.map} +0 -0
  131. /package/dist/{chunk-TNCKOL2O.js.map → chunk-LCW7SEPG.js.map} +0 -0
  132. /package/dist/{chunk-UET462HB.js.map → chunk-TITDJWVZ.js.map} +0 -0
  133. /package/dist/{chunk-C7FQBSON.js.map → chunk-V3DX4DUF.js.map} +0 -0
  134. /package/dist/{chunk-DOGPOY4B.js.map → chunk-VDN7LMPL.js.map} +0 -0
  135. /package/dist/{chunk-EDNJLUSE.js.map → chunk-VJIL6PIL.js.map} +0 -0
  136. /package/dist/{chunk-NS55SM7A.js.map → chunk-WJJGW5PL.js.map} +0 -0
  137. /package/dist/{chunks-SX77P5CV.js.map → chunks-CCMRL4MJ.js.map} +0 -0
  138. /package/dist/{formula-JGFTXWYT.js.map → formula-7RANCW5L.js.map} +0 -0
  139. /package/dist/{formula-5S7WWXGH.js.map → formula-7UYJOVTB.js.map} +0 -0
  140. /package/dist/{model-bundle-FSPZZ2EM.js.map → model-bundle-2C5IYDLE.js.map} +0 -0
  141. /package/dist/{models-CJPAQK5R.js.map → models-SEQZHZFT.js.map} +0 -0
  142. /package/dist/{pdf-ocr-76SAUXMZ.js.map → pdf-ocr-UUBWBEHL.js.map} +0 -0
  143. /package/dist/{pdf-ocr-CPU55CVF.js.map → pdf-ocr-VJSF2SQC.js.map} +0 -0
  144. /package/dist/{profile-io-LBM263K7.js.map → profile-io-RUFZOZO6.js.map} +0 -0
  145. /package/dist/{rasterize-46RG4ZF6.js.map → rasterize-IOYXR5SI.js.map} +0 -0
  146. /package/dist/{redact-7ILEAUTS.js.map → redact-NMIKVHMP.js.map} +0 -0
  147. /package/dist/{render-NGP5T24S.js.map → render-YZ2TEC3N.js.map} +0 -0
  148. /package/dist/{seal-RGUPOLXD.js.map → seal-ECV44TUH.js.map} +0 -0
  149. /package/dist/{setup-AU4OCHNH.js.map → setup-MPIEP6XT.js.map} +0 -0
  150. /package/dist/{visual-NZLYIYEM.js.map → visual-ICMOWUCR.js.map} +0 -0
  151. /package/dist/{watch-MKPUCPXM.js.map → watch-RYO7MN5B.js.map} +0 -0
package/README.md CHANGED
@@ -84,18 +84,42 @@ MCP 등록 대신 스킬(SKILL.md) 형태로 쓰려면:
84
84
  * **✏️ 양식 자동 채우기**: 공문서 양식 템플릿(신청서, 보고서)에 값을 넣으면 자동으로 빈칸을 채웁니다. 원본 서식(글꼴, 크기, 정렬)을 100% 보존합니다.
85
85
  * **👓 내장 OCR — API 키 없이 (v4.2)**: 스캔본·이미지 PDF와 PNG/JPG/WebP 이미지를 **로컬 CPU 추론**(PP-OCRv5 korean)으로 읽습니다. 외부 서비스도 API 키도 없이, 텍스트층이 깨진 페이지만 골라 OCR 하고, 래스터 괘선까지 찾아 스캔본에서도 표를 복원합니다.
86
86
  * **📑 RAG·인용 대응 (v4.1~4.8)**: 헤딩·개조식 위계를 breadcrumb으로 보존한 구조 청크(`--format chunks`)와, 조판 캐시로 복원한 **실제 쪽 번호** 기준 페이지별 마크다운(`pages`)을 냅니다 — 답변에 "몇 쪽" 각주를 붙일 수 있습니다.
87
- * **🕶️ 개인정보 마스킹 (v4.1)**: 주민번호·전화·이메일·카드·계좌를 탐지해 **원본 서식 그대로** 가린 문서를 냅니다 (`kordoc redact`). 자동 검출 보조 도구이므로 공개 전 사람 확인은 필수입니다.
87
+ * **🕶️ 개인정보 마스킹 (v4.1)**: 주민·외국인등록번호·전화·이메일·카드·계좌·사업자등록번호·여권·운전면허(v4.14.4 부터 opt-in 으로 이름·주소)를 탐지해 **원본 서식 그대로** 가린 HWPX/HWP를 냅니다 (`kordoc redact`). 본문·표·머리말/꼬리말·각주·글상자·필드·미리보기(텍스트·이미지)·문서 정보(제목·작성자)까지 가리고, 결과 파일을 다시 훑어 남은 개인정보가 있으면 실패(exit 2)로 알립니다. **PDF·DOCX·XLSX 등은 원본 파일을 고치지 않고 마스킹된 마크다운만** 냅니다 — PDF 자체의 가림(redaction)은 지원하지 않습니다. 이미지 속 글자와 문맥 없는 맨이름은 탐지하지 못하는 자동 검출 보조 도구이므로 공개 전 사람 확인은 필수입니다.
88
88
  * **🤖 AI 에이전트 연동 (MCP)**: `Claude Desktop`, `Cursor`, `Codex`와 같은 도구에서 직접 `kordoc`을 호출해 문서를 읽고 코딩할 수 있습니다.
89
89
 
90
90
  ---
91
91
 
92
+ ## v4.14.4 변경사항
93
+
94
+ 읽기 품질을 한 번 더 점검했습니다. 원본 HWPX 를 정답으로 PDF 글을 재는 벤치를 새로 세워 띄어쓰기·읽기 순서까지 보고, PDF 표·OCR·개인정보 마스킹을 고쳤습니다.
95
+
96
+ - **📊 PDF 표**: 쪽을 넘는 표의 쪼개진 행과 반복 머리 행, 쪽을 넘는 한 칸, 표 위에 걸친 그림·글상자, 셀 간격 표를 바로잡았습니다. hwpx↔pdf 430쌍 1,784표 정확 일치 90.1% → 92.9%, 표 안의 표 63.7% → 72.0%. 텍스트층이 있는 417쌍만 보면 94.5%, 칸 글이 제자리에 있는 비율 0.768 → 0.903.
97
+ - **✍️ PDF 띄어쓰기·줄 이음**: 본문을 시각 줄이 아니라 문단으로 복원합니다. 한글이 글자 단위로 줄바꿈된 문서에서 "재 일학도의용군인"·"또 는" 처럼 낱말 중간에 공백이 들어가던 것을 조사·어미·문서 안 어휘로 판정해 붙입니다. 어절 F1 0.925 → 0.971.
98
+ - **🧮 한컴 밖에서 만든 PDF**: 예산서처럼 글자마다 떨어져 찍힌 숫자("2 0 , 7 7 5" → "20,775"), 행마다 끊어 그은 괘선, 빈칸 위에 따로 얹은 숫자를 바로잡았습니다. PDF 글자 커버리지 0.99569 → 0.99760.
99
+ - **🔎 글자를 곡선으로 그린 PDF**: 한글을 글꼴 대신 곡선으로 그린 쪽은 텍스트층에 한글이 없어 종전엔 경고 없이 한글이 빠졌습니다. 이런 쪽을 찾아(`ocrReason: "vector_text"`) OCR 을 켜면 읽고, 끄면 경고합니다. 표는 그 쪽의 실제 괘선으로 복원합니다.
100
+ - **👓 OCR**: 점선 괘선·색 칸 경계·목차 리더 점·숫자 앞 △ 를 바로잡았습니다. 글자 오류율 0.171 → 0.096(정답지인 텍스트층 수정분 포함), OCR 표 셀 F1 0.440 → 0.618.
101
+ - **🕶️ 개인정보 마스킹 인명·주소** (opt-in, `--rules …,name,address`): 역할어·호칭·라벨·표 머리글 문맥으로 이름을, 도로명·지번 문법으로 주소를 찾아 가립니다(주소는 시·군·구까지 남김). 외부 정답(473문장) 인명 354/411·주소 141/141, 실문서 표본 오탐 0. 공무원 결재란 이름은 정보공개 관행상 공개 대상이라 기본값에는 넣지 않았습니다.
102
+
103
+ ## v4.14.3 변경사항
104
+
105
+ 읽기 품질을 전면 점검했습니다. 코퍼스를 넓히고(rhwp 표본 1,351파일·정책브리핑 hwpx+pdf 짝 200쌍) 포맷마다 정답지 벤치를 다시 세운 뒤 표·PDF·OCR·개인정보 마스킹을 고쳤고, 검색 색인처럼 글자만 필요한 호출자를 위한 두 가지를 더했습니다.
106
+
107
+ - **📊 PDF 표 복원**: 쪽을 넘는 표를 잇고, 표 안의 표를 틀 칸 안에 넣습니다. hwpx↔pdf 430쌍 1,784표 정확 일치 64.1% → 90.1%, 셀 F1 0.790 → 0.945.
108
+ - **🧾 HWP·HWP3·HWPX 표와 주석**: HWP 표를 HWPX 와 같은 규칙으로 배치해 hwp↔hwpx 쌍 3,593표가 모두 정확히 일치합니다(0.824 → 1). HWP3 표 격자를 복원하고, HWPX 각주·미주 번호와 문서 정보(제목·지은이 등)를 읽습니다.
109
+ - **👓 OCR**: 기울기를 보정하고 세로로 이어진 글 상자를 나눠 읽으며 공문서 기호(○·△·따옴표)를 되살립니다. 글자 오류율 0.201 → 0.171, 3° 기울어진 스캔 57.6% → 22.0%.
110
+ - **🕶️ 개인정보 마스킹**: 파일 안 모든 글자 저장소(머리말·각주·글상자·필드·미리보기·문서 정보 등)를 같은 길이로 가리고 결과를 다시 훑습니다. 5,081문서 오탐 387 → 0.
111
+ - **💲 본문의 `$` 가 수식이 되지 않습니다**: 한글 문서(HWPX·HWP·HWP3)의 리터럴 `$` 는 `\$` 로 내고 `$…$` 는 수식에만 씁니다. "echo $HOME $PATH" 같은 글이 마크다운 렌더러에서 수식으로 바뀌던 것. 역슬래시 뒤 구두점("C:\.Pls")에서 역슬래시가 사라지던 것도 고쳤습니다.
112
+ - **📝 미기입 누름틀 안내문을 빼고 읽습니다**: 한컴이 화면에만 흐리게 보이고 인쇄하지 않는 HWPX 안내문을 마크다운에서 뺍니다(블록 글에는 남김). 누름틀을 채우면 수정 표시를 켭니다.
113
+ - **🖼️ 이미지 끄기** (`images: false` / `--no-images`): 결과에 이미지 바이트를 싣지 않습니다. 그림 자리 표시(`![image](…)`)와 본문 글자는 그대로라 그림 위치는 알 수 있습니다. PDF 는 PNG 인코딩을 건너뜁니다. 그림 많은 PDF 실측: JSON 출력 200MB → 9.5MB, CPU 시간 30% 감소.
114
+ - **🚀 상주 파싱 워커** (`kordoc parse-worker`): 프로세스를 띄워 둔 채 stdin 요청 한 줄마다 한 줄 JSON 을 돌려줍니다. 파일마다 node 를 새로 띄우지 않아 대량 색인에 알맞습니다. 응답에 메모리 사용량(`rss`)을 실어 호스트가 워커 교체 시점을 정할 수 있습니다.
115
+
92
116
  ## v4.14.2 변경사항
93
117
 
94
118
  공문서 출력을 한글 2024 실렌더 PDF로 다시 대조해 "진짜 공문처럼" 다듬었습니다.
95
119
 
96
120
  - **📐 항목 둘째 줄이 첫 줄 내용과 정확히 맞습니다**: 부호(□ ○ - ※ 1. 가.) 뒤를 공백이 아니라 탭으로 두고, 한글의 "내어쓰기용 자동 탭"을 씁니다. 양쪽 정렬이 부호 뒤 공백을 늘려 첫 줄 내용이 밀리던 것, 자간을 줄인 줄에서 내용이 당겨지던 것이 구조적으로 없어졌습니다. 기안문·보고서·계획서·통지·회의록·업무보고·개조식·보도자료 전부.
97
121
  - **✂️ 낱말이 줄 끝에서 쪼개지지 않습니다**: 본문을 어절 단위 줄바꿈으로 바꾸고("동대/문" → "동대문") 외톨이줄 보호를 켰습니다. 날짜·금액은 묶음 빈칸으로 묶어 "2026. 1. / 22." 처럼 갈라지지 않습니다. 곧은따옴표는 ‘ ’ “ ”로 바꿉니다.
98
- - **📏 한글 글꼴 실폭으로 조판을 재현합니다**: 한컴돋움·HY견고딕·휴먼명조 등 8종의 실제 글자 폭으로 줄바꿈을 계산해(한글 2024 PDF 154줄 전부 일치), 짧은 꼬리 줄은 자간으로 올리고 긴 어절이 넘어가 공백이 크게 벌어지는 줄은 자간을 살짝 줄여 메웁니다. 종전처럼 한 줄을 과하게 눌러 옆 줄과 밀도가 달라 보이는 일이 줄었습니다.
122
+ - **📏 한글 글꼴 실폭으로 조판을 재현합니다**: 한컴돋움·HY견고딕·휴먼명조 등 8종의 실제 글자 폭으로 줄바꿈을 계산합니다(한글 2024 PDF 154줄 전부 일치). 마지막 줄이 짧으면(줄 폭 절반 이하) 자간·장평을 최대 15%까지 줄여 한 줄 올리고, 한 줄에 맞춘다는 판단엔 2% 여유를 둬 한글에서 넘치지 않게 했습니다. 공백 없는 긴 목록("구(강남·강서·…·종로)가")처럼 한 줄보다 긴 어절이 있는 문단은 그 문단만 글자 단위로 조판하되 가운뎃점·쉼표 뒤에서만 끊어, 앞 줄이 크게 벌어지지 않습니다.
99
123
  - **🧩 PPTX 감지** (#80, PR #81 @LimePencil): PPTX 를 넣으면 "HWPX 섹션 없음" 대신 지원하지 않는 형식이라고 알려 줍니다.
100
124
  - **🧹 기술부채**: 1,200줄 넘던 `mcp.ts`·`cli.ts`·`hwp5/parser.ts`·`svg-render.ts` 를 기능별 모듈로 나눴고, 파서가 묶음 빈칸을 버리던 것과 `kordoc render` 미리보기의 탭 폭·줄 끝 공백 정렬을 고쳤습니다.
101
125
 
@@ -898,6 +922,24 @@ CLI로도: `kordoc render 결재문서.hwpx -o 결재문서.svg` — 조판 캐
898
922
  reflow 조판되며 `--no-reflow`로 끌 수 있습니다 (`--highlight 예산,집행` 지원),
899
923
  연속 렌더는 `kordoc render-worker`(stdin NDJSON, 미리보기 앱 연동용)
900
924
 
925
+ ### 글자만 필요할 때 (이미지 끄기·상주 파싱 워커)
926
+
927
+ ```typescript
928
+ const result = await parse(buffer, { images: false }) // 이미지 바이트 없이 (그림 자리 표시는 남김)
929
+ ```
930
+
931
+ CLI 는 `kordoc 문서.pdf --format json --no-images`. 대량 변환은 `kordoc parse-worker` 를 띄워 두고
932
+ stdin 으로 한 줄씩 요청합니다 (한 줄 = 한 JSON).
933
+
934
+ ```text
935
+ 시작 {"ready":true,"version":"4.14.4","protocol":1}
936
+ 요청 {"id":1,"file":"문서.hwpx","images":false,"ocr":"off"}
937
+ 응답 {"id":1,"rss":183500800,"result":{ …--format json 과 같은 결과, 실패도 success:false 로… }}
938
+ 종료 {"cmd":"quit"} (또는 stdin 닫기)
939
+ ```
940
+
941
+ `ocr` 은 `"off"`·`"auto"`(OCR 필요 페이지만)·`"force"`, 그 밖에 `formulaOcr`·`password` 를 받습니다.
942
+
901
943
  ### 페이지 범위 지정
902
944
 
903
945
  ```typescript
@@ -946,7 +988,7 @@ for (const p of r.pageQuality ?? []) {
946
988
  }
947
989
  ```
948
990
 
949
- 신호 키: `textChars`, `hangulRatio`, `controlCharRatio`, `replacementCharRatio`, `puaRatio` / `needsOcr` (페이지·문서 단위) / `ocrReason` (`low_text` | `high_pua` | `high_control` | `high_replacement`).
991
+ 신호 키: `textChars`, `hangulRatio`, `controlCharRatio`, `replacementCharRatio`, `puaRatio` / `needsOcr` (페이지·문서 단위) / `ocrReason` (`low_text` | `high_pua` | `high_control` | `high_replacement` | `garbled_hangul` | `vector_text`). `vector_text` 는 글자를 곡선으로 그려 텍스트층에 글이 없는 쪽입니다(v4.14.4).
950
992
 
951
993
  ## CLI
952
994
 
@@ -973,8 +1015,11 @@ npx kordoc patch 원본.hwpx 편집.md -o 반영.hwpx # 서식 보존 라
973
1015
  npx kordoc seal 신청서.hwpx --image 도장.png --anchor "(인)" -o 날인.hwpx # 도장/서명 날인
974
1016
  npx kordoc validate 산출물.hwpx # HWPX 구조 검증 (ZIP·필수 파트·XML)
975
1017
  npx kordoc lint 보고서.md # 공문서 표기법 검수 19룰 — 입력은 md/txt('-'=stdin), error 있으면 exit 1
976
- npx kordoc redact 민원서류.hwpx -o 마스킹.hwpx # 개인정보 탐지 + 서식 보존 마스킹 (v4.1)
1018
+ npx kordoc redact 민원서류.hwpx -o 마스킹.hwpx # 개인정보 탐지 + 서식 보존 마스킹 (머리말·각주·미리보기·문서 정보 포함, 잔존 재검사 — 남으면 exit 2)
977
1019
  npx kordoc redact 민원서류.hwpx --mask-char '*' -o 마스킹.hwpx # 마스크 문자 지정 (기본 ●)
1020
+ npx kordoc redact 계약서.hwp --rules rrn,phone,crn --json --dry-run # 룰 선택(법인등록번호 crn·IP 는 opt-in) + 위치별 리포트만
1021
+ npx kordoc redact 민원서류.hwpx --rules rrn,phone,email,name,address -o 마스킹.hwpx # 이름·주소까지 (opt-in)
1022
+ npx kordoc redact 공문.pdf # PDF·DOCX 등은 원본을 고치지 않고 마스킹된 마크다운(.redacted.md)만 — PDF 가림 미지원
978
1023
  npx kordoc profile 기관서식.hwpx # 표 서식 프로필(JSON) 추출 → generate --profile 로 재현
979
1024
  npx kordoc render 결재문서.hwpx -o 미리보기.svg # 레이아웃 보존 SVG 렌더 (캐시 없는 문서는 자동 reflow 조판, --no-reflow로 끔)
980
1025
  npx kordoc render 결재문서.hwpx --reflow-mode charAll -o 미리보기.svg # reflow 줄바꿈: keep(어절, 기본) | charAll(글자)
@@ -1091,7 +1136,7 @@ codex mcp add kordoc -- npx -y kordoc mcp
1091
1136
  | `generate_document` | 마크다운(표·수식·차트 포함) → HWPX 생성, 공문서 프리셋 (v3.5) |
1092
1137
  | `place_seal` | 도장/서명 이미지를 앵커 문구 위에 부유 배치 (v3.16) |
1093
1138
  | `render_document` | HWPX·HWP를 조판 그대로 PNG/JPEG 이미지(응답)·SVG/HTML/PDF 파일로 렌더 — 생성·수정 결과를 AI가 눈으로 검증 (v4.1, HWP·포맷 확장 v4.13.1) |
1094
- | `redact_document` | 개인정보(주민번호·전화·이메일·카드·계좌) 탐지 + 서식 보존 마스킹, 리포트 반환 (v4.1) |
1139
+ | `redact_document` | 개인정보(주민·외국인등록번호·전화·이메일·카드·계좌·사업자등록번호·여권·운전면허, opt-in 법인등록번호·IP·이름·주소) 탐지 + 서식 보존 마스킹 — HWPX/HWP는 머리말·각주·미리보기·문서 정보까지 가리고 잔존 재검사, 그 외 포맷은 마스킹된 마크다운만 (v4.1) |
1095
1140
  | `parse_chunks` | RAG용 구조 청크 JSON — 헤딩·개조식 위계 breadcrumb + 표 독립 청크 (v4.1) |
1096
1141
  | `crop_regions` | 렌더 영역(표·이미지·문단·도형)을 페이지 이미지에서 실배율로 잘라 저장 + regions.json (v4.13) |
1097
1142
  | `extract_tables` | 표 분류(데이터표/조직도류/불확실) + 페이지·bbox + 정책별 crop — 조직도는 이미지로, 데이터표는 구조로 (v4.13.1) |
@@ -1148,7 +1193,7 @@ PPTX는 감지만 지원합니다. `parse()`는 PPTX 입력에 `success: false`,
1148
1193
  | `placeSealHwpx(buffer, seals)` | 도장/서명 이미지를 앵커 문구 위에 부유 배치 (v3.16) |
1149
1194
  | `validateHwpx(buffer)` | HWPX 구조 검증 — ZIP·mimetype·필수 파트·XML 웰폼드 (v3.16) |
1150
1195
  | `lintGongmunText(text, { document? })` | 공문서 표기법 검수 19룰 + AI 슬롭 2룰 — 텍스트/마크다운 입력 (v4.0.1, v4.12.1 보강). `document: true` 면 붙임/"끝." 문서 단위 검사 포함 |
1151
- | `redactMarkdown(text, options?)` / `redactText(...)` | 개인정보 탐지 + 마스킹 (기본 룰: 주민번호·전화·이메일·카드·계좌, v4.1) |
1196
+ | `redactMarkdown(text, options?)` / `redactText(...)` | 개인정보 탐지 + 마스킹 (기본 룰: 주민·외국인등록번호·전화·이메일·카드·계좌·사업자등록번호·여권·운전면허, opt-in: 법인등록번호·IP — 텍스트 단위, 파일 단위 마스킹은 CLI `redact`/MCP `redact_document`, v4.1) |
1152
1197
  | `blocksToChunks(blocks, options?)` | RAG용 구조 청크 — 헤딩·개조식 위계 breadcrumb + 표 독립 청크 (v4.1) |
1153
1198
  | `blocksToMarkdown(blocks)` | IRBlock[] → Markdown 문자열 |
1154
1199
  | `blocksToPages(blocks)` | IRBlock[] → `[{ pageNumber, markdown }]` 페이지별 마크다운 (v4.8) |
@@ -51,22 +51,22 @@ import {
51
51
  unknownFontWarnings,
52
52
  usesGaejosikMunche,
53
53
  validateHwpx
54
- } from "./chunk-EISO3POT.js";
54
+ } from "./chunk-XVXHXQIO.js";
55
55
  import {
56
56
  applySplices,
57
57
  buildParagraphSplices,
58
58
  buildRangeSplices,
59
59
  placeSealHwpx,
60
60
  scanSectionXml
61
- } from "./chunk-ZWOFQRIB.js";
61
+ } from "./chunk-HIJUKZHM.js";
62
62
  import {
63
63
  DEFAULT_REDACT_RULES,
64
64
  redactMarkdown,
65
65
  redactText
66
- } from "./chunk-DZIXKL7E.js";
66
+ } from "./chunk-DPWWXUOG.js";
67
67
  import {
68
68
  blocksToChunks
69
- } from "./chunk-EDNJLUSE.js";
69
+ } from "./chunk-VJIL6PIL.js";
70
70
  import {
71
71
  SPACE_EM_FIXED,
72
72
  SPACE_EM_FONT,
@@ -91,19 +91,20 @@ import {
91
91
  renderSceneToHtml,
92
92
  simulateWrap,
93
93
  simulateWrapKeepWord
94
- } from "./chunk-TSFS5VKD.js";
94
+ } from "./chunk-D4U2TXDC.js";
95
95
  import "./chunk-7FCNOKOR.js";
96
- import "./chunk-7IAQ4HOK.js";
96
+ import "./chunk-6BWU6SQX.js";
97
97
  import "./chunk-MOL7MDBG.js";
98
- import "./chunk-ILU76UF3.js";
98
+ import "./chunk-YAKC46HZ.js";
99
99
  import {
100
100
  blocksToMarkdown,
101
+ flattenLayoutTables,
101
102
  hasStructuredCellContent
102
- } from "./chunk-QC5T6D4J.js";
103
- import "./chunk-C7FQBSON.js";
103
+ } from "./chunk-NLU6LBLY.js";
104
+ import "./chunk-V3DX4DUF.js";
104
105
  import {
105
106
  VERSION
106
- } from "./chunk-UET462HB.js";
107
+ } from "./chunk-TITDJWVZ.js";
107
108
  export {
108
109
  BUILTIN_TEMPLATES,
109
110
  DEFAULT_REDACT_RULES,
@@ -140,6 +141,7 @@ export {
140
141
  fillHwpx,
141
142
  fillWithUniqueGuard,
142
143
  fitRatioForFewerLines,
144
+ flattenLayoutTables,
143
145
  formatFillValue,
144
146
  gongmunLintWarnings,
145
147
  hasStructuredCellContent,
@@ -192,4 +194,4 @@ export {
192
194
  usesGaejosikMunche,
193
195
  validateHwpx
194
196
  };
195
- //# sourceMappingURL=-AQKXLFIM.js.map
197
+ //# sourceMappingURL=-Z4TJ3QGK.js.map
@@ -1,6 +1,237 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkHR2XNERBcjs = require('./chunk-HR2XNERB.cjs');
3
+ var _chunkWJHAU73Bcjs = require('./chunk-WJHAU73B.cjs');
4
+
5
+ // src/shared/symbol-fonts.ts
6
+ var WINGDINGS = [
7
+ "\u{1F589}",
8
+ "\u2702",
9
+ "\u2701",
10
+ "\u{1F453}",
11
+ "\u{1F56D}",
12
+ "\u{1F56E}",
13
+ "\u{1F56F}",
14
+ "\u{1F57F}",
15
+ "\u2706",
16
+ "\u{1F582}",
17
+ "\u{1F583}",
18
+ "\u{1F4EA}",
19
+ "\u{1F4EB}",
20
+ "\u{1F4EC}",
21
+ "\u{1F4ED}",
22
+ "\u{1F4C1}",
23
+ "\u{1F4C2}",
24
+ "\u{1F4C4}",
25
+ "\u{1F5CF}",
26
+ "\u{1F5D0}",
27
+ "\u{1F5C4}",
28
+ "\u231B",
29
+ "\u{1F5AE}",
30
+ "\u{1F5B0}",
31
+ "\u{1F5B2}",
32
+ "\u{1F5B3}",
33
+ "\u{1F5B4}",
34
+ "\u{1F5AB}",
35
+ "\u{1F5AC}",
36
+ "\u2707",
37
+ "\u270D",
38
+ "\u{1F58E}",
39
+ "\u270C",
40
+ "\u{1F44C}",
41
+ "\u{1F44D}",
42
+ "\u{1F44E}",
43
+ "\u261C",
44
+ "\u261E",
45
+ "\u261D",
46
+ "\u261F",
47
+ "\u{1F590}",
48
+ "\u263A",
49
+ "\u{1F610}",
50
+ "\u2639",
51
+ "\u{1F4A3}",
52
+ "\u2620",
53
+ "\u{1F3F3}",
54
+ "\u{1F3F1}",
55
+ "\u2708",
56
+ "\u263C",
57
+ "\u{1F4A7}",
58
+ "\u2744",
59
+ "\u{1F546}",
60
+ "\u271E",
61
+ "\u{1F548}",
62
+ "\u2720",
63
+ "\u2721",
64
+ "\u262A",
65
+ "\u262F",
66
+ "\u0950",
67
+ "\u2638",
68
+ "\u2648",
69
+ "\u2649",
70
+ "\u264A",
71
+ "\u264B",
72
+ "\u264C",
73
+ "\u264D",
74
+ "\u264E",
75
+ "\u264F",
76
+ "\u2650",
77
+ "\u2651",
78
+ "\u2652",
79
+ "\u2653",
80
+ "\u{1F670}",
81
+ "\u{1F675}",
82
+ "\u25CF",
83
+ "\u25CB",
84
+ "\u25A0",
85
+ "\u25A1",
86
+ "\u25A1",
87
+ "\u2751",
88
+ "\u2752",
89
+ "\u2B27",
90
+ "\u29EB",
91
+ "\u25C6",
92
+ "\u2756",
93
+ "\u2B25",
94
+ "\u2327",
95
+ "\u2BB9",
96
+ "\u2318",
97
+ "\u{1F3F5}",
98
+ "\u{1F3F6}",
99
+ "\u{1F676}",
100
+ "\u{1F677}",
101
+ "",
102
+ "\u24EA",
103
+ "\u2460",
104
+ "\u2461",
105
+ "\u2462",
106
+ "\u2463",
107
+ "\u2464",
108
+ "\u2465",
109
+ "\u2466",
110
+ "\u2467",
111
+ "\u2468",
112
+ "\u2469",
113
+ "\u24FF",
114
+ "\u2776",
115
+ "\u2777",
116
+ "\u2778",
117
+ "\u2779",
118
+ "\u277A",
119
+ "\u277B",
120
+ "\u277C",
121
+ "\u277D",
122
+ "\u277E",
123
+ "\u277F",
124
+ "\u{1F662}",
125
+ "\u{1F660}",
126
+ "\u{1F661}",
127
+ "\u{1F663}",
128
+ "\u{1F65E}",
129
+ "\u{1F65C}",
130
+ "\u{1F65D}",
131
+ "\u{1F65F}",
132
+ "\xB7",
133
+ "\u2022",
134
+ "\u25AA",
135
+ "\u26AA",
136
+ "\u25CB",
137
+ "\u25EF",
138
+ "\u25C9",
139
+ "\u25CE",
140
+ "\u{1F53F}",
141
+ "\u25AA",
142
+ "\u25FB",
143
+ "\u{1F7C2}",
144
+ "\u2726",
145
+ "\u2605",
146
+ "\u2736",
147
+ "\u2734",
148
+ "\u2739",
149
+ "\u2735",
150
+ "\u2BD0",
151
+ "\u2316",
152
+ "\u27E1",
153
+ "\u2311",
154
+ "\u2BD1",
155
+ "\u272A",
156
+ "\u2730",
157
+ "\u{1F550}",
158
+ "\u{1F551}",
159
+ "\u{1F552}",
160
+ "\u{1F553}",
161
+ "\u{1F554}",
162
+ "\u{1F555}",
163
+ "\u{1F556}",
164
+ "\u{1F557}",
165
+ "\u{1F558}",
166
+ "\u{1F559}",
167
+ "\u{1F55A}",
168
+ "\u{1F55B}",
169
+ "\u2BB0",
170
+ "\u2BB1",
171
+ "\u2BB2",
172
+ "\u2BB3",
173
+ "\u2BB4",
174
+ "\u2BB5",
175
+ "\u2BB6",
176
+ "\u2BB7",
177
+ "\u{1F66A}",
178
+ "\u{1F66B}",
179
+ "\u{1F655}",
180
+ "\u{1F654}",
181
+ "\u{1F657}",
182
+ "\u{1F656}",
183
+ "\u{1F650}",
184
+ "\u{1F651}",
185
+ "\u{1F652}",
186
+ "\u{1F653}",
187
+ "\u232B",
188
+ "\u2326",
189
+ "\u2B98",
190
+ "\u2B9A",
191
+ "\u2B99",
192
+ "\u2B9B",
193
+ "\u2B88",
194
+ "\u2B8A",
195
+ "\u2B89",
196
+ "\u2B8B",
197
+ "\u2190",
198
+ "\u2192",
199
+ "\u2191",
200
+ "\u2193",
201
+ "\u2196",
202
+ "\u2197",
203
+ "\u2199",
204
+ "\u2198",
205
+ "\u2B05",
206
+ "\u2794",
207
+ "\u2B06",
208
+ "\u2B07",
209
+ "\u2B09",
210
+ "\u2B08",
211
+ "\u2B0B",
212
+ "\u2B0A",
213
+ "\u21E6",
214
+ "\u21E8",
215
+ "\u21E7",
216
+ "\u21E9",
217
+ "\u2B04",
218
+ "\u21F3",
219
+ "\u2B00",
220
+ "\u2B01",
221
+ "\u2B03",
222
+ "\u2B02",
223
+ "\u{1F8AC}",
224
+ "\u{1F8AD}",
225
+ "\u2717",
226
+ "\u2714",
227
+ "\u2612",
228
+ "\u2611",
229
+ ""
230
+ ];
231
+ function wingdingsChar(code) {
232
+ if (code < 33 || code > 255) return void 0;
233
+ return WINGDINGS[code - 33] || void 0;
234
+ }
4
235
 
5
236
  // src/shared/pua.ts
6
237
  var BMP_SYMBOL_MAP = {
@@ -188,7 +419,7 @@ var BOXED_NUMBER_START = 983729;
188
419
  var BOXED_NUMBER_END = 983748;
189
420
  function mapPuaChar(code) {
190
421
  if (code >= 61472 && code <= 61695) {
191
- return BMP_SYMBOL_MAP[code - 61440];
422
+ return _nullishCoalesce(BMP_SYMBOL_MAP[code - 61440], () => ( wingdingsChar(code - 61440)));
192
423
  }
193
424
  if (code >= BOXED_NUMBER_START && code <= BOXED_NUMBER_END) {
194
425
  return String.fromCodePoint(9312 + (code - BOXED_NUMBER_START));
@@ -264,32 +495,54 @@ function buildTable(rows, options) {
264
495
  }
265
496
  function buildTableDirect(rows, numRows, options) {
266
497
  let maxCols = 0;
498
+ const rowCap = Math.min(MAX_ROWS, numRows + rows.reduce((n, r) => n + r.length, 0) + 1);
267
499
  for (const row of rows) {
268
500
  for (const cell of row) {
269
501
  const end = (_nullishCoalesce(cell.colAddr, () => ( 0))) + cell.colSpan;
270
502
  if (end > maxCols) maxCols = end;
503
+ if (cell.rowAddr !== void 0 && cell.rowAddr >= numRows) numRows = Math.min(cell.rowAddr + 1, rowCap);
271
504
  }
272
505
  }
273
506
  if (maxCols > MAX_COLS) maxCols = MAX_COLS;
274
- if (maxCols === 0) return { rows: 0, cols: 0, cells: [], hasHeader: false };
507
+ if (maxCols === 0 || numRows === 0) return { rows: 0, cols: 0, cells: [], hasHeader: false };
275
508
  const grid = Array.from(
276
509
  { length: numRows },
277
510
  () => Array.from({ length: maxCols }, () => ({ text: "", colSpan: 1, rowSpan: 1 }))
278
511
  );
512
+ const owner = Array.from({ length: numRows }, () => new Array(maxCols));
279
513
  const anchorCols = /* @__PURE__ */ new Set();
280
- for (const row of rows) {
281
- for (const cell of row) {
282
- const r = _nullishCoalesce(cell.rowAddr, () => ( 0));
283
- const c = _nullishCoalesce(cell.colAddr, () => ( 0));
284
- if (r >= numRows || c >= maxCols || r < 0 || c < 0) continue;
514
+ for (let ri = 0; ri < rows.length; ri++) {
515
+ for (const cell of rows[ri]) {
516
+ const text = _optionalChain([options, 'optionalAccess', _3 => _3.keepEmptyParagraphs]) ? cell.text : cell.text.trim();
517
+ const r = Math.min(_nullishCoalesce(cell.rowAddr, () => ( ri)), numRows - 1);
518
+ let c = _nullishCoalesce(cell.colAddr, () => ( 0));
519
+ if (r < 0 || c < 0) continue;
520
+ if (cell.colAddr === void 0) while (c < maxCols - 1 && owner[r][c]) c++;
521
+ if (c >= maxCols) c = maxCols - 1;
522
+ const own = owner[r][c];
523
+ if (own) {
524
+ if (text.trim()) own.text = own.text ? `${own.text}
525
+ ${text}` : text;
526
+ continue;
527
+ }
528
+ let colSpan = Math.max(1, Math.min(cell.colSpan, maxCols - c));
529
+ for (let dc = 1; dc < colSpan; dc++) if (owner[r][c + dc]) {
530
+ colSpan = dc;
531
+ break;
532
+ }
533
+ let rowSpan = Math.max(1, Math.min(cell.rowSpan, numRows - r));
534
+ for (let dr = 1; dr < rowSpan; dr++) {
535
+ if (owner[r + dr].slice(c, c + colSpan).some(Boolean)) {
536
+ rowSpan = dr;
537
+ break;
538
+ }
539
+ }
540
+ const ir = { text, colSpan, rowSpan };
285
541
  anchorCols.add(c);
286
- grid[r][c] = { text: _optionalChain([options, 'optionalAccess', _3 => _3.keepEmptyParagraphs]) ? cell.text : cell.text.trim(), colSpan: cell.colSpan, rowSpan: cell.rowSpan };
287
- for (let dr = 0; dr < cell.rowSpan; dr++) {
288
- for (let dc = 0; dc < cell.colSpan; dc++) {
289
- if (dr === 0 && dc === 0) continue;
290
- if (r + dr < numRows && c + dc < maxCols) {
291
- grid[r + dr][c + dc] = { text: "", colSpan: 1, rowSpan: 1 };
292
- }
542
+ for (let dr = 0; dr < rowSpan; dr++) {
543
+ for (let dc = 0; dc < colSpan; dc++) {
544
+ owner[r + dr][c + dc] = ir;
545
+ grid[r + dr][c + dc] = dr === 0 && dc === 0 ? ir : { text: "", colSpan: 1, rowSpan: 1 };
293
546
  }
294
547
  }
295
548
  }
@@ -306,6 +559,11 @@ function trimAndReturn(grid, numRows, maxCols, anchorCols, options) {
306
559
  }
307
560
  if (effectiveCols < maxCols && effectiveCols > 0) {
308
561
  const trimmed = grid.map((row) => row.slice(0, effectiveCols));
562
+ for (const row of trimmed) {
563
+ for (let c = 0; c < row.length; c++) {
564
+ if (c + row[c].colSpan > effectiveCols) row[c].colSpan = effectiveCols - c;
565
+ }
566
+ }
309
567
  return { rows: numRows, cols: effectiveCols, cells: trimmed, hasHeader: numRows > 1 };
310
568
  }
311
569
  return { rows: numRows, cols: maxCols, cells: grid, hasHeader: numRows > 1 };
@@ -318,13 +576,16 @@ function convertTableToText(rows) {
318
576
  function escapeGfm(text) {
319
577
  const NUL = String.fromCharCode(0);
320
578
  const spans = [];
321
- const masked = text.replace(/!\[[^\]]*\]\([^)\n]*\)|\]\((?:https?:|mailto:|tel:|#)[^)\n]*\)|\$\$[^$]*\$\$|\$[^$\n]*\$/gi, (m) => {
579
+ const masked = text.replace(/!\[[^\]]*\]\([^)\n]*\)|\]\((?:https?:|mailto:|tel:|#)[^)\n]*\)|(?<!\\)\$\$(?:\\[\s\S]|[^\\$])*\$\$|(?<!\\)\$(?:\\[^\n]|[^\\$\n])*\$/gi, (m) => {
322
580
  spans.push(m);
323
581
  return NUL + (spans.length - 1) + NUL;
324
582
  });
325
- const escaped = masked.replace(/([~*_`])/g, "\\$1");
583
+ const escaped = masked.replace(/\\(?=[!-#%-\/:-@\[-\x60{}~])/g, "\\\\").replace(/([~*_`])/g, "\\$1").replace(/(?<!\\)\|/g, "\\|").replace(/^([ \t]*)(?=#{1,6}(?:[ \t]|$))/gm, "$1\\").replace(/<(?!\/?u>)(?=[A-Za-z/!?])/g, "\\<");
326
584
  return escaped.replace(new RegExp(NUL + "(\\d+)" + NUL, "g"), (_, n) => spans[Number(n)]);
327
585
  }
586
+ function escapeLiteralDollar(text) {
587
+ return text.includes("$") ? text.replace(/\$/g, "\\$") : text;
588
+ }
328
589
  var HWP_SHAPE_ALT_TEXT_RE = /^(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?$/gm;
329
590
  function sanitizePua(text) {
330
591
  return mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "");
@@ -423,6 +684,7 @@ function dedupeRunningHeaders(blocks) {
423
684
  function spansToMarkdown(spans) {
424
685
  let out = "";
425
686
  for (const s of spans) {
687
+ if (s.placeholder) continue;
426
688
  const text = sanitizePua(_nullishCoalesce(s.text, () => ( "")));
427
689
  if (!text) continue;
428
690
  let marker = s.code ? "`" : s.bold && s.italic ? "***" : s.bold ? "**" : s.italic ? "*" : "";
@@ -455,7 +717,8 @@ function blocksToMarkdown(blocks) {
455
717
  if (block.type === "heading" && block.text) {
456
718
  const prefix = "#".repeat(Math.min(block.level || 2, 6));
457
719
  const headingText = sanitizeText(block.text);
458
- if (headingText) lines.push("", `${prefix} ${escapeGfm(headingText)}`, "");
720
+ const note = block.footnoteText ? ` (\uC8FC: ${block.footnoteText})` : "";
721
+ if (headingText) lines.push("", `${prefix} ${escapeGfm(headingText + note)}`, "");
459
722
  continue;
460
723
  }
461
724
  if (block.type === "image" && block.text) {
@@ -501,7 +764,7 @@ function blocksToMarkdown(blocks) {
501
764
  if (_optionalChain([block, 'access', _26 => _26.spans, 'optionalAccess', _27 => _27.length])) {
502
765
  let rendered = spansToMarkdown(block.spans);
503
766
  if (block.href) {
504
- const href = _chunkHR2XNERBcjs.sanitizeHref.call(void 0, block.href);
767
+ const href = _chunkWJHAU73Bcjs.sanitizeHref.call(void 0, block.href);
505
768
  if (href) rendered = `[${rendered}](${href})`;
506
769
  }
507
770
  if (block.footnoteText) rendered += ` (\uC8FC: ${block.footnoteText})`;
@@ -509,7 +772,7 @@ function blocksToMarkdown(blocks) {
509
772
  continue;
510
773
  }
511
774
  if (block.href) {
512
- const href = _chunkHR2XNERBcjs.sanitizeHref.call(void 0, block.href);
775
+ const href = _chunkWJHAU73Bcjs.sanitizeHref.call(void 0, block.href);
513
776
  if (href) text = `[${text}](${href})`;
514
777
  }
515
778
  if (block.footnoteText) {
@@ -549,16 +812,22 @@ function hasStructuredCellContent(table) {
549
812
  }
550
813
  return false;
551
814
  }
815
+ function noteSuffix(b) {
816
+ return b.footnoteText && b.text ? ` (\uC8FC: ${b.footnoteText})` : "";
817
+ }
818
+ function visibleText(b) {
819
+ return _optionalChain([b, 'access', _31 => _31.spans, 'optionalAccess', _32 => _32.some, 'call', _33 => _33((s) => s.placeholder)]) ? b.spans.filter((s) => !s.placeholder).map((s) => s.text).join("") : _nullishCoalesce(b.text, () => ( ""));
820
+ }
552
821
  function cellInnerHtml(cell) {
553
- if (_optionalChain([cell, 'access', _31 => _31.blocks, 'optionalAccess', _32 => _32.length])) {
822
+ if (_optionalChain([cell, 'access', _34 => _34.blocks, 'optionalAccess', _35 => _35.length])) {
554
823
  return cell.blocks.map((b) => {
555
824
  if (b.type === "table" && b.table) {
556
825
  const cap = b.table.caption ? sanitizeText(b.table.caption) : "";
557
826
  return (cap ? cap + "<br>" : "") + tableToHtml(b.table);
558
827
  }
559
828
  if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
560
- const t = sanitizeText(_nullishCoalesce(b.text, () => ( "")));
561
- return t ? t.replace(/\n/g, "<br>") : "";
829
+ const t = sanitizeText(visibleText(b));
830
+ return t ? (t + noteSuffix(b)).replace(/\n/g, "<br>") : "";
562
831
  }).filter(Boolean).join("<br>");
563
832
  }
564
833
  return sanitizeText(cell.text).replace(/\n/g, "<br>");
@@ -583,7 +852,7 @@ function tableToHtml(table) {
583
852
  const rowHtml = [];
584
853
  for (let c = 0; c < numCols; c++) {
585
854
  if (skip.has(`${r},${c}`)) continue;
586
- const cell = _optionalChain([cells, 'access', _33 => _33[r], 'optionalAccess', _34 => _34[c]]);
855
+ const cell = _optionalChain([cells, 'access', _36 => _36[r], 'optionalAccess', _37 => _37[c]]);
587
856
  if (!cell) continue;
588
857
  for (let dr = 0; dr < cell.rowSpan; dr++) {
589
858
  for (let dc = 0; dc < cell.colSpan; dc++) {
@@ -598,7 +867,7 @@ function tableToHtml(table) {
598
867
  const attrStr = attrs.length ? " " + attrs.join(" ") : "";
599
868
  rowHtml.push(`<${tag}${attrStr}>${text}</${tag}>`);
600
869
  }
601
- if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
870
+ lines.push(`<tr>${rowHtml.join("")}</tr>`);
602
871
  }
603
872
  lines.push("</table>");
604
873
  return lines.join("\n");
@@ -606,7 +875,8 @@ function tableToHtml(table) {
606
875
  function tableToMarkdown(table) {
607
876
  if (table.rows === 0 || table.cols === 0) return "";
608
877
  const { cells, rows: numRows, cols: numCols } = table;
609
- if ((hasMergedCells(table) || hasStructuredCellContent(table)) && !tableContainsInlineMath(table)) {
878
+ if (hasStructuredCellContent(table)) return tableToHtml(table);
879
+ if (hasMergedCells(table) && !tableContainsInlineMath(table)) {
610
880
  return tableToHtml(table);
611
881
  }
612
882
  if (numRows === 1 && numCols === 1) {
@@ -628,9 +898,9 @@ function tableToMarkdown(table) {
628
898
  for (let r = 0; r < numRows; r++) {
629
899
  for (let c = 0; c < numCols; c++) {
630
900
  if (skip.has(`${r},${c}`)) continue;
631
- const cell = _optionalChain([cells, 'access', _35 => _35[r], 'optionalAccess', _36 => _36[c]]);
901
+ const cell = _optionalChain([cells, 'access', _38 => _38[r], 'optionalAccess', _39 => _39[c]]);
632
902
  if (!cell) continue;
633
- display[r][c] = (_optionalChain([cell, 'access', _37 => _37.blocks, 'optionalAccess', _38 => _38.some, 'call', _39 => _39((b) => b.spans || b.type === "image" && b.text)]) ? cell.blocks.map((b) => b.type === "image" && b.text ? `![image](${b.text})` : b.spans ? spansToMarkdown(b.spans) : escapeGfm(sanitizeText(_nullishCoalesce(b.text, () => ( ""))))).filter(Boolean).join("<br>") : escapeGfm(sanitizeText(cell.text)).replace(/\n/g, "<br>")).replace(/\|/g, "\\|");
903
+ display[r][c] = (_optionalChain([cell, 'access', _40 => _40.blocks, 'optionalAccess', _41 => _41.some, 'call', _42 => _42((b) => b.spans || b.type === "image" && b.text)]) ? cell.blocks.map((b) => b.type === "image" && b.text ? `![image](${b.text})` : b.spans ? spansToMarkdown(b.spans) + escapeGfm(noteSuffix(b)) : escapeGfm(sanitizeText(_nullishCoalesce(b.text, () => ( ""))) + noteSuffix(b))).filter(Boolean).join("<br>") : escapeGfm(sanitizeText(cell.text)).replace(/\n/g, "<br>")).replace(/(?<!\\)\|/g, "\\|");
634
904
  for (let dr = 0; dr < cell.rowSpan; dr++) {
635
905
  for (let dc = 0; dc < cell.colSpan; dc++) {
636
906
  if (dr === 0 && dc === 0) continue;
@@ -791,7 +1061,7 @@ function encodePng(width, height, rgba) {
791
1061
  return out;
792
1062
  }
793
1063
  function inlineImagesIntoMarkdown(markdown, images, opts) {
794
- const compress = _optionalChain([opts, 'optionalAccess', _40 => _40.compress]) !== false;
1064
+ const compress = _optionalChain([opts, 'optionalAccess', _43 => _43.compress]) !== false;
795
1065
  const uris = /* @__PURE__ */ new Map();
796
1066
  for (const img of images) {
797
1067
  let bytes = img.data;
@@ -829,5 +1099,9 @@ function inlineImagesIntoMarkdown(markdown, images, opts) {
829
1099
 
830
1100
 
831
1101
 
832
- exports.mapPuaText = mapPuaText; exports.MAX_COLS = MAX_COLS; exports.MAX_ROWS = MAX_ROWS; exports.buildTable = buildTable; exports.convertTableToText = convertTableToText; exports.flattenLayoutTables = flattenLayoutTables; exports.dedupeRunningHeaders = dedupeRunningHeaders; exports.blocksToMarkdown = blocksToMarkdown; exports.hasStructuredCellContent = hasStructuredCellContent; exports.encodePng = encodePng; exports.inlineImagesIntoMarkdown = inlineImagesIntoMarkdown;
833
- //# sourceMappingURL=chunk-QOMNEWCQ.cjs.map
1102
+
1103
+
1104
+
1105
+
1106
+ exports.WINGDINGS = WINGDINGS; exports.mapPuaText = mapPuaText; exports.MAX_COLS = MAX_COLS; exports.MAX_ROWS = MAX_ROWS; exports.buildTable = buildTable; exports.convertTableToText = convertTableToText; exports.escapeGfm = escapeGfm; exports.escapeLiteralDollar = escapeLiteralDollar; exports.flattenLayoutTables = flattenLayoutTables; exports.dedupeRunningHeaders = dedupeRunningHeaders; exports.blocksToMarkdown = blocksToMarkdown; exports.hasStructuredCellContent = hasStructuredCellContent; exports.noteSuffix = noteSuffix; exports.encodePng = encodePng; exports.inlineImagesIntoMarkdown = inlineImagesIntoMarkdown;
1107
+ //# sourceMappingURL=chunk-5LAZQCUJ.cjs.map