Tekninen artikkeli

PDF:n merkityn sisällön lukeminen ja kirjoittaminen Delphissä

Merkitty sisältö on mekanismi jonka ISO 32000-1 §14.6 määrittelee sivun sisällön tunnistamiseen, ja tunnistettu PDF ja PDF/UA on molemmat rakennettu sen varaan. PDFium Component paljastaa sen suoraan: PageObjectMarks lukee jokaisen BDC-tunnisteen ja sen ominaisuusluettelon sivuoliolta, AddPageObjectMark kirjoittaa yhden, RemovePageObjectMark poistaa yhden, ja PageObjectMarkedContentID raportoi MCID:n joka linkittää sisällön rakennepuuhun

Kunnes rakennepuu voidaan liittää takaisin sisältöön jota se kuvaa, saavutettavuustyökalut ovat arvailua. Rakennepuu sanoo "tämä on otsikko"; MCID sanoo mitkä merkinnät millä sivulla tuo otsikko todellisuudessa on. molemmat puoliskot pitää olla luettavissa ennen kuin sovellus voi tarkistaa, korjata tai raportoida tunnistuksesta

Mikä on merkki, tavuina?

BDC-operaattori jolla on tunnisteen nimi ja valinnainen ominaisuusluettelo, jonka EMC sulkee. Sisältövirrassa se näyttää tältä /P <</MCID 3>> BDC ... EMC: tunniste /P nimeää roolin, sanakirja kantaa ominaisuuksia, ja kaiken operaattoreiden välissä on merkitty sisältö. Sivuolio tuon välivälin sisällä kantaa merkin, mikä on se minkä PDFium palauttaa ja minkä PDFium Component muuttaa tietueeksi

TPdfContentMark pitää kahvan, tunnisteen Name:n ja taulukon TPdfContentMarkParam-arvoja. Jokaisella parametrilla on Key, Kind ja yksi merkityksellinen arvokenttä jonka se laji valitsee: pmpInt, pmpFloat, pmpString tai pmpBlob. Laji tulee PDFiumin omasta tyyppiraportista eikä siitä getteristä joka sattui onnistumaan, mikä on ero ominaisuusluettelon lukemisen ja sen arvailemisen välillä

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;

Miksi pmpUnknown tarkoittaa kahta eri asiaa

pmpUnknown palautetaan kun PDFium raportoi FPDF_OBJECT_UNKNOWN:n, ja PDFium palauttaa sen myös avaimelle jota ei ole olemassa. Kahta tapausta ei voida erottaa tällä tasolla, ja muun olettaminen olisi pahempaa kuin asian sanoaminen

Käytännön seuraus koodillesi: käsittele pmpUnknown:ta "ei käyttökelpoista arvoa tässä" sen sijaan että lajina jonka saatat siltiin selvittää. Jos ominaisuus merkitsee työnkulullesi, varmista että se on läsnä tunnistamallasi lajilla, äläkä päätä puuttumista tuntemattomasta — merkki jonka ominaisuusluetteloa et voi lukea on merkki josta sinun pitäisi raportoida, ei sellainen jonka hiljaa hyväksyt

Merkkitietue on tilannevedos, ei kahva jonka omistat

Handle-kenttä kuuluu kirjastolle. Se vanhenee heti kun merkki poistetaan, sivuolio tuhotaan tai sivu purkaa, joten tietue on vain luku -tilannevedos jolla on lyhyt elämä. Välimuistita se sivunvaihdon yli ja pidät osoitinta muistiin jonka moottori on vaatinut takaisin

Tämä on sama kurinalaisuus joka koskee sivuoliokahvoja yleensä PDFiumissa, ja se nappaa ihmiset samassa paikassa: luettelokontrolli joka on täytetty merkkitietueilla, käyttäjä joka siirtyy toiselle sivulle, ja kaatumis joka näyttää irralliselta siirtymiseltä. Kopioi ulos arvot joita tarvitset — nimi, avaimet, numerot — ja anna kahvan mennä. Muistiinpanot sivuoliokahvojen vanhenemisesta muunnoksen jälkeen käsittelevät yleisen säännön ja miten se puree muualla

Merkin lisääminen ja tallennusaskel joka on helppo ohittaa

AddPageObjectMark ottaa sivuolioindeksin, tunnistenimen ja täydellisen parametrijoukon. Parametrit kirjoitetaan joukkona eikä paikataan yksi avain kerrallaan, mikä on syy miksi TPdfContentMarkParam:lla ei ole Has*-vartioita — "päivitä yksi kenttä olemassa olevasta tietueesta" -tapaus jonka nuo varaisivat ei nouse

Osa joka kannattaa sanoa nimenomaisesti: merkin lisääminen rakentaa sivun sisältövirran uudelleen niin että tunniste säilyy tallennuksen. Tämän piti olla nimenomainen koska SaveAs ei tuota sisältöä uudelleen itsestään — muutos joka eli vain oliomallissa hylättiin, ja tallennettu tiedosto näyttäisi tismalleen samalta kuin mistä aloitit. Jos olet koskaan lisännyt jotain PDFium-sivulle ja löytänyt sen puuttuvan tulosteesta, tämä on yleensä syy

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;

Mitä tämä tekee ja ei tee asiakirjasta

Merkit yksinään eivät tee tunnistetusta PDF:stä. Vaatimustenvastainen tunnistettu asiakirja tarvitsee rakennepuun jonka elementit viittaavat näihin MCID:ihin, /MarkInfo-merkinnän joka julistaa asiakirjan tunnistetuksi, ja roolinimet jotka tarkoittavat mitä standardi sanoo niiden tarkoittavan. /P-merkin kirjoittaminen MCID:n kanssa johon mikään rakenneryhmä ei osoita antaa sinulle sisältöä joka väittää olevansa tunnistettua ja rakennepuun joka ei koskaan mainitse sitä

Missä merkitty sisältö aidosti ansaitsee paikkansa tällä tasolla on tarkastus ja korjaus: sen auditoiminen mitkä sivuoliot on tunnistettu, sellaisten taustaelementtien löytäminen jotka olisi pitänyt merkitä sellaisiksi, tai MCID:ien täsmääminen rakennepuuhun löytääkseen orvot. Tuon työn rakennepuupuolta varten katso artikkeli PDF/UA-rakennepuun validointi, ja lukukokemusta varten jota tunnisteet lopulta ovat, muistiinpanot saavutettavan PDF-lukijan rakentaminen Delphissä

PDFium Component antaa Delphi-, C++Builder- ja Lazarus-sovelluksille korkean tason VCL-API:n PDFium-moottorin yli, jolloin merkitty sisältö, rakennepuut ja saavutettavuusvalidointi on tavoitettavissa tavallisesta Pascal-koodista — katso PDFium Component -tuotesivulta koko API-pinta