init
This commit is contained in:
166
backend/app/services/docx_generator.py
Normal file
166
backend/app/services/docx_generator.py
Normal file
@@ -0,0 +1,166 @@
|
||||
import os
|
||||
import re
|
||||
from docx import Document
|
||||
from docx.shared import Pt, Inches, Cm, RGBColor
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.oxml.ns import qn
|
||||
|
||||
|
||||
def generate_person_doc(person_name: str, person_info: str, photo_path: str,
|
||||
stories: list[dict], output_path: str):
|
||||
"""
|
||||
为一位讲述者生成最终 DOCX 文档
|
||||
|
||||
person_info: 个人信息文本
|
||||
photo_path: 照片路径(可为空)
|
||||
stories: [{title, content}]
|
||||
"""
|
||||
doc = Document()
|
||||
|
||||
# 设置默认字体
|
||||
style = doc.styles['Normal']
|
||||
font = style.font
|
||||
font.name = '宋体'
|
||||
font.size = Pt(12)
|
||||
style.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
|
||||
|
||||
# 照片
|
||||
if photo_path and os.path.exists(photo_path):
|
||||
try:
|
||||
doc.add_picture(photo_path, width=Inches(1.5))
|
||||
last_paragraph = doc.paragraphs[-1]
|
||||
last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 姓名
|
||||
name_para = doc.add_paragraph()
|
||||
name_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
name_run = name_para.add_run(person_name)
|
||||
name_run.font.size = Pt(18)
|
||||
name_run.font.bold = True
|
||||
name_run.font.name = '黑体'
|
||||
name_run.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
|
||||
|
||||
# 个人信息
|
||||
if person_info:
|
||||
info_para = doc.add_paragraph()
|
||||
info_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
info_run = info_para.add_run(person_info.strip())
|
||||
info_run.font.size = Pt(11)
|
||||
info_run.font.color.rgb = RGBColor(0x66, 0x66, 0x66)
|
||||
|
||||
# 分隔线
|
||||
doc.add_paragraph("─" * 30).alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
# 故事
|
||||
for i, story in enumerate(stories):
|
||||
# 故事标题
|
||||
title_para = doc.add_paragraph()
|
||||
title_run = title_para.add_run(story.get("title", f"故事 {i + 1}"))
|
||||
title_run.font.size = Pt(14)
|
||||
title_run.font.bold = True
|
||||
title_run.font.name = '黑体'
|
||||
title_run.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
|
||||
|
||||
# 故事梗概
|
||||
summary = story.get("summary", "")
|
||||
if summary:
|
||||
p = doc.add_paragraph()
|
||||
run = p.add_run("【故事梗概】")
|
||||
run.font.size = Pt(11)
|
||||
run.font.bold = True
|
||||
run.font.color.rgb = RGBColor(0x33, 0x33, 0x99)
|
||||
p = doc.add_paragraph(summary.strip())
|
||||
p.paragraph_format.first_line_indent = Cm(0.74)
|
||||
p.paragraph_format.line_spacing = 1.5
|
||||
for run in p.runs:
|
||||
run.font.size = Pt(11)
|
||||
|
||||
# 关键词标签
|
||||
tags = story.get("tags", [])
|
||||
if tags:
|
||||
p = doc.add_paragraph()
|
||||
run = p.add_run("【关键词标签】")
|
||||
run.font.size = Pt(11)
|
||||
run.font.bold = True
|
||||
run.font.color.rgb = RGBColor(0x33, 0x33, 0x99)
|
||||
tag_text = "、".join(tags)
|
||||
p = doc.add_paragraph(tag_text)
|
||||
p.paragraph_format.line_spacing = 1.5
|
||||
for run in p.runs:
|
||||
run.font.size = Pt(10)
|
||||
run.font.color.rgb = RGBColor(0x66, 0x66, 0x66)
|
||||
|
||||
# 故事内容(支持 Markdown 格式)
|
||||
content = story.get("content", "")
|
||||
if content:
|
||||
_add_markdown_content(doc, content)
|
||||
|
||||
# 多个故事之间加分隔
|
||||
if i < len(stories) - 1:
|
||||
doc.add_paragraph("─" * 30).alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
# 保存
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
doc.save(output_path)
|
||||
return output_path
|
||||
|
||||
|
||||
def _add_markdown_content(doc, content: str):
|
||||
"""将 Markdown 格式的内容添加到 DOCX,支持标题、引用、列表等"""
|
||||
lines = content.split("\n")
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
|
||||
if not line:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# ## 二级标题
|
||||
if line.startswith("## "):
|
||||
title = line[3:].strip()
|
||||
p = doc.add_paragraph()
|
||||
run = p.add_run(title)
|
||||
run.font.size = Pt(13)
|
||||
run.font.bold = True
|
||||
run.font.name = '黑体'
|
||||
run.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
|
||||
p.paragraph_format.space_before = Pt(12)
|
||||
|
||||
# ### 三级标题
|
||||
elif line.startswith("### "):
|
||||
title = line[4:].strip()
|
||||
p = doc.add_paragraph()
|
||||
run = p.add_run(title)
|
||||
run.font.size = Pt(12)
|
||||
run.font.bold = True
|
||||
run.font.name = '黑体'
|
||||
run.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
|
||||
p.paragraph_format.space_before = Pt(6)
|
||||
|
||||
# > 引用
|
||||
elif line.startswith("> "):
|
||||
quote_text = line[2:].strip()
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.left_indent = Cm(1)
|
||||
run = p.add_run(quote_text)
|
||||
run.font.italic = True
|
||||
run.font.color.rgb = RGBColor(0x55, 0x55, 0x55)
|
||||
run.font.size = Pt(11)
|
||||
|
||||
# - 列表项
|
||||
elif line.startswith("- "):
|
||||
item_text = line[2:].strip()
|
||||
p = doc.add_paragraph(style='List Bullet')
|
||||
run = p.add_run(item_text)
|
||||
run.font.size = Pt(12)
|
||||
|
||||
# 普通段落
|
||||
else:
|
||||
p = doc.add_paragraph(line)
|
||||
p.paragraph_format.first_line_indent = Cm(0.74)
|
||||
p.paragraph_format.line_spacing = 1.5
|
||||
|
||||
i += 1
|
||||
Reference in New Issue
Block a user