PDF 構文 ファイル 解析手順
PDF構文 (PDF Syntax ISO 32000-1)
PDF1.7はISO技術委員会によって2008年1月に“ISO 32000-1規格”として承認され、
2017年7月にはPDF2.0の規格ISO3200-2が承認され、2020年12月にはこの規格は第二稿になりました。
ここでは32000-2規格で説明されているPDF1.7の構文(構造)について説明しています。
PDFファイルは8-bitデータを単位として構成されていて一般の文書編集アプリケーションで開くことができその内容を読取ることができます。ただし、バイナリのデータもそのまま(表示可能な文字に変換されずに)格納されていますので文字化けしているように表示されますが、これはPDFの仕様です。
ここではそのデータの一部を読み解くことでPDF文書へのデータ追加などPDF再構成の意味を説明し
PDF-ToolsでのPDFデータ解析や編集をより詳細にできるようにします。
0.PDFファイル 解析手順
PDFデータは1バイト(8ビット)のシーケンスで構成されています。
このシーケンスを複数のASCII文字で構成されたバイトデータの組み合わせ(キーワード)として解析します。
なお解析手順は
HelloWorld.pdfを使って説明します。
このPDFは
ダウンロード、または
表示できます。
ダウンロードしたPDF文書を通常のエディタで開くとその内容を見ることができます。
PDF構造の概要は、こちらを参照してください。
0.1 PDFファイルを通常のエディターで開く
0.2 PDFファイル終端を探す
PDFファイルは以下の「%%EOF」の行で終端されます。
%%EOF
ただし、この行以降に文字などのデータがあっても無視される仕様ですので「%%EOF」の行は必ずしもファイルの最後ではありません。
0.3 クロスリファレンス テーブルへのオフセット値
「%%EOF」行の直前2行には以下のように「startxref」キーワードが記載された行と数字だけが記された行が必ずあります。
startxref
609
%%EOF
この数値はファイルの先頭からのバイト数を表していて、その位置にクロスリファレンスを表す「xref」キーワードが記載されていることを示しています。
ただし、PDF 1.5以上でクロスリファレンス ストリームを含む場合はこの限りではありません。
PDF Imager-LP(無償版)でクロスリファレンステーブルの位置(オフセット)を表示するソースコード
//初期化
MlpInitialize("0-032E-E03H5EW76R15");
//オープン
if (0 <= MlpOpenDoc("HelloWorld.pdf", "", ""))
{
//Primitiveインターフェース
PRIMITIVE_HANDLE h = MlpGetPrimitiveInterface();
//クロスリファレンステーブルの位置(オフセット)
printf("startxref\n%d\n%%%%EOF\n", PrmGetXrefOffset(h));
//クローズ
MlpCloseDoc();
}
//後始末
MlpUninitialize();
using (var mlp = new PdfImager()){
//ライセンスキー
mlp.Initialize("0-032E-E03H5EW76R15");
//オープン
if (0 <= mlp.OpenDoc("HelloWorld.pdf"))
{
//Primitiveインターフェース
PrimitiveInterface prm = mlp.GetPrimitiveInterface();
//クロスリファレンステーブルの位置(オフセット)
Console.WriteLine("startxref\n{0}\n%%EOF", prm.GetXrefOffset());
//クローズ
mlp.CloseDoc();
}
}
import PdfImagerLP
#インスタンス生成
mlp = PdfImagerLP.Mlp();
#初期化
mlp = PdfImagerLP.Mlp()
if mlp.Initialize("0-032E-E03H5EW76R15") !=0:
print("Bad License Key.")
exit()
#オープン
if 0 <= mlp.OpenDoc("HelloWorld.pdf"):
#Primitiveインターフェース
prm = mlp.GetPrimitiveInterface();
#クロスリファレンステーブルの位置(オフセット)
print('startxref', prm.GetXrefOffset())
#クローズ
mlp.CloseDoc()
#後始末
mlp.Uninitialize()
0.4 トレイラー ディクショナリ
「startxref」行の前には以下のように「trailer」キーワードが記載された行に続いて、ディクショナリ(「<<」と「>>」で囲まれキーと値のペアが複数個内包されたもの)があります。
PDFファイル終端の一般形は以下のとおりです。
trailer
<< key1 value1
key2 value2
...
keyn valuen
>>
startxref
Byte_offset_of_last_cross-reference_section
%%EOF
以下は、その具体例です。見やすいように改行してあります。
trailer
<<
/Info 2 0 R
/Root 1 0 R
/Size 8
/ID [<1775615b6d180ff72f4473d56aaa72bf><a5902498ce444a8aa67f819e1023432d>]
>>
startxref
609
%%EOF
このtrailerディクショナリ(詳細は「2.4トレイラー(trailer)」を参照してください)にはPDFを構成するオブジェクト ツリーのルートやPDFの概要が記されたオブジェクトへの参照が示されています。
PDFデータを解析する場合はまずルートオブジェクトを探します。
このPDF文書の場合は、Rootキーとペアとなる値「1 0 R」がルートオブジェクトです。
PDF Imager-LP(無償版)でトレイラーを表示するソースコード
#define PRETTY 1 //改行して見易く
//初期化
MlpInitialize("0-032E-E03H5EW76R15");
//オープン
if (0 <= MlpOpenDoc("HelloWorld.pdf", "", "")) {
//Primitiveインターフェース
PRIMITIVE_HANDLE h = MlpGetPrimitiveInterface();
//トレイラー
char *data
PrmStringTrailer(h, PRETTY, &data);
printf("%s\n", data);
//クローズ
MlpCloseDoc();
}
//後始末
MlpUninitialize();
using (var mlp = new PdfImager()){
const bool PRETTY = true; //改行して見易く
//ライセンスキー
mlp.Initialize("0-032E-E03H5EW76R15");
//オープン
if (0 <= mlp.OpenDoc("HelloWorld.pdf"))
{
//Primitiveインターフェース
PrimitiveInterface prm = mlp.GetPrimitiveInterface();
//トレイラー
Console.WriteLine(prm.StringTrailer(PRETTY));
//クローズ
mlp.CloseDoc();
}
}
import PdfImagerLP
#インスタンス生成
mlp = PdfImagerLP.Mlp();
#初期化
res = mlp.Initialize("0-032E-E03H5EW76R15")
#オープン
if 0 <= mlp.OpenDoc("HelloWorld.pdf"):
#Primitiveインターフェース
prm = mlp.GetPrimitiveInterface();
#トレイラー
print(prm.StringTrailer(True))
#クローズ
mlp.CloseDoc()
#後始末
mlp.Uninitialize()
0.5 ルート オブジェクト
ルート(Catalog) オブジェクトを探すにはTrailerの「/Root」キーからたどります。
HelloWorld.pdfの場合は番号1のオブジェクトがそれにあたります。
以下がそのオブジェクトです、見やすいように改行してあります。
1 0 obj
<<
/Pages 3 0 R
/Type /Catalog
>>
endobj
このディクショナリの「/Pages」で示されたオブジェクト(複数の場合があります)からPDF各ページのコンテンツ(内容)が記載されたオブジェクトをたどれます。
PDF Imager-LP(無償版)でルート(Catalog) オブジェクトを表示するソースコード
#define PRETTY 1 //改行して見易く
//初期化
MlpInitialize("0-032E-E03H5EW76R15");
//オープン
if (0 <= MlpOpenDoc("HelloWorld.pdf", "", ""))
{
//Primitiveインターフェース
PRIMITIVE_HANDLE h = MlpGetPrimitiveInterface();
//ルート(Catalog) オブジェクト
char *data;
PrmStringCatalog(h, PRETTY, ∓data);
printf("%s\n", data);
//クローズ
MlpCloseDoc();
}
//後始末
MlpUninitialize();
using (var mlp = new PdfImager()){
const bool PRETTY = true; //改行して見易く
//ライセンスキー
mlp.Initialize("0-032E-E03H5EW76R15");
//オープン
if (0 <= mlp.OpenDoc("HelloWorld.pdf"))
{
// Primitive インターフェース
PrimitiveInterface prm = mlp.GetPrimitiveInterface();
//ルート(Catalog) オブジェクト
Console.WriteLine("{0}", prm.StringCatalog(PRETTY));
//クローズ
mlp.CloseDoc();
}
}
import PdfImagerLP
#インスタンス生成
mlp = PdfImagerLP.Mlp();
#初期化
res = mlp.Initialize("0-032E-E03H5EW76R15")
if res!=0:
print("Bad License Key.")
exit()
#オープン
if 0 <= mlp.OpenDoc("HelloWorld.pdf"):
#Primitiveインターフェース
prm = mlp.GetPrimitiveInterface();
#ルート(Catalog) オブジェクト
print(prm.StringCatalog(True))
#クローズ
mlp.CloseDoc()
#後始末
mlp.Uninitialize()
0.6 ページ・ツリー オブジェクト
HelloWorld.pdfのルートオブジェクトからたどったページツリーオブジェクトは以下のとおりです。見やすいように改行してあります。
3 0 obj
<<
/Count 1
/Kids [ 4 0 R ]
/Type /Pages
>>
endobj
この記述からこのPDF文書は全1ページ("/Count")で構成されていて、そのページの情報("/Kids")が4番目のオブジェクトに記載されていることがわかります。
PDF Imager-LP(無償版)でPageTreeディクショナリーを表示するソースコード
// 初期化
MlpInitialize("0-032E-E03H5EW76R15");
// オープン
if (0 <= MlpOpenDoc("HelloWorld.pdf", "", "") {
// Primitive インターフェース
PRIMITIVE_HANDLE h = MlpGetPrimitiveInterface();
// ページ・ツリー
PrmShowPageTree(h, TRUE);
// クローズ
PrmCloseInterface(h);
MlpCloseDoc();
}
// 後始末
MlpUninitialize();
const bool PRETTY = true; //改行して見易く
using (var mlp = new PdfImager()){
// ライセンスキー
mlp.Initialize("0-032E-E03H5EW76R15");
// オープン
if(0 <= mlp.OpenDoc("HelloWorld.pdf"))
{
// Primitive インターフェース
PrimitiveInterface prm = mlp.GetPrimitiveInterface();
// ページ・ツリー
Prm.ShowPageTree(true);
// クローズ
prm.CloseInterface();
mlp.CloseDoc();
}
}
import PdfImagerLP
#インスタンス生成
mlp = PdfImagerLP.Mlp();
# ライセンスキー
mlp.Initialize("0-032E-E03H5EW76R15")
# オープン
if 0 <= mlp.OpenDoc("HelloWorld.pdf"):
# Primitiveインターフェース
prm = mlp.GetPrimitiveInterface();
# ページ・ツリー
prm.ShowPageTree(True)
# クローズ
prm.CloseInterface()
mlp.CloseDoc()
# 後始末
mlp.Uninitialize()
0.7 ページ オブジェクト
HelloWorld.pdfのページツリーからたどったページ オブジェクトは以下のとおりです。見やすいように改行してあります。
4 0 obj
<<
/Contents 5 0 R
/MediaBox [ 0 0 595 842 ]
/Parent 3 0 R
/Resources 6 0 R
/Type /Page
>>
endobj
ここから、このページの大きさ("/MediaBox")は幅が595高さが842であることがわかります。
さらにページの内容("/Contents")が5番目のオブジェクトに記載されていることがわかります。
PDF Imager-LP(無償版)でPageオブジェクトとContentsオブジェクトを表示するソースコード
// 初期化
MlpInitializeA("0-032E-E03H5EW76R15");
// オープン
if (0 <= MlpOpenDocA("HelloWorld.pdf", "", "")) {
// Primitive インターフェース
PRIMITIVE_HANDLE h = MlpGetPrimitiveInterface();
// ページ
PrmObjectHandle pageh = PrmObjectHandlePage(h, 1); //先頭ページ
PrmShowObjectHandle(h, pg, TRUE, 0, TRUE);
// クローズ
PrmCloseInterface(h);
MlpCloseDoc();
}
// 後始末
MlpUninitialize();
}
using (var mlp = new PdfImager()) {
// ライセンスキー
mlp.Initialize("0-032E-E03H5EW76R15");
// オープン
if (0 <= mlp.OpenDoc("HelloWorld.pdf"))
{
// Primitive インターフェース
PrimitiveInterface prm = mlp.GetPrimitiveInterface();
// ページ
PrmObjectHandle pageh = prm.ObjectHandlePage(1); //先頭ページ
prm.ShowObjectHandle(pg, true, false, true);
// クローズ
prm.CloseInterface();
mlp.CloseDoc();
}
}
import PdfImagerLP
# 初期化
mlp = PdfImagerLP.Mlp()
mlp.Initialize("0-032E-E03H5EW76R15")
if 0 <= mlp.OpenDoc("HelloWorld.pdf"):
# Primitiveインターフェース
prm = mlp.GetPrimitiveInterface();
# ページ
pageh = prm.ObjectHandlePage()
prm.ShowObjectHandle(t, resolve=True, pretty=True)
# クローズ
prm.CloseInterface()
mlp.CloseDoc()
# 終了
mlp.Uninitialize()
0.8 ページコンテンツ オブジェクト
HelloWorld.pdfのページのページコンテンツ オブジェクトは以下のとおりです。
5 0 obj
<< /Length 75 >>
stream
BT
1 0 0 1 100 600 Tm
/SF1 50 Tf
0 Ts 0 Tr 0 Tc 0 Tw
(Hello, World.) Tj
ET
endstream
endobj
ここにあるデータは圧縮されていないので、その内容を見ることができます。
この描画コマンドは"Hello, World."という文字列を指定のフォント(7 0 objで指定されたHelvetica)と大きさ(50ポイント)で指定の位置に描画します。
PDF Imager-LP(無償版)でページコンテンツを表示するソースコード
// 初期化
MlpInitializeA("0-032E-E03H5EW76R15");
// オープン
if (0 <= MlpOpenDocA("HelloWorld.pdf", "", "")) {
// Primitive インターフェース
PRIMITIVE_HANDLE h = MlpGetPrimitiveInterface();
// ページ
PrmObjectHandle pageh = PrmObjectHandlePage(h, 1);
// ページコンテンツ
PrmObjectHandle cont = PrmObjectHandleDictionaryByString(h, pageh, "Contents");
PrmShowObjectHandle(h, cont, TRUE, TRUE, TRUE);
// クローズ
PrmCloseInterface
MlpCloseDoc();
}
// 後始末
MlpUninitialize();
using (var mlp = new PdfImager()){
// ライセンスキー
mlp.Initialize("0-032E-E03H5EW76R15");
// オープン
if (0 <= mlp.OpenDoc("HelloWorld.pdf"))
{
// Primitive インターフェース
PrimitiveInterface prm = mlp.GetPrimitiveInterface();
// ページ
PrmObjectHandle pageh = prm.ObjectHandlePage();
// ページコンテンツ
PrmObjectHandle cont = prm.ObjectHandleDictionaryByString(pageh, "Contents");
prm.ShowObjectHandle(pg, true, true, true);
// クローズ
mlp.CloseInterface();
mlp.CloseDoc();
}
}
from PdfImagerLP import Mlp
mlp = Mlp()
if mlp.Initialize("0-032E-E03H5EW76R15") != 0:
print("Bad License Key.")
exit()
if 0 <= mlp.OpenDoc("HelloWorld.pdf"):
# Primitiveインターフェース
prm = mlp.GetPrimitiveInterface();
# ページ
page = prm.ObjectHandlePage()
# ページコンテンツ
contentsh = prm.ObjectHandleDictionaryByString(pg, "Contents")
prm.ShowObjectHandle(contentsh, True, True, True)
#クローズ
prm.CloseInterface()
mlp.CloseDoc()
#後始末
mlp.Uninitialize()
>>>「PDF 構造 -概要-」
ご質問 ・ お問い合わせ