liuziyi

liuziyi

分享一个word文档专业排版工具,附源码

📄 完整代码 (含自定义字体与WPS适配)

import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from docx import Document
from docx.shared import Pt, Cm
from docx.oxml.ns import qn
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
import os
import re
import sys

# --- 1. 环境检测与 .doc 转换模块 (仅Windows有效) ---
if sys.platform.startswith('win'):
    try:
        import win32com.client
        CONVERT_ENABLED = True
    except ImportError:
        CONVERT_ENABLED = False
        print("提示: 如需处理 .doc 文件,请安装 pywin32: pip install pywin32")
else:
    CONVERT_ENABLED = False

def convert_doc_to_docx(doc_path):
    """将旧版 .doc 转换为 .docx"""
    if not CONVERT_ENABLED:
        return False, "非Windows环境,无法转换 .doc 文件"
        
    word = None
    try:
        # 优先尝试 WPS
        word = win32com.client.Dispatch("Kwps.Application")
    except:
        try:
            # 备选尝试 Office
            word = win32com.client.Dispatch("Word.Application")
        except:
            return False, "未检测到WPS或Office软件"

    try:
        doc = word.Documents.Open(doc_path)
        docx_path = doc_path + "x"
        # WPS和Office通用的保存格式代码 (wdFormatXMLDocument)
        doc.SaveAs(docx_path, 12) 
        doc.Close()
        word.Quit()
        return True, docx_path
    except Exception as e:
        if word: word.Quit()
        return False, f"转换失败: {str(e)}"

# --- 2. 核心配置 (初始化默认值) ---
CONFIG = {
    "margin": (2.54, 2.54, 3.17, 3.17),
    "western_font": "Times New Roman",
    "line_height": Pt(28.95),
    "line_rule": WD_LINE_SPACING.EXACTLY,
    "fonts": {
        "title": "方正小标宋_GBK",
        "heading_1": "黑体",
        "heading_2": "楷体_GB2312",
        "heading_3": "仿宋_GB2312",
        "body": "仿宋_GB2312"
    },
    "font_sizes": {
        "title": 22,
        "heading_1": 16,
        "heading_2": 16,
        "heading_3": 16,
        "body": 16
    }
}

def set_font(run, chinese_font, western_font):
    """强制设置字体 (解决WPS中字体不生效的问题)"""
    run.font.name = western_font
    r = run._element.rPr
    if r is not None:
        # 移除旧的东亚字体设置
        rFonts = r.find(qn('w:eastAsia'))
        if rFonts is not None:
            r.remove(rFonts)
        # 添加新的东亚字体
        new_rFonts = r.makeelement(qn('w:eastAsia'), {})
        new_rFonts.set(qn('w:val'), chinese_font)
        r.append(new_rFonts)

