PDFlibPas, PDF Developer Library dari losLab, menyimpan teks desimal persis seperti yang diparsenya untuk setiap bilangan real di dokumen dan menuliskan teks itu kembali apa adanya setiap kali nilainya tidak pernah diubah. Sejak v3.539.19, setelan SetPrecision hanya mengatur angka yang dibuat atau disunting oleh library, jadi load-and-save biasa tidak lagi membulatkan /Gamma CalRGB dari 2.22221 turun ke 2.2222 lalu menggeser warna halaman yang tidak disentuh siapa pun. Perubahannya kecil di kode dan besar di apa yang dikatakannya soal parser: nilai yang Anda decode dan literal yang Anda keluarkan adalah dua hal berbeda, dan perjalanan bolak-balik lewat Double bukan transformasi identitas
Kenapa save yang tidak mengubah apa pun menggeser warna halaman?
Karena parameter color space-nya yang diformat ulang, bukan gambarnya. File yang membongkar ini adalah dokumen kantor 35 halaman di korpus regresi lokal, dengan gambar header yang dipakai ulang di setiap halaman. Memuatnya lalu menyimpannya kembali langsung menghasilkan image stream yang identik byte per byte dengan inputnya, dan perbandingan hash stream melaporkan dokumennya tidak berubah. Perbandingan hasil render tidak setuju: setiap satu dari 35 halaman menunjukkan perbedaan piksel di header, dan tidak di tempat lain
Gambar headernya digambar lewat color space CalRGB, yang didefinisikan ISO 32000-1 §8.6.5.3 dengan sebuah /WhitePoint, array /Gamma tiga elemen yang opsional, dan /Matrix sembilan elemen yang juga opsional. Array-array itu adalah objek numerik biasa di dictionary color space. TPDFNumeric menyimpan masing-masingnya sebagai Double dan tidak sebagai apa pun yang lain, dan TPDFNumeric.Output memformat Double itu lewat PDFPrecNum, yang default-nya empat tempat desimal. Jadi /Gamma berubah dari 2.22221 jadi 2.2222, satu entry matrix berubah dari 0.71519 jadi 0.7152, dan renderer dengan setia menghasilkan warna yang sedikit berbeda dari kalibrasi yang sedikit berbeda. Byte gambarnya tidak bersalah; angka-angka di sekitarnya yang bersalah. Bagian yang tidak nyaman adalah betapa tak terlihatnya hal ini. Membandingkan byte stream hasil decode tidak bisa melihatnya, karena angka-angka itu hidup di dictionary, bukan di stream. Membandingkan payload lampiran juga tidak bisa. Bahkan revision diff yang dijelaskan di artikel modification level membuat fingerprint dari body objek yang sudah dinormalisasi, jadi kedua revisi menghasilkan hash yang sama dan diff-nya melaporkan keduanya identik. Hanya rendering yang menangkapnya, itulah kenapa baseline korpus merender setiap halaman alih-alih mempercayai pemeriksaan struktural saja
Nilai yang Anda parse bukan literal yang seharusnya Anda tulis
Bilangan real PDF adalah string desimal, dan ISO 32000-1 §7.3.3 eksplisit bahwa ia hanya string desimal: tanpa notasi radix, tanpa bentuk eksponen. Annex C lalu mencantumkan presisi yang diharapkan dihormati implementasi, sekitar lima digit desimal signifikan di bagian pecahannya. Presisi output default empat sudah di bawah itu, dan makin buruk di dekat nol: PLDoubleToStr menskalakan nilainya, membulatkan ke integer, dan mengeluarkan 0 ketika hasilnya nol, jadi entry matrix -0.000012345 tidak kehilangan satu digit, ia hilang sama sekali
Menaikkan default-nya hanya memindahkan tebingnya. Perbaikannya adalah berhenti berpura-pura bahwa Double itu bilangannya. Ketika tokenizer di TPDFStructure.Decode mengenali real standar, artinya token itu memuat titik desimal dan tanpa penanda eksponen, ia menyimpan teks sumbernya di field baru FOriginalText bersama nilai hasil konversinya. Output lalu memilih teks itu dan hanya jatuh kembali ke pemformatan ketika tidak ada yang bisa dipilih
// Lib/PDFlibStruct.pas: seluruh perbaikannya di sisi output
Function TPDFNumeric.Output: AnsiString;
Begin
If FOriginalText<> '' Then
Result:= FOriginalText
Else
Result:= PLDoubleToStr(FValue, Owner.PDFPrecNum);
End;
Procedure TPDFNumeric.SetTo(Const Value: Double);
Begin
FOriginalText:= ''; // angka yang disunting adalah angka baru
FValue:= Value;
FChanged:= True;
End;
Dua batasnya disengaja. Integer tidak dipertahankan, karena pemformatan integer sudah lossless. Bentuk eksponen seperti 6.02E23 ditoleransi di input demi producer yang rusak, tapi tidak dipertahankan di output, karena menuliskannya kembali akan melestarikan sintaks yang dilarang §7.3.3; bentuk itu melewati formatter seperti angka lain buatan library. Tokenizer-nya juga menerapkan perbaikan minimal yang biasa sebelum menyimpan teks, jadi literal bertitik di depan seperti .5 disimpan sebagai 0.5 dan literal bertitik di belakang seperti 5. sebagai 5.0. Keduanya adalah angka yang sama bagi setiap reader dan jauh lebih luas diterima
Apa yang dijamin SetPrecision setelah v3.539.19?
TPDFlib.SetPrecision sekarang mengendalikan jumlah tempat desimal angka yang dihasilkan library sendiri: nilai yang digambar lewat painter, angka yang dibuat dari Double seperti lewat NewNumeric, dan nilai hasil parse mana pun yang sejak itu disunting dengan SetTo. Perhatikan bahwa teks yang didekode lewat object API, misalnya literal yang dikirim ke SetObjectFromString, melewati tokenizer yang sama dan dipertahankan dengan cara yang sama. Desimal hasil parse yang tidak pernah diubah mempertahankan presisi inputnya terlepas dari setelannya, dan mengubah setelan setelah load tidak menyentuhnya secara retroaktif. Entri referensi SetPrecision diperbarui di rilis yang sama untuk menyatakan persis ini, karena kata-kata yang lama menyiratkan setelannya berlaku untuk setiap angka di file
Pembersihannya terjadi di SetTo, bukan diturunkan dari flag Changed, dan perbedaan itu penting. Pipeline save mereset Changed pada objek begitu objek itu selesai ditulis, jadi pemeriksaan berbentuk "keluarkan teks asli kecuali berubah" akan mulai mengeluarkan teks basi untuk nilai yang disunting, disimpan, lalu disunting lagi di sesi yang sama. Mengikat teks aslinya pada assignment itu sendiri membuat keduanya mustahil berselisih. Test regresinya memaku setiap perilaku ini dengan nilai-nilai dari file aslinya
uses
PDFlibStruct;
var
Structure: TPDFStructure;
Values: TPDFArray;
Number: TPDFNumeric;
begin
Structure := TPDFStructure.Create;
try
Structure.PDFPrecNum := 4;
Values := TPDFArray(Structure.Decode('[2.22221 0.71519 -0.000012345 1 0.12567]'));
// Input yang tidak disunting bertahan apa adanya, termasuk nilai yang
// akan dilipat jadi 0 oleh pemformatan empat tempat
Assert(Values.Output = '[ 2.22221 0.71519 -0.000012345 1 0.12567 ]');
// Penyuntingan membuang teks aslinya lalu mengikuti PDFPrecNum
Number := TPDFNumeric(Values.Item[0]);
Number.SetTo(0.123456);
Assert(Number.Output = '0.1235');
Assert(Structure.NewNumeric(0.123456).Output = '0.1235');
// Menurunkan presisi setelahnya tidak mencapai input yang tak tersentuh
Structure.PDFPrecNum := 2;
Assert(TPDFNumeric(Values.Item[1]).Output = '0.71519');
finally
Structure.Free;
end;
end;
Kenapa content model masih menormalisasi angka?
Karena TPDFContentProgram menjanjikan operand numerik yang kanonik, dan janji itu lebih bernilai daripada teks apa adanya di dalam content stream. Content model yang bisa disunting, model yang sama yang jadi fondasi graphics-state tracker, ada supaya NormalizeContentStreams, optimizer, dan Emit menghasilkan output yang stabil dan bisa dibandingkan dari input sembarang. Kalau operand hasil parse membawa teks aslinya masuk ke model, urutan operator seperti 0.50000 0 0 RG akan dikeluarkan berbeda dari 0.5 0 0 RG, dan setiap perbandingan di hilir akan melenceng mengikuti kebiasaan pemformatan producer-nya
Jadi model-nya membuang teks aslinya di kedua titik masuknya. NormalizeContentNumbers berjalan pada setiap operand saat parser mendorongnya masuk dan sekali lagi di dalam SetOperand ketika sumber pasokan pemanggil didekode, dan ia berulang menembus array serta dictionary supaya dash pattern, array TJ, dan dictionary properti marked content ikut tercakup. Memanggil SetTo(AsDouble) pada setiap numeric sudah cukup, karena itulah persis operasi yang membersihkan teksnya. Data inline image mentah dibiarkan seperti apa adanya, seperti selama ini
// Lib/PDFlibContentModel.pas: content model menepati kontraknya
Procedure NormalizeContentNumbers(Obj: TPDFObject);
Var
K: Integer;
Begin
If Obj is TPDFNumeric Then
TPDFNumeric(Obj).SetTo(TPDFNumeric(Obj).AsDouble)
Else If Obj is TPDFArray Then
For K:= 0 To TPDFArray(Obj).Count- 1 Do
NormalizeContentNumbers(TPDFArray(Obj).Item[K])
Else If Obj is TPDFDictionary Then
For K:= 0 To TPDFDictionary(Obj).Count- 1 Do
NormalizeContentNumbers(TPDFDictionary(Obj).Entry[K].Value);
End;
Aturan praktisnya bagi pemanggil karena itu sederhana. LoadFromFile biasa yang diikuti SaveToFile meninggalkan content stream yang tak tersentuh dan angka dictionary yang tak tersentuh seperti semula. Halaman yang melewati NormalizeContentStreams, atau penyuntingan apa pun yang dilakukan lewat content model, keluar dalam bentuk kanonik sebagaimana dirancang, dan sisa dokumennya tetap dipertahankan. Itu dua permintaan yang berbeda, dan sekarang keduanya melakukan dua hal yang berbeda
Berapa biayanya, dan di mana jaminannya berhenti
Setiap TPDFNumeric sekarang membawa satu referensi AnsiString tambahan, dan setiap desimal hasil parse menjaga teks sumbernya tetap hidup selama objeknya hidup. Pada dokumen dengan jutaan bilangan real itu memori yang nyata, dan itu termasuk dalam pengukuran dokumen besar mana pun, bukan sesuatu yang bisa diabaikan begitu saja. Jaminannya juga dibatasi pada dokumen milik angka itu sendiri: menyalin objek antar dokumen atau merekonstruksi nilai lewat object API menghasilkan angka baru, yang mengikuti presisi output seperti angka baru lainnya. Layak untuk tepat soal apa yang diklaim dan tidak diklaim rilis ini. Load-and-save atas dokumen yang tak tersentuh sekarang mempertahankan angka kalibrasi yang benar-benar dikonsumsi renderer, dan itulah properti yang diperiksa baseline korpus. Ia tidak mengklaim output yang identik byte per byte, yang juga bergantung pada penomoran objek, kompresi stream, dan identifier trailer yang dibahas di artikel PDF ID deterministik. Dan ia tidak membuat fingerprint diff bisa melihat perbedaan pembulatan di file buatan perangkat lunak lain, karena itu tetap menghash body yang sudah dinormalisasi. Pelajarannya berlanjut jauh melampaui CalRGB: ketika parser hanya menyimpan nilai hasil konversinya, setiap save adalah penyuntingan, dan satu-satunya cara menyadarinya adalah dengan melihat hasil rendernya. Penanganan numerik dan semantik SetPrecision didokumentasikan di halaman produk losLab PDF Developer Library