QuestPDF

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

Рабочий процесс строится вокруг декларативного Fluent API: разработчик описывает не координаты каждой строки, а иерархию контейнеров — страницу, колонку, строку, таблицу, текстовый блок или слой. Движок сам измеряет элементы, переносит содержимое на следующие страницы и повторяет те области, которые должны оставаться в шапке или подвале. Благодаря этому шаблон документа можно разделить на небольшие компоненты, связать с моделью данных и хранить вместе с остальным исходным кодом.

Визуальная часть разработки сосредоточена в QuestPDF Companion. Слева показано дерево элементов, справа — отрисованная страница; выбранный узел подсвечивается, а панели измерений сообщают его размер и положение. Из предпросмотра можно перейти к соответствующей строке кода, найти текст, увеличить фрагмент, измерить расстояние между блоками и разобрать причину переполнения, не сохраняя новый файл после каждого изменения.

Скачать QuestPDF

Оценка 9.7 Рекомендуем
  • Редактирование PDF
  • Русский интерфейс
  • Просто новичкам
Скачать бесплатно на Windows
Лучшая альтернатива
QuestPDF
Оценка 8.5
  • Требуется знание C#
  • Нет визуальной верстки
  • Платно для крупных компаний
Скачать QuestPDF
Загрузка начнётся после нажатия

Как устроена работа с QuestPDF

Шаблон начинается с объекта документа и одного или нескольких описаний страниц. Внутри страницы доступны области Background, Header, Content, Footer и Foreground. Основной поток помещают в Content: он получает пространство между шапкой и подвалом и при необходимости продолжается на следующем листе. Фон и передний слой занимают всю страницу независимо от полей, поэтому подходят для бланка, декоративной рамки, пометки Черновик или полупрозрачного водяного знака.

Каждый вызов Fluent API возвращает контейнер, к которому последовательно применяются ограничения размера, отступы, фон, границы, выравнивание и содержимое. Порядок имеет значение: Padding перед Border создаёт один результат, а Border перед Padding — другой, потому что обёртки образуют вложенную цепочку. Такой подход полезно воспринимать как дерево компоновки, где внешний узел задаёт доступное пространство, а внутренний сообщает, сколько места ему требуется.

Обычная схема включает типизированную модель, класс документа и отдельные компоненты для повторяющихся фрагментов. Модель содержит только данные — реквизиты, позиции, суммы и даты. Класс документа определяет формат листа и общую структуру. Компоненты отвечают за шапку, адреса, таблицу, итоги и подписи. При таком разделении изменение оформления не затрагивает расчёты, а один и тот же блок можно использовать в нескольких шаблонах.

QuestPDF не превращает готовую веб-страницу в PDF и не интерпретирует произвольный CSS. Макет описывается средствами библиотеки, поэтому поведение переносов и размеров контролируется явно. Это уменьшает зависимость от браузерного движка, но требует перенести существующий HTML-шаблон в C#-компоненты. Для проекта, где исходником уже служит сложная веб-вёрстка, эта работа может оказаться существенной.

Минимальный документ

using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;

Document.Create(document =>
{
    document.Page(page =>
    {
        page.Size(PageSizes.A4);
        page.Margin(24);
        page.DefaultTextStyle(x => x.FontSize(11));

        page.Header().Text("Отчёт").Bold().FontSize(20);
        page.Content().PaddingVertical(16).Text("Содержимое документа");
        page.Footer().AlignCenter().Text(text =>
        {
            text.CurrentPageNumber();
            text.Span(" / ");
            text.TotalPages();
        });
    });
}).GeneratePdf("report.pdf");

В этом примере движок сам вычисляет высоту заголовка и подвала, а оставшееся место отдаёт содержимому. Нумерация страниц рассчитывается во втором проходе, поэтому TotalPages можно использовать без предварительного знания длины документа. Если основной блок разрастается, новые страницы получают ту же конфигурацию, шапку и подвал автоматически.

Добавление пакета и подготовка проекта

Пакет подключают к проекту через менеджер NuGet командой dotnet add package QuestPDF или через окно управления зависимостями в IDE. После восстановления пакетов становятся доступны пространства имён Fluent, Helpers и Infrastructure. Первое отвечает за цепочки компоновки, второе содержит готовые размеры страниц, цвета и тестовые заполнители, а третье — интерфейсы контейнеров, компонентов и настройки лицензии.

До создания документа нужно явно выбрать подходящий тип лицензии в QuestPDF.Settings.License. Community предназначена для пользователей и организаций, которые соответствуют условиям бесплатного использования; Evaluation помогает проверить возможности; платные варианты применяют там, где этого требуют условия коммерческой эксплуатации. Отсутствие настройки не следует маскировать обработчиком исключений: правильнее вынести её в точку запуска приложения и покрыть тестом конфигурации.

Для шаблонов полезно создать отдельный проект или папку, где лежат классы документов, общие стили, шрифты и изображения. Серверный обработчик должен передавать туда уже проверенную модель, а не выполнять внутри Compose запросы к базе или внешним службам. Компоновка может запускаться несколько раз и в другом порядке, поэтому она должна быть детерминированной и не зависеть от побочных эффектов.

При обновлении пакета стоит собрать эталонный набор документов и сравнить не только факт успешной генерации, но и число страниц, размеры файла, наличие нужных шрифтов и результат визуального рендеринга. Изменения движка типографики или нативных зависимостей способны исправить одну редкую ситуацию и одновременно проявить несовместимость с собственным нестандартным SVG. Небольшой регрессионный набор снижает риск обнаружить это уже после выпуска счетов клиентам.

Интерфейс QuestPDF Companion

Окно QuestPDF Companion с деревом документа и предпросмотром счёта

В основном окне слева расположена иерархия документа, а центральную область занимает страница. Дерево повторяет логику Fluent API: в нём видны Page, Header, Content, Column, Row, Text Block, Padding, Alignment и пользовательские компоненты. Вложенность помогает понять, какой контейнер ограничивает размер выбранного элемента и почему конкретный текст оказался в этой позиции.

Предпросмотр поддерживает прокрутку, перетаскивание и масштабирование. Сочетание Ctrl с колесом меняет масштаб, Ctrl+E вписывает страницу в окно, двойной щелчок выбирает элемент, а Ctrl+щелчок пытается открыть реализацию в указанном редакторе кода. Для ссылок используется Alt+щелчок: внешняя ссылка открывается в браузере, SectionLink переводит к внутренней секции документа.

Связь с приложением устанавливается через локальный порт. В коде вместо обычного GeneratePdf вызывают ShowInCompanion, после чего документ передаётся в окно предпросмотра. При работе через Visual Studio удобно включить Hot Reload on File Save; в Rider изменения применяются кнопкой Apply changes или сочетанием Alt+F10; из терминала подходит dotnet watch. Если порт занят или блокируется политикой безопасности, его можно изменить и в вызове, и в настройках Companion.

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

Дерево можно скрыть

Предпросмотр QuestPDF Companion без панели иерархии

Клавиша Ctrl+W скрывает дерево и отдаёт больше места странице. Такой режим удобен для проверки общей композиции, длинных таблиц и нескольких форматов листа. Для поиска причины переполнения панель возвращают тем же сочетанием: без иерархии видно следствие, но трудно определить контейнер, который наложил конфликтующие ограничения.

