在批量生成或归档 Word 报告时,页眉页脚的统一是典型脚本化需求:页码便于引用定位,公司标识与密级标注承担识别和防泄密职能。手工处理几十份文档时,容易出现封面显示页码、双面打印页码不靠外侧、页眉格式不一致等问题。本文使用 Free Spire.Doc for Python 演示一条完整的页眉页脚配置链路,包括基础文字页眉与页码域、封面页独立、奇偶页不同、Logo 行内/浮动页眉、跨文档 Clone 统一格式,以及分发前锁定页眉。安装命令如下:
- pip install spire.doc.free
复制代码
演示输入为两份文件:季度业务报告.docx(4 页,第 1 页为封面,后 3 页为正文,含一个财务数据表格)和 2025年度报告.docx(2 页历史文档,没有任何页眉页脚)。
先弄清两个默认值。页眉页脚排版由 PageSetup 上的 HeaderDistance 和 FooterDistance 控制,前者是页眉起点距页顶的距离,后者是页脚起点距页底的距离,新建文档默认值都是 36.0 pt。正文区从上边距 72 pt 处开始,页眉从 36 pt 处开始,二者之间约 36 pt 就是页眉实际可用高度。单行 9 pt 小字页眉加分隔线没有问题;放入高图片时,页眉区放不下就会把正文整体下移,这也是后面 Logo 两种放法差异的来源。
基础页眉页脚可以用 Section.HeadersFooters 下的 Header 和 Footer 容器完成。各自 AddParagraph 后按普通段落排版,页码用域字段插入,FieldPage 是当前页码,FieldNumPages 是总页数。示例代码如下:
- from spire.doc import *
- from spire.doc.common import *
- document = Document()
- document.LoadFromFile('季度业务报告.docx')
- section = document.Sections[0]
- # 页眉:公司标识 + 报告名,灰色 9pt,底部单线
- header_paragraph = section.HeadersFooters.Header.AddParagraph()
- tr = header_paragraph.AppendText('星河科技 · 2026 年第三季度业务报告')
- tr.CharacterFormat.FontName = '微软雅黑'
- tr.CharacterFormat.FontSize = 9
- tr.CharacterFormat.TextColor = Color.FromArgb(255, 110, 116, 124)
- header_paragraph.Format.Borders.Bottom.BorderType = BorderStyle.Single
- header_paragraph.Format.Borders.Bottom.Space = 1.0
- # 页脚:第 X 页,共 Y 页(域字段),右对齐,顶部单线
- footer_paragraph = section.HeadersFooters.Footer.AddParagraph()
- footer_paragraph.Format.HorizontalAlignment = HorizontalAlignment.Right
- footer_paragraph.AppendText('第 ')
- footer_paragraph.AppendField('page number', FieldType.FieldPage)
- footer_paragraph.AppendText(' 页,共 ')
- footer_paragraph.AppendField('number of pages', FieldType.FieldNumPages)
- footer_paragraph.AppendText(' 页')
复制代码
域字段的文字属性需要单独处理。AppendField 返回的是域对象而不是 TextRange,混排时“第”“页”等通过 AppendText 返回值设置格式,域本身的字体则要在插入后遍历段落的 ChildObjects,对 DocumentObjectType.TextRange 类型对象逐个设置,因为域求值后的文字也属于 TextRange:
- for k in range(footer_paragraph.ChildObjects.Count):
- obj = footer_paragraph.ChildObjects.get_Item(k)
- if obj.DocumentObjectType == DocumentObjectType.TextRange:
- obj.CharacterFormat.FontName = '微软雅黑'
- obj.CharacterFormat.FontSize = 9
- obj.CharacterFormat.TextColor = Color.FromArgb(255, 110, 116, 124)
复制代码
页码必须用域,不能写死。这份报告 4 页,手工填“1 / 4”看似可行,但正文增删一页后所有页码都会错;域字段在每次打开或渲染时重新求值,页码始终正确。渲染输出里页脚显示“2 / 4”,说明两个域都已正确求值。
封面页需要保持空白。带封面的报告不应在封面出现页眉和页码,打开 PageSetup.DifferentFirstPageHeaderFooter 后,第 1 页改用 FirstPageHeader 和 FirstPageFooter 两个独立容器,其余页面继续使用常规 Header / Footer。示例:
- document = Document()
- document.LoadFromFile('季度业务报告_基础页眉页脚.docx')
- section = document.Sections[0]
- # 打开“首页不同”开关
- section.PageSetup.DifferentFirstPageHeaderFooter = True
- # FirstPageHeader / FirstPageFooter 不添加任何内容,封面即保持空白
复制代码
这里不需要执行清除操作。FirstPageHeader 是独立容器,留空就是空白封面,常规页眉不受影响。保存后检查,FirstPageHeader 与 FirstPageFooter 的段落数都是 0,封面页渲染确认无页眉线、无页码,第 2 页起的页眉页脚原样保留。
奇偶页不同用于双面打印。报告双面打印装订时,页码应始终靠外侧:奇数页(右手页)靠右,偶数页(左手页)靠左,翻阅时页码位置固定。密级标注也常只放偶数页页眉。这需要打开第二个开关 DifferentOddAndEvenPagesHeaderFooter,两个开关可以叠加:
- document = Document()
- document.LoadFromFile('季度业务报告_基础页眉页脚.docx')
- section = document.Sections[0]
- # 封面空白 + 奇偶页不同,两个开关叠加
- section.PageSetup.DifferentFirstPageHeaderFooter = True
- section.PageSetup.DifferentOddAndEvenPagesHeaderFooter = True
- # 开关打开后,原 Header/Footer 只作用于奇数页
- # 偶数页页眉:密级标注,右对齐(靠外侧)
- even_header = section.HeadersFooters.EvenHeader.AddParagraph()
- even_header.Format.HorizontalAlignment = HorizontalAlignment.Right
- tr = even_header.AppendText('星河科技 · 机密')
- tr.CharacterFormat.FontName = '微软雅黑'
- tr.CharacterFormat.FontSize = 9
- tr.CharacterFormat.TextColor = Color.FromArgb(255, 110, 116, 124)
- even_header.Format.Borders.Bottom.BorderType = BorderStyle.Single
- # 偶数页页脚:页码左对齐(靠外侧)
- even_footer = section.HeadersFooters.EvenFooter.AddParagraph()
- even_footer.Format.HorizontalAlignment = HorizontalAlignment.Left
- even_footer.AppendField('page number', FieldType.FieldPage)
- even_footer.AppendText(' / ')
- even_footer.AppendField('number of pages', FieldType.FieldNumPages)
复制代码
开关语义容易误解:打开奇偶开关后,之前设置在 Header / Footer 上的内容只作用于奇数页,偶数页必须单独往 EvenHeader / EvenFooter 里写。漏写时偶数页页眉会直接消失,而不是继承奇数页设置。渲染确认:第 2 页(偶数页)页眉为“星河科技 · 机密”、页码“2 / 4”居左;第 3 页(奇数页)页眉为报告名、页码“3 / 4”居右。
Logo 页眉有行内与浮动两种放法,效果差别很大。AppendPicture 返回的 DocPicture 支持 Width / Height 设置显示尺寸,源图 800 × 200 px 按 1/4 缩放为 200 × 50 pt。行内图片参与页眉段落的文字排版,页眉行高会被撑到图片高度:
- header_paragraph = section.HeadersFooters.Header.AddParagraph()
- pic = header_paragraph.AppendPicture('星河科技_Logo.png')
- pic.Width = 200.0 # 显示尺寸 200 × 50 pt
- pic.Height = 50.0
- header_paragraph.Format.Borders.Bottom.BorderType = BorderStyle.Single
复制代码
浮动图片把环绕方式设为 Behind(衬于文字下方),定位基准改为页面,图片就脱离文字流,不再占用页眉行高:
- pic = header_paragraph.AppendPicture('星河科技_Logo.png')
- pic.TextWrappingStyle = TextWrappingStyle.Behind
- pic.HorizontalOrigin = HorizontalOrigin.Page # 水平定位基准:页面左缘
- pic.VerticalOrigin = VerticalOrigin.Page # 垂直定位基准:页面顶缘
- pic.VerticalAlignment = ShapeVerticalAlignment.Top
- pic.Width = 200.0
- pic.Height = 50.0
复制代码
两种放法渲染后的差异可以用正文起始位置量化:行内版正文第一行墨迹出现在页面渲染图第 61 行像素,浮动版在第 13 行。行内 Logo 的 50 pt 高度全部转化为正文下移,浮动 Logo 则完全不占版面空间。如果 Logo 只是页眉的装饰性元素,用浮动;如果页眉就是要展示完整的企业标识横幅,行内更直观。源图按显示尺寸的整数倍准备(这里 4 倍)可以避免缩放锯齿。
跨文档统一时,Clone 是必须的。存量文档没有页眉,一份份重配不现实,规范做法是把一份做好的文档当模板,把页眉页脚对象复制过去:
- source = Document()
- source.LoadFromFile('季度业务报告_基础页眉页脚.docx')
- src_header = source.Sections[0].HeadersFooters.Header
- src_footer = source.Sections[0].HeadersFooters.Footer
- target = Document()
- target.LoadFromFile('2025年度报告.docx')
- for i in range(target.Sections.Count):
- section = target.Sections.get_Item(i)
- for j in range(src_header.ChildObjects.Count):
- obj = src_header.ChildObjects.get_Item(j)
- section.HeadersFooters.Header.ChildObjects.Add(obj.Clone())
- for j in range(src_footer.ChildObjects.Count):
- obj = src_footer.ChildObjects.get_Item(j)
- section.HeadersFooters.Footer.ChildObjects.Add(obj.Clone())
复制代码
跨文档复制必须调用 Clone()。页眉段落对象归属于源文档的对象树,直接 Add 原对象会让两个文档共享同一块内存,保存时内容错乱;Clone 生成深拷贝,各自独立。本次运行复制了页眉对象 1 个、页脚对象 1 个(各是一个段落),2025年度报告.docx 渲染确认第 1 页出现页眉线与页脚“1 / 2”。
分发前还需要锁定页眉。页眉页脚做好后,文档正文可能仍需要他人补充数据,但密级标注和 Logo 不应被随手改动。用窗体保护配合节级豁免可以实现“正文可编辑、页眉页脚锁定”:
- document = Document()
- document.LoadFromFile('季度业务报告_基础页眉页脚.docx')
- section = document.Sections[0]
- # 全文按“只能填写窗体”保护(此类型下页眉页脚不可编辑)
- document.Protect(ProtectionType.AllowOnlyFormFields, 'XH2026')
- # 第 1 节豁免窗体保护:正文保持可编辑
- section.ProtectForm = False
复制代码
Protect 的第一个参数选 AllowOnlyFormFields 而不是 ReadOnly,是因为前者允许配合节级豁免:ProtectForm = False 的节里正文仍可自由编辑,而页眉页脚不在豁免范围,保持锁定。保存后重新加载验证,GetProtectionType() 返回 AllowOnlyFormFields,第 1 节 ProtectForm 为 False,与设置一致;渲染外观与未保护版本完全相同,保护不影响文档显示。
这条配置链路适合两类场景。一是报告批量生成的末端环节:上游系统产出正文后,按文档类型套用对应页眉配置,对外报告用 Logo 浮动页眉加页码域,内部资料用密级标注加奇偶页页码。二是存量文档规范化:交接、归档或应付审计前,用 Clone 复制方式把历史文档统一到同一套页眉模板,不用逐份打开手工设置。配置本身建议与代码分离:页眉文字、字体颜色、Logo 路径、启用哪些开关,放进 JSON 或 YAML 配置文件,脚本读取后按文档类型选择模板,新增报告规格时只改配置不改代码。锁定环节只在文档即将外发时执行,密码由分发流程统一管理。
本文涉及的全部操作与渲染输出在 Free Spire.Doc for Python 环境实测通过;文档加载、页眉页脚读写、图片插入与保护均为免费版可用功能。 |