トップ «前の日記(2020-01-01) 最新 次の日記(2020-01-11)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2020-01-05

_ PDFからテキスト抽出

yancyaさんのQiitaの「RubyでPDFと戯れるの巻」は参考になるが、origamiよりもソースが読みやすいので、pdf-readerを使ってPDFからのテキスト抽出をしていた。

多分現在のorigamiもそうだろうが、エンコーディング情報が入っていれば、テキストの抽出はえらく簡単で、少なくともpdf-readerだと次のようにすればよい。

reader = PDF::Reader.new(filename)
reader.pages.each do |page|
  puts page.text
end

が、これはあくまでもエンコーディングが設定されている(yancyaさんの記事だとToUnicodeが設定されている場合なのだが、CMap(キャラクターマッピング表ということかな)があればOKのようだ)、埋め込みフォントを利用して、かつグリフのみの場合で詰んだ。

上のやりかたで、ある特定ページからのputs page.textで出力される文字がむちゃくちゃになる。

はて? と生データを見ることにする。

puts page.raw_content

raw_contentによって、ynacyaさんの記事と同様の内容を得られる。 こんな感じだ。

 
/C0_0 1 Tf (略)[<001B00190003>-4139.9 <00190015>-2120 <001600180015000F001400190013>]TJ 

で、/C0_0というフォントを見てみると

page = reader.page(出力がおかしくなったページ)
puts page.fonts[:C0_0]

次のような出力を得られる。

{:BaseFont=>:"MS-Mincho-Identity-H", :DescendantFonts=>[{:BaseFont=>:"CNBLDP+MS-Mincho", :CIDSystemInfo=>{:Ordering=>"Identity", :Re\
gistry=>"Adobe", :Supplement=>0}, :DW=>1000, :FontDescriptor=>{:Ascent=>723, :CIDSet=>#<PDF::Reader::Stream:0x00007f2ca02b4138 (略)  :CapHeight=>709, :Descent=>-241, :Flags=>6, :FontBBox=>[-82, -13\
7, 996, 859], :FontFile3=>#<PDF::Reader::Stream:0x00005640ea24f6b8 (略)

ToUnicodeは無い。

pdf-readerは何を根拠にテキストを出力しているのだろうか? 不思議に思ってpdf-readerのFontオブジェクトを作ってみる。

c0_0 = PDF::Reader::Font.new(page.objects, page.fonts[:C0_0])
puts c0_0.to_utf8(0x1b)  #=>▯
puts c0_0.to_utf8(0x19)  #=>▯
puts c0_0.to_utf8(3)     #=>▯

確かにpage.textで表示された文字列に一致しているが……

puts c0_0.encoding  #=>  #9647, 1=>9647, 2=>9647, 3=>9647, 4=>9647... 

CID 9647に相当する文字を5078.Adobe-Japan1-6.pdfで見てみるとiアクセンテギュみたいだが、該当フォントがないので▯になっているのかなぁ。

しかし、そもそもOrderingがIdentityでJapanXではないのだから、見ている表が違うわけだろう。

要は埋め込みフォントのグリフを直接指定しているに違いない。

それで、埋め込みフォントを抜いてみることにする。

sudo apt-get install fontforge

でfontforgeをインストールして、フォントファイルとしてPDFでフィルタして読み込むと、どのフォントか? と聞かれるので、CNBLDP+MS-Mincho (上でC0_0のDescendantFontsのBaseFontとされているもの)を選ぶ。

すると、3がスペース、0x1bが8、0x19が6のグリフを持つフォントが表示された(その前にfontforgeはCMapが無いけど無視するか? と聞いてくる。そもそもCMapが無いのでこんなことしているのでGive Upをクリックする)。これは、該当PDFの表示と一致する。

ということは、文字コードに相当するものがどこにも無いのだから、TJで指定されているCIDに相当するグリフを埋め込みフォントから拾い出して文字認識させるか、PDFをそのままOCRするか、しかテキストを取得する方法は無いということだ。

というわけで、フリーのOCRでPDFを入力できるもので試したが、結果はひどいもので(多分、余分な情報が多いのだと思う)、グリフを使うほうが良さそうだなというところまでは来たのだが、fontforgeと同じようなグリフの取り出しってどうやればできるんだろうか? pdf-readerのstreamを使ってデータは取り出せるのはわかっているのだが、フォントの格納形式はわからないんだよな。

_ pdf-reader その他の使い方

pdf-readerを利用して単に文字コード変換テーブルを内包したPDFからテキストを抽出するだけならば、各ページオブジェクトのtextプロパティを呼び出せば良い。上のエントリーの最初のコード片参照。

より詳細な情報は、reader#objectsを利用する。

reader = PDF::Reader.new(filename)
1.upto(reader.objects.length - 1) {|i|  # objectsをeachで回すと最初のnilがスキップされる。PDF内のobjectのIDとインデックスを一致させるためだと思うが、配列としてアクセスするほうが話が簡単だった)
  puts "#{i}: #{reader.objects[i].inspect}"
}

objectsはHashとPDF::Reader::Streamのいずれかが入る。Hashが示すオブジェクトの実データはPDF::Reader::Referenceが示すStreamに格納される。

{:Contents=>#<PDF::Reader::Reference:0x00005640ea49b1d8 @id=2, @gen=0>, :CropBox=>[39.6567, 137.534, 555.563, 704.466], :MediaBox=>[0, \
0, 595.22, 842], :Parent=>#<PDF::Reader::Reference:0x00005640ea498460 @id=114, @gen=0>, :Resources=>{:ExtGState=>{:GS0=>#<PDF::Reader::
Reference:0x00005640ea4aacf0 @id=130, @gen=0>}, :Font=>{:C2_0=>#<PDF::Reader::Reference:0x00005640ea4aa610 @id=126, @gen=0>, :C2_1=>#<P
DF::Reader::Reference:0x00005640ea4a9698 @id=139, @gen=0>}, :ProcSet=>[:PDF, :Text]}, :Rotate=>0, :Type=>:Page}

