Marked content je mechanismus, který ISO 32000-1 §14.6 definuje pro značkování obsahu stránky a na kterém jsou postavená značkovaná PDF i PDF/UA. PDFium Component ho vystavuje přímo: PageObjectMarks čte z objektu stránky každou BDC značku a její property list, AddPageObjectMark jednu zapisuje, RemovePageObjectMark jednu maže a PageObjectMarkedContentID hlásí MCID, které spojuje obsah se stromem struktury
Dokud nelze strom struktury spojit zpět s obsahem, který popisuje, je accessibility nástroj jen hádáním. Strom struktury říká „toto je nadpis"; MCID říká, které značky na které stránce ten nadpis skutečně jsou. Obě poloviny musí být čitelné, než aplikace může značkování zkontrolovat, opravit nebo ohlásit
Co je značka, v bajtech?
Operátor BDC s názvem značky a volitelným property listem, uzavřený EMC. V content streamu vypadá jako /P <</MCID 3>> BDC ... EMC: značka /P pojmenovává roli, slovník nese vlastnosti a vše mezi operátory je značkovaný obsah. Objekt stránky uvnitř tohoto rozpětí nese značku, což je to, co PDFium vrací a co PDFium Component převádí na záznam
TPdfContentMark drží handle, značku Name a pole TPdfContentMarkParam. Každý parametr má Key, Kind a jedno smysluplné pole hodnoty vybrané podle toho druhu: pmpInt, pmpFloat, pmpString nebo pmpBlob. Druh pochází z vlastního hlášení typu PDFium spíše než z toho, který getter zrovna uspěl, což je rozdíl mezi čtením property listu a hádáním
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;
Proč pmpUnknown znamená dvě různé věci
pmpUnknown se vrací, když PDFium nahlásí FPDF_OBJECT_UNKNOWN, a PDFium ho vrací i pro klíč, který neexistuje. Tyto dva případy nelze na této vrstvě rozlišit a předstírat opak by bylo horší než to říct na rovinu
Praktický důsledek pro váš kód: zacházejte s pmpUnknown jako se „zde není žádná použitelná hodnota" spíše než jako s typem, který byste mohli přesto dekódovat. Pokud na vlastnosti ve vaší rouře záleží, ověřte, že je přítomná s druhem, který rozpoznáte, a nevyvozujte absenci z unknown — značka, jejíž property list neumíte přečíst, je značka, o které byste měli hlásit, ne kterou byste potichu přijali
Záznam značky je snapshot, ne handle, který vlastníte
Pole Handle patří knihovně. Zastará ve chvíli, kdy je značka odstraněna, objekt stránky zničen nebo stránka uvolněna, takže záznam je read-only snapshot s krátkým životem. Uložíte-li ho do mezipaměti přes přepnutí stránky, držíte ukazatel do paměti, kterou si už engine přivlastnil
Stejná kázeň platí pro handle objektů stránky obecně v PDFium a chytá lidi na stejném místě: seznamový ovládací prvek naplněný záznamy značek, uživatel přejde na jinou stránku a pád, který vypadá nesouvisečně s navigací. Vytáhněte si hodnoty, které potřebujete — název, klíče, čísla — a handle pusťte. Poznámky k zastarávání handle objektů stránky po transformaci rozebírají obecné pravidlo a to, kde kousne jinde
Přidání značky a krok uložení, který se snadno přehlédne
AddPageObjectMark přijímá index objektu stránky, název značky a úplnou sadu parametrů. Parametry se zapisují jako sada spíše než patchované klíč po klíči, což je důvod, proč TPdfContentMarkParam nemá Has* sentinel — případ „aktualizovat jedno pole existujícího záznamu", který by chránily, nenastává
Stojí za to říct explicitně: přidání značky přestavuje content stream stránky, aby značka přežila uložení. To muselo být explicitní, protože SaveAs sám o sobě obsah neregeneruje — změna, která by žila jen v objektovém modelu, by byla zahazována a uložený soubor by vypadal přesně jako ten, se kterým jste začali. Pokud jste kdy do stránky PDFium něco přidali a nenašli to ve výstupu, tohle je obvykle 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;
Co z dokumentu to dělá a nedělá
Samotné značky nečiní značkované PDF. Konformní značkovaný dokument potřebuje strom struktury, jehož elementy odkazují na tyto MCID, položku /MarkInfo deklarující dokument jako značkovaný a názvy rolí, které znamenají to, co říká standard. Zápis značky /P s MCID, na kterou neukazuje žádný element struktury, vám dá obsah, který tvrdí, že je značkovaný, a strom struktury, který se o něm nikdy nezmíní
Kde marked content na této úrovni skutečně vydělává své místo, je inspekce a oprava: audit toho, které objekty stránky jsou značkované, hledání artefaktů, které měly být jako takové označeny, nebo matchování MCID vůči stromu struktury k nalezení sirotků. Pro polovinu té práce na stromu struktury viz průvodce validací stromu struktury PDF/UA a pro čtenářský zážitek, pro který značky nakonec jsou, poznámky k budování přístupné čtečky PDF v Delphi
PDFium Component dává aplikacím Delphi, C++Builder a Lazarus vysokoúrovňové VCL API nad enginem PDFium, s marked content, stromy struktury a validací přístupnosti dostupnými z běžného Pascal kódu — viz stránka produktu PDFium Component pro úplný API povrch