Введение
Не каждая задача «печати» подразумевает реальный физический принтер или диалог выбора — часто нужно просто сгенерировать PDF-файл программно: автоматический отчёт по расписанию, экспорт документа по нажатию кнопки «Сохранить как PDF», пакетная генерация счетов для множества клиентов без какого-либо взаимодействия с пользователем на каждый файл. QPdfWriter решает именно эту задачу — он реализует тот же интерфейс QPagedPaintDevice, что и QPrinter, но напрямую пишет в PDF-файл, минуя систему печати ОС целиком. В статье разберём, как использовать QPdfWriter для программной, неинтерактивной генерации PDF.
Концепция
QPdfWriter создаётся с указанием пути к выходному файлу и поддерживает настройку размера страницы, разрешения, метаданных документа (заголовок, автор) — всё это устанавливается программно, без диалогов. Поскольку QPdfWriter, как и QPrinter, наследует QPagedPaintDevice, один и тот же код отрисовки, написанный через QPainter, можно использовать взаимозаменяемо для печати на реальный принтер или для сохранения в PDF — это удобно, когда приложение должно поддерживать оба сценария без дублирования логики формирования содержимого документа.
Пример кода: QPdfWriter
#include <QPdfWriter>
#include <QPainter>
#include <QPageSize>
#include <QDateTime>
#include <QDebug>
bool generateInvoicePdf(const QString &filePath, const QString &clientName,
double amount, const QStringList &items)
{
QPdfWriter writer(filePath);
writer.setPageSize(QPageSize(QPageSize::A4));
writer.setResolution(300);
writer.setTitle("Счёт для " + clientName);
writer.setCreator("MyApp Invoice Generator");
QPainter painter;
if (!painter.begin(&writer)) {
qWarning() << "Не удалось создать PDF-файл:" << filePath;
return false;
}
const QRectF pageRect(0, 0, writer.width(), writer.height());
const int margin = writer.resolution() / 2; // отступ примерно 0.5 дюйма
QFont titleFont("Arial", 22, QFont::Bold);
painter.setFont(titleFont);
painter.drawText(QRectF(margin, margin, pageRect.width() - 2 * margin, 60),
Qt::AlignLeft, "Счёт на оплату");
QFont infoFont("Arial", 12);
painter.setFont(infoFont);
int y = margin + 100;
painter.drawText(margin, y, "Клиент: " + clientName);
y += 30;
painter.drawText(margin, y, "Дата: " + QDateTime::currentDateTime().toString("dd.MM.yyyy"));
y += 50;
QFont itemFont("Arial", 11);
painter.setFont(itemFont);
for (const QString &item : items) {
painter.drawText(margin, y, "• " + item);
y += 25;
}
y += 30;
QFont totalFont("Arial", 14, QFont::Bold);
painter.setFont(totalFont);
painter.drawText(margin, y, QString("Итого к оплате: %1 руб.").arg(amount, 0, 'f', 2));
painter.end();
qDebug() << "PDF успешно сгенерирован:" << filePath;
return true;
}
void demonstrateUsage()
{
generateInvoicePdf("invoice_2024_105.pdf", "ООО \"Ромашка\"", 15400.0,
{"Консультационные услуги — 10 000 руб.",
"Разработка макета — 5 400 руб."});
}
Пояснения к коду
writer.setResolution(300) устанавливает разрешение в точках на дюйм, влияющее на то, как координаты, переданные в QPainter (в логических единицах QPdfWriter), отображаются на физические размеры страницы — это явно отличается от поведения экранного виджета, где аналогичная настройка не требуется, поскольку DPI экрана определяется системой автоматически. setTitle()/setCreator() записывают метаданные непосредственно в структуру PDF-файла, видимые, например, в свойствах документа при открытии в Adobe Reader или аналогичной программе.
Вычисление margin как половины значения resolution() — простой способ получить отступ примерно в полдюйма независимо от выбранного разрешения; такой относительный подход к расчёту координат избавляет от необходимости пересчитывать абсолютные значения отступов при изменении настроек разрешения документа.
Подводные камни
- Путь к выходному файлу, на запись в который у процесса нет прав (системная защищённая директория, файл уже открыт и заблокирован другим процессом для чтения) приводит к тому, что
painter.begin(&writer)вернётfalseбез более конкретной диагностики причины — стоит явно проверять права на запись или перехватывать типичные ситуации (заблокированный файл) до попытки генерации, чтобы дать пользователю понятное сообщение об ошибке. - Жёстко заданные пиксельные координаты вместо относительных, основанных на
resolution()и размере страницы. Код, написанный с абсолютными числами координат для одного конкретного разрешения, выглядит некорректно (слишком мелко или со смещением) при измененииsetResolution()на другое значение — пример демонстрирует более устойчивый подход через вычисление отступов и позиций относительно текущего разрешения. - Отсутствие обработки переполнения страницы при динамическом, заранее не известном количестве элементов (
itemsв примере) — если список содержит больше позиций, чем уместится на одной странице A4 при заданном размере шрифта, текст просто продолжит печататься за пределами видимой области страницы без вызова ошибки, и эта часть содержимого будет потеряна визуально; для произвольного числа элементов нужна логика разбиения на несколько страниц, разбираемая в статье 195. - QPdfWriter не поддерживает добавление страниц к уже существующему PDF-файлу — каждый вызов создаёт новый файл с нуля; для объединения нескольких сгенерированных частей в один PDF-документ или для дополнения существующего файла новыми страницами нужны либо генерация всего содержимого за один проход через многостраничную логику (
writer.newPage()), либо использование отдельных инструментов слияния PDF, не входящих в стандартный APIQPdfWriter.