Технічна стаття

Друк завантаженого 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-файли з чорним прямокутником поверх імені чи номера, але читачі могли легко виділити текст під ним, скопіювати його та вставити нібито видалений рядок у звичайний блокнот. Цей прямокутник був просто малюнком. Текст залишався на місці, перебуваючи в потоці вмісту на один шар нижче за фарбу, повністю неушкодженим та доступним для виділення. Бібліотека losLab PDF Library — рушій PDF для Delphi та C++Builder, доступний через клас TPDFlib, — розглядає цю різницю як головну мету редагування, а не як другорядну деталь.

Чому варто малювати сторінку 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,3-5' друкує сторінку 1 та сторінки з 3 по 5, а порожній рядок друкує весь документ. Параметр Copies передається безпосередньо у Printer.Copies. Проте варто враховувати особливості сортування (Collate): оскільки бібліотека VCL підтримує сортування копій несумісно у різних версіях Delphi, 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 та PHYSICALOFFSETY і зсуває початок координат пристрою для компенсації цього зміщення, щоб початок сторінки збігався з кутом паперу. Другий параметр, AutoRotate, увімкнено за замовчуванням: він зчитує MediaBox кожної сторінки і, якщо сторінка ширша, ніж вища, перемикає принтер в альбомну орієнтацію, щоб широка сторінка не обрізалася. AutoRotate вимикається, якщо ви явно задаєте орієнтацію Orientation, тому явний вибір орієнтації завжди має пріоритет.

Як відрендерити сторінку PDF у будь-який контекст пристрою?

Метод RenderLoadedPageToDC(PageIndex, DC, DPI, W, H) є базовою функцією, що лежить в основі циклу друку, і є корисною сама по собі. PrintLoadedDocument викликає її для кожної сторінки на полотно принтера, але цільовим об'єктом може бути будь-який Windows-контекст HDC: контекст пам'яті, полотно компонента у кастомному переглядачі або область попереднього перегляду друку. Параметр PageIndex починається з 0, W та H — це одиниці пристрою цільової поверхні. Рендерер проходить по об'єкту сторінки /Contents і надсилає виклики GDI у ваш DC. Слід знати про одну важливу особливість: метод не малює тло. Поверхня належить викликачу, тому якщо вам потрібен білий папір позаду сторінки, ви повинні самостійно заповнити DC перед рендерингом, що дозволяє накладати сторінку поверх існуючого полотна за потреби.

// 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 у bitmap. Різниця полягає в тому, хто володіє поверхнею: виклик 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 друкуватимуться як точне прев'ю, а не як спектрофотометрична кольоропроба (це ж обмеження стосується і рендерера bitmap).

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