PDFium Component skapar axiala och radiella PDF-gradianter i Delphi via TPdf.SaveAsShadingCreated. Du beskriver varje gradient i en TPdfShadingCreate-post (typ, färgutrymme, koordinater, färger eller stopp, målsida), och metoden skriver en verifierad kopia där varje fyllning är en äkta ISO 32000-1 shading-ordbok, registrerad i sidans /Resources /Shading-ordbok och målad av operatorn sh, så att PDFium renderar den nativt
Behovet dyker upp i rapport- och certifikatgeneratorer. En formgivare lämnar över en mockup med ett mjukt blått-till-vitt-sidhuvudband eller en radiell glöd bakom ett sigill, och sidobjekts-API:et som täcker text, banor och bilder har ingenting för det. Du kan fejka en gradient med hundra tunna rektanglar i något olika färger, men resultatet bandar synligt vid inzoomning, blåser upp innehållsströmmen och skriver ut dåligt. PDF har haft mjuka shadings sedan 1.3; arbetet ligger i att skriva dem så att en renderare faktiskt ritar dem
Varför skriver PDFium Component gradianter som ett separat sparande?
TPdf.SaveAsShadingCreated är en sparoperation snarare än ett sidobjektsanrop eftersom PDFium:s publika ABI saknar en funktion som skapar en shading-ordbok eller sänder ut en sh-operator. Sidobjektslagret som används vid att skapa PDF:er från grunden med PDFium Component bygger banor, text och bilder, och där stannar det. Shadings bor en nivå lägre, i objektgrafen och innehållsströmmens syntax
Så metoden arbetar i steg. PDFium sparar först det öppna dokumentet nativt, vilket behåller varje redigering du gjort via det normala API:et. PDFium Component skriver sedan om det mellanlagrade resultatet: den allokerar ett indirekt objekt per shading, lägger till resursposten på varje målsida, fogar en ny innehållsström till sidans /Contents, och skriver en färsk korsreferenstabell. Innan något når din ström eller fil laddas resultatet om med en strikt korsreferenskontroll, och metoden returnerar False med ett meddelande i rapporten om den omläsningen misslyckas. Filoverloaden skriver till en temporär fil först, så ett misslyckat sparande förstör inte ett befintligt mål
Att bygga en tvåfärgad axial gradient
En axial shading (ISO 32000-1 §8.7.4.5.3, Type 2) blandar färger längs en linje från x0 y0 till x1 y1, så Coords tar fyra värden. En radiell shading (§8.7.4.5.4, Type 3) blandar mellan två cirklar och tar sex: x0 y0 r0 x1 y1 r1. Med vanliga StartColor och EndColor sänder PDFium Component ut en Type 2-exponentialfunktion (§7.10.3) med /N 1, vilket är en rak linjär interpolation, och sätter /Extend [true true] så att slutfärgerna fortsätter förbi båda ändarna av axeln
uses
System.SysUtils, PDFium, FPdfCompress;
procedure AddHeaderBand(const InFile, OutFile: string);
var
Pdf: TPdf;
Fills: TPdfShadingCreateShadings;
Report: TPdfShadingCreateReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := InFile;
Pdf.Active := True;
SetLength(Fills, 1);
Fills[0] := Default(TPdfShadingCreate);
Fills[0].ShadingType := pstAxial;
Fills[0].ColorSpace := pscDeviceRGB;
Fills[0].Coords := [0, 792, 0, 640]; // x0 y0 x1 y1, uppifrån och ner
Fills[0].StartColor := [0.12, 0.35, 0.75]; // mörkblå vid överkanten
Fills[0].EndColor := [1, 1, 1]; // vitt längre ner
Fills[0].PageNumber := 1; // 1-baserat
Fills[0].ResourceName := 'HeaderBand'; // valfritt, standard är Sh1, Sh2...
if not Pdf.SaveAsShadingCreated(OutFile, Fills, Report) then
raise Exception.Create('Shading creation failed: ' + Report.ErrorMessage);
finally
Pdf.Free;
end;
end;
Komponentantalet hos varje färg måste matcha färgutrymmet: en för pscDeviceGray och pscCalGray, tre för pscDeviceRGB, pscCalRGB och pscLab, fyra för pscDeviceCMYK. För pscICCBased kommer antalet från profilbytena du skickar i IccProfile, som måste exponera 1, 3 eller 4 komponenter; profilen bäddas in som sin egen ICC-ström med matchande /N. Kalibrerade utrymmen accepterar valfria CalWhitePoint (tre värden, positivt Y), CalGamma (ett värde för CalGray, tre för CalRGB) och, för Lab, en sexvärdes LabRange. Varje koordinat och komponent kontrolleras för NaN och oändlighet innan en byte skrivs
Notera att koordinaterna ligger i standardanvändarutrymme, med origo i sidans nedre vänstra hörn. Kom ihåg att axeln bara definierar var blandningen sker: det målade området är vad klippet tillåter, vilket spelar större roll än det låter, som nästa avsnitt visar
Flerstopps-gradianter kompileras till en Type 3-stitching-funktion
PDFium Component gör om en Stops-array till funktionen en PDF-läsare förväntar sig: två stopp blir en enda Type 2-funktion, och 3 till 256 stopp blir en Type 3-stitching-funktion (ISO 32000-1 §7.10.4) byggd av ett linjärt Type 2-segment per intilliggande par. Varje TPdfShadingCreateStop bär en Position och en Color. När Stops inte är tomt ersätter det StartColor och EndColor helt
Stitching-funktionen behöver tre arrayer som är lätta att få subtilt fel för hand. /Functions håller segmenten, /Bounds håller bara de inre stoppositionerna (k segment behöver k-1-gränser), och /Encode mappar varje subdomän tillbaka till [0 1] så att varje segment ser hela sitt indatavidd. Komponenten härleder alla tre från dina stopp, vilket är varför den är strikt mot indatan: positioner måste vara ändliga, strikt stigande, börja på 0 och sluta på 1 (inom 1e-6). En stopplista som börjar på 0.1 skulle lämna en del av domänen utan definierad funktion, och metoden avvisar den i stället för att gissa
function Stop(Position: Double; const Rgb: array of Double): TPdfShadingCreateStop;
var
I: Integer;
begin
Result.Position := Position;
SetLength(Result.Color, Length(Rgb));
for I := 0 to High(Rgb) do
Result.Color[I] := Rgb[I];
end;
procedure BuildSealGlow(var Fill: TPdfShadingCreate);
begin
Fill := Default(TPdfShadingCreate);
Fill.ShadingType := pstRadial;
Fill.ColorSpace := pscDeviceRGB;
// x0 y0 r0 x1 y1 r1: en punkt i sigillcentrum som växer till en 90 pt-cirkel
Fill.Coords := [450, 160, 0, 450, 160, 90];
Fill.Stops := [Stop(0.0, [1.00, 0.95, 0.70]),
Stop(0.4, [0.95, 0.75, 0.30]),
Stop(1.0, [1.00, 1.00, 1.00])];
Fill.PageNumber := 1;
end;
För en trestopps-gradient ger det /Functions med två Type 2-segment, /Bounds [0.4] och /Encode [0 1 0 1]. Segmentgränserna är hårda färgsammanfogningar, så en stopplista jämnar inte ut över dem; ser en sammanfogning för skarp ut, lägg till stopp i stället för att förvänta dig att läsaren interpolerar krökning
Varför ritar PDFium tyst ingenting?
PDFium renderar en nyskapad shading bara när två detaljer är rätt: /ShadingType måste vara ett heltal, och sh-anropet måste sitta inne i en klippbana. Får du fel på den ena öppnas filen, validerar strukturellt och visar en orörd sida. Inget fel, ingen varning, ingen loggrad
Fälla ett: /ShadingType är ett tal, inte ett namn
ISO 32000-1 tabell 78 definierar /ShadingType som ett heltal: 2 för axial, 3 för radiell. Den första versionen av den här skrivaren sände ut /ShadingType /Axial eftersom det läste bättre i en hexdump. PDFium läser nyckeln med en heltalsgetter, får 0 för ett namn, klassar shadingen som ogiltig, misslyckas att läsa in den och hoppar över sh-operatorn utan klagan. Sidan renderades blank och ingenting i PDFium sa varför. Orsaken blev synlig först efter att en minimal handskriven PDF byggts och öppnats i en andra, oberoende renderare, som rapporterade en okänd shading-typ 0. Lärdomen generaliseras till varje PDF-skrivare: när specifikationen typar en uppräkning som heltal, skriv heltalet, hur frestande ett självbeskrivande namn än ser ut
Fälla två: en innehållsström som bara håller sh har ingen begränsningsruta
Operatorn sh (ISO 32000-1 §8.7.4.2) målar shadingen över det aktuella klippområdet, så sidans egen utsträckning begränsar den på papper. PDFium bygger dock ett shading-sidobjekt vars rektangel kommer antingen från den aktiva klippbanan eller från begränsningsrutan som ackumulerats av tidigare ritaoperatorer i samma ström, och renderaren gallrar objekt med en tom rektangel. En färsk tillfogad ström som inte innehåller något utöver /Sh1 sh har inget av dem, så gradienten kastas bort före rastreringen. TPdf.SaveAsShadingCreated läser därför varje sidas crop-ruta (med fall tillbaka på mediaboxen, sedan sidstorleken) och wrappar varje anrop i ett explicit re W n-klipp. På en US Letter-sida med origo i noll ser den tillfogade strömmen ut så här:
q
0 0 612 792 re W n
/HeaderBand sh
Q
Paret q/Q isolerar klippet och eventuell transform från det befintliga sidinnehållet, så ingenting som ritas senare på sidan ärver dem. Skriver du shading-anrop själv i ett annat verktyg, kopiera den formen: ett explicit klipp i samma ström är billigt och tar bort beroendet av vad som kom före
Hur samspelar en anpassad gradientmatris med klippet?
En gradientmatris i PDFium Component appliceras som en cm-operator placerad efter klippet och före sh, så klippet förblir helsida medan bara gradientgeometrin transformeras. Sätt HasMatrix till True och fyll MatrixA till MatrixF, samma sex tal som vilken PDF-transformationsmatris som helst. Värdena måste vara ändliga och determinanten skild från noll, eftersom en singulär matris skulle kollapsa shadingen på en linje. Komponenten registrerar också de sex värdena som /Matrix i shading-ordboken, men för ett direkt sh-anrop är det cm:en i innehållsströmmen som sätter det koordinatsystem PDFium målar i
Ordningen spelar roll här, och det är den tredje fällan. Operanderna re tolkas i den aktuella transformationsmatrisen. Lägg cm:en först och den sidstor-klipprektangeln skalas, roteras eller skjuvas tillsammans med gradienten, så en matris som plattar till en ellips plattar också till det synliga området och delar av sidan tappar sin fyllning. Att klippa först i otransformerat användarutrymme och sedan konkatenera matrisen ger förväntat resultat: en transformerad gradient som täcker hela sidan. Guiden till Form XObject-stämplar och deras sexsiffriga matris går igenom vad varje matriskomponent gör om du behöver bygga en
procedure MakeEllipticalGlow(var Fill: TPdfShadingCreate);
begin
BuildSealGlow(Fill); // radiell fyllning centrerad på 450,160
Fill.Coords := [0, 0, 0, 0, 0, 90]; // cirklar i origo
Fill.HasMatrix := True;
Fill.MatrixA := 1.6; // sträck horisontellt
Fill.MatrixB := 0;
Fill.MatrixC := 0;
Fill.MatrixD := 0.8; // komprimera vertikalt
Fill.MatrixE := 450; // flytta sedan till sigillcentrum
Fill.MatrixF := 160;
end;
Att läsa rapporten och känna gränserna
TPdfShadingCreateReport talar om vad som skrevs: SourceByteCount och OutputByteCount, CreatedShadingObjectNumbers (ett objektnummer per indatapost, i ordning), AttachedShadingCount, Succeeded och ErrorMessage. Valideringsfel namnger posten och fältet, till exempel en stopplista som inte är strikt stigande, så de är värda att logga ordagrant
if Pdf.SaveAsShadingCreated(OutFile, Fills, Report) then
Writeln(Format('%d shading(s) attached, %d -> %d bytes',
[Report.AttachedShadingCount, Report.SourceByteCount,
Report.OutputByteCount]))
else
Writeln('Rejected: ' + Report.ErrorMessage);
Profilen är medvetet avgränsad. Den skapar bara axiala och radiella shadings; funktionsbaserade och mesh-shadings (Type 1 och 4 till 7) och mönsterfärgutrymmen avvisas. Ett anrop accepterar 1 till 4096 fyllningar, sidnummer måste finnas, och resursnamn begränsas till 127 tecken av bokstäver, siffror, understrykning, minus och punkt. Krypterad indata vägras vid trailersteget. Ett signerat dokument behöver AllowSignedDocument := True, och eftersom utdata är en fullständig omskrivning snarare än en inkrementell uppdatering kommer befintliga signaturer inte längre att validera, så signera efter att du lagt till gradienter. Varje fyllning målas i slutet av sidinnehållet, ovanpå det som redan finns där; för en bakgrundseffekt, skapa gradienten först och lägg till text och bilder efteråt, eller använd optional content som beskrivs i artikeln om att styra PDF optional content-lager i Delphi när fyllningen ska kunna växlas
FAQ
Stödjer SaveAsShadingCreated flerstopps-gradianter?
Ja. Skicka 2 till 256 poster i Stops med positioner strikt stigande från 0 till 1. Två stopp ger en Type 2-funktion och tre eller fler ger en Type 3-stitching-funktion med härledda /Bounds- och /Encode-arrayer
Varför är min gradient osynlig i PDFium när filen ser giltig ut?
Kontrollera att /ShadingType är heltalet 2 eller 3 i stället för ett namn, och att operatorn sh körs inne i ett re W n-klipp i samma innehållsström. PDFium hoppar tyst över ogiltiga shading-typer och gallrar shading-objekt med en tom rektangel
Vilka färgutrymmen kan en skapad shading använda?
TPdfShadingCreate accepterar DeviceGray, DeviceRGB, DeviceCMYK, CalGray, CalRGB, Lab och ICCBased med en 1-, 3- eller 4-komponentsprofil. Mönster- och Indexed-färgutrymmen är inte del av skapelseprofilen
Gradientsskapande, optional content och resten av de mellanlagrade skrivarna levereras med PDFium Component for Delphi and C++Builder, som wrappar PDFium för Delphi-, C++Builder- och Lazarus-projekt