上のHashはContents(ページ内のテキストなど)を示す。実際のデータは、@id=2のStreamであることがわかる。

puts reader.objects[2].filtered_data  # filetered_dataは圧縮などがされている状態から実データを復元する。ここではContentsということがわかっているのでputsで呼び出す。
#=> 0 0 0 rg /GS0 gs 297.96 717.2 0.24 25.56 re f 39.6 ...

Streamからは、フォントのグリフや、CMapも取得できる。FontDescriptorなどの非バイナリオブジェクトは、Hashとなる。

下の例は、FontDescriptorで、フォントデータは、FontFile2で示されるID(objectsのインデックスに等しい)のPDF::Reader::Streamから取得できる。
{:Ascent=>880, :CIDSet=>#, :CapHeight=>0, :Descent=>-120, :Flags=>4, 
:FontBBox=>[-26, -174, 1000, 881], 
:FontFile2=>#, 
:FontName=>:"CJKHCO+RgGothic-Md-90ms-RKSJ-H", :ItalicAngle=>0, :StemV=>0, :Type=>:FontDescriptor}
上記のFontDescriptorをポイントするBaseFontを示す。ここでFontDescriptorとして示されている@id=13、つまりreader#objects[13]が上記である。
{:BaseFont=>:"CJKHCO+RgGothic-Md-90ms-RKSJ-H", :CIDSystemInfo=>{:Ordering=>"Identity", :Registry=>"Adobe", :Supplement=>0}, :CIDToGIDMap=>:Identity, :DW=>1000, 
:FontDescriptor=>#, :Subtype=>:CIDFontType2, :Type=>:Font, :W=>[17, [500], 19, 28, 500]}

2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|

ジェズイットを見習え