fix:实现基础功能
This commit is contained in:
415
old/main.py
Normal file
415
old/main.py
Normal file
@@ -0,0 +1,415 @@
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
from pptx import Presentation
|
||||
from pptx.util import Pt
|
||||
|
||||
|
||||
def get_system_fonts():
|
||||
"""获取系统中可用的字体列表"""
|
||||
fonts = set()
|
||||
if platform.system() == "Windows":
|
||||
try:
|
||||
# 读取Windows字体目录
|
||||
system_fonts_dir = Path(os.environ['WINDIR']) / 'Fonts'
|
||||
user_fonts_dir = Path.home() / 'AppData' / 'Local' / 'Microsoft' / 'Windows' / 'Fonts'
|
||||
|
||||
# 检查系统字体目录
|
||||
if system_fonts_dir.exists():
|
||||
for font_file in system_fonts_dir.glob('*.ttf'):
|
||||
fonts.add(font_file.stem)
|
||||
for font_file in system_fonts_dir.glob('*.ttc'):
|
||||
fonts.add(font_file.stem)
|
||||
for font_file in system_fonts_dir.glob('*.otf'):
|
||||
fonts.add(font_file.stem)
|
||||
|
||||
# 检查用户字体目录
|
||||
if user_fonts_dir.exists():
|
||||
for font_file in user_fonts_dir.glob('*.ttf'):
|
||||
fonts.add(font_file.stem)
|
||||
for font_file in user_fonts_dir.glob('*.ttc'):
|
||||
fonts.add(font_file.stem)
|
||||
for font_file in user_fonts_dir.glob('*.otf'):
|
||||
fonts.add(font_file.stem)
|
||||
|
||||
except Exception as e:
|
||||
print(f"读取系统字体时出错: {e}")
|
||||
# 备选方案:返回常见字体
|
||||
fonts = {"微软雅黑", "宋体", "黑体", "楷体", "仿宋", "Arial", "Times New Roman", "Courier New",
|
||||
"Microsoft YaHei"}
|
||||
return fonts
|
||||
|
||||
|
||||
def is_font_available(font_name):
|
||||
"""检查字体是否在系统中可用"""
|
||||
system_fonts = get_system_fonts()
|
||||
# 检查字体名称的多种可能形式
|
||||
check_names = [font_name, font_name.replace(" ", ""), font_name.replace("-", ""), font_name.lower(),
|
||||
font_name.upper()]
|
||||
for name in check_names:
|
||||
if name in system_fonts:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def install_fonts_from_directory(fonts_dir="fonts"):
|
||||
"""从指定目录安装字体到系统"""
|
||||
if platform.system() != "Windows":
|
||||
print("字体安装功能目前仅支持Windows系统")
|
||||
return False
|
||||
|
||||
# 获取系统字体目录
|
||||
user_fonts_dir = Path.home() / 'AppData' / 'Local' / 'Microsoft' / 'Windows' / 'Fonts'
|
||||
|
||||
# 优先使用用户字体目录(不需要管理员权限)
|
||||
target_font_dir = user_fonts_dir
|
||||
|
||||
# 创建目标目录(如果不存在)
|
||||
target_font_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 检查字体目录是否存在
|
||||
if not os.path.exists(fonts_dir):
|
||||
print(f"字体目录 {fonts_dir} 不存在,请创建该目录并将字体文件放入")
|
||||
return False
|
||||
|
||||
# 遍历字体目录中的字体文件
|
||||
font_extensions = ['.ttf', '.ttc', '.otf', '.fon']
|
||||
font_files = []
|
||||
for ext in font_extensions:
|
||||
font_files.extend(Path(fonts_dir).glob(f'*{ext}'))
|
||||
|
||||
if not font_files:
|
||||
print(f"在 {fonts_dir} 目录中未找到字体文件 (.ttf, .ttc, .otf, .fon)")
|
||||
return False
|
||||
|
||||
# 安装字体文件
|
||||
installed_fonts = []
|
||||
for font_file in font_files:
|
||||
try:
|
||||
# 复制字体文件到系统字体目录
|
||||
target_path = target_font_dir / font_file.name
|
||||
if not target_path.exists(): # 避免重复安装
|
||||
shutil.copy2(font_file, target_path)
|
||||
print(f"已安装字体: {font_file.name}")
|
||||
installed_fonts.append(font_file.name)
|
||||
else:
|
||||
pass
|
||||
# print(f"字体已存在: {font_file.name}") # 减少日志输出
|
||||
except Exception as e:
|
||||
print(f"安装字体 {font_file.name} 时出错: {str(e)}")
|
||||
continue
|
||||
|
||||
if installed_fonts:
|
||||
print(f"共安装了 {len(installed_fonts)} 个新字体文件")
|
||||
print("注意:新安装的字体可能需要重启Python环境后才能在PowerPoint中使用")
|
||||
return True
|
||||
else:
|
||||
print("没有安装新字体文件,可能已存在于系统中")
|
||||
return False
|
||||
|
||||
|
||||
def replace_text_in_slide(prs, slide_index, placeholder, text):
|
||||
"""
|
||||
在指定幻灯片中替换指定占位符的文本,并保持原有格式。
|
||||
:param prs: Presentation 对象
|
||||
:param slide_index: 要操作的幻灯片索引(从0开始)
|
||||
:param placeholder: 占位符名称 (shape.name)
|
||||
:param text: 要替换的文本
|
||||
"""
|
||||
slide = prs.slides[slide_index]
|
||||
for shape in slide.shapes:
|
||||
if shape.name == placeholder:
|
||||
if not shape.has_text_frame:
|
||||
continue
|
||||
|
||||
# --- 1. 保存原有格式信息 ---
|
||||
original_paragraph_formats = []
|
||||
|
||||
for paragraph in shape.text_frame.paragraphs:
|
||||
paragraph_format = {
|
||||
'alignment': paragraph.alignment,
|
||||
'space_before': getattr(paragraph, 'space_before', None),
|
||||
'space_after': getattr(paragraph, 'space_after', None),
|
||||
'line_spacing': getattr(paragraph, 'line_spacing', None),
|
||||
# [修复] 补充读取缩进属性,防止后面恢复时 KeyError
|
||||
'left_indent': getattr(paragraph, 'left_indent', None),
|
||||
'right_indent': getattr(paragraph, 'right_indent', None),
|
||||
'first_line_indent': getattr(paragraph, 'first_line_indent', None),
|
||||
'font_info': []
|
||||
}
|
||||
|
||||
for run in paragraph.runs:
|
||||
run_format = {
|
||||
'font_name': run.font.name,
|
||||
'font_size': run.font.size,
|
||||
'bold': run.font.bold,
|
||||
'italic': run.font.italic,
|
||||
'underline': run.font.underline,
|
||||
'color': run.font.color,
|
||||
'character_space': getattr(run.font, 'space', None),
|
||||
'all_caps': getattr(run.font, 'all_caps', None),
|
||||
'small_caps': getattr(run.font, 'small_caps', None)
|
||||
}
|
||||
paragraph_format['font_info'].append(run_format)
|
||||
|
||||
original_paragraph_formats.append(paragraph_format)
|
||||
|
||||
# --- 2. 设置新文本内容 ---
|
||||
shape.text = text
|
||||
|
||||
# --- 3. 恢复原有格式 ---
|
||||
for i, paragraph in enumerate(shape.text_frame.paragraphs):
|
||||
# 如果新文本段落数超过原格式数量,使用最后一个格式或默认格式
|
||||
orig_idx = i if i < len(original_paragraph_formats) else -1
|
||||
if not original_paragraph_formats: break # 防止空列表
|
||||
|
||||
original_para = original_paragraph_formats[orig_idx]
|
||||
|
||||
# 恢复段落格式
|
||||
if original_para['alignment'] is not None:
|
||||
paragraph.alignment = original_para['alignment']
|
||||
if original_para['space_before'] is not None:
|
||||
paragraph.space_before = original_para['space_before']
|
||||
if original_para['space_after'] is not None:
|
||||
paragraph.space_after = original_para['space_after']
|
||||
if original_para['line_spacing'] is not None:
|
||||
paragraph.line_spacing = original_para['line_spacing']
|
||||
if original_para['left_indent'] is not None:
|
||||
paragraph.left_indent = original_para['left_indent']
|
||||
if original_para['right_indent'] is not None:
|
||||
paragraph.right_indent = original_para['right_indent']
|
||||
if original_para['first_line_indent'] is not None:
|
||||
paragraph.first_line_indent = original_para['first_line_indent']
|
||||
|
||||
# 恢复字体格式 (尽量应用到所有 runs)
|
||||
# 注意:shape.text = text 会把所有内容变成一个 run,但也可能有多个
|
||||
for j, run in enumerate(paragraph.runs):
|
||||
# 通常取第一个run的格式,或者按顺序取
|
||||
font_idx = j if j < len(original_para['font_info']) else 0
|
||||
if not original_para['font_info']: break
|
||||
|
||||
original_font = original_para['font_info'][font_idx]
|
||||
|
||||
# 字体名称
|
||||
if original_font['font_name'] is not None:
|
||||
font_name = original_font['font_name']
|
||||
if is_font_available(font_name):
|
||||
run.font.name = font_name
|
||||
else:
|
||||
run.font.name = "微软雅黑"
|
||||
# print(f"警告: 字体 '{font_name}' 不可用,已替换")
|
||||
|
||||
# 其他属性
|
||||
if original_font['font_size'] is not None:
|
||||
run.font.size = original_font['font_size']
|
||||
if original_font['bold'] is not None:
|
||||
run.font.bold = original_font['bold']
|
||||
if original_font['italic'] is not None:
|
||||
run.font.italic = original_font['italic']
|
||||
if original_font['underline'] is not None:
|
||||
run.font.underline = original_font['underline']
|
||||
|
||||
if original_font['character_space'] is not None:
|
||||
try:
|
||||
run.font.space = original_font['character_space']
|
||||
except:
|
||||
pass
|
||||
|
||||
if original_font['all_caps'] is not None:
|
||||
run.font.all_caps = original_font['all_caps']
|
||||
|
||||
# 颜色处理
|
||||
if original_font['color'] is not None:
|
||||
try:
|
||||
# 仅当它是RGB类型时才复制,主题色可能导致报错
|
||||
if hasattr(original_font['color'], 'rgb'):
|
||||
run.font.color.rgb = original_font['color'].rgb
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def replace_picture(prs, slide_index, placeholder, img_path):
|
||||
"""
|
||||
在指定幻灯片中替换指定占位符的图片。
|
||||
:param prs: Presentation 对象
|
||||
:param slide_index: 要操作的幻灯片索引(从0开始)
|
||||
:param placeholder: 占位符名称
|
||||
:param img_path: 要替换的图片路径
|
||||
"""
|
||||
if not os.path.exists(img_path):
|
||||
print(f"警告: 图片路径不存在 {img_path}")
|
||||
return
|
||||
|
||||
slide = prs.slides[slide_index]
|
||||
sp_tree = slide.shapes._spTree
|
||||
|
||||
# 先找到要替换的形状及其索引位置
|
||||
for i, shape in enumerate(slide.shapes):
|
||||
if shape.name == placeholder:
|
||||
# 保存旧形状的位置信息
|
||||
left = shape.left
|
||||
top = shape.top
|
||||
width = shape.width
|
||||
height = shape.height
|
||||
|
||||
# 从幻灯片中移除旧图片占位符
|
||||
sp_tree.remove(shape._element)
|
||||
|
||||
# 添加新图片
|
||||
new_shape = slide.shapes.add_picture(img_path, left, top, width, height)
|
||||
|
||||
# 插入新图片到旧形状原来的位置
|
||||
sp_tree.insert(i, new_shape._element)
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# --- 1. 资源准备 ---
|
||||
# 安装字体(如果存在fonts目录)
|
||||
if install_fonts_from_directory("../fonts"):
|
||||
print("等待系统识别新安装的字体...")
|
||||
time.sleep(2)
|
||||
|
||||
source_file = r"../templates/大班幼儿学期发展报告.pptx"
|
||||
output_folder = "output"
|
||||
excel_file = os.path.join("../data/names.xlsx")
|
||||
image_folder = os.path.join("../data/images")
|
||||
|
||||
# 创建输出文件夹
|
||||
os.makedirs(output_folder, exist_ok=True)
|
||||
|
||||
# 检查源文件是否存在
|
||||
if not os.path.exists(source_file):
|
||||
print(f"错误: 找不到模版文件 {source_file}")
|
||||
sys.exit(1)
|
||||
if not os.path.exists(excel_file):
|
||||
print(f"错误: 找不到数据文件 {excel_file}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# --- 2. 定义辅助函数 (显式传入 prs) ---
|
||||
def replace_one_page(current_prs, name, class_name):
|
||||
"""替换第一页信息"""
|
||||
replace_text_in_slide(current_prs, 0, "name", name)
|
||||
replace_text_in_slide(current_prs, 0, "class", class_name)
|
||||
|
||||
|
||||
def replace_two_page(current_prs, comments, teacher_name):
|
||||
"""替换第二页信息"""
|
||||
replace_text_in_slide(current_prs, 1, "comments", comments)
|
||||
replace_text_in_slide(current_prs, 1, "teacher_name", teacher_name)
|
||||
|
||||
|
||||
def replace_three_page(current_prs, name, english_name, sex, birthday, zodiac, friend, hobby, game, food, me_image):
|
||||
"""替换第三页信息
|
||||
:param current_prs: Presentation对象,当前演示文稿
|
||||
:param name: 学生姓名
|
||||
:param english_name: 学生英文名
|
||||
:param sex: 性别
|
||||
:param birthday: 生日
|
||||
:param zodiac: 生肖
|
||||
:param friend: 好朋友
|
||||
:param hobby: 爱好
|
||||
:param game: 爱玩的游戏
|
||||
:param food: 爱吃的食物
|
||||
:param me_image: 自己的照片
|
||||
"""
|
||||
replace_text_in_slide(current_prs, 2, "name", name)
|
||||
replace_text_in_slide(current_prs, 2, "english_name", english_name)
|
||||
replace_text_in_slide(current_prs, 2, "sex", sex)
|
||||
replace_text_in_slide(current_prs, 2, "birthday", birthday)
|
||||
replace_text_in_slide(current_prs, 2, "zodiac", zodiac)
|
||||
replace_text_in_slide(current_prs, 2, "friend", friend)
|
||||
replace_text_in_slide(current_prs, 2, "hobby", hobby)
|
||||
replace_text_in_slide(current_prs, 2, "game", game)
|
||||
replace_text_in_slide(current_prs, 2, "food", food)
|
||||
replace_picture(current_prs, 2, "me_image", me_image)
|
||||
|
||||
|
||||
def replace_four_page(current_prs, class_image):
|
||||
"""替换第四页信息"""
|
||||
replace_picture(current_prs, 3, "class_image", class_image)
|
||||
|
||||
|
||||
def replace_five_page(current_prs, image1, image2):
|
||||
"""替换第五页信息"""
|
||||
replace_picture(current_prs, 4, "image1", image1)
|
||||
replace_picture(current_prs, 4, "image2", image2)
|
||||
|
||||
|
||||
# --- 3. 读取数据并处理 ---
|
||||
try:
|
||||
df = pd.read_excel(excel_file, sheet_name="Sheet1")
|
||||
datas = df[["姓名", "英文名", "性别", "生日", "属相", "我的好朋友", "我的爱好", "喜欢的游戏", "喜欢吃的食物",
|
||||
"评价"]].values.tolist()
|
||||
|
||||
class_name = "K4D"
|
||||
teacher_name = ["简蜜", "王敏千", "李玉香"]
|
||||
|
||||
print(f"开始处理,共 {len(datas)} 位学生...")
|
||||
|
||||
# --- 4. 循环处理 ---
|
||||
for i, (name, english_name, sex, birthday, zodiac, friend, hobby, game, food, comments) in enumerate(datas):
|
||||
|
||||
# 班级图片
|
||||
|
||||
print(f"[{i + 1}/{len(datas)}] 正在生成: {name}")
|
||||
|
||||
# [修复] 每次循环重新加载模版,保证文件干净
|
||||
prs = Presentation(source_file)
|
||||
|
||||
# 替换第一页内容
|
||||
replace_one_page(prs, name, class_name)
|
||||
|
||||
# 替换第二页内容
|
||||
teacher_names = " ".join(teacher_name)
|
||||
replace_two_page(prs, comments, teacher_names)
|
||||
|
||||
# 替换第三页内容
|
||||
student_image_folder = os.path.join(image_folder, name)
|
||||
if os.path.exists(student_image_folder):
|
||||
me_image = os.path.join(student_image_folder, "me_image.jpg")
|
||||
replace_three_page(prs, name, english_name, sex, birthday, zodiac, friend, hobby, game, food, me_image)
|
||||
else:
|
||||
print(f"错误: 学生图片文件夹不存在 {student_image_folder}")
|
||||
|
||||
# 替换第四页内容
|
||||
class_image = os.path.join(image_folder, class_name + ".jpg")
|
||||
if os.path.exists(class_image):
|
||||
replace_four_page(prs, class_image)
|
||||
else:
|
||||
print(f"错误: 班级图片文件不存在 {class_image}")
|
||||
continue
|
||||
|
||||
# 替换第五页内容
|
||||
if os.path.exists(student_image_folder):
|
||||
image1 = os.path.join(student_image_folder, "1.jpg")
|
||||
image2 = os.path.join(student_image_folder, "1.jpg")
|
||||
replace_five_page(prs, image1, image2)
|
||||
else:
|
||||
print(f"错误: 学生图片文件夹不存在 {student_image_folder}")
|
||||
|
||||
# 获取文件拓展名
|
||||
file_ext = os.path.splitext(source_file)[1]
|
||||
safe_name = str(name).strip() # 去除可能存在的空格
|
||||
new_filename = f"{class_name} {safe_name} 幼儿成长报告{file_ext}"
|
||||
output_path = os.path.join(output_folder, new_filename)
|
||||
|
||||
# 保存
|
||||
try:
|
||||
prs.save(output_path)
|
||||
except PermissionError:
|
||||
print(f"保存失败: 文件 {new_filename} 可能已被打开,请关闭后重试。")
|
||||
|
||||
print("\n所有报告生成完毕!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"程序运行出错: {str(e)}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
Reference in New Issue
Block a user