Сообщить об ошибке.

Создание оглавления и схемы PDF-документа

Модуль fpdf2 поддерживает автоматическое создание содержания PDF-документа, основываясь на секциях/заголовках документа, а так же может дополнительно отображать структуру документа на экране, позволяя пользователю интерактивно перемещаться от одной части документа к другой. Схема состоит из древовидной иерархии элементов схемы (иногда называемых закладками), которые служат в качестве визуального оглавления, отображающего структуру документа для пользователя.

Начиная с версии FPDF2.3.3, обе функциональности поддерживаются за счет использования метода FPDF.start_section(), который добавляет запись во внутреннюю "схему" таблицы, которая затем используется для визуализации содержания и схемы PDF-документа.

Обратите внимание, что по умолчанию вызов FPDF.start_section() записывает только текущую позицию в PDF и ничего не отображает. Однако можно настроить глобальные стили заголовков, вызвав метод FPDF.set_section_title_styles(), после чего вызов FPDF.start_section() будет визуализировать заголовки секции с использованием определенных стилей.

Чтобы отобразить схему документа для создаваемого PDF-документа, нужно просто вызвать метод FPDF.start_section() для каждого иерархического раздела, который необходимо определить.

Если также нужно куда-то вставить оглавление, то вызовите метод FPDF.insert_toc_placeholder() в любом месте, где необходимо его разместить. Обратите внимание, что разрыв страницы всегда будет запускаться после вставки оглавления.

Пример с подробным описанием, что происходит в коде.

from fpdf import FPDF, TitleStyle

def print_text(pdf, text, **kwargs):
    """функция печати абзацев"""
    # атрибут pdf.epw возвращает ширину документа
    pdf.multi_cell(w=pdf.epw, h=pdf.font_size, txt=text, ln=1, **kwargs)

def render_toc(pdf, outline):
    """Составление содержания PDF-документа"""
    pdf.y = 20
    pdf.set_font("Sans", size=16)
    pdf.cell(w=pdf.epw, h=pdf.font_size, txt="Содержание:", ln=1, align="C")
    pdf.y += 20
    pdf.set_font("Serif", size=12)
    for section in outline:
        link = pdf.add_link()
        pdf.set_link(link, page=section.page_number)
        text = f'{" " * section.level * 2} {section.name}'
        text += (
            f' {"." * (60 - section.level*2 - len(section.name))} {section.page_number}'
        )
        pdf.multi_cell(w=pdf.epw, h=pdf.font_size, txt=text, ln=1, link=link)


pdf = FPDF()
# директория где лежат системные шрифты OS Linux
font_dir = '/usr/share/fonts/truetype/freefont'
# добавляем TTF-шрифты, поддерживающие кириллицу.
# обратите внимание курсив и жирный курсив
# не добавляем, т.к. ими пользоваться не будем
# шрифт FreeSerif,
pdf.add_font("Serif", style="", fname=f"{font_dir}/FreeSerif.ttf", uni=True)
pdf.add_font("Serif", style="B", fname=f"{font_dir}/FreeSerifBold.ttf", uni=True)
# шрифт FreeSans
pdf.add_font("Sans", style="", fname=f"{font_dir}/FreeSans.ttf", uni=True)
pdf.add_font("Sans", style="B", fname=f"{font_dir}/FreeSansBold.ttf", uni=True)
# устанавливаем по умолчанию шрифт
pdf.set_font("Serif", size=13)
# определение стилей заголовков
pdf.set_section_title_styles(
    # Стиль названия секции:
    TitleStyle(font_family="Sans", font_style="B", font_size_pt=24,
               color=128, underline=True,
               t_margin=10, l_margin=10, b_margin=0),
    # Стиль названия заголовка:
    TitleStyle(font_family="Sans", font_style="B", font_size_pt=20,
               color=128, underline=True,               
               t_margin=10, l_margin=20, b_margin=5)
)
# добавляем страницу
pdf.add_page()
# установка курсора 
pdf.set_y(pdf.eph/2 - 30)
# установка шрифта
pdf.set_font(size=40)
# печатаем Название документа
print_text(pdf, "Название документа", align="C")
pdf.set_font(size=12)
# выводим содержание/схему 
# документа на новой странице
pdf.add_page()
pdf.insert_toc_placeholder(render_toc)
################### Текст документа ###################
# это текст, который вставляется в функцию `print_text()` (Для уменьшения количества кода)
text = """Также как новая модель организационной деятельности требует определения и уточнения первоочередных требований.

Курс на социально-ориентированный национальный проект способствует подготовке внутренних резервов и ресурсов.

Реализация намеченных плановых заданий представляет собой интересный эксперимент проверки вывода текущих активов."""
pdf.start_section("Секция 1")
pdf.start_section("Заголовок 1.1", level=1)
print_text(pdf, text)
pdf.add_page()
pdf.start_section("Заголовок 1.2", level=1)
print_text(pdf, text)
pdf.add_page()
pdf.start_section("Секция 2")
pdf.start_section("Заголовок 2.1", level=1)
print_text(pdf, text)
pdf.add_page()
pdf.start_section("Заголовок 2.2", level=1)
print_text(pdf, text)
pdf.output("simple_outline.pdf")

