Menempelkan stempel tanda air atau logo ke setiap halaman dokumen terlihat seperti pekerjaan lima menit sampai Anda membuka hasilnya di pemeriksa ukuran berkas. Pendekatan yang jelas adalah berjalan di halaman-halaman dan, di setiap halaman, membangun objek teks atau gambar yang sama lagi. Hal tersebut berfungsi secara visual, dan boros dengan cara yang berlipat ganda. Tanda air "DRAFT" diagonal yang digambar langsung ke laporan seratus halaman adalah seratus salinan dari data path dan teks yang sama yang duduk di aliran konten, dan berkas yang disimpan membawa masing-masing dari mereka.
Form XObject adalah konstruksi yang disediakan PDF untuk menghindari hal ini secara tepat. Ia membungkus bagian konten yang dapat digunakan kembali, seluruh halaman atau templat kecil, menjadi objek bernama tunggal yang dapat dicat berkali-kali di banyak posisi. Konten hidup di dalam berkas sekali. Setiap halaman yang menginginkan stempel menyimpan instruksi singkat yang mengatakan "cat XObject N di sini, dengan transformasi ini". Tanda air seratus halaman kemudian menambahkan satu objek konten ke berkas alih-alih seratus, dan itulah perbedaan antara dokumen yang tumbuh secara linier dengan jumlah halamannya dan dokumen yang tidak. Tanda air, stempel logo, templat nomor halaman, dan segel semuanya adalah bentuk masalah yang sama, dan Form XObject adalah alat yang tepat untuk masing-masing masalah tersebut.
Mengapa satu objek tersimpan mengalahkan seratus penggambaran ulang
Penghematan tersebut bersifat struktural, bukan kosmetik. Halaman PDF merender dengan mengeksekusi aliran kontennya, urutan operator gambar. Ketika Anda menggambar ulang stempel per halaman, Anda menambahkan urutan operator lengkap untuk stempel tersebut ke setiap aliran halaman, dan byte diduplikasi sebanyak halaman yang Anda miliki. Form XObject memindahkan operator tersebut ke dalam satu aliran yang disimpan sekali dalam dokumen. Referensi yang disimpan halaman individu kecil: ia mendorong matriks transformasi, memanggil XObject, dan memulihkan status. Jumlah halaman tidak lagi melipatgandakan biaya karya seni.
Ini sangat penting ketika stempelnya berat. Segel vektor dengan ratusan segmen path, atau bitmap logo, mahal untuk disimpan. Disimpan sekali dan direferensikan, bagian yang berat dibayar sekali saja dan biaya tambahan per halaman adalah beberapa byte panggilan. Hasil visual pada halaman identik dengan gambar ulang langsung, yang merupakan intinya. Pembaca tidak dapat membedakannya; ukuran berkas sangat bisa membedakannya.
Menangkap halaman ke dalam XObject
PDFium membangun objek yang dapat digunakan kembali dari halaman yang sudah ada. Sumbernya adalah halaman dalam beberapa dokumen yang Anda buka, PDF satu halaman kecil yang tidak berisi apa-apa selain karya seni tanda air Anda, atau halaman tertentu dari berkas yang lebih besar. CreateXObjectFromPage menangkap konten halaman sumber tersebut ke dalam handle yang dapat digunakan kembali yang dimiliki oleh dokumen tujuan, yaitu dokumen yang sedang Anda stempel.
var
Dest, Stamp: TPdf;
XObject: TPdfXObject;
begin
Dest := TPdf.Create;
Stamp := TPdf.Create;
try
Dest.LoadFromFile('Report.pdf');
Stamp.LoadFromFile('Watermark.pdf'); // one page of artwork
// Capture page 0 of the stamp document into a reusable handle that
// is owned by Dest. Source must be active; the index is zero-based.
XObject := Dest.CreateXObjectFromPage(Stamp, 0);
if XObject = nil then
raise Exception.Create('Could not build the stamp XObject');
// ... place it, then free it before closing Stamp (see below) ...
Tanda tangannya adalah CreateXObjectFromPage(Source: TPdf; SourcePageIndex: Integer): TPdfXObject. Metode mengembalikan nil jika gagal alih-alih memunculkan kesalahan, sehingga pemeriksaan eksplisit di atas tidak opsional. Handle yang kembali adalah TPdfXObject yang Anda miliki, dan dua batasan masa pakai (lifetime constraints) yang dilampirkan padanya adalah bagian dari seluruh latihan ini yang menjebak orang, sehingga mereka mendapatkan bagian mereka sendiri di bawah.
Menempatkan stempel pada halaman
XObject yang ditangkap tidak melakukan apa-apa dengan sendirinya. Untuk membuatnya muncul, Anda menyisipkan salinannya ke halaman dokumen saat ini dengan InsertFormObjectFromXObject. Panggilan tersebut mengembalikan objek halaman yang mendasarinya, yaitu FPDF_PAGEOBJECT, dan handle yang dikembalikan adalah cara Anda memposisikan penempatan. Tanpa transformasi, stempel mendarat di titik asal dalam koordinat halaman sumber sendiri, yang jarang Anda inginkan.
Karena InsertFormObjectFromXObject menyisipkan satu salinan per panggilan dan mengembalikan objek halaman baru setiap kali, Anda dapat mengecat XObject yang sama beberapa kali pada satu halaman dengan transformasi yang berbeda, dan konten yang disimpan tetap dihitung sekali dalam berkas. Logo sudut dan tanda air halaman penuh yang samar dapat berasal dari objek tangkapan yang sama.
var
PageObj: FPDF_PAGEOBJECT;
M: TPdfMatrix;
begin
// The current page of Dest receives one copy of the XObject.
PageObj := Dest.InsertFormObjectFromXObject(XObject);
if PageObj = nil then
raise Exception.Create('Insert failed on this page');
// Position it: move 200 units right, 500 up, at 70% scale.
M := TPdfMatrix.Create;
try
M.Scale(0.7, 0.7);
M.Translate(200, 500);
FPDFPageObj_SetMatrix(PageObj, M.Handle);
finally
M.Free;
end;
// Dest.SaveLoadedDocument(...) when every page is done.
end;
Satu detail kepemilikan membuat pembersihan aman. Setelah disisipkan, objek halaman adalah milik halaman, bukan milik XObject. Membebaskan XObject kemudian tidak membatalkan penempatan yang sudah Anda buat. Itulah yang memungkinkan urutan create-place-free yang dijelaskan di bawah ini berfungsi.
Aturan masa pakai handle yang sering menjebak orang
Dua batasan mengatur handle XObject, dan mengabaikan salah satunya menghasilkan kegagalan yang tampak tidak terkait dengan penyebabnya. Pertama, dokumen sumber harus aktif pada saat Anda memanggil CreateXObjectFromPage. Tangkapan membaca konten halaman sumber dari dokumen sumber langsung, sehingga dokumen dan halamannya harus terbuka dan valid saat handle dibangun. Kedua, dan ini yang mengejutkan orang, handle harus dibebaskan sebelum halaman sumber ditutup, dan dalam praktiknya sebelum Anda menutup atau membebaskan dokumen sumber asalnya.
Alasannya adalah bahwa XObject adalah referensi ke dalam struktur yang masih dimiliki oleh dokumen sumber. Ia bukan salinan terpisah yang berdiri sendiri yang dapat Anda bawa-bawa setelah sumbernya hilang. Tutup sumbernya terlebih dahulu dan handle dibiarkan menunjuk ke konten yang telah dirobohkan, sehingga membebaskannya nanti, atau penggunaan lainnya, beroperasi pada memori yang tidak lagi valid. Gejalanya adalah klasik untuk handle menggantung: access violation saat shutdown, atau kerusakan terputus-putus yang berpindah-pindah tergantung pada urutan alokasi, dengan tumpukan (stack) yang menunjuk ke kode pembersihan alih-alih garis yang sebenarnya menyebabkan masalah. Perbaikannya adalah pengurutan, bukan pengodean defensif. Bangun XObject, sisipkan ke setiap halaman yang membutuhkannya, bebaskan XObject, dan baru kemudian tutup dokumen sumber. Destruktor TPdfXObject melepaskan handle PDFium yang mendasarinya untuk Anda, sehingga membebaskan pembungkus (wrapper) pada waktu yang tepat adalah seluruh tanggung jawab Anda.
Matriks, dan apa arti enam angka tersebut
Penempatan adalah transformasi afin 2D, sama dengan yang digunakan PDF di mana-mana untuk memposisikan konten (ISO 32000-1, bagian 8.3.4). Ini adalah kem angka, ditulis a, b, c, d, e, f, dan PDFium mengeksposnya sebagai rekaman FS_MATRIX. Mereka memetakan titik dari ruang objek itu sendiri ke ruang halaman:
// x' = a*x + c*y + e
// y' = b*x + d*y + f
//
// a, d : horizontal and vertical scale
// b, c : the shear / rotation terms
// e, f : translation (where the origin lands on the page)
Anda dapat mengisi keenam nilai tersebut secara manual, tetapi menyusunnya secara manual adalah tempat terjadinya kesalahan rotasi, karena rotasi mencampur keempat nilai a, b, c, d bersama-sama. Pembungkus TPdfMatrix menyusun operasi umum untuk Anda dan melakukan post-multiplies saat berjalan, sehingga Translate, Scale, dan Rotate terikat dalam urutan Anda memanggilnya. Tanda air diagonal adalah rotasi yang diikuti oleh penerjemahan (translation) untuk menempatkannya kembali ke tengah; logo sudut adalah skala yang diikuti oleh penerjemahan. Ketika matriks siap, serahkan nilai mentahnya ke FPDFPageObj_SetMatrix(PageObj, M.Handle), di mana M.Handle is FS_MATRIX yang mendasarinya. Tingkat lebih rendah FPDFPageObj_Transform, yang mengambil enam nilai secara langsung sebagai doubles, tersedia ketika Anda lebih suka meneruskan angka alih-alih membangun pembungkus.
Menyetempel setiap halaman, dalam urutan yang benar
Pola lengkap menyatukan potongan-potongan tersebut dengan urutan yang dituntut oleh aturan masa pakai. Buka kedua dokumen, tangkap stempel sekali, telusuri halaman tujuan memilih masing-masing secara bergiliran dan menyisipkan serta memposisikan salinan, lalu bebaskan XObject, lalu simpan, dan biarkan dokumen sumber ditutup terakhir.
procedure StampEveryPage(const ASource, AStamp, AOutput: string);
var
Dest, Stamp: TPdf;
XObject: TPdfXObject;
PageObj: FPDF_PAGEOBJECT;
M: TPdfMatrix;
i: Integer;
begin
Dest := TPdf.Create;
Stamp := TPdf.Create;
try
Dest.LoadFromFile(ASource);
Stamp.LoadFromFile(AStamp);
// 1. Capture the artwork once. Stamp is active here.
XObject := Dest.CreateXObjectFromPage(Stamp, 0);
if XObject = nil then
raise Exception.Create('Could not capture the stamp page');
try
// 2. Place a copy on every page of Dest.
for i := 0 to Dest.PageCount - 1 do
begin
Dest.CurrentPageIndex := i; // make page i current
PageObj := Dest.InsertFormObjectFromXObject(XObject);
if PageObj = nil then
Continue;
M := TPdfMatrix.Create;
try
M.Rotate(45); // diagonal watermark
M.Translate(150, 100); // nudge into position
FPDFPageObj_SetMatrix(PageObj, M.Handle);
finally
M.Free;
end;
end;
finally
XObject.Free; // 3. free BEFORE Stamp closes
end;
// 4. Write the result while Dest is still open.
Dest.SaveLoadedDocument(AOutput);
finally
Stamp.Free; // source closes last
Dest.Free;
end;
end;
Bentuk blok try melakukan pekerjaan yang sebenarnya. Bagian finally dalam membebaskan XObject sebelum kontrol dapat mencapai bagian finally luar yang membebaskan Stamp, sehingga handle selalu dilepaskan saat sumbernya masih hidup, bahkan jika pengecualian terjadi di tengah perulangan. Pahami sarang (nesting) tersebut dengan benar dan aturan masa pakai akan berjalan dengan sendirinya. (Gunakan pemilih halaman saat ini mana pun yang diekspos oleh build Anda; badan perulangan tetap sama dengan kedua cara tersebut.)
Penyetempelan adalah satu sudut dari perangkat yang lebih besar untuk membangun dan mengedit konten halaman. Jika stempel Anda sendiri adalah gambar alih-alih halaman yang ditangkap, mengonversi gambar ke dokumen PDF dengan PDFium membahas tentang memasukkan bitmap tersebut ke dalam dokumen terlebih dahulu. Dan ketika hal yang ingin Anda bawa di samping stempel yang terlihat adalah berkas alih-alih tinta di halaman, bekerja dengan lampiran PDF di Delphi menunjukkan sisi berkas yang tertanam. Semua itu dikirimkan bersama dengan PDFium Component untuk Delphi dan C++Builder, bersama dengan API perenderan, pengeditan, dan dokumen yang dibahas di bagian lain di blog ini.