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:
minsung
2026-04-20 15:56:11 +09:00
parent 2ec2759a20
commit 1d40d90242
3 changed files with 44 additions and 6 deletions

View File

@@ -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: