Setiap karakter yang terlihat di dalam PDF membawa referensi ke font yang menggambarnya, dan PDFium Component membiarkan Anda mengikuti referensi itu kembali ke objek font-nya lalu membaca apa yang diketahuinya. Satuan aksesnya adalah karakter, bukan dokumen: Anda memilih sebuah karakter lewat indeksnya di dalam teks halaman lalu menanyakan nama family, base name, weight, sudut italic, dan apakah face di baliknya benar-benar dibawa di dalam file itu. Properti terakhir itulah yang sebenarnya diincar sebagian besar analisis, karena font yang tertanam ikut bepergian bersama dokumennya sementara font yang tidak tertanam hanyalah janji bahwa mesin pembacanya kebetulan memasang typeface yang sama
Component ini mengeksposnya lewat objek TPdf dan TPdfView yang sama yang Anda pakai untuk rendering dan ekstraksi teks. Tidak ada objek "tabel font" terpisah yang perlu dibuka. Begitu teks sebuah halaman selesai di-parsing, properti font-nya menggantung pada indeks karakter, dan Anda membacanya satu glyph pada satu waktu. Rancangan itu cocok dengan cara PDF menyimpan informasinya sejak awal: satu halaman bisa berganti font puluhan kali, dan satu-satunya jawaban jujur untuk "dokumen ini pakai font apa" adalah "tergantung karakter mana yang Anda maksud"
Membaca font di balik satu karakter
Operasi berguna yang terkecil adalah mengambil satu indeks karakter lalu menumpahkan semua yang bisa diberitahukan PDFium tentang font-nya. Setiap properti font pada TPdf dan TPdfView diindeks berdasarkan posisi karakter, sehingga indeksnya merajut semuanya. Halamannya juga harus menjadi halaman yang aktif supaya indeks itu terurai terhadap teks yang benar, dan hal itu mulai penting begitu Anda melewati halaman satu
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;
Beberapa signature-nya mengejutkan orang yang datang dari library lain. FontAscent dan FontDescent menerima dua argumen, indeks karakter dan sebuah ukuran poin, karena PDFium melaporkan metrik itu dalam satuan ruang glyph yang baru menjadi piksel setelah Anda menskalakannya dengan ukuran teks yang dipakai. Berikan nilai yang sudah Anda baca dari FontSize[CharIndex] dan Anda memperoleh ascent serta descent dalam poin yang sama dengan sisa tata letaknya. Descent kembali bernilai negatif, karena ia mengukur di bawah baseline. Nama family dan base name adalah dua string terpisah dengan sengaja: base name adalah entry /BaseFont mentah dari PDF-nya, sering membawa prefiks subset seperti ABCDEF+, sementara nama family adalah nama bersih yang menjadi hasil penguraian renderer-nya
Mengubah sebuah klik menjadi indeks karakter
Di dalam sebuah viewer, Anda jarang tahu indeksnya sejak awal. Pengguna mengklik sebuah glyph dan Anda harus menerjemahkan koordinat pikselnya menjadi karakter yang ada di bawahnya. CharacterIndexAtPos melakukan persis itu, menerima posisi mouse beserta sebuah toleransi lalu mengembalikan indeks karakter terdekat, atau nilai negatif ketika kliknya mendarat di ruang kosong atau di halaman yang kosong
procedure TfrmMain.PdfViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
begin
if not PdfView.Active then
Exit;
// Kelonggaran 4 px tiap arah agar klik yang nyaris meleset tetap kena glyph.
Index := PdfView.CharacterIndexAtPos(X, Y, 4.0, 4.0);
if Index < 0 then
Exit; // klik di antara glyph; biarkan panelnya
PdfView.CurrentCharIndex := Index;
DescribeFontAt(PdfView.Pdf, Index);
end;
Toleransinya layak disetel. Terlalu ketat dan pengguna merasa harus mendarat tepat di batang sebuah huruf; terlalu longgar dan sebuah klik di margin melompat ke karakter jauh yang sama sekali tidak mereka maksud. Tiga sampai lima piksel perangkat adalah titik awal yang masuk akal untuk penglihatan di layar. Indeks yang dikembalikan menunjuk ke dalam teks halaman aktif yang sudah di-parsing, ruang indeks yang sama seperti yang diharapkan setiap properti font, sehingga Anda bisa menyerahkannya langsung ke rutin di atas. Menyimpannya di CurrentCharIndex itu opsional namun praktis: view tersebut memeliharanya sebagai gagasannya tentang glyph yang sedang difokuskan, dan itu berguna jika bagian lain UI ingin membaca pilihannya tanpa menurunkannya ulang
Penanaman adalah properti yang penting
Untuk sebagian besar pekerjaan nyata, satu-satunya pertanyaan yang layak dijawab adalah apakah setiap font tertanam. Sebuah dokumen yang seluruh font-nya menumpang di dalamnya akan ter-render sama di RIP milik biro cetak, di laptop seorang kolega, dan di server yang sama sekali tanpa GUI. Dokumen yang bersandar pada Helvetica yang tidak tertanam sedang berjudi bahwa setiap mesin tadi punya face yang cocok, dan ketika judinya kalah, pembacanya menggantinya dengan sesuatu yang mirip, metriknya bergeser, dan sebuah formulir yang tertata rapi mengalir ulang tepat cukup untuk rusak. Menelusuri teks halaman lalu mengelompokkan font berdasarkan status penanamannya memberi Anda jawaban itu dengan murah
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; // spasi bentukan dan sejenisnya tidak berfont
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;
Dua detail menjaga ini tetap jujur. Pertama, CharacterCount berlaku per halaman, sehingga audit seluruh dokumen berarti menyetel Pdf.PageNumber ke setiap halaman secara bergiliran lalu menjalankan loop-nya lagi dan menggabungkan hasilnya. Kedua, lapisan teksnya memuat karakter bentukan seperti spasi yang disimpulkan pembacanya di antara kata, dan karakter itu tidak punya objek font di belakangnya; pemeriksaan base name kosong melewatinya alih-alih mencatat hantu. Base name adalah kunci yang tepat untuk deduplikasi di sini karena prefiks subset yang dibawanya membedakan dua subset berbeda dari family yang sama, dan biasanya itulah yang ingin Anda ketahui
Menarik keluar face yang tertanam
Ketika sebuah font tertanam, Anda bisa membaca byte-nya secara langsung. FontData mengembalikan program font mentahnya, data TrueType atau CFF yang sama yang dibawa PDF-nya, dan itu sudah cukup untuk menulis sebuah file font mandiri atau untuk mengambil sidik jari face-nya terhadap sebuah pustaka yang sudah dikenal. Ia mengembalikan array kosong ketika font-nya tidak tertanam, sehingga pemeriksaan penanaman dan pemeriksaan panjangnya bersama-sama menjaga penulisannya
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;
Byte-nya adalah subset yang tertanam, bukan font ritel aslinya, sehingga yang Anda peroleh biasanya hanya mencakup glyph yang benar-benar dipakai dokumen itu. Hal itu tepat sekali untuk forensik dan verifikasi namun buruk untuk dipakai ulang; subset Times New Roman yang berisi tiga puluh glyph bukanlah font yang bisa Anda pasang lalu pakai mengetik. Perlakukan ekstraksi sebagai cara memeriksa apa yang dikirimkan, bukan sebagai tool pemulihan font. Jika Anda butuh base name yang cocok untuk melabeli keluarannya, baca FontBaseName[CharIndex] berdampingan dengan datanya, lalu buang tag subset di depannya bila Anda menginginkan family-nya saja
Memahami angka weight
FontWeight mengembalikan kelas weight numeriknya, skala 100 sampai 900 yang sama seperti yang dipakai CSS, tempat 400 berarti regular dan 700 berarti bold. PDFium melaporkan apa pun yang dinyatakan font-nya, dan itu tidak selalu kelipatan seratus yang bulat; sebuah face bisa mengumumkan 350 atau 650, dan memperlakukan apa pun pada atau di atas 600 sebagai "cukup tebal untuk diperhitungkan" lebih tahan banting daripada menguji tepat 700. Sudut italic adalah sinyal pendampingnya: nilai bukan nol, biasanya negatif, berarti face-nya adalah desain oblique atau italic sejati, dan nol berarti tegak. Bersama-sama keduanya membiarkan Anda membedakan run bold-italic dari yang regular tanpa me-render apa pun, dan itulah jenis pemeriksaan yang ingin dilakukan secara massal oleh sebuah lintasan preflight atau audit aksesibilitas
Tak satu pun pembacaan ini membutuhkan bitmap yang sudah di-render. Semuanya berasal dari lapisan teks yang sudah di-parsing, jadi sebuah dokumen yang terbuka pada halaman yang tepat sudah menjadi seluruh penyiapan yang Anda perlukan, dan itu membuat pemeriksaan font murah untuk dijalankan pada seluruh arsip. Jika Anda memasangkannya dengan ekstraksi teks, indeks karakter yang sama berbaris dengan teks yang Anda tarik keluar, sehingga font sebuah glyph dan nilai Unicode-nya adalah dua kali pembacaan terhadap satu indeks. Artikel pendamping tentang ekstraksi teks dari dokumen PDF dengan PDFium Component membahas sisi lapisan teks itu lebih mendalam
Properti font yang ditampilkan di sini adalah bagian dari PDFium Delphi VCL Component