Artikel Teknis

HotPDF canvas draw: gambar vektor PDF di Delphi

HotPDF menggambar grafik vektor dengan membangun jalur pada halaman saat ini lalu memintanya dicepat. Tidak ada langkah bitmap di antaranya. Garis yang Anda gambar dengan MoveTo dan LineTo berakhir sebagai operator jalur PDF dalam aliran konten, sehingga tetap vektor sejati: tajam pada zoom 50%, tajam pada 1600%, dan sebagian kecil dari ukuran yang akan dikenakan versi raster. Untuk diagram, aturan tabel, sumbu bagan, dan dekorasi formulir, itu persis yang Anda inginkan, dan API di baliknya cukup kecil untuk dipelajari dalam satu dudukan

Seluruh permukaan gambar hidup pada THotPDF.CurrentPage. Antara BeginDoc dan EndDoc Anda menyetel warna dan lebar garis pada objek halaman itu, meletakkan geometri, dan memanggil operator pengecatan untuk meng-commit-nya. Empat primitif yang akan paling Anda gunakan adalah MoveTo dan LineTo untuk jalur arbitrer, Rectangle untuk kotak, Circle untuk cakram, dan dua operator pengecatan Stroke dan Fill

Sistem koordinat adalah kiri-bawah

Inilah satu hal yang menjebak semua orang yang tiba dari VCL. TCanvas yang Anda gunakan untuk mengecat kontrol menempatkan origin di sudut kiri-atas dengan Y tumbuh ke bawah. PDF melakukan sebaliknya. HotPDF mengukur dari sudut kiri-bawah halaman dalam poin (1/72 inci), dengan Y meningkat saat Anda bergerak ke atas. Titik di Y := 720 duduk dekat atas halaman US Letter, yang tingginya 792 poin, dan Y := 50 duduk dekat bawah. Jika gambar pertama Anda keluar tercermin vertikal, inilah sebabnya: kode yang diporting dari grafik layar mengasumsikan arah yang salah dan keluar dari tepi bawah

Konvensi yang sama mengatur TextOut, jadi teks dan bentuk berbagi satu model mental setelah Anda internalisasi. Rencanakan tata letak dengan memutuskan di mana dasar setiap elemen duduk, bukan atasnya, dan sisanya mengikuti

Perbandingan asal kiri-atas kanvas layar dan asal kiri-bawah PDF: titik yang sama dekat atas halaman US Letter terbaca Y = 72 dalam kode TCanvas tetapi Y = 720 dalam poin HotPDF, sehingga kode yang diporting tanpa membalik Y menggambar secara termirroring
HotPDF mengukur dari sudut kiri-bawah dalam point, sehingga titik dekat puncak halaman 612 kali 792 membawa Y = 720 — lokasi fisik yang sama yang dialamatkan kode TCanvas sebagai Y kecil ke bawah

Jalur: MoveTo, LineTo, Stroke

Jalur yang digores adalah pena diangkat, ditempatkan, dan diseret. MoveTo mengangkat pena dan menyetel titik awal tanpa menandai apa pun. Setiap LineTo memperluas jalur saat ini ke titik baru. Tidak ada yang muncul di halaman sampai Anda memanggil Stroke, yang menggambar jalur yang terakumulasi menggunakan warna gores dan lebar garis saat ini, lalu membersihkan jalur sehingga MoveTo berikutnya mulai segar

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'DrawPaths.pdf';
    Pdf.BeginDoc;

    // Lebar garis dalam poin dan berlaku sampai Anda mengubahnya.
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.SetRGBStrokeColor(clBlack);

    // Sebuah aturan horizontal dekat atas halaman (Y diukur dari bawah).
    Pdf.CurrentPage.MoveTo(72, 720);
    Pdf.CurrentPage.LineTo(523, 720);
    Pdf.CurrentPage.Stroke;          // commit jalur ini; tidak ada yang tergambar sebelumnya

    // Polyline tersambung yang lebih tebal: tiga segmen dalam satu jalur.
    Pdf.CurrentPage.SetLineWidth(3);
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(30, 90, 200));
    Pdf.CurrentPage.MoveTo(72, 640);
    Pdf.CurrentPage.LineTo(172, 690);
    Pdf.CurrentPage.LineTo(272, 620);
    Pdf.CurrentPage.LineTo(372, 680);
    Pdf.CurrentPage.Stroke;

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Dua detail menghemat waktu debug nyata. Lebar garis adalah status, bukan argumen: SetLineWidth menyetelnya sekali dan setiap Stroke berikutnya menggunakan nilai itu sampai Anda mengubahnya lagi, yang menjadi alasan polyline di atas lebih tebal dari aturan. Dan jalur mereset setelah setiap Stroke, jadi Stroke yang terlupakan berarti geometri yang Anda susun dengan begitu hati-hati tidak pernah merender sama sekali. Jika sebuah bentuk hilang dari output, panggilan pengecatan adalah tempat pertama untuk dicari

