Technical Article

Delphi ile PDF'de Vektör Grafikleri: Yollar ve Gradyanlar

PDF'ye dokunan çoğu Delphi kodu, bu biçimi iki şey için bir kap olarak görür: metin dizileri ve birkaç yerleştirilmiş bit eşlem (bitmap). Bu görüş gidebildiği yere kadar doğrudur ve biçimin en yetenekli kısmını kullanılmadan bırakır. Bir PDF sayfası, PostScript ile aynı görüntüleme modeli üzerine inşa edilmiş, çözünürlükten bağımsız bir 2B tuvaldir. Herhangi bir yakınlaştırmada keskin kalan ve cihazın tam çözünürlüğünde yazdırılan vektörler olarak çizgiler, eğriler, dolu bölgeler, gradyanlar ve tekrarlayan desenler çizebilir. Bir logo, grafik, filigran veya sertifika kenarlığı çiziyorsanız, vektör yolu neredeyse her zaman doğru temel ögedir; birçok programın bunun yerine kullandığı rasterleştirilmiş görüntüden daha küçük ve daha nettir.

Bu makale, ISO 32000-1'in tanımladığı vektör modelini inceliyor ve eşleşen PDFlibPas çağrılarını gösteriyor. Amaç, API onunla yakından eşleştiği için spesifikasyonu somutlaştırmaktır; birini anlamak diğerini de öğretir.

Sayfa bir yol makinesidir

ISO 32000-1 §8.5 grafikleri asla örtüşmeyen iki aşamada açıklar. İlk olarak, görünür bir sonucu olmayan saf geometriden ibaret bir yol (path) oluşturursunuz. Ardından, bu yolu ana hatlarını çizen (stroke), içini dolduran (fill) veya her ikisini birden yapan tek bir işlemle boyarsınız. İnşaat sırasında sayfada hiçbir şey görünmez. Yol, bir boyama operatörü onu tüketene kadar grafik durumunda tutulan soyut bir noktalar ve segmentler dizisidir; bu noktada işlenir ve atılır.

Bir yol, bir veya daha fazla alt yoldan (subpath) oluşur. Alt yol bir noktada başlar ve segmentler eklenerek büyür: düz çizgiler, kübik Bezier eğrileri ve bazı platformlarda kendi kapalı alt yolları olarak eklenen tam dikdörtgenler. PDFlibPas'ta başlangıç noktasını belirleyen StartPath ile bir yol açar, ardından AddLineToPath and AddCurveToPath ile genişletirsiniz. Her çağrı örtük bir geçerli noktayı ilerletir, böylece bir sonraki segment bir öncelikinin bittiği yerden devam eder. ClosePath alt yolun başlangıcına geri dönen son düz bir segment çizer; bu, stroking için önemlidir çünkü iki gevşek uç kapağı yerine kapanış köşesinde gerçek bir çizgi birleşimi üretir。

// A closed quadrilateral, stroked then filled
PDF.SetLineColor(0, 0, 0);
PDF.SetFillColor(0.6, 0.8, 1.0);
PDF.SetLineWidth(1.5);

PDF.StartPath(150, 100);           // open the path at the first vertex
PDF.AddLineToPath(220, 140);
PDF.AddLineToPath(180, 210);
PDF.AddLineToPath(110, 170);
PDF.ClosePath;                     // straight segment back to (150, 100)
PDF.DrawPath(2);                   // 2 = fill and stroke; path is consumed

Eğriler, iki Bezier kontrol noktası ve bir bitiş noktası alan AddCurveToPath işlevini kullanır: AddCurveToPath(CtAX, CtAY, CtBX, CtBY, EndX, EndY). Eğri, yol boyunca iki kontrol noktasına doğru çekilerek geçerli noktadan (EndX, EndY) konumuna kadar uzanır. Dairesel yaylar, yarıçapın geçerli nokta ile merkez arasındaki mesafeden alındığı AddArcToPath(CenterX, CenterY, TotalAngle) aracılığıyla kullanılabilir ve motor yayı bir Bezier segmentleri zinciri olarak verir. Dikdörtgenlerin, önünde bir StartPath olmadan kendi alt yolu olarak tam bir kapalı dikdörtgen ekleyen AddBoxToPath(Left, Top, Width, Height) şeklinde bir kısayolu vardır.

