PDF на Rust: Полное руководство по созданию документов с библиотекой printpdf
1. Что это и зачем нужна
printpdf — это мощная и гибкая библиотека для языка программирования Rust, предназначенная для создания PDF-документов «с нуля». В отличие от многих других инструментов, которые просто конвертируют HTML или Markdown в PDF, printpdf предоставляет низкоуровневый API, дающий полный контроль над каждым аспектом документа: от размещения текста и векторной графики до встраивания шрифтов и изображений. Эта библиотека написана на чистом Rust без внешних зависимостей (кроме стандартной библиотеки), что делает её идеальным выбором для систем, где важны безопасность памяти, производительность и переносимость.
Зачем использовать printpdf? В современном мире PDF является универсальным форматом для обмена документами. Генерация PDF на серверной стороне требуется в самых разных сценариях: создание счетов и накладных в ERP-системах, формирование отчётов и аналитических дашбордов, генерация сертификатов и дипломов, создание печатных форм и бланков. Rust, с его акцентом на производительность и надёжность, идеально подходит для таких задач. printpdf позволяет делать это эффективно, без накладных расходов на запуск внешних процессов (как в случае с wkhtmltopdf) или использования тяжеловесных браузерных движков.
Ключевые особенности библиотеки включают: поддержку всех стандартных объектов PDF (страницы, шрифты, изображения, графика), работу с Unicode и встраивание TrueType/OpenType шрифтов, создание многостраничных документов, использование цветовых пространств RGB, CMYK и Grayscale, а также точное позиционирование элементов с помощью координатной системы PDF. Это не инструмент для конвертации «на лету», а скорее «конструктор» для профессионального создания документов.
2. Установка
Для установки библиотеки printpdf в ваш проект на Rust используется стандартный менеджер пакетов Cargo. Вам нужно добавить зависимость в файл Cargo.toml вашего проекта.
Откройте терминал в корневой папке вашего проекта и выполните команду:
cargo add printpdf
Или вручную добавьте следующую строку в раздел [dependencies] вашего Cargo.toml:
[dependencies]
printpdf = "0.7"
После этого Cargo автоматически загрузит библиотеку и все её внутренние зависимости при следующей сборке проекта. Убедитесь, что у вас установлен Rust (rustc) и Cargo. Если нет — посетите rustup.rs для установки.
3. Быстрый старт — минимальный рабочий пример
Давайте создадим простейший PDF-документ с одной страницей и текстом. Этот пример покажет базовый цикл работы с библиотекой: создание документа, добавление страницы, выбор шрифта, рисование текста и сохранение файла.
use printpdf::*;
use std::fs::File;
use std::io::BufWriter;
fn main() {
// Создаём новый PDF-документ
let (doc, page1, layer1) = PdfDocument::new(
"Мой первый PDF на Rust",
Mm(210.0), // Ширина страницы (A4)
Mm(297.0), // Высота страницы (A4)
"Layer 1", // Имя слоя
);
// Получаем шрифт по умолчанию (встроенный Helvetica)
let font = doc.add_builtin_font(BuiltinFont::Helvetica).unwrap();
// Начинаем работу со слоем
let current_layer = doc.get_page(page1).get_layer(layer1);
// Используем шрифт размером 48pt, чёрного цвета
current_layer.use_text("Hello, Rust PDF!", 48.0, Mm(50.0), Mm(200.0), &font);
// Сохраняем документ в файл
let file = File::create("hello_rust.pdf").unwrap();
let writer = BufWriter::new(file);
doc.save(&mut writer).unwrap();
println!("PDF файл 'hello_rust.pdf' успешно создан!");
}
После запуска этого кода (cargo run) в корневой папке проекта появится файл hello_rust.pdf. Откройте его — вы увидите фразу "Hello, Rust PDF!" на странице формата A4.
4. Основные методы/функции
Ниже представлены ключевые методы библиотеки printpdf, которые вы будете использовать чаще всего.
4.1 PdfDocument::new
Сигнатура: pub fn new(title: &str, width: Mm, height: Mm, layer_name: &str) -> (PdfDocument, PageIndex, LayerIndex)
Что делает: Создаёт новый пустой PDF-документ с одной страницей и одним слоем. Возвращает кортеж из трёх элементов: сам документ, индекс созданной страницы и индекс слоя на этой странице.
let (doc, page, layer) = PdfDocument::new("Invoice", Mm(210.0), Mm(297.0), "MainLayer");
4.2 doc.add_builtin_font
Сигнатура: pub fn add_builtin_font(&mut self, font: BuiltinFont) -> Result<IndirectFontRef, &'static str>
Что делает: Добавляет в документ один из встроенных шрифтов (Helvetica, Times, Courier и их вариации). Возвращает ссылку на шрифт, которую можно использовать для рисования текста.
let font = doc.add_builtin_font(BuiltinFont::TimesRoman).unwrap();
4.3 current_layer.use_text
Сигнатура: pub fn use_text(&self, text: &str, font_size: f64, x: Mm, y: Mm, font: &IndirectFontRef)
Что делает: Размещает текстовую строку на слое в указанных координатах с заданным шрифтом и размером. Координата y отсчитывается от нижнего края страницы.
current_layer.use_text("Счет №123", 24.0, Mm(20.0), Mm(270.0), &font);
4.4 doc.add_page
Сигнатура: pub fn add_page(&mut self, width: Mm, height: Mm, layer_name: &str) -> (PageIndex, LayerIndex)
Что делает: Добавляет новую страницу в конец документа. Возвращает индексы новой страницы и её первого слоя. Полезно для создания многостраничных документов.
let (page2, layer2) = doc.add_page(Mm(210.0), Mm(297.0), "Page2Layer");
4.5 doc.add_font
Сигнатура: pub fn add_font(&mut self, font_data: &[u8]) -> Result<IndirectFontRef, Box<dyn Error>>
Что делает: Загружает внешний шрифт из буфера байтов (например, из файла .ttf). Позволяет использовать любые шрифты, включая кириллические, иконки и т.д.
let font_data = std::fs::read("path/to/font.ttf").unwrap();
let custom_font = doc.add_font(&font_data).unwrap();
4.6 current_layer.set_fill_color
Сигнатура: pub fn set_fill_color(&self, color: Color)
Что делает: Устанавливает цвет заливки для последующих графических операций (заливка прямоугольников, кругов и т.д.). Цвет задаётся через структуру Color (например, Color::Rgb(Rgb::new(r, g, b), None)).
use printpdf::*;
current_layer.set_fill_color(Color::Rgb(Rgb::new(200, 50, 50), None)); // Красный цвет
4.7 current_layer.add_rect
Сигнатура: pub fn add_rect(&self, x: Mm, y: Mm, width: Mm, height: Mm)
Что делает: Добавляет прямоугольник на слой. Сам по себе он невидим — его нужно либо залить цветом (fill()), либо обвести контуром (set_outline_color() + stroke()).
current_layer.add_rect(Mm(10.0), Mm(10.0), Mm(190.0), Mm(277.0)); // Рамка страницы
current_layer.set_outline_color(Color::Rgb(Rgb::new(0, 0, 0), None));
current_layer.stroke(); // Применить обводку
4.8 current_layer.add_image
Сигнатура: pub fn add_image(&self, image: &Image, x: Mm, y: Mm, width: Mm, height: Mm)
Что делает: Размещает ранее загруженное изображение на странице в заданных координатах и с заданными размерами. Изображение должно быть предварительно загружено с помощью Image::from_* методов.
let image = Image::from_reader(&mut std::io::BufReader::new(
std::fs::File::open("logo.png").unwrap()
)).unwrap();
current_layer.add_image(&image, Mm(150.0), Mm(250.0), Mm(50.0), Mm(50.0));
4.9 doc.save
Сигнатура: pub fn save(&self, writer: W) -> Result<(), Box<dyn Error>> where W: Write
Что делает: Сериализует весь документ в формат PDF и записывает его в переданный writer (например, BufWriter<File> или Vec<u8>). Это финальный шаг создания документа.
let mut bytes = Vec::new();
doc.save(&mut bytes).unwrap(); // Сохраняем в память
5. Реальный практический пример (150+ строк)
В этом примере мы создадим полноценный шаблон счёта-фактуры (Invoice). Документ будет содержать: логотип компании (заглушка), реквизиты, таблицу с товарами и итоговую сумму. Мы используем внешний шрифт для поддержки кириллицы.
use printpdf::*;
use std::fs::File;
use std::io::BufWriter;
fn main() -> Result<(), Box> {
// 1. Создаём документ формата A4
let (doc, page, layer) = PdfDocument::new(
"Счет-фактура №123",
Mm(210.0),
Mm(297.0),
"MainLayer",
);
// 2. Загружаем шрифт (например, DejaVu Sans для кириллицы)
// Скачайте файл DejaVuSans.ttf и положите рядом с проектом
let font_data = std::fs::read("DejaVuSans.ttf")?;
let font = doc.add_font(&font_data)?;
let current_layer = doc.get_page(page).get_layer(layer);
// 3. Рисуем рамку документа
current_layer.set_outline_color(Color::Rgb(Rgb::new(0, 0, 0), None));
current_layer.add_rect(Mm(10.0), Mm(10.0), Mm(190.0), Mm(277.0));
current_layer.stroke();
// 4. Заголовок
current_layer.use_text("СЧЕТ-ФАКТУРА №123 от 15.10.2023", 20.0, Mm(15.0), Mm(270.0), &font);
// 5. Реквизиты продавца
current_layer.use_text("Продавец: ООО 'Рога и Копыта'", 12.0, Mm(15.0), Mm(250.0), &font);
current_layer.use_text("ИНН: 1234567890, КПП: 123456789", 10.0, Mm(15.0), Mm(240.0), &font);
current_layer.use_text("Адрес: г. Москва, ул. Ленина, д. 1", 10.0, Mm(15.0), Mm(230.0), &font);
// 6. Реквизиты покупателя
current_layer.use_text("Покупатель: ООО 'ТехноИнновации'", 12.0, Mm(15.0), Mm(210.0), &font);
current_layer.use_text("ИНН: 9876543210, КПП: 987654321", 10.0, Mm(15.0), Mm(200.0), &font);
// 7. Таблица товаров (шапка)
let table_top_y = 180.0;
let col_widths = [10.0, 80.0, 30.0, 30.0, 30.0]; // №, Наименование, Кол Также в библиотеке
Машинное обучение на JavaScript: библиотеки для Node.js — обзор и примеры
JWT токены в Java: полное руководство по работе с JSON Web Tokens
PHPUnit для PHP: полное руководство с примерами
Стандартная библиотека Java: полный обзор для начинающих и профи
Отправка SMS на C# (.NET): Интеграция с API SMS-сервисов через HttpClient