PDF内のすべての可視文字には、それを描画したフォントへの参照が含まれており、PDFium Componentを使用すると、その参照をフォントオブジェクトまでたどり、その情報を読み取ることができます。アクセスの単位はドキュメントではなく文字です。ページテキスト内のインデックスで文字を選択し、ファミリ名、ベース名、太さ、斜体の角度、および基になるフォントフェイスが実際にファイル内に保持されているかどうかを尋ねます。この最後のプロパティは、ほとんどの分析で本当に求められているものです。なぜなら、埋め込まれたフォントはドキュメントと一緒に移動しますが、埋め込まれていないフォントは、リーダーのマシンに偶然同じタイプフェイスがインストールされているという約束にすぎないからです
コンポーネントは、レンダリングやテキスト抽出に使用するのと同じ TPdf および TPdfView オブジェクトを介してこれらを公開します。開くための個別の「フォントテーブル」オブジェクトはありません。ページのテキストが解析されると、フォントプロパティは文字インデックスにぶら下がり、1グリフずつ読み取ります。この設計は、PDFがそもそも情報を保存する方法に適合しています。1つのページでフォントが何十回も切り替わる可能性があり、「このドキュメントはどのフォントですか」に対する唯一の正直な答えは、「どの文字を意味するかによる」です
1つの文字の背後にあるフォントを読み取る
最も小さな有用な操作は、文字インデックスを取得し、PDFiumがそのフォントについて教えられるすべてをダンプすることです。TPdf および TPdfView のすべてのフォントプロパティは文字位置によってインデックス付けされているため、インデックスはそれらすべてを貫通しています。また、インデックスが正しいテキストに対して解決されるようにするには、ページが現在のページである必要があります。これは、1ページ目を過ぎると重要になります
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;
シグネチャのいくつかは、他のライブラリから来た人々を驚かせます。FontAscent と FontDescent は、文字インデックスとポイントサイズの2つの引数を取ります。これは、PDFiumがこれらのメトリックをグリフ空間単位で報告し、テキストが設定されたサイズでスケーリングした場合にのみピクセルになるためです。すでに FontSize[CharIndex] から読み取った値を渡すと、残りのレイアウトと同じポイント数でアセントとディセントが得られます。ディセントはベースラインの下を測定するため、負の値で返されます。ファミリ名とベース名が意図的に別々の文字列になっています。ベース名はPDFからの生の /BaseFont エントリであり、多くの場合 ABCDEF+ のようなサブセットプレフィックスを伴いますが、ファミリ名はレンダラーが解決するクリーンアップされた名前です
クリックを文字インデックスに変換する
ビューアでは、インデックスを事前に知っていることはめったにありません。ユーザーがグリフをクリックすると、ピクセル座標をその下の文字に変換する必要があります。CharacterIndexAtPos はまさにそれを行い、マウス位置と許容範囲を受け取り、最も近い文字のインデックスを返すか、クリックが空白や空のページに着地した場合は負の値を返します
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;
許容範囲は調整する価値があります。きつすぎると、ユーザーは文字の正確なステムに着地しなければならないように感じます。緩すぎると、マージンでのクリックが、意図したものとは関係のない遠く離れた文字にスナップしてしまいます。画面での表示には、3〜5デバイスピクセルが妥当な開始点です。返されるインデックスは、現在のページの解析されたテキストへのインデックスであり、すべてのフォントプロパティが期待するのと同じインデックス空間であるため、上記のルーチンに直接渡すことができます。それを CurrentCharIndex に保存することはオプションですが便利です。ビューはそれをフォーカスされたグリフの概念として保持します。これは、UIの他の部分が選択を再導出せずに読み取りたい場合に便利です
埋め込みが重要なプロパティです
ほとんどの実際の作業において、答える価値のある唯一の質問は、各フォントが埋め込まれているかどうかです。すべてのフォントが内部に搭載されているドキュメントは、印刷局のRIP、同僚のラップトップ、およびGUIがまったくないサーバーで同じようにレンダリングされます。埋め込まれていない Helvetica に依存するドキュメントは、それらのすべてのマシンに一致するフォントフェイスがあることに賭けており、その賭けが失敗すると、リーダーは近いものを代用し、メトリックがずれ、慎重にレイアウトされたフォームが壊れる程度にリフローします。ページテキストをたどり、埋め込みステータスごとにフォントを分類することで、その答えを安価に得ることができます
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;
2つの詳細がこれを正確に保ちます。第一に、CharacterCount はページごとの値であるため、ドキュメント全体の監査は、Pdf.PageNumber を各ページに順番に設定し、ループを再度実行して結果をマージすることを意味します。第二に、テキストレイヤーには、読者が単語間に推測する空白などの生成された文字が含まれており、それらの背後にはフォントオブジェクトがありません。空のベース名チェックは、ファントムを記録するのではなく、それらをスキップします。ここではベース名が重複排除のための正しいキーです。なぜなら、それが持つサブセットプレフィックスは同じファミリの2つの異なるサブセットを区別するからです。これは通常知りたいことです
埋め込まれたフェイスを引き出す
フォントが埋め込まれている場合、そのバイトを直接読み取ることができます。FontData は、生のフォントプログラム、つまりPDFが保持しているのと同じTrueTypeまたはCFFデータを返します。これは、スタンドアロンのフォントファイルを書き込んだり、既知のライブラリに対してフォントフェイスのフィンガープリントを作成したりするのに十分です。フォントが埋め込まれていない場合は空の配列を返すため、埋め込みチェックと長さチェックの両方で書き込みを保護します
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;
バイトは元の市販フォントではなく、埋め込まれたサブセットであるため、通常得られるのはドキュメントで実際に使用されたグリフのみです。これは、フォレンジックや検証にはまさに適していますが、再利用には適していません。30個のグリフを含む Times New Roman のサブセットは、インストールして入力できるフォントではありません。抽出は、出荷されたものを検査する方法として扱い、フォント復元ツールとしては扱わないでください。出力をラベル付けするために一致するベース名が必要な場合は、データと一緒に FontBaseName[CharIndex] を読み取り、プレーンなファミリ名が必要な場合は先頭のサブセットタグを削除します
ウェイト番号を理解する
FontWeight は、CSSが使用するのと同じ100から900のスケールで数値のウェイトクラスを返します。ここで、400はレギュラー、700はボールドです。PDFiumはフォントが宣言しているものをすべて報告しますが、常にキリの良い百とは限りません。フォントフェイスが350や650を宣言することもあり、600以上を「重要とみなすのに十分な太さ」として扱う方が、正確に700かどうかをテストするよりも優れています。斜体の角度は付随するシグナルです。通常は負のゼロ以外の値は、フォントフェイスがオブリークまたは真のイタリックデザインであることを意味し、ゼロは直立であることを意味します。これらを組み合わせることで、何もレンダリングすることなくボールドイタリックのランをレギュラーのランから区別することができます。これは、プリフライトパスやアクセシビリティ監査が大量に実行したい種類のチェックです
これらの読み取りはどれもレンダリングされたビットマップを必要としません。これらは解析されたテキストレイヤーから取得されるため、正しいページでドキュメントを開くことだけが必要な設定であり、これにより、アーカイブ全体にわたってフォント検査を低コストで実行できます。これをテキスト抽出と組み合わせる場合、同じ文字インデックスが抽出したテキストと一致するため、グリフのフォントとそのUnicode値は1つのインデックスに対する2回の読み取りになります。関連する記事 PDFium Componentを使用してPDFドキュメントからテキストを抽出する では、テキストレイヤーのその側面についてさらに詳しく説明しています
ここで紹介するフォントプロパティは、PDFium Delphi VCL Component の一部です