Každý viditeľný znak v PDF nesie odkaz na písmo, ktoré ho vykreslilo, a PDFium VCL vám umožňuje tento odkaz sledovať späť k objektu písma a prečítať to, čo o sebe vie. Jednotkou prístupu je znak, nie dokument: vyberiete si znak podľa jeho indexu v texte stránky a zistíte názov rodiny písiem, základný názov, hrúbku, uhol sklonu kurzívy a to, či je samotný font skutočne prenášaný vo vnútri súboru. Práve táto posledná vlastnosť je tým, o čo pri väčšine analýz naozaj ide. Vložené (embedded) písmo totiž cestuje s dokumentom, kým nevložené písmo je iba prísľubom, že počítač čitateľa má náhodou nainštalovaný rovnaký typ písma.
Komponent tieto vlastnosti sprístupňuje prostredníctvom rovnakých objektov TPdf a TPdfView, ktoré používate na vykresľovanie a extrakciu textu. Neotvára sa žiadny samostatný objekt s „tabuľkou písiem“. Po analýze textu stránky sú vlastnosti písma naviazané na index znaku a vy ich čítate po jednotlivých glyfoch. Tento návrh zodpovedá spôsobu, akým PDF tieto informácie ukladá: jedna stránka môže zmeniť písmo desiatky ráz a jedinou pravdivou odpoveďou na otázku „v akom písme je tento dokument“ je „záleží na tom, ktorý znak máte na mysli“.
Čítanie písma za jedným znakom
Najmenšou užitočnou operáciou je vziať index znaku a vypísať všetko, čo vám PDFium dokáže o jeho písme povedať. Každá vlastnosť písma v triedach TPdf a TPdfView je indexovaná podľa pozície znaku, takže index prechádza všetkými týmito vlastnosťami. Stránka musí byť zároveň nastavená ako aktuálna stránka, aby sa index vyriešil voči správnemu textu, čo je dôležité najmä vtedy, keď sa presuniete za prvú stránku.
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;
Niektoré deklarácie prekvapia vývojárov prichádzajúcich z iných knižníc. FontAscent a FontDescent berú dva parametre, index znaku a veľkosť v bodoch (point size), pretože PDFium hlási tieto metriky v jednotkách priestoru glyfov, ktoré sa stanú pixelmi až po ich prepočítaní veľkosťou, na ktorú bol text nastavený. Odovzdajte hodnotu, ktorú ste už prečítali z FontSize[CharIndex], a získate ascent (výšku horného preťahu) a descent (hĺbku dolného preťahu) v rovnakých bodoch ako zvyšok rozloženia. Descent sa vracia ako záporná hodnota, pretože sa meria pod účaří (baseline). Názov rodiny písiem a základný názov sú samostatné reťazce zámerne: základný názov je surový záznam /BaseFont z PDF, ktorý často nesie prefix podskupiny ako napríklad ABCDEF+, zatiaľ čo názov rodiny je vyčistený názov, na ktorý ho vykresľovacie jadro nakoniec preloží.
Preklad kliknutia na index znaku
V prehliadači zriedkakedy poznáte index znaku vopred. Používateľ klikne na glyf a vy musíte preložiť pixelovú súradnicu na znak pod ňou. Metóda CharacterIndexAtPos robí presne to: berie pozíciu myši a toleranciu a vracia index najbližšieho znaku alebo zápornú hodnotu, ak kliknutie dopadlo na biele miesto alebo prázdnu časť stránky.
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;
Toleranciu je dobré vyladiť. Ak je príliš tesná, používatelia majú pocit, že musia kliknúť na presnú líniu písmena; ak je príliš voľná, kliknutie na okraj sa prichytí k vzdialenému znaku, ktorý nemá nič spoločné s tým, čo chceli. Tri až päť hardvérových pixelov je rozumným východiskovým bodom pre prezeranie na obrazovke. Vrátený index smeruje do analyzovaného textu aktuálnej stránky, čo je rovnaký indexový priestor, aký očakáva každá vlastnosť písma, takže ho môžete odovzdáť priamo do vyššie uvedenej rutiny. Uloženie do CurrentCharIndex je voliteľné, ale pohodlné: zobrazenie si ho uchováva ako svoju predstavu o zameranom glyfe, čo je praktické, ak iné časti používateľského rozhrania chcú čítať výber bez opätovného odvodzovania.
Vloženie písma je kľúčová vlastnosť
Pri väčšine reálnych úloh je jedinou otázkou, na ktorú stojí za to odpovedať, či je každé písmo vložené. Dokument, ktorého všetky písma sa nachádzajú priamo v ňom, sa vykreslí rovnako na tlačiarenskom RIPe, na notebooku kolegu aj na serveri úplne bez grafického rozhrania. Dokument, ktorý sa spolieha na nevložený font Helvetica, riskuje a vsádza na to, že každý z týchto strojov má nainštalované zhodné písmo. Ak táto stávka nevyjde, čítačka nahradí písmo niečím podobným, čím sa posunú metriky a starostlivo navrhnuté rozloženie formulára sa zmení práve toľko, aby sa rozsypalo. Prejdenie textu stránky a roztriedenie písiem podľa stavu vloženia vám poskytne túto odpoveď rýchlo a efektívne.
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;
Dva detaily udržiavajú tento prístup presný. Po prvé, CharacterCount sa vzťahuje na konkrétnu stránku, takže audit celého dokumentu znamená postupné prepínanie Pdf.PageNumber na každú stránku a opätovné spustenie cyklu s následným zlúčením výsledkov. Po druhé, textová vrstva obsahuje generované znaky, ako sú medzery, ktoré čítačka predpokladá medzi slovami, a tie nemajú priradený žiadny objekt písma; kontrola prázdneho základného názvu ich preskočí namiesto zaznamenávania fantómových položiek. Základný názov je tu správnym kľúčom na odstránenie duplicít, pretože prefix podskupiny, ktorý nesie, odlišuje dve rôzne podskupiny rovnakej rodiny písiem, čo je zvyčajne to, čo potrebujete vedieť.
Extrahovanie vloženého písma
Keď je písmo vložené, môžete priamo čítať jeho bajty. Vlastnosť FontData vracia surový program písma, teda rovnaké dáta TrueType alebo CFF, aké prenáša PDF, čo stačí na zápis samostatného súboru písma alebo na overenie vzorky písma voči známej knižnici. Ak písmo nie je vložené, vracia prázdne pole, takže kontrola vloženia a dĺžky dát spoločne chránia zápis.
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;
Získané bajty predstavujú vloženú podskupinu znakov, nie pôvodné komerčné písmo, takže to, čo dostanete späť, zvyčajne pokrýva iba tie glyfy, ktoré dokument skutočne použil. To je presne to, čo potrebujete na forenznú analýzu a verifikáciu, ale nehodí sa to na opätovné použitie; podskupina písma Times New Roman, ktorá obsahuje tridsať glyfov, nie je písmom, ktoré by ste si mohli nainštalovať a písať s ním. Extrakciu vnímajte ako spôsob kontroly toho, čo bolo s dokumentom dodané, nie ako nástroj na obnovu plnohodnotných písiem. Ak potrebujete zodpovedajúci základný názov na označenie výstupu, prečítajte si vlastnosť FontBaseName[CharIndex] spolu s dátami a v prípade potreby odstráňte prefix podskupiny, ak chcete čistý názov rodiny.
Porozumenie hodnote hrúbky písma
Metóda FontWeight vracia číselnú triedu hrúbky písma, čo je rovnaká stupnica od 100 do 900, akú používa CSS, kde 400 znamená bežné písmo (regular) a 700 je tučné (bold). PDFium hlási to, čo písmo deklaruje, čo nemusí byť vždy zaokrúhlená stovka. Písmo môže deklarovať hodnotu 350 alebo 650, pričom posudzovanie akejkoľvek hodnoty od 600 vyššie ako „dostatočne tučnej“ funguje v praxi lepšie než testovanie presnej hodnoty 700. Uhol sklonu kurzívy (italic angle) je sprievodným signálom: nenulová hodnota, zvyčajne záporná, znamená, že písmo je šikmé alebo skutočná kurzíva, zatiaľ čo nula znamená vzpriamené písmo. Spoločne vám umožňujú odlíšiť tučnú kurzívu od bežného textu bez potreby akéhokoľvek vykresľovania, čo je presne typ kontroly, akú chce hromadne vykonávať predbežná kontrola (preflight) alebo audit prístupnosti.
Žiadne z týchto čítaní nevyžaduje vykreslenú bitmapu. Pochádzajú z analyzovanej textovej vrstvy, takže otvorený dokument na správnej stránke je celým nastavením, ktoré potrebujete, vďaka čomu je kontrola písiem lacná na prevádzku naprieč celým archívom. Ak to kombinujete s extrakciou textu, rovnaké indexy znakov sa zhodujú s textom, ktorý vyťahujete, takže font glyfu a jeho hodnota v Unicode sú dvoma čítaniami voči jednému indexu. Sprievodný článok o extrahovaní textu z PDF dokumentov pomocou PDFium VCL sa tejto téme textovej vrstvy venuje podrobnejšie.
Vlastnosti písiem uvedené v tomto článku sú súčasťou komponentu PDFium Delphi VCL Component.