Кастомизация сборки PDF через WeasyPrint

WeasyPrint использует собственный движок отрисовки HTML. Движок поддерживает большинство возможностей языка стилей CSS, но не поддерживает скрипты JavaScript. Полный список особенностей движка приведён в разделе Supported Features документации WeasyPrint.

Способы кастомизации, специфичные для генерации PDF, преимущественно связаны с поддержкой следующих стандартов:

Шаблон для рендеринга страниц #

Файл print.html используется как шаблон при рендеринге каждой страницы документации.

Для превращения шаблона в конечную HTML-страницу используется шаблонизатор Jinja. Руководство по синтаксису шаблонизатора можно найти на его официальном сайте: Template Designer Documentation.

Шаблону доступен набор переменных и функций, описанных в разделе API для шаблонов.

В Собираке отключена поддержка стандартного синтаксиса комментариев внутри шаблона Jinja, чтобы избежать коллизии с идентификаторами заголовков в Markdown. Чтобы добавить комментарий внутрь шаблона, используйте синтаксис со сдвоенными фигурными скобками: {{# Комментарий #}}.

Статические файлы #

Все файлы, необходимые для корректного отображения темы (стили, изображения, шрифты), должны находиться в поддиректории _static рядом с шаблоном. Шаблон должен ссылаться на эти файлы через относительные пути, например:

<link rel='stylesheet' href='_static/theme.css'/>

Плагин для обработки страниц #

В файле extension.py может быть определён обработчик страниц — код на Python для дополнительной обработки содержимого перед рендерингом.

Класс плагина должен наследоваться от класса WeasyPrintProcessor. Плагин имеет почти неограниченные возможности в рамках обработки отдельной страницы, см. API для обработчиков.

Нумерация заголовков #

Нумерация для каждого заголовка (или информация о том, что этот заголовок не нумеруется) доступна через объект RT в момент применения темы. Один из способов перенести эту информацию в документ — в коде theme.py привести её к строке и записать в атрибут data-number заголовка (вы можете использовать другое название атрибута). Пример ниже реализует это для заголовков первого уровня:

class MyWeasyPrintProcessor(WeasyPrintProcessor):
    async def process_header(self, header: Header, page: Page):
        header, = await super().process_header(header, page)
        if header.level == 1:
            header.attributes['data-number'] = str(RT[page].number)
        return header,

Стиль CSS может сослаться на содержимое этого атрибута через функцию attr() — например, для псевдоселектора ::before. Пример ниже отображает номер заголовка перед текстом заголовка серым цветом:

h1::before {
    color: gray;
    content: attr(data-number) '. ';
}

Колонтитулы #

Специальное правило @page описывает стиль каждой страницы печатного документа. Свойство margin этого правила определяет отступы, в которых не может быть размещён основной контент страницы, но на этих отступах могут быть размещён дополнительный контент — колонтитулы. Содержимое и стили колонтитулов настраиваются с помощью правил, вложенных в @page.

Разные правила соответствуют разным зонам на полях: например, @top-left-corner отвечает за зону выше и левее основного контента, а @top-left — за зону выше, но не левее. Полный список доступных зон можно посмотреть по ссылке: Page-Margin Box Definitions.

Часто в колонтитулах требуется показывать заголовок текущей главы документа — иными словами, содержимое последнего встреченного заголовка. Это можно сделать с помощью свойств string-set и content. Пример ниже демонстрирует это на примере заголовков <h1>: при обработке каждого такого заголовка обновляется содержимое строки current-title (вы можете использовать другое название строки), а правило @top-left подставляет эту строку в верхний левый угол страницы.

@page {
    @top-left {
        content: string(current-title);
    }
}

h1 {
    string-set: current-title content();
}

Нумерация страниц #

В любой момент в CSS доступен счётчик page, равный номеру текущей страницы PDF-документа. Получить его текущее значение можно через функцию counter(). Например, так можно подставить номер страницы в верхний правый колонтитул:

@page {
    @top-right {
        content: counter(page);
    }
}

Помимо значения счётчика в текущем месте документа, существует способ получить значение для места, указанного в атрибуте href текущего элемента. Для этого необходимо использовать функцию target-counter(). Например, в момент обработки ссылки в оглавлении можно узнать, какое значение page будет там, куда ведёт эта ссылка:

ul.toc a::after {
    content: ' (стр. ' target-counter(attr(href), page) ')';
}

Обложка #

Тема по умолчанию размещает на первой странице PDF-документа простую обложку. По центру обложки отображается название документа (из настройки title), а внизу — текущий год. Оба элемента можно заменить на произвольный текст, если добавить в variables переменные COVER_CENTER и COVER_BOTTOM соответственно. Также можно добавить произвольный текст в верхней части обложки с помощью переменной COVER_TOP.

Более тонкая настройка внешнего вида обложки требует написания собственных стилей CSS. Если вы хотите изменить внешний вид обложки в рамках темы по умолчанию, ознакомьтесь с её файлом _cover.scss. В нём задан не только стиль для блока .cover, но и параметры страницы cover: размер отступов и скрытие колонтитулов. Тип страницы определён с помощью правила @page и привязан к блоку .cover через свойство page.

Пример ниже добавляет к стандартной обложке фон, а также меняет цвет всего текста на ней.

@page cover {
    background-image: url('cover.png');
    background-size: cover;
}

.cover {
    color: white;
}

Горизонтальные страницы #

Тема, используемая Собиракой по умолчанию, определяет специальный тип страниц landscape с горизонтальной ориентацией. Тип применяется к любой странице, на которой находится элемент со свойством page: landscape.

Работа устройства показана на большой горизонтальной схеме ниже.

`<div style="page:landscape; page-break-after:always"></div>`{=html}

![Большая горизонтальная схема](diagram.png)

Подсветка кода #

Поскольку WeasyPrint не поддерживает JavaScript, подсветка блоков кода должна быть реализована во время сборки HTML-содержимого. Собирака реализует её с помощью библиотеки Pygments, но по умолчанию не подключает соответствующие стили CSS, оставляя это на усмотрение автора темы.

Самый простой способ добавить стиль — выбрать понравившийся на странице Styles официального сайта Pygments, а затем локально сгенерировать необходимый файл CSS. Например:

pygmentize -f html -S tango > _static/pygments-tango.css

Полученный файл укажите в стилях документа обычным способом:

<link rel='stylesheet' href='_static/pygments-tango.css'/>

Файлы стилей SASS #

Некоторые темы оформления (в том числе фирменная тема sobiraka2025) используют препроцессор SASS и его языки SASS или SCSS для генерации стилей. Собирака запускает генерацию в рамках процесса сборки документации. Обратите внимание, что необходимая для этого команда sass не входит в Собираку и должна быть установлена отдельно.

Исходный файл стиля должен располагаться по адресу sass/print.sass или sass/print.scss в директории темы. При необходимости он может подключать другие файлы с помощью SASS-директивы @use.

Тема может включать в себя несколько вариантов оформления в поддиректории _flavors. Для выбора нужного варианта следует указать его название в настройке pdf.theme.flavor. Разработчик темы должен подключить выбранный пользователем вариант с помощью конструкции @use 'flavor'. Какие именно SASS-переменные и миксины возможно переопределить таким способом, зависит от конкретной темы.

Также возможно добавление файлов SASS/SCSS из директории проекта. Файлы, указанные в pdf.theme.customization, участвуют в генерации основного стиля наравне с собственными файлами темы. Из файлов, указанных в pdf.custom_styles, генерируются дополнительные CSS-стили.

Теги <link> со ссылками на все собранные стили CSS будут автоматически включены в переменную head, которую рекомендуется подключить в HTML-шаблон, см. API для шаблонов.