PDFium Component vytváří axiální a radiální PDF gradienty v Delphi přes TPdf.SaveAsShadingCreated. Každý gradient popíšete v záznamu TPdfShadingCreate (typ, barevný prostor, souřadnice, barvy nebo zastávky, cílová stránka) a metoda zapíše ověřenou kopii, ve které je každá výplň skutečným slovníkem shading ISO 32000-1, zaregistrovaným ve slovníku /Resources /Shading stránky a malovaným operátorem sh, takže ho PDFium renderuje nativně
Potřeba se ukazuje u generátorů reportů a certifikátů. Designér předá mock-up s měkkým pásem modro-bílé hlavičky nebo radiální září za pečetí a page-object API, které pokrývá text, cesty a obrázky, na ně nemá nic. Gradient si můžete předstírat stovkou tenkých obdélníků v mírně odlišných barvách, ale výsledek se při zoomu viditelně pásuje, nafukuje content stream a špatně se tiskne. PDF má hladké shadings od 1.3; práce je v tom zapsat je tak, aby je renderer skutečně nakreslil
Proč píše PDFium Component gradienty jako samostatné uložení?
TPdf.SaveAsShadingCreated je uložovací operace místo volání page-object, protože veřejné ABI PDFium nemá žádnou funkci, která by vytvořila slovník shading nebo vyslala operátor sh. Vrstva page-object používaná při tvorbě PDF od nuly s PDFium Component staví cesty, text a obrázky a tam končí. Shadings žijí o úroveň níž, v grafu objektů a syntaxi content streamu
Metoda proto pracuje po etapách. PDFium nejdřív nativně uloží otevřený dokument, což zachová každou editaci, kterou jste udělali normálním API. PDFium Component pak přepíše ten připravený výstup: alokuje jeden indirect objekt na shading, přidá resource položku na každou cílovou stránku, připojí nový content stream k /Contents stránky a zapíše čerstvou cross-reference tabulku. Než cokoli dosáhne vašeho streamu nebo souboru, výsledek se znovu načte s přísnou kontrolou cross-reference a metoda vrátí False se zprávou v reportu, pokud to načtení selže. Overload souboru píše nejdřív do dočasného souboru, takže selhané uložení nepřepíše existující cíl
Stavba dvoubarevného axiálního gradientu
Axiální shading (ISO 32000-1 §8.7.4.5.3, Type 2) míchá barvy podél linie z x0 y0 do x1 y1, takže Coords bere čtyři hodnoty. Radiální shading (§8.7.4.5.4, Type 3) míchá mezi dvěma kruhy a bere šest: x0 y0 r0 x1 y1 r1. S prostými StartColor a EndColor vysílá PDFium Component exponenciální funkci Type 2 (§7.10.3) s /N 1, což je přímá lineární interpolace, a nastavuje /Extend [true true], takže koncové barvy pokračují za oběma konci osy
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, shora dolů
Fills[0].StartColor := [0.12, 0.35, 0.75]; // tmavě modrá u horní hrany
Fills[0].EndColor := [1, 1, 1]; // bílá níž
Fills[0].PageNumber := 1; // 1-based
Fills[0].ResourceName := 'HeaderBand'; // volitelné, default je Sh1, Sh2...
if not Pdf.SaveAsShadingCreated(OutFile, Fills, Report) then
raise Exception.Create('Shading creation failed: ' + Report.ErrorMessage);
finally
Pdf.Free;
end;
end;
Počet složek každé barvy musí odpovídat barevnému prostoru: jedna pro pscDeviceGray a pscCalGray, tři pro pscDeviceRGB, pscCalRGB a pscLab, čtyři pro pscDeviceCMYK. Pro pscICCBased pochází počet z bajtů profilu, které předáte v IccProfile, které musí vystavovat 1, 3 nebo 4 složky; profil se embedduje jako vlastní ICC stream s odpovídajícím /N. Kalibrované prostory přijímají volitelné CalWhitePoint (tři hodnoty, kladné Y), CalGamma (jedna hodnota pro CalGray, tři pro CalRGB) a pro Lab šestihodnotový LabRange. Každá souřadnice a složka se kontroluje na NaN a nekonečno, než se zapíše jediný bajt
Všimněte si, že souřadnice jsou ve výchozím user space s počátkem vlevo dole na stránce. Mějte na paměti, že osa definuje jen to, kde se míchání děje: malovaná oblast je cokoli, co clip dovolí, což záleží víc, než to zní, jak ukážou další oddíly
Gradienty s více zastávkami se kompilují na stitching funkci Type 3
PDFium Component mění pole Stops na funkci, kterou čtečka PDF očekává: dvě zastávky se stanou jedinou funkcí Type 2 a 3 až 256 zastávek se stane stitching funkcí Type 3 (ISO 32000-1 §7.10.4) z jednoho lineárního segmentu Type 2 na sousední pár. Každý TPdfShadingCreateStop nese Position a Color. Není-li Stops prázdné, zcela nahradí StartColor a EndColor
Stitching funkce potřebuje tři pole, která se ručně snadno pokazí jemně. /Functions drží segmenty, /Bounds drží jen vnitřní pozice zastávek (k segmentů potřebuje k-1 mezí) a /Encode mapuje každý subdomain zpět na [0 1], takže každý segment vidí svůj plný vstupní rozsah. Komponenta všechna tři odvozuje z vašich zastávek, proto je v přísnosti k vstupu: pozice musí být konečné, přísně rostoucí, začínat na 0 a končit na 1 (v rámci 1e-6). Seznam zastávek začínající na 0,1 by nechal část domény bez definované funkce a metoda ho odmítne místo hádání
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: bod ve středu pečetě rostoucí do kruhu 90 pt
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;
Pro třízastávkový gradient to produkuje /Functions se dvěma segmenty Type 2, /Bounds [0.4] a /Encode [0 1 0 1]. Hranice segmentů jsou tvrdé spoje barev, takže seznam zastánek se přes ně nevyhlazuje; vypadá-li spoj moc ostře, přidejte zastávky místo očekávání, že čtečka bude interpolovat zakřivení
Proč PDFium tiše nenakreslí nic?
PDFium renderuje nově vytvořený shading jen tehdy, když jsou správně dva detaily: /ShadingType musí být integer a volání sh musí sedět uvnitř ořezové cesty. Pokazíte-li kterýkoli, soubor se otevře, strukturně zvaliduje a ukáže nedotčenou stránku. Žádná chyba, žádné varování, žádný log
Past jedna: /ShadingType je číslo, ne název
ISO 32000-1 tabulka 78 definuje /ShadingType jako integer: 2 pro axiální, 3 pro radiální. První verze tohoto writeru vysílala /ShadingType /Axial, protože se to lépe četlo v hex dumpu. PDFium čte klíč integer getterem, dostane u názvu 0, klasifikuje shading jako neplatný, selže ho načíst a přeskočí operátor sh bez stížnosti. Stránka se vyrenderovala prázdná a nic v PDFium neřeklo proč. Příčina se stala viditelnou až po postavení minimálního ručně psaného PDF a otevření v druhém, nezávislém rendereru, který nahlásil neznámý shading typ 0. Lekce se zobecňuje na každého writeru PDF: když specifikace typuje enumeraci jako integer, pište integer, jakkoli lákavě vypadá sebepopisný název
Past druhá: content stream držící jen sh nemá žádný bounding box
Operátor sh (ISO 32000-1 §8.7.4.2) maluje shading přes aktuální ořezovou oblast, takže ho na papíře ohraničuje rozsah samotné stránky. PDFium ale staví shading page object, jehož obdélník pochází buď z aktivní cesty clipu, nebo z bounding boxu akumulovaného dřívějšími kreslicími operátory ve stejném streamu, a renderer vyřazuje objekty s prázdným obdélníkem. Čerstvě připojený stream, který neobsahuje nic než /Sh1 sh, nemá ani jedno, takže gradient se zahodí dřív, než dojde na rasterizaci. TPdf.SaveAsShadingCreated proto čte crop box každé stránky (s fallbackem na media box, pak na velikost stránky) a balí každé vyvolání do explicitního clipu re W n. Na stránce US Letter s počátkem v nule vypadá připojený stream takhle:
q
0 0 612 792 re W n
/HeaderBand sh
Q
Pár q/Q izoluje clip a jakoukoli transformaci od existujícího obsahu stránky, takže nic později nakresleného na stránce je nezdědí. Píšete-li vyvolání shadingů sami v jiném nástroji, zkopírujte tento tvar: explicitní clip ve stejném streamu je levný a odstraní závislost na tom, co přišlo před ním
Jak interaguje vlastní matice gradientu s clipem?
Matice gradientu v PDFium Component se aplikuje jako operátor cm umístěný za clipem a před sh, takže clip zůstává celostránkový, zatímco se transformuje jen geometrie gradientu. Nastavte HasMatrix na True a vyplňte MatrixA až MatrixF, stejných šest čísel jako jakákoli transformační matice PDF. Hodnoty musí být konečné a determinant nenulový, protože singulární matice by shládla shading na linii. Komponenta taky zaznamenává šest hodnot jako /Matrix ve slovníku shading, ale u přímého volání sh je to cm v content streamu, které nastavuje souřadný systém, v němž PDFium maluje
Pořadí tu záleží a je to třetí past. Operandi re se interpretují v aktuální transformační matici. Dejte cm první a stránkově velký clip obdélník se škáluje, otočí nebo zkosití spolu s gradientem, takže matice, která smáčkne elipsu, smáčkne i viditelnou oblast a části stránky přijdou o výplň. Ořezat nejdřív v netransformovaném user space a pak připojit matici dává očekávaný výsledek: transformovaný gradient pokrývající celou stránku. Průvodce stampy Form XObject a jejich šestičíselnou maticí projde, co každá složka matice dělá, pokud jednu potřebujete postavit
procedure MakeEllipticalGlow(var Fill: TPdfShadingCreate);
begin
BuildSealGlow(Fill); // radiální výplň centrovaná na 450,160
Fill.Coords := [0, 0, 0, 0, 0, 90]; // kruhy v počátku
Fill.HasMatrix := True;
Fill.MatrixA := 1.6; // natáhni vodorovně
Fill.MatrixB := 0;
Fill.MatrixC := 0;
Fill.MatrixD := 0.8; // smáčkni svisle
Fill.MatrixE := 450; // pak přesuň do středu pečetě
Fill.MatrixF := 160;
end;
Čtení reportu a znalost limitů
TPdfShadingCreateReport vám řekne, co se zapsalo: SourceByteCount a OutputByteCount, CreatedShadingObjectNumbers (jedno číslo objektu na vstupní položku, v pořadí), AttachedShadingCount, Succeeded a ErrorMessage. Validační chyby jmenují položku i pole, třeba seznam zastávek, který není přísně rostoucí, takže stojí za to je logovat slovo od slova
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);
Profil je záměrně ohraničený. Vytváří jen axiální a radiální shadings; funkční a mesh shadings (Typy 1 a 4 až 7) i pattern barevné prostory se odmítají. Jedno volání přijme 1 až 4096 výplní, čísla stránek musí existovat a názvy zdrojů jsou omezené na 127 znaků písmen, číslic, podtržítka, minusu a tečky. Šifrovaný vstup se odmítne už ve fázi traileru. Podepsaný dokument potřebuje AllowSignedDocument := True a protože výstup je kompletní přepis místo inkrementálního updatu, existující podpisy už nebudou validovat, takže podepisujte až po přidání gradientů. Každá výplň se maluje na konci obsahu stránky, navrch nad tím, co už tam je; pro efekt pozadí vytvořte nejdřív gradient a text a obrázky přidávejte poté, nebo použijte optional content, jak popisuje článek o řízení vrstev optional content PDF v Delphi, když má být výplň přepínatelná
FAQ
Podporuje SaveAsShadingCreated gradienty s více zastávkami?
Ano. Předávejte 2 až 256 položek v Stops s pozicemi přísně rostoucími od 0 do 1. Dvě zastávky produkují funkci Type 2 a tři a více produkují stitching funkci Type 3 s odvozenými poli /Bounds a /Encode
Proč je můj gradient v PDFium neviditelný, když soubor vypadá validně?
Zkontrolujte, že /ShadingType je integer 2 nebo 3 místo názvu a že operátor sh běží uvnitř clipu re W n ve stejném content streamu. PDFium tiše přeskočí neplatné shading typy a vyřazuje shading objekty s prázdným obdélníkem
Které barevné prostory může vytvořený shading používat?
TPdfShadingCreate přijímá DeviceGray, DeviceRGB, DeviceCMYK, CalGray, CalRGB, Lab a ICCBased s profilem o 1, 3 nebo 4 složkách. Pattern a Indexed barevné prostory nejsou součástí vytvářecího profilu
Vytváření gradientů, optional content i zbytek připravených writerů dodává PDFium Component for Delphi and C++Builder, který obaluje PDFium pro projekty v Delphi, C++Builderu a Lazarusu