Techninis straipsnis

PDF pažymėto turinio skaitymas ir rašymas Delphi

Pažymėtas turinys yra mechanizmas, kurį ISO 32000-1 §14.6 apibrėžia puslapio turinio žymėjimui, ant kurio pastatyti tiek pažymėtas PDF, tiek PDF/UA. PDFium Component atskleidžia jį tiesiogiai: PageObjectMarks skaito kiekvieną BDC žymą ir jos savybių sąrašą iš puslapio objekto, AddPageObjectMark užrašo vieną, RemovePageObjectMark ištrina vieną, o PageObjectMarkedContentID praneša MCID, siejantį turinį su struktūros medžiu

Kol struktūros medis negali būti susietas atgal su turiniu, kurį jis aprašo, prieinamumo įrankiai yra spėliojimas. Struktūros medis sako „ tai yra antraštė“; MCID sako, kuriose žymose kuriame puslapyje ta antraštė iš tikrųjų yra. Abi puses turi būti įmanoma perskaityti, kol programa gali tikrinti, taisyti ar pranešti apie žymėjimą

Kas yra žyma, baitais?

BDC operatorius su žymos vardu ir pasirinktiniu savybių sąrašu, uždarytas EMC. Turinio sraute tai atrodo kaip /P <</MCID 3>> BDC ... EMC: žyma /P įvardija vaidmenį, žodynas neša savybes, o viskas tarp operatorių yra pažymėtas turinys. Puslapio objektas tojespan neša žymą, ką PDFium ir grąžina, ką PDFium Component paverčia įrašu

TPdfContentMark laiko deskriptorių, žymos Name ir TPdfContentMarkParam masyvą. Kiekvienas parametras turi Key, Kind ir vieną prasmingą reikšmės lauką, parinktą pagal tą rūšį: pmpInt, pmpFloat, pmpString arba pmpBlob. Rūšis ateina iš PDFium paties tipo pranešimo, o ne iš to, kuris getteris pasisekė, kas ir yra skirtumas tarp savybių sąrašo skaitymo ir spėjimo

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;

Kodėl pmpUnknown reiškia du skirtingus dalykus

pmpUnknown grąžinamas, kai PDFium praneša FPDF_OBJECT_UNKNOWN, o PDFium taip pat grąžina tai raktui, kurio nėra. Šių dviejų atvejų šiame sluoksnyje negalima atskirti, ir apsimesti, kad galima, būtų blogiau, nei taip pasakyti

Praktinė pasekmė jūsų kodui: traktuokite pmpUnknown kaip „čia nėra naudojamos reikšmės“, o ne kaip tipą, kurį vis tiek galite dekoduoti. Jei savybė svarbi jūsų darbo eigai, patikrinkite, kad ji yra su jums žinoma rūšimi, ir neišveskite nebuvimo iš nežinomo — žyma, kurios savybių sąrašo negalite perskaityti, yra žyma, apie kurią turėtumėte pranešti, o ne tokią, kurią tylom priimate

Žymos įrašas yra momentinė nuotrauka, o ne deskriptorius, kurį valdote

Handle laukas priklauso bibliotekai. Jis pasensta akimirksniu, kai žyma pašalinama, puslapio objektas sunaikinamas arba puslapis iškraunamas, todėl įrašas yra tik skaitoma momentinė nuotrauka su trumpu gyvenimu. Podėliukite ją per puslapio perjungimą, ir laikote rodyklę į atmintį, kurią variklis jau atsiėmė

Tai yra ta pati drausmė, kuri taikoma puslapio objektų deskriptoriams apskritai PDFium, ir ji pagauna žmones toje pačioje vietoje: sąrašo valdiklis, užpildytas žymų įrašais, naudotojas, perjungiantis į kitą puslapį, ir triktis, atrodanti nesusijusi su naršymu. Nusikopijuokite reikšmes, kurių reikia — pavadinimą, raktus, skaičius — ir paleiskite deskriptorių. Puslapio objektų deskriptorių pasenimo po transformacijos pastabos padengia bendrąją taisyklę ir kaip ji kanda kitur

Žymos pridėjimas ir išsaugojimo žingsnis, kurį lengva praleisti

AddPageObjectMark ima puslapio objekto indeksą, žymos pavadinimą ir baigtą parametrų rinkinį. Parametrai užrašomi kaip rinkinys, o ne pataisomi po vieną raktą, todėl TPdfContentMarkParam neturi Has* sargybinių — atvejis „atnaujinti vieną esamo įrašo lauką“, kurį jie saugotų, nekyla

Dalis, verta pasakyti aiškiai: žymos pridėjimas atstato puslapio turinio srautą, kad žyma išgyventų išsaugojimą. Tai turėjo būti aišku, nes SaveAs pats neregeneruoja turinio — pakeitimas, gyvenęs tik objektų modelyje, būtų išmestas, o išsaugotas failas atrodytų lygiai kaip tas, su kuriuo pradėjote. Jei kada nors pridėjote ką nors į PDFium puslapį ir radote, kad išvesties nėra, tai dažniausiai yra priežastis

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;

Ką tai padaro ir ko nepadaro dokumentui

Vien žymos nepaverčia PDF pažymėtu. Atitinkamas pažymėtas dokumentas reikalauja struktūros medžio, kurio elementai rodo į šiuos MCID, /MarkInfo įrašo, skelbiančio dokumentą pažymėtu, ir vaidmenų vardų, reiškiančių tai, ką sako standartas. /P žymos užrašymas su MCID, į kurį nerodo joks struktūros elementas, duoda turinį, teigiantį, kad yra pažymėtas, ir struktūros medį, kuris apie jį niekada nepraso

Kur pažymėtas turinys iš tikrųjų atsiperka šiame lygyje, tai inspekcija ir taisymas: kurio puslapio objektai yra pažymėti auditas, artefaktų, kurie turėjo būti pažymėti kaip tokie, radimas ar MCID sutikdinimas su struktūros medžiu, kad rastų našlaičius. Dėl to darbo struktūros medžio pusės žr. PDF/UA struktūros medžio tikrinimo aprašymą, o dėl skaitymo patirties, kuriai galiausiai reikia žymų, — prieinamo PDF skaitytuvo kūrimo Delphi pastabas

PDFium Component duoda Delphi, C++Builder ir Lazarus programoms aukšto lygio VCL API per PDFium variklį, kuriame pažymėtas turinys, struktūros medžiai ir prieinamumo tikrinimas pasiekiami iš įprasto Pascal kodo — žr. PDFium Component produkto puslapį pilmam API paviršiui