Tehnični članak

Kako deluje grafika v PDF: tokovi vsebine in operaterji

Stran PDF ne shranjuje slikovnih pik in ne shranjuje drevesa geometrijskih objektov, kot to počne SVG. Shranjuje program. Vsaka črta, krivulja, polnilo in vstavljena slika na strani je rezultat izvajanja zaporedja operaterjev v toku vsebine od zgoraj navzdol glede na trenutno grafično stanje. Ko razumete to dejstvo, večina obnašanja formata ni več presenetljiva: zakaj polnilo potrebuje ločen operater risanja po tem, ko je pot zgrajena; zakaj se barve in širine črt prelijejo iz ene oblike v naslednjo, če jih ne ločite; zakaj lahko ista risalna koda po eni sami transformaciji koordinat pristane na povsem drugem mestu. To je pregled modela izvajanja, kot ga določa ISO 32000: operaterji, na katere naletite ob odpiranju toka vsebine, in pravila, ki določajo, kaj se prikaže na strani

Tok vsebine je postfiksna bajtna koda

Tok vsebine je preprosto bajtno zaporedje operandov, ki jim sledijo operaterji. Najprej so navedeni operandi, nato pa operater, ki jih uporabi, kar je ravno obratno od klica funkcije in enako delovanju skladovnega stroja: naloži številke na sklad, nato pa izvedi ukaz. V tem jeziku ni gnezdenja, sintakse izrazov ali spremenljivk. Obris trikotnika je sestavljen iz petih takšnih vrstic:

100 100 m    % moveto: start a new subpath at (100, 100)
200 200 l    % lineto: add a segment to (200, 200)
300 100 l    % lineto: add a segment to (300, 100)
h            % closepath: connect back to the start
S            % stroke: paint the path outline

Operaterji so namerno kratki. Dejanska stran vsebuje na tisoče takih navodil, ki so običajno stisnjena s filtrom FlateDecode. Cena te kompaktnosti pa je, da tok ne nosi nobene strukture, po kateri bi lahko poizvedovali: pregledovalnik ne more vprašati "kje je naslov na tej strani", ampak lahko le izvede program in ugotovi, kam se izriše črnilo. To je glavni razlog, zakaj je ekstrahiranje besedila iz poljubnih datotek PDF tako zahtevno

Izhodišče je spodaj levo, os Y pa raste navzgor

Preden določite koordinate, morate vedeti, kje se nahaja točka (0, 0). PDF postavi izhodišče v spodnji levi kot strani, pri čemer os X raste v desno, os Y pa navzgor. Merska enota so točke, pri čemer je 72 točk enako enemu palcu (ISO 32000-2 §8.3.2). Na strani velikosti US Letter se zgornji rob nahaja pri y = 792 in ne pri y = 0. Vsakdo, ki prihaja iz sveta zaslonske grafike, kjer je izhodišče zgoraj levo in os Y raste navzdol, to ob prvem poskusu razume narobe in izriše črto pod spodnji rob strani. Enota je prav tako neodvisna od medija: 72 enot predstavlja en palec, ne glede na to, ali se stran izrisuje na zaslonu telefona ali na tiskarskem stroju

Večina knjižnic za izrisovanje strani neposredno deduje to konvencijo. V knjižnici HotPDF se na primer funkcija TextOut in klici poti merijo od spodaj levo navzgor v točkah, zato vrednost blizu višine strani postavi vsebino na vrh:

// HotPDF, Delphi: y measured from the bottom edge upward, in points
Pdf.CurrentPage.SetLineWidth(2.0);
Pdf.CurrentPage.MoveTo(100, 700);   // near the top of the page
Pdf.CurrentPage.LineTo(300, 700);
Pdf.CurrentPage.Stroke;             // emits the moveto/lineto/stroke operators

