PDF Library for Delphi menghapus operator identity text matrix, 1 0 0 1 0 0 Tm, dalam optimasi peephole content stream saat save hanya ketika text matrix dan text line matrix sudah berupa identity: tepat setelah BT, atau tepat setelah identity Tm sebelumnya. Identity cm tetap selalu dibuang, karena cm mengalikan CTM sedangkan Tm mengganti kedua text matrix sekaligus. Sejak v3.539.28 setiap identity Tm lainnya tetap berada di stream
Bug yang diperbaiki ini tipe yang senyap. Sebuah report generator mengeluarkan BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET, mengandalkan identity Tm untuk mengirim string kedua kembali ke origin text space sebelum menerapkan logika posisinya sendiri. Optimizer lama melihat enam angka yang membentuk identity matrix, menyimpulkan operator itu mustahil mengubah apa pun, dan menghapusnya. Tak ada yang gagal, tak ada yang mencatat warning, dan halaman tersimpan menggambar "Total" tepat setelah "Invoice" pada baseline yang sama — persis kelas cacat yang tak disadari siapa pun sampai pelanggan mencetak PDF-nya
Kenapa 1 0 0 1 0 0 Tm tak selalu no-op?
Identity Tm hanya no-op kalau ia akan mengganti dua matrix yang sudah berisi identity, dan itu properti dari operator sebelumnya, bukan dari operannya sendiri. ISO 32000-1 §9.4.1 menyatakan BT menginisialisasi text matrix (Tm) dan text line matrix (Tlm) ke identity, dan §9.4.2 mendefinisikan Tm sebagai penyetel keduanya ke nilai yang diberikan, bukan penggabung ke atasnya. Bandingkan dengan cm (§8.4.4) yang mengalikan kanan current transformation matrix: perkalian dengan identity membiarkan CTM mana pun tak berubah, jadi 1 0 0 1 0 0 cm aman dihapus di mana saja. Di dalam text object gambarnya berbeda. Td, TD, T*, dan Tm non-identity semuanya menggerakkan Tlm, dan setiap operator penampil teks (Tj, TJ, ', ") memajukan Tm selebar glyph yang digambar. Setelah salah satu dari itu, identity Tm adalah reset sungguhan ke origin. Kalau Anda pernah menelusuri posisi teks secara manual dengan state tracker CTM dan text matrix content stream, ini pembedaan yang sama antara menggabungkan state dan menggantinya
Cara backward scan memutuskan identity Tm mana yang dibuang
TPDFContentPeepholeOptimizer.RemoveIdentityMatrices kini menelusuri mundur dari setiap identity Tm dan membuangnya hanya kalau scan mencapai BT atau identity Tm lain lebih dulu. Identity Tm sebelumnya tetap terhitung entah ia disimpan atau baru saja dijadwalkan untuk dihapus, karena bagaimanapun ia meninggalkan kedua matrix pada identity, persis seperti yang dilakukan BT. Aturannya mengelompokkan setiap operator yang bisa ditemui ke salah satu dari dua grup:
- Berhenti dan simpan Tm-nya:
Td,TD,T*,Tmnon-identity,Tj,TJ,',",ET, operator apa pun yang tak dikenali parser, atau awal stream - Lewati dan lanjutkan scan: operator yang tak pernah menyentuh Tm maupun Tlm, seperti
Tf,Tc, color setter,gs, operator marked-content, dancm
Kasus-kasus konservatif itu disengaja. Operator tak dikenal bisa berupa apa saja, jadi scan menolak menalar melewatinya. ET menutup text object, sehingga Tm setelahnya tak punya BT yang mengesahkan nilai matrix-nya. Scan juga bekerja pada satu content stream dalam satu waktu, yang penting untuk halaman yang /Contents-nya berupa array: layer yang mulai di tengah text object, tanpa BT miliknya sendiri, menyimpan identity Tm-nya meski layer sebelumnya membuatnya redundan. Itu biaya beberapa byte pada file aneh dan tak pernah menggeser glyph. Kalau Anda menyunting teks halaman di level instruksi, seperti di walkthrough mapping karakter-ke-byte konten, model TPDFContentProgram hasil parse yang sama itulah yang ditulis ulang oleh optimizer
uses
PDFlibContentModel, PDFlibContentOptimize;
function OptimizeSnippet(const Source: AnsiString): AnsiString;
var
Prog: TPDFContentProgram;
Optimizer: TPDFContentPeepholeOptimizer;
begin
Result := Source;
Prog := TPDFContentProgram.Create;
try
if not Prog.Parse(Source) then
Exit; // stream rusak: biarkan byte-nya apa adanya
Optimizer := TPDFContentPeepholeOptimizer.Create(Prog);
try
Optimizer.Run; // mengembalikan jumlah instruksi yang dihapus
finally
Optimizer.Free;
end;
Result := Prog.Emit; // satu instruksi per baris
finally
Prog.Free;
end;
end;
// Dihapus: Tm tepat setelah BT, yang kedua dari dua identity Tm beruntun
// OptimizeSnippet('BT /F1 12 Tf 1 0 0 1 0 0 Tm (hello) Tj ET')
// Disimpan: Tm setelah Td, setelah Tj, setelah non-identity Tm, atau di luar BT
// OptimizeSnippet('BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET')
Jalankan helper pada stream invoice dari pembukaan dan identity Tm-nya selamat, karena backward scan menabrak Tj sebelum mencapai BT. Selipkan /F1 12 Tf, 2 Tc, dan 0 g di antara BT dan identity Tm, dan tetap terbuang, karena tak satu pun dari itu menyentuh text matrix. Urutan seperti BT 10 20 Td 1 0 0 1 0 0 Tm 1 0 0 1 0 0 Tm kehilangan tepat satu operator: identity Tm pertama mereset matrix yang digerakkan Td, dan hanya yang kedua yang redundan
Kapan peephole optimizer benar-benar berjalan?
Optimizer hanya berjalan pada compression pass, di dalam TPDFPageTree.Compress, dan hanya pada content stream yang belum terkompresi Flate. TPDFlib.SetOptimizeContentStreams(1) adalah defaultnya, dan switch yang sama diekspos sebagai field OptimizeContentStreams milik TPDFlibSaveOptions; CompressContent maupun CompressPage sama-sama menghormatinya. Stream yang /Filter-nya sudah /FlateDecode dilewati sepenuhnya, jadi memuat PDF terkompresi yang sudah ada lalu menyimpannya lagi tidak menulis ulang operatornya. Kalau stream gagal di-parse, byte hasil decode aslinya dikompresi apa adanya. TPDFlib.NormalizeContentStreams mem-parse dan menulis ulang konten dengan spacing dan angka kanonik tapi tak pernah memanggil optimizer, yang menjadikannya baseline yang berguna saat Anda ingin melihat seberapa besar kontribusi aturan peephole terhadap ukuran, di samping kemenangan yang lebih besar yang dibahas di optimasi ukuran file PDF dengan font subsetting
var
Lib: TPDFlib;
Options: TPDFlibSaveOptions;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('report.pdf', '') <> 1 then
Exit;
// Stream tak terkompresi melewati aturan peephole, lalu Flate
Lib.SetOptimizeContentStreams(1);
Lib.CompressContent;
Lib.SaveToFile('report-optimized.pdf');
// Pilihan yang sama lewat save options bawaan; False menolak
Options.CompressContent := True;
Options.CompressFonts := True;
Options.CompressImages := True;
Options.Linearize := False;
Options.KeepModDate := False;
Options.OptimizeContentStreams := False;
Options.GarbageCollect := False;
Options.PackObjectStreams := True;
Lib.SaveToFileOptions('report-plain.pdf', Options);
finally
Lib.Free;
end;
end;
Apa yang sebenarnya dijamin regression test lama?
Regression test lama hanya menjamin satu bentuk: identity Tm tepat setelah BT dihapus. Peephole_RemovesIdentityTextMatrix memberikan BT 1 0 0 1 0 0 Tm (hello) Tj ET ke optimizer dan meng-assert bahwa tak ada Tm tersisa. Rilis sebelumnya sudah mencatat bahwa membuang identity Tm tak aman ketika Tlm bukan identity, lalu tetap mempertahankan perilakunya karena test "mengunci"-nya. Dibaca ulang dengan cermat, test itu tak berkata apa-apa soal identity Tm setelah Td atau setelah teks tampil; memperlakukan cakupan satu sampel sebagai kontrak seluruh aturan itulah kesalahan sebenarnya. Perbaikannya menjaga kasus asli itu tetap lolos dan menambah enam kasus yang mengunci bentuk yang boleh dibuang maupun yang disimpan, termasuk sebuah Tm di luar text object mana pun dan satu yang mengikuti ET
Trade-off-nya mudah diterima begitu dituliskan. Generator yang membungkus setiap text object dengan BT 1 0 0 1 0 0 Tm ... tetap mendapatkan operator redundan itu dihapus, dan di situlah hampir semua penghematan berasal. Yang dilepas optimizer adalah identity Tm sesekali di tengah text object, segenggam byte per halaman sebelum Flate melihatnya, sebagai ganti jaminan yang dinyatakan terang di header modul: setiap transform ekuivalen output dan tak pernah mengubah halaman yang terlihat. Size optimizer yang menggeser teks bukan optimizer, itu rendering bug dengan rasio kompresi yang bagus
Parser content stream, peephole optimizer, dan opsi kompresi saat save yang dijelaskan di sini semuanya tersedia di PDF Library for Delphi dan C++Builder, yang juga mengekspos NormalizeContentStreams, CompressContent, dan TPDFlibSaveOptions untuk menyetel cara setiap dokumen ditulis