Sebagian besar kode Delphi yang menyentuh PDF memperlakukan format tersebut sebagai wadah untuk dua hal: rangkaian teks dan beberapa bitmap yang ditempatkan. Pandangan tersebut benar sejauh ini, dan membiarkan bagian format yang paling mumpuni tidak digunakan. Halaman PDF adalah kanvas 2D yang tidak bergantung pada resolusi (resolution-independent) yang dibangun di atas model pencitraan yang sama dengan PostScript. Dokumen ini dapat menggambar garis, kurva, area berisi (filled regions), gradien, dan pola berulang, semuanya sebagai vektor yang tetap tajam pada perbesaran apa pun dan dicetak pada resolusi penuh perangkat. Jika Anda menggambar logo, grafik, tanda air, atau batas sertifikat, jalur vektor (vector path) hampir selalu merupakan primitif yang tepat, dan itu lebih kecil serta lebih tajam daripada gambar raster yang sering dipilih oleh banyak program.
Artikel ini membahas model vektor sebagaimana yang didefinisikan dalam ISO 32000-1 dan menunjukkan panggilan PDFlibPas yang cocok. Tujuannya adalah untuk membuat spesifikasi tersebut konkret, karena API memetakan langsung ke spesifikasi tersebut, dan memahami salah satu darinya akan mengajarkan Anda tentang yang lainnya.
Halaman adalah mesin pembuat path
ISO 32000-1 §8.5 menjelaskan grafis dalam dua fase yang tidak pernah tumpang tindih. Pertama, Anda membuat jalur (path), yang merupakan geometri murni tanpa hasil yang terlihat. Kemudian Anda mengecat jalur tersebut dalam satu operasi yang menggarisbawahi garis luarnya (stroke), mengisi bagian dalamnya (fill), atau melakukan keduanya. Tidak ada yang muncul di halaman selama konstruksi. Path adalah urutan abstrak dari titik-titik dan segmen yang disimpan dalam status grafis hingga operator pengecatan mengonsumsinya, di mana pada titik itu ia dirender dan dibuang.
Sebuah path terbuat dari satu atau beberapa subpath. Subpath dimulai di satu titik dan berkembang dengan menambahkan segmen: garis lurus, kurva Bezier kubik, dan pada beberapa platform seluruh persegi panjang ditambahkan sebagai subpath tertutup mereka sendiri. Di PDFlibPas, Anda membuka path dengan StartPath, yang menetapkan titik awal, lalu memperpanjangnya dengan AddLineToPath and AddCurveToPath. Setiap panggilan memajukan titik saat ini (current point) secara implisit, sehingga segmen berikutnya berlanjut dari tempat segmen terakhir berakhir. ClosePath menggambar segmen lurus terakhir kembali ke awal subpath, yang penting untuk stroking karena menghasilkan sambungan garis (line join) yang nyata pada simpul penutup, bukan dua ujung terpisah.
// 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
Kurva menggunakan AddCurveToPath, yang mengambil dua titik kontrol Bezier dan titik akhir: AddCurveToPath(CtAX, CtAY, CtBX, CtBY, EndX, EndY). Kurva berjalan dari titik saat ini ke (EndX, EndY), ditarik ke arah dua titik kontrol di sepanjang jalurnya. Busur lingkaran tersedia melalui AddArcToPath(CenterX, CenterY, TotalAngle), di mana jari-jarinya diambil dari jarak antara titik saat ini dan pusatnya, dan mesin mengeluarkan busur sebagai rantai segmen Bezier. Persegi panjang memiliki jalan pintas, AddBoxToPath(Left, Top, Width, Height), yang menambahkan persegi panjang tertutup lengkap sebagai subpath-nya sendiri tanpa didahului oleh StartPath.
Dua aturan isian, dan mengapa keduanya berbeda
Saat Anda mengisi path yang melintasi dirinya sendiri atau berisi loop dalam (inner loop), perender memerlukan aturan untuk menentukan area mana yang berada di dalam bentuk dan mana yang merupakan lubang. ISO 32000-1 §8.5.3.3 mendefinisikan dua aturan, dan mereka dapat mengecat geometri yang sama secara berbeda. Aturan nonzero winding menghitung perlintasan bertanda dari sinar yang dipancarkan dari titik uji ke tak terhingga, menambahkan satu untuk setiap segmen yang melintas dari kiri ke kanan dan mengurangi satu untuk setiap segmen yang melintas ke arah sebaliknya; titik berada di dalam ketika totalnya tidak nol. Aturan even-odd mengabaikan arah dan hanya menghitung perlintasan, menyebut titik berada di dalam ketika hitungannya ganjil.
Kasus klasik di mana keduanya berbeda adalah bentuk dengan lubang, donat atau cincin pencuci. Gambar batas luar dan batas dalam di dalamnya. Di bawah aturan even-odd, loop dalam selalu melubangi bentuk, karena titik mana pun di antara kedua batas tersebut dilintasi sekali dan titik mana pun di dalam loop dalam dilintasi dua kali. Di bawah aturan nonzero winding, lubang hanya muncul jika putaran loop dalam berlawanan arah dengan loop luar; jika diputar ke arah yang sama, putaran tersebut akan memperkuat alih-alih membatalkan, dan area dalam akan terisi penuh. Bintang bersudut lima yang digambar sebagai garis luar tunggal yang berpotongan sendiri menunjukkan pembagian yang sama: even-odd membiarkan pentagon tengah kosong sementara nonzero winding mengisinya.
PDFlibPas memilih aturan tersebut berdasarkan panggilan yang Anda buat untuk mengecat, bukan berdasarkan bendera (flag). DrawPath mengisi dengan aturan nonzero winding; DrawPathEvenOdd mengisi dengan aturan even-odd. Keduanya mengambil mode integer yang sama: 0 hanya menggarisbawahi garis luar, 1 hanya mengisi, dan 2 mengisi serta menggarisbawahi. Aturan even-odd adalah alat yang lebih mudah untuk lubang tembusan karena tidak mengharuskan Anda mengatur arah subpath.
// 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
Gradien aksial memvariasikan warna di sepanjang garis
Warna isian datar (flat fill) adalah satu nilai di seluruh area. Gradien memvariasikan warna secara terus-menerus, dan jenis yang paling sederhana adalah gradien aksial, atau linier. ISO 32000-1 §8.7.4.5 menentukannya sebagai arsir aksial Tipe 2 (Type 2 axial shading): Anda memberikan dua titik yang menentukan sumbu, warna awal pada titik pertama dan warna akhir pada titik kedua, dan perender menginterpolasi warna di sepanjang sumbu tersebut. Setiap titik di area yang terisi mengambil warna dari proyeksi tegak lurusnya ke sumbu, sehingga gradien berjalan dalam pita-pita pada sudut siku-siku terhadap garis di antara kedua titik tersebut.
Di PDFlibPas, gradien adalah sumber daya dokumen bernama yang Anda buat sekali dan kemudian pilih sebagai cat aktif. NewRGBAxialShader mendaftarkannya. Tanda tangannya adalah NewRGBAxialShader(ShaderName, StartX, StartY, StartRed, StartGreen, StartBlue, EndX, EndY, EndRed, EndGreen, EndBlue, Extend): dua ujung sumbu, tiga nilai RGB di setiap ujung sebagai nilai dalam rentang 0 hingga 1, dan bendera Extend. Dengan Extend disetel ke 1, warna ujung akan berlanjut sebagai isian padat di luar ujung sumbu, yang biasanya Anda inginkan agar sudut area di luar sumbu tidak dibiarkan tidak dicat; 0 membiarkannya tidak tersentuh. Setelah shader ada, Anda mengikatnya dengan SetFillShader untuk area terisi, SetLineShader untuk garis luar stroke, atau SetTextShader untuk teks. Pengikatan tetap aktif untuk panggilan gambar berikutnya, sehingga path yang Anda cat berikutnya akan mengambil gradien alih-alih warna datar.
// 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
Sumbu di sini adalah vertikal, dari y=100 ke y=260 pada x yang tetap, sehingga pita warna berjalan secara horizontal dan persegi panjang memudar dari biru di dasarnya menjadi putih di bagian atasnya. Karena shader dipanggil berdasarkan nama, satu definisi dapat mengisi berapa pun bentuk pada halaman, dan beralih kembali ke warna datar hanyalah panggilan SetFillColor biasa sebelum path berikutnya.
Pola ubin mengulangi sebuah sel
Ketika gradien memvariasikan satu warna dengan mulus, pola ubin (tiling pattern) mengulangi sebagian kecil karya seni di seluruh area. ISO 32000-1 §8.7.3.1 mendefinisikan pola ubin sebagai sel pola (pattern cell), yaitu bagian konten independen, yang direplikasi oleh perender pada kisi tetap untuk mengubin area yang sedang dicat. Ini adalah cara Anda membangun arsir (hatching) untuk isian teknik, motif merek yang berulang di belakang header, atau latar belakang bertekstur yang tetap tajam dalam bentuk vektor dan hampir tidak memiliki bobot ukuran berkas tidak peduli seberapa besar areanya, karena sel tersebut disimpan sekali dan direferensikan di mana-mana.
PDFlibPas membangun sel pola dari konten halaman yang ditangkap. Anda menangkap halaman atau area dengan CapturePage, mengubah tangkapan tersebut menjadi pola bernama dengan NewTilingPatternFromCapturedPage(PatternName, CaptureID), lalu memilih pola tersebut sebagai isian saat ini dengan SetFillTilingPattern(PatternName). Sejak saat itu, setiap path yang Anda isi dicat dengan sel berulang alih-alih warna datar, persis seperti cara kerja isian shader tetapi dengan sel ubin sebagai sumber catnya. Urutannya lebih rumit daripada satu panggilan, jadi jika langkah penangkapan (capture) belum familier, perlakukan pola sebagai operasi dua tahap: hasilkan sel yang ditangkap terlebih dahulu, lalu ikat sebagai isian berdasarkan nama sebelum menggambar area yang ingin Anda ubin.
Menyatukan primitif bersama-sama
Bagian-bagian tersebut disusun secara langsung. Blob Bezier yang terisi adalah path kurva yang dicat dengan DrawPath. Garis luar yang sama yang dicat dengan DrawPathEvenOdd setelah menambahkan loop dalam akan menunjukkan lubang yang jika menggunakan isian winding akan tertutup. Persegi panjang berisi gradien adalah kotak yang diikat ke shader. Contoh di bawah ini menggambar ketiganya secara berurutan sehingga perbedaan antara kedua aturan isian tersebut terlihat pada satu halaman, lalu meletakkan panel gradien di bawahnya.
// 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);
Dua detail penting untuk ingat. Panggilan pengecatan menentukan aturan isian, jadi pilihan antara DrawPath dan DrawPathEvenOdd adalah pilihan antara nonzero winding dan even-odd, dan untuk bentuk dengan lubang, aturan even-odd membebaskan Anda dari keharusan memikirkan arah subpath. Dan status grafis (graphics state) diambil sampelnya pada saat Anda mengecat: atur warna, lebar garis, dan pengikatan shader Anda sebelum panggilan pengecatan, karena itulah status yang dibaca mesin. Buat terlebih dahulu, konfigurasikan statusnya, cat terakhir, dan model vektor akan berperilaku secara terprediksi setiap saat.
Dari sini, langkah wajar berikutnya adalah membaca kembali vektor dan teks dari dokumen yang sudah ada, yang dibahas dalam artikel kami tentang ekstraksi teks, gambar, dan font, dan merender model gambar yang sama ke Windows device context untuk pratinjau di layar dan pencetakan, yang dibahas dalam panduan cetak dan pratinjau. Panggilan path, shader, dan pola yang dijelaskan di sini dikirim sebagai bagian dari Delphi PDF Library bersama dengan API teks, gambar, formulir, dan penandatanganan yang dibahas di bagian lain di blog ini.