Stránka PDF neukládá pixely a neukládá ani strom tvarových objektů, jako to dělá SVG. Ukládá program. Každá čára, křivka, výplň a umístěný obrázek na stránce je výsledkem spuštění sekvence operátorů v toku obsahu, shora dolů, proti průběžnému grafickému stavu. Pochopte tento jediný fakt a většina chování formátu vás přestane překvapovat: proč výplň potřebuje samostatný operátor kreslení poté, co je sestavena cesta, proč barvy a tloušťky čar unikají z jednoho tvaru do dalšího, pokud je neuzavřete do závorek, proč stejný kód kreslení může skončit na úplně jiných místech po jediné transformaci souřadnic. Toto je prohlídka tohoto modelu provádění, jak jej definuje norma ISO 32000: operátory, se kterými se setkáte, když otevřete tok obsahu, a pravidla, která rozhodují o tom, co se zobrazí na stránce
Tok obsahu je postfixový bytecode
Tok obsahu je plochá sekvence bajtů operandů následovaných operátory. Operandy jdou jako první, operátor, který je spotřebuje, jde jako poslední, což je obrácené pořadí než volání funkce a je to identické se zásobníkovým strojem: vložte čísla a poté vydejte sloveso. Neexistuje žádné vnořování, žádná syntaxe výrazů, žádné proměnné. Obrys trojúhelníku tvoří pět řádků tohoto kódu:
100 100 m % moveto: zahájí novou dílčí cestu na (100, 100)
200 200 l % lineto: přidá úsek k (200, 200)
300 100 l % lineto: přidá úsek k (300, 100)
h % closepath: spojí zpět na začátek
S % stroke: nakreslí obrys cesty
Operátory jsou záměrně stručné. Skutečná stránka představuje tisíce z nich, obvykle komprimovaných pomocí FlateDecode. Cenou za tuto kompaktnost je, že tok nenese žádnou strukturu, na kterou byste se mohli dotazovat: prohlížeč se nemůže zeptat "kde je nadpis na této stránce", může pouze spustit program a podívat se, jaký inkoust dopadne kam. To je hlavní důvod, proč je extrakce textu z libovolných PDF složitá
Počátek je vlevo dole a Y roste nahoru
Než dává smysl jakákoliv souřadnice, musíte vědět, kde je (0, 0). PDF umístí počátek do levého dolního rohu stránky, přičemž osa X roste doprava a osa Y nahoru, měřeno v bodech při 72 bodech na palec (ISO 32000-2 §8.3.2). Na stránce formátu US Letter se horní okraj nachází na y = 792, nikoli na y = 0. Každý, kdo přichází ze souřadnic obrazovky, kde je počátek vlevo nahoře a Y roste dolů, to napoprvé pochopí obráceně a nakreslí první čáru mimo spodní okraj stránky. Jednotka je také nezávislá na médiu: 72 jednotek je jeden palec bez ohledu na to, zda se stránka vykreslí na obrazovku telefonu, nebo na osvitovou jednotku
Většina knihoven pro kreslení stránek přímo dědí tuto konvenci. Například v knihovně HotPDF funkce TextOut a volání cesty vše měří od levého dolního rohu v bodech, takže hodnota blízká výšce stránky umístí obsah nahoru:
// HotPDF, Delphi: y se měří od spodního okraje směrem nahoru, v bodech
Pdf.CurrentPage.SetLineWidth(2.0);
Pdf.CurrentPage.MoveTo(100, 700); // blízko horního okraje stránky
Pdf.CurrentPage.LineTo(300, 700);
Pdf.CurrentPage.Stroke; // vygeneruje operátory moveto/lineto/stroke
Tato sekvence volání se zkompiluje přesně na operátory m, l a S výše. Knihovna je typistkou pro tok obsahu, nic víc, a to, že víte, co generuje, vám umožňuje analyzovat výstup, když tvar dopadne někam, kam jste neočekávali
Vytvořte cestu a poté ji vymalujte
PDF odděluje konstrukci cesty od malování cesty a toto oddělení není pedantstvím. Nejprve popíšete tvar pomocí konstrukčních operátorů, které nepřidají nic viditelného, a poté vydáte jediný operátor malování, který rozhodne, co dělat s nashromážděnou cestou. Ten samý trojúhelník může být obrysem, pevnou výplní nebo obojím, a to závisí pouze na slovese, kterým končíte
Konstrukčních operátorů je málo. m zahájí novou dílčí cestu v určitém bodě. l přidá rovný úsek. c přidá kubickou Bézierovu křivku ze šesti operandů, dvou kontrolních bodů a jednoho koncového bodu. re je zkratka, která přidá celý obdélník z čtveřice hodnot x, y, šířka, výška. h uzavře aktuální dílčí cestu zpět na její začátek. Žádný z nich nanáší inkoust na stránku; pouze akumulují geometrii
200 250 m % zahájení dílčí cesty
300 350 400 450 500 250 c % kubická Bézierova křivka: dva kontrolní body, pak koncový bod
150 200 re % obdélník o rozměrech 150 x 200, přidán jako samostatná dílčí cesta
h % uzavření
Původní příklad použil nyní zastaralou variantu operátoru křivky y; c se svými třemi explicitními body je forma, kterou uvidíte v praxi, a po které byste měli sáhnout. Jakmile cesta existuje, dokončí ji jeden operátor kreslení. Slovník je malý a stojí za to si ho zapamatovat, protože každý tvar na každé stránce končí jedním z nich:
Snakreslí obrys cesty (tah) pomocí aktuální tloušťky čáry a barvy tahufvyplní vnitřek pomocí aktuální barvy výplně a pravidla nenulového vinutíf*vyplní pomocí pravidla sudo-licho, což je důležité pro tvary protínající se samy sebe a tvary s díramiBvyplní a poté protáhne (obrys) v jedné operaci;bnejprve uzavře cestunnenamaluje nic, což je způsob, jak se z cesty stane oblast oříznutí bez zanechání viditelné stopy
Pravidlo vinutí je věc, kterou lidé dělají špatně. Nenulové pravidlo (f, B) počítá podepsané průsečíky paprsku z testovaného bodu a vyplní tam, kde počet není nula, takže díra zůstane prázdná pouze tehdy, pokud je její dílčí cesta vinuta opačně než ta vnější. Sudo-licho pravidlo (f*, B*) se přepíná při každém průsečíku bez ohledu na směr. Pokud vyjde tvar "koblihy" plný (solidní), vnitřní kruh je vinut stejným směrem jako vnější a vy jej musíte buď obrátit, nebo přepnout na sudo-liché pravidlo
Barva je režim, ne parametr
Barva v toku obsahu je lepkavá. Nastavíte barvu a ta zůstane nastavena, dokud nenastavíte jinou, nebo neobnovíte dřívější stav, což je důvod, proč změna barvy, která není v závorkách, tiše zbarví vše, co je za ní nakresleno. PDF také udržuje barvu výplně a barvu tahu jako dvě nezávislá nastavení s operátory psanými malými písmeny pro výplň a velkými písmeny pro tah. Barevné prostory zařízení mají každý svou vlastní zkratku:
0.5 g % Výplň DeviceGray, středně šedá (0 = černá, 1 = bílá)
0.2 0.6 0.8 rg % Výplň DeviceRGB
0.8 0.2 0.1 RG % Tah DeviceRGB (velká písmena = tah)
0.2 0.8 0.0 0.1 k % Výplň DeviceCMYK
DeviceRGB vyhovuje výstupu na obrazovku, DeviceCMYK je to, co očekává tisková produkce a DeviceGray je nejmenší volba pro jednobarevný obsah. Prostory zařízení jsou pohodlné, ale nejsou kalibrované: stejná trojice RGB se může vykreslit odlišně na dvou monitorech, což je problém, k jehož řešení existují barevné prostory založené na ICC profilech a výstupní záměry PDF/A. Pro barevně kritickou práci zvolíte kalibrovaný prostor s cs a CS a nastavíte komponenty pomocí sc a scn, ale pro běžné dokumenty nesou zátěž zkratky zařízení. Knihovna to zapouzdří do volání s kontrolou typu. HotPDF například přijímá jeden TColor a generuje odpovídající operátory:
Pdf.CurrentPage.SetRGBFillColor(clRed);
Pdf.CurrentPage.Rectangle(100, 100, 200, 150); // x, y, šířka, výška
Pdf.CurrentPage.Fill;
Pdf.CurrentPage.SetRGBFillColor(RGB(0, 255, 0));
Pdf.CurrentPage.Circle(150, 400, 50); // x, y, poloměr
Pdf.CurrentPage.Fill;
Grafický stav a zásobník q/Q
Všechno, co není samotná cesta, žije v grafickém stavu: aktuální transformační matice, barvy výplně a tahu, tloušťka čáry, vzor čárkování, oblast oříznutí, průhlednost alfa. Stav je globální a měnitelný, takže jediný bezpečný způsob, jak provést lokální změnu, je uložit celou věc, upravit ji, nakreslit a vrátit zpět. To dělají q a Q. q vloží kopii aktuálního stavu na zásobník; Q ji vyjme a zahodí každou změnu provedenou od odpovídajícího q
q % uložte celý grafický stav
2 0 0 2 100 100 cm % zřetězení transformace: 2x zvětšení, posunutí na (100,100)
0.8 g % šedá výplň, omezená na tento blok
% ... nakreslete zmenšený/zvětšený, šedý obsah ...
Q % obnovit: transformace a barva se vrátí na původní hodnoty
Nevyvážené q a Q jsou častým způsobem, jak se ručně sestavený nebo slepený tok obsahu pokazí. Zatoulané q bez odpovídajícího Q ponechává zásobník hluboko, když stránka skončí; nadbytečné Q způsobí jeho podtečení (underflow). V každém případě může prohlížeč zachovat v platnosti staré oříznutí nebo transformaci a obsah zmizí, nebo přistane na špatném místě. Když grafika zmizí z žádného důvodu, který by se dal vysvětlit cestou, zkontrolujte nejdříve zásobník stavů
CTM transformuje každou souřadnici
Aktuální transformační matice (CTM) se nachází mezi čísly ve vašich operátorech a skutečnou stránkou. Každá souřadnice je vynásobena CTM předtím, než se cokoliv nakreslí, takže změna matice změní to, kde a jak se veškeré následné kreslení objeví, aniž byste se dotkli jediné souřadnice cesty. Operátor cm zřetězí novou matici na aktuální matici a vezme šest operandů, které se mapují na afinní matici [a b c d e f]:
1 0 0 1 100 50 cm % posunutí o (100, 50): e a f nesou posunutí (offset)
2 0 0 1.5 0 0 cm % měřítko x o 2, y o 1.5: a a d jsou koeficienty změny měřítka
0.707 0.707 -0.707 0.707 0 0 cm % otočení o 45 stupňů (cos/sin v a, b, c, d)
Lidé chybují ve dvou věcech. Za prvé cm skládá transformační matice, místo toho aby je nahrazovalo, takže transformace se kumulují a na pořadí záleží: změna měřítka a poté translace není to samé jako translace a poté změna měřítka. Za druhé, rotace a změna měřítka se otáčejí (pivotují) kolem aktuálního počátku, nikoliv středu vašeho tvaru, takže k otočení něčeho na místě je třeba to posunout k počátku, otočit a pak posunout zpět, vše zabalené v q/Q. Tato samá matice je to, co umísťuje obrázky, což je poslední část, kterou stojí za to vidět
Obrázky a znovupoužitelný obsah jsou objekty XObject
Rastrové obrázky nežijí přímo uvnitř (inline) toku obsahu. Ukládají se jako obrazové objekty XObject, externí objekty se svým vlastním slovníkem, který popisuje šířku, výšku, bitovou hloubku, barevný prostor a kompresní filtr, a tok obsahu na ně pouze odkazuje. Fotografie ve formátu JPEG se deklaruje takto:
/Photo <<
/Type /XObject
/Subtype /Image
/Width 640
/Height 480
/BitsPerComponent 8
/ColorSpace /DeviceRGB
/Filter /DCTDecode % data obrázku tvoří tok JPEG
>>
Obrazový objekt XObject se vykresluje do jednotkového čtverce: v uživatelském prostoru vždy zabírá oblast od (0, 0) do (1, 1). Nepředáváte mu pozici ani velikost. Místo toho nastavíte CTM tak, aby se jednotkový čtverec mapoval do obdélníku, který chcete, a poté jej vyvoláte pomocí Do. Proto je umístění obrázku vždy transformace následovaná voláním (vyvoláním), uzavřená do sekvence uložení/obnovení, aby změna měřítka neovlivnila do další operace:
q
640 0 0 480 50 300 cm % mapování jednotkového čtverce do rámečku 640x480 na souřadnici (50, 300)
/Photo Do % nakreslí obrazový XObject
Q
Stejný mechanismus Do řídí objekty typu formulář (form XObjects), které obsahují znovu použitelný kus grafiky, logo nebo opakované razítko, jako jejich vlastní obsahový tok s ohraničujícím rámečkem (bounding box). Definujte to jednou, vyvolejte to mnohokrát s různou CTM a bajty se v souboru objeví pouze jednou. Většina knihoven to skryje za jedno jediné volání pro umístění: HotPDF registruje bitmapu pomocí AddImage a umísťuje ji pomocí ShowImage, přičemž bere explicitní hodnoty x, y, šířku a výšku namísto toho, aby vás nutilo vytvářet matici ručně:
var
Bmp: TBitmap;
ImgIndex: Integer;
begin
Bmp := TBitmap.Create;
try
Bmp.LoadFromFile('logo.bmp');
ImgIndex := Pdf.AddImage(Bmp, icFlate);
// x, y (dole vlevo), šířka, výška, úhel rotace
Pdf.CurrentPage.ShowImage(ImgIndex, 50, 300, 200, 150, 0);
finally
Bmp.Free;
end;
end;
Pod tímto jediným řádkem knihovna zapíše slovník obrazového objektu XObject, nastaví CTM tak, aby přizpůsobila velikost a umístila jednotkový čtverec a vygeneruje operátor Do. Podkladový model je ten, který stojí za to znát, protože vysvětluje každý podivný výsledek: roztažený obrázek je CTM s neshodujícími se koeficienty změny měřítka, logo identické na čtyřiceti stránkách je jeden formulářový XObject vyvolaný čtyřicetkrát, a obrázek, který se vykreslí obráceně vzhůru nohama je převrácení znaménka v matici, nikoliv poškozený soubor
Kam to vede
Grafický model je malý, jakmile uvidíte jeho tvar. Tok obsahu je postfixový bytecode běžící proti stavu, který se dá měnit (měnitelný stav); souřadnice začínají vlevo dole a procházejí přes matici CTM; cesty se tvoří potichu a malují se pomocí jednoho konkrétního operátoru; nastavení barev a čar přetrvávají, dokud je neuzavřete do závorek pomocí q/Q; obrázky a znovupoužitelná grafika jsou objekty typu XObject, které jsou umístěny prostřednictvím transformace jednotkového čtverce. Téměř každý matoucí výsledek vykreslování se redukuje na jedno z těchto pěti pravidel. Chcete-li vidět, jak tyto grafické operátory sedí ve větším modelu objektů, ve slovnících stránek a tabulce křížových odkazů, která na ně ukazuje, technický přehled struktury souboru PDF pokrývá tuto vrstvu a vytvoření jednoduchého dokumentu PDF od nuly prochází bajty od začátku do konce. Kreslení textu žije ve své vlastní rodině operátorů a má svá vlastní úskalí, viz doprovodný článek o zpracování textu a písem v PDF
Zde uvedená volání kreslení v jazyce Delphi: MoveTo, LineTo, Stroke, Rectangle, Fill, SetRGBFillColor, AddImage a ShowImage, jsou součástí komponenty HotPDF Component pro Delphi a C++Builder, která tyto operátory toku obsahu vygeneruje za vás