PDF 文書の解析
Quartz は、PDF 文書の構造とコンテンツストリームを検査することができる関数を提供します。文書の構造を調べることで、文書カタログのエントリと各エントリに関連した内容を読み取ることができます。カタログを再帰的に走査することにより、文書全体を検査することができます。
PDF のコンテンツストリームは、その名前が示唆しているとおりです。つまり、'BT 12 / F71 Tf (このテキストを描画) Tj . . . ' などのデータの連続ストリームで、ここで PDF オペレータとそのデスクリプタが実際の PDF コンテンツと混ざり合っています。コンテンツストリームを検査するには、それに順番にアクセスする必要があります。
この章では、PDF 文書の構造を調べ、PDF 文書の内容を解析する方法を示します。
PDF 文書の構造の検査
PDF ファイルには複数ページのイメージとテキストが含まれている場合があります。Quartz を使用して、PDF ページ上のオブジェクトだけでなく、文書やページレベルのメタデータにアクセスできます。この説では、アクセス可能なメタデータについて簡単に紹介します。
PDF 文書オブジェクト(CGPDFDocument) には、PDF 文書に関連する、カタログおよびコンテンツを含むすべての情報が含まれます。カタログ内のエントリは、PDF 文書の内容を繰り返し記述します。 CGPDFDocumentGetCatalog 関数を呼び出して、PDF 文書カタログの内容にアクセスできます。
PDF ページオブジェクト(CGPDFPage) は、PDF 文書のページを表し、ページ辞書やページの内容を含む特定のページに関連する情報を含みます。CGPDFPageGetDictionary 関数を呼び出してページ辞書を得ることができます。
図 14-1 に、図 13-2 に示した PDF ファイルを構成する 2 つのイメージ(テキストと雄鶏のイメージ) を説明するメタデータの一部を示します。
図 14-1   PDF ファイル内の 2 つのイメージのメタデータ
PDF メタデータにアクセスすることにより、より有用な情報を得ることができます。図 14-1 のアイテムは単なるサンプルです。たとえば、リスト 14-1 に示したコードを使用して、PDF にサムネイル画像があるかどうかを確認できます(図 14-2 を参照)。
リスト 14-1   PDF のサムネイル表示を得る
CGPDFDictionaryRef d; CGPDFStreamRef stream; // represents a sequence of bytes d = CGPDFPageGetDictionary(page); // check for thumbnail data if (CGPDFDictionaryGetStream (d, “Thumb”, &stream)){ // get the data if it exists data = CGPDFStreamCopyData (stream, &format);
Quartz はデータストリームの復号と解読をあなたに代わりすべて行います。
図 14-2 サムネールイメージ
Quartz には、PDF メタデータ内のアイテムの個々の値を得るために使用できるいくつかの関数が用意されています。CGPDFObjectGetValue 関数を使用して、CGPDFObjectRef、PDF オブジェクト型(kCGPDFObjectTypeBoolean、kCGPDFObjectTypeInteger などなど)、および値の格納域を渡して下さい。返ってくると、格納域は値で満たされています。
さまざまなノードとその子供にアクセスするため、PDF ファイルの階層を横断するために使用できる他の多くの関数があります。たとえば、CGPDFArray 関数(CGPDFArrayGetBoolean、CGPDFArrayGetDictionary、CGPDFArrayGetInteger などなど) を使用すると、値の配列にアクセスして特定の型の値を得られます。これらの関数の使用方法の詳細については、PDF 仕様書を参照してください。
PDF コンテンツの解析
PDF コンテンツストリームには、アプリケーションにとって重要な PDF コンテンツストリームの一部を知らせる演算子が含まれています。演算子は、単一の点またはシーケンスをマークします。演算子は、プロパティリストまたはそれに関連したオブジェクトを持つタグとして指定されます。タグは、点またはコンテンツシーケンスが表すものを指定します。プロパティリストは、PDF コンテンツの作成者が指定したキー値のペアを含む辞書です。PDF コンテンツストリームを解析すると、アプリケーションは全ての目的のマーカーを検索さし、タグ、プロパティリスト、またはマーカーに関連したオブジェクトを検査し、さらなる適切な処理が実行されます。PDF 演算子の完全なリストについては、PDF リファレンス を参照してください。
CGPDFScanner オブジェクト(CGPDFScannerRef データ型) を使用して、PDF コンテンツストリームを解析します。CGPDFScanner オブジェクトは、呼び出し関数を登録したストリームの全ての演算子の呼び出し関数を呼び出します。
以下の節で説明するタスクを実行して、コンテンツストリームを解析します:
- 演算子呼び出し関数を書く。 処理したい演算子に対してのみ呼び出し関数を記述する必要があります。
- 演算子の表の作成と設定。
- PDF 文書を開く。
- 各ページのコンテンツストリームをスキャン。
そうするのが適切な場合は、スキャナ、コンテンツストリーム、および演算子の表を解放する必要があります。
以下の節では、コンテンツストリームを解析して マーク付きコンテンツ演算子 (表 14-1 を参照) を検索する方法を示します。マーク付きコンテンツ演算子は、PDF コンテンツで使用される PDF 演算子の一部のみを表します。独自のコードを書くときは、アプリケーションに適した PDF 演算子を探して下さい。
表 14-1 マーク付きコンテンツ演算子は、解析できる PDF 演算子の一部を表します
演算子 | 説明 |
---|---|
MP | 関連しているタグがあるマークされた点。 |
DP | タグとそれに関連したプロパティリストまたはオブジェクトがあるマークされた点。 |
BMC | マーク付きコンテンツシーケンスの開始(マーク付きコンテンツの開始) を示し、シーケンスの終了を知らせる EMC マーカーと対になります。関連するタグがあります。 |
BDC | マーク付きコンテンツシーケンスの開始を知らせ、シーケンスの終了を知らせる EMC マーカーと対になります。タグとそれに関連したプロパティリストまたはオブジェクトがあります。 |
EMC | BMC または BDC マーカーで始まるマーク付きコンテンツシーケンス(マーク付きコンテンツの終わり) の終わりを通知します。この演算子にはタグが関連していません。 |
演算子呼び出し関数を書く
Quartz が PDF 演算子の呼び出し関数を呼び出すと、CGPDFScanner オブジェクトと、呼び出し関数で必要な全ての情報へのポインタを渡します。通常、呼び出し関数は演算子に関連した全てのアイテムを取得します。たとえば、リスト 14-2 に示した MP 演算子の呼び出し関数は、CGPDFScannerPopName 関数を呼び出して、演算子に関連した文字列をスタックから得ます。リスト内のコードがスキャナスタックから名前を得るのに成功すると、その名前を印刷します。
Quartz には、オブジェクト、ブール値、名前、数値、文字列、配列、辞書、及びストリームを得るための CGPDFScannerPop 関数が用意されています。各関数は、項目を得るのが成功したかどうかを示すブール値を返します。
リスト 14-2 MP 演算子の呼び出し関数
static void op_MP (CGPDFScannerRef s, void *info) { const char *name; if (!CGPDFScannerPopName(s, &name)) return; printf("MP /%s\n", name); }
演算子の表の作成と設定
CGPDFOperatorTable オブジェクトには、記述した PDF 演算子呼び出し関数が格納されます。リスト 14-3 に示したように、CGPDFOperatorTableCreate 関数は演算子の表を作成します。演算子の表を作成した後、表に追加する呼び出し関数ごとに CGPDFOperatorTableSetCallback 関数を呼び出します。表、PDF 演算子を指定する文字列、およびその演算子を処理するために書く呼び出し関数へのポインタを渡します。呼び出し関数には、何でも好きな名前を付けられます。CGPDFOperatorTableSetCallback 関数に渡す呼び出し関数名のスペルが間違っていないことを確認してください。
リスト 14-3 のコードは、表 14-1 にリストされているマーク付きコンテンツ演算子の各々の呼び出し関数を設定します。あなたのアプリケーションは、関心のある演算子の呼び出し関数のみを設定して下さい。PDF 演算子文字列は、Adobe の PDF リファレンス に定義されています。
リスト 14-3 演算子の表用の呼び出し関数の設定
CGPDFOperatorTableRef myTable; myTable = CGPDFOperatorTableCreate(); CGPDFOperatorTableSetCallback (myTable, "MP", &op_MP); CGPDFOperatorTableSetCallback (myTable, "DP", &op_DP); CGPDFOperatorTableSetCallback (myTable, "BMC", &op_BMC); CGPDFOperatorTableSetCallback (myTable, "BDC", &op_BDC); CGPDFOperatorTableSetCallback (myTable, "EMC", &op_EMC);
PDF 文書を開く
PDF 文書の内容をスキャンする前に、それを開く必要があります。リスト 14-4 に、コードに提供された URL から CGPDFDocument オブジェクトを作成するコードの断片を示します。リストはコードの断片であるため、すべての変数が宣言されているわけではありません。番号付きコード行の詳細な説明は、リストの後に表示します。
リスト 14-4 URL から PDF 文書を開く
CGPDFDocumentRef myDocument;
myDocument = CGPDFDocumentCreateWithURL(url);// 1
if (myDocument == NULL) {// 2
error ("can't open `%s'.", filename);
CFRelease (url);
return EXIT_FAILURE;
}
CFRelease (url);
if (CGPDFDocumentIsEncrypted (myDocument)) {// 3
if (!CGPDFDocumentUnlockWithPassword (myDocument, "")) {
printf ("Enter password: ");
fflush (stdout);
password = fgets(buffer, sizeof(buffer), stdin);
if (password != NULL) {
buffer[strlen(buffer) - 1] = '\0';
if (!CGPDFDocumentUnlockWithPassword (myDocument, password))
error("invalid password.");
}
}
}
if (!CGPDFDocumentIsUnlocked (myDocument)) {// 4
error("can't unlock `%s'.", filename);
CGPDFDocumentRelease(myDocument);
return EXIT_FAILURE;
}
}
if (CGPDFDocumentGetNumberOfPages(myDocument) == 0) {// 5
CGPDFDocumentRelease(myDocument);
return EXIT_FAILURE;
}
コードの動作は以下の通りです:
- コードに提供された URL から CGPDFDocument オブジェクトを作成します。
- CGPDFDocument オブジェクトが作成されたことを確認します。そうでない場合、文書なしで続行することは意味がないため、コードは終了します。
- 文書が暗号化されているかどうかを確認します。文書が暗号化されている場合、コードは空白のパスワードを使用して開こうとします。それが失敗すると、コードはユーザーにパスワードを要求し、パスワードで文書のを解錠しようとします。
- 文書が解錠されているかどうかを確認します。そうでない場合、コードは終了します。
- 文書に少なくとも 1 ページあることを確認します。それ以外の場合、コードは終了します。
各ページのコンテンツストリームをスキャン
リスト 14-5 のコードの断片は、文書内の各ページをスキャンします。スキャナが、あなたが呼び出し関数を登録した PDF 演算子の 1 つに遭遇すると、Quartz は呼び出し関数を呼び出します。コードの各行の詳細な説明は、リストの後に示します。
リスト 14-5 文書の各ページをスキャン
int k; CGPDFPageRef myPage; CGPDFScannerRef myScanner; CGPDFContentStreamRef myContentStream; numOfPages = CGPDFDocumentGetNumberOfPages (myDocument);// 1 for (k = 0; k < numOfPages; k++) { myPage = CGPDFDocumentGetPage (myDocument, k + 1 );// 2 myContentStream = CGPDFContentStreamCreateWithPage (myPage);// 3 myScanner = CGPDFScannerCreate (myContentStream, myTable, NULL);// 4 CGPDFScannerScan (myScanner);// 5 CGPDFPageRelease (myPage);// 6 CGPDFScannerRelease (myScanner);// 7 CGPDFContentStreamRelease (myContentStream);// 8 } CGPDFOperatorTableRelease(myTable);// 9
コードの動作は以下の通りです:
- 以前に開いた文書のページ数を得ます。PDF 文書を開く を参照してください。
- スキャンするページを得ます。ページの番号は 1 から始まります。
- ページのコンテンツストリームを作成します。
- コンテンツストリーム用のスキャナを作成します。 以前に作成して呼び出し関数を設定したコンテンツストリームと演算子の表を渡さなければなりません。演算子の表の作成と設定 を参照してください。呼び出し関数に必要な全てのデータを渡すこともできます。
- スキャナに関連したコンテンツストリームを解析します。Quartz は、呼び出し関数を提供した演算子の 1 つに遭遇するたびに呼び出し関数を呼び出します。
- ページを解放します。
- スキャナを解放します。
- コンテンツストリームを解放します。
- PDF 内のすべてのページをスキャンした後、演算子の表を解放します。