Każdy widoczny znak w dokumencie PDF zawiera odwołanie do czcionki, która go narysowała, a komponent PDFium pozwala podążać za tym odwołaniem do obiektu czcionki i odczytywać to, co o nim wie. Jednostką dostępu jest znak, a nie dokument: wybierasz znak na podstawie jego indeksu w tekście strony i pytasz o nazwę rodziny, nazwę bazową, grubość, kąt pochylenia oraz o to, czy podstawowy krój pisma jest faktycznie zawarty w pliku. Ta ostatnia właściwość to coś, czego w rzeczywistości poszukuje większość analiz, ponieważ osadzona czcionka podróżuje wraz z dokumentem, a nieosadzona to tylko obietnica, że na komputerze czytelnika zainstalowany jest ten sam krój pisma
Komponent udostępnia je poprzez te same obiekty TPdf i TPdfView, których używasz do renderowania i ekstrakcji tekstu. Nie ma oddzielnego obiektu "tabeli czcionek" do otwarcia. Po przeanalizowaniu tekstu strony właściwości czcionki są powiązane z indeksem znaku, a ty odczytujesz je jeden glif na raz. Taki projekt odpowiada sposobowi, w jaki PDF przechowuje informacje w pierwszej kolejności: pojedyncza strona może zmieniać czcionki dziesiątki razy, a jedyna uczciwa odpowiedź na pytanie "jaką czcionką jest napisany ten dokument", to "zależy, który znak masz na myśli"
Odczytywanie czcionki za jednym znakiem
Najmniejszą użyteczną operacją jest pobranie indeksu znaku i zrzucenie wszystkiego, co PDFium może ci powiedzieć o jego czcionce. Każda właściwość czcionki w TPdf i TPdfView jest indeksowana według pozycji znaku, więc indeks przewija się przez nie wszystkie. Strona musi być również stroną bieżącą, aby indeks odnosił się do właściwego tekstu, co ma znaczenie, gdy przejdziesz dalej niż pierwsza strona
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;
Kilka sygnatur zaskakuje ludzi przechodzących z innych bibliotek. FontAscent i FontDescent przyjmują dwa argumenty, indeks znaku i rozmiar punktowy, ponieważ PDFium zgłasza te metryki w jednostkach przestrzeni glifów, które stają się pikselami dopiero po przeskalowaniu ich przez rozmiar, w jakim ustawiono tekst. Przekaż wartość, którą już odczytałeś z FontSize[CharIndex], a otrzymasz wydłużenie górne (ascent) i dolne (descent) w tych samych punktach co reszta układu. Wydłużenie dolne wraca ujemne, ponieważ mierzy się je poniżej linii bazowej. Nazwa rodziny i nazwa bazowa to celowo oddzielne ciągi znaków: nazwa bazowa to surowy wpis /BaseFont z pliku PDF, często zawierający prefiks podzbioru, taki jak ABCDEF+, podczas gdy nazwa rodziny to oczyszczona nazwa, na którą renderer ją rozwiązuje
Zamiana kliknięcia w indeks znaku
W przeglądarce rzadko znasz indeks z góry. Użytkownik klika glif, a ty musisz przetłumaczyć współrzędną piksela na znak znajdujący się pod nim. CharacterIndexAtPos robi dokładnie to, biorąc pozycję myszy oraz tolerancję i zwracając indeks najbliższego znaku lub wartość ujemną, gdy kliknięcie trafiło na biały znak lub pustą stronę
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;
Warto dostosować tolerancję. Zbyt wąska sprawia, że użytkownicy mają wrażenie, iż muszą trafić w dokładny rdzeń litery; zbyt luźna, a kliknięcie na marginesie przyciąga do jakiegoś odległego znaku, który nie ma nic wspólnego z tym, o co im chodziło. Od trzech do pięciu pikseli urządzenia to rozsądny punkt wyjścia do oglądania na ekranie. Zwrócony indeks odnosi się do przetworzonego tekstu bieżącej strony, tej samej przestrzeni indeksów, jakiej oczekuje każda właściwość czcionki, więc możesz przekazać go prosto do powyższej procedury. Przechowywanie go w CurrentCharIndex jest opcjonalne, ale wygodne: widok zachowuje go jako swoje pojęcie zogniskowanego glifu, co jest przydatne, jeśli inne części interfejsu użytkownika chcą odczytać zaznaczenie bez ponownego jego wyliczania
Osadzanie to właściwość, która ma znaczenie
W przypadku większości prawdziwych prac, jedynym pytaniem wartym odpowiedzi jest to, czy każda czcionka jest osadzona. Dokument, którego wszystkie czcionki są w nim zawarte, renderuje się tak samo na RIP-ie biura druku, laptopie kolegi i serwerze bez GUI w ogóle. Dokument, który opiera się na nieosadzonej czcionce Helvetica, gra w ciemno, że każda z tych maszyn ma pasujący krój, a kiedy ta próba się nie powiedzie, czytnik podstawia coś zbliżonego, metryki się przesuwają, a starannie ułożony formularz przestawia się na tyle, by ulec zniszczeniu. Przejście przez tekst strony i pogrupowanie czcionek według statusu osadzenia daje tę odpowiedź niewielkim kosztem
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;
Dwa szczegóły sprawiają, że to rozwiązanie jest uczciwe. Po pierwsze, CharacterCount przypada na stronę, więc audyt całego dokumentu oznacza ustawienie Pdf.PageNumber na każdą stronę po kolei, ponowne uruchomienie pętli i scalenie wyników. Po drugie, warstwa tekstu zawiera wygenerowane znaki, takie jak spacje, które czytnik wywnioskowuje między słowami, i za nimi nie kryje się żaden obiekt czcionki; sprawdzenie puste nazwy bazowej pomija je, zamiast logować jako fantom. Nazwa bazowa jest tu właściwym kluczem do deduplikacji, ponieważ prefiks podzbioru, który przenosi, odróżnia dwa różne podzbiory tej samej rodziny, a to zwykle chcesz wiedzieć
Wyciąganie osadzonego kroju
Gdy czcionka jest osadzona, możesz odczytać jej bajty bezpośrednio. FontData zwraca surowy program czcionki, te same dane TrueType lub CFF, które przenosi PDF, co wystarcza do napisania samodzielnego pliku czcionki lub do identyfikacji kroju na podstawie znanej biblioteki. Zwraca pustą tablicę, gdy czcionka nie jest osadzona, więc sprawdzenie osadzenia i sprawdzenie długości razem chronią operację zapisu
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;
Bajty to osadzony podzbiór, a nie oryginalna czcionka detaliczna, więc to, co otrzymujesz z powrotem, zwykle obejmuje tylko te glify, których dokument faktycznie użył. Jest to dokładnie to, czego potrzeba do celów kryminalistycznych i weryfikacji, ale słabo nadaje się do ponownego wykorzystania; podzbiór czcionki Times New Roman zawierający trzydzieści glifów nie jest czcionką, którą można zainstalować i nią pisać. Traktuj wyodrębnianie jako sposób na zbadanie tego, co zostało dostarczone, a nie jako narzędzie do odzyskiwania czcionek. Jeśli potrzebujesz pasującej nazwy bazowej do etykietowania wyjścia, odczytaj FontBaseName[CharIndex] obok danych, i usuń wiodący znacznik podzbioru, jeśli chcesz otrzymać samą rodzinę
Zrozumienie liczby określającej grubość
FontWeight zwraca liczbową klasę grubości, tę samą skalę od 100 do 900, której używa CSS, gdzie 400 to normalna czcionka (regular), a 700 to pogrubiona (bold). PDFium zgłasza wszystko, co deklaruje czcionka, a to nie zawsze jest okrągła setka; krój może komunikować 350 lub 650, a traktowanie wszystkiego od 600 wzwyż jako "na tyle pogrubione, że ma to znaczenie" sprawdza się lepiej niż testowanie na dokładnie 700. Kąt pochylenia jest sygnałem towarzyszącym: niezerowa wartość, zwykle ujemna, oznacza, że krój ma pochyły lub prawdziwie pochylony wygląd (italic), a zero oznacza projekt pionowy (upright). Razem pozwalają odróżnić fragment pogrubiony z kursywą od zwykłego bez konieczności renderowania czegokolwiek, co jest rodzajem sprawdzania, jakie przeprowadzają wsadowo narzędzia typu preflight lub audyty dostępności
Żaden z tych odczytów nie wymaga wyrenderowanej mapy bitowej. Pochodzą one z przeanalizowanej warstwy tekstu, więc otwarty dokument na odpowiedniej stronie to cała konfiguracja, jakiej potrzebujesz, co sprawia, że inspekcja czcionek jest tania w wykonaniu dla całego archiwum. Jeśli łączysz to z wyodrębnianiem tekstu, te same indeksy znaków pasują do tekstu, który pobierasz, więc czcionka glifu i jego wartość Unicode to dwa odczyty dla jednego indeksu. Towarzyszący artykuł na temat ekstrakcji tekstu z dokumentów PDF za pomocą komponentu PDFium obejmuje tę stronę warstwy tekstu bardziej szczegółowo
Właściwości czcionek przedstawione tutaj są częścią Komponentu PDFium Delphi VCL