To zaporedje klicev se prevede natanko v operaterje m, l in S, ki so prikazani zgoraj. Knjižnica le zapisuje tok vsebine namesto vas in poznavanje tega, kar oddaja, vam omogoča, da lažje ugotovite razlog, ko se oblika izriše na nepričakovanem mestu

Zgradite pot, nato jo narišite

PDF ločuje gradnjo poti od njenega izrisa in ta ločitev ni le formalnost. Najprej opišete obliko z operaterji za gradnjo, ki na zaslonu ne pustijo sledi, nato pa izvedete en sam operater risanja, ki določi, kaj storiti z zgrajeno potjo. Isti trikotnik je lahko le obris, polna oblika ali oboje, odvisno od operaterja na koncu

Operaterjev za gradnjo je malo. m začne novo podpot v točki. l doda raven segment. c doda kubično Bezierjevo krivuljo iz šestih operandov (dve kontrolni točki in končna točka). re je bližnjica, ki doda celoten pravokotnik z vrednostmi x, y, širino in višino. h zapre trenutno podpot nazaj do njenega začetka. Nobeden od njih ne izriše ničesar, le kopičijo geometrijo

200 250 m                    % start the subpath
300 350 400 450 500 250 c    % cubic Bezier: two control points, then endpoint
150 200 re                   % a 150 x 200 rectangle, added as its own subpath
h                            % close

Izvirni primer je uporabljal zdaj zastarelo varianto y operaterja krivulje; c s tremi eksplicitnimi točkami je oblika, ki jo srečate v praksi in ki jo morate doseči. Ko pot obstaja, jo zaključi en sam operater risanja. Besedišče je majhno in vredno pomnjenja, ker vsaka oblika na vsaki strani konča z enim od teh:

  • S obrišе pot z njenim orisom z uporabo trenutne širine črte in barve poteze
  • f zapolni notranjost z uporabo trenutne barve polnila in pravila navijanja na nič
  • f* zapolni z uporabo pravila lihih-sodih, ki je pomembno pri samosečnih oblikah in oblikah z luknjami
  • B zapolni in nato obrišе v enem koraku; b najprej zapre pot
  • n ne nariše ničesar, kar je način, kako pot postane območje izreza brez vidne sledi

Pravilo navijanja je del, ki ga ljudje napačno razumejo. Pravilo navijanja na nič (f, B) šteje predznačena križanja žarka iz testne točke in zapolni povsod, kjer število ni nič, zato luknja ostane prazna le, če se njena podpot navija nasprotno od zunanje. Liho-sodo (f*, B*) preklopi ob vsakem križanju ne glede na smer. Če oblika "krofne" pride polna, je notranja krožnica navita v isti smeri kot zunanja in jo morate bodisi obrniti bodisi preklopiti na liho-sodo

Barva je način, ne parameter

Barva v toku vsebine je lepljiva. Nastavite barvo in ostane nastavljena, dokler ne nastavite druge ali obnovite prejšnjega stanja, zato tiha sprememba barve brez oklepanja obarva vse, kar je narisano za njo. PDF prav tako hrani barvo polnila in barvo poteze kot dve neodvisni nastavitvi, pri čemer so operaterji z malimi črkami za polnilo in z velikimi za potezo. Barvni prostori naprave imajo vsak svojo kratico:

0.5 g                % DeviceGray fill, mid gray (0 = black, 1 = white)
0.2 0.6 0.8 rg       % DeviceRGB fill
0.8 0.2 0.1 RG       % DeviceRGB stroke (uppercase = stroke)
0.2 0.8 0.0 0.1 k    % DeviceCMYK fill

DeviceRGB je primeren za zaslonski izhod, DeviceCMYK pričakuje tiskarska proizvodnja, DeviceGray pa je najmanjša izbira za enobarvno vsebino. Barvni prostori naprave so priročni, a nekalibriran: isti trojček RGB se lahko različno prikaže na dveh monitorjih, kar je problem, ki ga rešujejo barvni prostori na osnovi ICC in izhodni nameni PDF/A. Za barvno kritično delo izberete kalibrirani prostor s cs in CS ter nastavite komponente s sc in scn, toda za navadne dokumente kratice naprave nosijo breme. Knjižnica te ovije v tipkane klice. HotPDF na primer sprejme en sam TColor in odda ustrezne operaterje:

