112 lines
4.1 KiB
Python
112 lines
4.1 KiB
Python
"""
|
||
文件样式管理器
|
||
- 提供预设管理、Docx<->HTML映射、验证
|
||
- 与exporter.py集成, 支持首页「文件样式设置」UI
|
||
- 符合图片UI中的所有控制(字体、字号、边距、页眉页脚、方案页数等)
|
||
"""
|
||
import json
|
||
import os
|
||
from docx import Document
|
||
from docx.shared import Pt, Cm
|
||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||
import config
|
||
|
||
DEFAULT_PRESETS = {
|
||
'standard': {
|
||
'name': '标准投标格式',
|
||
'body_font': '宋体',
|
||
'body_size_pt': 12,
|
||
'body_line_spacing': 1.5,
|
||
'heading_font': '黑体',
|
||
'heading1_size_pt': 16,
|
||
'heading2_size_pt': 14,
|
||
'margins_cm': {'top': 2.5, 'bottom': 2.5, 'left': 3.0, 'right': 2.5},
|
||
'header_text': '标桥AI编标',
|
||
'footer_text': '第 {page} 页 / 共 {total} 页',
|
||
'page_count_target': 100,
|
||
'figure_enabled': True,
|
||
'table_enabled': True,
|
||
'description': '招标文件标准格式,宋体正文,黑体标题,标准边距'
|
||
},
|
||
'detailed': {
|
||
'name': '详细技术方案',
|
||
'body_font': '宋体',
|
||
'body_size_pt': 11,
|
||
'body_line_spacing': 1.8,
|
||
'heading_font': '黑体',
|
||
'heading1_size_pt': 18,
|
||
'heading2_size_pt': 14,
|
||
'margins_cm': {'top': 2.8, 'bottom': 2.8, 'left': 3.2, 'right': 2.8},
|
||
'header_text': '',
|
||
'footer_text': '',
|
||
'page_count_target': 200,
|
||
'figure_enabled': True,
|
||
'table_enabled': True,
|
||
'description': '详细版,更多图表,较大页数'
|
||
}
|
||
}
|
||
|
||
def get_preset(name='standard'):
|
||
"""返回预设配置"""
|
||
return DEFAULT_PRESETS.get(name, DEFAULT_PRESETS['standard'])
|
||
|
||
def save_preset(name, config_dict):
|
||
"""保存自定义预设到 data/style_presets.json"""
|
||
path = os.path.join(config.DATA_DIR, 'style_presets.json')
|
||
os.makedirs(config.DATA_DIR, exist_ok=True)
|
||
try:
|
||
if os.path.exists(path):
|
||
with open(path, 'r', encoding='utf-8') as f:
|
||
presets = json.load(f)
|
||
else:
|
||
presets = {}
|
||
presets[name] = config_dict
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
json.dump(presets, f, ensure_ascii=False, indent=2)
|
||
return True
|
||
except Exception:
|
||
return False
|
||
|
||
def apply_preset_to_document(doc: Document, preset: dict):
|
||
"""将预设应用到Document,覆盖exporter.py硬编码值"""
|
||
# Page setup
|
||
section = doc.sections[0]
|
||
m = preset.get('margins_cm', {'top': 2.5, 'bottom': 2.5, 'left': 3.0, 'right': 2.5})
|
||
section.top_margin = Cm(m['top'])
|
||
section.bottom_margin = Cm(m['bottom'])
|
||
section.left_margin = Cm(m['left'])
|
||
section.right_margin = Cm(m['right'])
|
||
|
||
# Header / Footer (basic)
|
||
if preset.get('header_text'):
|
||
header = section.header
|
||
if header.paragraphs:
|
||
p = header.paragraphs[0]
|
||
p.text = preset['header_text']
|
||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||
if preset.get('footer_text'):
|
||
footer = section.footer
|
||
if footer.paragraphs:
|
||
p = footer.paragraphs[0]
|
||
p.text = preset['footer_text']
|
||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||
|
||
# The rest of the document (headings, body) is applied in exporter by reading preset
|
||
return doc
|
||
|
||
def docx_to_html_spec(preset):
|
||
"""Docx preset -> HTML/CSS for preview/dark-bid"""
|
||
return {
|
||
'body': f'font-family: {preset.get("body_font", "宋体")}; font-size: {preset.get("body_size_pt", 12)}pt; line-height: {preset.get("body_line_spacing", 1.5)};',
|
||
'heading1': f'font-family: {preset.get("heading_font", "黑体")}; font-size: 16pt; font-weight: bold;',
|
||
'margins': preset.get('margins_cm', {}),
|
||
'header': preset.get('header_text', ''),
|
||
'footer': preset.get('footer_text', ''),
|
||
}
|
||
|
||
# For future AI extraction in parser
|
||
def extract_style_hints_from_text(text: str):
|
||
"""Placeholder for AI to extract style requirements from tender text"""
|
||
# Can be expanded with ai_client.chat using a prompt for "提取字体、页边距、图表要求"
|
||
return get_preset('standard')
|