- numbering.xml
- document.xml
代码实现与效果展示
from docx import Document
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.oxml.xmlchemy import BaseOxmlElement
from docx.shared import StoryChild, RGBColor, Pt
def SetFontByXml(element, font_name, font_size, is_bold=False):
'''
通过修改XML来自定义字体
element: 可以是需要设置序号字体的带有序号的段落; 也可以是run对象
'''
if element is None:
return
# 判断rPr标签是否存在
if isinstance(element, StoryChild): # doc element
rPr_elements = element._element.find(qn('w:rPr'))
elif isinstance(element, BaseOxmlElement): # CT by xml
rPr_elements = element.xpath('.//w:rPr')
else:
return
if rPr_elements is not None and len(rPr_elements) > 0:
rPr_element = GetFirstValidElement(elements=rPr_elements)
rPr_element.clear() # 清空原先的设置
else:
rPr_element = OxmlElement('w:rPr')
# 增加w:rFonts标签和w:sz标签
font_name_element = OxmlElement('w:rFonts') # 修改字体名称
font_name_element.set(qn('w:ascii'), font_name)
font_name_element.set(qn('w:h-ansi'), font_name)
font_name_element.set(qn('w:east-asian'), font_name)
font_size_element = OxmlElement('w:sz') # 修改字体大小
font_size_element.set(qn('w:val'), str(GetFontSize(font_size) * 2)) # val是pt的2倍
if is_bold:
bold_element = OxmlElement('w:b') # 字体加粗
rPr_element.append(bold_element)
rPr_element.append(font_name_element)
rPr_element.append(font_size_element)
# 将已定义的字体标签添加到rPr标签内
if isinstance(element, StoryChild): # doc element
element._element.insert(index=0, element=rPr_element)
elif isinstance(element, BaseOxmlElement): # CT by xml
element.insert(index=0, element=rPr_element) # 添加到w:t前
# 根据传入的中英文(例如五号/10.5)获取字号数组
def GetFontSize(font_size):
chinese_sizes = {
"初号": 42, "小初": 36, "一号": 26, "小一": 24,
"二号": 22, "小二": 18, "三号": 16, "小三": 15,
"四号": 14, "小四": 12, "五号": 10.5, "小五": 9,
"六号": 7.5, "小六": 6.5, "七号": 5.5, "八号": 5
}
if isinstance(font_size, float) or isinstance(font_size, int):
# 如果是整数,假设为英文字号
return float(font_size)
elif font_size.isdigit():
# 如果是纯数字,假设为英文字号
return float(font_size)
elif font_size in chinese_sizes:
# 如果是中文字号,映射为磅数
return chinese_sizes[font_size]
else:
# 如果不是中文也不是纯数字,返回None或者设定一个默认值
return None
# 定义字体样式
def SetFont(run, name, size, is_bold=False, is_italic=False, color=None) -> None:
'''
通过传入元素的run对象进行字体属性设置
name表示字体名称、size表示字体大小(并用整数表示)、is_bold表示字体是否加粗
is_italic表示字体是否为斜体、color表示字体颜色(用Tuple表示RGB)
'''
if run:
font = run.font
font.name = name # 设置字体名称
font.size = Pt(GetFontSize(font_size=size)) # 设置字体大小
run._element.rPr.rFonts.set(qn("w:eastAsia"), name) # 设置中文字体属性
font.bold = is_bold # 粗体
font.italic = is_italic # 斜体
if color: # 颜色
font.color.rgb = RGBColor(*color)
doc = Document()
p = doc.add_paragraph(style='List Number')
SetFontByXml(p, font_name='黑体', font_size='一号', is_bold=True)
r = p.add_run('123')
SetFont(run=r, name='宋体', size='小四')
# 保存文档
doc.save('./test.docx')
以下是test.docx
的内容展示,可以发现序号
与正文
被设置为了不同的字体样式
原理与思路
问题
由于直接修改run对象的字体样式只对正文
生效,而对序号
样式不生效。所以可以考虑通过修改word文档的XML来使序号
字体样式生效。
一开始我尝试先对word/numbering.xml
中的序号字体样式进行修改,发现有时候序号样式不生效,经过测试发现会被word/document.xml
对应的段落字体样式覆盖。所以说明document.xml
的字体样式优先级比numbering.xml
高。
解决方案
发现了字体样式优先级的问题后,可以考虑只对document.xml
中的样式进行设置。通过查看其XML代码,可以发现同时设置序号与正文字体样式设置的XML模板:
<w:p w14:paraId="45659768"
w14:textId="3BB12CE7"
w:rsidR="004438A0"
w:rsidRPr="00843982"
w:rsidRDefault="002B1108"
w:rsidP="002B1108">
<w:rPr>
<w:rFonts w:ascii="楷体"
w:eastAsia="楷体"
w:hAnsi="楷体"/>
</w:rPr>
<w:pPr>
<w:pStyle w:val="a3"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="1"/>
</w:numPr>
<w:ind w:firstLineChars="0"/>
</w:pPr>
<w:r w:rsidRPr="00013C3C">
<w:rPr>
<w:rFonts w:ascii="宋体"
w:eastAsia="宋体"
w:hAnsi="宋体"/>
</w:rPr>
<w:t>测试文本</w:t>
</w:r>
</w:p>
其中用于设置序号字体(楷体)样式的<w:rPr>
标签放在<w:p>
或者<w:pPr>
的下一级均可使序号字体样式生效,而正文的字体样式(宋体)的<w:rPr>
放在<w:r>
的下一级即可。
这说明其实设置序号字体样式的方式是通过设置整个段落w:p
标签的样式,然后run对象中的字体样式优先级比w:p
中的rPr
高,会将w:p
段落字体样式进行覆盖,从而达到设置正文字体样式的效果。