HotPDF Delphi Component her PDF sayısını THPDFNumericObject.Value içinde saklar; bu alan Single olarak bildirilmiştir ve bir Single her 32 bitlik tamsayıyı tutamaz. AcroForm alan bayrakları (/Ff) 32 bitlik bir maskedir; yüksek biti ayarlı her maske yükleme sırasında yuvarlanıyordu: $8000FFFF, $80010000 olarak geri geliyordu. v2.989.5'ten itibaren alan bayrağı okumaları özgün tamsayı belirtecini tam olarak ayrıştırır ve bitlerini mevcut Integer API'sine kopyalar
Hata, PDF formlarına hiç bulaşmasanız bile yakından bakmaya değer. Hiçbir şey çökmüyordu, hiçbir istisna fırlamıyordu ve test dosyalarının çoğu geçiyordu; çünkü gerçek maskelerin çoğu küçüktür. Hata yalnızca yüksek bir bit ile düşük yaygın bayraklar birlikte bulunduğunda ortaya çıktı ve o zaman tek bir biti kaybetmiyor, birkaçını birden yeniden yazıyordu. Bir bit maskesini kayan noktalı bir türde saklamak klasik bir Delphi tuzağıdır; üretim kodundaki görünümü tam olarak budur
Bir Single, AcroForm alan bayrağı bitlerini neden kaybeder?
Bir Delphi Single, 24 bitlik bir anlamlı kısma (significand) sahiptir; 2^24'e kadar (16777216) her tamsayıyı tam olarak gösterir, ötesini ise boşluksuz gösteremez. 2^31 ile 2^32 arasında ardışık Single değerleri 256 arayla durur. Bit 32 ayarlı bir alan bayrağı maskesi tam o aralıkta yaşar; düşük sekiz biti, çoğu zaman daha fazlası, 256'nın en yakın katına yuvarlanır
ISO 32000-1 §7.3.3, tamsayı ve gerçel nesneleri ayrı sayı türleri olarak tanımlar ve Ek C her biri için farklı uygulama sınırları listeler; bir PDF tamsayısının 32 bitlik bir float'a sığması gerekmez. Alan bayrakları girdisi Tablo 221'de tamsayı olarak tanımlanır ve bir miktar olarak değil bit bit okunur. Bir miktarı küçücük bir kesirle yuvarlamak zararsızdır; bir maskeyi yuvarlamaksa komşu bitlere taşınır. Bütün etkiyi birkaç satırlık sade Delphi ile yeniden üretebilirsiniz:
var
S: Single;
Mask: Int64;
begin
S := $8000FFFF; // 2147549183, Single olarak gösterilemez
Mask := Round(S); // 2147549184
Writeln(IntToHex(Mask, 8)); // 80010000: 1-16 bitleri temizlendi, bit 17 ayarlı
S := $80003FFF;
Mask := Round(S);
Writeln(IntToHex(Mask, 8)); // 80004000: 1-14 bitleri temizlendi, bit 15 ayarlı
end;
Ders, PDF'in ötesine genellenir. Bir Single, bir Double, float tutan bir Variant ya da float'a çözülmüş bir JSON sayısı, bit alanı için yanlış kaptır; tesadüfen test ettiğiniz değerler küçük olsa bile. Double, tam aralığı 2^53'e iter; bu 32 bitlik maskelerde sorunu gizler ama türün yanlış niyeti ifade etmesini önlemez. Cardinal, Integer ya da Int64 kullanın ve dönüştürmeyi zaten yuvarlanmış bir float'tan değil kaynak metinden yapın
Hangi alan bayrakları döner: Radio, Pushbutton, Password ve Multiline
Yukarıdaki iki maske tesadüfi değil; tek bir yuvarlama adımının okuyucunun gördüğü alan türünü nasıl değiştirdiğini gösterirler. PDF, bayrak bitlerini 1'den sayar; bit n'in değeri 2^(n-1)'dir. ISO 32000-1 §12.7.3.1 Tablo 226 her alan için 1-3 bitlerini, Tablo 228 düğme bayraklarını, Tablo 230 ise metin alanı bayraklarını tanımlar
$8000FFFF'den$80010000'e: bit 16 (Radio,$8000, Tablo 228) temizlenir ve bit 17 (Pushbutton,$10000) ayarlanır; radyo düğmesi bir itme düğmesine dönüşür- Aynı yuvarlama 1-3 bitlerini de temizler; Tablo 226'daki ReadOnly, Required ve NoExport, Radio ile birlikte kaybolur
$80003FFF'ten$80004000'e: Tablo 230'daki bit 13 (Multiline,$1000) ile bit 14 (Password,$2000) ikisi de temizlenir; maskeli bir parola kutusu tek satırlık düz bir metin alanına dönüşür- Bit 32'nin altında hasar 2^24'te başlar:
16777219(Comb artı ReadOnly ve Required),16777220'ye yuvarlanır; bu da ReadOnly ile Required'ı düşürüp NoExport ekler
Son durum, HotPDF için form düzenlemenin ötesinde önemliydi. ExportLoadedPagesStructured, JSON ve XML form modelini etkin maskeden kurar ve değer bastırma kararında NoExport'ı kullanır; yuvarlanmış bir maske sıradan bir değeri gizleyebiliyor ya da yanlış readOnly ve required üyeleri raporlayabiliyordu. Yapılandırılmış dışa aktarma yolu aynı tam okumayı v2.989.8'de aldı
HotPDF, tam 32 bitlik tamsayı belirtecini nasıl okur
HotPDF Delphi Component, okuma tarafını sayısal nesne modelini değiştirmeden düzeltir. Ayrıştırıcı, özgün belirteç metnini Single değerin yanında zaten saklar; alan bayrağı okuyucu bu belirteci bir tamsayı olarak ayrıştırır, sınırlı bir Int64 içinde biriktirir ve ortaya çıkan 32 biti, genel API'nin döndürdüğü Integer'a kopyalar. Genel sayısal API'ler ile eski tamsayı olmayan yedek yol olduğu gibi kalır; tam yolu yalnızca alan bayrağı tüketicileri kullanır
Ayrıştırıcı, gerçek dosyaların yüksek bitli maskeyi yazdığı her iki biçimi de kabul etmek zorundadır. Bazı yazarlar, Ek C tamsayı sınırı olan 2^31 - 1'in üzerinde olmasına rağmen pratikte yaygın olan işaretsiz 2147549183 değerini yazar; diğerleri işaretli -2147418113 değerini. İkisi de aynı bitleri anlatır. Sınırlı ayrıştırıcının bir sürümü şöyle görünür:
function TryParseFlagMask(const Token: AnsiString; out Mask: Cardinal): Boolean;
var
P, Digit: Integer;
Negative: Boolean;
Number, Limit: Int64;
begin
Result := False;
Mask := 0;
if Token = '' then Exit;
P := 1;
Negative := Token[1] = '-';
if Token[1] in ['+', '-'] then Inc(P);
if P > Length(Token) then Exit;
// İşaretli belirteçler -2^31'e dek, işaretsizler 2^32 - 1'e dek
if Negative then Limit := 2147483648 else Limit := 4294967295;
Number := 0;
while P <= Length(Token) do
begin
if not (Token[P] in ['0'..'9']) then Exit; // gerçel sayılar eski yedeği kullanır
Digit := Ord(Token[P]) - Ord('0');
if Number > (Limit - Digit) div 10 then Exit; // sınırlı, taşma yok
Number := Number * 10 + Digit;
Inc(P);
end;
if Negative and (Number <> 0) then Number := 4294967296 - Number;
Mask := Cardinal(Number);
Result := True;
end;
İki ayrıntı bilinçlidir. Sınır her çarpmadan önce kontrol edilir; 400 haneli bir belirteç Int64'ü taşırma yerine temizce başarısız olur. 3.0 gibi bir belirteç de reddedilir; bu, çağıranı Round(Value)'a geri gönderir. Tam yol, tamsayı maskelerini iyileştirirken bozuk olanlar için yeni anlam icat etmez
Uygulama kodundan bakınca imzalarda hiçbir şey değişmez. THotPDF.GetLoadedFormFieldFlags(FieldIndex: Integer): Integer, etkin maskeyi döndürür ve HotPDF'in miras kalan AcroForm alan değerlerini ve resetleri nasıl çözdüğü makalesinde anlatıldığı gibi /Parent zincirini izler; THotPDF.SetLoadedFormFieldFlags(FieldIndex, AFlags: Integer) ise eksiksiz bir yerel maske yazar. Bit 32 ayarlı bir maske salt negatif bir Integer'dır; o yüzden değeri dönüştürmek yerine bitleri taşıyın:
const
FF_RADIO = $00008000; // bit 16, ISO 32000-1 Tablo 228
FF_PUSHBUTTON = $00010000; // bit 17, Tablo 228
var
Pdf: THotPDF;
I, Native: Integer;
Bits: Cardinal;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('survey.pdf') <= 0 then Exit;
for I := 0 to Pdf.GetFormFieldCount - 1 do
begin
Native := Pdf.GetLoadedFormFieldFlags(I);
Move(Native, Bits, SizeOf(Bits)); // aynı 32 bit, dönüşüm yok
if (Bits and FF_RADIO) <> 0 then
Writeln(Pdf.GetFormFieldName(I), ': radio, mask $', IntToHex(Bits, 8));
end;
Bits := $80000002; // yüksek bit artı Required
Move(Bits, Native, SizeOf(Native)); // Native = -2147483646
Pdf.SetLoadedFormFieldFlags(0, Native);
Pdf.SaveLoadedDocument('survey-out.pdf');
finally
Pdf.Free;
end;
end;
Miras bir maskeyi okumak çocuk sözlüğüne dokunmaz; yazmak ise seçili alandaki maskenin tamamını yerel bir girdiyle değiştirir. SetLoadedFormFieldReadOnly ile SetLoadedFormFieldRequired kısayolları etkin miras maskeden başlar, bir biti çevirir ve öteki her biti tam olarak korur. Bayraklardan çok değerleri doldurmak için HotPDF ile yüklü bir PDF'de form alanı değeri ayarlama yazısına bakın
Value ve RememberSourceToken sırası neden önemlidir?
Sıra önemlidir çünkü THPDFNumericObject, sakladığı belirtece yalnızca Single değer değişmediği sürece güvenir. RememberSourceToken, belirteci geçerli Value anlık görüntüsüyle birlikte saklar ve SourceToken getter'ı, Value o görüntüden saptığı anda boş bir dizge döndürür. Düzenlenen bir sayının bayat özgün metniyle kaydedilmesini önleyen kural tam olarak bu geçersizleştirmedir
Kural, yazma tarafında tuzağa dönüşür. Önce belirteci hatırlayıp değeri sonra atarsanız anlık görüntü eski değeri tutar, getter belirteç bildirmez ve serileştirici yuvarlanmış Single'ı biçimlemeye döner. Kütüphanenin içindeki her bayrak yazıcısı — kısayollar ve yerel alan kurucuları dahil — aynı sırayı izler:
// Doğru: anlık görüntü Value ile eşleşir, tam belirteç kaydetmeyi atlatır
Item := THPDFNumericObject.Create(nil);
Item.Value := AFlags;
Item.RememberSourceToken(AnsiString(IntToStr(AFlags)));
// Yanlış: anlık görüntü Value değişmeden alınır, SourceToken
// geçersizleşir ve yerine yuvarlanmış Single yazılır
Item := THPDFNumericObject.Create(nil);
Item.RememberSourceToken(AnsiString(IntToStr(AFlags)));
Item.Value := AFlags;
SaveNumericObject, geçerli bir belirteç olduğunda onu yazdığı için sırayı doğru kurmak; kaydedilip yeniden açılan dosyanın yuvarlanmış bir yaklaşım değil özgün tamsayıyı taşımasını sağlayan şeydir
İki alanın tek adı paylaşmasına yol açan null belirteci
Aynı sürüm, bu maskeleri test ederken bulunan ikinci bir ayrıştırıcı kusurunu da düzeltti: akıştan okunan bir sözlükteki null değeri hiçbir zaman tüketilmiyordu. HotPDF bir sonraki belirteci sınıflandırmak için göz atar ve o kontrol, akış konumunu belirtecin başında bırakır. Akış sözlüğü okuyucusunun otNull dalı null nesnesini kuruyordu ama null'un dört baytının ötesine ilerlemiyordu
Sonuç askıda kalma değil sessiz bir yanlış okumaydı. Bir sonraki anahtar araması yanlış yerden ileriye taradı, geçerli sözlüğün sonunu aştı ve ardından gelen dolaylı nesneden anahtarlar okudu; bu anahtarlar geçerli alanın üzerine yazdı. Alan listesindeki belirti, iki farklı alanın aynı adı ve aynı değeri bildirmesiydi. Dizi ve span ayrıştırıcıları null'u zaten doğru tüketiyordu; düzeltme genel ayrıştırıcıyı yeniden yazmak yerine o tek daldaki akışı dört bayt ilerletir. Hareket etmeyen bir imleç, HotPDF'i bozuk girdide askıya alan PDF ayrıştırıcı imleç takılmalarındaki hatayla aynı ailedendir; belirtisi farklıdır
Düzeltme mirası da netleştirdi. Yerel bir /Ff null ya da null'a çözülen bir referans yok sayılır ve arama ebeveyne devam eder; açık bir 0 ise ebeveyni ezen gerçek bir maskedir
Bir {$R-} yönergesinin kanıtladığı ve kanıtlamadığı
Bayrak hatasına ilk hipotez, büyük bir maske Integer'a atandığında beklenen bir aralık kontrolü hatasıydı. Yanlış çıktı ve nedeni bilinmeye değer bir Delphi ayrıntısı: HPDFDoc.pas içinde ParseSimpleDictionary'den önce konmuş yerel bir {$R-} yönergesinin eşleşen bir geri yüklemesi yoktu; ünitedeki ondan sonraki her fonksiyon aralık kontrolleri kapalı derleniyordu. Delphi, bir anahtar yönergesini fonksiyon sonunda sıfırlamaz ve bir include dosyası, yönerge durumunu dahil edildiği noktadan miras alır
Bu yüzden komut satırı -$R+ ya da -Cr yalnızca ünitenin başlangıç ayarını kanıtlar; aşağıda kalan belirli bir fonksiyon hakkında hiçbir şey söylemez. Belirli bir rutin için aralık kontrolünün etkin olup olmadığını bilmek istiyorsanız üzerindeki en yakın yönergeyi bulun, sonra davranışı gerçek bir sınır değeriyle teyit edin. İki eski yerel bayrak tüketicisi, hipotezin öngördüğü aralık istisnasını hiç fırlatmadı; o eksik istisna bir düzeltmenin kanıtı olarak gösterilemezdi. Gerçek kusur, bilinen bayrak bitlerindeki Single yuvarlamasıydı
Tam belirteç okuması kapsamına karşı da dürüsttür. Ünite genelindeki yönerge durumunu değiştirmez, rezerve bitlere anlam yüklemez ve her bit kombinasyonunun geçerli bir alan olduğunu iddia etmez. Garantisi şudur: dosyadaki 32 bit, kodunuzun gördüğü 32 bittir
SSS
THPDFNumericObject.Value neden Double ya da Int64 yapılmıyor?
Genel bir alanın türünü değiştirmek onu okuyan ya da atayan her çağıranı kırar; üstelik PDF sayıları tamsayı olduğu kadar gerçeldir. Single'ı koruyup alan bayraklarını özgün belirteçten okumak, hiçbir mevcut bildirimi ya da çağrı kuralını değiştirmeden bit maskelerindeki hatayı düzeltir
SetLoadedFormFieldFlags, bit 32 ayarlı maskeleri kabul eder mi?
Evet. Maskeyi işaretli bir Integer olarak iletin; $80000002 ile -2147483646 aynı 32 bittir. GetLoadedFormFieldFlags, Required dahil, kaydedip yeniden yükledikten sonra aynı değeri döndürür
Hangi HotPDF sürümü tam AcroForm alan bayraklarını okur?
Tam alan bayrağı okumaları ile null belirteç düzeltmesi HotPDF v2.989.5 ile geldi; yapılandırılmış JSON ve XML dışa aktarımı tam maskeyi v2.989.8'de benimsedi. Daha eski sürümler 2^24'ün üzerindeki her maskeyi Single üzerinden yuvarlar
Tam AcroForm alan bayrakları, miras kalan alan araması ve yüklü form düzenleme, Delphi ve C++Builder için HotPDF Componentin parçasıdır; kütüphane, PDF belgeleri oluşturan, yükleyen ve düzenleyen yerel bir PDF kütüphanesidir