Koordinat adalah poin, dan poin adalah pecahan. MoveTo dan LineTo menerima nilai Single, jadi hairline di 0.5 poin atau posisi di 72.25 legal dan bermakna, tidak dibulatkan ke unit utuh terdekat. Presisi itu penting dalam dua arah berlawanan. Lebar garis di bawah sekitar 0.5 bisa merender sebagai garis-setipis-mungkin yang bergantung perangkat yang hilang di layar dan muncul kembali saat dicetak, jadi aturan yang terlihat menginginkan lebar yang Anda setel dengan sengaja alih-alih default. Di ujung lain, menjentikkan aturan tabel dan garis kisi ke koordinat poin-utuh menjaga kisi padat dari terlihat sedikit tidak rata di mana garis yang berdekatan membulat berbeda. Putuskan spasi kisi dalam poin di muka dan seluruh tata letak mewarisinya

Bentuk terisi dan warna

Primitif tertutup bisa diisi alih-alih digaris-besarkan. Rectangle mengambil posisi dan ukuran, Circle mengambil pusat dan jari-jari, dan salah satunya di-commit dengan Fill, yang mengecat interior dalam warna isian saat ini, atau dengan Stroke untuk kerangka saja. Warna isian dan warna gores adalah bagian status terpisah, disetel dengan SetRGBFillColor dan SetRGBStrokeColor, keduanya mengambil satu TColor. Itu berarti Anda bisa menggunakan kembali konstanta warna Delphi dan pembantu RGB secara langsung

Model path HotPDF: MoveTo, LineTo, Rectangle, dan Circle membangun current path tak terlihat dalam memori, dan hanya commit Stroke, Fill, atau FillAndStroke yang melukisnya menggunakan status grafis persisten berupa stroke color, fill color, dan line width sebelum mengosongkan buffer
Geometri terakumulasi diam-diam di path saat ini sampai sebuah painting operator meng-commit-nya dengan stroke color, fill color, dan line width yang tersimpan — panggilan paint yang terlupa membiarkan bentuk tak tergambar
// Rectangle(X, Y, Width, Height): X dan Y adalah sudut kiri-bawah.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;

// Circle(X, Y, Radius): X dan Y adalah pusatnya.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;

// Hanya kerangka: atur warna gores dan lebar, lalu Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 400, 160, 60);
Pdf.CurrentPage.Stroke;

Perhatikan bentuk argumen pada Rectangle. Itu posisi-plus-ukuran, X, Y, Width, Height, bukan dua sudut berlawanan. TCanvas.Rectangle yang dikenal pengembang Delphi mengambil (Left, Top, Right, Bottom), jadi ingatan otot akan menyerahkan sudut kedua ke HotPDF di mana ia mengharapkan lebar dan tinggi, dan kotak keluar ukuran yang salah. Pasangan (X, Y) adalah sudut kiri-bawah, konsisten dengan origin halaman. Untuk lingkaran, (X, Y) adalah pusat dan argumen ketiga adalah jari-jari dalam poin

Satu pilihan warna yang salah didapat oleh contoh asli

