Teknik Makale

PDFium Component: Delphi'de PDF Form Sekme Düzeni ve /Tabs

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: /Annots dizisinin 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

R, C, S, A ve W sayfa /Tabs politika değerleri, Delphi için PDFium Component'in beyan edilen düzen uygulandığında ya da düşüşe gidildiğinde raporladığı durum alanlarıyla
eksik bir /Tabs, raporlanan bir düşüştür; annotation dizisi düzenine geçilir, beyan edilen politika gibi sunulmaz

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

Delphi'de PDFium Component: annotation dizisi sekme düzeni sağ sütuna atlar, beyan edilen /Tabs R satır düzeni iki sütunlu formu satır satır gezer
satır düzeni, /Rotate ve okuma yönünden sonra görüntüleme geometrisinde hesaplanır; ham /Annots düzeninde değil

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

Yapı OBJR'si annotation indeks 2'yi hedeflerken Delphi'de PDFium Component form API'leri widget sayar; Widget B'nin alan sıra numarası 1'dir
annotation indeks 2, alan sıra numarası 1 değildir; FocusFormField çağırmadan önce açıkça çevirin

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