Files
visual-vue-v1/soft_copy_right/generate_source_code_pdf.py
2026-07-27 22:46:43 +08:00

281 lines
9.0 KiB
Python

# -*- coding: utf-8 -*-
"""生成软著程序鉴别材料PDF
从项目 src 目录收集源代码,生成前30页 + 后30页的程序鉴别材料PDF。
自动适配 Windows / macOS / Linux 的中文字体。
"""
import os
import sys
import glob
import platform
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.enums import TA_CENTER
# ============================================================
# 配置
# ============================================================
# 项目根目录(脚本所在目录的上级目录)
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.dirname(SCRIPT_DIR)
OUTPUT_PDF = os.path.join(SCRIPT_DIR, "程序鉴别材料_源代码.pdf")
# 软件信息
SOFTWARE_NAME = "数据可视化大屏展示系统V1.0"
SOFTWARE_VERSION = "V1.0"
COPYRIGHT_OWNER = "[著作权人姓名]"
# 分页参数
LINES_PER_PAGE = 55 # 每页约55行代码
PAGES_FRONT = 30 # 前30页
PAGES_BACK = 30 # 后30页
# ============================================================
# 字体注册(自动适配多平台)
# ============================================================
def find_chinese_fonts():
"""在当前操作系统中查找可用的中文字体"""
system = platform.system()
if system == "Windows":
candidates = [
("SimSun", "C:/Windows/Fonts/simsun.ttc"),
("SimHei", "C:/Windows/Fonts/simhei.ttf"),
("MSYH", "C:/Windows/Fonts/msyh.ttc"),
]
elif system == "Darwin": # macOS
candidates = [
("PingFang", "/System/Library/Fonts/PingFang.ttc"),
("STHeiti", "/System/Library/Fonts/STHeiti Medium.ttc"),
("STSong", "/Library/Fonts/Songti.ttc"),
]
else: # Linux
candidates = [
("NotoSansCJK", "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"),
("NotoSansCJK", "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc"),
("NotoSansCJK-OTF", "/usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf"),
("WenQuanYi", "/usr/share/fonts/wenquanyi/wqy-zenhei/wqy-zenhei.ttc"),
("WenQuanYi-Micro", "/usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc"),
("DroidSans", "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf"),
]
# 也可从环境变量覆盖
env_font = os.environ.get("SOFTCOPY_FONT_PATH")
env_font_name = os.environ.get("SOFTCOPY_FONT_NAME", "CustomFont")
if env_font and os.path.exists(env_font):
candidates.insert(0, (env_font_name, env_font))
found = []
for name, path in candidates:
if os.path.exists(path):
found.append((name, path))
return found
def register_fonts():
"""注册中文字体,返回 (body_font, heading_font)"""
fonts = find_chinese_fonts()
if not fonts:
print("错误: 未找到可用的中文字体!")
print("请安装中文字体或设置环境变量 SOFTCOPY_FONT_PATH")
if platform.system() == "Linux":
print(" Ubuntu/Debian: sudo apt install fonts-noto-cjk")
sys.exit(1)
# 注册第一个找到的字体作为正文字体
body_name, body_path = fonts[0]
pdfmetrics.registerFont(TTFont(body_name, body_path))
print(f"正文字体: {body_name} ({body_path})")
# 尝试找一个不同的字体作为标题字体
heading_name = body_name
for name, path in fonts[1:]:
if name != body_name:
pdfmetrics.registerFont(TTFont(name, path))
heading_name = name
print(f"标题字体: {heading_name} ({path})")
break
else:
# 如果只有一个字体,标题也用它
heading_name = body_name
return body_name, heading_name
# ============================================================
# 源代码收集
# ============================================================
def collect_source_files():
"""收集项目 src 目录下的所有源代码文件"""
extensions = ["*.vue", "*.ts", "*.js", "*.css", "*.scss", "*.less"]
source_files = []
src_dir = os.path.join(PROJECT_DIR, "src")
for ext in extensions:
pattern = os.path.join(src_dir, "**", ext)
files = glob.glob(pattern, recursive=True)
# 排除 node_modules
files = [f for f in files if "node_modules" not in f]
source_files.extend(sorted(files))
return source_files
def read_all_code(files):
"""读取所有代码内容,返回行列表"""
all_lines = []
for filepath in files:
try:
with open(filepath, "r", encoding="utf-8") as f:
lines = f.readlines()
rel_path = os.path.relpath(filepath, PROJECT_DIR)
all_lines.append(f"\n{'=' * 70}")
all_lines.append(f"文件: {rel_path}")
all_lines.append(f"{'=' * 70}\n")
for line in lines:
all_lines.append(line.rstrip("\n"))
except Exception as e:
print(f"警告: 无法读取文件 {filepath}: {e}")
return all_lines
# ============================================================
# PDF 生成
# ============================================================
def generate_pdf(lines_front, lines_back, body_font, heading_font):
"""生成程序鉴别材料PDF"""
doc = SimpleDocTemplate(
OUTPUT_PDF,
pagesize=A4,
rightMargin=2 * cm,
leftMargin=2 * cm,
topMargin=2 * cm,
bottomMargin=2 * cm,
)
styles = getSampleStyleSheet()
# 代码样式
code_style = ParagraphStyle(
"CodeStyle",
parent=styles["Normal"],
fontName=body_font,
fontSize=8,
leading=12,
leftIndent=0,
spaceAfter=0,
wordWrap="CJK",
)
# 标题样式
title_style = ParagraphStyle(
"TitleStyle",
parent=styles["Title"],
fontName=heading_font,
fontSize=16,
alignment=TA_CENTER,
spaceAfter=20,
)
story = []
# 封面页
story.append(Paragraph("程序鉴别材料", title_style))
story.append(Paragraph("(源代码)", title_style))
story.append(Spacer(1, 30))
story.append(Paragraph(f"软件全称:{SOFTWARE_NAME}", code_style))
story.append(Paragraph(f"版本号:{SOFTWARE_VERSION}", code_style))
story.append(Paragraph(f"著作权人:{COPYRIGHT_OWNER}", code_style))
story.append(PageBreak())
# 前30页
story.append(Paragraph("—— 源代码前30页 ——", title_style))
story.append(Spacer(1, 10))
for i, line in enumerate(lines_front):
if i > 0 and i % LINES_PER_PAGE == 0:
story.append(PageBreak())
# 转义HTML特殊字符
safe_line = line.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
# 处理空格
safe_line = safe_line.replace(" ", "&nbsp;")
p = Paragraph(safe_line, code_style)
story.append(p)
story.append(PageBreak())
# 后30页
story.append(Paragraph("—— 源代码后30页 ——", title_style))
story.append(Spacer(1, 10))
for i, line in enumerate(lines_back):
if i > 0 and i % LINES_PER_PAGE == 0:
story.append(PageBreak())
safe_line = line.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
safe_line = safe_line.replace(" ", "&nbsp;")
p = Paragraph(safe_line, code_style)
story.append(p)
doc.build(story)
print(f"PDF已生成: {OUTPUT_PDF}")
# ============================================================
# 主流程
# ============================================================
if __name__ == "__main__":
print("=" * 60)
print("程序鉴别材料PDF生成工具")
print(f"项目目录: {PROJECT_DIR}")
print(f"输出文件: {OUTPUT_PDF}")
print("=" * 60)
# 注册字体
body_font, heading_font = register_fonts()
# 收集源代码文件
print("\n正在收集源代码文件...")
files = collect_source_files()
print(f"找到 {len(files)} 个源代码文件")
# 读取代码内容
print("正在读取代码内容...")
all_lines = read_all_code(files)
print(f"总代码行数: {len(all_lines)}")
# 计算前后30页
total_lines_needed = (PAGES_FRONT + PAGES_BACK) * LINES_PER_PAGE
if len(all_lines) <= total_lines_needed:
# 代码不足60页,全部提交
lines_front = all_lines[: len(all_lines) // 2]
lines_back = all_lines[len(all_lines) // 2 :]
else:
lines_front = all_lines[: PAGES_FRONT * LINES_PER_PAGE]
lines_back = all_lines[-PAGES_BACK * LINES_PER_PAGE :]
print(f"前30页: {len(lines_front)} 行")
print(f"后30页: {len(lines_back)} 行")
# 生成PDF
print("\n正在生成PDF...")
generate_pdf(lines_front, lines_back, body_font, heading_font)
print("完成!")