slidegent / src /ppt /template_loader.py
jomasego's picture
Upload folder using huggingface_hub
ad0ebea verified
Raw
History Blame Contribute Delete
3.53 kB
from __future__ import annotations
from collections import Counter
from pathlib import Path
from pptx import Presentation
from src.utils.validators import validate_template
def _rgb_to_tuple(rgb_obj) -> tuple[int, int, int] | None:
if rgb_obj is None:
return None
try:
rgb_str = str(rgb_obj)
if len(rgb_str) == 6:
return (int(rgb_str[0:2], 16), int(rgb_str[2:4], 16), int(rgb_str[4:6], 16))
except Exception:
return None
return None
def parse_template_style(template_path: str | Path) -> dict:
validated_path = validate_template(template_path)
prs = Presentation(str(validated_path))
if len(prs.slides) == 0:
return {
"layout_name_to_index": {layout.name: idx for idx, layout in enumerate(prs.slide_layouts)},
"preferred_layout_indices": {
"title_slide": 0,
"content_slide": min(1, len(prs.slide_layouts) - 1),
"split_slide": min(3, len(prs.slide_layouts) - 1),
},
"font": {},
"dominant_text_color": None,
"shape_positions": [],
"reference_slide_index": 0,
}
reference_slide_index = 1 if len(prs.slides) > 1 else 0
reference_slide = prs.slides[reference_slide_index]
font_candidates = []
colors = []
shape_positions = []
for shape in reference_slide.shapes:
shape_positions.append(
{
"left": int(shape.left),
"top": int(shape.top),
"width": int(shape.width),
"height": int(shape.height),
}
)
if not getattr(shape, "has_text_frame", False):
continue
for paragraph in shape.text_frame.paragraphs:
for run in paragraph.runs:
font = run.font
rgb = _rgb_to_tuple(getattr(font.color, "rgb", None))
if rgb:
colors.append(rgb)
font_candidates.append(
{
"name": font.name,
"size_pt": float(font.size.pt) if font.size else None,
"bold": font.bold,
"italic": font.italic,
"color_rgb": rgb,
}
)
chosen_font = {}
if font_candidates:
# Pick the most complete entry to reuse as baseline style.
chosen_font = max(
font_candidates,
key=lambda entry: sum(
1
for key in ("name", "size_pt", "bold", "italic", "color_rgb")
if entry.get(key) is not None
),
)
dominant_color = Counter(colors).most_common(1)[0][0] if colors else chosen_font.get("color_rgb")
layout_name_to_index = {layout.name: idx for idx, layout in enumerate(prs.slide_layouts)}
return {
"layout_name_to_index": layout_name_to_index,
"preferred_layout_indices": {
"title_slide": 0,
"content_slide": min(1, len(prs.slide_layouts) - 1),
"split_slide": min(3, len(prs.slide_layouts) - 1),
},
"font": chosen_font,
"dominant_text_color": dominant_color,
"shape_positions": shape_positions,
"reference_slide_index": reference_slide_index,
}
def load_template(template_path: str | Path) -> dict:
"""Backward-compatible alias used by service/tests."""
return parse_template_style(template_path)