You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ReportLab自定义页码索引:从指定页面起始的实现方案

如何在ReportLab中让页码从指定页面开始计数?

更新:请查看第二个代码块获取解决方案

如何将页码索引的起始位置设置为指定页面?

举个例子:如果文档包含封面、目录和正文,需要让页码从正文第一页开始计数,而非从第一页(封面)开始。注意,需求不是隐藏页码,而是改变“第一页”的逻辑定义。更理想的情况是支持前几页用罗马数字(i,ii,iii...),正文切换为阿拉伯数字(1,2,3...)的编号方案。

以下是原始示例代码:

import os

from reportlab.lib.pagesizes import A4
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame, Paragraph, PageBreak, NextPageTemplate
from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch  # Importing inch unit


class Example(BaseDocTemplate):
    
    def __init__(self, fname, **kwargs):
        super().__init__(fname, **kwargs)    
        
        
        frame_example = Frame(self.leftMargin - 0.5*inch, 
                              self.bottomMargin + 0.25*inch, 
                              self.width + 1*inch, 
                              self.height - 0.5*inch, 
                              id='example')
        
        # Define some page templates to display the page number
        self.addPageTemplates([PageTemplate(id='front_page', frames=frame_example, onPage=self.doFootingsFrontPage),
                               PageTemplate(id='other_pages', frames=frame_example, onPage=self.doFootings)])
        self.elements = []
        
        # Defining some basic paragrah styles for the example
        ps1 = ParagraphStyle(fontName='Helvetica', fontSize=14, name='frontPage_PS',
                             leftIndent=20, firstLineIndent=-20, spaceBefore=5, leading=16)
        
        ps2 = ParagraphStyle(fontName='Helvetica', fontSize=12, name='header_PS',
                             leftIndent=20, firstLineIndent=-20, spaceBefore=5, leading=16)
        
        ps3 = ParagraphStyle(fontName='Helvetica', fontSize=10, name='text_PS',
                             leftIndent=20, firstLineIndent=-20, spaceBefore=5, leading=16)
        
        # Storing the styles into a class attribute so that we can call them later on
        self.styleSheet = getSampleStyleSheet()
        for style in [ps1, ps2, ps3]:
            self.styleSheet.add(style)

        # Generate the front page
        self.doFrontPage()
        
        # Initialize the TOC
        toc = TableOfContents(dotsMinLevel=0)
        toc.levelStyles = [self.styleSheet['header_PS']]
        
        # Add the TOC
        self.elements.append(toc)
        self.elements.append(PageBreak())
        
        for n in range(2):
            self.doOtherPage(n)
        
        # Build the document
        self.multiBuild(self.elements)
        
        
    def afterFlowable(self, flowable):
        "Registers TOC entries."
        if flowable.__class__.__name__ == 'Paragraph':
            text = flowable.getPlainText()
            style = flowable.style.name
            if style == 'header_PS':
                self.notify('TOCEntry', (0, text, self.page))
                
    def doFootings(self, canvas, doc):
        # Create the footer
        x = A4[0]-128
        y = 40
        
        canvas.saveState()
        txtFooting = "Page {}".format(int(canvas._pageNumber))
        canvas.drawString(x, y, txtFooting)
        canvas.restoreState()
        
    def doFootingsFrontPage(self, canvas, doc):
        # Create the footer
        x = 50
        y = 40
        
        canvas.saveState()
        txtFooting = "I am the front page - I dont want to have a page number"
        canvas.drawString(x, y, txtFooting)
        canvas.restoreState()
        
    def doFrontPage(self):
        txt = 'This is the front page'
        self.elements.append(Paragraph(txt, self.styleSheet['frontPage_PS']))
        self.elements.append(NextPageTemplate("other_pages"))
        self.elements.append(PageBreak())
        
    def doOtherPage(self, n):
        txt = 'Header {:.0f}'.format(n+1)
        self.elements.append(Paragraph(txt, self.styleSheet['header_PS']))
        
        txt ='Who stole my page number? I should be Page {:.0f}'.format(n+1)
        
        for ii in range(10):
            self.elements.append(Paragraph(txt, self.styleSheet['text_PS']))
            
        self.elements.append(PageBreak())



if __name__ == '__main__':
    
    fname = os.path.abspath(os.path.join('.', 'example_reportlab.pdf'))
    Report = Example(fname)

该示例生成4页PDF,包含封面、目录和两个正文小节,期望正文第一页(当前第3页)作为页码1,且目录中对应条目显示正确页码。

尝试参考ReportLab官方示例未成功后,最终基于偏移量计算思路实现了解决方案:

import os

from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame, Paragraph, PageBreak, NextPageTemplate
from reportlab.platypus.tables import TableStyle
from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch  # Importing inch unit




