Technical Article

Печать загруженного PDF в Delphi с помощью HotPDF и TPrinter

HotPDF выводит загруженный PDF-файл на физический принтер, в любой контекст устройства Windows или в буфер обмена с помощью трех вызовов: PrintLoadedDocument управляет VCL-компонентом TPrinter, RenderLoadedPageToDC отрисовывает страницу непосредственно в любой HDC, а RenderLoadedPageToClipboard копирует страницу как растровое изображение или векторный метафайл. HotPDF — это нативный VCL-компонент PDF для Delphi и C++Builder, в котором этот первоклассный путь печати появился в версии v2.331.0 и был доработан в версии v2.348.0, так что вам больше не придется вручную писать цикл TPrinter вокруг рендерера растровых изображений. Все три вызова переносят векторное содержимое страницы непосредственно на целевую поверхность, благодаря чему распечатка остается настолько четкой, насколько позволяет разрешение принтера

Зачем отрисовывать страницу PDF напрямую в принтер вместо растра?

Страница PDF — это программа, состоящая из операторов векторного рисования (контуров, текста и изображений, выполняемых в соответствии с графической моделью ISO 32000-1), а не сетка пикселей. Очевидный способ напечатать ее — растрировать страницу в изображение и перенести его на холст принтера. Это работает, но при этом теряется главное преимущество принтера. Современный лазерный принтер обеспечивает разрешение 600 или 1200 DPI. Растровое изображение, созданное с разрешением 150 или 300 DPI и затем растянутое под размер листа, получается размытым, с неровными краями текста и видимыми артефактами интерполяции. Метод RenderLoadedPageToDC идет лучшим путем: он воспроизводит поток команд содержимого страницы в виде вызовов GDI непосредственно в целевом контексте устройства, поэтому векторный текст и графика доходят до принтера в виде векторов и растрируются один раз самим принтером с его физическим разрешением

Как напечатать загруженный PDF на физическом принтере в Delphi?

HotPDF печатает загруженный документ с помощью метода PrintLoadedDocument(Printer, Options), который принимает настроенный VCL TPrinter и запись THPDFPrintOptions, отправляет выбранные страницы на печать и возвращает количество отправленных страниц. Сначала задайте Printer.PrinterIndex для выбора устройства, заполните запись параметров (функция класса THPDFPrintOptions.Default возвращает разумные настройки по умолчанию) и выполните вызов. Библиотека открывает задание с помощью Printer.BeginDoc, обходит диапазон страниц, вызывая Printer.NewPage между ними, и завершает его вызовом Printer.EndDoc, полностью избавляя вас от ручного управления жизненным циклом задания печати

uses Printers;

var
  Pdf: THotPDF;
  Opts: THPDFPrintOptions;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('invoice.pdf') > 0 then
    begin
      Printer.PrinterIndex := -1;         // -1 selects the default printer
      Opts := THPDFPrintOptions.Default;  // all pages, 300 DPI, FitPage, colour
      Opts.PageRange := '1,3-5';          // one-based; empty prints every page
      Opts.Copies := 2;
      Pdf.PrintLoadedDocument(Printer, Opts);
    end;
  finally
    Pdf.Free;
  end;
end;

Строка PageRange использует индексацию с 1 и читается так же, как в стандартном диалоге печати: значение '1,3-5' напечатает первую страницу, а также страницы с 3 по 5, а пустая строка напечатает весь документ. Параметр Copies передается напрямую в Printer.Copies. Тем не менее, стоит учитывать особенности параметра Collate: поскольку в разных версиях Delphi библиотека VCL предоставляет доступ к сортировке копий непоследовательно, HotPDF задает количество копий и оставляет сортировку на усмотрение драйвера принтера по умолчанию. Если вам гарантированно необходим определенный порядок сортировки копий, настройте его в драйвере через TPrinterSetupDialog, а не полагайтесь только на этот флаг

Как параметры DPI и FitPage определяют масштаб печати?

Параметры FitPage и DPI взаимодействуют друг с другом, и этот принцип важно понимать перед запуском кода в рабочую среду. При значении FitPage в положении True (по умолчанию) HotPDF считывает размеры MediaBox каждой страницы, масштабирует их под физические размеры Printer.PageWidth и Printer.PageHeight с сохранением пропорций и выполняет отрисовку в масштабе, заполняющем лист бумаги. В этом режиме значение Options.DPI переопределяется масштабом масштабирования, так как выходной контент должен строго соответствовать физическим габаритам страницы принтера. Если же FitPage равен False, HotPDF отрисовывает каждую страницу в масштабе 1:1 с точным соблюдением Options.DPI, сопоставляя 72 единицы PDF с пикселями устройства DPI. Это необходимо, когда размер исходной страницы и формат бумаги в лотке изначально совпадают и вам важно точное разрешение вывода. Выбирайте FitPage при различиях в размерах исходных страниц и бумаги, и 1:1, когда они совпадают и вам нужен предсказуемый масштаб

Две опции, добавленные в версии v2.348.0, решают проблемы с распечатками, которые выглядят почти правильно. У каждого физического принтера есть аппаратные поля — непечатная область по краям листа, куда физические механизмы не могут дотянуться, и по умолчанию начало координат страницы ложится внутри этого поля, из-за чего страницы с запечаткой в край теряют полосы с двух сторон. Установите IgnoreHardMargin в значение True, и HotPDF запросит у принтера параметры PHYSICALOFFSETX and PHYSICALOFFSETY, а затем сдвинет начало координат устройства для компенсации этого смещения, сопоставляя начало координат страницы с физическим углом листа. Вторая опция, AutoRotate, включена по умолчанию: она считывает MediaBox каждой страницы, и если страница шире своей высоты, переключает принтер в альбомную ориентацию, предотвращая обрезку широких страниц. Параметр AutoRotate отключается при явном задании Orientation, так что осознанный выбор ориентации всегда имеет приоритет

