Teknisk artikel

Læsning og skrivning af PDF marked content i Delphi

Marked content er den mekanisme, ISO 32000-1 §14.6 definerer til mærkning af side-indhold, og både tagget PDF og PDF/UA bygger på den. PDFium Component udstiller den direkte: PageObjectMarks læser hvert BDC-mærke og dens egenskabs-liste af et side-objekt, AddPageObjectMark skriver et, RemovePageObjectMark sletter et, og PageObjectMarkedContentID rapporterer den MCID, der linker indhold til strukturtræet

Indtil strukturtræet kan kobles tilbage til det indhold, det beskriver, er tilgængeligheds-værktøj gætterier. Strukturtræet siger "dette er en overskrift"; MCID'en siger, hvilke mærker på hvilken side, den overskrift reelt er. Begge halvdele skal være læsbare, før en applikation kan tjekke, reparere eller rapportere på mærkning

Hvad er et mærke, i byte?

En BDC-operator med et tag-navn og en valgfri egenskabs-liste, lukket af EMC. I indholds-strømmen ser det ud som /P <</MCID 3>> BDC ... EMC: tag'et /P navngiver rollen, dictionary'en bærer egenskaber, og alt mellem operatorerne er det markerede indhold. Et side-objekt inden for det spænd bærer mærket, hvilket er det, PDFium håndterer tilbage, og hvad PDFium Component forvandler til en record

TPdfContentMark holder et handle, tag'ets Name, og et array af TPdfContentMarkParam. Hver parameter har en Key, en Kind og ét meningsfuldt værdi-felt valgt af den kind: pmpInt, pmpFloat, pmpString eller pmpBlob. Kind'en kommer fra PDFiums egen type-rapport frem for fra den getter, der tilfældigvis lykkedes, hvilket er forskellen mellem at læse en egenskabs-liste og at gætte på én

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;

Hvorfor pmpUnknown betyder to forskellige ting

pmpUnknown returneres, når PDFium rapporterer FPDF_OBJECT_UNKNOWN, og PDFium returnerer også det for en nøgle, der ikke eksisterer. De to tilfælde kan ikke skelnes på dette lag, og at lade som om ville være værre end at sige det

Den praktiske konsekvens for din kode: behandl pmpUnknown som "ingen brugbar værdi her" frem for som en type, du alligevel kunne afkode. Hvis en egenskab betyder noget for din arbejdsgang, så verificér at den er til stede med en kind, du genkender, og udled ikke fravær fra en unknown — et mærke, hvis egenskabs-liste du ikke kan læse, er et mærke, du bør rapportere på, ikke et, du tavst bør acceptere

En mærke-record er et snapshot, ikke et handle du ejer

Handle-feltet tilhører biblioteket. Det bliver stale det øjeblik, mærket fjernes, side-objektet destrueres, eller siden unloades, så recorden er et skrivebeskyttet snapshot med et kort liv. Cache den på tværs af et side-skift, og du holder en pointer ind i hukommelse, motoren har reclaim-et

Dette er samme disciplin, der gælder for side-objekt-handles generelt i PDFium, og den fanger folk det samme sted: et list-kontrol befolket med mærke-record'er, en bruger der navigerer til en anden side, og et crash, der ser urelateret ud til navigationen. Kopiér de værdier ud, du har brug for — navnet, nøglerne, tallene — og lad handle'et gå. Noterne om side-objekt-handles der bliver stale efter en transform dækker den generelle regel og hvordan den bider andetsteds

Tilføjelse af et mærke, og det gem-trin der er let at misse

AddPageObjectMark tager side-objekt-indekset, et tag-navn og et komplet parameter-sæt. Parametre skrives som et sæt frem for at blive patchet én nøgle ad gangen, hvilket er grunden til, at TPdfContentMarkParam ikke har nogen Has*-sentinels — "opdatér ét felt af en eksisterende record"-tilfældet, de ville beskytte, opstår ikke

Det, der er værd at sige eksplicit: at tilføje et mærke genopbygger side-indholds-strømmen, så tag'et overlever en gemning. Det var nødt til at være eksplicit, fordi SaveAs ikke regenererer indhold på egen hånd — en ændring, der kun levede i objekt-modellen, ville blive kasseret, og den gemte fil ville se præcis ud som den, du startede med. Har du nogensinde tilføjet noget til en PDFium-side og fundet det manglende i outputtet, er det sædvanligvis hvorfor

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;

Hvad dette gør og ikke gør et dokument til

Mærker alene gør ikke en PDF tagget. Et overholdende tagget dokument har brug for et strukturtræ, hvis elementer refererer disse MCID'er, en /MarkInfo-post der erklærer dokumentet markeret, og rolle-navne der betyder, hvad standarden siger, de betyder. At skrive et /P-mærke med en MCID, intet struktur-element peger på, giver dig indhold, der giver sig ud for at være tagget, og et strukturtræ, der aldrig nævner det

Hvor marked content reelt tjener sig ind på dette niveau, er inspektion og reparation: revision af hvilke side-objekter der er tagget, find artefakter der burde have været mærket som sådanne, eller match MCID'er mod et strukturtræ for at finde de forældreløse. For strukturtræs-halvdelen af det arbejde, se gennemgangen af PDF/UA-strukturtræs-validering, og for den læseoplevelse, tag'ene i sidste ende er til, noterne om at bygge en tilgængelig PDF-læser i Delphi

PDFium Component giver Delphi-, C++Builder- og Lazarus-applikationer en high-level VCL-API over PDFium-motoren, med marked content, strukturtræer og tilgængeligheds-validering nåbart fra almindelig Pascal-kode — se PDFium Component-produktsiden for den fulde API-flade