Раніше при рендерингу в зображення PDF-файлу, заголовок якого мав градієнтне тло в стилі Word або Excel, замість змішування кольорів відображався порожній білий прямокутник. Тепер рендерер сторінок HotPDF підтримує оператор sh (shading) для осьових градієнтів типу 2 (Type 2 axial) та радіальних градієнтів типу 3 (Type 3 radial): він обчислює функцію затінення вздовж осі градієнта та малює перехід кольорів. У попередніх версіях цей оператор ігнорувався, а область залишалася білою. Два типи градієнтів, які покривають майже всі офісні документи, тепер підтримуються; сітчасті градієнти (mesh types) не підтримуються, і в цій статті відверто описувено межі цієї підтримки.
Чому градієнт PDF рендериться як білий прямокутник?
Градієнт PDF — це векторна структура, а не растрове зображення, яке рендерер може перенести на полотно. Словник затінення (shading dictionary) (ISO 32000-1 §8.7.4.5) описує геометрію (вісь або пара кіл у масиві Coords) та функцію кольору, а оператор sh малює це затінення у поточній області відсікання — таку ж графічну модель пропонує HotPDF, коли ви малюєте контури та заливки на полотні сторінки. Рендерер сторінок HotPDF проходить по потоку вмісту оператор за оператором. Будь-який оператор, для якого немає відповідного коду, просто пропускається, щоб решта сторінки все одно відображалася. До версії 2.335.0, оператор sh знаходився в цьому пропущеному наборі: градієнт заголовка або сторінки ніяк не впливав на результат, тому тло залишалося білим. Файл при цьому не був пошкоджений. У рендерера просто не було обробника для цього конкретного оператора, а біле вікно — це саме те, як виглядає відсутність обробника.
Ланцюжок обчислення функції затінення
Затінення не можна намалювати, поки не буде обчислено його функцію, тому цей механізм мав з'явитися першим. Осьові затінення типу 2 та радіальні типу 3 містять параметр /Function, який відображає параметричну позицію вздовж градієнта в колір (ISO 32000-1 §7.10), а обчислювач функцій HotPDF визначає FunctionType і викликає потрібний код. Експоненціальна інтерполяція типу 2 (§7.10.3) обчислює C0 + t^N * (C1 - C0) з обмеженням Domain та Range; вибіркові функції типу 0 (§7.10.2) зчитують сітку 8-, 16- або 32-бітних вибірок і вибирають найближчу через карту Encode — випадок, детально розглянутий у статті про вибір кольорів за таблицею вибірок типу 0; функції зшивання типу 3 (§7.10.4) вибирають підфункцію за масивом Bounds і викликають її рекурсивно. Функції калькулятора PostScript типу 4 (§7.10.5) не обчислюються — вони безпечно повертають помилку обчислення замість неправильного кольору, що відповідає консервативному підходу HotPDF. Цей обчислювач з'явився у версії 2.334.0, на один реліз раніше самого оператора, оскільки якість малювання затінення повністю залежить від базової функції.
Як HotPDF перетворює безперервний градієнт на пікселі?
HotPDF апроксимує безперервний градієнт кінцевим набором смуг суцільного кольору. Малювальник затінення проходить по осі градієнта, розбиває її максимум на 64 смуги (менше, якщо вісь коротка в пікселях пристрою), обчислює функцію в параметричній точці кожної смуги та заповнює прямокутник, перпендикулярний до осі, суцільним пензлем. Це компроміс між кількістю смуг та плавністю переходу: більша кількість смуг забезпечує більш плавний перехід, але вимагає більше операцій заливки (64 є встановленим лімітом). Оскільки кількість смуг також залежить від довжини осі в пікселях пристрою, роздільна здатність має значення. Рендеринг тієї ж сторінки з вищим DPI подовжує вісь, тому короткий градієнт отримує більше смуг, а смугастість стає менш помітною до досягнення ліміту в 64 смуги.
// The shading discretizer uses up to 64 color bands along the gradient
// axis, but never more than the axis is long in device pixels. Rendering
// the same page at a higher DPI narrows that axis, so a short gradient
// gains bands and the banding softens until the 64-band ceiling is hit.
Thumb := Pdf.RenderLoadedPageToBitmap(PageIndex, 96); // coarser bands
Proof := Pdf.RenderLoadedPageToBitmap(PageIndex, 300); // up to 64 bands
Рендеринг затіненої сторінки в bitmap
Публічна точка входу залишилася незмінною: метод RenderLoadedPageToBitmap приймає індекс сторінки (з 0) та значення DPI, повертає 24-бітне растрове зображення, що належить викликачу, та повертає nil у разі збою замість генерування винятку, тому підтримка затінення є прозорою для вашого існуючого коду. Сторінка, заголовок якої раніше рендерився білим, тепер повертає таке ж растрове зображення з градієнтною заливкою без потреби змінювати API. Важливо пам'ятати про володіння ресурсом: викликач повинен самостійно звільнити повернутий bitmap. Весь конвеєр рендерингу (інтерпретація потоку вмісту, вбудовані гліфи та кешування сторінок) описано в статті про рендеринг сторінок PDF у bitmap в Delphi.
var
Pdf: THotPDF;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('quarterly-report.pdf') > 0 then
begin
// Page 1 (zero-based) at 150 DPI. A gradient-filled header that
// used to come back blank now carries its color blend.
Bmp := Pdf.RenderLoadedPageToBitmap(0, 150);
if Assigned(Bmp) then
try
Bmp.SaveToFile('page1.bmp');
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
Простий тест регресії можна реалізувати за допомогою того ж виклику. Відрендерите сторінку, зчитайте колір пікселя в області градієнта та переконайтеся, що він не є білим тлом, яке раніше використовував рендерер — однорядкова перевірка, яка дозволяє виявити повернення до старої поведінки без підтримки градієнтів.
Bmp := Pdf.RenderLoadedPageToBitmap(0, 150);
if Assigned(Bmp) then
try
// True when the sh operator painted; False if the region fell back to white.
HeaderPainted := Bmp.Canvas.Pixels[Bmp.Width div 2, 8] <> clWhite;
finally
Bmp.Free;
end;
Що HotPDF не малює: сітчасті затінення та межі відсікання
Чотири типи сітчастих затінень (mesh shadings) не підтримуються, і це варто враховувати. Вільні трикутні сітки Гуро (тип 4), двовимірні сітки Гуро (тип 5), сітки Кунса (тип 6) та тензорні сітки (тип 7), визначені в ISO 32000-1 §8.7.4.5.5–§8.7.4.5.8, не рендеряться; сторінка, яка їх використовує, все одно буде оброблена, але відповідна область залишиться без змін. Варто також згадати ще два моменти. Смуги прямокутників покривають усю область затінення, а не обрізаються точно по поточному контуру відсікання, тому градієнт у круглій або складній фігурі малюватиметься як обмежувальний прямокутник до появи точного відсікання в наступних версіях. HotPDF також припускає, що вихідні дані функції є RGB або градаціями сірого, тому затінення в CMYK або інших колірних просторах апроксимуються без повноцінного керування кольором. Для документів, з якими працює більшість розробників на Delphi та C++Builder (звіти, рахунки-фактури, презентації), осьові та радіальні градієнти покривають практично всі потреби, а складні дизайнерські макети з великою кількістю сіток варто сприймати лише як попередній перегляд, а не як точну копію.
Рендерер сторінок HotPDF, описана тут підтримка оператора sh та вбудований обчислювач функцій типів 0/2/3 постачаються у складі компонента HotPDF Component для Delphi та C++Builder — нативної VCL-бібліотеки без зовнішніх DLL-залежностей, яка містить функції створення, редагування, вилучення тексту та рендерингу сторінок PDF в одному пакеті.