Le contenu marqué est le mécanisme qu'ISO 32000-1 §14.6 définit pour baliser le contenu de page, et le PDF balisé et PDF/UA sont tous deux construits dessus. PDFium Component l'expose directement : PageObjectMarks lit chaque balise BDC et sa liste de propriétés sur un objet de page, AddPageObjectMark en écrit une, RemovePageObjectMark en supprime une, et PageObjectMarkedContentID signale le MCID qui relie le contenu à l'arborescence de structure
Tant que l'arborescence de structure ne peut pas être raccordée au contenu qu'elle décrit, l'outillage d'accessibilité est de la devinette. L arborescence de structure dit « ceci est un titre » ; le MCID dit quelles marques sur quelle page ce titre est effectivement. Les deux moitiés doivent être lisibles avant qu'une application puisse vérifier, réparer ou rendre compte du balisage
Qu'est-ce qu'une marque, en octets ?
Un opérateur BDC avec un nom de balise et une liste de propriétés optionnelle, fermé par EMC. Dans le flux de contenu cela ressemble à /P <</MCID 3>> BDC ... EMC : la balise /P nomme le rôle, le dictionnaire porte les propriétés, et tout entre les opérateurs est le contenu marqué. Un objet de page à l'intérieur de cette portée porte la marque, ce que PDFium remet et que PDFium Component transforme en enregistrement
TPdfContentMark détient un handle, la balise Name, et un tableau de TPdfContentMarkParam. Chaque paramètre a une Key, un Kind et un champ de valeur significatif unique sélectionné par ce genre : pmpInt, pmpFloat, pmpString ou pmpBlob. Le genre vient du propre rapport de type de PDFium plutôt que de la getter qui a réussi à succéder, ce qui est la différence entre lire une liste de propriétés et en deviner une
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;
Pourquoi pmpUnknown signifie deux choses différentes
pmpUnknown est renvoyé quand PDFium signale FPDF_OBJECT_UNKNOWN, et PDFium le renvoie aussi pour une clé qui n'existe pas. Les deux cas ne peuvent pas être distingués à cette couche, et prétendre le contraire serait pire que de le dire
La conséquence pratique pour votre code : traitez pmpUnknown comme « aucune valeur utilisable ici » plutôt que comme un type que vous pourriez décoder quand même. Si une propriété compte pour votre flux, vérifiez qu'elle est présente avec un genre que vous reconnaissez, et n'inférez pas l'absence depuis un inconnu — une marque dont vous ne pouvez pas lire la liste de propriétés est une marque sur laquelle vous devriez rendre compte, pas une que vous devriez accepter silencieusement
Un enregistrement de marque est un instantané, pas un handle que vous possédez
Le champ Handle appartient à la bibliothèque. Il devient périmé dès que la marque est supprimée, que l'objet de page est détruit ou que la page est déchargée, donc l'enregistrement est un instantané en lecture seule à vie courte. Le cacher à travers un changement de page et vous tenez un pointeur dans de la mémoire que le moteur a réclamée
C'est la même discipline qui s'applique aux handles d'objets de page généralement dans PDFium, et elle attrape les gens au même endroit : un contrôle liste rempli d'enregistrements de marques, un utilisateur naviguant vers une autre page, et un crash qui paraît sans rapport avec la navigation. Copiez les valeurs dont vous avez besoin — le nom, les clés, les nombres — et laissez le handle partir. Les notes sur les handles d'objets de page devenant périmés après une transformation couvrent la règle générale et comment elle mord ailleurs
Ajouter une marque, et l'étape de sauvegarde facile à manquer
AddPageObjectMark prend l'index d'objet de page, un nom de balise et un ensemble complet de paramètres. Les paramètres sont écrits comme un ensemble plutôt que patchés une clé à la fois, ce qui est pourquoi TPdfContentMarkParam n'a pas de sentinelles Has* — le cas « mettre à jour un seul champ d'un enregistrement existant » que celles-ci garderaient ne se présente pas
La partie qui vaut d'être énoncée explicitement : ajouter une marque reconstruit le flux de contenu de la page afin que la balise survive à une sauvegarde. Cela a dû être explicite car SaveAs ne régénère pas le contenu de lui-même — un changement qui ne vivait que dans le modèle objet serait abandonné, et le fichier enregistré ressemblerait exactement à celui avec lequel vous avez commencé. Si vous avez déjà ajouté quelque chose à une page PDFium et l'avez trouvé manquant dans la sortie, c'est habituellement pourquoi
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;
Ce que cela fait et ne fait pas d'un document
Les marques seules ne font pas un PDF balisé. Un document balisé conforme a besoin d'une arborescence de structure dont les éléments référencent ces MCIDs, d'une entrée /MarkInfo déclarant le document marqué, et de noms de rôles qui signifient ce que la norme dit qu'ils signifient. Écrire une marque /P avec un MCID qu'aucun élément de structure ne pointe vous donne du contenu qui se prétend balisé et une arborescence de structure qui ne le mentionne jamais
Là où le contenu marqué gagne réellement son utilité à ce niveau, c'est l'inspection et la réparation : auditer quels objets de page sont balisés, trouver des artéfacts qui auraient dû être marqués comme tels, ou mettre en correspondance des MCIDs avec une arborescence de structure pour trouver les orphelins. Pour la moitié arborescence de structure de ce travail, voir la visite guidée de la validation d'arborescence de structure PDF/UA, et pour l'expérience de lecture à laquelle les balises sont ultimement destinées, les notes sur la construction d'un lecteur PDF accessible en Delphi
PDFium Component donne aux applications Delphi, C++Builder et Lazarus une API VCL de haut niveau sur le moteur PDFium, avec contenu marqué, arborescences de structure et validation d'accessibilité accessibles depuis du code Pascal ordinaire — voir la page produit PDFium Component pour la surface API complète