Pdf.CurrentPage.SetRGBFillColor(clRed);
Pdf.CurrentPage.Rectangle(100, 100, 200, 150);  // x, y, width, height
Pdf.CurrentPage.Fill;

Pdf.CurrentPage.SetRGBFillColor(RGB(0, 255, 0));
Pdf.CurrentPage.Circle(150, 400, 50);           // x, y, radius
Pdf.CurrentPage.Fill;

Grafično stanje in sklad q/Q

Vse, kar ni pot sama, živi v grafičnem stanju: trenutna transformacijska matrika, barve polnila in poteze, širina črte, vzorec črt, območje izreza, alfa. Stanje je globalno in spremenljivo, zato je edini varen način za lokalno spremembo, da celotno stanje shranite, ga spremenite, narišete in ga povrnete. To počneta q in Q. q potisne kopijo trenutnega stanja na sklad; Q jo odstranitvena, pri čemer zavrže vsako spremembo, narejeno od ujemajočega se q

q                    % save the entire graphics state
2 0 0 2 100 100 cm   % concatenate a transform: scale 2x, translate to (100,100)
0.8 g                % gray fill, scoped to this block
% ... draw scaled, gray content ...
Q                    % restore: transform and color revert

Neuravnotežena q in Q sta pogost razlog, zakaj ročno zgrajen ali sestavljen tok vsebine gre narobe. Zalutali q brez ujemajočega se Q pusti sklad globok, ko se stran konča; dodaten Q ga prelije. V katerem koli primeru pregledovalnik morda ohrani star izrez ali transformacijo in vsebina izgine ali pristane na napačnem mestu. Ko grafika izgine brez razloga, ki bi ga pot razložila, najprej previdite sklad stanja

CTM transformira vsako koordinato

Trenutna transformacijska matrika sedi med številkami v vaših operaterjih in dejansko stranjo. Vsaka koordinata se pomnoži s CTM, preden se karkoli nariše, zato sprememba matrike spremeni, kje in kako se prikaže vse nadaljnje risanje, ne da bi se dotaknili katere koli koordinate poti. Operater cm konkatenira novo matriko na trenutno, pri čemer sprejme šest operandov, ki se preslikajo na afino matriko [a b c d e f]:

1 0 0 1 100 50 cm        % translate by (100, 50): e and f carry the offset
2 0 0 1.5 0 0 cm         % scale x by 2, y by 1.5: a and d are the scale factors
0.707 0.707 -0.707 0.707 0 0 cm   % rotate 45 degrees (cos/sin in a, b, c, d)

Dve stvari zmede ljudi. Prvič, cm sestavlja in ne nadomešča, zato se transformacije kopičijo in vrstni red je pomemben: skaliranje nato premikanje ni enako kot premikanje nato skaliranje. Drugič, rotacija in skaliranje se vrtita okoli trenutnega izhodišča in ne okoli središča vaše oblike, zato morate za rotiranje nečesa na mestu to prevesti na izhodišče, rotirati, nato prevesti nazaj, vse zapakirano v q/Q. Ta ista matrika je tisto, kar postavi slike, zadnji del, ki je vreden videti

Slike in vsebina za večkratno uporabo so XObjects

Rasterske slike ne živijo vgradzeno v toku vsebine. Shranjene so kot slikovni XObjects, zunanji objekti z lastnim slovarjem, ki opisuje širino, višino, bitno globino, barvni prostor in filter stiskanja, tok vsebine pa se nanje le sklicuje. Fotografija s podporo JPEG se razglasi takole:

