Odborný článok

Čítanie a zápis označeného obsahu PDF v Delphi

Označený obsah je mechanizmus, ktorý ISO 32000-1 §14.6 definuje pre značenie obsahu strany a značené PDF aj PDF/UA sú na ňom postavené. PDFium Component ho vystavuje priamo: PageObjectMarks číta každý BDC tag a jeho zoznam vlastností z objektu strany, AddPageObjectMark jeden zapíše, RemovePageObjectMark jeden vymaže a PageObjectMarkedContentID hlási MCID, ktorý prepája obsah so stromom štruktúry

Kým sa strom štruktúry nedá prepojiť späť s obsahom, ktorý opisuje, nástroje prístupnosti sú hádaním. Strom štruktúry hovorí „toto je nadpis"; MCID hovorí, ktoré značky na ktorej strane ten nadpis skutočne je. obe polovice sa musia dať čítať, než môže aplikácia značenie skontrolovať, opraviť alebo o ňom reportovať

Čo je značka, v bajtoch?

Operátor BDC s názvom tagu a voliteľným zoznamom vlastností, uzavretý EMC. V obsahovom streame vyzerá ako /P <</MCID 3>> BDC ... EMC: tag /P menuje rolu, slovník nesie vlastnosti a všetko medzi operátormi je označený obsah. Objekt strany vnútri tohto rozsahu nesie značku, čo je to, čo PDFium vracia a čo PDFium Component mení na záznam

TPdfContentMark drží handle, tag Name a pole TPdfContentMarkParam. Každý parameter má Key, Kind a jedno zmysluplné pole hodnoty vybrané týmto druhom: pmpInt, pmpFloat, pmpString alebo pmpBlob. Druch prichádza z vlastného typového hlásenia PDFia a nie od toho, ktorý getter sa náhodou ujal, čo je rozdiel medzi čítaním zoznamu vlastností a hádaním jedného

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;

Prečo pmpUnknown znamená dve rozličné veci

pmpUnknown sa vráti, keď PDFium nahlási FPDF_OBJECT_UNKNOWN, a PDFium ho vracia aj pre kľúč, ktorý neexistuje. Tieto dva prípady sa v tejto vrstve nedajú odlíšiť a tváriť sa, že áno, by bola horšie než to povedať otvorene

Praktický dôsledok pre váš kód: zaobchádzajte s pmpUnknown ako s „tu nie je žiadna použiteľná hodnota" a nie ako s typom, ktorý by sa dal napriek tomu dekódovať. Ak na vlastnosti vo vašom workflow záleží, overte, že je prítomná s druhom, ktorý rozpoznáte, a neodvozdujte absenciu z unknown — značka, ktorej zoznam vlastností nedokážete prečítať, je značka, o ktorej by ste mali reportovať, a nie taká, ktorú by ste mlčky prijali

Záznam značky je snímok, nie handle, ktorý vlastníte

Pole Handle patrí knižnici. Stane sa zastaralým v okamihu, keď je značka odstránená, objekt strany zničený alebo strana uvoľnená, takže záznam je len na čítanie so krátkou životnosťou. Keď ho uložíte do vyrovnávacej pamäte naprieč prepnutím strany, držíte ukazovateľ do pamäte, ktorú si engine už vyhradil

To je rovnaká disciplína, ktorá platí všeobecne pre handle objektov strany v PDFiu a chytá ľudí na tom istom mieste: ovládací prvok zoznamu naplnený záznamami značiek, používateľ, ktorý prejde na inú stranu, a pád, ktorý vyzerá nesúvisí s navigáciou. Skopírujte si hodnoty, ktoré potrebujete — názov, kľúče, čísla — a handle nechajte ísť. Poznámky k zastarávaniu handle objektov strany po transformácii pokrývajú všeobecné pravidlo a to, ako zahreje inde

Pridanie značky a krok uloženia, ktorý sa ľahko prehliadne

AddPageObjectMark prijíma index objektu strany, názov tagu a úplnú sadu parametrov. Parametre sa zapisujú ako sada a nie patchované jeden kľúč po druhom, čo je dôvod, prečo TPdfContentMarkParam nemá žiadne Has* sentinel — prípad „aktualizuj jedno pole existujúceho záznamu", ktorý by tie strážili, sa nevyskytuje

Časť, ktorú sa oplatí povedať explicitne: pridanie značky prestaví obsahový stream strany, aby tag prežil uloženie. To muselo byť explicitné, pretože SaveAs sám o sebe neregeneruje obsah — zmena, ktorá by žila iba v objektovom modeli, by bola zahodená a uložený súbor by vyzeral presne ako ten, s ktorým ste začali. Ak ste kedy niečo pridali na stranu PDFia a našli ste to chýbať vo výstupe, to je zvyčajne dôvod

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;

Čo z toho dokument urobí a čo nie

Samotné značky neurobia značené PDF. Zodpovedajúci značený dokument potrebuje strom štruktúry, ktorého elementy referencujú tieto MCID, položku /MarkInfo vyhlasujúcu dokument za značený, a názvy rolí, ktoré znamenajú to, čo hovorí štandard. Zápis značky /P s MCID, na ktorý neukazuje žiadny element štruktúry, vám dá obsah, ktorý tvrdí, že je značený, a strom štruktúry, ktorý o ňom nikdy nespomenie

Kde označený obsah na tejto úrovni skutočne odvádza svoju prácu, je inšpekcia a oprava: audit, ktoré objekty strany sú značené, hľadanie artefaktov, ktoré mali byť ako také označené, alebo párovanie MCID proti stromu štruktúry na nájdenie sirôt. Pre polovicu tejto práce na strome štruktúry pozri návod k validácii stromu štruktúry PDF/UA a pre čítacie rozhranie, pre ktoré sú značky napokon tu, poznámky k stavbe prístupnej čítačky PDF v Delphi

PDFium Component dáva aplikáciám Delphi, C++Builder a Lazarus VCL API vysokej úrovne nad enginom PDFium, s označeným obsahom, stromami štruktúry a validáciou prístupnosti dostupnými z bežného Pascal kódu — kompletnú API plochu nájdete na produktovej stránke PDFium Component