class Example(BaseDocTemplate):
    
    def __init__(self, fname, **kwargs):
        super().__init__(fname, **kwargs)    
        
        self.start_page_number = None
        
        frame_example = Frame(self.leftMargin - 0.5*inch, 
                              self.bottomMargin + 0.25*inch, 
                              self.width + 1*inch, 
                              self.height - 0.5*inch, 
                              id='example')
        
        self.addPageTemplates([PageTemplate(id='front_page', frames=frame_example, onPage=self.doFootingsFrontPage),
                               PageTemplate(id='other_pages', frames=frame_example, onPage=self.doFootings)])
        self.elements = []
        
        # Defining some basic paragrah style for the example
        ps1 = ParagraphStyle(fontName='Helvetica', fontSize=14, name='frontPage_PS',
                             leftIndent=20, firstLineIndent=-20, spaceBefore=5, leading=16)
        
        ps2 = ParagraphStyle(fontName='Helvetica', fontSize=12, name='header_PS',
                             leftIndent=20, firstLineIndent=-20, spaceBefore=5, leading=16)
        
        ps3 = ParagraphStyle(fontName='Helvetica', fontSize=10, name='text_PS',
                             leftIndent=20, firstLineIndent=-20, spaceBefore=5, leading=16)
        
        # Storing the styles into a class attribute so that we can call them later on
        self.styleSheet = getSampleStyleSheet()
        for style in [ps1, ps2, ps3]:
            self.styleSheet.add(style)
        
        
        defaultTableStyle = TableStyle([
                                ('VALIGN', (0,0), (-1,-1), 'TOP'),
                                ('RIGHTPADDING', (0,0), (-1,-1), 0),
                                ('LEFTPADDING', (0,0), (-1,-1), 10),
                                ('LINEBEFORE', (0,0), (0, -1), 2.5, colors.blue)
                            ])
        
        # Generate each page one by one
        self.doFrontPage()
        
        # Initialize the TOC
        toc = TableOfContents(dotsMinLevel=0)
        toc.levelStyles = [self.styleSheet['header_PS']]
        toc.tableStyle = defaultTableStyle
        
        # Add the TOC
        self.elements.append(toc)
        self.elements.append(PageBreak())
        
        
        for n in range(2):
            self.doOtherPage(n)
        
        # Build the document
        self.multiBuild(self.elements)
        
    def afterFlowable(self, flowable):
        "Registers TOC entries."
        if flowable.__class__.__name__ == 'Paragraph':
            text = flowable.getPlainText()
            style = flowable.style.name
            if style == 'header_PS':
                if self.start_page_number is None:
                    self.start_page_number = self.page
                    
                page_number = self.page - self.start_page_number + 1
                self.notify('TOCEntry', (0, text, page_number))
                
                
    def doFootings(self, canvas, doc):
        # Create the footer
        x = A4[0]-128
        y = 40
        
        if self.start_page_number is not None:
            page_number = self.page - self.start_page_number + 1
            if page_number < 1:
                return
            canvas.saveState()
            txtFooting = "Page {}".format(int(page_number))
            canvas.drawString(x, y, txtFooting)
            canvas.restoreState()
        
    def doFootingsFrontPage(self, canvas, doc):
        # Create the footer
        x = 50
        y = 40
        
        canvas.saveState()
        txtFooting = "I am the front page - I dont want to have a page number"
        canvas.drawString(x, y, txtFooting)
        canvas.restoreState()
        
    def doFrontPage(self):
        txt = 'This is the front page'
        self.elements.append(Paragraph(txt, self.styleSheet['frontPage_PS']))
        self.elements.append(NextPageTemplate("other_pages"))
        self.elements.append(PageBreak())
        
        
    def doOtherPage(self, n):
        txt = 'Header {:.0f}'.format(n+1)
        self.elements.append(Paragraph(txt, self.styleSheet['header_PS']))
        
        txt ='Who stole my page number? I should be Page {:.0f}'.format(n+1)
        
        for ii in range(10):
            self.elements.append(Paragraph(txt, self.styleSheet['text_PS']))
            
        self.elements.append(PageBreak())




if __name__ == '__main__':
    
    fname = os.path.abspath(os.path.join('.', 'example_reportlab.pdf'))
    Report = Example(fname)

核心思路

通过计算ReportLab内部逻辑页码与目标起始页码的偏移量,得到实际显示的页码,避免从零开始计数。具体来说:

  1. 新增start_page_number属性记录正文起始的逻辑页码
  2. 在注册TOC条目和绘制页脚时,用当前逻辑页码 - 起始逻辑页码 + 1计算显示页码
  3. 确保非正文页面不显示调整后的页码

内容的提问来源于stack exchange,提问作者gsalles

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 06:09:50