581 lines
28 KiB
Python
581 lines
28 KiB
Python
# Copyright (c) 2025-2026 Jeffrey Hsu - JITToolBox
|
||
# #
|
||
# This program is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU General Public License as published by
|
||
# the Free Software Foundation, either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
# #
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU General Public License for more details.
|
||
# #
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
||
import io
|
||
import os
|
||
import traceback
|
||
from typing import Callable
|
||
|
||
from docx import Document
|
||
from docx.enum.section import WD_ORIENT
|
||
from docx.enum.table import WD_ALIGN_VERTICAL, WD_ROW_HEIGHT_RULE
|
||
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
|
||
from docx.oxml import OxmlElement
|
||
from docx.oxml.ns import qn
|
||
from docx.shared import Pt, Cm
|
||
from docx.text.run import Run
|
||
|
||
from module import LOGLEVEL
|
||
from module.achievement.excel import ExcelReader
|
||
|
||
|
||
class DocxWriter:
|
||
full_file_path: str
|
||
|
||
def __init__(self, save_path: str, filename: str, excel_reader: ExcelReader,
|
||
signal: Callable[[str, str], None] = lambda x, y: print(x)):
|
||
super().__init__()
|
||
self.save_path = save_path
|
||
self.filename = filename
|
||
self.excel_reader = excel_reader
|
||
self.full_file_path = os.path.join(save_path, filename) + '.docx'
|
||
self.signal = signal
|
||
|
||
def write(self) -> int:
|
||
try:
|
||
if not os.path.exists(self.save_path):
|
||
os.makedirs(self.save_path)
|
||
if os.path.exists(self.full_file_path):
|
||
self.signal(f"文件'{self.filename}'已存在,将覆盖原文件", LOGLEVEL.WARNING)
|
||
doc = Document()
|
||
for section in doc.sections:
|
||
new_width, new_height = Cm(29.7), Cm(21) # A4纸张
|
||
# 设置纸张方向为横向
|
||
section.orientation = WD_ORIENT.LANDSCAPE
|
||
|
||
# 设置页边距(例如:上下左右均为2厘米)
|
||
section.top_margin = Cm(2)
|
||
section.bottom_margin = Cm(2)
|
||
section.left_margin = Cm(2)
|
||
section.right_margin = Cm(1)
|
||
|
||
section.page_width = new_width
|
||
section.page_height = new_height
|
||
|
||
paragraph = doc.add_paragraph()
|
||
# 设置段落居中对齐
|
||
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||
run = paragraph.add_run(f"《{self.excel_reader.course_name}》课程目标达成情况评价报告")
|
||
self.set_run_font(run, 16, '黑体', '黑体', True)
|
||
|
||
paragraph = doc.add_paragraph()
|
||
run = paragraph.add_run(
|
||
f"1.班级{'(年级)' if len(self.excel_reader.class_list) == 1 else ''}课程目标达成情况分析")
|
||
self.set_run_font(run, 14, 'Times New Roman', '黑体', True)
|
||
|
||
# 班级课程目标达成情况分析表
|
||
class_info_str = "班级:{} 学生人数:{} 任课教师:{}"
|
||
for r_index in range(len(self.excel_reader.class_list)):
|
||
self.signal(f"正在生成 {self.excel_reader.class_list[r_index]} 表格", LOGLEVEL.INFO)
|
||
paragraph = doc.add_paragraph()
|
||
# 设置段落为分散对齐
|
||
paragraph.alignment = WD_ALIGN_PARAGRAPH.DISTRIBUTE
|
||
run = paragraph.add_run(class_info_str.format(
|
||
self.excel_reader.class_list[r_index],
|
||
self.excel_reader.class_number[r_index],
|
||
self.excel_reader.course_teacher_name
|
||
))
|
||
self.set_run_font(run, 10.5, 'Times New Roman', '宋体')
|
||
|
||
if len(self.excel_reader.class_list) == 1:
|
||
rows = 14 + self.excel_reader.class_number[r_index] + self.excel_reader.kpi_number
|
||
else:
|
||
rows = 12 + self.excel_reader.class_number[r_index] + self.excel_reader.kpi_number
|
||
cols = 2 + self.calculate_columns(self.excel_reader.achievement_level[r_index]) # 加上第一列
|
||
|
||
table = doc.add_table(rows=rows, cols=cols)
|
||
|
||
# 设置外侧框线粗1.5磅,内侧框线粗0.5磅
|
||
self.set_table_borders(table)
|
||
|
||
# 合并前六行头两个单元格
|
||
for row in range(6):
|
||
cell_start = table.cell(row, 0)
|
||
cell_end = table.cell(row, 1)
|
||
cell_start.merge(cell_end)
|
||
# 合并前三行的单元格
|
||
for row in range(3):
|
||
col_span = 2 # 从第三列开始
|
||
for performance in self.excel_reader.achievement_level[r_index]:
|
||
non_none_count = 3 - performance.scores.count(None)
|
||
if non_none_count > 1:
|
||
cell_start = table.cell(row, col_span)
|
||
cell_end = table.cell(row, col_span + non_none_count - 1)
|
||
cell_start.merge(cell_end)
|
||
col_span += non_none_count
|
||
|
||
X = self.excel_reader.kpi_number + 5 # X的值
|
||
if len(self.excel_reader.class_list) == 1:
|
||
X += 2
|
||
|
||
# 第一、二行合并第二个单元格
|
||
for row in range(rows - X, rows - X + 2):
|
||
table.cell(row, 0).merge(table.cell(row, 1))
|
||
|
||
# 第三行的合并操作
|
||
# 第一个单元格与第三+len(a[0])-1行第二个单元格合并
|
||
table.cell(rows - X + 2, 0).merge(table.cell(rows - X + 2 + self.excel_reader.kpi_number - 1, 1))
|
||
for i in range(self.excel_reader.kpi_number):
|
||
# 第三个与第四个单元格合并
|
||
table.cell(rows - X + 2 + i, 2).merge(table.cell(rows - X + i + 2, 3))
|
||
# 从第五个单元格合并到最后
|
||
start_cell = table.cell(rows - X + 2 + i, 4)
|
||
end_cell = table.cell(rows - X + 2 + i, cols - 1)
|
||
start_cell.merge(end_cell)
|
||
|
||
# 第三+len(a[0])行的合并操作
|
||
# 合并第一个到第二个单元格
|
||
table.cell(rows - X + 2 + self.excel_reader.kpi_number, 0).merge(
|
||
table.cell(rows - X + 2 + self.excel_reader.kpi_number, 1))
|
||
# 合并第三个到最后一个单元格
|
||
start_cell = table.cell(rows - X + 2 + self.excel_reader.kpi_number, 2)
|
||
end_cell = table.cell(rows - X + 2 + self.excel_reader.kpi_number, cols - 1)
|
||
start_cell.merge(end_cell)
|
||
|
||
if len(self.excel_reader.class_list) == 1:
|
||
# 合并前两个单元格
|
||
for i in range(2):
|
||
start = rows - X + 3 + i + self.excel_reader.kpi_number
|
||
table.cell(start, 0).merge(table.cell(start, 1))
|
||
# 依次合并后面的单元格
|
||
for row in range(start, start + self.excel_reader.kpi_number):
|
||
col_span = 2 # 从第三列开始
|
||
for performance in self.excel_reader.achievement_level[r_index]:
|
||
non_none_count = 3 - performance.scores.count(None)
|
||
if non_none_count > 1:
|
||
try:
|
||
cell_start = table.cell(row, col_span)
|
||
cell_end = table.cell(row, col_span + non_none_count - 1)
|
||
cell_start.merge(cell_end)
|
||
except IndexError:
|
||
self.signal(f"单元格合并失败:({row}, {col_span}),需要自行检查表格准确性",
|
||
LOGLEVEL.WARNING)
|
||
col_span += non_none_count
|
||
|
||
start = rows - X + 3 + self.excel_reader.kpi_number
|
||
if len(self.excel_reader.class_list) == 1:
|
||
start += 2
|
||
end = rows
|
||
for row in range(start, end):
|
||
start_cell = table.cell(row, 1)
|
||
end_cell = table.cell(row, cols - 1)
|
||
start_cell.merge(end_cell)
|
||
|
||
# 填充表格数据
|
||
self.put_data_to_table(table, self.excel_reader.get_word_template, r_index)
|
||
|
||
doc.add_page_break()
|
||
|
||
if len(self.excel_reader.class_list) > 1:
|
||
paragraph = doc.add_paragraph()
|
||
run = paragraph.add_run("2.年级课程目标达成情况分析")
|
||
self.set_run_font(run, 14, 'Times New Roman', '黑体', True)
|
||
|
||
# 2.年级课程目标达成情况分析
|
||
class_info_str = "班级:{} 课程负责人:{}"
|
||
paragraph = doc.add_paragraph()
|
||
paragraph.alignment = WD_ALIGN_PARAGRAPH.DISTRIBUTE
|
||
run = paragraph.add_run(class_info_str.format(
|
||
self.excel_reader.total_class_str,
|
||
self.excel_reader.course_lead_teacher_name
|
||
))
|
||
self.set_run_font(run, 10.5, "Times New Roman", '宋体')
|
||
|
||
rows = 10 + len(self.excel_reader.class_list)
|
||
cols = 2 + self.calculate_columns(self.excel_reader.achievement_level[0]) # 加上第一列
|
||
|
||
table = doc.add_table(rows=rows, cols=cols)
|
||
|
||
# 设置外侧框线粗1.5磅,内侧框线粗0.5磅
|
||
self.set_table_borders(table)
|
||
|
||
# 合并前六行头两个单元格
|
||
for row in range(rows - 2):
|
||
cell_start = table.cell(row, 0)
|
||
cell_end = table.cell(row, 1)
|
||
cell_start.merge(cell_end)
|
||
# 合并前三行的单元格
|
||
for row in range(3):
|
||
col_span = 2 # 从第三列开始
|
||
for performance in self.excel_reader.achievement_level[0]:
|
||
non_none_count = 3 - performance.scores.count(None)
|
||
if non_none_count > 1:
|
||
cell_start = table.cell(row, col_span)
|
||
cell_end = table.cell(row, col_span + non_none_count - 1)
|
||
cell_start.merge(cell_end)
|
||
col_span += non_none_count
|
||
# 合并中间X行的单元格
|
||
for row in range(6, 8 + len(self.excel_reader.class_list)):
|
||
col_span = 2
|
||
for performance in self.excel_reader.achievement_level[0]:
|
||
non_none_count = 3 - performance.scores.count(None)
|
||
if non_none_count > 1:
|
||
cell_start = table.cell(row, col_span)
|
||
cell_end = table.cell(row, col_span + non_none_count - 1)
|
||
cell_start.merge(cell_end)
|
||
col_span += non_none_count
|
||
# 合并最后两行的单元格
|
||
for row in range(2):
|
||
cell_start = table.cell(rows - row - 1, 1)
|
||
cell_end = table.cell(rows - row - 1, cols - 1)
|
||
cell_start.merge(cell_end)
|
||
|
||
# 填充数据
|
||
self.put_data_to_table(table, self.excel_reader.get_word_template_part_2)
|
||
|
||
doc.add_page_break()
|
||
|
||
# 3. 课程目标达成情况的合理性评价
|
||
paragraph = doc.add_paragraph()
|
||
run = paragraph.add_run(f"{3 if len(self.excel_reader.class_list) > 1 else 2}"
|
||
f". 课程目标达成情况的合理性评价")
|
||
self.set_run_font(run, 14, 'Times New Roman', '黑体', True)
|
||
|
||
rows = 11
|
||
cols = 6
|
||
table = doc.add_table(rows=rows, cols=cols)
|
||
# 设置外侧框线粗1.5磅,内侧框线粗0.5磅
|
||
self.set_table_borders(table)
|
||
|
||
# 合并第一行
|
||
cell_start = table.cell(0, 0)
|
||
cell_end = table.cell(0, cols - 1)
|
||
cell_start.merge(cell_end)
|
||
# 合并第二行至最后
|
||
for i in range(1, rows):
|
||
match i:
|
||
case 2:
|
||
table.cell(i, 2).merge(table.cell(i, 3))
|
||
table.cell(i, 4).merge(table.cell(i, 5))
|
||
table.cell(i, 1).width = Cm(7.42)
|
||
table.cell(i, 2).width = Cm(7.42)
|
||
table.cell(i, 4).width = Cm(7.41)
|
||
case 8 | 10:
|
||
table.cell(i - 1, 0).merge(table.cell(i, 0))
|
||
table.cell(i, 1).width = Cm(11.23)
|
||
table.cell(i, 2).width = Cm(1.48)
|
||
table.cell(i, 3).width = Cm(3.4)
|
||
table.cell(i, 4).width = Cm(1.39)
|
||
case _:
|
||
cell_start = table.cell(i, 1)
|
||
cell_end = table.cell(i, cols - 1)
|
||
cell_start.merge(cell_end)
|
||
# 填充数据
|
||
self.put_data_to_table(table, self.excel_reader.get_word_template_part_3)
|
||
|
||
# 应用样式
|
||
self.signal("正在应用文字样式", LOGLEVEL.INFO)
|
||
# 遍历文档中的所有段落
|
||
for index, paragraph in enumerate(doc.paragraphs):
|
||
if index == 0:
|
||
self.set_paragraph_format(paragraph, WD_LINE_SPACING.ONE_POINT_FIVE)
|
||
else:
|
||
self.set_paragraph_format(paragraph)
|
||
|
||
# 遍历文档中的所有表格及其单元格
|
||
t_len = len(doc.tables)
|
||
part_1_table_index = [x for x in range(len(self.excel_reader.class_list))]
|
||
part_2_table_index = [len(part_1_table_index)] if len(self.excel_reader.class_list) != 1 else []
|
||
part_3_table_index = [t_len - 1]
|
||
|
||
for t_index, table in enumerate(doc.tables):
|
||
self.set_table_borders(table)
|
||
# part_3_table_index 表格第9和11行(索引8和10)特殊边框处理
|
||
if t_index in part_3_table_index:
|
||
for r_idx in [8, 10]:
|
||
row = table.rows[r_idx]
|
||
prev_row = table.rows[r_idx - 1]
|
||
# 上一行(第8行和第10行,索引7和9)第2-6列移除下边框
|
||
for c_idx in range(1, 6):
|
||
self.set_cell_border(prev_row.cells[c_idx], bottom=0)
|
||
# 第2列(索引1):没有上边框和右边框
|
||
self.set_cell_border(row.cells[1], top=0, right=0)
|
||
# 第3-5列(索引2-4):没有上边框和左右边框
|
||
for c_idx in [2, 3, 4]:
|
||
self.set_cell_border(row.cells[c_idx], top=0, left=0, right=0)
|
||
# 第6列(索引5):没有上边框和左边框
|
||
self.set_cell_border(row.cells[5], top=0, left=0)
|
||
# 插入签名图片
|
||
if self.excel_reader.major_director_signature_image is not None:
|
||
self.insert_pil_image(table.cell(8, 3),
|
||
self.excel_reader.major_director_signature_image,
|
||
height=Cm(1.2))
|
||
if self.excel_reader.course_leader_signature_image is not None:
|
||
self.insert_pil_image(table.cell(10, 3),
|
||
self.excel_reader.course_leader_signature_image,
|
||
height=Cm(1.2))
|
||
for r_index, row in enumerate(table.rows):
|
||
row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST
|
||
# part_3_table_index 表格第9和11行(索引8和10)行高为1.2cm
|
||
if t_index in part_3_table_index and r_index in [8, 10]:
|
||
row.height = Cm(1.2)
|
||
else:
|
||
row.height = Cm(0.7)
|
||
for c_index, cell in enumerate(row.cells):
|
||
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||
self.set_cell_margins(cell, start=57, end=57)
|
||
for paragraph in cell.paragraphs:
|
||
for run in paragraph.runs:
|
||
self.set_run_font(run, 10.5, 'Times New Roman', '宋体')
|
||
self.set_paragraph_format(paragraph)
|
||
special_cell = []
|
||
if t_index in part_1_table_index:
|
||
ll = [(9 + x + self.excel_reader.class_number[t_index], 4) for x in
|
||
range(self.excel_reader.kpi_number)]
|
||
start_line = 9 + self.excel_reader.kpi_number
|
||
special_cell.extend(ll)
|
||
special_cell.extend([
|
||
(1, 2),
|
||
(2, 2),
|
||
(start_line + self.excel_reader.class_number[t_index], 2),
|
||
])
|
||
if len(self.excel_reader.class_list) == 1:
|
||
special_cell.extend([
|
||
(start_line + 3 + self.excel_reader.class_number[t_index], 1),
|
||
(start_line + 4 + self.excel_reader.class_number[t_index], 1),
|
||
])
|
||
lst = [(start_line + 3 + self.excel_reader.class_number[t_index], 1)]
|
||
else:
|
||
special_cell.extend([
|
||
(start_line + 1 + self.excel_reader.class_number[t_index], 1),
|
||
(start_line + 2 + self.excel_reader.class_number[t_index], 1),
|
||
])
|
||
lst = [(start_line + 1 + self.excel_reader.class_number[t_index], 1)]
|
||
if (r_index, c_index) in lst:
|
||
self.insert_image_from_data(cell,
|
||
self.excel_reader.pic_list[t_index],
|
||
Cm(7 * self.excel_reader.kpi_number)
|
||
)
|
||
if (len(self.excel_reader.class_list) == 1 and
|
||
r_index == start_line + 2 + self.excel_reader.class_number[t_index]):
|
||
self.change_font_to_wingding2(paragraph)
|
||
elif t_index in part_2_table_index:
|
||
special_cell = [
|
||
(1, 2),
|
||
(2, 2),
|
||
(8 + len(self.excel_reader.class_list), 1),
|
||
(9 + len(self.excel_reader.class_list), 1),
|
||
]
|
||
if r_index == 7 + len(self.excel_reader.class_list):
|
||
self.change_font_to_wingding2(paragraph)
|
||
elif t_index in part_3_table_index:
|
||
special_cell = [
|
||
(1, 1),
|
||
(2, 1),
|
||
(3, 1),
|
||
(4, 1),
|
||
(5, 1),
|
||
(6, 1),
|
||
(7, 1),
|
||
(8, 1),
|
||
(9, 1),
|
||
(10, 1),
|
||
]
|
||
if r_index == 0:
|
||
for run in paragraph.runs:
|
||
self.set_run_font(run, 10.5, 'Times New Roman', '宋体', True)
|
||
else:
|
||
self.change_font_to_wingding2(paragraph)
|
||
if any(r_index == pair[0] and c_index >= pair[1] for pair in special_cell):
|
||
paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
||
else:
|
||
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||
|
||
doc.save(self.full_file_path)
|
||
|
||
except PermissionError as e:
|
||
raise Exception(f"""
|
||
简要错误信息:{str(e)}
|
||
这可能是由于Word文件被占用所导致的,请关闭相应文件后再试。
|
||
""")
|
||
except Exception as _:
|
||
error_message = traceback.format_exc()
|
||
raise Exception(f"""
|
||
原始错误信息:
|
||
{error_message}
|
||
""")
|
||
|
||
def set_save_path(self, save_path: str):
|
||
self.save_path = save_path
|
||
self.full_file_path = os.path.join(save_path, self.filename)
|
||
|
||
def set_filename(self, filename: str):
|
||
self.filename = filename
|
||
self.full_file_path = os.path.join(self.save_path, filename)
|
||
|
||
def set_run_font(self, run: Run, size, western_font, chinese_font, bold=False):
|
||
run.font.size = Pt(size)
|
||
run.font.name = western_font
|
||
run.bold = bold
|
||
run._element.rPr.rFonts.set(qn('w:eastAsia'), chinese_font)
|
||
|
||
def set_cell_border(self, cell, **kwargs):
|
||
"""
|
||
设置单元格边框
|
||
kwargs: top, bottom, left, right, inside_h, inside_v
|
||
值为0时移除边框,值大于0时设置边框粗细
|
||
"""
|
||
tc = cell._tc
|
||
tcPr = tc.get_or_add_tcPr()
|
||
|
||
# 创建一个新的'TC边框'元素
|
||
tcBorders = OxmlElement('w:tcBorders')
|
||
|
||
# 对于每个边框方向
|
||
for key, value in kwargs.items():
|
||
if value is not None:
|
||
tag = 'w:{}'.format(key)
|
||
border = OxmlElement(tag)
|
||
if value == 0:
|
||
# 移除边框
|
||
border.set(qn('w:val'), 'nil')
|
||
else:
|
||
border.set(qn('w:val'), 'single')
|
||
border.set(qn('w:sz'), str(int(value * 8)))
|
||
border.set(qn('w:space'), '0')
|
||
border.set(qn('w:color'), 'auto')
|
||
tcBorders.append(border)
|
||
|
||
# 将边框添加到单元格属性中
|
||
tcPr.append(tcBorders)
|
||
|
||
def set_paragraph_format(self, paragraph, line_spacing_rule: WD_LINE_SPACING = WD_LINE_SPACING.SINGLE):
|
||
paragraph_format = paragraph.paragraph_format
|
||
paragraph_format.line_spacing_rule = line_spacing_rule # 单倍行距
|
||
paragraph_format.space_before = Pt(0) # 段前间距
|
||
paragraph_format.space_after = Pt(0) # 段后间距
|
||
|
||
def calculate_columns(self, performance_list):
|
||
"""
|
||
计算表格的列数
|
||
:param performance_list:
|
||
:return:
|
||
"""
|
||
col_count = 0
|
||
for performance in performance_list:
|
||
col_count += 3 - performance.scores.count(None) # 非None值的数量
|
||
return col_count
|
||
|
||
def put_data_to_table(self, table, gen_func, *args):
|
||
"""填充表格数据"""
|
||
data = gen_func(*args)
|
||
for i, row in enumerate(table.rows):
|
||
for j, cell in enumerate(row.cells):
|
||
if cell.text == "":
|
||
# 填充数据
|
||
# 这里需要根据您的数据结构来调整
|
||
d = next(data)
|
||
cell.text = str(d)
|
||
|
||
def set_cell_margins(self, cell, **kwargs):
|
||
"""
|
||
设置单元格的边距。
|
||
cell: 要修改的单元格实例
|
||
kwargs: 边距值,以twips为单位(1/1440英寸)
|
||
示例:set_cell_margins(cell, top=50, start=50, bottom=50, end=50)
|
||
"""
|
||
tc = cell._tc
|
||
tcPr = tc.get_or_add_tcPr()
|
||
tcMar = OxmlElement('w:tcMar')
|
||
|
||
for m in ["top", "start", "bottom", "end"]:
|
||
if m in kwargs:
|
||
node = OxmlElement("w:{}".format(m))
|
||
node.set(qn('w:w'), str(kwargs.get(m)))
|
||
node.set(qn('w:type'), 'dxa')
|
||
tcMar.append(node)
|
||
|
||
tcPr.append(tcMar)
|
||
|
||
def is_start_of_merged_cells(self, cell):
|
||
""" 检查单元格是否是合并单元格的起始单元格 """
|
||
merge = cell._element.xpath('./w:tcPr/w:vMerge')
|
||
if merge:
|
||
return merge[0].get(qn('w:val')) == 'restart'
|
||
return False
|
||
|
||
def is_merged_vertically(self, cell):
|
||
""" 检查单元格是否垂直合并 """
|
||
merge = cell._element.xpath('./w:tcPr/w:vMerge')
|
||
return bool(merge)
|
||
|
||
def set_table_borders(self, table, outside_sz=1.5, inside_sz=0.5):
|
||
"""
|
||
设置整个表格的边框样式
|
||
outside_sz: 外侧边框大小
|
||
inside_sz: 内部分割线大小
|
||
"""
|
||
for row in table.rows:
|
||
for cell in row.cells:
|
||
self.set_cell_border(cell, top=inside_sz, bottom=inside_sz, left=inside_sz, right=inside_sz)
|
||
|
||
# 设置外侧边框
|
||
for cell in table.rows[0].cells:
|
||
self.set_cell_border(cell, top=outside_sz)
|
||
for cell in table.rows[-1].cells:
|
||
self.set_cell_border(cell, bottom=outside_sz)
|
||
for row in table.rows:
|
||
self.set_cell_border(row.cells[0], left=outside_sz)
|
||
self.set_cell_border(row.cells[-1], right=outside_sz)
|
||
|
||
def insert_image_from_data(self, cell, image_data, width=Cm(21), height=Cm(4.5)):
|
||
# 创建一个临时文件
|
||
with io.BytesIO(image_data) as image_stream:
|
||
image_stream.seek(0)
|
||
# 插入图片
|
||
# 在单元格中添加一个段落
|
||
paragraph = cell.paragraphs[0]
|
||
# 在段落中插入图片
|
||
run = paragraph.add_run()
|
||
run.add_picture(image_stream, width=width, height=height)
|
||
|
||
def insert_pil_image(self, cell, pil_image, width=None, height=Cm(4.5)):
|
||
"""插入PIL Image对象到单元格"""
|
||
image_stream = io.BytesIO()
|
||
pil_image.save(image_stream, format='PNG')
|
||
image_stream.seek(0)
|
||
paragraph = cell.paragraphs[0]
|
||
run = paragraph.add_run()
|
||
run.add_picture(image_stream, width=width, height=height)
|
||
|
||
def is_chinese(self, char):
|
||
"""判断字符是否为中文"""
|
||
if '\u4e00' <= char <= '\u9fff':
|
||
return True
|
||
return False
|
||
|
||
def change_font_to_wingding2(self, paragraph):
|
||
for run in paragraph.runs:
|
||
if not run.text.strip(): # 跳过不包含文本的runs
|
||
continue
|
||
new_text = ""
|
||
for char in run.text:
|
||
if char in ['R', '£']:
|
||
# 对于特殊字符,创建新的run
|
||
if new_text:
|
||
new_run = paragraph.add_run(new_text)
|
||
self.set_run_font(new_run, 10.5, 'Times New Roman', '宋体')
|
||
new_text = ""
|
||
special_run = paragraph.add_run(char)
|
||
self.set_run_font(special_run, 10.5, 'Wingdings 2', '宋体')
|
||
else:
|
||
# 累积其他字符
|
||
new_text += char
|
||
if new_text:
|
||
# 创建剩余文本的run
|
||
new_run = paragraph.add_run(new_text)
|
||
self.set_run_font(new_run, 10.5, 'Times New Roman', '宋体')
|
||
run.clear()
|