Teknisk artikel

Läsa och skriva PDF-markerat innehåll i Delphi

Markerat innehåll är den mekanism ISO 32000-1 §14.6 definierar för att tagga sidinnehåll, och både taggad PDF och PDF/UA är byggda på den. PDFium Component exponerar den direkt: PageObjectMarks läser varje BDC-tagg och dess egenskapslista från ett sidobjekt, AddPageObjectMark skriver en, RemovePageObjectMark raderar en, och PageObjectMarkedContentID rapporterar det MCID som länkar innehåll till strukturträdet

Fram till att strukturträdet kan fogas tillbaka till innehållet det beskriver är tillgänglighetsverktyg gissningar. Strukturträdet säger "detta är en rubrik"; MCID:t säger vilka märken på vilken sida den rubriken faktiskt är. Båda halvorna måste vara läsbara innan en applikation kan kontrollera, reparera eller rapportera om taggning

Vad är ett märke, i byte?

En BDC-operator med ett taggnamn och en valfri egenskapslista, stängd av EMC. I innehållsströmmen ser det ut som /P <</MCID 3>> BDC ... EMC: taggen /P namnger rollen, ordboken bär egenskaper, och allting mellan operatorerna är det markerade innehållet. Ett sidobjekt inuti det spannet bär märket, vilket är vad PDFium lämnar tillbaka och vad PDFium Component förvandlar till en post

TPdfContentMark håller en referens, taggens Name, och en array av TPdfContentMarkParam. Varje parameter har en Key, en Kind och ett meningsfullt värdefält valt av det slaget: pmpInt, pmpFloat, pmpString eller pmpBlob. Slaget kommer från PDFiums egen typrapport i stället för från vilken getter som råkade lyckas, vilket är skillnaden mellan att läsa en egenskapslista och att gissa på en

Diagram över PDF markerat innehålls anatomi i Delphi: en /P-tagg med en MCID-egenskapslista mellan BDC och EMC, TPdfContentMark-postfälten, och MCID-länken till strukturträdet
Ett märke är en tagg och en typad egenskapslista mellan BDC och EMC, rapporterad som en post vars MCID ansluter till strukturträdet
var
  Marks: TPdfContentMarks;
  M: TPdfContentMark;
  P: TPdfContentMarkParam;
  I: Integer;
begin
  Pdf.PageNumber := 1;                    // PageNumber är 1-baserad
  for I := 0 to Pdf.ObjectCount - 1 do    // sidobjektsindex är 0-baserade
  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;

Varför pmpUnknown betyder två olika saker

pmpUnknown returneras när PDFium rapporterar FPDF_OBJECT_UNKNOWN, och PDFium returnerar det även för en nyckel som inte existerar. De två fallen kan inte åtskiljas på detta lager, och att låtsas annorlunda skulle vara värre än att säga det

Den praktiska konsekvensen för din kod: behandla pmpUnknown som "inget användbart värde här" i stället för som en typ du ändå kanske kan avkoda. Om en egenskap betyder något för ditt arbetsflöde, verifiera att den finns med ett slag du känner igen, och härled inte frånvaro från ett okänt — ett märke vars egenskapslista du inte kan läsa är ett märke du bör rapportera om, inte ett du tyst bör acceptera

En märkpost är en ögonblicksbild inte en referens du äger

Handle-fältet tillhör biblioteket. Det blir inaktuellt i det ögonblick märket tas bort, sidobjektet förstörs eller sidan urladdas, så posten är en skrivskyddad ögonblicksbild med kort liv. Cacha den över ett sidbyte och du håller en pekare in i minne motorn har återtagit

Detta är samma disciplin som gäller för sidobjektsreferenser generellt i PDFium, och det fångar folk på samma plats: en listkontroll befolkad med märkposter, en användare som navigerar till en annan sida, och en krasch som ser orelaterad ut till navigeringen. Kopiera ut de värden du behöver — namnet, nycklarna, numren — och släpp referensen. Noterna om sidobjektsreferenser som blir inaktuella efter en transform täcker den allmänna regeln och hur den biter annorstädes

Att lägga till ett märke och det sparsteg som är lätt att missa

AddPageObjectMark tar sidobjektindexet, ett taggnamn och en komplett parametermängd. Parametrar skrivs som en mängd i stället för att patchas en nyckel i taget, vilket är varför TPdfContentMarkParam inte har några Has*-sentinelvärden — fallet "uppdatera ett fält av en befintlig post" dessa skulle vakta uppstår inte

Den del som är värd att uttala explicit: att lägga till ett märke bygger om sidinnehållsströmmen så att taggen överlever ett sparande. Detta var tvungen att vara explicit eftersom SaveAs inte regenererar innehåll på egen hand — en ändring som enbart levde i objektmodellen skulle förkastas, och den sparade filen skulle se exakt ut som den du startade med. Om du någonsin har lagt till någonting till en PDFium-sida och funnit det saknas i utdata, är detta oftast varför

Diagram över att lägga till en markerat-innehåll-tagg i Delphi: AddPageObjectMark bygger om sidans innehållsström före SaveAs, medan en ändring bara i objektmodellen tyst släpps från den sparade filen
AddPageObjectMark bygger om innehållsströmmen så att SaveAs gör taggen bestående, och en ändring som stannar vid objektmodellen når aldrig filen
var
  Params: TPdfContentMarkParams;
begin
  SetLength(Params, 1);
  Params[0].Key := 'MCID';
  Params[0].Kind := pmpInt;
  Params[0].IntValue := NextMcid;
  Pdf.AddPageObjectMark(ObjectIndex, 'P', Params);   // bygger om innehållsströmmen
  Pdf.UpdatePage;
  Pdf.SaveAs('tagged-out.pdf');
end;

Vad detta gör och inte gör ett dokument till

Märken ensamma gör inte en taggad PDF. Ett överensstämmande taggat dokument behöver ett strukturträd vars element refererar dessa MCID, en /MarkInfo-post som deklarerar dokumentet markerat, och rollnamn som betyder vad standarden säger att de betyder. Att skriva ett /P-märke med ett MCID inget strukturelement pekar på ger dig innehåll som påstår sig vara taggat och ett strukturträd som aldrig nämner det

PDFium Component-diagram som visar att BDC-märken ensamma inte gör en taggad PDF, som också behöver ett strukturträd som refererar MCID:er, en MarkInfo-deklaration och standardrollnamn
Märken stöder granskning och reparation av föräldralösa medan ett överensstämmande taggat dokument också behöver ett strukturträd, MarkInfo och standardrollnamn

Där markerat innehåll genuint tjänar sin plats på denna nivå är inspektion och reparation: granska vilka sidobjekt som är taggade, hitta artefakter som borde ha markerats som sådana, eller matcha MCID mot ett strukturträd för att hitta de föräldralösa. För strukturträdshalvan av det arbetet, se genomgången av PDF/UA-strukturträdvalidering, och för den läsupplevelse taggarna i slutändan är till för, noterna om att bygga en tillgänglig PDF-läsare i Delphi

PDFium Component ger Delphi-, C++Builder- och Lazarus-applikationer ett hög-nivå VCL-API över PDFium-motorn, med markerat innehåll, strukturträd och tillgänglighetsvalidering nåbara från vanlig Pascal-kod — se PDFium Component-produktsidan för hela API-ytan