PDFlibPas, losLab PDF Developer Library untuk Delphi, menulis setiap angka yang dimasukkannya ke content stream dengan pemisah desimal titik dan tanpa exponent, apa pun kata pengaturan regional Windows. Sejak v3.539.26 AddPageMatrix, ScalePage, DeskewPage, RedactRegion, output text-to-path, dan recoloring memformat operan lewat PLDoubleToStrConst, dan sejak v3.539.33 parser yang membaca kembali angka-angka itu memakai PLTryStrToFloatInvariant alih-alih locale sistem. Di mesin Jerman, Prancis, atau Brasil, kode yang sama kini menghasilkan byte yang sama seperti di mesin AS — satu-satunya perilaku yang bisa ditoleransi sebuah file format
Kenapa locale desimal-koma merusak PDF tanpa error?
Locale desimal-koma merusak PDF secara senyap karena koma bukan karakter angka dalam sintaks PDF, sehingga kerusakannya terbaca sebagai token valid dengan makna yang salah. Sebelum perbaikan, PLFloatToStr tak lebih dari panggilan FloatToStr telanjang, dan FloatToStr mengikuti FormatSettings.DecimalSeparator. Dengan pemisah koma, AddPageMatrix(0.5, 0.5, 0, 0) menulis 0,5 0 0 0,5 0 0 cm. ISO 32000-1 §7.3.3 hanya mengizinkan digit, satu titik, dan tanda di depan dalam sebuah angka, jadi parser konten membaca baris itu sebagai angka 0 disusul token tak dikenal ,5, dan operator cm berakhir dengan operan yang salah. Tak ada yang raise, tak ada yang mencatat log. Halamannya sekadar terrender dengan transformation matrix yang bergeser, dan menelusuri mundur dari gambar yang meleset ke sebuah pengaturan locale adalah sore hari yang sengsara
Cacat kedua bersembunyi di belakang yang pertama. FloatToStr memakai format ffGeneral, yang beralih ke notasi exponent begitu magnitudo turun di bawah 1E-4, sehingga offset kecil keluar sebagai 1E-5. §7.3.3 yang sama menyatakan PDF tidak mendukung bentuk exponent, artinya mesin berlocale AS pun bisa menulis operan tak valid kalau nilainya cukup kecil. Regression test untuk rilis ini mengunci kedua bentuk kegagalan: mereka membalik pemisah menjadi koma, memanggil API, dan memindai konten hasilnya mencari token mana pun yang memuat koma atau exponent
uses
System.SysUtils, PDFlibrary;
var
Lib: TPDFlib;
OldSeparator: Char;
begin
Lib := TPDFlib.Create;
try
Lib.SetPageDimensions(300, 200);
Lib.DrawBox(10, 10, 20, 20, 1);
OldSeparator := FormatSettings.DecimalSeparator;
try
FormatSettings.DecimalSeparator := ','; // simulasikan desktop de-DE
Lib.AddPageMatrix(0.5, 0.25, 1E-5, 12.75);
// v3.539.26 ke atas menulis: 0.5 0 0 0.25 0.00001 12.75 cm
// build lama menulis: 0,5 0 0 0,25 1E-5 12,75 cm
Writeln(Lib.GetPageContentToString);
finally
FormatSettings.DecimalSeparator := OldSeparator;
end;
finally
Lib.Free;
end;
end;
Dua jenis angka, dua keluarga helper
Perbaikan di PDFlibPas adalah pemisahan ketat: angka yang ditampilkan ke orang boleh mengikuti locale, dan angka yang ditulis untuk mesin tidak pernah. PLFloatToStr dan PLStrToFloat tetap berada di PDFlibExtra.pas untuk teks yang menghadap pengguna, dan deklarasinya kini membawa komentar yang menyatakan persis itu. Semua yang berujung menjadi sintaks PDF melewati PLDoubleToStrConst dengan jumlah tempat desimal tetap yang dipilih sesuai pekerjaannya: enam untuk matrix, empat untuk koordinat dan penyesuaian TJ, tiga untuk warna dan rectangle FDF. Audit untuk v3.539.26 menyentuh lebih banyak call site daripada yang disarankan bug report awal:
AddPageMatrix,ScalePage, danDeskewPage, yang semuanya menambahkancmdi depan konten halaman yang sudah ada- Builder elemen halaman yang mengeluarkan reset
Tm, majuTJ, dan transformcm - Matrix penempatan glyph dan titik outline di konverter text-to-path
- Kotak isian hitam yang ditambahkan
RedactRegion, nilai/Rectdi export FDF, dan operan yang ditulis recoloring
PLDoubleToStrConst adalah formatter buatan tangan alih-alih wrapper di sekitar FloatToStrF, dan tiga propertinya penting di sini. Ia selalu menulis titik dan membuang nol di ekor, jadi 0.5 tetap 0.5 bukan 0.500000. Ia tak pernah menulis exponent untuk input finite. Dan nilai non-nol yang lebih kecil dari presisi yang diminta tetap mempertahankan digit signifikannya alih-alih runtuh menjadi nol, jadi PLDoubleToStrConst(1E-9, 6) mengembalikan 0.000000001; hanya nilai di bawah sekitar 5E-16 yang menjadi 0. Aturan terakhir itu ada karena membulatkan scale factor kecil ke nol mengubah matrix yang valid menjadi singular — bug yang lebih buruk daripada yang sedang diperbaiki
uses
PDFlibExtra;
procedure CheckNumberHelpers;
var
V: Double;
begin
// Output mesin: desimal titik, tanpa exponent, nol di ekor dibuang
Assert(PLDoubleToStrConst(1E-5, 6) = '0.00001');
Assert(PLDoubleToStrConst(-0.5, 6) = '-0.5');
Assert(PLDoubleToStrConst(0.000012346, 4) = '0.00001235'); // menjaga 4 digit signifikan
Assert(PLDoubleToStrConst(12345.25, 4) = '12345.25');
// Input mesin: kegagalan lembut alih-alih EConvertError
Assert(PLTryStrToFloatInvariant('0.5', V) and (V = 0.5));
Assert(not PLTryStrToFloatInvariant('0,5', V)); // angka konten tak pernah memakai koma
end;
Kenapa sisi parsing lebih berbahaya daripada sisi penulisan?
Sisi parsing lebih berbahaya karena parser terikat locale tidak menghasilkan angka yang salah, ia melempar exception. PLStrToFloat memanggil StrToFloat, yang me-raise EConvertError ketika teks tak cocok dengan pemisah sistem. Di sistem desimal-koma, itu berarti RecolorPage gugur begitu bertemu operator 0.5 g yang biasa saja, jadi semua halaman dunia nyata gagal, bukan hanya yang eksotis. RenderPageRegionToFile menolak format clip yang terdokumentasi miliknya sendiri, "10.5,20.5,50.5,40.5", dan atribut panjang SVG, warna export SVG, daftar vertex anotasi, serta nilai solidity output intent ditolak atau diam-diam diganti default. Library yang bekerja sempurna di mesin developer dan gagal di pelanggan pertama di Munich persis jenis kode yang, seperti kasus di artikel soal kode Delphi yang bekerja karena kebetulan, tampak benar hanya karena tempat ia diuji
v3.539.33 mengklasifikasikan setiap panggilan StrToFloat dan TryStrToFloat menurut asal inputnya. Operan content stream, atribut SVG, string warna painter, dan daftar clip serta vertex berpemisah koma semuanya punya sintaks titik yang tetap, jadi kini mereka melewati PLTryStrToFloatInvariant, yang memangkas whitespace teks, mem-parse-nya dengan PLInvariantFormatSettings, dan mengembalikan False untuk input kosong, malformed, atau non-finite alih-alih raise. Daftar berpemisah koma tak menyisakan ruang kompromi, karena koma tak bisa menjadi delimiter daftar sekaligus tanda desimal. Pass yang sama juga memperbaiki satu tulisan out-of-bounds: RenderPageRegionToFile dulu menyimpan nilai clip kelima melewati buffer empat elemennya. Untuk pipeline recoloring yang dijelaskan di panduan mengonversi PDF ke satu color space, hasil praktisnya RecolorPage dan RecolorDocument tak lagi gugur di sistem desimal-koma. Nilai rule yang diketik caller ke CheckDocumentPolicy adalah satu-satunya kasus parsing yang memakai helper lenient, dengan alasan yang dijelaskan bagian berikutnya
Apa yang terjadi kalau hanya satu ujung round trip yang diperbaiki?
Memperbaiki hanya satu ujung round trip locale merusak kode yang tadinya jalan, dan itulah kenapa perubahan atribut struktur di v3.539.32 memindahkan writer dan reader bersamaan. Wrapper SetStructElem* meneruskan angka sebagai string: SetStructElemBBox memformat empat nilai menjadi satu string, menyimpannya lewat AddTagAttribute, dan writer /A belakangan mem-parse string itu untuk memutuskan apakah ia menjadi angka, array, atau name. Kedua ujung memakai locale sistem, jadi di sistem desimal-koma round trip-nya konsisten dengan dirinya sendiri. Bug itu muncul hanya ketika seorang caller mengikuti dokumentasi dan memberikan "0.5" ke AddTagAttribute: reader tak sanggup mem-parse-nya dan menerbitkan PDF name /0.5. Placeholder PDF/VCR punya masalah cermin, karena library menghasilkan GTS_BBox dengan titik lalu memvalidasinya dengan locale sebelum menyimpan
Mengubah hanya writer ke titik justru lebih buruk daripada tidak melakukan apa-apa, karena setiap nilai SetStructElem* akan gagal di reader terikat locale dan terdegradasi menjadi name. Jadi writer kini memakai PLDoubleToStrConst(v, 6), dan reader memakai PLTryStrToFloatLenient yang baru, yang mencoba bentuk titik lebih dulu lalu mundur ke locale sistem. Caller berlocale koma yang dulu memberikan "1,25" tetap mendapat angka 1.25. Trade-off-nya disengaja dan terdokumentasi: di sistem Jerman, "1.500" dulu menjadi name karena StrToFloat menolak pemisah ribuan, dan kini terbaca sebagai 1.5, sementara string literal NAN dan INF tak lagi diterima sebagai angka
uses
System.SysUtils, PDFlibrary;
var
Lib: TPDFlib;
begin
FormatSettings.DecimalSeparator := ','; // caller berdesimal-koma
Lib := TPDFlib.Create;
try
Lib.BeginTag('Figure', 'Sales chart', '');
Lib.SetStructElemBBox(10.5, 20.25, 200.5, 100.75); // /BBox [ 10.5 20.25 200.5 100.75 ]
Lib.AddTagAttribute('Layout', 'SpaceAfter', '0.5'); // /SpaceAfter 0.5, dulu /0.5
Lib.AddTagAttribute('Layout', 'StartIndent', '1,25'); // tetap /StartIndent 1.25
Lib.DrawText(20, 20, 'figure');
Lib.EndTag;
Lib.SaveToFile('tagged.pdf');
finally
Lib.Free;
end;
end;
Di mana NaN dan infinity dihentikan
AddPageMatrix, ScalePage, dan RedactRegion kini menolak argumen NaN dan infinite di muka dan mengembalikan 0, karena tak ada angka PDF yang bisa merepresentasikannya. ScalePage sudah menolak faktor nol atau kurang, tapi NaN lolos dari tes <= 0, sehingga scale NaN dulu berjalan sampai ke formatter. Di v3.539.26 formatter itu masih memanggil Round pada NaN, yang me-raise EInvalidOp di Win32 di mana unit x87 tidak memask operasi tak valid; v3.539.31 membuat PLDoubleToStrConst menulis 0 untuk NaN sebagai pertahanan terakhir, tapi nol di dalam matrix adalah transform singular, jadi pengecekan level API tetap perbaikan yang sesungguhnya. Dua batas tetap dipertahankan dengan sengaja. String state metafile ditulis dan dibaca dengan locale di dalam satu proses dan tak pernah keluar darinya, jadi dibiarkan apa adanya. Dan test yang memformat 1E-5 lewat jalur elemen halaman harus membaca konten sebelum layer ditulis ulang, karena menulis ulang operan pada presisi dokumen secara sah mengubah nilai itu menjadi 0
Kalau aplikasi Anda dikirim ke pelanggan di luar dunia desimal-titik, kebiasaan paling aman adalah yang kini dipakai test suite PDFlibPas: jalankan jalur penghasil PDF sekali dengan FormatSettings.DecimalSeparator disetel ke koma lalu pindai output mencari koma dan exponent. Artikel soal menjaga presisi desimal hasil parse membahas separuh lain cerita yang sama, bagaimana angka yang dibaca dari file yang sudah ada mempertahankan teks aslinya saat save. Unduhan, referensi API lengkap, dan build trial ada di halaman produk PDFlibPas Delphi PDF library