Tehnični članak

Branje in pisanje označene vsebine PDF v Delphiju

Označena vsebina je mehanizem, ki ga ISO 32000-1 §14.6 opredeljuje za označevanje vsebine strani, na njej pa sta zgrajena tako označeni PDF kot PDF/UA. PDFium Component jo izpostavi neposredno: PageObjectMarks prebere vsako oznako BDC in njen seznam lastnosti z objekta strani, AddPageObjectMark eno zapiše, RemovePageObjectMark eno izbriše, PageObjectMarkedContentID pa poroča o MCID, ki povezuje vsebino z drevesom strukture

Dokler drevesa strukture ni mogoče spojiti nazaj z vsebino, ki jo opisuje, je orodje dostopnosti ugibanje. Drevo strukture pravi "to je naslov"; MCID pove, katere oznake na kateri strani je ta naslov dejansko. Oboji polovici morata biti berljivi, preden aplikacija lahko preverja, popravlja ali poroča o označevanju

Kaj je oznaka, v bajtih?

Operator BDC z imenom oznake in izbirnim seznamom lastnosti, zaprt z EMC. V toku vsebine izgleda kot /P <</MCID 3>> BDC ... EMC: oznaka /P poimenuje vlogo, slovar nosi lastnosti, vse med operatorjema pa je označena vsebina. Objekt strani znotraj tega razpona nosi oznako, kar je tisto, kar vrne PDFium in kar PDFium Component spremeni v zapis

TPdfContentMark drži ročaj, oznako Name in polje TPdfContentMarkParam. Vsak parameter ima Key, Kind in eno smiselno polje vrednosti, ki ga izbere ta vrsta: pmpInt, pmpFloat, pmpString ali pmpBlob. Vrsta prihaja iz poročila o vrsti samega PDFium in ne od tega, kateri getter je slučajno uspel, kar je razlika med branjem seznama lastnosti in ugibanjem o njem

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;

Zakaj pmpUnknown pomeni dve različni stvari

pmpUnknown se vrne, ko PDFium poroča FPDF_OBJECT_UNKNOWN, PDFium pa to vrne tudi za ključ, ki ne obstaja. Obeh primerov na tej plasti ni mogoče razločiti in delati, kot da bi bilo, bi bilo huje kot priznati

Praktična posledica za vašo kodo: obravnavajte pmpUnknown kot "tukaj ni uporabne vrednosti" in ne kot vrsto, ki bi jo morda vseeno dekodirali. Če lastnost šteje za vaš delovni tok, preverite, da je prisotna z vrsto, ki jo prepoznate, in ne sklepite o odsotnosti iz neznanega — oznaka, katere seznama lastnosti ne morete prebrati, je oznaka, o kateri bi morali poročati, in ne tista, ki bi jo tiho sprejeli

Zapis oznake je posnetek in ne ročaj, ki ga posedujete

Polje Handle pripada knjižnici. Postane zastarelo v trenutku, ko je oznaka odstranjena, objekt strani uničen ali stran razložena, tako je zapis bralni posnetek s kratko življenjsko dobo. Predpomnilnite ga čez preklop strani in držite kazalec v pomnilnik, ki ga je pogon prevzel nazaj

To je ista disciplina, ki velja za ročaje objektov strani pri PDFiumu na splošno, in ljudi ujame na istem mestu: kontrolnik seznama, naseljen z zapisi oznak, uporabnik, ki navigira na drugo stran, in zrušitev, ki je videti nepovezana z navigacijo. Kopirajte ven vrednosti, ki jih potrebujete — ime, ključe, številke — in ročaj izpustite. Zapiski o zastarevanju ročajev objektov strani po transformaciji obravnavajo splošno pravilo in kako grize drugje

Dodajanje oznake in korak shranjevanja, ki ga je lahko spregledati

AddPageObjectMark vzame indeks objekta strani, ime oznake in popolno množico parametrov. Parametri se zapišejo kot množica in ne zakrpajo enega ključa naenkrat, zato TPdfContentMarkParam nima stražarskih Has* — primer "posodobi eno polje obstoječega zapisa", ki bi ga ti varovali, ne nastopi

Del, ki se splača povedati izrecno: dodajanje oznake znova zgradi tok vsebine strani, tako oznaka preživi shranjevanje. To je moralo biti izrecno, ker SaveAs sam po sebi ne regenerira vsebine — sprememba, ki bi živela samo v modelu objektov, bi bila zavržena, shranjena datoteka pa bi izgledala natanko tako kot tista, s katero ste začeli. Če ste kdaj dodali kaj na stran PDFium in ugotovili, da manjka v izhodu, je to običajno 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;

Kaj to dokument naredi in česa ne

Oznake same ne naredijo označenega PDF. Skladen označeni dokument potrebuje drevo strukture, katerega elementi referencirajo te MCID, vnos /MarkInfo, ki dokument razglaša označen, in imena vlog, ki pomenijo to, kar pravi standard. Pisanje oznake /P z MCID, na katerega ne kaže noben element strukture, vam da vsebino, ki trdi, da je označena, in drevo strukture, ki se je nikoli ne dotakne

Kjer označena vsebina na tej ravni resnično prispeva svoj delež, so pregled in popravilo: revizija, kateri objekti strani so označeni, iskanje pripomočkov, ki bi morali biti označeni kot taki, ali primerjava MCID z drevesom strukture, da se najdejo sirote. Za polovico dela na drevesu strukture glejte sprehod skozi validacijo drevesa strukture PDF/UA, za izkušnjo branja, za katero so oznake končno namenjene, pa zapiske o gradnji dostopnega bralnika PDF v Delphiju

PDFium Component daje aplikacijam Delphi, C++Builder in Lazarus visokonivojski API VCL nad pogonom PDFium, z označeno vsebino, drevesi strukture in validacijo dostopnosti dosegljivimi iz običajne kode Pascal — glejte stran izdelka PDFium Component za popolno ploskev API