Документы PDF, в заголовках которых используются градиентные фоны в стиле Word или Excel, при экспорте в изображения раньше часто возвращали пустой белый прямоугольник вместо плавного смешивания цветов. Рендерер страниц HotPDF теперь поддерживает оператор PDF sh (затенение, shading) для осевых (Тип 2) и радиальных (Тип 3) градиентов: он вычисляет значения функции затенения вдоль оси градиента и отрисовывает смешение, тогда как ранние версии библиотеки просто игнорировали этот оператор, оставляя область чисто белой. Поддерживаются оба типа затенений, покрывающих практически любые градиенты офисных документов; сеточные градиенты не поддерживаются, и в этой статье мы честно опишем границы возможностей
Почему градиент PDF отрисовывается в виде белого прямоугольника?
Градиент в PDF — это векторная конструкция, а не растровое изображение, которое рендерер может легко перенести на холст. Словарь затенения (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. Составные (stitching) функции Типа 3 (§7.10.4) выбирают подфункцию по массиву Bounds и выполняют рекурсивный вызов. Калькуляторные функции PostScript Типа 4 (§7.10.5) не вычисляются — они корректно возвращают ошибку вычисления вместо выдачи неверного цвета, что соответствует консервативной политике HotPDF на всем пути рендеринга. Этот вычислитель появился в версии 2.334.0, на один выпуск раньше самого оператора, поскольку отрисовщик затенений напрямую зависит от качества лежащей под ним функции
Как HotPDF преобразует непрерывный градиент в пиксели?
HotPDF аппроксимирует непрерывное смешение набором полос сплошного цвета. Отрисовщик затенений обходит ось градиента, разбивая ее на число полос до 64 (меньше, если ось коротка в пикселях устройства), вычисляет функцию в параметрическом положении каждой полосы и заполняет прямоугольник, перпендикулярный оси, сплошной кистью. Здесь соблюдается компромисс между количеством полос и плавностью: большее число полос точнее передает градиент, но требует большего числа операций заливки, и 64 полосы являются разумным пределом. Поскольку это количество также зависит от длины оси в пикселях устройства, разрешение рендеринга имеет значение. Отрисовка той же страницы с более высоким значением DPI удлиняет ось, поэтому короткий градиент получает больше полос, и его переходы смягчаются до достижения лимита
// 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 lengthens 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
Рендеринг затененной страницы в TBitmap
Общая точка входа осталась неизменной: RenderLoadedPageToBitmap принимает индекс страницы с 0 и разрешение DPI, возвращает управляемый вызывающей стороной объект TBitmap, а в случае ошибки возвращает nil вместо вызова исключения, поэтому поддержка затенений прозрачна для уже существующего кода. Страница, которая раньше рендерила градиентный заголовок пустым, теперь возвращает то же изображение, но с отрисованным смешением цветов, и для этого не требуется никаких изменений API. Важно помнить об освобождении ресурсов: вызывающая сторона сама удаляет возвращенный bitmap. Весь конвейер рендеринга — интерпретация потока команд, встроенные глифы и кэширование страниц — описан в руководстве по рендерингу страниц PDF в TBitmap в 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) градиентов не поддерживаются, и этот факт стоит учитывать при подготовке документов. Свободные сетки треугольников Гуро (Тип 4), регулярные сетки Гуро (Тип 5), сетки Coons patch (Тип 6) и сетки tensor-product patch (Тип 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 для Delphi и C++Builder — нативной VCL-библиотеки без внешних зависимостей от DLL, объединяющей создание, редактирование, извлечение текста и рендеринг страниц PDF в одном пакете