Страницы, поля и области макета

Размер страницы задают готовой константой PageSizes или собственными шириной и высотой. Можно выбрать книжную или альбомную ориентацию, установить минимальный либо максимальный размер и использовать разные конфигурации страниц в одном документе. Для чеков и лент полезна непрерывная высота, а для сертификатов — альбомный лист с фоновым SVG, занимающим всю площадь.

Поля допускают одну величину для всех сторон, отдельные горизонтальные и вертикальные значения либо индивидуальную настройку каждой стороны. Единицы измерения задаются явно: точки, миллиметры, сантиметры, дюймы и другие поддерживаемые варианты. Внутри шаблона лучше придерживаться одной системы и преобразовывать требования типографии в одном месте, иначе сочетание точек и миллиметров усложняет проверку.

Header и Footer повторяются на каждой странице и занимают место в основном потоке. Если их суммарная высота вместе с полями оставляет Content отрицательное или слишком малое пространство, возникает DocumentLayoutException. Для декоративного элемента, который не должен сжимать основной текст, следует использовать Background, Foreground или Layers, а не увеличивать шапку до размера всей страницы.

Background рисуется позади основного содержимого, Foreground — поверх него. Они игнорируют поля и охватывают лист полностью, что позволяет размещать цветную подложку до края или водяной знак поверх таблицы. При этом сам разработчик отвечает за читаемость: непрозрачный Foreground способен закрыть текст, а тяжёлое растровое изображение на каждой странице заметно увеличит итоговый файл.

Разные шапки и условия показа

Для первой страницы часто нужна расширенная шапка с адресами, а для остальных — компактное название документа. Это реализуется элементами ShowOnce, SkipOnce или условной композицией. ShowOnce отображает фрагмент только один раз в начале потока; SkipOnce пропускает первое появление и показывает блок далее. В многостраничных таблицах отдельный Table.Header повторяет названия столбцов независимо от шапки страницы.

Условие ShowIf удобно для необязательного комментария, скидки или блока подписи, но данные нужно проверить до построения дерева. Если условие зависит от значения, которое меняется внутри Compose, результат может отличаться между проходами измерения. Предсказуемее вычислить флаги в модели и использовать их как неизменяемые значения.

Колонки, строки и точное распределение места

Column располагает элементы сверху вниз и умеет автоматически продолжать поток на следующей странице. Между элементами можно установить Spacing, а отдельные Item снабдить Padding, границей, фоном и ограничениями. Column подходит для большинства документов: заголовок, реквизиты, таблица, итоги и примечания естественно образуют вертикальную последовательность.

Row делит доступную ширину между элементами. RelativeItem получает долю оставшегося пространства, ConstantItem — фиксированную ширину, AutoItem — ширину собственного содержимого. Например, в строке реквизитов логотипу задают ConstantItem, адресу — RelativeItem, а номеру счета — AutoItem. Если сумма фиксированных ширин превышает контейнер, сжатия как в браузере не произойдёт: движок сообщит о невозможной компоновке.

Alignment управляет положением ребёнка внутри выделенной области, но не меняет его собственные требования. AlignRight сработает, только если внешняя область шире содержимого. Чтобы прижать цену к правому краю таблицы, нужно сначала дать ячейке всю доступную ширину, а затем применить выравнивание; попытка выровнять текст в AutoItem визуально ничего не изменит.

Width, Height, MinWidth, MaxWidth, MinHeight и MaxHeight накладывают ограничения. Точное значение следует использовать там, где размер действительно фиксирован: штрихкод, фотография, подпись или ячейка номера. Для длинного текста безопаснее максимальная ширина или относительная колонка. Жёсткая высота, внутри которой может оказаться непредсказуемое описание, — частая причина LayoutException.

Измерение положения и размеров

Получение координат элемента в QuestPDF Companion

Режим координат включается клавишей 2. Указатель показывает номер страницы и положение выбранной точки, что помогает сопоставить макет с требованиями типографии или формой заказчика. Значения следует интерпретировать в системе координат страницы, а не окна: масштаб предпросмотра не меняет реальные размеры документа.

Вертикальное измерение отступа в QuestPDF Companion

Клавиши 3 и 4 включают вертикальное и горизонтальное измерение. Это практичнее подбора на глаз, когда нужно выдержать 50 пунктов от верхнего края или одинаковую ширину блоков. Измерение выявляет и скрытые отступы: если расстояние неожиданно велико, в дереве обычно находится лишний Padding, Spacing или высота пустого контейнера.

Горизонтальное измерение блока в QuestPDF Companion

Таблицы для отчётов и счетов

Table начинается с определения колонок. ConstantColumn сохраняет фиксированную ширину, RelativeColumn делит оставшееся место пропорционально коэффициентам. Комбинация подходит для счета: номер позиции и количество получают узкие фиксированные колонки, название товара растягивается, цена и сумма занимают предсказуемую ширину. До добавления данных полезно проверить самый длинный заголовок и самое широкое числовое значение.

Ячейки добавляются последовательно либо привязываются к конкретной строке и колонке. Доступны ColumnSpan и RowSpan, поэтому можно строить многоуровневые шапки и объединять группы. Явное позиционирование удобно, но требует следить за пересечениями: две ячейки в одной области создают наложение, которое может быть намеренным только при использовании соответствующего сценария.

Table.Header повторяется на каждой странице, а Table.Footer — внизу каждой части таблицы. В шапке размещают названия столбцов и единицы измерения; в подвале — промежуточные отметки или пояснение. Общий итог, который должен появиться один раз после всех строк, обычно добавляют отдельным элементом Column после таблицы, иначе он будет повторяться.

Стиль ячеек удобно вынести в локальную функцию: граница снизу, вертикальный отступ, размер шрифта и выравнивание применяются одинаково к заголовку и данным. Для зебры строк цвет можно выбирать по индексу. При большом количестве записей не следует создавать новый объект изображения или шрифта в каждой ячейке; повторяемые ресурсы загружают один раз и переиспользуют.

Поведение строк на границе страницы

Обычная строка таблицы переносится согласно возможностям её содержимого. Если одна ячейка содержит длинный абзац, высота строки может распределиться между страницами. Когда строку необходимо сохранить целиком, применяют ShowEntire, но только после оценки максимального размера: элемент, который физически выше страницы, никогда не сможет разместиться и вызовет исключение. EnsureSpace мягче — он начинает блок на новой странице при недостатке места, но допускает перенос, если блок слишком велик.

Повторяемая шапка должна быть компактной. Высокая шапка с несколькими абзацами уменьшает полезную площадь каждой страницы и увеличивает их число. Если описание столбцов длинное, лучше использовать короткие заголовки и вынести расшифровку перед таблицей либо в примечание после неё.

Текст, стили и шрифты

Text принимает простую строку или делегат, в котором создаются Span, Line, Hyperlink и элементы нумерации страниц. Разные фрагменты одного абзаца могут иметь собственный вес, цвет, размер, подчёркивание, фон, надстрочное или подстрочное положение. Такой блок сохраняет единый перенос строк, поэтому подходит для реквизитов с выделенной меткой и обычным значением.

