HotPDF rendert Separation- en DeviceN-spotkleuren op geladen PDF-pagina's door de kleurruimte op te lossen via HPDFResolveColorSpace, de tinttransformatiefunctie te evalueren met HPDFEvalTintTransform en het resultaat via de alternatieve ruimte te converteren naar RGB voor het scherm. Sinds v2.375.0 evalueert die pijplijn alle vier de PDF-functietypen, inclusief Type 4 PostScript-calculators, zodat een drukklaar bestand met Pantone-inkten de werkelijke kleuren weergeeft in plaats van een tijdelijke aanduiding. Dit artikel bespreekt hoe de pijplijn werkt en, even nuttig, hoe deze faalde tijdens de ontwikkeling
Het startscenario is altijd hetzelfde. Een klant ontvangt een PDF van een drukkerij: de kop is opgemaakt in een benoemde spot-inkt, de verpakkingsafbeelding maakt gebruik van een DeviceN-mengsel van twee inkten en de hoofdtekst is effen zwart. In Acrobat ziet het er perfect uit. In uw Delphi-applicatie rendert de kop zwart, of erger nog, helemaal niet, en de klant meldt een bug in uw software in plaats van in het bestand. Het bestand is in orde; de renderer spreekt gewoon niet de kleurruimten die het bestand gebruikt
Waarom renderen spotkleuren zwart in een PDF-viewer?
Spotkleuren renderen zwart, of verdwijnen, wanneer de renderer alleen de apparaatkleuroperatoren (rg, g, k) implementeert en de generieke operatoren negeert. ISO 32000-1 §8.6 definieert drie groepen kleurruimten: apparaatruimten (DeviceGray, DeviceRGB, DeviceCMYK), op CIE gebaseerde ruimten (CalGray, CalRGB, Lab, ICCBased) en speciale ruimten (Indexed, Separation, DeviceN, Pattern). Alles buiten de apparaatgroep wordt geselecteerd met de generieke operatoren: cs en CS selecteren een ruimte op naam uit het paginabronnenwoordenboek, waarna sc, SC, scn en SCN de componentwaarden leveren. Een renderer die deze operatoren overslaat, behoudt de laatst ingestelde kleur, wat voor een pagina die begint met een kop in spotkleur de initiële DeviceGray-zwartkleur is
HotPDF heeft de generieke operatorenset in v2.333.0 aan haar paginarenderer toegevoegd, samen met een uniform resolutiepad: elke vermelding van een /ColorSpace-bron, of het nu een kale naam, een inline array of een indirecte verwijzing is, wordt geanalyseerd in één THPDFColorSpace-record, en elk verzoek voor een vul- of omlijningskleur wordt doorgeleid via een enkele HPDFResolveColor-aanroep. De familieopsomming toont de dekking in één oogopslag
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;
Eén ontwerpbeslissing betaalt zichzelf herhaaldelijk terug: csfUnsupported is een volwaardige familie, geen fout. Een ruimte die de renderer niet kan interpreteren, valt terug op een gedefinieerde fallback in plaats van dat de pagina mislukt, wat overeenkomt met het gedrag van reguliere viewers en voorkomt dat een enkele exotische opvulling een verder renderbaar document onleesbaar maakt
Hoe zet een tinttransformatie een inktwaarde om in een echte kleur?
Een Separation-kleurruimte bevat drie stukken informatie: de inktnaam, een alternatieve kleurruimte en een tinttransformatiefunctie. De array [/Separation /PANTONE485 /DeviceCMYK f] zegt: wanneer de inhoudsstroom 0.8 scn schrijft, voer dan de tintwaarde 0.8 in functie f in en schilder het resulterende CMYK-viertal. DeviceN generaliseert dit naar N inkten met een functie met N invoeren. De inktnaam zelf is op het scherm alleen ter indicatie; de tinttransformatie vormt de gehele renderingssemantiek, dus een renderer die de ruimte analyseert maar de functie overslaat, heeft nog niets nuttigs gedaan
HPDFEvalTintTransform is de functie-engine achter die stap. Geïntroduceerd in v2.334.0 evalueert het Type 2 exponentiële functies (C0 + x^N * (C1 - C0) met inperking van Domain en Range), Type 3 stitching-functies (op Bounds geselecteerde subfunctierecursie met Encode-herkoppeling) en Type 0 sampled-functies met 8-, 16- en 32-bits samples. Type 0 is hetzelfde opzoektabelmechanisme dat we vanuit de creatiezijde hebben behandeld in het artikel over het bouwen van Type 0-kleuren-LUT's; de renderingszijde doorloopt exact dezelfde structuur in omgekeerde volgorde, van gedecodeerde sample-bytes terug naar componentwaarden
Type 4 PostScript-calculatorfuncties waren de laatste hindernis. Tot v2.375.0 vielen ze terug op een neutrale tijdelijke aanduiding; sinds v2.375.0 voert HPDFEvalPostScriptCalculator de volledige ISO 32000-1 §7.10.5 operatorenset uit op een begrensde operandenstapel: rekenkundige, vergelijkings-, booleaanse en bitwise operatoren, stapelmanipulatie inclusief roll en if/ifelse-voorwaarden. De specifieke semantiek is strenger dan het lijkt. PostScript-round rondt de helft af naar de grotere waarde, dus Delphi's banker's-rounding Round kan niet worden gebruikt; de trigonometrische operatoren werken in graden, waarbij atan waarden retourneert in [0, 360); en exp is een machtsverheffing met twee operanden, niet de natuurlijke e-macht. Dezelfde evaluator drijft ook op functies gebaseerde verloop-invullingen aan, wat de reden is waarom axiale en radiale verloop-renderings in dezelfde release calculator-gestuurde overgangen kregen
// 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;
Eerlijkheid over precisie: een softwarecalculator die in dubbele precisie evalueert, komt niet bit voor bit overeen met een RIP, en afronding aan de Range-grens kan een minst significant bit afwijken van een andere implementatie. Voor schermweergave en regressie-renderings is dat irrelevant; als u kleurbeheerd proefdrukken bouwt, is de tinttransformatie slechts de eerste stap en hebt u stroomafwaarts een echte CMM nodig
CalGray, CalRGB, Lab en ICCBased zonder een ICC-engine
De op CIE gebaseerde families nemen de andere vertakking van dezelfde resolver. HotPDF converteert Lab-waarden via de standaard Lab naar XYZ naar sRGB-keten, inclusief de 6/29 breekpuntkubus in de inverse overdrachtsfunctie, en handelt CalRGB af met haar gamma per kanaal plus 3x3 lineaire matrix, en CalGray met haar enkele gamma. Het prestatiegerelateerde detail is de witpuntafhandeling: de XYZ-naar-sRGB-conversiematrix is via Bradford-aanpassing aangepast aan het witpunt dat in de kleurruimte is gedeclareerd en wordt opgeslagen in het resolved THPDFColorSpace-record, zodat het werk per pixel beperkt blijft tot een enkele 3x3-vermenigvuldiging, ongeacht hoe exotisch de gedeclareerde lichtbron is
ICCBased-ruimten krijgen een bewust pragmatische behandeling. De PDF-specificatie vereist dat elke ICCBased-stroom een alternatieve ruimte /Alternate declareert of een geïmpliceerde ruimte via het componentenaantal /N, juist zodat viewers zonder kleurbeheer-engine toch verstandig kunnen renderen. HotPDF lost ICCBased op via dat alternatief, of raadt DeviceGray, DeviceRGB of DeviceCMYK op basis van /N (1, 3 of 4) wanneer de vermelding afwezig is, en analyseert de profielbytes nooit. Dat betekent dat er geen lcms-afhankelijkheid is en geen kosten voor het opzoeken van profielen, ten koste van de colorimetrische nauwkeurigheid: een ICCBased-ruimte waarvan het profiel sterk afwijkt van het alternatief, toont de alternatieve rendering. Voor weergave op het scherm en miniaturen is dit dezelfde afweging die elke lichtgewicht viewer maakt, en dat is de grens die u duidelijk in uw eigen documentatie moet vermelden
De bug die ervoor zorgde dat elke zoekactie naar een benoemde kleurruimte mislukte
De operatoren-plumbing uit v2.333.0 werd geleverd met een defect dat tweeënveertig releases lang onzichtbaar bleef: de handlers voor cs en CS zochten hun operand op met het leidende scheidingsteken er nog aan (/CS0), terwijl de sleutels in het bronnenwoordenboek zonder scheidingsteken waren opgeslagen (CS0). Elke zoekactie naar een benoemde kleurruimte mislukte, honderd procent van de tijd, en de code viel in stilte terug op het standaard DeviceGray. Het zichtbare symptoom was op de meest subtiele manier misleidend: een Separation-vulling van 1 scn werd DeviceGray 1.0, wat wit schildert, en witte inkt op een witte pagina is geen renderingsfout waarvan iemand een screenshot maakt. De oplossing, in v2.375.0, was een gedeelde helper voor naamnormalisatie die bij elke zoekactie van operand naar bron wordt toegepast
Twee gerelateerde defecten kwamen voort uit hetzelfde onderzoek. Ten eerste werden indirecte verwijzingen naar objecten met array-waarden onopgelost geretourneerd: de documenttoegang van de renderer had alleen getypeerde resolvers voor stromen en woordenboeken, dus /CS0 5 0 R die verwees naar een losstaande array [/Separation ...] kwam terug als de ruwe verwijdering en de ruimte werd geanalyseerd als niet-ondersteund. Ten tweede handhaafde HPDFReadNumericArray een strikte lengtesemantiek, waarbij de PDF-array minstens zo lang moest zijn als de geleverde buffer. Het lezen van de /C0 en /C1 van een Type 2-functie in een buffer van vier elementen mislukte daardoor voor alternatieven met één en drie componenten, waardoor beide arrays op nul bleven staan, en elke niet-CMYK exponentiële tint zwart rendeerde totdat v2.376.0 de tolerante lezer HPDFReadNumericArrayUpTo introduceerde. De overdraagbare les: elke PDF-sleutel waarvan gedocumenteerd is dat deze een numerieke array van variabele lengte bevat, moet worden gelezen met een lezer die vult wat bestaat, omdat een buffer van vaste grootte met strikte controle geldige bestanden in stille nullen verandert
Hoe test u spotkleur-renderings zonder uzelf voor de gek te houden?
De ongemakkelijke vraag is waarom de testsuite gedurende dit alles groen bleef. De oorspronkelijke regressietest, SeparationRendersDistinguishable, controleerde alleen of de gerenderde bitmap niet volledig zwart was. Een pijplijn die elke spotkleur liet terugvallen op DeviceGray produceerde grijze en witte uitvoer, wat niet zwart is, dus de bewering slaagde terwijl de hele functie in feite niet werkte. Zwakke beweringen van de vorm "niet blanco", "niet helemaal zwart" of "de hash is niet nul" kunnen een werkende renderer niet onderscheiden van een kapotte, omdat bijna elke foutmodus nog steeds pixels genereert
De stijl van beweringen die deze fouten daadwerkelijk onderschept, legt de verwachte kleur vast: render een handgemaakte, minimale PDF waarvan de Separation-inkt resulteert in een bekende tint, en tel vervolgens de pixels die dominant zijn in die tint. Het renderen naar een bitmap voor inspectie maakt gebruik van hetzelfde ingangspunt RenderLoadedPageToBitmap dat is beschreven in de handleiding voor pagina-naar-bitmap-renderings
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;
De testopstelling (fixture) is even belangrijk als de bewering. Een handgemaakt PDF-bestand van een paar honderd bytes met één Separation-vulling en niets anders laat geen twijfel bestaan over wat de verwachte uitvoer moet zijn; een bestand uit de praktijk test meer code, maar kan u niet vertellen welke stap is mislukt. We beschouwen het tellen van pixels met de verwachte kleur nu als de absolute ondergrens voor elke rooktest (smoke test) voor renderings, omdat dit de enige stijl van beweringen is die deze defecten aan het licht heeft gebracht
Het renderen van spotkleuren en CIE kleurruimten maakt deel uit van de pijplijn voor geladen documenten in het HotPDF Component voor Delphi en C++Builder, samen met de functie-engine, de verloop-rendering en de exportpaden naar bitmaps die hierboven zijn getoond