Spaces:
Running
on
Zero
Running
on
Zero
HyunsangJoo
commited on
Commit
·
209d412
1
Parent(s):
8a27d1f
옵션 추가, 로컬 ui 테스트, readme 업데이트
Browse files- README.md +52 -0
- app.py +33 -5
- dots_ocr/utils/pptx_generator.py +53 -22
README.md
CHANGED
|
@@ -12,3 +12,55 @@ short_description: Convert pdf/image to pptx with text ready to edit.
|
|
| 12 |
---
|
| 13 |
|
| 14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
## 🏗 전체 과정 요약 (4단계)
|
| 19 |
+
|
| 20 |
+
마치 **[사진 촬영] → [탐정 수사] → [설계도 작성] → [건물 조립]** 과정과 같습니다.
|
| 21 |
+
|
| 22 |
+
### 1단계: 사진 찍기 (이미지 준비) 📸
|
| 23 |
+
* **담당:** `dots_ocr/utils/doc_utils.py`
|
| 24 |
+
* **내용:** PDF 파일은 AI가 바로 보기 어렵습니다. 그래서 책을 스캔하듯이 모든 페이지를 **고화질 이미지(사진)**로 변환합니다.
|
| 25 |
+
* **핵심 기술:** 작은 글씨도 잘 보이게 **2배 확대(Zoom-in)**해서 찍습니다.
|
| 26 |
+
|
| 27 |
+
### 2단계: 탐정 로봇의 분석 (좌표 & 내용 추출) 🕵️
|
| 28 |
+
* **담당:** `dots_ocr/parser.py`, `dots_ocr/model/inference.py`
|
| 29 |
+
* **내용:** 똑똑한 AI(탐정)가 사진을 보고 두 가지를 찾아냅니다.
|
| 30 |
+
1. **글자 읽기:** "여기에 '안녕하세요'라고 써있네."
|
| 31 |
+
2. **위치 찾기(좌표):** "이 글자는 종이 왼쪽에서 10칸, 위에서 20칸 떨어진 곳에 있어."
|
| 32 |
+
* **핵심 기술:** AI에게 이미지를 보낼 때 `<|img|>` 같은 특수 암호를 써서 전송합니다.
|
| 33 |
+
|
| 34 |
+
### 3단계: 설계도 그리기 (데이터 정리) 📝
|
| 35 |
+
* **담당:** `dots_ocr/utils/output_cleaner.py`
|
| 36 |
+
* **내용:** AI가 찾아낸 뒤죽박죽인 정보들을 깔끔한 **설계도(JSON)**로 정리합니다.
|
| 37 |
+
* "1번 상자: [10, 20] 위치, 내용 '제목'"
|
| 38 |
+
* "2번 상자: [50, 80] 위치, 내용 '본문'"
|
| 39 |
+
|
| 40 |
+
### 4단계: 건축가의 조립 (PPT 만들기) 🔨
|
| 41 |
+
* **담당:** `dots_ocr/utils/pptx_generator.py`
|
| 42 |
+
* **내용:** 빈 PPT 슬라이드를 꺼내고, 설계도를 보며 글상자와 표를 배치합니다.
|
| 43 |
+
* **핵심 기술:**
|
| 44 |
+
* **비율 계산:** 사진 크기와 PPT 크기가 달라도 비율(%)을 계산해서 정확한 위치에 넣습니다.
|
| 45 |
+
* **폰트 계산:** 글자가 상자 밖으로 튀어나가지 않게 적절한 폰트 크기를 자동으로 계산합니다.
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
## 🔍 심화 탐구: 좌표와 크기의 비밀
|
| 50 |
+
|
| 51 |
+
### Q1. 좌표는 어떻게 측정하나요?
|
| 52 |
+
* **기준점:** 종이의 **왼쪽 맨 위 모서리**가 `(0, 0)`입니다.
|
| 53 |
+
* **방향:** 오른쪽으로 갈수록 x값이 커지고, 아래로 갈수록 y값이 커집니다.
|
| 54 |
+
* **단위:** **픽셀(Pixel)**이라는 점의 개수를 셉니다.
|
| 55 |
+
|
| 56 |
+
### Q2. 이미지를 확대하면 좌표가 망가지지 않나요?
|
| 57 |
+
망가지지 않습니다! **비율(Scale)**을 사용하기 때문입니다.
|
| 58 |
+
* **작은 사진:** 가로 100 중에 10 위치 (10%)
|
| 59 |
+
* **큰 사진:** 가로 200 중에 20 위치 (10%)
|
| 60 |
+
* **결론:** 크기는 달라져도 "전체에서 10% 지점"이라는 사실은 변하지 않으므로, PPT에서도 제자리에 들어갑니다.
|
| 61 |
+
|
| 62 |
+
### Q3. PPT 페이지 크기는 어떻게 정하나요?
|
| 63 |
+
프로그램이 눈치껏 상황에 맞춰 결정합니다.
|
| 64 |
+
1. **배경 이미지가 있을 때:** 원본 이미지 크기를 그대로 따라갑니다. (가장 정확!)
|
| 65 |
+
2. **이미지가 없을 때:** 가장 멀리 있는 좌표(오른쪽 끝, 아래쪽 끝)를 찾아서 크기를 짐작합니다.
|
| 66 |
+
3. **최종 설정:** PPT 프로그램에 맞게 **가로를 10인치(약 25.4cm)**로 고정하고, 세로 길이는 비율에 맞춰 자동으로 늘리거나 줄입니다.
|
app.py
CHANGED
|
@@ -781,7 +781,9 @@ def save_results(
|
|
| 781 |
all_results: List[Dict],
|
| 782 |
file_stem: str,
|
| 783 |
include_background: bool,
|
| 784 |
-
images: List[Image.Image]
|
|
|
|
|
|
|
| 785 |
) -> Tuple[Optional[str], Optional[str], List[str], List[List[Dict]]]:
|
| 786 |
"""결과 저장 로직 분리 (부분 저장 지원용)"""
|
| 787 |
try:
|
|
@@ -799,7 +801,11 @@ def save_results(
|
|
| 799 |
background_images = images[:processed_count] if include_background else []
|
| 800 |
|
| 801 |
page_count, box_count = build_pptx_from_results(
|
| 802 |
-
all_results,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 803 |
)
|
| 804 |
print(f"📊 PPTX 저장: {page_count}페이지, {box_count}개 텍스트박스")
|
| 805 |
|
|
@@ -838,6 +844,8 @@ def process_document(
|
|
| 838 |
prompt_mode: str,
|
| 839 |
quality_mode: str,
|
| 840 |
include_background: bool,
|
|
|
|
|
|
|
| 841 |
) -> Tuple[Optional[str], Optional[str], Optional[str], str, Any, str]:
|
| 842 |
"""문서 처리 메인 함수"""
|
| 843 |
|
|
@@ -863,6 +871,7 @@ def process_document(
|
|
| 863 |
print(f" 프롬프트 모드: {prompt_mode}")
|
| 864 |
print(f" 품질 모드: {quality_mode} ({target_max_pixels} pixels)")
|
| 865 |
print(f" 배경 포함: {include_background}")
|
|
|
|
| 866 |
print("=" * 60)
|
| 867 |
|
| 868 |
# 프롬프트 모드 변환
|
|
@@ -927,7 +936,12 @@ def process_document(
|
|
| 927 |
|
| 928 |
# 결과 저장 (정상 완료 시)
|
| 929 |
pptx_path, json_path, layout_img_paths, json_data = save_results(
|
| 930 |
-
all_results,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 931 |
)
|
| 932 |
|
| 933 |
# Markdown 결과
|
|
@@ -961,7 +975,12 @@ def process_document(
|
|
| 961 |
if all_results:
|
| 962 |
print(f"⚠️ 에러 발생! 현재까지 처리된 {len(all_results)}페이지 결과를 저장합니다...")
|
| 963 |
pptx_path, json_path, layout_img_paths, json_data = save_results(
|
| 964 |
-
all_results,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 965 |
)
|
| 966 |
|
| 967 |
combined_markdown = "\n\n---\n\n".join(all_markdown) if all_markdown else f"*처리 도중 오류 발생: {str(e)}*"
|
|
@@ -1119,6 +1138,15 @@ with gr.Blocks(title="PDF/Image to PPTX Converter") as demo:
|
|
| 1119 |
label="PPTX에 배경 이미지 포함",
|
| 1120 |
value=True
|
| 1121 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1122 |
|
| 1123 |
run_btn = gr.Button("🚀 변환 실행", variant="primary", size="lg")
|
| 1124 |
|
|
@@ -1177,7 +1205,7 @@ with gr.Blocks(title="PDF/Image to PPTX Converter") as demo:
|
|
| 1177 |
|
| 1178 |
run_btn.click(
|
| 1179 |
fn=process_document,
|
| 1180 |
-
inputs=[file_input, prompt_mode, quality_mode, include_background],
|
| 1181 |
outputs=[pptx_output, json_output, processed_image, extracted_content, layout_json, log_output]
|
| 1182 |
)
|
| 1183 |
|
|
|
|
| 781 |
all_results: List[Dict],
|
| 782 |
file_stem: str,
|
| 783 |
include_background: bool,
|
| 784 |
+
images: List[Image.Image],
|
| 785 |
+
use_dark_mode: bool = False,
|
| 786 |
+
show_border: bool = False
|
| 787 |
) -> Tuple[Optional[str], Optional[str], List[str], List[List[Dict]]]:
|
| 788 |
"""결과 저장 로직 분리 (부분 저장 지원용)"""
|
| 789 |
try:
|
|
|
|
| 801 |
background_images = images[:processed_count] if include_background else []
|
| 802 |
|
| 803 |
page_count, box_count = build_pptx_from_results(
|
| 804 |
+
all_results,
|
| 805 |
+
background_images,
|
| 806 |
+
Path(pptx_path),
|
| 807 |
+
use_dark_mode=use_dark_mode,
|
| 808 |
+
show_border=show_border
|
| 809 |
)
|
| 810 |
print(f"📊 PPTX 저장: {page_count}페이지, {box_count}개 텍스트박스")
|
| 811 |
|
|
|
|
| 844 |
prompt_mode: str,
|
| 845 |
quality_mode: str,
|
| 846 |
include_background: bool,
|
| 847 |
+
use_dark_mode: bool,
|
| 848 |
+
show_border: bool,
|
| 849 |
) -> Tuple[Optional[str], Optional[str], Optional[str], str, Any, str]:
|
| 850 |
"""문서 처리 메인 함수"""
|
| 851 |
|
|
|
|
| 871 |
print(f" 프롬프트 모드: {prompt_mode}")
|
| 872 |
print(f" 품질 모드: {quality_mode} ({target_max_pixels} pixels)")
|
| 873 |
print(f" 배경 포함: {include_background}")
|
| 874 |
+
print(f" 스타일 옵션: 다크모드={use_dark_mode}, 테두리={show_border}")
|
| 875 |
print("=" * 60)
|
| 876 |
|
| 877 |
# 프롬프트 모드 변환
|
|
|
|
| 936 |
|
| 937 |
# 결과 저장 (정상 완료 시)
|
| 938 |
pptx_path, json_path, layout_img_paths, json_data = save_results(
|
| 939 |
+
all_results,
|
| 940 |
+
file_stem,
|
| 941 |
+
include_background,
|
| 942 |
+
images,
|
| 943 |
+
use_dark_mode=use_dark_mode,
|
| 944 |
+
show_border=show_border
|
| 945 |
)
|
| 946 |
|
| 947 |
# Markdown 결과
|
|
|
|
| 975 |
if all_results:
|
| 976 |
print(f"⚠️ 에러 발생! 현재까지 처리된 {len(all_results)}페이지 결과를 저장합니다...")
|
| 977 |
pptx_path, json_path, layout_img_paths, json_data = save_results(
|
| 978 |
+
all_results,
|
| 979 |
+
file_stem,
|
| 980 |
+
include_background,
|
| 981 |
+
images,
|
| 982 |
+
use_dark_mode=use_dark_mode,
|
| 983 |
+
show_border=show_border
|
| 984 |
)
|
| 985 |
|
| 986 |
combined_markdown = "\n\n---\n\n".join(all_markdown) if all_markdown else f"*처리 도중 오류 발생: {str(e)}*"
|
|
|
|
| 1138 |
label="PPTX에 배경 이미지 포함",
|
| 1139 |
value=True
|
| 1140 |
)
|
| 1141 |
+
with gr.Row():
|
| 1142 |
+
use_dark_mode = gr.Checkbox(
|
| 1143 |
+
label="다크 모드 (검정 배경/흰 글씨)",
|
| 1144 |
+
value=False
|
| 1145 |
+
)
|
| 1146 |
+
show_border = gr.Checkbox(
|
| 1147 |
+
label="텍스트 박스 테두리 표시",
|
| 1148 |
+
value=False
|
| 1149 |
+
)
|
| 1150 |
|
| 1151 |
run_btn = gr.Button("🚀 변환 실행", variant="primary", size="lg")
|
| 1152 |
|
|
|
|
| 1205 |
|
| 1206 |
run_btn.click(
|
| 1207 |
fn=process_document,
|
| 1208 |
+
inputs=[file_input, prompt_mode, quality_mode, include_background, use_dark_mode, show_border],
|
| 1209 |
outputs=[pptx_output, json_output, processed_image, extracted_content, layout_json, log_output]
|
| 1210 |
)
|
| 1211 |
|
dots_ocr/utils/pptx_generator.py
CHANGED
|
@@ -254,22 +254,30 @@ def _add_textbox(
|
|
| 254 |
scale_y,
|
| 255 |
category: str = "",
|
| 256 |
page_height: int = 0,
|
| 257 |
-
|
|
|
|
| 258 |
) -> None:
|
| 259 |
"""
|
| 260 |
텍스트 박스 추가 (AutoSize 강제 적용 - 순서 수정 최종 버전)
|
| 261 |
"""
|
| 262 |
try:
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
|
|
|
| 267 |
|
| 268 |
if width <= 0 or height <= 0:
|
| 269 |
return
|
| 270 |
|
| 271 |
textbox = slide.shapes.add_textbox(left, top, width, height)
|
| 272 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
# 1. 텍스트 프레임 설정
|
| 274 |
text_frame = textbox.text_frame
|
| 275 |
text_frame.clear()
|
|
@@ -301,7 +309,7 @@ def _add_textbox(
|
|
| 301 |
run.font.size = _calculate_font_size(width, height, cleaned_text, category, is_bold=is_bold)
|
| 302 |
|
| 303 |
# 4. 색상 및 배경
|
| 304 |
-
if
|
| 305 |
run.font.color.rgb = RGBColor(255, 255, 255)
|
| 306 |
textbox.fill.solid()
|
| 307 |
textbox.fill.fore_color.rgb = RGBColor(0, 0, 0)
|
|
@@ -345,7 +353,9 @@ def _add_table(
|
|
| 345 |
bbox,
|
| 346 |
html_text,
|
| 347 |
scale_x,
|
| 348 |
-
scale_y
|
|
|
|
|
|
|
| 349 |
) -> None:
|
| 350 |
"""PPTX 슬라이드에 표 추가"""
|
| 351 |
try:
|
|
@@ -360,11 +370,11 @@ def _add_table(
|
|
| 360 |
if rows == 0 or cols == 0:
|
| 361 |
return
|
| 362 |
|
| 363 |
-
# 2. 위치 및 크기 계산
|
| 364 |
-
left = int(bbox[0] * scale_x)
|
| 365 |
-
top = int(bbox[1] * scale_y)
|
| 366 |
-
width = int((bbox[2] - bbox[0]) * scale_x)
|
| 367 |
-
height = int((bbox[3] - bbox[1]) * scale_y)
|
| 368 |
|
| 369 |
# 3. 표 생성
|
| 370 |
graphic_frame = slide.shapes.add_table(rows, cols, left, top, width, height)
|
|
@@ -387,17 +397,34 @@ def _add_table(
|
|
| 387 |
for paragraph in cell.text_frame.paragraphs:
|
| 388 |
paragraph.font.size = Pt(9)
|
| 389 |
|
| 390 |
-
# 헤더 행(첫 행)
|
| 391 |
if r_idx == 0:
|
| 392 |
paragraph.font.bold = True
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
else:
|
| 397 |
-
# 나머지
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 401 |
|
| 402 |
except Exception as e:
|
| 403 |
print(f"Table add failed: {e}")
|
|
@@ -407,6 +434,8 @@ def build_pptx_from_results(
|
|
| 407 |
parse_results: List[Dict],
|
| 408 |
background_images: List[Image.Image],
|
| 409 |
output_path: Path,
|
|
|
|
|
|
|
| 410 |
) -> Tuple[int, int]:
|
| 411 |
"""파싱 결과로부터 PPTX 생성"""
|
| 412 |
prs = Presentation()
|
|
@@ -467,7 +496,7 @@ def build_pptx_from_results(
|
|
| 467 |
|
| 468 |
if category == "Table":
|
| 469 |
if not text.strip(): continue
|
| 470 |
-
_add_table(slide, bbox, text, scale_x, scale_y)
|
| 471 |
total_boxes += 1
|
| 472 |
continue
|
| 473 |
|
|
@@ -477,7 +506,9 @@ def build_pptx_from_results(
|
|
| 477 |
_add_textbox(
|
| 478 |
slide, bbox, text, scale_x, scale_y,
|
| 479 |
category=category,
|
| 480 |
-
page_height=page_height
|
|
|
|
|
|
|
| 481 |
)
|
| 482 |
total_boxes += 1
|
| 483 |
|
|
|
|
| 254 |
scale_y,
|
| 255 |
category: str = "",
|
| 256 |
page_height: int = 0,
|
| 257 |
+
use_dark_mode: bool = False,
|
| 258 |
+
show_border: bool = False
|
| 259 |
) -> None:
|
| 260 |
"""
|
| 261 |
텍스트 박스 추가 (AutoSize 강제 적용 - 순서 수정 최종 버전)
|
| 262 |
"""
|
| 263 |
try:
|
| 264 |
+
# [수정] int(버림) 대신 round(반올림)를 사용하여 좌표 정밀도 향상
|
| 265 |
+
left = int(round(bbox[0] * scale_x))
|
| 266 |
+
top = int(round(bbox[1] * scale_y))
|
| 267 |
+
width = int(round((bbox[2] - bbox[0]) * scale_x))
|
| 268 |
+
height = int(round((bbox[3] - bbox[1]) * scale_y))
|
| 269 |
|
| 270 |
if width <= 0 or height <= 0:
|
| 271 |
return
|
| 272 |
|
| 273 |
textbox = slide.shapes.add_textbox(left, top, width, height)
|
| 274 |
|
| 275 |
+
# [추가] 테두리 옵션
|
| 276 |
+
if show_border:
|
| 277 |
+
line = textbox.line
|
| 278 |
+
line.color.rgb = RGBColor(200, 200, 200) # 연한 회색 테두리
|
| 279 |
+
line.width = Pt(1)
|
| 280 |
+
|
| 281 |
# 1. 텍스트 프레임 설정
|
| 282 |
text_frame = textbox.text_frame
|
| 283 |
text_frame.clear()
|
|
|
|
| 309 |
run.font.size = _calculate_font_size(width, height, cleaned_text, category, is_bold=is_bold)
|
| 310 |
|
| 311 |
# 4. 색상 및 배경
|
| 312 |
+
if use_dark_mode:
|
| 313 |
run.font.color.rgb = RGBColor(255, 255, 255)
|
| 314 |
textbox.fill.solid()
|
| 315 |
textbox.fill.fore_color.rgb = RGBColor(0, 0, 0)
|
|
|
|
| 353 |
bbox,
|
| 354 |
html_text,
|
| 355 |
scale_x,
|
| 356 |
+
scale_y,
|
| 357 |
+
use_dark_mode: bool = False,
|
| 358 |
+
show_border: bool = False
|
| 359 |
) -> None:
|
| 360 |
"""PPTX 슬라이드에 표 추가"""
|
| 361 |
try:
|
|
|
|
| 370 |
if rows == 0 or cols == 0:
|
| 371 |
return
|
| 372 |
|
| 373 |
+
# 2. 위치 및 크기 계산 (반올림 적용)
|
| 374 |
+
left = int(round(bbox[0] * scale_x))
|
| 375 |
+
top = int(round(bbox[1] * scale_y))
|
| 376 |
+
width = int(round((bbox[2] - bbox[0]) * scale_x))
|
| 377 |
+
height = int(round((bbox[3] - bbox[1]) * scale_y))
|
| 378 |
|
| 379 |
# 3. 표 생성
|
| 380 |
graphic_frame = slide.shapes.add_table(rows, cols, left, top, width, height)
|
|
|
|
| 397 |
for paragraph in cell.text_frame.paragraphs:
|
| 398 |
paragraph.font.size = Pt(9)
|
| 399 |
|
| 400 |
+
# 헤더 행(첫 행) 스타일링
|
| 401 |
if r_idx == 0:
|
| 402 |
paragraph.font.bold = True
|
| 403 |
+
if use_dark_mode:
|
| 404 |
+
# 다크모드 헤더: 흰 글씨 / 짙은 회색 배경
|
| 405 |
+
paragraph.font.color.rgb = RGBColor(255, 255, 255)
|
| 406 |
+
cell.fill.solid()
|
| 407 |
+
cell.fill.fore_color.rgb = RGBColor(50, 50, 50)
|
| 408 |
+
else:
|
| 409 |
+
# 기본 헤더: 흰 글씨 / 검정 배경
|
| 410 |
+
paragraph.font.color.rgb = RGBColor(255, 255, 255)
|
| 411 |
+
cell.fill.solid()
|
| 412 |
+
cell.fill.fore_color.rgb = RGBColor(0, 0, 0)
|
| 413 |
else:
|
| 414 |
+
# 나머지 행
|
| 415 |
+
if use_dark_mode:
|
| 416 |
+
# 다크모드 내용: 흰 글씨 / 검정 배경
|
| 417 |
+
paragraph.font.color.rgb = RGBColor(255, 255, 255)
|
| 418 |
+
cell.fill.solid()
|
| 419 |
+
cell.fill.fore_color.rgb = RGBColor(0, 0, 0)
|
| 420 |
+
else:
|
| 421 |
+
# 기본 내용: 검정 글씨 / 흰 배경
|
| 422 |
+
paragraph.font.color.rgb = RGBColor(0, 0, 0)
|
| 423 |
+
cell.fill.solid()
|
| 424 |
+
cell.fill.fore_color.rgb = RGBColor(255, 255, 255)
|
| 425 |
+
|
| 426 |
+
# 표 테두리는 기본적으로 존재하므로 show_border 옵션은 표에서는 생략하거나
|
| 427 |
+
# 필요하다면 별도 스타일 적용 가능 (여기서는 텍스트박스와 일관성을 위해 매개변수만 ��아둠)
|
| 428 |
|
| 429 |
except Exception as e:
|
| 430 |
print(f"Table add failed: {e}")
|
|
|
|
| 434 |
parse_results: List[Dict],
|
| 435 |
background_images: List[Image.Image],
|
| 436 |
output_path: Path,
|
| 437 |
+
use_dark_mode: bool = False,
|
| 438 |
+
show_border: bool = False
|
| 439 |
) -> Tuple[int, int]:
|
| 440 |
"""파싱 결과로부터 PPTX 생성"""
|
| 441 |
prs = Presentation()
|
|
|
|
| 496 |
|
| 497 |
if category == "Table":
|
| 498 |
if not text.strip(): continue
|
| 499 |
+
_add_table(slide, bbox, text, scale_x, scale_y, use_dark_mode=use_dark_mode, show_border=show_border)
|
| 500 |
total_boxes += 1
|
| 501 |
continue
|
| 502 |
|
|
|
|
| 506 |
_add_textbox(
|
| 507 |
slide, bbox, text, scale_x, scale_y,
|
| 508 |
category=category,
|
| 509 |
+
page_height=page_height,
|
| 510 |
+
use_dark_mode=use_dark_mode,
|
| 511 |
+
show_border=show_border
|
| 512 |
)
|
| 513 |
total_boxes += 1
|
| 514 |
|