822 lines
31 KiB
Python
822 lines
31 KiB
Python
"""
|
||
文章排版样式管理模块
|
||
|
||
负责管理文档的排版样式,包括内置样式和自定义样式。
|
||
支持样式的创建、编辑、删除、导入和导出等功能。
|
||
"""
|
||
|
||
import os
|
||
import json
|
||
from typing import Dict, Any, List, Optional, Union
|
||
from dataclasses import dataclass, asdict, field
|
||
from copy import deepcopy
|
||
|
||
|
||
@dataclass
|
||
class FontStyle:
|
||
"""字体样式配置"""
|
||
name: str = "宋体"
|
||
size: int = 12
|
||
bold: bool = False
|
||
italic: bool = False
|
||
color: str = "#000000" # RGB颜色值
|
||
|
||
|
||
@dataclass
|
||
class ParagraphStyle:
|
||
"""段落样式配置"""
|
||
line_spacing: float = 1.5
|
||
space_before: int = 0 # 段前间距(磅)
|
||
space_after: int = 0 # 段后间距(磅)
|
||
first_line_indent: float = 0.0 # 首行缩进(字符)
|
||
alignment: str = "left" # left, center, right, justify
|
||
keep_with_next: bool = False # 与下段同页
|
||
|
||
|
||
@dataclass
|
||
class HeadingStyle:
|
||
"""标题样式配置"""
|
||
font: FontStyle
|
||
paragraph: ParagraphStyle
|
||
numbering: bool = False # 是否自动编号
|
||
outline_level: int = 0 # 大纲级别
|
||
|
||
|
||
@dataclass
|
||
class ListStyle:
|
||
"""列表样式配置"""
|
||
font: FontStyle
|
||
paragraph: ParagraphStyle
|
||
bullet_symbol: str = "•" # 无序列表符号
|
||
number_format: str = "1." # 有序列表格式
|
||
|
||
|
||
@dataclass
|
||
class SpecialStyle:
|
||
"""特殊元素样式配置"""
|
||
font: FontStyle
|
||
paragraph: ParagraphStyle
|
||
background_color: str = "#F5F5F5" # 背景色
|
||
border: bool = False # 是否有边框
|
||
|
||
|
||
@dataclass
|
||
class DocumentStyle:
|
||
"""文档排版样式配置"""
|
||
# 基础信息
|
||
name: str = "默认样式"
|
||
description: str = "标准文档样式"
|
||
author: str = "系统"
|
||
version: str = "1.0"
|
||
|
||
# 页面设置
|
||
page_margin_top: float = 2.54 # 页边距(cm)
|
||
page_margin_bottom: float = 2.54
|
||
page_margin_left: float = 3.17
|
||
page_margin_right: float = 3.17
|
||
|
||
# 基础字体和段落
|
||
body_font: Optional[FontStyle] = None
|
||
body_paragraph: Optional[ParagraphStyle] = None
|
||
|
||
# 标题样式 (1-6级)
|
||
heading_styles: Optional[Dict[int, HeadingStyle]] = None
|
||
|
||
# 列表样式
|
||
unordered_list: Optional[ListStyle] = None
|
||
ordered_list: Optional[ListStyle] = None
|
||
|
||
# 特殊元素样式
|
||
code_block: Optional[SpecialStyle] = None
|
||
quote_block: Optional[SpecialStyle] = None
|
||
table_style: Optional[SpecialStyle] = None
|
||
|
||
def __post_init__(self):
|
||
"""初始化默认值"""
|
||
if self.body_font is None:
|
||
self.body_font = FontStyle()
|
||
if self.body_paragraph is None:
|
||
self.body_paragraph = ParagraphStyle()
|
||
if self.heading_styles is None:
|
||
self.heading_styles = self._create_default_headings()
|
||
if self.unordered_list is None:
|
||
self.unordered_list = ListStyle(
|
||
font=FontStyle(),
|
||
paragraph=ParagraphStyle(space_after=6)
|
||
)
|
||
if self.ordered_list is None:
|
||
self.ordered_list = ListStyle(
|
||
font=FontStyle(),
|
||
paragraph=ParagraphStyle(space_after=6)
|
||
)
|
||
if self.code_block is None:
|
||
self.code_block = SpecialStyle(
|
||
font=FontStyle(name="Courier New", size=10),
|
||
paragraph=ParagraphStyle(space_before=6, space_after=6),
|
||
background_color="#F5F5F5"
|
||
)
|
||
if self.quote_block is None:
|
||
self.quote_block = SpecialStyle(
|
||
font=FontStyle(italic=True),
|
||
paragraph=ParagraphStyle(first_line_indent=2.0, space_before=6, space_after=6),
|
||
background_color="#F9F9F9"
|
||
)
|
||
if self.table_style is None:
|
||
self.table_style = SpecialStyle(
|
||
font=FontStyle(size=11),
|
||
paragraph=ParagraphStyle(space_before=6, space_after=6),
|
||
border=True
|
||
)
|
||
|
||
def _create_default_headings(self) -> Dict[int, HeadingStyle]:
|
||
"""创建默认标题样式"""
|
||
headings = {}
|
||
base_sizes = [18, 16, 14, 13, 12, 11] # 各级标题字号
|
||
|
||
for level in range(1, 7):
|
||
font_size = base_sizes[level - 1] if level <= len(base_sizes) else 11
|
||
headings[level] = HeadingStyle(
|
||
font=FontStyle(
|
||
name="微软雅黑",
|
||
size=font_size,
|
||
bold=True,
|
||
color="#1F497D"
|
||
),
|
||
paragraph=ParagraphStyle(
|
||
line_spacing=1.3,
|
||
space_before=12 if level <= 2 else 6,
|
||
space_after=6,
|
||
keep_with_next=True
|
||
),
|
||
outline_level=level
|
||
)
|
||
|
||
return headings
|
||
|
||
|
||
class StyleManager:
|
||
"""排版样式管理器"""
|
||
|
||
def __init__(self, styles_dir: str = "data/styles"):
|
||
"""
|
||
初始化样式管理器
|
||
|
||
Args:
|
||
styles_dir: 样式文件存储目录
|
||
"""
|
||
self.styles_dir = styles_dir
|
||
self.builtin_styles = self._create_builtin_styles()
|
||
self.custom_styles = {}
|
||
self._load_custom_styles()
|
||
|
||
def _create_builtin_styles(self) -> Dict[str, DocumentStyle]:
|
||
"""创建内置样式(专业版)"""
|
||
styles = {}
|
||
|
||
# 1. 爆款文章风格 - 参考知乎、头条等平台
|
||
styles["爆款文章风格"] = self._create_viral_style()
|
||
|
||
# 2. 微信公众号风格 - 专业的新媒体排版
|
||
styles["微信公众号风格"] = self._create_wechat_style()
|
||
|
||
# 3. 知乎高赞回答风格 - 逻辑清晰,层次分明
|
||
styles["知乎高赞回答风格"] = self._create_zhihu_style()
|
||
|
||
# 4. 小红书笔记风格 - 清新文艺,少女心
|
||
styles["小红书笔记风格"] = self._create_xiaohongshu_style()
|
||
|
||
# 5. 今日头条新闻风格 - 信息量大,节奏紧凑
|
||
styles["今日头条新闻风格"] = self._create_toutiao_style()
|
||
|
||
# 6. B站UP主视频脚本风格 - 轻松活泼,年轻化
|
||
styles["B站UP主视频脚本风格"] = self._create_bilibili_style()
|
||
|
||
# 7. 企业微信群通知风格 - 正式严肃
|
||
styles["企业微信群通知风格"] = self._create_enterprise_style()
|
||
|
||
# 8. 情感鸡汤文风格 - 温暖治愈
|
||
styles["情感鸡汤文风格"] = self._create_emotional_style()
|
||
|
||
return styles
|
||
|
||
def _create_viral_style(self) -> DocumentStyle:
|
||
"""创建爆款文章风格"""
|
||
viral_style = DocumentStyle(
|
||
name="爆款文章风格",
|
||
description="高阅读量爆款文章风格,层次分明,吸引眼球",
|
||
author="系统内置",
|
||
body_font=FontStyle(name="微软雅黑", size=14, color="#333333"),
|
||
body_paragraph=ParagraphStyle(
|
||
line_spacing=1.8,
|
||
first_line_indent=0.0, # 爆款文章不缩进
|
||
space_after=12,
|
||
space_before=0
|
||
)
|
||
)
|
||
# 设置爆款文章的标题样式
|
||
if viral_style.heading_styles:
|
||
# 主标题:大胆吸引眼球
|
||
viral_style.heading_styles[1].font = FontStyle(name="黑体", size=22, bold=True, color="#FF4500")
|
||
viral_style.heading_styles[1].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=20, space_after=20, alignment="center"
|
||
)
|
||
|
||
# 二级标题:强烈对比
|
||
viral_style.heading_styles[2].font = FontStyle(name="微软雅黑", size=18, bold=True, color="#FF6B35")
|
||
viral_style.heading_styles[2].paragraph = ParagraphStyle(
|
||
line_spacing=1.4, space_before=16, space_after=12, alignment="left"
|
||
)
|
||
|
||
# 三级标题:精彩分段
|
||
viral_style.heading_styles[3].font = FontStyle(name="微软雅黑", size=16, bold=True, color="#4CAF50")
|
||
viral_style.heading_styles[3].paragraph = ParagraphStyle(
|
||
line_spacing=1.4, space_before=12, space_after=8
|
||
)
|
||
|
||
# 四级及以下:细分点
|
||
for level in range(4, 7):
|
||
viral_style.heading_styles[level].font = FontStyle(name="微软雅黑", size=15, bold=True, color="#2196F3")
|
||
viral_style.heading_styles[level].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=8, space_after=6
|
||
)
|
||
|
||
# 设置特殊元素样式
|
||
viral_style.quote_block = SpecialStyle(
|
||
font=FontStyle(name="楷体", size=13, italic=True, color="#666666"),
|
||
paragraph=ParagraphStyle(
|
||
line_spacing=1.6, space_before=10, space_after=10,
|
||
first_line_indent=0, alignment="center"
|
||
),
|
||
background_color="#F8F9FA"
|
||
)
|
||
|
||
return viral_style
|
||
|
||
def _create_wechat_style(self) -> DocumentStyle:
|
||
"""创建微信公众号风格"""
|
||
wechat_style = DocumentStyle(
|
||
name="微信公众号风格",
|
||
description="专业的微信公众号排版,阅读体验佳",
|
||
author="系统内置",
|
||
body_font=FontStyle(name="微软雅黑", size=14, color="#3C4043"),
|
||
body_paragraph=ParagraphStyle(
|
||
line_spacing=1.75,
|
||
first_line_indent=0.0,
|
||
space_after=14,
|
||
space_before=0
|
||
)
|
||
)
|
||
|
||
if wechat_style.heading_styles:
|
||
# 公众号标题的精心设计
|
||
wechat_style.heading_styles[1].font = FontStyle(name="黑体", size=20, bold=True, color="#1AAD19")
|
||
wechat_style.heading_styles[1].paragraph = ParagraphStyle(
|
||
line_spacing=1.2, space_before=18, space_after=16, alignment="center"
|
||
)
|
||
|
||
wechat_style.heading_styles[2].font = FontStyle(name="微软雅黑", size=17, bold=True, color="#576B95")
|
||
wechat_style.heading_styles[2].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=14, space_after=10
|
||
)
|
||
|
||
wechat_style.heading_styles[3].font = FontStyle(name="微软雅黑", size=15, bold=True, color="#FA5151")
|
||
wechat_style.heading_styles[3].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=10, space_after=8
|
||
)
|
||
|
||
# 微信公众号的引用样式
|
||
wechat_style.quote_block = SpecialStyle(
|
||
font=FontStyle(name="微软雅黑", size=13, italic=True, color="#888888"),
|
||
paragraph=ParagraphStyle(
|
||
line_spacing=1.5, space_before=12, space_after=12,
|
||
first_line_indent=1.0, alignment="left"
|
||
),
|
||
background_color="#F7F7F7",
|
||
border=True
|
||
)
|
||
|
||
return wechat_style
|
||
|
||
def _create_zhihu_style(self) -> DocumentStyle:
|
||
"""创建知乎高赞回答风格"""
|
||
zhihu_style = DocumentStyle(
|
||
name="知乎高赞回答风格",
|
||
description="逻辑清晰,层次分明,专业权威",
|
||
author="系统内置",
|
||
body_font=FontStyle(name="微软雅黑", size=15, color="#1A1A1A"),
|
||
body_paragraph=ParagraphStyle(
|
||
line_spacing=1.6,
|
||
first_line_indent=0.0,
|
||
space_after=10,
|
||
space_before=0
|
||
)
|
||
)
|
||
|
||
if zhihu_style.heading_styles:
|
||
# 知乎风格的标题设计
|
||
zhihu_style.heading_styles[1].font = FontStyle(name="黑体", size=20, bold=True, color="#0084FF")
|
||
zhihu_style.heading_styles[1].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=16, space_after=14
|
||
)
|
||
|
||
zhihu_style.heading_styles[2].font = FontStyle(name="微软雅黑", size=17, bold=True, color="#00A6FB")
|
||
zhihu_style.heading_styles[2].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=12, space_after=10
|
||
)
|
||
|
||
zhihu_style.heading_styles[3].font = FontStyle(name="微软雅黑", size=16, bold=True, color="#FF6B6B")
|
||
zhihu_style.heading_styles[3].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=10, space_after=8
|
||
)
|
||
|
||
return zhihu_style
|
||
|
||
def _create_xiaohongshu_style(self) -> DocumentStyle:
|
||
"""创建小红书笔记风格"""
|
||
xiaohongshu_style = DocumentStyle(
|
||
name="小红书笔记风格",
|
||
description="清新文艺,适合生活方式类内容",
|
||
author="系统内置",
|
||
body_font=FontStyle(name="华文细黑", size=14, color="#333333"),
|
||
body_paragraph=ParagraphStyle(
|
||
line_spacing=1.8,
|
||
first_line_indent=0.0,
|
||
space_after=12,
|
||
space_before=0
|
||
)
|
||
)
|
||
|
||
if xiaohongshu_style.heading_styles:
|
||
xiaohongshu_style.heading_styles[1].font = FontStyle(name="华文细黑", size=18, bold=True, color="#FF69B4")
|
||
xiaohongshu_style.heading_styles[1].paragraph = ParagraphStyle(
|
||
line_spacing=1.2, space_before=15, space_after=12, alignment="center"
|
||
)
|
||
|
||
xiaohongshu_style.heading_styles[2].font = FontStyle(name="微软雅黑", size=16, bold=True, color="#FF6EB4")
|
||
xiaohongshu_style.heading_styles[2].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=12, space_after=8
|
||
)
|
||
|
||
xiaohongshu_style.heading_styles[3].font = FontStyle(name="微软雅黑", size=15, bold=True, color="#FFB6C1")
|
||
xiaohongshu_style.heading_styles[3].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=8, space_after=6
|
||
)
|
||
|
||
return xiaohongshu_style
|
||
|
||
def _create_toutiao_style(self) -> DocumentStyle:
|
||
"""创建今日头条新闻风格"""
|
||
toutiao_style = DocumentStyle(
|
||
name="今日头条新闻风格",
|
||
description="信息密度高,节奏紧凑,突出重点",
|
||
author="系统内置",
|
||
body_font=FontStyle(name="微软雅黑", size=14, color="#222222"),
|
||
body_paragraph=ParagraphStyle(
|
||
line_spacing=1.5,
|
||
first_line_indent=0.0,
|
||
space_after=8,
|
||
space_before=0
|
||
)
|
||
)
|
||
|
||
if toutiao_style.heading_styles:
|
||
toutiao_style.heading_styles[1].font = FontStyle(name="黑体", size=19, bold=True, color="#D43F3A")
|
||
toutiao_style.heading_styles[1].paragraph = ParagraphStyle(
|
||
line_spacing=1.2, space_before=12, space_after=12
|
||
)
|
||
|
||
toutiao_style.heading_styles[2].font = FontStyle(name="微软雅黑", size=16, bold=True, color="#D9534F")
|
||
toutiao_style.heading_styles[2].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=10, space_after=8
|
||
)
|
||
|
||
toutiao_style.heading_styles[3].font = FontStyle(name="微软雅黑", size=15, bold=True, color="#F0AD4E")
|
||
toutiao_style.heading_styles[3].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=8, space_after=6
|
||
)
|
||
|
||
return toutiao_style
|
||
|
||
def _create_bilibili_style(self) -> DocumentStyle:
|
||
"""创建B站UP主视频脚本风格"""
|
||
bilibili_style = DocumentStyle(
|
||
name="B站UP主视频脚本风格",
|
||
description="轻松活泼,适合年轻受众,有趣有料",
|
||
author="系统内置",
|
||
body_font=FontStyle(name="微软雅黑", size=14, color="#222222"),
|
||
body_paragraph=ParagraphStyle(
|
||
line_spacing=1.6,
|
||
first_line_indent=0.0,
|
||
space_after=10,
|
||
space_before=0
|
||
)
|
||
)
|
||
|
||
if bilibili_style.heading_styles:
|
||
bilibili_style.heading_styles[1].font = FontStyle(name="黑体", size=18, bold=True, color="#00A1D6")
|
||
bilibili_style.heading_styles[1].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=14, space_after=12
|
||
)
|
||
|
||
bilibili_style.heading_styles[2].font = FontStyle(name="微软雅黑", size=16, bold=True, color="#FB7299")
|
||
bilibili_style.heading_styles[2].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=10, space_after=8
|
||
)
|
||
|
||
bilibili_style.heading_styles[3].font = FontStyle(name="微软雅黑", size=15, bold=True, color="#9C88FF")
|
||
bilibili_style.heading_styles[3].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=8, space_after=6
|
||
)
|
||
|
||
return bilibili_style
|
||
|
||
def _create_enterprise_style(self) -> DocumentStyle:
|
||
"""创建企业微信群通知风格"""
|
||
enterprise_style = DocumentStyle(
|
||
name="企业微信群通知风格",
|
||
description="正式严肃,信息传达清晰,商务风格",
|
||
author="系统内置",
|
||
body_font=FontStyle(name="宋体", size=14, color="#2C2C2C"),
|
||
body_paragraph=ParagraphStyle(
|
||
line_spacing=1.5,
|
||
first_line_indent=2.0,
|
||
space_after=10,
|
||
space_before=0
|
||
)
|
||
)
|
||
|
||
if enterprise_style.heading_styles:
|
||
enterprise_style.heading_styles[1].font = FontStyle(name="黑体", size=18, bold=True, color="#1F4E79")
|
||
enterprise_style.heading_styles[1].paragraph = ParagraphStyle(
|
||
line_spacing=1.2, space_before=16, space_after=12, alignment="center"
|
||
)
|
||
|
||
enterprise_style.heading_styles[2].font = FontStyle(name="微软雅黑", size=16, bold=True, color="#2F5597")
|
||
enterprise_style.heading_styles[2].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=12, space_after=8
|
||
)
|
||
|
||
return enterprise_style
|
||
|
||
def _create_emotional_style(self) -> DocumentStyle:
|
||
"""创建情感鸡汤文风格"""
|
||
emotional_style = DocumentStyle(
|
||
name="情感鸡汤文风格",
|
||
description="温暖治愈,情感丰富,適合心灵鸡汤类内容",
|
||
author="系统内置",
|
||
body_font=FontStyle(name="楷体", size=14, color="#444444"),
|
||
body_paragraph=ParagraphStyle(
|
||
line_spacing=1.8,
|
||
first_line_indent=1.5,
|
||
space_after=12,
|
||
space_before=0
|
||
)
|
||
)
|
||
|
||
if emotional_style.heading_styles:
|
||
emotional_style.heading_styles[1].font = FontStyle(name="华文行楷", size=18, bold=True, color="#E91E63")
|
||
emotional_style.heading_styles[1].paragraph = ParagraphStyle(
|
||
line_spacing=1.2, space_before=16, space_after=14, alignment="center"
|
||
)
|
||
|
||
emotional_style.heading_styles[2].font = FontStyle(name="楷体", size=16, bold=True, color="#FF5722")
|
||
emotional_style.heading_styles[2].paragraph = ParagraphStyle(
|
||
line_spacing=1.3, space_before=12, space_after=10
|
||
)
|
||
|
||
# 情感鸡汤文的引用样式
|
||
emotional_style.quote_block = SpecialStyle(
|
||
font=FontStyle(name="华文行楷", size=13, italic=True, color="#795548"),
|
||
paragraph=ParagraphStyle(
|
||
line_spacing=1.6, space_before=10, space_after=10,
|
||
alignment="center"
|
||
),
|
||
background_color="#FFF3E0"
|
||
)
|
||
|
||
return emotional_style
|
||
|
||
def _load_custom_styles(self) -> None:
|
||
"""加载自定义样式"""
|
||
if not os.path.exists(self.styles_dir):
|
||
os.makedirs(self.styles_dir, exist_ok=True)
|
||
return
|
||
|
||
try:
|
||
for filename in os.listdir(self.styles_dir):
|
||
if filename.endswith('.json'):
|
||
filepath = os.path.join(self.styles_dir, filename)
|
||
style = self.load_style_from_file(filepath)
|
||
if style:
|
||
self.custom_styles[style.name] = style
|
||
except Exception as e:
|
||
print(f"加载自定义样式失败: {e}")
|
||
|
||
def get_all_styles(self) -> Dict[str, DocumentStyle]:
|
||
"""获取所有样式(内置+自定义)"""
|
||
all_styles = deepcopy(self.builtin_styles)
|
||
all_styles.update(self.custom_styles)
|
||
return all_styles
|
||
|
||
def get_style_names(self) -> List[str]:
|
||
"""获取所有样式名称"""
|
||
return list(self.get_all_styles().keys())
|
||
|
||
def get_style(self, name: str) -> Optional[DocumentStyle]:
|
||
"""
|
||
获取指定样式
|
||
|
||
Args:
|
||
name: 样式名称
|
||
|
||
Returns:
|
||
DocumentStyle: 样式对象,如果不存在返回None
|
||
"""
|
||
if name in self.builtin_styles:
|
||
return deepcopy(self.builtin_styles[name])
|
||
elif name in self.custom_styles:
|
||
return deepcopy(self.custom_styles[name])
|
||
return None
|
||
|
||
def create_custom_style(self, style: DocumentStyle) -> bool:
|
||
"""
|
||
创建自定义样式
|
||
|
||
Args:
|
||
style: 样式对象
|
||
|
||
Returns:
|
||
bool: 是否创建成功
|
||
"""
|
||
try:
|
||
print(f"尝试创建自定义样式: {style.name}") # 调试信息
|
||
|
||
if style.name in self.builtin_styles:
|
||
print(f"错误: {style.name} 是内置样式,不能覆盖")
|
||
return False # 不能覆盖内置样式
|
||
|
||
# 检查样式对象的完整性
|
||
if not style.body_font:
|
||
print("错误: 样式缺少body_font")
|
||
return False
|
||
if not style.body_paragraph:
|
||
print("错误: 样式缺少body_paragraph")
|
||
return False
|
||
|
||
print("样式对象检查通过")
|
||
|
||
# 保存到内存
|
||
self.custom_styles[style.name] = style
|
||
print(f"样式已保存到内存: {style.name}")
|
||
|
||
# 保存到文件
|
||
file_result = self.save_style_to_file(style)
|
||
print(f"文件保存结果: {file_result}")
|
||
|
||
if not file_result:
|
||
# 如果文件保存失败,从内存中移除
|
||
del self.custom_styles[style.name]
|
||
print("文件保存失败,已从内存中移除")
|
||
return False
|
||
|
||
print(f"样式 '{style.name}' 创建成功")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"创建自定义样式异常: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def update_custom_style(self, style: DocumentStyle) -> bool:
|
||
"""
|
||
更新自定义样式
|
||
|
||
Args:
|
||
style: 样式对象
|
||
|
||
Returns:
|
||
bool: 是否更新成功
|
||
"""
|
||
try:
|
||
if style.name in self.builtin_styles:
|
||
return False # 不能修改内置样式
|
||
|
||
self.custom_styles[style.name] = style
|
||
self.save_style_to_file(style)
|
||
return True
|
||
except Exception as e:
|
||
print(f"更新自定义样式失败: {e}")
|
||
return False
|
||
|
||
def delete_custom_style(self, name: str) -> bool:
|
||
"""
|
||
删除自定义样式
|
||
|
||
Args:
|
||
name: 样式名称
|
||
|
||
Returns:
|
||
bool: 是否删除成功
|
||
"""
|
||
try:
|
||
if name in self.builtin_styles:
|
||
return False # 不能删除内置样式
|
||
|
||
if name in self.custom_styles:
|
||
del self.custom_styles[name]
|
||
# 删除文件
|
||
filepath = os.path.join(self.styles_dir, f"{name}.json")
|
||
if os.path.exists(filepath):
|
||
os.remove(filepath)
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
print(f"删除自定义样式失败: {e}")
|
||
return False
|
||
|
||
def save_style_to_file(self, style: DocumentStyle, filepath: Optional[str] = None) -> bool:
|
||
"""
|
||
保存样式到文件
|
||
|
||
Args:
|
||
style: 样式对象
|
||
filepath: 文件路径,如果为None则使用默认路径
|
||
|
||
Returns:
|
||
bool: 是否保存成功
|
||
"""
|
||
try:
|
||
if filepath is None:
|
||
os.makedirs(self.styles_dir, exist_ok=True)
|
||
filepath = os.path.join(self.styles_dir, f"{style.name}.json")
|
||
|
||
print(f"保存样式到文件: {filepath}") # 调试信息
|
||
|
||
# 检查目录权限
|
||
if not os.access(self.styles_dir, os.W_OK):
|
||
print(f"错误: 没有写入权限 - {self.styles_dir}")
|
||
return False
|
||
|
||
# 尝试序列化样式对象
|
||
style_dict = asdict(style)
|
||
print("样式对象序列化成功")
|
||
|
||
# 尝试写入文件
|
||
with open(filepath, 'w', encoding='utf-8') as f:
|
||
json.dump(style_dict, f, ensure_ascii=False, indent=2)
|
||
|
||
print(f"样式文件保存成功: {filepath}")
|
||
|
||
# 验证文件是否存在且可读
|
||
if os.path.exists(filepath) and os.path.getsize(filepath) > 0:
|
||
print("文件存在性验证通过")
|
||
return True
|
||
else:
|
||
print("错误: 文件保存后验证失败")
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"保存样式文件异常: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def load_style_from_file(self, filepath: str) -> Optional[DocumentStyle]:
|
||
"""
|
||
从文件加载样式
|
||
|
||
Args:
|
||
filepath: 文件路径
|
||
|
||
Returns:
|
||
DocumentStyle: 样式对象,加载失败返回None
|
||
"""
|
||
try:
|
||
with open(filepath, 'r', encoding='utf-8') as f:
|
||
style_dict = json.load(f)
|
||
|
||
# 递归转换字典为dataclass
|
||
style = self._dict_to_style(style_dict)
|
||
return style
|
||
except Exception as e:
|
||
print(f"加载样式文件失败: {e}")
|
||
return None
|
||
|
||
def _dict_to_style(self, data: Dict[str, Any]) -> DocumentStyle:
|
||
"""将字典转换为DocumentStyle对象"""
|
||
# 转换嵌套的字体样式
|
||
if 'body_font' in data and data['body_font']:
|
||
data['body_font'] = FontStyle(**data['body_font'])
|
||
|
||
# 转换段落样式
|
||
if 'body_paragraph' in data and data['body_paragraph']:
|
||
data['body_paragraph'] = ParagraphStyle(**data['body_paragraph'])
|
||
|
||
# 转换标题样式
|
||
if 'heading_styles' in data and data['heading_styles']:
|
||
heading_styles = {}
|
||
for level, heading_data in data['heading_styles'].items():
|
||
if isinstance(heading_data, dict):
|
||
font_data = heading_data.get('font', {})
|
||
para_data = heading_data.get('paragraph', {})
|
||
heading_styles[int(level)] = HeadingStyle(
|
||
font=FontStyle(**font_data) if font_data else FontStyle(),
|
||
paragraph=ParagraphStyle(**para_data) if para_data else ParagraphStyle(),
|
||
numbering=heading_data.get('numbering', False),
|
||
outline_level=heading_data.get('outline_level', int(level))
|
||
)
|
||
data['heading_styles'] = heading_styles
|
||
|
||
# 转换列表样式
|
||
for list_type in ['unordered_list', 'ordered_list']:
|
||
if list_type in data and data[list_type]:
|
||
list_data = data[list_type]
|
||
font_data = list_data.get('font', {})
|
||
para_data = list_data.get('paragraph', {})
|
||
data[list_type] = ListStyle(
|
||
font=FontStyle(**font_data) if font_data else FontStyle(),
|
||
paragraph=ParagraphStyle(**para_data) if para_data else ParagraphStyle(),
|
||
bullet_symbol=list_data.get('bullet_symbol', '•'),
|
||
number_format=list_data.get('number_format', '1.')
|
||
)
|
||
|
||
# 转换特殊样式
|
||
for special_type in ['code_block', 'quote_block', 'table_style']:
|
||
if special_type in data and data[special_type]:
|
||
special_data = data[special_type]
|
||
font_data = special_data.get('font', {})
|
||
para_data = special_data.get('paragraph', {})
|
||
data[special_type] = SpecialStyle(
|
||
font=FontStyle(**font_data) if font_data else FontStyle(),
|
||
paragraph=ParagraphStyle(**para_data) if para_data else ParagraphStyle(),
|
||
background_color=special_data.get('background_color', '#F5F5F5'),
|
||
border=special_data.get('border', False)
|
||
)
|
||
|
||
return DocumentStyle(**data)
|
||
|
||
def export_style(self, name: str, export_path: str) -> bool:
|
||
"""
|
||
导出样式到指定路径
|
||
|
||
Args:
|
||
name: 样式名称
|
||
export_path: 导出路径
|
||
|
||
Returns:
|
||
bool: 是否导出成功
|
||
"""
|
||
style = self.get_style(name)
|
||
if style:
|
||
return self.save_style_to_file(style, export_path)
|
||
return False
|
||
|
||
def import_style(self, import_path: str) -> Optional[str]:
|
||
"""
|
||
从指定路径导入样式
|
||
|
||
Args:
|
||
import_path: 导入路径
|
||
|
||
Returns:
|
||
Optional[str]: 导入的样式名称,失败返回None
|
||
"""
|
||
style = self.load_style_from_file(import_path)
|
||
if style:
|
||
if self.create_custom_style(style):
|
||
return style.name
|
||
return None
|
||
|
||
def duplicate_style(self, source_name: str, new_name: str) -> bool:
|
||
"""
|
||
复制样式
|
||
|
||
Args:
|
||
source_name: 源样式名称
|
||
new_name: 新样式名称
|
||
|
||
Returns:
|
||
bool: 是否复制成功
|
||
"""
|
||
source_style = self.get_style(source_name)
|
||
if source_style and new_name not in self.get_all_styles():
|
||
source_style.name = new_name
|
||
source_style.description = f"基于 {source_name} 复制"
|
||
return self.create_custom_style(source_style)
|
||
return False
|
||
|
||
|
||
# 创建全局样式管理器实例
|
||
style_manager = StyleManager()
|
||
|
||
|
||
# 兼容性函数
|
||
def get_all_styles() -> Dict[str, DocumentStyle]:
|
||
"""获取所有样式(兼容旧接口)"""
|
||
return style_manager.get_all_styles()
|
||
|
||
|
||
def get_style(name: str) -> Optional[DocumentStyle]:
|
||
"""获取指定样式(兼容旧接口)"""
|
||
return style_manager.get_style(name) |