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

Diagram anatómie označeného obsahu PDF v Delphi: značka /P so zoznamom vlastností MCID medzi BDC a EMC, polia záznamu TPdfContentMark a odkaz MCID na strom štruktúry
Značka je tag a typovaný zoznam vlastností medzi BDC a EMC, hlásený ako záznam, ktorej MCID sa pripojí ku stromu štruktúry
var
  Marks: TPdfContentMarks;
  M: TPdfContentMark;
  P: TPdfContentMarkParam;
  I: Integer;
begin
  Pdf.PageNumber := 1;                    // PageNumber je indexované od 1
  for I := 0 to Pdf.ObjectCount - 1 do    // indexy objektov strany sú indexované od 0
  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

Diagram pridávania značky označeného obsahu v Delphi: AddPageObjectMark prestaví obsahový tok strany pred SaveAs, zatiaľ čo zmena len-v-objektovom-modeli sa mlčky zahodí z uloženého súboru
AddPageObjectMark znovu vybuduje content stream, takže SaveAs uchová značku, a zmena, ktorá skončí na objektovom modeli, nikdy nedosiahne súbor
var
  Params: TPdfContentMarkParams;
begin
  SetLength(Params, 1);
  Params[0].Key := 'MCID';
  Params[0].Kind := pmpInt;
  Params[0].IntValue := NextMcid;
  Pdf.AddPageObjectMark(ObjectIndex, 'P', Params);   // prestaví obsahový 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

Diagram PDFium Component ukazujúci, že samotné značky BDC neurobia označkované PDF, ktoré tiež potrebuje strom štruktúry odkazujúci MCID, deklaráciu MarkInfo a štandardné mená rolí
Značky podporujú audit a opravu sirotov, zatiaľ čo zhodný tagovaný dokument navyše potrebuje strom štruktúry, MarkInfo a štandardné názvy rolí

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