İki doldurma kuralı ve neden uyuşmadıkları

Kendi kendini kesen veya iç döngü içeren bir yolu doldurduğunuzda, oluşturucu hangi bölgelerin şeklin içinde olduğunu ve hangilerinin delik olduğunu belirlemek için bir kurala ihtiyaç duyar. ISO 32000-1 §8.5.3.3 iki kural tanımlar ve bunlar aynı geometriyi farklı şekilde boyayabilir. Sıfır dışı sarma (nonzero winding) kuralı, bir test noktasından sonsuza doğru gönderilen bir ışının işaretli kesişimlerini sayar; soldan sağa geçen her segment için bir ekler ve diğer yöne geçen her segment için bir çıkarır; toplam sıfır olmadığında nokta içeridedir. Çift-tek (even-odd) kuralı yönü göz ardı eder ve sadece kesişimleri sayarak, sayı tek olduğunda noktanın içeride olduğunu kabul eder。

Ayrıştıkları klasik durum, delikli bir şekildir, örneğin bir halka (donut) veya pul. Dış bir sınır ve bunun içine bir iç sınır çizin. Çift-tek kuralı altında, iç döngü her zaman bir delik açar, çünkü iki sınır arasındaki herhangi bir nokta bir kez geçilir ve iç döngünün içindeki herhangi bir nokta iki kez geçilir. Sıfır dışı sarma kuralı altında, delik yalnızca iç döngü dış döngüye göre ters yönde sarılıyorsa görünür; ikisini aynı yönde sararsanız, sarımlar birbirini iptal etmek yerine güçlendirir ve iç bölge tamamen doldurulur. Tek bir kendi kendini kesen çizgiyle çizilen beş köşeli bir yıldız aynı bölünmeyi gösterir: çift-tek merkezi beşgeni boş bırakırken, sıfır dışı sarma doldurur.

PDFlibPas kuralı bir bayrağa göre değil, boyamak için yaptığınız çağrıya göre seçer. DrawPath sıfır dışı sarma kuralıyla doldurur; DrawPathEvenOdd çift-tek kuralıyla doldurur. Her ikisi de aynı tamsayı modunu alır: 0 yalnızca ana hattı çizer, 1 yalnızca doldurur ve 2 doldurur ve ana hattı çizer. Çift-tek kuralı, tam olarak alt yol yönünü yönetmenizi gerektirmediği için delik açmak için daha kolay bir araçtır.

// Same two boxes, two fill rules, two different results.
// Nonzero winding: both boxes wind the same way, so the inner one
// does NOT cut a hole and the whole outer box fills solid.
PDF.SetFillColor(0.2, 0.4, 0.8);
PDF.AddBoxToPath(100, 100, 200, 120);   // outer
PDF.AddBoxToPath(140, 130, 120,  60);   // inner
PDF.DrawPath(1);                         // 1 = fill, nonzero winding

// Even-odd: the inner box is crossed an even number of times,
// so it punches a clean rectangular hole through the outer box.
PDF.SetFillColor(0.2, 0.4, 0.8);
PDF.AddBoxToPath(100, 300, 200, 120);   // outer
PDF.AddBoxToPath(140, 330, 120,  60);   // inner cut-out
PDF.DrawPathEvenOdd(1);                  // 1 = fill, even-odd

Eksenel gradyanlar rengi bir çizgi boyunca değiştirir

Düz bir dolgu rengi, tüm bölge boyunca tek bir değerdir. Bir gradyan rengi sürekli olarak değiştirir ve en basit türü eksenel veya doğrusal gradyandır. ISO 32000-1 §8.7.4.5 bunu Tip 2 eksenel gölgelendirme (axial shading) olarak belirtir: bir ekseni tanımlayan iki nokta, ilk noktada bir başlangıç rengi ve ikincisinde bir bitiş rengi verirsiniz ve oluşturucu rengi bu eksen boyunca enterpole eder. Doldurulan bölgedeki her nokta, eksen üzerindeki dik izdüşümünün rengini alır, böylece gradyan iki nokta arasındaki çizgiye dik açılı bantlar halinde uzanır。

