2026-04-23 14:36:26 +08:00

112 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
文件样式管理器
- 提供预设管理、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')