DefaultTextStyle задаёт наследуемый стиль на странице или контейнере. В нём удобно закрепить основной шрифт, размер и цвет, а заголовки строить поверх базового набора. Наследование уменьшает число повторов, но может скрыть причину неожиданного оформления. Если небольшой блок внезапно получил крупный шрифт, нужно проверить не только его вызовы, но и стиль всех родителей в дереве.

Поддерживаются межстрочный интервал, расстояние между символами и словами, выравнивание абзаца, обрезка по числу строк, направление письма и OpenType-функции. Стандартные лигатуры отключены по умолчанию и включаются явно. Это важно для поиска и копирования текста: визуально красивое соединение символов должно сопровождаться корректным представлением исходных букв.

Для русского текста требуется шрифт с кириллическими глифами. При включённой проверке отсутствующих глифов библиотека остановит генерацию с DocumentDrawingException; при отключённой проверке в файле могут появиться пустые квадраты. На сервере не стоит полагаться на набор шрифтов рабочей станции: файлы шрифтов включают в развёртывание и регистрируют через FontManager или каталог обнаружения.

Стабильные шрифты в контейнере и облаке

QuestPDF.Settings.UseEnvironmentFonts управляет использованием системных шрифтов. Значение true удобно на компьютере разработчика, но разные образы Linux и Windows могут подобрать разные гарнитуры. Для воспроизводимого результата параметр отключают, регистрируют только поставляемые шрифты и проверяют, что лицензия гарнитуры разрешает встраивание в PDF.

Автоматическое подмножество шрифта уменьшает файл: в документ попадают использованные глифы, а не вся гарнитура. Однако несколько начертаний — Regular, Medium, Bold и Italic — остаются отдельными ресурсами. Искусственное выделение не всегда заменяет настоящий файл начертания, особенно в сложной типографике и при требованиях PDF/A.

Правостороннее и двунаправленное содержимое задаётся направлением контейнера или документа. Для арабского и иврита важны не только AlignRight, но и порядок символов, форма глифов и смешанные фрагменты с числами. Проверять такой документ нужно реальным текстом, а не латинским заполнителем: проблемы проявляются именно на стыке направлений и гарнитур.

Растровые изображения и SVG

Растровое изображение можно передать путём, массивом байтов, потоком или подготовленным объектом. Контейнер определяет область, а режим FitArea, FitWidth, FitHeight или FitUnproportionally задаёт масштабирование. Последний вариант заполняет область ценой искажения пропорций, поэтому для фотографий и логотипов обычно выбирают сохранение соотношения сторон.

QuestPDF не выводит изображение в физическом размере только по его DPI: макет задаёт размер явно. Для печатной фотографии шириной 100 миллиметров нужно установить именно эту ширину и подобрать исходное разрешение, достаточное для выбранного ImageRasterDpi. Большой файл, уменьшенный до маленькой иконки, расходует память на декодирование без заметного выигрыша качества.

Непрозрачные растровые изображения могут перекодироваться в JPEG с выбранным уровнем качества, а изображения с альфа-каналом сохраняются как PNG. Параметр ImageCompressionQuality влияет на компромисс между размером и деталями. Технические схемы с тонкими линиями лучше оставлять в PNG или SVG, а фотографии допускают JPEG; иначе вокруг текста и контрастных границ появятся артефакты.

Если одна картинка повторяется во множестве строк, её нужно загрузить и декодировать один раз. SharedImage или заранее созданный объект предотвращает повторное включение одинаковых данных и снижает время генерации. Особенно заметен эффект в каталоге на сотни страниц, где логотип или пиктограмма присутствует в каждой карточке.

Векторная графика

SVG сохраняет чёткость при масштабировании и подходит для логотипов, диаграмм, подписей и фоновых шаблонов. Содержимое можно передать строкой, путём к файлу или предварительно разобранным SvgImage. Предварительная загрузка полезна, когда один вектор повторяется на каждой странице: разбор XML выполняется один раз.

Поддержка SVG широка, но не равна браузерной. Внешние ресурсы, редкие фильтры, нестандартные шрифты или особенности конкретного редактора могут отрисоваться иначе. Надёжный процесс включает очистку SVG, перевод текста в контуры только при необходимости, проверку встроенных изображений и визуальный тест на целевой системе.

Если SVG содержит текст, нужный шрифт должен быть доступен тем же способом, что и для обычного Text. Иначе подпись векторного логотипа может исчезнуть, хотя геометрия отрисуется. Для фирменного знака безопаснее использовать контуры, если это разрешено владельцем бренда и не мешает доступности.

Графики, штрихкоды, QR-коды и карты

QuestPDF предоставляет контейнеры и интеграционные примеры для сложной графики, но не заменяет специализированный движок диаграмм. Практический путь — построить график библиотекой, получить SVG или растровое изображение и поместить его в макет. SVG предпочтителен для линий и подписей, потому что остаётся резким при печати и обычно занимает меньше места.

Для штрихкодов и QR-кодов важно передавать изображению достаточную область и не применять непропорциональное растяжение. Размер модуля должен соответствовать разрешению принтера, а вокруг кода оставляют свободную зону. Проверка не заканчивается визуальным просмотром: сгенерированный код следует считать камерой и промышленным сканером на образце печати.

Карты обычно формируют внешним картографическим компонентом в виде изображения. QuestPDF размещает готовый результат, добавляет подписи и координаты, но не запрашивает тайлы сам по себе. Если документ собирается в изолированной среде, все карты и значки нужно подготовить заранее, чтобы генерация не зависела от сети.

Для нестандартных эффектов можно подключить SkiaSharp отдельно и рисовать на холсте, однако эта зависимость не входит в основной пакет. Официальная рекомендация для масштабируемой графики — по возможности генерировать SVG. Холст оправдан для фильтров, сложных путей и алгоритмических изображений, когда SVG неудобен или слишком велик.

Многостраничный поток и управление переносами

Большинство контейнеров участвует в постраничном потоке: движок измеряет доступную высоту, решает, что можно отрисовать, и переносит остаток. PageBreak принудительно завершает страницу. PreventPageBreak просит не начинать перенос внутри выбранного участка, EnsureSpace проверяет минимальный остаток перед началом, а ShowEntire требует поместить весь блок на одной странице.

Эти элементы нельзя применять механически ко всему документу. ShowEntire вокруг таблицы из сотен строк гарантированно создаст невыполнимое требование. PreventPageBreak вокруг большого компонента может многократно откладывать его размещение. Ограничения следует ставить на небольшой смысловой фрагмент: заголовок с двумя строками, карточку товара, подпись с датой или короткий итог.

Repeat повторяет элемент на каждой странице, ShowOnce оставляет первое появление, SkipOnce — все, кроме первого. StopPaging прекращает дальнейшее продолжение содержимого, что полезно для намеренного отсечения, но опасно для данных: без явной отметки пользователь не узнает, что часть строк потеряна. Если требуется ограничить объём, лучше показать число скрытых элементов.

MultiColumn распределяет поток по нескольким вертикальным колонкам и может балансировать их высоту. Он подходит для словаря, справочника и журнала, но требует больше вычислений, чем обычная колонка. В отчёте с тысячами записей стоит измерить время и память, прежде чем использовать балансировку на каждой странице.