Как отрисовать страницу PDF в произвольный контекст устройства?

Метод RenderLoadedPageToDC(PageIndex, DC, DPI, W, H) является базовой функцией, лежащей в основе цикла печати, и он полезен сам по себе. PrintLoadedDocument вызывает его для каждой страницы на холсте принтера, но целью вызова может быть любой Windows-совместимый HDC: закадровый контекст устройства в памяти, холст компонента в собственном просмотрщике или поверхность предварительного просмотра печати. Параметр PageIndex отсчитывается от нуля, W и H задают размеры целевой поверхности в единицах устройства, а рендерер обходит поток /Contents страницы и генерирует вызовы GDI в ваш DC. Важная особенность: метод не выполняет заливку фонового цвета. Фоном владеет вызывающая сторона, поэтому если вам нужен белый лист бумаги под страницей, вам необходимо самостоятельно залить DC перед вызовом рендеринга, что позволяет при необходимости наложить страницу PDF поверх уже существующего изображения на холсте

// Draw page 1 into an off-screen 24-bit surface at 150 DPI
var
  Surface: TBitmap;
begin
  Surface := TBitmap.Create;
  try
    Surface.PixelFormat := pf24bit;
    Surface.SetSize(1275, 1650);           // 8.5 x 11 in at 150 DPI
    Surface.Canvas.Brush.Color := clWhite;
    Surface.Canvas.FillRect(Surface.Canvas.ClipRect);  // caller paints the background
    Pdf.RenderLoadedPageToDC(0, Surface.Canvas.Handle, 150, Surface.Width, Surface.Height);
    // Surface now holds the composited page, ready to blit or save
  finally
    Surface.Free;
  end;
end;

Если вам нужен готовый объект TBitmap (например, для создания эскиза, экспорта в графический файл или кэширования для предварительного просмотра), используйте метод RenderLoadedPageToBitmap, описанный в статье о рендеринге страниц PDF в TBitmap. Разница заключается в том, кто управляет памятью поверхности: метод рендеринга в bitmap сам выделяет и возвращает буфер пикселей, в то время как RenderLoadedPageToDC рисует в уже предоставленном вами контексте устройства (что как раз характерно для холста принтера или обработчика рисования). Оба метода используют один и тот же интерпретатор контента и встроенный движок отрисовки шрифтов, поэтому очертания текста в обоих случаях выводятся абсолютно одинаково

Как скопировать страницу PDF в Word или графический редактор?

Метод RenderLoadedPageToClipboard(PageIndex, DPI, AsMetafile) помещает одну страницу в буфер обмена Windows, делая ее доступной для вставки в Word, PowerPoint, письмо или графический редактор. Флаг AsMetafile задает формат буфера, и этот выбор крайне важен. Передайте False, и HotPDF запишет 24-разрядное растровое изображение устройства (CF_BITMAP), отрисованное с вашим разрешением DPI — фиксированную сетку пикселей, которая вставится в любую программу, но потеряет четкость при масштабировании на стороне получателя. Передайте True, и метод поместит расширенный метафайл (CF_ENHMETAFILE), в котором текст и векторная графика страницы будут представлены командами рисования GDI, поэтому результат вставки останется абсолютно четким при любом масштабировании и подстроится под любые размеры, заданные в целевом документе

// Copy page 2 as a scalable vector metafile for pasting into Word
Pdf.RenderLoadedPageToClipboard(1, 300, True);

// Copy the same page as a fixed 300-DPI bitmap instead
Pdf.RenderLoadedPageToClipboard(1, 300, False);

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

Где заканчивается поддержка печати

Все описанные на этой странице возможности поддерживаются исключительно в среде Windows. Методы PrintLoadedDocument, RenderLoadedPageToDC и RenderLoadedPageToClipboard построены на базе TPrinter, модели контекста устройства GDI, буфера обмена Windows и расширенных метафайлов — все это конструкции Win32 и Win64. На других целевых платформах остальные функции HotPDF по-прежнему позволяют создавать, редактировать и читать PDF, но этот слой печати и работы с буфером обмена на основе GDI недоступен. Рендерер также ориентирован на стандартный набор элементов (текст, пути рисования и изображения), поэтому дизайнерские макеты с градиентами и печать с управлением цветом по ICC выведутся в режиме предварительного просмотра, а не точной цветопередачи (аналогично ограничениям растрового рендерера)

Для пакетной печати множества файлов загружайте и отправляйте на печать каждый документ по очереди, контролируя объем используемой памяти: PrintLoadedDocument отрисовывает по одной странице за раз, но сама объектная модель загруженного документа находится в памяти на протяжении всей работы. Если вы управляете очередью печати сотен крупных файлов, с процессом печати отлично сочетается потоковое Direct File API для работы с большими PDF — сначала разделите, объедините или выберите нужные страницы на диске, а затем загружайте для печати только результирующий небольшой файл. Именно в таком виде методы PrintLoadedDocument, RenderLoadedPageToDC и RenderLoadedPageToClipboard поставляются в составе компонента HotPDF для Delphi и C++Builder — нативной библиотеки VCL без внешних зависимостей от DLL, которая решает задачи создания, редактирования, извлечения текста, рендеринга и теперь — прямой печати в TPrinter, в любой контекст устройства или в буфер обмена