PDFium Component skaber axiale og radiale PDF-gradients i Delphi gennem TPdf.SaveAsShadingCreated. Du beskriver hver gradient i en TPdfShadingCreate-post (type, color space, koordinater, farver eller stops, målside), og metoden skriver en verificeret kopi, hvor hver fyld er en ægte ISO 32000-1 shading-dictionary, registreret i sidens /Resources /Shading-dictionary og malet af sh-operatoren, så PDFium renderer den nativt
Behovet viser sig i rapport- og certifikatgeneratorer. En designer afleverer en mockup med et blødt blåt-til-hvidt headerbånd eller en radial glød bag et segl, og page-object-API'en, der dækker tekst, stier og billeder, har intet til det. Du kan fake en gradient med hundrede tynde rektangler i let forskellige farver, men resultatet bander synligt ved indzoomning, oppuster content streamen og printer dårligt. PDF har haft bløde shadings siden 1.3; arbejdet består i at skrive dem, så en renderer faktisk tegner dem
Hvorfor skriver PDFium Component gradients som en separat gemning?
TPdf.SaveAsShadingCreated er en gem-operation snarere end et page-object-kald, fordi PDFium's offentlige ABI ingen funktion har, der skaber en shading-dictionary eller udsender en sh-operator. Det page-object-lag, der bruges ved at skabe PDF-filer fra bunden med PDFium Component, bygger stier, tekst og billeder, og dér stopper det. Shadings lever ét niveau lavere, i objektgrafen og content stream-syntaksen
Så metoden arbejder i stadier. PDFium gemmer først det åbne dokument nativt, hvilket bevarer hver redigering, du lavede gennem den normale API. PDFium Component omskriver derefter det scenariserede output: den allokerer ét indirekte objekt pr. shading, tilføjer resource-indgangen til hver målside, tilføjer en ny content stream til sidens /Contents og skriver en frisk cross-reference-tabel. Før noget når din stream eller fil, genindlæses resultatet med et strengt cross-reference-tjek, og metoden returnerer False med en besked i rapporten, hvis den genindlæsning fejler. Fil-overloaden skriver først til en midlertidig fil, så en mislykket gemning ikke ødelægger et eksisterende mål
At bygge en tofarvet axial gradient
En axial shading (ISO 32000-1 §8.7.4.5.3, Type 2) blander farver langs en linje fra x0 y0 til x1 y1, så Coords tager fire værdier. En radial shading (§8.7.4.5.4, Type 3) blander mellem to cirkler og tager seks: x0 y0 r0 x1 y1 r1. Med almindelige StartColor og EndColor udsender PDFium Component en Type 2 eksponentiel funktion (§7.10.3) med /N 1, hvilket er en lige linjeinterpolation, og sætter /Extend [true true], så slutfarverne fortsætter forbi begge ender af aksen
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, top til bund
Fills[0].StartColor := [0.12, 0.35, 0.75]; // mørkeblå ved den øverste kant
Fills[0].EndColor := [1, 1, 1]; // hvid længere nede
Fills[0].PageNumber := 1; // 1-baseret
Fills[0].ResourceName := 'HeaderBand'; // valgfri, default er Sh1, Sh2...
if not Pdf.SaveAsShadingCreated(OutFile, Fills, Report) then
raise Exception.Create('Shading creation failed: ' + Report.ErrorMessage);
finally
Pdf.Free;
end;
end;
Komponentantallet for hver farve skal matche color space: én for pscDeviceGray og pscCalGray, tre for pscDeviceRGB, pscCalRGB og pscLab, fire for pscDeviceCMYK. For pscICCBased kommer antallet fra de profilbytes, du giver med i IccProfile, som skal eksponere 1, 3 eller 4 komponenter; profilen indlejres som sin egen ICC-stream med matchende /N. Kalibrerede spaces accepterer valgfri CalWhitePoint (tre værdier, positiv Y), CalGamma (én værdi for CalGray, tre for CalRGB) og, for Lab, en seksværdi LabRange. Hver koordinat og komponent tjekkes for NaN og uendelig, før en byte skrives
Bemærk, at koordinater er i default user space, med origo i sidens nederste venstre hjørne. Husk, at aksen kun definerer, hvor blandingen sker: det malede område er hvad end clip'et tillader, hvilket betyder mere, end det lyder, som de næste afsnit viser
Multi-stop-gradients kompilerer til en Type 3 stitching-funktion
PDFium Component omsætter et Stops-array til den funktion, en PDF-reader forventer: to stops bliver til én enkelt Type 2-funktion, og 3 til 256 stops bliver til en Type 3 stitching-funktion (ISO 32000-1 §7.10.4) bestående af ét lineært Type 2-segment pr. nabopar. Hver TPdfShadingCreateStop bærer en Position og en Color. Er Stops ikke tom, erstatter den StartColor og EndColor fuldstændig
Stitching-funktionen behøver tre arrays, der er lette at få subtilt forkert i hånden. /Functions rummer segmenterne, /Bounds rummer kun de indre stoppositioner (k segmenter behøver k-1 bounds), og /Encode mapper hvert subdomæne tilbage til [0 1], så hvert segment ser sit fulde inputinterval. Komponenten udleder alle tre af dine stops, hvilket er grunden til, at den er streng med inputtet: positioner skal være endelige, strengt stigende, starte ved 0 og ende ved 1 (inden for 1e-6). En stopliste, der starter ved 0,1, ville efterlade en del af domænet uden en defineret funktion, og metoden afviser den i stedet for at gætte
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: et punkt i seglets center voksende til 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;
For en tre-stop-gradient producerer det /Functions med to Type 2-segmenter, /Bounds [0.4] og /Encode [0 1 0 1]. Segmentgrænserne er hårde farvesamlinger, så en stopliste udjævner ikke hen over dem; ser en samling for skarp ud, så tilføj stops frem for at forvente, at readeren interpolerer krumning
Hvorfor tegner PDFium stille intet?
PDFium renderer en nyoprettet shading kun, når to detaljer er rigtige: /ShadingType skal være et heltal, og sh-kaldet skal ligge inde i en clipping path. Få bare én af dem forkert, og filen åbner, validerer strukturelt og viser en urørt side. Ingen fejl, ingen advarsel, ingen loglinje
Fælde én: /ShadingType er et tal, ikke et navn
ISO 32000-1 Tabel 78 definerer /ShadingType som et heltal: 2 for axial, 3 for radial. Den første version af denne writer udsendte /ShadingType /Axial, fordi det læste bedre i en hex-dump. PDFium læser nøglen med en integer-getter, får 0 for et navn, klassificerer shadingen som ugyldig, fejler at loade den og springer sh-operatoren over uden klage. Siden renderede blank, og intet i PDFium sagde hvorfor. Årsagen blev først synlig, efter at en minimal håndskrevet PDF blev bygget og åbnet i en anden, uafhængig renderer, som rapporterede en ukendt shadingtype på 0. Lektionen generaliserer til enhver PDF-writer: når specifikationen typer en enumeration som et heltal, så skriv heltallet, hvor fristende et selvbeskrivende navn end ser ud
Fælde to: en content stream med kun sh har ingen bounding box
sh-operatoren (ISO 32000-1 §8.7.4.2) maler shadingen hen over den aktuelle clipping-region, så sidens egen udstrækning afgrænser den på papir. PDFium bygger dog et shading page-objekt, hvis rektangel kommer enten fra den aktive clip-sti eller fra den bounding box, tidligere tegneoperatorer i samme stream har akkumuleret, og rendereren kuller objekter med et tomt rektangel. En frisk tilføjet stream, der ikke rummer andet end /Sh1 sh, har intet af det, så gradienten droppes før rasteriseringen. TPdf.SaveAsShadingCreated læser derfor hver sides crop box (med fallback til media box og derefter sidestørrelsen) og pakker hver invokation ind i et eksplicit re W n-clip. På en US Letter-side med origo ved nul ser den tilføjede stream sådan ud:
q
0 0 612 792 re W n
/HeaderBand sh
Q
q/Q-parret isolerer clip'et og enhver transform fra det eksisterende sideindhold, så intet tegnet senere på siden arver dem. Skriver du shading-invokationer selv i et andet værktøj, så kopier denne form: et eksplicit clip i samme stream er billigt og fjerner afhængigheden af, hvad end der kom før
Hvordan interagerer en custom gradientmatrix med clip'et?
En gradientmatrix i PDFium Component anvendes som en cm-operator placeret efter clip'et og før sh, så clip'et forbliver fuld side, mens kun gradientgeometrien transformeres. Sæt HasMatrix til True og udfyld MatrixA gennem MatrixF, de samme seks tal som enhver PDF-transformationmatrix. Værdierne skal være endelige, og determinanten ikke-nul, fordi en singulær matrix ville kollapse shadingen ned på en linje. Komponenten registrerer også de seks værdier som /Matrix i shading-dictionaryen, men ved et direkte sh-kald er det cm'en i content streamen, der sætter det koordinatsystem, PDFium maler i
Rækkefølgen betyder noget her, og det er tredje fælde. re-operanderne fortolkes i den aktuelle transformationmatrix. Sæt cm'en først, og sidestørrelses-clip-rektanglet skaleres, roteres eller skævvrides med gradienten, så en matrix, der flader en ellipse ud, også flader det synlige område ud, og dele af siden mister deres fyld. At clippe først i utransformeret user space og derefter konkatenerer matrixen giver det forventede resultat: en transformeret gradient, der dækker hele siden. Guiden til Form XObject-stempler og deres sekstalsmatrix gennemgår, hvad hver matrixkomponent gør, hvis du skal bygge én
procedure MakeEllipticalGlow(var Fill: TPdfShadingCreate);
begin
BuildSealGlow(Fill); // radial fyld centreret ved 450,160
Fill.Coords := [0, 0, 0, 0, 0, 90]; // cirkler i origo
Fill.HasMatrix := True;
Fill.MatrixA := 1.6; // stræk vandret
Fill.MatrixB := 0;
Fill.MatrixC := 0;
Fill.MatrixD := 0.8; // komprimér lodret
Fill.MatrixE := 450; // flyt derefter til seglets center
Fill.MatrixF := 160;
end;
At læse rapporten og kende grænserne
TPdfShadingCreateReport fortæller dig, hvad der blev skrevet: SourceByteCount og OutputByteCount, CreatedShadingObjectNumbers (ét objektnummer pr. inputindgang, i rækkefølge), AttachedShadingCount, Succeeded og ErrorMessage. Valideringsfejl navngiver indgangen og feltet, for eksempel en stopliste, der ikke er strengt stigende, så de er værd at logge ordret
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 er bevidst afgrænset. Den skaber kun axiale og radiale shadings; funktionsbaserede og mesh-shadings (Type 1 og 4 til 7) og pattern color spaces afvises. Ét kald accepterer 1 til 4096 fyld, sidenumre skal eksistere, og resource-navne er begrænset til 127 tegn af bogstaver, cifre, underscore, minus og punktum. Krypteret input afvises ved trailer-stadiet. Et signeret dokument behøver AllowSignedDocument := True, og da outputtet er en fuldstændig omskrivning frem for en trinvis opdatering, validerer eksisterende signaturer ikke længere, så signér efter tilføjelse af gradients. Hvert fyld males til sidst i sideindholdet, oven på det, der allerede er der; til en baggrundseffekt, skab gradienten først og tilføj tekst og billeder bagefter, eller brug optional content som beskrevet i artiklen om at styre PDF optional content layers i Delphi, når fyldet skal kunne slås til og fra
FAQ
Understøtter SaveAsShadingCreated multi-stop-gradients?
Ja. Giv 2 til 256 indgange med i Stops med positioner strengt stigende fra 0 til 1. To stops producerer en Type 2-funktion, og tre eller flere producerer en Type 3 stitching-funktion med udledte /Bounds- og /Encode-arrays
Hvorfor er min gradient usynlig i PDFium, når filen ser gyldig ud?
Tjek at /ShadingType er heltallet 2 eller 3 frem for et navn, og at sh-operatoren kører inde i et re W n-clip i samme content stream. PDFium springer ugyldige shadingtyper stille over og kuller shading-objekter med et tomt rektangel
Hvilke color spaces kan en skabt shading bruge?
TPdfShadingCreate accepterer DeviceGray, DeviceRGB, DeviceCMYK, CalGray, CalRGB, Lab og ICCBased med en 1-, 3- eller 4-komponentprofil. Pattern- og Indexed color spaces er ikke en del af skabelsesprofilen
Gradientskabelse, optional content og resten af de scenariserede writers følger med PDFium Component for Delphi og C++Builder, som wrapper PDFium til Delphi-, C++Builder- og Lazarus-projekter