Разрыв перед заголовком

Типовая ошибка — заголовок раздела остаётся внизу страницы, а первый абзац переходит на следующую. Решение состоит не в фиксированной высоте всего раздела, а в EnsureSpace на связке заголовка и небольшого начала текста. Движок перенесёт блок, если остатка мало, но позволит продолжить длинное содержимое далее.

Для таблицы похожий приём применяется к названию и Table.Header. Название можно объединить с минимальным набором строк, чтобы на странице не оставалась одинокая подпись. Саму таблицу при этом не запирают в ShowEntire.

Навигация, оглавление и ссылки

Section присваивает имя фрагменту, который может занимать несколько страниц. SectionLink создаёт кликабельную область, ведущую к этой секции, а номер страницы секции можно вывести в оглавлении. Имена должны быть уникальными и стабильными; удобнее строить их из идентификатора модели, а не из отображаемого заголовка, который может повторяться или меняться при переводе.

Гиперссылки добавляются как часть текста или контейнера. В итоговом PDF они открывают указанный адрес, а в Companion проверяются Alt+щелчком. Текст ссылки должен сообщать назначение сам по себе; длинный технический адрес лучше скрыть за понятной подписью. Для PDF/UA важны читаемый порядок и семантика, поэтому декоративный значок без альтернативного описания не должен быть единственным носителем действия.

Закладки формируются из структуры разделов и помогают перемещаться в длинных отчётах. Они не заменяют видимое оглавление, если документ печатают. Практичный шаблон создаёт оба механизма из одного списка разделов: видимая таблица содержит название и номер страницы, а закладка открывает тот же участок.

Номера страниц доступны только после измерения всего документа, поэтому движок выполняет два прохода. Код компонента не должен менять данные между проходами. Вызов DateTime.Now внутри Compose может дать разные значения, а генератор случайных чисел — другую длину текста; такие значения вычисляют заранее и передают через модель.

Компоненты, данные и повторное использование

Интерфейс IComponent позволяет вынести фрагмент в класс с методом Compose. Компонент получает данные через конструктор и возвращает дерево контейнеров. Это хорошая граница для адресной карточки, строки итогов, подписи, заголовка таблицы или фирменного блока. Компонент не должен сохранять состояние между вызовами: движок вправе вызвать Compose несколько раз.

Локальные функции подходят для небольших стилей, а extension methods — для общих модификаторов контейнера. Например, CellStyle может применить границу и отступ к любой ячейке, а SectionTitle — общий размер и цвет заголовка. Методы расширения лучше делать узкими: функция, которая одновременно меняет фон, высоту, текст и условие показа, трудно предсказуема в другом контексте.

Dynamic Component получает информацию о доступном пространстве и может возвращать разное содержимое на разных страницах. Это мощный механизм для сложных подвалов, продолжений таблицы и зависимых от страницы блоков, но его состояние требует осторожности. Compose может вызываться повторно, поэтому изменения состояния должны следовать документированному шаблону, а исключения из динамического компонента могут проявиться как DocumentDrawingException.

Lazy уменьшает пиковое потребление памяти, откладывая построение тяжёлых участков. Он полезен в отчётах с большим количеством однотипных страниц, но не отменяет необходимость освобождать собственные потоки и изображения. Ленивая композиция не должна обращаться к уже закрытому контексту базы данных; все данные либо материализуют заранее, либо дают компоненту безопасный поставщик с контролируемым временем жизни.

Отделение расчётов от оформления

Суммы, налоги, округление и группировку следует вычислять до вызова Document.Create. Тогда шаблон только отображает готовые значения и не дублирует бизнес-логику. Это особенно важно для денежных документов: форматирование числа не должно незаметно менять рассчитанную сумму, а итог на странице должен совпадать с выгрузкой и записью в базе.

Для локализации модель может содержать уже выбранные подписи, культуру чисел и формат дат. Компоненты получают объект ресурсов, а не обращаются к глобальной текущей культуре. Такой подход позволяет параллельно генерировать документы на разных языках без гонок и сохраняет одинаковый результат в тестах.

Форматы вывода и интеграция

Основной результат — PDF, который можно записать в файл, поток или массив байтов. Поток подходит для ответа ASP.NET, массив — для очереди или хранилища объектов, файл — для пакетного задания. При работе с HTTP важно не держать весь документ в нескольких копиях: если API уже принимает поток, лишнее преобразование в byte[] увеличивает память на размер файла.

Кроме PDF поддерживаются XPS, SVG и растровые изображения PNG, JPEG и WebP. Не все функции одинаково представлены во вспомогательных форматах: шифрование, вложения и структура PDF к ним неприменимы. Изображения удобны для миниатюр и визуальных тестов, SVG — для отдельной страницы или компонента, XPS — для специфических рабочих процессов печати.

GeneratePdfAndShow облегчает разработку на рабочем компьютере, но запуск внешнего просмотрщика не подходит для службы или контейнера. В серверном коде используют GeneratePdf и сами управляют сохранением. Companion также следует включать только в сценарии разработки: производственный обработчик не должен ждать подключения окна предпросмотра.

Метаданные Title, Author, Subject, Keywords, Creator, Producer и даты задаются через объект DocumentMetadata. Они видны в свойствах PDF и помогают поиску, но не заменяют содержимое. Поля с персональными данными следует заполнять осознанно: служебное имя пользователя или путь сборки не должны случайно попадать в распространяемый документ.

РезультатТипичный сценарийОграничение
PDFСчета, отчёты, архивные документыТребует проверки профиля соответствия и шрифтов
XPSПечать и обмен в среде MicrosoftЧасть PDF-специфичных функций отсутствует
SVGВекторный предпросмотр отдельной страницыМногостраничность обрабатывается отдельно
PNG/JPEG/WebPМиниатюры и визуальные тестыТекст становится растровым изображением

Операции с существующими PDF

DocumentOperation загружает готовый PDF и применяет к нему последовательность операций. Можно выбрать страницы диапазоном, изменить их порядок, объединить несколько файлов, наложить или подложить другой PDF, добавить вложения, шифрование, расширенные метаданные и линейное представление для постепенной загрузки. Результат сохраняется новым файлом, поэтому исходник разумно оставлять неизменным.

Диапазоны страниц поддерживают отдельные номера, интервалы и специальные обозначения конца. Перед массовой обработкой нужно проверить, как интерпретируется нумерация, и валидировать пользовательский ввод: запрос несуществующей страницы должен давать понятную ошибку, а не молча формировать неполный пакет. Для юридически значимых документов полезно записывать перечень входных файлов и выбранных диапазонов в журнал операции.

Overlay помещает страницы другого PDF поверх целевых, Underlay — под ними. Это позволяет наносить водяной знак, фирменный бланк или фон без повторной генерации исходного документа. Размер и ориентация слоёв должны совпадать с целевыми страницами; если они различаются, необходимо заранее подготовить шаблон нужного формата, иначе содержимое сместится или обрежется.