def process_document(input_path, output_path):
    """核心处理逻辑"""
    # --- 步骤1: 处理文件格式 ---
    file_ext = os.path.splitext(input_path)[1].lower()
    temp_docx_path = None
    
    if file_ext == ".doc":
        print("转换中: .doc -> .docx")
        success, result = convert_doc_to_docx(input_path)
        if not success:
            return False, result
        temp_docx_path = result
        process_path = temp_docx_path
    elif file_ext == ".docx":
        process_path = input_path
    else:
        return False, "不支持的文件格式"

    try:
        doc = Document(process_path)
        
        # 设置页边距
        for section in doc.sections:
            section.top_margin = Cm(CONFIG["margin"][0])
            section.bottom_margin = Cm(CONFIG["margin"][1])
            section.left_margin = Cm(CONFIG["margin"][2])
            section.right_margin = Cm(CONFIG["margin"][3])

        # --- 步骤2: 遍历段落并应用自定义样式 ---
        for para in doc.paragraphs:
            if not para.text.strip():
                continue
                
            text = para.text.strip()
            
            # 标题1: 匹配 "一、" 格式
            if re.match(r'^[一二三四五六七八九十]、', text):
                pf = para.paragraph_format
                pf.first_line_indent = Pt(0) # 取消缩进
                pf.alignment = WD_ALIGN_PARAGRAPH.LEFT
                pf.line_spacing_rule = CONFIG["line_rule"]
                pf.line_spacing = CONFIG["line_height"]
                
                for run in para.runs:
                    set_font(run, CONFIG["fonts"]["heading_1"], CONFIG["western_font"])
                    run.font.size = Pt(CONFIG["font_sizes"]["heading_1"])
                    run.font.bold = False
                    
            # 标题2: 匹配 "(一)" 格式
            elif re.match(r'^[((][\u4e00-\u9fa5]+[))]', text):
                pf = para.paragraph_format
                pf.first_line_indent = Pt(0)
                pf.alignment = WD_ALIGN_PARAGRAPH.LEFT
                pf.line_spacing_rule = CONFIG["line_rule"]
                pf.line_spacing = CONFIG["line_height"]
                
                for run in para.runs:
                    set_font(run, CONFIG["fonts"]["heading_2"], CONFIG["western_font"])
                    run.font.size = Pt(CONFIG["font_sizes"]["heading_2"])
                    run.font.bold = False
                    
            # 标题3: 匹配 "1." 或 "1、" 格式
            elif re.match(r'^\d+[\.、]', text):
                pf = para.paragraph_format
                pf.first_line_indent = Pt(0)
                pf.alignment = WD_ALIGN_PARAGRAPH.LEFT
                pf.line_spacing_rule = CONFIG["line_rule"]
                pf.line_spacing = CONFIG["line_height"]
                
                for run in para.runs:
                    set_font(run, CONFIG["fonts"]["heading_3"], CONFIG["western_font"])
                    run.font.size = Pt(CONFIG["font_sizes"]["heading_3"])
                    run.font.bold = True
                    
            # 正文: 默认样式
            else:
                pf = para.paragraph_format
                pf.first_line_indent = Cm(0.74) # 首行缩进2字符
                pf.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
                pf.line_spacing_rule = CONFIG["line_rule"]
                pf.line_spacing = CONFIG["line_height"]
                
                for run in para.runs:
                    set_font(run, CONFIG["fonts"]["body"], CONFIG["western_font"])
                    run.font.size = Pt(CONFIG["font_sizes"]["body"])
                    run.font.bold = False

        # --- 步骤3: 处理文档大标题 (第1段) ---
        if len(doc.paragraphs) > 0:
            first_para = doc.paragraphs[0]
            if first_para.text.strip():
                pf = first_para.paragraph_format
                pf.alignment = WD_ALIGN_PARAGRAPH.CENTER
                pf.space_after = Pt(12)
                
                for run in first_para.runs:
                    set_font(run, CONFIG["fonts"]["title"], CONFIG["western_font"])
                    run.font.size = Pt(CONFIG["font_sizes"]["title"])
                    run.font.bold = False

        doc.save(output_path)
        
        # 清理临时文件
        if temp_docx_path and os.path.exists(temp_docx_path):
            os.remove(temp_docx_path)
            
        return True, f"成功! 保存至: {output_path}"
        
    except Exception as e:
        if temp_docx_path and os.path.exists(temp_docx_path):
            os.remove(temp_docx_path)
        return False, f"处理异常: {str(e)}"