PDFlibPas'ta bir gradyan, bir kez oluşturduğunuz ve ardından etkin boya olarak seçtiğiniz adlandırılmış bir belge kaynağıdır. NewRGBAxialShader bunu kaydeder. İmzası şöyledir: NewRGBAxialShader(ShaderName, StartX, StartY, StartRed, StartGreen, StartBlue, EndX, EndY, EndRed, EndGreen, EndBlue, Extend): iki eksen uç noktası, 0 ile 1 aralığında değerler olarak her iki uçtaki RGB üçlüleri ve bir Extend bayrağı. Extend değeri 1 olarak ayarlandığında, bitiş renkleri eksen uç noktalarının ötesinde düz dolgu olarak devam eder, bu genellikle eksen dışındaki bir bölgenin köşelerinin boyanmadan kalmaması için istediğiniz şeydir; 0 ise bunlara dokunmaz. Gölgelendirici (shader) oluşturulduktan sonra, onu dolu bölgeler için SetFillShader, çizgili ana hatlar için SetLineShader veya metin için SetTextShader ile bağlarsınız. Bağlama, sonraki çizim çağrıları için etkin kalır, sobering bir sonraki boyadığınız yol düz bir renk yerine gradyanı alır.

// Define a vertical gradient once: blue at the bottom to white at the top.
PDF.NewRGBAxialShader('panelGrad',
  0, 100,   0.10, 0.25, 0.55,    // start point and start RGB
  0, 260,   1.00, 1.00, 1.00,    // end point and end RGB
  1);                            // 1 = extend ends as solid color

// Select the gradient as the fill, then paint a rectangle with it.
PDF.SetFillShader('panelGrad');
PDF.AddBoxToPath(80, 100, 300, 160);
PDF.DrawPath(1);                 // 1 = fill, now filled by the shader

Buradaki eksen dikey olup, sabit bir x'te y=100 ile y=260 arasındadır, dolayısıyla renk bantları yatay olarak uzanır ve dikdörtgen tabanındaki maviden tepesindeki beyaza doğru solar. Gölgelendirici isme göre anahtarlandığından, bir tanım sayfadaki herhangi bir sayıda şekli doldurabilir ve düz bir renge geri dönmek, bir sonraki yoldan önce başka bir SetFillColor çağrısı yapmak kadar kolaydır。

Döşeme desenleri bir hücreyi yineler

Bir gradyanın tek bir rengi sorunsuz bir şekilde değiştirdiği yerde, döşeme deseni (tiling pattern) bir bölge boyunca küçük bir sanat eserini yineler. ISO 32000-1 §8.7.3.1 döşeme desenini, oluşturucunun boyanan alanı döşemek için sabit bir ızgara üzerinde çoğalttığı bağımsız bir içerik parçası olan desen hücresi (pattern cell) olarak tanımlar. Bu, bir mühendislik dolgusu için tarama, bir başlığın arkasında tekrarlayan bir marka motifi veya alan ne kadar büyük olursa olsun vektör keskinliğinde kalan ve neredeyse hiçbir ağırlığı olmayan dokulu bir arka planı bu şekilde oluşturursunuz, çünkü hücre bir kez saklanır ve her yerde referans gösterilir。