Объединение существующих файлов отличается от объединения объектов Document, которые генерируются в одном процессе. В первом случае работает API операций и qpdf; во втором можно сохранять сквозную или раздельную нумерацию ещё на этапе компоновки. Выбор зависит от того, есть ли исходные шаблоны в коде или поступают готовые PDF от внешних систем.

Вложения и веб-оптимизация

AddAttachment помещает внутрь PDF файл с ключом, описанием, MIME-типом и отношением к основному документу. Для электронного счёта это может быть XML с машинно-читаемыми данными, для отчёта — CSV с исходной таблицей. Получатель увидит вложение только в просмотрщике, который поддерживает такую функцию, поэтому критически важную информацию нельзя хранить исключительно внутри вложения.

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

Пароли, разрешения и ограничения доступа

Шифрование поддерживает 40-, 128- и 256-битные варианты. UserPassword ограничивает открытие или доступ в соответствии с настройками, OwnerPassword даёт владельцу полный контроль. Пустой пароль владельца и одинаковые пароли пользователя и владельца считаются слабой конфигурацией. Для нового документа выбирают современный вариант, если совместимость со старым оборудованием не требует иного.

Разрешения управляют печатью, извлечением содержимого, аннотациями, заполнением форм, сборкой страниц, изменением и шифрованием метаданных. Они являются политикой, которую соблюдают корректные просмотрщики, но не заменяют защиту исходных данных и контроль доступа к файлу. Конфиденциальный документ нужно хранить в защищённом хранилище даже при наличии пароля.

Decrypt снимает шифрование при наличии необходимых данных, а RemoveRestrictions удаляет ограничения, в том числе связанные с некоторыми подписанными файлами. Перед такой обработкой требуется правовое основание и понимание последствий: изменение подписанного PDF может разрушить доверие к подписи. В автоматическом процессе полезно сначала определить статус подписи и отклонить файл, если правила организации запрещают вмешательство.

Пароли нельзя зашивать в исходный код или выводить в лог. Их получают из защищённого хранилища секретов и держат в памяти минимально необходимое время. Ошибка операции должна сообщать имя входного файла и тип проблемы, но не раскрывать пароль или содержимое защищённого документа.

PDF/A, PDF/UA и электронные счета

DocumentSettings позволяет выбрать профиль PDF/A для долговременного хранения и PDF/UA для доступности. Эти режимы накладывают требования на шрифты, метаданные, цвет и структуру. Простого переключателя недостаточно для качественного результата: документ должен содержать корректные языковые настройки, логический порядок чтения, альтернативные описания изображений и семантические роли.

Доступный PDF строится как тегированный документ. Семантические элементы отмечают заголовки, абзацы, списки, таблицы, подписи и иллюстрации. Визуальное расположение само по себе не сообщает экранному диктору порядок чтения, поэтому две колонки без семантики могут звучать вперемешку. Проверку проводят не только валидатором, но и реальным средством чтения с экрана.

Таблица должна иметь распознаваемые заголовочные ячейки и логическую связь с данными. SemanticTable упрощает автоматическую разметку типовой структуры, однако сложные объединения строк и колонок требуют внимательной проверки. Декоративные изображения помечают соответствующим образом, а содержательным дают альтернативный текст, который объясняет смысл, а не повторяет слово картинка.

PDF/A-3 допускает вложения и используется в сценариях ZUGFeRD и Factur-X, где визуальный счёт объединён с XML по стандарту EN 16931. QuestPDF может создать PDF/A, добавить файл и расширить XMP-метаданные. Корректность электронного счёта зависит также от схемы и бизнес-правил XML, поэтому итог проверяют профильным валидатором, а не только открытием PDF.

Валидация соответствия

Для PDF/A и PDF/UA подходит veraPDF, а для ZUGFeRD и Factur-X — специализированная проверка структуры счёта. Валидатор включают в CI и сохраняют отчёт рядом с тестовым документом. Однократная ручная проверка не гарантирует, что следующий шаблон с новым шрифтом или изображением останется соответствующим.

Режим соответствия может сделать двоичный файл невоспроизводимым байт в байт из-за обязательных метаданных. Поэтому тесты не должны сравнивать только SHA-256 готового PDF. Надёжнее проверять число страниц, извлечённый текст, ключевые метаданные, результат валидатора и растровый снимок с допустимым порогом различий.

Производительность и размер файлов

Скорость зависит от числа элементов, объёма текста, изображений, сложных SVG и выбранных форматов. Таблица на десятки тысяч строк создаёт большое дерево и много операций измерения. Перед оптимизацией нужно измерить отдельные этапы: получение данных, композицию, отрисовку, операции с готовым PDF и передачу результата. Иначе медленный запрос к базе ошибочно принимают за проблему рендеринга.

QuestPDF.Settings.EnableCaching по умолчанию сохраняет результаты расчётов компоновки и обычно ускоряет генерацию ценой небольшой дополнительной памяти. Отключать кэш следует только после профилирования. Если процесс упирается в память, сначала проверяют повторное декодирование изображений, удержание больших byte[] и параллельную генерацию слишком большого числа документов.

ImageCompressionQuality и ImageRasterDpi влияют на размер и качество. Повышение DPI полезно для мелких деталей, но увеличивает пиксельные данные квадратично. Для отчёта, который читают на экране, не нужен тот же запас, что для печатного каталога. Можно подготовить два профиля: архивный с высоким качеством и облегчённый для электронной отправки.

CompressDocument запускает дополнительное сжатие и уменьшает файл с небольшим ростом времени. Эффект зависит от содержимого: текст и повторяемые команды сжимаются хорошо, уже оптимизированный JPEG — почти нет. Если документ неожиданно велик, следует посмотреть, не встроены ли одинаковые изображения несколько раз и не попал ли полный набор глифов большого шрифта.

Параллельная генерация

В веб-службе несколько документов могут формироваться одновременно. Ограничение параллелизма защищает память и процессор от всплеска, особенно при изображениях и тысячах страниц. Очередь с фиксированным числом работников обычно предсказуемее, чем Task.WhenAll для сотен запросов. Метрики должны включать длительность, размер результата, число страниц и пиковое потребление памяти.

Общие неизменяемые ресурсы можно кэшировать, но потоки и изменяемые объекты нельзя бездумно делить между задачами. Шрифт или подготовленное изображение, предназначенные для повторного использования, должны поддерживать такой режим. Если сторонняя библиотека графиков не потокобезопасна, её вызовы изолируют или синхронизируют отдельно от QuestPDF.

Поиск ошибок в Companion

Лупа в предпросмотре QuestPDF Companion

Лупа включается клавишей 1 и увеличивает область под курсором без изменения общего масштаба. Она помогает проверить линии таблицы, выравнивание цифр, границы и качество небольших иконок. Для оценки реального качества печати всё равно нужен экспорт при целевом DPI и пробный лист: экранное сглаживание может скрыть слишком тонкую линию.

Выбор элемента и просмотр его параметров в QuestPDF Companion

Элемент выбирают в дереве или двойным щелчком на странице. Остальное содержимое затемняется, выбранная область получает рамку, а верхняя панель показывает страницу, координаты и размер. Если элемент повторяется на нескольких страницах, стрелки вверх и вниз переходят между экземплярами; Escape снимает выделение.

Поиск текста в структуре документа QuestPDF Companion