Оглавление и схема PDF-документа, полученного из HTML.

При получении PDF-документа из HTML-разметки, используемый класс HTMLMixin автоматически создает структура документа, которую можно вставить в оглавление с помощью специального тега <toc>.

Пользовательский стиль оглавления может быть достигнут путем переопределения метода render_toc() в подклассе fpdf.html.HTML2FPDF:

from fpdf import FPDF, HTMLMixin, HTML2FPDF

class MyHTML2FPDF(HTML2FPDF):
    # переопределяем метод составления содержания 
    def render_toc(self, pdf, outline):
        pdf.cell(txt='Содержание:', ln=1)
        for section in outline:
            pdf.cell(txt=f'* {section.name} (page {section.page_number})', ln=1)

class MyPDF(FPDF, HTMLMixin):
    # устанавливаем класс, который будет 
    # преобразовывать HTML в PDF 
    HTML2FPDF_CLASS = MyHTML2FPDF

pdf = MyPDF()
# директория где лежат системные шрифты OS Linux
font_dir = '/usr/share/fonts/truetype/freefont'
# добавляем TTF-шрифты, поддерживающие кириллицу.
# курсив и жирный курсив не добавляем, т.к. ими пользоваться не будем
pdf.add_font("Serif", style="", fname=f"{font_dir}/FreeSerif.ttf", uni=True)
pdf.add_font("Serif", style="B", fname=f"{font_dir}/FreeSerifBold.ttf", uni=True)
pdf.add_page()
# устанавливаем добавленный шрифт по умолчанию
pdf.set_font("Serif", size=12)
pdf.write_html("""<toc></toc>
    <h1>Заголовок 1</h1>
    <h2>Заголовок 2</h2>
    <h3>Заголовок 3</h3>
    <h4>Заголовок 4</h4>
    <h5>Заголовок 5</h5>
    <h6>Заголовок 6</h6>
    <p>Параграф<p><p>Параграф<p><p>Параграф<p><p>Параграф<p>""")
pdf.output("html_toc.pdf")

Пример PDF-документа, созданного из HTML, со стандартным схемой и содержанием.

Обратите внимание на HTML-тег <toc pages="1"></toc>. Атрибут pages="1" говорит о том сколько страниц нужно пропустить вначале документа перед его контентом, чтобы уместить содержание.

from fpdf import FPDF, HTMLMixin, HTML2FPDF

class MyFPDF(FPDF, HTMLMixin):
    pass