# --- 3. 美观的GUI界面 (含自定义配置) ---
class ModernTypesettingApp:
    def __init__(self, root):
        self.root = root
        self.root.title("🖋️ WPS 智能排版大师 (含自定义)")
        self.root.geometry("750x600")
        self.root.resizable(True, True)
        
        self.setup_styles()
        self.create_widgets()

    def setup_styles(self):
        style = ttk.Style()
        style.configure("TLabel", font=("微软雅黑", 10))
        style.configure("TButton", font=("微软雅黑", 9))
        self.root.configure(bg='#f5f5f5')

    def create_widgets(self):
        notebook = ttk.Notebook(self.root)
        notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # --- Tab 1: 主操作界面 ---
        tab_main = ttk.Frame(notebook)
        notebook.add(tab_main, text="📄 文档处理")

        main_frame = ttk.LabelFrame(tab_main, text=" 任务配置 ", padding=20)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)

        # 输入输出
        tk.Label(main_frame, text="输入文件:", font=("微软雅黑", 10)).grid(row=0, column=0, sticky=tk.W, pady=10)
        self.input_entry = tk.Entry(main_frame, width=40, font=("Consolas", 10), relief="solid")
        self.input_entry.grid(row=0, column=1, padx=10, pady=10, sticky=tk.EW)
        tk.Button(main_frame, text="📂 选择文件", command=self.browse_input, bg="#007ACC", fg="white").grid(row=0, column=2, padx=10)

        tk.Label(main_frame, text="输出目录:", font=("微软雅黑", 10)).grid(row=1, column=0, sticky=tk.W, pady=10)
        self.output_entry = tk.Entry(main_frame, width=40, font=("Consolas", 10), relief="solid")
        self.output_entry.grid(row=1, column=1, padx=10, pady=10, sticky=tk.EW)
        tk.Button(main_frame, text="📁 选择文件夹", command=self.browse_output, bg="#28A745", fg="white").grid(row=1, column=2, padx=10)

        # 控制按钮
        btn_frame = tk.Frame(main_frame)
        btn_frame.grid(row=2, column=0, columnspan=3, pady=30)
        
        self.start_btn = tk.Button(btn_frame, text="🚀 开始排版", 
                                  command=self.start_process, 
                                  bg="#DC3545", fg="white", width=15, height=2, font=("微软雅黑", 10, "bold"))
        self.start_btn.pack(side=tk.LEFT, padx=20)
        
        tk.Button(btn_frame, text="❌ 退出", command=self.root.quit, 
                 width=15, height=2).pack(side=tk.LEFT, padx=20)

        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪: 支持 .doc 和 .docx 格式")
        status_bar = tk.Label(self.root, textvariable=self.status_var, relief="sunken", 
                             bg="#e9ecef", fg="gray", font=("Consolas", 9))
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        # --- Tab 2: 字体配置界面 ---
        tab_config = ttk.Frame(notebook)
        notebook.add(tab_config, text="🎨 字体配置")

        config_frame = ttk.LabelFrame(tab_config, text=" 自定义样式设置 ", padding=20)
        config_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)

        # 字体选择行
        tk.Label(config_frame, text="字体名称", font=("微软雅黑", 10, "bold")).grid(row=0, column=1, padx=40)
        tk.Label(config_frame, text="字号(磅)", font=("微软雅黑", 10, "bold")).grid(row=0, column=2, padx=20)

        # 标题配置
        tk.Label(config_frame, text="文档大标题:", fg="blue").grid(row=1, column=0, sticky=tk.W, pady=10)
        self.title_font = ttk.Combobox(config_frame, values=[
            '方正小标宋_GBK', '黑体', '宋体', '微软雅黑'
        ], width=15)
        self.title_font.set(CONFIG["fonts"]["title"])
        self.title_font.grid(row=1, column=1)
        
        self.title_size = ttk.Combobox(config_frame, values=[16, 18, 20, 22, 24, 26, 28], width=8)
        self.title_size.set(CONFIG["font_sizes"]["title"])
        self.title_size.grid(row=1, column=2)

        # 一级标题
        tk.Label(config_frame, text="一级标题(一、):", fg="darkgreen").grid(row=2, column=0, sticky=tk.W, pady=10)
        self.l1_font = ttk.Combobox(config_frame, values=[
            '黑体', '方正小标宋_GBK', '楷体_GB2312'
        ], width=15)
        self.l1_font.set(CONFIG["fonts"]["heading_1"])
        self.l1_font.grid(row=2, column=1)
        
        self.l1_size = ttk.Combobox(config_frame, values=[14, 15, 16, 17, 18], width=8)
        self.l1_size.set(CONFIG["font_sizes"]["heading_1"])
        self.l1_size.grid(row=2, column=2)

        # 二级标题
        tk.Label(config_frame, text="二级标题((一)):", fg="purple").grid(row=3, column=0, sticky=tk.W, pady=10)
        self.l2_font = ttk.Combobox(config_frame, values=[
            '楷体_GB2312', '仿宋_GB2312', '黑体'
        ], width=15)
        self.l2_font.set(CONFIG["fonts"]["heading_2"])
        self.l2_font.grid(row=3, column=1)
        
        self.l2_size = ttk.Combobox(config_frame, values=[14, 15, 16, 17, 18], width=8)
        self.l2_size.set(CONFIG["font_sizes"]["heading_2"])
        self.l2_size.grid(row=3, column=2)

        # 正文配置
        tk.Label(config_frame, text="正文/三级标题:", fg="gray").grid(row=4, column=0, sticky=tk.W, pady=10)
        self.body_font = ttk.Combobox(config_frame, values=[
            '仿宋_GB2312', '宋体', '黑体'
        ], width=15)
        self.body_font.set(CONFIG["fonts"]["body"])
        self.body_font.grid(row=4, column=1)
        
        self.body_size = ttk.Combobox(config_frame, values=[14, 15, 16, 17, 18], width=8)
        self.body_size.set(CONFIG["font_sizes"]["body"])
        self.body_size.grid(row=4, column=2)

        # 操作按钮
        opt_frame = tk.Frame(config_frame)
        opt_frame.grid(row=5, column=0, columnspan=3, pady=30)
        
        tk.Button(opt_frame, text="💾 保存配置", command=self.save_config, 
                 bg="#17A2B8", fg="white", width=15).pack(side=tk.LEFT, padx=20)
        tk.Button(opt_frame, text="🔙 恢复默认", command=self.reset_config, 
                 bg="#6C757D", fg="white", width=15).pack(side=tk.LEFT, padx=20)

    def browse_input(self):
        file_path = filedialog.askopenfilename(
            title="选择Word文档",
            filetypes=[("Word文档", "*.docx *.doc"), ("All Files", "*.*")]
        )
        if file_path:
            self.input_entry.delete(0, tk.END)
            self.input_entry.insert(0, file_path)

    def browse_output(self):
        dir_path = filedialog.askdirectory()
        if dir_path:
            self.output_entry.delete(0, tk.END)
            self.output_entry.insert(0, dir_path)

    def save_config(self):
        """保存用户自定义的字体设置"""
        try:
            CONFIG["fonts"]["title"] = self.title_font.get()
            CONFIG["fonts"]["heading_1"] = self.l1_font.get()
            CONFIG["fonts"]["heading_2"] = self.l2_font.get()
            CONFIG["fonts"]["body"] = self.body_font.get()
            
            CONFIG["font_sizes"]["title"] = int(self.title_size.get())
            CONFIG["font_sizes"]["heading_1"] = int(self.l1_size.get())
            CONFIG["font_sizes"]["heading_2"] = int(self.l2_size.get())
            CONFIG["font_sizes"]["body"] = int(self.body_size.get())
            
            self.status_var.set("✅ 配置已更新,下次处理生效")
            messagebox.showinfo("成功", "字体配置已保存!")
        except Exception as e:
            messagebox.showerror("错误", f"配置保存失败: {e}")

    def reset_config(self):
        """恢复默认配置"""
        # 这里直接写死默认值
        self.title_font.set('方正小标宋_GBK')
        self.l1_font.set('黑体')
        self.l2_font.set('楷体_GB2312')
        self.body_font.set('仿宋_GB2312')
        
        self.title_size.set(22)
        self.l1_size.set(16)
        self.l2_size.set(16)
        self.body_size.set(16)
        
        self.save_config()
        self.status_var.set("⚙️ 已恢复默认设置")

    def start_process(self):
        input_file = self.input_entry.get()
        output_dir = self.output_entry.get()

        if not input_file or not output_dir:
            messagebox.showwarning("警告", "请输入文件路径和输出目录!")
            return

        if not os.path.exists(input_file):
            messagebox.showerror("错误", "输入文件不存在!")
            return

        # 生成输出路径
        filename = os.path.basename(input_file)
        name, ext = os.path.splitext(filename)
        output_file = os.path.join(output_dir, f"{name}_已排版{ext}")

        self.status_var.set("🔄 正在处理中 (可能需要几秒)...")
        self.root.update_idletasks()

        success, msg = process_document(input_file, output_file)

        if success:
            self.status_var.set("✅ 处理完成!")
            messagebox.showinfo("成功", f"任务完成!\n\n{msg}")
        else:
            self.status_var.set("❌ 处理失败")
            messagebox.showerror("错误", f"处理失败:\n{msg}")

if __name__ == "__main__":
    root = tk.Tk()
    app = ModernTypesettingApp(root)
    root.mainloop()

💡 使用前准备

  1. 安装依赖
    如果你有 .doc 文件需要处理,请务必在终端运行:

    pip install pywin32
    

    (如果没有 .doc 文件,只处理 .docx,则不需要安装)

  2. 字体库
    代码中使用了 仿宋_GB2312楷体_GB2312。如果你的电脑(特别是非Windows系统)没有这些字体,WPS 可能会显示异常。如果报错,可以在配置界面手动选择系统中已有的字体(如“仿宋”、“楷体”)。

🛠️ 功能说明

  • Tab 1 (文档处理):选择你的 .doc.docx 文件,点击开始。
  • Tab 2 (字体配置):你可以在这里修改“文档大标题”、“一级标题”、“二级标题”和“正文”的字体及大小。修改后点击“保存配置”,下次处理文档时就会生效。

posted on 2026-04-26 16:46  刘子毅  阅读(21)  评论(0)    收藏  举报

导航