Ctrl+F открывает поиск по тексту. Результат одновременно подсвечивается в дереве и предпросмотре, что полезно в длинном отчёте с повторяющимися позициями. Поиск работает по сформированному содержимому, поэтому отсутствие строки может означать не проблему интерфейса, а условие ShowIf, обрезку данных или неверную культуру форматирования.

Исключения выполнения

Отображение исключения и стека вызовов в QuestPDF Companion

DocumentComposeException возникает во время построения дерева: причиной бывают неверные данные, ошибка в цикле, обращение к null или неправильный вызов API. Стек обычно ведёт в пользовательский код. Исправление начинается с проверки модели и выделения минимального компонента, который воспроизводит сбой.

DocumentDrawingException появляется на этапе превращения дерева в команды рисования. Он может указывать на ошибку динамического компонента, повреждённое изображение, проблему шрифта или внутренний дефект. Если собственный код и ресурсы исключены, полезно подготовить минимальный пример, версию среды, входной файл и полный стек для сообщения разработчикам.

LayoutException означает, что корректно построенное дерево содержит несовместимые ограничения. Типичный случай — внутренний элемент шириной 150 пунктов помещён в контейнер шириной 100 или ShowEntire окружает блок выше страницы. Увеличение листа может скрыть симптом, но правильнее найти узел, который требует невозможный размер.

Диагностика переполнения

Цветовая диагностика ошибки компоновки в QuestPDF Companion

В режиме ошибки дерево помечает элементы цветными точками. Отдельно выделяется предполагаемая первопричина, узлы со статусом Wrap, частично и полностью отрисованные, пустые и не попавшие на страницу. Выбор узла показывает доступное пространство и причину переноса. Начинать следует с ближайшего красного ограничения, а не с верхнего Page, который только передал размер детям.

QuestPDF.Settings.EnableDebugging добавляет подробный контекст и DebugPointer. Указатель с понятным именем — Таблица позиций, Блок подписи, Карточка клиента — быстрее приводит к нужному месту, чем длинная цепочка без меток. Подробная диагностика увеличивает объём служебных данных, поэтому её включают в разработке и при расследовании, а не обязательно в каждом производственном запросе.

Настройки Companion

Настройки темы, редактора кода и порта в QuestPDF Companion

В настройках выбираются системная, светлая или тёмная тема, редактор для перехода к коду, видимость дерева и порт соединения. Поддерживаются Visual Studio, VS Code и Rider. Выбранный редактор должен быть установлен и доступен пользователю, от имени которого запущено окно; в удалённой сессии переход может не сработать из-за другого профиля или пути.

Индикатор порта показывает, слушает ли Companion соединения. Если код вызывает ShowInCompanion, а окно остаётся пустым, проверяют совпадение порта, локальный брандмауэр, второй запущенный экземпляр и архитектуру процесса. После смены порта требуется изменить вызов или вернуть значение по умолчанию в обоих местах.

Тема влияет только на окно, а не на итоговый PDF. Для проверки тёмного фирменного макета нужно смотреть сам документ, не ориентируясь на фон приложения. Серый фон вокруг белой страницы помогает заметить границы листа, но не является частью файла.

Развёртывание на Windows, Linux и macOS

Пакет содержит управляемую часть и нативные зависимости для поддерживаемых сред. При публикации .NET выбирает нужные runtime assets. Для self-contained и контейнерных сборок необходимо сохранить нативные файлы и проверить, что оптимизатор или собственный скрипт копирования не удалил их как неиспользуемые. Ошибка несовпадения версий нативной и управляемой частей обычно означает смешанные файлы после неполного обновления.

На Linux важны архитектура, версия glibc и наличие необходимых системных возможностей. Контейнер лучше строить из закреплённого базового образа и проверять генерацию в CI на том же образе. Рабочий файл, созданный на машине разработчика, не доказывает, что в минимальном контейнере доступны шрифты и права на временный каталог.

Поддержка Native AOT и trimming позволяет собирать приложения с быстрым запуском и меньшим набором динамических зависимостей. Однако весь проект должен быть совместим с AOT: сторонняя библиотека диаграмм, сериализатор или механизм загрузки шрифтов через отражение могут стать ограничением. Проверять нужно итоговый опубликованный бинарник, а не только обычный запуск под JIT.

Мобильные Android и iOS и WebAssembly не относятся к поддерживаемым целевым платформам движка. Для мобильного клиента практичнее генерировать документ на сервере или использовать другой компонент, рассчитанный на эту среду. Попытка перенести нативные библиотеки вручную создаёт трудно поддерживаемую сборку и не гарантирует корректную работу.

Шрифты и файловая система в производстве

Пути к ресурсам строят относительно каталога приложения или используют встроенные ресурсы. Абсолютный путь с компьютера разработчика ломается после публикации. На Linux регистр символов учитывается, поэтому Logo.svg и logo.svg — разные файлы. Проверка старта может заранее убедиться, что все обязательные шрифты и шаблоны доступны.

В бессерверной функции запись разрешена только в определённый временный каталог, а время жизни процесса ограничено. Генерация в MemoryStream уменьшает зависимость от файловой системы, но большие документы всё равно требуют контроля памяти. Для очень крупных отчётов лучше асинхронное задание с объектным хранилищем, а не синхронный HTTP-запрос.

Тестирование шаблонов и контроль качества

Юнит-тест проверяет расчёты и компоненты на конкретной модели, но не заменяет визуальную проверку. Полезный набор включает короткий и длинный текст, пустые коллекции, максимальные суммы, отрицательные значения, разные языки, отсутствие необязательного изображения и таблицу на много страниц. Каждый случай должен либо успешно сформировать документ, либо дать ожидаемую валидационную ошибку до генерации.

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

Точный SHA-256 PDF часто непригоден как единственный критерий: метаданные, идентификаторы и режимы соответствия могут менять байты без визуальной разницы. Хэш полезен для контроля неизменности уже выпущенного файла, но регрессионный тест шаблона должен анализировать смысловые свойства. Для критичных форм применяют и ручное утверждение новой эталонной версии.

Нагрузочный тест моделирует реальный размер данных и параллелизм. Один счёт на десять строк не выявит проблему каталога на пять тысяч страниц. Метрики сохраняют по сценариям, чтобы отличать рост времени из-за нового изображения от общего изменения инфраструктуры.

Практический сценарий: счёт или акт

Модель счёта обычно содержит продавца, покупателя, номер, даты, валюту, позиции, налоги, скидки и итог. Шапка строится Row: реквизиты слева, номер и даты справа. Позиции помещаются в Table с повторяемым заголовком. Итоги выводятся отдельной правой колонкой, а комментарий и условия оплаты — после таблицы.

Денежные значения форматируют одной культурой и округляют бизнес-логикой до передачи в шаблон. Ширина колонок проверяется на отрицательных значениях и больших суммах. Для количества и цены лучше выравнивание по правому краю, для наименования — перенос по словам. Если валюта различается по строкам, символ в каждой ячейке может ухудшить сравнение; отдельная колонка или подпись валюты в заголовке обычно яснее.

