前回は、PDFドキュメントでテキストがどのようにエンコーディングされているかを説明した。今回からは、その仕組みに対応するソースコードを書いていこう。

まずは、フォント情報の取得からだ。

Tfオペレータに対するコールバックの追加

この連載で説明しているサンプルでは、テキストの抽出はPDFViewControllerクラスで行っていた。これを拡張していこう。

前にも説明したが、PDFデータはストリームとして与えられる。テキストとエンコーディングがどのような関係でストリームに登場するか見てみると、まずフォント情報に内包される形でエンコーディングが指定される。続いて、テキストデータが登場する、という流れだ。ということはエンコーディングを利用するには、フォント情報をピックアップすることと、得られたエンコーディングを保持しておく、という対応が必要になるだろう。

現在のエンコーディングを保持しておくために、PDFViewControllerクラスにインスタンス変数を追加しよう。_encodingという名前にしておく。

List 1.

@interface PDFViewController : UIViewController
{
    CGPDFDocumentRef        _document;
    int                     _index;
    NSMutableString*        _text;
    CGPDFContentStreamRef   _stream;
    NSString*               _encoding;
    ...

次に、PDFオペレータテーブルの設定を変更する。フォント情報をキャプチャするために、コールバックを追加するのだ。対象となるオペレータ名は「Tf」となる。コールバック関数としては、operator_Fontという名前のものを用意した。

List 2.

- (IBAction)textAction
{
    ...

    // PDFコンテントストリームを取得
    _stream = CGPDFContentStreamCreateWithPage(page);

    // PDFオペレータテーブルを作成
    CGPDFOperatorTableRef   table;
    table = CGPDFOperatorTableCreate();
    CGPDFOperatorTableSetCallback(table, "TJ", operator_Text);
    CGPDFOperatorTableSetCallback(table, "Tj", operator_Text);
    CGPDFOperatorTableSetCallback(table, "Tf", operator_Font);

これでストリーム中にTfオペレータが現れるたびに、operator_Fontが呼び出される事になる。

フォント情報とエンコーディングの取得

operator_Font関数では、operatorFontScanned:メソッドを呼ぶ事にしよう。このメソッドで、エンコーディングを取り出す事になる。

まずは、フォント名を取得する。

List 3.

- (void)operatorFontScanned:(CGPDFScannerRef)scanner
{
    bool    result;

    // フォントサイズの取得
    CGPDFInteger    size;
    result = CGPDFScannerPopInteger(scanner, &size);
    if (!result) {
        return;
    }

    // フォント名の取得
    const char* name;
    result = CGPDFScannerPopName(scanner, &name);
    if (!result) {
        return;
    }

    ...

PDFデータ中のフォント情報は、前回Voyeurで確認したように、「/F2.1 1」という形になっている。先頭の「/F2.1」がフォント名で、それに続く「1」はフォントサイズとなる。このフォント名が欲しい訳なのだが、CGPDFのAPIではストリームとして流れて来たPDFデータを、スタックに順次積んでいく構造になっている。そこで、まず始めにフォントサイズをポップし、それからフォント名を取得している。

フォント名が取得できたら、次はフォントデータを取り出す。フォントデータは、これも前回説明したように、/Resourceの下に入っている。この領域のデータにアクセスするために、CGPDFContentStreamGetResourceという関数が用意されている。この関数に、アクセスしたいリソースのカテゴリと名前を入れる事で、データを取得する事ができる。次のように使う。

List 4.

    ...

    // フォントの取得
    CGPDFObjectRef  object;
    object = CGPDFContentStreamGetResource(_stream, "Font", name);
    if (!object) {
        return;
    }

    // PDF辞書の取得
    CGPDFDictionaryRef  dict;
    result = CGPDFObjectGetValue(object, kCGPDFObjectTypeDictionary, &dict);
    if (!result) {
        return;
    }

    // エンコーディングの取得
    const char* encoding;
    result = CGPDFDictionaryGetName(dict, "Encoding", &encoding);
    if (!result) {
        return;
    }

    // エンコーディングの設定
    [_encoding release], _encoding = nil;
    _encoding = [[NSString stringWithCString:encoding encoding:NSASCIIStringEncoding] retain];
}

CGPDFContentStreamGetResource関数の、第一引き数にはストリーム、第二引き数にカテゴリ名である"Font"、そして第三引き数にさきほど取得したフォント名を渡してやる。すると、フォント情報がCGPDFDictionaryの形でとれるはずだ。この辞書から、キー"Encoding"を指定して、エンコーディング名を取り出せることになる。

これでPDFドキュメントからエンコーディングが取得できた。このようにCGPDFのAPIを使いこなすには、PDFフォーマットに対する知識が必須となる事が分かるだろう。

次回は、このエンコーディングをもとに、テキストデータのUnicodeへの変換を行おう。

ここまでのソースコード: PDF-4.zip