Knihovna HotPDF vykresluje přímé barvy Separation a DeviceN na načtených stránkách PDF tak, že vyhodnotí barevný prostor pomocí HPDFResolveColorSpace, provede funkci transformace odstínu (tint-transform) pomocí HPDFEvalTintTransform a převede výsledek přes náhradní barevný prostor do RGB pro obrazovku. Od verze v2.375.0 tento kanál vyhodnocuje všechny čtyři typy funkcí PDF, včetně kalkulátorů PostScript Type 4, takže soubor připravený pro tisk s inkousty Pantone zobrazuje své skutečné barvy namísto zástupné barvy. Tento článek popisuje, jak tento kanál funguje a také jak selhal, když jsme jej vytvářeli
Problém se objevuje v okamžiku, kdy formulář přestane být plochý. A VAT return or customs declaration built as a dynamic XFA form does not have twelve fields named Total_Price_1 through Total_Price_12; it has one Detail subform declared once in the template DOM and instantiated as many times as the data demands. The values live in a second tree, the Data DOM inside the datasets packet, where each line item is a repeated <Detail> element under <Receipt>. Flat field names cannot address "the price on the third line" or "every line over 200"; that is exactly the job the Scripting Object Model chapter of the Adobe XFA 3.3 specification gives to SOM expressions, and it is the syntax the losLab PDF Library XFA field-value APIs speak
Proč se přímé barvy v prohlížeči PDF vykreslují černě?
Přímé barvy (spot colors) se vykreslují černě nebo mizí, pokud vykreslovač implementuje pouze operátory barev zařízení (rg, g, k) a ignoruje ty obecné. Norma ISO 32000-1 §8.6 definuje tři skupiny barevných prostorů: prostory zařízení (DeviceGray, DeviceRGB, DeviceCMYK), prostory založené na CIE (CalGray, CalRGB, Lab, ICCBased) a speciální prostory (Indexed, Separation, DeviceN, Pattern). Vše mimo skupinu zařízení se vybírá pomocí obecných operátorů: cs a CS volí prostor podle názvu ze slovníku prostředků stránky, poté sc, SC, scn a SCN dodávají hodnoty komponent. Vykreslovač, který tyto operátory přeskočí, zachová naposledy nastavenou barvu, což je u stránky začínající nadpisem v přímé barvě výchozí černá barva DeviceGray
HotPDF přidalo sadu obecných operátorů do svého vykreslovače stránek ve verzi v2.333.0 spolu s jednotnou vyhodnocovací cestou: každá položka prostředku /ColorSpace, ať už jde o prostý název, vložené pole nebo nepřímý odkaz, je analyzována do jednoho záznamu THPDFColorSpace a každý požadavek na barvu výplně nebo tahu protéká jediným voláním HPDFResolveColor. Výčet rodin ukazuje pokrytí na první pohled
type
THPDFColorSpaceFamily = (csfDeviceGray, csfDeviceRGB, csfDeviceCMYK,
csfIndexed, csfCalGray, csfCalRGB, csfLab,
csfICCBased, csfSeparation, csfDeviceN,
csfUnsupported);
function HPDFResolveColorSpace(Obj: THPDFObject): THPDFColorSpace;
function HPDFResolveColor(const CS: THPDFColorSpace;
const Comps: THPDFColorComps; CompCount: Integer): THPDFRenderColor;
Jedno rozhodnutí o návrhu se opakovaně vyplácí: csfUnsupported je plnohodnotná rodina, nikoli chyba. Prostor, který vykreslovač nedokáže interpretovat, degraduje na definovaný fallback, namísto aby se vykreslení stránky přerušilo, což odpovídá chování běžných prohlížečů a zabraňuje tomu, aby jediná exotická výplň znečitelnila jinak vykreslitelný dokument
Jak transformace odstínu převádí hodnotu inkoustu na skutečnou barvu?
Prostor Separation nese tři informace: název inkoustu, náhradní barevný prostor a funkci transformace odstínu (tint-transform). Pole [/Separation /PANTONE485 /DeviceCMYK f] říká: když proud obsahu zapíše 0.8 scn, předejte hodnotu odstínu 0,8 do funkce f a vykreslete výslednou čtveřici CMYK. DeviceN to zobecňuje na N inkoustů s funkcí o N vstupech. Samotný název inkoustu má na obrazovce pouze informativní charakter; transformace odstínu představuje celou sémantiku vykreslování, takže vykreslovač, který sice analyzuje prostor, ale přeskočí funkci, dosud neudělal nic užitečného
Metoda HPDFEvalTintTransform je funkční jádro na pozadí tohoto kroku. Byla zavedena ve verzi v2.334.0 a vyhodnocuje exponenciální funkce Type 2 (C0 + x^N * (C1 - C0) s ořezáním podle Domain a Range), propojovací funkce Type 3 (rekurze podfunkcí vybraných podle Bounds s přemapováním Encode) a vzorkované funkce Type 0 s 8, 16 a 32bitovými vzorky. Type 0 je stejný mechanismus vyhledávacích tabulek, kterým jsme se zabývali ze strany vytváření v článku o sestavování barevných vyhledávacích tabulek Type 0 (LUT); strana vykreslování prochází identickou strukturu v opačném směru, od dekódovaných bajtů vzorků zpět k hodnotám komponent
Funkce kalkulátoru PostScript Type 4 byly poslední překážkou. Až do verze v2.375.0 degradovaly na neutrální zástupnou barvu; od verze v2.375.0 provádí HPDFEvalPostScriptCalculator kompletní sadu operátorů podle normy ISO 32000-1 §7.10.5 na omezeném zásobníku operandů: aritmetické, porovnávací, booleovské a bitové operátory, manipulaci se zásobníkem včetně roll a podmínky if/ifelse. Okrajová sémantika je přísnější, než se zdá. Operátor PostScriptu round zaokrouhluje poloviny směrem k vyšší hodnotě, takže nelze použít bankovní zaokrouhlování Delphi Round; goniometrické operátory pracují ve stupních, přičemž atan vrací hodnoty v [0, 360); a exp je mocnina se dvěma operandy, nikoli přirozená exponenciála. Stejný vyhodnocovač řídí také gradientní výplně založené na funkcích, což je důvod, proč vykreslování axiálního a radiálního stínování získalo ve stejné verzi rampy řízené kalkulátorem
// Evaluate a Separation/DeviceN tint transform (Type 0/2/3/4).
function HPDFEvalTintTransform(FuncObj: THPDFObject;
const Inputs: THPDFColorComps; InputCount: Integer;
out AltComps: THPDFColorComps): Boolean;
// Type 4 PostScript calculator, ISO 32000-1 7.10.5 operator set.
function HPDFEvalPostScriptCalculator(const Prog: TBytes;
const Inputs: THPDFColorComps; InputCount: Integer;
var Outputs: THPDFColorComps; OutCount: Integer): Boolean;
Upřímně k přesnosti: softwarový kalkulátor vyhodnocovaný v přesnosti double nebude odpovídat RIPu bit po bitu a ořezání na hranici Range se může lišit o nejméně významný bit (LSB) od jiné implementace. Pro zobrazení na obrazovce a regresní vykreslování je to nepodstatné; pokud stavíte nátisk s řízením barev, transformace odstínu je pouze první fází a dále potřebujete skutečný systém správy barev (CMM)
CalGray, CalRGB, Lab a ICCBased bez jádra ICC
Rodiny založené na CIE se ubírají druhou větví stejného vyhodnocovače. HotPDF převádí hodnoty Lab standardním řetězcem Lab na XYZ na sRGB, včetně zlomového bodu 6/29 v inverzní přenosové funkci, a zpracovává CalRGB s gammou pro jednotlivé kanály a lineární maticí 3x3 a CalGray s jednou gammou. Detailem důležitým pro výkon je zpracování bílého bodu: převodní matice XYZ na sRGB je pomocí Bradfordovy metody přizpůsobena bílému bodu deklarovanému v barevném prostoru a uložena do mezipaměti v vyhodnoceném záznamu THPDFColorSpace, takže práce na pixel zůstává jediným násobením 3x3, bez ohledu na to, jak exotický je deklarovaný světelný zdroj
Prostory ICCBased získávají záměrně pragmatické zpracování. Specifikace PDF vyžaduje, aby každý proud ICCBased deklaroval náhradní prostor /Alternate nebo jej nepřímo určil pomocí počtu komponent /N, právě proto, aby prohlížeče bez jádra pro správu barev stále dokázaly rozumně kreslit. HotPDF vyhodnocuje ICCBased přes tento náhradní prostor, nebo odhaduje DeviceGray, DeviceRGB či DeviceCMYK podle toho, zda je /N rovno 1, 3 nebo 4, pokud položka chybí, a bajty profilu nikdy neanalyzuje. To znamená nulovou závislost na lcms a žádné náklady na vyhledávání profilů, avšak za cenu kolorimetrické přesnosti: prostor ICCBased, jehož profil se výrazně odchyluje od náhradního, zobrazí náhradní vykreslení. Pro zobrazení na obrazovce a náhledy je to stejný kompromis, jaký dělá každý lehký prohlížeč, což je omezení, které byste měli jasně uvést ve své vlastní dokumentaci
Chyba, která způsobila selhání každého vyhledávání pojmenovaného barevného prostoru
Zpracování operátorů ve verzi v2.333.0 bylo dodáno s chybou, která zůstala neviditelná po dalších čtyřicet dva vydání: obsluhy cs and CS vyhledávaly svůj operand se zachovaným počátečním lomítkem (/CS0) vůči klíčům slovníku prostředků uloženým bez lomítka (CS0). Každé vyhledávání pojmenovaného barevného prostoru selhalo, ve sto procentech případů, a kód tiše spadl k výchozímu DeviceGray. Viditelný příznak byl záludný tím nejhorším způsobem: výplň Separation o hodnotě 1 scn se změnila na DeviceGray 1.0, což kreslí bíle, a bílý inkoust na bílé stránce není chyba vykreslování, kterou by kdokoli zachytil na snímku obrazovky. Nápravou ve verzi v2.375.0 byl sdílený pomocník pro normalizaci názvů aplikovaný při každém vyhledávání operandu v prostředcích
Ze stejného vyšetřování vyplynuly dvě příbuzné chyby. Za prvé, nepřímé odkazy na objekty s hodnotou pole se vracely nevyřešené: přístup k dokumentu ve vykreslovači měl typované vyhodnocovače pouze pro proudy a slovníky, takže /CS0 5 0 R ukazující na samostatné pole [/Separation ...] se vrátil jako surový odkaz a prostor se vyhodnotil jako nepodporovaný. Za druhé, funkce HPDFReadNumericArray vynucovala striktní sémantiku délky vyžadující, aby pole PDF bylo alespoň tak dlouhé jako dodaný buffer. Čtení /C0 a /C1 funkce Type 2 do čtyřprvkového bufferu proto selhalo u jedno- a tříkomponentních náhrad, což ponechalo obě pole vynulovaná, a každý jiný než CMYK exponenciální odstín se vykresloval černě, dokud verze v2.376.0 nezavedla tolerantní čtečku HPDFReadNumericArrayUpTo. Poučení pro budoucí vývoj: jakýkoli klíč PDF dokumentovaný jako obsahující číselné pole proměnné délky musí být čten čtečkou, která naplní to, co existuje, protože buffer pevné velikosti se striktním porovnáváním mění platné soubory na tiché nuly
Jak testovat vykreslování přímých barev, aniž byste klamali sami sebe?
Nepříjemnou otázkou je, proč testovací sada zůstala po celou dobu zelená. Podeslední verze regression testu, SeparationRendersDistinguishable, pouze tvrdila, že vykreslená bitmapa není zcela černá. Kanál, který degradoval každou přímou barvu na DeviceGray, produkoval šedý a bílý výstup, což není černá, takže tvrzení prošlo, i když byla celá funkce nefunkční. Slabá tvrzení typu „není prázdný“, „není zcela černý“ nebo „otisk je nenulový“ nedokážou rozlišit fungující vykreslovač od rozbitého, protože téměř jakýkoli chybový stav stále produkuje nějaké pixely
Styl tvrzení, který tyto chyby skutečně zachytí, uzamkne očekávanou barvu: vykreslí ručně vytvořené minimální PDF, jehož inkoust Separation se překládá na známý odstín, a poté spočítá pixely, v nichž tento odstín převládá. Vykreslování do bitmapy pro kontrolu používá stejný přístupový bod RenderLoadedPageToBitmap popsaný v průvodci vykreslováním stránek do bitmapy
var
Pdf: THotPDF;
Bmp: TBitmap;
X, Y, RedHits: Integer;
Px: TColor;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('spot-red-fixture.pdf', '') > 0 then
begin
Bmp := Pdf.RenderLoadedPageToBitmap(0, 96);
try
RedHits := 0;
for Y := 0 to Bmp.Height - 1 do
for X := 0 to Bmp.Width - 1 do
begin
Px := Bmp.Canvas.Pixels[X, Y];
if (GetRValue(Px) > 180) and (GetGValue(Px) < 100) and
(GetBValue(Px) < 100) then
Inc(RedHits);
end;
// Lock the expected ink: demand a real area of red-dominant
// pixels, never settle for "not all black".
Assert(RedHits > 500);
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
Vykreslování přímých barev a barevných prostorů CIE je součástí kanálu pro načtené dokumenty v produktu HotPDF Component pro Delphi and C++Builder, společně s jádrem funkcí, vykreslováním stínování a cestami pro export bitmap popsanými výše