Логотип загружают один раз, а отсутствие файла обрабатывают до композиции. Адреса могут занимать разную высоту, поэтому их не помещают в жёсткий контейнер. Для электронной отправки добавляют метаданные и при необходимости XML-вложение; для печати проверяют поля, минимальную толщину линий и расположение подписи.

Если счёт должен соответствовать ZUGFeRD или Factur-X, визуальная и XML-части формируются из одной модели. После генерации XML валидируется отдельно, затем прикрепляется к PDF/A-3 с правильным отношением и XMP-метаданными. Несовпадение итогов между видимой таблицей и XML недопустимо, поэтому оба представления получают уже рассчитанные значения из общего слоя.

Практический сценарий: аналитический отчёт

Отчёт начинается с титульной страницы и оглавления, затем содержит секции с таблицами, диаграммами и выводами. Section связывает пункты оглавления с содержимым. На первой странице раздела можно вывести крупный заголовок, а на последующих — компактную шапку с названием и периодом. Нумерация страниц остаётся сквозной.

Диаграммы генерируются в SVG и переиспользуются как подготовленные объекты. Под каждой диаграммой следует текстовое объяснение и таблица исходных значений, особенно если требуется доступность. Цвета выбирают с достаточным контрастом и не используют оттенок как единственный признак серии: добавляют подписи, штриховку или разные маркеры.

Большие таблицы разбивают логически, а не только по страницам. Группировка по подразделению с повторяемым заголовком облегчает чтение. Промежуточный итог можно выводить после каждой группы, а общий — один раз в конце. Для пустой группы шаблон либо пропускает раздел, либо явно пишет Нет данных, но не оставляет пустую рамку.

Приложения из готовых PDF объединяются через DocumentOperation. Перед добавлением можно выбрать нужные страницы и наложить единый номер проекта. Если приложение уже подписано, любое изменение требует отдельного решения: иногда правильнее вложить файл без изменения, а не объединять страницы.

Практический сценарий: сертификат и бланк

Сертификат удобно строить на альбомной странице. Background содержит векторную рамку и фирменный узор, Content — имя, название курса и дату, Foreground — защитный элемент или номер. Текст имени помещают в контейнер с MaxWidth и Shrink, чтобы длинная строка уменьшилась в разумных пределах, а не вышла за рамку.

Если дизайн подготовлен в графическом редакторе, фон экспортируют в чистый SVG без внешних шрифтов и ссылок. Поля для данных оставляют прозрачными. В QuestPDF задают точные отступы и проверяют координаты измерителем Companion. Печатная проба обязательна, потому что принтер может добавить собственные непечатаемые поля.

Серийный QR-код связывают с записью в системе и проверяют сканированием. Код не должен перекрываться водяным знаком или полупрозрачной текстурой. Номер сертификата выводят и обычным текстом, чтобы документ оставался идентифицируемым при повреждении кода.

Локализация, RTL и смешанные алфавиты

Многоязычный шаблон нельзя сводить к подстановке переведённых строк. Длина заголовков меняется, даты и суммы форматируются по культуре, а выбранный шрифт должен содержать все нужные символы. Для каждого языка полезно хранить не только словарь подписей, но и набор контрольных данных: длинное имя организации, адрес в несколько строк, отрицательную сумму, дробную единицу измерения и текст с редкими буквами. Такой набор быстро обнаруживает обрезание и ошибочный перенос до передачи документа пользователю.

Основной шрифт задают вместе с последовательностью запасных семейств. Font fallback применяется, когда в первом семействе нет конкретного глифа, поэтому латиница, кириллица, арабский текст, иероглифы и эмодзи могут потребовать разных файлов. Регистрация шрифтов должна происходить до генерации, а сами файлы включают в публикацию приложения. Надежда на случайно установленный системный шрифт делает результат зависимым от компьютера или контейнера.

Направление содержимого настраивается для всего документа или отдельного контейнера. Режим справа налево влияет не только на выравнивание букв: меняется порядок элементов Row и Table, исходная сторона выравнивания и логика переноса. В арабском или иврите это удобно для шапки, адреса и описаний, но артикулы, номера счетов, адреса электронной почты и фрагменты латиницы часто требуют локального возврата к направлению слева направо.

container.ContentFromRightToLeft().Column(column =>
{
    column.Item().Text(model.CustomerName);
    column.Item().ContentFromLeftToRight().Text(model.InvoiceNumber);
});

Числа и даты форматируют до передачи в Text либо с явно выбранной CultureInfo. Иначе сервер с другой региональной настройкой может заменить десятичную запятую точкой, изменить порядок дня и месяца или вывести название месяца на неожиданном языке. Для финансового документа культуру, валюту и правило округления фиксируют в модели подготовки данных; макет получает уже согласованную строку и не дублирует расчёты.

При смешанном тексте проверяют не только внешний вид, но и копирование из готового PDF. Лигатуры, комбинируемые знаки и двунаправленные последовательности способны выглядеть правильно и извлекаться в другом порядке. Контрольный тест копирует номер, имя и одну строку описания из нескольких языковых вариантов. Для доступного документа дополнительно проверяют чтение экранным диктором и логический порядок элементов, а не только геометрическое расположение на странице.

Включённая проверка глифов помогает обнаружить отсутствующие символы, но в потоке с непредсказуемыми пользовательскими данными она может выявить ошибку только на редкой записи. Поэтому набор поддерживаемых письменностей документируют, входные строки валидируют, а неизвестные символы регистрируют вместе с названием выбранного шрифта. Исправление затем сводится к добавлению подходящего fallback, а не к бессистемной замене шрифта во всём шаблоне.

Ограничения, которые важно учитывать

QuestPDF ориентирован на разработчика. В нём нет перетаскивания блоков мышью и формы, где менеджер без кода меняет шаблон. Companion показывает и измеряет результат, но не записывает изменения обратно в C#. Для организации, где макеты постоянно редактируют дизайнеры без участия программиста, потребуется собственный слой шаблонов или другое решение.

Произвольный HTML и CSS не являются входным форматом основного API. Сторонние расширения с похожими названиями существуют, но имеют отдельные ограничения и жизненный цикл. При выборе следует не приписывать их возможности самой библиотеке. Если бизнес уже поддерживает HTML-шаблоны, сравнение с Chromium-конвертером должно учитывать стоимость переноса.

Библиотека умеет выполнять ряд операций над существующим PDF, однако это не интерактивный редактор страниц. Пользователь не открывает файл, не двигает текст мышью и не исправляет абзац как в офисной программе. Операции задаются кодом: выбрать страницы, объединить, наложить слой, зашифровать или добавить вложение.

Лицензирование зависит от типа пользователя и выручки организации. Бесплатная возможность не означает одинаковые условия для любой компании. До внедрения необходимо зафиксировать основание выбранного типа лицензии и пересматривать его при изменении статуса организации. Техническая команда не должна принимать юридическое решение по одному комментарию в коде.

Нативные зависимости увеличивают размер пакета по сравнению с чисто управляемой библиотекой. Это плата за собственный движок рендеринга и поддержку разных платформ. В маленькой утилите размер публикации может быть важнее удобства API; в серверном генераторе обычно важнее предсказуемая компоновка и скорость.

Сравнение QuestPDF с аналогами

