HotPDF рендерить завантажену сторінку PDF у Delphi TBitmap за допомогою одного виклику: RenderLoadedPageToBitmap(PageIndex, DPI). Функція інтерпретує потік вмісту сторінки та повертає 24-бітний RGB-bitmap, що належить викликачу, у вибраній вами роздільній здатності, що є саме тим, що потрібно для стрічки мініатюр, попереднього перегляду друку або конвеєра експорту PDF у зображення. У цій статті розглядається сам API, а потім частина, яка відрізняє корисний рендерер від іграшкового: малювання тексту безпосередньо з вбудованих програми шрифтів, а не зі схожих системних шрифтів
Сторінка PDF — це не малюнок. Це програма: потік операторів, які будують шляхи, вибирають шрифти, встановлюють кольори та розташовують гліфи, що виконуються відповідно до графічної моделі, визначеної стандартом ISO 32000-1 §8. Ніщо у файлі не говорить про те, як виглядає конкретний піксель. Щоб створити bitmap, ви повинні виконати цю програму — підтримувати поточну матрицю перетворення, стек графічного стану для q/Q, шлях відсікання, простори кольорів заливки та обводки — і раструвати результат. Ось чому «просто показати сторінку 3 як зображення» — це інтерпретатор потоку вмісту, а не конвертація формату файлу
Чому рендеринг сторінки PDF складніший, ніж малювання зображення?
Рендерер HotPDF, представлений у версії v2.253.0, побудований у вигляді шести окремих модулів, які відображають цю модель: ядро афінної матриці для алгебри перетворення PDF [a b c d e f], стек графічного стану, розпізнавач колірного простору (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), конструктор шляхів, який пов'язує оператори шляхів PDF з GDI, шар метрик шрифтів, який зчитує масиви /Widths для правильних зсувів, та інтерпретатор, який розподіляє оператори та керує іншими п'ятьма модулями. Об'єкти зображень XObject проходять через той самий стек декодування, який бібліотека використовує для вилучення, тому кожен фільтр зображень, який HotPDF може декодувати для вилучення — включаючи зображення JPEG 2000, стиснуті за допомогою JPXDecode — також з'являється у візуалізованому виводі
Рендеринг завантаженої сторінки в TBitmap
RenderLoadedPageToBitmap приймає індекс сторінки, що починається з нуля, та значення DPI, де 72 DPI відображає одну одиницю простору користувача PDF в один піксель. Він повертає nil у разі помилки (індекс поза діапазоном, відсутність ресурсів) замість виклику винятку, тому переглядач може пропустити пошкоджену сторінку і продовжувати роботу. Викликач володіє повернутим bitmap і повинен звільнити його
var
Pdf: THotPDF;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('report.pdf') > 0 then
begin
Bmp := Pdf.RenderLoadedPageToBitmap(0, 144); // page 1 at 144 DPI
if Bmp <> nil then
try
Image1.Picture.Assign(Bmp);
finally
Bmp.Free; // caller owns the bitmap
end;
end;
finally
Pdf.Free;
end;
end;
Аргумент DPI виконує роботу з масштабування для кожного поширеного сценарію. Стрічка мініатюр рендериться при 36 або 48 DPI й отримує малі, швидкі bitmap; екранний попередній перегляд при 96 або 144 DPI відповідає типовій щільності дисплея; шлях експорту при 300 DPI створює зображення друкарської якості. Обертання сторінки із запису /Rotate та перевертання початку координат /MediaBox (PDF розміщує початок координат знизу ліворуч, GDI — зверху ліворуч) обробляються всередині матриці переходу сторінки на пристрій, тому сторінка US Letter при 72 DPI повертається як рівно 612×792 пікселів у правильній орієнтації
Чому на візуалізованих мініатюрах PDF відображаються неправильні гліфи?
Неправильні або приблизні гліфи у візуалізованому PDF майже завжди означають, що рендерер підміняє системний шрифт замість використання шрифту, вбудованого у файл. Перший рендерер HotPDF робив саме це: він видаляв префікс підмножини з /BaseFont (перетворюючи ABCDEF+Arial на Arial), запитував у GDI системний шрифт з таким ім'ям і малював ним текст. Для документа, який використовує Arial або Times New Roman зі стандартним кодуванням, результат виглядає схожим. Але це лише наближення, яке дає збій у певних випадках
Субсетовані вбудовані шрифти є найгіршим випадком. Субсетований шрифт може містить лише ті сорок гліфів, які документ реально використовує, з кодами символів, призначеними в приватному для цього файлу порядку — код 1 може бути «T», код 2 «h» і так далі. Системний шрифт нічого не знає про це приватне призначення, тому текст або зникає, або виводиться зовсім іншими символами. Користувацькі кодування, символьні шрифти, шрифти штрих-кодів та будь-які накреслення, не встановлені на машині рендерингу, зазнають невдачі таким самим чином. Рендерер, який зупиняється на заміні системного шрифту, створює мініатюри, на яких впізнається сторінка — доки на сторінці не використовуються шрифти, які взагалі зробили вбудовування необхідним
Рендеринг вбудованих гліфів: малювання безпосередньо з програми шрифту
HotPDF закрив цю прогалину протягом п'яти випусків (від v2.268.0 до v2.272.0), розібравши вбудовані програми шрифтів та відтворивши контури їхніх гліфів як заповнені векторні шляхи GDI. Текст на візуалізованій сторінці тепер береться з тих самих даних контуру, які використовує сумісний переглядач, що означає, що субсетовані шрифти, користувацькі кодування та невстановлені накреслення рендеряться зі своїми точними формами. Покриття створювалося за типами шрифтів
Для шрифтів Type0/CIDFontType2 з вбудованою програмою TrueType (FontFile2) рендерер аналізує таблиці glyf та loca безпосередньо: квадратичні контури перетворюються на кубічні криві Безьє, які розуміє GDI, реконструюються передбачувані точки на кривій між послідовними точками поза кривою, а складені гліфи рекурсивно відтворюються. Підтримуються як макети Identity, так і явні потокові макети CIDToGIDMap, а зсуви CID враховують записи ширини /W та /DW, тому двобайтовий текст Identity-H крокує правильно
Програми CFF (FontFile3, будь то CIDFontType0C, Type1C або обгортка OpenType) отримують повний інтерпретатор charstring Type 2: лінії, криві, сімейство flex, маски підказок та виклики локальних/глобальних підпрограм з правильним зміщенням підпрограми. Програми CFF з CID-ключами відображають коди символів через набір символів шрифту, що важливо для субсетованих шрифтів, порядок гліфів у яких відрізняється від порядку CID, і вибір font-DICT для кожного гліфа через FDArray/FDSelect враховується. Прості (не CID) шрифти TrueType розпізнають однобайтні коди через власну таблицю cmap вбудованого шрифту з надійним ланцюжком субтаблиць — формати Unicode 4 та 12 спочатку, потім символьні субтаблиці з дзеркалом приватного використання F000, потім застарілі формати Macintosh — тоді як прості шрифти Type1 розпізнаються через вбудоване кодування програми CFF
Два вдосконалення доповнюють картину. По-перше, словники /Encoding простих шрифтів розпізнаються відповідно до пріоритету, який приписує стандарт ISO 32000-1 §9.6.6: масиви /Differences замінюють базове кодування, яке замінює власну карту програми шрифту — шлях, від якого залежать інструменти на основі TeX та PostScript, причому назви гліфів розпізнаються через Adobe Glyph List, набір символів CFF або TrueType cmap. По-друге, шрифти Type3, гліфи яких самі є невеликими потоками вмісту, відтворюються через рендерер із скомпонованими матрицею шрифту, розміром шрифту та текстовою матрицею; зсуви в просторі гліфів інтерпретуються через /FontMatrix, як вимагає ISO 32000-1 §9.6.5, а процедури гліфів, що оголошують обмежувальну рамку d1, відсікаються за нею, тому некоректний гліф штрих-коду не може вийти за межі своєї комірки. Коли код не може бути відображений — пошкоджена програма, невідображений символ — рендерер повертається до малювання системного шрифту для цього гліфа замість відмови від усього проходу
Як зробити повторний рендеринг швидким?
Відповідь, яку постачає HotPDF, — це кеш останніх використаних сторінок: RenderLoadedPageToBitmapCached зберігає до RenderCacheCapacity рендерених сторінок (за замовчуванням 8), індексованих за номером сторінки та DPI, а влучання в кеш повертає нову копію, що належить викликачу, без роботи з потоком вмісту — зазвичай у тисячі разів швидше, ніж повторна інтерпретація сторінки. Цей шаблон ідеально підходить для переглядачів: користувач, який перемикається між двома сторінками, або подія зміни розміру, яка повторно запитує ту саму сторінку з тим самим DPI, щоразу потрапляє в кеш
// Thumbnail strip: first pass renders, scrolling back hits the cache
for I := 0 to ThumbCount - 1 do
begin
Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 48);
if Bmp <> nil then
try
ThumbList.AddThumbnail(I, Bmp);
finally
Bmp.Free;
end;
end;
// After editing a loaded page in place:
Pdf.InvalidateRenderedPageCache; // next render reflects the change
Будьте чесними щодо використання пам'яті перед підвищенням ємності. Сторінка US Letter при 300 DPI має розмір 2550×3300 пікселів, приблизно 25 МБ як 24-бітний bitmap, тому вісім кешованих сторінок у роздільній здатності експорту займають близько 200 МБ. При DPI мініатюр ті самі вісім записів коштують менше мегабайта. Визначайте розмір RenderCacheCapacity для DPI, з яким ви реально працюєте, і викликайте InvalidateRenderedPageCache після будь-кового редагування на місці — кеш індексується лише за сторінкою та DPI, і він не може побачити, що базовий вміст змінився. Завантаження нового документа очищає його автоматично
Другий кеш працює під кешем сторінок: декодовані об'єкти зображень XObject зберігаються у сховищі з обмеженням байтів ImageCacheMaxBytes (за замовчуванням 32 МБ) з витісненням за принципом найдавнішого використання. Зображення логотипа або бланка листа, яке повторюється на кожній сторінці, декодується один раз завантаження документа замість кожного оператора Do, що приблизно вдвічі скорочує час рендерингу для сторінок із спільними зображеннями та прискорює багатосторінковий експорт TIFF на ту саму величину. InvalidateRenderedPageCache очищає і цей кеш також
Що все ще рендериться наближено
Рендерер орієнтований на загальну підмножину документів PDF, і варто знати, де знаходяться межі. Колірні простори CalRGB, Lab та ICC-based є скоріше наближеними, ніж керованими кольором — апаратні колірні простори, палітри Indexed та вибірки колірних таблиць Type 0 підтримуються, але файл для друкарського виробництва, що покладається на інтенції рендерингу ICC, не буде колірометрично точним. Шаблони затінення (sh) та режими змішування, крім простого альфа-каналу, також знаходяться поза межами роботи, а рекурсія Form XObject обмежена по глибині для захисту від зациклення. Для рахунків-фактур, звітів, договорів та форм — сторінок, що складаються з тексту, шляхів та зображень — вивід є точним; для дизайнерського макета, повного градієнтів та груп прозорості, сприймайте bitmap як ескіз, а не як зразок
Практичне розуміння: якщо ваш конвеєр генерує документи за допомогою HotPDF або споживає типові бізнес-PDF, RenderLoadedPageToBitmap точно відтворює їх з точними вбудованими формами гліфів, правильними зсувами CID та правильною геометрією сторінки. Наближення живуть у кутах графічної моделі, які бізнес-документи відвідують рідко
Метод RenderLoadedPageToBitmap, його кешований варіант та конвеєр рендерингу вбудованих гліфів, описаний тут, постачаються як частина HotPDF Component для Delphi та C++Builder — рідної бібліотеки VCL без зовнішніх залежностей DLL, що охоплює створення, редагування, вилучення тексту та рендеринг сторінок PDF в одному пакеті