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:
@@ -13,6 +13,10 @@ import json
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||||
|
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
||||||
|
|
||||||
SUPPORTED = {'.pdf', '.hwp', '.hwpx', '.hml', '.html', '.htm'}
|
SUPPORTED = {'.pdf', '.hwp', '.hwpx', '.hml', '.html', '.htm'}
|
||||||
SKIP_NAMES = {'README.md', 'CLAUDE.md', 'AGENT_GUIDE.md'}
|
SKIP_NAMES = {'README.md', 'CLAUDE.md', 'AGENT_GUIDE.md'}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ def _com_hwp_to_hml(hwp_path: Path, hml_path: Path, timeout: int = 15) -> bool:
|
|||||||
t.start()
|
t.start()
|
||||||
t.join(timeout)
|
t.join(timeout)
|
||||||
if t.is_alive():
|
if t.is_alive():
|
||||||
print(f' COM 타임아웃 ({timeout}초) — pyhwp로 전환')
|
print(f' COM 타임아웃 ({timeout}초) -> pyhwp로 전환')
|
||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,35 @@ from pathlib import Path
|
|||||||
import fitz # PyMuPDF
|
import fitz # PyMuPDF
|
||||||
from PIL import Image
|
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 # 면적 대비 문자 수
|
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) 텍스트가 충분하면 텍스트 계열
|
# 1) 텍스트가 충분하면 텍스트 계열
|
||||||
|
# (drawing이 많아도 text_density > 4면 표 테두리/장식선으로 간주)
|
||||||
if text_density > 4:
|
if text_density > 4:
|
||||||
if not images:
|
if not images:
|
||||||
return "text"
|
return "text"
|
||||||
# 이미지가 있어도 작은 이미지(로고 등)면 text
|
|
||||||
large_images = [
|
large_images = [
|
||||||
img for img in images
|
img for img in images
|
||||||
if doc.extract_image(img[0])["width"] > 150
|
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"
|
return "text-with-photo" if large_images else "text"
|
||||||
|
|
||||||
# 2) 벡터 드로잉이 많으면 다이어그램
|
# 2) 텍스트가 적을 때 벡터 드로잉이 많으면 다이어그램
|
||||||
if drawing_density > 1.5:
|
# - 밀도 기준: 소형 페이지
|
||||||
|
# - 절대 수 기준: 대형 엔지니어링 페이지 (도면, 플로우차트 등)
|
||||||
|
if drawing_density > 1.5 or n_drawings >= 40:
|
||||||
return "diagram"
|
return "diagram"
|
||||||
|
|
||||||
# 3) 래스터 이미지가 있으면 다이어그램 여부 분석
|
# 3) 래스터 이미지가 있으면 다이어그램 여부 분석
|
||||||
@@ -170,6 +203,7 @@ def convert_pdf(pdf_path: Path, output_dir: Path) -> dict:
|
|||||||
converter = PdfConverter(artifact_dict=create_model_dict())
|
converter = PdfConverter(artifact_dict=create_model_dict())
|
||||||
rendered = converter(str(pdf_path))
|
rendered = converter(str(pdf_path))
|
||||||
full_text, _, marker_images = text_from_rendered(rendered)
|
full_text, _, marker_images = text_from_rendered(rendered)
|
||||||
|
full_text = _fix_marker_latex(full_text)
|
||||||
|
|
||||||
# marker 추출 이미지 저장
|
# marker 추출 이미지 저장
|
||||||
if marker_images:
|
if marker_images:
|
||||||
|
|||||||
Reference in New Issue
Block a user