Tehnički članak

Čitanje i pisanje označenog sadržaja PDF-a u Delphi

Označeni sadržaj jeste mehanizam koji ISO 32000-1 §14.6 definiše za označavanje sadržaja stranice, i označeni PDF i PDF/UA su oba izgrađeni na njemu. PDFium Component ga izlaže direktno: PageObjectMarks čita svaku BDC oznaku i njenu listu svojstava sa objekta stranice, AddPageObjectMark piše jednu, RemovePageObjectMark briše jednu, a PageObjectMarkedContentID prijavljuje MCID koji povezuje sadržaj sa stablom strukture

Dok se stablo strukture ne može spojiti nazad sa sadržajem koji opisuje, alat za pristupačnost je nagađanje. Stablo strukture kaže «ovo je naslov»; MCID kaže koje oznake na kojoj stranici taj naslov zapravo jeste. Obve polovine moraju biti čitljive pre nego što aplikacija može proveriti, popraviti ili izveštavati o označavanju

Šta je oznaka, u bajtovima?

BDC operator sa imenom oznake i opcionom listom svojstava, zatvoren EMC-om. U toku sadržaja izgleda kao /P <</MCID 3>> BDC ... EMC: oznaka /P imenuje ulogu, rečnik nosi svojstva, a sve između operatora jeste označeni sadržaj. Objekat stranice unutar tog raspona nosi oznaku, što je ono što PDFium predaje i što PDFium Component pretvara u zapis

TPdfContentMark drži ručicu, oznaku Name, i niz TPdfContentMarkParam. Svaki parametar ima Key, Kind i jedno polje smislene vrednosti koje bira taj tip: pmpInt, pmpFloat, pmpString ili pmpBlob. Tip dolazi iz sopstvenog izveštaja tipa PDFium-a a ne iz gettera koji je slučajno uspeo, što je razlika između čitanja liste svojstava i pogađanja jedne

var
  Marks: TPdfContentMarks;
  M: TPdfContentMark;
  P: TPdfContentMarkParam;
  I: Integer;
begin
  Pdf.PageNumber := 1;                    // PageNumber is 1-based
  for I := 0 to Pdf.ObjectCount - 1 do    // page object indexes are 0-based
  begin
    Marks := Pdf.PageObjectMarks(I);
    for M in Marks do
    begin
      Memo1.Lines.Add('mark ' + M.Name +
        ' (MCID ' + IntToStr(Pdf.PageObjectMarkedContentID(I)) + ')');
      for P in M.Params do
        case P.Kind of
          pmpInt:    Memo1.Lines.Add('  ' + P.Key + ' = ' + IntToStr(P.IntValue));
          pmpString: Memo1.Lines.Add('  ' + P.Key + ' = ' + P.StringValue);
          pmpFloat:  Memo1.Lines.Add('  ' + P.Key + ' = ' + FloatToStr(P.FloatValue));
          pmpBlob:   Memo1.Lines.Add('  ' + P.Key + ' = ' +
                       IntToStr(Length(P.BlobValue)) + ' bytes');
        end;
    end;
  end;
end;

Zašto pmpUnknown znači dve različite stvari

pmpUnknown se vraća kad PDFium prijavi FPDF_OBJECT_UNKNOWN, a PDFium to vraća i za ključ koji ne postoji. Dva slučaja se ne mogu razlikovati na ovom sloju, i pretvaranje da se mogu bilo bi gore od priznanja da ne mogu

Praktična posledica za vaš kod: tretirajte pmpUnknown kao «ovde nema upotrebljive vrednosti» a ne kao tip koji biste ionako dekodirali. Ako svojstvo važi vašem toku rada, proverite da je prisutno sa tipom koji prepoznajete, i ne zaključujte odsustvo iz nepoznatog — oznaka čiju listu svojstava ne možete pročitati jeste oznaka o kojoj treba izveštavati, a ne ona koju treba tiho prihvatiti

Zapis oznake jeste snimak, a ne ručica koju posedujete

Polje Handle pripada biblioteci. Ono zastari čim se oznaka ukloni, objekat stranice uništi ili stranica isprazni, tako da je zapis snimak samo za čitanje sa kratkim životom. Keširajte ga kroz prebacivanje stranica i držite pokazivač u memoriju koju je motor povratio

To je ista disciplina koja važi za ručice objekata stranice generalno u PDFium-u, i hvata ljude na istom mestu: kontrola liste popunjena zapisima oznaka, korisnik koji navigira na drugu stranicu, i pad koji deluje nepovezano sa navigacijom. Prepišite vrednosti koje trebaju — ime, ključeve, brojeve — i pustite ručicu. Beleške o zastarevanju ručica objekata stranice nakon transformacije pokrivaju opšte pravilo i kako grize drugde

Dodavanje oznake, i korak snimanja koji je lako propustiti

AddPageObjectMark uzima indeks objekta stranice, ime oznake i kompletan skup parametara. Parametri se pišu kao skup a ne krpe jedan ključ u isto vreme, što je razlog zašto TPdfContentMarkParam nema Has* senitore — slučaj «ažuriraj jedno polje postojećeg zapisa» koji bi oni čuvali ne nastaje

Deo koji vredi izreći eksplicitno: dodavanje oznake ponovo izgrađuje tok sadržaja stranice tako da oznaka preživi snimanje. Ovo je moralo biti eksplicitno jer SaveAs ne regeneriše sadržaj sam od sebe — promena koja je živela samo u modelu objekata bi bila odbačena, a snimljeni fajl bi izgledao tačno kao onaj sa kojim ste krenuli. Ako ste ikada dodali nešto na PDFium stranicu i našli da nedostaje u izlazu, ovo je obično razlog

var
  Params: TPdfContentMarkParams;
begin
  SetLength(Params, 1);
  Params[0].Key := 'MCID';
  Params[0].Kind := pmpInt;
  Params[0].IntValue := NextMcid;
  Pdf.AddPageObjectMark(ObjectIndex, 'P', Params);   // rebuilds the content stream
  Pdf.UpdatePage;
  Pdf.SaveAs('tagged-out.pdf');
end;

Šta ovo čini a ne čini od dokumenta

Samo oznake ne prave označeni PDF. Usaglašen označeni dokument treba stablo strukture čiji elementi referenciraju ove MCID-ove, /MarkInfo unos koji deklariše dokument označenim, i imena uloga koja znače ono što standard kaže da znače. Pisanje /P oznake sa MCID-om na koji nijedan element strukture ne pokazuje daje vam sadržaj koji tvrdi da je označen i stablo strukture koje ga nikada ne pominje

Tamo gde označeni sadržaj iskreno zaslužuje svoju vrednost na ovom nivou jeste inspekcija i popravka: revizija koji su objekti stranice označeni, pronalaženje artefakata koji su trebali biti označeni kao takvi, ili usklađivanje MCID-ova sa stablom strukture da se nađu siročad. Za strukturu-stabla pola tog rada, pogledajte prolaz kroz validaciju stabla strukture PDF/UA, a za iskustvo čitanja kojem su oznake konačno namenjene, beleške o izgradnji pristupačnog PDF čitača u Delphi

PDFium Component daje Delphi, C++Builder i Lazarus aplikacijama visokonivoiski VCL API preko PDFium motora, sa označenim sadržajem, stablima strukture i validacijom pristupačnosti dostupnim iz običnog Pascal koda — pogledajte stranicu PDFium Component proizvoda za kompletnu API površinu