Versi lebih lama dari contoh ini menyemai warna dengan Random($FFFFFF) pada setiap bentuk. Itu terlihat hidup, dan itu insting yang salah untuk dokumen yang dihasilkan. PDF yang Anda bangun dari kode biasanya juga sesuatu yang ingin Anda uji, dan warna isian acak membuat output tidak mungkin dibandingkan run-ke-run: diff byte-demi-byte terhadap file diketahui-baik gagal setiap kali, tanpa alasan nyata. Pilih warna eksplisit. Saat Anda ingin variasi melintasi serangkaian bentuk, kendarikan dari data Anda atau array palet tetap, sehingga input yang sama selalu menghasilkan file yang sama. Determinisme bernilai lebih dari kebaruan ketika artefak bergerak melalui pipeline rilis

Menyusun primitif bersama: sebuah kotak callout

Setiap primitif sederhana sendiri; hasilnya terlihat saat segelintir mereka menyusun menjadi sesuatu yang benar-benar dibutuhkan laporan. Callout, kotak beranotasi yang menunjuk pada figur dan menjelaskannya, menggunakan semua yang dicakup sejauh ini: persegi panjang terisi dengan batas, garis penunjuk yang digores, titik untuk menjangkar penunjuk, dan teks yang ditata di dalam kotak menggunakan koordinat kiri-bawah yang sama yang digunakan bentuk. FillAndStroke mendapat tempatnya di sini, mengecat interior dan kerangka satu jalur dalam satu commit alih-alih membangun persegi panjang dua kali

Anatomi kotak callout yang dirakit dari primitif HotPDF: persegi panjang yang dicommit dengan FillAndStroke, garis pointer yang digores dari MoveTo dan LineTo, titik jangkar yang diisi, dan bilah label TextOut yang berbagi grid kiri-bawah yang sama dengan bentuk-bentuk itu
Empat commit membangun callout — FillAndStroke melukis panel dan bingkai sekaligus, pointer dan titik memakai ulang state stroke dan fill, dan setiap offset label adalah aritmetika polos terhadap sudut box (90, 600)
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'Callout.pdf';
    Pdf.BeginDoc;

    // 1. Kotaknya: isian pucat plus batas terlihat, satu jalur, satu commit.
    //    Rectangle adalah sudut kiri-bawah plus ukuran, Y diukur dari bawah
    Pdf.CurrentPage.SetRGBFillColor(RGB(255, 244, 214));   // panel amber pucat
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(180, 130, 40));  // rim yang lebih gelap
    Pdf.CurrentPage.SetLineWidth(1);
    Pdf.CurrentPage.Rectangle(90, 600, 240, 70);
    Pdf.CurrentPage.FillAndStroke;

    // 2. Penunjuknya: satu segmen tergaris dari tepi kotak turun
    //    ke arah objek yang sedang dianotasi
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.MoveTo(90, 615);        // tepi kiri kotak
    Pdf.CurrentPage.LineTo(66, 546);
    Pdf.CurrentPage.Stroke;

    // 3. Sebuah titik terisi menjangkarkan penunjuk pada targetnya
    Pdf.CurrentPage.SetRGBFillColor(RGB(180, 130, 40));
    Pdf.CurrentPage.Circle(64, 542, 3);
    Pdf.CurrentPage.Fill;

    // 4. Labelnya, diposisikan relatif terhadap sudut kiri-bawah kotak.
    //    Teks dan bentuk berbagi satu sistem koordinat, sehingga offset-nya
    //    adalah aritmetika biasa terhadap (90, 600)
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 10);
    Pdf.CurrentPage.TextOut(102, 645, 0, 'Check this total');
    Pdf.CurrentPage.SetFont('Arial', [], 9);
    Pdf.CurrentPage.TextOut(102, 628, 0, 'The rounding rule changed in the');
    Pdf.CurrentPage.TextOut(102, 616, 0, 'June release; verify against v2.1');

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Perhatikan betapa sedikit manajemen status yang dibutuhkan komposit. Warna isian, warna gores, dan lebar garis masing-masing disetel segera sebelum bentuk yang menggunakannya, sehingga setiap blok gambar terbaca sebagai unit mandiri dan bisa diurutkan ulang atau diekstrak ke pembantu tanpa menyeret status tersembunyi. Bungkus ini dalam prosedur yang mengambil titik jangkar dan string, dan Anda memiliki anotasi diagram yang dapat digunakan kembali dengan biaya empat puluh baris

