更新文本样式高级编辑功能

This commit is contained in:
taiyi 2025-09-22 21:10:29 +08:00
parent 93781d3ebf
commit a91445add9
4 changed files with 241 additions and 87 deletions

View File

@ -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:
"""重置所有配置为默认值"""

View File

@ -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'(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|_(.+?)_', r'\1\2',
processed_text[fmt['start']:fmt['end']])
run = paragraph.add_run(clean_text)
self._apply_body_font_style(run)
run.italic = True
elif fmt['type'] == 'code':
clean_text = re.sub(r'`([^`]+)`', r'\1', processed_text[fmt['start']:fmt['end']])
run = paragraph.add_run(clean_text)
run.font.name = 'Courier New'
run.font.size = Pt(10)
# 代码样式优先使用样式中的设置
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)
if code_style.font.color != "#000000":
run.font.color.rgb = RGBColor.from_string(code_style.font.color.replace('#', ''))
else:
run.font.name = 'Courier New'
run.font.size = Pt(10)
elif fmt['type'] == 'strikethrough':
clean_text = re.sub(r'~~(.+?)~~', r'\1', processed_text[fmt['start']:fmt['end']])
run = paragraph.add_run(clean_text)
self._apply_body_font_style(run)
run.font.strike = True
elif fmt['type'] == 'link':
# 对于链接,只显示链接文本
run = paragraph.add_run(fmt['text'])
self._apply_body_font_style(run)
run.font.color.rgb = RGBColor(0, 0, 255) # 蓝色
run.underline = True
@ -265,9 +396,26 @@ class DocxGenerator:
# 添加剩余的普通文本
if current_pos < len(processed_text):
paragraph.add_run(processed_text[current_pos:])
run = paragraph.add_run(processed_text[current_pos:])
self._apply_body_font_style(run)
def _add_code_block(self, doc: Document, content: str, language: str) -> 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:
"""
添加免责声明

View File

@ -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

11
main.py
View File

@ -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():