Bir PDF sayfası pikselleri saklamaz ve SVG'nin yaptığı gibi bir şekil nesneleri ağacını da saklamaz. O bir program saklar. Sayfadaki her satır, eğri, dolgu ve yerleştirilmiş görüntü, çalışan bir grafik durumuna (graphics state) karşı yukarıdan aşağıya doğru bir içerik akışındaki bir dizi operatörün yürütülmesinin sonucudur. Bu tek gerçeği anladığınızda, formatın davranışının çoğu şaşırtıcı olmaktan çıkar: yol oluşturulduktan sonra bir dolgunun neden ayrı bir boyama operatörüne ihtiyaç duyduğu, onları parantez içine almadığınız (bracket) sürece renklerin ve çizgi genişliklerinin neden bir şekilden diğerine sızdığı, aynı çizim kodunun tek bir koordinat dönüşümünden sonra neden tamamen farklı yerlere inebileceği. Bu, ISO 32000'de tanımlandığı şekliyle bu yürütme modelinin bir turudur: bir içerik akışını açtığınızda karşılaştığınız operatörler ve sayfada neyin görüneceğine karar veren kurallar
İçerik akışı postfix bayt kodudur
Bir içerik akışı, işlenenlerin (operands) ve ardından operatörlerin geldiği düz bir bayt dizisidir. Önce işlenenler gelir, onları tüketen operatör en son gelir; bu, bir işlev (function) çağrısının tersidir ve bir yığın makinesiyle (stack machine) aynıdır: sayıları itin (push), ardından fiili verin. İç içe geçme, ifade sözdizimi, değişken yoktur. Bir üçgen taslağı bunun beş satırıdır:
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
Operatörler bilerek kısa ve özdür. Gerçek bir sayfa bunlardan binlercesidir ve genellikle FlateDecode ile sıkıştırılır. Bu kompaktlığın bedeli, akışın sorgulayabileceğiniz hiçbir yapı taşımamasıdır: bir görüntüleyici "bu sayfadaki başlık nerede" diye soramaz, sadece programı çalıştırabilir ve hangi mürekkebin nereye indiğini görebilir. Rastgele PDF'lerden metin çıkarmanın zor olmasının temel nedeni budur
Orijin sol alttadır ve Y yukarı doğru büyür
Herhangi bir koordinatın anlamlı olması için (0, 0) noktasının nerede olduğunu bilmeniz gerekir. PDF, orijini sayfanın sol alt köşesine koyar; X sağa doğru artar ve Y yukarı doğru artar; inç başına 72 nokta olacak şekilde noktalarla (points) ölçülür (ISO 32000-2 §8.3.2). Bir US Letter sayfasında üst kenar y = 0'da değil y = 792'de oturur. Orijinin sol üstte olduğu ve Y'nin aşağı doğru büyüdüğü ekran grafiklerinden gelen herkes bunu ilk denemede ters anlar ve ilk çizgiyi sayfanın altından dışarı çizer. Birim (unit) ayrıca ortamdan da bağımsızdır: sayfa ister bir telefon ekranında ister bir film kaydedicide (imagesetter) oluşturulsun, 72 birim bir inçtir
Çoğu sayfa çizim kütüphanesi bu kuralı doğrudan devralır. Örneğin HotPDF'te, TextOut ve yol çağrılarının hepsi sol alttan nokta (point) cinsinden ölçüm yapar, bu nedenle sayfa yüksekliğine yakın bir değer içeriği en üste koyar:
// 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
Bu çağrı dizisi tam olarak yukarıdaki m, l ve S operatörlerine derlenir (compile). Kütüphane, içerik akışı için bir daktilocudur, daha fazlası değil ve ne yaydığını (emit) bilmek, bir şekil beklemediğiniz bir yere indiğinde çıktı hakkında mantık yürütmenizi sağlayan şeydir
Yolu oluşturun, ardından boyayın
PDF, yol (path) oluşturmayı yol boyamadan (painting) ayırır ve bu ayrım bir ukalalık değildir. İlk olarak hiçbir görünür şey eklemeyen oluşturma operatörleriyle bir şekil tanımlarsınız, ardından birikmiş yolla ne yapacağına karar veren tek bir boyama operatörü verirsiniz. Aynı üçgen, yalnızca sonlandırdığınız fiile bağlı olarak bir anahat (outline), düz bir dolgu veya her ikisi de olabilir
Oluşturma operatörleri azdır. m bir noktada yeni bir alt yol (subpath) başlatır. l düz bir bölüm ekler. c altı işlenenden, iki kontrol noktasından ve bir uç noktadan oluşan bir kübik Bezier eğrisi ekler. re bir x, y, genişlik, yükseklik dörtlüsünden tam bir dikdörtgen ekleyen bir kısayoldur. h geçerli alt yolu başlangıcına geri kapatır. Hiçbiri sayfaya mürekkep sürmez; sadece geometriyi biriktirirler
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
Orijinal örnek, eğri (curve) operatörünün artık kullanılmayan y varyantını (variant) kullanıyordu; üç açık noktaya sahip olan c pratikte göreceğiniz ve ulaşmanız gereken biçimdir. Yol var olduğunda, bir boyama operatörü onu bitirir. Kelime dağarcığı küçüktür ve ezberlemeye değerdir, çünkü her sayfadaki her şekil bunlardan biriyle biter:
Smevcut çizgi genişliğini ve çizgi rengini kullanarak yolun anahattını çizer (stroke)fmevcut dolgu (fill) rengini ve sıfır olmayan (nonzero) sarma kuralını kullanarak içini doldururf*kendi kendine kesişen (self-intersecting) şekiller ve delikli şekiller için önemli olan çift-tek (even-odd) kuralını kullanarak doldururBtek bir işlemde doldurur ve ardından çizer;bönce yolu kapatırnhiçbir şey boyamaz, bir yolun görünür bir iz bırakmadan nasıl bir kırpma (clip) bölgesi haline geldiği budur
Sarma kuralı insanların yanıldığı kısımdır. Sıfır olmayan (f, B) test noktasından bir ışının işaretli kesişimlerini sayar ve sayımın sıfır olmadığı her yeri doldurur, bu nedenle bir delik yalnızca alt yolu dıştakine zıt yönde sarılırsa boş kalır. Çift-tek (f*, B*) yönden bağımsız olarak her kesişimde geçiş yapar (toggle). Eğer bir "çörek" (donut) şekli katı (solid) çıkarsa, iç daire dış daire ile aynı şekilde sarılmıştır ve siz onu ya tersine çevirirsiniz ya da çift-tek'e geçersiniz
Renk bir parametre değil, bir moddur
İçerik akışında renk yapışkandır (sticky). Bir renk ayarlarsınız ve siz başka bir renk ayarlayana veya daha önceki bir durumu (state) geri yükleyene kadar ayarlanmış olarak kalır, bu yüzden parantez içine alınmamış bir renk değişikliği kendisinden sonra çizilen her şeyi sessizce renklendirir. PDF ayrıca dolgu rengini (lowercase) ve çizgi rengini (uppercase) dolgu için küçük harfli operatörler ve çizgi için büyük harfli operatörlerle iki bağımsız ayar olarak tutar. Cihaz renk uzaylarının (device color spaces) her birinin kendi kısaltması vardır:
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 ekran çıktısına uyar, DeviceCMYK baskı üretiminin beklediği şeydir ve DeviceGray monokrom içerik için en küçük seçimdir. Cihaz uzayları kullanışlıdır ancak kalibre edilmemiştir: aynı RGB üçlüsü iki monitörde farklı şekilde oluşturulabilir, ICC tabanlı renk uzaylarının ve PDF/A çıktı amaçlarının (output intents) çözmek için var olduğu sorun budur. Renk açısından kritik işler için cs ve CS ile kalibre edilmiş bir uzay seçer ve bileşenleri sc ve scn ile ayarlarsınız, ancak sıradan belgeler için cihaz kısaltmaları yükü taşır. Bir kütüphane bunları türlendirilmiş çağrılarla (typed calls) sarar. Örneğin HotPDF, tek bir TColor alır ve eşleşen operatörleri yayar:
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;
Grafik durumu ve q/Q yığını
Yolun (path) kendisi olmayan her şey grafik durumunda yaşar: mevcut dönüşüm matrisi (transformation matrix), dolgu ve çizgi renkleri, çizgi genişliği, kesikli çizgi deseni (dash pattern), kırpma (clip) bölgesi, alfa (alpha). Durum küreseldir ve değiştirilebilirdir, bu nedenle yerel bir değişiklik yapmanın tek güvenli yolu her şeyi kaydetmek, değiştirmek, çizmek ve geri almaktır. q ve Q'nun yaptığı şey budur. q mevcut durumun bir kopyasını bir yığına (stack) iter; Q eşleşen q'dan bu yana yapılan her değişikliği atarak onu çeker (pop)
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
Dengesiz q ve Q, elle oluşturulmuş veya dikilmiş (stitched) bir içerik akışının yanlış gitmesinin yaygın bir yoludur. Eşleşen bir Q'su olmayan başıboş bir q, sayfa bittiğinde yığını derin bırakır; fazladan bir Q onu eksik akıtır (underflow). Her iki durumda da bir görüntüleyici eski bir kırpmayı veya dönüşümü yürürlükte tutabilir ve içerik kaybolur veya yanlış yere iner. Grafikler, yolun açıklayamayacağı bir nedenden dolayı ortadan kaybolduğunda, önce durum yığınını denetleyin
CTM her koordinatı dönüştürür
Mevcut dönüşüm matrisi (current transformation matrix - CTM), operatörlerinizdeki sayılar ile gerçek sayfa arasında yer alır. Herhangi bir şey çizilmeden önce her koordinat CTM ile çarpılır, bu nedenle matrisi değiştirmek, tek bir yol koordinatına dokunmadan sonraki tüm çizimlerin nerede ve nasıl görüneceğini değiştirir. cm operatörü, afin (affine) matrisine [a b c d e f] eşlenen altı işleneni alarak mevcut matrisin üzerine yeni bir matris bitiştirir (concatenate):
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)
İki şey insanları yanıltır. Birincisi, cm değiştirmek yerine birleştirir, bu nedenle dönüşümler birikir ve sıra önemlidir: önce ölçekleyip (scale) sonra ötelemek (translate), önce öteleyip sonra ölçeklemekle aynı şey değildir. İkincisi, döndürme (rotation) ve ölçekleme şeklinizin merkezi etrafında değil, mevcut orijin etrafında döner (pivot), bu nedenle bir şeyi yerinde döndürmek için onu orijine ötelersiniz, döndürürsünüz, ardından hepsi q/Q içine sarılmış olarak geri ötelersiniz. Aynı matris görüntüleri yerleştiren şeydir, görülmeye değer son parçadır
Görüntüler ve yeniden kullanılabilir içerik XObject'lerdir
Raster görüntüler içerik akışında satır içi (inline) olarak yaşamazlar. Genişlik, yükseklik, bit derinliği, renk uzayı ve sıkıştırma filtresini açıklayan kendi sözlüklerine sahip dış nesneler (external objects) olan görüntü XObject'leri olarak depolanırlar ve içerik akışı yalnızca bunlara başvurur. JPEG destekli bir fotoğraf kendini şu şekilde bildirir:
/Photo <<
/Type /XObject
/Subtype /Image
/Width 640
/Height 480
/BitsPerComponent 8
/ColorSpace /DeviceRGB
/Filter /DCTDecode % the image data is a JPEG stream
>>
Bir görüntü XObject'i birim kareye (unit square) çizilir: kullanıcı uzayında her zaman (0, 0)'dan (1, 1)'e kadar olan bölgeyi kaplar. Ona bir konum veya boyut aktarmazsınız. Bunun yerine, birim karenin istediğiniz dikdörtgene eşlenmesi için CTM'yi ayarlarsınız, ardından onu Do ile çağırırsınız (invoke). Bu nedenle, bir görüntüyü yerleştirmek her zaman bir dönüşüm (transform) ve ardından bir çağrıdır ve ölçeğin (scale) bir sonraki işleme sızmaması (bleed) için bir kaydet/geri yükle (save/restore) işlemine sarılır:
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
Aynı Do mekanizması, sınırlayıcı bir kutuya (bounding box) sahip kendi içerik akışları olarak, yeniden kullanılabilir bir grafik yığınını, bir logoyu veya tekrarlanan bir damgayı tutan form XObject'lerini yönlendirir. Onu bir kez tanımlayın, farklı bir CTM ile birçok kez çağırın ve baytlar dosyada yalnızca bir kez görünür. Çoğu kütüphane bunu tek bir yerleştirme çağrısının arkasına gizler: HotPDF bir bit eşlemi (bitmap) AddImage ile kaydeder ve onu matrisi elle (by hand) oluşturmanızı istemek yerine açık bir x, y, genişlik ve yükseklik alarak ShowImage ile yerleştirir:
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;
Bu tek satırın altında, kütüphane görüntü XObject sözlüğünü yazar, CTM'yi birim kareyi boyutlandıracak ve konumlandıracak şekilde ayarlar ve Do yayar. Alttaki model bilinmeye değer olanıdır, çünkü her garip sonucu açıklar: gerilmiş (stretched) bir görüntü, eşleşmeyen ölçek faktörlerine sahip bir CTM'dir, kırk sayfada aynı olan bir logo, kırk kez çağrılan tek bir form XObject'idir ve baş aşağı (upside down) oluşturulan (render) bir görüntü, bozuk bir dosya değil, matriste bir işaret dönüşüdür (sign flip)
Bunun varacağı yer
Şeklini bir kez gördüğünüzde grafik modeli küçüktür. Bir içerik akışı, değiştirilebilir bir duruma (mutable state) karşı çalışan postfix bayt kodudur; koordinatlar sol alttan başlar ve CTM'den geçer; yollar sessizce oluşturulur ve kasıtlı bir operatörle boyanır; renk ve çizgi ayarları siz onları q/Q ile parantez içine alana kadar (bracket) devam eder; görüntüler ve yeniden kullanılabilir grafikler bir birim karenin dönüştürülmesiyle (transforming) yerleştirilen XObject'lerdir. Kafa karıştırıcı hemen hemen her oluşturma (rendering) sonucu bu beş kuraldan birine indirgenir. Bu grafik operatörlerinin daha büyük nesne modeli, onlara işaret eden sayfa sözlükleri ve çapraz başvuru tablosu (cross-reference table) içinde nasıl oturduğunu görmek istiyorsanız, PDF dosya yapısına teknik genel bakış bu katmanı kapsar ve sıfırdan basit bir PDF oluşturma baytları baştan sona dolaşır. Metin çizimi, kendi operatör ailesinde yaşar ve PDF metin ve yazı tipi işleme (handling) hakkındaki tamamlayıcı makalede ele alınan kendi tuzaklarına (pitfalls) sahiptir
Burada gösterilen MoveTo, LineTo, Stroke, Rectangle, Fill, SetRGBFillColor, AddImage ve ShowImage gibi Delphi çizim çağrıları, bu içerik akışı (content-stream) operatörlerini sizin için yayan (emit) Delphi ve C++Builder için HotPDF Bileşeninin (Component) bir parçasıdır