ПрограммаЛучше подходит дляГлавное ограничение
QuestPDFКодовой верстки отчётов, счетов и доступных PDF в .NETШаблон нужно писать на C#
iText for .NETГлубокой работы со структурой PDF, подписями, формами и стандартамиAGPL или коммерческая лицензия
PDFsharp и MigraDocБазовой генерации и привычной объектной модели документов в .NETМеньше современных инструментов предпросмотра и компоновки
IronPDFHTML/CSS в PDF и проектов, где шаблон уже существует как веб-страницаКоммерческая лицензия и браузерный движок
Aspose.PDF for .NETШирокого набора конвертаций и обработки существующих PDFКоммерческая лицензия
Syncfusion PDF FrameworkКорпоративной экосистемы .NET с созданием, просмотром и конвертациямиУсловия лицензии зависят от сценария использования

QuestPDF разумно выбирать, когда документ является частью .NET-приложения, данные типизированы, а команде нужна предсказуемая постраничная компоновка и быстрый визуальный цикл через Companion. iText сильнее там, где центральная задача — сложная структура существующего PDF, цифровые подписи и специализированные стандарты. PDFsharp с MigraDoc подходит для более простых шаблонов и проектов, которым важна знакомая модель.

IronPDF удобнее при наличии готового HTML/CSS и необходимости воспроизвести веб-страницу, но приносит особенности браузерного рендеринга. Aspose.PDF и Syncfusion предлагают широкий набор корпоративных функций и конвертаций; их выбирают, когда один поставщик должен закрыть несколько форматов и просмотр. Решение следует сравнивать на собственном документе: одинаковое слово таблица не означает одинаковое поведение на сотнях страниц.

Частые проблемы и способы исправления

Документ не помещается на страницу

Сначала включают подробную диагностику и открывают ошибку в Companion. Затем находят ближайший узел со статусом Wrap и проверяют Width, Height, ShowEntire, MinSize, фиксированные колонки и суммарную высоту Header с Footer. Удалять ограничения по одному эффективнее, чем увеличивать страницу: так становится понятно, какое именно требование конфликтует.

Вместо русских букв появляются квадраты

Используемый шрифт не содержит глифы или не попал в публикацию. Нужно зарегистрировать гарнитуру с кириллицей, проверить точное имя файла и включить CheckIfAllTextGlyphsAreAvailable в тестовой среде. В контейнере следует отключить зависимость от случайных системных шрифтов и поставлять собственный набор.

Companion не видит документ

Проверяют, что запущено окно Companion, вызван ShowInCompanion, версии интеграции совместимы и порт совпадает. Затем исключают занятый порт и локальный брандмауэр. При dotnet watch процесс может перезапуститься и потерять соединение; после восстановления он должен снова отправить документ.

Файл слишком большой

Определяют вклад изображений, шрифтов и повторяемых ресурсов. Фотографии уменьшают до разумного разрешения, выбирают качество JPEG, SVG очищают от лишних данных, одинаковые ресурсы переиспользуют, включают CompressDocument. Нельзя оценивать только исходный размер картинки: после декодирования огромный PNG может занять сотни мегабайт памяти, даже если хорошо сжат на диске.

Генерация занимает слишком много памяти

Сокращают параллелизм, исключают лишние byte[] и MemoryStream, переиспользуют изображения, рассматривают Lazy для тяжёлых участков и профилируют сторонние графические библиотеки. Если отчёт содержит миллионы элементов, его лучше разделить на логические тома или формировать пакет асинхронно, чем пытаться удержать всё в одном запросе.

SVG отображается не так, как в браузере

Упрощают файл, убирают внешние ресурсы, проверяют встроенные изображения, фильтры, прозрачность и шрифты. Экспорт из редактора в базовый SVG часто надёжнее сохранения с фирменными расширениями. Минимальный проблемный фрагмент помогает понять, какой элемент не поддерживается, и заменить его растром или более простым путём.

Текст копируется неверно

Проверяют шрифт, OpenType-функции и работу лигатур. Для сложных глифов важно наличие корректного ActualText, а стандартные лигатуры включают только при необходимости. После изменения документ тестируют в нескольких просмотрщиках: визуально одинаковый PDF может по-разному вести себя при выделении и чтении экранным диктором.

Операция с PDF завершается ошибкой

Проверяют существование файла, пароль, диапазон страниц и целевой путь. Сообщение должно включать имя проблемного файла. Для повреждённого PDF полезно отдельно открыть его валидатором или qpdf-инструментом. Не следует продолжать пакетную обработку молча: итоговый объединённый файл без одной части может выглядеть правдоподобно и быть фактически неполным.

Организация рабочего процесса в команде

Шаблоны рассматривают как обычный код: изменения проходят review, а запрос на слияние содержит тестовый PDF или снимки изменённых страниц. Рецензент проверяет и оформление, и условия, при которых блок появляется. Название метода ComposeTotals само по себе не гарантирует правильного округления, поэтому рядом должен быть тест бизнес-логики.

Общие токены дизайна — цвета, размеры шрифтов, отступы, толщина линий — хранятся в одном классе. Это позволяет обновить фирменный стиль без поиска чисел по десяткам компонентов. Однако токен не должен скрывать единицы измерения: имя InvoiceMargin и значение с Unit.Centimetre яснее абстрактного числа 20.

Для каждого шаблона полезен каталог эталонных моделей: минимальная, обычная, максимальная, локализованная и ошибочная. Companion открывает их по отдельной команде или тесту. Такой стенд ускоряет изменение, потому что разработчик сразу видит влияние на все крайние случаи, а не только на один красивый счёт.

Перед выпуском фиксируют версию зависимостей и базовый образ. Автоматическое обновление без регрессионных тестов может изменить нативный движок или поведение SVG. В то же время бессрочная заморозка лишает исправлений. Практичный компромисс — плановое обновление с набором проверок и возможностью отката.

Когда QuestPDF даёт наибольшую пользу

Наиболее естественный сценарий — сервер или корпоративное приложение на .NET, где PDF формируется из объектов предметной области. Типизация, условия и циклы уже доступны на C#, а документ можно разделить на компоненты и тестировать. В таком проекте отсутствие визуального конструктора компенсируется контролем исходного кода и быстрым предпросмотром.

Библиотека особенно удобна для многостраничных таблиц, повторяемых шапок, колонтитулов, счетов, отчётов и архивных форм, где браузерная модель страниц создаёт непредсказуемые разрывы. Явные элементы EnsureSpace, ShowEntire, Repeat и Section позволяют выразить требования документа непосредственно, а не обходить их набором CSS-правил печати.

Если задача состоит в ручном исправлении полученного PDF, заполнении формы оператором или визуальном редактировании готовой страницы, нужен другой класс инструмента. QuestPDF автоматизирует создание и кодовые операции; его сильная сторона — повторяемый результат из данных, а не интерактивная правка пользователем.

Перед окончательным выбором стоит реализовать один реальный шаблон с самым длинным текстом, сложной таблицей, фирменными шрифтами и целевым стандартом. Такой прототип покажет не только красоту API, но и размер публикации, скорость, качество SVG, требования лицензии и удобство поддержки командой. После этого решение основывается на измеренном рабочем процессе, а не на коротком примере Hello PDF.