pdf = MyFPDF()
# директория где лежат системные шрифты OS Linux
font_dir = '/usr/share/fonts/truetype/freefont'
# добавляем TTF-шрифты, поддерживающие кириллицу.
pdf.add_font("Sans", style="", fname=f"{font_dir}/FreeSans.ttf", uni=True)
pdf.add_font("Sans", style="B", fname=f"{font_dir}/FreeSansBold.ttf", uni=True)
pdf.add_font("Sans", style="I", fname=f"{font_dir}/FreeSansOblique.ttf", uni=True)
pdf.add_font("Sans", style="BI", fname=f"{font_dir}/FreeSansBoldOblique.ttf", uni=True)
# устанавливаем добавленный шрифт по умолчанию
pdf.set_font("Sans", size=13)
# Добавляем страницу
pdf.add_page()
# это абзацы, которые вставляются в html (Для уменьшения количества кода)
p_tags = """
<p>Новая модель организационной деятельности требует определения и уточнения первоочередных требований.</p>
<p>Курс на социально-ориентированный проект способствует подготовке резервов и ресурсов.</p>
<p>Реализация намеченных плановых заданий представляет собой интересный эксперимент.</p>"""
# преобразуем HTML в PDF
pdf.write_html(
    f"""<h1>Название Документа</h1>
    <br><br><br>
    <u>Содержание:</u>
    <br>
    <toc pages="1"></toc>
    <h2>Заголовок 0</h2>{p_tags}<h2>Заголовок 1</h2>{p_tags}<h2>Заголовок 2</h2>{p_tags}
    <h2>Заголовок 3</h2>{p_tags}<h2>Заголовок 4</h2>{p_tags}<h2>Заголовок 5</h2>{p_tags}
    <h2>Заголовок 6</h2>{p_tags}<h2>Заголовок 7</h2>{p_tags}<h2>Заголовок 8</h2>{p_tags}
    <h2>Заголовок 9</h2>{p_tags}<h2>Заголовок 10</h2>{p_tags}<h2>Заголовок 11</h2>{p_tags}
    <h2>Заголовок 12</h2>{p_tags}<h2>Заголовок 13</h2>{p_tags}<h2>Заголовок 14</h2>{p_tags}
    <h2>Заголовок 15</h2>{p_tags}<h2>Заголовок 16</h2>{p_tags}<h2>Заголовок 17</h2>{p_tags}
    <h2>Заголовок 18</h2>{p_tags}<h2>Заголовок 19</h2>{p_tags}<h2>Заголовок 20</h2>{p_tags}
    <h2>Заголовок 21</h2>{p_tags}<h2>Заголовок 22</h2>{p_tags}<h2>Заголовок 23</h2>{p_tags}
    <h2>Заголовок 24</h2>{p_tags}<h2>Заголовок 25</h2>{p_tags}<h2>Заголовок 26</h2>{p_tags}
     """
)
pdf.output("html_toc_2_pages.pdf")

Справка по методам модуля FPDF2, используемым в материале.

FPDF.start_section(name, level=0):

Метод FPDF.start_section() начинает раздел в структуре документа. Если метод FPDF.section_title_styles() был предварительно определен и настроен, то название раздела будет выведено в виде заголовка.

Аргументы:

  • name: строка, название раздела;
  • level=0: целое число, уровень раздела в структуре документа. По умолчанию 0 - означает верхний уровень.

FPDF.insert_toc_placeholder(render_toc_function, pages=1):

Метод FPDF.insert_toc_placeholder() настраивает визуализацию содержания/схемы PDF-документа в конце его создания и заранее резервирует немного вертикального пространства, чтобы вставить его.

Аргументы:

  • render_toc_function: функция, которая будет вызываться для визуализации содержания/схемы PDF-документа. Эта функция получит два аргумента: pdf - экземпляр FPDF, и схему outline, список OutlineSection.
  • pages=1: целое число - количество страниц, которые будет охватывать оглавление, включая текущую страницу. После вызова этого метода произойдет столько разрывов страниц, сколько содержит аргумент pages.

FPDF.set_section_title_styles(level0, level1, level2, level3, level4, level5, level6):

Метод FPDF.set_section_title_styles() устанавливает стиль заголовков разделов. После вызова этого метода вызовы FPDF.start_section() будут визуально отображать имена разделов.

Аргументы:

  • level0: принимает тип TitleStyle, определяет стиль заголовка раздела верхнего уровня;
  • level1=None: тип TitleStyle, необязательный стиль для заголовков разделов уровня 1;
  • level2=None: тип TitleStyle, необязательный стиль для заголовков разделов уровня 2;
  • level3=None: тип TitleStyle, необязательный стиль для заголовков разделов уровня 3;
  • level4=None: тип TitleStyle, необязательный стиль для заголовков разделов уровня 4;
  • level5=None: тип TitleStyle, необязательный стиль для заголовков разделов уровня 5;
  • level6=None: тип TitleStyle, необязательный стиль для заголовков разделов уровня 6;

FPDF.TitleStyle(font_family, font_style, font_size_pt, color, underline, t_margin, l_margin, b_margin):

Класс FPDF.TitleStyle() встроенный тип, который задает стиль заголовков разделов.

Аргументы:

  • font_family=None: необязательный str, название шрифта;
  • font_style=None: необязательный str, стиль шрифта (жирный 'B', курсив 'I', и их сочетание 'BI')
  • font_size_pt=None: необязательный, int, размер шрифта в пунктах;
  • color=None: необязательный, может принимать int или tuple, цвет загоровка;
  • underline=None: необязательный bool, подчеркивать или нет.
  • t_margin=None: необязательный int, отступ сверху;
  • l_margin=None: необязательный int, отступ слева;
  • b_margin=None: необязательный int, отступ снизу.