Маркираното съдържание е механизмът, който ISO 32000-1 §14.6 дефинира за маркиране на съдържание на страница, а маркираният PDF и PDF/UA са построени върху него. PDFium Component го излага директно: PageObjectMarks чете всеки BDC маркер и неговия списък със свойства от обект на страница, AddPageObjectMark записва такъв, RemovePageObjectMark изтрива такъв, а PageObjectMarkedContentID отчита MCID, който свързва съдържанието с дървото на структурата
Докато дървото на структурата не може да бъде съединено обратно към съдържанието, което описва, инструментите за достъпност са отгатване. Дървото на структурата казва «това е заглавие»; MCID казва кои марки на коя страница това заглавие реално е. И двете половини трябва да са четими, преди приложение да може да проверява, поправя или отчита маркиране
Какво е марка, в байтове?
Оператор BDC с име на марка и незадължителен списък със свойства, затворен от EMC. В потока на съдържанието изглежда като /P <</MCID 3>> BDC ... EMC: марката /P назовава ролята, речникът носи свойства, а всичко между операторите е маркираното съдържание. Обект на страница вътре в този интервал носи марката, което е това, което PDFium връща и което PDFium Component превръща в запис
TPdfContentMark държи манипулатор, марката Name и масив от TPdfContentMarkParam. Всеки параметър има Key, Kind и едно смислено поле за стойност, избрано от този вид: pmpInt, pmpFloat, pmpString или pmpBlob. Видът идва от собствения отчет за типове на PDFium, а не от това кой getter се е случил да успее, което е разликата между четене на списък със свойства и отгатване на такъв
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;
Защо pmpUnknown означава две различни неща
pmpUnknown се връща, когато PDFium отчита FPDF_OBJECT_UNKNOWN, а PDFium също така връща това за ключ, който не съществува. Двата случая не могат да бъдат разграничени на това ниво, а преструвката, че е възможно, би била по-лоша от това да се каже
Практическото следствие за вашия код: третирайте pmpUnknown като «тук няма използваема стойност», а не като вид, който можете да декодирате така или иначе. Ако свойство има значение за вашия работен процес, проверете, че присъства с вид, който разпознавате, и не извеждайте отсъствие от непознат — марка, чиито списък със свойства не можете да прочетете, е марка, за която трябва да отчитате, а не такава, която трябва мълчаливо да приемете
Запис на марка е снимка, а не манипулатор, който притежавате
Полето Handle принадлежи на библиотеката. То остарява в момента, в който марката бъде премахната, обектът на страница унищожен или страницата разтоварена, така че записът е снимка само за четене с кратък живот. Кеширайте го през превключване на страница и държите указател в памет, която двигателят е върнал към системата
Това е същата дисциплина, която се прилага за манипулатори на обекти на страница изобщо в PDFium, и хваща хората на същото място: контрол списък, населен с записи на марки, потребител, навигиращ към друга страница, и срив, който изглежда несвързан с навигацията. Копирайте стойностите, от които се нуждаете — името, ключовете, числата — и оставете манипулатора да си отиде. Записките за остаряване на манипулатори на обекти на страница след трансформация разглеждат общото правило и как то хапе другаде
Добавяне на марка и стъпката за запис, която лесно се пропуска
AddPageObjectMark приема индекса на обект на страница, име на марка и пълен набор от параметри. Параметрите се записват като набор, а не се закърпват един ключ по едно време, което е причината TPdfContentMarkParam няма Has* сентинели — случай «обнови едно поле на съществуващ запис», който те биха пазили, не възниква
Частта, която си струва да се каже изрично: добавянето на марка прездравява потока на съдържанието на страницата, така че марката оцелява запис. Това трябваше да бъде изрично, защото SaveAs не регенерира съдържание само по себе си — промяна, която живее само в обектния модел, би била отхвърлена, а записаният файл би изглеждал точно като този, с който сте започнали. Ако някога сте добавяли нещо към страница на PDFium и сте го открили липсващо в изхода, това обикновено е причината
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;
Какво това прави и какво не прави от документ
Самите марки не правят маркиран PDF. Съответстващ маркиран документ се нуждае от дърво на структурата, чиито елементи реферират тези MCID, запис /MarkInfo, деклариращ документа маркиран, и имена на роли, които означават това, което стандартът казва, че означават. Записването на марка /P с MCID, към който никой структурен елемент не сочи, ви дава съдържание, което твърди, че е маркирано, и дърво на структурата, което никога не го споменава
Там, където маркираното съдържание наистина печели стойността си на това ниво, е инспекция и поправка: одит кои обекти на страница са маркирани, намиране на артефакти, които е трябвало да бъдат маркирани като такива, или съпоставяне на MCID срещу дърво на структурата, за да се намерят сираките. За половината от тази работа, която е в дървото на структурата, вижте ръководството за валидация на структурно дърво на PDF/UA, а за изживяването на четене, за което марките в крайна сметка са — записките за изграждане на достъпен PDF четец в Delphi
PDFium Component дава на Delphi, C++Builder и Lazarus приложения API от високо ниво VCL върху двигателя PDFium, с маркирано съдържание, дървета на структурата и валидация на достъпност, достъпни от обикновен Pascal код — вижте страницата на продукта PDFium Component за пълната API повърхност