Позначений вміст — це механізм, який 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 високорівневий VCL API поверх рушія PDFium, із позначеним вмістом, деревами структури та валідацією доступності, доступними зі звичайного Pascal-коду — дивіться сторінку продукту PDFium Component щодо повної поверхні API