PDFlibPas, desen hücresini yakalanan sayfa içeriğinden oluşturur. CapturePage ile bir sayfayı veya bölgeyi yakalar, yakalamayı NewTilingPatternFromCapturedPage(PatternName, CaptureID) ile adlandırılmış bir desene dönüştürür ve ardından SetFillTilingPattern(PatternName) ile bu deseni geçerli dolgu olarak seçersiniz. Bu noktadan itibaren, doldurduğunuz herhangi bir yol, tıpkı gölgelendirici dolgusunun çalışması gibi ancak boya kaynağı olarak döşenmiş bir hücreyle düz bir renk yerine tekrarlayan hücreyle boyanır. Bu sıra tek bir çağrıdan daha karmaşıktır, bu nedenle yakalama adımı tanıdık gelmiyorsa, deseni iki aşamalı bir işlem olarak ele alın: önce yakalanan hücreyi oluşturun, ardından döşenmesini istediğiniz bölgeyi çizmeden önce adıyla bir dolgu olarak bağlayın。

Temel ögeleri bir araya getirme

Parçalar doğrudan birleşir. Dolu bir Bezier damlası, DrawPath ile boyanmış bir eğriler yoludur. Bir iç döngü eklendikten sonra DrawPathEvenOdd ile boyanmış aynı ana hat, sarma dolgusunun kapatacağı bir delik gösterir. Gradyan dolgulu bir dikdörtgen, bir gölgelendiriciye bağlı bir kutudur. Aşağıdaki örnek, her iki doldurma kuralı arasındaki farkın tek bir sayfada görülebilmesi için üçünü sırayla çizer, ardından altlarına bir gradyan paneli yerleştirir。

// 1. A filled Bezier shape (nonzero winding).
PDF.SetFillColor(0.85, 0.30, 0.25);
PDF.StartPath(120, 480);
PDF.AddCurveToPath(160, 560, 240, 560, 280, 480);   // top lobe
PDF.AddCurveToPath(240, 420, 160, 420, 120, 480);   // bottom lobe
PDF.ClosePath;
PDF.DrawPath(1);                                     // 1 = fill

// 2. The same outline, plus an inner loop, filled even-odd to show a hole.
PDF.SetFillColor(0.85, 0.30, 0.25);
PDF.StartPath(120, 300);
PDF.AddCurveToPath(160, 380, 240, 380, 280, 300);
PDF.AddCurveToPath(240, 240, 160, 240, 120, 300);
PDF.ClosePath;
PDF.MovePath(180, 300);                              // new subpath: the hole
PDF.AddArcToPath(200, 300, 360);                     // a full circle
PDF.ClosePath;
PDF.DrawPathEvenOdd(1);                              // hole is punched out

// 3. A rectangle filled with an axial gradient.
PDF.NewRGBAxialShader('footerGrad',
  60, 100,  0.95, 0.55, 0.10,
  60, 200,  0.20, 0.10, 0.40,
  1);
PDF.SetFillShader('footerGrad');
PDF.AddBoxToPath(60, 100, 340, 100);
PDF.DrawPath(1);

İki ayrıntıyı akılda tutmakta fayda var. Boyama çağrıları doldurma kuralına karar verir, bu nedenle DrawPath ile DrawPathEvenOdd arasındaki seçim sıfır dışı sarma ile çift-tek arasındaki seçimdir ve delikli şekiller için çift-tek kuralı sizi alt yol yönü hakkında akıl yürütmekten kurtarır. Ve grafik durumu boyadığınız anda örneklenir: renklerinizi, çizgi genişliğinizi ve gölgelendirici bağınızı boyama çağrısından önce ayarlayın, çünkü motorun okuduğu durum budur. Önce oluşturun, durumu yapılandırın, en son boyayın; vektör modeli her seferinde öngörülebilir şekilde davranacaktır。

Buradan sonraki doğal adımlar, metin, resim ve yazı tipi çıkarma hakkındaki makalemizde ele alınan mevcut bir belgeden vektörleri ve metinleri geri okumak ve aynı çizim modelini ekran önizlemesi ve yazdırma için bir Windows cihaz bağlamına (device context) işlemektir; bu konu yazdırma ve önizleme kılavuzunda ele alınmıştır. Burada açıklanan yol, gölgelendirici ve desen çağrıları, bu blogun başka yerlerinde ele alınan metin, resim, form ve imzalama API'lerinin yanı sıra Delphi PDF Kütüphanesinin bir parçası olarak sunulur。