/Photo <<
  /Type /XObject
  /Subtype /Image
  /Width 640
  /Height 480
  /BitsPerComponent 8
  /ColorSpace /DeviceRGB
  /Filter /DCTDecode        % the image data is a JPEG stream
>>

Slikovni XObject se nariše v enotni kvadrat: vedno zasede območje od (0, 0) do (1, 1) v uporabniškem prostoru. Ne posredujete mu položaja ali velikosti. Namesto tega nastavite CTM tako, da se ta enotni kvadrat preslika na pravokotnik, ki ga želite, nato pa ga pokličete z Do. Zato je postavljanje slike vedno transformacija, ki ji sledi klicanje, zapakirano v shrani/obnovi, da skaliranje ne krvavi v naslednjo operacijo:

q
640 0 0 480 50 300 cm    % map the unit square to a 640x480 box at (50, 300)
/Photo Do                % paint the image XObject
Q

Isti mehanizem Do poganja obrazčne XObjects, ki vsebujejo večkratno uporabni kos grafike, logotip ali ponavljajoč se žig, kot lastni tok vsebine z mejnim poljem. Definirajte ga enkrat, pokličite ga večkrat z različnim CTM in bajti se v datoteki pojavijo le enkrat. Večina knjižnic to skrije za enim klicem za postavitev: HotPDF registrira bitno sliko z AddImage in jo postavi z ShowImage, pri čemer sprejme eksplicitne vrednosti x, y, širino in višino namesto da bi vas prosila, da sami zgradite matriko:

var
  Bmp: TBitmap;
  ImgIndex: Integer;
begin
  Bmp := TBitmap.Create;
  try
    Bmp.LoadFromFile('logo.bmp');
    ImgIndex := Pdf.AddImage(Bmp, icFlate);
    // x, y (bottom-left), width, height, rotation angle
    Pdf.CurrentPage.ShowImage(ImgIndex, 50, 300, 200, 150, 0);
  finally
    Bmp.Free;
  end;
end;

Pod tisto eno vrstico knjižnica zapiše slovar slikovnega XObject, nastavi CTM za velikost in položaj enotnega kvadrata ter odda Do. Model spodaj je tisti, ki je vreden poznavanja, ker razloži vsak čuden rezultat: raztegnjena slika je CTM z neujemajočimi se faktorji skaliranja, logotip, ki je enak na štiridesetih straneh, je en obrazčni XObject, poklizan štirideset krat, in slika, ki se prikaže narobe, je predznakovni obrat v matriki in ne pokvarjena datoteka

Kam to vodi

Grafični model je majhen, ko vidite njegovo obliko. Tok vsebine je postfiksna bajtna koda, ki teče na spremenljivem stanju; koordinate se začnejo spodaj levo in gredo skozi CTM; poti se gradijo tiho in rišejo z enim namernim operaterjem; barva in nastavitve črt trajajo, dokler jih ne oklepate s q/Q; slike in grafika za večkratno uporabo so XObjects, postavljeni s transformacijo enotnega kvadrata. Skoraj vsak zmedeni rezultat upodabljanja se zmanjša na eno od teh petih pravil. Če želite videti, kako ti grafični operaterji sedijo znotraj večjega modela objektov, slovarjev strani in navzkrižne referenčne tabele, ki kažejo nanje, tehnični pregled strukture datoteke PDF pokriva to plast, in gradnja preprostega PDF od začetka hodi po bajtih od konca do konca. Risanje besedila živi v svoji lastni družini operaterjev in ima lastne pasti, ki so obravnavane v spremljevalnem sestavku o ravnanju z besedilom in pisavami v PDF

Klici za risanje v Delphiju, ki so prikazani tukaj, MoveTo, LineTo, Stroke, Rectangle, Fill, SetRGBFillColor, AddImage in ShowImage, so del komponente HotPDF za Delphi in C++Builder, ki za vas oddaja te operaterje toka vsebine