pdf для Rust: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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]; // №, Наименование, Кол                    

Рекомендаций