Di mana gambar vektor berguna, dan di mana tidak

Jangkau panggilan jalur dan bentuk ini ketika geometri dihasilkan: garis kisi bagan dan batang, garis yang ditarik tabel faktur, kotak callout pada diagram, tanda logo yang dinyatakan sebagai segelintir jalur. Semuanya diskalakan tanpa kabur dan menambah hampir tidak ada apa-apa ke ukuran file, karena persegi panjang adalah beberapa angka alih-alih ribuan piksel. Sisi sebaliknya juga jujur. Jika yang sebenarnya Anda miliki adalah foto atau tangkapan layar, gambar sebagai gambar dengan AddImage dan ShowImage sebagai gantinya; menelusuri bitmap dengan panggilan vektor tidak membeli Anda apa pun. Segmen lurus, persegi panjang, dan lingkaran di atas membawa mayoritas besar pekerjaan pelaporan nyata, dan tiga penyempurnaan yang ditanyakan pengembang berikutnya, kurva, pola garis-putus, dan transparansi, duduk pada objek halaman yang sama

Kurva, garis-putus, dan transparansi secara singkat

Kurva bebas memperluas mesin jalur yang sama yang sudah Anda miliki. CurveToC(X1, Y1, X2, Y2, X3, Y3) menambahkan segmen Bezier kubik dari titik saat ini ke (X3, Y3), membungkuk ke arah dua titik kontrol, dan varian singkat CurveToV dan CurveToY mencakup kasus di mana satu titik kontrol bertepatan dengan titik akhir. Jalur bisa mencampur segmen LineTo dan CurveToC secara bebas sebelum satu Stroke atau Fill meng-commit-nya, yang merupakan bagaimana sudut membulat dan garis bagan halus dibangun

Goresan putus-putus adalah status, persis seperti lebar garis. SetDash([3, 3], 0) mengganti setiap goresan berikutnya ke pola tiga-poin-nyala, tiga-poin-mati, array mengeja panjang nyala-mati dalam poin dan argumen kedua memfase di mana siklus dimulai; NoDash mengembalikan pena ke garis padat. Setel, gores garis kisi yang menginginkannya, dan reset sebelum aturan padat berikutnya, atau putus-putus diam-diam menginfeksi semua yang mengikuti

Transparansi berjalan melalui status-grafik bernama alih-alih argumen warna, karena alfa dalam PDF adalah properti kamus status-grafik. Daftarkan satu pada dokumen dengan RegisterExtGState, meneruskan alfa isian dan alfa goresan antara 0 dan 1, lalu terapkan nama yang dikembalikannya dengan CurrentPage.SetGraphicsState; isian dan goresan dari titik itu mengecat pada opasitas terdaftar. Itu upacara yang lebih berat daripada penyetel warna, dan berharga pertama kali batang sorot harus duduk di atas teks tanpa menyembunyikannya

Kebiasaan tersisa yang layak dijaga adalah verifikasi. Geometri yang dihasilkan bisa lewat di mesin Anda dan gagal di pelanggan, biasanya karena substitusi font dalam teks apa pun yang Anda campur atau asumsi ukuran-halaman yang tidak berlaku. Buka file yang selesai pada beberapa tingkat zoom untuk mengkonfirmasi tepi tetap bersih, dan periksa bahwa setiap bentuk mendarat di dalam kotak margin yang Anda maksudkan. Dengan skema warna deterministik, pemeriksaan itu bisa diautomasi terhadap PDF referensi alih-alih dilihat dengan mata

Panggilan MoveTo, LineTo, Stroke, Fill, dan warna yang ditunjukkan di sini adalah bagian dari HotPDF Delphi Component untuk Delphi dan C++Builder