Spaces:
Running
Running
# slide_themes.py | |
from pptx.util import Inches, Pt | |
from pptx.dml.color import RGBColor | |
from pptx.enum.text import PP_ALIGN | |
class SlideThemeManager: | |
def __init__(self): | |
# 16:9 簡報尺寸 (單位:英吋) | |
self.slide_width = Inches(13.333) # 16:9 寬度 | |
self.slide_height = Inches(7.5) # 16:9 高度 | |
# 版型配置 - 重新設計,減少留白,增加多樣化配色,改善文字對比度 | |
self.themes = { | |
"商務專業": { | |
"bg_color": RGBColor(248, 252, 255), # 更淺的藍白背景 | |
"title_color": RGBColor(8, 47, 91), # 更深的藍色標題 | |
"text_color": RGBColor(33, 37, 41), # 深黑文字,提高對比度 | |
"accent_color": RGBColor(52, 144, 220), # 藍色強調 | |
"secondary_color": RGBColor(255, 255, 255), # 純白輔助背景 | |
"content_bg_color": RGBColor(255, 255, 255), # 內容框背景色 | |
"layout": "image_right", | |
"has_gradient": True, | |
"gradient_start": RGBColor(248, 252, 255), | |
"gradient_end": RGBColor(235, 245, 255), | |
# 縮小邊距,擴大內容區域 | |
"title_area": {"left": 0.2, "top": 0.1, "width": 12.9, "height": 1.0}, | |
"content_area": {"left": 0.2, "top": 1.3, "width": 6.8, "height": 5.9}, | |
"image_area": {"left": 7.2, "top": 1.3, "width": 5.9, "height": 4.8} | |
}, | |
"科技創新": { | |
"bg_color": RGBColor(18, 28, 42), # 深藍黑背景 | |
"title_color": RGBColor(120, 255, 235), # 更亮的青綠標題 | |
"text_color": RGBColor(245, 248, 252), # 更亮的淺色文字 | |
"accent_color": RGBColor(255, 107, 107), # 紅色強調 | |
"secondary_color": RGBColor(35, 47, 62), # 深灰輔助 | |
"content_bg_color": RGBColor(35, 47, 62), # 內容框背景色 | |
"layout": "image_bottom", | |
"has_gradient": True, | |
"gradient_start": RGBColor(18, 28, 42), | |
"gradient_end": RGBColor(35, 47, 62), | |
"title_area": {"left": 0.2, "top": 0.1, "width": 12.9, "height": 1.0}, | |
"content_area": {"left": 0.2, "top": 1.3, "width": 12.9, "height": 3.2}, | |
"image_area": {"left": 0.2, "top": 4.7, "width": 12.9, "height": 2.6} | |
}, | |
"創意設計": { | |
"bg_color": RGBColor(255, 245, 250), # 更淺的粉色背景 | |
"title_color": RGBColor(136, 14, 79), # 更深的紫色標題 | |
"text_color": RGBColor(33, 33, 33), # 深黑文字,提高對比度 | |
"accent_color": RGBColor(255, 152, 0), # 橙色強調 | |
"secondary_color": RGBColor(255, 255, 255), # 純白輔助 | |
"content_bg_color": RGBColor(255, 255, 255), # 內容框背景色 | |
"layout": "image_left", | |
"has_gradient": True, | |
"gradient_start": RGBColor(255, 245, 250), | |
"gradient_end": RGBColor(250, 224, 235), | |
"title_area": {"left": 0.2, "top": 0.1, "width": 12.9, "height": 1.0}, | |
"content_area": {"left": 6.0, "top": 1.3, "width": 7.1, "height": 5.9}, | |
"image_area": {"left": 0.2, "top": 1.3, "width": 5.6, "height": 4.8} | |
}, | |
"教育學術": { | |
"bg_color": RGBColor(252, 255, 252), # 更淺的綠白背景 | |
"title_color": RGBColor(46, 125, 50), # 更深的綠色標題 | |
"text_color": RGBColor(33, 37, 41), # 深黑文字 | |
"accent_color": RGBColor(255, 193, 7), # 黃色強調 | |
"secondary_color": RGBColor(255, 255, 255), # 純白輔助 | |
"content_bg_color": RGBColor(255, 255, 255), # 內容框背景色 | |
"layout": "image_top", | |
"has_gradient": True, | |
"gradient_start": RGBColor(252, 255, 252), | |
"gradient_end": RGBColor(240, 248, 240), | |
"title_area": {"left": 0.2, "top": 0.1, "width": 12.9, "height": 1.0}, | |
"content_area": {"left": 0.2, "top": 4.0, "width": 12.9, "height": 3.3}, | |
"image_area": {"left": 0.2, "top": 1.3, "width": 12.9, "height": 2.5} | |
}, | |
"現代簡約": { | |
"bg_color": RGBColor(255, 255, 255), # 純白背景 | |
"title_color": RGBColor(33, 37, 41), # 深黑標題 | |
"text_color": RGBColor(33, 37, 41), # 深黑文字,提高對比度 | |
"accent_color": RGBColor(0, 123, 255), # 藍色強調 | |
"secondary_color": RGBColor(248, 249, 250), # 淺灰輔助 | |
"content_bg_color": RGBColor(248, 249, 250), # 內容框背景色 | |
"layout": "image_right", | |
"has_gradient": False, | |
"title_area": {"left": 0.2, "top": 0.1, "width": 12.9, "height": 1.0}, | |
"content_area": {"left": 0.2, "top": 1.3, "width": 6.8, "height": 5.9}, | |
"image_area": {"left": 7.2, "top": 1.3, "width": 5.9, "height": 4.8} | |
}, | |
"溫暖橙色": { | |
"bg_color": RGBColor(255, 252, 245), # 更淺的橙白背景 | |
"title_color": RGBColor(191, 54, 12), # 更深的橙色標題 | |
"text_color": RGBColor(33, 37, 41), # 深黑文字,提高對比度 | |
"accent_color": RGBColor(255, 138, 101), # 淺橙強調 | |
"secondary_color": RGBColor(255, 255, 255), # 純白輔助 | |
"content_bg_color": RGBColor(255, 255, 255), # 內容框背景色 | |
"layout": "image_bottom", | |
"has_gradient": True, | |
"gradient_start": RGBColor(255, 252, 245), | |
"gradient_end": RGBColor(255, 243, 224), | |
"title_area": {"left": 0.2, "top": 0.1, "width": 12.9, "height": 1.0}, | |
"content_area": {"left": 0.2, "top": 1.3, "width": 12.9, "height": 3.2}, | |
"image_area": {"left": 0.2, "top": 4.7, "width": 12.9, "height": 2.6} | |
}, | |
"深邃紫色": { | |
"bg_color": RGBColor(67, 18, 125), # 深紫背景 | |
"title_color": RGBColor(224, 164, 234), # 更亮的淺紫標題 | |
"text_color": RGBColor(255, 255, 255), # 純白文字 | |
"accent_color": RGBColor(255, 204, 0), # 金黃強調 | |
"secondary_color": RGBColor(98, 26, 142), # 中紫輔助 | |
"content_bg_color": RGBColor(98, 26, 142), # 內容框背景色 | |
"layout": "image_left", | |
"has_gradient": True, | |
"gradient_start": RGBColor(67, 18, 125), | |
"gradient_end": RGBColor(118, 30, 152), | |
"title_area": {"left": 0.2, "top": 0.1, "width": 12.9, "height": 1.0}, | |
"content_area": {"left": 6.0, "top": 1.3, "width": 7.1, "height": 5.9}, | |
"image_area": {"left": 0.2, "top": 1.3, "width": 5.6, "height": 4.8} | |
}, | |
"清新藍綠": { | |
"bg_color": RGBColor(240, 255, 252), # 更淺的藍綠背景 | |
"title_color": RGBColor(0, 105, 92), # 更深的藍綠標題 | |
"text_color": RGBColor(33, 37, 41), # 深黑文字 | |
"accent_color": RGBColor(255, 111, 97), # 珊瑚紅強調 | |
"secondary_color": RGBColor(255, 255, 255), # 純白輔助 | |
"content_bg_color": RGBColor(255, 255, 255), # 內容框背景色 | |
"layout": "image_top", | |
"has_gradient": True, | |
"gradient_start": RGBColor(240, 255, 252), | |
"gradient_end": RGBColor(224, 242, 235), | |
"title_area": {"left": 0.2, "top": 0.1, "width": 12.9, "height": 1.0}, | |
"content_area": {"left": 0.2, "top": 4.0, "width": 12.9, "height": 3.3}, | |
"image_area": {"left": 0.2, "top": 1.3, "width": 12.9, "height": 2.5} | |
} | |
} | |
# 圖片風格 | |
self.image_styles = { | |
"professional": "business professional corporate clean", | |
"creative": "creative artistic colorful vibrant", | |
"minimalist": "minimal clean simple white space", | |
"modern": "modern contemporary sleek design", | |
"natural": "natural outdoor organic environment", | |
"technology": "technology digital modern tech innovation" | |
} | |
def get_theme(self, theme_name): | |
"""獲取指定主題""" | |
return self.themes.get(theme_name, self.themes["商務專業"]) | |
def get_all_theme_names(self): | |
"""獲取所有主題名稱""" | |
return list(self.themes.keys()) | |
def apply_background(self, slide, theme): | |
"""應用背景色彩或漸變""" | |
try: | |
# 獲取投影片背景 | |
background = slide.background | |
fill = background.fill | |
if theme.get("has_gradient", False): | |
# 應用漸變背景 | |
fill.gradient() | |
fill.gradient_angle = 45.0 # 45度角漸變 | |
# 設置漸變色彩停靠點 | |
gradient_stops = fill.gradient_stops | |
gradient_stops[0].color.rgb = theme["gradient_start"] | |
gradient_stops[1].color.rgb = theme["gradient_end"] | |
else: | |
# 應用純色背景 | |
fill.solid() | |
fill.fore_color.rgb = theme["bg_color"] | |
except Exception as e: | |
print(f"應用背景錯誤: {e}") | |
def add_decorative_elements(self, slide, theme): | |
"""添加裝飾性元素(邊框、圖形等)""" | |
try: | |
# 根據主題添加裝飾線條或圖形 | |
if theme.get("layout") == "image_right": | |
# 在左側內容區域添加垂直裝飾線 | |
line = slide.shapes.add_connector( | |
connector_type=1, # 直線 | |
begin_x=Inches(0.1), | |
begin_y=Inches(1.3), | |
end_x=Inches(0.1), | |
end_y=Inches(7.2) | |
) | |
line.line.color.rgb = theme["accent_color"] | |
line.line.width = Pt(4) | |
elif theme.get("layout") == "image_bottom": | |
# 在標題下方添加水平裝飾線 | |
line = slide.shapes.add_connector( | |
connector_type=1, # 直線 | |
begin_x=Inches(0.2), | |
begin_y=Inches(1.2), | |
end_x=Inches(13.1), | |
end_y=Inches(1.2) | |
) | |
line.line.color.rgb = theme["accent_color"] | |
line.line.width = Pt(3) | |
except Exception as e: | |
print(f"添加裝飾元素錯誤: {e}") | |
def format_title(self, shape, theme, font_size, font_getter=None): | |
"""格式化標題""" | |
try: | |
paragraph = shape.text_frame.paragraphs[0] | |
if font_getter: | |
paragraph.font.name = font_getter() | |
else: | |
paragraph.font.name = self.get_font_name() | |
paragraph.font.size = Pt(font_size) | |
paragraph.font.color.rgb = theme["title_color"] | |
paragraph.alignment = PP_ALIGN.LEFT | |
paragraph.font.bold = True | |
# 為深色背景的主題添加陰影效果 | |
if self.is_dark_background(theme): | |
try: | |
# 添加文字陰影增強可讀性 | |
paragraph.font.color.rgb = RGBColor(255, 255, 255) | |
except: | |
pass | |
except Exception as e: | |
print(f"格式化標題錯誤: {e}") | |
def format_content(self, paragraph, theme, font_size, font_getter=None): | |
"""格式化內容""" | |
try: | |
if font_getter: | |
paragraph.font.name = font_getter() | |
else: | |
paragraph.font.name = self.get_font_name() | |
paragraph.font.size = Pt(font_size) | |
paragraph.font.color.rgb = theme["text_color"] | |
paragraph.space_before = Pt(6) | |
paragraph.space_after = Pt(6) | |
paragraph.line_spacing = 1.2 | |
except Exception as e: | |
print(f"格式化內容錯誤: {e}") | |
def get_font_name(self): | |
"""獲取中文字型名稱""" | |
import os | |
# 檢查是否有自定義中文字型檔案 | |
font_path = os.path.join(os.path.dirname(__file__), "cht.ttf") | |
if os.path.exists(font_path): | |
return "cht" | |
else: | |
return "Arial Unicode MS" | |
def is_dark_background(self, theme): | |
"""判斷是否為深色背景""" | |
bg_color = theme["bg_color"] | |
# 計算亮度(簡單的RGB平均值判斷) | |
# RGBColor 物件沒有 .r 屬性,需要使用 ._color_val 或直接從 RGB 值計算 | |
try: | |
# 嘗試獲取 RGB 值 | |
r = bg_color._color_val & 0xFF | |
g = (bg_color._color_val >> 8) & 0xFF | |
b = (bg_color._color_val >> 16) & 0xFF | |
brightness = (r + g + b) / 3 | |
return brightness < 128 | |
except: | |
# 如果無法獲取,使用保守判斷 | |
# 檢查是否為已知的深色主題 | |
dark_themes = ["科技創新", "深邃紫色"] | |
theme_name = getattr(theme, 'name', '') | |
return theme_name in dark_themes | |
def setup_slide_background_and_layout(self, slide, theme): | |
"""設置投影片背景和基本布局""" | |
# 應用背景 | |
self.apply_background(slide, theme) | |
# 添加裝飾元素 | |
self.add_decorative_elements(slide, theme) | |
def create_content_box_with_background(self, slide, theme, content_area): | |
"""創建帶背景的內容框""" | |
try: | |
# 在內容區域添加背景框 | |
bg_shape = slide.shapes.add_shape( | |
1, # 矩形 | |
Inches(content_area["left"] - 0.1), | |
Inches(content_area["top"] - 0.1), | |
Inches(content_area["width"] + 0.2), | |
Inches(content_area["height"] + 0.2) | |
) | |
# 設置背景框樣式 | |
fill = bg_shape.fill | |
fill.solid() | |
# 使用主題中定義的內容背景色 | |
if "content_bg_color" in theme: | |
fill.fore_color.rgb = theme["content_bg_color"] | |
else: | |
# 回退邏輯 | |
if self.is_dark_background(theme): | |
fill.fore_color.rgb = RGBColor(40, 40, 40) # 深色背景用深灰框 | |
else: | |
fill.fore_color.rgb = RGBColor(255, 255, 255) # 淺色背景用白色框 | |
# 設置邊框 | |
bg_shape.line.color.rgb = theme["accent_color"] | |
bg_shape.line.width = Pt(2) | |
# 對於深色背景,增加透明度 | |
if self.is_dark_background(theme): | |
fill.transparency = 0.2 # 20% 透明度 | |
else: | |
fill.transparency = 0.05 # 5% 透明度 | |
return bg_shape | |
except Exception as e: | |
print(f"創建內容背景框錯誤: {e}") | |
return None |