3.223.0 sürümünden itibaren PDFium Component for Delphi, form alanı gezinmesini her sayfanın beyan ettiği /Tabs politikasına göre sıralar: satır düzeni için R, sütun düzeni için C, yapı düzeni için S; üstüne PDF 2.0'ın A ve W değerleri. TPdf.FormFieldTabOrder o düzeni döndürür ve durum aşırı yükü, beyan edilen politikanın uygulandığını ya da bir düşüşe gidildiğini raporlar
Bütün bunların arkasındaki şikayet hep aynıdır. Kullanıcı iki sütunlu bir sigorta formunu doldurur, ilk ad alanından sonra Tab tuşuna basar ve imleç, sağ sütunun dibine atlar. İndekslerde bozuk hiçbir şey yoktur. Üretici, sağ sütunu basitçe /Annots içine önce yazmıştır ve görsel düzeni düzeltmek için sayfada /Tabs /R beyan etmiştir; /Annots gezinip beyanı yok sayan bir görüntüleyici yanlış yere düşer. Widget indeksi ile annotation indeksi yazısı, widget sıra numaralarının /Annots dizisiyle ilişkisini kapsıyor. PDFium Component'te beyan edilen politika, kurulabildiği her an kazanır; kurulamadığında bileşen size nedenini söyler
/Tabs girişi gerçekte neyi beyan eder?
/Tabs, bir işlemciye o sayfadaki annotationları hangi düzende ziyaret edeceğini söyleyen, sayfa nesnesinde isteğe bağlı bir addır (ISO 32000-2 §7.7.3.3; ISO 32000-1'de Tablo 30) ve semantiği §12.5.1 altında annotationlarla ilgili olarak açıklanır. PDF 1.5 üç değer getirdi, PDF 2.0 iki değer ekledi:
/R, satır düzeni: yukarıdan aşağıya satırlar ve her satır içinde görüntüleyicinin okuma yönü/C, sütun düzeni: okuma yönünde sütunlar ve her sütun içinde yukarıdan aşağıya/S, yapı düzeni: mantıksal yapı ağacının annotationları referans gösterme düzeni/A, annotation dizisi düzeni:/Annotsdizisinin kendi düzeni (PDF 2.0)/W, widget düzeni: önce dizge düzeninde widgetler, sonra öteki annotationlar (PDF 2.0)
Elle yazılmış uygulamaları düşüren ayrıntı kalıtımdır. /Tabs yalnızca sayfa sözlüğüne aittir. ISO 32000-2 §7.7.3.4, kalıtsal sayfa özniteliklerini Resources, MediaBox, CropBox ve Rotate ile sınırlar; dolayısıyla bir /Pages düğümünde duran bir /Tabs anahtarı anlamsızdır ve öntan zincirinde bir tane arayan okuyucu, yazarın hiç koymadığı bir politika icat eder. PDFium Component /Tabs değerini sayfanın kendisinden okur, başka hiçbir yerden
Beyan edilen düzeni FormFieldTabOrder ile okumak
TPdf.FormFieldTabOrder, geçerli sayfanın form alanlarını, beyan edilen sekme düzeninde 0 tabanlı widget sıra numaraları olarak döndürür ve out Status: TPdfFormTabOrderStatus parametreli aşırı yük, o düzenin nasıl üretildiğini açıklar. Aynı ikili TPdfView üzerinde de vardır; sade aşırı yük aynı politikayı kullanır ve durumu atar
uses
PDFium;
const
ModeNames: array[TPdfFormTabOrderMode] of string =
('unspecified', 'R', 'C', 'S', 'A', 'W', 'unsupported');
procedure DumpTabOrder(const FileName: string; PageNo: Integer);
var
Pdf: TPdf;
Order: TPdfIntegerArray;
Status: TPdfFormTabOrderStatus;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.FormFill := True; // zaten varsayılan; alanlar için zorunlu
Pdf.Active := True;
if not Pdf.Active or (Pdf.FormType <> ftAcroForm) then
Exit;
Pdf.PageNumber := PageNo;
Order := Pdf.FormFieldTabOrder(Status);
Writeln('requested ', ModeNames[Status.RequestedMode],
', applied ', ModeNames[Status.EffectiveMode]);
if Status.UsedFallback then
Writeln('fallback: ', Status.ErrorMessage);
if Status.UnstructuredWidgetCount > 0 then
Writeln(Status.UnstructuredWidgetCount, ' widgets not in the structure tree');
for I := 0 to High(Order) do
Writeln(I, ': field ', Order[I], ' ',
string(Pdf.FormFieldInfo[Order[I]].Name));
finally
Pdf.Free;
end;
end;
Durum kaydı, sessiz bir sıralama kararını günlüğe yazabileceğiniz bir şeye çevirir. RequestedMode, sayfanın beyan ettiğidir; EffectiveMode, uygulananıdır ve UsedFallback, beyan edilen politikanın yerine getirilemediğinde ve annotation dizisi düzeninin döndüğünde true olur; ErrorMessage içinde kısa bir gerekçeyle. SnapshotAvailable, bileşenin belge meta verisini hiç okuyup okuyamadığını söyler. Eksik bir /Tabs, beyan edilen politika gibi sunulmak yerine annotation dizisi düzenine bir düşüş olarak raporlanır; destek günlüğü böylece "yazar bu düzeni istedi" ile "sayfa hiçbir şey istemedi"yi ayırt edebilir
Satır ve sütun düzeni neden /Rect değerlerini sıralayamıyor?
Satır ve sütun düzeni, ham annotation koordinatlarında değil görüntüleme geometrisinde hesaplanmak zorundadır; çünkü bir sayfanın /Rotate değeri ve belgenin okuma yönü, "satır"ın ne demek olduğunu ikisi de değiştirir. /Rotate 90 bir sayfanın /Rect dizilerini y koordinatına göre sıralayın, görüntüleyicinin sütun olarak gösterdiğine ulaşırsınız. Bileşen bu yüzden her widget dikdörtgenini, içsel dönüş dahil, sayfanın görüntüleme dönüşümü üzerinden 1/64 nokta çözünürlüğünde eşler ve oradan gruplar
Okuma yönü, katalogdaki ViewerPreferences sözlüğünden gelir (ISO 32000-2 §12.2). /Direction /R2L, satır içindeki düzeni ve sütunların düzenini tersine çevirir; Arapça ya da İbranice bir formun beklediği de budur. /Direction yokken L2R geçerlidir. L2R ya da R2L dışında bir şey taşıdığında satır ve sütun düzeni, gerekçeyi durum içinde bırakarak annotation dizisi düzenine düşer; bir yön tahmin etmek, yön olmadığını kabul etmekten kötüdür
Gruplama, işlemcilerin meşru olarak ayrıştığı yerdir; tam kuralı bilmekte fayda var. Satır düzeni için henüz ziyaret edilmemiş en üstteki widget çapa olur ve düşey merkezi, çapanın üstü ile altı arasına düşen her widget onun satırına katılır. Sütun düzeni bunu yatayda aynalar. Eşitlikler, widget sıra numarası üzerinde belirlenimci biçimde kırılır. Temiz bir ızgarada bu, makul her görüntüleyiciyle uyuşur. Ağır üst üste binen ya da kademeli alanlı bir düzende farklı tolerans kullanan başka bir işlemci farklı gruplayabilir ve ISO 32000'in hiçbir yerinde iki cevaptan biri yanlış sayılmaz
Yapı düzeni sırayı nasıl belirler?
Yapı düzeni, yapı ağacındaki nesne başvurusu (OBJR) sözlüklerini, /K dizilerinin derinlik öncelikli bir gezintinin gerçekten ulaştığı düzende izler. /ParentTree anahtar düzenini izlemez ve bir StructTreeRoot dökümünün rastlantısal olarak ürettiği düzeni de izlemez. O düzenler, tek üreticiden gelen dosyalarda sık sık çakışır ve bir düzenleyici ağacı yeniden düzenledikten sonra ayrışır; etiketli bir formun okuma düzeninin en çok önemli olduğu an tam da budur. O ağacı kendiniz yönetiyorsanız /ParentTree ile /StructParents eşit tutma rehberi, onunla tutarlı kalmak zorunda olan ters eşlemeyi kapsıyor
Yapı ağacının hiç referans göstermediği widgetleri belirtim işlemciye bırakır. PDFium Component onları, yapılandırılmış widgetlerin ardından annotation dizisi düzeninde ekler ve kaç tanesini eklediğini UnstructuredWidgetCount içinde raporlar. Hiç yapı ağacı olmayan bir belgede /S beyan eden bir sayfa boş bir gezinti sayılır: her widget o kuyruğa düşer, sayı bunu söyler ve sonuç tam bir düşüş olarak işaretlenmez. Döngülü bir yapı ağacı, iki kez referans gösterilen bir OBJR ya da /Pg değeri annotationın sahibi sayfayla çelişen bir OBJR, yapı düzeninin düşmesine yol açar. O kusurlar S'yi kullanılamaz kılar ama öteki sayfalarda R ile C'yi rahatsız etmez
Sekme düzeni değerleri neden annotation indeksi değildir?
FormFieldTabOrder çağrısının döndürdüğü dizideki değerler yalnızca widgetleri sayarken, bir OBJR tam /Annots dizisinde yaşayan bir annotation nesnesini tanımlar. İki indeks uzayını karıştırmak, yapı düzenini tüketen her kodda tek başına en olası hatadır. /Annots değeri [Link, Widget A, Widget B] olan bir sayfa alın. Widget B için OBJR, annotation indeks 2'yi işaret eder. Widget B'nin alan sıra numarası 1'dir. FocusFormField çağrısına 2 verin; sayfanın kaç widgetine bağlı olarak ya yanlış alanı odaklarsınız ya hiçbir şeyi
Bileşen o dönüşümü kendi içinde yapar; sonucu bu yüzden doğrudan FocusFormField ile FormFieldInfoya girebilir. Kendi kodunuz OBJR hedeflerini ya da öteki annotation indekslerini okuyorsa, onları alan gibi ele almadan önce açıkça çevirin:
// Bir sayfa annotation indeksini, FocusFormField, FormFieldInfo ve
// FormFieldTabOrder kullandığı widget sıra numarasına çevirir; widget değilse -1
function AnnotationToFieldOrdinal(Pdf: TPdf; AnnotIndex: Integer): Integer;
var
I: Integer;
begin
Result := -1;
if (AnnotIndex < 0) or (AnnotIndex >= Pdf.AnnotationCount) then
Exit;
if Pdf.Annotation[AnnotIndex].Subtype <> anWidget then
Exit; // bağlantılar, notlar ve popuplar alan değildir
Result := 0;
for I := 0 to AnnotIndex - 1 do
if Pdf.Annotation[I].Subtype = anWidget then
Inc(Result);
end;
Sonucu yalnızca sayfa yüklü ve düzenlenmemiş kaldığı sürece geçerli sayın. Bir widgetın önünde herhangi bir annotation eklemek ya da çıkarmak iki indeks uzayını da kaydırır ve düzenlemeden önceki önbelleklenmiş bir eşleme, yalnızca müşteri dosyalarında yeniden üretilen birer fazlalık hatalarının ta kendisidir
FocusNextFormField ile sayfalar arası gezinme
FocusNextFormField(AcrossPages: Boolean; WrapDocument: Boolean = True; MaxPagesToScan: Integer = 256), geçerli sayfanın sekme düzenini gezer, sonra sonraki sayfalara sürer ve 3.223.0'dan itibaren ziyaret ettiği her sayfa kendi beyan ettiği politikayı uygular. Sayfalar arası aşırı yük 3.220.0'da geldi; form alanı gezinme yazısındaki, sarmayı sezip PageNumber değerini kendinizin değiştirmesine dair tavsiye artık yalnızca tek sayfa içinde dönen argsız aşırı yük için geçerli
procedure TFormViewer.HandleTabKey(Shift: TShiftState);
var
Field: Integer;
begin
if ssShift in Shift then
Field := PdfView.FocusPreviousFormField(True) // sayfalar arası, sarar
else
Field := PdfView.FocusNextFormField(True);
if Field < 0 then
Beep // tarama içinde uygun hiçbir şey yok; sayfa ve odak değişmedi
else
UpdateFieldStatus(PdfView.PageNumber, Field);
end;
Dönüş değeri, o anda odağı taşıyan sayfanın sayfa yerel alan sıra numarasıdır; yanında PageNumber değerini okuyun. Sayfalar arası gezinti, kullanıcının anlamlı biçimde sekme atlayamayacağı widgetleri atlar: gizli, görünmez, NoView, salt okunur, imza, bilinmeyen tür ve boş dikdörtgenli widgetler. WrapDocument = False geçmek, -1 ile belge sınırında durur. MaxPagesToScan değeri 1 ile 4096 arasında olmalıdır ve incelenen her sayfa, geçerli olan dahil, sayılır. Tarama hiçbir şey bulamazsa ya da yerel motor hedefte odağı reddederse çağrı -1 döndürür ve önceki sayfa ile odağı tam olarak eski hallerinde bırakır
Beyan edilen düzen nerede geçerliliğini yitiriyor?
Beyan edilen politika, bileşen sayfa meta verisini güvenilir okuyabildiğinde yerine getirilir; okuyamadığı her durum, açık bir gerekçeyle annotation dizisi widget düzenine düşer. Sınırlar listelemeye değer kadar spesifiktir:
- Meta veri, orijinal dosya baytlarından değil, geçerli yerel belgenin sınırlı özel bir serileştirmesinden gelir; çünkü bellek içi sayfa taşıma ve düzenlemeler, diskteki dosyayı bayat kılar. Anlık görüntü içerik revizyonu başına önbelleklenir; haber verilen düzenlemeler onu geçersiz kılar ve başarısız anlık görüntüler de önbelleklenir
- Şifreli belgeler ile sayfa nesnesi düzenlemesi sırasında istenen anlık görüntülerin hiç anlık görüntüsü yoktur ve daima düşer
- Anlık görüntü 64 MiB, 65536 etkin nesne, 4096 sayfa, sayfa başına 4096 annotation, toplamda 65536 annotation ve 64 yapı derinliğinde sınırlıdır; daha büyük belgeler gezinmeyi tıkamak yerine düşer
- Yalnızca AcroForm belgeleri kapsanır. XFA formları bu profilin dışında kalır
- Satır ve sütun düzeni sonlu, boş olmayan widget dikdörtgenleri ve akla uygun sayfa boyutları ister; yozlaşmış geometri düşüşü tetikler
- Bu API'ler gezinmeyi sizin kodunuzdan sürer. Tab tuşunun motora iletildiğinde PDFium'un kendi halletmesi ikame edilmez; beyan edilen düzeni istiyorsanız tuşu
FocusNextFormFieldüzerinden yönlendirin
Sorgular geçerli sayfayı, yerel odağı ve odaktaki alandaki bekleyen düzenleme metnini korur ve hiçbir belge ya da sayfa yaşam döngüsü eylemi çalıştırmaz. Her tuş vuruşunda FormFieldTabOrder çağırmak bu yüzden güvenlidir ve verilen bir içerik revizyonunda ilk çağrıdan sonra ucuzdur
Beyan edilen sekme düzeni, durum teşhisleri ve sayfalar arası odak gezinmesi, Delphi ve C++Builder için PDFium Componentin parçasıdır; ürün sayfası, TPdfFormTabOrderMode ile TPdfFormTabOrderStatus girişleri dahil tam API referansına bağlanıyor