fix: LaTeX 백슬래시 복원, HWP 인코딩 오류 수정, 다이어그램 감지 튜닝
- pdf.py: marker-pdf가 손상시킨 \times·\frac 등 LaTeX 백슬래시 복원 후처리 추가 - pdf.py: 다이어그램 감지에 절대 drawing 수 기준(>= 40) 추가 (대형 엔지니어링 페이지 대응) - hwp.py: COM 타임아웃 메시지의 em dash → ASCII (cp949 인코딩 오류 수정) - convert.py: Windows stdout/stderr UTF-8 강제 설정 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,35 @@ from pathlib import Path
|
||||
import fitz # PyMuPDF
|
||||
from PIL import Image
|
||||
|
||||
# marker-pdf가 Python 이스케이프로 LaTeX 백슬래시를 손상시키는 버그 복원.
|
||||
# \frac → \x0c(formfeed) + rac : \x0c = \f 대체
|
||||
# \times → 완전히 사라짐(imes만 남음): \t = tab이 strip됨
|
||||
# \beta → \x08(backspace) + eta : \x08 = \b 대체
|
||||
_CTRL_TO_LETTER = [('\x0c', 'f'), ('\x09', 't'), ('\x08', 'b'), ('\x0b', 'v')]
|
||||
# \t가 완전히 사라진 케이스의 알려진 suffix → 원래 명령어
|
||||
_DROPPED_T_SUFFIXES = {
|
||||
'imes': 'times', 'ext': 'text', 'heta': 'theta',
|
||||
'au': 'tau', 'op': 'top', 'ilde': 'tilde',
|
||||
}
|
||||
|
||||
|
||||
def _fix_marker_latex(text: str) -> str:
|
||||
"""marker-pdf가 손상시킨 LaTeX 백슬래시를 복구한다."""
|
||||
# 제어문자가 LaTeX 이스케이프 첫 글자를 대체한 경우 복원
|
||||
# e.g. \x0c + rac → \f + rac = \frac
|
||||
for ctrl, letter in _CTRL_TO_LETTER:
|
||||
text = text.replace(ctrl, '\\' + letter)
|
||||
|
||||
# \t가 완전히 사라진 케이스: 수식 내에서 suffix 단독 출현 → 전체 명령어로 복원
|
||||
# e.g. $imes → $\times, {1.1 imes → {1.1 \times
|
||||
for suffix, full_cmd in _DROPPED_T_SUFFIXES.items():
|
||||
text = re.sub(
|
||||
rf'(?<=[${{\s\^_{{]){re.escape(suffix)}(?=[^a-zA-Z])',
|
||||
rf'\\{full_cmd}',
|
||||
text,
|
||||
)
|
||||
return text
|
||||
|
||||
|
||||
# ── 페이지 분류 ───────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -62,14 +91,16 @@ def classify_page(page: fitz.Page, doc: fitz.Document) -> str:
|
||||
|
||||
text_density = text_len / page_area * 10_000 # 면적 대비 문자 수
|
||||
|
||||
# 벡터 드로잉 밀도 (flowchart, CAD export 등은 수백 개 드로잉 포함)
|
||||
drawing_density = len(drawings) / page_area * 10_000
|
||||
# 벡터 드로잉: 밀도 + 절대 개수 모두 확인
|
||||
# (엔지니어링 대형 페이지는 밀도가 낮아도 드로잉 수가 많으면 다이어그램)
|
||||
n_drawings = len(drawings)
|
||||
drawing_density = n_drawings / page_area * 10_000
|
||||
|
||||
# 1) 텍스트가 충분하면 텍스트 계열
|
||||
# (drawing이 많아도 text_density > 4면 표 테두리/장식선으로 간주)
|
||||
if text_density > 4:
|
||||
if not images:
|
||||
return "text"
|
||||
# 이미지가 있어도 작은 이미지(로고 등)면 text
|
||||
large_images = [
|
||||
img for img in images
|
||||
if doc.extract_image(img[0])["width"] > 150
|
||||
@@ -77,8 +108,10 @@ def classify_page(page: fitz.Page, doc: fitz.Document) -> str:
|
||||
]
|
||||
return "text-with-photo" if large_images else "text"
|
||||
|
||||
# 2) 벡터 드로잉이 많으면 다이어그램
|
||||
if drawing_density > 1.5:
|
||||
# 2) 텍스트가 적을 때 벡터 드로잉이 많으면 다이어그램
|
||||
# - 밀도 기준: 소형 페이지
|
||||
# - 절대 수 기준: 대형 엔지니어링 페이지 (도면, 플로우차트 등)
|
||||
if drawing_density > 1.5 or n_drawings >= 40:
|
||||
return "diagram"
|
||||
|
||||
# 3) 래스터 이미지가 있으면 다이어그램 여부 분석
|
||||
@@ -170,6 +203,7 @@ def convert_pdf(pdf_path: Path, output_dir: Path) -> dict:
|
||||
converter = PdfConverter(artifact_dict=create_model_dict())
|
||||
rendered = converter(str(pdf_path))
|
||||
full_text, _, marker_images = text_from_rendered(rendered)
|
||||
full_text = _fix_marker_latex(full_text)
|
||||
|
||||
# marker 추출 이미지 저장
|
||||
if marker_images:
|
||||
|
||||
Reference in New Issue
Block a user