Marked content is het mechanisme dat ISO 32000-1 §14.6 definieert voor het taggen van paginacontent, en zowel tagged PDF als PDF/UA zijn erop gebouwd. PDFium Component ontsluit het direct: PageObjectMarks leest elke BDC-tag en zijn propertylijst van een pagina-object, AddPageObjectMark schrijft er een, RemovePageObjectMark verwijdert er een, en PageObjectMarkedContentID rapporteert de MCID die content koppelt aan de structuurboom
Totdat de structuurboom kan worden teruggekoppeld naar de content die hij beschrijft, is toegankelijkheidsgereedschap giswerk. De structuurboom zegt "dit is een kop"; de MCID zegt welke marks op welke pagina die kop daadwerkelijk zijn. Beide helften moeten leesbaar zijn voordat een applicatie tagging kan controleren, repareren of rapporteren
Wat is een mark, in bytes?
Een BDC-operator met een tagnaam en een optionele propertylijst, gesloten door EMC. In de contentstream ziet dat eruit als /P <</MCID 3>> BDC ... EMC: de tag /P benoemt de rol, het dictionary draagt properties, en alles tussen de operatoren is de marked content. Een pagina-object binnen die overspanning draagt de mark, wat PDFium teruggeeft en wat PDFium Component in een record omzet
TPdfContentMark houdt een handle vast, de tag Name, en een array van TPdfContentMarkParam. Elke parameter heeft een Key, een Kind en één betekenisvol waardeveld dat door dat kind wordt gekozen: pmpInt, pmpFloat, pmpString of pmpBlob. Het kind komt uit PDFiums eigen typerapport in plaats van uit welke getter toevallig slaagde, wat het verschil is tussen een propertylijst lezen en ernaar gissen
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;
Waarom pmpUnknown twee verschillende dingen betekent
pmpUnknown wordt teruggegeven wanneer PDFium FPDF_OBJECT_UNKNOWN rapporteert, en PDFium geeft dat ook terug voor een sleutel die niet bestaat. De twee gevallen zijn op deze laag niet te onderscheiden, en anders doen alsof zou erger zijn dan het zeggen
De praktische consequentie voor je code: behandel pmpUnknown als "hier geen bruikbare waarde" in plaats van als een type dat je toch zou kunnen decoderen. Als een property ertoe doet voor je workflow, verifieer dat hij aanwezig is met een kind dat je herkent, en leid geen afwezigheid af uit een onbekende — een mark waarvan je de propertylijst niet kunt lezen is een mark waarover je moet rapporteren, niet er een die je stilzwijgend moet accepteren
Een mark-record is een momentopname, geen handle die je bezit
Het veld Handle behoort aan de bibliotheek. Het wordt stale het moment dat de mark wordt verwijderd, het pagina-object wordt vernietigd of de pagina wordt gelost, dus het record is een alleen-lezen-momentopname met een kort leven. Cache het over een pagina-wissel en je houdt een pointer vast naar geheugen dat de engine heeft teruggeclaimd
Dit is dezelfde discipline die in het algemeen geldt voor pagina-object-handles in PDFium, en het vangt mensen op dezelfde plek: een lijstbesturing gevuld met mark-records, een gebruiker die naar een andere pagina navigeert, en een crash die er niet mee in verband lijkt te staan. Kopieer de waarden eruit die je nodig hebt — de naam, de sleutels, de getallen — en laat de handle los. De notities over pagina-object-handles die stale raken na een transformatie behandelen de algemene regel en hoe hij elders bijt
Een mark toevoegen, en de opslagstap die gemakkelijk wordt gemist
AddPageObjectMark neemt de pagina-object-index, een tagnaam en een complete parameterset. Parameters worden als een verzameling geschreven in plaats van één sleutel tegelijk gepatcht, wat is waarom TPdfContentMarkParam geen Has*-sentinels heeft — het "werk één veld van een bestaand record bij"-geval dat die zouden bewaken doet zich niet voor
Het deel dat de moeite waard is om ronduit te stellen: een mark toevoegen herbouwt de pagina-contentstream zodat de tag een opslag overleeft. Dit moest expliciet zijn omdat SaveAs op zichzelf geen content regenereert — een verandering die alleen in het objectmodel leefde zou worden weggegooid, en het opgeslagen bestand zou er precies zo uitzien als waarmee je begon. Als je ooit iets aan een PDFium-pagina hebt toegevoegd en het miste in de uitvoer, is dit meestal waarom
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;
Wat dit een document wel en niet maakt
Alleen marks maken nog geen tagged PDF. Een conformerend getagd document heeft een structuurboom nodig waarvan de elementen naar deze MCIDs verwijzen, een /MarkInfo-entry die het document als tagged declareert, en rolnamen die betekenen wat de standaard zegt dat ze betekenen. Een /P-mark schrijven met een MCID waar geen structuurelement naar wijst, geeft je content die beweert getagd te zijn en een structuurboom die het nooit noemt
Waar marked content op dit niveau werkelijk zijn waarde bewijst, is inspectie en reparatie: auditeren welke pagina-objecten getagd zijn, artifacts vinden die als zodanig hadden moeten worden gemarkeerd, of MCIDs matchen tegen een structuurboom om de wezen te vinden. Voor de structuurboom-helft van dat werk, zie de doorloop van PDF/UA-structuurboomvalidatie, en voor de leeservaring waarvoor de tags uiteindelijk zijn, de notities over het bouwen van een toegankelijke PDF-reader in Delphi
PDFium Component geeft Delphi-, C++Builder- en Lazarus-applicaties een hoogwaardige VCL-API over de PDFium-engine, met marked content, structuurbomen en toegankelijkheidsvalidatie bereikbaar vanuit gewone Pascal-code — zie de PDFium Component-productpagina voor het volledige API-oppervlak