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