HotPDF dapat membangun dokumen berhalaman dari pohon deklaratif alih-alih dari koordinat. Anda merakit THPDFDOMDocument dari section, stack, teks, list, dan table, menyerahkannya ke THPDFDOMRenderer, dan renderer akan mengukur, memberi halaman, menggambar perlengkapan halaman, dan, jika diminta, menerbitkan structure tree PDF/UA yang membuat hasilnya aksesibel. Kode layout tidak pernah menghitung koordinat y
Siapa pun yang pernah memelihara generator laporan berbasis koordinat tahu mengapa ini penting. Versi pertama berfungsi. Kemudian alamat pelanggan bertambah menjadi tiga baris, sebuah table bertambah barisnya, judul yang dilokalkan menjadi wrap, dan setiap posisi y di hilir menjadi salah. Perbaikan menumpuk sebagai pemeriksaan page break manual yang tersebar di logika bisnis, dan persyaratan tagged-PDF yang datang dua tahun kemudian tidak dapat dipasang belakangan pada kode yang tidak tahu apa itu paragraf
Apa yang dimiliki pohon ini, dan mengapa kepemilikannya ketat?
DOM ini menegakkan single ownership di setiap level: dokumen memiliki section-nya, section memiliki body, header, dan footer-nya, serta stack, container, dan table memiliki child-nya. Penggunaan ulang terjadi melalui Clone atau melalui factory yang terdaftar, tidak pernah dengan melampirkan objek yang sama ke dua parent. Aturan itu bukan sekadar formalitas. Komponen yang muncul dua kali dalam pohon akan diukur dua kali dengan constraint berbeda dan dibebaskan dua kali saat teardown
Konsekuensi praktis bagi kode pemanggil adalah bahwa helper mengembalikan instance baru. Mendaftarkan factory dengan RegisterComponent dan memanggil CreateComponent memberi Anda resep bernama yang menghasilkan komponen segar setiap kali, dan itulah cara perlengkapan berulang seperti blok tanda tangan atau footer legal seharusnya masuk ke dalam pohon
uses
HPDFDoc, HPDFLayoutDOM;
var
Doc: THPDFDOMDocument;
Section: THPDFDOMSection;
Table: THPDFDOMTable;
Row: THPDFDOMTableRow;
I: Integer;
begin
Doc := THPDFDOMDocument.Create;
Doc.GenerateStructure := True; // terbitkan structure tree PDF/UA
Doc.Language := 'en-US';
Section := Doc.AddSection;
Section.PageWidth := 595; // A4 dalam point
Section.PageHeight := 842;
Section.MarginLeft := 56;
Section.MarginTop := 56;
Section.MarginRight := 56;
Section.MarginBottom := 56;
Section.Style.FontName := 'Helvetica';
Section.Style.FontSize := 10;
Section.Body.AddHeading('Annual maintenance report', 1);
Section.Body.AddText('Every asset inspected during the reporting ' +
'period is listed below, grouped by site.');
Section.Body.AddSpacer(12);
Table := THPDFDOMTable.Create('assets');
Table.AddColumn(3); // bobot, bukan lebar absolut
Table.AddColumn(1);
Table.AddColumn(1);
Table.RepeatHeaders := True;
Row := Table.AddRow(18, True); // baris header
Row[0].Text := 'Asset';
Row[1].Text := 'Last service';
Row[2].Text := 'Status';
for I := 0 to High(Assets) do
begin
Row := Table.AddRow(16);
Row[0].Text := Assets[I].Name;
Row[1].Text := Assets[I].ServiceDate;
Row[2].Text := Assets[I].Status;
end;
Section.Body.Add(Table);
end;
Bagaimana pagination menghindari biaya kuadratik?
Cara naif memberi halaman pada sebuah pohon adalah dengan meng-clone apa pun yang tidak muat dan membawanya ke halaman berikutnya. Pada table dengan sepuluh ribu baris, ini meng-clone baris yang tersisa sekali per halaman dan mengubah dokumen linear menjadi dokumen kuadratik
HotPDF membelah secara sempit sebagai gantinya. Renderer level atas menelusuri child body berdasarkan indeks dan tidak pernah meng-clone seluruh section atau body. Hanya stack dan container bersarang yang benar-benar melintasi batas halaman yang subtree-nya di-clone, dan dua tipe leaf berat membawa cursor alih-alih salinan: kelanjutan teks menyimpan rentang karakter sumber yang masih tertunggak, dan kelanjutan table menyimpan potongan baris yang belum ditempatkan. Dokumen panjang tetap linear, dan paragraf panjang berbiaya sama baik ia terputus sekali atau lima kali
Pengukuran tetap jujur soal efek samping. THPDFLayoutElement.Measure diharuskan bebas dari efek samping menggambar, dan penempatan sesungguhnya selalu berjalan melalui THotPDF.PlaceLayoutElement, rutin pusat yang sama yang mengukur ulang fragmen yang ditempatkan, menyiapkan kepemilikan overflow, dan mencatat diagnostik. Renderer DOM hanya memutuskan kebijakan halaman baru, perlengkapan halaman, spasi, dan masa hidup kelanjutan
Aturan header table yang mencegah dokumen tak berhingga
Mengulang header table di berbagai halaman kedengarannya sederhana tetapi menyembunyikan dua mode kegagalan. HotPDF mensyaratkan bahwa baris header hanya muncul pada rangkaian awal baris berturut-turut, dan pemecahan pertama harus memuat semua baris header ditambah setidaknya satu baris body. Tanpa aturan kedua, header yang lebih tinggi daripada ruang tersisa menghasilkan halaman yang hanya berisi header, diikuti halaman identik berikutnya, tanpa henti
Halaman kelanjutan menggambar ulang header, dan salinan yang digambar ulang itu ditandai sebagai artifact, bukan sebagai konten, yang merupakan jawaban tepat baik untuk aksesibilitas maupun ekstraksi teks. Baris header asli tetap berada dalam struktur table logis tepat satu kali. Lewatkan ini dan screen reader akan mengumumkan ulang judul kolom di tengah data, dan ekstraktor teks akan menyisipkan baris header duplikat di antara baris body
Ada juga batas atas defensif pada kedalaman kelanjutan, karena komponen kustom bebas mengimplementasikan Split dengan cara yang selalu mengembalikan tail yang setara. Renderer memeriksa batas ini setelah melepaskan tail dan sebelum memulai halaman berikutnya, dan iterasi saat ini membebaskan tail dalam blok finally-nya sendiri, sehingga komponen pihak ketiga yang berperilaku salah gagal dengan error yang dapat didiagnosis alih-alih memenuhi disk
Satu elemen logis, banyak fragmen halaman
Tagging otomatis adalah tempat model pagination dan model struktur harus sepakat. Paragraf yang terputus di dua halaman adalah satu paragraf logis, sehingga harus tetap menjadi satu elemen struktur. Namun identifier marked content bersifat per halaman, sehingga setiap fragmen yang terlihat memerlukan MCID-nya sendiri pada halaman tempat ia muncul
HotPDF menyelesaikan ini dengan menjaga satu elemen struktur dan menambahkan referensi marked-content ke array /K-nya untuk setiap fragmen, dengan pasangan /Pg dan /MCID mengidentifikasi halaman dan identifiernya. Slot ParentTree untuk MCID itu menunjuk kembali ke elemen yang sama. Ini persis yang diharapkan ISO 14289, dan inilah sebabnya clone kelanjutan berbeda dari clone biasa: Clone biasa berarti konten logis baru dan mendapatkan identitas semantik baru, sedangkan clone kelanjutan internal mewarisi identitas komponen yang dilanjutkannya
Penggunaan ulang elemen dicari melalui indeks identitas semantik yang diurutkan berdasarkan pointer komponen dan dicari dengan perbandingan biner, yang menjaga pencarian tetap logaritmik pada pohon besar. Indeks hanya menyimpan referensi non-owning; masa hidup objek struktur itu sendiri tetap berada pada object graph PDF
Aturan struktur yang ditegakkan renderer di awal
Dengan GenerateStructure diaktifkan, beberapa aturan PDF/UA diperiksa saat pohon sedang dirender, bukan setelah berkas ada. Heading dimulai pada level 1 dan tidak boleh melompati level. LI hanya boleh muncul di dalam L, dan Lbl serta LBody hanya di dalam LI. TR milik table, dan TH serta TD milik baris. Figure tanpa teks alternatif ditolak dalam mode PDF/UA
Menolak di awal adalah pilihan yang disengaja di sini. Validator yang melaporkan teks alternatif yang hilang setelah dokumen ditulis memberitahu Anda bahwa sekumpulan sepuluh ribu statement perlu diregenerasi; renderer yang menolak komponen memberitahu Anda komponen mana, sementara data yang menghasilkannya masih dalam scope. Verifikasi konformansi tetap berada dalam pipeline sebagai langkah terpisah, dan mekanismenya dibahas dalam validasi PDF/A, PDF/X, dan PDF/UA
var
Pdf: THotPDF;
Renderer: THPDFDOMRenderer;
Stats: THPDFDOMRenderStatistics;
begin
Pdf := THotPDF.Create(nil);
Renderer := THPDFDOMRenderer.Create;
try
Pdf.FileName := 'maintenance-report.pdf';
Pdf.BeginDoc;
Stats := Renderer.Render(Doc, Pdf);
Pdf.EndDoc;
Writeln(Format('%d page(s), %d placement(s), %d split(s)',
[Stats.PageCount, Stats.PlacementCount, Stats.SplitCount]));
Writeln(Format('structure elements=%d marked content=%d artifacts=%d',
[Stats.StructureElementCount, Stats.MarkedContentCount,
Stats.ArtifactCount]));
Writeln(Format('deepest continuation chain: %d',
[Stats.MaximumContinuationDepth]));
finally
Renderer.Free;
Doc.Free;
Pdf.Free;
end;
end;
Rekaman statistik ini lebih berguna dari yang terlihat sekilas. SplitCount yang meningkat tajam setelah perubahan template biasanya berarti sebuah komponen mulai mengukur lebih tinggi daripada container-nya. MaximumContinuationDepth yang perlahan naik adalah peringatan dini untuk komponen yang Split-nya membuat kemajuan terlalu sedikit per halaman. Dan membandingkan ArtifactCount dengan jumlah halaman kelanjutan mengonfirmasi bahwa header yang diulang benar-benar ditandai sebagai artifact
Di mana DOM cocok berdampingan dengan API langsung
DOM tidak menggantikan penggambaran langsung; ia berada di atas objek halaman yang sama. Apa pun yang ditempatkan renderer dapat diselingi dengan panggilan langsung pada THotPDF, yang penting ketika sebuah laporan memerlukan satu elemen yang diposisikan manual seperti gambar tanda tangan pada lokasi yang tepat. Penutupan halaman tetap berada di bawah kendali AddPage dan EndDoc, sehingga mode immediate flush tidak menahan halaman yang sudah selesai di memori dan memori resident tetap dikendalikan oleh kelanjutan saat ini, resource font, dan object graph dokumen biasa
Pilih DOM ketika konten digerakkan oleh data dan layout digerakkan oleh aturan, dan pertahankan penggambaran langsung untuk artwork tetap. Jika masalah Anda saat ini secara khusus adalah pagination table, pendekatan yang lebih sempit dalam menghasilkan table dalam PDF layak dibaca lebih dulu, dan perilaku level teks seperti justifikasi dijelaskan dalam justifikasi teks
Layout deklaratif, tagging otomatis, dan API penggambaran langsung dikirimkan dalam komponen yang sama untuk Delphi dan C++Builder; daftar fitur lengkapnya ada di halaman komponen PDF Delphi HotPDF