diff --git a/config.py b/config.py index c942a9a..58790d8 100644 --- a/config.py +++ b/config.py @@ -45,6 +45,10 @@ class Config: # 文档格式配置 self.line_spacing = 1.5 self.title_levels = 6 # 支持的最大标题层级 + + # 排版样式配置 + self.current_style = "爆款文章风格" # 当前选中的样式 + self.use_custom_style = False # 是否使用自定义样式 def load_from_file(self, file_path: str) -> bool: """ @@ -97,6 +101,8 @@ class Config: section = config_parser['DocumentFormat'] self.line_spacing = section.getfloat('line_spacing', self.line_spacing) self.title_levels = section.getint('title_levels', self.title_levels) + self.current_style = section.get('current_style', self.current_style) + self.use_custom_style = section.getboolean('use_custom_style', self.use_custom_style) return True @@ -149,7 +155,9 @@ class Config: # 保存文档格式配置 config_parser['DocumentFormat'] = { 'line_spacing': str(self.line_spacing), - 'title_levels': str(self.title_levels) + 'title_levels': str(self.title_levels), + 'current_style': self.current_style, + 'use_custom_style': str(self.use_custom_style) } # 确保目录存在 @@ -197,7 +205,9 @@ class Config: }, 'document_format': { 'line_spacing': self.line_spacing, - 'title_levels': self.title_levels + 'title_levels': self.title_levels, + 'current_style': self.current_style, + 'use_custom_style': self.use_custom_style } } @@ -242,6 +252,8 @@ class Config: df = config_dict['document_format'] self.line_spacing = df.get('line_spacing', self.line_spacing) self.title_levels = df.get('title_levels', self.title_levels) + self.current_style = df.get('current_style', self.current_style) + self.use_custom_style = df.get('use_custom_style', self.use_custom_style) def reset_to_defaults(self) -> None: """重置所有配置为默认值""" diff --git a/docx_generator.py b/docx_generator.py index 3cb072c..24f4c15 100644 --- a/docx_generator.py +++ b/docx_generator.py @@ -8,6 +8,7 @@ import os import re from typing import List, Dict, Any, Callable, Optional from docx import Document +from docx.document import Document as DocxDocument from docx.shared import Inches, Pt, RGBColor from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.style import WD_STYLE_TYPE @@ -16,6 +17,7 @@ from config import config from text_processor import text_processor from image_processor import ImageProcessor from markdown_parser import MarkdownParser +from style_manager import style_manager # 免责声明文本 @@ -28,6 +30,7 @@ class DocxGenerator: def __init__(self): """初始化DOCX生成器""" self.temp_files = [] # 跟踪临时文件以便清理 + self.current_document_style = None # 当前使用的文档样式 def generate(self, sections: List[Dict[str, Any]], image_files: List[str], output_path: str, progress_callback: Optional[Callable] = None) -> bool: @@ -81,7 +84,7 @@ class DocxGenerator: # 清理临时文件 self._cleanup_temp_files() - def _setup_document_styles(self, doc: Document) -> None: + def _setup_document_styles(self, doc) -> None: """ 设置文档样式 @@ -89,19 +92,19 @@ class DocxGenerator: doc: DOCX文档对象 """ try: - # 设置默认字体和行距 - styles = doc.styles - - # 设置正文样式 - if 'Normal' in styles: - normal_style = styles['Normal'] - if config.line_spacing > 0: - normal_style.paragraph_format.line_spacing = config.line_spacing + # 获取当前选中的样式 + current_style = style_manager.get_style(config.current_style) + if not current_style: + print(f"警告: 找不到样式 '{config.current_style}',使用默认样式") + return + + self.current_document_style = current_style + print(f"应用文档样式: {current_style.name}") except Exception as e: print(f"设置文档样式时出错: {e}") - def _add_section_to_doc(self, doc: Document, section: Dict[str, Any], + def _add_section_to_doc(self, doc: DocxDocument, section: Dict[str, Any], image_files: List[str], image_index: int, image_count: int, output_path: str) -> int: """ @@ -122,14 +125,69 @@ class DocxGenerator: if section['level'] > 0 and section['level'] <= config.title_levels: heading_text = text_processor.process_text_content(section['content']) para = doc.add_heading(level=section['level']) + # 清空默认内容,应用自定义样式 + para.clear() + run = para.add_run(heading_text) + + # 应用标题样式 + if hasattr(self, 'current_document_style') and self.current_document_style and self.current_document_style.heading_styles: + if section['level'] in self.current_document_style.heading_styles: + heading_style = self.current_document_style.heading_styles[section['level']] + if heading_style.font: + run.font.name = heading_style.font.name + run.font.size = Pt(heading_style.font.size) + run.font.bold = heading_style.font.bold + run.font.italic = heading_style.font.italic + if heading_style.font.color != "#000000": + run.font.color.rgb = RGBColor.from_string(heading_style.font.color.replace('#', '')) + + if heading_style.paragraph: + para_style = heading_style.paragraph + if para_style.line_spacing > 0: + para.paragraph_format.line_spacing = para_style.line_spacing + if para_style.space_before > 0: + para.paragraph_format.space_before = Pt(para_style.space_before) + if para_style.space_after > 0: + para.paragraph_format.space_after = Pt(para_style.space_after) + if para_style.first_line_indent > 0: + para.paragraph_format.first_line_indent = Pt(para_style.first_line_indent * 12) + + # 设置对齐方式 + if para_style.alignment == "center": + para.alignment = WD_ALIGN_PARAGRAPH.CENTER + elif para_style.alignment == "right": + para.alignment = WD_ALIGN_PARAGRAPH.RIGHT + elif para_style.alignment == "justify": + para.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + else: + para.alignment = WD_ALIGN_PARAGRAPH.LEFT + else: + # 默认样式 + run.font.size = Pt(18 - section['level'] * 2 if section['level'] <= 6 else 10) + run.font.bold = True + self._apply_inline_formatting(para, heading_text) elif section['content'] != '前置内容': heading_text = text_processor.process_text_content(section['content']) para = doc.add_paragraph() run = para.add_run(heading_text) - run.font.size = Pt(14) - run.font.bold = True - para.space_after = Pt(12) + + # 应用样式设置 + if hasattr(self, 'current_document_style') and self.current_document_style and self.current_document_style.heading_styles: + if section['level'] in self.current_document_style.heading_styles: + heading_style = self.current_document_style.heading_styles[section['level']] + if heading_style.font: + run.font.name = heading_style.font.name + run.font.size = Pt(heading_style.font.size) + run.font.bold = heading_style.font.bold + run.font.italic = heading_style.font.italic + if heading_style.font.color != "#000000": + run.font.color.rgb = RGBColor.from_string(heading_style.font.color.replace('#', '')) + else: + run.font.size = Pt(14) + run.font.bold = True + + para.paragraph_format.space_after = Pt(12) # 处理章节中的元素 elements = section.get('elements', []) @@ -150,7 +208,7 @@ class DocxGenerator: return image_index - def _add_element_to_doc(self, doc: Document, element: Dict[str, Any]) -> None: + def _add_element_to_doc(self, doc: DocxDocument, element: Dict[str, Any]) -> None: """ 将解析的元素添加到文档中 @@ -167,14 +225,50 @@ class DocxGenerator: elif element_type == 'unordered_list': para = doc.add_paragraph(style='List Bullet') self._apply_inline_formatting(para, content) + # 应用列表样式 + if hasattr(self, 'current_document_style') and self.current_document_style and self.current_document_style.unordered_list: + list_style = self.current_document_style.unordered_list + if list_style.paragraph: + if list_style.paragraph.space_before > 0: + para.paragraph_format.space_before = Pt(list_style.paragraph.space_before) + if list_style.paragraph.space_after > 0: + para.paragraph_format.space_after = Pt(list_style.paragraph.space_after) elif element_type == 'ordered_list': para = doc.add_paragraph(style='List Number') self._apply_inline_formatting(para, content) + # 应用列表样式 + if hasattr(self, 'current_document_style') and self.current_document_style and self.current_document_style.ordered_list: + list_style = self.current_document_style.ordered_list + if list_style.paragraph: + if list_style.paragraph.space_before > 0: + para.paragraph_format.space_before = Pt(list_style.paragraph.space_before) + if list_style.paragraph.space_after > 0: + para.paragraph_format.space_after = Pt(list_style.paragraph.space_after) elif element_type == 'blockquote': para = doc.add_paragraph(style='Quote') self._apply_inline_formatting(para, content) + # 应用引用样式 + if hasattr(self, 'current_document_style') and self.current_document_style and self.current_document_style.quote_block: + quote_style = self.current_document_style.quote_block + if quote_style.paragraph: + if quote_style.paragraph.line_spacing > 0: + para.paragraph_format.line_spacing = quote_style.paragraph.line_spacing + if quote_style.paragraph.space_before > 0: + para.paragraph_format.space_before = Pt(quote_style.paragraph.space_before) + if quote_style.paragraph.space_after > 0: + para.paragraph_format.space_after = Pt(quote_style.paragraph.space_after) + if quote_style.paragraph.first_line_indent > 0: + para.paragraph_format.first_line_indent = Pt(quote_style.paragraph.first_line_indent * 12) + + # 设置对齐方式 + if quote_style.paragraph.alignment == "center": + para.alignment = WD_ALIGN_PARAGRAPH.CENTER + elif quote_style.paragraph.alignment == "right": + para.alignment = WD_ALIGN_PARAGRAPH.RIGHT + elif quote_style.paragraph.alignment == "justify": + para.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY elif element_type == 'code_block': self._add_code_block(doc, element.get('content', ''), element.get('language', '')) @@ -188,7 +282,7 @@ class DocxGenerator: elif element_type == 'empty': doc.add_paragraph() - def _add_formatted_paragraph(self, doc: Document, content: str) -> None: + def _add_formatted_paragraph(self, doc: DocxDocument, content: str) -> None: """ 添加带格式的段落 @@ -203,7 +297,29 @@ class DocxGenerator: para = doc.add_paragraph() self._apply_inline_formatting(para, content) - if config.line_spacing > 0: + # 应用样式中的段落格式 + if hasattr(self, 'current_document_style') and self.current_document_style: + if self.current_document_style.body_paragraph: + body_para = self.current_document_style.body_paragraph + if body_para.line_spacing > 0: + para.paragraph_format.line_spacing = body_para.line_spacing + if body_para.space_before > 0: + para.paragraph_format.space_before = Pt(body_para.space_before) + if body_para.space_after > 0: + para.paragraph_format.space_after = Pt(body_para.space_after) + if body_para.first_line_indent > 0: + para.paragraph_format.first_line_indent = Pt(body_para.first_line_indent * 12) # 字符转磅 + + # 设置对齐方式 + if body_para.alignment == "center": + para.alignment = WD_ALIGN_PARAGRAPH.CENTER + elif body_para.alignment == "right": + para.alignment = WD_ALIGN_PARAGRAPH.RIGHT + elif body_para.alignment == "justify": + para.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + else: + para.alignment = WD_ALIGN_PARAGRAPH.LEFT + elif config.line_spacing > 0: para.paragraph_format.line_spacing = config.line_spacing def _apply_inline_formatting(self, paragraph, text: str) -> None: @@ -222,7 +338,8 @@ class DocxGenerator: # 如果没有格式,直接添加文本 if not formatting: - paragraph.add_run(processed_text) + run = paragraph.add_run(processed_text) + self._apply_body_font_style(run) return current_pos = 0 @@ -230,34 +347,48 @@ class DocxGenerator: for fmt in formatting: # 添加格式前的普通文本 if fmt['start'] > current_pos: - paragraph.add_run(processed_text[current_pos:fmt['start']]) + run = paragraph.add_run(processed_text[current_pos:fmt['start']]) + self._apply_body_font_style(run) # 创建格式化的run if fmt['type'] == 'bold': clean_text = re.sub(r'\*\*(.+?)\*\*|__(.+?)__', r'\1\2', processed_text[fmt['start']:fmt['end']]) run = paragraph.add_run(clean_text) + self._apply_body_font_style(run) run.bold = True elif fmt['type'] == 'italic': clean_text = re.sub(r'(? None: + def _apply_body_font_style(self, run) -> None: + """ + 应用正文字体样式到run + + Args: + run: DOCX run对象 + """ + if hasattr(self, 'current_document_style') and self.current_document_style and self.current_document_style.body_font: + body_font = self.current_document_style.body_font + run.font.name = body_font.name + run.font.size = Pt(body_font.size) + run.font.bold = body_font.bold + run.font.italic = body_font.italic + if body_font.color != "#000000": + run.font.color.rgb = RGBColor.from_string(body_font.color.replace('#', '')) + + def _add_code_block(self, doc: DocxDocument, content: str, language: str) -> None: """ 添加代码块 @@ -278,17 +426,32 @@ class DocxGenerator: """ para = doc.add_paragraph(style='No Spacing') run = para.add_run(content) - run.font.name = 'Courier New' - run.font.size = Pt(10) - # 设置背景色(如果支持) - try: + # 应用代码块样式 + if hasattr(self, 'current_document_style') and self.current_document_style and self.current_document_style.code_block: + code_style = self.current_document_style.code_block + if code_style.font: + run.font.name = code_style.font.name + run.font.size = Pt(code_style.font.size) + run.font.bold = code_style.font.bold + run.font.italic = code_style.font.italic + if code_style.font.color != "#000000": + run.font.color.rgb = RGBColor.from_string(code_style.font.color.replace('#', '')) + + if code_style.paragraph: + para_style = code_style.paragraph + if para_style.space_before > 0: + para.paragraph_format.space_before = Pt(para_style.space_before) + if para_style.space_after > 0: + para.paragraph_format.space_after = Pt(para_style.space_after) + else: + # 默认样式 + run.font.name = 'Courier New' + run.font.size = Pt(10) para.paragraph_format.space_before = Pt(6) para.paragraph_format.space_after = Pt(6) - except: - pass - def _add_table_to_doc(self, doc: Document, rows: List[List[str]]) -> None: + def _add_table_to_doc(self, doc: DocxDocument, rows: List[List[str]]) -> None: """ 添加表格到文档 @@ -307,9 +470,29 @@ class DocxGenerator: for j, cell_data in enumerate(row_data): if j < len(row_cells): processed_text = text_processor.process_text_content(cell_data) - row_cells[j].text = processed_text + cell_para = row_cells[j].paragraphs[0] + cell_para.clear() + run = cell_para.add_run(processed_text) + + # 应用表格样式 + if hasattr(self, 'current_document_style') and self.current_document_style and self.current_document_style.table_style: + table_style = self.current_document_style.table_style + if table_style.font: + run.font.name = table_style.font.name + run.font.size = Pt(table_style.font.size) + run.font.bold = table_style.font.bold + run.font.italic = table_style.font.italic + if table_style.font.color != "#000000": + run.font.color.rgb = RGBColor.from_string(table_style.font.color.replace('#', '')) + + if table_style.paragraph: + para_style = table_style.paragraph + if para_style.space_before > 0: + cell_para.paragraph_format.space_before = Pt(para_style.space_before) + if para_style.space_after > 0: + cell_para.paragraph_format.space_after = Pt(para_style.space_after) - def _add_horizontal_rule(self, doc: Document) -> None: + def _add_horizontal_rule(self, doc: DocxDocument) -> None: """ 在文档中添加横线 @@ -322,7 +505,7 @@ class DocxGenerator: run.text = " " * 100 # 足够长的下划线作为横线 para.alignment = WD_ALIGN_PARAGRAPH.CENTER - def _insert_section_image(self, doc: Document, image_files: List[str], + def _insert_section_image(self, doc: DocxDocument, image_files: List[str], image_index: int, image_count: int, output_path: str) -> int: """ 为章节插入图片 @@ -358,7 +541,7 @@ class DocxGenerator: return image_index - def _insert_image(self, doc: Document, image_path: str, output_path: str) -> None: + def _insert_image(self, doc: DocxDocument, image_path: str, output_path: str) -> None: """ 插入图片到文档 @@ -392,7 +575,7 @@ class DocxGenerator: except Exception as e: raise Exception(f"插入图片失败: {str(e)}") - def _add_disclaimer(self, doc: Document) -> None: + def _add_disclaimer(self, doc: DocxDocument) -> None: """ 添加免责声明 diff --git a/gui_config.py b/gui_config.py index c2b1ee2..229755b 100644 --- a/gui_config.py +++ b/gui_config.py @@ -7,6 +7,7 @@ GUI配置窗口模块 import tkinter as tk from tkinter import ttk, filedialog, messagebox from config import config +from gui_style_manager import create_style_tab def show_config_window(): @@ -37,10 +38,9 @@ def show_config_window(): notebook.add(image_frame, text='图片处理') _create_image_tab(image_frame) - # 文档格式选项卅 - format_frame = ttk.Frame(notebook) - notebook.add(format_frame, text='文档格式') - _create_format_tab(format_frame) + # 文章样式选项卡 + style_frame = create_style_tab(notebook) + notebook.add(style_frame, text='文章样式') # 底部按钮 button_frame = ttk.Frame(config_window) @@ -238,32 +238,6 @@ def _create_image_tab(parent): strategy_var.trace('w', lambda *args: setattr(config, 'image_strategy', strategy_var.get())) -def _create_format_tab(parent): - """创建文档格式选项卡""" - # 标题 - ttk.Label(parent, text='文档格式设置', font=('', 12, 'bold')).pack(anchor='w', padx=10, pady=(10, 5)) - ttk.Separator(parent, orient='horizontal').pack(fill='x', padx=10, pady=5) - - # 行间距 - spacing_frame = ttk.Frame(parent) - spacing_frame.pack(fill='x', padx=10, pady=5) - ttk.Label(spacing_frame, text='行间距:', width=12).pack(side='left') - spacing_var = tk.StringVar(value=str(config.line_spacing)) - spacing_entry = ttk.Entry(spacing_frame, textvariable=spacing_var, width=8) - spacing_entry.pack(side='left') - spacing_var.trace('w', lambda *args: _update_line_spacing(spacing_var.get())) - - # 最大标题层级 - title_frame = ttk.Frame(parent) - title_frame.pack(fill='x', padx=10, pady=5) - ttk.Label(title_frame, text='最大标题层级:', width=12).pack(side='left') - title_var = tk.StringVar(value=str(config.title_levels)) - title_combo = ttk.Combobox(title_frame, textvariable=title_var, values=['1', '2', '3', '4', '5', '6'], - state='readonly', width=8) - title_combo.pack(side='left') - title_var.trace('w', lambda *args: _update_title_levels(title_var.get())) - - def _update_image_width(value): """更新图片宽度""" try: @@ -272,22 +246,6 @@ def _update_image_width(value): pass -def _update_line_spacing(value): - """更新行间距""" - try: - config.line_spacing = float(value) - except: - pass - - -def _update_title_levels(value): - """更新标题层级""" - try: - config.title_levels = int(value) - except: - pass - - def _reset_to_default(char_vars): """重置为默认值""" from config import Config diff --git a/main.py b/main.py index 30fa3e0..d282233 100644 --- a/main.py +++ b/main.py @@ -193,16 +193,16 @@ class TxtToDocxApp: self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, maximum=100) # 状态栏和按钮区域 - bottom_frame = ttk.Frame(main_frame) - bottom_frame.pack(fill='x', pady=(0, 0)) + self.bottom_frame = ttk.Frame(main_frame) + self.bottom_frame.pack(fill='x', pady=(0, 0)) # 状态文本 self.status_text = tk.StringVar(value='状态: 就绪') - status_label = ttk.Label(bottom_frame, textvariable=self.status_text) + status_label = ttk.Label(self.bottom_frame, textvariable=self.status_text) status_label.pack(side='left') # 右侧按钮 - right_buttons = ttk.Frame(bottom_frame) + right_buttons = ttk.Frame(self.bottom_frame) right_buttons.pack(side='right') self.convert_btn = ttk.Button(right_buttons, text='开始批量转换', @@ -348,7 +348,8 @@ class TxtToDocxApp: # 禁用按钮,显示进度条 self.convert_btn.configure(state='disabled') - self.progress_bar.pack(fill='x', padx=10, pady=5, before=self.root.children[list(self.root.children.keys())[-2]]) + # 将进度条插入到底部框架之前 + self.progress_bar.pack(fill='x', padx=10, pady=5, before=self.bottom_frame) # 在线程中执行转换,避免界面卡死 def run_conversion():