Plik PDF, którego nagłówek zawiera tło z gradientem w stylu programów Word lub Excel, po wyrenderowaniu do obrazu powracał dawniej z pustym białym prostokątem w miejscu, gdzie powinno znajdować się przejście tonalne kolorów. Renderer stron w komponencie HotPDF obsługuje teraz operator PDF sh (shading) dla gradientów osiowych typu 2 (Type 2 axial) i promieniowych typu 3 (Type 3 radial): wyznacza on funkcję cieniowania wzdłuż osi gradientu i rysuje przejście tonalne, podczas gdy wcześniejsze wersje po cichu pomijały ten operator, pozostawiając ten obszar biały jak papier. Dwa typy cieniowania, które pokrywają niemal każdy gradient w dokumentach biurowych, są już obsługiwane; typy siatkowe (mesh) nie są — a ten artykuł uczciwie przedstawia granice tych możliwości
Dlaczego gradient PDF renderuje się jako biały prostokąt?
Gradient w formacie PDF to struktura wektorowa, a nie mapa bitowa, którą renderer mógłby po prostu skopiować (blitować). Słownik cieniowania (ISO 32000-1 §8.7.4.5) opisuje geometrię — oś lub parę okręgów w tablicy Coords — oraz funkcję koloru, a operator sh nanosi to cieniowanie na bieżący obszar przycinania (clip region). Jest to ten sam model graficzny, który HotPDF udostępnia podczas rysowania ścieżek i wypełnień na płótnie strony. Renderer stron w HotPDF przechodzi przez strumień zawartości operator po operatorze, a każdy operator, dla którego nie ma ścieżki kodu, jest pomijany, dzięki czemu reszta strony nadal się renderuje. Przed wersją v2.335.0 operator sh znajdował się w tej pomijanej grupie: gradienty nagłówka lub tła strony nie wnosiły nic do wyniku, więc tło pozostawało białe. Plik nie był uszkodzony — renderer po prostu nie posiadał procedury obsługi tego konkretnego operatora, a białe pole to klasyczny objaw braku takiej procedury
Łańcuch wyznaczania funkcji za cieniowaniem
Cieniowanie nie może zostać narysowane, dopóki nie zostanie obliczona jego funkcja, dlatego ten mechanizm musiał zostać wdrożony w pierwszej kolejności. Zarówno cieniowanie osiowe typu 2, jak i promieniowe typu 3 posiadają parametr /Function, który mapuje parametryczną pozycję wzdłuż gradientu na kolor (ISO 32000-1 §7.10). Ewaluator funkcji HotPDF rozpoznaje FunctionType i przekazuje do niego wykonanie. Interpolacja wykładnicza typu 2 (§7.10.3) oblicza wyrażenie C0 + t^N * (C1 - C0) z ograniczeniem wartości do zakresów Domain i Range. Funkcje próbkowane typu 0 (§7.10.2) odczytują siatkę 8-, 16- lub 32-bitowych próbek i wybierają najbliższą poprzez mapowanie Encode (przypadek ten szczegółowo omówiono w naszym poradniku dotyczącym próbkowanego wyszukiwania kolorów typu 0). Funkcje łączące typu 3 (§7.10.4) wybierają podfunkcję na podstawie tablicy Bounds i wywołują ją rekurencyjnie. Funkcje kalkulatora PostScript typu 4 (§7.10.5) nie są obliczane — w ich przypadku następuje kontrolowane zgłoszenie błędu obliczenia zamiast rysowania błędnego koloru, co jest zgodne z zachowawczą polityką komponentu HotPDF. Ten ewaluator został udostępniony w wersji v2.334.0, o jedno wydanie wcześniej niż sam operator, ponieważ moduł rysujący cieniowanie jest tak dokładny, jak leżąca u jego podstaw funkcja
Jak HotPDF zamienia ciągły gradient na piksele?
HotPDF przybliża ciągłe przejście tonalne za pomocą skończonej liczby pasm o jednolitych kolorach. Mechanizm cieniujący przechodzi wzdłuż osi gradientu, dzieli ją na maksymalnie 64 pasma — mniej, gdy oś jest krótka w pikselach urządzenia — oblicza wartość funkcji w parametrycznym położeniu każdego pasma i wypełnia prostokąt prostopadły do osi jednolitym pędzlem. Kompromis polega na zrównoważeniu liczby pasm i płynności: większa liczba pasm dokładniej odwzorowuje przejście, ale wymaga więcej operacji wypełniania, a 64 to celowo przyjęty limit. Ponieważ liczba ta zależy od długości osi w pikselach urządzenia, rozdzielczość ma znaczenie. Renderowanie tej samej strony przy wyższym DPI wydłuża oś, dzięki czemu krótki gradient zyskuje więcej pasm, a jego paski stają się łagodniejsze aż do osiągnięcia limitu
Renderowanie cieniowanej strony do mapy bitowej
Publiczny punkt wejścia nie uległ zmianie: metoda RenderLoadedPageToBitmap przyjmuje indeks strony (od 0) oraz rozdzielczość DPI, zwraca mapę bitową 24-bit (którą zarządza wywołujący) i zwraca nil w przypadku niepowodzenia zamiast zgłaszać wyjątek. Wsparcie dla cieniowania jest więc niewidoczne dla kodu, który już posiadasz. Strona, która dawniej renderowała nagłówek gradientowy jako pusty, teraz zwraca tę samą mapę bitową z narysowanym przejściem tonalnym, bez potrzeby modyfikacji API. Jedyną rzeczą, o której należy pamiętać, jest zarządzanie pamięcią: wywołujący musi zwolnić zwróconą mapę bitową. Cały proces renderowania — interpretacja strumienia zawartości, osadzanie glifów i buforowanie stron — opisano w poradniku o renderowaniu stron PDF do mapy bitowej w Delphi
// 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
Prosty test regresji można oprzeć na tym samym wywołaniu. Wyrenderuj stronę, pobierz próbkę piksela w miejscu, gdzie powinno znajdować się pasmo gradientu, i upewnij się, że nie jest on biały (jak domyślne tło papieru, do którego dawniej powracał renderer) — to jednolinijkowy test sprawdzający, czy mechanizm nie powrócił po cichu do dawnego ignorowania operatora
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;
Prosty test regresji można oprzeć na tym samym wywołaniu. Wyrenderuj stronę, pobierz próbkę piksela w miejscu, gdzie powinno znajdować się pasmo gradientu, i upewnij się, że nie jest on biały (jak domyślne tło papieru, do którego dawniej powracał renderer) — to jednolinijkowy test sprawdzający, czy mechanizm nie powrócił po cichu do dawnego ignorowania operatora
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;
Czego HotPDF nie rysuje: cieniowania siatkowe i krawędzie przycinania
Cztery typy cieniowania siatkowego (mesh shading) wykraczają poza zakres obsługi i należy o tym wiedzieć. Siatki trójkątne Gourauda o kształcie dowolnym (Typ 4), siatki Gourauda w postaci kraty (Typ 5), siatki płatów Coonsa (Typ 6) oraz siatki płatów tensorowych (Typ 7), zdefiniowane w ISO 32000-1 od §8.7.4.5.5 do §8.7.4.5.8, nie są renderowane. Strona korzystająca z takich cieniowań nadal się renderuje, ale dany obszar pozostaje niezmieniony. Warto również jasno określić dwa kolejne ograniczenia. Prostokąty pasm pokrywają cały pas cieniowania, a no-go dla precyzyjnego przycinania do bieżącej ścieżki (clip path). Oznacza to, że gradient wpisany w zaokrąglony lub nieregularny kształt będzie rysowany jako prostokąt go otaczający, dopóki w kolejnych wersjach nie zostanie wprowadzona obsługa dokładnego przycinania regionów. HotPDF zakłada również, że wyjściem funkcji jest format RGB lub skala szarości, więc cieniowanie zdefiniowane w CMYK lub nietypowej przestrzeni barw będzie przybliżane, a nie zarządzane kolorymetrycznie. W przypadku dokumentów, które najczęściej przetwarzają zespoły pracujące w Delphi i C++Builder — raportów, faktur, zestawień czy prezentacji wyeksportowanych do PDF — cieniowanie osiowe i promieniowe pokrywa praktycznie każdy napotkany gradient. Bardziej zaawansowane projekty z dużą ilością grafik siatkowych należy traktować jako poglądowe, a nie jako gotowe odbitki próbne
Renderer stron HotPDF, opisana tutaj obsługa operatora sh oraz ewaluator funkcji typu 0/2/3 wchodzą w skład komponentu HotPDF Component dla Delphi i C++Builder — natywnej biblioteki VCL bez zewnętrznych zależności DLL, obsługującej tworzenie, edycję, ekstrakcję tekstu oraz renderowanie stron w jednym pakiecie