Vsak viden znak v PDF-ju vsebuje sklic na pisavo, s katero je bil izrisan, in PDFium VCL vam omogoča, da temu sklicu sledite nazaj do objekta pisave ter preberete, kaj ta ve. Enota za dostop je znak, ne dokument: znak izberete po njegovem indeksu v besedilu strani in zahtevate ime družine pisave, osnovno ime, težo, kot ležeče pisave (italic angle) in podatek o tem, ali je pisava dejansko vgrajena v datoteko. Ta zadnja lastnost je tista, ki jo večina analiz najbolj potrebuje, saji vgrajena pisava potuje z dokumentom, medtem ko je nevgrajena le obljuba, da ima bralčev računalnik nameščeno isto pisavo.
Komponenta te podatke izpostavlja prek istih objektov TPdf in TPdfView, ki ju uporabljate za izrisovanje in ekstrakcijo besedila. Ni posebnega objekta »tabela pisav« (font table), ki bi ga morali odpreti. Ko je besedilo strani razčlenjeno, so lastnosti pisave vezane na indeks znaka, vi pa jih berete za vsak glif posebej. Ta zasnova ustreza načinu, kako PDF sploh shranjuje informacije: ena sama stran lahko zamenja pisavo več desetkrat, edini iskren odgovor na vprašanje »katera pisava je v tem dokumentu« pa je »odvisno od tega, kateri znak imate v mislih«.
Branje pisave za posameznim znakom
Najmanjša uporabna operacija je, da vzamete indeks znaka in izpišete vse, kar vam PDFium lahko pove o njegovi pisavi. Vsaka lastnost pisave v TPdf in TPdfView je indeksirana s položajem znaka, zato se indeks povezuje skozi vse te lastnosti. Stran mora biti prav tako nastavljena kot trenutna stran, da se indeks pravilno razreši glede na ustrezno besedilo, kar je pomembno, ko se pomaknete dlje od prve strani.
procedure DescribeFontAt(Pdf: TPdf; CharIndex: Integer);
var
Report: TStringList;
PtSize: Single;
begin
Report := TStringList.Create;
try
PtSize := Pdf.FontSize[CharIndex];
Report.Add('Character : ' + Pdf.Character[CharIndex]);
Report.Add('Family : ' + Pdf.FontFamilyName[CharIndex]);
Report.Add('Base name : ' + Pdf.FontBaseName[CharIndex]);
Report.Add('Weight : ' + IntToStr(Pdf.FontWeight[CharIndex]));
Report.Add('Italic : ' + IntToStr(Pdf.FontItalicAngle[CharIndex]) + ' deg');
Report.Add('Size : ' + FormatFloat('0.0', PtSize) + ' pt');
Report.Add('Ascent : ' + FormatFloat('0.0', Pdf.FontAscent[CharIndex, PtSize]));
Report.Add('Descent : ' + FormatFloat('0.0', Pdf.FontDescent[CharIndex, PtSize]));
Report.Add('Embedded : ' + BoolToStr(Pdf.FontIsEmbedded[CharIndex], True));
ShowMessage(Report.Text);
finally
Report.Free;
end;
end;
Nekaj podpisov metod (signatures) lahko preseneti tiste, ki prihajajo iz drugih knjižnic. FontAscent in FontDescent sprejmeta dva argumenta, indeks znaka in velikost v točkah (point size), saj PDFium te metrike sporoča v enotah prostora glifov, ki postanejo slikovne pike šele, ko jih prilagodite glede na velikost, pri kateri je bilo besedilo nastavljeno. Posredujte vrednost, ki ste jo že prebrali iz FontSize[CharIndex], in dobili boste zgornjo (ascent) in spodnjo (descent) linijo v enakih točkah kot preostala postavitev. Spodnja linija (descent) se vrne kot negativna vrednost, saj meri pod izhodiščno črto (baseline). Ime družine in osnovno ime sta namerno ločena niza: osnovno ime je surovi vnos /BaseFont iz PDF-ja, ki pogosto vsebuje predpono podnabora (subset prefix), kot je ABCDEF+, medtem ko je ime družine očiščeno ime, v katerega ga razreši izrisovalnik.
Pretvorba klika v indeks znaka
V bralniku redko vnaprej poznate indeks znaka. Uporabnik klikne na glif, vi pa morate slikovno koordinato pretvoriti v znak pod njo. Metoda CharacterIndexAtPos naredi natanko to: sprejme položaj miške ter toleranco in vrne indeks najbližjega znaka ali pa negativno vrednost, če je uporabnik kliknil na prazno površino ali prazno stran.
procedure TfrmMain.PdfViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
begin
if not PdfView.Active then
Exit;
// 4 px of slack in each direction so a near-miss still hits the glyph.
Index := PdfView.CharacterIndexAtPos(X, Y, 4.0, 4.0);
if Index < 0 then
Exit; // clicked between glyphs; leave the panel alone
PdfView.CurrentCharIndex := Index;
DescribeFontAt(PdfView.Pdf, Index);
end;
Toleranco je vredno natančno nastaviti. Če je pretesna, imajo uporabniki občutek, da morajo zadeti točno določeno linijo črke; če je preohlapna, pa se klik na robu strani pripne na nek oddaljen znak, ki nima nobene zveze s tem, kar so želeli. Tri do pet slikovnih pik na napravi je razumno izhodišče za ogled na zaslonu. Vrnjeni indeks se nanaša na razčlenjeno besedilo trenutne strani – to je isti indeksni prostor, ki ga pričakuje vsaka lastnost pisave, zato ga lahko neposredno posredujete zgornji rutini. Shranjevanje v CurrentCharIndex je neobvezno, a priročno: pogled to ohranja kot svojo predstavo o fokusiranem glifu, kar je koristno, če želijo drugi deli uporabniškega vmesnika prebrati izbiro brez ponovnega izračunavanja.
Vgrajevanje je lastnost, ki je zares pomembna
Za večino resnega dela je edino pomembno vprašanje, ali je posamezna pisava vgrajena. Dokument, katerega vse pisave so vgrajene vanj, se izriše enako na tiskarskih napravah (RIP), na prenosniku sodelavca in na strežniku brez grafičnega vmesnika. Dokument, ki se zanaša na nevgrajeno pisavo Helvetica, tvega, da ima vsaka od teh naprav ujemajočo se pisavo; ko pa se to tveganje ne obnese, bralnik uporabi nekaj podobnega, metrike se spremenijo in natančno oblikovan obrazec se preoblikuje ravno toliko, da se pokvari. Sprehod po besedilu strani in razvrščanje pisav glede na status vgrajenosti vam hitro ponudi ta odgovor.
procedure ReportNonEmbeddedFonts(Pdf: TPdf);
var
Embedded, External: TStringList;
I: Integer;
Name: string;
begin
Embedded := TStringList.Create;
External := TStringList.Create;
try
Embedded.Sorted := True;
Embedded.Duplicates := dupIgnore;
External.Sorted := True;
External.Duplicates := dupIgnore;
for I := 0 to Pdf.CharacterCount - 1 do
begin
Name := Pdf.FontBaseName[I];
if Name = '' then
Continue; // generated spaces and the like have no font
if Pdf.FontIsEmbedded[I] then
Embedded.Add(Name)
else
External.Add(Name);
end;
if External.Count > 0 then
ShowMessage(IntToStr(External.Count) +
' non-embedded font(s):' + sLineBreak + External.Text)
else
ShowMessage('All ' + IntToStr(Embedded.Count) +
' font(s) on this page are embedded.');
finally
Embedded.Free;
External.Free;
end;
end;
Dve podrobnosti skrbita za natančnost. Prvič, CharacterCount velja na posamezno stran, zato celostna revizija dokumenta pomeni zaporedno nastavljanje Pdf.PageNumber na vsako stran in ponoven zagon zanke ob združevanju rezultatov. Drugič, plast besedila vsebuje generirane znake, kot so presledki, ki jih bralnik predvideva med besedami, ti pa nimajo objekta pisave; preverjanje praznega osnovnega imena jih preskoči, namesto da bi zabeležilo fantomski vnos. Osnovno ime je tukaj pravi ključ za preprečevanje podvajanja, saj predpona podnabora, ki jo vsebuje, ločuje dva različna podnabora iste družine, relativno pogosto tisto, kar želite vedeti.
Pridobivanje vgrajene pisave
Ko je pisava vgrajena, lahko neposredno preberete njene bajte. Lastnost FontData vrne surovi program pisave – iste podatke TrueType ali CFF, ki jih vsebuje PDF – kar zadošča za zapis samostojne datoteke pisave ali za primerjavo pisave z znano knjižnico. Vrne prazno polje, ko pisava ni vgrajena, zato preverjanje vgrajenosti in preverjanje dolžine skupaj varujeta zapis.
procedure SaveEmbeddedFont(Pdf: TPdf; CharIndex: Integer;
const OutputFile: string);
var
Data: TBytes;
Stream: TFileStream;
begin
if not Pdf.FontIsEmbedded[CharIndex] then
begin
ShowMessage('That glyph''s font is not embedded; nothing to extract.');
Exit;
end;
Data := Pdf.FontData[CharIndex];
if Length(Data) = 0 then
Exit;
Stream := TFileStream.Create(OutputFile, fmCreate);
try
Stream.WriteBuffer(Data[0], Length(Data));
finally
Stream.Free;
end;
ShowMessage('Wrote ' + IntToStr(Length(Data)) + ' bytes.');
end;
Bajte predstavljajo le vgrajeni podnabor, ne pa celotne izvirne pisave, zato vrnjeni podatki običajno pokrivajo le glife, ki so bili dejansko uporabljeni v dokumentu. To je popolnoma primerno za forenziko in preverjanje, ne pa za ponovno uporabo; podnabor pisave Times New Roman, ki vsebuje le trideset glifov, ni pisava, ki bi jo lahko namestili in z njo tipkali. Ekstrakcijo obravnavajte kot način za preverjanje poslanega gradiva, ne pa kot orodje za obnovitev pisave. Če za poimenovanje izhoda potrebujete ujemajoče se osnovno ime, poleg podatkov preberite še FontBaseName[CharIndex] in odstranite vodilno oznako podnabora, če želite le čisto ime družine.
Razumevanje vrednosti teže pisave
FontWeight vrne številčni razred teže pisave – isto lestvico od 100 do 900, kot jo uporablja CSS, kjer je 400 običajna (regular), 700 pa krepka (bold) pisava. PDFium poroča o tistem, kar pisava sama deklarira, kar ni vedno okrogla stotica; pisava lahko sporoči 350 ali 650, pri čemer se obravnavanje česar koli nad 600 kot »dovolj krepko« obnese bolje kot preverjanje natanko vrednosti 700. Kot ležeče pisave (italic angle) je spremljevalni signal: nenična vrednost, običajno negativna, pomeni, da gre za poševno ali pravo ležečo pisavo, nič pa pomeni pokončno pisavo. Skupaj vam omogočata, da ločite krepko-ležeči del besedila od običajnega, ne da bi izrisali karkoli, kar je ravno takšno preverjanje, kot ga želijo preverjanja pred pošiljanjem (preflight pass) or revizije dostopnosti izvajati v velikem obsegu.
Nobeno od teh branj ne zahteva izrisane bitne slike. Podatki prihajajo iz razčlenjene plasti besedila, zato je odprt dokument na pravi strani edina priprava, ki jo potrebujete, kar omogoča poceni izvajanje pregledovanja pisav v celotnem arhivu. Če to združujete z ekstrakcijo besedila, se isti indeksi znakov ujemajo z besedilom, ki ga pridobite, zato sta pisava glifa in njegova Unicode vrednost dva branja na istem indeksu. Spremljajoči članek o ekstrakciji besedila iz PDF dokumentov s PDFium VCL podrobneje obravnava to stran plasti besedila.
Lastnosti pisave, prikazane tukaj, so del komponente PDFium Delphi VCL Component.